This commit is contained in:
Jamie Pine 2022-09-04 15:58:16 -07:00
parent 7be22c96e5
commit a9087ca429
No known key found for this signature in database
GPG key ID: D5AC85A0C2F646E9
16 changed files with 181 additions and 106 deletions

View file

@ -27,7 +27,8 @@
"trivago",
"tsparticles",
"unlisten",
"upsert"
"upsert",
"valtio"
],
"[rust]": {
"editor.defaultFormatter": "rust-lang.rust-analyzer"

View file

@ -18,13 +18,15 @@
},
"dependencies": {
"@rspc/client": "^0.0.5",
"@sd/core": "workspace:*",
"@sd/config": "workspace:*",
"@sd/core": "workspace:*",
"@sd/interface": "workspace:*",
"@tanstack/react-query": "^4.0.10",
"eventemitter3": "^4.0.7",
"immer": "^9.0.15",
"lodash": "^4.17.21",
"valtio": "^1.6.4",
"valtio-persist": "^1.0.2",
"zustand": "4.0.0"
},
"devDependencies": {

View file

@ -0,0 +1,48 @@
import { proxy, ref } from 'valtio';
export type ExplorerLayoutMode = 'list' | 'grid';
export enum ExplorerKind {
Location,
Tag,
Space
}
const state = {
locationId: null as number | null,
layoutMode: 'grid' as ExplorerLayoutMode,
gridItemSize: 100,
listItemSize: 40,
selectedRowIndex: 1,
showInspector: true,
multiSelectIndexes: [] as number[],
contextMenuObjectId: null as number | null,
newThumbnails: {} as Record<string, boolean>
};
export const explorerStore = proxy({
...state,
reset: () => resetStore(explorerStore, state),
addNewThumbnail: (cas_id: string) => {
explorerStore.newThumbnails[cas_id] = true;
},
selectMore: (indexes: number[]) => {
if (!explorerStore.multiSelectIndexes.length && indexes.length) {
explorerStore.multiSelectIndexes = [explorerStore.selectedRowIndex, ...indexes];
} else {
explorerStore.multiSelectIndexes = [
...new Set([...explorerStore.multiSelectIndexes, ...indexes])
];
}
}
});
export function resetStore<T extends Record<string, any>, E extends Record<string, any>>(
store: T,
defaults: E
) {
for (const key in defaults) {
// @ts-ignore
store[key] = defaults[key];
}
}

View file

@ -1,2 +1,2 @@
export * from './useLibraryStore';
export * from './useExplorerStore';
export * from './explorerStore';

View file

@ -1,57 +0,0 @@
import produce from 'immer';
import create from 'zustand';
export type ExplorerLayoutMode = 'list' | 'grid';
export enum ExplorerKind {
Location,
Tag,
Space
}
type ExplorerStore = {
layoutMode: ExplorerLayoutMode;
locationId: number | null; // used by top bar
gridItemSize: number;
listItemSize: number;
showInspector: boolean;
selectedRowIndex: number;
multiSelectIndexes: number[];
contextMenuObjectId: number | null;
newThumbnails: Record<string, boolean>;
addNewThumbnail: (cas_id: string) => void;
selectMore: (indexes: number[]) => void;
reset: () => void;
set: (changes: Partial<ExplorerStore>) => void;
};
export const useExplorerStore = create<ExplorerStore>((set) => ({
layoutMode: 'grid',
locationId: null,
gridItemSize: 100,
listItemSize: 40,
showInspector: true,
selectedRowIndex: 1,
multiSelectIndexes: [],
contextMenuObjectId: -1,
newThumbnails: {},
addNewThumbnail: (cas_id) =>
set((state) =>
produce(state, (draft) => {
draft.newThumbnails[cas_id] = true;
})
),
selectMore: (indexes) => {
set((state) =>
produce(state, (draft) => {
if (!draft.multiSelectIndexes.length && indexes.length) {
draft.multiSelectIndexes = [draft.selectedRowIndex, ...indexes];
} else {
draft.multiSelectIndexes = [...new Set([...draft.multiSelectIndexes, ...indexes])];
}
})
);
},
reset: () => set(() => ({})),
set: (changes) => set((state) => ({ ...state, ...changes }))
}));

View file

@ -5,7 +5,7 @@ import create from 'zustand';
import { devtools, persist } from 'zustand/middleware';
import { useBridgeQuery } from '../index';
import { useExplorerStore } from './useExplorerStore';
import { explorerStore } from './explorerStore';
type LibraryStore = {
// the uuid of the currently active library
@ -28,7 +28,7 @@ export const useLibraryStore = create<LibraryStore>()(
})
);
// reset other stores
useExplorerStore().reset();
explorerStore.reset();
},
init: async (libraries) => {
set((state) =>

View file

@ -68,6 +68,8 @@
"tailwindcss": "^3.1.6",
"use-count-up": "^3.0.1",
"use-debounce": "^8.0.3",
"valtio": "^1.6.4",
"valtio-persist": "^1.0.2",
"zod": "^3.18.0",
"zustand": "4.0.0"
},

View file

@ -1,6 +1,6 @@
import {
explorerStore,
rspc,
useExplorerStore,
useLibraryMutation,
useLibraryQuery,
useLibraryStore
@ -17,6 +17,7 @@ import {
TrashSimple
} from 'phosphor-react';
import React, { memo, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { useSnapshot } from 'valtio';
import { Inspector } from '../explorer/Inspector';
import { WithContextMenu } from '../layout/MenuOverlay';
@ -29,7 +30,7 @@ interface Props {
}
export default function Explorer(props: Props) {
const addNewThumbnail = useExplorerStore((store) => store.addNewThumbnail);
const { addNewThumbnail, selectedRowIndex, showInspector } = useSnapshot(explorerStore);
const currentLibraryUuid = useLibraryStore((store) => store.currentLibraryUuid);
@ -39,11 +40,6 @@ export default function Explorer(props: Props) {
}
});
const { selectedRowIndex, showInspector } = useExplorerStore((store) => ({
selectedRowIndex: store.selectedRowIndex,
showInspector: store.showInspector
}));
return (
<div className="relative">
<ExplorerContextMenu>

View file

@ -1,4 +1,4 @@
import { useExplorerStore, useLibraryMutation, useLibraryQuery } from '@sd/client';
import { explorerStore, useLibraryMutation, useLibraryQuery } from '@sd/client';
import { ExplorerData } from '@sd/core';
import {
ArrowBendUpRight,
@ -11,6 +11,7 @@ import {
TrashSimple
} from 'phosphor-react';
import React from 'react';
import { useSnapshot } from 'valtio';
import { WithContextMenu } from '../layout/MenuOverlay';
@ -19,7 +20,7 @@ interface Props {
}
export default function ExplorerContextMenu(props: Props) {
const contextMenuObjectId = useExplorerStore((store) => store.contextMenuObjectId);
const contextMenuObjectId = useSnapshot(explorerStore).contextMenuObjectId;
const { data: tags } = useLibraryQuery(['tags.getAll'], {});

View file

@ -1,7 +1,8 @@
import { useExplorerStore } from '@sd/client';
import { explorerStore } from '@sd/client';
import { ExplorerItem } from '@sd/core';
import clsx from 'clsx';
import React from 'react';
import { useSnapshot } from 'valtio';
import FileThumb from './FileThumb';
import { isObject } from './utils';
@ -13,17 +14,17 @@ interface Props extends React.HTMLAttributes<HTMLDivElement> {
}
function FileItem(props: Props) {
const set = useExplorerStore.getState().set;
const size = useExplorerStore((state) => state.gridItemSize) || 100;
const { gridItemSize } = useSnapshot(explorerStore);
return (
<div
onContextMenu={(e) => {
const objectId = isObject(props.data) ? props.data.id : props.data.file?.id;
if (objectId != undefined) {
set({ contextMenuObjectId: objectId });
if (props.index != undefined) set({ selectedRowIndex: props.index });
explorerStore.contextMenuObjectId = objectId;
if (props.index != undefined) {
explorerStore.selectedRowIndex = props.index;
}
}
}}
draggable
@ -31,7 +32,7 @@ function FileItem(props: Props) {
className={clsx('inline-block w-[100px] mb-3', props.className)}
>
<div
style={{ width: size, height: size }}
style={{ width: gridItemSize, height: gridItemSize }}
className={clsx(
'border-2 border-transparent rounded-lg text-center mb-1 active:translate-y-[1px]',
{
@ -49,7 +50,7 @@ function FileItem(props: Props) {
'border-4 border-gray-250 rounded-sm shadow-md shadow-gray-750 max-h-full max-w-full overflow-hidden'
)}
data={props.data}
size={size}
size={gridItemSize}
/>
</div>
</div>

View file

@ -1,5 +1,5 @@
import { EllipsisHorizontalIcon } from '@heroicons/react/24/solid';
import { LocationContext, useBridgeQuery, useExplorerStore, useLibraryQuery } from '@sd/client';
import { LocationContext, explorerStore, useBridgeQuery, useLibraryQuery } from '@sd/client';
import { ExplorerContext, ExplorerItem, FilePath } from '@sd/core';
import { useVirtualizer } from '@tanstack/react-virtual';
import clsx from 'clsx';

View file

@ -1,7 +1,8 @@
import { AppPropsContext, useExplorerStore } from '@sd/client';
import { AppPropsContext, explorerStore } from '@sd/client';
import { ExplorerItem } from '@sd/core';
import clsx from 'clsx';
import React, { useContext } from 'react';
import { useSnapshot } from 'valtio';
import icons from '../../assets/icons';
import { Folder } from '../icons/Folder';
@ -16,7 +17,7 @@ interface Props {
export default function FileThumb({ data, ...props }: Props) {
const appProps = useContext(AppPropsContext);
const newThumbnails = useExplorerStore((store) => store.newThumbnails);
const { newThumbnails } = useSnapshot(explorerStore);
if (isPath(data) && data.is_dir) return <Folder size={props.size * 0.7} />;

View file

@ -1,9 +1,10 @@
import { ExplorerLayoutMode, useExplorerStore } from '@sd/client';
import { ExplorerLayoutMode, explorerStore } from '@sd/client';
import { ExplorerContext, ExplorerItem, FilePath } from '@sd/core';
import { useVirtualizer } from '@tanstack/react-virtual';
import React, { memo, useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import { useKey, useWindowSize } from 'rooks';
import { useSnapshot } from 'valtio';
import FileItem from './FileItem';
import FileRow from './FileRow';
@ -23,16 +24,16 @@ export const VirtualizedList: React.FC<Props> = ({ data, context }) => {
const [goingUp, setGoingUp] = useState(false);
const [width, setWidth] = useState(0);
const { gridItemSize, layoutMode, listItemSize, selectedRowIndex } = useExplorerStore(
(state) => ({
selectedRowIndex: state.selectedRowIndex,
gridItemSize: state.gridItemSize,
layoutMode: state.layoutMode,
listItemSize: state.listItemSize
})
);
// const { gridItemSize, layoutMode, listItemSize, selectedRowIndex } = useExplorerStore(
// (state) => ({
// selectedRowIndex: state.selectedRowIndex,
// gridItemSize: state.gridItemSize,
// layoutMode: state.layoutMode,
// listItemSize: state.listItemSize
// })
// );
const set = useExplorerStore.getState().set;
const { gridItemSize, layoutMode, listItemSize, selectedRowIndex } = useSnapshot(explorerStore);
useLayoutEffect(() => {
setWidth(innerRef.current?.offsetWidth || 0);
@ -61,14 +62,14 @@ export const VirtualizedList: React.FC<Props> = ({ data, context }) => {
e.preventDefault();
setGoingUp(true);
if (selectedRowIndex !== -1 && selectedRowIndex !== 0)
set({ selectedRowIndex: selectedRowIndex - 1 });
explorerStore.selectedRowIndex = selectedRowIndex - 1;
});
useKey('ArrowDown', (e) => {
e.preventDefault();
setGoingUp(false);
if (selectedRowIndex !== -1 && selectedRowIndex !== (data.length ?? 1) - 1)
set({ selectedRowIndex: selectedRowIndex + 1 });
explorerStore.selectedRowIndex = selectedRowIndex + 1;
});
// const Header = () => (
@ -164,7 +165,7 @@ const WrappedItem: React.FC<WrappedItemProps> = memo(({ item, index, isSelected,
}, [item, setSearchParams]);
const onClick = useCallback(() => {
useExplorerStore.getState().set({ selectedRowIndex: isSelected ? -1 : index });
explorerStore.selectedRowIndex = isSelected ? -1 : index;
}, [isSelected, index]);
if (kind === 'list') {

View file

@ -1,5 +1,5 @@
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline';
import { AppPropsContext, useExplorerStore, useLibraryMutation } from '@sd/client';
import { AppPropsContext, explorerStore, useLibraryMutation } from '@sd/client';
import { Dropdown } from '@sd/ui';
import clsx from 'clsx';
import {
@ -13,6 +13,7 @@ import {
} from 'phosphor-react';
import React, { DetailedHTMLProps, HTMLAttributes, RefAttributes, useContext } from 'react';
import { useNavigate } from 'react-router-dom';
import { useSnapshot } from 'valtio';
import { Shortcut } from '../primitive/Shortcut';
import { DefaultProps } from '../primitive/types';
@ -80,7 +81,7 @@ const SearchBar = React.forwardRef<HTMLInputElement, DefaultProps>((props, ref)
});
export const TopBar: React.FC<TopBarProps> = (props) => {
const { layoutMode, set, locationId, showInspector } = useExplorerStore();
const { layoutMode, locationId, showInspector } = useSnapshot(explorerStore);
const { mutate: generateThumbsForLocation } = useLibraryMutation(
'jobs.generateThumbsForLocation',
{
@ -159,7 +160,7 @@ export const TopBar: React.FC<TopBarProps> = (props) => {
left
active={layoutMode === 'list'}
icon={Rows}
onClick={() => set({ layoutMode: 'list' })}
onClick={() => (explorerStore.layoutMode = 'list')}
/>
</Tooltip>
<Tooltip label="Grid view">
@ -168,7 +169,7 @@ export const TopBar: React.FC<TopBarProps> = (props) => {
right
active={layoutMode === 'grid'}
icon={SquaresFour}
onClick={() => set({ layoutMode: 'grid' })}
onClick={() => (explorerStore.layoutMode = 'grid')}
/>
</Tooltip>
</div>
@ -194,7 +195,7 @@ export const TopBar: React.FC<TopBarProps> = (props) => {
<div className="flex mr-3 space-x-2">
<TopBarButton
active={showInspector}
onClick={() => set({ showInspector: !showInspector })}
onClick={() => (explorerStore.showInspector = !showInspector)}
className="my-2"
icon={SidebarSimple}
/>

View file

@ -1,5 +1,5 @@
/* eslint-disable react-hooks/exhaustive-deps */
import { useExplorerStore, useLibraryQuery, useLibraryStore } from '@sd/client';
import { explorerStore, useLibraryQuery, useLibraryStore } from '@sd/client';
import React, { useEffect } from 'react';
import { useParams, useSearchParams } from 'react-router-dom';
import z from 'zod';
@ -20,10 +20,8 @@ export function useExplorerParams() {
export const LocationExplorer: React.FC<unknown> = () => {
const { location_id, path } = useExplorerParams();
// for top bar location context, could be replaced with react context as it is child component
const set = useExplorerStore((state) => state.set);
useEffect(() => {
set({ locationId: location_id });
explorerStore.locationId = location_id;
}, [location_id]);
const library_id = useLibraryStore((state) => state.currentLibraryUuid);

View file

@ -236,6 +236,8 @@ importers:
scripts: '*'
tsconfig: '*'
typescript: ^4.7.4
valtio: ^1.6.4
valtio-persist: ^1.0.2
zustand: 4.0.0
dependencies:
'@rspc/client': 0.0.5
@ -246,6 +248,8 @@ importers:
eventemitter3: 4.0.7
immer: 9.0.15
lodash: 4.17.21
valtio: 1.6.4
valtio-persist: 1.0.2_valtio@1.6.4
zustand: 4.0.0_immer@9.0.15
devDependencies:
'@types/lodash': 4.14.182
@ -340,6 +344,8 @@ importers:
typescript: ^4.7.4
use-count-up: ^3.0.1
use-debounce: ^8.0.3
valtio: ^1.6.4
valtio-persist: ^1.0.2
vite: ^3.0.3
vite-plugin-svgr: ^2.2.1
zod: ^3.18.0
@ -369,7 +375,7 @@ importers:
byte-size: 8.1.0
clsx: 1.2.1
immer: 9.0.15
jotai: 1.8.2_immer@9.0.15+react@18.2.0
jotai: 1.8.2_umupx7nluxw42lgh4svggrlwmq
lodash: 4.17.21
moment: 2.29.4
phosphor-react: 1.4.1_react@18.2.0
@ -398,6 +404,8 @@ importers:
tailwindcss: 3.1.8
use-count-up: 3.0.1_react@18.2.0
use-debounce: 8.0.4_react@18.2.0
valtio: 1.6.4_react@18.2.0+vite@3.0.9
valtio-persist: 1.0.2_valtio@1.6.4
zod: 3.18.0
zustand: 4.0.0_immer@9.0.15+react@18.2.0
devDependencies:
@ -12458,7 +12466,7 @@ packages:
regenerator-runtime: 0.13.9
dev: true
/jotai/1.8.2_immer@9.0.15+react@18.2.0:
/jotai/1.8.2_umupx7nluxw42lgh4svggrlwmq:
resolution: {integrity: sha512-jRKTQKChLAJGB3zG5zklJoMtpWYixUi2jK3pZd5ZRpH4pQnjYYEOAVYCzZGr4Ce5Uj7j0ffYoLM4ULAAQXUoGw==}
engines: {node: '>=12.7.0'}
peerDependencies:
@ -12494,6 +12502,7 @@ packages:
dependencies:
immer: 9.0.15
react: 18.2.0
valtio: 1.6.4_react@18.2.0+vite@3.0.9
dev: false
/jpeg-js/0.4.2:
@ -14942,6 +14951,10 @@ packages:
forwarded: 0.2.0
ipaddr.js: 1.9.1
/proxy-compare/2.3.0:
resolution: {integrity: sha512-c3L2CcAi7f7pvlD0D7xsF+2CQIW8C3HaYx2Pfgq8eA4HAl3GAH6/dVYsyBbYF/0XJs2ziGLrzmz5fmzPm6A0pQ==}
dev: false
/prr/1.0.1:
resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
dev: true
@ -18311,6 +18324,73 @@ packages:
spdx-expression-parse: 3.0.1
dev: true
/valtio-persist/1.0.2_valtio@1.6.4:
resolution: {integrity: sha512-OBVEUZTS1heiA5R3j8CPDuXIMmmjIvq/4CiO+pElXd7f7b7nR3vIH5qql35hXw/AkLdftqTUcVCNVf6yAJ1ypA==}
peerDependencies:
valtio: ^1.2.5
dependencies:
lodash: 4.17.21
valtio: 1.6.4
dev: false
/valtio/1.6.4:
resolution: {integrity: sha512-wEslN1bO1ihuv2Xj4jIxkGaDgsnwSvZdcaYcJ6aSwaNYU0ZMe+eZipfEzbTqd4CwnMu7ihDBNes0ZP5CIl1NqA==}
engines: {node: '>=12.7.0'}
peerDependencies:
'@babel/helper-module-imports': '>=7.12'
'@babel/types': '>=7.13'
aslemammad-vite-plugin-macro: '>=1.0.0-alpha.1'
babel-plugin-macros: '>=3.0'
react: '>=16.8'
vite: '>=2.8.6'
peerDependenciesMeta:
'@babel/helper-module-imports':
optional: true
'@babel/types':
optional: true
aslemammad-vite-plugin-macro:
optional: true
babel-plugin-macros:
optional: true
react:
optional: true
vite:
optional: true
dependencies:
proxy-compare: 2.3.0
use-sync-external-store: 1.2.0
dev: false
/valtio/1.6.4_react@18.2.0+vite@3.0.9:
resolution: {integrity: sha512-wEslN1bO1ihuv2Xj4jIxkGaDgsnwSvZdcaYcJ6aSwaNYU0ZMe+eZipfEzbTqd4CwnMu7ihDBNes0ZP5CIl1NqA==}
engines: {node: '>=12.7.0'}
peerDependencies:
'@babel/helper-module-imports': '>=7.12'
'@babel/types': '>=7.13'
aslemammad-vite-plugin-macro: '>=1.0.0-alpha.1'
babel-plugin-macros: '>=3.0'
react: '>=16.8'
vite: '>=2.8.6'
peerDependenciesMeta:
'@babel/helper-module-imports':
optional: true
'@babel/types':
optional: true
aslemammad-vite-plugin-macro:
optional: true
babel-plugin-macros:
optional: true
react:
optional: true
vite:
optional: true
dependencies:
proxy-compare: 2.3.0
react: 18.2.0
use-sync-external-store: 1.2.0_react@18.2.0
vite: 3.0.9
dev: false
/varname/2.0.3:
resolution: {integrity: sha512-+DofT9mJAUALhnr9ipZ5Z2icwaEZ7DAajOZT4ffXy3MQqnXtG3b7atItLQEJCkfcJTOf9WcsywneOEibD4eqJg==}
engines: {node: '>=0.10'}