diff options
Diffstat (limited to 'docs/r/ctxedit.js')
-rw-r--r-- | docs/r/ctxedit.js | 560 |
1 files changed, 0 insertions, 560 deletions
diff --git a/docs/r/ctxedit.js b/docs/r/ctxedit.js deleted file mode 100644 index 1ab896b08..000000000 --- a/docs/r/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 -})(); |