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 /src | |
| 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
Diffstat (limited to 'src')
| -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 | } |