diff --git a/frontend/src/app.html b/frontend/src/app.html index 6769ed5..ca08e96 100644 --- a/frontend/src/app.html +++ b/frontend/src/app.html @@ -2,7 +2,8 @@ - + + %sveltekit.head% diff --git a/frontend/src/lib/components/layout/Breadcrumbs.svelte b/frontend/src/lib/components/layout/Breadcrumbs.svelte index c224c48..79eb7e2 100644 --- a/frontend/src/lib/components/layout/Breadcrumbs.svelte +++ b/frontend/src/lib/components/layout/Breadcrumbs.svelte @@ -14,6 +14,7 @@ import { page } from "$app/stores"; import { t, _ } from "$lib/i18n"; + import Icon from "$lib/ui/Icon.svelte"; let { maxVisible = 3 } = $props(); @@ -82,30 +83,103 @@ .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .join(" "); } + + function getCrumbMeta(item) { + if (item.path === "/") { + return { + icon: "home", + tone: "from-sky-100 to-cyan-100 text-sky-700 ring-sky-200", + }; + } + + const segment = item.path.split("/").filter(Boolean).at(-1) || ""; + + const map = { + dashboards: { + icon: "dashboard", + tone: "from-sky-100 to-sky-200 text-sky-700 ring-sky-200", + }, + datasets: { + icon: "database", + tone: "from-emerald-100 to-emerald-200 text-emerald-700 ring-emerald-200", + }, + storage: { + icon: "storage", + tone: "from-amber-100 to-amber-200 text-amber-800 ring-amber-200", + }, + reports: { + icon: "reports", + tone: "from-violet-100 to-fuchsia-100 text-violet-700 ring-violet-200", + }, + admin: { + icon: "admin", + tone: "from-rose-100 to-rose-200 text-rose-700 ring-rose-200", + }, + settings: { + icon: "settings", + tone: "from-slate-100 to-slate-200 text-slate-700 ring-slate-200", + }, + git: { + icon: "storage", + tone: "from-orange-100 to-orange-200 text-orange-700 ring-orange-200", + }, + }; + + return ( + map[segment] || { + icon: "layers", + tone: "from-slate-100 to-slate-200 text-slate-600 ring-slate-200", + } + ); + } diff --git a/frontend/src/lib/components/layout/Sidebar.svelte b/frontend/src/lib/components/layout/Sidebar.svelte index 6efab49..e2e27eb 100644 --- a/frontend/src/lib/components/layout/Sidebar.svelte +++ b/frontend/src/lib/components/layout/Sidebar.svelte @@ -24,59 +24,68 @@ } from "$lib/stores/sidebar.js"; import { t } from "$lib/i18n"; import { browser } from "$app/environment"; + import Icon from "$lib/ui/Icon.svelte"; - // Sidebar categories with sub-items matching Superset-style navigation - let categories = [ - { - id: "dashboards", - label: $t.nav?.dashboards || "DASHBOARDS", - icon: "M3 3h18v18H3V3zm16 16V5H5v14h14z", - path: "/dashboards", - subItems: [ - { label: $t.nav?.overview || "Overview", path: "/dashboards" }, - ], - }, - { - id: "datasets", - label: $t.nav?.datasets || "DATASETS", - icon: "M3 3h18v18H3V3zm2 2v14h14V5H5zm2 2h10v2H7V7zm0 4h10v2H7v-2zm0 4h6v2H7v-2z", - path: "/datasets", - subItems: [ - { label: $t.nav?.all_datasets || "All Datasets", path: "/datasets" }, - ], - }, - { - id: "storage", - label: $t.nav?.storage || "STORAGE", - icon: "M4 4h16v16H4V4zm2 2v12h12V6H6zm2 2h8v2H8V8zm0 4h8v2H8v-2zm0 4h5v2H8v-2z", - path: "/storage", - subItems: [ - { label: $t.nav?.backups || "Backups", path: "/storage/backups" }, - { - label: $t.nav?.repositories || "Repositories", - path: "/storage/repos", - }, - ], - }, - { - id: "reports", - label: $t.nav?.reports || "REPORTS", - icon: "M4 5h16M4 12h16M4 19h10", - path: "/reports", - subItems: [{ label: $t.nav?.reports || "Reports", path: "/reports" }], - }, - { - id: "admin", - label: $t.nav?.admin || "ADMIN", - icon: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z", - path: "/admin", - subItems: [ - { label: $t.nav?.admin_users || "Users", path: "/admin/users" }, - { label: $t.nav?.admin_roles || "Roles", path: "/admin/roles" }, - { label: $t.nav?.settings || "Settings", path: "/settings" }, - ], - }, - ]; + function buildCategories() { + return [ + { + id: "dashboards", + label: $t.nav?.dashboards || "DASHBOARDS", + icon: "dashboard", + tone: "from-sky-100 to-sky-200 text-sky-700 ring-sky-200", + path: "/dashboards", + subItems: [ + { label: $t.nav?.overview || "Overview", path: "/dashboards" }, + ], + }, + { + id: "datasets", + label: $t.nav?.datasets || "DATASETS", + icon: "database", + tone: "from-emerald-100 to-emerald-200 text-emerald-700 ring-emerald-200", + path: "/datasets", + subItems: [ + { label: $t.nav?.all_datasets || "All Datasets", path: "/datasets" }, + ], + }, + { + id: "storage", + label: $t.nav?.storage || "STORAGE", + icon: "storage", + tone: "from-amber-100 to-amber-200 text-amber-800 ring-amber-200", + path: "/storage", + subItems: [ + { label: $t.nav?.backups || "Backups", path: "/storage/backups" }, + { + label: $t.nav?.repositories || "Repositories", + path: "/storage/repos", + }, + ], + }, + { + id: "reports", + label: $t.nav?.reports || "REPORTS", + icon: "reports", + tone: "from-violet-100 to-fuchsia-100 text-violet-700 ring-violet-200", + path: "/reports", + subItems: [{ label: $t.nav?.reports || "Reports", path: "/reports" }], + }, + { + id: "admin", + label: $t.nav?.admin || "ADMIN", + icon: "admin", + tone: "from-rose-100 to-rose-200 text-rose-700 ring-rose-200", + path: "/admin", + subItems: [ + { label: $t.nav?.admin_users || "Users", path: "/admin/users" }, + { label: $t.nav?.admin_roles || "Roles", path: "/admin/roles" }, + { label: $t.nav?.settings || "Settings", path: "/settings" }, + ], + }, + ]; + } + + let categories = buildCategories(); let isExpanded = true; let activeCategory = "dashboards"; @@ -93,57 +102,7 @@ } // Reactive categories to update translations - $: categories = [ - { - id: "dashboards", - label: $t.nav?.dashboards || "DASHBOARDS", - icon: "M3 3h18v18H3V3zm16 16V5H5v14h14z", - path: "/dashboards", - subItems: [ - { label: $t.nav?.overview || "Overview", path: "/dashboards" }, - ], - }, - { - id: "datasets", - label: $t.nav?.datasets || "DATASETS", - icon: "M3 3h18v18H3V3zm2 2v14h14V5H5zm2 2h10v2H7V7zm0 4h10v2H7v-2zm0 4h6v2H7v-2z", - path: "/datasets", - subItems: [ - { label: $t.nav?.all_datasets || "All Datasets", path: "/datasets" }, - ], - }, - { - id: "storage", - label: $t.nav?.storage || "STORAGE", - icon: "M4 4h16v16H4V4zm2 2v12h12V6H6zm2 2h8v2H8V8zm0 4h8v2H8v-2zm0 4h5v2H8v-2z", - path: "/storage", - subItems: [ - { label: $t.nav?.backups || "Backups", path: "/storage/backups" }, - { - label: $t.nav?.repositories || "Repositories", - path: "/storage/repos", - }, - ], - }, - { - id: "reports", - label: $t.nav?.reports || "REPORTS", - icon: "M4 5h16M4 12h16M4 19h10", - path: "/reports", - subItems: [{ label: $t.nav?.reports || "Reports", path: "/reports" }], - }, - { - id: "admin", - label: $t.nav?.admin || "ADMIN", - icon: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 3c1.66 0 3 1.34 3 3s-1.34 3-3 3-3-1.34-3-3 1.34-3 3-3zm0 14.2c-2.5 0-4.71-1.28-6-3.22.03-1.99 4-3.08 6-3.08 1.99 0 5.97 1.09 6 3.08-1.29 1.94-3.5 3.22-6 3.22z", - path: "/admin", - subItems: [ - { label: $t.nav?.admin_users || "Users", path: "/admin/users" }, - { label: $t.nav?.admin_roles || "Roles", path: "/admin/roles" }, - { label: $t.nav?.settings || "Settings", path: "/settings" }, - ], - }, - ]; + $: categories = buildCategories(); // Update active item when page changes $: if ($page && $page.url.pathname !== activeItem) { @@ -238,7 +197,12 @@ : 'justify-center'}" > {#if isExpanded} - Menu + + + + + Menu + {:else} M {/if} @@ -264,16 +228,9 @@ aria-expanded={expandedCategories.has(category.id)} >
- - - + + + {#if isExpanded} {category.label} {#if isExpanded} - - - + /> {/if}
@@ -332,18 +282,9 @@ class="flex items-center justify-center w-full px-4 py-2 text-sm text-gray-600 hover:bg-gray-100 rounded-lg transition-colors" on:click={handleToggleClick} > - - - + + + Collapse @@ -354,17 +295,7 @@ on:click={handleToggleClick} aria-label="Expand sidebar" > - - - + Expand diff --git a/frontend/src/lib/components/layout/TaskDrawer.svelte b/frontend/src/lib/components/layout/TaskDrawer.svelte index cbe9e4b..ce2cd71 100644 --- a/frontend/src/lib/components/layout/TaskDrawer.svelte +++ b/frontend/src/lib/components/layout/TaskDrawer.svelte @@ -24,6 +24,7 @@ import PasswordPrompt from "../../../components/PasswordPrompt.svelte"; import { t } from "$lib/i18n"; import { api } from "$lib/api.js"; + import Icon from "$lib/ui/Icon.svelte"; let isOpen = false; let activeTaskId = null; @@ -209,9 +210,7 @@
{#if !activeTaskId && recentTasks.length > 0} - - - + {:else if activeTaskId} {/if}

@@ -256,17 +245,7 @@ on:click={handleClose} aria-label="Close drawer" > - - - +

@@ -301,18 +280,12 @@ {:else}
- - - +

{$t.tasks?.select_task || 'No recent tasks'}

{/if} @@ -330,4 +303,3 @@ {/if} - diff --git a/frontend/src/lib/components/layout/TopNavbar.svelte b/frontend/src/lib/components/layout/TopNavbar.svelte index 6bf280c..b086d0c 100644 --- a/frontend/src/lib/components/layout/TopNavbar.svelte +++ b/frontend/src/lib/components/layout/TopNavbar.svelte @@ -25,6 +25,7 @@ import { sidebarStore, toggleMobileSidebar } from "$lib/stores/sidebar.js"; import { t } from "$lib/i18n"; import { auth } from "$lib/auth/store.js"; + import Icon from "$lib/ui/Icon.svelte"; const dispatch = createEventDispatcher(); @@ -99,19 +100,7 @@ on:click={handleHamburgerClick} aria-label="Toggle menu" > - - - - - + @@ -119,14 +108,9 @@ href="/" class="flex items-center text-xl font-bold text-gray-800 hover:text-primary transition-colors" > - - - + + + Superset Tools @@ -147,7 +131,7 @@
(e.key === "Enter" || e.key === " ") && handleActivityClick()} @@ -155,18 +139,7 @@ tabindex="0" aria-label="Activity" > - - - + {#if activeCount > 0} + export let name = "circle"; + export let size = 20; + export let className = ""; + export let strokeWidth = 1.9; + + const iconPaths = { + home: ["M3 11l9-7 9 7", "M5 10v9h14v-9", "M10 19v-5h4v5"], + dashboard: ["M4 4h16v16H4z", "M4 10h16", "M10 4v16"], + database: [ + "M4 7c0-1.7 3.6-3 8-3s8 1.3 8 3-3.6 3-8 3-8-1.3-8-3z", + "M4 12c0 1.7 3.6 3 8 3s8-1.3 8-3", + "M4 17c0 1.7 3.6 3 8 3s8-1.3 8-3", + "M4 7v10", + "M20 7v10", + ], + storage: [ + "M3 8l9-4 9 4-9 4-9-4z", + "M3 13l9 4 9-4", + "M3 17l9 4 9-4", + ], + reports: ["M5 5h14v14H5z", "M8 9h8", "M8 13h8", "M8 17h5"], + admin: ["M12 3l8 4v5c0 5.2-3.4 8.6-8 9.9C7.4 20.6 4 17.2 4 12V7l8-4z", "M9 12l2 2 4-4"], + chevronDown: ["M6 9l6 6 6-6"], + chevronLeft: ["M15 6l-6 6 6 6"], + chevronRight: ["M9 6l6 6-6 6"], + menu: ["M4 7h16", "M4 12h16", "M4 17h16"], + activity: [ + "M12 3v3", + "M12 18v3", + "M4.9 4.9l2.1 2.1", + "M17 17l2.1 2.1", + "M3 12h3", + "M18 12h3", + "M4.9 19.1L7 17", + "M17 7l2.1-2.1", + "M12 15a3 3 0 100-6 3 3 0 000 6z", + ], + layers: ["M12 4l8 4-8 4-8-4 8-4z", "M4 12l8 4 8-4", "M4 16l8 4 8-4"], + back: ["M19 12H5", "M12 5l-7 7 7 7"], + close: ["M18 6L6 18", "M6 6l12 12"], + list: ["M8 7h12", "M8 12h12", "M8 17h12", "M4 7h.01", "M4 12h.01", "M4 17h.01"], + clipboard: ["M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2", "M9 5a2 2 0 002 2h2a2 2 0 002-2", "M9 5a2 2 0 012-2h2a2 2 0 012 2"], + settings: ["M12 8.5a3.5 3.5 0 100 7 3.5 3.5 0 000-7z", "M19.4 15a1 1 0 00.2 1.1l.1.1a1 1 0 010 1.4l-1.1 1.1a1 1 0 01-1.4 0l-.1-.1a1 1 0 00-1.1-.2 1 1 0 00-.6.9V20a1 1 0 01-1 1h-1.6a1 1 0 01-1-1v-.2a1 1 0 00-.6-.9 1 1 0 00-1.1.2l-.1.1a1 1 0 01-1.4 0l-1.1-1.1a1 1 0 010-1.4l.1-.1a1 1 0 00.2-1.1 1 1 0 00-.9-.6H4a1 1 0 01-1-1v-1.6a1 1 0 011-1h.2a1 1 0 00.9-.6 1 1 0 00-.2-1.1l-.1-.1a1 1 0 010-1.4l1.1-1.1a1 1 0 011.4 0l.1.1a1 1 0 001.1.2 1 1 0 00.6-.9V4a1 1 0 011-1h1.6a1 1 0 011 1v.2a1 1 0 00.6.9 1 1 0 001.1-.2l.1-.1a1 1 0 011.4 0l1.1 1.1a1 1 0 010 1.4l-.1.1a1 1 0 00-.2 1.1 1 1 0 00.9.6H20a1 1 0 011 1v1.6a1 1 0 01-1 1h-.2a1 1 0 00-.9.6z"], + }; + + $: paths = iconPaths[name] || iconPaths.dashboard; + + + diff --git a/frontend/src/routes/+layout.svelte b/frontend/src/routes/+layout.svelte index 30095de..ecbce28 100644 --- a/frontend/src/routes/+layout.svelte +++ b/frontend/src/routes/+layout.svelte @@ -56,7 +56,7 @@ -
+
diff --git a/frontend/static/favicon.svg b/frontend/static/favicon.svg new file mode 100644 index 0000000..096e980 --- /dev/null +++ b/frontend/static/favicon.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + +