Files: 54f45b9e3a2114fd3c234a8de046349c9a3dab1c / src / components / PatchBoot.js
11732 bytesRaw
1 | |
2 | import './AppSelector.js' |
3 | import './AppRunner.js' |
4 | import './SourceViewer.js' |
5 | import { default as pull } from 'pull-stream' |
6 | |
7 | class PatchBoot extends HTMLElement { |
8 | constructor() { |
9 | super(); |
10 | } |
11 | connectedCallback() { |
12 | const componentArea = this.attachShadow({ mode: 'open' }) |
13 | |
14 | componentArea.innerHTML = ` |
15 | <div id="component-root"> |
16 | <style> |
17 | * { |
18 | box-sizing: border-box; |
19 | overflow-wrap: anywhere; |
20 | } |
21 | |
22 | #component-root { |
23 | background-color: #ffffff; |
24 | font-family: Inter, 'Helvetica Neue', Arial, Helvetica, sans-serif; |
25 | --lineColor1: #79cfd9; |
26 | --lineColor2: #b0bec5; |
27 | --topBarHeight: 45px; |
28 | } |
29 | |
30 | |
31 | .flex { |
32 | display: flex; |
33 | width: 100vw; |
34 | } |
35 | |
36 | #sidebar { |
37 | flex-shrink: 0; |
38 | width: 248px; |
39 | border-right: 1px solid var(--lineColor1); |
40 | height: 100vh; |
41 | display: flex; |
42 | flex-direction: column; |
43 | overflow: hidden; |
44 | transition: width 0.3s ease-in-out, margin-left 0.3s ease-in-out; |
45 | background: #ffffff; |
46 | } |
47 | |
48 | #sidebar.gone { |
49 | width: 0px; |
50 | margin-left: -1px; |
51 | } |
52 | |
53 | #sidebar-inner { |
54 | width: 247px; |
55 | height: 100vh; |
56 | display: flex; |
57 | flex-direction: column; |
58 | overflow: hidden; |
59 | } |
60 | |
61 | #close-apps, |
62 | #close-apps-backdrop { |
63 | display: none; |
64 | } |
65 | |
66 | @media screen and (max-width: 500px) { |
67 | #close-apps { |
68 | display: block; |
69 | } |
70 | |
71 | #close-apps-backdrop { |
72 | display: block; |
73 | content: ""; |
74 | position: absolute; |
75 | background: rgba(0, 0, 0, 0.2); |
76 | transition: width 0.3s ease-in-out; |
77 | top: 0; |
78 | bottom: 0; |
79 | right: 0; |
80 | left: 0; |
81 | } |
82 | |
83 | #sidebar { |
84 | position: absolute; |
85 | top: 0; |
86 | right: 16px; |
87 | bottom: 0; |
88 | left: 0; |
89 | width: unset; |
90 | border: none; |
91 | transition: right 0.3s ease-in-out; |
92 | box-shadow: -5px 0 10px 0 black; |
93 | z-index: 100; |
94 | } |
95 | |
96 | #sidebar-inner { |
97 | width: calc(100vw - 16px); |
98 | } |
99 | |
100 | #sidebar.gone, |
101 | #close-apps-backdrop.gone { |
102 | width: unset; |
103 | margin: 0; |
104 | right: 100vw; |
105 | } |
106 | } |
107 | |
108 | #connecting { |
109 | padding: 0 0.5rem; |
110 | animation: 1s infinite alternate ease-in-out loading-color; |
111 | } |
112 | |
113 | @keyframes loading-color { |
114 | from { |
115 | color: black; |
116 | } |
117 | to { |
118 | color: var(--lineColor1); |
119 | } |
120 | } |
121 | |
122 | #connecting p { |
123 | margin: 0.5rem 0; |
124 | } |
125 | |
126 | #connecting .muted { |
127 | color: rgba(0,0,0,0); |
128 | } |
129 | |
130 | .waited #connecting .muted { |
131 | color: rgba(0, 0, 0, 0.4); |
132 | } |
133 | |
134 | .muted { |
135 | color: rgba(0, 0, 0, 0.4); |
136 | } |
137 | |
138 | .bar { |
139 | border-bottom: 1px solid var(--lineColor1); |
140 | border-radius: 0; |
141 | padding: 0.5rem; |
142 | background: #e0f7fa; |
143 | background: #79cfd9; |
144 | display: flex; |
145 | justify-content: space-between; |
146 | height: var(--topBarHeight); |
147 | line-height: 28px; |
148 | } |
149 | |
150 | .bar h1 { |
151 | display: block; |
152 | font-size: 1rem; |
153 | margin: 0; |
154 | padding: 0; |
155 | } |
156 | |
157 | #title-ext { |
158 | font-weight: 500; |
159 | } |
160 | |
161 | .icons { |
162 | display: flex; |
163 | } |
164 | |
165 | .icons button { |
166 | margin: 0 2px; |
167 | padding: 6px; |
168 | border: none; |
169 | border-radius: 50%; |
170 | height: 28px; |
171 | background-color: rgba(0,0,0,0.027450980392156863); |
172 | } |
173 | |
174 | .icons button:hover { |
175 | background-color: rgba(0,0,0,0.13333333333333334); |
176 | } |
177 | |
178 | .icons button svg { |
179 | display: block; |
180 | height: 16px; |
181 | width: 16px; |
182 | } |
183 | |
184 | .svghover .onhover { |
185 | display: none; |
186 | } |
187 | |
188 | .svghover:hover path { |
189 | display: none; |
190 | } |
191 | |
192 | .svghover:hover .onhover { |
193 | display: unset; |
194 | } |
195 | |
196 | #info { |
197 | transition: all 0.3s ease-in-out; |
198 | max-height: 90vh; |
199 | overflow-y: auto; |
200 | padding: 0.5rem; |
201 | } |
202 | |
203 | #status { |
204 | max-height: 90vh; |
205 | padding: 0.5rem; |
206 | background: #def3f6; |
207 | transform: all 0.3s ease-in-out; |
208 | } |
209 | |
210 | .hidden { |
211 | display: none; |
212 | } |
213 | |
214 | #info.hidden |
215 | #status.hidden { |
216 | max-height: 0 !important; |
217 | border: none; |
218 | padding: 0; |
219 | margin: 0; |
220 | opacity: 0; |
221 | overflow: hidden; |
222 | } |
223 | |
224 | #outer { |
225 | position: absolute; |
226 | right: 0; |
227 | left: 0; |
228 | bottom: 0; |
229 | top: 0; |
230 | padding: 2rem; |
231 | background: rgba(0, 0, 0, 0.2); |
232 | height: 100vh; |
233 | width: 100vw; |
234 | max-height: 100vh; |
235 | max-width: 100vw; |
236 | } |
237 | |
238 | #inner { |
239 | background: white; |
240 | display: flex; |
241 | flex-direction: column; |
242 | justify-content: space-between; |
243 | opacity: 1; |
244 | height: 100%; |
245 | width: 100%; |
246 | max-height: 100%; |
247 | max-width: 100%; |
248 | box-shadow: 4px 4px 12px -8px black; |
249 | } |
250 | |
251 | #inner * { |
252 | margin: 0.2rem; |
253 | } |
254 | |
255 | #inner .main { |
256 | overflow: auto; |
257 | background: lightgray; |
258 | max-width: 100%; |
259 | flex: 1; |
260 | } |
261 | |
262 | .modal-open { |
263 | overflow: hidden; |
264 | max-height: 100vh; |
265 | max-width: 100vw; |
266 | } |
267 | |
268 | app-selector { |
269 | display: flex; |
270 | flex-direction: column; |
271 | flex: 0 auto; |
272 | max-height: calc(100vh - 45px); |
273 | } |
274 | |
275 | .main { |
276 | flex-grow: 1; |
277 | display: flex; |
278 | flex-direction: column; |
279 | height: 100vh; |
280 | min-width: 45px; |
281 | overflow: hidden; |
282 | } |
283 | |
284 | #view { |
285 | height: 100%; |
286 | overflow: hidden; |
287 | } |
288 | |
289 | #view:empty { |
290 | height: 0; |
291 | } |
292 | |
293 | app-runner { |
294 | width: 100%; |
295 | height: 100%; |
296 | display: block; |
297 | } |
298 | |
299 | </style> |
300 | <div class="flex"> |
301 | <div id="sidebar"> |
302 | <div id="sidebar-inner"> |
303 | <header class="bar"> |
304 | <h1>PatchBoot</h1> |
305 | <div class="icons"> |
306 | <button id="close-apps"> |
307 | <svg width="24" viewBox="0 0 24 24"> |
308 | <path fill="currentColor" d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z" /> |
309 | </svg> |
310 | </button> |
311 | </div> |
312 | </header> |
313 | </div> |
314 | </div> |
315 | <div class="main"> |
316 | <header class="bar"> |
317 | <div class="icons"> |
318 | <button id="toggle-apps"> |
319 | <svg width="24" viewBox="0 0 24 24"> |
320 | <path fill="currentColor" |
321 | d="M16,20H20V16H16M16,14H20V10H16M10,8H14V4H10M16,8H20V4H16M10,14H14V10H10M4,14H8V10H4M4,20H8V16H4M10,20H14V16H10M4,8H8V4H4V8Z" /> |
322 | </svg> |
323 | </button> |
324 | </div> |
325 | <h1 id="title-ext"></h1> |
326 | <div></div> |
327 | </header> |
328 | <div id="connecting"> |
329 | <p>Connecting to SSB</p> |
330 | <p class="muted"><small>If nothing happens, please make sure you have an SSB server running and the plugin |
331 | intstalled.</small></p> |
332 | </div> |
333 | <div id="info" class="hidden"> |
334 | <h2>No App is Running yet</h2> |
335 | <p> |
336 | Only execute apps you trust, |
337 | as they’ll have full access to your SSB account. |
338 | </p> |
339 | </div> |
340 | <div id="status" class="hidden"></div> |
341 | <div id="view"></div> |
342 | </div> |
343 | </div> |
344 | <div id="close-apps-backdrop"></div> |
345 | </div> |
346 | ` |
347 | const componentRoot = componentArea.getElementById('component-root') |
348 | const sidebar = componentArea.getElementById('sidebar') |
349 | const sidebarToggle = componentArea.getElementById('toggle-apps') |
350 | const sidebarClose = componentArea.getElementById('close-apps') |
351 | const sidebarCloseBackdrop = componentArea.getElementById('close-apps-backdrop') |
352 | |
353 | const closeSidebar = () => { |
354 | console.log('closing') |
355 | sidebar.classList.add('gone') |
356 | sidebarCloseBackdrop.classList.add('gone') |
357 | sidebarCloseBackdrop.removeEventListener('click', closeSidebar) |
358 | } |
359 | |
360 | const openSidebar = () => { |
361 | console.log('opening') |
362 | sidebar.classList.remove('gone') |
363 | sidebarCloseBackdrop.classList.remove('gone') |
364 | sidebarCloseBackdrop.addEventListener('click', closeSidebar) |
365 | } |
366 | |
367 | sidebarToggle.addEventListener('click', e => { |
368 | console.log('toggling', sidebar.classList, sidebar.classList.contains('gone')) |
369 | if (sidebar.classList.contains('gone')) openSidebar() |
370 | else closeSidebar() |
371 | }) |
372 | sidebarClose.addEventListener('click', closeSidebar) |
373 | sidebarCloseBackdrop.addEventListener('click', closeSidebar) |
374 | |
375 | setTimeout(() => { |
376 | componentRoot.classList.add('waited') |
377 | }, 1000) |
378 | |
379 | const selectionArea = componentArea.getElementById('sidebar-inner') |
380 | this.ssbConnect().then(sbot => { |
381 | |
382 | if (componentArea.getElementById('connecting')) componentArea.getElementById('connecting').classList.add('hidden') |
383 | if (componentArea.getElementById('info')) componentArea.getElementById('info').classList.remove('hidden') |
384 | |
385 | const selector = document.createElement('app-selector') |
386 | selector.sbot = sbot |
387 | selector.addEventListener('run', run) |
388 | selector.addEventListener('show-source', showSource) |
389 | selectionArea.appendChild(selector) |
390 | |
391 | const statusBar = componentArea.getElementById('status') |
392 | |
393 | const view = componentArea.getElementById('view') |
394 | //const shadowView = view.attachShadow({ mode: 'closed' }); |
395 | //const shadowHtml = componentArea.createElement('html') |
396 | //shadowView.appendChild(shadowHtml) |
397 | |
398 | function run(event) { |
399 | const app = event.detail |
400 | componentArea.getElementById('info').classList.add('hidden') |
401 | componentArea.getElementById('title-ext').innerHTML = app.name |
402 | statusBar.classList.remove('hidden') |
403 | statusBar.innerText = 'Loading ' + app.name |
404 | view.innerHTML = '' |
405 | const appRunner = document.createElement('app-runner') |
406 | appRunner.sbot = sbot |
407 | appRunner.app = app |
408 | view.appendChild(appRunner) |
409 | |
410 | appRunner.addEventListener('loaded', e => { |
411 | statusBar.classList.add('hidden') |
412 | }) |
413 | } |
414 | |
415 | function showSource(event) { |
416 | const app = event.detail |
417 | console.log('showSource', app) |
418 | const outer = document.createElement('div') |
419 | outer.id = 'outer' |
420 | const oldTop = window.scrollY |
421 | const oldLeft = window.scrollX |
422 | window.scroll(0, 0) |
423 | componentRoot.classList.add('modal-open') |
424 | componentArea.appendChild(outer) |
425 | //const inner = document.createElement('div') |
426 | const sourceViewer = document.createElement('source-viewer') |
427 | sourceViewer.id = 'inner' |
428 | sourceViewer.app = app |
429 | sourceViewer.sbot = sbot |
430 | sourceViewer.name = app.name || app.comment || '' |
431 | outer.appendChild(sourceViewer) |
432 | const close = () => { |
433 | componentArea.removeChild(outer) |
434 | componentRoot.classList.remove('modal-open') |
435 | window.scroll(oldLeft, oldTop) |
436 | } |
437 | outer.addEventListener('click', close) |
438 | sourceViewer.addEventListener('close', close) |
439 | sourceViewer.addEventListener('click', e => e.stopPropagation()) |
440 | } |
441 | }, |
442 | error => { |
443 | console.log('An error occured', error) |
444 | }) |
445 | |
446 | } |
447 | |
448 | |
449 | } |
450 | |
451 | customElements.define('patch-boot', PatchBoot) |
Built with git-ssb-web