diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/MenuItem.mjs | 101 | ||||
-rw-r--r-- | src/css/index.css | 33 | ||||
-rw-r--r-- | src/dumbyUtils.mjs | 9 | ||||
-rw-r--r-- | src/dumbymap.mjs | 26 | ||||
-rw-r--r-- | src/editor.mjs | 31 |
5 files changed, 151 insertions, 49 deletions
diff --git a/src/MenuItem.mjs b/src/MenuItem.mjs index 734c313..e974f9e 100644 --- a/src/MenuItem.mjs +++ b/src/MenuItem.mjs | |||
@@ -1,31 +1,94 @@ | |||
1 | import { createGeoLink } from './dumbymap'; | 1 | import { createGeoLink } from './dumbymap'; |
2 | 2 | ||
3 | export function nextMap() { | 3 | class Item { |
4 | const element = document.createElement('div'); | 4 | constructor({ text, innerHTML, onclick }) { |
5 | element.className = 'menu-item'; | 5 | this.text = text; |
6 | element.innerHTML = 'Next Map <span class="info">(Tab)</span>'; | 6 | this.innerHTML = innerHTML; |
7 | element.onclick = () => this.utils.focusNextMap(); | 7 | this.onclick = onclick; |
8 | } | ||
8 | 9 | ||
9 | return element; | 10 | get element() { |
11 | const element = document.createElement('div'); | ||
12 | element.innerHTML = this.innerHTML ? this.innerHTML : this.text; | ||
13 | element.classList.add('menu-item'); | ||
14 | element.onclick = this.onclick; | ||
15 | return element; | ||
16 | } | ||
10 | } | 17 | } |
11 | 18 | ||
12 | export function nextBlock() { | 19 | class Folder { |
13 | const element = document.createElement('div'); | 20 | constructor({ text, innerHTML, items }) { |
14 | element.className = 'menu-item'; | 21 | this.text = text; |
15 | element.innerHTML = 'Next Block <span class="info">(n)</span>'; | 22 | this.innerHTML = innerHTML; |
16 | element.onclick = () => this.utils.focusNextBlock(); | 23 | this.items = items; |
24 | this.utils; | ||
25 | } | ||
26 | |||
27 | get element() { | ||
28 | const element = document.createElement('div'); | ||
29 | element.classList.add(this.className); | ||
30 | element.className = 'menu-item folder'; | ||
31 | element.innerHTML = this.innerHTML; | ||
32 | element.style.cssText = 'position: relative; overflow: visible;'; | ||
33 | element.onmouseover = () => { | ||
34 | // Prepare submenu | ||
35 | this.submenu = document.createElement('div'); | ||
36 | this.submenu.className = 'sub-menu'; | ||
37 | this.submenu.style.cssText = `position: absolute; left: 105%; top: 0px;`; | ||
38 | this.items.forEach(item => this.submenu.appendChild(item)); | ||
17 | 39 | ||
18 | return element; | 40 | // hover effect |
41 | element.parentElement | ||
42 | .querySelectorAll('.sub-menu') | ||
43 | .forEach(sub => sub.remove()); | ||
44 | element.appendChild(this.submenu); | ||
45 | }; | ||
46 | return element; | ||
47 | } | ||
19 | } | 48 | } |
20 | 49 | ||
21 | export function nextLayout() { | 50 | export const pickMapItem = dumbymap => |
22 | const element = document.createElement('div'); | 51 | new Folder({ |
23 | element.className = 'menu-item'; | 52 | innerHTML: '<span>Focus a Map<span><span class="info">(Tab)</span>', |
24 | element.innerHTML = 'Next Layout <span class="info">(x)</span>'; | 53 | items: dumbymap.utils.renderedMaps().map( |
25 | element.onclick = () => this.utils.switchToNextLayout(); | 54 | map => |
55 | new Item({ | ||
56 | text: map.id, | ||
57 | onclick: () => map.classList.add('focus'), | ||
58 | }).element, | ||
59 | ), | ||
60 | }).element; | ||
26 | 61 | ||
27 | return element; | 62 | export const pickBlockItem = dumbymap => |
28 | } | 63 | new Folder({ |
64 | innerHTML: '<span>Focus Block<span><span class="info">(n/p)</span>', | ||
65 | items: dumbymap.blocks.map( | ||
66 | (block, index) => | ||
67 | new Item({ | ||
68 | text: `Block ${index}`, | ||
69 | onclick: () => block.classList.add('focus'), | ||
70 | }).element, | ||
71 | ), | ||
72 | }).element; | ||
73 | |||
74 | export const pickLayoutItem = dumbymap => | ||
75 | new Folder({ | ||
76 | innerHTML: '<span>Switch Layout<span><span class="info">(x)</span>', | ||
77 | items: [ | ||
78 | new Item({ | ||
79 | text: 'EDIT', | ||
80 | onclick: () => document.body.setAttribute('data-mode', 'editing'), | ||
81 | }).element, | ||
82 | ...dumbymap.layouts.map( | ||
83 | layout => | ||
84 | new Item({ | ||
85 | text: layout.name, | ||
86 | onclick: () => | ||
87 | dumbymap.container.setAttribute('data-layout', layout.name), | ||
88 | }).element, | ||
89 | ), | ||
90 | ], | ||
91 | }).element; | ||
29 | 92 | ||
30 | export class GeoLink { | 93 | export class GeoLink { |
31 | constructor({ range }) { | 94 | constructor({ range }) { |
diff --git a/src/css/index.css b/src/css/index.css index 221ee69..3133c5f 100644 --- a/src/css/index.css +++ b/src/css/index.css | |||
@@ -52,7 +52,6 @@ body { | |||
52 | box-sizing: border-box; | 52 | box-sizing: border-box; |
53 | flex-direction: column; | 53 | flex-direction: column; |
54 | align-items: stretch; | 54 | align-items: stretch; |
55 | |||
56 | height: 100%; | 55 | height: 100%; |
57 | gap: 0.5em; | 56 | gap: 0.5em; |
58 | 57 | ||
@@ -124,10 +123,9 @@ body { | |||
124 | 123 | ||
125 | .container__suggestion { | 124 | .container__suggestion { |
126 | display: flex; | 125 | display: flex; |
126 | overflow: hidden; | ||
127 | justify-content: space-between; | 127 | justify-content: space-between; |
128 | align-items: center; | 128 | align-items: center; |
129 | |||
130 | overflow: hidden; | ||
131 | height: fit-content; | 129 | height: fit-content; |
132 | 130 | ||
133 | cursor: pointer; | 131 | cursor: pointer; |
@@ -167,13 +165,16 @@ body { | |||
167 | 165 | ||
168 | .menu-item { | 166 | .menu-item { |
169 | display: flex; | 167 | display: flex; |
168 | box-sizing: border-box; | ||
170 | justify-content: space-between; | 169 | justify-content: space-between; |
171 | |||
172 | padding: 0.5rem; | 170 | padding: 0.5rem; |
173 | 171 | ||
174 | z-index: 9999; | 172 | z-index: 9999; |
173 | text-wrap: nowrap; | ||
175 | 174 | ||
176 | cursor: pointer; | 175 | cursor: pointer; |
176 | border: 2px solid transparent; | ||
177 | border-radius: 5px; | ||
177 | 178 | ||
178 | &:hover { | 179 | &:hover { |
179 | background: rgb(226 232 240); | 180 | background: rgb(226 232 240); |
@@ -181,6 +182,30 @@ body { | |||
181 | 182 | ||
182 | .info { | 183 | .info { |
183 | color: steelblue; | 184 | color: steelblue; |
185 | padding-inline: 1em; | ||
184 | font-weight: bold; | 186 | font-weight: bold; |
185 | } | 187 | } |
186 | } | 188 | } |
189 | |||
190 | .folder::after { | ||
191 | content: '⏵'; | ||
192 | } | ||
193 | |||
194 | .sub-menu { | ||
195 | width: fit-content; | ||
196 | |||
197 | position: absolute; | ||
198 | z-index: 100; | ||
199 | |||
200 | border: 2px solid gray; | ||
201 | border-radius: 6px; | ||
202 | |||
203 | background: white; | ||
204 | min-width: 6rem; | ||
205 | max-height: 40vh; | ||
206 | .menu-item { | ||
207 | margin: 0 auto; | ||
208 | padding-inline: 0.5em; | ||
209 | min-width: 5em; | ||
210 | } | ||
211 | } | ||
diff --git a/src/dumbyUtils.mjs b/src/dumbyUtils.mjs index cfac00b..140d671 100644 --- a/src/dumbyUtils.mjs +++ b/src/dumbyUtils.mjs | |||
@@ -1,17 +1,12 @@ | |||
1 | export function focusNextMap(reverse = false) { | 1 | export function focusNextMap(reverse = false) { |
2 | const renderedList = Array.from( | 2 | const renderedList = this.utils.renderedMaps(); |
3 | this.htmlHolder.querySelectorAll('[data-render=fulfilled]'), | 3 | |
4 | ); | ||
5 | const mapNum = renderedList.length; | 4 | const mapNum = renderedList.length; |
6 | if (mapNum === 0) return; | 5 | if (mapNum === 0) return; |
7 | 6 | ||
8 | // Get current focused map element | 7 | // Get current focused map element |
9 | const currentFocus = this.container.querySelector('.mapclay.focus'); | 8 | const currentFocus = this.container.querySelector('.mapclay.focus'); |
10 | 9 | ||
11 | // Remove class name of focus for ALL candidates | ||
12 | // This may trigger animation | ||
13 | renderedList.forEach(ele => ele.classList.remove('focus')); | ||
14 | |||
15 | // Get next existing map element | 10 | // Get next existing map element |
16 | const padding = reverse ? -1 : 1; | 11 | const padding = reverse ? -1 : 1; |
17 | let nextIndex = currentFocus | 12 | let nextIndex = currentFocus |
diff --git a/src/dumbymap.mjs b/src/dumbymap.mjs index dc22021..faa0621 100644 --- a/src/dumbymap.mjs +++ b/src/dumbymap.mjs | |||
@@ -153,7 +153,7 @@ export const generateMaps = (container, { delay, mapCallback }) => { | |||
153 | const showcase = document.createElement('div'); | 153 | const showcase = document.createElement('div'); |
154 | container.appendChild(showcase); | 154 | container.appendChild(showcase); |
155 | showcase.classList.add('Showcase'); | 155 | showcase.classList.add('Showcase'); |
156 | const renderMaps = []; | 156 | const renderPromises = []; |
157 | 157 | ||
158 | const dumbymap = { | 158 | const dumbymap = { |
159 | layouts, | 159 | layouts, |
@@ -161,8 +161,11 @@ export const generateMaps = (container, { delay, mapCallback }) => { | |||
161 | htmlHolder, | 161 | htmlHolder, |
162 | showcase, | 162 | showcase, |
163 | blocks, | 163 | blocks, |
164 | renderMaps: renderMaps, | ||
165 | utils: { | 164 | utils: { |
165 | renderedMaps: () => | ||
166 | Array.from( | ||
167 | container.querySelectorAll('.mapclay[data-render=fulfilled]'), | ||
168 | ), | ||
166 | focusNextMap: throttle(utils.focusNextMap, utils.focusDelay), | 169 | focusNextMap: throttle(utils.focusNextMap, utils.focusDelay), |
167 | switchToNextLayout: throttle(utils.switchToNextLayout, 300), | 170 | switchToNextLayout: throttle(utils.switchToNextLayout, 300), |
168 | focusNextBlock: utils.focusNextBlock, | 171 | focusNextBlock: utils.focusNextBlock, |
@@ -259,10 +262,6 @@ export const generateMaps = (container, { delay, mapCallback }) => { | |||
259 | ); | 262 | ); |
260 | }; | 263 | }; |
261 | //}}} | 264 | //}}} |
262 | // Draggable Blocks {{{ | ||
263 | // Add draggable part for blocks | ||
264 | |||
265 | // }}} | ||
266 | // CSS observer {{{ | 265 | // CSS observer {{{ |
267 | // Focus Map {{{ | 266 | // Focus Map {{{ |
268 | // Set focusArea | 267 | // Set focusArea |
@@ -282,6 +281,13 @@ export const generateMaps = (container, { delay, mapCallback }) => { | |||
282 | visibilityProperty: true, | 281 | visibilityProperty: true, |
283 | }); | 282 | }); |
284 | 283 | ||
284 | if (focus) { | ||
285 | dumbymap.utils | ||
286 | .renderedMaps() | ||
287 | .filter(map => map !== target) | ||
288 | .forEach(map => map.classList.remove('focus')); | ||
289 | } | ||
290 | |||
285 | if (shouldBeInShowcase) { | 291 | if (shouldBeInShowcase) { |
286 | if (showcase.contains(target)) return; | 292 | if (showcase.contains(target)) return; |
287 | 293 | ||
@@ -448,7 +454,7 @@ export const generateMaps = (container, { delay, mapCallback }) => { | |||
448 | // FIXME HACK use MutationObserver for animation | 454 | // FIXME HACK use MutationObserver for animation |
449 | if (!target.animations) target.animations = Promise.resolve(); | 455 | if (!target.animations) target.animations = Promise.resolve(); |
450 | target.animations = target.animations.then(async () => { | 456 | target.animations = target.animations.then(async () => { |
451 | await new Promise(resolve => setTimeout(resolve, 150)); | 457 | await new Promise(resolve => setTimeout(resolve, 100)); |
452 | target.setAttribute('data-report', passNum); | 458 | target.setAttribute('data-report', passNum); |
453 | }); | 459 | }); |
454 | }; | 460 | }; |
@@ -505,9 +511,9 @@ export const generateMaps = (container, { delay, mapCallback }) => { | |||
505 | // Render maps with delay | 511 | // Render maps with delay |
506 | const timer = setTimeout( | 512 | const timer = setTimeout( |
507 | () => | 513 | () => |
508 | render(target, configList).forEach(renderMap => { | 514 | render(target, configList).forEach(renderPromise => { |
509 | renderMaps.push(renderMap); | 515 | renderPromises.push(renderPromise); |
510 | renderMap.then(afterMapRendered); | 516 | renderPromise.then(afterMapRendered); |
511 | }), | 517 | }), |
512 | delay ?? 1000, | 518 | delay ?? 1000, |
513 | ); | 519 | ); |
diff --git a/src/editor.mjs b/src/editor.mjs index f965b56..9946686 100644 --- a/src/editor.mjs +++ b/src/editor.mjs | |||
@@ -10,13 +10,17 @@ const HtmlContainer = document.querySelector('.DumbyMap'); | |||
10 | const textArea = document.querySelector('.editor textarea'); | 10 | const textArea = document.querySelector('.editor textarea'); |
11 | let dumbymap; | 11 | let dumbymap; |
12 | 12 | ||
13 | const toggleEditing = () => { | 13 | new MutationObserver(() => { |
14 | if (document.body.getAttribute('data-mode') === 'editing') { | 14 | if (document.body.getAttribute('data-mode') === 'editing') { |
15 | document.body.removeAttribute('data-mode'); | 15 | HtmlContainer.setAttribute('data-layout', 'normal'); |
16 | } else { | ||
17 | document.body.setAttribute('data-mode', 'editing'); | ||
18 | } | 16 | } |
19 | HtmlContainer.setAttribute('data-layout', 'normal'); | 17 | }).observe(document.body, { |
18 | attributes: true, | ||
19 | attributeFilter: ['data-mode'], | ||
20 | }); | ||
21 | const toggleEditing = () => { | ||
22 | const mode = document.body.getAttribute('data-mode'); | ||
23 | document.body.setAttribute('data-mode', mode === 'editing' ? '' : 'editing'); | ||
20 | }; | 24 | }; |
21 | // }}} | 25 | // }}} |
22 | // Set up EasyMDE {{{ | 26 | // Set up EasyMDE {{{ |
@@ -274,6 +278,15 @@ window.onhashchange = () => { | |||
274 | const menu = document.createElement('div'); | 278 | const menu = document.createElement('div'); |
275 | menu.id = 'menu'; | 279 | menu.id = 'menu'; |
276 | menu.onclick = () => (menu.style.display = 'none'); | 280 | menu.onclick = () => (menu.style.display = 'none'); |
281 | new MutationObserver(() => { | ||
282 | if (menu.style.display === 'none') { | ||
283 | menu.style.cssText = ''; | ||
284 | menu.replaceChildren(); | ||
285 | } | ||
286 | }).observe(menu, { | ||
287 | attributes: true, | ||
288 | attributeFilter: ['style'], | ||
289 | }); | ||
277 | document.body.append(menu); | 290 | document.body.append(menu); |
278 | 291 | ||
279 | const rendererOptions = {}; | 292 | const rendererOptions = {}; |
@@ -657,13 +670,13 @@ document.oncontextmenu = e => { | |||
657 | if (selection) { | 670 | if (selection) { |
658 | e.preventDefault(); | 671 | e.preventDefault(); |
659 | menu.innerHTML = ''; | 672 | menu.innerHTML = ''; |
660 | menu.style.cssText = `display: block; left: ${e.clientX + 10}px; top: ${e.clientY + 5}px;`; | ||
661 | const addGeoLink = new menuItem.GeoLink({ range }); | 673 | const addGeoLink = new menuItem.GeoLink({ range }); |
662 | menu.appendChild(addGeoLink.createElement()); | 674 | menu.appendChild(addGeoLink.createElement()); |
663 | } | 675 | } |
664 | menu.appendChild(menuItem.nextMap.bind(dumbymap)()); | 676 | menu.style.cssText = `overflow: visible; display: block; left: ${e.clientX + 10}px; top: ${e.clientY + 5}px;`; |
665 | menu.appendChild(menuItem.nextBlock.bind(dumbymap)()); | 677 | menu.appendChild(menuItem.pickMapItem(dumbymap)); |
666 | menu.appendChild(menuItem.nextLayout.bind(dumbymap)()); | 678 | menu.appendChild(menuItem.pickBlockItem(dumbymap)); |
679 | menu.appendChild(menuItem.pickLayoutItem(dumbymap)); | ||
667 | }; | 680 | }; |
668 | 681 | ||
669 | const actionOutsideMenu = e => { | 682 | const actionOutsideMenu = e => { |