mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-02 10:03:28 +00:00
Explorer Grid View (#334)
added grid view, as well as: - moved location context to client lib - merged library settings with main settings - added some missing settings - removed demo locations due to FileItem props syntax change, they are currently being replaced anyway by Oscar in another PR - added functioning favorite button to the inspector, that works now
This commit is contained in:
parent
fa1c39657d
commit
7839fe43e1
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
@ -12,6 +12,7 @@
|
|||
"ipfs",
|
||||
"Keepsafe",
|
||||
"nodestate",
|
||||
"overscan",
|
||||
"pathctx",
|
||||
"prismjs",
|
||||
"proptype",
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type LibraryCommand = { key: "FileReadMetaData", params: { id: number, } } | { key: "FileSetNote", params: { id: number, note: string | null, } } | { key: "FileDelete", params: { id: number, } } | { key: "TagCreate", params: { name: string, color: string, } } | { key: "TagUpdate", params: { name: string, color: string, } } | { key: "TagAssign", params: { file_id: number, tag_id: number, } } | { key: "TagDelete", params: { id: number, } } | { key: "LocCreate", params: { path: string, } } | { key: "LocUpdate", params: { id: number, name: string | null, } } | { key: "LocDelete", params: { id: number, } } | { key: "LocRescan", params: { id: number, } } | { key: "SysVolumeUnmount", params: { id: number, } } | { key: "GenerateThumbsForLocation", params: { id: number, path: string, } } | { key: "IdentifyUniqueFiles", params: { id: number, path: string, } };
|
||||
export type LibraryCommand = { key: "FileReadMetaData", params: { id: number, } } | { key: "FileSetNote", params: { id: number, note: string | null, } } | { key: "FileSetFavorite", params: { id: number, favorite: boolean, } } | { key: "FileDelete", params: { id: number, } } | { key: "TagCreate", params: { name: string, color: string, } } | { key: "TagUpdate", params: { name: string, color: string, } } | { key: "TagAssign", params: { file_id: number, tag_id: number, } } | { key: "TagDelete", params: { id: number, } } | { key: "LocCreate", params: { path: string, } } | { key: "LocUpdate", params: { id: number, name: string | null, } } | { key: "LocDelete", params: { id: number, } } | { key: "LocFullRescan", params: { id: number, } } | { key: "LocQuickRescan", params: { id: number, } } | { key: "SysVolumeUnmount", params: { id: number, } } | { key: "GenerateThumbsForLocation", params: { id: number, path: string, } } | { key: "IdentifyUniqueFiles", params: { id: number, path: string, } };
|
|
@ -168,3 +168,30 @@ pub async fn set_note(
|
|||
|
||||
Ok(CoreResponse::Success(()))
|
||||
}
|
||||
|
||||
pub async fn favorite(
|
||||
ctx: LibraryContext,
|
||||
id: i32,
|
||||
favorite: bool,
|
||||
) -> Result<CoreResponse, CoreError> {
|
||||
let _response = ctx
|
||||
.db
|
||||
.file()
|
||||
.find_unique(file::id::equals(id))
|
||||
.update(vec![file::favorite::set(favorite)])
|
||||
.exec()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
ctx.emit(CoreEvent::InvalidateQuery(ClientQuery::LibraryQuery {
|
||||
library_id: ctx.id.to_string(),
|
||||
query: LibraryQuery::LibGetExplorerDir {
|
||||
limit: 0,
|
||||
path: "".to_string(),
|
||||
location_id: 0,
|
||||
},
|
||||
}))
|
||||
.await;
|
||||
|
||||
Ok(CoreResponse::Success(()))
|
||||
}
|
||||
|
|
|
@ -175,7 +175,7 @@ impl Node {
|
|||
description,
|
||||
} => {
|
||||
self.library_manager
|
||||
.edit_library(id, name, description)
|
||||
.edit(id, name, description)
|
||||
.await
|
||||
.unwrap();
|
||||
CoreResponse::Success(())
|
||||
|
@ -210,15 +210,19 @@ impl Node {
|
|||
sys::delete_location(&ctx, id).await?;
|
||||
CoreResponse::Success(())
|
||||
}
|
||||
LibraryCommand::LocRescan { id } => {
|
||||
LibraryCommand::LocFullRescan { id } => {
|
||||
sys::scan_location(&ctx, id, String::new()).await;
|
||||
CoreResponse::Success(())
|
||||
}
|
||||
LibraryCommand::LocQuickRescan { id: _ } => todo!(),
|
||||
// CRUD for files
|
||||
LibraryCommand::FileReadMetaData { id: _ } => todo!(),
|
||||
LibraryCommand::FileSetNote { id, note } => {
|
||||
file::set_note(ctx, id, note).await?
|
||||
}
|
||||
LibraryCommand::FileSetFavorite { id, favorite } => {
|
||||
file::favorite(ctx, id, favorite).await?
|
||||
}
|
||||
// ClientCommand::FileEncrypt { id: _, algorithm: _ } => todo!(),
|
||||
LibraryCommand::FileDelete { id } => {
|
||||
ctx.db
|
||||
|
@ -345,6 +349,7 @@ pub enum LibraryCommand {
|
|||
// Files
|
||||
FileReadMetaData { id: i32 },
|
||||
FileSetNote { id: i32, note: Option<String> },
|
||||
FileSetFavorite { id: i32, favorite: bool },
|
||||
// FileEncrypt { id: i32, algorithm: EncryptionAlgorithm },
|
||||
FileDelete { id: i32 },
|
||||
// Tags
|
||||
|
@ -356,7 +361,8 @@ pub enum LibraryCommand {
|
|||
LocCreate { path: String },
|
||||
LocUpdate { id: i32, name: Option<String> },
|
||||
LocDelete { id: i32 },
|
||||
LocRescan { id: i32 },
|
||||
LocFullRescan { id: i32 },
|
||||
LocQuickRescan { id: i32 },
|
||||
// System
|
||||
SysVolumeUnmount { id: i32 },
|
||||
GenerateThumbsForLocation { id: i32, path: String },
|
||||
|
|
|
@ -156,7 +156,7 @@ impl LibraryManager {
|
|||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) async fn edit_library(
|
||||
pub(crate) async fn edit(
|
||||
&self,
|
||||
id: String,
|
||||
name: Option<String>,
|
||||
|
@ -223,7 +223,7 @@ impl LibraryManager {
|
|||
.find(|lib| lib.id.to_string() == library_id)
|
||||
.map(|v| v.clone())
|
||||
}
|
||||
|
||||
|
||||
/// load the library from a given path
|
||||
pub(crate) async fn load(
|
||||
id: Uuid,
|
||||
|
|
|
@ -18,10 +18,10 @@ export function setTransport(_transport: BaseTransport) {
|
|||
}
|
||||
|
||||
// extract keys from generated Rust query/command types
|
||||
type QueryKeyType = ClientQuery['key'];
|
||||
type LibraryQueryKeyType = LibraryQuery['key'];
|
||||
type CommandKeyType = ClientCommand['key'];
|
||||
type LibraryCommandKeyType = LibraryCommand['key'];
|
||||
export type QueryKeyType = ClientQuery['key'];
|
||||
export type LibraryQueryKeyType = LibraryQuery['key'];
|
||||
export type CommandKeyType = ClientCommand['key'];
|
||||
export type LibraryCommandKeyType = LibraryCommand['key'];
|
||||
|
||||
// extract the type from the union
|
||||
type CQType<K> = Extract<ClientQuery, { key: K }>;
|
||||
|
|
|
@ -10,6 +10,7 @@ export interface AppProps {
|
|||
transport: BaseTransport;
|
||||
platform: Platform;
|
||||
cdn_url?: CdnUrl;
|
||||
data_path?: string;
|
||||
convertFileSrc: (url: string) => string;
|
||||
openDialog: (options: { directory?: boolean }) => Promise<string | string[] | null>;
|
||||
onClose?: () => void;
|
||||
|
|
9
packages/client/src/context/LocationContext.ts
Normal file
9
packages/client/src/context/LocationContext.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { createContext } from 'react';
|
||||
|
||||
export const LocationContext = createContext<{
|
||||
location_id: number;
|
||||
data_path: string;
|
||||
}>({
|
||||
location_id: 1,
|
||||
data_path: ''
|
||||
});
|
|
@ -1 +1,2 @@
|
|||
export * from './AppPropsContext';
|
||||
export * from './LocationContext';
|
||||
|
|
|
@ -1,16 +1,21 @@
|
|||
import create from 'zustand';
|
||||
|
||||
type LayoutMode = 'list' | 'grid';
|
||||
|
||||
type ExplorerStore = {
|
||||
selectedRowIndex: number;
|
||||
layoutMode: LayoutMode;
|
||||
setSelectedRowIndex: (index: number) => void;
|
||||
locationId: number;
|
||||
setLocationId: (index: number) => void;
|
||||
newThumbnails: Record<string, boolean>;
|
||||
addNewThumbnail: (cas_id: string) => void;
|
||||
setLayoutMode: (mode: LayoutMode) => void;
|
||||
reset: () => void;
|
||||
};
|
||||
|
||||
export const useExplorerStore = create<ExplorerStore>((set) => ({
|
||||
layoutMode: 'grid',
|
||||
selectedRowIndex: 1,
|
||||
setSelectedRowIndex: (index) => set((state) => ({ ...state, selectedRowIndex: index })),
|
||||
locationId: -1,
|
||||
|
@ -21,5 +26,6 @@ export const useExplorerStore = create<ExplorerStore>((set) => ({
|
|||
...state,
|
||||
newThumbnails: { ...state.newThumbnails, [cas_id]: true }
|
||||
})),
|
||||
setLayoutMode: (mode: LayoutMode) => set((state) => ({ ...state, layoutMode: mode })),
|
||||
reset: () => set(() => ({}))
|
||||
}));
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
"@sd/client": "workspace:*",
|
||||
"@sd/core": "workspace:*",
|
||||
"@sd/ui": "workspace:*",
|
||||
"@types/styled-components": "^5.1.25",
|
||||
"@vitejs/plugin-react": "^1.3.2",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"byte-size": "^8.1.0",
|
||||
|
@ -54,6 +55,7 @@
|
|||
"react-transition-group": "^4.4.2",
|
||||
"react-virtuoso": "^2.12.1",
|
||||
"rooks": "^5.11.2",
|
||||
"styled-components": "^5.3.5",
|
||||
"tailwindcss": "^3.0.24",
|
||||
"use-debounce": "^8.0.1",
|
||||
"zustand": "4.0.0-rc.1"
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import '@fontsource/inter/variable.css';
|
||||
import { BaseTransport, ClientProvider, setTransport } from '@sd/client';
|
||||
import { BaseTransport, ClientProvider, setTransport, useBridgeQuery } from '@sd/client';
|
||||
import { useCoreEvents } from '@sd/client';
|
||||
import { AppProps, AppPropsContext } from '@sd/client';
|
||||
import React from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { ErrorBoundary } from 'react-error-boundary';
|
||||
import { QueryClient, QueryClientProvider } from 'react-query';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
@ -13,12 +13,24 @@ import './style.scss';
|
|||
|
||||
const queryClient = new QueryClient();
|
||||
|
||||
function RouterContainer() {
|
||||
function RouterContainer(props: { props: AppProps }) {
|
||||
useCoreEvents();
|
||||
const [appProps, setAppProps] = useState(props.props);
|
||||
const { data: client } = useBridgeQuery('NodeGetState');
|
||||
|
||||
useEffect(() => {
|
||||
setAppProps({
|
||||
...appProps,
|
||||
data_path: client?.data_path
|
||||
});
|
||||
}, [client?.data_path]);
|
||||
|
||||
return (
|
||||
<MemoryRouter>
|
||||
<AppRouter />
|
||||
</MemoryRouter>
|
||||
<AppPropsContext.Provider value={Object.assign({ isFocused: true }, appProps)}>
|
||||
<MemoryRouter>
|
||||
<AppRouter />
|
||||
</MemoryRouter>
|
||||
</AppPropsContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -34,11 +46,9 @@ export default function App(props: AppProps) {
|
|||
<>
|
||||
<ErrorBoundary FallbackComponent={ErrorFallback} onReset={() => {}}>
|
||||
<QueryClientProvider client={queryClient} contextSharing={false}>
|
||||
<AppPropsContext.Provider value={Object.assign({ isFocused: true }, props)}>
|
||||
<ClientProvider>
|
||||
<RouterContainer />
|
||||
</ClientProvider>
|
||||
</AppPropsContext.Provider>
|
||||
<ClientProvider>
|
||||
<RouterContainer props={props} />
|
||||
</ClientProvider>
|
||||
</QueryClientProvider>
|
||||
</ErrorBoundary>
|
||||
</>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { AppPropsContext } from '@sd/client';
|
||||
import { AppPropsContext, useBridgeQuery } from '@sd/client';
|
||||
import clsx from 'clsx';
|
||||
import React, { useContext } from 'react';
|
||||
import { Outlet } from 'react-router-dom';
|
||||
|
|
|
@ -12,21 +12,22 @@ import { OverviewScreen } from './screens/Overview';
|
|||
import { PhotosScreen } from './screens/Photos';
|
||||
import { RedirectPage } from './screens/Redirect';
|
||||
import { TagScreen } from './screens/Tag';
|
||||
import { CurrentLibrarySettings } from './screens/settings/CurrentLibrarySettings';
|
||||
import { SettingsScreen } from './screens/settings/Settings';
|
||||
import AppearanceSettings from './screens/settings/client/AppearanceSettings';
|
||||
import ExtensionSettings from './screens/settings/client/ExtensionsSettings';
|
||||
import GeneralSettings from './screens/settings/client/GeneralSettings';
|
||||
import KeybindSettings from './screens/settings/client/KeybindSettings';
|
||||
import ContactsSettings from './screens/settings/library/ContactsSettings';
|
||||
import KeysSettings from './screens/settings/library/KeysSetting';
|
||||
import LibraryGeneralSettings from './screens/settings/library/LibraryGeneralSettings';
|
||||
import LocationSettings from './screens/settings/library/LocationSettings';
|
||||
import NodesSettings from './screens/settings/library/NodesSettings';
|
||||
import SecuritySettings from './screens/settings/library/SecuritySettings';
|
||||
import SharingSettings from './screens/settings/library/SharingSettings';
|
||||
import SyncSettings from './screens/settings/library/SyncSettings';
|
||||
import TagsSettings from './screens/settings/library/TagsSettings';
|
||||
import ExperimentalSettings from './screens/settings/node/ExperimentalSettings';
|
||||
import LibrarySettings from './screens/settings/node/LibrariesSettings';
|
||||
import NodesSettings from './screens/settings/node/NodesSettings';
|
||||
import P2PSettings from './screens/settings/node/P2PSettings';
|
||||
|
||||
export function AppRouter() {
|
||||
|
@ -57,28 +58,27 @@ export function AppRouter() {
|
|||
<Route path="content" element={<ContentScreen />} />
|
||||
<Route path="photos" element={<PhotosScreen />} />
|
||||
<Route path="debug" element={<DebugScreen />} />
|
||||
<Route path={'library-settings'} element={<CurrentLibrarySettings />}>
|
||||
<Route index element={<LocationSettings />} />
|
||||
<Route path="general" element={<LibraryGeneralSettings />} />
|
||||
<Route path="locations" element={<LocationSettings />} />
|
||||
<Route path="tags" element={<TagsSettings />} />
|
||||
<Route path="keys" element={<KeysSettings />} />
|
||||
</Route>
|
||||
<Route path={'settings'} element={<SettingsScreen />}>
|
||||
<Route index element={<GeneralSettings />} />
|
||||
<Route path="general" element={<GeneralSettings />} />
|
||||
<Route path="appearance" element={<AppearanceSettings />} />
|
||||
<Route path="nodes" element={<NodesSettings />} />
|
||||
<Route path="keybinds" element={<KeybindSettings />} />
|
||||
<Route path="extensions" element={<ExtensionSettings />} />
|
||||
<Route path="p2p" element={<P2PSettings />} />
|
||||
<Route path="contacts" element={<ContactsSettings />} />
|
||||
<Route path="experimental" element={<ExperimentalSettings />} />
|
||||
<Route path="keys" element={<KeysSettings />} />
|
||||
<Route path="library" element={<LibrarySettings />} />
|
||||
<Route path="libraries" element={<LibrarySettings />} />
|
||||
<Route path="security" element={<SecuritySettings />} />
|
||||
<Route path="locations" element={<LocationSettings />} />
|
||||
<Route path="sharing" element={<SharingSettings />} />
|
||||
<Route path="sync" element={<SyncSettings />} />
|
||||
<Route path="tags" element={<TagsSettings />} />
|
||||
<Route path="library" element={<LibraryGeneralSettings />} />
|
||||
<Route path="locations" element={<LocationSettings />} />
|
||||
<Route path="tags" element={<TagsSettings />} />
|
||||
<Route path="nodes" element={<NodesSettings />} />
|
||||
<Route path="keys" element={<KeysSettings />} />
|
||||
</Route>
|
||||
<Route path="explorer/:id" element={<ExplorerScreen />} />
|
||||
<Route path="tag/:id" element={<TagScreen />} />
|
||||
|
|
|
@ -72,12 +72,11 @@ export function Device(props: DeviceProps) {
|
|||
key={key}
|
||||
selected={selectedFile === location.name}
|
||||
onClick={() => handleSelect(location.name)}
|
||||
fileName={location.name}
|
||||
folder={location.folder}
|
||||
format={location.format}
|
||||
iconName={location.icon}
|
||||
/>
|
||||
))}
|
||||
{props.locations.length === 0 && (
|
||||
<div className="w-full my-5 text-center text-gray-450">No locations</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,151 +1,109 @@
|
|||
import { LocationContext, useExplorerStore } from '@sd/client';
|
||||
import { File, FilePath } from '@sd/core';
|
||||
import clsx from 'clsx';
|
||||
import { FilePlus, FileText, Plus, Share, Trash } from 'phosphor-react';
|
||||
import React, { MouseEventHandler } from 'react';
|
||||
import React, { MouseEventHandler, useContext } from 'react';
|
||||
|
||||
import icons from '../../assets/icons';
|
||||
import { ReactComponent as Folder } from '../../assets/svg/folder.svg';
|
||||
import { WithContextMenu } from '../layout/MenuOverlay';
|
||||
import { DefaultProps } from '../primitive/types';
|
||||
import FileThumb from './FileThumb';
|
||||
|
||||
interface Props extends DefaultProps {
|
||||
fileName: string;
|
||||
iconName?: string;
|
||||
format?: string;
|
||||
folder?: boolean;
|
||||
interface Props extends React.HTMLAttributes<HTMLDivElement> {
|
||||
file?: FilePath | null;
|
||||
selected?: boolean;
|
||||
onClick?: MouseEventHandler<HTMLDivElement>;
|
||||
}
|
||||
|
||||
export default function FileItem(props: Props) {
|
||||
// const Shadow = () => {
|
||||
// return (
|
||||
// <div
|
||||
// className={clsx(
|
||||
// 'absolute opacity-100 transition-opacity duration-200 top-auto bottom-auto w-[64px] h-[40px] shadow-xl shadow-red-500',
|
||||
// { 'opacity-100': props.selected }
|
||||
// )}
|
||||
// />
|
||||
// );
|
||||
// };
|
||||
const location = useContext(LocationContext);
|
||||
|
||||
return (
|
||||
<WithContextMenu
|
||||
menu={[
|
||||
[
|
||||
<div {...props} className={clsx('inline-block w-[100px] mb-3', props.className)} draggable>
|
||||
<div
|
||||
className={clsx(
|
||||
'border-2 border-transparent rounded-lg text-center w-[100px] h-[100px] mb-1',
|
||||
{
|
||||
label: 'Details',
|
||||
icon: FileText,
|
||||
onClick() {}
|
||||
},
|
||||
{
|
||||
label: 'Share',
|
||||
icon: Share,
|
||||
onClick() {
|
||||
navigator.share?.({
|
||||
title: 'Spacedrive',
|
||||
text: 'Check out this cool app',
|
||||
url: 'https://spacedrive.com'
|
||||
});
|
||||
}
|
||||
'bg-gray-50 dark:bg-gray-650': props.selected
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
label: 'More actions...',
|
||||
icon: Plus,
|
||||
onClick() {},
|
||||
children: [
|
||||
[
|
||||
{
|
||||
label: 'Move to library',
|
||||
icon: FilePlus,
|
||||
onClick() {}
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
label: 'Delete',
|
||||
icon: Trash,
|
||||
danger: true,
|
||||
onClick() {}
|
||||
}
|
||||
]
|
||||
]}
|
||||
>
|
||||
<div onClick={props.onClick} className="inline-block w-[100px] mb-3" draggable>
|
||||
<div
|
||||
className={clsx(
|
||||
'border-2 border-transparent rounded-lg text-center w-[100px] h-[100px] mb-1',
|
||||
{
|
||||
'bg-gray-50 dark:bg-gray-650': props.selected
|
||||
}
|
||||
)}
|
||||
>
|
||||
{props.folder ? (
|
||||
<div className="flex items-center justify-center w-full h-full active:translate-y-[1px]">
|
||||
<div className="w-[70px]">
|
||||
<Folder className="" />
|
||||
</div>
|
||||
)}
|
||||
>
|
||||
{props.file?.is_dir ? (
|
||||
<div className="flex items-center justify-center w-full h-full active:translate-y-[1px]">
|
||||
<div className="w-[70px]">
|
||||
<Folder className="" />
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className={clsx(
|
||||
'w-[64px] mt-1.5 m-auto transition duration-200 rounded-lg h-[90px] relative active:translate-y-[1px]',
|
||||
{
|
||||
'': props.selected
|
||||
}
|
||||
)}
|
||||
>
|
||||
<svg
|
||||
className="absolute top-0 left-0 pointer-events-none fill-gray-150 dark:fill-gray-550"
|
||||
width="65"
|
||||
height="85"
|
||||
viewBox="0 0 65 81"
|
||||
>
|
||||
<path d="M0 8C0 3.58172 3.58172 0 8 0H39.6863C41.808 0 43.8429 0.842855 45.3431 2.34315L53.5 10.5L62.6569 19.6569C64.1571 21.1571 65 23.192 65 25.3137V73C65 77.4183 61.4183 81 57 81H8C3.58172 81 0 77.4183 0 73V8Z" />
|
||||
</svg>
|
||||
<svg
|
||||
width="22"
|
||||
height="22"
|
||||
className="absolute top-1 -right-[1px] z-10 fill-gray-50 dark:fill-gray-500 pointer-events-none"
|
||||
viewBox="0 0 41 41"
|
||||
>
|
||||
<path d="M41.4116 40.5577H11.234C5.02962 40.5577 0 35.5281 0 29.3238V0L41.4116 40.5577Z" />
|
||||
</svg>
|
||||
<div className="absolute flex flex-col items-center justify-center w-full h-full">
|
||||
{props.iconName && icons[props.iconName as keyof typeof icons] ? (
|
||||
(() => {
|
||||
const Icon = icons[props.iconName as keyof typeof icons];
|
||||
return (
|
||||
<Icon className="mt-2 pointer-events-none margin-auto w-[40px] h-[40px]" />
|
||||
);
|
||||
})()
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<span className="mt-1 text-xs font-bold text-center uppercase cursor-default text-gray-450">
|
||||
{props.format}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<span
|
||||
</div>
|
||||
) : props.file?.file?.has_thumbnail ? (
|
||||
<div
|
||||
className={clsx(
|
||||
'px-1.5 py-[1px] rounded-md text-sm font-medium text-gray-550 dark:text-gray-300 cursor-default',
|
||||
'flex items-center justify-center h-full p-1 overflow-hidden rounded border-gray-550 shrink-0',
|
||||
props.selected && 'border-primary'
|
||||
)}
|
||||
>
|
||||
<div className="border-4 rounded border-gray-550">
|
||||
<FileThumb
|
||||
className="rounded-sm"
|
||||
file={props.file}
|
||||
locationId={location.location_id}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className={clsx(
|
||||
'w-[64px] mt-1.5 m-auto transition duration-200 rounded-lg h-[90px] relative active:translate-y-[1px]',
|
||||
{
|
||||
'bg-primary !text-white': props.selected
|
||||
'': props.selected
|
||||
}
|
||||
)}
|
||||
>
|
||||
{props.fileName}
|
||||
</span>
|
||||
</div>
|
||||
<svg
|
||||
className="absolute top-0 left-0 pointer-events-none fill-gray-150 dark:fill-gray-550"
|
||||
width="65"
|
||||
height="85"
|
||||
viewBox="0 0 65 81"
|
||||
>
|
||||
<path d="M0 8C0 3.58172 3.58172 0 8 0H39.6863C41.808 0 43.8429 0.842855 45.3431 2.34315L53.5 10.5L62.6569 19.6569C64.1571 21.1571 65 23.192 65 25.3137V73C65 77.4183 61.4183 81 57 81H8C3.58172 81 0 77.4183 0 73V8Z" />
|
||||
</svg>
|
||||
<svg
|
||||
width="22"
|
||||
height="22"
|
||||
className="absolute top-1 -right-[1px] z-0 fill-gray-50 dark:fill-gray-500 pointer-events-none"
|
||||
viewBox="0 0 41 41"
|
||||
>
|
||||
<path d="M41.4116 40.5577H11.234C5.02962 40.5577 0 35.5281 0 29.3238V0L41.4116 40.5577Z" />
|
||||
</svg>
|
||||
<div className="absolute flex flex-col items-center justify-center w-full h-full">
|
||||
{props.file?.extension && icons[props.file.extension as keyof typeof icons] ? (
|
||||
(() => {
|
||||
const Icon = icons[props.file.extension as keyof typeof icons];
|
||||
return (
|
||||
<Icon className="mt-2 pointer-events-none margin-auto w-[40px] h-[40px]" />
|
||||
);
|
||||
})()
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
<span className="mt-1 text-xs font-bold text-center uppercase cursor-default text-gray-450">
|
||||
{props.file?.extension}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</WithContextMenu>
|
||||
<div className="flex justify-center">
|
||||
<span
|
||||
className={clsx(
|
||||
'px-1.5 py-[1px] truncate text-center rounded-md text-xs font-medium text-gray-550 dark:text-gray-300 cursor-default',
|
||||
{
|
||||
'bg-primary !text-white': props.selected
|
||||
}
|
||||
)}
|
||||
>
|
||||
{props.file?.name}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
import { DotsVerticalIcon } from '@heroicons/react/solid';
|
||||
import { useBridgeQuery, useLibraryQuery } from '@sd/client';
|
||||
import { LocationContext, useBridgeQuery, useLibraryQuery } from '@sd/client';
|
||||
import { useExplorerStore } from '@sd/client';
|
||||
import { AppPropsContext } from '@sd/client';
|
||||
import { FilePath } from '@sd/core';
|
||||
import clsx from 'clsx';
|
||||
import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useSearchParams } from 'react-router-dom';
|
||||
import { Virtuoso, VirtuosoHandle } from 'react-virtuoso';
|
||||
import { Virtuoso, VirtuosoGrid, VirtuosoHandle } from 'react-virtuoso';
|
||||
import { useKey, useWindowSize } from 'rooks';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import FileItem from './FileItem';
|
||||
import FileThumb from './FileThumb';
|
||||
|
||||
interface IColumn {
|
||||
|
@ -32,13 +34,18 @@ const columns = ensureIsColumns([
|
|||
|
||||
type ColumnKey = typeof columns[number]['key'];
|
||||
|
||||
const LocationContext = React.createContext<{
|
||||
location_id: number;
|
||||
data_path: string;
|
||||
}>({
|
||||
location_id: 1,
|
||||
data_path: ''
|
||||
});
|
||||
// these styled components are out of place, but are here to follow the virtuoso docs. could probably be translated to tailwind somehow, since the `components` prop only accepts a styled div, not a react component.
|
||||
const GridContainer = styled.div`
|
||||
display: flex;
|
||||
margin-top: 60px;
|
||||
margin-left: 10px;
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
`;
|
||||
const GridItemContainer = styled.div`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
`;
|
||||
|
||||
export const FileList: React.FC<{ location_id: number; path: string; limit: number }> = (props) => {
|
||||
const size = useWindowSize();
|
||||
|
@ -51,7 +58,7 @@ export const FileList: React.FC<{ location_id: number; path: string; limit: numb
|
|||
|
||||
const path = props.path;
|
||||
|
||||
const { selectedRowIndex, setSelectedRowIndex, setLocationId } = useExplorerStore();
|
||||
const { selectedRowIndex, setSelectedRowIndex, setLocationId, layoutMode } = useExplorerStore();
|
||||
const [goingUp, setGoingUp] = useState(false);
|
||||
|
||||
const { data: currentDir } = useLibraryQuery('LibGetExplorerDir', {
|
||||
|
@ -64,7 +71,7 @@ export const FileList: React.FC<{ location_id: number; path: string; limit: numb
|
|||
if (selectedRowIndex === 0 && goingUp) {
|
||||
VList.current?.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
if (selectedRowIndex != -1) {
|
||||
if (selectedRowIndex != -1 && typeof VList.current?.scrollIntoView === 'function') {
|
||||
VList.current?.scrollIntoView({
|
||||
index: goingUp ? selectedRowIndex - 1 : selectedRowIndex
|
||||
});
|
||||
|
@ -88,12 +95,12 @@ export const FileList: React.FC<{ location_id: number; path: string; limit: numb
|
|||
setSelectedRowIndex(selectedRowIndex + 1);
|
||||
});
|
||||
|
||||
const Row = (index: number) => {
|
||||
const row = currentDir?.contents?.[index];
|
||||
|
||||
if (!row) return null;
|
||||
|
||||
return <RenderRow key={index} row={row} rowIndex={index} dirId={currentDir?.directory.id} />;
|
||||
const createRenderItem = (RenderItem: React.FC<RenderItemProps>) => {
|
||||
return (index: number) => {
|
||||
const row = currentDir?.contents?.[index];
|
||||
if (!row) return null;
|
||||
return <RenderItem key={index} index={index} item={row} dirId={currentDir?.directory.id} />;
|
||||
};
|
||||
};
|
||||
|
||||
const Header = () => (
|
||||
|
@ -116,46 +123,90 @@ export const FileList: React.FC<{ location_id: number; path: string; limit: numb
|
|||
</div>
|
||||
);
|
||||
|
||||
return useMemo(
|
||||
() => (
|
||||
<div
|
||||
ref={tableContainer}
|
||||
style={{ marginTop: -44 }}
|
||||
className="w-full pl-2 bg-white cursor-default dark:bg-gray-650"
|
||||
return (
|
||||
<div
|
||||
ref={tableContainer}
|
||||
style={{ marginTop: -44 }}
|
||||
className="w-full pl-2 bg-white cursor-default dark:bg-gray-600"
|
||||
>
|
||||
<LocationContext.Provider
|
||||
value={{ location_id: props.location_id, data_path: client?.data_path as string }}
|
||||
>
|
||||
<LocationContext.Provider
|
||||
value={{ location_id: props.location_id, data_path: client?.data_path as string }}
|
||||
>
|
||||
{layoutMode === 'grid' && (
|
||||
<VirtuosoGrid
|
||||
ref={VList}
|
||||
overscan={5000}
|
||||
components={{
|
||||
Item: GridItemContainer,
|
||||
List: GridContainer
|
||||
}}
|
||||
style={{ height: size.innerHeight ?? 600 }}
|
||||
totalCount={currentDir?.contents.length || 0}
|
||||
itemContent={createRenderItem(RenderGridItem)}
|
||||
className="w-full overflow-x-hidden outline-none explorer-scroll"
|
||||
/>
|
||||
)}
|
||||
{layoutMode === 'list' && (
|
||||
<Virtuoso
|
||||
data={currentDir?.contents}
|
||||
data={currentDir?.contents} // this might be redundant, row data is retrieved by index in renderRow
|
||||
ref={VList}
|
||||
style={{ height: size.innerHeight ?? 600 }}
|
||||
totalCount={currentDir?.contents.length || 0}
|
||||
itemContent={Row}
|
||||
components={{ Header, Footer: () => <div className="w-full " /> }}
|
||||
itemContent={createRenderItem(RenderRow)}
|
||||
components={{
|
||||
Header,
|
||||
Footer: () => <div className="w-full " />
|
||||
}}
|
||||
increaseViewportBy={{ top: 400, bottom: 200 }}
|
||||
className="outline-none explorer-scroll"
|
||||
/>
|
||||
</LocationContext.Provider>
|
||||
</div>
|
||||
),
|
||||
[props.location_id, size.innerWidth, currentDir?.directory.id, tableContainer.current]
|
||||
)}
|
||||
</LocationContext.Provider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const RenderRow: React.FC<{
|
||||
row: FilePath;
|
||||
rowIndex: number;
|
||||
interface RenderItemProps {
|
||||
item: FilePath;
|
||||
index: number;
|
||||
dirId: number;
|
||||
}> = ({ row, rowIndex, dirId }) => {
|
||||
}
|
||||
|
||||
const RenderGridItem: React.FC<RenderItemProps> = ({ item, index, dirId }) => {
|
||||
// return <div className="flex m-2 bg-red-600 w-44 h-44"></div>;
|
||||
const { selectedRowIndex, setSelectedRowIndex } = useExplorerStore();
|
||||
const isActive = selectedRowIndex === rowIndex;
|
||||
const isActive = selectedRowIndex === index;
|
||||
|
||||
let [_, setSearchParams] = useSearchParams();
|
||||
|
||||
function selectFileHandler() {
|
||||
if (selectedRowIndex == rowIndex) setSelectedRowIndex(-1);
|
||||
else setSelectedRowIndex(rowIndex);
|
||||
if (selectedRowIndex == index) setSelectedRowIndex(-1);
|
||||
else setSelectedRowIndex(index);
|
||||
}
|
||||
|
||||
return (
|
||||
<FileItem
|
||||
onDoubleClick={() => {
|
||||
if (item.is_dir) {
|
||||
setSearchParams({ path: item.materialized_path });
|
||||
}
|
||||
}}
|
||||
file={item}
|
||||
selected={isActive}
|
||||
onClick={selectFileHandler}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const RenderRow: React.FC<RenderItemProps> = ({ item, index, dirId }) => {
|
||||
const { selectedRowIndex, setSelectedRowIndex } = useExplorerStore();
|
||||
const isActive = selectedRowIndex === index;
|
||||
|
||||
let [_, setSearchParams] = useSearchParams();
|
||||
|
||||
function selectFileHandler() {
|
||||
if (selectedRowIndex == index) setSelectedRowIndex(-1);
|
||||
else setSelectedRowIndex(index);
|
||||
}
|
||||
|
||||
return useMemo(
|
||||
|
@ -163,14 +214,14 @@ const RenderRow: React.FC<{
|
|||
<div
|
||||
onClick={selectFileHandler}
|
||||
onDoubleClick={() => {
|
||||
if (row.is_dir) {
|
||||
setSearchParams({ path: row.materialized_path });
|
||||
if (item.is_dir) {
|
||||
setSearchParams({ path: item.materialized_path });
|
||||
}
|
||||
}}
|
||||
className={clsx(
|
||||
'table-body-row mr-2 flex flex-row rounded-lg border-2',
|
||||
isActive ? 'border-primary-500' : 'border-transparent',
|
||||
rowIndex % 2 == 0 && 'bg-[#00000006] dark:bg-[#00000030]'
|
||||
index % 2 == 0 && 'bg-[#00000006] dark:bg-[#00000030]'
|
||||
)}
|
||||
>
|
||||
{columns.map((col) => (
|
||||
|
@ -179,12 +230,12 @@ const RenderRow: React.FC<{
|
|||
className="flex items-center px-4 py-2 pr-2 table-body-cell"
|
||||
style={{ width: col.width }}
|
||||
>
|
||||
<RenderCell file={row} dirId={dirId} colKey={col?.key} />
|
||||
<RenderCell file={item} dirId={dirId} colKey={col?.key} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
[row.id, isActive]
|
||||
[item.id, isActive]
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -193,29 +244,22 @@ const RenderCell: React.FC<{
|
|||
dirId?: number;
|
||||
file?: FilePath;
|
||||
}> = ({ colKey, file, dirId }) => {
|
||||
const location = useContext(LocationContext);
|
||||
|
||||
if (!file || !colKey || !dirId) return <></>;
|
||||
|
||||
const row = file;
|
||||
if (!row) return <></>;
|
||||
const appProps = useContext(AppPropsContext);
|
||||
|
||||
const value = row[colKey];
|
||||
if (!value) return <></>;
|
||||
|
||||
const location = useContext(LocationContext);
|
||||
const { newThumbnails } = useExplorerStore();
|
||||
|
||||
const hasNewThumbnail = !!newThumbnails[row?.file?.cas_id ?? ''];
|
||||
|
||||
switch (colKey) {
|
||||
case 'name':
|
||||
return (
|
||||
<div className="flex flex-row items-center overflow-hidden">
|
||||
<div className="flex items-center justify-center w-6 h-6 mr-3 shrink-0">
|
||||
<FileThumb
|
||||
hasThumbnailOverride={hasNewThumbnail}
|
||||
file={row}
|
||||
locationId={location.location_id}
|
||||
/>
|
||||
<FileThumb file={row} locationId={location.location_id} />
|
||||
</div>
|
||||
{/* {colKey == 'name' &&
|
||||
(() => {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useBridgeQuery } from '@sd/client';
|
||||
import { useBridgeQuery, useExplorerStore } from '@sd/client';
|
||||
import { AppPropsContext } from '@sd/client';
|
||||
import { FilePath } from '@sd/core';
|
||||
import clsx from 'clsx';
|
||||
|
@ -10,22 +10,23 @@ import { Folder } from '../icons/Folder';
|
|||
export default function FileThumb(props: {
|
||||
file: FilePath;
|
||||
locationId: number;
|
||||
hasThumbnailOverride: boolean;
|
||||
className?: string;
|
||||
}) {
|
||||
const appProps = useContext(AppPropsContext);
|
||||
const { data: client } = useBridgeQuery('NodeGetState');
|
||||
const { newThumbnails } = useExplorerStore();
|
||||
|
||||
const hasNewThumbnail = !!newThumbnails[props?.file?.file?.cas_id ?? ''];
|
||||
|
||||
if (props.file.is_dir) {
|
||||
return <Folder size={100} />;
|
||||
}
|
||||
|
||||
if (client?.data_path && (props.file.file?.has_thumbnail || props.hasThumbnailOverride)) {
|
||||
if (appProps?.data_path && (props.file.file?.has_thumbnail || hasNewThumbnail)) {
|
||||
return (
|
||||
<img
|
||||
className="pointer-events-none z-90"
|
||||
className={clsx('pointer-events-none z-90', props.className)}
|
||||
src={appProps?.convertFileSrc(
|
||||
`${client.data_path}/thumbnails/${props.locationId}/${props.file.file?.cas_id}.webp`
|
||||
`${appProps.data_path}/thumbnails/${props.locationId}/${props.file.file?.cas_id}.webp`
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { Transition } from '@headlessui/react';
|
||||
import { ShareIcon } from '@heroicons/react/solid';
|
||||
import { useInspectorStore } from '@sd/client';
|
||||
import { useInspectorStore, useLibraryCommand } from '@sd/client';
|
||||
import { FilePath, LocationResource } from '@sd/core';
|
||||
import { Button, TextArea } from '@sd/ui';
|
||||
import moment from 'moment';
|
||||
import { Heart, Link } from 'phosphor-react';
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { default as types } from '../../constants/file-types.json';
|
||||
import FileThumb from './FileThumb';
|
||||
|
@ -44,6 +44,26 @@ export const Inspector = (props: {
|
|||
// when quickly navigating files, which cancels update function
|
||||
const { notes, setNote, unCacheNote } = useInspectorStore();
|
||||
|
||||
const [favorite, setFavorite] = useState(false);
|
||||
|
||||
const { mutate: fileToggleFavorite, isLoading: isFavoriteLoading } = useLibraryCommand(
|
||||
'FileSetFavorite',
|
||||
{
|
||||
onError: () => setFavorite(!!props.selectedFile?.file?.favorite)
|
||||
}
|
||||
);
|
||||
|
||||
const toggleFavorite = () => {
|
||||
if (!isFavoriteLoading) {
|
||||
fileToggleFavorite({ id: file_id, favorite: !favorite });
|
||||
setFavorite(!favorite);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setFavorite(!!props.selectedFile?.file?.favorite);
|
||||
}, [props.selectedFile]);
|
||||
|
||||
// show cached note over server note, important to check for undefined not falsey
|
||||
const note =
|
||||
notes[file_id] === undefined ? props.selectedFile?.file?.note || null : notes[file_id];
|
||||
|
@ -63,95 +83,80 @@ export const Inspector = (props: {
|
|||
}, [note]);
|
||||
|
||||
return (
|
||||
// <Transition
|
||||
// as={React.Fragment}
|
||||
// show={true}
|
||||
// enter="transition-translate ease-in-out duration-200"
|
||||
// enterFrom="translate-x-64"
|
||||
// enterTo="translate-x-0"
|
||||
// leave="transition-translate ease-in-out duration-200"
|
||||
// leaveFrom="translate-x-0"
|
||||
// leaveTo="translate-x-64"
|
||||
// >
|
||||
<div className="flex p-2 pr-1 mr-1 pb-[51px] w-[330px] flex-wrap overflow-x-hidden custom-scroll inspector-scroll">
|
||||
<div className="p-2 pr-1 w-[330px] overflow-x-hidden custom-scroll inspector-scroll pb-[55px]">
|
||||
{!!file_path && (
|
||||
<div className="flex flex-col w-full pb-2 overflow-hidden bg-white rounded-lg select-text dark:bg-gray-600 bg-opacity-70">
|
||||
<div className="flex items-center justify-center w-full h-64 overflow-hidden rounded-t-lg bg-gray-50 dark:bg-gray-900">
|
||||
<div>
|
||||
<div className="flex items-center justify-center w-full h-64 mb-[10px] overflow-hidden rounded-lg bg-gray-50 dark:bg-gray-900">
|
||||
<FileThumb
|
||||
hasThumbnailOverride={false}
|
||||
className="!m-0 flex flex-shrink flex-grow-0"
|
||||
file={file_path}
|
||||
locationId={props.locationId}
|
||||
/>
|
||||
</div>
|
||||
<h3 className="pt-3 pl-3 text-base font-bold">{file_path?.name}</h3>
|
||||
<div className="flex flex-row m-3 space-x-2">
|
||||
<Button size="sm" noPadding>
|
||||
<Heart className="w-[18px] h-[18px]" />
|
||||
</Button>
|
||||
<Button size="sm" noPadding>
|
||||
<ShareIcon className="w-[18px] h-[18px]" />
|
||||
</Button>
|
||||
<Button size="sm" noPadding>
|
||||
<Link className="w-[18px] h-[18px]" />
|
||||
</Button>
|
||||
</div>
|
||||
{file_path?.file?.cas_id && (
|
||||
<MetaItem title="Unique Content ID" value={file_path.file.cas_id as string} />
|
||||
)}
|
||||
<Divider />
|
||||
<MetaItem title="URI" value={full_path} />
|
||||
<Divider />
|
||||
<MetaItem
|
||||
title="Date Created"
|
||||
value={moment(file_path?.date_created).format('MMMM Do YYYY, h:mm:ss a')}
|
||||
/>
|
||||
<Divider />
|
||||
<MetaItem
|
||||
title="Date Indexed"
|
||||
value={moment(file_path?.date_indexed).format('MMMM Do YYYY, h:mm:ss a')}
|
||||
/>
|
||||
{!file_path?.is_dir && (
|
||||
<>
|
||||
<Divider />
|
||||
<div className="flex flex-row items-center px-3 py-2 meta-item">
|
||||
{file_path?.extension && (
|
||||
<span className="inline px-1 mr-1 text-xs font-bold uppercase bg-gray-500 rounded-md text-gray-150">
|
||||
{file_path?.extension}
|
||||
</span>
|
||||
<div className="flex flex-col w-full pb-2 overflow-hidden bg-white rounded-lg select-text dark:bg-gray-550 dark:bg-opacity-40">
|
||||
<h3 className="pt-3 pl-3 text-base font-bold">{file_path?.name}</h3>
|
||||
<div className="flex flex-row m-3 space-x-2">
|
||||
<Button onClick={toggleFavorite} size="sm" noPadding>
|
||||
<Heart weight={favorite ? 'fill' : 'regular'} className="w-[18px] h-[18px]" />
|
||||
</Button>
|
||||
<Button size="sm" noPadding>
|
||||
<ShareIcon className="w-[18px] h-[18px]" />
|
||||
</Button>
|
||||
<Button size="sm" noPadding>
|
||||
<Link className="w-[18px] h-[18px]" />
|
||||
</Button>
|
||||
</div>
|
||||
{file_path?.file?.cas_id && (
|
||||
<MetaItem title="Unique Content ID" value={file_path.file.cas_id as string} />
|
||||
)}
|
||||
<Divider />
|
||||
<MetaItem title="URI" value={full_path} />
|
||||
<Divider />
|
||||
<MetaItem
|
||||
title="Date Created"
|
||||
value={moment(file_path?.date_created).format('MMMM Do YYYY, h:mm:ss a')}
|
||||
/>
|
||||
<Divider />
|
||||
<MetaItem
|
||||
title="Date Indexed"
|
||||
value={moment(file_path?.date_indexed).format('MMMM Do YYYY, h:mm:ss a')}
|
||||
/>
|
||||
{!file_path?.is_dir && (
|
||||
<>
|
||||
<Divider />
|
||||
<div className="flex flex-row items-center px-3 py-2 meta-item">
|
||||
{file_path?.extension && (
|
||||
<span className="inline px-1 mr-1 text-xs font-bold uppercase bg-gray-500 rounded-md text-gray-150">
|
||||
{file_path?.extension}
|
||||
</span>
|
||||
)}
|
||||
<p className="text-xs text-gray-600 break-all truncate dark:text-gray-300">
|
||||
{file_path?.extension
|
||||
? //@ts-ignore
|
||||
types[file_path.extension.toUpperCase()]?.descriptions.join(' / ')
|
||||
: 'Unknown'}
|
||||
</p>
|
||||
</div>
|
||||
{file_path.file && (
|
||||
<>
|
||||
<Divider />
|
||||
<MetaItem
|
||||
title="Note"
|
||||
value={
|
||||
<TextArea
|
||||
className="mt-2 text-xs leading-snug !py-2"
|
||||
value={note || ''}
|
||||
onChange={handleNoteUpdate}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<p className="text-xs text-gray-600 break-all truncate dark:text-gray-300">
|
||||
{file_path?.extension
|
||||
? //@ts-ignore
|
||||
types[file_path.extension.toUpperCase()]?.descriptions.join(' / ')
|
||||
: 'Unknown'}
|
||||
</p>
|
||||
</div>
|
||||
{file_path.file && (
|
||||
<>
|
||||
<Divider />
|
||||
<MetaItem
|
||||
title="Note"
|
||||
value={
|
||||
<TextArea
|
||||
className="mt-2 text-xs leading-snug !py-2"
|
||||
value={note || ''}
|
||||
onChange={handleNoteUpdate}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{/* <div className="flex flex-row m-3">
|
||||
<Button size="sm">Mint</Button>
|
||||
</div> */}
|
||||
{/* <MetaItem title="Date Last Modified" value={file?.date_modified} />
|
||||
<MetaItem title="Date Indexed" value={file?.date_indexed} /> */}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
// </Transition>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -86,13 +86,7 @@ export const Sidebar: React.FC<SidebarProps> = (props) => {
|
|||
let locations = Array.isArray(locationsResponse) ? locationsResponse : [];
|
||||
|
||||
// initialize libraries
|
||||
const { init: initLibraries, switchLibrary: _switchLibrary } = useLibraryStore();
|
||||
|
||||
const switchLibrary = (uuid: string) => {
|
||||
navigate('overview');
|
||||
|
||||
_switchLibrary(uuid);
|
||||
};
|
||||
const { init: initLibraries, switchLibrary } = useLibraryStore();
|
||||
|
||||
const { currentLibrary, libraries, currentLibraryUuid } = useCurrentLibrary();
|
||||
|
||||
|
@ -163,7 +157,7 @@ export const Sidebar: React.FC<SidebarProps> = (props) => {
|
|||
{
|
||||
name: 'Library Settings',
|
||||
icon: CogIcon,
|
||||
onPress: () => navigate('library-settings/general')
|
||||
onPress: () => navigate('settings/library')
|
||||
},
|
||||
{
|
||||
name: 'Add Library',
|
||||
|
@ -192,7 +186,7 @@ export const Sidebar: React.FC<SidebarProps> = (props) => {
|
|||
</SidebarLink>
|
||||
<SidebarLink to="content">
|
||||
<Icon component={CirclesFour} />
|
||||
Content
|
||||
Spaces
|
||||
</SidebarLink>
|
||||
<SidebarLink to="photos">
|
||||
<Icon component={PhotographIcon} />
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/outline';
|
||||
import { useLibraryCommand } from '@sd/client';
|
||||
import { useExplorerStore } from '@sd/client';
|
||||
import { AppPropsContext, useExplorerStore, useLibraryCommand } from '@sd/client';
|
||||
import { Dropdown } from '@sd/ui';
|
||||
import clsx from 'clsx';
|
||||
import {
|
||||
|
@ -10,12 +9,13 @@ import {
|
|||
IconProps,
|
||||
Key,
|
||||
List,
|
||||
Rows,
|
||||
SquaresFour,
|
||||
Tag,
|
||||
TerminalWindow
|
||||
} from 'phosphor-react';
|
||||
import React, { DetailedHTMLProps, HTMLAttributes, useContext } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { AppPropsContext } from '../../AppPropsContext';
|
||||
|
||||
import { Shortcut } from '../primitive/Shortcut';
|
||||
import { DefaultProps } from '../primitive/types';
|
||||
|
@ -36,12 +36,12 @@ const TopBarButton: React.FC<TopBarButtonProps> = ({ icon: Icon, ...props }) =>
|
|||
<button
|
||||
{...props}
|
||||
className={clsx(
|
||||
'mr-[1px] py-0.5 px-0.5 text-md font-medium hover:bg-gray-150 dark:transparent dark:hover:bg-gray-550 dark:active:bg-gray-500 rounded-md transition-colors duration-100',
|
||||
'mr-[1px] py-0.5 px-0.5 text-md font-medium hover:bg-gray-150 dark:transparent dark:hover:bg-gray-550 rounded-md transition-colors duration-100',
|
||||
{
|
||||
'rounded-r-none rounded-l-none': props.group && !props.left && !props.right,
|
||||
'rounded-r-none': props.group && props.left,
|
||||
'rounded-l-none': props.group && props.right,
|
||||
'dark:bg-gray-450 dark:hover:bg-gray-450 dark:active:bg-gray-450': props.active
|
||||
'dark:bg-gray-550': props.active
|
||||
},
|
||||
props.className
|
||||
)}
|
||||
|
@ -51,25 +51,30 @@ const TopBarButton: React.FC<TopBarButtonProps> = ({ icon: Icon, ...props }) =>
|
|||
);
|
||||
};
|
||||
|
||||
const SearchBar: React.FC<SearchBarProps> = (props) => { //TODO: maybe pass the appProps, so we can have the context in the TopBar if needed again
|
||||
const SearchBar: React.FC<SearchBarProps> = (props) => {
|
||||
//TODO: maybe pass the appProps, so we can have the context in the TopBar if needed again
|
||||
const appProps = useContext(AppPropsContext);
|
||||
|
||||
return (
|
||||
return (
|
||||
<div className="relative flex h-7">
|
||||
<input
|
||||
placeholder="Search"
|
||||
className="w-32 h-[30px] focus:w-52 text-sm p-3 rounded-lg outline-none focus:ring-2 placeholder-gray-400 dark:placeholder-gray-500 bg-[#F6F2F6] border border-gray-50 dark:bg-gray-650 dark:border-gray-550 focus:ring-gray-100 dark:focus:ring-gray-600 transition-all"
|
||||
className="w-32 h-[30px] focus:w-52 text-sm p-3 rounded-lg outline-none focus:ring-2 placeholder-gray-400 dark:placeholder-gray-450 bg-[#F6F2F6] border border-gray-50 shadow-md dark:bg-gray-600 dark:border-gray-550 focus:ring-gray-100 dark:focus:ring-gray-550 dark:focus:bg-gray-800 transition-all"
|
||||
/>
|
||||
<div className="space-x-1 absolute top-[2px] right-1">
|
||||
<Shortcut chars={ appProps?.platform === "macOS" || appProps?.platform === "browser" ? "⌘K" : "CTRL+K" } />
|
||||
<Shortcut
|
||||
chars={
|
||||
appProps?.platform === 'macOS' || appProps?.platform === 'browser' ? '⌘K' : 'CTRL+K'
|
||||
}
|
||||
/>
|
||||
{/* <Shortcut chars="S" /> */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const TopBar: React.FC<TopBarProps> = (props) => {
|
||||
const { locationId } = useExplorerStore();
|
||||
const { locationId, layoutMode, setLayoutMode } = useExplorerStore();
|
||||
const { mutate: generateThumbsForLocation } = useLibraryCommand('GenerateThumbsForLocation', {
|
||||
onMutate: (data) => {
|
||||
console.log('GenerateThumbsForLocation', data);
|
||||
|
@ -90,27 +95,40 @@ export const TopBar: React.FC<TopBarProps> = (props) => {
|
|||
<>
|
||||
<div
|
||||
data-tauri-drag-region
|
||||
className="flex h-[2.95rem] -mt-0.5 max-w z-10 pl-3 flex-shrink-0 items-center border-b dark:bg-gray-600 border-gray-100 dark:border-gray-800 !bg-opacity-60 backdrop-blur"
|
||||
className="flex h-[2.95rem] -mt-0.5 max-w z-10 pl-3 flex-shrink-0 items-center border-b dark:bg-gray-600 border-gray-100 dark:border-gray-800 !bg-opacity-90 backdrop-blur"
|
||||
>
|
||||
<div className="flex">
|
||||
<div className="flex ">
|
||||
<TopBarButton icon={ChevronLeftIcon} onClick={() => navigate(-1)} />
|
||||
<TopBarButton icon={ChevronRightIcon} onClick={() => navigate(1)} />
|
||||
</div>
|
||||
|
||||
{/* <div className="flex mx-8 space-x-[1px]">
|
||||
<TopBarButton active group left icon={List} />
|
||||
<TopBarButton group icon={Columns} />
|
||||
<TopBarButton group right icon={SquaresFour} />
|
||||
</div> */}
|
||||
<div data-tauri-drag-region className="flex flex-row justify-center flex-grow ">
|
||||
<div className="flex mx-8 space-x-2 pointer-events-auto">
|
||||
<TopBarButton icon={Tag} />
|
||||
<TopBarButton icon={FolderPlus} />
|
||||
<TopBarButton icon={TerminalWindow} />
|
||||
<div data-tauri-drag-region className="flex flex-row justify-center flex-grow">
|
||||
<div className="flex mx-8">
|
||||
<TopBarButton
|
||||
group
|
||||
left
|
||||
active={layoutMode === 'list'}
|
||||
icon={Rows}
|
||||
onClick={() => setLayoutMode('list')}
|
||||
/>
|
||||
<TopBarButton
|
||||
group
|
||||
right
|
||||
active={layoutMode === 'grid'}
|
||||
icon={SquaresFour}
|
||||
onClick={() => setLayoutMode('grid')}
|
||||
/>
|
||||
</div>
|
||||
<SearchBar />
|
||||
|
||||
<div className="flex mx-8 space-x-2">
|
||||
<TopBarButton icon={Key} />
|
||||
<TopBarButton icon={Cloud} />
|
||||
{/* <TopBarButton icon={Cloud} /> */}
|
||||
<TopBarButton
|
||||
icon={ArrowsClockwise}
|
||||
onClick={() => {
|
||||
|
@ -119,11 +137,6 @@ export const TopBar: React.FC<TopBarProps> = (props) => {
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* <img
|
||||
alt="spacedrive-logo"
|
||||
src="/images/spacedrive_logo.png"
|
||||
className="w-8 h-8 mt-[1px] mr-2 pointer-events-none"
|
||||
/> */}
|
||||
<div className="flex mr-3 space-x-2">
|
||||
<Dropdown
|
||||
// className="absolute block h-6 w-44 top-2 right-4"
|
||||
|
@ -145,11 +158,7 @@ export const TopBar: React.FC<TopBarProps> = (props) => {
|
|||
buttonComponent={<TopBarButton icon={List} />}
|
||||
/>
|
||||
</div>
|
||||
{/*<TopBarButton onClick={() => {*/}
|
||||
{/* setSettingsOpen(!settingsOpen);*/}
|
||||
{/*}} className="mr-[8px]" icon={CogIcon} />*/}
|
||||
</div>
|
||||
{/* <div className="h-[1px] flex-shrink-0 max-w bg-gray-200 dark:bg-gray-700" /> */}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -16,7 +16,7 @@ interface LocationListItemProps {
|
|||
export default function LocationListItem({ location }: LocationListItemProps) {
|
||||
const [hide, setHide] = useState(false);
|
||||
|
||||
const { mutate: locRescan } = useLibraryCommand('LocRescan');
|
||||
const { mutate: locRescan } = useLibraryCommand('LocFullRescan');
|
||||
|
||||
const { mutate: deleteLoc, isLoading: locDeletePending } = useLibraryCommand('LocDelete', {
|
||||
onSuccess: () => {
|
||||
|
@ -28,7 +28,6 @@ export default function LocationListItem({ location }: LocationListItemProps) {
|
|||
|
||||
return (
|
||||
<div className="flex w-full px-4 py-2 border border-gray-500 rounded-lg bg-gray-550">
|
||||
<DotsVerticalIcon className="w-5 h-5 mt-3 mr-1 -ml-3 cursor-move drag-handle opacity-10" />
|
||||
<Folder size={30} className="mr-3" />
|
||||
<div className="flex flex-col">
|
||||
<h1 className="pt-0.5 text-sm font-semibold">{location.name}</h1>
|
||||
|
|
|
@ -14,7 +14,7 @@ export const SettingsHeading: React.FC<{ className?: string; children: string }>
|
|||
children,
|
||||
className
|
||||
}) => (
|
||||
<div className={clsx('mt-5 mb-1 ml-1 text-xs font-semibold text-gray-300', className)}>
|
||||
<div className={clsx('mt-5 mb-1 ml-1 text-xs font-semibold text-gray-400', className)}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -1,14 +1,20 @@
|
|||
import { LockClosedIcon } from '@heroicons/react/solid';
|
||||
import { Input } from '@sd/ui';
|
||||
import React from 'react';
|
||||
|
||||
export const ContentScreen: React.FC<{}> = (props) => {
|
||||
const [address, setAddress] = React.useState('');
|
||||
return (
|
||||
<div className="flex flex-col w-full h-screen p-5 custom-scroll page-scroll">
|
||||
<div className="flex flex-col space-y-5 pb-7">
|
||||
<p className="px-5 py-3 mb-3 text-sm text-gray-400 rounded-md bg-gray-50 dark:text-gray-400 dark:bg-gray-600">
|
||||
<b>Note: </b>This is a pre-alpha build of Spacedrive, many features are yet to be
|
||||
functional.
|
||||
</p>
|
||||
</div>
|
||||
{/* <div className="relative flex flex-col space-y-5 pb-7">
|
||||
<LockClosedIcon className="absolute w-4 h-4 ml-3 text-gray-250 top-[30px]" />
|
||||
<Input
|
||||
className="pl-9"
|
||||
placeholder="0f2z49zA"
|
||||
value={address}
|
||||
onChange={(e) => setAddress(e.target.value)}
|
||||
/>
|
||||
</div> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -29,7 +29,7 @@ export const ExplorerScreen: React.FC<{}> = () => {
|
|||
);
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-col w-full">
|
||||
<div className="relative flex flex-col w-full bg-gray-600">
|
||||
<TopBar />
|
||||
<div className="relative flex flex-row w-full max-h-full">
|
||||
<FileList location_id={location_id} path={path} limit={limit} />
|
||||
|
|
|
@ -217,40 +217,9 @@ export const OverviewScreen = () => {
|
|||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col pb-4 mt-4 space-y-4">
|
||||
<Device
|
||||
name={`James' MacBook Pro`}
|
||||
size="1TB"
|
||||
locations={[
|
||||
{ name: 'Documents', folder: true },
|
||||
{ name: 'Movies', folder: true },
|
||||
{ name: 'Downloads', folder: true },
|
||||
{ name: 'Minecraft', folder: true },
|
||||
{ name: 'Projects', folder: true },
|
||||
{ name: 'Notes', folder: true }
|
||||
]}
|
||||
type="desktop"
|
||||
/>
|
||||
<Device
|
||||
name={`James' iPhone 12`}
|
||||
size="47.7GB"
|
||||
locations={[
|
||||
{ name: 'Camera Roll', folder: true },
|
||||
{ name: 'Notes', folder: true },
|
||||
{ name: 'App.tsx', format: 'tsx', icon: 'reactts' },
|
||||
{ name: 'vite.config.js', format: 'js', icon: 'vite' }
|
||||
]}
|
||||
type="phone"
|
||||
/>
|
||||
<Device
|
||||
name={`Spacedrive Server`}
|
||||
size="5GB"
|
||||
locations={[
|
||||
{ name: 'Cached', folder: true },
|
||||
{ name: 'Photos', folder: true },
|
||||
{ name: 'Documents', folder: true }
|
||||
]}
|
||||
type="server"
|
||||
/>
|
||||
<Device name={`James' MacBook Pro`} size="1TB" locations={[]} type="desktop" />
|
||||
<Device name={`James' iPhone 12`} size="47.7GB" locations={[]} type="phone" />
|
||||
<Device name={`Spacedrive Server`} size="5GB" locations={[]} type="server" />
|
||||
</div>
|
||||
<div className="px-5 py-3 text-sm text-gray-400 rounded-md bg-gray-50 dark:text-gray-400 dark:bg-gray-600">
|
||||
<b>Note: </b>This is a pre-alpha build of Spacedrive, many features are yet to be
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
import { CogIcon, DatabaseIcon, KeyIcon, TagIcon } from '@heroicons/react/outline';
|
||||
import { HardDrive, ShareNetwork } from 'phosphor-react';
|
||||
import React from 'react';
|
||||
|
||||
import { SidebarLink } from '../../components/file/Sidebar';
|
||||
import {
|
||||
SettingsHeading,
|
||||
SettingsIcon,
|
||||
SettingsScreenContainer
|
||||
} from '../../components/settings/SettingsScreenContainer';
|
||||
|
||||
export const CurrentLibrarySettings: React.FC = () => {
|
||||
return (
|
||||
<SettingsScreenContainer>
|
||||
<SettingsHeading className="!mt-0">Library Settings</SettingsHeading>
|
||||
<SidebarLink to="/library-settings/general">
|
||||
<SettingsIcon component={CogIcon} />
|
||||
General
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/library-settings/locations">
|
||||
<SettingsIcon component={HardDrive} />
|
||||
Locations
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/library-settings/tags">
|
||||
<SettingsIcon component={TagIcon} />
|
||||
Tags
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/library-settings/keys">
|
||||
<SettingsIcon component={KeyIcon} />
|
||||
Keys
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/library-settings/backups">
|
||||
<SettingsIcon component={DatabaseIcon} />
|
||||
Backups
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/library-settings/backups">
|
||||
<SettingsIcon component={ShareNetwork} />
|
||||
Sync
|
||||
</SidebarLink>
|
||||
</SettingsScreenContainer>
|
||||
);
|
||||
};
|
|
@ -1,30 +0,0 @@
|
|||
import { useLibraryQuery } from '@sd/client';
|
||||
import React from 'react';
|
||||
|
||||
import LocationListItem from '../../components/location/LocationListItem';
|
||||
import { InputContainer } from '../../components/primitive/InputContainer';
|
||||
import { SettingsContainer } from '../../components/settings/SettingsContainer';
|
||||
import { SettingsHeader } from '../../components/settings/SettingsHeader';
|
||||
|
||||
// const exampleLocations = [
|
||||
// { option: 'Macintosh HD', key: 'macintosh_hd' },
|
||||
// { option: 'LaCie External', key: 'lacie_external' },
|
||||
// { option: 'Seagate 8TB', key: 'seagate_8tb' }
|
||||
// ];
|
||||
|
||||
export default function LocationSettings() {
|
||||
const { data: locations } = useLibraryQuery('SysGetLocations');
|
||||
|
||||
return (
|
||||
<SettingsContainer>
|
||||
{/*<Button size="sm">Add Location</Button>*/}
|
||||
<SettingsHeader title="Locations" description="Manage your storage locations." />
|
||||
|
||||
<div className="grid space-y-2">
|
||||
{locations?.map((location) => (
|
||||
<LocationListItem key={location.id} location={location} />
|
||||
))}
|
||||
</div>
|
||||
</SettingsContainer>
|
||||
);
|
||||
}
|
|
@ -1,11 +1,28 @@
|
|||
import {
|
||||
CogIcon,
|
||||
CollectionIcon,
|
||||
DatabaseIcon,
|
||||
GlobeAltIcon,
|
||||
HeartIcon,
|
||||
InformationCircleIcon,
|
||||
KeyIcon,
|
||||
LibraryIcon,
|
||||
LightBulbIcon,
|
||||
TagIcon,
|
||||
TerminalIcon
|
||||
} from '@heroicons/react/outline';
|
||||
import { HardDrive, PaintBrush, ShareNetwork } from 'phosphor-react';
|
||||
import {
|
||||
BookOpen,
|
||||
Cloud,
|
||||
HardDrive,
|
||||
Hash,
|
||||
Info,
|
||||
KeyReturn,
|
||||
PaintBrush,
|
||||
PuzzlePiece,
|
||||
ShareNetwork,
|
||||
UsersFour
|
||||
} from 'phosphor-react';
|
||||
import React from 'react';
|
||||
|
||||
import { SidebarLink } from '../../components/file/Sidebar';
|
||||
|
@ -23,61 +40,75 @@ export const SettingsScreen: React.FC = () => {
|
|||
<SettingsIcon component={CogIcon} />
|
||||
General
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/settings/libraries">
|
||||
<SettingsIcon component={CollectionIcon} />
|
||||
Libraries
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/settings/appearance">
|
||||
<SettingsIcon component={PaintBrush} />
|
||||
Appearance
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/settings/keybinds">
|
||||
<SettingsIcon component={KeyReturn} />
|
||||
Keybinds
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/settings/extensions">
|
||||
<SettingsIcon component={PuzzlePiece} />
|
||||
Extensions
|
||||
</SidebarLink>
|
||||
|
||||
<SettingsHeading>Node</SettingsHeading>
|
||||
<SettingsHeading>Library</SettingsHeading>
|
||||
<SidebarLink to="/settings/library">
|
||||
<SettingsIcon component={CogIcon} />
|
||||
General
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/settings/nodes">
|
||||
<SettingsIcon component={GlobeAltIcon} />
|
||||
<SettingsIcon component={ShareNetwork} />
|
||||
Nodes
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/settings/locations">
|
||||
<SettingsIcon component={HardDrive} />
|
||||
Locations
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/settings/tags">
|
||||
<SettingsIcon component={TagIcon} />
|
||||
Tags
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/settings/keys">
|
||||
<SettingsIcon component={KeyIcon} />
|
||||
Keys
|
||||
</SidebarLink>
|
||||
{/* <SidebarLink to="/settings/backups">
|
||||
<SettingsIcon component={DatabaseIcon} />
|
||||
Backups
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/settings/backups">
|
||||
<SettingsIcon component={ShareNetwork} />
|
||||
Sync
|
||||
</SidebarLink> */}
|
||||
<SettingsHeading>Advanced</SettingsHeading>
|
||||
<SidebarLink to="/settings/p2p">
|
||||
<SettingsIcon component={ShareNetwork} />
|
||||
P2P
|
||||
Networking
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/settings/library">
|
||||
<SettingsIcon component={CollectionIcon} />
|
||||
Libraries
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/settings/security">
|
||||
<SettingsIcon component={KeyIcon} />
|
||||
Security
|
||||
</SidebarLink>
|
||||
<SettingsHeading>Developer</SettingsHeading>
|
||||
<SidebarLink to="/settings/experimental">
|
||||
<SettingsIcon component={TerminalIcon} />
|
||||
Experimental
|
||||
Developer
|
||||
</SidebarLink>
|
||||
{/* <SettingsHeading>Library</SettingsHeading>
|
||||
<SidebarLink to="/settings/library">
|
||||
<SettingsIcon component={CollectionIcon} />
|
||||
My Libraries
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/settings/locations">
|
||||
<SettingsIcon component={HardDrive} />
|
||||
Locations
|
||||
</SidebarLink>
|
||||
|
||||
<SidebarLink to="/settings/keys">
|
||||
<SettingsIcon component={KeyIcon} />
|
||||
Keys
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/settings/tags">
|
||||
<SettingsIcon component={TagIcon} />
|
||||
Tags
|
||||
</SidebarLink> */}
|
||||
|
||||
{/* <SettingsHeading>Cloud</SettingsHeading>
|
||||
<SidebarLink to="/settings/sync">
|
||||
<SettingsIcon component={CloudIcon} />
|
||||
Sync
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/settings/contacts">
|
||||
<SettingsIcon component={UsersIcon} />
|
||||
Contacts
|
||||
</SidebarLink> */}
|
||||
<SettingsHeading>Resources</SettingsHeading>
|
||||
<SidebarLink to="/settings/about">
|
||||
<SettingsIcon component={BookOpen} />
|
||||
About
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/settings/changelog">
|
||||
<SettingsIcon component={LightBulbIcon} />
|
||||
Changelog
|
||||
</SidebarLink>
|
||||
<SidebarLink to="/settings/support">
|
||||
<SettingsIcon component={HeartIcon} />
|
||||
Support
|
||||
</SidebarLink>
|
||||
</SettingsScreenContainer>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
import { SearchIcon } from '@heroicons/react/solid';
|
||||
import { Button, Input } from '@sd/ui';
|
||||
import React from 'react';
|
||||
|
||||
import { InputContainer } from '../../../components/primitive/InputContainer';
|
||||
import { SettingsContainer } from '../../../components/settings/SettingsContainer';
|
||||
import { SettingsHeader } from '../../../components/settings/SettingsHeader';
|
||||
|
||||
// extensions should cache their logos in the app data folder
|
||||
interface ExtensionItemData {
|
||||
name: string;
|
||||
uuid: string;
|
||||
platforms: ['windows' | 'macOS' | 'linux'];
|
||||
installed: boolean;
|
||||
description: string;
|
||||
logoUri: string;
|
||||
}
|
||||
|
||||
const extensions: ExtensionItemData[] = [
|
||||
{
|
||||
name: 'Apple Photos',
|
||||
uuid: 'com.apple.photos',
|
||||
installed: true,
|
||||
platforms: ['macOS'],
|
||||
description: 'Import photos and videos with metadata from Apple Photos.',
|
||||
logoUri: 'https://apple.com/apple-logo.png'
|
||||
},
|
||||
{
|
||||
name: 'Twitch VOD Archiver',
|
||||
uuid: 'com.apple.photos',
|
||||
installed: false,
|
||||
platforms: ['macOS'],
|
||||
description: 'Apple Photos is a photo management application for Mac.',
|
||||
logoUri: 'https://apple.com/apple-logo.png'
|
||||
},
|
||||
{
|
||||
name: 'Shared Clipboard',
|
||||
uuid: 'com.apple.photos',
|
||||
installed: false,
|
||||
platforms: ['macOS'],
|
||||
description: 'Apple Photos is a photo management application for Mac.',
|
||||
logoUri: 'https://apple.com/apple-logo.png'
|
||||
}
|
||||
];
|
||||
|
||||
function ExtensionItem(props: { extension: ExtensionItemData }) {
|
||||
const { installed, name, description } = props.extension;
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-[290px] px-4 py-4 bg-gray-600 border border-gray-500 rounded">
|
||||
<h3 className="m-0 text-sm font-bold">{name}</h3>
|
||||
<p className="mt-1 mb-1 text-xs text-gray-300 ">{description}</p>
|
||||
<Button size="sm" className="mt-2" variant={installed ? 'gray' : 'primary'}>
|
||||
{installed ? 'Installed' : 'Install'}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ExtensionSettings() {
|
||||
// const { data: volumes } = useBridgeQuery('SysGetVolumes');
|
||||
|
||||
return (
|
||||
<SettingsContainer>
|
||||
<SettingsHeader
|
||||
title="Extensions"
|
||||
description="Install extensions to extend the functionality of this client."
|
||||
rightArea={
|
||||
<div className="relative mt-6">
|
||||
<SearchIcon className="absolute w-[18px] h-auto top-[8px] left-[11px] text-gray-350" />
|
||||
<Input className="w-56 !p-0.5 !pl-9" placeholder="Search extensions" />
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="flex flex-wrap gap-3">
|
||||
{extensions.map((extension) => (
|
||||
<ExtensionItem key={extension.uuid} extension={extension} />
|
||||
))}
|
||||
</div>
|
||||
</SettingsContainer>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Toggle } from '../../../components/primitive';
|
||||
import { InputContainer } from '../../../components/primitive/InputContainer';
|
||||
import { SettingsContainer } from '../../../components/settings/SettingsContainer';
|
||||
import { SettingsHeader } from '../../../components/settings/SettingsHeader';
|
||||
|
||||
export default function AppearanceSettings() {
|
||||
return (
|
||||
<SettingsContainer>
|
||||
<SettingsHeader title="Keybinds" description="Manage client keybinds" />
|
||||
<InputContainer
|
||||
mini
|
||||
title="Sync with Library"
|
||||
description="If enabled your keybinds will be synced with library, otherwise they will apply only to this client."
|
||||
>
|
||||
<Toggle value />
|
||||
</InputContainer>
|
||||
</SettingsContainer>
|
||||
);
|
||||
}
|
|
@ -6,7 +6,7 @@ import { SettingsHeader } from '../../../components/settings/SettingsHeader';
|
|||
export default function NodesSettings() {
|
||||
return (
|
||||
<SettingsContainer>
|
||||
<SettingsHeader title="Nodes" description="Manage the nodes in your Spacedrive network." />
|
||||
<SettingsHeader title="Backups" description="Manage database backups." />
|
||||
</SettingsContainer>
|
||||
);
|
||||
}
|
|
@ -17,6 +17,8 @@ export default function LibraryGeneralSettings() {
|
|||
const [name, setName] = useState('');
|
||||
const [description, setDescription] = useState('');
|
||||
const [encryptLibrary, setEncryptLibrary] = useState(false);
|
||||
// prevent auto update when switching library
|
||||
const [blockAutoUpdate, setBlockAutoUpdate] = useState(false);
|
||||
|
||||
const [nameDebounced] = useDebounce(name, 500);
|
||||
const [descriptionDebounced] = useDebounce(description, 500);
|
||||
|
@ -42,6 +44,18 @@ export default function LibraryGeneralSettings() {
|
|||
}
|
||||
}, [libraries]);
|
||||
|
||||
useEffect(() => {
|
||||
if (currentLibrary) {
|
||||
setBlockAutoUpdate(true);
|
||||
setName(currentLibrary.config.name);
|
||||
setDescription(currentLibrary.config.description);
|
||||
}
|
||||
}, [currentLibraryUuid]);
|
||||
|
||||
useEffect(() => {
|
||||
if (blockAutoUpdate) setBlockAutoUpdate(false);
|
||||
}, [blockAutoUpdate]);
|
||||
|
||||
return (
|
||||
<SettingsContainer>
|
||||
<SettingsHeader
|
||||
|
@ -49,8 +63,8 @@ export default function LibraryGeneralSettings() {
|
|||
description="General settings related to the currently active library."
|
||||
/>
|
||||
<div className="flex flex-row pb-3 space-x-5">
|
||||
<div className="flex flex-col flex-grow ">
|
||||
<span className="mt-2 mb-1 text-xs font-semibold text-gray-300">Name</span>
|
||||
<div className="flex flex-col flex-grow">
|
||||
<span className="mb-1 font-medium text-gray-700 dark:text-gray-100">Name</span>
|
||||
<Input
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
|
@ -58,7 +72,7 @@ export default function LibraryGeneralSettings() {
|
|||
/>
|
||||
</div>
|
||||
<div className="flex flex-col flex-grow">
|
||||
<span className="mt-2 mb-1 text-xs font-semibold text-gray-300">Description</span>
|
||||
<span className="mb-1 font-medium text-gray-700 dark:text-gray-100">Description</span>
|
||||
<Input
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
|
@ -76,13 +90,21 @@ export default function LibraryGeneralSettings() {
|
|||
<Toggle value={encryptLibrary} onChange={setEncryptLibrary} />
|
||||
</div>
|
||||
</InputContainer>
|
||||
<InputContainer mini title="Export Library" description="Export this library to a file.">
|
||||
<div className="mt-2">
|
||||
<Button size="sm" variant="gray">
|
||||
Export
|
||||
</Button>
|
||||
</div>
|
||||
</InputContainer>
|
||||
<InputContainer
|
||||
mini
|
||||
title="Delete Library"
|
||||
description="This is permanent, your files will not be deleted, only the Spacedrive library."
|
||||
>
|
||||
<div className="mt-2">
|
||||
<Button size="sm" variant="colored" className="bg-red-500 border-red-500">
|
||||
Delete Library
|
||||
Delete
|
||||
</Button>
|
||||
</div>
|
||||
</InputContainer>
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
import React from 'react';
|
||||
|
||||
import { SettingsContainer } from '../../../components/settings/SettingsContainer';
|
||||
import { SettingsHeader } from '../../../components/settings/SettingsHeader';
|
||||
|
||||
export default function NodesSettings() {
|
||||
return (
|
||||
<SettingsContainer>
|
||||
<SettingsHeader
|
||||
title="Nodes"
|
||||
description="Manage the nodes connected to this library. A node is an instance of Spacedrive's backend, running on a device or server. Each node carries a copy of the database and synchronizes via peer-to-peer connections in realtime."
|
||||
/>
|
||||
</SettingsContainer>
|
||||
);
|
||||
}
|
|
@ -4,6 +4,7 @@ import { useBridgeCommand, useBridgeQuery } from '@sd/client';
|
|||
import { AppPropsContext } from '@sd/client';
|
||||
import { LibraryConfig, LibraryConfigWrapped } from '@sd/core';
|
||||
import { Button, Input } from '@sd/ui';
|
||||
import { DotsSixVertical } from 'phosphor-react';
|
||||
import React, { useContext, useState } from 'react';
|
||||
|
||||
import Card from '../../../components/layout/Card';
|
||||
|
@ -26,6 +27,7 @@ function LibraryListItem(props: { library: LibraryConfigWrapped }) {
|
|||
|
||||
return (
|
||||
<Card>
|
||||
<DotsSixVertical weight="bold" className="mt-[15px] mr-3 opacity-30" />
|
||||
<div className="flex-grow my-0.5">
|
||||
<h3 className="font-semibold">{props.library.config.name}</h3>
|
||||
<p className="mt-0.5 text-xs text-gray-200">{props.library.uuid}</p>
|
||||
|
|
|
@ -29,7 +29,7 @@ body {
|
|||
width: 8px;
|
||||
}
|
||||
&::-webkit-scrollbar-track {
|
||||
@apply bg-[#00000006] dark:bg-[#00000030] mt-[55px] rounded-[6px];
|
||||
@apply bg-[#00000006] dark:bg-[#00000030] mt-[53px] rounded-[6px];
|
||||
}
|
||||
&::-webkit-scrollbar-thumb {
|
||||
@apply rounded-[6px] bg-gray-300 dark:bg-gray-550;
|
||||
|
|
129
pnpm-lock.yaml
129
pnpm-lock.yaml
|
@ -283,6 +283,7 @@ importers:
|
|||
'@types/react-router-dom': ^5.3.3
|
||||
'@types/react-table': ^7.7.12
|
||||
'@types/react-window': ^1.8.5
|
||||
'@types/styled-components': ^5.1.25
|
||||
'@types/tailwindcss': ^3.0.10
|
||||
'@vitejs/plugin-react': ^1.3.2
|
||||
autoprefixer: ^10.4.7
|
||||
|
@ -314,6 +315,7 @@ importers:
|
|||
react-transition-group: ^4.4.2
|
||||
react-virtuoso: ^2.12.1
|
||||
rooks: ^5.11.2
|
||||
styled-components: ^5.3.5
|
||||
tailwindcss: ^3.0.24
|
||||
typescript: ^4.7.2
|
||||
use-debounce: ^8.0.1
|
||||
|
@ -333,6 +335,7 @@ importers:
|
|||
'@sd/client': link:../client
|
||||
'@sd/core': link:../../core
|
||||
'@sd/ui': link:../ui
|
||||
'@types/styled-components': 5.1.25
|
||||
'@vitejs/plugin-react': 1.3.2
|
||||
autoprefixer: 10.4.7
|
||||
byte-size: 8.1.0
|
||||
|
@ -361,6 +364,7 @@ importers:
|
|||
react-transition-group: 4.4.2_ef5jwxihqo6n7gxfmzogljlgcm
|
||||
react-virtuoso: 2.13.2_ef5jwxihqo6n7gxfmzogljlgcm
|
||||
rooks: 5.11.2_ef5jwxihqo6n7gxfmzogljlgcm
|
||||
styled-components: 5.3.5_ef5jwxihqo6n7gxfmzogljlgcm
|
||||
tailwindcss: 3.1.3
|
||||
use-debounce: 8.0.1_react@18.1.0
|
||||
zustand: 4.0.0-rc.1_immer@9.0.15+react@18.1.0
|
||||
|
@ -2058,6 +2062,24 @@ packages:
|
|||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
/@babel/traverse/7.18.2_supports-color@5.5.0:
|
||||
resolution: {integrity: sha512-9eNwoeovJ6KH9zcCNnENY7DMFwTU9JdGCFtqNLfUAqtUHRCOsTOqWoffosP8vKmNYeSBUv3yVJXjfd8ucwOjUA==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
dependencies:
|
||||
'@babel/code-frame': 7.16.7
|
||||
'@babel/generator': 7.18.2
|
||||
'@babel/helper-environment-visitor': 7.18.2
|
||||
'@babel/helper-function-name': 7.17.9
|
||||
'@babel/helper-hoist-variables': 7.16.7
|
||||
'@babel/helper-split-export-declaration': 7.16.7
|
||||
'@babel/parser': 7.18.4
|
||||
'@babel/types': 7.18.4
|
||||
debug: 4.3.4_supports-color@5.5.0
|
||||
globals: 11.12.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
dev: false
|
||||
|
||||
/@babel/types/7.13.0:
|
||||
resolution: {integrity: sha512-hE+HE8rnG1Z6Wzo+MhaKE5lM5eMx71T4EHJgku2E3xIfaULhDcxiiRxUYgwX8qwP1BBSlag+TdGOt6JAidIZTA==}
|
||||
dependencies:
|
||||
|
@ -2182,6 +2204,24 @@ packages:
|
|||
engines: {node: '>=10.0.0'}
|
||||
dev: true
|
||||
|
||||
/@emotion/is-prop-valid/1.1.3:
|
||||
resolution: {integrity: sha512-RFg04p6C+1uO19uG8N+vqanzKqiM9eeV1LDOG3bmkYmuOj7NbKNlFC/4EZq5gnwAIlcC/jOT24f8Td0iax2SXA==}
|
||||
dependencies:
|
||||
'@emotion/memoize': 0.7.5
|
||||
dev: false
|
||||
|
||||
/@emotion/memoize/0.7.5:
|
||||
resolution: {integrity: sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==}
|
||||
dev: false
|
||||
|
||||
/@emotion/stylis/0.8.5:
|
||||
resolution: {integrity: sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==}
|
||||
dev: false
|
||||
|
||||
/@emotion/unitless/0.7.5:
|
||||
resolution: {integrity: sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==}
|
||||
dev: false
|
||||
|
||||
/@fiahfy/icns/0.0.7:
|
||||
resolution: {integrity: sha512-0apAtbUXTU3Opy/Z4h69o53voBa+am8FmdZauyagUMskAVYN1a5yIRk48Sf+tEdBLlefbvqLWPJ4pxr/Y/QtTg==}
|
||||
engines: {node: '>=8'}
|
||||
|
@ -5193,6 +5233,13 @@ packages:
|
|||
resolution: {integrity: sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==}
|
||||
dev: true
|
||||
|
||||
/@types/hoist-non-react-statics/3.3.1:
|
||||
resolution: {integrity: sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==}
|
||||
dependencies:
|
||||
'@types/react': 18.0.9
|
||||
hoist-non-react-statics: 3.3.2
|
||||
dev: false
|
||||
|
||||
/@types/html-minifier-terser/5.1.2:
|
||||
resolution: {integrity: sha512-h4lTMgMJctJybDp8CQrxTUiiYmedihHWkjnF/8Pxseu2S6Nlfcy8kwboQ8yejh456rP2yWoEVm1sS/FVsfM48w==}
|
||||
dev: true
|
||||
|
@ -5412,6 +5459,14 @@ packages:
|
|||
resolution: {integrity: sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==}
|
||||
dev: true
|
||||
|
||||
/@types/styled-components/5.1.25:
|
||||
resolution: {integrity: sha512-fgwl+0Pa8pdkwXRoVPP9JbqF0Ivo9llnmsm+7TCI330kbPIFd9qv1Lrhr37shf4tnxCOSu+/IgqM7uJXLWZZNQ==}
|
||||
dependencies:
|
||||
'@types/hoist-non-react-statics': 3.3.1
|
||||
'@types/react': 18.0.9
|
||||
csstype: 3.1.0
|
||||
dev: false
|
||||
|
||||
/@types/tailwindcss/3.0.10:
|
||||
resolution: {integrity: sha512-1UnZIHO0NOPyPlPFV0HuMjki2SHkvG9uBA1ZehWj/OQMSROk503nuNyyfmJSIT289yewxTbKoPG+KLxYRvfIIg==}
|
||||
dev: true
|
||||
|
@ -6455,6 +6510,23 @@ packages:
|
|||
- supports-color
|
||||
dev: true
|
||||
|
||||
/babel-plugin-styled-components/2.0.7_styled-components@5.3.5:
|
||||
resolution: {integrity: sha512-i7YhvPgVqRKfoQ66toiZ06jPNA3p6ierpfUuEWxNF+fV27Uv5gxBkf8KZLHUCc1nFA9j6+80pYoIpqCeyW3/bA==}
|
||||
peerDependencies:
|
||||
styled-components: '>= 2'
|
||||
dependencies:
|
||||
'@babel/helper-annotate-as-pure': 7.16.7
|
||||
'@babel/helper-module-imports': 7.16.7
|
||||
babel-plugin-syntax-jsx: 6.18.0
|
||||
lodash: 4.17.21
|
||||
picomatch: 2.3.1
|
||||
styled-components: 5.3.5_ef5jwxihqo6n7gxfmzogljlgcm
|
||||
dev: false
|
||||
|
||||
/babel-plugin-syntax-jsx/6.18.0:
|
||||
resolution: {integrity: sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==}
|
||||
dev: false
|
||||
|
||||
/babel-runtime/6.26.0:
|
||||
resolution: {integrity: sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==}
|
||||
dependencies:
|
||||
|
@ -7059,6 +7131,10 @@ packages:
|
|||
engines: {node: '>=10'}
|
||||
dev: true
|
||||
|
||||
/camelize/1.0.0:
|
||||
resolution: {integrity: sha512-W2lPwkBkMZwFlPCXhIlYgxu+7gC/NUlCtdK652DAJ1JdgV0sTrvuPFshNPrFa1TY2JOkLhgdeEBplB4ezEa+xg==}
|
||||
dev: false
|
||||
|
||||
/caniuse-lite/1.0.30001352:
|
||||
resolution: {integrity: sha512-GUgH8w6YergqPQDGWhJGt8GDRnY0L/iJVQcU3eJ46GYf52R8tk0Wxp0PymuFVZboJYXGiCqwozAYZNRjVj6IcA==}
|
||||
|
||||
|
@ -7773,6 +7849,11 @@ packages:
|
|||
util.promisify: 1.0.0
|
||||
dev: true
|
||||
|
||||
/css-color-keywords/1.0.0:
|
||||
resolution: {integrity: sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==}
|
||||
engines: {node: '>=4'}
|
||||
dev: false
|
||||
|
||||
/css-loader/3.6.0:
|
||||
resolution: {integrity: sha512-M5lSukoWi1If8dhQAUCvj4H8vUt3vOnwbQBH9DdTm/s4Ym2B/3dPMtYZeJmq7Q3S3Pa+I94DcZ7pc9bP14cWIQ==}
|
||||
engines: {node: '>= 8.9.0'}
|
||||
|
@ -7861,6 +7942,14 @@ packages:
|
|||
nth-check: 2.1.1
|
||||
dev: true
|
||||
|
||||
/css-to-react-native/3.0.0:
|
||||
resolution: {integrity: sha512-Ro1yETZA813eoyUp2GDBhG2j+YggidUmzO1/v9eYBKR2EHVEniE2MI/NqpTQ954BMpTPZFsGNPm46qFB9dpaPQ==}
|
||||
dependencies:
|
||||
camelize: 1.0.0
|
||||
css-color-keywords: 1.0.0
|
||||
postcss-value-parser: 4.2.0
|
||||
dev: false
|
||||
|
||||
/css-what/6.1.0:
|
||||
resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==}
|
||||
engines: {node: '>= 6'}
|
||||
|
@ -7946,6 +8035,19 @@ packages:
|
|||
dependencies:
|
||||
ms: 2.1.2
|
||||
|
||||
/debug/4.3.4_supports-color@5.5.0:
|
||||
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
|
||||
engines: {node: '>=6.0'}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
dependencies:
|
||||
ms: 2.1.2
|
||||
supports-color: 5.5.0
|
||||
dev: false
|
||||
|
||||
/decamelize/1.2.0:
|
||||
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
@ -15030,6 +15132,10 @@ packages:
|
|||
kind-of: 6.0.3
|
||||
dev: true
|
||||
|
||||
/shallowequal/1.1.0:
|
||||
resolution: {integrity: sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==}
|
||||
dev: false
|
||||
|
||||
/sharp/0.30.5:
|
||||
resolution: {integrity: sha512-0T28KxqY4DzUMLSAp1/IhGVeHpPIQyp1xt7esmuXCAfyi/+6tYMUeRhQok+E/+E52Yk5yFjacXp90cQOkmkl4w==}
|
||||
engines: {node: '>=12.13.0'}
|
||||
|
@ -15605,6 +15711,29 @@ packages:
|
|||
inline-style-parser: 0.1.1
|
||||
dev: true
|
||||
|
||||
/styled-components/5.3.5_ef5jwxihqo6n7gxfmzogljlgcm:
|
||||
resolution: {integrity: sha512-ndETJ9RKaaL6q41B69WudeqLzOpY1A/ET/glXkNZ2T7dPjPqpPCXXQjDFYZWwNnE5co0wX+gTCqx9mfxTmSIPg==}
|
||||
engines: {node: '>=10'}
|
||||
requiresBuild: true
|
||||
peerDependencies:
|
||||
react: '>= 16.8.0'
|
||||
react-dom: '>= 16.8.0'
|
||||
react-is: '>= 16.8.0'
|
||||
dependencies:
|
||||
'@babel/helper-module-imports': 7.16.7
|
||||
'@babel/traverse': 7.18.2_supports-color@5.5.0
|
||||
'@emotion/is-prop-valid': 1.1.3
|
||||
'@emotion/stylis': 0.8.5
|
||||
'@emotion/unitless': 0.7.5
|
||||
babel-plugin-styled-components: 2.0.7_styled-components@5.3.5
|
||||
css-to-react-native: 3.0.0
|
||||
hoist-non-react-statics: 3.3.2
|
||||
react: 18.1.0
|
||||
react-dom: 18.1.0_react@18.1.0
|
||||
shallowequal: 1.1.0
|
||||
supports-color: 5.5.0
|
||||
dev: false
|
||||
|
||||
/supports-color/2.0.0:
|
||||
resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==}
|
||||
engines: {node: '>=0.8.0'}
|
||||
|
|
Loading…
Reference in a new issue