From cbd58ee43865b6e27a5c65e160900809c2be34c4 Mon Sep 17 00:00:00 2001 From: Jamie Pine <32987599+jamiepine@users.noreply.github.com> Date: Tue, 4 Oct 2022 09:16:49 -0700 Subject: [PATCH] Rename Files to Objects (#396) * backend rename complete * finalize Object refactor frontend + remove some exhaustive dep suppression + FIX INVALIDATE QUERY BUG --- .gitignore | 1 + apps/mobile/src/components/device/Device.tsx | 14 +- apps/mobile/src/types/bindings.ts | 14 +- .../migration.sql | 148 ++++++++---------- core/prisma/schema.prisma | 136 ++++++++-------- core/src/api/files.rs | 19 ++- core/src/api/libraries.rs | 2 +- core/src/api/locations.rs | 18 +-- core/src/api/tags.rs | 44 +++--- core/src/api/utils/invalidate.rs | 8 +- core/src/object/identifier_job.rs | 54 +++---- core/src/object/mod.rs | 2 +- core/src/object/preview/thumb.rs | 16 +- core/src/util/db.rs | 2 +- package.json | 6 +- packages/client/src/core.ts | 14 +- packages/interface/src/AppRouter.tsx | 7 +- .../explorer/ExplorerContextMenu.tsx | 2 +- .../src/components/explorer/FileItem.tsx | 2 +- .../src/components/explorer/FileThumb.tsx | 4 +- .../src/components/explorer/Inspector.tsx | 4 +- .../components/explorer/VirtualizedList.tsx | 1 - .../explorer/inspector/FavoriteButton.tsx | 4 +- .../components/explorer/inspector/Note.tsx | 24 +-- .../src/components/explorer/utils.ts | 2 +- .../src/components/layout/Sidebar.tsx | 8 +- packages/interface/src/hooks/useCounter.ts | 5 +- .../src/screens/LocationExplorer.tsx | 1 - packages/interface/src/screens/Overview.tsx | 48 ++---- packages/interface/src/screens/Redirect.tsx | 19 --- .../library/LibraryGeneralSettings.tsx | 7 +- .../screens/settings/library/TagsSettings.tsx | 52 +++--- 32 files changed, 313 insertions(+), 375 deletions(-) rename core/prisma/migrations/{20220909073230_init => 20221004133318_init}/migration.sql (57%) delete mode 100644 packages/interface/src/screens/Redirect.tsx diff --git a/.gitignore b/.gitignore index 73aded57f..fc00b6ad0 100644 --- a/.gitignore +++ b/.gitignore @@ -66,3 +66,4 @@ examples/*/*.lock /sdserver_data .spacedrive +dev.db-journal \ No newline at end of file diff --git a/apps/mobile/src/components/device/Device.tsx b/apps/mobile/src/components/device/Device.tsx index bcf1e246b..499a4c683 100644 --- a/apps/mobile/src/components/device/Device.tsx +++ b/apps/mobile/src/components/device/Device.tsx @@ -14,7 +14,7 @@ const placeholderFileItems: FilePath[] = [ date_indexed: '2020-01-01T00:00:00.000Z', date_modified: '2020-01-01T00:00:00.000Z', extension: '', - file_id: 1, + object_id: 1, id: 1, location_id: 1, materialized_path: '', @@ -28,7 +28,7 @@ const placeholderFileItems: FilePath[] = [ date_indexed: '2020-01-01T00:00:00.000Z', date_modified: '2020-01-01T00:00:00.000Z', extension: '', - file_id: 2, + object_id: 2, id: 2, location_id: 2, materialized_path: '', @@ -42,7 +42,7 @@ const placeholderFileItems: FilePath[] = [ date_indexed: '2020-01-01T00:00:00.000Z', date_modified: '2020-01-01T00:00:00.000Z', extension: 'tsx', - file_id: 3, + object_id: 3, id: 3, location_id: 3, materialized_path: '', @@ -56,7 +56,7 @@ const placeholderFileItems: FilePath[] = [ date_indexed: '2020-01-01T00:00:00.000Z', date_modified: '2020-01-01T00:00:00.000Z', extension: 'vite', - file_id: 4, + object_id: 4, id: 4, location_id: 4, materialized_path: '', @@ -70,7 +70,7 @@ const placeholderFileItems: FilePath[] = [ date_indexed: '2020-01-01T00:00:00.000Z', date_modified: '2020-01-01T00:00:00.000Z', extension: 'docker', - file_id: 5, + object_id: 5, id: 5, location_id: 5, materialized_path: '', @@ -90,7 +90,7 @@ export interface DeviceProps { const Device = ({ name, locations, size, type }: DeviceProps) => { return ( - + {type === 'phone' && ( @@ -107,7 +107,7 @@ const Device = ({ name, locations, size, type }: DeviceProps) => { {/* Size */} - {size} + {size} {/* Locations/Files TODO: Maybe use FlashList? */} } -export type ExplorerItem = { type: "Path" } & { id: number, is_dir: boolean, location_id: number, materialized_path: string, name: string, extension: string | null, file_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string, file: File | null } | { type: "Object" } & { id: number, cas_id: string, integrity_checksum: string | null, name: string | null, extension: string | null, kind: number, size_in_bytes: string, key_id: number | null, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, ipfs_id: string | null, note: string | null, date_created: string, date_modified: string, date_indexed: string, paths: Array } +export type ExplorerItem = { type: "Path" } & { id: number, is_dir: boolean, location_id: number, materialized_path: string, name: string, extension: string | null, object_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string, object: Object | null } | { type: "Object" } & { id: number, cas_id: string, integrity_checksum: string | null, name: string | null, extension: string | null, kind: number, size_in_bytes: string, key_id: number | null, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, ipfs_id: string | null, note: string | null, date_created: string, date_modified: string, date_indexed: string, file_paths: Array } -export interface File { id: number, cas_id: string, integrity_checksum: string | null, name: string | null, extension: string | null, kind: number, size_in_bytes: string, key_id: number | null, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, ipfs_id: string | null, note: string | null, date_created: string, date_modified: string, date_indexed: string } - -export interface FilePath { id: number, is_dir: boolean, location_id: number, materialized_path: string, name: string, extension: string | null, file_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string } +export interface FilePath { id: number, is_dir: boolean, location_id: number, materialized_path: string, name: string, extension: string | null, object_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string } export interface GenerateThumbsForLocationArgs { id: number, path: string } @@ -93,17 +91,19 @@ export interface NodeConfig { version: string | null, id: string, name: string, export interface NodeState { version: string | null, id: string, name: string, p2p_port: number | null, data_path: string } +export interface Object { id: number, cas_id: string, integrity_checksum: string | null, name: string | null, extension: string | null, kind: number, size_in_bytes: string, key_id: number | null, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, ipfs_id: string | null, note: string | null, date_created: string, date_modified: string, date_indexed: string } + export type RuleKind = "AcceptFilesByGlob" | "RejectFilesByGlob" | "AcceptIfChildrenDirectoriesArePresent" | "RejectIfChildrenDirectoriesArePresent" export interface SetFavoriteArgs { id: number, favorite: boolean } export interface SetNoteArgs { id: number, note: string | null } -export interface Statistics { id: number, date_captured: string, total_file_count: number, library_db_size: string, total_bytes_used: string, total_bytes_capacity: string, total_unique_bytes: string, total_bytes_free: string, preview_media_bytes: string } +export interface Statistics { id: number, date_captured: string, total_object_count: number, library_db_size: string, total_bytes_used: string, total_bytes_capacity: string, total_unique_bytes: string, total_bytes_free: string, preview_media_bytes: string } -export interface Tag { id: number, pub_id: Array, name: string | null, color: string | null, total_files: number | null, redundancy_goal: number | null, date_created: string, date_modified: string } +export interface Tag { id: number, pub_id: Array, name: string | null, color: string | null, total_objects: number | null, redundancy_goal: number | null, date_created: string, date_modified: string } -export interface TagAssignArgs { file_id: number, tag_id: number, unassign: boolean } +export interface TagAssignArgs { object_id: number, tag_id: number, unassign: boolean } export interface TagCreateArgs { name: string, color: string } diff --git a/core/prisma/migrations/20220909073230_init/migration.sql b/core/prisma/migrations/20221004133318_init/migration.sql similarity index 57% rename from core/prisma/migrations/20220909073230_init/migration.sql rename to core/prisma/migrations/20221004133318_init/migration.sql index 14f5cc59c..5bf3a2f9d 100644 --- a/core/prisma/migrations/20220909073230_init/migration.sql +++ b/core/prisma/migrations/20221004133318_init/migration.sql @@ -1,14 +1,5 @@ -- CreateTable -CREATE TABLE "_migrations" ( - "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, - "name" TEXT NOT NULL, - "checksum" TEXT NOT NULL, - "steps_applied" INTEGER NOT NULL DEFAULT 0, - "applied_at" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP -); - --- CreateTable -CREATE TABLE "sync_events" ( +CREATE TABLE "sync_event" ( "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "node_id" INTEGER NOT NULL, "timestamp" TEXT NOT NULL, @@ -16,14 +7,14 @@ CREATE TABLE "sync_events" ( "kind" INTEGER NOT NULL, "column" TEXT, "value" TEXT NOT NULL, - CONSTRAINT "sync_events_node_id_fkey" FOREIGN KEY ("node_id") REFERENCES "nodes" ("id") ON DELETE RESTRICT ON UPDATE CASCADE + CONSTRAINT "sync_event_node_id_fkey" FOREIGN KEY ("node_id") REFERENCES "node" ("id") ON DELETE RESTRICT ON UPDATE CASCADE ); -- CreateTable CREATE TABLE "statistics" ( "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "date_captured" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "total_file_count" INTEGER NOT NULL DEFAULT 0, + "total_object_count" INTEGER NOT NULL DEFAULT 0, "library_db_size" TEXT NOT NULL DEFAULT '0', "total_bytes_used" TEXT NOT NULL DEFAULT '0', "total_bytes_capacity" TEXT NOT NULL DEFAULT '0', @@ -33,7 +24,7 @@ CREATE TABLE "statistics" ( ); -- CreateTable -CREATE TABLE "nodes" ( +CREATE TABLE "node" ( "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "pub_id" BLOB NOT NULL, "name" TEXT NOT NULL, @@ -45,7 +36,7 @@ CREATE TABLE "nodes" ( ); -- CreateTable -CREATE TABLE "volumes" ( +CREATE TABLE "volume" ( "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "node_id" INTEGER NOT NULL, "name" TEXT NOT NULL, @@ -59,7 +50,7 @@ CREATE TABLE "volumes" ( ); -- CreateTable -CREATE TABLE "locations" ( +CREATE TABLE "location" ( "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "pub_id" BLOB NOT NULL, "node_id" INTEGER NOT NULL, @@ -73,11 +64,11 @@ CREATE TABLE "locations" ( "is_online" BOOLEAN NOT NULL DEFAULT true, "is_archived" BOOLEAN NOT NULL DEFAULT false, "date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "locations_node_id_fkey" FOREIGN KEY ("node_id") REFERENCES "nodes" ("id") ON DELETE RESTRICT ON UPDATE CASCADE + CONSTRAINT "location_node_id_fkey" FOREIGN KEY ("node_id") REFERENCES "node" ("id") ON DELETE RESTRICT ON UPDATE CASCADE ); -- CreateTable -CREATE TABLE "files" ( +CREATE TABLE "object" ( "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "cas_id" TEXT NOT NULL, "integrity_checksum" TEXT, @@ -97,18 +88,18 @@ CREATE TABLE "files" ( "date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "date_modified" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "date_indexed" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT "files_key_id_fkey" FOREIGN KEY ("key_id") REFERENCES "keys" ("id") ON DELETE SET NULL ON UPDATE CASCADE + CONSTRAINT "object_key_id_fkey" FOREIGN KEY ("key_id") REFERENCES "key" ("id") ON DELETE SET NULL ON UPDATE CASCADE ); -- CreateTable -CREATE TABLE "file_paths" ( +CREATE TABLE "file_path" ( "id" INTEGER NOT NULL, "is_dir" BOOLEAN NOT NULL DEFAULT false, "location_id" INTEGER NOT NULL, "materialized_path" TEXT NOT NULL, "name" TEXT NOT NULL, "extension" TEXT, - "file_id" INTEGER, + "object_id" INTEGER, "parent_id" INTEGER, "key_id" INTEGER, "date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, @@ -116,19 +107,19 @@ CREATE TABLE "file_paths" ( "date_indexed" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY ("location_id", "id"), - CONSTRAINT "file_paths_file_id_fkey" FOREIGN KEY ("file_id") REFERENCES "files" ("id") ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT "file_paths_location_id_fkey" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE CASCADE ON UPDATE CASCADE, - CONSTRAINT "file_paths_key_id_fkey" FOREIGN KEY ("key_id") REFERENCES "keys" ("id") ON DELETE SET NULL ON UPDATE CASCADE + CONSTRAINT "file_path_object_id_fkey" FOREIGN KEY ("object_id") REFERENCES "object" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "file_path_location_id_fkey" FOREIGN KEY ("location_id") REFERENCES "location" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "file_path_key_id_fkey" FOREIGN KEY ("key_id") REFERENCES "key" ("id") ON DELETE SET NULL ON UPDATE CASCADE ); -- CreateTable -CREATE TABLE "file_conflicts" ( - "original_file_id" INTEGER NOT NULL, - "detactched_file_id" INTEGER NOT NULL +CREATE TABLE "file_conflict" ( + "original_object_id" INTEGER NOT NULL, + "detactched_object_id" INTEGER NOT NULL ); -- CreateTable -CREATE TABLE "keys" ( +CREATE TABLE "key" ( "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "checksum" TEXT NOT NULL, "name" TEXT, @@ -150,34 +141,34 @@ CREATE TABLE "media_data" ( "duration_seconds" INTEGER, "codecs" TEXT, "streams" INTEGER, - CONSTRAINT "media_data_id_fkey" FOREIGN KEY ("id") REFERENCES "files" ("id") ON DELETE CASCADE ON UPDATE CASCADE + CONSTRAINT "media_data_id_fkey" FOREIGN KEY ("id") REFERENCES "object" ("id") ON DELETE CASCADE ON UPDATE CASCADE ); -- CreateTable -CREATE TABLE "tags" ( +CREATE TABLE "tag" ( "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "pub_id" BLOB NOT NULL, "name" TEXT, "color" TEXT, - "total_files" INTEGER DEFAULT 0, + "total_objects" INTEGER DEFAULT 0, "redundancy_goal" INTEGER DEFAULT 1, "date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "date_modified" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ); -- CreateTable -CREATE TABLE "tags_on_file" ( +CREATE TABLE "tag_on_object" ( "date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "tag_id" INTEGER NOT NULL, - "file_id" INTEGER NOT NULL, + "object_id" INTEGER NOT NULL, - PRIMARY KEY ("tag_id", "file_id"), - CONSTRAINT "tags_on_file_tag_id_fkey" FOREIGN KEY ("tag_id") REFERENCES "tags" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, - CONSTRAINT "tags_on_file_file_id_fkey" FOREIGN KEY ("file_id") REFERENCES "files" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + PRIMARY KEY ("tag_id", "object_id"), + CONSTRAINT "tag_on_object_tag_id_fkey" FOREIGN KEY ("tag_id") REFERENCES "tag" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "tag_on_object_object_id_fkey" FOREIGN KEY ("object_id") REFERENCES "object" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION ); -- CreateTable -CREATE TABLE "labels" ( +CREATE TABLE "label" ( "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "pub_id" BLOB NOT NULL, "name" TEXT, @@ -186,18 +177,18 @@ CREATE TABLE "labels" ( ); -- CreateTable -CREATE TABLE "label_on_file" ( +CREATE TABLE "label_on_object" ( "date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "label_id" INTEGER NOT NULL, - "file_id" INTEGER NOT NULL, + "object_id" INTEGER NOT NULL, - PRIMARY KEY ("label_id", "file_id"), - CONSTRAINT "label_on_file_label_id_fkey" FOREIGN KEY ("label_id") REFERENCES "labels" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, - CONSTRAINT "label_on_file_file_id_fkey" FOREIGN KEY ("file_id") REFERENCES "files" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + PRIMARY KEY ("label_id", "object_id"), + CONSTRAINT "label_on_object_label_id_fkey" FOREIGN KEY ("label_id") REFERENCES "label" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "label_on_object_object_id_fkey" FOREIGN KEY ("object_id") REFERENCES "object" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION ); -- CreateTable -CREATE TABLE "spaces" ( +CREATE TABLE "space" ( "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "pub_id" BLOB NOT NULL, "name" TEXT, @@ -207,18 +198,18 @@ CREATE TABLE "spaces" ( ); -- CreateTable -CREATE TABLE "file_in_space" ( +CREATE TABLE "object_in_space" ( "date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "space_id" INTEGER NOT NULL, - "file_id" INTEGER NOT NULL, + "object_id" INTEGER NOT NULL, - PRIMARY KEY ("space_id", "file_id"), - CONSTRAINT "file_in_space_space_id_fkey" FOREIGN KEY ("space_id") REFERENCES "spaces" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, - CONSTRAINT "file_in_space_file_id_fkey" FOREIGN KEY ("file_id") REFERENCES "files" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + PRIMARY KEY ("space_id", "object_id"), + CONSTRAINT "object_in_space_space_id_fkey" FOREIGN KEY ("space_id") REFERENCES "space" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "object_in_space_object_id_fkey" FOREIGN KEY ("object_id") REFERENCES "object" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION ); -- CreateTable -CREATE TABLE "jobs" ( +CREATE TABLE "job" ( "id" BLOB NOT NULL PRIMARY KEY, "name" TEXT NOT NULL, "node_id" INTEGER NOT NULL, @@ -231,11 +222,11 @@ CREATE TABLE "jobs" ( "date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "date_modified" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "seconds_elapsed" INTEGER NOT NULL DEFAULT 0, - CONSTRAINT "jobs_node_id_fkey" FOREIGN KEY ("node_id") REFERENCES "nodes" ("id") ON DELETE CASCADE ON UPDATE CASCADE + CONSTRAINT "job_node_id_fkey" FOREIGN KEY ("node_id") REFERENCES "node" ("id") ON DELETE CASCADE ON UPDATE CASCADE ); -- CreateTable -CREATE TABLE "albums" ( +CREATE TABLE "album" ( "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "pub_id" BLOB NOT NULL, "name" TEXT NOT NULL, @@ -245,29 +236,29 @@ CREATE TABLE "albums" ( ); -- CreateTable -CREATE TABLE "files_in_albums" ( +CREATE TABLE "object_in_album" ( "date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "album_id" INTEGER NOT NULL, - "file_id" INTEGER NOT NULL, + "object_id" INTEGER NOT NULL, - PRIMARY KEY ("album_id", "file_id"), - CONSTRAINT "files_in_albums_album_id_fkey" FOREIGN KEY ("album_id") REFERENCES "albums" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, - CONSTRAINT "files_in_albums_file_id_fkey" FOREIGN KEY ("file_id") REFERENCES "files" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + PRIMARY KEY ("album_id", "object_id"), + CONSTRAINT "object_in_album_album_id_fkey" FOREIGN KEY ("album_id") REFERENCES "album" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "object_in_album_object_id_fkey" FOREIGN KEY ("object_id") REFERENCES "object" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION ); -- CreateTable -CREATE TABLE "comments" ( +CREATE TABLE "comment" ( "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "pub_id" BLOB NOT NULL, "content" TEXT NOT NULL, "date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "date_modified" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, - "file_id" INTEGER, - CONSTRAINT "comments_file_id_fkey" FOREIGN KEY ("file_id") REFERENCES "files" ("id") ON DELETE SET NULL ON UPDATE CASCADE + "object_id" INTEGER, + CONSTRAINT "comment_object_id_fkey" FOREIGN KEY ("object_id") REFERENCES "object" ("id") ON DELETE SET NULL ON UPDATE CASCADE ); -- CreateTable -CREATE TABLE "indexer_rules" ( +CREATE TABLE "indexer_rule" ( "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "kind" INTEGER NOT NULL, "name" TEXT NOT NULL, @@ -277,60 +268,57 @@ CREATE TABLE "indexer_rules" ( ); -- CreateTable -CREATE TABLE "indexer_rules_in_location" ( +CREATE TABLE "indexer_rule_in_location" ( "date_created" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, "location_id" INTEGER NOT NULL, "indexer_rule_id" INTEGER NOT NULL, PRIMARY KEY ("location_id", "indexer_rule_id"), - CONSTRAINT "indexer_rules_in_location_location_id_fkey" FOREIGN KEY ("location_id") REFERENCES "locations" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, - CONSTRAINT "indexer_rules_in_location_indexer_rule_id_fkey" FOREIGN KEY ("indexer_rule_id") REFERENCES "indexer_rules" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION + CONSTRAINT "indexer_rule_in_location_location_id_fkey" FOREIGN KEY ("location_id") REFERENCES "location" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION, + CONSTRAINT "indexer_rule_in_location_indexer_rule_id_fkey" FOREIGN KEY ("indexer_rule_id") REFERENCES "indexer_rule" ("id") ON DELETE NO ACTION ON UPDATE NO ACTION ); -- CreateIndex -CREATE UNIQUE INDEX "_migrations_checksum_key" ON "_migrations"("checksum"); +CREATE UNIQUE INDEX "node_pub_id_key" ON "node"("pub_id"); -- CreateIndex -CREATE UNIQUE INDEX "nodes_pub_id_key" ON "nodes"("pub_id"); +CREATE UNIQUE INDEX "volume_node_id_mount_point_name_key" ON "volume"("node_id", "mount_point", "name"); -- CreateIndex -CREATE UNIQUE INDEX "volumes_node_id_mount_point_name_key" ON "volumes"("node_id", "mount_point", "name"); +CREATE UNIQUE INDEX "location_pub_id_key" ON "location"("pub_id"); -- CreateIndex -CREATE UNIQUE INDEX "locations_pub_id_key" ON "locations"("pub_id"); +CREATE UNIQUE INDEX "object_cas_id_key" ON "object"("cas_id"); -- CreateIndex -CREATE UNIQUE INDEX "files_cas_id_key" ON "files"("cas_id"); +CREATE UNIQUE INDEX "object_integrity_checksum_key" ON "object"("integrity_checksum"); -- CreateIndex -CREATE UNIQUE INDEX "files_integrity_checksum_key" ON "files"("integrity_checksum"); +CREATE INDEX "file_path_location_id_idx" ON "file_path"("location_id"); -- CreateIndex -CREATE INDEX "file_paths_location_id_idx" ON "file_paths"("location_id"); +CREATE UNIQUE INDEX "file_path_location_id_materialized_path_name_extension_key" ON "file_path"("location_id", "materialized_path", "name", "extension"); -- CreateIndex -CREATE UNIQUE INDEX "file_paths_location_id_materialized_path_name_extension_key" ON "file_paths"("location_id", "materialized_path", "name", "extension"); +CREATE UNIQUE INDEX "file_conflict_original_object_id_key" ON "file_conflict"("original_object_id"); -- CreateIndex -CREATE UNIQUE INDEX "file_conflicts_original_file_id_key" ON "file_conflicts"("original_file_id"); +CREATE UNIQUE INDEX "file_conflict_detactched_object_id_key" ON "file_conflict"("detactched_object_id"); -- CreateIndex -CREATE UNIQUE INDEX "file_conflicts_detactched_file_id_key" ON "file_conflicts"("detactched_file_id"); +CREATE UNIQUE INDEX "key_checksum_key" ON "key"("checksum"); -- CreateIndex -CREATE UNIQUE INDEX "keys_checksum_key" ON "keys"("checksum"); +CREATE UNIQUE INDEX "tag_pub_id_key" ON "tag"("pub_id"); -- CreateIndex -CREATE UNIQUE INDEX "tags_pub_id_key" ON "tags"("pub_id"); +CREATE UNIQUE INDEX "label_pub_id_key" ON "label"("pub_id"); -- CreateIndex -CREATE UNIQUE INDEX "labels_pub_id_key" ON "labels"("pub_id"); +CREATE UNIQUE INDEX "space_pub_id_key" ON "space"("pub_id"); -- CreateIndex -CREATE UNIQUE INDEX "spaces_pub_id_key" ON "spaces"("pub_id"); +CREATE UNIQUE INDEX "album_pub_id_key" ON "album"("pub_id"); -- CreateIndex -CREATE UNIQUE INDEX "albums_pub_id_key" ON "albums"("pub_id"); - --- CreateIndex -CREATE UNIQUE INDEX "comments_pub_id_key" ON "comments"("pub_id"); +CREATE UNIQUE INDEX "comment_pub_id_key" ON "comment"("pub_id"); diff --git a/core/prisma/schema.prisma b/core/prisma/schema.prisma index 0b8c45a6c..e05e3d6a8 100644 --- a/core/prisma/schema.prisma +++ b/core/prisma/schema.prisma @@ -8,16 +8,6 @@ generator client { output = "../src/prisma.rs" } -model Migration { - id Int @id @default(autoincrement()) - name String - checksum String @unique - steps_applied Int @default(0) - applied_at DateTime @default(now()) - - @@map("_migrations") -} - model SyncEvent { id Int @id @default(autoincrement()) node_id Int @@ -33,13 +23,13 @@ model SyncEvent { node Node @relation(fields: [node_id], references: [id]) - @@map("sync_events") + @@map("sync_event") } model Statistics { id Int @id @default(autoincrement()) date_captured DateTime @default(now()) - total_file_count Int @default(0) + total_object_count Int @default(0) library_db_size String @default("0") total_bytes_used String @default("0") total_bytes_capacity String @default("0") @@ -65,7 +55,7 @@ model Node { Location Location[] - @@map("nodes") + @@map("node") } model Volume { @@ -81,7 +71,7 @@ model Volume { date_modified DateTime @default(now()) @@unique([node_id, mount_point, name]) - @@map("volumes") + @@map("volume") } model Location { @@ -103,10 +93,10 @@ model Location { file_paths FilePath[] indexer_rules IndexerRulesInLocation[] - @@map("locations") + @@map("location") } -model File { +model Object { id Int @id @default(autoincrement()) // content addressable storage id - sha256 sampled checksum cas_id String @unique @@ -118,11 +108,11 @@ model File { kind Int @default(0) size_in_bytes String key_id Int? - // handy ways to mark a file + // handy ways to mark an object hidden Boolean @default(false) favorite Boolean @default(false) important Boolean @default(false) - // if we have generated preview media for this file + // if we have generated preview media for this object has_thumbnail Boolean @default(false) has_thumbstrip Boolean @default(false) has_video_preview Boolean @default(false) @@ -130,24 +120,24 @@ model File { ipfs_id String? // plain text note note String? - // the original known creation date of this file + // the original known creation date of this object date_created DateTime @default(now()) - // the last time this file was modified + // the last time this object was modified date_modified DateTime @default(now()) - // when this file was first indexed + // when this object was first indexed date_indexed DateTime @default(now()) - tags TagOnFile[] - labels LabelOnFile[] - albums FileInAlbum[] - spaces FileInSpace[] - paths FilePath[] + tags TagOnObject[] + labels LabelOnObject[] + albums ObjectInAlbum[] + spaces ObjectInSpace[] + file_paths FilePath[] comments Comment[] media_data MediaData? key Key? @relation(fields: [key_id], references: [id]) - @@map("files") + @@map("object") } model FilePath { @@ -160,8 +150,8 @@ model FilePath { // the name and extension name String extension String? - // the unique File for this file path - file_id Int? + // the unique Object for this file path + object_id Int? // the parent in the file tree parent_id Int? key_id Int? // replacement for encryption @@ -172,7 +162,7 @@ model FilePath { date_modified DateTime @default(now()) date_indexed DateTime @default(now()) - file File? @relation(fields: [file_id], references: [id], onDelete: Cascade, onUpdate: Cascade) + object Object? @relation(fields: [object_id], references: [id], onDelete: Cascade, onUpdate: Cascade) location Location? @relation(fields: [location_id], references: [id], onDelete: Cascade, onUpdate: Cascade) // NOTE: this self relation for the file tree was causing SQLite to go to forever bed, disabling until workaround @@ -184,15 +174,15 @@ model FilePath { @@id([location_id, id]) @@unique([location_id, materialized_path, name, extension]) @@index([location_id]) - @@map("file_paths") + @@map("file_path") } // if there is a conflicting cas_id, the conficting file should be updated to have a larger cas_id as the field is unique, however this record is kept to tell the indexer (upon discovering this CAS) that there is alternate versions of the file and to check by a full integrity hash to define for which to associate with. model FileConflict { - original_file_id Int @unique - detactched_file_id Int @unique + original_object_id Int @unique + detactched_object_id Int @unique - @@map("file_conflicts") + @@map("file_conflict") } // keys allow us to know exactly which files can be decrypted with a given key @@ -207,10 +197,10 @@ model Key { // so we know which algorithm to use, can be null if user must select algorithm Int? @default(0) - files File[] + objects Object[] file_paths FilePath[] - @@map("keys") + @@map("key") } model MediaData { @@ -227,8 +217,8 @@ model MediaData { codecs String? // eg: "h264,acc" streams Int? - // change this relation to File after testing - files File? @relation(fields: [id], references: [id], onDelete: Cascade, onUpdate: Cascade) + // change this relation to Object after testing + objects Object? @relation(fields: [id], references: [id], onDelete: Cascade, onUpdate: Cascade) @@map("media_data") } @@ -238,27 +228,27 @@ model Tag { pub_id Bytes @unique name String? color String? - total_files Int? @default(0) + total_objects Int? @default(0) redundancy_goal Int? @default(1) date_created DateTime @default(now()) date_modified DateTime @default(now()) - tag_files TagOnFile[] + tag_objects TagOnObject[] - @@map("tags") + @@map("tag") } -model TagOnFile { +model TagOnObject { date_created DateTime @default(now()) tag_id Int tag Tag @relation(fields: [tag_id], references: [id], onDelete: NoAction, onUpdate: NoAction) - file_id Int - file File @relation(fields: [file_id], references: [id], onDelete: NoAction, onUpdate: NoAction) + object_id Int + object Object @relation(fields: [object_id], references: [id], onDelete: NoAction, onUpdate: NoAction) - @@id([tag_id, file_id]) - @@map("tags_on_file") + @@id([tag_id, object_id]) + @@map("tag_on_object") } model Label { @@ -268,22 +258,22 @@ model Label { date_created DateTime @default(now()) date_modified DateTime @default(now()) - label_files LabelOnFile[] + label_objects LabelOnObject[] - @@map("labels") + @@map("label") } -model LabelOnFile { +model LabelOnObject { date_created DateTime @default(now()) label_id Int label Label @relation(fields: [label_id], references: [id], onDelete: NoAction, onUpdate: NoAction) - file_id Int - file File @relation(fields: [file_id], references: [id], onDelete: NoAction, onUpdate: NoAction) + object_id Int + object Object @relation(fields: [object_id], references: [id], onDelete: NoAction, onUpdate: NoAction) - @@id([label_id, file_id]) - @@map("label_on_file") + @@id([label_id, object_id]) + @@map("label_on_object") } model Space { @@ -294,22 +284,22 @@ model Space { date_created DateTime @default(now()) date_modified DateTime @default(now()) - files FileInSpace[] + objects ObjectInSpace[] - @@map("spaces") + @@map("space") } -model FileInSpace { +model ObjectInSpace { date_created DateTime @default(now()) space_id Int space Space @relation(fields: [space_id], references: [id], onDelete: NoAction, onUpdate: NoAction) - file_id Int - file File @relation(fields: [file_id], references: [id], onDelete: NoAction, onUpdate: NoAction) + object_id Int + object Object @relation(fields: [object_id], references: [id], onDelete: NoAction, onUpdate: NoAction) - @@id([space_id, file_id]) - @@map("file_in_space") + @@id([space_id, object_id]) + @@map("object_in_space") } model Job { @@ -329,7 +319,7 @@ model Job { nodes Node @relation(fields: [node_id], references: [id], onDelete: Cascade, onUpdate: Cascade) - @@map("jobs") + @@map("job") } model Album { @@ -341,22 +331,22 @@ model Album { date_created DateTime @default(now()) date_modified DateTime @default(now()) - files FileInAlbum[] + objects ObjectInAlbum[] - @@map("albums") + @@map("album") } -model FileInAlbum { +model ObjectInAlbum { date_created DateTime @default(now()) album_id Int album Album @relation(fields: [album_id], references: [id], onDelete: NoAction, onUpdate: NoAction) - file_id Int - file File @relation(fields: [file_id], references: [id], onDelete: NoAction, onUpdate: NoAction) + object_id Int + object Object @relation(fields: [object_id], references: [id], onDelete: NoAction, onUpdate: NoAction) - @@id([album_id, file_id]) - @@map("files_in_albums") + @@id([album_id, object_id]) + @@map("object_in_album") } model Comment { @@ -365,10 +355,10 @@ model Comment { content String date_created DateTime @default(now()) date_modified DateTime @default(now()) - file_id Int? - file File? @relation(fields: [file_id], references: [id]) + object_id Int? + object Object? @relation(fields: [object_id], references: [id]) - @@map("comments") + @@map("comment") } model IndexerRule { @@ -381,7 +371,7 @@ model IndexerRule { locations IndexerRulesInLocation[] - @@map("indexer_rules") + @@map("indexer_rule") } model IndexerRulesInLocation { @@ -394,5 +384,5 @@ model IndexerRulesInLocation { indexer_rule IndexerRule @relation(fields: [indexer_rule_id], references: [id], onDelete: NoAction, onUpdate: NoAction) @@id([location_id, indexer_rule_id]) - @@map("indexer_rules_in_location") + @@map("indexer_rule_in_location") } diff --git a/core/src/api/files.rs b/core/src/api/files.rs index 0250e2a88..251b5234c 100644 --- a/core/src/api/files.rs +++ b/core/src/api/files.rs @@ -1,4 +1,4 @@ -use crate::{invalidate_query, prisma::file}; +use crate::{invalidate_query, prisma::object}; use rspc::Type; use serde::Deserialize; @@ -26,8 +26,11 @@ pub(crate) fn mount() -> RouterBuilder { .library_mutation("setNote", |_, args: SetNoteArgs, library| async move { library .db - .file() - .update(file::id::equals(args.id), vec![file::note::set(args.note)]) + .object() + .update( + object::id::equals(args.id), + vec![object::note::set(args.note)], + ) .exec() .await?; @@ -40,10 +43,10 @@ pub(crate) fn mount() -> RouterBuilder { |_, args: SetFavoriteArgs, library| async move { library .db - .file() + .object() .update( - file::id::equals(args.id), - vec![file::favorite::set(args.favorite)], + object::id::equals(args.id), + vec![object::favorite::set(args.favorite)], ) .exec() .await?; @@ -56,8 +59,8 @@ pub(crate) fn mount() -> RouterBuilder { .library_mutation("delete", |_, id: i32, library| async move { library .db - .file() - .delete(file::id::equals(id)) + .object() + .delete(object::id::equals(id)) .exec() .await?; diff --git a/core/src/api/libraries.rs b/core/src/api/libraries.rs index c6c19c50a..c89e00663 100644 --- a/core/src/api/libraries.rs +++ b/core/src/api/libraries.rs @@ -57,7 +57,7 @@ pub(crate) fn mount() -> RouterBuilder { let params = vec![ id::set(1), // Each library is a database so only one of these ever exists date_captured::set(Utc::now().into()), - total_file_count::set(0), + total_object_count::set(0), library_db_size::set(library_db_size.to_string()), total_bytes_used::set(0.to_string()), total_bytes_capacity::set(total_capacity.to_string()), diff --git a/core/src/api/locations.rs b/core/src/api/locations.rs index aed2faa5f..18754df3d 100644 --- a/core/src/api/locations.rs +++ b/core/src/api/locations.rs @@ -6,7 +6,7 @@ use crate::{ scan_location, LocationCreateArgs, LocationError, LocationUpdateArgs, }, object::preview::THUMBNAIL_CACHE_DIR_NAME, - prisma::{file, file_path, indexer_rule, indexer_rules_in_location, location, tag}, + prisma::{file_path, indexer_rule, indexer_rules_in_location, location, object, tag}, }; use rspc::{self, ErrorCode, Type}; @@ -29,14 +29,14 @@ pub enum ExplorerContext { // Space(object_in_space::Data), } -file_path::include!(file_path_with_file { file }); -file::include!(file_with_paths { paths }); +file_path::include!(file_path_with_object { object }); +object::include!(object_with_file_paths { file_paths }); #[derive(Serialize, Deserialize, Type, Debug)] #[serde(tag = "type")] pub enum ExplorerItem { - Path(Box), - Object(Box), + Path(Box), + Object(Box), } #[derive(Clone, Serialize, Deserialize, Type, Debug)] @@ -100,7 +100,7 @@ pub(crate) fn mount() -> RouterBuilder { file_path::location_id::equals(location.id), file_path::parent_id::equals(Some(directory.id)), ]) - .include(file_path_with_file::include()) + .include(file_path_with_object::include()) .exec() .await?; @@ -109,16 +109,16 @@ pub(crate) fn mount() -> RouterBuilder { items: file_paths .into_iter() .map(|mut file_path| { - if let Some(file) = &mut file_path.file.as_mut() { + if let Some(object) = &mut file_path.object.as_mut() { // TODO: Use helper function to build this url as as the Rust file loading layer let thumb_path = library .config() .data_directory() .join(THUMBNAIL_CACHE_DIR_NAME) - .join(&file.cas_id) + .join(&object.cas_id) .with_extension("webp"); - file.has_thumbnail = thumb_path.exists(); + object.has_thumbnail = thumb_path.exists(); } ExplorerItem::Path(Box::new(file_path)) }) diff --git a/core/src/api/tags.rs b/core/src/api/tags.rs index 111d5e242..acc727fc5 100644 --- a/core/src/api/tags.rs +++ b/core/src/api/tags.rs @@ -4,10 +4,10 @@ use tracing::info; use uuid::Uuid; use crate::{ - api::locations::{file_with_paths, ExplorerContext, ExplorerData, ExplorerItem}, + api::locations::{object_with_file_paths, ExplorerContext, ExplorerData, ExplorerItem}, invalidate_query, object::preview::THUMBNAIL_CACHE_DIR_NAME, - prisma::{file, tag, tag_on_file}, + prisma::{object, tag, tag_on_object}, }; use super::{utils::LibraryRequest, RouterBuilder}; @@ -20,7 +20,7 @@ pub struct TagCreateArgs { #[derive(Debug, Type, Deserialize)] pub struct TagAssignArgs { - pub file_id: i32, + pub object_id: i32, pub tag_id: i32, pub unassign: bool, } @@ -52,32 +52,32 @@ pub(crate) fn mount() -> RouterBuilder { let files: Vec = library .db - .file() - .find_many(vec![file::tags::some(vec![tag_on_file::tag_id::equals( - tag_id, - )])]) - .include(file_with_paths::include()) + .object() + .find_many(vec![object::tags::some(vec![ + tag_on_object::tag_id::equals(tag_id), + ])]) + .include(object_with_file_paths::include()) .exec() .await? .into_iter() - .map(|mut file| { + .map(|mut object| { // sorry brendan // grab the first path and tac on the name - let oldest_path = &file.paths[0]; - file.name = Some(oldest_path.name.clone()); - file.extension = oldest_path.extension.clone(); + let oldest_path = &object.file_paths[0]; + object.name = Some(oldest_path.name.clone()); + object.extension = oldest_path.extension.clone(); // a long term fix for this would be to have the indexer give the Object a name and extension, sacrificing its own and only store newly found Path names that differ from the Object name let thumb_path = library .config() .data_directory() .join(THUMBNAIL_CACHE_DIR_NAME) - .join(&file.cas_id) + .join(&object.cas_id) .with_extension("webp"); - file.has_thumbnail = thumb_path.exists(); + object.has_thumbnail = thumb_path.exists(); - ExplorerItem::Object(Box::new(file)) + ExplorerItem::Object(Box::new(object)) }) .collect(); @@ -88,12 +88,12 @@ pub(crate) fn mount() -> RouterBuilder { items: files, }) }) - .library_query("getForFile", |_, file_id: i32, library| async move { + .library_query("getForFile", |_, object_id: i32, library| async move { Ok(library .db .tag() - .find_many(vec![tag::tag_files::some(vec![ - tag_on_file::file_id::equals(file_id), + .find_many(vec![tag::tag_objects::some(vec![ + tag_on_object::object_id::equals(object_id), ])]) .exec() .await?) @@ -128,17 +128,17 @@ pub(crate) fn mount() -> RouterBuilder { if args.unassign { library .db - .tag_on_file() - .delete(tag_on_file::tag_id_file_id(args.tag_id, args.file_id)) + .tag_on_object() + .delete(tag_on_object::tag_id_object_id(args.tag_id, args.object_id)) .exec() .await?; } else { library .db - .tag_on_file() + .tag_on_object() .create( tag::id::equals(args.tag_id), - file::id::equals(args.file_id), + object::id::equals(args.object_id), vec![], ) .exec() diff --git a/core/src/api/utils/invalidate.rs b/core/src/api/utils/invalidate.rs index 895a64dff..14639145a 100644 --- a/core/src/api/utils/invalidate.rs +++ b/core/src/api/utils/invalidate.rs @@ -92,7 +92,7 @@ impl InvalidRequests { #[allow(clippy::crate_in_macro_def)] macro_rules! invalidate_query { ($ctx:expr, $key:literal) => {{ - let _: &crate::library::LibraryContext = &$ctx; // Assert the context is the correct type + let ctx: &crate::library::LibraryContext = &$ctx; // Assert the context is the correct type #[cfg(debug_assertions)] { @@ -105,13 +105,15 @@ macro_rules! invalidate_query { .push(crate::api::utils::InvalidationRequest { key: $key, arg_ty: None, - macro_src: concat!(file!(), ":", line!()), + macro_src: concat!(file!(), ":", line!()), }) } } // The error are ignored here because they aren't mission critical. If they fail the UI might be outdated for a bit. - crate::api::utils::InvalidateOperationEvent::dangerously_create($key, serde_json::Value::Null) + ctx.emit(crate::api::CoreEvent::InvalidateOperation( + crate::api::utils::InvalidateOperationEvent::dangerously_create($key, serde_json::Value::Null) + )) }}; ($ctx:expr, $key:literal: $arg_ty:ty, $arg:expr $(,)?) => {{ let _: $arg_ty = $arg; // Assert the type the user provided is correct diff --git a/core/src/object/identifier_job.rs b/core/src/object/identifier_job.rs index 39e50fc54..d7c5ff478 100644 --- a/core/src/object/identifier_job.rs +++ b/core/src/object/identifier_job.rs @@ -1,7 +1,7 @@ use crate::{ job::{JobError, JobReportUpdate, JobResult, JobState, StatefulJob, WorkerContext}, library::LibraryContext, - prisma::{file, file_path, location}, + prisma::{file_path, location, object}, }; use chrono::{DateTime, FixedOffset}; @@ -162,10 +162,10 @@ impl StatefulJob for FileIdentifierJob { for file_path in &file_paths { // get the cas_id and extract metadata match assemble_object_metadata(&data.location_path, file_path).await { - Ok(file) => { - let cas_id = file.cas_id.clone(); + Ok(object) => { + let cas_id = object.cas_id.clone(); // create entry into chunks for created file data - chunk.insert(file_path.id, file); + chunk.insert(file_path.id, object); cas_lookup.insert(cas_id, file_path.id); } Err(e) => { @@ -177,23 +177,23 @@ impl StatefulJob for FileIdentifierJob { // find all existing files by cas id let generated_cas_ids = chunk.values().map(|c| c.cas_id.clone()).collect(); - let existing_files = db - .file() - .find_many(vec![file::cas_id::in_vec(generated_cas_ids)]) + let existing_objects = db + .object() + .find_many(vec![object::cas_id::in_vec(generated_cas_ids)]) .exec() .await?; - info!("Found {} existing files", existing_files.len()); + info!("Found {} existing files", existing_objects.len()); - for existing_file in &existing_files { + for existing_object in &existing_objects { if let Err(e) = db .file_path() .update( file_path::location_id_id( state.init.location_id, - *cas_lookup.get(&existing_file.cas_id).unwrap(), + *cas_lookup.get(&existing_object.cas_id).unwrap(), ), - vec![file_path::file_id::set(Some(existing_file.id))], + vec![file_path::object_id::set(Some(existing_object.id))], ) .exec() .await @@ -202,26 +202,26 @@ impl StatefulJob for FileIdentifierJob { } } - let existing_files_cas_ids = existing_files + let existing_object_cas_ids = existing_objects .iter() - .map(|file| file.cas_id.clone()) + .map(|object| object.cas_id.clone()) .collect::>(); - // extract files that don't already exist in the database - let new_files = chunk + // extract objects that don't already exist in the database + let new_objects = chunk .iter() .map(|(_id, create_file)| create_file) - .filter(|create_file| !existing_files_cas_ids.contains(&create_file.cas_id)) + .filter(|create_file| !existing_object_cas_ids.contains(&create_file.cas_id)) .collect::>(); - if !new_files.is_empty() { + if !new_objects.is_empty() { // assemble prisma values for new unique files - let mut values = Vec::with_capacity(new_files.len() * 3); - for file in &new_files { + let mut values = Vec::with_capacity(new_objects.len() * 3); + for object in &new_objects { values.extend([ - PrismaValue::String(file.cas_id.clone()), - PrismaValue::Int(file.size_in_bytes), - PrismaValue::DateTime(file.date_created), + PrismaValue::String(object.cas_id.clone()), + PrismaValue::Int(object.size_in_bytes), + PrismaValue::DateTime(object.date_created), ]); } @@ -230,9 +230,9 @@ impl StatefulJob for FileIdentifierJob { let created_files: Vec = db ._query_raw(Raw::new( &format!( - "INSERT INTO files (cas_id, size_in_bytes, date_created) VALUES {} + "INSERT INTO object (cas_id, size_in_bytes, date_created) VALUES {} ON CONFLICT (cas_id) DO NOTHING RETURNING id, cas_id", - vec!["({}, {}, {})"; new_files.len()].join(",") + vec!["({}, {}, {})"; new_objects.len()].join(",") ), values, )) @@ -256,7 +256,7 @@ impl StatefulJob for FileIdentifierJob { state.init.location_id, *cas_lookup.get(&created_file.cas_id).unwrap(), ), - vec![file_path::file_id::set(Some(created_file.id))], + vec![file_path::object_id::set(Some(created_file.id))], ) .exec() .await @@ -305,7 +305,7 @@ impl StatefulJob for FileIdentifierJob { fn orphan_path_filters(location_id: i32, file_path_id: Option) -> Vec { let mut params = vec![ - file_path::file_id::equals(None), + file_path::object_id::equals(None), file_path::is_dir::equals(false), file_path::location_id::equals(location_id), ]; @@ -329,7 +329,7 @@ async fn count_orphan_file_paths( .db .file_path() .count(vec![ - file_path::file_id::equals(None), + file_path::object_id::equals(None), file_path::is_dir::equals(false), file_path::location_id::equals(location_id), ]) diff --git a/core/src/object/mod.rs b/core/src/object/mod.rs index d5dd6cd56..3e151e7d0 100644 --- a/core/src/object/mod.rs +++ b/core/src/object/mod.rs @@ -28,7 +28,7 @@ pub struct ObjectsForExplorer { #[derive(Debug, Serialize, Deserialize, Type)] pub enum ObjectData { - Object(Box), + Object(Box), Path(Box), } diff --git a/core/src/object/preview/thumb.rs b/core/src/object/preview/thumb.rs index e2e94d868..64bb59ae6 100644 --- a/core/src/object/preview/thumb.rs +++ b/core/src/object/preview/thumb.rs @@ -38,7 +38,7 @@ pub struct ThumbnailJobState { root_path: PathBuf, } -file_path::include!(file_path_with_file { file }); +file_path::include!(file_path_with_object { object }); #[derive(Debug, Serialize, Deserialize, Clone, Copy)] enum ThumbnailJobStepKind { @@ -49,7 +49,7 @@ enum ThumbnailJobStepKind { #[derive(Debug, Serialize, Deserialize)] pub struct ThumbnailJobStep { - file: file_path_with_file::Data, + file_path: file_path_with_object::Data, kind: ThumbnailJobStepKind, } @@ -179,7 +179,7 @@ impl StatefulJob for ThumbnailJob { let step = &state.steps[0]; ctx.progress(vec![JobReportUpdate::Message(format!( "Processing {}", - step.file.materialized_path + step.file_path.materialized_path ))]); let data = state @@ -188,16 +188,16 @@ impl StatefulJob for ThumbnailJob { .expect("critical error: missing data on job state"); // assemble the file path - let path = data.root_path.join(&step.file.materialized_path); + let path = data.root_path.join(&step.file_path.materialized_path); trace!("image_file {:?}", step); // get cas_id, if none found skip - let cas_id = match &step.file.file { + let cas_id = match &step.file_path.object { Some(f) => f.cas_id.clone(), _ => { warn!( "skipping thumbnail generation for {}", - step.file.materialized_path + step.file_path.materialized_path ); return Ok(()); } @@ -327,10 +327,10 @@ async fn get_files_by_extension( .db .file_path() .find_many(params) - .include(file_path_with_file::include()) + .include(file_path_with_object::include()) .exec() .await? .into_iter() - .map(|file| ThumbnailJobStep { file, kind }) + .map(|file_path| ThumbnailJobStep { file_path, kind }) .collect()) } diff --git a/core/src/util/db.rs b/core/src/util/db.rs index 949dec7f3..9c600b527 100644 --- a/core/src/util/db.rs +++ b/core/src/util/db.rs @@ -8,7 +8,7 @@ pub enum MigrationError { #[error("An error occurred while initialising a new database connection: {0}")] NewClient(#[from] Box), #[cfg(debug_assertions)] - #[error("An error occurred during migartion: {0}")] + #[error("An error occurred during migration: {0}")] MigrateFailed(#[from] DbPushError), #[cfg(not(debug_assertions))] #[error("An error occurred during migration: {0}")] diff --git a/package.json b/package.json index 22e7effb3..0c204f884 100644 --- a/package.json +++ b/package.json @@ -3,11 +3,11 @@ "version": "0.0.0", "private": true, "scripts": { - "prep": "pnpm db:gen && cargo test", + "prep": "pnpm gen:prisma && cargo test", "build": "turbo run build", "landing-web": "turbo run dev --parallel --filter=@sd/landing --filter=@sd/web", - "db:migrate": "cd crates && cargo prisma migrate dev", - "db:gen": "cd core && cargo prisma generate", + "gen:migrations": "cd core && cargo prisma migrate dev", + "gen:prisma": "cd core && cargo prisma generate", "format": "prettier --config .prettierrc.cli.js --write \"**/*.{ts,tsx,html,scss,json,yml,md}\"", "desktop": "pnpm --filter @sd/desktop --", "web": "pnpm --filter @sd/web -- ", diff --git a/packages/client/src/core.ts b/packages/client/src/core.ts index c3d16c9aa..454d21f64 100644 --- a/packages/client/src/core.ts +++ b/packages/client/src/core.ts @@ -53,11 +53,9 @@ export type ExplorerContext = { type: "Location" } & Location | { type: "Tag" } export interface ExplorerData { context: ExplorerContext, items: Array } -export type ExplorerItem = { type: "Path" } & { id: number, is_dir: boolean, location_id: number, materialized_path: string, name: string, extension: string | null, file_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string, file: File | null } | { type: "Object" } & { id: number, cas_id: string, integrity_checksum: string | null, name: string | null, extension: string | null, kind: number, size_in_bytes: string, key_id: number | null, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, ipfs_id: string | null, note: string | null, date_created: string, date_modified: string, date_indexed: string, paths: Array } +export type ExplorerItem = { type: "Path" } & { id: number, is_dir: boolean, location_id: number, materialized_path: string, name: string, extension: string | null, object_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string, object: Object | null } | { type: "Object" } & { id: number, cas_id: string, integrity_checksum: string | null, name: string | null, extension: string | null, kind: number, size_in_bytes: string, key_id: number | null, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, ipfs_id: string | null, note: string | null, date_created: string, date_modified: string, date_indexed: string, file_paths: Array } -export interface File { id: number, cas_id: string, integrity_checksum: string | null, name: string | null, extension: string | null, kind: number, size_in_bytes: string, key_id: number | null, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, ipfs_id: string | null, note: string | null, date_created: string, date_modified: string, date_indexed: string } - -export interface FilePath { id: number, is_dir: boolean, location_id: number, materialized_path: string, name: string, extension: string | null, file_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string } +export interface FilePath { id: number, is_dir: boolean, location_id: number, materialized_path: string, name: string, extension: string | null, object_id: number | null, parent_id: number | null, key_id: number | null, date_created: string, date_modified: string, date_indexed: string } export interface GenerateThumbsForLocationArgs { id: number, path: string } @@ -93,17 +91,19 @@ export interface NodeConfig { version: string | null, id: string, name: string, export interface NodeState { version: string | null, id: string, name: string, p2p_port: number | null, data_path: string } +export interface Object { id: number, cas_id: string, integrity_checksum: string | null, name: string | null, extension: string | null, kind: number, size_in_bytes: string, key_id: number | null, hidden: boolean, favorite: boolean, important: boolean, has_thumbnail: boolean, has_thumbstrip: boolean, has_video_preview: boolean, ipfs_id: string | null, note: string | null, date_created: string, date_modified: string, date_indexed: string } + export type RuleKind = "AcceptFilesByGlob" | "RejectFilesByGlob" | "AcceptIfChildrenDirectoriesArePresent" | "RejectIfChildrenDirectoriesArePresent" export interface SetFavoriteArgs { id: number, favorite: boolean } export interface SetNoteArgs { id: number, note: string | null } -export interface Statistics { id: number, date_captured: string, total_file_count: number, library_db_size: string, total_bytes_used: string, total_bytes_capacity: string, total_unique_bytes: string, total_bytes_free: string, preview_media_bytes: string } +export interface Statistics { id: number, date_captured: string, total_object_count: number, library_db_size: string, total_bytes_used: string, total_bytes_capacity: string, total_unique_bytes: string, total_bytes_free: string, preview_media_bytes: string } -export interface Tag { id: number, pub_id: Array, name: string | null, color: string | null, total_files: number | null, redundancy_goal: number | null, date_created: string, date_modified: string } +export interface Tag { id: number, pub_id: Array, name: string | null, color: string | null, total_objects: number | null, redundancy_goal: number | null, date_created: string, date_modified: string } -export interface TagAssignArgs { file_id: number, tag_id: number, unassign: boolean } +export interface TagAssignArgs { object_id: number, tag_id: number, unassign: boolean } export interface TagCreateArgs { name: string, color: string } diff --git a/packages/interface/src/AppRouter.tsx b/packages/interface/src/AppRouter.tsx index 363be4cfb..fc8c33ca6 100644 --- a/packages/interface/src/AppRouter.tsx +++ b/packages/interface/src/AppRouter.tsx @@ -1,5 +1,5 @@ import { useCurrentLibrary, useInvalidateQuery } from '@sd/client'; -import { Route, Routes } from 'react-router-dom'; +import { Navigate, Route, Routes } from 'react-router-dom'; import { AppLayout } from './AppLayout'; import { NotFound } from './NotFound'; @@ -10,7 +10,6 @@ import { DebugScreen } from './screens/Debug'; import { LocationExplorer } from './screens/LocationExplorer'; import { OverviewScreen } from './screens/Overview'; import { PhotosScreen } from './screens/Photos'; -import { RedirectPage } from './screens/Redirect'; import { TagExplorer } from './screens/TagExplorer'; import { SettingsScreen } from './screens/settings/Settings'; import AppearanceSettings from './screens/settings/client/AppearanceSettings'; @@ -49,12 +48,12 @@ export function AppRouter() { Please select or create a library in the sidebar. +

Please select or create a library in the sidebar.

} /> ) : ( <> - } /> + } /> } /> } /> } /> diff --git a/packages/interface/src/components/explorer/ExplorerContextMenu.tsx b/packages/interface/src/components/explorer/ExplorerContextMenu.tsx index 7dd7424ae..03479e169 100644 --- a/packages/interface/src/components/explorer/ExplorerContextMenu.tsx +++ b/packages/interface/src/components/explorer/ExplorerContextMenu.tsx @@ -34,7 +34,7 @@ const AssignTagMenuItems = (props: { objectId: number }) => { assignTag({ tag_id: tag.id, - file_id: props.objectId, + object_id: props.objectId, unassign: active }); }} diff --git a/packages/interface/src/components/explorer/FileItem.tsx b/packages/interface/src/components/explorer/FileItem.tsx index fbf98e4da..9ede86e8d 100644 --- a/packages/interface/src/components/explorer/FileItem.tsx +++ b/packages/interface/src/components/explorer/FileItem.tsx @@ -21,7 +21,7 @@ function FileItem({ data, selected, index, ...rest }: Props) { return (
{ - const objectId = isObject(data) ? data.id : data.file?.id; + const objectId = isObject(data) ? data.id : data.object?.id; if (objectId != undefined) { getExplorerStore().contextMenuObjectId = objectId; if (index != undefined) { diff --git a/packages/interface/src/components/explorer/FileThumb.tsx b/packages/interface/src/components/explorer/FileThumb.tsx index 1acb66384..4f480299c 100644 --- a/packages/interface/src/components/explorer/FileThumb.tsx +++ b/packages/interface/src/components/explorer/FileThumb.tsx @@ -23,7 +23,7 @@ export default function FileThumb({ data, ...props }: Props) { if (isPath(data) && data.is_dir) return ; - const cas_id = isObject(data) ? data.cas_id : data.file?.cas_id; + const cas_id = isObject(data) ? data.cas_id : data.object?.cas_id; if (cas_id) { // this won't work @@ -32,7 +32,7 @@ export default function FileThumb({ data, ...props }: Props) { const has_thumbnail = isObject(data) ? data.has_thumbnail : isPath(data) - ? data.file?.has_thumbnail + ? data.object?.has_thumbnail : new_thumbnail; const url = platform.getThumbnailUrlById(cas_id); diff --git a/packages/interface/src/components/explorer/Inspector.tsx b/packages/interface/src/components/explorer/Inspector.tsx index fec89f030..51ff16ae4 100644 --- a/packages/interface/src/components/explorer/Inspector.tsx +++ b/packages/interface/src/components/explorer/Inspector.tsx @@ -1,6 +1,6 @@ import { ShareIcon } from '@heroicons/react/24/solid'; import { useLibraryQuery } from '@sd/client'; -import { ExplorerContext, ExplorerItem, File, FilePath, Location } from '@sd/client'; +import { ExplorerContext, ExplorerItem, FilePath, Location, Object } from '@sd/client'; import { Button, TextArea } from '@sd/ui'; import clsx from 'clsx'; import moment from 'moment'; @@ -24,7 +24,7 @@ interface Props { export const Inspector = (props: Props) => { const is_dir = props.data?.type === 'Path' ? props.data.is_dir : false; - const objectData = isObject(props.data) ? props.data : props.data.file; + const objectData = isObject(props.data) ? props.data : props.data.object; // this prevents the inspector from fetching data when the user is navigating quickly const [readyToFetch, setReadyToFetch] = useState(false); diff --git a/packages/interface/src/components/explorer/VirtualizedList.tsx b/packages/interface/src/components/explorer/VirtualizedList.tsx index d668bffb2..771867997 100644 --- a/packages/interface/src/components/explorer/VirtualizedList.tsx +++ b/packages/interface/src/components/explorer/VirtualizedList.tsx @@ -198,6 +198,5 @@ const WrappedItem: React.FC = ({ item, index, isSelected, kind // selected={isSelected} // /> // ); - // // eslint-disable-next-line react-hooks/exhaustive-deps // }, [item, index, isSelected]); }; diff --git a/packages/interface/src/components/explorer/inspector/FavoriteButton.tsx b/packages/interface/src/components/explorer/inspector/FavoriteButton.tsx index c95ef374f..a0ad17115 100644 --- a/packages/interface/src/components/explorer/inspector/FavoriteButton.tsx +++ b/packages/interface/src/components/explorer/inspector/FavoriteButton.tsx @@ -1,11 +1,11 @@ import { useLibraryMutation } from '@sd/client'; -import { File } from '@sd/client'; +import { Object as SDObject } from '@sd/client'; import { Button } from '@sd/ui'; import { Heart } from 'phosphor-react'; import { useEffect, useState } from 'react'; interface Props { - data: File; + data: SDObject; } export default function FavoriteButton(props: Props) { diff --git a/packages/interface/src/components/explorer/inspector/Note.tsx b/packages/interface/src/components/explorer/inspector/Note.tsx index 500cafe79..1eaa0e1be 100644 --- a/packages/interface/src/components/explorer/inspector/Note.tsx +++ b/packages/interface/src/components/explorer/inspector/Note.tsx @@ -1,6 +1,5 @@ -/* eslint-disable react-hooks/exhaustive-deps */ import { useLibraryMutation } from '@sd/client'; -import { File } from '@sd/client'; +import { Object as SDObject } from '@sd/client'; import { TextArea } from '@sd/ui'; import { debounce } from 'lodash'; import { useCallback, useState } from 'react'; @@ -9,25 +8,28 @@ import { Divider } from './Divider'; import { MetaItem } from './MetaItem'; interface Props { - data: File; + data: SDObject; } export default function Note(props: Props) { // notes are cached in a store by their file id // this is so we can ensure every note has been sent to Rust even // when quickly navigating files, which cancels update function - const [note, setNote] = useState((props.data as File)?.note || ''); + const [note, setNote] = useState(props.data.note || ''); const { mutate: fileSetNote } = useLibraryMutation('files.setNote'); const debouncedNote = useCallback( - debounce((note: string) => { - fileSetNote({ - id: props.data.id, - note - }); - }, 2000), - [props.data.id] + (note: string) => + debounce( + () => + fileSetNote({ + id: props.data.id, + note + }), + 2000 + ), + [props.data.id, fileSetNote] ); // when input is updated, cache note diff --git a/packages/interface/src/components/explorer/utils.ts b/packages/interface/src/components/explorer/utils.ts index 0cdf23b85..5257353d9 100644 --- a/packages/interface/src/components/explorer/utils.ts +++ b/packages/interface/src/components/explorer/utils.ts @@ -1,4 +1,4 @@ -import { ExplorerItem, File, FilePath } from '@sd/client'; +import { ExplorerItem, FilePath } from '@sd/client'; export function isPath(item: ExplorerItem): item is Extract { return item.type === 'Path'; diff --git a/packages/interface/src/components/layout/Sidebar.tsx b/packages/interface/src/components/layout/Sidebar.tsx index 9c06f598d..3ab74b69c 100644 --- a/packages/interface/src/components/layout/Sidebar.tsx +++ b/packages/interface/src/components/layout/Sidebar.tsx @@ -186,10 +186,10 @@ export function Sidebar() { buttonText={isLoadingLibraries ? 'Loading...' : library ? library.config.name : ' '} buttonTextClassName={library === null || isLoadingLibraries ? 'text-gray-300' : undefined} items={[ - libraries?.map((library) => ({ - name: library.config.name, - selected: library.uuid === library?.uuid, - onPress: () => switchLibrary(library.uuid) + libraries?.map((lib) => ({ + name: lib.config.name, + selected: lib.uuid === library?.uuid, + onPress: () => switchLibrary(lib.uuid) })) || [], [ { diff --git a/packages/interface/src/hooks/useCounter.ts b/packages/interface/src/hooks/useCounter.ts index b6d4cacc4..7132e8f2f 100644 --- a/packages/interface/src/hooks/useCounter.ts +++ b/packages/interface/src/hooks/useCounter.ts @@ -53,13 +53,12 @@ const useCounter = ({ name, start = 0, end, duration = 2, saveState = true }: Us duration, easing: 'easeOutCubic' }); - + useEffect(() => { if (saveState && value == end) { setLastValue(name, end); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [end, value]); + }, [end, name, saveState, setLastValue, value]); if (start === end) return end; diff --git a/packages/interface/src/screens/LocationExplorer.tsx b/packages/interface/src/screens/LocationExplorer.tsx index a6751bfdc..2816fba4c 100644 --- a/packages/interface/src/screens/LocationExplorer.tsx +++ b/packages/interface/src/screens/LocationExplorer.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react-hooks/exhaustive-deps */ import { getExplorerStore, useCurrentLibrary, useLibraryQuery } from '@sd/client'; import { useEffect } from 'react'; import { useParams, useSearchParams } from 'react-router-dom'; diff --git a/packages/interface/src/screens/Overview.tsx b/packages/interface/src/screens/Overview.tsx index 45d53ce23..7bf735605 100644 --- a/packages/interface/src/screens/Overview.tsx +++ b/packages/interface/src/screens/Overview.tsx @@ -100,42 +100,22 @@ export const OverviewScreen = () => { // get app props from context useEffect(() => { - if (platform.demoMode === true) { - if (!Object.entries(overviewStats).length) - setOverviewStats({ - total_bytes_capacity: '8093333345230', - preview_media_bytes: '2304387532', - library_db_size: '83345230', - total_file_count: '20342345', - total_bytes_free: '89734502034', - total_bytes_used: '8093333345230', - total_unique_bytes: '9347397' - }); - } else { - const newStatistics: OverviewStats = { - total_bytes_capacity: '0', - preview_media_bytes: '0', - library_db_size: '0', - total_file_count: '0', - total_bytes_free: '0', - total_bytes_used: '0', - total_unique_bytes: '0' - }; + const newStatistics: OverviewStats = { + total_bytes_capacity: '0', + preview_media_bytes: '0', + library_db_size: '0', + total_object_count: '0', + total_bytes_free: '0', + total_bytes_used: '0', + total_unique_bytes: '0' + }; - Object.entries((libraryStatistics as Statistics) || {}).forEach(([key, value]) => { - newStatistics[key as keyof Statistics] = `${value}`; - }); + Object.entries((libraryStatistics as Statistics) || {}).forEach(([key, value]) => { + newStatistics[key as keyof Statistics] = `${value}`; + }); - setOverviewStats(newStatistics); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [platform, libraryStatistics]); - - // useEffect(() => { - // setTimeout(() => { - // setOverviewStat('total_bytes_capacity', '4093333345230'); - // }, 2000); - // }, [overviewStats]); + setOverviewStats(newStatistics); + }, [platform, libraryStatistics, setOverviewStats]); const displayableStatItems = Object.keys(StatItemNames) as unknown as keyof typeof StatItemNames; diff --git a/packages/interface/src/screens/Redirect.tsx b/packages/interface/src/screens/Redirect.tsx deleted file mode 100644 index 95e663b77..000000000 --- a/packages/interface/src/screens/Redirect.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { useEffect } from 'react'; -import { useNavigate } from 'react-router'; - -export interface RedirectPageProps { - to: string; -} - -export const RedirectPage: React.FC = (props) => { - const { to: destination } = props; - - const navigate = useNavigate(); - - useEffect(() => { - navigate(destination); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return null; -}; diff --git a/packages/interface/src/screens/settings/library/LibraryGeneralSettings.tsx b/packages/interface/src/screens/settings/library/LibraryGeneralSettings.tsx index c828bac8c..fe771c5d7 100644 --- a/packages/interface/src/screens/settings/library/LibraryGeneralSettings.tsx +++ b/packages/interface/src/screens/settings/library/LibraryGeneralSettings.tsx @@ -35,16 +35,14 @@ export default function LibraryGeneralSettings() { }); } } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [nameDebounced, descriptionDebounced]); + }, [nameDebounced, descriptionDebounced, library, editLibrary]); useEffect(() => { if (library) { setName(library.config.name); setDescription(library.config.description); } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [libraries]); + }, [libraries, library]); useEffect(() => { if (library) { @@ -52,7 +50,6 @@ export default function LibraryGeneralSettings() { setName(library.config.name); setDescription(library.config.description); } - // eslint-disable-next-line react-hooks/exhaustive-deps }, [library]); useEffect(() => { diff --git a/packages/interface/src/screens/settings/library/TagsSettings.tsx b/packages/interface/src/screens/settings/library/TagsSettings.tsx index 7276ca920..005b38e69 100644 --- a/packages/interface/src/screens/settings/library/TagsSettings.tsx +++ b/packages/interface/src/screens/settings/library/TagsSettings.tsx @@ -1,5 +1,5 @@ import { TrashIcon } from '@heroicons/react/24/outline'; -import { useLibraryMutation, useLibraryQuery } from '@sd/client'; +import { Tag, useLibraryMutation, useLibraryQuery } from '@sd/client'; import { TagUpdateArgs } from '@sd/client'; import { Button, Input } from '@sd/ui'; import clsx from 'clsx'; @@ -23,11 +23,7 @@ export default function TagsSettings() { const { data: tags } = useLibraryQuery(['tags.list']); - const [selectedTag, setSelectedTag] = useState(null); - - const currentTag = useMemo(() => { - return tags?.find((t) => t.id === selectedTag); - }, [tags, selectedTag]); + const [selectedTag, setSelectedTag] = useState(tags?.[0] ?? null); const { mutate: createTag, isLoading } = useLibraryMutation('tags.create', { onError: (e) => { @@ -38,25 +34,27 @@ export default function TagsSettings() { } }); - const { mutate: updateTag } = useLibraryMutation('tags.update'); + const updateTag = useLibraryMutation('tags.update'); - const { mutate: deleteTag, isLoading: tagDeleteLoading } = useLibraryMutation('tags.delete'); - - // set default selected tag - useEffect(() => { - if (!currentTag && tags?.length) setSelectedTag(tags[0].id); - }, [currentTag, tags]); - - useEffect(() => { - reset(currentTag); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [currentTag]); - - const { register, handleSubmit, watch, reset, control } = useForm({ - defaultValues: currentTag as TagUpdateArgs + const deleteTag = useLibraryMutation('tags.delete', { + onSuccess: () => { + setSelectedTag(null); + } }); - const submitTagUpdate = handleSubmit((data) => updateTag(data)); + const { register, handleSubmit, watch, reset, control } = useForm({ + defaultValues: selectedTag as TagUpdateArgs + }); + + const setTag = useCallback( + (tag: Tag | null) => { + if (tag) reset(tag); + setSelectedTag(tag); + }, + [setSelectedTag, reset] + ); + + const submitTagUpdate = handleSubmit((data) => updateTag.mutate(data)); // eslint-disable-next-line react-hooks/exhaustive-deps const autoUpdateTag = useCallback(useDebounce(submitTagUpdate, 500), []); @@ -113,11 +111,11 @@ export default function TagsSettings() {
{tags?.map((tag) => (
setSelectedTag(tag.id === selectedTag ? null : tag.id)} + onClick={() => setTag(tag.id === selectedTag?.id ? null : tag)} key={tag.id} className={clsx( 'flex items-center rounded px-1.5 py-0.5', - selectedTag === tag.id && 'ring' + selectedTag?.id === tag.id && 'ring' )} style={{ backgroundColor: tag.color + 'CC' }} > @@ -126,7 +124,7 @@ export default function TagsSettings() { ))}
- {currentTag ? ( + {selectedTag ? (
@@ -159,9 +157,9 @@ export default function TagsSettings() { title="Delete Tag" description="Are you sure you want to delete this tag? This cannot be undone and tagged files will be unlinked." ctaAction={() => { - deleteTag(currentTag.id); + deleteTag.mutate(selectedTag.id); }} - loading={tagDeleteLoading} + loading={deleteTag.isLoading} ctaDanger ctaLabel="Delete" trigger={