2026-01-17 21:49:22 +13:00
|
|
|
<script>
|
2026-01-18 23:01:03 +13:00
|
|
|
import { onMount } from "svelte";
|
|
|
|
|
import Footer from "./components/Footer.svelte";
|
|
|
|
|
import AppSidebar from "./components/AppSidebar.svelte";
|
|
|
|
|
import * as Sidebar from "./lib/components/ui/sidebar";
|
|
|
|
|
import { Button } from "./lib/components/ui/button";
|
|
|
|
|
import SettingsPanel from "./components/SettingsPanel.svelte";
|
2026-01-18 23:18:38 +13:00
|
|
|
import ScanPanel from "./components/scan/ScanPanel.svelte";
|
2026-01-18 23:01:03 +13:00
|
|
|
import HistoryPanel from "./components/HistoryPanel.svelte";
|
2026-01-18 23:18:38 +13:00
|
|
|
import LibraryPanel from "./components/library/LibraryPanel.svelte";
|
2026-01-19 02:10:08 +13:00
|
|
|
import AutomationList from "./routes/automation/AutomationList.svelte";
|
2026-02-28 21:48:25 +13:00
|
|
|
import { Menu, AlertTriangle, AlertCircle } from "lucide-svelte";
|
2026-01-18 23:01:03 +13:00
|
|
|
import ToastHost from "./components/ToastHost.svelte";
|
|
|
|
|
import { healthCheck } from "./lib/api.js";
|
|
|
|
|
import { currentTheme, themes } from "./lib/themeStore.js";
|
|
|
|
|
|
|
|
|
|
let currentView = "scanner";
|
|
|
|
|
let apiConfigured = false;
|
2026-01-19 02:10:08 +13:00
|
|
|
let apiReachable = true;
|
|
|
|
|
let apiErrorMessage = "";
|
2026-01-18 23:01:03 +13:00
|
|
|
let selectedFiles = [];
|
|
|
|
|
let metadataProvider = "omdb";
|
|
|
|
|
let scanPanelKey = 0;
|
|
|
|
|
let sidebarOpen = true;
|
|
|
|
|
let sidebarCollapsed = false;
|
|
|
|
|
let isMobile = false;
|
2026-01-17 21:49:22 +13:00
|
|
|
|
|
|
|
|
// Apply theme on mount and when it changes
|
|
|
|
|
function applyTheme(themeName) {
|
2026-01-18 23:01:03 +13:00
|
|
|
const theme = themes[themeName];
|
|
|
|
|
if (!theme) return;
|
2026-01-17 21:49:22 +13:00
|
|
|
|
2026-01-18 23:01:03 +13:00
|
|
|
const root = document.documentElement;
|
2026-01-17 21:49:22 +13:00
|
|
|
Object.entries(theme.colors).forEach(([key, value]) => {
|
2026-01-18 23:01:03 +13:00
|
|
|
root.style.setProperty(`--${key}`, value);
|
|
|
|
|
});
|
2026-01-17 21:49:22 +13:00
|
|
|
|
2026-01-18 23:01:03 +13:00
|
|
|
if (themeName === "light") {
|
|
|
|
|
root.classList.add("light-theme");
|
2026-01-17 21:49:22 +13:00
|
|
|
} else {
|
2026-01-18 23:01:03 +13:00
|
|
|
root.classList.remove("light-theme");
|
2026-01-17 21:49:22 +13:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function updateLayout() {
|
2026-01-18 23:01:03 +13:00
|
|
|
isMobile = window.innerWidth < 768;
|
2026-01-17 21:49:22 +13:00
|
|
|
if (isMobile) {
|
2026-01-18 23:01:03 +13:00
|
|
|
sidebarOpen = false;
|
2026-01-17 21:49:22 +13:00
|
|
|
} else {
|
2026-01-18 23:01:03 +13:00
|
|
|
sidebarOpen = true;
|
2026-01-17 21:49:22 +13:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onMount(async () => {
|
2026-01-18 23:01:03 +13:00
|
|
|
updateLayout();
|
|
|
|
|
const onResize = () => updateLayout();
|
|
|
|
|
window.addEventListener("resize", onResize);
|
2026-01-17 21:49:22 +13:00
|
|
|
|
|
|
|
|
// Initialize theme
|
2026-01-18 23:01:03 +13:00
|
|
|
applyTheme($currentTheme);
|
2026-01-17 21:49:22 +13:00
|
|
|
|
|
|
|
|
try {
|
2026-01-18 23:01:03 +13:00
|
|
|
const health = await healthCheck();
|
|
|
|
|
apiConfigured = health.api_key_configured;
|
2026-01-19 02:10:08 +13:00
|
|
|
apiReachable = true;
|
|
|
|
|
apiErrorMessage = "";
|
2026-01-17 21:49:22 +13:00
|
|
|
} catch (err) {
|
2026-01-18 23:01:03 +13:00
|
|
|
console.error("Health check failed:", err);
|
2026-01-19 02:10:08 +13:00
|
|
|
apiReachable = false;
|
|
|
|
|
apiErrorMessage = "Backend unreachable. Start the server to use Sublogue.";
|
2026-01-17 21:49:22 +13:00
|
|
|
}
|
|
|
|
|
return () => {
|
2026-01-18 23:01:03 +13:00
|
|
|
window.removeEventListener("resize", onResize);
|
|
|
|
|
};
|
|
|
|
|
});
|
2026-01-17 21:49:22 +13:00
|
|
|
|
|
|
|
|
// Watch for theme changes
|
|
|
|
|
$: if ($currentTheme) {
|
2026-01-18 23:01:03 +13:00
|
|
|
applyTheme($currentTheme);
|
2026-01-17 21:49:22 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function checkApiStatus() {
|
|
|
|
|
try {
|
2026-01-18 23:01:03 +13:00
|
|
|
const health = await healthCheck();
|
|
|
|
|
apiConfigured = health.api_key_configured;
|
2026-01-19 02:10:08 +13:00
|
|
|
apiReachable = true;
|
|
|
|
|
apiErrorMessage = "";
|
2026-01-17 21:49:22 +13:00
|
|
|
} catch (err) {
|
2026-01-18 23:01:03 +13:00
|
|
|
console.error("Health check failed:", err);
|
2026-01-19 02:10:08 +13:00
|
|
|
apiReachable = false;
|
|
|
|
|
apiErrorMessage = "Backend unreachable. Start the server to use Sublogue.";
|
2026-01-17 21:49:22 +13:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function navigateTo(view) {
|
2026-01-18 23:01:03 +13:00
|
|
|
currentView = view;
|
2026-01-17 21:49:22 +13:00
|
|
|
// Re-check API status when navigating to scanner (in case settings were just saved)
|
2026-01-18 23:01:03 +13:00
|
|
|
if (view === "scanner") {
|
|
|
|
|
await checkApiStatus();
|
2026-01-17 21:49:22 +13:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleProcessComplete() {
|
2026-01-18 23:01:03 +13:00
|
|
|
scanPanelKey += 1;
|
2026-01-17 21:49:22 +13:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleSidebarToggle() {
|
|
|
|
|
if (isMobile) {
|
2026-01-18 23:01:03 +13:00
|
|
|
sidebarOpen = !sidebarOpen;
|
2026-01-17 21:49:22 +13:00
|
|
|
} else {
|
2026-01-18 23:01:03 +13:00
|
|
|
sidebarCollapsed = !sidebarCollapsed;
|
2026-01-17 21:49:22 +13:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
$: sidebarWidth = isMobile
|
2026-01-18 23:01:03 +13:00
|
|
|
? "15.5rem"
|
2026-01-17 21:49:22 +13:00
|
|
|
: sidebarCollapsed
|
2026-01-18 23:01:03 +13:00
|
|
|
? "3.75rem"
|
|
|
|
|
: "16rem";
|
2026-01-17 21:49:22 +13:00
|
|
|
</script>
|
|
|
|
|
|
2026-01-18 23:01:03 +13:00
|
|
|
<Sidebar.Provider
|
|
|
|
|
style={`--sidebar-width: ${sidebarWidth}; --header-height: 4rem;`}
|
|
|
|
|
>
|
2026-01-17 21:49:22 +13:00
|
|
|
{#if isMobile && sidebarOpen}
|
|
|
|
|
<div
|
2026-02-28 21:48:25 +13:00
|
|
|
class="fixed inset-0 z-30 bg-black/50 backdrop-blur-sm"
|
2026-01-17 21:49:22 +13:00
|
|
|
on:click={() => (sidebarOpen = false)}
|
|
|
|
|
aria-hidden="true"
|
|
|
|
|
></div>
|
|
|
|
|
{/if}
|
|
|
|
|
<AppSidebar
|
|
|
|
|
{currentView}
|
|
|
|
|
onNavigate={navigateTo}
|
|
|
|
|
onToggleSidebar={handleSidebarToggle}
|
|
|
|
|
open={isMobile ? sidebarOpen : true}
|
|
|
|
|
collapsed={!isMobile && sidebarCollapsed}
|
|
|
|
|
{isMobile}
|
|
|
|
|
/>
|
|
|
|
|
<Sidebar.Inset>
|
|
|
|
|
<!-- Main Content -->
|
2026-02-28 21:48:25 +13:00
|
|
|
<main class="flex-1 min-h-0">
|
2026-01-17 21:49:22 +13:00
|
|
|
{#if isMobile && !sidebarOpen}
|
2026-02-28 21:48:25 +13:00
|
|
|
<div class="px-4 pt-4">
|
2026-01-17 21:49:22 +13:00
|
|
|
<Button
|
|
|
|
|
variant="outline"
|
|
|
|
|
size="sm"
|
2026-02-28 21:48:25 +13:00
|
|
|
className="gap-2"
|
2026-01-17 21:49:22 +13:00
|
|
|
on:click={() => (sidebarOpen = true)}
|
|
|
|
|
aria-label="Show sidebar"
|
|
|
|
|
>
|
2026-02-28 21:48:25 +13:00
|
|
|
<Menu class="h-3.5 w-3.5" />
|
2026-01-17 21:49:22 +13:00
|
|
|
Menu
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
2026-02-28 21:48:25 +13:00
|
|
|
|
|
|
|
|
<!-- Status banners -->
|
2026-01-19 02:10:08 +13:00
|
|
|
{#if !apiReachable}
|
2026-02-28 21:48:25 +13:00
|
|
|
<div class="mx-4 sm:mx-6 md:mx-8 mt-4 flex items-start gap-3 rounded-xl border border-red-500/20 bg-red-500/8 px-4 py-3">
|
|
|
|
|
<AlertCircle class="h-4 w-4 text-red-400 mt-0.5 shrink-0" />
|
|
|
|
|
<p class="text-[13px] text-red-300 leading-relaxed">{apiErrorMessage}</p>
|
2026-01-19 02:10:08 +13:00
|
|
|
</div>
|
|
|
|
|
{:else if !apiConfigured && currentView === "scanner"}
|
2026-02-28 21:48:25 +13:00
|
|
|
<div class="mx-4 sm:mx-6 md:mx-8 mt-4 flex items-start gap-3 rounded-xl border border-yellow-500/15 bg-yellow-500/6 px-4 py-3">
|
|
|
|
|
<AlertTriangle class="h-4 w-4 text-yellow-400 mt-0.5 shrink-0" />
|
|
|
|
|
<p class="text-[13px] text-yellow-200/80 leading-relaxed">
|
|
|
|
|
Configure a metadata source in Settings › Integrations to get started
|
|
|
|
|
</p>
|
2026-01-17 21:49:22 +13:00
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
|
2026-02-28 21:48:25 +13:00
|
|
|
<div class="px-4 sm:px-6 md:px-8 py-7 sm:py-9 md:py-10">
|
2026-01-18 23:01:03 +13:00
|
|
|
{#if currentView === "settings"}
|
2026-01-17 21:49:22 +13:00
|
|
|
<SettingsPanel />
|
2026-01-18 23:01:03 +13:00
|
|
|
{:else if currentView === "history"}
|
2026-01-17 21:49:22 +13:00
|
|
|
<HistoryPanel />
|
2026-01-18 23:01:03 +13:00
|
|
|
{:else if currentView === "scanner"}
|
2026-01-17 21:49:22 +13:00
|
|
|
{#key scanPanelKey}
|
|
|
|
|
<ScanPanel
|
|
|
|
|
bind:selectedFilePaths={selectedFiles}
|
|
|
|
|
bind:metadataProvider
|
2026-01-18 23:01:03 +13:00
|
|
|
{apiConfigured}
|
|
|
|
|
onOpenSettings={() => navigateTo("settings")}
|
|
|
|
|
onOpenHistory={() => navigateTo("history")}
|
2026-01-17 21:49:22 +13:00
|
|
|
/>
|
|
|
|
|
{/key}
|
2026-01-18 23:01:03 +13:00
|
|
|
{:else if currentView === "library"}
|
2026-01-18 22:29:51 +13:00
|
|
|
<LibraryPanel />
|
2026-01-19 02:10:08 +13:00
|
|
|
{:else if currentView === "automation"}
|
|
|
|
|
<AutomationList />
|
2026-01-17 21:49:22 +13:00
|
|
|
{/if}
|
|
|
|
|
</div>
|
|
|
|
|
</main>
|
|
|
|
|
|
|
|
|
|
<Footer />
|
|
|
|
|
</Sidebar.Inset>
|
|
|
|
|
<ToastHost />
|
|
|
|
|
</Sidebar.Provider>
|