web: collapsible Tags section + file size on its own row
Groups score, color label, keywords, and auto-labels under one collapsible "Tags" section on the metadata sidebar (open by default, choice persists). Moves file size onto its own row with a HardDrive icon so dimensions and weight read as independent facts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@
|
|||||||
ExternalLink,
|
ExternalLink,
|
||||||
File,
|
File,
|
||||||
Folder,
|
Folder,
|
||||||
|
HardDrive,
|
||||||
ImageIcon,
|
ImageIcon,
|
||||||
Loader2,
|
Loader2,
|
||||||
MapPin,
|
MapPin,
|
||||||
@@ -345,11 +346,19 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Dimensions + file size -->
|
<!-- Dimensions -->
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<ImageIcon class="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
<ImageIcon class="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||||
<span class="min-w-0 flex-1 truncate px-1 py-0.5 text-muted-foreground">
|
<span class="min-w-0 flex-1 truncate px-1 py-0.5 text-muted-foreground">
|
||||||
{dims} · {sizeStr}
|
{dims}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- File size -->
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<HardDrive class="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
|
||||||
|
<span class="min-w-0 flex-1 truncate px-1 py-0.5 text-muted-foreground">
|
||||||
|
{sizeStr}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -386,11 +395,25 @@
|
|||||||
></textarea>
|
></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Score + color label — two separate sections. Click a star/swatch to
|
<!-- Tags — score, color label, keywords, and auto-labels grouped under
|
||||||
set, click the active one to clear. Sits next to Keywords because
|
one collapsible section. Score + color are stored on the mule-
|
||||||
these are the per-photo culling marks the user reaches for in the
|
sidecar (PhotoPrism's PUT can't persist them); keywords live on
|
||||||
same workflow. Stored on the mule-sidecar; PhotoPrism's PUT can't
|
Details; auto-labels come from PhotoPrism's TF classifier and are
|
||||||
persist them. -->
|
read-only. Open by default since these are the culling marks the
|
||||||
|
user reaches for first. -->
|
||||||
|
<details
|
||||||
|
class="rounded border border-border"
|
||||||
|
open={getMetadataSectionOpen('tags', true)}
|
||||||
|
ontoggle={(e) => setMetadataSection('tags', e.currentTarget.open)}
|
||||||
|
>
|
||||||
|
<summary
|
||||||
|
class="cursor-pointer px-2 py-1 text-[10px] uppercase tracking-wide text-muted-foreground"
|
||||||
|
>
|
||||||
|
<span class="inline-flex items-center gap-1">
|
||||||
|
<Tag class="h-3 w-3" /> Tags
|
||||||
|
</span>
|
||||||
|
</summary>
|
||||||
|
<div class="space-y-2 p-2 pt-1">
|
||||||
<div class="space-y-1">
|
<div class="space-y-1">
|
||||||
<div class="text-[10px] uppercase tracking-wide text-muted-foreground">Score</div>
|
<div class="text-[10px] uppercase tracking-wide text-muted-foreground">Score</div>
|
||||||
<div class="flex items-center gap-0.5" role="group" aria-label="Rating">
|
<div class="flex items-center gap-0.5" role="group" aria-label="Rating">
|
||||||
@@ -429,9 +452,7 @@
|
|||||||
|
|
||||||
<!-- Keywords as chips -->
|
<!-- Keywords as chips -->
|
||||||
<div class="space-y-1">
|
<div class="space-y-1">
|
||||||
<div class="flex items-center gap-1 text-[10px] uppercase tracking-wide text-muted-foreground">
|
<div class="text-[10px] uppercase tracking-wide text-muted-foreground">Keywords</div>
|
||||||
<Tag class="h-3 w-3" /> Keywords
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-wrap gap-1">
|
<div class="flex flex-wrap gap-1">
|
||||||
{#each keywords as kw (kw)}
|
{#each keywords as kw (kw)}
|
||||||
<span
|
<span
|
||||||
@@ -463,6 +484,33 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Auto-labels (PhotoPrism's TensorFlow classifier output). Read-
|
||||||
|
only: editing labels requires re-indexing on PhotoPrism's
|
||||||
|
side. The dashed border + lower contrast distinguishes them
|
||||||
|
from the user-editable Keywords chips above. -->
|
||||||
|
{#if (photo.Labels ?? []).length > 0}
|
||||||
|
<div class="space-y-1">
|
||||||
|
<div class="text-[10px] uppercase tracking-wide text-muted-foreground">
|
||||||
|
Labels (auto)
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-wrap gap-1">
|
||||||
|
{#each photo.Labels ?? [] as lbl (lbl.UID ?? lbl.Label?.Slug)}
|
||||||
|
{@const slug = lbl.Label?.Slug}
|
||||||
|
{@const name = lbl.Label?.Name ?? slug ?? '(unknown)'}
|
||||||
|
<a
|
||||||
|
href={slug ? `/?q=${encodeURIComponent(`label:${slug}`)}` : '#'}
|
||||||
|
class="inline-flex items-center rounded-full border border-dashed border-border bg-muted/40 px-1.5 py-0.5 text-[10px] text-muted-foreground hover:text-foreground"
|
||||||
|
title={`Source: ${lbl.Source ?? 'classifier'}`}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</a>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
<!-- Related strips (only the /review route opts in). The three
|
<!-- Related strips (only the /review route opts in). The three
|
||||||
scopes match the three decisions the user usually makes here:
|
scopes match the three decisions the user usually makes here:
|
||||||
"did all these come from the same shoot?" (folder), "same
|
"did all these come from the same shoot?" (folder), "same
|
||||||
@@ -492,33 +540,6 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<!-- Auto-labels (PhotoPrism's TensorFlow classifier output). Read-only:
|
|
||||||
editing labels requires re-indexing on PhotoPrism's side. The
|
|
||||||
dashed border + lower contrast distinguishes them from the user-
|
|
||||||
editable Keywords chips above. -->
|
|
||||||
{#if (photo.Labels ?? []).length > 0}
|
|
||||||
<div class="space-y-1">
|
|
||||||
<div
|
|
||||||
class="flex items-center gap-1 text-[10px] uppercase tracking-wide text-muted-foreground"
|
|
||||||
>
|
|
||||||
<Tag class="h-3 w-3" /> Labels (auto)
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-wrap gap-1">
|
|
||||||
{#each photo.Labels ?? [] as lbl (lbl.UID ?? lbl.Label?.Slug)}
|
|
||||||
{@const slug = lbl.Label?.Slug}
|
|
||||||
{@const name = lbl.Label?.Name ?? slug ?? '(unknown)'}
|
|
||||||
<a
|
|
||||||
href={slug ? `/?q=${encodeURIComponent(`label:${slug}`)}` : '#'}
|
|
||||||
class="inline-flex items-center rounded-full border border-dashed border-border bg-muted/40 px-1.5 py-0.5 text-[10px] text-muted-foreground hover:text-foreground"
|
|
||||||
title={`Source: ${lbl.Source ?? 'classifier'}`}
|
|
||||||
>
|
|
||||||
{name}
|
|
||||||
</a>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<!-- GPS detail. Static default (closed); user's expand/collapse
|
<!-- GPS detail. Static default (closed); user's expand/collapse
|
||||||
choice persists across photo switches via the view store.
|
choice persists across photo switches via the view store.
|
||||||
Avoid data-driven defaults here — they make the `open` attr
|
Avoid data-driven defaults here — they make the `open` attr
|
||||||
|
|||||||
Reference in New Issue
Block a user