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 | |
| parent | f82f540a0cf07e8fd95e36dea10a49aa839832d0 (diff) | |
add basic layer ui
| -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); | 
