diff --git a/documentation/AETHER_NATIVE_APP_ELECTRON_NEW_2026.md b/documentation/AETHER_NATIVE_APP_ELECTRON_NEW_2026.md index cd7f43b2..405b8a92 100644 --- a/documentation/AETHER_NATIVE_APP_ELECTRON_NEW_2026.md +++ b/documentation/AETHER_NATIVE_APP_ELECTRON_NEW_2026.md @@ -7,39 +7,37 @@ The Aether Native App serves as the OS-level bridge for the SvelteKit frontend. ## 2. The Three-Layer Architecture -### 2.1 The Engine (`electron_native.js`) -* **Process:** Main Process (Node.js) +### 2.1 The Engine (`src/main/*.ts`) +* **Process:** Main Process (Node.js/TypeScript) * **Role:** Performs the actual OS-level operations. * **Key Responsibilities:** - * Executing shell commands via `child_process`. - * Managing the permanent Hashed Cache (`file_cache/`). - * Performing atomic "Safe Handover" copies to the operational `temp/` directory. - * Resolving global path placeholders like `[home]` and `[tmp]`. + * **Shell Handlers (`shell_handlers.ts`)**: Executing shell commands, AppleScripts, and process management. + * **File Handlers (`file_handlers.ts`)**: Managing the permanent Hashed Cache (`file_cache/`) and atomic Safe Handover. + * Resolving global path placeholders like `[home]` and `[tmp]` via `file_utils.ts`. -### 2.2 The Bridge (`preload.js`) +### 2.2 The Bridge (`src/preload/index.ts`) * **Process:** Preload Script * **Role:** The security gatekeeper. * **Key Responsibilities:** - * Uses Electron's `contextBridge` to securely expose specific Main Process functions to the UI. + * Uses Electron's `contextBridge` to securely expose Main Process functions to the UI. * Exposes the `window.aetherNative` object. * Ensures that only whitelisted IPC channels can be triggered by the renderer. -### 2.3 The Messenger (`electron_relay.ts`) +### 2.3 The Messenger (`src/lib/electron/electron_relay.ts`) * **Process:** Renderer Process (SvelteKit) * **Role:** The TypeScript API used by Svelte components. * **Key Responsibilities:** - * Detecting if the app is running in "Native Mode". - * Providing a clean, typed interface for native calls. - * Implementing "Smart Fallbacks" (e.g., resolving placeholders UI-side if the bridge is outdated). + * Standardizing IPC calls to snake_case. + * Implementing "Smart Fallbacks" (e.g., UI-side placeholder resolution if the bridge is inactive). --- ## 3. Core Lifecycle -1. **Seed Phase:** The Electron shell reads `~/seed.json` to identify the device. -2. **Hydration Phase:** The shell authenticates with the Aether V3 API to pull device-specific settings (room assignment, sync timers). -3. **Injection Phase:** The shell injects the **JWT (Auth Token)** and **Native Config** into the SvelteKit environment. -4. **Observation Phase:** `LauncherBackgroundSync.svelte` force-hydrates absolute OS paths (Home/Tmp) into the global `ae_loc` store for immediate use. +1. **Seed Phase:** The Electron shell reads `resources/seed_config.json` to identify the device. +2. **Hydration Phase:** The shell authenticates with the Aether V3 API to pull device-specific settings. +3. **Injection Phase:** The shell injects the **JWT** and **Native Config** into the SvelteKit environment. +4. **Observation Phase:** Background sync loops manage file warming and heartbeat telemetry. --- @@ -48,17 +46,20 @@ The Aether Native App serves as the OS-level bridge for the SvelteKit frontend. ### File & Cache Management - `check_cache({hash})`: Verify if a file exists in the hidden hashed storage. - `download_to_cache({url, hash})`: Secure background download using the native API key. -- `launch_from_cache({hash, filename})`: **Safe Handover** — copies from cache to temp with the original name and triggers the launcher. +- `launch_from_cache({hash, filename})`: **Safe Handover** — copies from cache to temp with the original name and triggers the specialized launcher. ### OS Execution (The "Actuators") -- `launch_presentation({path, app})`: Intelligent application selection. +- `launch_presentation({path, app})`: Phase 5 specialized launcher with auto-focus (`activate`) and slideshow start. - **Linux:** Triggers `libreoffice --impress`. - - **macOS:** Triggers AppleScript for Keynote/PowerPoint. -- `run_cmd({cmd})`: Executes raw shell commands with automatic `[home]` and `[tmp]` resolution. -- `kill_processes([names])`: Force-closes presentation apps to clean the podium between sessions. + - **macOS:** Triggers AppleScript for Keynote or PowerPoint. +- `control_presentation({app, action})`: Remote navigation for active decks. + - **Actions:** `next`, `prev`, `start`, `stop`. +- `run_cmd({cmd})`: Executes raw shell commands with automatic path resolution. +- `kill_processes([names])`: Force-closes apps to clean the podium. -### System Metadata -- `get_device_info()`: Provides OS version, CPU/RAM stats, and absolute system paths. +### Metadata & Discovery +- `get_device_info()`: Provides CPU/RAM stats and absolute system paths. +- `list_tools()`: Returns a JSON manifest of all available native functions for self-documentation. --- diff --git a/documentation/AETHER_SVELTE5_PERFORMANCE_REVIEW.md b/documentation/AETHER_SVELTE5_PERFORMANCE_REVIEW.md new file mode 100644 index 00000000..e069f4eb --- /dev/null +++ b/documentation/AETHER_SVELTE5_PERFORMANCE_REVIEW.md @@ -0,0 +1,187 @@ +# Aether Svelte 5 App — Performance Review + +Date: 2026-01-26 + +This document collects findings from a quick code review of the Aether Svelte 5 app (focused on `src/routes/+layout.svelte`, `src/routes/+page.svelte`, and the events launcher layout). It lists concrete performance issues observed, their likely impact, remediation recommendations (quick wins first), code examples for safe refactors, measurement steps, and a prioritized action plan. + +**Scope** +- Files inspected: `src/routes/+layout.svelte`, `src/routes/+page.svelte`, event launcher `+layout.svelte` and related store initialization logic. +- Platform: SvelteKit with Vite (assumed from repo files). Builds use Tailwind CSS and manual global CSS. + +--- + +## Summary of Key Findings + +- Large synchronous imports at module evaluation time (notably `highlight.js` and its stylesheet) increase initial bundle size and run before first paint. +- Many components and icon imports are static at top-level even when rendered conditionally (`Analytics`, `MyClipboard`, `E_app_debug_menu`, `E_app_sys_menu`, multiple Lucide icons). This inflates the client bundle. +- Significant initialization and cache logic runs synchronously during module evaluation: store writes, cache expiration checks, IndexedDB operations, localStorage population, calls to `invalidateAll()` and conditional `window.location.reload()` paths — these can block initial render and cause reload loops or extra round trips. +- Dexie `liveQuery` usages in nested layouts are eager and may execute multiple DB queries on route entry, causing CPU and I/O work up-front. +- Multiple synchronous CSS imports (global CSS + highlight CSS) can delay first paint and increase transfer size. +- Many localStorage/sessionStorage writes and store updates are done immediately and synchronously; iterating many keys on startup can stall the main thread. +- Analytics, WebSocket connections, and external network resource usage may be triggered too early. + +Impact: Slow Time to First Paint (TTFP), increased Time to Interactive (TTI), larger JS bundles, higher CPU main-thread blocking, worse Lighthouse/perceived performance. + +--- + +## Concrete, Prioritized Recommendations + +Priority ordering: Quick wins (low code risk) → Short-term (moderate refactor) → Long-term (architectural). + +### Quick Wins (apply first) +- Lazy-load `highlight.js` and its CSS in `onMount` only when highlighting is needed. This removes a heavy library and stylesheet from the initial bundle. +- Dynamically import non-critical components (`Analytics`, debug menus, clipboard UI, large icon modules) when they will actually render. +- Move non-UI, non-blocking initialization to `onMount` and/or `requestIdleCallback` so SSR rendering and first paint aren't blocked by client-only logic. +- Guard `invalidateAll()` / `window.location.reload()` so they run only after a user-visible prompt or on explicit action; avoid automatic reload on subtle mismatches. + +### Short-Term Changes (next 1–2 days) +- Defer expensive Dexie `liveQuery` and database queries until their containing route/section mounts, or add limits/pagination to reduce initial query size. +- Batch localStorage writes (use a single write or `requestIdleCallback`/`setTimeout(…,0)`), or store a single object rather than many keys. +- Replace heavy icon imports with an icon-on-demand strategy (SVG sprite, `unplugin-icons`, or inlining only icons used on first paint). +- Lazy-load large CSS (e.g., highlight theme) with dynamic import or by adding/removing link elements. + +### Long-Term / Architectural +- Split routes to ensure route-level code-splitting is effective — verify that `+layout` doesn't import many route-specific modules. Keep `+layout.svelte` as thin as possible. +- Use a bundle analyzer to identify largest modules and tune dependencies. +- Serve production build with proper HTTP caching, compression (Brotli), and HTTP/2 or HTTP/3. +- Consider server-side rendering vs. partial hydration boundaries (islands) for heavy interactive regions. + +--- + +## Concrete Code Examples + +These are safe, copy-pasteable snippets for common quick fixes. + +1) Lazy-load `highlight.js` in `src/routes/+layout.svelte` (move work to `onMount`): + +```svelte + +``` + +2) Dynamic component import for `Analytics`: + +```svelte + + +{#if showAnalytics && Analytics} + +{/if} +``` + +3) Batch localStorage writes using `requestIdleCallback` fallback: + +```ts +function batchSetLocalStorage(obj: Record) { + const work = () => { + for (const [k, v] of Object.entries(obj)) { + try { localStorage.setItem(k, JSON.stringify(v)); } catch (e) {} + } + }; + if ('requestIdleCallback' in window) { + (window as any).requestIdleCallback(work); + } else { + setTimeout(work, 0); + } +} +``` + +4) Avoid automatic reloads — example guard: + +```ts +// Only force reload after user consent or if critical mismatch +if (flag_reload) { + const reloadKey = 'aether_reload_shown_v' + ($ae_loc?.ver ?? 'unknown'); + if (!localStorage.getItem(reloadKey)) { + if (confirm('A new version is available. Reload now?')) { + localStorage.setItem(reloadKey, '1'); + location.reload(); + } + } +} +``` + +--- + +## Measurement & Tooling + +Run these locally to find concrete hotspots and bundle contributors. + +- Bundle analysis (Vite): + +```bash +# Build and open analyzer (if plugin configured) +npm run build +npx vite build --sourcemap +# Use source-map-explorer or webpack-bundle-analyzer (adapt if using Rollup) +npx source-map-explorer dist/assets/*.js +``` + +- Vite visualizer (install `rollup-plugin-visualizer` or `vite-plugin-visualizer`): + +```bash +# add plugin and run build with report +# then open .html output from the plugin +``` + +- Lighthouse / DevTools: + - Run Lighthouse (Performance, TTI, Largest Contentful Paint, Main thread) in Chrome. + - In Performance panel record page load to find long main-thread tasks (JS parsing/execution) and identify blocking code paths. + +- Runtime marks: add `performance.mark('init-start')` / `performance.mark('init-end')` to measure areas (e.g., store population, Dexie queries) then `performance.measure()`. + +--- + +## Prioritized Action Plan (Checklist) + +- [ ] Move `highlight.js` imports and theme stylesheet to `onMount` (quick win). +- [ ] Convert `Analytics` and other developer tools to dynamic imports (quick win). +- [ ] Move non-critical store population, IndexedDB maintenance, and cache-expiration logic to `onMount` or `requestIdleCallback`. +- [ ] Add guard & confirmation around `invalidateAll()` and `location.reload()` to avoid reload loops. +- [ ] Defer Dexie `liveQuery` initialization to the route or component that needs it (avoid running all live queries globally). +- [ ] Batch localStorage writes and avoid iterating thousands of keys synchronously. +- [ ] Run bundle analysis and produce a top-10 biggest modules list. +- [ ] Replace heavy icon imports with icon-on-demand solution. +- [ ] Re-run Lighthouse and validate improvements. + +--- + +## Appendix: Quick Checklist for PRs + +- Ensure client-only code is inside `onMount` or guarded by `if (browser)`. +- Where dynamic import used, use `` pattern. +- Add performance marks around expensive blocks and log durations in dev mode. +- Avoid unconditional network or socket connections in top-level layout; open them after user interaction or when component mounts. + +--- + +If you want, I can implement the first quick wins now: lazy-load `highlight.js` and convert `Analytics` import in `src/routes/+layout.svelte`, then run a local bundle analysis. Tell me which of the quick wins you'd like me to patch first and I'll start. diff --git a/src/lib/electron/electron_relay.ts b/src/lib/electron/electron_relay.ts index 7260aa7b..7cce0a89 100644 --- a/src/lib/electron/electron_relay.ts +++ b/src/lib/electron/electron_relay.ts @@ -139,3 +139,55 @@ export async function launch_presentation({ return await open_local_file_v2(cleaned_path); } + +/** + * Control Presentation (Phase 5) + * Sends navigation commands to the active presentation (Next, Prev, Stop). + */ +export async function control_presentation({ + app, + action +}: { + app: 'powerpoint' | 'keynote', + action: 'next' | 'prev' | 'start' | 'stop' +}) { + if (!native) return { success: false, error: 'Native bridge not available' }; + + // Check if the native bridge has the direct implementation + if (native.control_presentation) { + return await native.control_presentation({ app, action }); + } + + // Fallback to generic osascript if direct handler is missing + let script = ''; + if (app === 'powerpoint') { + switch (action) { + case 'next': script = 'tell application "Microsoft PowerPoint" to next slide of slide show view of active presentation'; break; + case 'prev': script = 'tell application "Microsoft PowerPoint" to previous slide of slide show view of active presentation'; break; + case 'start': script = 'tell application "Microsoft PowerPoint" to run slide show of active presentation'; break; + case 'stop': script = 'tell application "Microsoft PowerPoint" to stop slide show of active presentation'; break; + } + } else if (app === 'keynote') { + switch (action) { + case 'next': script = 'tell application "Keynote" to show next'; break; + case 'prev': script = 'tell application "Keynote" to show previous'; break; + case 'start': script = 'tell application "Keynote" to start (front document)'; break; + case 'stop': script = 'tell application "Keynote" to stop'; break; + } + } + + if (script) { + return await run_osascript(script); + } + + return { success: false, error: `Unsupported app or action: ${app}/${action}` }; +} + +/** + * List Tools (Self-Documentation) + * Returns a JSON manifest of all available native bridge functions. + */ +export async function list_tools() { + if (!native || !native.list_tools) return []; + return await native.list_tools(); +} diff --git a/src/lib/electron/tmp_shell_handlers.ts b/src/lib/electron/tmp_shell_handlers.ts new file mode 100644 index 00000000..67427f19 --- /dev/null +++ b/src/lib/electron/tmp_shell_handlers.ts @@ -0,0 +1,211 @@ +import { ipcMain, shell } from 'electron'; +import { exec, execSync } from 'child_process'; +import * as fs from 'fs'; +import * as os from 'os'; +import { expandPath } from './file_utils'; + +export function registerShellHandlers() { + ipcMain.handle('native:open-folder', async (event, folderPath: string) => { + const cleanPath = expandPath(folderPath); + const error = await shell.openPath(cleanPath); + return { success: !error, error }; + }); + + ipcMain.handle('native:run-cmd', async (event, { cmd, timeout = 30000 }) => { + const cleanCmd = expandPath(cmd); + return new Promise((resolve) => { + exec(cleanCmd, { timeout }, (error, stdout, stderr) => { + resolve({ success: !error, stdout: stdout.trim(), stderr: stderr.trim(), error: error ? error.message : null }); + }); + }); + }); + + ipcMain.handle('native:run-cmd-sync', async (event, { cmd }) => { + const cleanCmd = expandPath(cmd); + try { + const stdout = execSync(cleanCmd).toString(); + return { success: true, stdout: stdout.trim() }; + } catch (error: any) { + return { success: false, error: error.message, stderr: error.stderr?.toString() }; + } + }); + + ipcMain.handle('native:run-osascript', async (event, script: string) => { + if (os.platform() !== 'darwin') return { success: false, error: 'AppleScript is only available on macOS' }; + const escapedScript = script.replace(/"/g, '\"'); + const cmd = `osascript -e "${escapedScript}"`; + return new Promise((resolve) => { + exec(cmd, (error, stdout, stderr) => { + resolve({ success: !error, stdout: stdout.trim(), stderr: stderr.trim(), error: error ? error.message : null }); + }); + }); + }); + + ipcMain.handle('native:kill-processes', async (event, { process_name_li = [] }) => { + console.log(`Native: Killing processes -> `, process_name_li); + const results = []; + for (const name of process_name_li) { + const cmd = os.platform() === 'win32' + ? `taskkill /F /IM ${name} /T` + : `pkill -f ${name}`; + try { + execSync(cmd); + results.push({ name, success: true }); + } catch (e: any) { + results.push({ name, success: false, error: e.message }); + } + } + return { success: true, results }; + }); + + ipcMain.handle('native:open-local-file-v2', async (event, filePath: string) => { + const cleanPath = expandPath(filePath); + const error = await shell.openPath(cleanPath); + return { success: !error, error }; + }); + + ipcMain.handle('native:launch-presentation', async (event, { path: rawPath, app: appType = 'default' }) => { + const cleanedPath = expandPath(rawPath); + console.log(`Native: Launching Presentation -> ${cleanedPath} (App: ${appType})`); + + if (os.platform() === 'linux') { + const cmd = `libreoffice --impress "${cleanedPath}"`; + return new Promise((resolve) => { + exec(cmd, (err, stdout, stderr) => { + if (err) resolve({ success: false, error: err.message }); + else resolve({ success: true, stdout, stderr }); + }); + }); + } + + if (os.platform() === 'darwin') { + let script = ''; + if (appType === 'keynote') { + script = ` + tell application "Keynote" + activate + open (POSIX file "${cleanedPath}") + delay 1 + start (front document) + end tell + `; + } else if (appType === 'powerpoint') { + script = ` + tell application "Microsoft PowerPoint" + activate + open (POSIX file "${cleanedPath}") + delay 1 + run slide show of active presentation + end tell + `; + } + + if (script) { + return new Promise((resolve) => { + const escapedScript = script.replace(/"/g, '\\"'); + exec(`osascript -e "${escapedScript}"`, (err, stdout, stderr) => { + if (err) resolve({ success: false, error: err.message }); + else resolve({ success: true }); + }); + }); + } + } + + const error = await shell.openPath(cleanedPath); + return { success: !error, error }; + }); + + ipcMain.handle('native:control-presentation', async (event, { app, action }) => { + if (os.platform() !== 'darwin') return { success: false, error: 'Presentation control is only available on macOS' }; + + let script = ''; + if (app === 'powerpoint') { + switch (action) { + case 'next': script = 'tell application "Microsoft PowerPoint" to next slide of slide show view of active presentation'; break; + case 'prev': script = 'tell application "Microsoft PowerPoint" to previous slide of slide show view of active presentation'; break; + case 'start': script = 'tell application "Microsoft PowerPoint" to run slide show of active presentation'; break; + case 'stop': script = 'tell application "Microsoft PowerPoint" to stop slide show of active presentation'; break; + } + } else if (app === 'keynote') { + switch (action) { + case 'next': script = 'tell application "Keynote" to show next'; break; + case 'prev': script = 'tell application "Keynote" to show previous'; break; + case 'start': script = 'tell application "Keynote" to start (front document)'; break; + case 'stop': script = 'tell application "Keynote" to stop'; break; + } + } + + if (!script) return { success: false, error: `Unsupported app or action: ${app}/${action}` }; + + return new Promise((resolve) => { + exec(`osascript -e "${script.replace(/"/g, '\\"')}"`, (error, stdout, stderr) => { + resolve({ success: !error, stdout: stdout.trim(), stderr: stderr.trim(), error: error ? error.message : null }); + }); + }); + }); + + ipcMain.handle('native:list-tools', async () => { + return [ + { + name: 'open_folder', + description: 'Opens a directory in the OS file explorer (Finder/Files/Explorer).', + params: { path: 'string' } + }, + { + name: 'run_cmd', + description: 'Executes an asynchronous shell command with a timeout.', + params: { cmd: 'string', timeout: 'number (optional)' } + }, + { + name: 'run_cmd_sync', + description: 'Executes a synchronous shell command.', + params: { cmd: 'string' } + }, + { + name: 'run_osascript', + description: 'Executes a raw AppleScript string (macOS only).', + params: { script: 'string' } + }, + { + name: 'kill_processes', + description: 'Forcefully terminates processes by name.', + params: { process_name_li: 'string[]' } + }, + { + name: 'open_local_file_v2', + description: 'Opens a local file using the default OS handler.', + params: { filePath: 'string' } + }, + { + name: 'launch_presentation', + description: 'Phase 5: Specialized launcher for PowerPoint, Keynote, and LibreOffice with auto-focus.', + params: { path: 'string', app: 'default|powerpoint|keynote' } + }, + { + name: 'control_presentation', + description: 'Phase 5: Remote navigation for active slideshows.', + params: { app: 'powerpoint|keynote', action: 'next|prev|start|stop' } + }, + { + name: 'check_cache', + description: 'Checks if a file exists in the local organized cache.', + params: { cache_root: 'string', hash: 'string', hash_prefix_length: 'number' } + }, + { + name: 'download_to_cache', + description: 'Downloads a file from the API directly into the native cache.', + params: { url: 'string', cache_root: 'string', hash: 'string', api_key: 'string', account_id: 'string' } + }, + { + name: 'launch_from_cache', + description: 'Atomic operation: Copies file from cache to temp with original name and launches via specialized handler.', + params: { cache_root: 'string', hash: 'string', temp_root: 'string', filename: 'string' } + }, + { + name: 'get_device_info', + description: 'Returns hardware and OS metadata (CPUs, RAM, IP addresses, Hostname).', + params: {} + } + ]; + }); +} diff --git a/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_app_modes.svelte b/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_app_modes.svelte new file mode 100644 index 00000000..d66d9904 --- /dev/null +++ b/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_app_modes.svelte @@ -0,0 +1,229 @@ + + +
+

+ +

+ +
+
+ {#if !$events_loc.launcher.app_mode || $events_loc.launcher.app_mode != 'default'} + + {/if} + {#if $events_loc.launcher.app_mode != 'native'} + + {/if} + {#if $events_loc.launcher.app_mode != 'onsite'} + + {/if} +
+ +
+ {#if $events_loc.launcher.hide__launcher_menu} + + {/if} + {#if !$events_loc.launcher.hide__launcher_header} + + {/if} + {#if $events_loc.launcher.hide__launcher_header} + + {/if} + + {#if !$events_loc.launcher.hide__launcher_footer} + + {/if} + {#if $events_loc.launcher.hide__launcher_footer} + + {/if} + + {#if !$events_loc.launcher.hide__launcher_menu} + + {/if} + {#if !$events_loc.launcher.hide__session_datetimes} + + {/if} + {#if $events_loc.launcher.hide__session_datetimes} + + {/if} + + + + {#if !$events_loc.launcher.hide__ws_element} + + {/if} + {#if $events_loc.launcher.hide__ws_element} + + {/if} + + {#if !$events_loc.launcher.hide__modal_header_title} + + {/if} + {#if $events_loc.launcher.hide__modal_header_title} + + {/if} +
+
+
diff --git a/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_controller.svelte b/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_controller.svelte new file mode 100644 index 00000000..3f6c1abb --- /dev/null +++ b/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_controller.svelte @@ -0,0 +1,105 @@ + + +
+

+ +

+ +
+
+ +
+
+ { + $events_sess.launcher.controller_unlock_group_code = + !$events_sess.launcher.controller_unlock_group_code; + + if ($events_loc.launcher.ws_connect) { + $events_sess.launcher.trigger__ws_disconnect = true; + } + }} + readonly={!$events_sess.launcher.controller_unlock_group_code} + /> + + + + {#if $events_loc.launcher.ws_connect} + + {/if} +
+
+
diff --git a/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_health.svelte b/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_health.svelte new file mode 100644 index 00000000..3c3ff4d7 --- /dev/null +++ b/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_health.svelte @@ -0,0 +1,70 @@ + + +{#if $ae_loc.is_native} +
+

+ +

+ +
+ +
+ Last Heartbeat: + + {$events_sess.launcher.heartbeat_info.last_timestamp || 'Pending...'} + + + Room Sync Status: + + {$events_sess.launcher.sync_stats.cached} / {$events_sess.launcher.sync_stats.total} Files + + + {#if $events_sess.launcher.sync_stats.currently_syncing} + + + Syncing: {$events_sess.launcher.sync_stats.currently_syncing} + + {/if} +
+ + + {#if $ae_loc.is_native && $ae_loc.native_device} +
+
Host: {$ae_loc.native_device.info_hostname || 'Loading...'}
+
IPs: {$ae_loc.native_device.info_ip_list || '...'}
+
+ {/if} +
+
+{/if} diff --git a/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_local_actions.svelte b/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_local_actions.svelte new file mode 100644 index 00000000..c1001668 --- /dev/null +++ b/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_local_actions.svelte @@ -0,0 +1,117 @@ + + +
+

+ +

+ +
+
+ + + The action happens when the option is selected + +
+ +
+ + + +
+ +
API: {$ae_api.base_url}
+
+
diff --git a/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_native_os.svelte b/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_native_os.svelte new file mode 100644 index 00000000..ee2f3104 --- /dev/null +++ b/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_native_os.svelte @@ -0,0 +1,143 @@ + + +{#if $ae_loc.is_native} +
+

+ +

+ +
+
+ + + +
+ + +
+
+ + +
+ +
+ + + + +
+ {#if remote_status} +
{remote_status}
+ {/if} +
+ +
+
+ +
+ + +
+
+ + {#if test_cmd_result} +
+
{test_cmd_result}
+ +
+ {/if} +
+
+
+{/if} + + diff --git a/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_screen_saver.svelte b/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_screen_saver.svelte new file mode 100644 index 00000000..5b1f9dd5 --- /dev/null +++ b/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_screen_saver.svelte @@ -0,0 +1,65 @@ + + +
+

+ +

+ +
+ + + + + +
+
diff --git a/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_sync_timers.svelte b/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_sync_timers.svelte new file mode 100644 index 00000000..50d5de11 --- /dev/null +++ b/src/routes/events/[event_id]/(launcher)/cfg_components/launcher_cfg_sync_timers.svelte @@ -0,0 +1,85 @@ + + +{#if $ae_loc.is_native} +
+

+ +

+ +
+ {#if $ae_loc.native_device} + + + + + +
+ * Prefix: {($ae_loc.native_device.hash_prefix_length || 2)} chars. Reload required. +
+ {:else} +
No device config hydrated.
+ {/if} +
+
+{/if} diff --git a/src/routes/events/[event_id]/(launcher)/launcher_cfg.svelte b/src/routes/events/[event_id]/(launcher)/launcher_cfg.svelte index bb5278f5..79071134 100644 --- a/src/routes/events/[event_id]/(launcher)/launcher_cfg.svelte +++ b/src/routes/events/[event_id]/(launcher)/launcher_cfg.svelte @@ -23,9 +23,14 @@ events_trig } from '$lib/stores/ae_events_stores'; - import * as native from '$lib/electron/electron_relay'; - - let test_cmd_result = $state(''); + // Sub-components + import LauncherCfgNativeOS from './cfg_components/launcher_cfg_native_os.svelte'; + import LauncherCfgSyncTimers from './cfg_components/launcher_cfg_sync_timers.svelte'; + import LauncherCfgHealth from './cfg_components/launcher_cfg_health.svelte'; + import LauncherCfgController from './cfg_components/launcher_cfg_controller.svelte'; + import LauncherCfgScreenSaver from './cfg_components/launcher_cfg_screen_saver.svelte'; + import LauncherCfgAppModes from './cfg_components/launcher_cfg_app_modes.svelte'; + import LauncherCfgLocalActions from './cfg_components/launcher_cfg_local_actions.svelte';
- + {#if $ae_loc.is_native} -
-

- -

- -
-
- - - -
- -
-
- -
- - -
-
- - {#if test_cmd_result} -
-
{test_cmd_result}
- -
- {/if} -
-
-
- - - -
- -

- - - -

- - - -
- - {#if $ae_loc.native_device} - - - - - - - - - - - -
- - * Prefix: {($ae_loc.native_device.hash_prefix_length || 2)} chars. Reload required. - -
- - {:else} - -
No device config hydrated.
- - {/if} - -
- -
- - - - - -
- -

- - - -

- - - -
- - - -
- - Last Heartbeat: - - - - {$events_sess.launcher.heartbeat_info.last_timestamp || 'Pending...'} - - - - - - Room Sync Status: - - - - {$events_sess.launcher.sync_stats.cached} / {$events_sess.launcher.sync_stats.total} Files - - - - - - {#if $events_sess.launcher.sync_stats.currently_syncing} - - - - - - Syncing: {$events_sess.launcher.sync_stats.currently_syncing} - - - - {/if} - -
- - - - - - {#if $ae_loc.is_native && $ae_loc.native_device} - -
- -
Host: {$ae_loc.native_device.info_hostname || 'Loading...'}
- -
IPs: {$ae_loc.native_device.info_ip_list || '...'}
- -
- - {/if} - -
- -
- - + + + {/if} - + + + + -
-

- -

- -
-
- -
-
- { - $events_sess.launcher.controller_unlock_group_code = - !$events_sess.launcher.controller_unlock_group_code; - - if ($events_loc.launcher.ws_connect) { - $events_sess.launcher.trigger__ws_disconnect = true; - } else { - // $events_sess.launcher.trigger__ws_connect = true; - } - // $events_loc.launcher.ws_connect = false; - }} - readonly={!$events_sess.launcher.controller_unlock_group_code} - /> - - - - {#if $events_loc.launcher.ws_connect} - - {/if} -
-
-
- - - -
-

- -

- -
- - - - - - - - - - -
-
- - - - - - -
-

- -

- -
-
- {#if !$events_loc.launcher.app_mode || $events_loc.launcher.app_mode != 'default'} - - {/if} - {#if $events_loc.launcher.app_mode != 'native'} - - {/if} - {#if $events_loc.launcher.app_mode != 'onsite'} - - {/if} -
- -
- {#if $events_loc.launcher.hide__launcher_menu} - - {/if} - {#if !$events_loc.launcher.hide__launcher_header} - - {/if} - {#if $events_loc.launcher.hide__launcher_header} - - {/if} - - {#if !$events_loc.launcher.hide__launcher_footer} - - {/if} - {#if $events_loc.launcher.hide__launcher_footer} - - {/if} - - - {#if !$events_loc.launcher.hide__launcher_menu} - - {/if} - - {#if !$events_loc.launcher.hide__session_datetimes} - - {/if} - {#if $events_loc.launcher.hide__session_datetimes} - - {/if} - - - - - {#if !$events_loc.launcher.hide__ws_element} - - {/if} - {#if $events_loc.launcher.hide__ws_element} - - {/if} - - - {#if !$events_loc.launcher.hide__modal_header_title} - - {/if} - {#if $events_loc.launcher.hide__modal_header_title} - - {/if} -
-
-
- -
-

- -

- - -
-
- - - The action happens when the option is selected - -
- -
- - - - - -
- -
API: {$ae_api.base_url}
-
-
- - - -
+
-
+ \ No newline at end of file