From d27ff3cd80bcdf8ff0dc65fe807db2052ea74dde Mon Sep 17 00:00:00 2001 From: Jamie Pine <32987599+jamiepine@users.noreply.github.com> Date: Tue, 4 Oct 2022 20:34:41 -0700 Subject: [PATCH] Object Kind (#397) * add temp custom icons and tweak styles * tweaks * - added Object type structs - added extension enums with variant macros - improved explorer rendering - added database migration hotfix * - added dev only data folder - improved explorer image loading * macros do rule ! * remove baselining Co-authored-by: Brendan Allan --- .cspell/project_words.txt | 1 + apps/desktop/src/index.tsx | 4 +- core/src/api/jobs.rs | 4 +- core/src/lib.rs | 3 + core/src/object/kind.rs | 267 ++++++++++++++++++ core/src/object/mod.rs | 39 +-- core/src/object/preview/thumb.rs | 70 ++--- packages/assets/svgs/folder.svg | 99 +++++-- packages/assets/svgs/video.svg | 94 ++++++ packages/assets/svgs/zip.svg | 154 ++++++++++ packages/client/src/stores/explorerStore.ts | 2 +- .../src/components/explorer/Explorer.tsx | 21 +- .../explorer/ExplorerContextMenu.tsx | 16 +- .../src/components/explorer/FileItem.tsx | 21 +- .../src/components/explorer/FileThumb.tsx | 22 ++ .../src/components/explorer/Inspector.tsx | 218 +++++++------- .../components/explorer/VirtualizedList.tsx | 5 +- .../src/components/layout/Sidebar.tsx | 2 +- .../src/components/layout/TopBar.tsx | 27 +- .../src/components/tooltip/Tooltip.tsx | 4 +- .../src/screens/LocationExplorer.tsx | 2 +- .../settings/client/GeneralSettings.tsx | 13 +- .../screens/settings/info/AboutSpacedrive.tsx | 8 - packages/ui/src/ContextMenu.tsx | 5 +- 24 files changed, 848 insertions(+), 253 deletions(-) create mode 100644 core/src/object/kind.rs create mode 100644 packages/assets/svgs/video.svg create mode 100644 packages/assets/svgs/zip.svg diff --git a/.cspell/project_words.txt b/.cspell/project_words.txt index 1125e97df..bc01cab56 100644 --- a/.cspell/project_words.txt +++ b/.cspell/project_words.txt @@ -15,6 +15,7 @@ haoyuan haris josephjacks justinhoffman +Keychain Keyslot keyslots lesterlee diff --git a/apps/desktop/src/index.tsx b/apps/desktop/src/index.tsx index 815573030..2e5aa61b0 100644 --- a/apps/desktop/src/index.tsx +++ b/apps/desktop/src/index.tsx @@ -3,7 +3,7 @@ import { TauriTransport } from '@rspc/tauri'; import { OperatingSystem, Operations, PlatformProvider, queryClient, rspc } from '@sd/client'; import SpacedriveInterface, { Platform } from '@sd/interface'; import { KeybindEvent } from '@sd/interface'; -import { dialog, invoke, os } from '@tauri-apps/api'; +import { dialog, invoke, os, shell } from '@tauri-apps/api'; import { listen } from '@tauri-apps/api/event'; import React, { useEffect } from 'react'; import { createRoot } from 'react-dom/client'; @@ -30,7 +30,7 @@ async function getOs(): Promise { const platform: Platform = { platform: 'tauri', getThumbnailUrlById: (casId) => `spacedrive://thumbnail/${encodeURIComponent(casId)}`, - openLink: open, + openLink: shell.open, getOs, openFilePickerDialog: () => dialog.open({ directory: true }) }; diff --git a/core/src/api/jobs.rs b/core/src/api/jobs.rs index 7668d1ee8..95525f3ab 100644 --- a/core/src/api/jobs.rs +++ b/core/src/api/jobs.rs @@ -51,8 +51,8 @@ pub(crate) fn mount() -> RouterBuilder { .spawn_job(Job::new( ThumbnailJobInit { location_id: args.id, - path: args.path, - background: false, // fix + path: PathBuf::new(), + background: true, }, Box::new(ThumbnailJob {}), )) diff --git a/core/src/lib.rs b/core/src/lib.rs index 3ab679658..14778b2ae 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -46,6 +46,9 @@ const CONSOLE_LOG_FILTER: LevelFilter = LevelFilter::INFO; impl Node { pub async fn new(data_dir: impl AsRef) -> Result<(Arc, Arc), NodeError> { let data_dir = data_dir.as_ref(); + #[cfg(debug_assertions)] + let data_dir = data_dir.join("dev"); + fs::create_dir_all(&data_dir).await?; tracing_subscriber::registry() diff --git a/core/src/object/kind.rs b/core/src/object/kind.rs new file mode 100644 index 000000000..dc0e7fe68 --- /dev/null +++ b/core/src/object/kind.rs @@ -0,0 +1,267 @@ +use std::{ + fmt::{Display, Formatter}, + str::FromStr, +}; + +use int_enum::IntEnum; +use rspc::Type; +use serde::{Deserialize, Serialize}; +use serde_json::Value; + +#[derive(Debug, Serialize, Deserialize, Clone, Copy, Type, IntEnum)] +#[repr(u8)] +pub enum ObjectKind { + // A file that can not be identified by the indexer + Unknown = 0, + // A known filetype, but without specific support + Document = 1, + // A virtual filesystem directory + Folder = 2, + // A file that contains human-readable text + Text = 3, + // A virtual directory int + Package = 4, + // An image file + Image = 5, + // An audio file + Audio = 6, + // A video file + Video = 7, + // A compressed archive of data + Archive = 8, + // An executable, program or application + Executable = 9, + // A link to another object + Alias = 10, + // Raw bytes encrypted by Spacedrive with self contained metadata + Encrypted = 11, + // A link can open web pages, apps or Spaces + Key = 12, + // A link can open web pages, apps or Spaces + Link = 13, + // A special filetype that represents a preserved webpage + WebPageArchive = 14, + // A widget is a mini app that can be placed in a Space at various sizes, associated Widget struct required + Widget = 15, + // Albums can only have one level of children, and are associated with the Album struct + Album = 16, + // Its like a folder, but appears like a stack of files, designed for burst photos / associated groups of files + Collection = 17, +} + +macro_rules! extension_enum { + ( + Extension { + $( $variant:ident($type:ident), )* + } + ) => { + #[derive(Debug, Serialize, Deserialize)] + pub enum Extension { + $( $variant($type), )* + } + // convert extension to object kind + impl From for ObjectKind { + fn from(ext: Extension) -> Self { + match ext { + $( Extension::$variant(_) => ObjectKind::$variant, )* + } + } + } + impl Display for Extension { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + $( Extension::$variant(x) => write!(f, "{}", x), )* + } + } + } + }; +} + +extension_enum! { + Extension { + Unknown(String), + Video(VideoExtension), + Image(ImageExtension), + Audio(AudioExtension), + Archive(ArchiveExtension), + Executable(ExecutableExtension), + Text(TextExtension), + Encrypted(EncryptedExtension), + Key(KeyExtension), + } +} + +/// Define a public enum with static array of all possible variants +/// including implementations to convert to/from string +macro_rules! enum_with_variants { + ( + $(#[$enum_attr:meta])* + $enum_name:ident $static_array_name:ident { + $($(#[$variant_attr:meta])* $variant:ident, )* + } + ) => { + #[derive(Debug, Serialize, Deserialize, Clone, Copy)] + #[serde(rename_all = "snake_case")] + $(#[$enum_attr])* + // construct enum + pub enum $enum_name { + $( $(#[$variant_attr])* $variant, )* + } + // a static array of all variants + pub static $static_array_name: &[$enum_name] = &[ + $( $enum_name::$variant, )* + ]; + // convert from string + impl FromStr for $enum_name { + type Err = serde_json::Error; + fn from_str(s: &str) -> Result { + serde_json::from_value(Value::String(s.to_string())) + } + } + // convert to string + impl std::fmt::Display for $enum_name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", serde_json::to_string(self).unwrap()) // SAFETY: This is safe + } + } + } +} + +// video extensions +enum_with_variants! { + VideoExtension ALL_VIDEO_EXTENSIONS { + Avi, + Asf, + Mpeg, + Mts, + Mpg, + Mpe, + Qt, + Mov, + Swf, + Mjpeg, + Ts, + Mxf, + M2v, + M2ts, + Flv, + Wm, + #[serde(rename = "3gp")] + _3gp, + M4v, + Wmv, + Mp4, + Webm, + } +} + +// image extensions +enum_with_variants! { + ImageExtension _ALL_IMAGE_EXTENSIONS { + Jpg, + Jpeg, + Png, + Gif, + Bmp, + Tiff, + Webp, + Svg, + Ico, + Heic, + } +} + +// audio extensions +enum_with_variants! { + AudioExtension _ALL_AUDIO_EXTENSIONS { + Mp3, + M4a, + Wav, + Aiff, + Aif, + Flac, + Ogg, + Opus, + } +} + +// archive extensions +enum_with_variants! { + ArchiveExtension _ALL_ARCHIVE_EXTENSIONS { + Zip, + Rar, + Tar, + Gz, + Bz2, + SevenZip, + } +} + +// executable extensions +enum_with_variants! { + ExecutableExtension _ALL_EXECUTABLE_EXTENSIONS { + Exe, + App, + Apk, + Deb, + Dmg, + Pkg, + Rpm, + Msi, + } +} + +// document extensions +enum_with_variants! { + DocumentExtension _ALL_DOCUMENT_EXTENSIONS { + Pdf, + Key, + Pages, + Numbers, + Doc, + Docx, + Xls, + Xlsx, + Ppt, + Pptx, + Odt, + Ods, + Odp, + Ics, + } +} + +// text file extensions +enum_with_variants! { + TextExtension _ALL_TEXT_EXTENSIONS { + Txt, + Rtf, + Csv, + Html, + Css, + Json, + Yaml, + Xml, + Md, + } +} +// encrypted file extensions +enum_with_variants! { + EncryptedExtension _ALL_ENCRYPTED_EXTENSIONS { + Bit, // Spacedrive encrypted file + Box, // Spacedrive container + Block,// Spacedrive block storage, + } +} + +// key extensions +enum_with_variants! { + KeyExtension _ALL_KEY_EXTENSIONS { + Pgp, + Pub, + Pem, + P12, + P8, + Keychain, + } +} diff --git a/core/src/object/mod.rs b/core/src/object/mod.rs index 3e151e7d0..29864fe3a 100644 --- a/core/src/object/mod.rs +++ b/core/src/object/mod.rs @@ -1,5 +1,6 @@ pub mod cas; pub mod identifier_job; +pub mod kind; pub mod preview; // Objects are primarily created by the identifier from Paths @@ -31,41 +32,3 @@ pub enum ObjectData { Object(Box), Path(Box), } - -#[derive(Debug, Serialize, Deserialize, Type)] -pub enum ObjectKind { - // A file that can not be identified by the indexer - Unknown, - // A known filetype, but without specific support - Document, - // A virtual filesystem directory - Folder, - // A file that contains human-readable text - TextFile, - // A virtual directory int - Package, - // An image file - Image, - // An audio file - Audio, - // A video file - Video, - // A compressed archive of data - Archive, - // An executable, program or application - Executable, - // A link to another object - Alias, - // Raw bytes encrypted by Spacedrive with self contained metadata - EncryptedBytes, - // A link can open web pages, apps or Spaces - Link, - // A special filetype that represents a preserved webpage - WebPageArchive, - // A widget is a mini app that can be placed in a Space at various sizes, associated Widget struct required - Widget, - // Albums can only have one level of children, and are associated with the Album struct - Album, - // Its like a folder, but appears like a stack of files, designed for burst photos / associated groups of files - Collection, -} diff --git a/core/src/object/preview/thumb.rs b/core/src/object/preview/thumb.rs index 64bb59ae6..c972ce9eb 100644 --- a/core/src/object/preview/thumb.rs +++ b/core/src/object/preview/thumb.rs @@ -3,6 +3,7 @@ use crate::{ invalidate_query, job::{JobError, JobReportUpdate, JobResult, JobState, StatefulJob, WorkerContext}, library::LibraryContext, + object::kind::{Extension, ImageExtension, VideoExtension, ALL_VIDEO_EXTENSIONS}, prisma::{file_path, location}, }; @@ -93,17 +94,20 @@ impl StatefulJob for ThumbnailJob { let root_path = location.local_path.map(PathBuf::from).unwrap(); // query database for all image files in this location that need thumbnails - let image_files = get_files_by_extension( + let image_files = get_files_by_extensions( &library_ctx, state.init.location_id, &state.init.path, - vec![ - "png".to_string(), - "jpeg".to_string(), - "jpg".to_string(), - "gif".to_string(), - "webp".to_string(), - ], + [ + ImageExtension::Png, + ImageExtension::Jpeg, + ImageExtension::Jpg, + ImageExtension::Gif, + ImageExtension::Webp, + ] + .into_iter() + .map(Extension::Image) + .collect(), ThumbnailJobStepKind::Image, ) .await?; @@ -112,38 +116,16 @@ impl StatefulJob for ThumbnailJob { #[cfg(feature = "ffmpeg")] let all_files = { // query database for all video files in this location that need thumbnails - let video_files = get_files_by_extension( + let video_files = get_files_by_extensions( &library_ctx, state.init.location_id, &state.init.path, - // Some formats extracted from https://ffmpeg.org/ffmpeg-formats.html - vec![ - "avi".to_string(), - "asf".to_string(), - "mpeg".to_string(), - // "mpg".to_string(), - "mts".to_string(), - "mpe".to_string(), - "vob".to_string(), - "qt".to_string(), - "mov".to_string(), - "asf".to_string(), - "asx".to_string(), - // "swf".to_string(), - "mjpeg".to_string(), - "ts".to_string(), - "mxf".to_string(), - // "m2v".to_string(), - "m2ts".to_string(), - "f4v".to_string(), - "wm".to_string(), - "3gp".to_string(), - "m4v".to_string(), - "wmv".to_string(), - "mp4".to_string(), - "webm".to_string(), - "flv".to_string(), - ], + ALL_VIDEO_EXTENSIONS + .into_iter() + .map(Clone::clone) + .filter(can_generate_thumbnail_for_video) + .map(Extension::Video) + .collect(), ThumbnailJobStepKind::Video, ) .await?; @@ -305,16 +287,16 @@ async fn generate_video_thumbnail>( Ok(()) } -async fn get_files_by_extension( +async fn get_files_by_extensions( ctx: &LibraryContext, location_id: i32, path: impl AsRef, - extensions: Vec, + extensions: Vec, kind: ThumbnailJobStepKind, ) -> Result, JobError> { let mut params = vec![ file_path::location_id::equals(location_id), - file_path::extension::in_vec(extensions), + file_path::extension::in_vec(extensions.iter().map(|e| e.to_string()).collect()), ]; let path_str = path.as_ref().to_string_lossy().to_string(); @@ -334,3 +316,11 @@ async fn get_files_by_extension( .map(|file_path| ThumbnailJobStep { file_path, kind }) .collect()) } + +pub fn can_generate_thumbnail_for_video(video_extension: &VideoExtension) -> bool { + use VideoExtension::*; + match video_extension { + Mpg | Swf | M2v => false, + _ => true, + } +} diff --git a/packages/assets/svgs/folder.svg b/packages/assets/svgs/folder.svg index 96896b72f..deea565c5 100644 --- a/packages/assets/svgs/folder.svg +++ b/packages/assets/svgs/folder.svg @@ -1,16 +1,83 @@ - - - - - - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/assets/svgs/video.svg b/packages/assets/svgs/video.svg new file mode 100644 index 000000000..5863eee76 --- /dev/null +++ b/packages/assets/svgs/video.svg @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/assets/svgs/zip.svg b/packages/assets/svgs/zip.svg new file mode 100644 index 000000000..4352de6bd --- /dev/null +++ b/packages/assets/svgs/zip.svg @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/client/src/stores/explorerStore.ts b/packages/client/src/stores/explorerStore.ts index 3f1a16db3..c37a1a5a8 100644 --- a/packages/client/src/stores/explorerStore.ts +++ b/packages/client/src/stores/explorerStore.ts @@ -2,7 +2,7 @@ import { proxy, useSnapshot } from 'valtio'; import { resetStore } from './util'; -export type ExplorerLayoutMode = 'list' | 'grid'; +export type ExplorerLayoutMode = 'list' | 'grid' | 'media'; export enum ExplorerKind { Location, diff --git a/packages/interface/src/components/explorer/Explorer.tsx b/packages/interface/src/components/explorer/Explorer.tsx index 7dcf6911f..5db120d91 100644 --- a/packages/interface/src/components/explorer/Explorer.tsx +++ b/packages/interface/src/components/explorer/Explorer.tsx @@ -6,7 +6,7 @@ import ExplorerContextMenu from './ExplorerContextMenu'; import { VirtualizedList } from './VirtualizedList'; interface Props { - data: ExplorerData; + data?: ExplorerData; } export default function Explorer(props: Props) { @@ -24,16 +24,17 @@ export default function Explorer(props: Props) {
-
- + +
+ {props.data && ( + + )} {expStore.showInspector && ( -
- {props.data.items[expStore.selectedRowIndex]?.id && ( - - )} +
+
)}
diff --git a/packages/interface/src/components/explorer/ExplorerContextMenu.tsx b/packages/interface/src/components/explorer/ExplorerContextMenu.tsx index 03479e169..8676dd0b7 100644 --- a/packages/interface/src/components/explorer/ExplorerContextMenu.tsx +++ b/packages/interface/src/components/explorer/ExplorerContextMenu.tsx @@ -1,4 +1,9 @@ -import { useExplorerStore, useLibraryMutation, useLibraryQuery } from '@sd/client'; +import { + getExplorerStore, + useExplorerStore, + useLibraryMutation, + useLibraryQuery +} from '@sd/client'; import { ContextMenu as CM } from '@sd/ui'; import { ArrowBendUpRight, @@ -59,7 +64,7 @@ interface Props { } export default function ExplorerContextMenu(props: Props) { - const store = useExplorerStore(); + const store = getExplorerStore(); return (
@@ -101,18 +106,13 @@ export default function ExplorerContextMenu(props: Props) { )} - - {/* {libraries.map(library => )} */} - - - - + diff --git a/packages/interface/src/components/explorer/FileItem.tsx b/packages/interface/src/components/explorer/FileItem.tsx index 9ede86e8d..1a8708d16 100644 --- a/packages/interface/src/components/explorer/FileItem.tsx +++ b/packages/interface/src/components/explorer/FileItem.tsx @@ -18,6 +18,8 @@ function FileItem({ data, selected, index, ...rest }: Props) { // props.index === store.selectedRowIndex + const isVid = isVideo(data.extension || ''); + return (
{ @@ -44,17 +46,23 @@ function FileItem({ data, selected, index, ...rest }: Props) { >
+ {data?.extension && isVid && ( +
+ {data.extension} +
+ )}
@@ -99,6 +107,11 @@ function isVideo(extension: string) { 'wmv', 'mp4', 'webm', - 'flv' + 'flv', + 'mpg', + 'hevc', + 'ogv', + 'swf', + 'wtv' ].includes(extension); } diff --git a/packages/interface/src/components/explorer/FileThumb.tsx b/packages/interface/src/components/explorer/FileThumb.tsx index 4f480299c..70ee33060 100644 --- a/packages/interface/src/components/explorer/FileThumb.tsx +++ b/packages/interface/src/components/explorer/FileThumb.tsx @@ -1,3 +1,5 @@ +import videoSvg from '@sd/assets/svgs/video.svg'; +import zipSvg from '@sd/assets/svgs/zip.svg'; import { getExplorerStore, useExplorerStore, usePlatform } from '@sd/client'; import { ExplorerItem } from '@sd/client'; import clsx from 'clsx'; @@ -14,6 +16,7 @@ interface Props { className?: string; style?: React.CSSProperties; iconClassNames?: string; + kind?: 'video' | 'image' | 'audio' | 'zip' | 'other'; } export default function FileThumb({ data, ...props }: Props) { @@ -41,11 +44,30 @@ export default function FileThumb({ data, ...props }: Props) { return ( ); + + if (props.kind === 'video') { + return ( +
+ +
+ ); + } + if (props.kind === 'zip') { + return ( +
+ +
+ ); + } } const Icon = icons[data.extension as keyof typeof icons]; diff --git a/packages/interface/src/components/explorer/Inspector.tsx b/packages/interface/src/components/explorer/Inspector.tsx index 51ff16ae4..37a44eab4 100644 --- a/packages/interface/src/components/explorer/Inspector.tsx +++ b/packages/interface/src/components/explorer/Inspector.tsx @@ -18,13 +18,13 @@ import { isObject } from './utils'; interface Props { context?: ExplorerContext; - data: ExplorerItem; + data?: ExplorerItem; } 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.object; + const objectData = props.data ? (isObject(props.data) ? props.data : props.data.object) : null; // this prevents the inspector from fetching data when the user is navigating quickly const [readyToFetch, setReadyToFetch] = useState(false); @@ -33,7 +33,7 @@ export const Inspector = (props: Props) => { setReadyToFetch(true); }, 350); return () => clearTimeout(timeout); - }, [props.data.id]); + }, [props.data?.id]); // this is causing LAG const { data: tags } = useLibraryQuery(['tags.getForFile', objectData?.id || -1], { @@ -41,113 +41,115 @@ export const Inspector = (props: Props) => { }); return ( -
- {!!props.data && ( - <> -
- -
-
-

- {props.data?.name} - {props.data?.extension && `.${props.data.extension}`} -

- {objectData && ( -
- - - - - - - - - -
- )} - {!!tags?.length && ( - <> - - - {tags?.map((tag) => ( -
setSelectedTag(tag.id === selectedTag ? null : tag.id)} - key={tag.id} - className={clsx( - 'flex items-center rounded px-1.5 py-0.5' - // selectedTag === tag.id && 'ring' - )} - style={{ backgroundColor: tag.color + 'CC' }} - > - {tag.name} -
- ))} -
- } - /> - - )} - {props.context?.type == 'Location' && props.data?.type === 'Path' && ( - <> - - - - )} - - - - - {!is_dir && ( - <> - -
- {props.data?.extension && ( - - {props.data?.extension} - - )} -

- {props.data?.extension - ? //@ts-ignore - types[props.data.extension.toUpperCase()]?.descriptions.join(' / ') - : 'Unknown'} -

+
+
+ {!!props.data && ( + <> +
+ +
+
+

+ {props.data?.name} + {props.data?.extension && `.${props.data.extension}`} +

+ {objectData && ( +
+ + + + + + + + +
- {objectData && ( - <> - - - {objectData.cas_id && ( - + )} + {!!tags?.length && ( + <> + + + {tags?.map((tag) => ( +
setSelectedTag(tag.id === selectedTag ? null : tag.id)} + key={tag.id} + className={clsx( + 'flex items-center rounded px-1.5 py-0.5' + // selectedTag === tag.id && 'ring' + )} + style={{ backgroundColor: tag.color + 'CC' }} + > + {tag.name} +
+ ))} +
+ } + /> + + )} + {props.context?.type == 'Location' && props.data?.type === 'Path' && ( + <> + + + + )} + + + + + {!is_dir && ( + <> + +
+ {props.data?.extension && ( + + {props.data?.extension} + )} - - )} - - )} -
- - )} +

+ {props.data?.extension + ? //@ts-ignore + types[props.data.extension.toUpperCase()]?.descriptions.join(' / ') + : 'Unknown'} +

+
+ {objectData && ( + <> + + + {objectData.cas_id && ( + + )} + + )} + + )} +
+ + )} +
); }; diff --git a/packages/interface/src/components/explorer/VirtualizedList.tsx b/packages/interface/src/components/explorer/VirtualizedList.tsx index 771867997..6c000db03 100644 --- a/packages/interface/src/components/explorer/VirtualizedList.tsx +++ b/packages/interface/src/components/explorer/VirtualizedList.tsx @@ -1,7 +1,7 @@ import { ExplorerLayoutMode, getExplorerStore, useExplorerStore } from '@sd/client'; import { ExplorerContext, ExplorerItem, FilePath } from '@sd/client'; import { useVirtualizer } from '@tanstack/react-virtual'; -import { memo, useCallback, useLayoutEffect, useMemo, useRef, useState } from 'react'; +import { memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; import { useKey, useOnWindowResize, useWindowSize } from 'rooks'; import { useSnapshot } from 'valtio'; @@ -33,6 +33,9 @@ export const VirtualizedList: React.FC = ({ data, context }) => { } useOnWindowResize(handleWindowResize); useLayoutEffect(() => handleWindowResize(), []); + useEffect(() => { + setWidth(innerRef.current?.offsetWidth || 0); + }, [explorerStore.showInspector]); // sizing calculations const amountOfColumns = Math.floor(width / explorerStore.gridItemSize) || 8, diff --git a/packages/interface/src/components/layout/Sidebar.tsx b/packages/interface/src/components/layout/Sidebar.tsx index 3ab74b69c..440026a5a 100644 --- a/packages/interface/src/components/layout/Sidebar.tsx +++ b/packages/interface/src/components/layout/Sidebar.tsx @@ -162,7 +162,7 @@ export function Sidebar() {
diff --git a/packages/interface/src/components/layout/TopBar.tsx b/packages/interface/src/components/layout/TopBar.tsx index 520d6a35e..f4f3fb03e 100644 --- a/packages/interface/src/components/layout/TopBar.tsx +++ b/packages/interface/src/components/layout/TopBar.tsx @@ -1,4 +1,4 @@ -import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline'; +import { ChevronLeftIcon, ChevronRightIcon, PhotoIcon } from '@heroicons/react/24/outline'; import { OperatingSystem, getExplorerStore, @@ -8,10 +8,14 @@ import { import { Dropdown } from '@sd/ui'; import clsx from 'clsx'; import { + Aperture, ArrowsClockwise, + FilmStrip, IconProps, + Image, Key, List, + MonitorPlay, Rows, SidebarSimple, SquaresFour @@ -49,7 +53,7 @@ const TopBarButton: React.FC = ({