mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-04-23 07:59:59 -04:00
removed old state code, replaced with react-query
This commit is contained in:
@@ -9,16 +9,11 @@ import {
|
||||
useNavigate
|
||||
} from 'react-router-dom';
|
||||
import { Sidebar } from './components/file/Sidebar';
|
||||
import { TopBar } from './components/layout/TopBar';
|
||||
import { SettingsScreen } from './screens/Settings';
|
||||
import { ExplorerScreen } from './screens/Explorer';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import { DebugGlobalStore } from './Debug';
|
||||
import { useCoreEvents } from './hooks/useCoreEvents';
|
||||
import { AppState, useAppState } from './store/global';
|
||||
import { Button } from './components/primitive';
|
||||
import { ErrorBoundary, FallbackProps } from 'react-error-boundary';
|
||||
import { useLocationStore } from './store/locations';
|
||||
import { OverviewScreen } from './screens/Overview';
|
||||
import { SpacesScreen } from './screens/Spaces';
|
||||
import { Modal } from './components/layout/Modal';
|
||||
@@ -138,29 +133,21 @@ function NotFound() {
|
||||
// process.exit();
|
||||
// });
|
||||
|
||||
export default function App() {
|
||||
function AppContainer() {
|
||||
useCoreEvents();
|
||||
|
||||
useEffect(() => {
|
||||
invoke<AppState>('get_config').then((state) => useAppState.getState().update(state));
|
||||
// invoke<Location[]>('get_mounts').then((locations) =>
|
||||
// //@ts-expect-error
|
||||
// useLocationStore.getState().setLocations(locations)
|
||||
// );
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ErrorBoundary
|
||||
FallbackComponent={ErrorFallback}
|
||||
// reset the state of your app so the error doesn't happen again
|
||||
onReset={() => {}}
|
||||
>
|
||||
<DebugGlobalStore />
|
||||
<BrowserRouter>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<Router />
|
||||
</QueryClientProvider>
|
||||
</BrowserRouter>
|
||||
<BrowserRouter>
|
||||
<Router />
|
||||
</BrowserRouter>
|
||||
);
|
||||
}
|
||||
|
||||
export default function App() {
|
||||
return (
|
||||
<ErrorBoundary FallbackComponent={ErrorFallback} onReset={() => {}}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<AppContainer />
|
||||
</QueryClientProvider>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import React from 'react';
|
||||
import { useAppState } from './store/global';
|
||||
import { useExplorerStore } from './store/explorer';
|
||||
|
||||
export function DebugGlobalStore() {
|
||||
useAppState();
|
||||
useExplorerStore();
|
||||
return <></>;
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import clsx from 'clsx';
|
||||
import { CirclesFour, EjectSimple, MonitorPlay, Planet } from 'phosphor-react';
|
||||
import React from 'react';
|
||||
import { NavLink, NavLinkProps } from 'react-router-dom';
|
||||
import { useLocations } from '../../store/locations';
|
||||
import { TrafficLights } from '../os/TrafficLights';
|
||||
import { Button } from '../primitive';
|
||||
import { Dropdown } from '../primitive/Dropdown';
|
||||
|
||||
@@ -1,19 +1,26 @@
|
||||
import { useEffect } from 'react';
|
||||
import { emit, listen, Event } from '@tauri-apps/api/event';
|
||||
import { useExplorerStore } from '../store/explorer';
|
||||
import { ClientEvent } from '@sd/core';
|
||||
// import { useExplorerStore } from '../store/explorer';
|
||||
import { CoreEvent } from '@sd/core';
|
||||
import { useQuery, useQueryClient } from 'react-query';
|
||||
|
||||
export function useCoreEvents() {
|
||||
const client = useQueryClient();
|
||||
useEffect(() => {
|
||||
listen('core_event', (e: Event<ClientEvent>) => {
|
||||
listen('core_event', (e: Event<CoreEvent>) => {
|
||||
console.log({ e });
|
||||
|
||||
switch (e.payload?.key) {
|
||||
case 'new_file_type_thumb':
|
||||
console.log(e.payload?.payload.file_id);
|
||||
|
||||
if (e.payload?.payload.icon_created)
|
||||
useExplorerStore.getState().nativeIconUpdated(e.payload.payload.file_id);
|
||||
case 'InvalidateQuery':
|
||||
let query = [e.payload.data.key];
|
||||
// TODO: find a way to make params accessible in TS
|
||||
// also this method will only work for queries that use the whole params obj as the second key
|
||||
// @ts-expect-error
|
||||
if (e.payload.data.params) {
|
||||
// @ts-expect-error
|
||||
query.push(e.payload.data.params);
|
||||
}
|
||||
client.invalidateQueries([e.payload.data.key]);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
@@ -1,35 +1,16 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import { FileList } from '../components/file/FileList';
|
||||
// import { FileList } from '../components/file/FileList';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import { IFile } from '../types';
|
||||
import { useExplorerStore } from '../store/explorer';
|
||||
import { Inspector } from '../components/file/Inspector';
|
||||
|
||||
export interface DirectoryResponse {
|
||||
directory: IFile;
|
||||
contents: IFile[];
|
||||
}
|
||||
import { Inspector } from '../components/file/Inspector';
|
||||
|
||||
export const ExplorerScreen: React.FC<{}> = () => {
|
||||
// let { slug } = useParams();
|
||||
const [currentDir, tempWatchDir] = useExplorerStore((state) => [
|
||||
state.currentDir,
|
||||
state.tempWatchDir
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
invoke<DirectoryResponse>('get_files', { path: tempWatchDir }).then((res) => {
|
||||
console.log({ res });
|
||||
useExplorerStore.getState().ingestDir(res.directory, res.contents);
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (currentDir === null) return <></>;
|
||||
|
||||
return (
|
||||
<div className="relative flex flex-row w-full bg-white dark:bg-gray-900">
|
||||
<FileList />
|
||||
<Inspector />
|
||||
{/* <FileList /> */}
|
||||
{/* <Inspector /> */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React, { useState } from 'react';
|
||||
import ReactJson from 'react-json-view';
|
||||
import FileItem from '../components/file/FileItem';
|
||||
import { useAppState } from '../store/global';
|
||||
|
||||
interface StatItemProps {
|
||||
name: string;
|
||||
@@ -23,7 +22,6 @@ const StatItem: React.FC<StatItemProps> = (props) => {
|
||||
|
||||
export const OverviewScreen: React.FC<{}> = (props) => {
|
||||
const [selectedFile, setSelectedFile] = useState<null | string>(null);
|
||||
const app = useAppState();
|
||||
|
||||
function handleSelect(key: string) {
|
||||
// if (selectedFile === key) setSelectedFile(null);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { useBridgeQuery } from '@sd/state';
|
||||
import React from 'react';
|
||||
import ReactJson from 'react-json-view';
|
||||
import FileItem from '../components/file/FileItem';
|
||||
import { Tag } from '../components/primitive/Tag';
|
||||
import { useAppState } from '../store/global';
|
||||
|
||||
export const SpacesScreen: React.FC<{}> = (props) => {
|
||||
const app = useAppState();
|
||||
const { data: client } = useBridgeQuery('ClientGetState');
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center w-full h-screen px-2 py-5">
|
||||
<div className="mt-2 mb-24 select-text">
|
||||
@@ -15,7 +15,7 @@ export const SpacesScreen: React.FC<{}> = (props) => {
|
||||
enableClipboard={false}
|
||||
displayDataTypes={false}
|
||||
theme="ocean"
|
||||
src={app.config}
|
||||
src={{ ...client }}
|
||||
style={{
|
||||
padding: 20,
|
||||
borderRadius: 5,
|
||||
|
||||
@@ -2,25 +2,21 @@ import { InputContainer } from '../../components/primitive/InputContainer';
|
||||
import { Button, Input } from '../../components/primitive';
|
||||
import { invoke } from '@tauri-apps/api';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useExplorerStore } from '../../store/explorer';
|
||||
import { useAppState } from '../../store/global';
|
||||
|
||||
import Listbox from '../../components/primitive/Listbox';
|
||||
import { useLocations } from '../../store/locations';
|
||||
|
||||
import ReactJson from 'react-json-view';
|
||||
import Slider from '../../components/primitive/Slider';
|
||||
import { useBridgeQuery } from '@sd/state';
|
||||
|
||||
export default function GeneralSettings() {
|
||||
const locations = useLocations();
|
||||
const { data: volumes } = useBridgeQuery('SysGetVolumes');
|
||||
const [tempWatchDir, setTempWatchDir] = useState('');
|
||||
|
||||
const [fakeSliderVal, setFakeSliderVal] = useState([30, 0]);
|
||||
|
||||
// const fileUploader = useRef<HTMLInputElement | null>(null);
|
||||
const app = useAppState();
|
||||
|
||||
const [tempWatchDir, setTempWatchDir] = useExplorerStore((state) => [
|
||||
state.tempWatchDir,
|
||||
state.setTempWatchDir
|
||||
]);
|
||||
const { data: client } = useBridgeQuery('ClientGetState');
|
||||
|
||||
return (
|
||||
<div className="flex flex-col max-w-2xl space-y-4">
|
||||
@@ -47,7 +43,7 @@ export default function GeneralSettings() {
|
||||
variant="primary"
|
||||
onClick={async () => {
|
||||
await invoke('scan_dir', {
|
||||
path: tempWatchDir
|
||||
path: client?.data_path
|
||||
});
|
||||
}}
|
||||
>
|
||||
@@ -62,11 +58,13 @@ export default function GeneralSettings() {
|
||||
<div className="flex flex-row space-x-2">
|
||||
<div className="flex flex-grow">
|
||||
<Listbox
|
||||
options={locations.map((location) => ({
|
||||
key: location.name,
|
||||
option: location.name,
|
||||
description: location.path
|
||||
}))}
|
||||
options={
|
||||
volumes?.map((volume) => ({
|
||||
key: volume.name,
|
||||
option: volume.name,
|
||||
description: volume.mount_point
|
||||
})) ?? []
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Button className="mb-3" variant="primary">
|
||||
@@ -92,7 +90,7 @@ export default function GeneralSettings() {
|
||||
<div className="flex flex-row">
|
||||
<Input
|
||||
className="flex-grow"
|
||||
value={app.config.data_dir}
|
||||
value={'uuuuuu'}
|
||||
placeholder="/users/jamie/Library/Application Support/spacedrive/cache"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import React from 'react';
|
||||
import { Button } from '../../components/primitive';
|
||||
import { useLocations } from '../../store/locations';
|
||||
import Listbox from '../../components/primitive/Listbox';
|
||||
import { InputContainer } from '../../components/primitive/InputContainer';
|
||||
|
||||
const exampleLocations = [
|
||||
{ option: 'Macintosh HD', key: 'macintosh_hd' },
|
||||
{ option: 'Lacie External', key: 'lacie_external' },
|
||||
{ option: 'LaCie External', key: 'lacie_external' },
|
||||
{ option: 'Seagate 8TB', key: 'seagate_8tb' }
|
||||
];
|
||||
|
||||
export default function LocationSettings() {
|
||||
const locations = useLocations();
|
||||
// const locations = useBridgeQuery("SysGetLocation")
|
||||
|
||||
return (
|
||||
<div className="max-w-md">
|
||||
@@ -22,13 +20,13 @@ export default function LocationSettings() {
|
||||
>
|
||||
<div className="flex flex-row space-x-2">
|
||||
<div className="flex flex-grow">
|
||||
<Listbox
|
||||
{/* <Listbox
|
||||
options={locations.map((location) => ({
|
||||
key: location.name,
|
||||
option: location.name,
|
||||
description: location.path
|
||||
}))}
|
||||
/>
|
||||
/> */}
|
||||
</div>
|
||||
<Button className="mb-3" variant="primary">
|
||||
Add Location
|
||||
|
||||
@@ -1,184 +0,0 @@
|
||||
import create from 'zustand';
|
||||
import { IDirectory, IFile } from '../types';
|
||||
import produce from 'immer';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
interface SelectedFile {
|
||||
id: number;
|
||||
index?: number;
|
||||
}
|
||||
|
||||
interface ExplorerStore {
|
||||
// storage
|
||||
files: Record<number, IFile>;
|
||||
// indexes
|
||||
dirs: Record<number, number[]>;
|
||||
// ingest
|
||||
ingestDir: (dir: IFile, children: IFile[]) => void;
|
||||
// selection
|
||||
selectedFile: SelectedFile | null;
|
||||
selectedFiles: SelectedFile[];
|
||||
selectedFilesHistory: SelectedFile[][];
|
||||
selectFile: (
|
||||
dirId: number,
|
||||
fileId: number,
|
||||
type?: 'below' | 'above',
|
||||
specificIndex?: number
|
||||
) => void;
|
||||
clearSelectedFiles: () => void;
|
||||
// selectAnotherFile?: (fileId: number) => void;
|
||||
// selectFilesBetween?: (firstFileId: number, secondFileId: number) => void;
|
||||
// explorer state
|
||||
currentDir: number | null;
|
||||
dirHistory: number[];
|
||||
goBack?: () => void;
|
||||
goForward?: () => void;
|
||||
nativeIconUpdated: (fileId: number) => void;
|
||||
|
||||
tempWatchDir: string;
|
||||
setTempWatchDir: (path: string) => void;
|
||||
}
|
||||
|
||||
export const useExplorerStore = create<ExplorerStore>((set, get) => ({
|
||||
files: {},
|
||||
dirs: {},
|
||||
selectedFile: null,
|
||||
selectedFileIndex: null,
|
||||
selectedFiles: [],
|
||||
selectedFilesHistory: [],
|
||||
currentDir: null,
|
||||
dirHistory: [],
|
||||
tempWatchDir: '/Users/jamie',
|
||||
setTempWatchDir: (path) =>
|
||||
set((state) =>
|
||||
produce(state, (draft) => {
|
||||
draft.tempWatchDir = path;
|
||||
})
|
||||
),
|
||||
|
||||
ingestDir: (dir, children) => {
|
||||
set((state) =>
|
||||
produce(state, (draft) => {
|
||||
draft.files[dir.id] = dir;
|
||||
// extract directory index of file ids
|
||||
draft.dirs[dir.id] = children.map((file) => file.id);
|
||||
// save files by id
|
||||
for (const index in children) {
|
||||
const child = children[index];
|
||||
draft.files[child.id] = child;
|
||||
}
|
||||
// if this dir in the history stack, remove history since then
|
||||
const existingDirHistoryIndex = draft.dirHistory.findIndex((i) => i === dir.id);
|
||||
if (existingDirHistoryIndex != -1) {
|
||||
draft.dirHistory.splice(existingDirHistoryIndex);
|
||||
}
|
||||
// push onto history stack
|
||||
draft.dirHistory.push(dir.id);
|
||||
|
||||
draft.currentDir = dir.id;
|
||||
})
|
||||
);
|
||||
},
|
||||
goBack: () => {
|
||||
set((state) =>
|
||||
produce(state, (draft) => {
|
||||
const prevDirId = draft.dirHistory[draft.dirHistory.length - 2];
|
||||
if (prevDirId == undefined) return;
|
||||
draft.currentDir = prevDirId;
|
||||
})
|
||||
);
|
||||
},
|
||||
goForward: () => {
|
||||
set((state) =>
|
||||
produce(state, (draft) => {
|
||||
const nextDirId = draft.dirHistory[draft.dirHistory.length];
|
||||
if (nextDirId == undefined) return;
|
||||
draft.currentDir = nextDirId;
|
||||
})
|
||||
);
|
||||
},
|
||||
selectFile: (dirId, fileId, type, specificIndex) => {
|
||||
set((state) =>
|
||||
produce(state, (draft) => {
|
||||
if (!draft.files[fileId]) return;
|
||||
const dirIndex = get().dirs[dirId];
|
||||
const maxIndex = dirIndex.length - 1;
|
||||
// discover index within active directory
|
||||
|
||||
const currentIndex =
|
||||
state.selectedFile?.index !== undefined
|
||||
? state.selectedFile.index
|
||||
: (() => {
|
||||
console.log('FINDING INDEX');
|
||||
|
||||
return dirIndex.findIndex((i) => i === fileId);
|
||||
})();
|
||||
// console.log('selecting file', { fileId, dirIndex, maxIndex, currentIndex });
|
||||
// if no type just select specified file
|
||||
if (!type) {
|
||||
draft.selectedFile = { id: fileId, index: specificIndex };
|
||||
return;
|
||||
}
|
||||
switch (type) {
|
||||
case 'above':
|
||||
if (currentIndex - 1 < 0)
|
||||
draft.selectedFile = { id: dirIndex[maxIndex], index: maxIndex };
|
||||
else draft.selectedFile = { id: dirIndex[currentIndex - 1], index: currentIndex - 1 };
|
||||
break;
|
||||
case 'below':
|
||||
if (currentIndex + 1 > maxIndex) draft.selectedFile = { id: dirIndex[0], index: 0 };
|
||||
else draft.selectedFile = { id: dirIndex[currentIndex + 1], index: currentIndex + 1 };
|
||||
break;
|
||||
}
|
||||
})
|
||||
);
|
||||
},
|
||||
clearSelectedFiles: () => {
|
||||
set((state) =>
|
||||
produce(state, (draft) => {
|
||||
draft.selectedFile = null;
|
||||
})
|
||||
);
|
||||
},
|
||||
nativeIconUpdated: (fileId: number) => {
|
||||
set((state) =>
|
||||
produce(state, (draft) => {
|
||||
if (!draft.files[fileId]) return;
|
||||
draft.files[fileId].has_native_icon = true;
|
||||
})
|
||||
);
|
||||
}
|
||||
}));
|
||||
|
||||
export function useSelectedFile(): null | IFile {
|
||||
const [file] = useExplorerStore((state) => [state.files[state.selectedFile?.id || -1]]);
|
||||
return file;
|
||||
}
|
||||
|
||||
export function useSelectedFileIndex(dirId: number): null | number {
|
||||
return useExplorerStore((state) => {
|
||||
const index = state.selectedFile?.index;
|
||||
if (index === undefined) return null;
|
||||
return index;
|
||||
});
|
||||
}
|
||||
|
||||
export function useFile(fileId: number): null | IFile {
|
||||
return useExplorerStore((state) => state.files[fileId || -1]);
|
||||
}
|
||||
|
||||
export function useCurrentDir(): IDirectory | null {
|
||||
return useExplorerStore((state) => {
|
||||
const children = useMemo(
|
||||
() => state.dirs[state.currentDir || -1].map((id) => state.files[id]),
|
||||
[state.currentDir]
|
||||
);
|
||||
const directory = state.files[state.currentDir || -1];
|
||||
|
||||
return {
|
||||
...directory,
|
||||
children,
|
||||
children_count: children.length
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import produce from 'immer';
|
||||
import create from 'zustand';
|
||||
|
||||
export interface AppState {
|
||||
config: {
|
||||
primary_db?: string;
|
||||
data_dir?: string;
|
||||
file_type_thumb_dir?: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface AppStoreState extends AppState {
|
||||
update: (newObj: AppState) => void;
|
||||
}
|
||||
|
||||
export const useAppState = create<AppStoreState>((set, get) => ({
|
||||
config: {},
|
||||
update: (newObj) => {
|
||||
set((store) =>
|
||||
produce(store, (draft) => {
|
||||
Object.keys(newObj).forEach((key) => {
|
||||
//@ts-ignore
|
||||
draft.config[key as keyof AppState] = newObj[key];
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
}));
|
||||
@@ -1,11 +0,0 @@
|
||||
import create from 'zustand';
|
||||
|
||||
interface AppState {
|
||||
bears: number;
|
||||
}
|
||||
|
||||
const useAppStateStore = create<AppState>((set) => ({
|
||||
bears: 0,
|
||||
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
|
||||
removeAllBears: () => set({ bears: 0 })
|
||||
}));
|
||||
@@ -1,34 +0,0 @@
|
||||
import create from 'zustand';
|
||||
import immer, { produce } from 'immer';
|
||||
|
||||
export interface Location {
|
||||
id: string;
|
||||
name: string;
|
||||
mount_point: string;
|
||||
total_capacity: number;
|
||||
available_capacity: number;
|
||||
is_removable: boolean;
|
||||
is_ejectable: boolean;
|
||||
is_root_filesystem: boolean;
|
||||
}
|
||||
|
||||
interface LocationStore {
|
||||
locations: Record<string, Location>;
|
||||
setLocations: (locations: Location[]) => void;
|
||||
}
|
||||
|
||||
export const useLocationStore = create<LocationStore>((set, get) => ({
|
||||
locations: {},
|
||||
setLocations: (locations) =>
|
||||
set((state) =>
|
||||
produce(state, (draft) => {
|
||||
for (let location of locations) {
|
||||
draft.locations[location.mount_point] = location;
|
||||
}
|
||||
})
|
||||
)
|
||||
}));
|
||||
|
||||
export const useLocations = () => {
|
||||
return useLocationStore((store) => Object.values(store.locations));
|
||||
};
|
||||
@@ -1,31 +0,0 @@
|
||||
// import create from 'zustand';
|
||||
// import immer, { produce } from 'immer';
|
||||
|
||||
// export interface Resource {
|
||||
// id: string;
|
||||
// }
|
||||
|
||||
// interface ResourceStore<R = Resource> {
|
||||
// locations: Record<string, R>;
|
||||
// setResources: (resource: R[]) => void;
|
||||
// }
|
||||
|
||||
// export const useResourceStore = create<ResourceStore>((set, get) => ({
|
||||
// locations: {},
|
||||
// setResources: (locations) =>
|
||||
// set((state) =>
|
||||
// produce(state, (draft) => {
|
||||
// for (let location of locations) {
|
||||
// draft.locations[location.path] = location;
|
||||
// }
|
||||
// })
|
||||
// )
|
||||
// }));
|
||||
|
||||
// export const useResources = () => {
|
||||
// return useResourceStore((store) => Object.values(store.locations));
|
||||
// };
|
||||
|
||||
// export function createResource<R extends Resource>() {
|
||||
|
||||
// }
|
||||
@@ -1,40 +0,0 @@
|
||||
import { Encryption } from './library';
|
||||
import { ImageMeta, VideoMeta } from './media';
|
||||
|
||||
export interface IFile {
|
||||
id: number;
|
||||
meta_integrity_hash: string;
|
||||
uri: string;
|
||||
is_dir: string;
|
||||
|
||||
date_created: Date;
|
||||
date_modified: Date;
|
||||
date_indexed: Date;
|
||||
|
||||
name: string;
|
||||
extension: string;
|
||||
size_in_bytes: string;
|
||||
|
||||
library_id: string;
|
||||
ipfs_id: string;
|
||||
storage_device_id: string;
|
||||
capture_device_id: string;
|
||||
parent_id: string;
|
||||
tags?: ITag[];
|
||||
|
||||
// this state is used to tell the renderer to look in the designated
|
||||
// folder for this media type
|
||||
has_native_icon?: boolean;
|
||||
has_thumb?: boolean;
|
||||
has_preview_media?: boolean;
|
||||
icon_b64?: string;
|
||||
}
|
||||
|
||||
export interface IDirectory extends IFile {
|
||||
children?: IFile[];
|
||||
children_count: number;
|
||||
}
|
||||
|
||||
export interface ITag {
|
||||
id: string;
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
export * from './library';
|
||||
export * from './filesystem';
|
||||
export * from './job';
|
||||
export * from './media';
|
||||
@@ -1,22 +0,0 @@
|
||||
export interface Job {
|
||||
// A job is used to define a task for the software to complete
|
||||
// These are intended to be stored in memory, or not persisted permanently
|
||||
object_ids: string[]; // array of object ids that concern this job
|
||||
type: JobType;
|
||||
created_at: Date;
|
||||
completed_at: Date;
|
||||
canceled_at: Date;
|
||||
parent_job_id: string;
|
||||
}
|
||||
|
||||
export enum JobType {
|
||||
SCAN,
|
||||
IMPORT,
|
||||
ENCRYPT,
|
||||
DECRYPT,
|
||||
COPY,
|
||||
MOVE,
|
||||
DELETE,
|
||||
RENDER_VIDEO,
|
||||
RENDER_IMAGE
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
export interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
|
||||
google_access_token: string;
|
||||
google_refresh_token: string;
|
||||
}
|
||||
|
||||
export interface Library {
|
||||
id: string;
|
||||
name: string;
|
||||
object_count: number;
|
||||
total_size: number;
|
||||
encryption: Encryption;
|
||||
|
||||
public: boolean;
|
||||
date_created: Date;
|
||||
}
|
||||
|
||||
export interface UserLibrary {
|
||||
library_id: string;
|
||||
user_id: string;
|
||||
date_joined: Date;
|
||||
role: UserLibraryRole;
|
||||
}
|
||||
|
||||
export enum Encryption {
|
||||
NONE,
|
||||
'128-AES',
|
||||
'192-AES',
|
||||
'256-AES'
|
||||
}
|
||||
|
||||
export enum UserLibraryRole {
|
||||
OWNER,
|
||||
READ_ONLY
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
export interface ImageMeta {
|
||||
type: 'image';
|
||||
dimensions: {
|
||||
width: string;
|
||||
height: string;
|
||||
};
|
||||
color_space: string;
|
||||
aperture: number;
|
||||
exposure_mode: number;
|
||||
exposure_program: number;
|
||||
f_number: number;
|
||||
flash: boolean;
|
||||
focal_length: number;
|
||||
has_alpha_channel: boolean;
|
||||
iso_speed: number;
|
||||
orientation: number;
|
||||
metering_mode: number;
|
||||
}
|
||||
|
||||
export interface VideoMeta {
|
||||
type: 'video';
|
||||
codecs: Array<string>;
|
||||
bitrate: {
|
||||
video: string;
|
||||
audio: string;
|
||||
};
|
||||
duration_seconds: number;
|
||||
}
|
||||
|
||||
export interface AudioMeta {}
|
||||
3
packages/core/bindings/ClientState.ts
Normal file
3
packages/core/bindings/ClientState.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import type { LibraryState } from "./LibraryState";
|
||||
|
||||
export interface ClientState { client_id: string, client_name: string, data_path: string, tcp_port: number, libraries: Array<LibraryState>, current_library_id: string, }
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ClientQuery } from "./ClientQuery";
|
||||
import type { CoreResource } from "./CoreResource";
|
||||
|
||||
export type CoreEvent = { key: "InvalidateQuery", payload: ClientQuery } | { key: "InvalidateResource", payload: CoreResource } | { key: "Log", payload: { message: string, } } | { key: "DatabaseDisconnected", payload: { reason: string | null, } };
|
||||
export type CoreEvent = { key: "InvalidateQuery", data: ClientQuery } | { key: "InvalidateResource", data: CoreResource } | { key: "Log", data: { message: string, } } | { key: "DatabaseDisconnected", data: { reason: string | null, } };
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { ClientState } from "./ClientState";
|
||||
import type { Directory } from "./Directory";
|
||||
import type { LocationResource } from "./LocationResource";
|
||||
import type { Volume } from "./Volume";
|
||||
|
||||
export type CoreResponse = { key: "Success" } | { key: "SysGetVolumes", data: Array<Volume> } | { key: "SysGetLocations", data: LocationResource } | { key: "LibGetExplorerDir", data: Directory };
|
||||
export type CoreResponse = { key: "Success" } | { key: "SysGetVolumes", data: Array<Volume> } | { key: "SysGetLocations", data: LocationResource } | { key: "LibGetExplorerDir", data: Directory } | { key: "ClientGetState", data: ClientState };
|
||||
2
packages/core/bindings/LibraryState.ts
Normal file
2
packages/core/bindings/LibraryState.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
|
||||
export interface LibraryState { library_id: string, library_path: string, offline: boolean, }
|
||||
@@ -1,9 +1,11 @@
|
||||
export * from './bindings/ClientCommand';
|
||||
export * from './bindings/ClientQuery';
|
||||
export * from './bindings/ClientState';
|
||||
export * from './bindings/CoreEvent';
|
||||
export * from './bindings/CoreResource';
|
||||
export * from './bindings/CoreResponse';
|
||||
export * from './bindings/Directory';
|
||||
export * from './bindings/FileResource';
|
||||
export * from './bindings/LibraryState';
|
||||
export * from './bindings/LocationResource';
|
||||
export * from './bindings/Volume';
|
||||
|
||||
@@ -72,14 +72,19 @@ impl Core {
|
||||
pub async fn command(&self, cmd: ClientCommand) -> Result<CoreResponse, CoreError> {
|
||||
info!("Core command: {:?}", cmd);
|
||||
Ok(match cmd {
|
||||
// CRUD for files
|
||||
ClientCommand::FileRead { id: _ } => todo!(),
|
||||
ClientCommand::FileDelete { id: _ } => todo!(),
|
||||
// CRUD for tags
|
||||
ClientCommand::TagCreate { name: _, color: _ } => todo!(),
|
||||
ClientCommand::TagAssign { file_id: _, tag_id: _ } => todo!(),
|
||||
ClientCommand::TagDelete { id: _ } => todo!(),
|
||||
// scan the contents of a location on the local filesystem
|
||||
ClientCommand::LocScan { id: _ } => todo!(),
|
||||
// CRUD for locations
|
||||
ClientCommand::LocUpdate { id: _, name: _ } => todo!(),
|
||||
ClientCommand::LocDelete { id: _ } => todo!(),
|
||||
// CRUD for libraries
|
||||
ClientCommand::SysVolumeUnmount { id: _ } => todo!(),
|
||||
ClientCommand::LibDelete { id: _ } => todo!(),
|
||||
})
|
||||
@@ -88,14 +93,19 @@ impl Core {
|
||||
pub async fn query(&self, query: ClientQuery) -> Result<CoreResponse, CoreError> {
|
||||
info!("Core query: {:?}", query);
|
||||
Ok(match query {
|
||||
// get system volumes without saving to library
|
||||
ClientQuery::SysGetVolumes => CoreResponse::SysGetVolumes(sys::volumes::get()?),
|
||||
// get location from library
|
||||
ClientQuery::SysGetLocation { id } => CoreResponse::SysGetLocations(sys::locations::get_location(id).await?),
|
||||
// return contents of a directory for the explorer
|
||||
ClientQuery::LibGetExplorerDir { path, limit: _ } => CoreResponse::LibGetExplorerDir(file::retrieve::get_dir_with_contents(&path).await?),
|
||||
ClientQuery::ClientGetState => todo!(),
|
||||
// return the client state from memory
|
||||
ClientQuery::ClientGetState => CoreResponse::ClientGetState(self.state.clone()),
|
||||
ClientQuery::LibGetTags => todo!(),
|
||||
})
|
||||
}
|
||||
// send an event to the client
|
||||
|
||||
pub async fn send(&self, event: CoreEvent) {
|
||||
self.event_sender.send(event).await.unwrap();
|
||||
}
|
||||
@@ -137,7 +147,7 @@ pub enum ClientQuery {
|
||||
|
||||
// represents an event this library can emit
|
||||
#[derive(Serialize, Deserialize, Debug, TS)]
|
||||
#[serde(tag = "key", content = "payload")]
|
||||
#[serde(tag = "key", content = "data")]
|
||||
#[ts(export)]
|
||||
pub enum CoreEvent {
|
||||
// most all events should be once of these two
|
||||
@@ -155,6 +165,7 @@ pub enum CoreResponse {
|
||||
SysGetVolumes(Vec<sys::volumes::Volume>),
|
||||
SysGetLocations(sys::locations::LocationResource),
|
||||
LibGetExplorerDir(file::retrieve::Directory),
|
||||
ClientGetState(ClientState),
|
||||
}
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
|
||||
@@ -4,9 +4,11 @@ use serde::{Deserialize, Serialize};
|
||||
use std::fs;
|
||||
use std::io::{BufReader, Write};
|
||||
use std::sync::RwLock;
|
||||
use ts_rs::TS;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, TS)]
|
||||
#[ts(export)]
|
||||
pub struct ClientState {
|
||||
// client id is a uniquely generated UUID
|
||||
pub client_id: String,
|
||||
@@ -24,7 +26,8 @@ pub struct ClientState {
|
||||
|
||||
pub static CLIENT_STATE_CONFIG_NAME: &str = "client_state.json";
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
|
||||
#[derive(Debug, Serialize, Deserialize, Clone, Default, TS)]
|
||||
#[ts(export)]
|
||||
pub struct LibraryState {
|
||||
pub library_id: String,
|
||||
pub library_path: String,
|
||||
|
||||
Reference in New Issue
Block a user