summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-hub/src')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/app.js74
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/de.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/en.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/es.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/it.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js1
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/platform.js27
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/style.css8
13 files changed, 96 insertions, 23 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/app.js b/packages/meshbay-hub/src/meshbay_hub/static/app.js
index 4091ae7..f49d412 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/app.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/app.js
@@ -569,7 +569,8 @@ function TransferWidget() {
// ── Nav ──────────────────────────────────────────────────────────────────────
-function Nav({ user, theme, onThemeChange, onLogout, onMenuToggle, unreadCount }) {
+function Nav({ user, theme, onThemeChange, onLogout, onMenuToggle, unreadCount,
+ hubUnset }) {
return html`
<nav class="nav">
<div class="nav-left">
@@ -590,7 +591,7 @@ function Nav({ user, theme, onThemeChange, onLogout, onMenuToggle, unreadCount }
${user ? html`
<${UserMenu} user=${user} theme=${theme}
onThemeChange=${onThemeChange} onLogout=${onLogout} />
- ` : html`
+ ` : hubUnset ? null : html`
<a class="nav-btn" href="#/login">${t('nav.login')}</a>
`}
</div>
@@ -681,18 +682,21 @@ function FirstRunPage({ onSet }) {
};
return html`
- <div class="auth-page">
- <h2>${t('firstrun.title')}</h2>
- <p class="settings-hint">${t('firstrun.hint')}</p>
- <form onSubmit=${submit}>
- <input type="url" placeholder="https://meshbay.org" required
- value=${url} onInput=${e => setUrl(e.target.value)} />
- <button type="submit" disabled=${busy}>
- ${busy ? '…' : t('firstrun.btn')}
- </button>
- </form>
- ${error && html`<p class="error-msg">${error}</p>`}
- <p class="settings-hint">${t('firstrun.note')}</p>
+ <div class="page-center">
+ <div class="card login-card">
+ <h2>${t('firstrun.title')}</h2>
+ <p class="settings-hint" style="margin-bottom:16px">${t('firstrun.hint')}</p>
+ <form onSubmit=${submit}>
+ <input type="text" placeholder="https://meshbay.org" required
+ autofocus value=${url}
+ onInput=${e => setUrl(e.target.value)} />
+ <button type="submit" disabled=${busy}>
+ ${busy ? t('firstrun.checking') : t('firstrun.btn')}
+ </button>
+ </form>
+ ${error && html`<div class="error-msg">${error}</div>`}
+ <p class="settings-hint" style="margin-top:16px">${t('firstrun.note')}</p>
+ </div>
</div>
`;
}
@@ -1185,6 +1189,26 @@ const PIPELINE_WINDOW = 8;
*/
async function _openDownloadTarget(filename, size = 0, pickerOpts = {},
swSize = size) {
+ // On a desktop build this is the whole answer, and it comes first.
+ //
+ // The two browser paths below are both unavailable there — `showDirectoryPicker`
+ // does not exist, and Chromium refuses a service worker on a custom scheme —
+ // so without this the chain fell all the way through to its floor, which
+ // collects the file in the page and hands the browser a blob. A gigabyte of
+ // film meant a gigabyte of RAM, and a Save As dialog at the *end*.
+ if (platform.capabilities.nativeSave) {
+ try {
+ const native = await platform.nativeSave(
+ filename, { auto: downloads.getMode() === 'auto' });
+ // Null means the person dismissed the dialog, which is not an error and
+ // must not start a transfer.
+ return native || false;
+ } catch (err) {
+ console.warn('[MeshBay] native save failed:', platform.bridgeMessage(err));
+ return false;
+ }
+ }
+
try {
const target = await downloads.openTarget(filename);
if (target) return target;
@@ -4013,10 +4037,20 @@ function SettingsPage({ user, theme, onThemeChange, groups }) {
hubFetch('/v1/hub/version').then(setHubInfo).catch(() => {});
}, []);
- useEffect(() => { downloads.savedDirectory().then(setDlDir); }, []);
+ useEffect(() => {
+ // The desktop build remembers a path; the browser remembers a handle. Both
+ // answer "where do downloads go", and the row below renders either.
+ if (platform.folder.available) platform.folder.get().then(setDlDir);
+ else downloads.savedDirectory().then(setDlDir);
+ }, []);
const pickFolder = useCallback(async () => {
try {
+ if (platform.folder.available) {
+ const dir = await platform.folder.choose();
+ if (dir) setDlDir(dir);
+ return;
+ }
const handle = await downloads.chooseDirectory();
setDlDir(handle);
} catch (err) {
@@ -4033,7 +4067,7 @@ function SettingsPage({ user, theme, onThemeChange, groups }) {
<div class="settings-section">
<h3 class="settings-heading">${t('settings.downloads')}</h3>
- ${!downloads.SUPPORTED
+ ${!(downloads.SUPPORTED || platform.folder.available)
? html`<p class="settings-hint">${t('settings.dl_unsupported')}</p>`
: html`
<label class="settings-choice">
@@ -4054,7 +4088,8 @@ function SettingsPage({ user, theme, onThemeChange, groups }) {
</label>
<div class="settings-row" style="margin-top:10px">
<span class="settings-label">
- ${dlDir ? t('settings.dl_folder', { name: dlDir.name })
+ ${dlDir ? t('settings.dl_folder',
+ { name: dlDir.name || String(dlDir) })
: t('settings.dl_no_folder')}
</span>
<span>
@@ -4063,7 +4098,8 @@ function SettingsPage({ user, theme, onThemeChange, groups }) {
</button>
${dlDir && html`
<button class="btn-secondary" onClick=${async () => {
- await downloads.forgetDirectory();
+ if (platform.folder.available) await platform.folder.forget();
+ else await downloads.forgetDirectory();
setDlDir(null);
}}>${t('settings.dl_forget')}</button>
`}
@@ -4869,7 +4905,7 @@ function App() {
onThemeChange=${changeTheme}
onLogout=${authCtx.logout}
onMenuToggle=${() => setMenuOpen(o => !o)}
- unreadCount=${unreadCount} />
+ unreadCount=${unreadCount} hubUnset=${needsHub} />
<div class="layout">
${user && html`<${Sidebar}
groups=${groups}
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
index ff7bd8a..e099edb 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/de.js
@@ -201,6 +201,7 @@ export default {
'firstrun.title': 'Which hub?',
'firstrun.hint': 'A hub holds your account and introduces you to nodes. It never sees your files, your messages or your keys.',
'firstrun.btn': 'Continue',
+ 'firstrun.checking': 'Checking…',
'firstrun.note': 'There is no default on purpose: this application only trusts a hub for its API, never for the interface, which ships with the application itself.',
'device.this_device': 'This device',
'settings.keys_heading': 'Keys on this device',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
index 738a862..a621084 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/en.js
@@ -193,6 +193,7 @@ export default {
'firstrun.title': 'Which hub?',
'firstrun.hint': 'A hub holds your account and introduces you to nodes. It never sees your files, your messages or your keys.',
'firstrun.btn': 'Continue',
+ 'firstrun.checking': 'Checking…',
'firstrun.note': 'There is no default on purpose: this application only trusts a hub for its API, never for the interface, which ships with the application itself.',
'device.this_device': 'This device',
'settings.keys_heading': 'Keys on this device',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
index fb5cb0e..5fb4950 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/es.js
@@ -197,6 +197,7 @@ export default {
'firstrun.title': 'Which hub?',
'firstrun.hint': 'A hub holds your account and introduces you to nodes. It never sees your files, your messages or your keys.',
'firstrun.btn': 'Continue',
+ 'firstrun.checking': 'Checking…',
'firstrun.note': 'There is no default on purpose: this application only trusts a hub for its API, never for the interface, which ships with the application itself.',
'device.this_device': 'This device',
'settings.keys_heading': 'Keys on this device',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
index a849730..c9480b6 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/fr.js
@@ -201,6 +201,7 @@ export default {
'firstrun.title': 'Quel hub ?',
'firstrun.hint': 'Un hub détient votre compte et vous met en relation avec les nœuds. Il ne voit ni vos fichiers, ni vos messages, ni vos clés.',
'firstrun.btn': 'Continuer',
+ 'firstrun.checking': 'Vérification…',
'firstrun.note': 'Il n’y a volontairement pas de valeur par défaut : cette application ne fait confiance à un hub que pour son API, jamais pour l’interface, qui est livrée avec l’application.',
'device.this_device': 'Cet appareil',
'settings.keys_heading': 'Clés sur cet appareil',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
index 17ad3e0..ac7c06c 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/it.js
@@ -200,6 +200,7 @@ export default {
'firstrun.title': 'Which hub?',
'firstrun.hint': 'A hub holds your account and introduces you to nodes. It never sees your files, your messages or your keys.',
'firstrun.btn': 'Continue',
+ 'firstrun.checking': 'Checking…',
'firstrun.note': 'There is no default on purpose: this application only trusts a hub for its API, never for the interface, which ships with the application itself.',
'device.this_device': 'This device',
'settings.keys_heading': 'Keys on this device',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
index 47221a6..b5d60ca 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/ja.js
@@ -195,6 +195,7 @@ export default {
'firstrun.title': 'Which hub?',
'firstrun.hint': 'A hub holds your account and introduces you to nodes. It never sees your files, your messages or your keys.',
'firstrun.btn': 'Continue',
+ 'firstrun.checking': 'Checking…',
'firstrun.note': 'There is no default on purpose: this application only trusts a hub for its API, never for the interface, which ships with the application itself.',
'device.this_device': 'This device',
'settings.keys_heading': 'Keys on this device',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
index 617cdcd..304c90a 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/nl.js
@@ -201,6 +201,7 @@ export default {
'firstrun.title': 'Which hub?',
'firstrun.hint': 'A hub holds your account and introduces you to nodes. It never sees your files, your messages or your keys.',
'firstrun.btn': 'Continue',
+ 'firstrun.checking': 'Checking…',
'firstrun.note': 'There is no default on purpose: this application only trusts a hub for its API, never for the interface, which ships with the application itself.',
'device.this_device': 'This device',
'settings.keys_heading': 'Keys on this device',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
index ddbd1a9..98f36ce 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pl.js
@@ -207,6 +207,7 @@ export default {
'firstrun.title': 'Which hub?',
'firstrun.hint': 'A hub holds your account and introduces you to nodes. It never sees your files, your messages or your keys.',
'firstrun.btn': 'Continue',
+ 'firstrun.checking': 'Checking…',
'firstrun.note': 'There is no default on purpose: this application only trusts a hub for its API, never for the interface, which ships with the application itself.',
'device.this_device': 'This device',
'settings.keys_heading': 'Keys on this device',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js
index 18c0921..5db5cd5 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/pt-BR.js
@@ -199,6 +199,7 @@ export default {
'firstrun.title': 'Which hub?',
'firstrun.hint': 'A hub holds your account and introduces you to nodes. It never sees your files, your messages or your keys.',
'firstrun.btn': 'Continue',
+ 'firstrun.checking': 'Checking…',
'firstrun.note': 'There is no default on purpose: this application only trusts a hub for its API, never for the interface, which ships with the application itself.',
'device.this_device': 'This device',
'settings.keys_heading': 'Keys on this device',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js
index d868335..b5f16fb 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/locales/zh-CN.js
@@ -186,6 +186,7 @@ export default {
'firstrun.title': 'Which hub?',
'firstrun.hint': 'A hub holds your account and introduces you to nodes. It never sees your files, your messages or your keys.',
'firstrun.btn': 'Continue',
+ 'firstrun.checking': 'Checking…',
'firstrun.note': 'There is no default on purpose: this application only trusts a hub for its API, never for the interface, which ships with the application itself.',
'device.this_device': 'This device',
'settings.keys_heading': 'Keys on this device',
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/platform.js b/packages/meshbay-hub/src/meshbay_hub/static/platform.js
index 994f558..372b666 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/platform.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/platform.js
@@ -176,13 +176,32 @@ export async function apiFetch(url, init) {
* in `downloads.js`. Adding a native writer must not remove the three that
* already work.
*/
-export async function nativeSave(suggestedName, size) {
+export async function nativeSave(suggestedName, { auto = true } = {}) {
if (!bridge || !bridge.saveFile) return null;
- return bridge.saveFile(suggestedName, size);
+ const sink = await bridge.saveFile(suggestedName, { auto });
+ if (!sink) return null;
+ // The shape every caller already expects from a download target: a `writable`
+ // with write/close/abort, and the name it was actually given on disk.
+ return {
+ name: sink.name,
+ writable: {
+ write: (bytes) => sink.write(bytes),
+ close: () => sink.close(),
+ abort: () => sink.abort(),
+ },
+ };
}
+/** Where downloads go on a desktop build. Null in a browser. */
+export const folder = {
+ available: Boolean(bridge && bridge.folder),
+ async choose() { return bridge && bridge.folder ? bridge.folder.choose() : null; },
+ async get() { return bridge && bridge.folder ? bridge.folder.get() : null; },
+ async forget() { return bridge && bridge.folder ? bridge.folder.forget() : false; },
+};
+
export default { isNative, hubBase, capabilities, secrets, nativeSave,
- apiFetch, device, bridgeMessage };
+ apiFetch, device, bridgeMessage, folder };
// Also a global, because `transport.js` is loaded as a classic script — it
// predates the module graph and exposes `MeshBayTransport` the same way. The
@@ -191,5 +210,5 @@ export default { isNative, hubBase, capabilities, secrets, nativeSave,
if (typeof window !== 'undefined') {
window.MeshBayPlatform = { isNative, hubBase, capabilities, secrets,
nativeSave, apiFetch, device,
- bridgeMessage };
+ bridgeMessage, folder };
}
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/style.css b/packages/meshbay-hub/src/meshbay_hub/static/style.css
index 6b0b6fe..828e104 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/style.css
+++ b/packages/meshbay-hub/src/meshbay_hub/static/style.css
@@ -278,6 +278,14 @@ a:hover { text-decoration: underline; }
padding: 24px;
}
+/* A centring page already reserves the whole viewport below the header, so the
+ main column's own padding is added on top of it and the page overflows by
+ exactly that much — 48px, and a scrollbar on sign-in at every window size.
+ Measured, not deduced: `.page-center` ended at 795 and `main` at 819.
+ The page brings its own 24px, so dropping main's here changes nothing
+ visible and removes the duplicated arithmetic rather than adding more. */
+.main:has(> .page-center) { padding: 0; }
+
/* ── Cards ────────────────────────────────────────────────────────────────── */
.card {