diff options
author | Hsieh Chin Fan <pham@topo.tw> | 2024-10-28 19:32:23 +0800 |
---|---|---|
committer | Hsieh Chin Fan <pham@topo.tw> | 2024-10-28 20:11:22 +0800 |
commit | ce1f619b9a7a64100a0315330244a296c091bbc9 (patch) | |
tree | 0165d829a83abe6d59a2fb69fec48b31213178eb | |
parent | 92dc83240b31455b8282242fafb0471681538fbe (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.mjs | 60 | ||||
-rw-r--r-- | src/dumbymap.mjs | 29 |
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 */ |
4 | import { GeoLink, getMarkersFromMaps, removeLeaderLines } from './Link.mjs' | 4 | import { GeoLink, getMarkersFromMaps, removeLeaderLines } from './Link.mjs' |
5 | import * as markers from './marker.mjs' | 5 | import * as markers from './marker.mjs' |
6 | import { 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 | */ |
537 | export const editMapByRawText = (ele) => new Item({ | 541 | export 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 | |||
571 | export 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 | */ |
139 | export const generateMaps = (container, { | 140 | export 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 | } |