feat: enable Immich ML, dark mode fixes, sidebar improvements #6

Merged
jimmy merged 6 commits from dev into main 2026-03-01 16:14:38 +08:00
Showing only changes of commit df994e0621 - Show all commits
@@ -44,19 +44,24 @@
{ label: "Speedtest", remoteHref: "https://speed.jimmygan.com:8443", icon: "speedtest", external: true }, { label: "Speedtest", remoteHref: "https://speed.jimmygan.com:8443", icon: "speedtest", external: true },
]; ];
const defaultTools2 = [];
function itemKey(item) { return item.id || item.label; } function itemKey(item) { return item.id || item.label; }
// All items indexed by key for cross-category lookup // All items indexed by key for cross-category lookup
const allItems = new Map([...defaultLinks, ...defaultMedia, ...defaultTools].map(i => [itemKey(i), i])); const allItems = new Map([...defaultLinks, ...defaultMedia, ...defaultTools, ...defaultTools2].map(i => [itemKey(i), i]));
// Build category lists from saved order, supporting cross-category moves // Build category lists from saved order, supporting cross-category moves
const categories = ["main", "media", "tools", "tools2"];
const categoryDefaults = { main: defaultLinks, media: defaultMedia, tools: defaultTools, tools2: defaultTools2 };
function buildLists(savedOrder) { function buildLists(savedOrder) {
if (!savedOrder) return { main: [...defaultLinks], media: [...defaultMedia], tools: [...defaultTools] }; if (!savedOrder) return { main: [...defaultLinks], media: [...defaultMedia], tools: [...defaultTools], tools2: [...defaultTools2] };
const used = new Set(); const used = new Set();
const result = { main: [], media: [], tools: [] }; const result = { main: [], media: [], tools: [], tools2: [] };
for (const cat of ["main", "media", "tools"]) { for (const cat of categories) {
const keys = savedOrder[cat] || []; const keys = savedOrder[cat] || [];
for (const k of keys) { for (const k of keys) {
if (allItems.has(k) && !used.has(k)) { if (allItems.has(k) && !used.has(k)) {
@@ -67,9 +72,8 @@
} }
// Append any new items not in saved order to their default category // Append any new items not in saved order to their default category
const defaults = { main: defaultLinks, media: defaultMedia, tools: defaultTools }; for (const cat of categories) {
for (const cat of ["main", "media", "tools"]) { for (const item of categoryDefaults[cat]) {
for (const item of defaults[cat]) {
if (!used.has(itemKey(item))) { if (!used.has(itemKey(item))) {
result[cat].push(item); result[cat].push(item);
used.add(itemKey(item)); used.add(itemKey(item));
@@ -84,6 +88,10 @@
let links = $derived(categoryLists.main); let links = $derived(categoryLists.main);
let media = $derived(categoryLists.media); let media = $derived(categoryLists.media);
let tools = $derived(categoryLists.tools); let tools = $derived(categoryLists.tools);
let tools2 = $derived(categoryLists.tools2);
// Collapsed state for categories (persisted in sidebarOrder)
let collapsed = $state(sidebarOrder?.collapsed ? { ...sidebarOrder.collapsed } : {});
// Drag-and-drop state // Drag-and-drop state
let dragCategory = $state(null); let dragCategory = $state(null);
@@ -116,13 +124,15 @@
e.preventDefault(); e.preventDefault();
if (dragCategory === null) { handleDragEnd(); return; } if (dragCategory === null) { handleDragEnd(); return; }
if (dragCategory === category && dragIndex === index) { handleDragEnd(); return; } if (dragCategory === category && dragIndex === index) { handleDragEnd(); return; }
const lists = { main: [...links], media: [...media], tools: [...tools] }; const lists = { main: [...links], media: [...media], tools: [...tools], tools2: [...tools2] };
const [moved] = lists[dragCategory].splice(dragIndex, 1); const [moved] = lists[dragCategory].splice(dragIndex, 1);
lists[category].splice(index, 0, moved); lists[category].splice(index, 0, moved);
const newOrder = { const newOrder = {
main: lists.main.map(itemKey), main: lists.main.map(itemKey),
media: lists.media.map(itemKey), media: lists.media.map(itemKey),
tools: lists.tools.map(itemKey), tools: lists.tools.map(itemKey),
tools2: lists.tools2.map(itemKey),
collapsed,
}; };
onOrderChange(newOrder); onOrderChange(newOrder);
handleDragEnd(); handleDragEnd();
@@ -139,13 +149,15 @@
function handleCategoryDrop(category, e) { function handleCategoryDrop(category, e) {
e.preventDefault(); e.preventDefault();
if (dragCategory === null) { handleDragEnd(); return; } if (dragCategory === null) { handleDragEnd(); return; }
const lists = { main: [...links], media: [...media], tools: [...tools] }; const lists = { main: [...links], media: [...media], tools: [...tools], tools2: [...tools2] };
const [moved] = lists[dragCategory].splice(dragIndex, 1); const [moved] = lists[dragCategory].splice(dragIndex, 1);
lists[category].push(moved); lists[category].push(moved);
const newOrder = { const newOrder = {
main: lists.main.map(itemKey), main: lists.main.map(itemKey),
media: lists.media.map(itemKey), media: lists.media.map(itemKey),
tools: lists.tools.map(itemKey), tools: lists.tools.map(itemKey),
tools2: lists.tools2.map(itemKey),
collapsed,
}; };
onOrderChange(newOrder); onOrderChange(newOrder);
handleDragEnd(); handleDragEnd();
@@ -171,6 +183,18 @@
if (dropCategory === category && index === dropIndex) return "border-t-2 border-primary-500"; if (dropCategory === category && index === dropIndex) return "border-t-2 border-primary-500";
return ""; return "";
} }
function toggleCollapse(cat) {
collapsed[cat] = !collapsed[cat];
const newOrder = {
main: links.map(itemKey),
media: media.map(itemKey),
tools: tools.map(itemKey),
tools2: tools2.map(itemKey),
collapsed,
};
onOrderChange(newOrder);
}
</script> </script>
{#snippet iconSvg(icon)} {#snippet iconSvg(icon)}
@@ -232,11 +256,23 @@
<!-- Nav Links --> <!-- Nav Links -->
<nav class="flex-1 px-3 mt-2 overflow-y-auto"> <nav class="flex-1 px-3 mt-2 overflow-y-auto">
{#each [["main", "Main", links], ["media", "Media", media], ["tools", "Tools", tools]] as [cat, label, items], ci} {#each [["main", "Main", links], ["media", "Media", media], ["tools", "Tools", tools], ["tools2", "Tools 2", tools2]] as [cat, label, items], ci}
{#if ci > 0} {#if ci > 0}
<div class="my-4 border-t border-surface-200 dark:border-surface-700"></div> <div class="my-4 border-t border-surface-200 dark:border-surface-700"></div>
{/if} {/if}
<p class="text-[10px] font-semibold uppercase tracking-wider text-surface-400 px-3 mb-2" ondragover={(e) => handleCategoryDragOver(cat, e)} ondrop={(e) => handleCategoryDrop(cat, e)}>{label}</p> <button
class="w-full text-left text-[10px] font-semibold uppercase tracking-wider text-surface-400 px-3 mb-2 flex items-center justify-between {cat !== 'main' ? 'cursor-pointer' : ''}"
ondragover={(e) => handleCategoryDragOver(cat, e)}
ondrop={(e) => handleCategoryDrop(cat, e)}
onclick={() => cat !== 'main' && toggleCollapse(cat)}
disabled={cat === 'main'}
>
{label}
{#if cat !== "main"}
<svg class="w-3 h-3 opacity-30 hover:opacity-50 transition-all duration-150 {collapsed[cat] ? '-rotate-90' : ''}" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M19 9l-7 7-7-7" /></svg>
{/if}
</button>
{#if cat === "main" || !collapsed[cat]}
{#each items as item, i} {#each items as item, i}
{#if canAccess(item.id)} {#if canAccess(item.id)}
{#if item.external || item.remoteHref || item.port} {#if item.external || item.remoteHref || item.port}
@@ -277,6 +313,7 @@
{/if} {/if}
{/if} {/if}
{/each} {/each}
{/if}
{/each} {/each}
</nav> </nav>