cq

Distributed social media platform
git clone git://git.finwo.net/app/cq
Log | Files | Refs

commit 07c195e7748ee2ad58ef2755dd0025b7614e2347
parent 298281b23ad15b471839c4acb05c599acdf15ab4
Author: finwo <finwo@pm.me>
Date:   Mon, 15 Sep 2025 21:04:25 +0200

Splitting off screens from components, chose font for now

Diffstat:
Apackages/app/src/assets/fonts/karla/karla-italic-latin-ext.woff2 | 0
Apackages/app/src/assets/fonts/karla/karla-italic-latin.woff2 | 0
Apackages/app/src/assets/fonts/karla/karla-normal-latin-ext.woff2 | 0
Apackages/app/src/assets/fonts/karla/karla-normal-latin.woff2 | 0
Apackages/app/src/assets/fonts/karla/karla.css | 37+++++++++++++++++++++++++++++++++++++
Apackages/app/src/component/icon.tsx | 32++++++++++++++++++++++++++++++++
Mpackages/app/src/component/screen-account-create.tsx | 113++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------
Mpackages/app/src/component/screen-account-select.tsx | 2+-
Mpackages/app/src/component/screen-menu.tsx | 1+
Mpackages/app/src/global.css | 75++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Mpackages/app/src/main.ts | 38+++++++++++++++++++++-----------------
Apackages/app/src/screen/account-create.tsx | 29+++++++++++++++++++++++++++++
Apackages/app/src/screen/account-select.tsx | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apackages/app/src/screen/home.tsx | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apackages/app/src/screen/menu.tsx | 68++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mpackages/app/src/service-worker.ts | 127+++++--------------------------------------------------------------------------
Mpackages/app/src/util/account-require.ts | 2+-
17 files changed, 470 insertions(+), 205 deletions(-)

diff --git a/packages/app/src/assets/fonts/karla/karla-italic-latin-ext.woff2 b/packages/app/src/assets/fonts/karla/karla-italic-latin-ext.woff2 Binary files differ. diff --git a/packages/app/src/assets/fonts/karla/karla-italic-latin.woff2 b/packages/app/src/assets/fonts/karla/karla-italic-latin.woff2 Binary files differ. diff --git a/packages/app/src/assets/fonts/karla/karla-normal-latin-ext.woff2 b/packages/app/src/assets/fonts/karla/karla-normal-latin-ext.woff2 Binary files differ. diff --git a/packages/app/src/assets/fonts/karla/karla-normal-latin.woff2 b/packages/app/src/assets/fonts/karla/karla-normal-latin.woff2 Binary files differ. diff --git a/packages/app/src/assets/fonts/karla/karla.css b/packages/app/src/assets/fonts/karla/karla.css @@ -0,0 +1,37 @@ +/* latin-ext */ +@font-face { + font-family: 'Karla'; + font-style: italic; + font-weight: 200 800; + font-display: swap; + src: url(/assets/fonts/karla/karla-italic-latin-ext.woff2) format('woff2'); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Karla'; + font-style: italic; + font-weight: 200 800; + font-display: swap; + src: url(/assets/fonts/karla/karla-italic-latin.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} +/* latin-ext */ +@font-face { + font-family: 'Karla'; + font-style: normal; + font-weight: 200 800; + font-display: swap; + src: url(/assets/fonts/karla/karla-normal-latin-ext.woff2) format('woff2'); + unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF; +} +/* latin */ +@font-face { + font-family: 'Karla'; + font-style: normal; + font-weight: 200 800; + font-display: swap; + src: url(/assets/fonts/karla/karla-normal-latin.woff2) format('woff2'); + unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; +} + diff --git a/packages/app/src/component/icon.tsx b/packages/app/src/component/icon.tsx @@ -0,0 +1,32 @@ +import { Vnode } from 'mithril'; + +const iconMap = { + 'trash': require('lucide/dist/esm/icons/trash-2.js').default, + 'menu' : require('lucide/dist/esm/icons/menu.js').default, + 'back' : require('lucide/dist/esm/icons/arrow-left.js').default, +}; + +const defaultAttributes = { + xmlns : "http://www.w3.org/2000/svg", + width : '1em', + height : '1em', + viewBox : "0 0 24 24", + fill : "none", + stroke : "currentColor", + "stroke-width" : 2, + "stroke-linecap" : "round", + "stroke-linejoin": "round", +}; + +type _Vnode = Vnode<{ icon: string }, typeof Icon>; + +export const Icon = { + view(vnode: _Vnode) { + return ( + <svg ns="http://www.w3.org/2000/svg" {...defaultAttributes} {...vnode.attrs} class="icon"> + {iconMap[vnode.attrs.icon].map(el => m(...el))} + </svg> + ); + }, +}; + diff --git a/packages/app/src/component/screen-account-create.tsx b/packages/app/src/component/screen-account-create.tsx @@ -62,72 +62,115 @@ export const AccountCreateScreen = { // Build root certificate outputEl.innerText += 'Generating recovery certificate\n'; - const rootCertificateId = randomString(16); const rootCertificateMeta = base64url.encode(JSON.stringify({ typ: 'certificate', iat: Date.now(), - iss: rootCertificateId, - sub: rootCertificateId, + iss: 'self', acc: vnode.state.accountId, roles: ['root'], })); const rootCertificateBody = base64url.encode(rootKey.publicKey); const rootCertificateSignature = base64url.encode(await rootKey.sign(`${rootCertificateMeta}.${rootCertificateBody}`)); + const rootCertificate = `${rootCertificateMeta}.${rootCertificateBody}.${rootCertificateSignature}`; + const rootCertificateId = base64url.encode(await window.crypto.subtle.digest("SHA-256", Buffer.from(rootCertificate))); + const rootCertificateHandle = await getFileHandle(`/remote/accounts/${vnode.state.accountId}/keychain/${rootCertificateId}`, { create: true }); + const rootCertificateWritable = await rootCertificateHandle.createWritable(); + await rootCertificateWritable.write(rootCertificate); + await rootCertificateWritable.close(); await new Promise(r => setTimeout(r, 500)); // Generate device key outputEl.innerText += 'Generating device key\n'; const deviceSeed = Buffer.from(await crypto.getRandomValues(new Uint8Array(32))); const deviceKey = await KeyPair.create(deviceSeed); + + // Store device key + const deviceKeyHandle = await getFileHandle(`/local/accounts/${vnode.state.accountId}/device-key.json`, { create: true }) + const deviceKeyWritable = await deviceKeyHandle.createWritable(); + await deviceKeyWritable.write(JSON.stringify(deviceKey.toJSON(), null, 2)); + await deviceKeyWritable.close(); await new Promise(r => setTimeout(r, 500)); // Build device certificate - const deviceCertificateId = randomString(16); + outputEl.innerText += 'Generating device certificate\n'; const deviceCertificateMeta = base64url.encode(JSON.stringify({ typ: 'certificate', iat: Date.now(), + par: [rootCertificateId], iss: rootCertificateId, - sub: deviceCertificateId, acc: vnode.state.accountId, - name: vnode.state.deviceName, - roles: ['device'], + roles: ['root'], })); const deviceCertificateBody = base64url.encode(deviceKey.publicKey); const deviceCertificateSignature = base64url.encode(await rootKey.sign(`${deviceCertificateMeta}.${deviceCertificateBody}`)); + const deviceCertificate = `${deviceCertificateMeta}.${deviceCertificateBody}.${deviceCertificateSignature}`; + const deviceCertificateId = base64url.encode(await window.crypto.subtle.digest("SHA-256", Buffer.from(deviceCertificate))); + const deviceCertificateHandle = await getFileHandle(`/remote/accounts/${vnode.state.accountId}/keychain/${deviceCertificateId}`, { create: true }); + const deviceCertificateWritable = await deviceCertificateHandle.createWritable(); + await deviceCertificateWritable.write(deviceCertificate); + await deviceCertificateWritable.close(); await new Promise(r => setTimeout(r, 500)); - // Build basic config - outputEl.innerText += 'Generating base configuration\n'; - const accountConfigHandle = await getFileHandle(`/local/accounts/${vnode.state.accountId}/config.json`, { create: true }) - const accountConfigWritable = await accountConfigHandle.createWritable(); - await accountConfigWritable.write(JSON.stringify({ - displayName: vnode.state.accountName - }, null, 2)); - await accountConfigWritable.close(); - await new Promise(r => setTimeout(r, 500)); - - // Store device key - outputEl.innerText += 'Safeguarding device key\n'; - const deviceKeyHandle = await getFileHandle(`/local/accounts/${vnode.state.accountId}/device-key.json`, { create: true }) - const deviceKeyWritable = await deviceKeyHandle.createWritable(); - await deviceKeyWritable.write(JSON.stringify(deviceKey.toJSON(), null, 2)); - await deviceKeyWritable.close(); - await new Promise(r => setTimeout(r, 500)); - - // Build keychain - outputEl.innerText += 'Generating key chain\n'; - const keychainHandle = await getFileHandle(`/remote/accounts/${vnode.state.accountId}/keychain`, { create: true }); + // Ensure a head index + outputEl.innerText += 'Initializing keychain\n'; + const keychainHandle = await getFileHandle(`/remote/accounts/${vnode.state.accountId}/keychain/heads`, { create: true }); const keychainWritable = await keychainHandle.createWritable(); - await keychainWritable.write(`${rootCertificateMeta}.${rootCertificateBody}.${rootCertificateSignature}\n`); - await keychainWritable.write(`${deviceCertificateMeta}.${deviceCertificateBody}.${deviceCertificateSignature}\n`); + await keychainWritable.write(`${deviceCertificateId}\n`); await keychainWritable.close(); await new Promise(r => setTimeout(r, 500)); - // Select the account - outputEl.innerText += 'Switching to new account\n'; - localStorage.selectedAccount = vnode.state.accountId; - await new Promise(r => setTimeout(r, 500)); - document.location.href=`#!${HomeScreen.routePath}`; + // // Build device certificate + // outputEl.innerText += 'Generating recovery certificate\n'; + // const deviceCertificateMeta = base64url.encode(JSON.stringify({ + // }); + + console.log({ + rootKey, + deviceKey, + rootCertificateId, + }); + + + // // Build device certificate + // const deviceCertificateMeta = base64url.encode(JSON.stringify({ + // typ: 'certificate', + // iat: Date.now(), + // iss: rootCertificateId, + // sub: deviceCertificateId, + // acc: vnode.state.accountId, + // name: vnode.state.deviceName, + // roles: ['device'], + // })); + // const deviceCertificateBody = base64url.encode(deviceKey.publicKey); + // const deviceCertificateSignature = base64url.encode(await rootKey.sign(`${deviceCertificateMeta}.${deviceCertificateBody}`)); + // await new Promise(r => setTimeout(r, 500)); + + // // Build basic config + // outputEl.innerText += 'Generating base configuration\n'; + // const accountConfigHandle = await getFileHandle(`/local/accounts/${vnode.state.accountId}/config.json`, { create: true }) + // const accountConfigWritable = await accountConfigHandle.createWritable(); + // await accountConfigWritable.write(JSON.stringify({ + // displayName: vnode.state.accountName + // }, null, 2)); + // await accountConfigWritable.close(); + // await new Promise(r => setTimeout(r, 500)); + + + // // Build keychain + // outputEl.innerText += 'Generating key chain\n'; + // const keychainHandle = await getFileHandle(`/remote/accounts/${vnode.state.accountId}/keychain`, { create: true }); + // const keychainWritable = await keychainHandle.createWritable(); + // await keychainWritable.write(`${rootCertificateMeta}.${rootCertificateBody}.${rootCertificateSignature}\n`); + // await keychainWritable.write(`${deviceCertificateMeta}.${deviceCertificateBody}.${deviceCertificateSignature}\n`); + // await keychainWritable.close(); + // await new Promise(r => setTimeout(r, 500)); + + // // Select the account + // outputEl.innerText += 'Switching to new account\n'; + // localStorage.selectedAccount = vnode.state.accountId; + // await new Promise(r => setTimeout(r, 500)); + // document.location.href=`#!${HomeScreen.routePath}`; + } } }, diff --git a/packages/app/src/component/screen-account-select.tsx b/packages/app/src/component/screen-account-select.tsx @@ -56,7 +56,7 @@ export const AccountSelectScreen = { return ( <div class="main"> <h3>Select account</h3> - <div id="accountSelectList"> + <div id="accountSelectList" class="entry-list"> {vnode.state.accounts.map(account => ( <a style="display:block;padding:1em" onclick={vnode.state.handler.selectAccount(account)}> <label>{account.displayName}</label> diff --git a/packages/app/src/component/screen-menu.tsx b/packages/app/src/component/screen-menu.tsx @@ -5,6 +5,7 @@ import {HomeScreen} from './screen-home'; import {AccountSelectScreen} from './screen-account-select'; import {ContactListScreen} from './screen-contact-list'; import {DeviceListScreen} from './screen-device-list'; +import {requireAccount} from '../util/account-require'; export const MenuScreen = { routePath: '/menu', diff --git a/packages/app/src/global.css b/packages/app/src/global.css @@ -1,13 +1,30 @@ :root { - background: #000000; - color: #FFFFFF; - font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + --col-bg: black; + --col-fg: white; +} + +@media (prefers-color-scheme: light) { + :root { + --col-bg: white; + --col-fg: black; + } +} + +:root { + background: var(--col-bg); + color: var(--col-fg); + font-family: Karla, sans-serif; font-size: x-large; + font-optical-sizing: auto; + font-weight: 400; + font-style: normal; + line-height:1.414em; } * { box-sizing: border-box; font-size: inherit; + font-weight: inherit; } body { @@ -15,12 +32,12 @@ body { padding: 0; } -h1 { font-size: 2.0rem; } -h2 { font-size: 1.6rem; } -h3 { font-size: 1.2rem; } -h4 { font-size: 1.0rem; } -h5 { font-size: 0.8rem; } -h6 { font-size: 0.6rem; } +h1 { font-size: 2.828rem; } +h2 { font-size: 2.000rem; } +h3 { font-size: 1.414rem; } +h4 { font-size: 1.000rem; } +h5 { font-size: 0.841rem; } +h6 { font-size: 0.707rem; } h1, h2, h3, h4, h5, h6, legend, label { font-weight: 500; @@ -45,18 +62,26 @@ button:focus, outline: none; } +.main { + max-width: 640px; + margin: 0 auto; + position: relative; + > * { + margin: 1rem; + } +} .btn, button { - border: 2px solid #FFFFFF; - background: #FFFFFF; - color: #000000; + border: 2px solid var(--col-fg); + background: var(--col-fg); + color: var(--col-bg); margin: 1em; padding: 0.25em 0.5em; cursor: pointer; text-decoration: none; &:active { - background: #000000; - color: #FFFFFF; + background: var(--col-bg); + color: var(--col-fg); } } @@ -65,27 +90,13 @@ input { padding: 0.5em 0px; color: inherit; border: none; + border-radius: 0; border-bottom: 2px solid #FFF; } -.icon { - stroke: #FFF; - vertical-align:bottom; - width:1em; - height:1em; -} - -.main { - max-width: 640px; - margin: 0 auto; - position: relative; - > * { - margin: 1rem; - } -} - .entry-list { - > a[href] { + > a { + cursor: pointer; text-decoration: none; } > * { @@ -97,6 +108,7 @@ input { } } +/* .accountListDeleteButton { display: none; cursor: pointer; @@ -116,3 +128,4 @@ input { } } } +*/ diff --git a/packages/app/src/main.ts b/packages/app/src/main.ts @@ -1,27 +1,31 @@ -globalThis.m = require('mithril'); - -import {AccountAddScreen} from './component/screen-account-add.js'; -import { AccountCreateScreen } from './component/screen-account-create.js'; -import { AccountSelectScreen } from './component/screen-account-select.js'; -import {ContactAddScreen} from './component/screen-contact-add.js'; -import {ContactListScreen} from './component/screen-contact-list.js'; -import {DeviceAddScreen} from './component/screen-device-add.js'; -import {DeviceListScreen} from './component/screen-device-list.js'; -import { HomeScreen } from './component/screen-home.js'; -import {MenuScreen} from './component/screen-menu.js'; +import {AccountCreateScreen} from "./screen/account-create"; +import {AccountSelectScreen} from "./screen/account-select"; +import {HomeScreen} from "./screen/home"; +import {MenuScreen} from "./screen/menu"; if ('serviceWorker' in navigator) { navigator.serviceWorker.register("/service-worker.js"); } +globalThis.m = require('mithril'); + +// import {AccountAddScreen} from './component/screen-account-add.js'; +// import {ContactAddScreen} from './component/screen-contact-add.js'; +// import {ContactListScreen} from './component/screen-contact-list.js'; +// import {DeviceAddScreen} from './component/screen-device-add.js'; +// import {DeviceListScreen} from './component/screen-device-list.js'; +// import { HomeScreen } from './component/screen-home.js'; +// import {MenuScreen} from './component/screen-menu.js'; + m.route(document.body, "/", { [HomeScreen.routePath ]: HomeScreen, + [MenuScreen.routePath ]: MenuScreen, [AccountSelectScreen.routePath]: AccountSelectScreen, [AccountCreateScreen.routePath]: AccountCreateScreen, - [AccountAddScreen.routePath ]: AccountAddScreen, - [MenuScreen.routePath ]: MenuScreen, - [ContactListScreen.routePath ]: ContactListScreen, - [ContactAddScreen.routePath ]: ContactAddScreen, - [DeviceListScreen.routePath ]: DeviceListScreen, - [DeviceAddScreen.routePath ]: DeviceAddScreen, + // [AccountAddScreen.routePath ]: AccountAddScreen, + // [MenuScreen.routePath ]: MenuScreen, + // [ContactListScreen.routePath ]: ContactListScreen, + // [ContactAddScreen.routePath ]: ContactAddScreen, + // [DeviceListScreen.routePath ]: DeviceListScreen, + // [DeviceAddScreen.routePath ]: DeviceAddScreen, }); diff --git a/packages/app/src/screen/account-create.tsx b/packages/app/src/screen/account-create.tsx @@ -0,0 +1,29 @@ +import {Icon} from "../component/icon"; +import {getDirectoryHandle} from "../util/opfs"; + +type _Vnode = Vnode<{}, typeof AccountCreateScreen>; + +export const AccountCreateScreen = { + routePath: '/account/create', + + async oninit(vnode: _Vnode) { + // vnode.state.loadAccounts(vnode); + }, + + view(vnode: _Vnode) { + return ( + <div class="main"> + <h3> + <span>Create account</span> + </h3> + + <div class="entry-list"> + <a style="display:block;padding:1em;"> + <label>Something</label> + </a> + </div> + + </div> + ); + }, +}; diff --git a/packages/app/src/screen/account-select.tsx b/packages/app/src/screen/account-select.tsx @@ -0,0 +1,85 @@ +import {Icon} from "../component/icon"; +import {getDirectoryHandle} from "../util/opfs"; +import {AccountCreateScreen} from "./account-create"; + +type _Vnode = Vnode<{}, typeof AccountSelectScreen>; + +type Contact = { + accountId: string; + displayName: string; +}; + +export const AccountSelectScreen = { + routePath: '/account/select', + accounts: [] as Array<{id:string,displayName:string}>, + + async loadAccounts(vnode: _Vnode, redraw: boolean = false) { + vnode.state.accounts = []; + const accountsHandle = await getDirectoryHandle('/local/accounts'); + + for await (const accountId of accountsHandle.keys()) { + // try { + // const accountConfigHandle = await getFileHandle(`/local/accounts/${accountId}/config.json`) + // const accountConfig = JSON.parse(Buffer.from(await (await accountConfigHandle.getFile()).arrayBuffer())); + // vnode.state.accounts.push({ + // ...accountConfig, + // id: accountId, + // }); + // } catch { + // // Intentionally empty + // } + } + + if (!vnode.state.accounts.length) { + document.location.href = `#!${AccountCreateScreen.routePath}`; + return; + } + + if (redraw) m.redraw(); + }, + + selectAccount(account: {id:string}) { + return async () => { + console.log(`SELECT ${account.id}`); + }; + }, + + async oninit(vnode: _Vnode) { + vnode.state.loadAccounts(vnode); + + // // Fetch contacts/follows + // const contactsHandle = await getFileHandle(`/local/accounts/${localStorage.selectedAccount}/contacts.json`, { create: true }) + // vnode.state.contacts = []; + // try { + // vnode.state.contacts = JSON.parse(Buffer.from(await (await contactsHandle.getFile()).bytes())); + // } catch { + // vnode.state.contacts = []; + // } + + // // const accountConfigHandle = await getFileHandle(`/local/accounts/${accountId}/config.json`) + // // const accountConfig = JSON.parse(Buffer.from(await (await accountConfigHandle.getFile()).bytes())); + + // // const deviceKeyWritable = await deviceKeyHandle.createWritable(); + // // await deviceKeyWritable.write(JSON.stringify(deviceKey.toJSON(), null, 2)); + // // await deviceKeyWritable.close(); + }, + + view(vnode: _Vnode) { + return ( + <div class="main"> + <h3> + <span>Select account</span> + </h3> + + <div class="entry-list"> + {vnode.state.accounts.map(account => ( + <a style="display:block;padding:1em" onclick={vnode.state.selectAccount(account)}> + <label>{account.displayName}</label> + </a> + ))} + </div> + + </div> + ); + }, +}; diff --git a/packages/app/src/screen/home.tsx b/packages/app/src/screen/home.tsx @@ -0,0 +1,66 @@ +// import {getFileHandle} from "../util/opfs"; +// import {requireAccount} from '../util/account-require'; +// import { AccountSelectScreen } from "./screen-account-select"; + +import {Icon} from "../component/icon"; +import {MenuScreen} from "./menu"; + +// import MenuIcon from 'lucide/dist/esm/icons/menu.js'; +// import {MenuScreen} from "./screen-menu"; + +type _Vnode = Vnode<{}, typeof HomeScreen>; + +type Contact = { + accountId: string; + displayName: string; +}; + +export const HomeScreen = { + routePath: '/', + contacts: [] as Contact[], + + async oninit(vnode: _Vnode) { + // if (!await requireAccount(true)) return; + + // // Fetch contacts/follows + // const contactsHandle = await getFileHandle(`/local/accounts/${localStorage.selectedAccount}/contacts.json`, { create: true }) + // vnode.state.contacts = []; + // try { + // vnode.state.contacts = JSON.parse(Buffer.from(await (await contactsHandle.getFile()).bytes())); + // } catch { + // vnode.state.contacts = []; + // } + + // // const accountConfigHandle = await getFileHandle(`/local/accounts/${accountId}/config.json`) + // // const accountConfig = JSON.parse(Buffer.from(await (await accountConfigHandle.getFile()).bytes())); + + // // const deviceKeyWritable = await deviceKeyHandle.createWritable(); + // // await deviceKeyWritable.write(JSON.stringify(deviceKey.toJSON(), null, 2)); + // // await deviceKeyWritable.close(); + }, + + view(vnode: _Vnode) { + return ( + <div class="main"> + <h3> + <span>CQ</span> + <a href={`#!${MenuScreen.routePath}`} style="float:right;"> + <Icon icon="menu"/> + </a> + </h3> + + <div class="entry-list"> + <div> + <label style="display:block;">Nikola Tesla</label> + <small>Edison has been acting strange lately, I suspect he's...</small> + </div> + <div> + <label style="display:block;">Thomas Edison</label> + <small>You got to drop by and look at my new invention, it's...</small> + </div> + </div> + + </div> + ); + }, +}; diff --git a/packages/app/src/screen/menu.tsx b/packages/app/src/screen/menu.tsx @@ -0,0 +1,68 @@ +// import {getFileHandle} from "../util/opfs"; +// import {requireAccount} from '../util/account-require'; +// import { AccountSelectScreen } from "./screen-account-select"; + +import {Icon} from "../component/icon"; +import {requireAccount} from "../util/account-require"; +import {HomeScreen} from "./home"; + +// import MenuIcon from 'lucide/dist/esm/icons/menu.js'; +// import {MenuScreen} from "./screen-menu"; + +type _Vnode = Vnode<{}, typeof MenuScreen>; + +type Contact = { + accountId: string; + displayName: string; +}; + +export const MenuScreen = { + routePath: '/menu', + contacts: [] as Contact[], + + async oninit(vnode: _Vnode) { + if (!await requireAccount(true)) return; + + // // Fetch contacts/follows + // const contactsHandle = await getFileHandle(`/local/accounts/${localStorage.selectedAccount}/contacts.json`, { create: true }) + // vnode.state.contacts = []; + // try { + // vnode.state.contacts = JSON.parse(Buffer.from(await (await contactsHandle.getFile()).bytes())); + // } catch { + // vnode.state.contacts = []; + // } + + // // const accountConfigHandle = await getFileHandle(`/local/accounts/${accountId}/config.json`) + // // const accountConfig = JSON.parse(Buffer.from(await (await accountConfigHandle.getFile()).bytes())); + + // // const deviceKeyWritable = await deviceKeyHandle.createWritable(); + // // await deviceKeyWritable.write(JSON.stringify(deviceKey.toJSON(), null, 2)); + // // await deviceKeyWritable.close(); + }, + + view(vnode: _Vnode) { + return ( + <div class="main"> + <h3> + <span>Menu</span> + <a href={`#!${HomeScreen.routePath}`} style="float:right;"> + <Icon icon="back"/> + </a> + </h3> + + <div class="entry-list"> + <a onclick={()=>{}} style="display:block;"> + Profile + </a> + <a onclick={()=>{}} style="display:block;"> + Preferences + </a> + <a onclick={()=>{}} style="display:block;"> + Switch account + </a> + </div> + + </div> + ); + }, +}; diff --git a/packages/app/src/service-worker.ts b/packages/app/src/service-worker.ts @@ -1,117 +1,6 @@ const CACHE_NAME = 'pwa-cache-v1'; const urlsToCache = '--FILES--'; - // '/', - // '/index.html', - // '/global.css', - // '/main.js', - // '/service-worker.js', - // '/assets/apple-touch-icon.png', - // '/assets/favicon.ico', - // '/assets/fonts/karla/karla.css', - // '/assets/fonts/karla/karla-italic-latin-ext.woff2', - // '/assets/fonts/karla/karla-italic-latin.woff2', - // '/assets/fonts/karla/karla-normal-latin-ext.woff2', - // '/assets/fonts/karla/karla-normal-latin.woff2', - // '/assets/icon.png', - // '/assets/icon-48.png', - // '/assets/icon-57.png', - // '/assets/icon-60.png', - // '/assets/icon-72.png', - // '/assets/icon-76.png', - // '/assets/icon-96.png', - // '/assets/icon-114.png', - // '/assets/icon-120.png', - // '/assets/icon-144.png', - // '/assets/icon-152.png', - // '/assets/icon-180.png', - // '/assets/icon-192.png', - // '/assets/icon-256.png', - // '/assets/icon-384.png', - // '/assets/icon-512.png', - // '/assets/manifest.json', - // ]); - -// // Install event - Cache essential resources -// self.addEventListener('install', event => { -// event.waitUntil( -// caches.open(CACHE_NAME) -// .then(cache => { -// console.log('Caching app shell'); -// return cache.addAll(urlsToCache); -// }) -// .then(() => { -// // Force activation of new service worker -// return self.skipWaiting(); -// }) -// ); -// }); - -// // Activate event - Clean up old caches -// self.addEventListener('activate', event => { -// event.waitUntil( -// caches.keys().then(cacheNames => { -// return Promise.all( -// cacheNames.map(cacheName => { -// if (cacheName !== CACHE_NAME) { -// console.log('Deleting old cache:', cacheName); -// return caches.delete(cacheName); -// } -// }) -// ); -// }).then(() => { -// // Take control of all pages -// return self.clients.claim(); -// }) -// ); -// }); - -// // Cache-first strategy for static assets -// self.addEventListener('fetch', event => { -// // Only handle GET requests -// if (event.request.method !== 'GET') return; - -// const url = new URL(event.request.url); - -// console.log('Fetch', event.request.url, event.request.mode); - -// // Cache-first for static assets -// if (url.pathname.match(/\.(css|js|png|jpg|jpeg|gif|svg|ico)$/)) { -// event.respondWith( -// caches.match(event.request) -// .then(response => { -// if (response) { -// console.log('Serving from cache:', event.request.url); -// return response; -// } - -// // Fetch and cache new resources -// return fetch(event.request) -// .then(response => { -// // Don't cache non-successful responses -// if (!response || response.status !== 200 || response.type !== 'basic') { -// return response; -// } - -// const responseToCache = response.clone(); -// caches.open(CACHE_NAME) -// .then(cache => { -// cache.put(event.request, responseToCache); -// }); - -// return response; -// }); -// }) -// .catch(() => { -// // Return fallback for images -// if (event.request.destination === 'image') { -// return caches.match('/images/fallback.png'); -// } -// }) -// ); -// } -// }); - self.addEventListener("install", event => { console.log("Service worker installed"); }); @@ -127,8 +16,6 @@ self.addEventListener('install', event => { event.waitUntil( caches.open('v1').then(cache => { return cache.addAll(urlsToCache); - - }) ); }); @@ -145,10 +32,10 @@ self.addEventListener('activate', event => { ); }); -self.addEventListener('fetch', event => { - event.respondWith( - caches.match(event.request).then(response => { - return response || fetch(event.request); - }) - ); -}); +// self.addEventListener('fetch', event => { +// event.respondWith( +// caches.match(event.request).then(response => { +// return response || fetch(event.request); +// }) +// ); +// }); diff --git a/packages/app/src/util/account-require.ts b/packages/app/src/util/account-require.ts @@ -1,4 +1,4 @@ -import {AccountSelectScreen} from "../component/screen-account-select"; +import {AccountSelectScreen} from "../screen/account-select"; import {getFileHandle} from "./opfs"; export async function requireAccount(redirect: boolean = false): Promise<boolean> {