mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-02 11:13:29 +00:00
(fix) notes: they work predictably
- added lodash for debounce outside react tree - added command function to bridge - updated FAQ
This commit is contained in:
parent
d950a19cc7
commit
ff9df3f8d1
|
@ -88,7 +88,7 @@ impl CoreContext {
|
|||
}
|
||||
pub fn queue_job(&self, job: Box<dyn Job>) {
|
||||
self.internal_sender
|
||||
.send(InternalEvent::JobIngest(job))
|
||||
.send(InternalEvent::JobQueue(job))
|
||||
.unwrap_or_else(|e| {
|
||||
println!("Failed to queue job. {:?}", e);
|
||||
});
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
use crate::{
|
||||
file::indexer::IndexerJob, node::get_nodestate, prisma::location, ClientQuery, CoreContext,
|
||||
CoreEvent,
|
||||
encode::ThumbnailJob,
|
||||
file::{cas::FileIdentifierJob, indexer::IndexerJob},
|
||||
node::get_nodestate,
|
||||
prisma::location,
|
||||
ClientQuery, CoreContext, CoreEvent,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{fs, io, io::Write, path::Path};
|
||||
|
@ -91,6 +94,17 @@ pub async fn new_location_and_scan(
|
|||
path: path.to_string(),
|
||||
}));
|
||||
|
||||
ctx.queue_job(Box::new(FileIdentifierJob {
|
||||
location_id: location.id,
|
||||
path: path.to_string(),
|
||||
}));
|
||||
|
||||
ctx.queue_job(Box::new(ThumbnailJob {
|
||||
location_id: location.id,
|
||||
path: path.to_string(),
|
||||
background: false,
|
||||
}));
|
||||
|
||||
Ok(location)
|
||||
}
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ The users that gain the most value will be creative people whose work produces o
|
|||
|
||||
## How will this make money?
|
||||
|
||||
During the early development and beta stages it will rely on donations via Open Collective and Twitch. However there are plans to create a business around hosted instances of Spacedrive. Otherwise it will remain entirely open and free.
|
||||
We will offer a range of optional services related to hosting, sharing and backing up Spacedrive data, this is slightly down the line, for now we are VC funded. Spacedrive itself will always remain open source and free.
|
||||
|
||||
## What is the stack?
|
||||
|
||||
|
|
|
@ -71,9 +71,16 @@ export function useBridgeCommand<
|
|||
CC extends CCType<K>,
|
||||
CR extends CRType<K>
|
||||
>(key: K, options: UseMutationOptions<ExtractData<CC>> = {}) {
|
||||
return useMutation<ExtractData<CR>, unknown, any>(
|
||||
return useMutation<ExtractData<CR>, unknown, ExtractParams<CC>>(
|
||||
[key],
|
||||
async (vars: ExtractParams<CC>) => await commandBridge(key, vars),
|
||||
async (vars?: ExtractParams<CC>) => await commandBridge(key, vars),
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
export function command<K extends CommandKeyType, CC extends CCType<K>, CR extends CRType<K>>(
|
||||
key: K,
|
||||
vars: ExtractParams<CC>
|
||||
): Promise<ExtractData<CR>> {
|
||||
return commandBridge(key, vars);
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
"clsx": "^1.1.1",
|
||||
"immer": "^9.0.14",
|
||||
"jotai": "^1.7.0",
|
||||
"lodash": "^4.17.21",
|
||||
"moment": "^2.29.3",
|
||||
"phosphor-react": "^1.4.1",
|
||||
"pretty-bytes": "^6.0.0",
|
||||
|
|
|
@ -4,10 +4,12 @@ import { createContext } from 'react';
|
|||
export const AppPropsContext = createContext<AppProps | null>(null);
|
||||
|
||||
export type Platform = 'browser' | 'macOS' | 'windows' | 'linux';
|
||||
export type CdnUrl = 'internal' | string;
|
||||
|
||||
export interface AppProps {
|
||||
transport: BaseTransport;
|
||||
platform: Platform;
|
||||
cdn_url?: CdnUrl;
|
||||
convertFileSrc: (url: string) => string;
|
||||
openDialog: (options: { directory?: boolean }) => Promise<string | string[] | null>;
|
||||
onClose?: () => void;
|
||||
|
|
|
@ -5,10 +5,11 @@ 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, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { useDebounce } from 'rooks';
|
||||
|
||||
import { default as types } from '../../constants/file-types.json';
|
||||
import { updateNote, useInspectorState } from '../../hooks/useInspectorState';
|
||||
import FileThumb from './FileThumb';
|
||||
|
||||
interface MetaItemProps {
|
||||
|
@ -37,32 +38,31 @@ export const Inspector = (props: {
|
|||
selectedFile?: FilePath;
|
||||
}) => {
|
||||
const file_path = props.selectedFile;
|
||||
|
||||
let full_path = `${props.location?.path}/${file_path?.materialized_path}`;
|
||||
|
||||
const [note, setNote] = useState('');
|
||||
const [lastFileId, setLastFileId] = useState(-1);
|
||||
// notes are stored in global state by their file id
|
||||
// this is so we can ensure every note has been sent to Rust even
|
||||
// when quickly navigating files, which cancels update function
|
||||
const { notes, setNote, unCacheNote } = useInspectorState();
|
||||
|
||||
const { mutate: fileSetNote } = useBridgeCommand('FileSetNote', {});
|
||||
const file_id = props.selectedFile?.file?.id || -1;
|
||||
// show cached note over server note
|
||||
const note =
|
||||
notes[file_id] === undefined ? props.selectedFile?.file?.note || null : notes[file_id];
|
||||
|
||||
const fileSetNoteDebounced = useDebounce(fileSetNote, 500);
|
||||
|
||||
useEffect(() => {
|
||||
if (props.selectedFile?.file) fileSetNoteDebounced({ id: props.selectedFile?.file.id, note });
|
||||
}, [note]);
|
||||
|
||||
useEffect(() => {
|
||||
if (props.selectedFile?.file) {
|
||||
if (lastFileId) {
|
||||
fileSetNote({ id: lastFileId, note });
|
||||
}
|
||||
setLastFileId(props.selectedFile?.file?.id);
|
||||
|
||||
setNote(props.selectedFile?.file?.note || '');
|
||||
} else {
|
||||
setNote('');
|
||||
// when input is updated
|
||||
function handleNoteUpdate(e: React.ChangeEvent<HTMLTextAreaElement>) {
|
||||
if (e.target.value !== note) {
|
||||
setNote(file_id, e.target.value);
|
||||
}
|
||||
}, [props.selectedFile]);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// if the notes are synced, remove cache
|
||||
if (notes[file_id] === props.selectedFile?.file?.note) {
|
||||
unCacheNote(file_id);
|
||||
}
|
||||
}, [note]);
|
||||
|
||||
return (
|
||||
<Transition
|
||||
|
@ -137,10 +137,8 @@ export const Inspector = (props: {
|
|||
value={
|
||||
<TextArea
|
||||
className="mt-2 text-xs leading-snug !py-2"
|
||||
value={note}
|
||||
onChange={(e) => {
|
||||
setNote(e.target.value);
|
||||
}}
|
||||
value={note || ''}
|
||||
onChange={handleNoteUpdate}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -207,7 +207,7 @@ export const Sidebar: React.FC<SidebarProps> = (props) => {
|
|||
<button
|
||||
onClick={() => {
|
||||
appProps?.openDialog({ directory: true }).then((result) => {
|
||||
createLocation({ path: result });
|
||||
if (result) createLocation({ path: result as string });
|
||||
});
|
||||
}}
|
||||
className={clsx(
|
||||
|
|
42
packages/interface/src/hooks/useInspectorState.tsx
Normal file
42
packages/interface/src/hooks/useInspectorState.tsx
Normal file
|
@ -0,0 +1,42 @@
|
|||
import { command } from '@sd/client';
|
||||
import produce from 'immer';
|
||||
import { debounce } from 'lodash';
|
||||
import create from 'zustand';
|
||||
|
||||
export type UpdateNoteFN = (vars: { id: number; note: string }) => void;
|
||||
|
||||
interface UseInspectorState {
|
||||
notes: Record<number, string>;
|
||||
setNote: (file_id: number, note: string) => void;
|
||||
unCacheNote: (file_id: number) => void;
|
||||
}
|
||||
|
||||
export const useInspectorState = create<UseInspectorState>((set) => ({
|
||||
notes: {},
|
||||
// set the note locally
|
||||
setNote: (file_id, note) => {
|
||||
set((state) => {
|
||||
const change = produce(state, (draft) => {
|
||||
draft.notes[file_id] = note;
|
||||
});
|
||||
updateNote(file_id, note);
|
||||
return change;
|
||||
});
|
||||
},
|
||||
// remove local note once confirmed saved server-side
|
||||
unCacheNote: (file_id) => {
|
||||
set((state) =>
|
||||
produce(state, (draft) => {
|
||||
delete draft.notes[file_id];
|
||||
})
|
||||
);
|
||||
}
|
||||
}));
|
||||
|
||||
// direct command call to update note
|
||||
export const updateNote = debounce(async (file_id: number, note: string) => {
|
||||
return await command('FileSetNote', {
|
||||
id: file_id,
|
||||
note
|
||||
});
|
||||
}, 500);
|
|
@ -285,6 +285,7 @@ importers:
|
|||
concurrently: ^7.2.1
|
||||
immer: ^9.0.14
|
||||
jotai: ^1.7.0
|
||||
lodash: ^4.17.21
|
||||
moment: ^2.29.3
|
||||
phosphor-react: ^1.4.1
|
||||
prettier: ^2.6.2
|
||||
|
@ -331,6 +332,7 @@ importers:
|
|||
clsx: 1.1.1
|
||||
immer: 9.0.15
|
||||
jotai: 1.7.2_mpj3nhqjq7gooak672z2nctrhm
|
||||
lodash: 4.17.21
|
||||
moment: 2.29.3
|
||||
phosphor-react: 1.4.1_react@18.1.0
|
||||
pretty-bytes: 6.0.0
|
||||
|
@ -11567,7 +11569,6 @@ packages:
|
|||
|
||||
/lodash/4.17.21:
|
||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||
dev: true
|
||||
|
||||
/lodash/4.17.5:
|
||||
resolution: {integrity: sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==}
|
||||
|
|
Loading…
Reference in a new issue