feat: mark already-added images as disabled in EntityPicker

This commit is contained in:
2026-03-28 15:16:37 +03:00
parent 3a644b3b0b
commit 62a9249abf
3 changed files with 33 additions and 8 deletions
+24 -6
View File
@@ -104,7 +104,7 @@
case 'Enter': { case 'Enter': {
event.preventDefault(); event.preventDefault();
const item = flatFiltered[highlightIndex]; const item = flatFiltered[highlightIndex];
if (item) { if (item && !item.disabled) {
onselect(item.value); onselect(item.value);
} }
break; break;
@@ -121,8 +121,9 @@
onclose(); onclose();
} }
function handleItemClick(value: string) { function handleItemClick(item: EntityPickerItem) {
onselect(value); if (item.disabled) return;
onselect(item.value);
} }
/** Track the flat index across groups for highlight matching. */ /** Track the flat index across groups for highlight matching. */
@@ -204,18 +205,22 @@
<button <button
type="button" type="button"
class="entity-picker-item" class="entity-picker-item"
class:entity-picker-item--highlighted={isHighlighted} class:entity-picker-item--highlighted={isHighlighted && !item.disabled}
class:entity-picker-item--current={isCurrent} class:entity-picker-item--current={isCurrent}
class:entity-picker-item--disabled={item.disabled}
data-highlighted={isHighlighted} data-highlighted={isHighlighted}
onclick={() => handleItemClick(item.value)} onclick={() => handleItemClick(item)}
onmouseenter={() => { highlightIndex = flatIdx; }} onmouseenter={() => { highlightIndex = flatIdx; }}
disabled={item.disabled}
> >
{#if item.icon} {#if item.icon}
<span class="entity-picker-item-icon">{@html item.icon}</span> <span class="entity-picker-item-icon">{@html item.icon}</span>
{/if} {/if}
<span class="entity-picker-item-content"> <span class="entity-picker-item-content">
<span class="entity-picker-item-label">{item.label}</span> <span class="entity-picker-item-label">{item.label}</span>
{#if item.description} {#if item.disabledHint}
<span class="entity-picker-item-hint">{item.disabledHint}</span>
{:else if item.description}
<span class="entity-picker-item-description">{item.description}</span> <span class="entity-picker-item-description">{item.description}</span>
{/if} {/if}
</span> </span>
@@ -422,4 +427,17 @@
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.entity-picker-item--disabled {
opacity: 0.45;
cursor: default;
pointer-events: none;
}
.entity-picker-item-hint {
font-size: var(--text-xs);
color: var(--text-tertiary);
font-style: italic;
white-space: nowrap;
}
</style> </style>
+2
View File
@@ -144,6 +144,8 @@ export interface EntityPickerItem {
description?: string; description?: string;
icon?: string; icon?: string;
group?: string; group?: string;
disabled?: boolean;
disabledHint?: string;
} }
/** Volume mount configuration for a project. */ /** Volume mount configuration for a project. */
+7 -2
View File
@@ -33,17 +33,22 @@
imagePickerLoading = true; imagePickerLoading = true;
try { try {
const registries = await api.listRegistries(); const registries = await api.listRegistries();
// Collect existing project images to mark as already added.
const existingImages = new Set(projects.map(p => p.image.toLowerCase()));
const items: EntityPickerItem[] = []; const items: EntityPickerItem[] = [];
for (const reg of registries) { for (const reg of registries) {
if (!reg.owner) continue; if (!reg.owner) continue;
try { try {
const images = await api.listRegistryImages(reg.id); const images = await api.listRegistryImages(reg.id);
for (const img of images) { for (const img of images) {
const alreadyAdded = existingImages.has(img.full_ref.toLowerCase());
items.push({ items.push({
value: JSON.stringify({ full_ref: img.full_ref, registryName: reg.name }), value: JSON.stringify({ full_ref: img.full_ref, registryName: reg.name }),
label: img.full_ref, label: img.full_ref,
description: reg.name, description: alreadyAdded ? undefined : reg.name,
group: reg.name group: reg.name,
disabled: alreadyAdded,
disabledHint: alreadyAdded ? 'Already added' : undefined
}); });
} }
} catch { } catch {