diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ae38d015b..b8249e7b8 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -2,6 +2,7 @@ version: 2 updates: - package-ecosystem: "npm" directory: "/" + target-branch: "develop" schedule: interval: "weekly" open-pull-requests-limit: 10 diff --git a/apps/cli/package.json b/apps/cli/package.json index 31818015b..1dd90deb6 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -17,7 +17,7 @@ "@orpc/client": "^1.14.4", "@solid-imager/client": "workspace:*", "@solid-imager/core": "workspace:*", - "incur": "^0.4.8", + "incur": "^0.5.1", "zod": "^4.4.3" }, "devDependencies": { diff --git a/apps/server/package.json b/apps/server/package.json index 5621a2a26..bf832c119 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -35,8 +35,8 @@ "docs:generate": "typedoc" }, "dependencies": { - "@electric-sql/pglite": "0.5.3", - "@electric-sql/pglite-pgvector": "0.0.4", + "@electric-sql/pglite": "0.5.5", + "@electric-sql/pglite-pgvector": "0.0.6", "@orpc/client": "^1.14.4", "@orpc/contract": "^1.14.4", "@orpc/experimental-pino": "^1.14.4", @@ -79,7 +79,7 @@ "@orpc/openapi": "^1.14.4", "@playwright/test": "^1.60.0", "@tailwindcss/vite": "^4.3.0", - "@tanstack/devtools-vite": "^0.7.0", + "@tanstack/devtools-vite": "^0.8.3", "@types/archiver": "^8.0.0", "@types/ffmpeg-static": "^5.1.0", "@types/fluent-ffmpeg": "^2.1.28", diff --git a/apps/server/src/components/media/legacy-media-grid-item.tsx b/apps/server/src/components/media/legacy-media-grid-item.tsx index 7d0c357a9..807190c66 100644 --- a/apps/server/src/components/media/legacy-media-grid-item.tsx +++ b/apps/server/src/components/media/legacy-media-grid-item.tsx @@ -16,10 +16,12 @@ export type ServerMediaGridItemProps = { priority?: boolean; sourceRootPath?: string; isBulkSelectMode?: boolean; + isPreviewSelected?: boolean; isSelected?: boolean; onToggleSelect?: () => void; onPrepareMediaDetail?: () => void; onPreviewSelect?: () => void; + onSelectGesture?: (event: MouseEvent | KeyboardEvent) => void; }; export function LegacyMediaGridItem(props: ServerMediaGridItemProps) { diff --git a/apps/server/src/components/media/v2-media-actions.tsx b/apps/server/src/components/media/v2-media-actions.tsx index 1ec14510b..3f8b651f6 100644 --- a/apps/server/src/components/media/v2-media-actions.tsx +++ b/apps/server/src/components/media/v2-media-actions.tsx @@ -1,5 +1,14 @@ import type { MediaDetails } from "@solid-imager/core/domain/media/schemas"; import { getErrorMessage } from "@solid-imager/core/utils"; +import { + AlertDialog, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@solid-imager/ui/alert-dialog"; import { Button } from "@solid-imager/ui/button"; import { Popover, @@ -11,9 +20,11 @@ import { toast } from "@solid-imager/ui/toast"; import { Binary, ChevronDown, + Download, Scan, ScanSearch, Sparkles, + Trash2, } from "@solid-imager/ui/v2/icons"; import { useNavigate } from "@tanstack/solid-router"; import { createEffect, createSignal, on, onCleanup } from "solid-js"; @@ -25,6 +36,7 @@ import { getCcipVectorStatus, startCcipExtraction, } from "~/infrastructure/api-clients/ai-api"; +import { deleteMedia } from "~/infrastructure/api-clients/media-api"; type MediaActionsProps = { media: MediaDetails; @@ -41,6 +53,7 @@ export function V2MediaActions(props: MediaActionsProps) { createSignal(false); const [isCharacterCropModalOpen, setIsCharacterCropModalOpen] = createSignal(false); + const [moreActionsOpen, setMoreActionsOpen] = createSignal(false); const [ccipStatus, setCcipStatus] = createSignal< "missing" | "processing" | "ready" | "stale" | "failed" >("missing"); @@ -49,6 +62,8 @@ export function V2MediaActions(props: MediaActionsProps) { ); const [isCcipJobPending, setIsCcipJobPending] = createSignal(false); const [isExtractingCcip, setIsExtractingCcip] = createSignal(false); + const [isDeleteDialogOpen, setIsDeleteDialogOpen] = createSignal(false); + const [isDeleting, setIsDeleting] = createSignal(false); const [ccipMissingStatusCount, setCcipMissingStatusCount] = createSignal(0); let ccipStatusRequestId = 0; @@ -101,6 +116,7 @@ export function V2MediaActions(props: MediaActionsProps) { ); const handleCcipExtraction = async () => { + setMoreActionsOpen(false); setIsExtractingCcip(true); const currentMediaId = props.media.id; const currentMediaSourceId = props.media.mediaSourceId; @@ -168,6 +184,31 @@ export function V2MediaActions(props: MediaActionsProps) { activateVectorSearch(props.media.id, { surface: "v2" }); void navigate({ to: "/v2/search" }); }; + const handleDownload = () => { + setMoreActionsOpen(false); + const anchor = document.createElement("a"); + anchor.href = `/api/sources/${encodeURIComponent(props.media.mediaSourceId)}/${encodeURIComponent(props.media.id)}`; + anchor.download = props.media.fileName; + anchor.click(); + }; + const handleDelete = async () => { + if (isDeleting()) return; + setIsDeleting(true); + try { + await deleteMedia(props.media.mediaSourceId, props.media.id); + setIsDeleteDialogOpen(false); + toast.success("Media deleted"); + await navigate({ + params: { mediaSourceId: props.media.mediaSourceId }, + replace: true, + to: "/v2/sources/$mediaSourceId", + }); + } catch (error) { + toast.error(`Failed to delete media: ${getErrorMessage(error)}`); + } finally { + setIsDeleting(false); + } + }; const ccipActionLabel = () => { if (ccipStatus() === "ready" || ccipStatus() === "stale") { return "Re-extract CCIP vector"; @@ -202,7 +243,11 @@ export function V2MediaActions(props: MediaActionsProps) {