diff --git a/svelte-app/src/components/DeviceDock.svelte b/svelte-app/src/components/DeviceDock.svelte index c248bfa..3798dad 100644 --- a/svelte-app/src/components/DeviceDock.svelte +++ b/svelte-app/src/components/DeviceDock.svelte @@ -143,8 +143,8 @@ .device-dock { width: 200px; min-width: 200px; - background: #16213e; - border-left: 2px solid #0f3460; + background: #111827; + border-left: 1px solid #1f2937; display: flex; flex-direction: column; position: relative; @@ -163,8 +163,8 @@ left: 4px; width: 24px; height: 24px; - background: #0f3460; - border: 1px solid #1a1a5e; + background: #1f2937; + border: 1px solid #374151; color: #e0e0e0; border-radius: 4px; cursor: pointer; @@ -177,7 +177,7 @@ } .collapse-btn:hover { - background: #e94560; + background: #3b82f6; } .dock-content { @@ -230,8 +230,8 @@ .search-box input { width: 100%; padding: 5px 22px 5px 8px; - background: #0f3460; - border: 1px solid #1a1a5e; + background: #1f2937; + border: 1px solid #374151; color: #e0e0e0; border-radius: 4px; font-size: 11px; @@ -241,7 +241,7 @@ } .search-box input:focus { - border-color: #e94560; + border-color: #3b82f6; } .search-box input::placeholder { @@ -268,7 +268,7 @@ } .clear-search:hover { - color: #e94560; + color: #3b82f6; } .dock-empty { @@ -291,8 +291,8 @@ width: 100%; padding: 4px 6px; margin-bottom: 1px; - background: #0d2847; - border: 1px solid #1a1a5e; + background: #1f2937; + border: 1px solid #374151; border-radius: 4px; color: #8899aa; font-size: 9px; @@ -305,7 +305,7 @@ } .zone-toggle:hover { - color: #e94560; + color: #3b82f6; } .zone-count { @@ -342,8 +342,8 @@ gap: 5px; width: 100%; padding: 3px 6px; - background: #0f3460; - border: 1px solid #1a1a5e; + background: #1f2937; + border: 1px solid #374151; border-radius: 3px; cursor: grab; user-select: none; @@ -353,8 +353,8 @@ } .device-item:hover { - background: #1a1a5e; - border-color: #e94560; + background: #374151; + border-color: #3b82f6; transform: translateX(-2px); } diff --git a/svelte-app/src/components/Palette.svelte b/svelte-app/src/components/Palette.svelte index aec8bca..43bca04 100644 --- a/svelte-app/src/components/Palette.svelte +++ b/svelte-app/src/components/Palette.svelte @@ -32,55 +32,64 @@ }; }); - let expandedSubgroups = $state>(new Set()); + let expandedGroups = $state>(new Set()); - function toggleSubgroup(key: string) { - const next = new Set(expandedSubgroups); + function toggleGroup(key: string) { + const next = new Set(expandedGroups); if (next.has(key)) next.delete(key); else next.add(key); - expandedSubgroups = next; + expandedGroups = next; } + + // Small symbols use grid layout (2 columns) + const GRID_GROUPS = new Set(['I/O Modules', 'Sensors', 'Controls', 'Other']);
{#each grouped as group (group.name)} -
-
{group.name}
+ {@const isGrid = GRID_GROUPS.has(group.name)} + {@const groupKey = group.name} + + + + {#if expandedGroups.has(groupKey)} + {#if group.subgroups.length > 0} {#each group.subgroups as sub (sub.name)} - {@const key = `${group.name}/${sub.name}`} - - {#if expandedSubgroups.has(key)} -
- {#each sub.symbols as sym (sym.id)} - - {/each} -
- {/if} +
{sub.name}
+
+ {#each sub.symbols as sym (sym.id)} + + {/each} +
{/each} {/if} + {#if group.symbols.length > 0} -
+
{#each group.symbols as sym (sym.id)} {/each}
{/if} -
+ {/if} {/each}
@@ -103,113 +112,156 @@ .palette { flex: 1; overflow-y: auto; - padding: 0 2px; min-height: 0; + padding-bottom: 40px; } - .group { - margin-bottom: 6px; + /* Scrollbar */ + .palette::-webkit-scrollbar { + width: 4px; + } + .palette::-webkit-scrollbar-track { + background: transparent; + } + .palette::-webkit-scrollbar-thumb { + background: #374151; + border-radius: 2px; } .group-header { + display: flex; + align-items: center; + gap: 6px; + width: 100%; + padding: 6px 4px; + background: none; + border: none; + border-bottom: 1px solid #1f2937; + color: #e5e7eb; font-size: 10px; - color: #e94560; + font-weight: 700; text-transform: uppercase; - letter-spacing: 1px; - font-weight: 600; - padding: 4px 6px 2px; + letter-spacing: 0.8px; + cursor: pointer; + transition: color 0.15s; + text-align: left; position: sticky; top: 0; - background: #16213e; + background: #111827; z-index: 1; } - .subgroup-header { - display: flex; - align-items: center; - gap: 4px; - width: 100%; - padding: 3px 6px 3px 12px; - background: none; - border: none; - color: #8899aa; - font-size: 9px; - text-transform: uppercase; - letter-spacing: 0.5px; - font-weight: 600; - cursor: pointer; - user-select: none; + .group-header:hover { + color: #3b82f6; } - .subgroup-header:hover { - color: #e94560; + .count { + margin-left: auto; + font-size: 9px; + color: #4b5563; + font-weight: 400; } .chevron { display: inline-block; width: 0; height: 0; - border-top: 4px solid transparent; - border-bottom: 4px solid transparent; - border-left: 5px solid currentColor; + border-top: 3.5px solid transparent; + border-bottom: 3.5px solid transparent; + border-left: 4.5px solid currentColor; transition: transform 0.15s ease; + flex-shrink: 0; } .chevron.open { transform: rotate(90deg); } - .subgroup-items { - padding-left: 8px; + .sub-label { + font-size: 9px; + color: #6b7280; + text-transform: uppercase; + letter-spacing: 0.3px; + font-weight: 600; + padding: 4px 4px 2px 14px; } - .group-items { + /* List layout (conveyance) */ + .items { display: flex; flex-direction: column; + gap: 1px; + padding: 0 2px 2px; + } + + /* Grid layout (small symbols) */ + .items.grid { + display: grid; + grid-template-columns: 1fr 1fr; gap: 2px; } - .palette-item { + .item { display: flex; align-items: center; - gap: 6px; + gap: 5px; width: 100%; - padding: 4px 6px; - background: #0f3460; - border: 1px solid #1a1a5e; - border-radius: 4px; + padding: 3px 5px; + background: transparent; + border: 1px solid transparent; + border-radius: 3px; cursor: grab; user-select: none; - transition: all 0.15s ease; - color: #ccc; + transition: all 0.12s ease; + color: #9ca3af; text-align: left; } - .palette-item:hover { - background: #1a1a5e; - border-color: #e94560; - transform: translateX(2px); + .item:hover { + background: #1f2937; + border-color: #374151; + color: #e5e7eb; } - .palette-item:active { + .item:active { cursor: grabbing; - transform: scale(0.98); + background: #374151; } - .palette-item img { - width: 36px; - height: 24px; + /* Grid items are more compact */ + .item.grid-item { + flex-direction: column; + align-items: center; + gap: 2px; + padding: 4px 2px 3px; + } + + .item.grid-item .item-name { + text-align: center; + font-size: 8px; + } + + .item img { + width: 28px; + height: 18px; object-fit: contain; - background: transparent; - border-radius: 3px; - padding: 2px; flex-shrink: 0; filter: invert(1); + opacity: 0.8; } - .palette-item span { + .item:hover img { + opacity: 1; + } + + .item.grid-item img { + width: 24px; + height: 20px; + } + + .item-name { font-size: 10px; - line-height: 1.2; + line-height: 1.1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; diff --git a/svelte-app/src/components/Toolbar.svelte b/svelte-app/src/components/Toolbar.svelte index e50294d..e654c8a 100644 --- a/svelte-app/src/components/Toolbar.svelte +++ b/svelte-app/src/components/Toolbar.svelte @@ -10,7 +10,6 @@ let pdfFileEl = $state(null!); let toolbarCollapsed = $state(false); - let projectOpen = $state(false); let pdfOpen = $state(false); let settingsOpen = $state(false); @@ -66,23 +65,10 @@ await loadPdfFromPath(mcm.pdfPath); } - function onCanvasSizeChange() { - layout.markDirty(); - layout.saveMcmState(); - } - - function onGridSizeChange() { - layout.markDirty(); - layout.saveMcmState(); - } - - function onShowGridChange() { - layout.markDirty(); - } - - function onMinSpacingChange() { - layout.saveMcmState(); - } + function onCanvasSizeChange() { layout.markDirty(); layout.saveMcmState(); } + function onGridSizeChange() { layout.markDirty(); layout.saveMcmState(); } + function onShowGridChange() { layout.markDirty(); } + function onMinSpacingChange() { layout.saveMcmState(); } function clearCanvas() { if (!confirm('Clear all placed symbols?')) return; @@ -93,11 +79,8 @@ const file = (e.target as HTMLInputElement).files?.[0]; if (!file) return; try { - if (file.name.endsWith('.svg')) { - await loadLayoutSVG(file); - } else { - await loadLayoutJSON(file); - } + if (file.name.endsWith('.svg')) await loadLayoutSVG(file); + else await loadLayoutJSON(file); } catch (err) { alert('Invalid layout file: ' + (err instanceof Error ? err.message : String(err))); } @@ -118,141 +101,104 @@
{#if !toolbarCollapsed}
- - - {#if projectOpen} -
- {#if layout.projects.length > 0} -
- - -
-
- - -
- {:else} -
- - -
- {/if} -
- {/if} + +
+ {#if layout.projects.length > 0} + + {/if} + +
- + +
+ + + + + +
+ +
+ + {#if pdfOpen}
-
+
-
-
- - -
-
-
{#if layout.pdfLoaded} -
- scale {Math.round(layout.pdfScale * 100)}% pos: {Math.round(layout.pdfOffsetX)},{Math.round(layout.pdfOffsetY)} +
+ + {Math.round(layout.pdfScale * 100)}% + +
{/if}
{/if} - + {#if settingsOpen}
-
-
- - -
-
- - -
+
+ + + +
-
- - +
+ + + +
-
- - -
-
-
{/if} - -
Symbols
+
+ +
{/if} @@ -260,10 +206,10 @@