summaryrefslogtreecommitdiff
path: root/docs/res/ctxedit.js
diff options
context:
space:
mode:
Diffstat (limited to 'docs/res/ctxedit.js')
-rw-r--r--docs/res/ctxedit.js560
1 files changed, 0 insertions, 560 deletions
diff --git a/docs/res/ctxedit.js b/docs/res/ctxedit.js
deleted file mode 100644
index 1ab896b08..000000000
--- a/docs/res/ctxedit.js
+++ /dev/null
@@ -1,560 +0,0 @@
-var CtxEdit = (function(){
-
-
-function getLocalObject(key) {
- let s = sessionStorage.getItem(key)
- if (s) {
- try {
- return JSON.parse(s)
- } catch (e) {
- console.error(
- `failed to parse sessionStorage value "${s}" for key ${key}`,
- err.stack || String(err)
- )
- }
- }
- return null
-}
-
-
-function setLocalObject(key, value) {
- let json = JSON.stringify(value)
- sessionStorage.setItem(key, json)
-}
-
-
-function rmLocalObject(key) {
- sessionStorage.removeItem(key)
-}
-
-
-class FloatProp {
- constructor(cssProp, unitSuffix) {
- this.cssProp = cssProp
- this.unitSuffix = unitSuffix
- }
-
- valueInStyle(s) {
- let v = s[this.cssProp]
- return v !== undefined ? parseFloat(v) : v
- }
-
- applyStyle(el, value) {
- el.style[this.cssProp] = value + this.unitSuffix
- }
-}
-
-class FontStyleProp {
-
- valueInStyle(s) {
- let italic = s['font-style'] == 'italic' || s['font-style'].indexOf('oblique') != -1
- let weight = parseFloat(s['font-weight'])
- if (isNaN(weight)) {
- weight = s['font-weight']
- if (weight == 'thin') { return italic ? 'thin-italic' : 'thin' }
- if (weight == 'extra-light') {return italic ? 'extra-light-italic' :'extra-light' }
- if (weight == 'light') { return italic ? 'light-italic' : 'light' }
- if (weight == 'normal') { return italic ? 'italic' : 'regular' }
- if (weight == 'medium') { return italic ? 'medium-italic' : 'medium' }
- if (weight == 'semi-bold') { return italic ? 'semi-bold-italic' : 'semi-bold' }
- if (weight == 'bold') { return italic ? 'bold-italic' : 'bold' }
- if (weight == 'extra-bold') { return italic ? 'extra-bold-italic' : 'extra-bold' }
- } else {
- if (weight <= 150) { return italic ? 'thin-italic' : 'thin' }
- if (weight <= 250) { return italic ? 'extra-light-italic' :'extra-light' }
- if (weight <= 350) { return italic ? 'light-italic' : 'light' }
- if (weight <= 450) { return italic ? 'italic' : 'regular' }
- if (weight <= 550) { return italic ? 'medium-italic' : 'medium' }
- if (weight <= 650) { return italic ? 'semi-bold-italic' : 'semi-bold' }
- if (weight <= 750) { return italic ? 'bold-italic' : 'bold' }
- if (weight <= 850) { return italic ? 'extra-bold-italic' : 'extra-bold' }
- }
- return italic ? 'black-italic' : 'black'
- }
-
- applyStyle(el, value) {
- let cl = el.classList
- for (let k of Array.from(cl.values())) {
- if (k.indexOf('font-style-') == 0) {
- cl.remove(k)
- }
- }
- cl.add('font-style-' + value)
- }
-}
-
-class LineHeightProp {
- valueInStyle(s) {
- let v = s['line-height']
- if (v === undefined) {
- return 1.0
- }
- if (v.lastIndexOf('px') == v.length - 2) {
- // compute
- return parseFloat(
- (parseFloat(v) / parseFloat(s['font-size'])).toFixed(3)
- )
- }
- v = parseFloat(v)
- return isNaN(v) ? 1.0 : v
- }
-
- applyStyle(el, value) {
- el.style['line-height'] = String(value)
- }
-}
-
-class TrackingProp {
- valueInStyle(s) {
- let v = s['letter-spacing']
- if (v === undefined) {
- return 0
- }
- if (v.lastIndexOf('px') == v.length - 2) {
- // compute
- return parseFloat(
- (parseFloat(v) / parseFloat(s['font-size'])).toFixed(3)
- )
- }
- v = parseFloat(v)
- return isNaN(v) ? 0 : v
- }
-
- applyStyle(el, value) {
- el.style['letter-spacing'] = value.toFixed(3) + 'em'
- }
-}
-
-const Props = {
- size: new FloatProp('font-size', 'px'),
- tracking: new TrackingProp(),
- lineHeight: new LineHeightProp(),
- style: new FontStyleProp(),
-}
-
-function valuesFromStyle(s) {
- let values = {}
- for (let name in Props) {
- let p = Props[name]
- values[name] = p.valueInStyle(s)
- }
- return values
-}
-
-
-class Editable {
- constructor(el, key) {
- this.el = el
- this.key = key
- this.defaultValues = valuesFromStyle(getComputedStyle(this.el))
- this.values = Object.assign({}, this.defaultValues)
- this.defaultExplicitTracking = this.defaultValues['tracking'] != 0
- this.explicitTracking = this.defaultExplicitTracking
- this.explicitTrackingKey = this.key + ":etracking"
- this.loadValues()
- this.updateSizeDependantProps()
- }
-
- resetValues() {
- this.values = Object.assign({}, this.defaultValues)
- let style = this.el.style
- for (let name in this.values) {
- Props[name].applyStyle(this.el, this.values[name])
- }
- rmLocalObject(this.key)
- rmLocalObject(this.explicitTrackingKey)
- this.explicitTracking = this.defaultExplicitTracking
- this.updateSizeDependantProps()
- }
-
- setExplicitTracking(explicitTracking) {
- if (this.explicitTracking !== explicitTracking) {
- this.explicitTracking = explicitTracking
- if (!this.explicitTracking) {
- this.updateSizeDependantProps()
- }
- }
- }
-
- setValue(name, value) {
- this.values[name] = value
- Props[name].applyStyle(this.el, value)
- if (name == 'size') {
- this.updateSizeDependantProps()
- }
- }
-
- updateSizeDependantProps() {
- let size = this.values.size
-
- // dynamic tracking
- if (!this.explicitTracking) {
- this.setValue('tracking', InterDynamicTracking(size))
- }
-
- // left indent
- // TODO: Consider making this part of dynamic metrics.
- let leftMargin = size / -16
- if (leftMargin == 0) {
- this.el.style.marginLeft = null
- } else {
- this.el.style.marginLeft = leftMargin.toFixed(1) + 'px'
- }
- }
-
- loadValues() {
- let values = getLocalObject(this.key)
- if (values && typeof values == 'object') {
- for (let name in values) {
- if (name in this.values) {
- let value = values[name]
- this.values[name] = value
- Props[name].applyStyle(this.el, value)
- } else if (console.warn) {
- console.warn(`Editable.loadValues ignoring unknown "${name}"`)
- }
- }
- // console.log(`loaded values for ${this}:`, values)
- }
- let etr = getLocalObject(this.explicitTrackingKey)
- this.explicitTracking = this.defaultExplicitTracking || etr
- }
-
- isDefaultValues() {
- for (let k in this.values) {
- if (this.values[k] !== this.defaultValues[k]) {
- return false
- }
- }
- return true
- }
-
- saveValues() {
- if (this.isDefaultValues()) {
- rmLocalObject(this.key)
- rmLocalObject(this.explicitTrackingKey)
- } else {
- setLocalObject(this.key, this.values)
- setLocalObject(this.explicitTrackingKey, this.explicitTracking ? "1" : "0")
- }
- // console.log(`saved values for ${this}`)
- }
-
- toString() {
- return `Editable(${this.key})`
- }
-}
-
-
-var supportsFocusTrick = (u =>
- u.indexOf('Firefox/') == -1
-)(navigator.userAgent)
-
-
-class CtxEdit {
- constructor() {
- this.bindings = new Bindings()
- this.keyPrefix = 'ctxedit:' + document.location.pathname + ':'
- this.editables = new Map()
- this.ui = $('#ctxedit-ui')
- this.currEditable = null
- this._saveValuesTimer = null
- this.isChangingBindings = true
- this.bindings = new Bindings()
- this.initBindings()
- this.initUI()
- this.addAllEditables()
- this.isChangingBindings = false
- this.preloadFonts()
-
- if (supportsFocusTrick) {
- this.ui.addEventListener('focus', ev => {
- if (this.currEditable) {
- ev.preventDefault()
- ev.stopImmediatePropagation()
- this.currEditable.el.focus() // breaks Firefox
- }
- }, {capture:true, passive:false})
- }
- }
-
- initUI() {
- $('.reset-button', this.ui).addEventListener('click', ev => this.reset())
- $('.dismiss-button', this.ui).addEventListener('click', ev => this.stopEditing())
- this.initRangeSliders()
- }
-
- initRangeSliders() {
- this._sliderTimers = new Map()
- $$('input[type="range"]', this.ui).forEach(input => {
- var binding = this.bindings.getBinding(input.dataset.binding)
-
- // create and hook up value tip
- let valtip = document.createElement('div')
- let valtipval = document.createElement('div')
- let valtipcallout = document.createElement('div')
- valtip.className = 'slider-value-tip'
- valtipval.className = 'value'
- valtipcallout.className = 'callout'
- valtipval.innerText = '0'
- valtip.appendChild(valtipval)
- valtip.appendChild(valtipcallout)
- binding.addOutput(valtipval)
- document.body.appendChild(valtip)
-
- let inputBounds = {}
- let min = parseFloat(input.getAttribute('min'))
- let max = parseFloat(input.getAttribute('max'))
- if (isNaN(min)) {
- min = 0
- }
- if (isNaN(max)) {
- max = 1
- }
- const sliderThumbWidth = 12
- const valtipYOffset = 14
-
- let updateValTipXPos = () => {
- let r = (binding.value - min) / (max - min)
- let sliderWidth = inputBounds.width - sliderThumbWidth
- let x = ((inputBounds.x + (sliderThumbWidth / 2)) + (sliderWidth * r)) - (valtip.clientWidth / 2)
- valtip.style.left = x + 'px'
- }
-
- binding.addListener(updateValTipXPos)
-
- let shownCounter = 0
- let showValTip = () => {
- if (++shownCounter == 1) {
- valtip.classList.add('visible')
- inputBounds = input.getBoundingClientRect()
- valtip.style.top = (inputBounds.y - valtip.clientHeight + valtipYOffset) + 'px'
- updateValTipXPos()
- }
- }
- let hideValTip = () => {
- if (--shownCounter == 0) {
- valtip.classList.remove('visible')
- }
- }
-
- input.addEventListener('pointerdown', showValTip)
- input.addEventListener('pointerup', hideValTip)
- input.addEventListener('pointercancel', hideValTip)
-
- let timer = null
- input.addEventListener('input', ev => {
- if (timer === null) {
- showValTip()
- } else {
- clearTimeout(timer)
- }
- timer = setTimeout(() => {
- timer = null
- hideValTip()
- }, 400)
- })
- })
- }
-
- initBindings() {
- let b = this.bindings
-
- // let updateTracking = fontSize => {
- // if (!this.currEditable.explicitTracking) {
- // var tracking = InterDynamicTracking(fontSize)
- // this.isChangingBindings = true
- // b.setValue('tracking', tracking)
- // this.isChangingBindings = false
- // }
- // }
-
- b.configure('tracking', 0, 'float', tracking => {
- if (!this.isChangingBindings && !this.currEditable.explicitTracking) {
- // console.log('enabled explicit tracking')
- this.currEditable.setExplicitTracking(true)
- this.setNeedsSaveValues()
- }
- })
- b.setFormatter('tracking', v => v.toFixed(3))
-
- b.configure('size', 0, 'float', size => {
- let ed = this.currEditable
- if (ed) {
- setTimeout(() => {
- // HERE BE DRAGONS! Feedback loop from Editable
- if (!ed.explicitTracking) {
- this.isChangingBindings = true
- b.setValue('tracking', ed.values.tracking)
- this.isChangingBindings = false
- }
- }, 10)
- }
- })
-
- b.configure('lineHeight', 1, 'float')
-
- b.bindAllInputs($$('.control input', this.ui))
- b.bindAllInputs($$('.control select', this.ui))
-
- $('.control input[data-binding="tracking"]').addEventListener("dblclick", ev => {
- let ed = this.currEditable
- setTimeout(() => {
- ed.setExplicitTracking(false)
- this.setNeedsSaveValues()
- this.isChangingBindings = true
- b.setValue('tracking', ed.values.tracking)
- this.isChangingBindings = false
- }, 50)
- })
-
- for (let binding of b.allBindings()) {
- binding.addListener(() => this.bindingChanged(binding))
- }
- }
-
- preloadFonts() {
- // Note: This has no effect on systems supporting variable fonts.
- [
- "regular",
- "italic",
- "medium",
- "medium-italic",
- "semi-bold",
- "semi-bold-italic",
- "bold",
- "bold-italic",
- "extra-bold",
- "extra-bold-italic",
- "black",
- "black-italic",
- ].forEach(style => {
- let e = document.createElement('div')
- e.className = 'font-preload font-style-' + style
- e.innerText = 'a'
- document.body.appendChild(e)
- })
- }
-
- bindingChanged(binding) {
- if (this.isChangingBindings) {
- // Note: this.isChangingBindings is true when binding values are
- // changed internally, in which case we do nothing here.
- return
- }
- if (this.currEditable) {
- this.currEditable.setValue(binding.name, binding.value)
- }
- this.setNeedsSaveValues()
- }
-
- reset() {
- for (let ed of this.editables.values()) {
- ed.resetValues()
- }
- this.updateBindingValues()
- }
-
- updateBindingValues() {
- if (this.currEditable) {
- this.isChangingBindings = true
- this.bindings.setValues(this.currEditable.values)
- this.isChangingBindings = false
- }
- }
-
- saveValues() {
- if (this._saveValuesTimer !== null) {
- clearTimeout(this._saveValuesTimer)
- this._saveValuesTimer = null
- }
- if (this.currEditable) {
- this.currEditable.saveValues()
- }
- }
-
- setNeedsSaveValues() {
- if (this._saveValuesTimer !== null) {
- clearTimeout(this._saveValuesTimer)
- }
- this._saveValuesTimer = setTimeout(() => this.saveValues(), 300)
- }
-
- setCurrEditable(ed) {
- if (this._saveValuesTimer !== null &&
- this.currEditable &&
- !this.isChangingBindings)
- {
- this.saveValues()
- }
- this.currEditable = ed
- this.updateBindingValues()
- if (this.currEditable) {
- this.showUI()
- } else {
- this.hideUI()
- }
- }
-
- onEditableReceivedFocus(ed) {
- // console.log(`onEditableReceivedFocus ${ed}`)
- clearTimeout(this._deselectTimer)
- this.setCurrEditable(ed)
- }
-
- onEditableLostFocus(ed) {
- // console.log(`onEditableLostFocus ${ed}`)
- // this.setCurrEditable(null)
- if (supportsFocusTrick) {
- this._deselectTimer = setTimeout(() => this.setCurrEditable(null), 10)
- }
- }
-
- showUI() {
- this.ui.classList.add('visible')
- }
-
- hideUI() {
- this.ui.classList.remove('visible')
- }
-
- stopEditing() {
- if (this.currEditable) {
- this.currEditable.el.blur()
- this.setCurrEditable(null)
- }
- }
-
- addAllEditables() {
- for (let el of $$('[data-ctxedit]')) {
- this.addEditable(el)
- }
- }
-
- addEditable(el) {
- let key = this.keyPrefix + el.dataset.ctxedit
- let existing = this.editables.get(key)
- if (existing) {
- throw new Error(`duplicate editable ${key}`)
- }
- let ed = new Editable(el, key)
- this.editables.set(key, ed)
- this.initEditable(ed)
- // this.showUI() // XXX
- }
-
- initEditable(ed) {
- // filter paste
- ed.el.addEventListener('paste', ev => {
- ev.preventDefault()
- let text = ev.clipboardData.getData("text/plain")
- document.execCommand("insertHTML", false, text)
- }, {capture:true,passive:false})
-
- ed.el.addEventListener('focus', ev => this.onEditableReceivedFocus(ed))
- ed.el.addEventListener('blur', ev => this.onEditableLostFocus(ed))
- }
-}
-
-return CtxEdit
-})();