mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-10-11 12:14:04 -04:00
fix indexer lag (reimplement debounced progress event for jobs)
This commit is contained in:
10 files changed
+265
-424
No files matched your search
+106
-106
@@ -2,136 +2,136 @@
|
||||
|
||||
export type Operations = {
|
||||
queries:
|
||||
{ key: ["library.getStatistics", LibraryArgs<null>], result: Statistics } |
|
||||
{ key: ["volumes.get"], result: Array<Volume> } |
|
||||
{ key: ["tags.get", LibraryArgs<number>], result: Tag | null } |
|
||||
{ key: ["tags.getForFile", LibraryArgs<number>], result: Array<Tag> } |
|
||||
{ key: ["tags.getExplorerData", LibraryArgs<number>], result: ExplorerData } |
|
||||
{ key: ["locations.getById", LibraryArgs<number>], result: Location | null } |
|
||||
{ key: ["locations.indexer_rulesget", LibraryArgs<number>], result: IndexerRule } |
|
||||
{ key: ["files.readMetadata", LibraryArgs<number>], result: null } |
|
||||
{ key: ["jobs.getHistory", LibraryArgs<null>], result: Array<JobReport> } |
|
||||
{ key: ["locations.getById", LibraryArgs<number>], result: Location | null } |
|
||||
{ key: ["locations.getExplorerData", LibraryArgs<LocationExplorerArgs>], result: ExplorerData } |
|
||||
{ key: ["getNode"], result: NodeState } |
|
||||
{ key: ["locations.get", LibraryArgs<null>], result: Array<Location> } |
|
||||
{ key: ["tags.get", LibraryArgs<number>], result: Tag | null } |
|
||||
{ key: ["locations.indexer_ruleslist", LibraryArgs<null>], result: Array<IndexerRule> } |
|
||||
{ key: ["library.get"], result: Array<LibraryConfigWrapped> } |
|
||||
{ key: ["tags.getAll", LibraryArgs<null>], result: Array<Tag> } |
|
||||
{ key: ["jobs.getHistory", LibraryArgs<null>], result: Array<JobReport> } |
|
||||
{ key: ["getNode"], result: NodeState } |
|
||||
{ key: ["library.getStatistics", LibraryArgs<null>], result: Statistics } |
|
||||
{ key: ["version"], result: string } |
|
||||
{ key: ["jobs.getRunning", LibraryArgs<null>], result: Array<JobReport> } |
|
||||
{ key: ["version"], result: string },
|
||||
{ key: ["library.get"], result: Array<LibraryConfigWrapped> } |
|
||||
{ key: ["locations.get", LibraryArgs<null>], result: Array<Location> } |
|
||||
{ key: ["volumes.get"], result: Array<Volume> } |
|
||||
{ key: ["locations.indexer_ruleslist", LibraryArgs<null>], result: Array<IndexerRule> } |
|
||||
{ key: ["locations.getExplorerData", LibraryArgs<LocationExplorerArgs>], result: ExplorerData } |
|
||||
{ key: ["tags.getExplorerData", LibraryArgs<number>], result: ExplorerData },
|
||||
mutations:
|
||||
{ key: ["locations.create", LibraryArgs<LocationCreateArgs>], result: Location } |
|
||||
{ key: ["locations.fullRescan", LibraryArgs<number>], result: null } |
|
||||
{ key: ["locations.quickRescan", LibraryArgs<null>], result: null } |
|
||||
{ key: ["files.setFavorite", LibraryArgs<SetFavoriteArgs>], result: null } |
|
||||
{ key: ["jobs.generateThumbsForLocation", LibraryArgs<GenerateThumbsForLocationArgs>], result: null } |
|
||||
{ key: ["locations.update", LibraryArgs<LocationUpdateArgs>], result: null } |
|
||||
{ key: ["tags.update", LibraryArgs<TagUpdateArgs>], result: null } |
|
||||
{ key: ["library.create", string], result: null } |
|
||||
{ key: ["files.delete", LibraryArgs<number>], result: null } |
|
||||
{ key: ["tags.create", LibraryArgs<TagCreateArgs>], result: Tag } |
|
||||
{ key: ["tags.delete", LibraryArgs<number>], result: null } |
|
||||
{ key: ["tags.assign", LibraryArgs<TagAssignArgs>], result: null } |
|
||||
{ key: ["library.edit", EditLibraryArgs], result: null } |
|
||||
{ key: ["locations.indexer_rulescreate", LibraryArgs<IndexerRuleCreateArgs>], result: IndexerRule } |
|
||||
{ key: ["files.setNote", LibraryArgs<SetNoteArgs>], result: null } |
|
||||
{ key: ["jobs.identifyUniqueFiles", LibraryArgs<IdentifyUniqueFilesArgs>], result: null } |
|
||||
{ key: ["library.delete", string], result: null } |
|
||||
{ key: ["locations.indexer_rulesdelete", LibraryArgs<number>], result: null } |
|
||||
{ key: ["library.create", string], result: null } |
|
||||
{ key: ["locations.quickRescan", LibraryArgs<null>], result: null } |
|
||||
{ key: ["locations.update", LibraryArgs<LocationUpdateArgs>], result: null } |
|
||||
{ key: ["tags.assign", LibraryArgs<TagAssignArgs>], result: null } |
|
||||
{ key: ["locations.fullRescan", LibraryArgs<number>], result: null } |
|
||||
{ key: ["files.delete", LibraryArgs<number>], result: null } |
|
||||
{ key: ["files.setFavorite", LibraryArgs<SetFavoriteArgs>], result: null } |
|
||||
{ key: ["files.setNote", LibraryArgs<SetNoteArgs>], result: null } |
|
||||
{ key: ["tags.delete", LibraryArgs<number>], result: null } |
|
||||
{ key: ["tags.update", LibraryArgs<TagUpdateArgs>], result: null } |
|
||||
{ key: ["locations.indexer_rulescreate", LibraryArgs<IndexerRuleCreateArgs>], result: IndexerRule } |
|
||||
{ key: ["locations.create", LibraryArgs<LocationCreateArgs>], result: Location } |
|
||||
{ key: ["jobs.generateThumbsForLocation", LibraryArgs<GenerateThumbsForLocationArgs>], result: null } |
|
||||
{ key: ["library.edit", EditLibraryArgs], result: null } |
|
||||
{ key: ["library.delete", string], result: null } |
|
||||
{ key: ["jobs.identifyUniqueFiles", LibraryArgs<IdentifyUniqueFilesArgs>], result: null } |
|
||||
{ key: ["tags.create", LibraryArgs<TagCreateArgs>], result: Tag } |
|
||||
{ key: ["locations.delete", LibraryArgs<number>], result: null },
|
||||
subscriptions:
|
||||
{ key: ["invalidateQuery"], result: InvalidateOperationEvent } |
|
||||
{ key: ["jobs.newThumbnail", LibraryArgs<null>], result: string }
|
||||
};
|
||||
|
||||
export interface InvalidateOperationEvent { key: string, arg: any }
|
||||
|
||||
export interface LocationExplorerArgs { location_id: number, path: string, limit: number, cursor: string | null }
|
||||
|
||||
export interface LibraryConfig { version: string | null, name: string, description: string }
|
||||
|
||||
export interface FileInSpace { date_created: string, space_id: number, space: Space | null, file_id: number, file: File | null }
|
||||
|
||||
export interface LabelOnFile { date_created: string, label_id: number, label: Label | null, file_id: number, file: File | null }
|
||||
|
||||
export interface JobReport { id: string, name: string, data: Array<number> | null, date_created: string, date_modified: string, status: JobStatus, task_count: number, completed_task_count: number, message: string, seconds_elapsed: number }
|
||||
|
||||
export type ExplorerItem = { type: "Path" } & FilePath | { type: "Object" } & File
|
||||
|
||||
export interface NodeConfig { version: string | null, id: string, name: string, p2p_port: number | null }
|
||||
|
||||
export interface LibraryConfigWrapped { uuid: string, config: LibraryConfig }
|
||||
|
||||
export interface ConfigMetadata { version: string | null }
|
||||
|
||||
export interface MediaData { id: number, pixel_width: number | null, pixel_height: number | null, longitude: number | null, latitude: number | null, fps: number | null, capture_device_make: string | null, capture_device_model: string | null, capture_device_software: string | null, duration_seconds: number | null, codecs: string | null, streams: number | null, files: File | null | null }
|
||||
|
||||
export interface Space { id: number, pub_id: Array<number>, name: string | null, description: string | null, date_created: string, date_modified: string, files: Array<FileInSpace> | null }
|
||||
|
||||
export interface Label { id: number, pub_id: Array<number>, name: string | null, date_created: string, date_modified: string, label_files: Array<LabelOnFile> | null }
|
||||
|
||||
export interface Volume { name: string, mount_point: string, total_capacity: bigint, available_capacity: bigint, is_removable: boolean, disk_type: string | null, file_system: string | null, is_root_filesystem: boolean }
|
||||
|
||||
export interface ExplorerData { context: ExplorerContext, items: Array<ExplorerItem> }
|
||||
|
||||
export interface LibraryArgs<T> { library_id: string, arg: T }
|
||||
|
||||
export interface TagUpdateArgs { id: number, name: string | null, color: string | null }
|
||||
|
||||
export interface LocationCreateArgs { path: string, indexer_rules_ids: Array<number> }
|
||||
|
||||
export interface FileInAlbum { date_created: string, album_id: number, album: Album | null, file_id: number, file: File | null }
|
||||
|
||||
export interface TagOnFile { date_created: string, tag_id: number, tag: Tag | null, file_id: number, file: File | null }
|
||||
|
||||
export interface Tag { id: number, pub_id: Array<number>, name: string | null, color: string | null, total_files: number | null, redundancy_goal: number | null, date_created: string, date_modified: string, tag_files: Array<TagOnFile> | null }
|
||||
|
||||
export interface IndexerRule { id: number, kind: number, name: string, parameters: Array<number>, date_created: string, date_modified: string, locations: Array<IndexerRulesInLocation> | null }
|
||||
|
||||
export interface Location { id: number, pub_id: Array<number>, node_id: number | null, name: string | null, local_path: string | null, total_capacity: number | null, available_capacity: number | null, filesystem: string | null, disk_type: number | null, is_removable: boolean | null, is_online: boolean, is_archived: boolean, date_created: string, node: Node | null | null, file_paths: Array<FilePath> | null, indexer_rules: Array<IndexerRulesInLocation> | null }
|
||||
|
||||
export interface TagCreateArgs { name: string, color: string }
|
||||
|
||||
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 Comment { id: number, pub_id: Array<number>, content: string, date_created: string, date_modified: string, file_id: number | null, file: File | null | null }
|
||||
|
||||
export interface Node { id: number, pub_id: Array<number>, name: string, platform: number, version: string | null, last_seen: string, timezone: string | null, date_created: string, sync_events: Array<SyncEvent> | null, jobs: Array<Job> | null, Location: Array<Location> | null }
|
||||
|
||||
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, file: File | null | null, location: Location | null | null, key: Key | null | null }
|
||||
|
||||
export interface LocationUpdateArgs { id: number, name: string | null, indexer_rules_ids: Array<number> }
|
||||
|
||||
export interface IndexerRuleCreateArgs { kind: RuleKind, name: string, parameters: Array<number> }
|
||||
|
||||
export interface Job { id: Array<number>, name: string, node_id: number, action: number, status: number, data: Array<number> | null, task_count: number, completed_task_count: number, date_created: string, date_modified: string, seconds_elapsed: number, nodes: Node | null }
|
||||
|
||||
export interface IndexerRuleCreateArgs { kind: RuleKind, name: string, parameters: Array<number> }
|
||||
|
||||
export interface LibraryConfig { version: string | null, name: string, description: string }
|
||||
|
||||
export interface LocationUpdateArgs { id: number, name: string | null, indexer_rules_ids: Array<number> }
|
||||
|
||||
export type RuleKind = "AcceptFilesByGlob" | "RejectFilesByGlob" | "AcceptIfChildrenDirectoriesArePresent" | "RejectIfChildrenDirectoriesArePresent"
|
||||
|
||||
export interface TagCreateArgs { name: string, color: string }
|
||||
|
||||
export interface EditLibraryArgs { id: string, name: string | null, description: string | null }
|
||||
|
||||
export type JobStatus = "Queued" | "Running" | "Completed" | "Canceled" | "Failed" | "Paused"
|
||||
|
||||
export interface GenerateThumbsForLocationArgs { id: number, path: string }
|
||||
|
||||
export interface IndexerRule { id: number, kind: number, name: string, parameters: Array<number>, date_created: string, date_modified: string, locations: Array<IndexerRulesInLocation> | null }
|
||||
|
||||
export interface JobReport { id: string, name: string, data: Array<number> | null, date_created: string, date_modified: string, status: JobStatus, task_count: number, completed_task_count: number, message: string, seconds_elapsed: number }
|
||||
|
||||
export interface Location { id: number, pub_id: Array<number>, node_id: number | null, name: string | null, local_path: string | null, total_capacity: number | null, available_capacity: number | null, filesystem: string | null, disk_type: number | null, is_removable: boolean | null, is_online: boolean, is_archived: boolean, date_created: string, node: Node | null | null, file_paths: Array<FilePath> | null, indexer_rules: Array<IndexerRulesInLocation> | null }
|
||||
|
||||
export interface TagUpdateArgs { id: number, name: string | null, color: string | null }
|
||||
|
||||
export interface Tag { id: number, pub_id: Array<number>, name: string | null, color: string | null, total_files: number | null, redundancy_goal: number | null, date_created: string, date_modified: string, tag_files: Array<TagOnFile> | null }
|
||||
|
||||
export interface Key { id: number, checksum: string, name: string | null, date_created: string | null, algorithm: number | null, files: Array<File> | null, file_paths: Array<FilePath> | null }
|
||||
|
||||
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, tags: Array<TagOnFile> | null, labels: Array<LabelOnFile> | null, albums: Array<FileInAlbum> | null, spaces: Array<FileInSpace> | null, paths: Array<FilePath> | null, comments: Array<Comment> | null, media_data: MediaData | null | null, key: Key | null | null }
|
||||
|
||||
export interface LibraryArgs<T> { library_id: string, arg: T }
|
||||
|
||||
export interface InvalidateOperationEvent { key: string, arg: any }
|
||||
|
||||
export interface FileInSpace { date_created: string, space_id: number, space: Space | null, file_id: number, file: File | null }
|
||||
|
||||
export interface TagOnFile { date_created: string, tag_id: number, tag: Tag | null, file_id: number, file: File | null }
|
||||
|
||||
export interface Space { id: number, pub_id: Array<number>, name: string | null, description: string | null, date_created: string, date_modified: string, files: Array<FileInSpace> | null }
|
||||
|
||||
export interface Comment { id: number, pub_id: Array<number>, content: string, date_created: string, date_modified: string, file_id: number | null, file: File | null | null }
|
||||
|
||||
export interface IdentifyUniqueFilesArgs { id: number, path: string }
|
||||
|
||||
export interface MediaData { id: number, pixel_width: number | null, pixel_height: number | null, longitude: number | null, latitude: number | null, fps: number | null, capture_device_make: string | null, capture_device_model: string | null, capture_device_software: string | null, duration_seconds: number | null, codecs: string | null, streams: number | null, files: File | null | null }
|
||||
|
||||
export type ExplorerContext = { type: "Location" } & Location | { type: "Tag" } & Tag
|
||||
|
||||
export interface NodeConfig { version: string | null, id: string, name: string, p2p_port: number | null }
|
||||
|
||||
export interface LabelOnFile { date_created: string, label_id: number, label: Label | null, file_id: number, file: File | null }
|
||||
|
||||
export interface SetFavoriteArgs { id: number, favorite: boolean }
|
||||
|
||||
export interface Volume { name: string, mount_point: string, total_capacity: bigint, available_capacity: bigint, is_removable: boolean, disk_type: string | null, file_system: string | null, is_root_filesystem: boolean }
|
||||
|
||||
export interface SetNoteArgs { id: number, note: string | null }
|
||||
|
||||
export interface LocationCreateArgs { path: string, indexer_rules_ids: Array<number> }
|
||||
|
||||
export interface LibraryConfigWrapped { uuid: string, config: LibraryConfig }
|
||||
|
||||
export interface Label { id: number, pub_id: Array<number>, name: string | null, date_created: string, date_modified: string, label_files: Array<LabelOnFile> | null }
|
||||
|
||||
export interface Album { id: number, pub_id: Array<number>, name: string, is_hidden: boolean, date_created: string, date_modified: string, files: Array<FileInAlbum> | null }
|
||||
|
||||
export interface SyncEvent { id: number, node_id: number, timestamp: string, record_id: Array<number>, kind: number, column: string | null, value: string, node: Node | null }
|
||||
|
||||
export interface FileInAlbum { date_created: string, album_id: number, album: Album | null, file_id: number, file: File | null }
|
||||
|
||||
export type ExplorerItem = { type: "Path" } & FilePath | { type: "Object" } & File
|
||||
|
||||
export interface LocationExplorerArgs { location_id: number, path: string, limit: number, cursor: 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 IndexerRulesInLocation { date_created: string, location_id: number, location: Location | null, indexer_rule_id: number, indexer_rule: IndexerRule | null }
|
||||
|
||||
export interface NodeState { version: string | null, id: string, name: string, p2p_port: number | null, data_path: string }
|
||||
|
||||
export type RuleKind = "AcceptFilesByGlob" | "RejectFilesByGlob" | "AcceptIfChildrenDirectoriesArePresent" | "RejectIfChildrenDirectoriesArePresent"
|
||||
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, file: File | null | null, location: Location | null | null, key: Key | null | null }
|
||||
|
||||
export interface Album { id: number, pub_id: Array<number>, name: string, is_hidden: boolean, date_created: string, date_modified: string, files: Array<FileInAlbum> | null }
|
||||
export interface ConfigMetadata { version: string | null }
|
||||
|
||||
export interface TagAssignArgs { file_id: number, tag_id: number, unassign: boolean }
|
||||
|
||||
export type JobStatus = "Queued" | "Running" | "Completed" | "Canceled" | "Failed" | "Paused"
|
||||
|
||||
export interface EditLibraryArgs { id: string, name: string | null, description: string | null }
|
||||
|
||||
export interface GenerateThumbsForLocationArgs { id: number, path: string }
|
||||
|
||||
export interface IdentifyUniqueFilesArgs { id: number, path: string }
|
||||
|
||||
export type ExplorerContext = { type: "Location" } & Location | { type: "Tag" } & Tag
|
||||
|
||||
export interface Key { id: number, checksum: string, name: string | null, date_created: string | null, algorithm: number | null, files: Array<File> | null, file_paths: Array<FilePath> | null }
|
||||
|
||||
export interface SyncEvent { id: number, node_id: number, timestamp: string, record_id: Array<number>, kind: number, column: string | null, value: string, node: Node | null }
|
||||
|
||||
export interface SetNoteArgs { id: number, note: string | null }
|
||||
|
||||
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, tags: Array<TagOnFile> | null, labels: Array<LabelOnFile> | null, albums: Array<FileInAlbum> | null, spaces: Array<FileInSpace> | null, paths: Array<FilePath> | null, comments: Array<Comment> | null, media_data: MediaData | null | null, key: Key | null | null }
|
||||
export interface ExplorerData { context: ExplorerContext, items: Array<ExplorerItem> }
|
||||
+106
-106
@@ -2,136 +2,136 @@
|
||||
|
||||
export type Operations = {
|
||||
queries:
|
||||
{ key: ["library.getStatistics", LibraryArgs<null>], result: Statistics } |
|
||||
{ key: ["volumes.get"], result: Array<Volume> } |
|
||||
{ key: ["tags.get", LibraryArgs<number>], result: Tag | null } |
|
||||
{ key: ["tags.getForFile", LibraryArgs<number>], result: Array<Tag> } |
|
||||
{ key: ["tags.getExplorerData", LibraryArgs<number>], result: ExplorerData } |
|
||||
{ key: ["locations.getById", LibraryArgs<number>], result: Location | null } |
|
||||
{ key: ["locations.indexer_rulesget", LibraryArgs<number>], result: IndexerRule } |
|
||||
{ key: ["files.readMetadata", LibraryArgs<number>], result: null } |
|
||||
{ key: ["jobs.getHistory", LibraryArgs<null>], result: Array<JobReport> } |
|
||||
{ key: ["locations.getById", LibraryArgs<number>], result: Location | null } |
|
||||
{ key: ["locations.getExplorerData", LibraryArgs<LocationExplorerArgs>], result: ExplorerData } |
|
||||
{ key: ["getNode"], result: NodeState } |
|
||||
{ key: ["locations.get", LibraryArgs<null>], result: Array<Location> } |
|
||||
{ key: ["tags.get", LibraryArgs<number>], result: Tag | null } |
|
||||
{ key: ["locations.indexer_ruleslist", LibraryArgs<null>], result: Array<IndexerRule> } |
|
||||
{ key: ["library.get"], result: Array<LibraryConfigWrapped> } |
|
||||
{ key: ["tags.getAll", LibraryArgs<null>], result: Array<Tag> } |
|
||||
{ key: ["jobs.getHistory", LibraryArgs<null>], result: Array<JobReport> } |
|
||||
{ key: ["getNode"], result: NodeState } |
|
||||
{ key: ["library.getStatistics", LibraryArgs<null>], result: Statistics } |
|
||||
{ key: ["version"], result: string } |
|
||||
{ key: ["jobs.getRunning", LibraryArgs<null>], result: Array<JobReport> } |
|
||||
{ key: ["version"], result: string },
|
||||
{ key: ["library.get"], result: Array<LibraryConfigWrapped> } |
|
||||
{ key: ["locations.get", LibraryArgs<null>], result: Array<Location> } |
|
||||
{ key: ["volumes.get"], result: Array<Volume> } |
|
||||
{ key: ["locations.indexer_ruleslist", LibraryArgs<null>], result: Array<IndexerRule> } |
|
||||
{ key: ["locations.getExplorerData", LibraryArgs<LocationExplorerArgs>], result: ExplorerData } |
|
||||
{ key: ["tags.getExplorerData", LibraryArgs<number>], result: ExplorerData },
|
||||
mutations:
|
||||
{ key: ["locations.create", LibraryArgs<LocationCreateArgs>], result: Location } |
|
||||
{ key: ["locations.fullRescan", LibraryArgs<number>], result: null } |
|
||||
{ key: ["locations.quickRescan", LibraryArgs<null>], result: null } |
|
||||
{ key: ["files.setFavorite", LibraryArgs<SetFavoriteArgs>], result: null } |
|
||||
{ key: ["jobs.generateThumbsForLocation", LibraryArgs<GenerateThumbsForLocationArgs>], result: null } |
|
||||
{ key: ["locations.update", LibraryArgs<LocationUpdateArgs>], result: null } |
|
||||
{ key: ["tags.update", LibraryArgs<TagUpdateArgs>], result: null } |
|
||||
{ key: ["library.create", string], result: null } |
|
||||
{ key: ["files.delete", LibraryArgs<number>], result: null } |
|
||||
{ key: ["tags.create", LibraryArgs<TagCreateArgs>], result: Tag } |
|
||||
{ key: ["tags.delete", LibraryArgs<number>], result: null } |
|
||||
{ key: ["tags.assign", LibraryArgs<TagAssignArgs>], result: null } |
|
||||
{ key: ["library.edit", EditLibraryArgs], result: null } |
|
||||
{ key: ["locations.indexer_rulescreate", LibraryArgs<IndexerRuleCreateArgs>], result: IndexerRule } |
|
||||
{ key: ["files.setNote", LibraryArgs<SetNoteArgs>], result: null } |
|
||||
{ key: ["jobs.identifyUniqueFiles", LibraryArgs<IdentifyUniqueFilesArgs>], result: null } |
|
||||
{ key: ["library.delete", string], result: null } |
|
||||
{ key: ["locations.indexer_rulesdelete", LibraryArgs<number>], result: null } |
|
||||
{ key: ["library.create", string], result: null } |
|
||||
{ key: ["locations.quickRescan", LibraryArgs<null>], result: null } |
|
||||
{ key: ["locations.update", LibraryArgs<LocationUpdateArgs>], result: null } |
|
||||
{ key: ["tags.assign", LibraryArgs<TagAssignArgs>], result: null } |
|
||||
{ key: ["locations.fullRescan", LibraryArgs<number>], result: null } |
|
||||
{ key: ["files.delete", LibraryArgs<number>], result: null } |
|
||||
{ key: ["files.setFavorite", LibraryArgs<SetFavoriteArgs>], result: null } |
|
||||
{ key: ["files.setNote", LibraryArgs<SetNoteArgs>], result: null } |
|
||||
{ key: ["tags.delete", LibraryArgs<number>], result: null } |
|
||||
{ key: ["tags.update", LibraryArgs<TagUpdateArgs>], result: null } |
|
||||
{ key: ["locations.indexer_rulescreate", LibraryArgs<IndexerRuleCreateArgs>], result: IndexerRule } |
|
||||
{ key: ["locations.create", LibraryArgs<LocationCreateArgs>], result: Location } |
|
||||
{ key: ["jobs.generateThumbsForLocation", LibraryArgs<GenerateThumbsForLocationArgs>], result: null } |
|
||||
{ key: ["library.edit", EditLibraryArgs], result: null } |
|
||||
{ key: ["library.delete", string], result: null } |
|
||||
{ key: ["jobs.identifyUniqueFiles", LibraryArgs<IdentifyUniqueFilesArgs>], result: null } |
|
||||
{ key: ["tags.create", LibraryArgs<TagCreateArgs>], result: Tag } |
|
||||
{ key: ["locations.delete", LibraryArgs<number>], result: null },
|
||||
subscriptions:
|
||||
{ key: ["invalidateQuery"], result: InvalidateOperationEvent } |
|
||||
{ key: ["jobs.newThumbnail", LibraryArgs<null>], result: string }
|
||||
};
|
||||
|
||||
export interface InvalidateOperationEvent { key: string, arg: any }
|
||||
|
||||
export interface LocationExplorerArgs { location_id: number, path: string, limit: number, cursor: string | null }
|
||||
|
||||
export interface LibraryConfig { version: string | null, name: string, description: string }
|
||||
|
||||
export interface FileInSpace { date_created: string, space_id: number, space: Space | null, file_id: number, file: File | null }
|
||||
|
||||
export interface LabelOnFile { date_created: string, label_id: number, label: Label | null, file_id: number, file: File | null }
|
||||
|
||||
export interface JobReport { id: string, name: string, data: Array<number> | null, date_created: string, date_modified: string, status: JobStatus, task_count: number, completed_task_count: number, message: string, seconds_elapsed: number }
|
||||
|
||||
export type ExplorerItem = { type: "Path" } & FilePath | { type: "Object" } & File
|
||||
|
||||
export interface NodeConfig { version: string | null, id: string, name: string, p2p_port: number | null }
|
||||
|
||||
export interface LibraryConfigWrapped { uuid: string, config: LibraryConfig }
|
||||
|
||||
export interface ConfigMetadata { version: string | null }
|
||||
|
||||
export interface MediaData { id: number, pixel_width: number | null, pixel_height: number | null, longitude: number | null, latitude: number | null, fps: number | null, capture_device_make: string | null, capture_device_model: string | null, capture_device_software: string | null, duration_seconds: number | null, codecs: string | null, streams: number | null, files: File | null | null }
|
||||
|
||||
export interface Space { id: number, pub_id: Array<number>, name: string | null, description: string | null, date_created: string, date_modified: string, files: Array<FileInSpace> | null }
|
||||
|
||||
export interface Label { id: number, pub_id: Array<number>, name: string | null, date_created: string, date_modified: string, label_files: Array<LabelOnFile> | null }
|
||||
|
||||
export interface Volume { name: string, mount_point: string, total_capacity: bigint, available_capacity: bigint, is_removable: boolean, disk_type: string | null, file_system: string | null, is_root_filesystem: boolean }
|
||||
|
||||
export interface ExplorerData { context: ExplorerContext, items: Array<ExplorerItem> }
|
||||
|
||||
export interface LibraryArgs<T> { library_id: string, arg: T }
|
||||
|
||||
export interface TagUpdateArgs { id: number, name: string | null, color: string | null }
|
||||
|
||||
export interface LocationCreateArgs { path: string, indexer_rules_ids: Array<number> }
|
||||
|
||||
export interface FileInAlbum { date_created: string, album_id: number, album: Album | null, file_id: number, file: File | null }
|
||||
|
||||
export interface TagOnFile { date_created: string, tag_id: number, tag: Tag | null, file_id: number, file: File | null }
|
||||
|
||||
export interface Tag { id: number, pub_id: Array<number>, name: string | null, color: string | null, total_files: number | null, redundancy_goal: number | null, date_created: string, date_modified: string, tag_files: Array<TagOnFile> | null }
|
||||
|
||||
export interface IndexerRule { id: number, kind: number, name: string, parameters: Array<number>, date_created: string, date_modified: string, locations: Array<IndexerRulesInLocation> | null }
|
||||
|
||||
export interface Location { id: number, pub_id: Array<number>, node_id: number | null, name: string | null, local_path: string | null, total_capacity: number | null, available_capacity: number | null, filesystem: string | null, disk_type: number | null, is_removable: boolean | null, is_online: boolean, is_archived: boolean, date_created: string, node: Node | null | null, file_paths: Array<FilePath> | null, indexer_rules: Array<IndexerRulesInLocation> | null }
|
||||
|
||||
export interface TagCreateArgs { name: string, color: string }
|
||||
|
||||
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 Comment { id: number, pub_id: Array<number>, content: string, date_created: string, date_modified: string, file_id: number | null, file: File | null | null }
|
||||
|
||||
export interface Node { id: number, pub_id: Array<number>, name: string, platform: number, version: string | null, last_seen: string, timezone: string | null, date_created: string, sync_events: Array<SyncEvent> | null, jobs: Array<Job> | null, Location: Array<Location> | null }
|
||||
|
||||
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, file: File | null | null, location: Location | null | null, key: Key | null | null }
|
||||
|
||||
export interface LocationUpdateArgs { id: number, name: string | null, indexer_rules_ids: Array<number> }
|
||||
|
||||
export interface IndexerRuleCreateArgs { kind: RuleKind, name: string, parameters: Array<number> }
|
||||
|
||||
export interface Job { id: Array<number>, name: string, node_id: number, action: number, status: number, data: Array<number> | null, task_count: number, completed_task_count: number, date_created: string, date_modified: string, seconds_elapsed: number, nodes: Node | null }
|
||||
|
||||
export interface IndexerRuleCreateArgs { kind: RuleKind, name: string, parameters: Array<number> }
|
||||
|
||||
export interface LibraryConfig { version: string | null, name: string, description: string }
|
||||
|
||||
export interface LocationUpdateArgs { id: number, name: string | null, indexer_rules_ids: Array<number> }
|
||||
|
||||
export type RuleKind = "AcceptFilesByGlob" | "RejectFilesByGlob" | "AcceptIfChildrenDirectoriesArePresent" | "RejectIfChildrenDirectoriesArePresent"
|
||||
|
||||
export interface TagCreateArgs { name: string, color: string }
|
||||
|
||||
export interface EditLibraryArgs { id: string, name: string | null, description: string | null }
|
||||
|
||||
export type JobStatus = "Queued" | "Running" | "Completed" | "Canceled" | "Failed" | "Paused"
|
||||
|
||||
export interface GenerateThumbsForLocationArgs { id: number, path: string }
|
||||
|
||||
export interface IndexerRule { id: number, kind: number, name: string, parameters: Array<number>, date_created: string, date_modified: string, locations: Array<IndexerRulesInLocation> | null }
|
||||
|
||||
export interface JobReport { id: string, name: string, data: Array<number> | null, date_created: string, date_modified: string, status: JobStatus, task_count: number, completed_task_count: number, message: string, seconds_elapsed: number }
|
||||
|
||||
export interface Location { id: number, pub_id: Array<number>, node_id: number | null, name: string | null, local_path: string | null, total_capacity: number | null, available_capacity: number | null, filesystem: string | null, disk_type: number | null, is_removable: boolean | null, is_online: boolean, is_archived: boolean, date_created: string, node: Node | null | null, file_paths: Array<FilePath> | null, indexer_rules: Array<IndexerRulesInLocation> | null }
|
||||
|
||||
export interface TagUpdateArgs { id: number, name: string | null, color: string | null }
|
||||
|
||||
export interface Tag { id: number, pub_id: Array<number>, name: string | null, color: string | null, total_files: number | null, redundancy_goal: number | null, date_created: string, date_modified: string, tag_files: Array<TagOnFile> | null }
|
||||
|
||||
export interface Key { id: number, checksum: string, name: string | null, date_created: string | null, algorithm: number | null, files: Array<File> | null, file_paths: Array<FilePath> | null }
|
||||
|
||||
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, tags: Array<TagOnFile> | null, labels: Array<LabelOnFile> | null, albums: Array<FileInAlbum> | null, spaces: Array<FileInSpace> | null, paths: Array<FilePath> | null, comments: Array<Comment> | null, media_data: MediaData | null | null, key: Key | null | null }
|
||||
|
||||
export interface LibraryArgs<T> { library_id: string, arg: T }
|
||||
|
||||
export interface InvalidateOperationEvent { key: string, arg: any }
|
||||
|
||||
export interface FileInSpace { date_created: string, space_id: number, space: Space | null, file_id: number, file: File | null }
|
||||
|
||||
export interface TagOnFile { date_created: string, tag_id: number, tag: Tag | null, file_id: number, file: File | null }
|
||||
|
||||
export interface Space { id: number, pub_id: Array<number>, name: string | null, description: string | null, date_created: string, date_modified: string, files: Array<FileInSpace> | null }
|
||||
|
||||
export interface Comment { id: number, pub_id: Array<number>, content: string, date_created: string, date_modified: string, file_id: number | null, file: File | null | null }
|
||||
|
||||
export interface IdentifyUniqueFilesArgs { id: number, path: string }
|
||||
|
||||
export interface MediaData { id: number, pixel_width: number | null, pixel_height: number | null, longitude: number | null, latitude: number | null, fps: number | null, capture_device_make: string | null, capture_device_model: string | null, capture_device_software: string | null, duration_seconds: number | null, codecs: string | null, streams: number | null, files: File | null | null }
|
||||
|
||||
export type ExplorerContext = { type: "Location" } & Location | { type: "Tag" } & Tag
|
||||
|
||||
export interface NodeConfig { version: string | null, id: string, name: string, p2p_port: number | null }
|
||||
|
||||
export interface LabelOnFile { date_created: string, label_id: number, label: Label | null, file_id: number, file: File | null }
|
||||
|
||||
export interface SetFavoriteArgs { id: number, favorite: boolean }
|
||||
|
||||
export interface Volume { name: string, mount_point: string, total_capacity: bigint, available_capacity: bigint, is_removable: boolean, disk_type: string | null, file_system: string | null, is_root_filesystem: boolean }
|
||||
|
||||
export interface SetNoteArgs { id: number, note: string | null }
|
||||
|
||||
export interface LocationCreateArgs { path: string, indexer_rules_ids: Array<number> }
|
||||
|
||||
export interface LibraryConfigWrapped { uuid: string, config: LibraryConfig }
|
||||
|
||||
export interface Label { id: number, pub_id: Array<number>, name: string | null, date_created: string, date_modified: string, label_files: Array<LabelOnFile> | null }
|
||||
|
||||
export interface Album { id: number, pub_id: Array<number>, name: string, is_hidden: boolean, date_created: string, date_modified: string, files: Array<FileInAlbum> | null }
|
||||
|
||||
export interface SyncEvent { id: number, node_id: number, timestamp: string, record_id: Array<number>, kind: number, column: string | null, value: string, node: Node | null }
|
||||
|
||||
export interface FileInAlbum { date_created: string, album_id: number, album: Album | null, file_id: number, file: File | null }
|
||||
|
||||
export type ExplorerItem = { type: "Path" } & FilePath | { type: "Object" } & File
|
||||
|
||||
export interface LocationExplorerArgs { location_id: number, path: string, limit: number, cursor: 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 IndexerRulesInLocation { date_created: string, location_id: number, location: Location | null, indexer_rule_id: number, indexer_rule: IndexerRule | null }
|
||||
|
||||
export interface NodeState { version: string | null, id: string, name: string, p2p_port: number | null, data_path: string }
|
||||
|
||||
export type RuleKind = "AcceptFilesByGlob" | "RejectFilesByGlob" | "AcceptIfChildrenDirectoriesArePresent" | "RejectIfChildrenDirectoriesArePresent"
|
||||
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, file: File | null | null, location: Location | null | null, key: Key | null | null }
|
||||
|
||||
export interface Album { id: number, pub_id: Array<number>, name: string, is_hidden: boolean, date_created: string, date_modified: string, files: Array<FileInAlbum> | null }
|
||||
export interface ConfigMetadata { version: string | null }
|
||||
|
||||
export interface TagAssignArgs { file_id: number, tag_id: number, unassign: boolean }
|
||||
|
||||
export type JobStatus = "Queued" | "Running" | "Completed" | "Canceled" | "Failed" | "Paused"
|
||||
|
||||
export interface EditLibraryArgs { id: string, name: string | null, description: string | null }
|
||||
|
||||
export interface GenerateThumbsForLocationArgs { id: number, path: string }
|
||||
|
||||
export interface IdentifyUniqueFilesArgs { id: number, path: string }
|
||||
|
||||
export type ExplorerContext = { type: "Location" } & Location | { type: "Tag" } & Tag
|
||||
|
||||
export interface Key { id: number, checksum: string, name: string | null, date_created: string | null, algorithm: number | null, files: Array<File> | null, file_paths: Array<FilePath> | null }
|
||||
|
||||
export interface SyncEvent { id: number, node_id: number, timestamp: string, record_id: Array<number>, kind: number, column: string | null, value: string, node: Node | null }
|
||||
|
||||
export interface SetNoteArgs { id: number, note: string | null }
|
||||
|
||||
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, tags: Array<TagOnFile> | null, labels: Array<LabelOnFile> | null, albums: Array<FileInAlbum> | null, spaces: Array<FileInSpace> | null, paths: Array<FilePath> | null, comments: Array<Comment> | null, media_data: MediaData | null | null, key: Key | null | null }
|
||||
export interface ExplorerData { context: ExplorerContext, items: Array<ExplorerItem> }
|
||||
@@ -143,7 +143,7 @@ impl StatefulJob for FileIdentifierJob {
|
||||
// analyze each file_path
|
||||
for file_path in &file_paths {
|
||||
// get the cas_id and extract metadata
|
||||
match prepare_file(&data.location_path, file_path).await {
|
||||
match assemble_object_metadata(&data.location_path, file_path).await {
|
||||
Ok(file) => {
|
||||
let cas_id = file.cas_id.clone();
|
||||
// create entry into chunks for created file data
|
||||
@@ -151,7 +151,7 @@ impl StatefulJob for FileIdentifierJob {
|
||||
cas_lookup.insert(cas_id, file_path.id);
|
||||
}
|
||||
Err(e) => {
|
||||
info!("Error processing file: {:#?}", e);
|
||||
info!("Error assembling Object metadata: {:#?}", e);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
@@ -167,12 +167,14 @@ impl StatefulJob for FileIdentifierJob {
|
||||
.exec()
|
||||
.await?;
|
||||
|
||||
info!("Found {} existing files", existing_files.len());
|
||||
info!("Found {} existing Objects", existing_files.len());
|
||||
|
||||
// link those existing files to their file paths
|
||||
// Had to put the file_path in a variable outside of the closure, to satisfy the borrow checker
|
||||
let library_ctx = ctx.library_ctx();
|
||||
|
||||
// if existing_files.len() > 0 {
|
||||
// connect Paths that match existing Objects in the database
|
||||
for existing_file in &existing_files {
|
||||
if let Err(e) = library_ctx
|
||||
.db
|
||||
@@ -203,6 +205,11 @@ impl StatefulJob for FileIdentifierJob {
|
||||
.filter(|create_file| !existing_files_cas_ids.contains(&create_file.cas_id))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if new_files.is_empty() {
|
||||
error!("This shouldn't happen?");
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// assemble prisma values for new unique files
|
||||
let mut values = Vec::with_capacity(new_files.len() * 3);
|
||||
for file in &new_files {
|
||||
@@ -213,6 +220,8 @@ impl StatefulJob for FileIdentifierJob {
|
||||
]);
|
||||
}
|
||||
|
||||
info!("{:?}", values);
|
||||
|
||||
// create new file records with assembled values
|
||||
let created_files: Vec<FileCreated> = ctx
|
||||
.library_ctx()
|
||||
@@ -220,7 +229,7 @@ impl StatefulJob for FileIdentifierJob {
|
||||
._query_raw(Raw::new(
|
||||
&format!(
|
||||
"INSERT INTO files (cas_id, size_in_bytes, date_created) VALUES {}
|
||||
ON CONFLICT (cas_id) DO NOTHING RETURNING id, cas_id",
|
||||
ON CONFLICT (cas_id) DO NOTHING RETURNING id, cas_id",
|
||||
vec!["({}, {}, {})"; new_files.len()].join(",")
|
||||
),
|
||||
values,
|
||||
@@ -231,7 +240,6 @@ impl StatefulJob for FileIdentifierJob {
|
||||
error!("Error inserting files: {:#?}", e);
|
||||
Vec::new()
|
||||
});
|
||||
|
||||
for created_file in created_files {
|
||||
// associate newly created files with their respective file_paths
|
||||
// TODO: this is potentially bottle necking the chunk system, individually linking file_path to file, 100 queries per chunk
|
||||
@@ -254,12 +262,13 @@ impl StatefulJob for FileIdentifierJob {
|
||||
}
|
||||
}
|
||||
|
||||
// handle last step
|
||||
// set the step data cursor to the last row of this chunk
|
||||
if let Some(last_row) = file_paths.last() {
|
||||
data.cursor.file_path_id = last_row.id;
|
||||
} else {
|
||||
return Ok(());
|
||||
}
|
||||
// }
|
||||
|
||||
ctx.progress(vec![
|
||||
JobReportUpdate::CompletedTaskCount(state.step_number),
|
||||
@@ -348,7 +357,7 @@ struct FileCreated {
|
||||
pub cas_id: String,
|
||||
}
|
||||
|
||||
async fn prepare_file(
|
||||
async fn assemble_object_metadata(
|
||||
location_path: impl AsRef<Path>,
|
||||
file_path: &file_path::Data,
|
||||
) -> Result<CreateFile, io::Error> {
|
||||
|
||||
+33
-8
@@ -18,7 +18,10 @@ use super::JobReport;
|
||||
// used to update the worker state from inside the worker thread
|
||||
#[derive(Debug)]
|
||||
pub enum WorkerEvent {
|
||||
Progressed(Vec<JobReportUpdate>),
|
||||
Progressed {
|
||||
updates: Vec<JobReportUpdate>,
|
||||
debounce: bool,
|
||||
},
|
||||
Completed(oneshot::Sender<()>),
|
||||
Failed(oneshot::Sender<()>),
|
||||
Paused(Vec<u8>, oneshot::Sender<()>),
|
||||
@@ -34,7 +37,18 @@ pub struct WorkerContext {
|
||||
impl WorkerContext {
|
||||
pub fn progress(&self, updates: Vec<JobReportUpdate>) {
|
||||
self.events_tx
|
||||
.send(WorkerEvent::Progressed(updates))
|
||||
.send(WorkerEvent::Progressed {
|
||||
updates,
|
||||
debounce: false,
|
||||
})
|
||||
.expect("critical error: failed to send worker worker progress event updates");
|
||||
}
|
||||
pub fn progress_debounced(&self, updates: Vec<JobReportUpdate>) {
|
||||
self.events_tx
|
||||
.send(WorkerEvent::Progressed {
|
||||
updates,
|
||||
debounce: true,
|
||||
})
|
||||
.expect("critical error: failed to send worker worker progress event updates");
|
||||
}
|
||||
|
||||
@@ -124,9 +138,10 @@ impl Worker {
|
||||
loop {
|
||||
interval.tick().await;
|
||||
if events_tx
|
||||
.send(WorkerEvent::Progressed(vec![
|
||||
JobReportUpdate::SecondsElapsed(1),
|
||||
]))
|
||||
.send(WorkerEvent::Progressed {
|
||||
updates: vec![JobReportUpdate::SecondsElapsed(1)],
|
||||
debounce: false,
|
||||
})
|
||||
.is_err() && events_tx.is_closed()
|
||||
{
|
||||
break;
|
||||
@@ -171,17 +186,27 @@ impl Worker {
|
||||
mut worker_events_rx: UnboundedReceiver<WorkerEvent>,
|
||||
library: LibraryContext,
|
||||
) {
|
||||
let mut last = Instant::now();
|
||||
|
||||
while let Some(command) = worker_events_rx.recv().await {
|
||||
let mut worker = worker.lock().await;
|
||||
|
||||
match command {
|
||||
WorkerEvent::Progressed(changes) => {
|
||||
WorkerEvent::Progressed { updates, debounce } => {
|
||||
if debounce {
|
||||
let current = Instant::now();
|
||||
if current.duration_since(last) > Duration::from_millis(1000 / 60) {
|
||||
last = current
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// protect against updates if job is not running
|
||||
if worker.report.status != JobStatus::Running {
|
||||
continue;
|
||||
};
|
||||
for change in changes {
|
||||
match change {
|
||||
for update in updates {
|
||||
match update {
|
||||
JobReportUpdate::TaskCount(task_count) => {
|
||||
worker.report.task_count = task_count as i32;
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ pub struct IndexerJobStepEntry {
|
||||
|
||||
impl IndexerJobData {
|
||||
fn on_scan_progress(ctx: WorkerContext, progress: Vec<ScanProgress>) {
|
||||
ctx.progress(
|
||||
ctx.progress_debounced(
|
||||
progress
|
||||
.iter()
|
||||
.map(|p| match p.clone() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { AppPropsContext, explorerStore } from '@sd/client';
|
||||
import { ExplorerItem } from '@sd/core';
|
||||
import clsx from 'clsx';
|
||||
import React, { useContext } from 'react';
|
||||
import React, { useContext, useState } from 'react';
|
||||
import { useSnapshot } from 'valtio';
|
||||
|
||||
import icons from '../../assets/icons';
|
||||
@@ -39,6 +39,7 @@ export default function FileThumb({ data, ...props }: Props) {
|
||||
if (file_thumb_url)
|
||||
return (
|
||||
<img
|
||||
// onLoad={}
|
||||
style={props.style}
|
||||
className={clsx('pointer-events-none z-90', props.className)}
|
||||
src={file_thumb_url}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import { ContextMenu, ContextMenuProps, Root, Trigger } from '@sd/ui';
|
||||
import React, { ComponentProps } from 'react';
|
||||
|
||||
export const WithContextMenu: React.FC<{
|
||||
menu: ContextMenuProps['items'];
|
||||
children: ComponentProps<typeof Trigger>['children'];
|
||||
}> = (props) => {
|
||||
const { menu: sections = [], children } = props;
|
||||
|
||||
return (
|
||||
<Root>
|
||||
<Trigger>{children}</Trigger>
|
||||
|
||||
<ContextMenu items={sections} />
|
||||
</Root>
|
||||
);
|
||||
};
|
||||
@@ -1,15 +1,7 @@
|
||||
import { Button } from '@sd/ui';
|
||||
import React from 'react';
|
||||
|
||||
import { WithContextMenu } from '../components/layout/MenuOverlay';
|
||||
|
||||
export const ContentScreen: React.FC<unknown> = (props) => {
|
||||
// const [address, setAddress] = React.useState('');
|
||||
return (
|
||||
<div className="flex flex-col w-full h-screen p-5 custom-scroll page-scroll">
|
||||
<WithContextMenu menu={[[{ label: 'jeff', children: [[{ label: 'jeff' }]] }]]}>
|
||||
<Button variant="gray">Test</Button>
|
||||
</WithContextMenu>
|
||||
</div>
|
||||
);
|
||||
return <div className="flex flex-col w-full h-screen p-5 custom-scroll page-scroll"></div>;
|
||||
};
|
||||
@@ -1,41 +0,0 @@
|
||||
import { ComponentMeta, ComponentStory } from '@storybook/react';
|
||||
import { FileText, Plus, Trash } from 'phosphor-react';
|
||||
import React from 'react';
|
||||
|
||||
import { ContextMenu } from './ContextMenu';
|
||||
|
||||
export default {
|
||||
title: 'UI/Context Menu',
|
||||
component: ContextMenu,
|
||||
argTypes: {},
|
||||
parameters: {},
|
||||
args: {}
|
||||
} as ComponentMeta<typeof ContextMenu>;
|
||||
|
||||
const Template: ComponentStory<typeof ContextMenu> = (args) => <ContextMenu {...args} />;
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
items: [
|
||||
[
|
||||
{
|
||||
label: 'New Item',
|
||||
icon: Plus,
|
||||
onClick: () => {}
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
label: 'View Info',
|
||||
icon: FileText,
|
||||
onClick: () => {}
|
||||
},
|
||||
{
|
||||
label: 'Delete',
|
||||
icon: Trash,
|
||||
danger: true,
|
||||
onClick: () => {}
|
||||
}
|
||||
]
|
||||
]
|
||||
};
|
||||
@@ -1,128 +0,0 @@
|
||||
import * as ContextMenuPrimitive from '@radix-ui/react-context-menu';
|
||||
import { Root, Trigger } from '@radix-ui/react-context-menu';
|
||||
import clsx from 'clsx';
|
||||
import { CaretRight, Icon } from 'phosphor-react';
|
||||
import { Question } from 'phosphor-react';
|
||||
import React from 'react';
|
||||
|
||||
export interface ContextMenuItem {
|
||||
label: string;
|
||||
icon?: Icon;
|
||||
danger?: boolean;
|
||||
active?: boolean;
|
||||
leftItem?: React.ReactNode;
|
||||
rightItem?: React.ReactNode;
|
||||
onClick?: React.MouseEventHandler<HTMLDivElement>;
|
||||
|
||||
children?: ContextMenuSection[];
|
||||
}
|
||||
|
||||
export type ContextMenuSection = (ContextMenuItem | string)[];
|
||||
|
||||
export interface ContextMenuProps {
|
||||
items?: ContextMenuSection[];
|
||||
className?: string;
|
||||
isChild?: boolean;
|
||||
}
|
||||
|
||||
export const ContextMenu: React.FC<ContextMenuProps> = (props) => {
|
||||
const { items: sections = [], className, isChild, ...rest } = props;
|
||||
|
||||
const ContentPrimitive = isChild ? ContextMenuPrimitive.SubContent : ContextMenuPrimitive.Content;
|
||||
|
||||
return (
|
||||
<ContentPrimitive
|
||||
sideOffset={7}
|
||||
// onInteractOutside={(e) => {
|
||||
// e.preventDefault();
|
||||
// }}
|
||||
alignOffset={7}
|
||||
className={clsx(
|
||||
'shadow-md min-w-[11rem] py-0.5 shadow-gray-300 dark:shadow-gray-750 flex flex-col select-none cursor-default bg-gray-50 text-gray-800 border-gray-200 dark:bg-gray-950 dark:text-gray-100 text-left text-sm rounded-lg ',
|
||||
className
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
{sections.map((sec, i) => (
|
||||
<React.Fragment key={i}>
|
||||
{i !== 0 && (
|
||||
<ContextMenuPrimitive.Separator className="mx-2 border-0 border-b pointer-events-none border-b-gray-300 dark:border-b-gray-600" />
|
||||
)}
|
||||
|
||||
<ContextMenuPrimitive.Group className="flex flex-col items-stretch">
|
||||
{sec.map((item) => {
|
||||
if (typeof item === 'string')
|
||||
return (
|
||||
<ContextMenuPrimitive.Label
|
||||
key={item}
|
||||
className="mt-1 ml-2 text-xs text-gray-400 uppercase"
|
||||
>
|
||||
{item}
|
||||
</ContextMenuPrimitive.Label>
|
||||
);
|
||||
|
||||
const { icon: ItemIcon } = item;
|
||||
|
||||
let ItemComponent:
|
||||
| typeof ContextMenuPrimitive.Item
|
||||
| typeof ContextMenuPrimitive.Trigger = ContextMenuPrimitive.Item;
|
||||
|
||||
if ((item.children?.length ?? 0) > 0)
|
||||
ItemComponent = (({ children, ref, ...props }) => (
|
||||
<ContextMenuPrimitive.ContextMenuSub>
|
||||
<ContextMenuPrimitive.SubTrigger {...props}>
|
||||
{children}
|
||||
</ContextMenuPrimitive.SubTrigger>
|
||||
|
||||
<ContextMenu
|
||||
isChild
|
||||
items={item.children}
|
||||
className="relative -left-1 -top-2"
|
||||
/>
|
||||
</ContextMenuPrimitive.ContextMenuSub>
|
||||
)) as typeof ContextMenuPrimitive.Trigger;
|
||||
|
||||
return (
|
||||
<ItemComponent
|
||||
style={{
|
||||
font: 'inherit',
|
||||
textAlign: 'inherit'
|
||||
}}
|
||||
className={clsx(
|
||||
'focus:outline-none group cursor-default flex-1 px-1.5 py-1 group-first:pt-1.5 [&[data-state="open"]_div]:bg-primary',
|
||||
item.danger && 'text-red-600 dark:text-red-400',
|
||||
item.active && 'bg-gray-100 dark:bg-gray-950'
|
||||
)}
|
||||
onClick={item.onClick}
|
||||
key={item.label}
|
||||
>
|
||||
<div
|
||||
className={clsx(
|
||||
'flex py-[0.3em] flex-row items-center px-1 rounded group-focus:bg-primary group-hover:bg-primary',
|
||||
item.danger &&
|
||||
'group-focus:bg-red-500 group-hover:bg-red-500 group-focus:text-white group-hover:text-white'
|
||||
)}
|
||||
>
|
||||
{ItemIcon && <ItemIcon size={18} />}
|
||||
{item.leftItem}
|
||||
|
||||
<ContextMenuPrimitive.Label className="ml-1.5 leading-snug flex-grow text-sm font-normal">
|
||||
{item.label}
|
||||
</ContextMenuPrimitive.Label>
|
||||
|
||||
{item.rightItem}
|
||||
{(item.children?.length ?? 0) > 0 && (
|
||||
<CaretRight weight="fill" size={12} alt="" />
|
||||
)}
|
||||
</div>
|
||||
</ItemComponent>
|
||||
);
|
||||
})}
|
||||
</ContextMenuPrimitive.Group>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</ContentPrimitive>
|
||||
);
|
||||
};
|
||||
|
||||
export { Trigger, Root };
|
||||
Reference in new issue
Block a user