aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/dumbymap.mjs
diff options
context:
space:
mode:
authorHsieh Chin Fan <pham@topo.tw>2024-09-23 12:55:49 +0800
committerHsieh Chin Fan <pham@topo.tw>2024-09-23 13:34:43 +0800
commit3bd8fd3da8e37fee57ff47eb43d3f97bfffa7c40 (patch)
treee58ac34f1e7195bb403299bd28e5f3af91550532 /src/dumbymap.mjs
parent5eefd9c2e4f948cddecf313231afdc62aa1531cd (diff)
feat: remove keydown events from dumbymap
* events now handed by editor * this fix document.onkeydown reset when generatedMaps() called more than one time * apply "bind" to remove methods about user interaction from generateMaps() * refactor focusNextmap(), using array to store rendered maps BREAKING CHANGE: generateMaps() now return dumbymap Object, contains key elements and methods
Diffstat (limited to 'src/dumbymap.mjs')
-rw-r--r--src/dumbymap.mjs107
1 files changed, 49 insertions, 58 deletions
diff --git a/src/dumbymap.mjs b/src/dumbymap.mjs
index 42a507f..d642504 100644
--- a/src/dumbymap.mjs
+++ b/src/dumbymap.mjs
@@ -137,19 +137,58 @@ export const markdown2HTML = (container, mdContent) => {
137 //}}} 137 //}}}
138} 138}
139// FIXME Don't use hard-coded CSS selector 139// FIXME Don't use hard-coded CSS selector
140export const generateMaps = async (container, callback) => { 140// TODO Use UI to switch layouts
141function focusNextMap(reverse = false) {
142 const mapNum = this.renderedMaps.length
143 if (mapNum === 0) return
144 // Get current focused map element
145 const currentFocus = this.container.querySelector('.map-container[data-focus]')
146
147 // Remove class name of focus for ALL candidates
148 // This may trigger animation
149 Array.from(this.container.querySelectorAll('.map-container'))
150 .forEach(ele => ele.removeAttribute('data-focus'))
151
152 // Get next existing map element
153 const padding = reverse ? -1 : 1
154 let nextIndex = currentFocus ? this.renderedMaps.indexOf(currentFocus) + padding : 0
155 nextIndex = (nextIndex + mapNum) % mapNum
156 const nextFocus = this.renderedMaps[nextIndex]
157 nextFocus.setAttribute('data-focus', "true")
158
159 return nextFocus
160}
161function focusDelay() {
162 return window.getComputedStyle(this.showcase).display === 'none' ? 50 : 300
163}
164
165function switchToNextLayout() {
166 const currentLayoutName = this.container.getAttribute('data-layout')
167 const currentIndex = layouts.map(l => l.name).indexOf(currentLayoutName)
168 const nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % layouts.length
169 const nextLayout = layouts[nextIndex]
170 this.container.setAttribute("data-layout", nextLayout.name)
171}
172
173export const generateMaps = (container, callback) => {
141 container.classList.add('Dumby') 174 container.classList.add('Dumby')
142 const htmlHolder = container.querySelector('.SemanticHtml') ?? container 175 const htmlHolder = container.querySelector('.SemanticHtml') ?? container
143 const blocks = Array.from(htmlHolder.querySelectorAll('.dumby-block')) 176 const blocks = Array.from(htmlHolder.querySelectorAll('.dumby-block'))
144 const showcase = document.createElement('div') 177 const showcase = document.createElement('div')
145 container.appendChild(showcase) 178 container.appendChild(showcase)
146 showcase.classList.add('Showcase') 179 showcase.classList.add('Showcase')
180 const renderedMaps = []
147 181
148 const dumbymap = { 182 const dumbymap = {
149 container, 183 container,
150 htmlHolder, 184 htmlHolder,
151 showcase, 185 showcase,
152 blocks, 186 blocks,
187 renderedMaps,
188 }
189 dumbymap.utils = {
190 focusNextMap: throttle(focusNextMap.bind(dumbymap), focusDelay.bind(dumbymap)),
191 switchToNextLayout: throttle(switchToNextLayout.bind(dumbymap), 300),
153 } 192 }
154 193
155 // LeaderLine {{{ 194 // LeaderLine {{{
@@ -249,7 +288,8 @@ export const generateMaps = async (container, callback) => {
249 288
250 // Placeholder for map in Showcase, it should has the same DOMRect 289 // Placeholder for map in Showcase, it should has the same DOMRect
251 const placeholder = target.cloneNode(true) 290 const placeholder = target.cloneNode(true)
252 placeholder.classList.remove('map-container') 291 placeholder.removeAttribute('id')
292 placeholder.classList.remove('map-container', 'data-focus')
253 target.parentElement.replaceChild(placeholder, target) 293 target.parentElement.replaceChild(placeholder, target)
254 294
255 // HACK Trigger CSS transition, if placeholde is the olny chil element in block, 295 // HACK Trigger CSS transition, if placeholde is the olny chil element in block,
@@ -292,58 +332,6 @@ export const generateMaps = async (container, callback) => {
292 const defaultLayout = layouts[0] 332 const defaultLayout = layouts[0]
293 container.setAttribute("data-layout", defaultLayout.name) 333 container.setAttribute("data-layout", defaultLayout.name)
294 334
295 const switchToNextLayout = throttle(() => {
296 const currentLayoutName = container.getAttribute('data-layout')
297 const currentIndex = layouts.map(l => l.name).indexOf(currentLayoutName)
298 const nextIndex = currentIndex === -1 ? 0 : (currentIndex + 1) % layouts.length
299 const nextLayout = layouts[nextIndex]
300 container.setAttribute("data-layout", nextLayout.name)
301 }, 300)
302
303 // TODO Use UI to switch layouts
304 const focusNextMap = (reverse = false) => {
305 // Decide how many candidates could be focused
306 const selector = '.map-container, [data-placeholder]'
307 const candidates = Array.from(htmlHolder.querySelectorAll(selector))
308 if (candidates.length <= 1) return
309
310 // Get current focused element
311 const currentFocus = htmlHolder.querySelector('.map-container[data-focus=true]')
312 ?? htmlHolder.querySelector('[data-placeholder]')
313
314 // Remove class name of focus for ALL candidates
315 // This may trigger animation
316 Array.from(container.querySelectorAll('.map-container'))
317 .forEach(ele => ele.removeAttribute('data-focus'));
318
319 // Focus next focus element
320 const nextIndex = currentFocus
321 ? (candidates.indexOf(currentFocus) + (reverse ? -1 : 1)) % candidates.length
322 : 0
323 const nextFocus = candidates.at(nextIndex)
324 nextFocus.setAttribute('data-focus', "true")
325 }
326 const focusDelay = () => getComputedStyle(showcase).display === 'none' ? 50 : 300
327 const focusNextMapWithThrottle = throttle(focusNextMap, focusDelay)
328
329 const originalKeyDown = document.onkeydown
330 document.onkeydown = (e) => {
331 const event = originalKeyDown(e)
332 if (!event) return
333
334 // Switch to next layout
335 if (event.key === 'x' && container.querySelector('.map-container')) {
336 e.preventDefault()
337 switchToNextLayout()
338 }
339
340 // Use Tab to change focus map
341 if (event.key === 'Tab') {
342 e.preventDefault()
343 focusNextMapWithThrottle(event.shiftKey)
344 }
345 }
346
347 // observe layout change 335 // observe layout change
348 const layoutObserver = new MutationObserver((mutations) => { 336 const layoutObserver = new MutationObserver((mutations) => {
349 const mutation = mutations.at(-1) 337 const mutation = mutations.at(-1)
@@ -358,7 +346,9 @@ export const generateMaps = async (container, callback) => {
358 } 346 }
359 347
360 Object.values(dumbymap) 348 Object.values(dumbymap)
361 .flat() 349 .filter(e => e instanceof HTMLElement)
350 .forEach(e => e.removeAttribute('style'))
351 dumbymap.blocks
362 .forEach(e => e.removeAttribute('style')) 352 .forEach(e => e.removeAttribute('style'))
363 353
364 if (newLayout) { 354 if (newLayout) {
@@ -386,6 +376,8 @@ export const generateMaps = async (container, callback) => {
386 // Render Maps {{{ 376 // Render Maps {{{
387 377
388 const afterEachMapLoaded = (mapContainer) => { 378 const afterEachMapLoaded = (mapContainer) => {
379 renderedMaps.push(mapContainer)
380 renderedMaps.sort((a, b) => mapIdList.indexOf(a.id) - mapIdList.indexOf(b.id))
389 mapContainer.setAttribute('tabindex', "-1") 381 mapContainer.setAttribute('tabindex', "-1")
390 382
391 const observer = mapFocusObserver() 383 const observer = mapFocusObserver()
@@ -454,12 +446,11 @@ export const generateMaps = async (container, callback) => {
454 }) 446 })
455 }) 447 })
456 448
457 const renderAllTargets = Promise.all(renderTargets) 449 Promise.all(renderTargets)
458 .then(() => { 450 .then(() => {
459 console.info('Finish Rendering') 451 console.info('Finish Rendering')
460 452
461 const maps = htmlHolder.querySelectorAll('.map-container') ?? [] 453 const maps = htmlHolder.querySelectorAll('.map-container') ?? []
462 focusNextMap()
463 Array.from(maps) 454 Array.from(maps)
464 .forEach(ele => { 455 .forEach(ele => {
465 callback(ele) 456 callback(ele)
@@ -479,5 +470,5 @@ export const generateMaps = async (container, callback) => {
479 }) 470 })
480 471
481 //}}} 472 //}}}
482 return renderAllTargets 473 return dumbymap
483} 474}