feat: Auto-save components, soft delete, tags, compact WCO header
- Auto-save rendered Vue components to DB on render_vue_component - Soft delete (archive) instead of hard delete for components - Tags support for component categorization - Gallery limited to 10 most recent items per section - Upsert with ON CONFLICT for component saves - PUT endpoint for partial component updates - Collapsible toolbar with animated toggle button - Window Controls Overlay support for PWA titlebar - Compact header mode (32px) with hidden dot toggle - Dynamic theme-color meta sync for Windows titlebar
This commit is contained in:
@@ -6,6 +6,7 @@ import TorchButton from './components/TorchButton.vue'
|
||||
import FloatingTerminal from './components/FloatingTerminal.vue'
|
||||
import FloatingResponse from './components/FloatingResponse.vue'
|
||||
import FloatingVoice from './components/FloatingVoice.vue'
|
||||
import AgentBar from './components/AgentBar.vue'
|
||||
import PwaInstallBanner from './components/PwaInstallBanner.vue'
|
||||
import { initWebMCP, getWebMCP } from './services/webmcp'
|
||||
import { initTorch, destroyTorch } from './services/torch'
|
||||
@@ -21,6 +22,8 @@ const router = useRouter()
|
||||
const showTerminal = ref(false)
|
||||
const showVoice = ref(false)
|
||||
const showDebugConsole = ref(false)
|
||||
const toolbarVisible = ref(true)
|
||||
const forceWco = ref(false)
|
||||
const debugLogs = ref<Array<{ type: string; message: string; time: string }>>([])
|
||||
|
||||
// Intercept console.log for debug panel
|
||||
@@ -242,7 +245,17 @@ function triggerToolFlash() {
|
||||
|
||||
type PageName = 'home' | 'canvas' | 'components' | 'themes' | 'project-canvas' | 'database' | 'source' | 'terminal' | 'tools' | 'agents'
|
||||
|
||||
function syncThemeColor() {
|
||||
const bg = getComputedStyle(document.documentElement).getPropertyValue('--bg-primary').trim()
|
||||
if (bg) {
|
||||
document.querySelector('meta[name="theme-color"]')?.setAttribute('content', bg)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
// Sync Windows titlebar color with CSS variable
|
||||
syncThemeColor()
|
||||
|
||||
// Connect to WebSocket for Claude status updates
|
||||
connectStatusWs()
|
||||
|
||||
@@ -348,10 +361,20 @@ watch(() => route.name, (newPage) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<header class="app-header">
|
||||
<div class="app-container" :class="{ wco: forceWco }">
|
||||
<header class="app-header" :class="{ 'wco-header': forceWco }">
|
||||
<div class="header-left">
|
||||
<h1 class="logo">Agent UI</h1>
|
||||
<button
|
||||
class="toolbar-toggle"
|
||||
:class="{ collapsed: !toolbarVisible }"
|
||||
@click="toolbarVisible = !toolbarVisible"
|
||||
:title="toolbarVisible ? 'Ocultar toolbar' : 'Mostrar toolbar'"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="3" y="3" width="18" height="18" rx="2"/>
|
||||
<line x1="9" y1="3" x2="9" y2="21"/>
|
||||
</svg>
|
||||
</button>
|
||||
<template v-if="projectCanvasStore.activeCanvas && route.name === 'project-canvas'">
|
||||
<span class="header-sep">/</span>
|
||||
<span class="header-canvas-name">{{ projectCanvasStore.activeCanvas.name }}</span>
|
||||
@@ -376,11 +399,12 @@ watch(() => route.name, (newPage) => {
|
||||
<path d="M21 3v5h-5"/>
|
||||
</svg>
|
||||
</button>
|
||||
<span class="wco-dot" :class="{ on: forceWco }" @click="forceWco = !forceWco"></span>
|
||||
<TorchButton />
|
||||
</div>
|
||||
</header>
|
||||
<main class="app-main">
|
||||
<Toolbar />
|
||||
<Toolbar :collapsed="!toolbarVisible" />
|
||||
<RouterView v-slot="{ Component }">
|
||||
<Transition name="page" mode="out-in">
|
||||
<component :is="Component" />
|
||||
@@ -490,6 +514,9 @@ watch(() => route.name, (newPage) => {
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Agent Bar (bottom pills) -->
|
||||
<AgentBar />
|
||||
|
||||
<!-- Floating Terminal -->
|
||||
<FloatingTerminal ref="terminalRef" v-model="showTerminal" />
|
||||
|
||||
@@ -544,51 +571,200 @@ watch(() => route.name, (newPage) => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.75rem 1.5rem;
|
||||
padding-top: calc(0.75rem + env(safe-area-inset-top, 0px));
|
||||
padding-left: calc(1.5rem + env(safe-area-inset-left, 0px));
|
||||
padding-right: calc(1.5rem + env(safe-area-inset-right, 0px));
|
||||
background: var(--bg-secondary);
|
||||
padding: 0.5rem 1rem;
|
||||
padding-top: calc(0.5rem + env(safe-area-inset-top, 0px));
|
||||
padding-left: calc(1rem + env(safe-area-inset-left, 0px));
|
||||
padding-right: calc(1rem + env(safe-area-inset-right, 0px));
|
||||
background: var(--bg-primary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
flex-shrink: 0;
|
||||
min-height: 40px;
|
||||
-webkit-app-region: drag;
|
||||
app-region: drag;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.5rem;
|
||||
gap: 0.75rem;
|
||||
-webkit-app-region: no-drag;
|
||||
app-region: no-drag;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
gap: 0.5rem;
|
||||
-webkit-app-region: no-drag;
|
||||
app-region: no-drag;
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: var(--text-primary);
|
||||
margin: 0;
|
||||
/* ── Compact header (WCO + manual toggle) ── */
|
||||
.wco-header,
|
||||
.wco-header.app-header {
|
||||
height: 32px;
|
||||
min-height: 32px;
|
||||
max-height: 32px;
|
||||
padding: 0 0.5rem;
|
||||
border-bottom: none;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.wco-header .header-left { gap: 0.35rem; overflow: visible; }
|
||||
.wco-header .header-right { gap: 0.2rem; }
|
||||
|
||||
.wco-header .toolbar-toggle {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.wco-header .toolbar-toggle svg { width: 12px; height: 12px; }
|
||||
|
||||
.wco-header .header-sep { font-size: 0.65rem; }
|
||||
.wco-header .header-canvas-name { font-size: 0.65rem; max-width: 100px; }
|
||||
.wco-header .header-canvas-badge { font-size: 0.5rem; padding: 0 0.2rem; }
|
||||
.wco-header :deep(.pwa-banner) { display: none; }
|
||||
|
||||
.wco-header .debug-btn {
|
||||
padding: 1px 4px;
|
||||
font-size: 8px;
|
||||
border-radius: 3px;
|
||||
gap: 2px;
|
||||
}
|
||||
.wco-header .debug-btn svg { width: 10px; height: 10px; }
|
||||
.wco-header .log-count { font-size: 7px; padding: 0 2px; min-width: 10px; }
|
||||
|
||||
.wco-header .refresh-btn {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.wco-header .refresh-btn svg { width: 12px; height: 12px; }
|
||||
|
||||
/* TorchButton compact via :deep */
|
||||
.wco-header :deep(.trigger-split) { border-radius: 4px; font-size: 0.65rem; }
|
||||
.wco-header :deep(.trigger-main) { padding: 0.1rem 0.25rem 0.1rem 0.35rem; gap: 0.25rem; }
|
||||
.wco-header :deep(.trigger-chevron) { padding: 0.1rem 0.25rem; }
|
||||
.wco-header :deep(.status-dot) { width: 5px; height: 5px; }
|
||||
.wco-header :deep(.chevron) { width: 10px; height: 10px; }
|
||||
.wco-header :deep(.trigger-name) { max-width: 60px; font-size: 0.65rem; }
|
||||
|
||||
/* Window Controls Overlay — real PWA titlebar */
|
||||
@media (display-mode: window-controls-overlay) {
|
||||
.app-header {
|
||||
position: fixed;
|
||||
top: env(titlebar-area-y, 0);
|
||||
left: env(titlebar-area-x, 0);
|
||||
width: env(titlebar-area-width, 100%);
|
||||
height: env(titlebar-area-height, 32px);
|
||||
min-height: unset;
|
||||
max-height: env(titlebar-area-height, 32px);
|
||||
padding: 0 0.5rem;
|
||||
z-index: 10000;
|
||||
border-bottom: none;
|
||||
box-sizing: border-box;
|
||||
background: var(--bg-primary);
|
||||
}
|
||||
|
||||
.app-container {
|
||||
padding-top: env(titlebar-area-height, 32px);
|
||||
}
|
||||
|
||||
/* All the same compacting as .wco-header */
|
||||
.header-left { gap: 0.35rem; }
|
||||
.header-right { gap: 0.2rem; }
|
||||
.toolbar-toggle { width: 22px; height: 22px; border-radius: 4px; }
|
||||
.toolbar-toggle svg { width: 12px; height: 12px; }
|
||||
.header-sep { font-size: 0.65rem; }
|
||||
.header-canvas-name { font-size: 0.65rem; max-width: 100px; }
|
||||
.header-canvas-badge { font-size: 0.5rem; padding: 0 0.2rem; }
|
||||
.debug-btn { padding: 1px 4px; font-size: 8px; border-radius: 3px; gap: 2px; }
|
||||
.debug-btn svg { width: 10px; height: 10px; }
|
||||
.log-count { font-size: 7px; padding: 0 2px; min-width: 10px; }
|
||||
.refresh-btn { width: 22px; height: 22px; border-radius: 4px; }
|
||||
.refresh-btn svg { width: 12px; height: 12px; }
|
||||
}
|
||||
|
||||
/* Tiny hidden toggle dot */
|
||||
.wco-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--border-color);
|
||||
opacity: 0.18;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.wco-dot:hover {
|
||||
opacity: 0.5;
|
||||
transform: scale(1.8);
|
||||
}
|
||||
|
||||
.wco-dot.on {
|
||||
background: #6366f1;
|
||||
opacity: 0.6;
|
||||
box-shadow: 0 0 4px rgba(99, 102, 241, 0.5);
|
||||
}
|
||||
|
||||
.toolbar-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 5px;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.toolbar-toggle:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--accent, #6366f1);
|
||||
border-color: var(--accent, #6366f1);
|
||||
}
|
||||
|
||||
.toolbar-toggle svg {
|
||||
transition: transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
}
|
||||
|
||||
.toolbar-toggle.collapsed svg {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
.toolbar-toggle.collapsed {
|
||||
color: var(--accent, #6366f1);
|
||||
border-color: rgba(99, 102, 241, 0.3);
|
||||
background: rgba(99, 102, 241, 0.08);
|
||||
}
|
||||
|
||||
.header-sep {
|
||||
color: var(--text-muted);
|
||||
font-size: 1.1rem;
|
||||
font-size: 0.85rem;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.header-canvas-name {
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
color: var(--text-secondary);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 160px;
|
||||
}
|
||||
|
||||
.header-canvas-badge {
|
||||
padding: 0.125rem 0.5rem;
|
||||
padding: 0.0625rem 0.375rem;
|
||||
background: rgba(99, 102, 241, 0.15);
|
||||
color: #6366f1;
|
||||
font-size: 0.6875rem;
|
||||
font-size: 0.625rem;
|
||||
font-weight: 500;
|
||||
border-radius: 999px;
|
||||
}
|
||||
@@ -597,12 +773,12 @@ watch(() => route.name, (newPage) => {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
border-radius: 5px;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
@@ -1115,13 +1291,13 @@ watch(() => route.name, (newPage) => {
|
||||
.debug-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 4px 8px;
|
||||
gap: 3px;
|
||||
padding: 3px 6px;
|
||||
background: rgba(100, 100, 100, 0.2);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 6px;
|
||||
border-radius: 5px;
|
||||
color: #888;
|
||||
font-size: 11px;
|
||||
font-size: 10px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
@@ -1140,11 +1316,12 @@ watch(() => route.name, (newPage) => {
|
||||
.log-count {
|
||||
background: #ef4444;
|
||||
color: white;
|
||||
font-size: 9px;
|
||||
padding: 1px 5px;
|
||||
font-size: 8px;
|
||||
padding: 1px 4px;
|
||||
border-radius: 10px;
|
||||
min-width: 16px;
|
||||
min-width: 14px;
|
||||
text-align: center;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* Debug Console Panel */
|
||||
|
||||
Reference in New Issue
Block a user