upd inventory
This commit is contained in:
parent
aa07fe016e
commit
fb47aacd83
|
|
@ -1 +1 @@
|
||||||
1.2.28
|
1.2.31
|
||||||
|
|
@ -1072,6 +1072,15 @@ export async function softDeleteAssetImage(id: string): Promise<void> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function reorderAssetImages(orderedIds: string[]): Promise<void> {
|
||||||
|
await db.transaction('rw', db.assetImages, async () => {
|
||||||
|
for (let i = 0; i < orderedIds.length; i++) {
|
||||||
|
const ai = await db.assetImages.get(orderedIds[i]);
|
||||||
|
if (ai) await db.assetImages.update(orderedIds[i], { sortOrder: i, updatedAt: now(), version: ai.version + 1 });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// --- AssetPersons ---
|
// --- AssetPersons ---
|
||||||
|
|
||||||
export async function addAssetPerson(assetId: string, personId: string): Promise<AssetPerson> {
|
export async function addAssetPerson(assetId: string, personId: string): Promise<AssetPerson> {
|
||||||
|
|
|
||||||
|
|
@ -353,13 +353,13 @@ async function doSync(since: Date | null): Promise<void> {
|
||||||
if (_syncInProgress) return;
|
if (_syncInProgress) return;
|
||||||
_syncInProgress = true;
|
_syncInProgress = true;
|
||||||
syncStatus.set('syncing');
|
syncStatus.set('syncing');
|
||||||
|
const syncStartedAt = new Date(); // record before push to avoid missing entities created during sync
|
||||||
try {
|
try {
|
||||||
await pushAll(since);
|
await pushAll(since);
|
||||||
const sinceWithBuffer = since ? new Date(since.getTime() - 60_000) : null;
|
const sinceWithBuffer = since ? new Date(since.getTime() - 60_000) : null;
|
||||||
const serverTimestamp = await pullAndMerge(sinceWithBuffer);
|
await pullAndMerge(sinceWithBuffer);
|
||||||
const syncTime = new Date(serverTimestamp);
|
lastSyncAt.set(syncStartedAt);
|
||||||
lastSyncAt.set(syncTime);
|
localStorage.setItem('lastSyncAt', syncStartedAt.toISOString());
|
||||||
localStorage.setItem('lastSyncAt', syncTime.toISOString());
|
|
||||||
syncStatus.set('idle');
|
syncStatus.set('idle');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const msg = err instanceof Error ? err.message : String(err);
|
const msg = err instanceof Error ? err.message : String(err);
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,14 @@
|
||||||
import { liveQuery } from 'dexie';
|
import { liveQuery } from 'dexie';
|
||||||
import { db } from '$lib/db/schema';
|
import { db } from '$lib/db/schema';
|
||||||
import {
|
import {
|
||||||
updateAsset, softDeleteAsset, getAssetImages, addAssetImage, softDeleteAssetImage,
|
updateAsset, softDeleteAsset, addAssetImage, softDeleteAssetImage, reorderAssetImages,
|
||||||
getAssetPersons, addAssetPerson, removeAssetPerson, getFamilyPersons, getRooms,
|
getAssetPersons, addAssetPerson, removeAssetPerson, getFamilyPersons, getRooms,
|
||||||
} from '$lib/db/repositories';
|
} from '$lib/db/repositories';
|
||||||
import { getAccessToken } from '$lib/auth/authStore';
|
import { getAccessToken } from '$lib/auth/authStore';
|
||||||
import { getImageUrl } from '$lib/db/imageStore';
|
import { getImageUrl } from '$lib/db/imageStore';
|
||||||
import MarkdownEditor from '$lib/components/MarkdownEditor.svelte';
|
import MarkdownEditor from '$lib/components/MarkdownEditor.svelte';
|
||||||
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
|
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
|
||||||
import { ChevronLeft, Package, Plus, Trash2, ScanSearch, Sparkles } from 'lucide-svelte';
|
import { ChevronLeft, Package, Plus, Trash2, ScanSearch, Sparkles, Star } from 'lucide-svelte';
|
||||||
import DarkSelect from '$lib/components/DarkSelect.svelte';
|
import DarkSelect from '$lib/components/DarkSelect.svelte';
|
||||||
import LabelConfirm from '$lib/components/LabelConfirm.svelte';
|
import LabelConfirm from '$lib/components/LabelConfirm.svelte';
|
||||||
import { recognizePhoto, enrichAsset } from '$lib/services/visionService';
|
import { recognizePhoto, enrichAsset } from '$lib/services/visionService';
|
||||||
|
|
@ -52,10 +52,18 @@
|
||||||
images = [];
|
images = [];
|
||||||
persons = [];
|
persons = [];
|
||||||
db.assets.get(id).then(a => { asset = a; });
|
db.assets.get(id).then(a => { asset = a; });
|
||||||
getAssetImages(id).then(r => { images = r; loadImageUrls(r); });
|
|
||||||
getAssetPersons(id).then(r => { persons = r; });
|
getAssetPersons(id).then(r => { persons = r; });
|
||||||
getFamilyPersons().then(r => { familyPersons = r; });
|
getFamilyPersons().then(r => { familyPersons = r; });
|
||||||
getRooms().then(r => { rooms = r; });
|
getRooms().then(r => { rooms = r; });
|
||||||
|
// Live subscription: update images whenever assetImages table changes
|
||||||
|
const sub = liveQuery(() =>
|
||||||
|
db.assetImages.where('assetId').equals(id).filter(ai => !ai.deletedAt).sortBy('sortOrder')
|
||||||
|
).subscribe(result => { images = result; });
|
||||||
|
return () => sub.unsubscribe();
|
||||||
|
});
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
loadImageUrls(images);
|
||||||
});
|
});
|
||||||
|
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
|
|
@ -154,13 +162,9 @@
|
||||||
if (!res.ok) throw new Error('Upload failed');
|
if (!res.ok) throw new Error('Upload failed');
|
||||||
const { id } = await res.json() as { id: string };
|
const { id } = await res.json() as { id: string };
|
||||||
await addAssetImage(assetId, id);
|
await addAssetImage(assetId, id);
|
||||||
// If no cover, set this as cover
|
|
||||||
if (!asset?.coverImageId) {
|
if (!asset?.coverImageId) {
|
||||||
await save({ coverImageId: id });
|
await save({ coverImageId: id });
|
||||||
}
|
}
|
||||||
const newImages = await getAssetImages(assetId);
|
|
||||||
images = newImages;
|
|
||||||
await loadImageUrls(newImages);
|
|
||||||
} finally {
|
} finally {
|
||||||
uploading = false;
|
uploading = false;
|
||||||
}
|
}
|
||||||
|
|
@ -178,9 +182,6 @@
|
||||||
if (asset?.coverImageId === img.imageId) {
|
if (asset?.coverImageId === img.imageId) {
|
||||||
await save({ coverImageId: null });
|
await save({ coverImageId: null });
|
||||||
}
|
}
|
||||||
const newImages = await getAssetImages(assetId);
|
|
||||||
images = newImages;
|
|
||||||
await loadImageUrls(newImages);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setCover(img: AssetImage) {
|
async function setCover(img: AssetImage) {
|
||||||
|
|
@ -204,6 +205,36 @@
|
||||||
return meta?.abbreviation ?? meta?.fullName ?? p.name.replace(/^Person /, '');
|
return meta?.abbreviation ?? meta?.fullName ?? p.name.replace(/^Person /, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let dragSrcId = $state<string | null>(null);
|
||||||
|
let dragOverId = $state<string | null>(null);
|
||||||
|
|
||||||
|
function onDragStart(e: DragEvent, id: string) {
|
||||||
|
dragSrcId = id;
|
||||||
|
e.dataTransfer!.effectAllowed = 'move';
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDragOver(e: DragEvent, id: string) {
|
||||||
|
e.preventDefault();
|
||||||
|
dragOverId = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
function onDragEnd() {
|
||||||
|
dragSrcId = null;
|
||||||
|
dragOverId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onDrop(e: DragEvent, targetId: string) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!dragSrcId || dragSrcId === targetId) { onDragEnd(); return; }
|
||||||
|
const ids = images.map(i => i.id);
|
||||||
|
const srcIdx = ids.indexOf(dragSrcId);
|
||||||
|
const tgtIdx = ids.indexOf(targetId);
|
||||||
|
ids.splice(srcIdx, 1);
|
||||||
|
ids.splice(tgtIdx, 0, dragSrcId);
|
||||||
|
onDragEnd();
|
||||||
|
await reorderAssetImages(ids);
|
||||||
|
}
|
||||||
|
|
||||||
let notesValue = $state('');
|
let notesValue = $state('');
|
||||||
let notesInit = $state(false);
|
let notesInit = $state(false);
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
|
|
@ -245,10 +276,21 @@
|
||||||
{#if images.length > 0}
|
{#if images.length > 0}
|
||||||
<div class="mb-4 flex gap-2 overflow-x-auto pb-1">
|
<div class="mb-4 flex gap-2 overflow-x-auto pb-1">
|
||||||
{#each images as img (img.id)}
|
{#each images as img (img.id)}
|
||||||
<div class="relative flex-shrink-0 group">
|
{@const isCover = img.imageId === asset.coverImageId}
|
||||||
|
{@const isDragOver = dragOverId === img.id && dragSrcId !== img.id}
|
||||||
|
<div
|
||||||
|
class="relative flex-shrink-0 group {isDragOver ? 'opacity-50' : ''}"
|
||||||
|
draggable="true"
|
||||||
|
ondragstart={(e) => onDragStart(e, img.id)}
|
||||||
|
ondragover={(e) => onDragOver(e, img.id)}
|
||||||
|
ondrop={(e) => onDrop(e, img.id)}
|
||||||
|
ondragend={onDragEnd}
|
||||||
|
role="listitem"
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
class="h-16 w-16 overflow-hidden rounded-lg border-2 {img.imageId === asset.coverImageId ? 'border-accent' : 'border-transparent'} hover:border-accent/50"
|
class="h-16 w-16 overflow-hidden rounded-lg border-2 {isCover ? 'border-accent' : 'border-transparent'} hover:border-accent/50 cursor-grab active:cursor-grabbing"
|
||||||
onclick={() => setCover(img)}
|
onclick={() => setCover(img)}
|
||||||
|
title={isCover ? 'Titelbild' : 'Als Titelbild setzen'}
|
||||||
>
|
>
|
||||||
{#if imageUrls.get(img.id)}
|
{#if imageUrls.get(img.id)}
|
||||||
<img src={imageUrls.get(img.id)} alt="" class="h-full w-full object-cover" />
|
<img src={imageUrls.get(img.id)} alt="" class="h-full w-full object-cover" />
|
||||||
|
|
@ -256,6 +298,17 @@
|
||||||
<div class="h-full w-full bg-white/10"></div>
|
<div class="h-full w-full bg-white/10"></div>
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
|
<!-- Cover star badge -->
|
||||||
|
{#if isCover}
|
||||||
|
<span class="absolute left-0.5 top-0.5 pointer-events-none">
|
||||||
|
<Star size={13} class="text-accent fill-accent drop-shadow" />
|
||||||
|
</span>
|
||||||
|
{:else}
|
||||||
|
<span class="absolute left-0.5 top-0.5 pointer-events-none hidden group-hover:block">
|
||||||
|
<Star size={13} class="text-white/60" />
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
<!-- Delete button -->
|
||||||
<button
|
<button
|
||||||
class="absolute -right-1 -top-1 hidden group-hover:flex h-5 w-5 items-center justify-center rounded-full bg-danger text-white"
|
class="absolute -right-1 -top-1 hidden group-hover:flex h-5 w-5 items-center justify-center rounded-full bg-danger text-white"
|
||||||
onclick={() => deleteImage(img)}
|
onclick={() => deleteImage(img)}
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue