aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/MenuItem.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'src/MenuItem.mjs')
-rw-r--r--src/MenuItem.mjs248
1 files changed, 124 insertions, 124 deletions
diff --git a/src/MenuItem.mjs b/src/MenuItem.mjs
index 2bf54c2..e1cb582 100644
--- a/src/MenuItem.mjs
+++ b/src/MenuItem.mjs
@@ -1,44 +1,44 @@
1class Item extends HTMLDivElement { 1class Item extends window.HTMLDivElement {
2 constructor({ text, innerHTML, onclick, style }) { 2 constructor ({ text, innerHTML, onclick, style }) {
3 super(); 3 super()
4 this.innerHTML = innerHTML ?? text; 4 this.innerHTML = innerHTML ?? text
5 this.onclick = onclick; 5 this.onclick = onclick
6 this.classList.add('menu-item'); 6 this.classList.add('menu-item')
7 this.style.cssText = style; 7 this.style.cssText = style
8 8
9 this.onmouseover = () => { 9 this.onmouseover = () => {
10 this.parentElement 10 this.parentElement
11 .querySelectorAll('.sub-menu') 11 .querySelectorAll('.sub-menu')
12 .forEach(sub => sub.remove()); 12 .forEach(sub => sub.remove())
13 } 13 }
14 } 14 }
15} 15}
16window.customElements.define('menu-item', Item, { extends: 'div' }); 16window.customElements.define('menu-item', Item, { extends: 'div' })
17 17
18class Folder extends HTMLDivElement { 18class Folder extends window.HTMLDivElement {
19 constructor({ text, innerHTML, items }) { 19 constructor ({ text, innerHTML, items }) {
20 super(); 20 super()
21 this.innerHTML = innerHTML ?? text; 21 this.innerHTML = innerHTML ?? text
22 this.classList.add('folder', 'menu-item'); 22 this.classList.add('folder', 'menu-item')
23 this.items = items; 23 this.items = items
24 this.onmouseover = () => { 24 this.onmouseover = () => {
25 if (this.querySelector('.sub-menu')) return; 25 if (this.querySelector('.sub-menu')) return
26 // Prepare submenu 26 // Prepare submenu
27 const submenu = document.createElement('div'); 27 const submenu = document.createElement('div')
28 submenu.className = 'sub-menu'; 28 submenu.className = 'sub-menu'
29 submenu.style.cssText = `position: absolute; left: 105%; top: 0px;`; 29 submenu.style.cssText = 'position: absolute; left: 105%; top: 0px;'
30 this.items.forEach(item => submenu.appendChild(item)); 30 this.items.forEach(item => submenu.appendChild(item))
31 submenu.onmouseleave = () => submenu.remove() 31 submenu.onmouseleave = () => submenu.remove()
32 32
33 // hover effect 33 // hover effect
34 this.parentElement 34 this.parentElement
35 .querySelectorAll('.sub-menu') 35 .querySelectorAll('.sub-menu')
36 .forEach(sub => sub.remove()); 36 .forEach(sub => sub.remove())
37 this.appendChild(submenu); 37 this.appendChild(submenu)
38 }; 38 }
39 } 39 }
40} 40}
41window.customElements.define('menu-folder', Folder, { extends: 'div' }); 41window.customElements.define('menu-folder', Folder, { extends: 'div' })
42 42
43export const pickMapItem = ({ utils }) => 43export const pickMapItem = ({ utils }) =>
44 new Folder({ 44 new Folder({
@@ -48,12 +48,12 @@ export const pickMapItem = ({ utils }) =>
48 new Item({ 48 new Item({
49 text: map.id, 49 text: map.id,
50 onclick: () => { 50 onclick: () => {
51 map.classList.add('focus'); 51 map.classList.add('focus')
52 map.scrollIntoView({ behavior: 'smooth' }); 52 map.scrollIntoView({ behavior: 'smooth' })
53 }, 53 }
54 }), 54 })
55 ), 55 )
56 }); 56 })
57 57
58export const pickBlockItem = ({ blocks, utils }) => 58export const pickBlockItem = ({ blocks, utils }) =>
59 new Folder({ 59 new Folder({
@@ -68,12 +68,12 @@ export const pickBlockItem = ({ blocks, utils }) =>
68 ?.textContent.substring(0, 15) 68 ?.textContent.substring(0, 15)
69 .concat(' ...'), 69 .concat(' ...'),
70 onclick: () => { 70 onclick: () => {
71 block.classList.add('focus'); 71 block.classList.add('focus')
72 utils.scrollToBlock(block); 72 utils.scrollToBlock(block)
73 }, 73 }
74 }), 74 })
75 ), 75 )
76 }); 76 })
77 77
78export const pickLayoutItem = ({ container, layouts }) => 78export const pickLayoutItem = ({ container, layouts }) =>
79 new Folder({ 79 new Folder({
@@ -82,78 +82,78 @@ export const pickLayoutItem = ({ container, layouts }) =>
82 new Item({ 82 new Item({
83 text: 'EDIT', 83 text: 'EDIT',
84 onclick: () => 84 onclick: () =>
85 container.closest('[data-mode]').setAttribute('data-mode', 'editing'), 85 container.closest('[data-mode]').setAttribute('data-mode', 'editing')
86 }), 86 }),
87 ...layouts.map( 87 ...layouts.map(
88 layout => 88 layout =>
89 new Item({ 89 new Item({
90 text: layout.name, 90 text: layout.name,
91 onclick: () => container.setAttribute('data-layout', layout.name), 91 onclick: () => container.setAttribute('data-layout', layout.name)
92 }), 92 })
93 ), 93 ),
94 new Item({ 94 new Item({
95 innerHTML: '<a href="https://github.com/outdoorsafetylab/dumbymap#layouts" class="external" style="display: block; padding: 0.5rem;">More...</a>', 95 innerHTML: '<a href="https://github.com/outdoorsafetylab/dumbymap#layouts" class="external" style="display: block; padding: 0.5rem;">More...</a>',
96 style: 'padding: 0;' 96 style: 'padding: 0;'
97 }), 97 })
98 ], 98 ]
99 }); 99 })
100 100
101export const addGeoLink = ({ utils }, range) => 101export const addGeoLink = ({ utils }, range) =>
102 new Item({ 102 new Item({
103 text: 'Add GeoLink', 103 text: 'Add GeoLink',
104 onclick: () => { 104 onclick: () => {
105 const content = range.toString(); 105 const content = range.toString()
106 // FIXME Apply geolink only on matching sub-range 106 // FIXME Apply geolink only on matching sub-range
107 const match = content.match(/(^\D*[\d.]+)\D+([\d.]+)\D*$/); 107 const match = content.match(/(^\D*[\d.]+)\D+([\d.]+)\D*$/)
108 if (!match) return false; 108 if (!match) return false
109 109
110 const [x, y] = match.slice(1); 110 const [x, y] = match.slice(1)
111 const anchor = document.createElement('a'); 111 const anchor = document.createElement('a')
112 anchor.textContent = content; 112 anchor.textContent = content
113 // FIXME apply WGS84 113 // FIXME apply WGS84
114 anchor.href = `geo:${y},${x}?xy=${x},${y}`; 114 anchor.href = `geo:${y},${x}?xy=${x},${y}`
115 115
116 // FIXME 116 // FIXME
117 if (utils.createGeoLink(anchor)) { 117 if (utils.createGeoLink(anchor)) {
118 range.deleteContents(); 118 range.deleteContents()
119 range.insertNode(anchor); 119 range.insertNode(anchor)
120 } 120 }
121 }, 121 }
122 }); 122 })
123 123
124export class Suggestion { 124export class Suggestion {
125 constructor({ text, replace }) { 125 constructor ({ text, replace }) {
126 this.text = text; 126 this.text = text
127 this.replace = replace; 127 this.replace = replace
128 } 128 }
129 129
130 createElement(codemirror) { 130 createElement (codemirror) {
131 const option = document.createElement('div'); 131 const option = document.createElement('div')
132 if (this.text.startsWith('<')) { 132 if (this.text.startsWith('<')) {
133 option.innerHTML = this.text; 133 option.innerHTML = this.text
134 } else { 134 } else {
135 option.innerText = this.text; 135 option.innerText = this.text
136 } 136 }
137 option.classList.add('container__suggestion'); 137 option.classList.add('container__suggestion')
138 option.onmouseover = () => { 138 option.onmouseover = () => {
139 Array.from(option.parentElement?.children)?.forEach(s => 139 Array.from(option.parentElement?.children)?.forEach(s =>
140 s.classList.remove('focus'), 140 s.classList.remove('focus')
141 ); 141 )
142 option.classList.add('focus'); 142 option.classList.add('focus')
143 }; 143 }
144 option.onmouseout = () => { 144 option.onmouseout = () => {
145 option.classList.remove('focus'); 145 option.classList.remove('focus')
146 }; 146 }
147 option.onclick = () => { 147 option.onclick = () => {
148 const anchor = codemirror.getCursor(); 148 const anchor = codemirror.getCursor()
149 codemirror.setSelection(anchor, { ...anchor, ch: 0 }); 149 codemirror.setSelection(anchor, { ...anchor, ch: 0 })
150 codemirror.replaceSelection(this.replace); 150 codemirror.replaceSelection(this.replace)
151 codemirror.focus(); 151 codemirror.focus()
152 const newAnchor = { ...anchor, ch: this.replace.length }; 152 const newAnchor = { ...anchor, ch: this.replace.length }
153 codemirror.setCursor(newAnchor); 153 codemirror.setCursor(newAnchor)
154 }; 154 }
155 155
156 return option; 156 return option
157 } 157 }
158} 158}
159 159
@@ -161,100 +161,100 @@ export const renderResults = ({ modal, modalContent }, map) =>
161 new Item({ 161 new Item({
162 text: 'Render Results', 162 text: 'Render Results',
163 onclick: () => { 163 onclick: () => {
164 modal.open(); 164 modal.open()
165 modal.overlayBlur = 3; 165 modal.overlayBlur = 3
166 modal.closeByEscKey = false; 166 modal.closeByEscKey = false
167 // HACK find another way to override inline style 167 // HACK find another way to override inline style
168 document.querySelector('.plainmodal-overlay-force').style.position = 168 document.querySelector('.plainmodal-overlay-force').style.position =
169 'relative'; 169 'relative'
170 170
171 modalContent.innerHTML = ''; 171 modalContent.innerHTML = ''
172 const sourceCode = document.createElement('div') 172 const sourceCode = document.createElement('div')
173 sourceCode.innerHTML = `<a href="${map.renderer.url ?? map.renderer.use}">Source Code</a>` 173 sourceCode.innerHTML = `<a href="${map.renderer.url ?? map.renderer.use}">Source Code</a>`
174 modalContent.appendChild(sourceCode) 174 modalContent.appendChild(sourceCode)
175 const printDetails = result => { 175 const printDetails = result => {
176 const funcBody = result.func.toString(); 176 const funcBody = result.func.toString()
177 const loc = funcBody.split('\n').length; 177 const loc = funcBody.split('\n').length
178 const color = 178 const color =
179 { 179 {
180 success: 'green', 180 success: 'green',
181 fail: 'red', 181 fail: 'red',
182 skip: 'black', 182 skip: 'black',
183 stop: 'yellow', 183 stop: 'yellow'
184 }[result.state] ?? 'black'; 184 }[result.state] ?? 'black'
185 printObject( 185 printObject(
186 result, 186 result,
187 modalContent, 187 modalContent,
188 `${result.func.name} <span style='float: right;'>${loc}LOC\x20\x20\x20<span style='display: inline-block; width: 100px; color: ${color};'>${result.state}</span></span>`, 188 `${result.func.name} <span style='float: right;'>${loc}LOC\x20\x20\x20<span style='display: inline-block; width: 100px; color: ${color};'>${result.state}</span></span>`
189 ); 189 )
190 }; 190 }
191 191
192 // Add contents about prepare steps 192 // Add contents about prepare steps
193 const prepareHeading = document.createElement('h3'); 193 const prepareHeading = document.createElement('h3')
194 prepareHeading.textContent = 'Prepare Steps'; 194 prepareHeading.textContent = 'Prepare Steps'
195 modalContent.appendChild(prepareHeading); 195 modalContent.appendChild(prepareHeading)
196 const prepareSteps = map.renderer.results.filter( 196 const prepareSteps = map.renderer.results.filter(
197 r => r.type === 'prepare', 197 r => r.type === 'prepare'
198 ); 198 )
199 prepareSteps.forEach(printDetails); 199 prepareSteps.forEach(printDetails)
200 200
201 // Add contents about render steps 201 // Add contents about render steps
202 const renderHeading = document.createElement('h3'); 202 const renderHeading = document.createElement('h3')
203 renderHeading.textContent = 'Render Steps'; 203 renderHeading.textContent = 'Render Steps'
204 modalContent.appendChild(renderHeading); 204 modalContent.appendChild(renderHeading)
205 const renderSteps = map.renderer.results.filter(r => r.type === 'render'); 205 const renderSteps = map.renderer.results.filter(r => r.type === 'render')
206 renderSteps.forEach(printDetails); 206 renderSteps.forEach(printDetails)
207 }, 207 }
208 }); 208 })
209 209
210function printObject(obj, parentElement, name = null) { 210function printObject (obj, parentElement, name = null) {
211 // Create <details> and <summary> inside 211 // Create <details> and <summary> inside
212 const detailsEle = document.createElement('details'); 212 const detailsEle = document.createElement('details')
213 const details = name ?? (obj instanceof Error ? obj.name : Object.values(obj)[0]); 213 const details = name ?? (obj instanceof Error ? obj.name : Object.values(obj)[0])
214 detailsEle.innerHTML = `<summary>${details}</summary>`; 214 detailsEle.innerHTML = `<summary>${details}</summary>`
215 parentElement.appendChild(detailsEle); 215 parentElement.appendChild(detailsEle)
216 216
217 detailsEle.onclick = () => { 217 detailsEle.onclick = () => {
218 // Don't add items if it has contents 218 // Don't add items if it has contents
219 if (detailsEle.querySelector(':scope > :not(summary)')) return; 219 if (detailsEle.querySelector(':scope > :not(summary)')) return
220 220
221 if (obj instanceof Error) { 221 if (obj instanceof Error) {
222 // Handle Error objects specially 222 // Handle Error objects specially
223 const errorProps = ['name', 'message', 'stack', ...Object.keys(obj)]; 223 const errorProps = ['name', 'message', 'stack', ...Object.keys(obj)]
224 errorProps.forEach(key => { 224 errorProps.forEach(key => {
225 const value = obj[key]; 225 const value = obj[key]
226 const valueString = key === 'stack' ? `<pre>${value}</pre>` : value; 226 const valueString = key === 'stack' ? `<pre>${value}</pre>` : value
227 const propertyElement = document.createElement('p'); 227 const propertyElement = document.createElement('p')
228 propertyElement.innerHTML = `<strong>${key}</strong>: ${valueString}`; 228 propertyElement.innerHTML = `<strong>${key}</strong>: ${valueString}`
229 detailsEle.appendChild(propertyElement); 229 detailsEle.appendChild(propertyElement)
230 }); 230 })
231 } else { 231 } else {
232 // Handle regular objects 232 // Handle regular objects
233 Object.entries(obj).forEach(([key, value]) => { 233 Object.entries(obj).forEach(([key, value]) => {
234 if (typeof value === 'object' && value !== null) { 234 if (typeof value === 'object' && value !== null) {
235 printObject(value, detailsEle, key); 235 printObject(value, detailsEle, key)
236 } else { 236 } else {
237 const valueString = 237 const valueString =
238 typeof value === 'function' 238 typeof value === 'function'
239 ? `<pre>${value}</pre>` 239 ? `<pre>${value}</pre>`
240 : value ?? typeof value; 240 : value ?? typeof value
241 const propertyElement = document.createElement('p'); 241 const propertyElement = document.createElement('p')
242 propertyElement.innerHTML = `<strong>${key}</strong>: ${valueString}`; 242 propertyElement.innerHTML = `<strong>${key}</strong>: ${valueString}`
243 detailsEle.appendChild(propertyElement); 243 detailsEle.appendChild(propertyElement)
244 } 244 }
245 }); 245 })
246 } 246 }
247 }; 247 }
248} 248}
249 249
250export const toggleBlockFocus = block => 250export const toggleBlockFocus = block =>
251 new Item({ 251 new Item({
252 text: 'Toggle Focus', 252 text: 'Toggle Focus',
253 onclick: () => block.classList.toggle('focus'), 253 onclick: () => block.classList.toggle('focus')
254 }); 254 })
255 255
256export const toggleMapFocus = map => 256export const toggleMapFocus = map =>
257 new Item({ 257 new Item({
258 text: 'Toggle Focus', 258 text: 'Toggle Focus',
259 onclick: () => map.classList.toggle('focus'), 259 onclick: () => map.classList.toggle('focus')
260 }); 260 })