diff --git a/.vscode/settings.json b/.vscode/settings.json index c05e7db2c..f05a1b362 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,10 @@ { - "cSpell.words": ["ipfs", "repr", "tailwindcss"], + "cSpell.words": [ + "ipfs", + "Keepsafe", + "repr", + "tailwindcss" + ], "rust-analyzer.procMacro.enable": true, "rust-analyzer.diagnostics.enableExperimental": false } diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 6c3c84425..b84db6bc8 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -32,6 +32,8 @@ "@headlessui/react": "^1.5.0", "@heroicons/react": "^1.0.6", "@radix-ui/react-dialog": "^0.1.7", + "@radix-ui/react-dropdown-menu": "^0.1.6", + "@radix-ui/react-icons": "^1.1.0", "@radix-ui/react-slider": "^0.1.4", "@sd/client": "workspace:*", "@sd/core": "workspace:*", diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json index 686ef7542..4e7a4ced5 100644 --- a/apps/desktop/src-tauri/tauri.conf.json +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -45,7 +45,7 @@ "allowlist": { "all": true, "protocol": { - "assetScope": ["$HOME/Library/Application Support/spacedrive/thumbnails/**/*.webp"] + "assetScope": ["*"] }, "dialog": { "all": true, diff --git a/apps/desktop/src/assets/svg/folder-white.svg b/apps/desktop/src/assets/svg/folder-white.svg new file mode 100644 index 000000000..fab45e48e --- /dev/null +++ b/apps/desktop/src/assets/svg/folder-white.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/apps/desktop/src/components/file/FileList.tsx b/apps/desktop/src/components/file/FileList.tsx index 6ed756ef3..dc54497e6 100644 --- a/apps/desktop/src/components/file/FileList.tsx +++ b/apps/desktop/src/components/file/FileList.tsx @@ -17,7 +17,7 @@ type ExplorerState = { }; export const useExplorerState = create((set) => ({ - selectedRowIndex: -1, + selectedRowIndex: 1, setSelectedRowIndex: (index) => set((state) => ({ ...state, selectedRowIndex: index })) })); @@ -52,6 +52,7 @@ export const FileList: React.FC<{ location_id: number; path: string; limit: numb const path = props.path; const { selectedRowIndex, setSelectedRowIndex } = useExplorerState(); + const [goingUp, setGoingUp] = useState(false); const { data: currentDir } = useBridgeQuery('LibGetExplorerDir', { location_id: props.location_id, @@ -60,16 +61,25 @@ export const FileList: React.FC<{ location_id: number; path: string; limit: numb }); useEffect(() => { - if (selectedRowIndex != -1) VList.current?.scrollIntoView({ index: selectedRowIndex }); + if (selectedRowIndex === 0 && goingUp) { + VList.current?.scrollTo({ top: 0, behavior: 'smooth' }); + } + if (selectedRowIndex != -1) { + VList.current?.scrollIntoView({ + index: goingUp ? selectedRowIndex - 1 : selectedRowIndex + }); + } }, [selectedRowIndex]); useKey('ArrowUp', (e) => { e.preventDefault(); + setGoingUp(true); if (selectedRowIndex != -1 && selectedRowIndex !== 0) setSelectedRowIndex(selectedRowIndex - 1); }); useKey('ArrowDown', (e) => { e.preventDefault(); + setGoingUp(false); if (selectedRowIndex != -1 && selectedRowIndex !== (currentDir?.contents.length ?? 1) - 1) setSelectedRowIndex(selectedRowIndex + 1); }); @@ -84,7 +94,7 @@ export const FileList: React.FC<{ location_id: number; path: string; limit: numb const Header = () => (
-

{currentDir?.directory.name}

+

{currentDir?.directory.name}

{columns.map((col) => ( @@ -107,7 +117,7 @@ export const FileList: React.FC<{ location_id: number; path: string; limit: numb
}} + increaseViewportBy={{ top: 400, bottom: 200 }} className="outline-none" /> @@ -180,9 +190,12 @@ const RenderCell: React.FC<{ colKey?: ColumnKey; dirId?: number; file?: FilePath const row = file; if (!row) return <>; + // @ts-expect-error const value = row[colKey]; if (!value) return <>; - const { data: client } = useBridgeQuery('ClientGetState'); + const { data: client } = useBridgeQuery('ClientGetState', undefined, { + refetchOnWindowFocus: false + }); const location = useContext(LocationContext); switch (colKey) { @@ -198,7 +211,7 @@ const RenderCell: React.FC<{ colKey?: ColumnKey; dirId?: number; file?: FilePath ) diff --git a/apps/desktop/src/components/file/FileThumb.tsx b/apps/desktop/src/components/file/FileThumb.tsx index 4e1889757..4bd587a6e 100644 --- a/apps/desktop/src/components/file/FileThumb.tsx +++ b/apps/desktop/src/components/file/FileThumb.tsx @@ -11,24 +11,25 @@ export default function FileThumb(props: { }) { const { data: client } = useBridgeQuery('ClientGetState'); - return ( - <> - {props.file.is_dir ? ( - - ) : ( - props.file.has_local_thumbnail && - client?.data_path && ( - - ) - )} - - ); + if (props.file.is_dir) { + return ( + + ); + } + + if (props.file.has_local_thumbnail && client?.data_path) { + return ( + + ); + } + + return
; } diff --git a/apps/desktop/src/components/file/Inspector.tsx b/apps/desktop/src/components/file/Inspector.tsx index bf41ca533..43067e5bc 100644 --- a/apps/desktop/src/components/file/Inspector.tsx +++ b/apps/desktop/src/components/file/Inspector.tsx @@ -2,33 +2,37 @@ import React from 'react'; import { Transition } from '@headlessui/react'; import { convertFileSrc } from '@tauri-apps/api/tauri'; import moment from 'moment'; -import { Button } from '../primitive'; +import { Button, Input, TextArea } from '../primitive'; import { ShareIcon } from '@heroicons/react/solid'; import { Heart, Link } from 'phosphor-react'; import { useExplorerState } from './FileList'; import { FilePath } from '@sd/core'; import FileThumb from './FileThumb'; +import { default as types } from '../../constants/file-types.json'; interface MetaItemProps { title: string; - value: string; + value: string | React.ReactNode; } const MetaItem = (props: MetaItemProps) => { return (
{props.title}
-

{props.value}

+ {typeof props.value === 'string' ? ( +

{props.value}

+ ) : ( + props.value + )}
); }; -const Divider = () =>
; +const Divider = () =>
; export const Inspector = (props: { selectedFile?: FilePath; locationId: number }) => { - const { selectedRowIndex } = useExplorerState(); - - const isOpen = !!props.selectedFile; + // const { selectedRowIndex } = useExplorerState(); + // const isOpen = !!props.selectedFile; const file = props.selectedFile; @@ -44,7 +48,7 @@ export const Inspector = (props: { selectedFile?: FilePath; locationId: number } >
{!!file && ( -
+
- + {file?.temp_cas_id && ( + + )} + + + + {!file?.is_dir && ( + <> +
+ {file?.extension && ( + + {file?.extension} + + )} +

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

+
+ + + )} + } + /> + {/*
*/} diff --git a/apps/desktop/src/components/file/Sidebar.tsx b/apps/desktop/src/components/file/Sidebar.tsx index f34047f22..ee3d025ca 100644 --- a/apps/desktop/src/components/file/Sidebar.tsx +++ b/apps/desktop/src/components/file/Sidebar.tsx @@ -14,7 +14,7 @@ import { platform } from '@tauri-apps/api/os'; interface SidebarProps extends DefaultProps {} -export const SidebarLink = (props: NavLinkProps) => ( +export const SidebarLink = (props: NavLinkProps & { children: React.ReactNode }) => ( {({ isActive }) => ( ( ); -const Heading: React.FC<{}> = ({ children }) => ( +const Heading: React.FC<{ children: React.ReactNode }> = ({ children }) => (
{children}
); @@ -53,7 +53,7 @@ export function MacOSTrafficLights() { export const Sidebar: React.FC = (props) => { const [isMacos, setIsMacos] = useState(false); - const { data: volumes } = useBridgeQuery('SysGetVolumes'); + const { data: locations } = useBridgeQuery('SysGetLocations'); useEffect(() => { platform().then((platform) => { @@ -113,40 +113,47 @@ export const Sidebar: React.FC = (props) => { - Spaces + Content - Media + Albums - + {/* Explorer - + */}
Locations - {volumes?.map((location, index) => { + {locations?.map((location, index) => { return (
- - - {location.name} -
- {location.is_removable && ( - + + {location.name} +
+ )} - +
); })} @@ -184,9 +191,6 @@ export const Sidebar: React.FC = (props) => { )}
- {/*
- Drop -
*/}
); }; diff --git a/apps/desktop/src/components/primitive/DropdownMenu.tsx b/apps/desktop/src/components/primitive/DropdownMenu.tsx new file mode 100644 index 000000000..05db1759e --- /dev/null +++ b/apps/desktop/src/components/primitive/DropdownMenu.tsx @@ -0,0 +1,212 @@ +import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'; +import { + CaretRightIcon, + CheckIcon, + CropIcon, + EyeClosedIcon, + EyeOpenIcon, + FileIcon, + FrameIcon, + GridIcon, + Link2Icon, + MixerHorizontalIcon, + PersonIcon, + TransparencyGridIcon +} from '@radix-ui/react-icons'; +import cx from 'clsx'; +import React, { ReactNode, useState } from 'react'; +import { Button } from './Button'; + +interface RadixMenuItem { + label: string; + shortcut?: string; + icon?: ReactNode; +} + +interface User { + name: string; + url?: string; +} + +const generalMenuItems: RadixMenuItem[] = [ + { + label: 'New File', + icon: , + shortcut: '⌘+N' + }, + { + label: 'Settings', + icon: , + shortcut: '⌘+,' + } +]; + +const regionToolMenuItems: RadixMenuItem[] = [ + { + label: 'Frame', + icon: , + shortcut: '⌘+F' + }, + { + label: 'Crop', + icon: , + shortcut: '⌘+S' + } +]; + +const users: User[] = [ + { + name: 'Adam', + url: 'https://github.com/adamwathan.png' + }, + { + name: 'Steve', + url: 'https://github.com/steveschoger.png' + }, + { + name: 'Robin', + url: 'https://github.com/robinmalfait.png' + } +]; + +interface Props {} + +const DropdownMenu = (props: Props) => { + const [showGrid, setShowGrid] = useState(false); + const [showUi, setShowUi] = useState(false); + + return ( +
+ + + + + + + {generalMenuItems.map(({ label, icon, shortcut }, i) => ( + + {icon} + {label} + {shortcut && {shortcut}} + + ))} + + + + + {showGrid ? ( + + ) : ( + + )} + Show Grid + + + + + + + {showUi ? ( + + ) : ( + + )} + Show UI + + + + + + + + + Region Tools + + + {regionToolMenuItems.map(({ label, icon, shortcut }, i) => ( + + {icon} + {label} + {shortcut && {shortcut}} + + ))} + + + + + + + Share + + + + {users.map(({ name, url }, i) => ( + + {url ? ( + + ) : ( + + )} + {name} + + ))} + + + + +
+ ); +}; + +export default DropdownMenu; diff --git a/apps/desktop/src/components/primitive/Input.tsx b/apps/desktop/src/components/primitive/Input.tsx index c81822ac6..c0c63b21e 100644 --- a/apps/desktop/src/components/primitive/Input.tsx +++ b/apps/desktop/src/components/primitive/Input.tsx @@ -48,6 +48,25 @@ export const Input = ({ size, ...props }: InputProps) => { ); }; +interface TextAreaProps extends React.InputHTMLAttributes { + variant?: keyof typeof variants; + size?: 'sm'; +} + +export const TextArea = ({ size, ...props }: TextAreaProps) => { + return ( +