feat(spa): topbar logo + 4-section sakai sidebar with primeicons
- manager.html: add favicon/apple-touch-icon/manifest link tags - AppTopbar: favicon SVG brand logo, version badge, settings cog + theme toggle - AppMenu: 4 sections (Home, Assets, Library, Get Started) with soon-disabled placeholders - AppMenuItem: 3 render branches (soon/disabled, external link, RouterLink) - vite.config.mts: fix renameManagerHtml plugin to handle Vite 5.4 output path - use import.meta.env.BASE_URL for logo src to avoid Rollup static-asset resolution
This commit is contained in:
@@ -4,6 +4,9 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Wildcard Pipeline Manager</title>
|
||||
<link rel="icon" type="image/svg+xml" href="/wp/images/favicon.svg" />
|
||||
<link rel="apple-touch-icon" href="/wp/images/apple-touch-icon.png" />
|
||||
<link rel="manifest" href="/wp/images/site.webmanifest" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
@@ -1,42 +1,74 @@
|
||||
<script setup lang="ts">
|
||||
import AppMenuItem from "./AppMenuItem.vue";
|
||||
|
||||
interface MenuItem { label: string; icon: string; to: string; }
|
||||
interface MenuSection { label: string; items: MenuItem[]; }
|
||||
interface Item {
|
||||
label: string;
|
||||
icon: string;
|
||||
to?: string;
|
||||
url?: string;
|
||||
soon?: boolean;
|
||||
}
|
||||
interface Section { label: string; items: Item[]; }
|
||||
|
||||
const sections: MenuSection[] = [
|
||||
const sections: Section[] = [
|
||||
{
|
||||
label: "Modules",
|
||||
label: "Home",
|
||||
items: [{ label: "Dashboard", icon: "pi pi-home", to: "/dashboard" }],
|
||||
},
|
||||
{
|
||||
label: "Assets",
|
||||
items: [
|
||||
{ label: "All modules", icon: "pi pi-list", to: "/modules" },
|
||||
{ label: "+ New wildcard", icon: "pi pi-plus", to: "/modules/wildcard/new" },
|
||||
{ label: "+ New fixed values", icon: "pi pi-plus", to: "/modules/fixed-values/new" },
|
||||
{ label: "Wildcards", icon: "pi pi-th-large", to: "/wildcards" },
|
||||
{ label: "Fixed Values", icon: "pi pi-tag", to: "/fixed-values" },
|
||||
{ label: "Combines", icon: "pi pi-link", soon: true },
|
||||
{ label: "Derivations", icon: "pi pi-code", soon: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Library",
|
||||
items: [
|
||||
{ label: "Categories", icon: "pi pi-tag", to: "/categories" },
|
||||
{ label: "Categories", icon: "pi pi-bookmark", to: "/categories" },
|
||||
{ label: "Import / Export", icon: "pi pi-arrow-right-arrow-left", to: "/import-export" },
|
||||
{ label: "Test runner", icon: "pi pi-bolt", to: "/test" },
|
||||
{ label: "Test Runner", icon: "pi pi-bolt", to: "/test" },
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "App",
|
||||
items: [{ label: "Settings", icon: "pi pi-cog", to: "/settings" }],
|
||||
label: "Get Started",
|
||||
items: [
|
||||
{
|
||||
label: "Documentation", icon: "pi pi-book",
|
||||
url: "https://github.com/DumiFlex/ComfyUI-WildcardPipeline/wiki",
|
||||
},
|
||||
{
|
||||
label: "View Source", icon: "pi pi-github",
|
||||
url: "https://github.com/DumiFlex/ComfyUI-WildcardPipeline",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav>
|
||||
<div v-for="section in sections" :key="section.label" class="mb-3">
|
||||
<div class="px-4 py-1 text-[10px] uppercase tracking-wider text-wp-text3 font-semibold">
|
||||
{{ section.label }}
|
||||
</div>
|
||||
<ul class="list-none p-0 m-0">
|
||||
<AppMenuItem v-for="item in section.items" :key="item.to" :item="item" />
|
||||
<nav class="app-menu">
|
||||
<div v-for="section in sections" :key="section.label" class="app-menu__section">
|
||||
<div class="app-menu__section-label">{{ section.label }}</div>
|
||||
<ul class="app-menu__list">
|
||||
<AppMenuItem v-for="item in section.items" :key="item.label" :item="item" />
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.app-menu { padding: 12px 0; }
|
||||
.app-menu__section { margin-bottom: 12px; }
|
||||
.app-menu__section-label {
|
||||
padding: 4px 16px;
|
||||
font-size: 10px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.8px;
|
||||
color: var(--wp-text3);
|
||||
font-weight: 600;
|
||||
}
|
||||
.app-menu__list { list-style: none; padding: 0; margin: 0; }
|
||||
</style>
|
||||
|
||||
@@ -2,19 +2,72 @@
|
||||
import { RouterLink } from "vue-router";
|
||||
|
||||
defineProps<{
|
||||
item: { label: string; icon: string; to: string };
|
||||
item: {
|
||||
label: string;
|
||||
icon: string;
|
||||
to?: string;
|
||||
url?: string;
|
||||
soon?: boolean;
|
||||
};
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<li>
|
||||
<RouterLink
|
||||
:to="item.to"
|
||||
class="flex items-center gap-2 px-4 py-2 text-wp-text hover:bg-wp-bg2 border-l-2 border-transparent"
|
||||
exact-active-class="!bg-wp-accent/15 !border-l-[color:var(--wp-accent)] !text-white"
|
||||
<span v-if="item.soon" class="sidebar-item sidebar-item--disabled">
|
||||
<i :class="['pi', 'pi-fw', item.icon]" aria-hidden="true" />
|
||||
<span class="sidebar-item__label">{{ item.label }}</span>
|
||||
<span class="sidebar-item__badge">soon</span>
|
||||
</span>
|
||||
<a
|
||||
v-else-if="item.url"
|
||||
:href="item.url"
|
||||
target="_blank" rel="noopener noreferrer"
|
||||
class="sidebar-item"
|
||||
>
|
||||
<i :class="['pi', item.icon, 'text-wp-text2']" aria-hidden="true" />
|
||||
<span class="text-sm">{{ item.label }}</span>
|
||||
<i :class="['pi', 'pi-fw', item.icon]" aria-hidden="true" />
|
||||
<span class="sidebar-item__label">{{ item.label }}</span>
|
||||
<i class="pi pi-external-link sidebar-item__external" aria-hidden="true" />
|
||||
</a>
|
||||
<RouterLink
|
||||
v-else-if="item.to"
|
||||
:to="item.to"
|
||||
exact-active-class="sidebar-item--active"
|
||||
class="sidebar-item"
|
||||
>
|
||||
<i :class="['pi', 'pi-fw', item.icon]" aria-hidden="true" />
|
||||
<span class="sidebar-item__label">{{ item.label }}</span>
|
||||
</RouterLink>
|
||||
</li>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.sidebar-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 8px 16px;
|
||||
color: var(--wp-text);
|
||||
text-decoration: none;
|
||||
border-left: 2px solid transparent;
|
||||
font-size: 13px;
|
||||
}
|
||||
.sidebar-item:hover { background: var(--wp-bg2); }
|
||||
.sidebar-item--active {
|
||||
background: var(--wp-accent-glow);
|
||||
border-left-color: var(--wp-accent);
|
||||
color: #fff;
|
||||
}
|
||||
.sidebar-item--active :deep(.pi) { color: var(--wp-accent); }
|
||||
.sidebar-item__label { flex: 1; }
|
||||
.sidebar-item__badge {
|
||||
font-size: 9.5px;
|
||||
padding: 1px 5px;
|
||||
background: var(--wp-bg3);
|
||||
color: var(--wp-text3);
|
||||
border-radius: 3px;
|
||||
}
|
||||
.sidebar-item__external { font-size: 10px; color: var(--wp-text3); }
|
||||
.sidebar-item--disabled { opacity: 0.5; cursor: not-allowed; pointer-events: none; }
|
||||
.sidebar-item :deep(.pi) { color: var(--wp-text2); }
|
||||
</style>
|
||||
|
||||
@@ -1,39 +1,67 @@
|
||||
<script setup lang="ts">
|
||||
import Button from "primevue/button";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useUiStore } from "../stores/uiStore";
|
||||
|
||||
const ui = useUiStore();
|
||||
const router = useRouter();
|
||||
const version = "1.4.0-dev";
|
||||
const logoSrc = `${import.meta.env.BASE_URL}images/favicon.svg`;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header class="h-12 px-4 flex items-center justify-between bg-wp-bg border-b border-wp-border sticky top-0 z-10">
|
||||
<div class="flex items-center gap-3">
|
||||
<header class="topbar">
|
||||
<div class="topbar-left">
|
||||
<Button
|
||||
icon="pi pi-bars"
|
||||
text
|
||||
rounded
|
||||
severity="secondary"
|
||||
icon="pi pi-bars" text rounded severity="secondary"
|
||||
aria-label="Toggle menu"
|
||||
@click="ui.toggleSidebar"
|
||||
/>
|
||||
<div class="flex items-center gap-2 font-semibold">
|
||||
<div
|
||||
class="w-5 h-5 rounded"
|
||||
style="background: var(--wp-brand-gradient)"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span>Wildcard Pipeline</span>
|
||||
</div>
|
||||
<RouterLink to="/" class="brand">
|
||||
<img :src="logoSrc" alt="" class="brand-logo" />
|
||||
<span class="brand-name">Wildcard Pipeline</span>
|
||||
<span class="brand-version">v{{ version }}</span>
|
||||
</RouterLink>
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<div class="topbar-right">
|
||||
<Button
|
||||
icon="pi pi-moon"
|
||||
text
|
||||
rounded
|
||||
severity="secondary"
|
||||
icon="pi pi-cog" text rounded severity="secondary"
|
||||
aria-label="Settings"
|
||||
@click="router.push('/settings')"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-moon" text rounded severity="secondary"
|
||||
aria-label="Toggle theme"
|
||||
@click="ui.toggleTheme"
|
||||
/>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.topbar {
|
||||
height: 56px;
|
||||
background: var(--wp-bg);
|
||||
border-bottom: 1px solid var(--wp-border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 16px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
.topbar-left, .topbar-right { display: flex; align-items: center; gap: 8px; }
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
text-decoration: none;
|
||||
color: var(--wp-text);
|
||||
font-weight: 600;
|
||||
}
|
||||
.brand:hover { color: #fff; }
|
||||
.brand-logo { width: 24px; height: 24px; }
|
||||
.brand-name { letter-spacing: 0.2px; }
|
||||
.brand-version { font-size: 11px; color: var(--wp-text3); font-weight: 400; }
|
||||
</style>
|
||||
|
||||
+20
-8
@@ -2,7 +2,7 @@ import { defineConfig } from "vite";
|
||||
import vue from "@vitejs/plugin-vue";
|
||||
import cssInjectedByJsPlugin from "vite-plugin-css-injected-by-js";
|
||||
import { resolve } from "node:path";
|
||||
import { copyFileSync, rmSync } from "node:fs";
|
||||
import { copyFileSync, existsSync, rmSync } from "node:fs";
|
||||
|
||||
// Two build targets controlled by --mode flag:
|
||||
// pnpm build:extension → js/main.js (ComfyUI web extension, critical-path bundle)
|
||||
@@ -71,18 +71,30 @@ export default defineConfig(({ mode }) => {
|
||||
}
|
||||
|
||||
// Manager SPA — Vue 3 + PrimeVue + Tailwind, served by aiohttp at /wp/
|
||||
// The inline plugin renames src/manager.html → index.html so aiohttp's
|
||||
// The inline plugin renames the emitted HTML → web/index.html so aiohttp's
|
||||
// SPA fallback (which serves web/index.html) picks it up correctly.
|
||||
// Post-write plugin: Vite preserves the input path for HTML outputs
|
||||
// (src/manager.html → web/src/manager.html). After the write, promote it
|
||||
// to web/index.html so aiohttp's SPA fallback can serve it.
|
||||
// Vite ≥5.4 emits the HTML directly to web/manager.html (basename only);
|
||||
// older versions used web/src/manager.html (full relative path). Handle both.
|
||||
// If Vite already emitted to web/index.html (future-proof), skip the copy.
|
||||
const renameManagerHtml = {
|
||||
name: "rename-manager-html",
|
||||
closeBundle() {
|
||||
const src = resolve(__dirname, "web/src/manager.html");
|
||||
const dest = resolve(__dirname, "web/index.html");
|
||||
copyFileSync(src, dest);
|
||||
rmSync(resolve(__dirname, "web/src"), { recursive: true, force: true });
|
||||
const candidates = [
|
||||
resolve(__dirname, "web/manager.html"),
|
||||
resolve(__dirname, "web/src/manager.html"),
|
||||
];
|
||||
for (const src of candidates) {
|
||||
if (existsSync(src)) {
|
||||
copyFileSync(src, dest);
|
||||
rmSync(resolve(__dirname, src.endsWith("src/manager.html") ? "web/src" : src), {
|
||||
recursive: true,
|
||||
force: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Vite already wrote web/index.html directly — nothing to do.
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
+5
-2
@@ -4,8 +4,11 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Wildcard Pipeline Manager</title>
|
||||
<script type="module" crossorigin src="/wp/assets/manager-DCWwvajO.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/wp/assets/manager-id15NJ7-.css">
|
||||
<link rel="icon" type="image/svg+xml" href="/wp/images/favicon.svg" />
|
||||
<link rel="apple-touch-icon" href="/wp/images/apple-touch-icon.png" />
|
||||
<link rel="manifest" href="/wp/images/site.webmanifest" />
|
||||
<script type="module" crossorigin src="/wp/assets/manager-Bo0Ofrce.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/wp/assets/manager-BYxj3PXg.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
Reference in New Issue
Block a user