diff options
Diffstat (limited to 'src/MenuItem.mjs')
-rw-r--r-- | src/MenuItem.mjs | 248 |
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 @@ | |||
1 | class Item extends HTMLDivElement { | 1 | class 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 | } |
16 | window.customElements.define('menu-item', Item, { extends: 'div' }); | 16 | window.customElements.define('menu-item', Item, { extends: 'div' }) |
17 | 17 | ||
18 | class Folder extends HTMLDivElement { | 18 | class 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 | } |
41 | window.customElements.define('menu-folder', Folder, { extends: 'div' }); | 41 | window.customElements.define('menu-folder', Folder, { extends: 'div' }) |
42 | 42 | ||
43 | export const pickMapItem = ({ utils }) => | 43 | export 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 | ||
58 | export const pickBlockItem = ({ blocks, utils }) => | 58 | export 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 | ||
78 | export const pickLayoutItem = ({ container, layouts }) => | 78 | export 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 | ||
101 | export const addGeoLink = ({ utils }, range) => | 101 | export 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 | ||
124 | export class Suggestion { | 124 | export 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 | ||
210 | function printObject(obj, parentElement, name = null) { | 210 | function 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 | ||
250 | export const toggleBlockFocus = block => | 250 | export 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 | ||
256 | export const toggleMapFocus = map => | 256 | export 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 | }) |