From 7839fe43e1ac177b5d1b6792882a37286f2534a5 Mon Sep 17 00:00:00 2001 From: Jamie Pine <32987599+jamiepine@users.noreply.github.com> Date: Thu, 14 Jul 2022 13:50:48 -0700 Subject: [PATCH] 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 --- .vscode/settings.json | 1 + core/bindings/LibraryCommand.ts | 2 +- core/src/file/mod.rs | 27 +++ core/src/lib.rs | 12 +- core/src/library/library_manager.rs | 4 +- packages/client/src/bridge.ts | 8 +- .../client/src/context/AppPropsContext.tsx | 1 + .../client/src/context/LocationContext.ts | 9 + packages/client/src/context/index.ts | 1 + .../client/src/stores/useExplorerStore.ts | 6 + packages/interface/package.json | 2 + packages/interface/src/App.tsx | 32 ++- packages/interface/src/AppLayout.tsx | 2 +- packages/interface/src/AppRouter.tsx | 22 +- .../src/components/device/Device.tsx | 7 +- .../src/components/file/FileItem.tsx | 210 +++++++----------- .../src/components/file/FileList.tsx | 156 ++++++++----- .../src/components/file/FileThumb.tsx | 13 +- .../src/components/file/Inspector.tsx | 167 +++++++------- .../interface/src/components/file/Sidebar.tsx | 12 +- .../src/components/layout/TopBar.tsx | 65 +++--- .../components/location/LocationListItem.tsx | 3 +- .../settings/SettingsScreenContainer.tsx | 2 +- packages/interface/src/screens/Content.tsx | 18 +- packages/interface/src/screens/Explorer.tsx | 2 +- packages/interface/src/screens/Overview.tsx | 37 +-- .../settings/CurrentLibrarySettings.tsx | 42 ---- .../src/screens/settings/LocationSettings.tsx | 30 --- .../src/screens/settings/Settings.tsx | 113 ++++++---- .../settings/client/ExtensionsSettings.tsx | 83 +++++++ .../settings/client/KeybindSettings.tsx | 21 ++ .../BackupsSettings.tsx} | 2 +- .../library/LibraryGeneralSettings.tsx | 30 ++- .../settings/library/NodesSettings.tsx | 15 ++ .../settings/node/LibrariesSettings.tsx | 2 + packages/interface/src/style.scss | 2 +- pnpm-lock.yaml | 129 +++++++++++ 37 files changed, 784 insertions(+), 506 deletions(-) create mode 100644 packages/client/src/context/LocationContext.ts delete mode 100644 packages/interface/src/screens/settings/CurrentLibrarySettings.tsx delete mode 100644 packages/interface/src/screens/settings/LocationSettings.tsx create mode 100644 packages/interface/src/screens/settings/client/ExtensionsSettings.tsx create mode 100644 packages/interface/src/screens/settings/client/KeybindSettings.tsx rename packages/interface/src/screens/settings/{node/NodesSettings.tsx => library/BackupsSettings.tsx} (75%) create mode 100644 packages/interface/src/screens/settings/library/NodesSettings.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index cedc4f182..a21f04485 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -12,6 +12,7 @@ "ipfs", "Keepsafe", "nodestate", + "overscan", "pathctx", "prismjs", "proptype", diff --git a/core/bindings/LibraryCommand.ts b/core/bindings/LibraryCommand.ts index 713fc8989..d99033a9f 100644 --- a/core/bindings/LibraryCommand.ts +++ b/core/bindings/LibraryCommand.ts @@ -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, } }; \ No newline at end of file +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, } }; \ No newline at end of file diff --git a/core/src/file/mod.rs b/core/src/file/mod.rs index c8955e7f1..780633b93 100644 --- a/core/src/file/mod.rs +++ b/core/src/file/mod.rs @@ -168,3 +168,30 @@ pub async fn set_note( Ok(CoreResponse::Success(())) } + +pub async fn favorite( + ctx: LibraryContext, + id: i32, + favorite: bool, +) -> Result { + 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(())) +} diff --git a/core/src/lib.rs b/core/src/lib.rs index dc16a1ce1..f865b766b 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -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 }, + 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 }, LocDelete { id: i32 }, - LocRescan { id: i32 }, + LocFullRescan { id: i32 }, + LocQuickRescan { id: i32 }, // System SysVolumeUnmount { id: i32 }, GenerateThumbsForLocation { id: i32, path: String }, diff --git a/core/src/library/library_manager.rs b/core/src/library/library_manager.rs index a4da40acd..5c1aca635 100644 --- a/core/src/library/library_manager.rs +++ b/core/src/library/library_manager.rs @@ -156,7 +156,7 @@ impl LibraryManager { .collect() } - pub(crate) async fn edit_library( + pub(crate) async fn edit( &self, id: String, name: Option, @@ -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, diff --git a/packages/client/src/bridge.ts b/packages/client/src/bridge.ts index 41e430c66..1c81f3519 100644 --- a/packages/client/src/bridge.ts +++ b/packages/client/src/bridge.ts @@ -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 = Extract; diff --git a/packages/client/src/context/AppPropsContext.tsx b/packages/client/src/context/AppPropsContext.tsx index 86876b309..d0f74afe9 100644 --- a/packages/client/src/context/AppPropsContext.tsx +++ b/packages/client/src/context/AppPropsContext.tsx @@ -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; onClose?: () => void; diff --git a/packages/client/src/context/LocationContext.ts b/packages/client/src/context/LocationContext.ts new file mode 100644 index 000000000..379ffa334 --- /dev/null +++ b/packages/client/src/context/LocationContext.ts @@ -0,0 +1,9 @@ +import { createContext } from 'react'; + +export const LocationContext = createContext<{ + location_id: number; + data_path: string; +}>({ + location_id: 1, + data_path: '' +}); diff --git a/packages/client/src/context/index.ts b/packages/client/src/context/index.ts index 70d70d64e..7e250580f 100644 --- a/packages/client/src/context/index.ts +++ b/packages/client/src/context/index.ts @@ -1 +1,2 @@ export * from './AppPropsContext'; +export * from './LocationContext'; diff --git a/packages/client/src/stores/useExplorerStore.ts b/packages/client/src/stores/useExplorerStore.ts index 185d88dd1..c16399e34 100644 --- a/packages/client/src/stores/useExplorerStore.ts +++ b/packages/client/src/stores/useExplorerStore.ts @@ -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; addNewThumbnail: (cas_id: string) => void; + setLayoutMode: (mode: LayoutMode) => void; reset: () => void; }; export const useExplorerStore = create((set) => ({ + layoutMode: 'grid', selectedRowIndex: 1, setSelectedRowIndex: (index) => set((state) => ({ ...state, selectedRowIndex: index })), locationId: -1, @@ -21,5 +26,6 @@ export const useExplorerStore = create((set) => ({ ...state, newThumbnails: { ...state.newThumbnails, [cas_id]: true } })), + setLayoutMode: (mode: LayoutMode) => set((state) => ({ ...state, layoutMode: mode })), reset: () => set(() => ({})) })); diff --git a/packages/interface/package.json b/packages/interface/package.json index 8577584cd..02e05a74d 100644 --- a/packages/interface/package.json +++ b/packages/interface/package.json @@ -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" diff --git a/packages/interface/src/App.tsx b/packages/interface/src/App.tsx index 514d60a36..a04b4382f 100644 --- a/packages/interface/src/App.tsx +++ b/packages/interface/src/App.tsx @@ -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 ( - - - + + + + + ); } @@ -34,11 +46,9 @@ export default function App(props: AppProps) { <> {}}> - - - - - + + + diff --git a/packages/interface/src/AppLayout.tsx b/packages/interface/src/AppLayout.tsx index 0c3ecbb11..ed5715a23 100644 --- a/packages/interface/src/AppLayout.tsx +++ b/packages/interface/src/AppLayout.tsx @@ -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'; diff --git a/packages/interface/src/AppRouter.tsx b/packages/interface/src/AppRouter.tsx index db0ffdacd..640d3f1eb 100644 --- a/packages/interface/src/AppRouter.tsx +++ b/packages/interface/src/AppRouter.tsx @@ -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() { } /> } /> } /> - }> - } /> - } /> - } /> - } /> - } /> - }> } /> } /> } /> - } /> + } /> + } /> } /> } /> } /> } /> - } /> + } /> } /> } /> } /> } /> } /> + } /> + } /> + } /> + } /> + } /> } /> } /> diff --git a/packages/interface/src/components/device/Device.tsx b/packages/interface/src/components/device/Device.tsx index 6f91b2e26..2cfd835b1 100644 --- a/packages/interface/src/components/device/Device.tsx +++ b/packages/interface/src/components/device/Device.tsx @@ -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 && ( +
No locations
+ )} ); diff --git a/packages/interface/src/components/file/FileItem.tsx b/packages/interface/src/components/file/FileItem.tsx index 509cec97c..b9598bf16 100644 --- a/packages/interface/src/components/file/FileItem.tsx +++ b/packages/interface/src/components/file/FileItem.tsx @@ -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 { + file?: FilePath | null; selected?: boolean; - onClick?: MouseEventHandler; } export default function FileItem(props: Props) { - // const Shadow = () => { - // return ( - //
- // ); - // }; + const location = useContext(LocationContext); return ( - +
-
-
- {props.folder ? ( -
-
- -
+ )} + > + {props.file?.is_dir ? ( +
+
+
- ) : ( -
- - - - - - -
- {props.iconName && icons[props.iconName as keyof typeof icons] ? ( - (() => { - const Icon = icons[props.iconName as keyof typeof icons]; - return ( - - ); - })() - ) : ( - <> - )} - - {props.format} - -
-
- )} -
-
- + ) : props.file?.file?.has_thumbnail ? ( +
+
+ +
+
+ ) : ( +
- {props.fileName} - -
+ + + + + + +
+ {props.file?.extension && icons[props.file.extension as keyof typeof icons] ? ( + (() => { + const Icon = icons[props.file.extension as keyof typeof icons]; + return ( + + ); + })() + ) : ( + <> + )} + + {props.file?.extension} + +
+
+ )}
- +
+ + {props.file?.name} + +
+
); } diff --git a/packages/interface/src/components/file/FileList.tsx b/packages/interface/src/components/file/FileList.tsx index 3409bdf2f..e2a792a60 100644 --- a/packages/interface/src/components/file/FileList.tsx +++ b/packages/interface/src/components/file/FileList.tsx @@ -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 ; + const createRenderItem = (RenderItem: React.FC) => { + return (index: number) => { + const row = currentDir?.contents?.[index]; + if (!row) return null; + return ; + }; }; const Header = () => ( @@ -116,46 +123,90 @@ export const FileList: React.FC<{ location_id: number; path: string; limit: numb
); - return useMemo( - () => ( -
+ - + {layoutMode === 'grid' && ( + + )} + {layoutMode === 'list' && (
}} + itemContent={createRenderItem(RenderRow)} + components={{ + Header, + Footer: () =>
+ }} increaseViewportBy={{ top: 400, bottom: 200 }} className="outline-none explorer-scroll" /> - -
- ), - [props.location_id, size.innerWidth, currentDir?.directory.id, tableContainer.current] + )} + +
); }; -const RenderRow: React.FC<{ - row: FilePath; - rowIndex: number; +interface RenderItemProps { + item: FilePath; + index: number; dirId: number; -}> = ({ row, rowIndex, dirId }) => { +} + +const RenderGridItem: React.FC = ({ item, index, dirId }) => { + // return
; 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 ( + { + if (item.is_dir) { + setSearchParams({ path: item.materialized_path }); + } + }} + file={item} + selected={isActive} + onClick={selectFileHandler} + /> + ); +}; + +const RenderRow: React.FC = ({ 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<{
{ - 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 }} > - +
))}
), - [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 (
- +
{/* {colKey == 'name' && (() => { diff --git a/packages/interface/src/components/file/FileThumb.tsx b/packages/interface/src/components/file/FileThumb.tsx index 7ae390f8c..62164a88d 100644 --- a/packages/interface/src/components/file/FileThumb.tsx +++ b/packages/interface/src/components/file/FileThumb.tsx @@ -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 ; } - if (client?.data_path && (props.file.file?.has_thumbnail || props.hasThumbnailOverride)) { + if (appProps?.data_path && (props.file.file?.has_thumbnail || hasNewThumbnail)) { return ( ); diff --git a/packages/interface/src/components/file/Inspector.tsx b/packages/interface/src/components/file/Inspector.tsx index 5f10760ba..42b2d127a 100644 --- a/packages/interface/src/components/file/Inspector.tsx +++ b/packages/interface/src/components/file/Inspector.tsx @@ -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 ( - // -
+
{!!file_path && ( -
-
+
+
-

{file_path?.name}

-
- - - -
- {file_path?.file?.cas_id && ( - - )} - - - - - - - {!file_path?.is_dir && ( - <> - -
- {file_path?.extension && ( - - {file_path?.extension} - +
+

{file_path?.name}

+
+ + + +
+ {file_path?.file?.cas_id && ( + + )} + + + + + + + {!file_path?.is_dir && ( + <> + +
+ {file_path?.extension && ( + + {file_path?.extension} + + )} +

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

+
+ {file_path.file && ( + <> + + + } + /> + )} -

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

-
- {file_path.file && ( - <> - - - } - /> - - )} - - )} - {/*
- -
*/} - {/* - */} + + )} +
)}
- // ); }; diff --git a/packages/interface/src/components/file/Sidebar.tsx b/packages/interface/src/components/file/Sidebar.tsx index d203333b1..153d755b9 100644 --- a/packages/interface/src/components/file/Sidebar.tsx +++ b/packages/interface/src/components/file/Sidebar.tsx @@ -86,13 +86,7 @@ export const Sidebar: React.FC = (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 = (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 = (props) => { - Content + Spaces diff --git a/packages/interface/src/components/layout/TopBar.tsx b/packages/interface/src/components/layout/TopBar.tsx index 151a880c3..beb32a322 100644 --- a/packages/interface/src/components/layout/TopBar.tsx +++ b/packages/interface/src/components/layout/TopBar.tsx @@ -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 = ({ icon: Icon, ...props }) => */} - - -
- {locations?.map((location) => ( - - ))} -
- - ); -} diff --git a/packages/interface/src/screens/settings/Settings.tsx b/packages/interface/src/screens/settings/Settings.tsx index fc903d4aa..0bcafc3f0 100644 --- a/packages/interface/src/screens/settings/Settings.tsx +++ b/packages/interface/src/screens/settings/Settings.tsx @@ -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 = () => { General
+ + + Libraries + Appearance + + + Keybinds + + + + Extensions + - Node + Library + + + General + - + Nodes + + + Locations + + + + Tags + + + + Keys + + {/* + + Backups + + + + Sync + */} + Advanced - P2P + Networking - - - Libraries - - - - Security - - Developer - Experimental + Developer - {/* Library - - - My Libraries - - - - Locations - - - - Keys - - - - Tags - */} - - {/* Cloud - - - Sync - - - - Contacts - */} + Resources + + + About + + + + Changelog + + + + Support + ); }; diff --git a/packages/interface/src/screens/settings/client/ExtensionsSettings.tsx b/packages/interface/src/screens/settings/client/ExtensionsSettings.tsx new file mode 100644 index 000000000..01c535a13 --- /dev/null +++ b/packages/interface/src/screens/settings/client/ExtensionsSettings.tsx @@ -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 ( +
+

{name}

+

{description}

+ +
+ ); +} + +export default function ExtensionSettings() { + // const { data: volumes } = useBridgeQuery('SysGetVolumes'); + + return ( + + + + +
+ } + /> + +
+ {extensions.map((extension) => ( + + ))} +
+ + ); +} diff --git a/packages/interface/src/screens/settings/client/KeybindSettings.tsx b/packages/interface/src/screens/settings/client/KeybindSettings.tsx new file mode 100644 index 000000000..914709fbc --- /dev/null +++ b/packages/interface/src/screens/settings/client/KeybindSettings.tsx @@ -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 ( + + + + + + + ); +} diff --git a/packages/interface/src/screens/settings/node/NodesSettings.tsx b/packages/interface/src/screens/settings/library/BackupsSettings.tsx similarity index 75% rename from packages/interface/src/screens/settings/node/NodesSettings.tsx rename to packages/interface/src/screens/settings/library/BackupsSettings.tsx index 75595f42f..56f0ab817 100644 --- a/packages/interface/src/screens/settings/node/NodesSettings.tsx +++ b/packages/interface/src/screens/settings/library/BackupsSettings.tsx @@ -6,7 +6,7 @@ import { SettingsHeader } from '../../../components/settings/SettingsHeader'; export default function NodesSettings() { return ( - + ); } diff --git a/packages/interface/src/screens/settings/library/LibraryGeneralSettings.tsx b/packages/interface/src/screens/settings/library/LibraryGeneralSettings.tsx index 87e70e4e0..9d45d2f3e 100644 --- a/packages/interface/src/screens/settings/library/LibraryGeneralSettings.tsx +++ b/packages/interface/src/screens/settings/library/LibraryGeneralSettings.tsx @@ -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 (
-
- Name +
+ Name setName(e.target.value)} @@ -58,7 +72,7 @@ export default function LibraryGeneralSettings() { />
- Description + Description setDescription(e.target.value)} @@ -76,13 +90,21 @@ export default function LibraryGeneralSettings() {
+ +
+ +
+
diff --git a/packages/interface/src/screens/settings/library/NodesSettings.tsx b/packages/interface/src/screens/settings/library/NodesSettings.tsx new file mode 100644 index 000000000..d24b4a6de --- /dev/null +++ b/packages/interface/src/screens/settings/library/NodesSettings.tsx @@ -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 ( + + + + ); +} diff --git a/packages/interface/src/screens/settings/node/LibrariesSettings.tsx b/packages/interface/src/screens/settings/node/LibrariesSettings.tsx index 3f1b9c3a0..5c12f8c3e 100644 --- a/packages/interface/src/screens/settings/node/LibrariesSettings.tsx +++ b/packages/interface/src/screens/settings/node/LibrariesSettings.tsx @@ -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 ( +

{props.library.config.name}

{props.library.uuid}

diff --git a/packages/interface/src/style.scss b/packages/interface/src/style.scss index b3e208b8d..e58d884fb 100644 --- a/packages/interface/src/style.scss +++ b/packages/interface/src/style.scss @@ -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; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b4f6a891d..bb35be3d4 100644 --- a/pnpm-lock.yaml +++ b/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'}