diff options
author | sanine <sanine.not@pm.me> | 2022-06-02 00:53:26 -0500 |
---|---|---|
committer | sanine <sanine.not@pm.me> | 2022-06-02 00:53:26 -0500 |
commit | e69dfc1b29f2fed1d1fd26dbeb9b248b1377c64c (patch) | |
tree | 44c6c389646d6e65f43930a97ac65acbaf0f7e52 /src | |
parent | f82f540a0cf07e8fd95e36dea10a49aa839832d0 (diff) |
add basic layer ui
Diffstat (limited to 'src')
-rw-r--r-- | src/Map/Layer.js | 187 | ||||
-rw-r--r-- | src/Map/Map.js | 1 | ||||
-rw-r--r-- | src/Util/DomUtil.js | 36 | ||||
-rw-r--r-- | src/index.html | 32 | ||||
-rw-r--r-- | src/main.js | 5 |
5 files changed, 257 insertions, 4 deletions
diff --git a/src/Map/Layer.js b/src/Map/Layer.js new file mode 100644 index 0000000..93e5799 --- /dev/null +++ b/src/Map/Layer.js @@ -0,0 +1,187 @@ +import h from '../Util/DomUtil.js'; + +class Layer { + constructor(id, name) { + this.id = id; + this.name = name; + this.subLayers = []; + this.visible = true; + } +} + +class LayerData { + constructor() { + this.layers = []; + this.idPool = 0; + } + + _getNewId() { + const id = this.idPool; + this.idPool += 1; + return `layer${id}`; + } + + _getContainerAndId(idChain) { + if (idChain.length === 0) + return [ this.layers, null ]; + + let container = this.layers; + while (idChain.length > 1) { + const id = idChain.pop(); + console.log(container, id); + container = container.find(layer => layer.id === id).subLayers; + console.log(idChain); + } + + return [ container, idChain[0] ]; + } + + _idToIndex(container, id) { + return container.findIndex(layer => layer.id === id); + } + + insertLayer(idChain) { + const layerId = this._getNewId(); + const newLayer = new Layer(layerId, `Layer ${layerId}`); + + const [ container, id ] = this._getContainerAndId(idChain); + if (id === null) { + container.push(newLayer); + return; + } + + const index = this._idToIndex(container, id); + container.splice(index+1, 0, newLayer); + } + + removeLayer(idChain) { + const [ container, id ] = this._getContainerAndId(idChain); + if (id === null) return; + + const index = this._idToIndex(container, id); + container.splice(index, 1); + } + + pushLayer(idChain) { + const [ container, id ] = this._getContainerAndId(idChain); + if (id === null) return; // nothing to do + + const index = this._idToIndex(container, id); + const after = container[index+1]; + if (!after) return; // no successor to push to, do nothing + after.subLayers.push(container[index]); + container.splice(index, 1); + } +} + + +class LayerView { + constructor(parentId) { + const parentElement = document.getElementById(parentId); + + const button = (label, f) => { + const b = h('button', label, { 'style': 'margin: 0 5px' }); + b.onclick = f; + return b; + } + + this.layersList = h('ul'); + + const root = h('fieldset', { 'class': 'layer-view' }, [ + h('legend', 'Layers'), + this.layersList, + button('Move Up', () => this.onMoveUp(this._getIdChain())), + button('Move Down', () => this.onMoveDown(this._getIdChain())), + button('Toggle Visible', () => this.onShowHideLayer(this._getIdChain())), + button('Rename', () => this.onRenameLayer(this._getIdChain())), + h('br'), h('br'), + button('Add Layer', () => this.onAddLayer(this._getIdChain())), + button('Remove Layer', () => this.onRemoveLayer(this._getIdChain())), + button('Push', () => this.onPushLayer(this._getIdChain())), + button('Pop', () => this.onPopLayer(this._getIdChain())), + ]); + + parentElement.appendChild(root); + + this.selectedId = null; + + this.onAddLayer = () => {}; + this.onRemoveLayer = () => {}; + this.onPushLayer = () => {}; + this.onPopLayer = () => {}; + + this.onMoveUp = () => {}; + this.onMoveDown = () => {}; + this.onShowHideLayer = () => {}; + this.onRenameLayer = () => {}; + } + + _getIdChain() { + const chain = []; + let el = this.selected; + + if (el === null || el === undefined) return []; + while (el !== this.layersList && el !== null) { + if (el.nodeName.toLowerCase() === 'li') + chain.push(el.id); + el = el.parentNode; + } + return chain; + } + + renderLayers(layers, root=this.layersList) { + let selectedId = ''; + if (this.selected) selectedId = this.selected.id; + if (root === this.layersList) this.layersList.replaceChildren(); + for (let layer of layers) { + let classList = ''; + if (layer.visible) classList += 'visible '; + const el = h( + 'li', + layer.name, + { + id: layer.id, + 'class': classList, + } + ); + + if (selectedId === layer.id) { + el.classList.add('selected'); + this.selected = el; + } + el.onclick = e => { + e.stopPropagation(); + if (this.selected) this.selected.classList.remove('selected'); + this.selected = el; + el.classList.add('selected'); + }; + if (layer.subLayers.length !== 0) { + const ul = h('ul'); + this.renderLayers(layer.subLayers, ul); + el.appendChild(ul); + } + root.prepend(el); + } + } +} + + +class LayerController { + constructor(data, view) { + this.data = data; + this.view = view; + + const respond = f => ( idChain => { f(idChain); this.updateView(); } ); + + this.view.onAddLayer = idChain => { this.data.insertLayer(idChain); this.updateView(); }; + this.view.onPushLayer = idChain => { this.data.pushLayer(idChain); this.updateView(); }; + this.view.onRemoveLayer = idChain => { this.data.removeLayer(idChain); this.updateView(); } + } + + updateView() { + this.view.renderLayers(this.data.layers); + } +} + + +export { LayerData, LayerView, LayerController }; diff --git a/src/Map/Map.js b/src/Map/Map.js new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/Map/Map.js @@ -0,0 +1 @@ + diff --git a/src/Util/DomUtil.js b/src/Util/DomUtil.js new file mode 100644 index 0000000..5349365 --- /dev/null +++ b/src/Util/DomUtil.js @@ -0,0 +1,36 @@ +function h(tagName, text, attributes, children) { + /* allow for not adding text */ + if (children === undefined && typeof(text) === 'object') { + children = attributes; + attributes = text; + text = null; + } + + /* allow for not adding attributes */ + if (children === undefined && Array.isArray(attributes)) { + children = attributes; + attributes = {}; + } + + /* allow for not adding children */ + if (children === undefined) { + children = []; + } + + const element = document.createElement(tagName); + if (text) + element.appendChild(document.createTextNode(text)); + + for (let key in attributes) { + const value = attributes[key]; + element.setAttribute(key, value); + } + + for (let child of children) + element.appendChild(child); + + return element; +} + + +export default h; diff --git a/src/index.html b/src/index.html index cebdef7..55e00b1 100644 --- a/src/index.html +++ b/src/index.html @@ -8,22 +8,46 @@ <style> body { font-family: monospace; - background-color: #333; + background-color: #222; color: white; } button { font-family: monospace; } - button.selected { - color: red; + + ul { + list-style: none; + } + + li { + user-select: none; + } + + li.selected { + background-color: white; + color: #222; + } + + li.selected li { + background-color: #222; + color: white; + } + + li:before { + content: 'x '; + } + + li.visible:before { + content: '👁 '; } </style> <script type="module" src="main.js"></script> </head> <body style="margin: 0; padding: 0; overflow: hidden"> - <div id="root"></div> <noscript> <h1>You need Javascript enabled to use this site</h1> </noscript> + + <div id="root"></div> </body> </html> diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..0474d7a --- /dev/null +++ b/src/main.js @@ -0,0 +1,5 @@ +import { LayerData, LayerView, LayerController } from './Map/Layer.js'; + +const view = new LayerView('root'); +const data = new LayerData(); +const con = new LayerController(data, view); |