f0d2926872
Add comprehensive READMEs for Bigscreen and Desktop modes, and relocate the Tauri frontend prototype into a new Bigscreen/ subdirectory. This moves package.json/package-lock, src, src-tauri, assets, views, styles and related files into Bigscreen/ to separate the controller-first shell from top-level docs. Update the root README.md to reframe the project as "NebulaOS", describe Bigscreen/Desktop modes, the vision, tech stack and development notes. This reorganizes the repo layout for clearer mode separation and documentation.
229 lines
8.8 KiB
JavaScript
229 lines
8.8 KiB
JavaScript
const SETTINGS_TEMPLATE = `
|
|
<section class="view settings-view" data-view="settings">
|
|
<header class="shell-topbar">
|
|
<div class="shell-topbar-content">
|
|
<p class="shell-brand">Nebula OS</p>
|
|
<div class="shell-status">
|
|
<span class="shell-avatar" aria-hidden="true"></span>
|
|
<p class="shell-time" data-clock>--:--</p>
|
|
</div>
|
|
</div>
|
|
<div class="shell-accent-line"></div>
|
|
</header>
|
|
|
|
<section class="settings-header-copy">
|
|
<p class="muted">System</p>
|
|
<h1 class="view-title">Settings</h1>
|
|
</section>
|
|
|
|
<section class="settings-body" data-focus-root>
|
|
<section class="settings-category-bar">
|
|
<button class="focusable settings-category" data-focusable="true" data-row="0" data-col="0" data-cat="network" data-focus-key="network">Network</button>
|
|
<button class="focusable settings-category" data-focusable="true" data-row="0" data-col="1" data-cat="audio" data-focus-key="audio">Audio</button>
|
|
<button class="focusable settings-category" data-focusable="true" data-row="0" data-col="2" data-cat="display" data-focus-key="display">Display</button>
|
|
<button class="focusable settings-category" data-focusable="true" data-row="0" data-col="3" data-cat="storage" data-focus-key="storage">Storage</button>
|
|
<button class="focusable settings-category" data-focusable="true" data-row="0" data-col="4" data-cat="system" data-focus-key="system">System</button>
|
|
</section>
|
|
|
|
<article class="panel settings-panel">
|
|
<div class="settings-panel-head">
|
|
<h2 class="settings-panel-title" data-panel-title>Network</h2>
|
|
<p class="muted" data-panel-copy>Optimize connection and online routing.</p>
|
|
</div>
|
|
<div class="settings-card-grid">
|
|
<button class="focusable settings-card" data-focusable="true" data-row="1" data-col="0" data-toggle="passkey-enabled" data-focus-key="passkey-enabled">
|
|
<p class="settings-card-title">Passkey Lock</p>
|
|
<p class="muted" data-passkey-enabled>Enabled</p>
|
|
</button>
|
|
<button class="focusable settings-card" data-focusable="true" data-row="1" data-col="1" data-toggle="passkey-change" data-focus-key="passkey-change">
|
|
<p class="settings-card-title">Reset Passkey</p>
|
|
<p class="muted" data-passkey-change>Clear and set a new passkey</p>
|
|
</button>
|
|
<button class="focusable settings-card" data-focusable="true" data-row="1" data-col="2" data-toggle="passkey-length" data-focus-key="passkey-length">
|
|
<p class="settings-card-title">Required Length</p>
|
|
<p class="muted" data-passkey-length>6 digits</p>
|
|
<p class="muted">Fixed for testing</p>
|
|
</button>
|
|
<button class="focusable settings-card" data-focusable="true" data-row="1" data-col="3" data-toggle="passkey-confirm" data-focus-key="passkey-confirm">
|
|
<p class="settings-card-title">Require Confirm</p>
|
|
<p class="muted" data-passkey-confirm>Disabled</p>
|
|
</button>
|
|
<button class="focusable settings-card" data-focusable="true" data-row="1" data-col="4" data-toggle="passkey-keyboard" data-focus-key="passkey-keyboard">
|
|
<p class="settings-card-title">Keyboard Support</p>
|
|
<p class="muted" data-passkey-keyboard>Enabled</p>
|
|
</button>
|
|
<div class="settings-card settings-card-info">
|
|
<p class="settings-card-title">Status</p>
|
|
<p class="muted" data-status-note>Applying Nebula profile</p>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
</section>
|
|
</section>
|
|
`;
|
|
|
|
const CATEGORIES = {
|
|
network: "Network",
|
|
audio: "Audio",
|
|
display: "Display",
|
|
storage: "Storage",
|
|
system: "System",
|
|
};
|
|
|
|
export const createSettingsView = ({ state, renderView }) => {
|
|
const updatePasskey = (partial) => {
|
|
state.passkey.updateConfig(partial);
|
|
refreshPanel();
|
|
};
|
|
|
|
const refreshPanel = () => {
|
|
const title = document.querySelector("[data-panel-title]");
|
|
const copy = document.querySelector("[data-panel-copy]");
|
|
const passkeyEnabled = document.querySelector("[data-passkey-enabled]");
|
|
const passkeyLength = document.querySelector("[data-passkey-length]");
|
|
const passkeyConfirm = document.querySelector("[data-passkey-confirm]");
|
|
const passkeyKeyboard = document.querySelector("[data-passkey-keyboard]");
|
|
const status = document.querySelector("[data-status-note]");
|
|
const passkeyConfig = state.passkey.getConfig();
|
|
|
|
document.querySelectorAll(".settings-category").forEach((button) => {
|
|
button.classList.toggle("is-active", button.dataset.cat === state.settingsCategory);
|
|
});
|
|
|
|
if (title) {
|
|
title.textContent = CATEGORIES[state.settingsCategory] ?? "Settings";
|
|
}
|
|
if (copy) {
|
|
if (state.settingsCategory === "system") {
|
|
copy.textContent = "Configure passkey security and controller login behavior.";
|
|
} else {
|
|
copy.textContent = `Tune ${CATEGORIES[state.settingsCategory] ?? "system"} options with controller-first cards.`;
|
|
}
|
|
}
|
|
if (passkeyEnabled) {
|
|
passkeyEnabled.textContent = passkeyConfig.enabled ? "Enabled" : "Disabled";
|
|
}
|
|
if (passkeyLength) {
|
|
passkeyLength.textContent = `${passkeyConfig.length} digits`;
|
|
}
|
|
if (passkeyConfirm) {
|
|
passkeyConfirm.textContent = passkeyConfig.requireConfirm ? "Enabled" : "Disabled";
|
|
}
|
|
if (passkeyKeyboard) {
|
|
passkeyKeyboard.textContent = passkeyConfig.keyboardSupport ? "Enabled" : "Disabled";
|
|
}
|
|
if (status) {
|
|
status.textContent = state.settingsCategory === "system"
|
|
? `Attempts: ${passkeyConfig.maxAttempts}, cooldown: ${passkeyConfig.cooldownSeconds}s`
|
|
: `${CATEGORIES[state.settingsCategory] ?? "System"} profile synced`;
|
|
}
|
|
|
|
document.querySelectorAll("[data-toggle^='passkey']").forEach((button) => {
|
|
button.classList.toggle("is-disabled", state.settingsCategory !== "system");
|
|
button.setAttribute("aria-disabled", state.settingsCategory === "system" ? "false" : "true");
|
|
});
|
|
};
|
|
|
|
const toggleCurrent = (suffix = "") => {
|
|
const key = suffix ? `${state.settingsCategory}_${suffix}` : state.settingsCategory;
|
|
state.settingsValues[key] = !Boolean(state.settingsValues[key]);
|
|
refreshPanel();
|
|
};
|
|
|
|
return {
|
|
id: "settings",
|
|
render: () => SETTINGS_TEMPLATE,
|
|
mount: () => {
|
|
const root = document.querySelector("[data-focus-root]");
|
|
root?.addEventListener("focusin", (event) => {
|
|
const focused = event.target.closest("[data-focusable='true']");
|
|
const col = Number(focused?.dataset.col ?? 0);
|
|
document.documentElement.style.setProperty("--nebula-accent-line-x", `${24 + col * 110}px`);
|
|
});
|
|
refreshPanel();
|
|
document.documentElement.style.setProperty("--nebula-focus-strength", "1");
|
|
},
|
|
getNavigationContract: () => {
|
|
const root = document.querySelector("[data-focus-root]");
|
|
return {
|
|
focusRoot: root,
|
|
defaultFocus: root?.querySelector("[data-cat='network']") ?? null,
|
|
layout: { type: "grid", cols: 5, rows: 2 },
|
|
hintsTemplate: "#global-hints-template",
|
|
nebulaNavigation: state.nebula.navigation,
|
|
onAccept: (element) => {
|
|
if (!element) {
|
|
return;
|
|
}
|
|
|
|
const category = element.dataset.cat;
|
|
const toggle = element.dataset.toggle;
|
|
|
|
if (category) {
|
|
state.settingsCategory = category;
|
|
refreshPanel();
|
|
return;
|
|
}
|
|
|
|
if (toggle === "primary") {
|
|
toggleCurrent();
|
|
return;
|
|
}
|
|
|
|
if (toggle === "secondary") {
|
|
toggleCurrent("secondary");
|
|
return;
|
|
}
|
|
|
|
if (state.settingsCategory !== "system") {
|
|
return;
|
|
}
|
|
|
|
if (toggle === "passkey-enabled") {
|
|
updatePasskey({ enabled: !state.passkey.getConfig().enabled });
|
|
return;
|
|
}
|
|
|
|
if (toggle === "passkey-change") {
|
|
state.passkey.resetSequence();
|
|
state.passkeySetupRequired = true;
|
|
state.locked = true;
|
|
state.activeView = "lock";
|
|
renderView("lock");
|
|
return;
|
|
}
|
|
|
|
if (toggle === "passkey-length") {
|
|
return;
|
|
}
|
|
|
|
if (toggle === "passkey-confirm") {
|
|
updatePasskey({ requireConfirm: !state.passkey.getConfig().requireConfirm });
|
|
return;
|
|
}
|
|
|
|
if (toggle === "passkey-keyboard") {
|
|
updatePasskey({ keyboardSupport: !state.passkey.getConfig().keyboardSupport });
|
|
}
|
|
},
|
|
onBack: () => {
|
|
state.activeView = "home";
|
|
renderView("home");
|
|
},
|
|
onMenu: () => {},
|
|
onAction: (action, element) => {
|
|
if (state.settingsCategory !== "system") {
|
|
return false;
|
|
}
|
|
|
|
if (element?.dataset.toggle !== "passkey-length") {
|
|
return false;
|
|
}
|
|
|
|
return false;
|
|
},
|
|
};
|
|
},
|
|
};
|
|
};
|