summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsanine <sanine.not@pm.me>2022-06-02 00:53:26 -0500
committersanine <sanine.not@pm.me>2022-06-02 00:53:26 -0500
commite69dfc1b29f2fed1d1fd26dbeb9b248b1377c64c (patch)
tree44c6c389646d6e65f43930a97ac65acbaf0f7e52
parentf82f540a0cf07e8fd95e36dea10a49aa839832d0 (diff)
add basic layer ui
-rw-r--r--src/Map/Layer.js187
-rw-r--r--src/Map/Map.js1
-rw-r--r--src/Util/DomUtil.js36
-rw-r--r--src/index.html32
-rw-r--r--src/main.js5
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);