aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--addon/index.mjs5
-rw-r--r--src/Layout.mjs179
-rw-r--r--src/css/dumbymap.css56
-rw-r--r--src/dumbymap.mjs15
-rw-r--r--src/editor.mjs2
5 files changed, 155 insertions, 102 deletions
diff --git a/addon/index.mjs b/addon/index.mjs
index b5d71ba..77716b6 100644
--- a/addon/index.mjs
+++ b/addon/index.mjs
@@ -1,4 +1,5 @@
1const url = new URL(window.location) 1const url = new URL(window.location)
2const use = url.searchParams.get('use')
2if (url.host === 'www.ptt.cc') { 3if (url.host === 'www.ptt.cc') {
3 const content = document.querySelector('#main-content') 4 const content = document.querySelector('#main-content')
4 Array.from(content.childNodes) 5 Array.from(content.childNodes)
@@ -22,7 +23,7 @@ const addBlocks = blockSelector
22 : undefined 23 : undefined
23 24
24const simpleRender = window.mapclay.renderWith(config => ({ 25const simpleRender = window.mapclay.renderWith(config => ({
25 use: 'Leaflet', 26 use: use ?? 'Leaflet',
26 width: '100%', 27 width: '100%',
27 height: '200px', 28 height: '200px',
28 XYZ: 'https://tile.openstreetmap.jp/styles/osm-bright/512/{z}/{x}/{y}.png', 29 XYZ: 'https://tile.openstreetmap.jp/styles/osm-bright/512/{z}/{x}/{y}.png',
@@ -36,7 +37,7 @@ const simpleRender = window.mapclay.renderWith(config => ({
36window.generateMaps(document.querySelector('main') ?? document.body, { 37window.generateMaps(document.querySelector('main') ?? document.body, {
37 crs: url.searchParams.get('crs') ?? 'EPSG:4326', 38 crs: url.searchParams.get('crs') ?? 'EPSG:4326',
38 addBlocks, 39 addBlocks,
39 initialLayout: '', 40 initialLayout: 'sticky',
40 render: simpleRender, 41 render: simpleRender,
41 autoMap: true, 42 autoMap: true,
42}) 43})
diff --git a/src/Layout.mjs b/src/Layout.mjs
index 485b513..f79c70e 100644
--- a/src/Layout.mjs
+++ b/src/Layout.mjs
@@ -86,6 +86,83 @@ export class SideBySide extends Layout {
86} 86}
87 87
88/** 88/**
89 * addDraggable.
90 *
91 * @param {HTMLElement} element
92 */
93const addDraggable = (element, { snap, left, top } = {}) => {
94 element.classList.add('draggable-block')
95
96 // Make sure current element always on top
97 const siblings = Array.from(
98 element.parentElement?.querySelectorAll(':scope > *') ?? [],
99 )
100 let popTimer = null
101 const onmouseover = () => {
102 popTimer = setTimeout(() => {
103 siblings.forEach(e => e.style.removeProperty('z-index'))
104 element.style.zIndex = '9001'
105 }, 200)
106 }
107 const onmouseout = () => {
108 clearTimeout(popTimer)
109 }
110 element.addEventListener('mouseover', onmouseover)
111 element.addEventListener('mouseout', onmouseout)
112
113 // Add draggable part
114 const draggablePart = document.createElement('div')
115 element.appendChild(draggablePart)
116 draggablePart.className = 'draggable-part'
117 draggablePart.innerHTML = '<div class="handle">\u2630</div>'
118
119 // Add draggable instance
120 const draggable = new PlainDraggable(element, {
121 left,
122 top,
123 handle: draggablePart,
124 snap,
125 })
126
127 // FIXME use pure CSS to hide utils
128 const utils = element.querySelector('.utils')
129 draggable.onDragStart = () => {
130 if (utils) utils.style.display = 'none'
131 element.classList.add('drag')
132 }
133
134 draggable.onDragEnd = () => {
135 if (utils) utils.style = ''
136 element.classList.remove('drag')
137 element.style.zIndex = '9000'
138 }
139
140 // Reposition draggable instance when resized
141 const resizeObserver = new window.ResizeObserver(() => {
142 draggable?.position()
143 })
144 resizeObserver.observe(element)
145
146 // Callback for remove
147 onRemove(element, () => {
148 resizeObserver.disconnect()
149 })
150
151 new window.MutationObserver(() => {
152 if (!element.classList.contains('draggable-block') && draggable) {
153 element.removeEventListener('mouseover', onmouseover)
154 element.removeEventListener('mouseout', onmouseout)
155 resizeObserver.disconnect()
156 }
157 }).observe(element, {
158 attributes: true,
159 attributeFilter: ['class'],
160 })
161
162 return draggable
163}
164
165/**
89 * Overlay Layout, Showcase occupies viewport, and HTML content becomes draggable blocks 166 * Overlay Layout, Showcase occupies viewport, and HTML content becomes draggable blocks
90 * 167 *
91 * @extends {Layout} 168 * @extends {Layout}
@@ -103,70 +180,6 @@ export class Overlay extends Layout {
103 } 180 }
104 181
105 /** 182 /**
106 * addDraggable.
107 *
108 * @param {HTMLElement} element
109 */
110 addDraggable = element => {
111 // Make sure current element always on top
112 const siblings = Array.from(
113 element.parentElement?.querySelectorAll(':scope > *') ?? [],
114 )
115 let popTimer = null
116 element.onmouseover = () => {
117 popTimer = setTimeout(() => {
118 siblings.forEach(e => e.style.removeProperty('z-index'))
119 element.style.zIndex = '9001'
120 }, 200)
121 }
122 element.onmouseout = () => {
123 clearTimeout(popTimer)
124 }
125
126 // Add draggable part
127 const draggablePart = document.createElement('div')
128 element.appendChild(draggablePart)
129 draggablePart.className = 'draggable-part'
130 draggablePart.innerHTML = '<div class="handle">\u2630</div>'
131
132 // Add draggable instance
133 const { left, top } = element.getBoundingClientRect()
134 const draggable = new PlainDraggable(element, {
135 top,
136 left,
137 handle: draggablePart,
138 snap: { x: { step: 20 }, y: { step: 20 } },
139 })
140
141 // FIXME use pure CSS to hide utils
142 const utils = element.querySelector('.utils')
143 draggable.onDragStart = () => {
144 utils.style.display = 'none'
145 element.classList.add('drag')
146 }
147
148 draggable.onDragEnd = () => {
149 utils.style = ''
150 element.classList.remove('drag')
151 element.style.zIndex = '9000'
152 }
153
154 // Reposition draggable instance when resized
155 new window.ResizeObserver(() => {
156 try {
157 draggable.position()
158 } catch (err) {
159 console.warn(err)
160 }
161 }).observe(element)
162
163 // Callback for remove
164 onRemove(element, () => {
165 draggable.remove()
166 })
167 }
168
169 /**
170 * enterHandler. 183 * enterHandler.
171 * 184 *
172 * @param {HTMLElement} options.hemlHolder - Parent element for block 185 * @param {HTMLElement} options.hemlHolder - Parent element for block
@@ -182,7 +195,7 @@ export class Overlay extends Layout {
182 } 195 }
183 196
184 // Create draggable blocks and set each position by previous one 197 // Create draggable blocks and set each position by previous one
185 let [left, top] = [20, 20] 198 const [left, top] = [20, 20]
186 blocks.forEach(block => { 199 blocks.forEach(block => {
187 const originLeft = Number(block.dataset.left) 200 const originLeft = Number(block.dataset.left)
188 const originTop = Number(block.dataset.top) 201 const originTop = Number(block.dataset.top)
@@ -210,8 +223,8 @@ export class Overlay extends Layout {
210 wrapper.style.left = left + 'px' 223 wrapper.style.left = left + 'px'
211 wrapper.style.top = top + 'px' 224 wrapper.style.top = top + 'px'
212 htmlHolder.appendChild(wrapper) 225 htmlHolder.appendChild(wrapper)
213 const { width } = wrapper.getBoundingClientRect() 226 const rect = wrapper.getBoundingClientRect()
214 left += width + 30 227 left += rect.width + 30
215 if (left > window.innerWidth) { 228 if (left > window.innerWidth) {
216 top += 200 229 top += 200
217 left = left % window.innerWidth 230 left = left % window.innerWidth
@@ -222,7 +235,14 @@ export class Overlay extends Layout {
222 wrapper, 235 wrapper,
223 { left: originLeft, top: originTop }, 236 { left: originLeft, top: originTop },
224 { resume: true, duration: 300 }, 237 { resume: true, duration: 300 },
225 ).finished.finally(() => this.addDraggable(wrapper)) 238 ).finished.finally(() => addDraggable(wrapper, {
239 left: rect.left,
240 top: rect.top,
241 snap: {
242 x: { step: 20 },
243 y: { step: 20 },
244 },
245 }))
226 246
227 // Trivial case: 247 // Trivial case:
228 // This hack make sure utils remains at the same place even when wrapper resized 248 // This hack make sure utils remains at the same place even when wrapper resized
@@ -271,3 +291,26 @@ export class Overlay extends Layout {
271 blocks.forEach(resumeFromDraggable) 291 blocks.forEach(resumeFromDraggable)
272 } 292 }
273} 293}
294
295/**
296 * Sticky Layout, Showcase is draggable and stick to viewport
297 *
298 * @extends {Layout}
299 */
300export class Sticky extends Layout {
301 draggable = document.createElement('div')
302
303 enterHandler = ({ showcase }) => {
304 showcase.replaceWith(this.draggable)
305 this.draggable.appendChild(showcase)
306 this.draggableInstance = addDraggable(this.draggable)
307 const rect = this.draggable.getBoundingClientRect()
308 this.draggable.style.cssText = `left: ${window.innerWidth - rect.width - 20}px; top: ${window.innerHeight - rect.height - 20}px;`
309 }
310
311 leaveHandler = ({ showcase }) => {
312 this.draggableInstance?.remove()
313 this.draggable.replaceWith(showcase)
314 this.draggable.querySelectorAll(':scope > :not(.mapclay)').forEach(e => e.remove())
315 }
316}
diff --git a/src/css/dumbymap.css b/src/css/dumbymap.css
index c0bb1a9..cc161fa 100644
--- a/src/css/dumbymap.css
+++ b/src/css/dumbymap.css
@@ -141,7 +141,7 @@ root {
141 background-repeat: no-repeat; 141 background-repeat: no-repeat;
142 background-position: right 2px top 2px; 142 background-position: right 2px top 2px;
143 143
144 color: #555; 144 color: #555 !important;
145 background-size: 12px 12px; 145 background-size: 12px 12px;
146 146
147 &.geolink { 147 &.geolink {
@@ -166,7 +166,7 @@ root {
166 } 166 }
167} 167}
168 168
169.Dumby:not([data-layout=""]) .dumby-block { 169.DumbyMap.Dumby[data-layout] .dumby-block {
170 padding: 1rem 1rem 1rem 2rem; 170 padding: 1rem 1rem 1rem 2rem;
171 171
172 position: relative; 172 position: relative;
@@ -495,7 +495,7 @@ root {
495 } 495 }
496} 496}
497 497
498.Dumby[data-layout='normal'] { 498.DumbyMap.Dumby[data-layout='normal'] {
499 max-width: 60em; 499 max-width: 60em;
500 500
501 &::after { 501 &::after {
@@ -595,6 +595,35 @@ root {
595 } 595 }
596} 596}
597 597
598.Dumby[data-layout='sticky'] {
599 .SemanticHtml {
600 max-width: 60em;
601 margin: 0 auto;
602 }
603
604 .draggable-block {
605 position: fixed;
606
607 &::before {
608 display: none;
609 }
610 }
611
612 .Showcase {
613 display: block;
614 overflow: hidden;
615 width: 20vw;
616 min-width: 10vw;
617 height: 200px;
618 min-height: 100px;
619 resize: both;
620
621 .mapclay {
622 border-radius: 4px !important;
623 }
624 }
625}
626
598.utils { 627.utils {
599 display: flex; 628 display: flex;
600 padding-block: 1rem; 629 padding-block: 1rem;
@@ -642,6 +671,7 @@ root {
642.draggable-block { 671.draggable-block {
643 overflow: visible; 672 overflow: visible;
644 width: fit-content; 673 width: fit-content;
674 height: fit-content;
645 675
646 position: absolute; 676 position: absolute;
647 677
@@ -659,7 +689,7 @@ root {
659 opacity: 0; 689 opacity: 0;
660 pointer-events: auto; 690 pointer-events: auto;
661 691
662 &:has(.dumby-block.focus) { 692 &:has(.dumby-block.focus, .mapclay.focus) {
663 visibility: visible; 693 visibility: visible;
664 opacity: 1; 694 opacity: 1;
665 } 695 }
@@ -730,7 +760,7 @@ root {
730 position: absolute; 760 position: absolute;
731 left: 0; 761 left: 0;
732 top: 0; 762 top: 0;
733 z-index: 1; 763 z-index: 2;
734 border-top-left-radius: 0.3rem; 764 border-top-left-radius: 0.3rem;
735 border-top-right-radius: 0.3rem; 765 border-top-right-radius: 0.3rem;
736 } 766 }
@@ -782,22 +812,6 @@ root {
782 font-weight: bold; 812 font-weight: bold;
783} 813}
784 814
785.Dumby[data-layout='sticky'] {
786 max-width: 80em;
787
788 .Showcase {
789 display: block;
790 width: 20vw;
791 height: 40vh;
792
793 position: absolute;
794 right: 20px;
795 bottom: 20px;
796
797 background: red;
798 }
799}
800
801.dumby-block details { 815.dumby-block details {
802 display: block; 816 display: block;
803 width: fit-content; 817 width: fit-content;
diff --git a/src/dumbymap.mjs b/src/dumbymap.mjs
index b612367..13f4574 100644
--- a/src/dumbymap.mjs
+++ b/src/dumbymap.mjs
@@ -5,7 +5,7 @@ import MarkdownItFrontMatter from 'markdown-it-front-matter'
5import MarkdownItInjectLinenumbers from 'markdown-it-inject-linenumbers' 5import MarkdownItInjectLinenumbers from 'markdown-it-inject-linenumbers'
6import * as mapclay from 'mapclay' 6import * as mapclay from 'mapclay'
7import { replaceTextNodes, onRemove, animateRectTransition, throttle, shiftByWindow } from './utils' 7import { replaceTextNodes, onRemove, animateRectTransition, throttle, shiftByWindow } from './utils'
8import { Layout, SideBySide, Overlay } from './Layout' 8import { Layout, SideBySide, Overlay, Sticky } from './Layout'
9import * as utils from './dumbyUtils' 9import * as utils from './dumbyUtils'
10import * as menuItem from './MenuItem' 10import * as menuItem from './MenuItem'
11import PlainModal from 'plain-modal' 11import PlainModal from 'plain-modal'
@@ -23,6 +23,7 @@ const defaultLayouts = [
23 new Layout({ name: 'normal' }), 23 new Layout({ name: 'normal' }),
24 new SideBySide({ name: 'side-by-side' }), 24 new SideBySide({ name: 'side-by-side' }),
25 new Overlay({ name: 'overlay' }), 25 new Overlay({ name: 'overlay' }),
26 new Sticky({ name: 'sticky' }),
26] 27]
27 28
28/** Cache across every dumbymap generation */ 29/** Cache across every dumbymap generation */
@@ -169,13 +170,12 @@ export const generateMaps = (container, {
169 /** Prepare Contaner */ 170 /** Prepare Contaner */
170 container.classList.add('Dumby') 171 container.classList.add('Dumby')
171 delete container.dataset.layout 172 delete container.dataset.layout
172 container.dataset.layout = initialLayout ?? defaultLayouts[0].name
173 173
174 /** Prepare Semantic HTML part and blocks of contents inside */ 174 /** Prepare Semantic HTML part and blocks of contents inside */
175 const htmlHolder = container.querySelector('.SemanticHtml') ?? 175 const htmlHolder = container.querySelector('.SemanticHtml') ??
176 Array.from(container.children).find(e => e.id?.includes('main') || e.className.includes('main')) ?? 176 Array.from(container.children).find(e => e.id?.includes('main') || e.className.includes('main')) ??
177 Array.from(container.children).sort((a, b) => a.textContent.length < b.textContent.length).at(0) 177 Array.from(container.children).sort((a, b) => a.textContent.length < b.textContent.length).at(0)
178 htmlHolder.classList.add('.SemanticHtml') 178 htmlHolder.classList.add('SemanticHtml')
179 179
180 const blocks = addBlocks(htmlHolder) 180 const blocks = addBlocks(htmlHolder)
181 blocks.forEach(b => { 181 blocks.forEach(b => {
@@ -280,13 +280,7 @@ export const generateMaps = (container, {
280 const mutation = mutations.at(-1) 280 const mutation = mutations.at(-1)
281 const target = mutation.target 281 const target = mutation.target
282 const focus = target.classList.contains('focus') 282 const focus = target.classList.contains('focus')
283 const shouldBeInShowcase = 283 const shouldBeInShowcase = focus && showcase.checkVisibility()
284 focus &&
285 showcase.checkVisibility({
286 contentVisibilityAuto: true,
287 opacityProperty: true,
288 visibilityProperty: true,
289 })
290 284
291 if (focus) { 285 if (focus) {
292 dumbymap.utils 286 dumbymap.utils
@@ -393,6 +387,7 @@ export const generateMaps = (container, {
393 }) 387 })
394 388
395 onRemove(htmlHolder, () => layoutObserver.disconnect()) 389 onRemove(htmlHolder, () => layoutObserver.disconnect())
390 container.dataset.layout = initialLayout ?? defaultLayouts[0].name
396 391
397 /** 392 /**
398 * afterMapRendered. callback of each map rendered 393 * afterMapRendered. callback of each map rendered
diff --git a/src/editor.mjs b/src/editor.mjs
index d56ad4b..2854373 100644
--- a/src/editor.mjs
+++ b/src/editor.mjs
@@ -24,7 +24,7 @@ const initialLayout = pageParams.get('layout')
24/** Variables: dumbymap and editor **/ 24/** Variables: dumbymap and editor **/
25const context = document.querySelector('[data-mode]') 25const context = document.querySelector('[data-mode]')
26const textArea = document.querySelector('.editor textarea') 26const textArea = document.querySelector('.editor textarea')
27const dumbyContainer = document.querySelector('.DumbyMap') 27const dumbyContainer = document.querySelector('.Dumby')
28dumbyContainer.dataset.scrollLine = '' 28dumbyContainer.dataset.scrollLine = ''
29/** Watch: DumbyMap */ 29/** Watch: DumbyMap */
30new window.MutationObserver(mutations => { 30new window.MutationObserver(mutations => {