mirror of
https://github.com/moku-project/Moku.git
synced 2026-06-13 01:09:56 -05:00
218 lines
6.0 KiB
Svelte
218 lines
6.0 KiB
Svelte
<script lang="ts">
|
|
import { onMount } from 'svelte'
|
|
import { page } from '$app/stores'
|
|
import { appState, app } from '$lib/state/app.svelte'
|
|
import { notifications } from '$lib/state/notifications.svelte'
|
|
import { settingsState, loadSettingsIntoState, updateSettings } from '$lib/state/settings.svelte'
|
|
import { applyTheme, mountSystemThemeSync } from '$lib/core/theme'
|
|
import { platformService } from '$lib/platform-service'
|
|
import * as discord from '$lib/core/discord'
|
|
import SplashScreen from '$lib/components/chrome/SplashScreen.svelte'
|
|
import AuthGate from '$lib/components/chrome/AuthGate.svelte'
|
|
import Sidebar from '$lib/components/chrome/Sidebar.svelte'
|
|
import TitleBar from '$lib/components/chrome/TitleBar.svelte'
|
|
import Toaster from '$lib/components/chrome/Toaster.svelte'
|
|
import Settings from '$lib/components/settings/Settings.svelte'
|
|
import ThemeEditor from '$lib/components/settings/ThemeEditor.svelte'
|
|
import { downloadStore } from '$lib/state/downloads.svelte'
|
|
import { seriesState } from '$lib/state/series.svelte'
|
|
import MangaPreview from '$lib/components/shared/manga/MangaPreview.svelte'
|
|
import '../app.css'
|
|
|
|
let { children } = $props()
|
|
|
|
const POLL_MS = 1500
|
|
let pollTimer: ReturnType<typeof setTimeout> | null = null
|
|
let polling = false
|
|
|
|
async function pollLoop() {
|
|
if (!polling) return
|
|
await downloadStore.poll()
|
|
if (polling) pollTimer = setTimeout(pollLoop, POLL_MS)
|
|
}
|
|
|
|
const isTauri = typeof window !== 'undefined' && '__TAURI_INTERNALS__' in window
|
|
const ringFull = $derived(appState.status === 'ready' || appState.status === 'auth')
|
|
|
|
let splashVisible = $state(true)
|
|
let bypassed = $state(false)
|
|
let themeEditorOpen = $state(false)
|
|
let themeEditorId = $state<string | null>(null)
|
|
|
|
const showApp = $derived(
|
|
appState.status === 'ready' ||
|
|
appState.status === 'auth' ||
|
|
bypassed
|
|
)
|
|
|
|
const isReaderRoute = $derived($page.url.pathname.startsWith('/reader'))
|
|
const readerContainerized = $derived(settingsState.settings.readerContainerized ?? false)
|
|
const strippedLayout = $derived(isReaderRoute && !readerContainerized)
|
|
|
|
onMount(async () => {
|
|
if (isTauri && settingsState.settings.autoStartServer) {
|
|
const { startProbe } = await import('$lib/state/boot.svelte')
|
|
|
|
platformService.launchServer({
|
|
binary: settingsState.settings.serverBinary,
|
|
binaryArgs: settingsState.settings.serverBinaryArgs,
|
|
webUiEnabled: settingsState.settings.suwayomiWebUI,
|
|
}).catch(() => {})
|
|
|
|
startProbe(
|
|
appState.authMode ?? 'NONE',
|
|
appState.authUser ?? '',
|
|
appState.authPass ?? '',
|
|
2000,
|
|
)
|
|
}
|
|
|
|
if (settingsState.settings.discordRpc) {
|
|
await discord.initRpc()
|
|
await discord.setIdle()
|
|
}
|
|
|
|
polling = true
|
|
pollLoop()
|
|
|
|
applyTheme(
|
|
settingsState.settings.theme ?? 'dark',
|
|
settingsState.settings.customThemes ?? []
|
|
)
|
|
|
|
return () => {
|
|
polling = false
|
|
if (pollTimer !== null) { clearTimeout(pollTimer); pollTimer = null }
|
|
discord.destroyRpc()
|
|
platformService.destroy()
|
|
}
|
|
})
|
|
|
|
$effect(() => {
|
|
document.documentElement.style.zoom = String(settingsState.settings.uiZoom ?? 1.0)
|
|
})
|
|
|
|
$effect(() => {
|
|
const theme = settingsState.settings.theme ?? 'dark'
|
|
const customThemes = settingsState.settings.customThemes ?? []
|
|
applyTheme(theme, customThemes)
|
|
})
|
|
|
|
$effect(() => {
|
|
const enabled = settingsState.settings.systemThemeSync ?? false
|
|
const darkTheme = settingsState.settings.systemThemeDark ?? 'dark'
|
|
const lightTheme = settingsState.settings.systemThemeLight ?? 'light'
|
|
mountSystemThemeSync(enabled, darkTheme, lightTheme, (id) => updateSettings({ theme: id }))
|
|
})
|
|
|
|
function onSplashReady() { splashVisible = false }
|
|
function onSplashBypass() { bypassed = true; splashVisible = false }
|
|
|
|
function openThemeEditor(id?: string | null) {
|
|
themeEditorId = id ?? null
|
|
themeEditorOpen = true
|
|
}
|
|
</script>
|
|
|
|
{#if splashVisible}
|
|
<SplashScreen
|
|
mode="loading"
|
|
{ringFull}
|
|
failed={appState.status === 'error'}
|
|
onReady={onSplashReady}
|
|
onBypass={onSplashBypass}
|
|
onRetry={() => window.location.reload()}
|
|
/>
|
|
{/if}
|
|
|
|
{#if showApp}
|
|
{#if strippedLayout}
|
|
{@render children()}
|
|
{:else}
|
|
<div class="frame">
|
|
{#if isTauri}
|
|
<TitleBar onClose={() => platformService.close()} />
|
|
{/if}
|
|
<div class="padding" class:padding-web={!isTauri}>
|
|
<div class="shell">
|
|
<div class="body">
|
|
<Sidebar />
|
|
<main class="main">
|
|
{@render children()}
|
|
</main>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
{/if}
|
|
|
|
{#if app.settingsOpen}
|
|
<Settings
|
|
onclose={() => app.setSettingsOpen(false)}
|
|
onOpenThemeEditor={openThemeEditor}
|
|
/>
|
|
{/if}
|
|
|
|
{#if themeEditorOpen}
|
|
<ThemeEditor
|
|
editingId={themeEditorId}
|
|
onClose={() => themeEditorOpen = false}
|
|
/>
|
|
{/if}
|
|
|
|
<AuthGate />
|
|
<Toaster toasts={notifications.toasts} />
|
|
{#if seriesState.previewManga}
|
|
<MangaPreview />
|
|
{/if}
|
|
|
|
<style>
|
|
.frame {
|
|
display: flex;
|
|
flex-direction: column;
|
|
width: 100%;
|
|
height: 100%;
|
|
box-sizing: border-box;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.padding {
|
|
display: flex;
|
|
flex: 1;
|
|
padding: 0 15px 15px;
|
|
min-height: 0;
|
|
}
|
|
|
|
.padding-web {
|
|
padding-top: 15px;
|
|
}
|
|
|
|
.shell {
|
|
display: flex;
|
|
flex-direction: column;
|
|
flex: 1;
|
|
border-radius: var(--radius-2xl);
|
|
overflow: hidden;
|
|
border: 1px solid var(--border-dim);
|
|
background: var(--bg-base);
|
|
min-height: 0;
|
|
min-width: 0;
|
|
}
|
|
|
|
.body {
|
|
display: flex;
|
|
flex: 1;
|
|
min-height: 0;
|
|
min-width: 0;
|
|
}
|
|
|
|
.main {
|
|
flex: 1;
|
|
overflow: hidden;
|
|
background: var(--bg-surface);
|
|
transform: translateZ(0);
|
|
contain: layout style;
|
|
min-width: 0;
|
|
}
|
|
</style>x |