patchboot
Version:
A Scuttlebutt bootloader
451 lines (389 loc) • 11.7 kB
JavaScript
import './AppSelector.js'
import './AppRunner.js'
import './SourceViewer.js'
import { default as pull } from 'pull-stream'
class PatchBoot extends HTMLElement {
constructor() {
super();
}
connectedCallback() {
const componentArea = this.attachShadow({ mode: 'open' })
componentArea.innerHTML = `
<div id="component-root">
<style>
* {
box-sizing: border-box;
overflow-wrap: anywhere;
}
#component-root {
background-color: #ffffff;
font-family: Inter, 'Helvetica Neue', Arial, Helvetica, sans-serif;
--lineColor1: #79cfd9;
--lineColor2: #b0bec5;
--topBarHeight: 45px;
}
.flex {
display: flex;
width: 100vw;
}
#sidebar {
flex-shrink: 0;
width: 248px;
border-right: 1px solid var(--lineColor1);
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
transition: width 0.3s ease-in-out, margin-left 0.3s ease-in-out;
background: #ffffff;
}
#sidebar.gone {
width: 0px;
margin-left: -1px;
}
#sidebar-inner {
width: 247px;
height: 100vh;
display: flex;
flex-direction: column;
overflow: hidden;
}
#close-apps,
#close-apps-backdrop {
display: none;
}
@media screen and (max-width: 500px) {
#close-apps {
display: block;
}
#close-apps-backdrop {
display: block;
content: "";
position: absolute;
background: rgba(0, 0, 0, 0.2);
transition: width 0.3s ease-in-out;
top: 0;
bottom: 0;
right: 0;
left: 0;
}
#sidebar {
position: absolute;
top: 0;
right: 16px;
bottom: 0;
left: 0;
width: unset;
border: none;
transition: right 0.3s ease-in-out;
box-shadow: -5px 0 10px 0 black;
z-index: 100;
}
#sidebar-inner {
width: calc(100vw - 16px);
}
#sidebar.gone,
#close-apps-backdrop.gone {
width: unset;
margin: 0;
right: 100vw;
}
}
#connecting {
padding: 0 0.5rem;
animation: 1s infinite alternate ease-in-out loading-color;
}
@keyframes loading-color {
from {
color: black;
}
to {
color: var(--lineColor1);
}
}
#connecting p {
margin: 0.5rem 0;
}
#connecting .muted {
color: rgba(0,0,0,0);
}
.waited #connecting .muted {
color: rgba(0, 0, 0, 0.4);
}
.muted {
color: rgba(0, 0, 0, 0.4);
}
.bar {
border-bottom: 1px solid var(--lineColor1);
border-radius: 0;
padding: 0.5rem;
background: #e0f7fa;
background: #79cfd9;
display: flex;
justify-content: space-between;
height: var(--topBarHeight);
line-height: 28px;
}
.bar h1 {
display: block;
font-size: 1rem;
margin: 0;
padding: 0;
}
#title-ext {
font-weight: 500;
}
.icons {
display: flex;
}
.icons button {
margin: 0 2px;
padding: 6px;
border: none;
border-radius: 50%;
height: 28px;
background-color: rgba(0,0,0,0.027450980392156863);
}
.icons button:hover {
background-color: rgba(0,0,0,0.13333333333333334);
}
.icons button svg {
display: block;
height: 16px;
width: 16px;
}
.svghover .onhover {
display: none;
}
.svghover:hover path {
display: none;
}
.svghover:hover .onhover {
display: unset;
}
#info {
transition: all 0.3s ease-in-out;
max-height: 90vh;
overflow-y: auto;
padding: 0.5rem;
}
#status {
max-height: 90vh;
padding: 0.5rem;
background: #def3f6;
transform: all 0.3s ease-in-out;
}
.hidden {
display: none;
}
#info.hidden
#status.hidden {
max-height: 0 ;
border: none;
padding: 0;
margin: 0;
opacity: 0;
overflow: hidden;
}
#outer {
position: absolute;
right: 0;
left: 0;
bottom: 0;
top: 0;
padding: 2rem;
background: rgba(0, 0, 0, 0.2);
height: 100vh;
width: 100vw;
max-height: 100vh;
max-width: 100vw;
}
#inner {
background: white;
display: flex;
flex-direction: column;
justify-content: space-between;
opacity: 1;
height: 100%;
width: 100%;
max-height: 100%;
max-width: 100%;
box-shadow: 4px 4px 12px -8px black;
}
#inner * {
margin: 0.2rem;
}
#inner .main {
overflow: auto;
background: lightgray;
max-width: 100%;
flex: 1;
}
.modal-open {
overflow: hidden;
max-height: 100vh;
max-width: 100vw;
}
app-selector {
display: flex;
flex-direction: column;
flex: 0 auto;
max-height: calc(100vh - 45px);
}
.main {
flex-grow: 1;
display: flex;
flex-direction: column;
height: 100vh;
min-width: 45px;
overflow: hidden;
}
#view {
height: 100%;
overflow: hidden;
}
#view:empty {
height: 0;
}
app-runner {
width: 100%;
height: 100%;
display: block;
}
</style>
<div class="flex">
<div id="sidebar">
<div id="sidebar-inner">
<header class="bar">
<h1>PatchBoot</h1>
<div class="icons">
<button id="close-apps">
<svg width="24" viewBox="0 0 24 24">
<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" />
</svg>
</button>
</div>
</header>
</div>
</div>
<div class="main">
<header class="bar">
<div class="icons">
<button id="toggle-apps">
<svg width="24" viewBox="0 0 24 24">
<path fill="currentColor"
d="M16,20H20V16H16M16,14H20V10H16M10,8H14V4H10M16,8H20V4H16M10,14H14V10H10M4,14H8V10H4M4,20H8V16H4M10,20H14V16H10M4,8H8V4H4V8Z" />
</svg>
</button>
</div>
<h1 id="title-ext"></h1>
<div></div>
</header>
<div id="connecting">
<p>Connecting to SSB</p>
<p class="muted"><small>If nothing happens, please make sure you have an SSB server running and the plugin
intstalled.</small></p>
</div>
<div id="info" class="hidden">
<h2>No App is Running yet</h2>
<p>
Only execute apps you trust,
as they’ll have full access to your SSB account.
</p>
</div>
<div id="status" class="hidden"></div>
<div id="view"></div>
</div>
</div>
<div id="close-apps-backdrop"></div>
</div>
`
const componentRoot = componentArea.getElementById('component-root')
const sidebar = componentArea.getElementById('sidebar')
const sidebarToggle = componentArea.getElementById('toggle-apps')
const sidebarClose = componentArea.getElementById('close-apps')
const sidebarCloseBackdrop = componentArea.getElementById('close-apps-backdrop')
const closeSidebar = () => {
console.log('closing')
sidebar.classList.add('gone')
sidebarCloseBackdrop.classList.add('gone')
sidebarCloseBackdrop.removeEventListener('click', closeSidebar)
}
const openSidebar = () => {
console.log('opening')
sidebar.classList.remove('gone')
sidebarCloseBackdrop.classList.remove('gone')
sidebarCloseBackdrop.addEventListener('click', closeSidebar)
}
sidebarToggle.addEventListener('click', e => {
console.log('toggling', sidebar.classList, sidebar.classList.contains('gone'))
if (sidebar.classList.contains('gone')) openSidebar()
else closeSidebar()
})
sidebarClose.addEventListener('click', closeSidebar)
sidebarCloseBackdrop.addEventListener('click', closeSidebar)
setTimeout(() => {
componentRoot.classList.add('waited')
}, 1000)
const selectionArea = componentArea.getElementById('sidebar-inner')
this.ssbConnect().then(sbot => {
if (componentArea.getElementById('connecting')) componentArea.getElementById('connecting').classList.add('hidden')
if (componentArea.getElementById('info')) componentArea.getElementById('info').classList.remove('hidden')
const selector = document.createElement('app-selector')
selector.sbot = sbot
selector.addEventListener('run', run)
selector.addEventListener('show-source', showSource)
selectionArea.appendChild(selector)
const statusBar = componentArea.getElementById('status')
const view = componentArea.getElementById('view')
//const shadowView = view.attachShadow({ mode: 'closed' });
//const shadowHtml = componentArea.createElement('html')
//shadowView.appendChild(shadowHtml)
function run(event) {
const app = event.detail
componentArea.getElementById('info').classList.add('hidden')
componentArea.getElementById('title-ext').innerHTML = app.name
statusBar.classList.remove('hidden')
statusBar.innerText = 'Loading ' + app.name
view.innerHTML = ''
const appRunner = document.createElement('app-runner')
appRunner.sbot = sbot
appRunner.app = app
view.appendChild(appRunner)
appRunner.addEventListener('loaded', e => {
statusBar.classList.add('hidden')
})
}
function showSource(event) {
const app = event.detail
console.log('showSource', app)
const outer = document.createElement('div')
outer.id = 'outer'
const oldTop = window.scrollY
const oldLeft = window.scrollX
window.scroll(0, 0)
componentRoot.classList.add('modal-open')
componentArea.appendChild(outer)
//const inner = document.createElement('div')
const sourceViewer = document.createElement('source-viewer')
sourceViewer.id = 'inner'
sourceViewer.app = app
sourceViewer.sbot = sbot
sourceViewer.name = app.name || app.comment || ''
outer.appendChild(sourceViewer)
const close = () => {
componentArea.removeChild(outer)
componentRoot.classList.remove('modal-open')
window.scroll(oldLeft, oldTop)
}
outer.addEventListener('click', close)
sourceViewer.addEventListener('close', close)
sourceViewer.addEventListener('click', e => e.stopPropagation())
}
},
error => {
console.log('An error occured', error)
})
}
}
customElements.define('patch-boot', PatchBoot)