aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorHsieh Chin Fan <pham@topo.tw>2024-10-28 19:32:23 +0800
committerHsieh Chin Fan <pham@topo.tw>2024-10-28 20:11:22 +0800
commitce1f619b9a7a64100a0315330244a296c091bbc9 (patch)
tree0165d829a83abe6d59a2fb69fec48b31213178eb
parent92dc83240b31455b8282242fafb0471681538fbe (diff)
feat: add menu-folder for editing map options
* add dumbymap.aliases for option aliases * don't remove sub-menu when child sub-menu exists * don't add render delay for config modification by menu-item * TODO sub-menu placement
-rw-r--r--src/MenuItem.mjs60
-rw-r--r--src/dumbymap.mjs29
2 files changed, 73 insertions, 16 deletions
diff --git a/src/MenuItem.mjs b/src/MenuItem.mjs
index 7e50991..e560b39 100644
--- a/src/MenuItem.mjs
+++ b/src/MenuItem.mjs
@@ -3,6 +3,7 @@ import { addMarkerByPoint } from './dumbyUtils.mjs'
3/* eslint-disable-next-line no-unused-vars */ 3/* eslint-disable-next-line no-unused-vars */
4import { GeoLink, getMarkersFromMaps, removeLeaderLines } from './Link.mjs' 4import { GeoLink, getMarkersFromMaps, removeLeaderLines } from './Link.mjs'
5import * as markers from './marker.mjs' 5import * as markers from './marker.mjs'
6import { parseConfigsFromYaml } from 'mapclay'
6 7
7/** 8/**
8 * @typedef {Object} RefLink 9 * @typedef {Object} RefLink
@@ -77,7 +78,10 @@ export class Folder extends window.HTMLDivElement {
77 const offset = this.items.length > 1 ? '-20px' : '0px' 78 const offset = this.items.length > 1 ? '-20px' : '0px'
78 submenu.style.cssText = `${style ?? ''}position: absolute; left: 105%; top: ${offset};` 79 submenu.style.cssText = `${style ?? ''}position: absolute; left: 105%; top: ${offset};`
79 this.items.forEach(item => submenu.appendChild(item)) 80 this.items.forEach(item => submenu.appendChild(item))
80 submenu.onmouseleave = () => submenu.remove() 81 submenu.onmouseleave = () => {
82 if (submenu.querySelectorAll('.sub-menu').length > 0) return
83 submenu.remove()
84 }
81 85
82 // hover effect 86 // hover effect
83 this.parentElement 87 this.parentElement
@@ -98,7 +102,7 @@ export const simplePlaceholder = (text) => new Item({
98}) 102})
99 103
100/** 104/**
101 * Creates a menu item for picking a map 105 * Pick up a map
102 * 106 *
103 * @param {Object} options - The options object 107 * @param {Object} options - The options object
104 * @param {Object} options.utils - Utility functions 108 * @param {Object} options.utils - Utility functions
@@ -532,20 +536,21 @@ export const addMarker = ({
532/** 536/**
533 * editByRawText. 537 * editByRawText.
534 * 538 *
535 * @param {HTMLElement} ele 539 * @param {HTMLElement} map
536 */ 540 */
537export const editMapByRawText = (ele) => new Item({ 541export const editMapByRawText = (map) => new Item({
538 text: 'Edit by Raw Text', 542 text: 'Edit by Raw Text',
539 onclick: () => { 543 onclick: () => {
540 const maps = Array.from(ele.querySelectorAll('.mapclay')) 544 const container = map.closest('.map-container')
545 const maps = Array.from(container.querySelectorAll('.mapclay'))
541 if (!maps) return false 546 if (!maps) return false
542 547
543 const rect = ele.getBoundingClientRect() 548 const rect = map.getBoundingClientRect()
544 const textArea = document.createElement('textarea') 549 const textArea = document.createElement('textarea')
545 textArea.className = 'edit-map' 550 textArea.className = 'edit-map'
546 textArea.style.cssText = `width: ${rect.width}px; height: ${rect.height}px;` 551 textArea.style.cssText = `width: ${rect.width}px; height: ${rect.height}px;`
547 textArea.value = maps.map(map => map.dataset.mapclay ?? '') 552 textArea.value = maps.map(map => map.dataset.mapclay ?? '')
548 .join('---') 553 .join('\n---\n')
549 .replaceAll(',', '\n') 554 .replaceAll(',', '\n')
550 .replaceAll(/["{}]/g, '') 555 .replaceAll(/["{}]/g, '')
551 .replaceAll(/:(\w)/g, ': $1') 556 .replaceAll(/:(\w)/g, ': $1')
@@ -554,10 +559,49 @@ export const editMapByRawText = (ele) => new Item({
554 const code = document.createElement('code') 559 const code = document.createElement('code')
555 code.className = 'map' 560 code.className = 'map'
556 code.textContent = textArea.value 561 code.textContent = textArea.value
562 container.dataset.render = 'no-delay'
557 textArea.replaceWith(code) 563 textArea.replaceWith(code)
558 }) 564 })
559 ele.replaceChildren(textArea) 565 container.replaceChildren(textArea)
560 566
561 return true 567 return true
562 }, 568 },
563}) 569})
570
571export const editMap = (map, dumbymap) => {
572 const options = Object.entries(dumbymap.aliases)
573 .map(([option, aliases]) =>
574 new Folder({
575 text: option,
576 items: Object.entries(aliases)
577 .map(([alias, value]) => {
578 const aliasValue = value.value ?? value
579 return new Item({
580 innerHTML: `<div>${alias}</div><div style="padding-left: 20px; color: gray; font-size: 1rem";">${aliasValue}</div>`,
581 style: 'display: flex; justify-content: space-between; max-width: 20rem;',
582 onclick: () => {
583 const container = map.closest('.map-container')
584 const configText = Array.from(container.querySelectorAll('.mapclay'))
585 .map(map => map.dataset.mapclay ?? '')
586 .join('\n---\n')
587 const configList = parseConfigsFromYaml(configText)
588 configList.find(config => config.id === map.id)[option] = aliasValue
589 const code = document.createElement('code')
590 code.className = 'map'
591 code.textContent = configList.map(JSON.stringify).join('\n---\n')
592 container.dataset.render = 'no-delay'
593 container.replaceChildren(code)
594 },
595 })
596 }),
597 }),
598 )
599 return new Folder({
600 text: 'Edit Map',
601 style: 'overflow: visible;',
602 items: [
603 editMapByRawText(map),
604 ...options,
605 ],
606 })
607}
diff --git a/src/dumbymap.mjs b/src/dumbymap.mjs
index f538d0d..1509c04 100644
--- a/src/dumbymap.mjs
+++ b/src/dumbymap.mjs
@@ -135,6 +135,7 @@ const defaultRender = mapclay.renderWith(config => ({
135 * @param {number} [options.delay=1000] mapDelay - Delay before rendering maps (in milliseconds) 135 * @param {number} [options.delay=1000] mapDelay - Delay before rendering maps (in milliseconds)
136 * @param {Function} options.render - Render function for maps 136 * @param {Function} options.render - Render function for maps
137 * @param {Function} options.renderCallback - Callback function to be called after map rendering 137 * @param {Function} options.renderCallback - Callback function to be called after map rendering
138 * @param {String | null} options.defaultApply
138 */ 139 */
139export const generateMaps = (container, { 140export const generateMaps = (container, {
140 contentSelector, 141 contentSelector,
@@ -144,6 +145,7 @@ export const generateMaps = (container, {
144 mapDelay = 1000, 145 mapDelay = 1000,
145 render = defaultRender, 146 render = defaultRender,
146 renderCallback = () => null, 147 renderCallback = () => null,
148 defaultApply = 'https://outdoorsafetylab.github.io/dumbymap/assets/default.yml',
147} = {}) => { 149} = {}) => {
148 /** Prepare: Contaner */ 150 /** Prepare: Contaner */
149 if (container.classList.contains('Dumby')) return 151 if (container.classList.contains('Dumby')) return
@@ -179,6 +181,7 @@ export const generateMaps = (container, {
179 get blocks () { return Array.from(container.querySelectorAll('.dumby-block')) }, 181 get blocks () { return Array.from(container.querySelectorAll('.dumby-block')) },
180 modal, 182 modal,
181 modalContent, 183 modalContent,
184 aliases: {},
182 utils: { 185 utils: {
183 ...utils, 186 ...utils,
184 renderedMaps: () => 187 renderedMaps: () =>
@@ -478,6 +481,7 @@ export const generateMaps = (container, {
478 */ 481 */
479 function renderMap (target) { 482 function renderMap (target) {
480 if (!target.isConnected) return 483 if (!target.isConnected) return
484 target.classList.add('map-container')
481 485
482 // Get text in code block starts with markdown text '```map' 486 // Get text in code block starts with markdown text '```map'
483 const configText = target 487 const configText = target
@@ -510,7 +514,7 @@ export const generateMaps = (container, {
510 .forEach(e => e.remove()) 514 .forEach(e => e.remove())
511 } 515 }
512 516
513 if (!target.renderMap) { 517 if (!target.renderMap || target.dataset.render === 'no-delay') {
514 target.renderMap = debounce( 518 target.renderMap = debounce(
515 (configList) => { 519 (configList) => {
516 // Render maps 520 // Render maps
@@ -522,7 +526,7 @@ export const generateMaps = (container, {
522 afterMapRendered(e.renderer) 526 afterMapRendered(e.renderer)
523 } 527 }
524 }) 528 })
525 }, mapDelay, 529 }, target.dataset.render === 'no-delay' ? 0 : mapDelay,
526 ) 530 )
527 } 531 }
528 target.renderMap(configList) 532 target.renderMap(configList)
@@ -592,12 +596,7 @@ export const generateMaps = (container, {
592 const rect = map.getBoundingClientRect() 596 const rect = map.getBoundingClientRect()
593 const [x, y] = [e.x - rect.left, e.y - rect.top] 597 const [x, y] = [e.x - rect.left, e.y - rect.top]
594 menu.appendChild(menuItem.simplePlaceholder(`MAP ID: ${map.id}`)) 598 menu.appendChild(menuItem.simplePlaceholder(`MAP ID: ${map.id}`))
595 menu.appendChild(new menuItem.Folder({ 599 menu.appendChild(menuItem.editMap(map, dumbymap))
596 text: 'Edit Map',
597 items: [
598 menuItem.editMapByRawText(map.parentElement),
599 ],
600 }))
601 menu.appendChild(menuItem.renderResults(dumbymap, map)) 600 menu.appendChild(menuItem.renderResults(dumbymap, map))
602 601
603 if (map.dataset.render === 'fulfilled') { 602 if (map.dataset.render === 'fulfilled') {
@@ -694,6 +693,20 @@ export const generateMaps = (container, {
694 } 693 }
695 } 694 }
696 695
696 /** Get default applied config */
697 if (defaultApply) {
698 fetch(defaultApply)
699 .then(res => res.text())
700 .then(rawText => {
701 const config = mapclay.parseConfigsFromYaml(rawText)?.at(0)
702 Object.entries(config.aliases)
703 .forEach(([option, aliases]) => {
704 dumbymap.aliases[option] = aliases
705 })
706 })
707 .catch(err => console.warn(`Fail to get aliases from ${defaultApply}`, err))
708 }
709
697 /** Return Object for utils */ 710 /** Return Object for utils */
698 return Object.seal(dumbymap) 711 return Object.seal(dumbymap)
699} 712}