Files
OSIT-AE-App-Svelte/src/routes/+layout.svelte
2026-02-16 17:22:36 -05:00

266 lines
10 KiB
Svelte

<script lang="ts">
/** @type {import('./$types').LayoutData} */
// /** @type {import('./$types').LayoutProps} */
let log_lvl: number = 1;
// *** Import Svelte specific
import { untrack } from 'svelte';
import { goto, invalidateAll } from '$app/navigation';
import '../app.css';
// *** Import other supporting libraries
// import {
// ArrowBigRight,
// CircleX,
// RefreshCw,
// RefreshCcw,
// RefreshCcwDot
// } from '@lucide/svelte';
// Highlight JS
import hljs from 'highlight.js/lib/core';
import 'highlight.js/styles/github-dark.css';
import { browser } from '$app/environment';
import { online } from 'svelte/reactivity/window';
import xml from 'highlight.js/lib/languages/xml'; // for HTML
import css from 'highlight.js/lib/languages/css';
import javascript from 'highlight.js/lib/languages/javascript';
import typescript from 'highlight.js/lib/languages/typescript';
hljs.registerLanguage('xml', xml); // for HTML
hljs.registerLanguage('css', css);
hljs.registerLanguage('javascript', javascript);
hljs.registerLanguage('typescript', typescript);
// *** Import Aether specific variables and functions
// import Analytics from '$lib/app_components/analytics.svelte';
import { ae_loc, ae_sess, ae_api, slct, slct_trigger } from '$lib/stores/ae_stores';
// import { events_loc, events_slct } from '$lib/stores/ae_events_stores';
// import MyClipboard from '$lib/app_components/e_app_clipboard.svelte';
import E_app_debug_menu from '$lib/app_components/e_app_debug_menu.svelte';
import E_app_sys_menu from '$lib/app_components/e_app_sys_menu.svelte';
interface Props {
data: any;
children?: import('svelte').Snippet;
}
let { data, children }: Props = $props();
// STABLE DERIVATION: Using prop data directly to avoid store loops
let ae_acct = $derived(data[data.account_id]);
let flag_clear_idb: boolean = $state(false);
let flag_clear_local: boolean = $state(false);
let flag_clear_sess: boolean = $state(false);
let flag_reload: boolean = $state(false);
let flag_new_ver: boolean = $state(false);
let flag_expired: boolean = $state(false);
let flag_denied: boolean = $state(false);
// Connection Status
let is_offline = $derived(browser && online.current === false);
let api_unreachable = $derived($ae_loc?.account_id === 'ghost');
let api_error_msg = $derived($ae_loc?.account_name || 'API Server Unreachable');
let show_connection_details = $state(true);
let last_reload_time = 0;
// 1. CONSOLIDATED SYNC EFFECT (One single point of entry for store updates)
$effect(() => {
if (!browser || !ae_acct) return;
untrack(() => {
if (log_lvl > 1) console.log("ROOT: Running Sync Effect");
// A. Sync Global Stores
const current_api = $ae_api;
const new_api = { ...current_api, ...(ae_acct.api || {}) };
// Deep check for JWT specifically to avoid extra triggers
if (current_api.jwt !== $ae_loc.jwt) new_api.jwt = $ae_loc.jwt;
if (JSON.stringify(current_api) !== JSON.stringify(new_api)) {
$ae_api = new_api;
}
const current_loc = $ae_loc;
const new_loc = { ...current_loc, ...(ae_acct.loc || {}) };
// Restore structural integrity if clobbered
if (!new_loc.sys_menu) new_loc.sys_menu = { hide: false, hide_access_type: false, expand_access_type: false, hide_edit_mode: false, expand_edit_mode: false, hide_user: false, expand_user: false, hide_theme: false, expand_theme: false, hide_cfg: false, expand_cfg: false };
if (!new_loc.debug_menu) new_loc.debug_menu = { hide: false, expand: false };
if (JSON.stringify(current_loc) !== JSON.stringify(new_loc)) {
$ae_loc = new_loc;
}
const current_slct = $slct;
const new_slct = { ...current_slct, ...(ae_acct.slct || {}) };
if (JSON.stringify(current_slct) !== JSON.stringify(new_slct)) {
$slct = new_slct;
}
// B. Version & Sanity Check
if (new_loc.ver && $ae_sess.ver && new_loc.ver !== $ae_sess.ver) {
if (!flag_new_ver) {
console.log("ROOT: Version mismatch detected");
flag_new_ver = true;
flag_reload = true;
}
}
// C. Access Check (Idempotent)
if (new_loc.site_access_key || new_loc.site_domain_access_key) {
const key = new_loc.allow_access;
const match = (new_loc.site_access_key === key || new_loc.site_domain_access_key === key);
if (!match && !new_loc.trusted_access) {
if (new_loc.allow_access !== false) $ae_loc.allow_access = false;
if (!flag_denied) flag_denied = true;
}
} else if (new_loc.allow_access !== true) {
$ae_loc.allow_access = true;
}
// D. Theme Initialization (Once)
if (!current_loc.theme_mode) {
if (window.matchMedia('(prefers-color-scheme: dark)').matches) $ae_loc.theme_mode = 'dark';
else $ae_loc.theme_mode = 'light';
}
});
});
// 2. RELOAD THROTTLE EFFECT
$effect(() => {
if (!browser) return;
if (flag_reload) {
untrack(() => {
const now = Date.now();
if (now - last_reload_time < 10000) {
console.warn("ROOT: Critical loop prevention - reload suppressed");
flag_reload = false;
return;
}
last_reload_time = now;
flag_reload = false;
if (flag_clear_local) clear_local();
if (flag_clear_sess) clear_sess();
console.log("ROOT: Executing throttled reload");
invalidateAll();
});
}
});
// 3. UI STATE & THEME DOM EFFECT
let is_hydrating = $state(true);
$effect(() => {
if (!browser) return;
// Theme DOM update
document.documentElement.setAttribute('data-theme', $ae_loc?.theme_name ?? 'nouveau');
// Hydration overlay timer
if ($ae_loc?.account_id) {
const timer = setTimeout(() => is_hydrating = false, 500);
return () => clearTimeout(timer);
}
});
function clear_idb() {
indexedDB.deleteDatabase('ae_archives_db');
indexedDB.deleteDatabase('ae_core_db');
indexedDB.deleteDatabase('ae_events_db');
indexedDB.deleteDatabase('ae_journals_db');
indexedDB.deleteDatabase('ae_posts_db');
indexedDB.deleteDatabase('ae_sponsorships_db');
}
function clear_local() {
localStorage.clear();
}
function clear_sess() {
sessionStorage.clear();
}
// 4. EXTERNAL INTERFACES EFFECT
$effect(() => {
if (!browser) return;
// Save DS to local
let ae_ds = ae_acct?.ds;
if (ae_ds) {
for (let [key, value] of Object.entries(ae_ds)) {
localStorage.setItem(`ae_ds__${key}`, JSON.stringify(value));
}
}
// Message Bridge
const handler = (event: MessageEvent) => {
if (event.data.type === 'api_download_blob') {
$ae_sess.api_download_kv[event.data.task_id] = { ...event.data };
} else if (event.data.type === 'api_post_json_form') {
$ae_sess.api_upload_kv[event.data.task_id] = { ...event.data };
}
};
window.addEventListener('message', handler);
// Iframe Detection
let iframe = data.url.searchParams.get('iframe');
if (iframe === 'true') $ae_loc.iframe = true;
else if (iframe === 'false') $ae_loc.iframe = false;
// Electron Detection
if ((window as any).native_app || (window as any).aetherNative) {
if (!$ae_loc.is_native) $ae_loc.is_native = true;
}
return () => window.removeEventListener('message', handler);
});
</script>
<svelte:head>
<link rel="stylesheet" href={ae_acct?.loc?.site_style_href} />
</svelte:head>
{#if browser && (is_offline || api_unreachable)}
<div class="fixed top-0 left-0 right-0 z-100 p-4 bg-orange-600/90 text-white text-center shadow-2xl flex items-center justify-center gap-4">
<span class="text-xl font-bold">{is_offline ? 'Offline' : api_error_msg}</span>
<button class="btn btn-sm variant-filled-white text-orange-600" onclick={() => window.location.reload()}>Retry</button>
</div>
{/if}
{#if browser && $ae_loc?.allow_access}
{@render children?.()}
{#if is_hydrating}
<div class="fixed inset-0 z-99 flex flex-col items-center justify-center bg-surface-50/80 dark:bg-surface-900/80 backdrop-blur-sm transition-opacity duration-500">
<div class="preset-filled-surface-100-900 p-8 rounded-2xl shadow-2xl flex flex-col items-center gap-4">
<span class="fas fa-cog fa-spin text-5xl text-primary-500"></span>
<div class="text-center font-bold text-xl">Hydrating Aether...</div>
</div>
</div>
{/if}
{:else if browser && flag_denied}
<div class="flex flex-col items-center justify-center h-screen gap-6 p-8">
<h1 class="text-4xl font-black text-error-500">Access Denied</h1>
<button class="btn variant-filled-primary" onclick={() => window.location.reload()}>Reload</button>
</div>
{:else if browser}
<div class="flex items-center justify-center h-screen"><span class="fas fa-spinner fa-spin text-4xl opacity-20"></span></div>
{/if}
{#if browser && (!$ae_loc?.iframe || $ae_loc?.trusted_access)}
<E_app_sys_menu {data} bind:hide={$ae_loc.sys_menu.hide} bind:expand={$ae_sess.sys_menu.expand} />
<!-- You must be in Edit Mode to initially see the Debug expand button. Once expanded, you can toggle the Edit Mode while still seeing the expanded Debug content. -->
{#if $ae_loc.edit_mode || $ae_loc.debug_menu.expand}
<E_app_debug_menu bind:hide={$ae_loc.debug_menu.hide} bind:expand={$ae_loc.debug_menu.expand} />
{/if}
{/if}