(fix) notes: they work predictably

- added lodash for debounce outside react tree
- added command function to bridge
- updated FAQ
This commit is contained in:
Jamie Pine 2022-06-22 04:13:11 -07:00
parent d950a19cc7
commit ff9df3f8d1
10 changed files with 99 additions and 34 deletions

View file

@ -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);
});

View file

@ -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)
}

View file

@ -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?

View file

@ -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);
}

View file

@ -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",

View file

@ -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;

View file

@ -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}
/>
}
/>

View file

@ -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(

View 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);

View file

@ -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==}