Skip to content

Commit

Permalink
[ENG-1210] Navigate to location after adding it (#1454)
Browse files Browse the repository at this point in the history
* remove annoying log

* return location id on creation

* add checkbox to open new location once it's been added

* redirect if checkbox was true and a location id was provided, and update bindings

* add `new()` for `MissingFieldError`

* return location id on location relink

* working, clean redirecting

* accordion closing tag

* navigate to location after adding

* chore: remove erroneous `{' '}`

* multiple location redirect support

* oops missed check

* cleanup

* fix bad merge

---------

Co-authored-by: ameer2468 <33054370+ameer2468@users.noreply.github.com>
  • Loading branch information
brxken128 and ameer2468 authored Oct 20, 2023
1 parent 5f417e7 commit cc72f54
Show file tree
Hide file tree
Showing 11 changed files with 165 additions and 47 deletions.
11 changes: 8 additions & 3 deletions core/src/api/locations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,11 +208,13 @@ pub(crate) fn mount() -> AlphaRouter<Ctx> {
R.with2(library())
.mutation(|(node, library), args: LocationCreateArgs| async move {
if let Some(location) = args.create(&node, &library).await? {
let id = Some(location.id);
scan_location(&node, &library, location).await?;
invalidate_query!(library, "locations.list");
Ok(id)
} else {
Ok(None)
}

Ok(())
})
})
.procedure("update", {
Expand Down Expand Up @@ -244,10 +246,13 @@ pub(crate) fn mount() -> AlphaRouter<Ctx> {
R.with2(library())
.mutation(|(node, library), args: LocationCreateArgs| async move {
if let Some(location) = args.add_library(&node, &library).await? {
let id = location.id;
scan_location(&node, &library, location).await?;
invalidate_query!(library, "locations.list");
Ok(Some(id))
} else {
Ok(None)
}
Ok(())
})
})
.procedure("fullRescan", {
Expand Down
20 changes: 15 additions & 5 deletions core/src/location/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::{
},
prisma::{file_path, indexer_rules_in_location, location, PrismaClient},
util::{
db::maybe_missing,
db::{maybe_missing, MissingFieldError},
error::{FileIOError, NonUtf8PathError},
},
Node,
Expand Down Expand Up @@ -532,7 +532,7 @@ pub async fn light_scan_location(
pub async fn relink_location(
Library { db, id, sync, .. }: &Library,
location_path: impl AsRef<Path>,
) -> Result<(), LocationError> {
) -> Result<i32, LocationError> {
let location_path = location_path.as_ref();
let mut metadata = SpacedriveLocationMetadataFile::try_load(&location_path)
.await?
Expand All @@ -547,7 +547,7 @@ pub async fn relink_location(
.ok_or_else(|| NonUtf8PathError(location_path.into()))?;

sync.write_op(
db,
&db,
sync.shared_update(
prisma_sync::location::SyncId {
pub_id: pub_id.clone(),
Expand All @@ -556,13 +556,23 @@ pub async fn relink_location(
json!(path),
),
db.location().update(
location::pub_id::equals(pub_id),
location::pub_id::equals(pub_id.clone()),
vec![location::path::set(Some(path))],
),
)
.await?;

Ok(())
let location_id = db
.location()
.find_unique(location::pub_id::equals(pub_id))
.select(location::select!({ id }))
.exec()
.await?
.ok_or_else(|| {
LocationError::MissingField(MissingFieldError::new("missing id of location"))
})?;

Ok(location_id.id)
}

#[derive(Debug)]
Expand Down
7 changes: 7 additions & 0 deletions core/src/util/db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ pub fn inode_to_db(inode: u64) -> Vec<u8> {
#[error("Missing field {0}")]
pub struct MissingFieldError(&'static str);

impl MissingFieldError {
#[must_use]
pub const fn new(value: &'static str) -> Self {
Self(value)
}
}

impl From<MissingFieldError> for rspc::Error {
fn from(value: MissingFieldError) -> Self {
rspc::Error::with_cause(
Expand Down
4 changes: 3 additions & 1 deletion interface/app/$libraryId/Explorer/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import {
type ExplorerItem,
type ExplorerLayout,
type ExplorerSettings,
type SortOrder
type SortOrder,
JobGroup
} from '@sd/client';

export enum ExplorerKind {
Expand Down Expand Up @@ -121,6 +122,7 @@ const state = {
tagAssignMode: false,
showInspector: false,
showMoreInfo: false,
jobsToRedirect: [] as {locationId: number | null}[],
mediaPlayerVolume: 0.7,
newThumbnails: proxySet() as Set<string>,
cutCopyState: { type: 'Idle' } as CutCopyState,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Pencil, Plus, Trash } from '@phosphor-icons/react';
import { useNavigate } from 'react-router';
import { useLibraryContext } from '@sd/client';
import { ContextMenu as CM, dialogManager, toast } from '@sd/ui';
import { AddLocationDialog } from '~/app/$libraryId/settings/library/locations/AddLocationDialog';
import DeleteDialog from '~/app/$libraryId/settings/library/locations/DeleteDialog';
Expand All @@ -14,6 +15,8 @@ interface Props {
export default ({ children, locationId }: Props) => {
const navigate = useNavigate();
const platform = usePlatform();
const libraryId = useLibraryContext().library.uuid;

return (
<CM.Root trigger={children}>
<CM.Item
Expand All @@ -22,7 +25,11 @@ export default ({ children, locationId }: Props) => {
const path = await openDirectoryPickerDialog(platform);
if (path !== '') {
dialogManager.create((dp) => (
<AddLocationDialog path={path ?? ''} {...dp} />
<AddLocationDialog
path={path ?? ''}
libraryId={libraryId}
{...dp}
/>
));
}
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { FolderSimplePlus } from '@phosphor-icons/react';
import clsx from 'clsx';
import { motion } from 'framer-motion';
import { useRef, useState } from 'react';
import { useNavigate } from 'react-router';
import { useLibraryContext } from '@sd/client';
import { Button, dialogManager, type ButtonProps } from '@sd/ui';
import { useCallbackToWatchResize } from '~/hooks';
import { usePlatform } from '~/util/Platform';
Expand All @@ -16,6 +18,9 @@ interface AddLocationButton extends ButtonProps {

export const AddLocationButton = ({ path, className, onClick, ...props }: AddLocationButton) => {
const platform = usePlatform();
const libraryId = useLibraryContext().library.uuid;
const navigate = useNavigate();

const transition = {
type: 'keyframes',
ease: 'easeInOut',
Expand Down Expand Up @@ -49,7 +54,7 @@ export const AddLocationButton = ({ path, className, onClick, ...props }: AddLoc
// Remember `path` will be `undefined` on web cause the user has to provide it in the modal
if (path !== '')
dialogManager.create((dp) => (
<AddLocationDialog path={path ?? ''} {...dp} />
<AddLocationDialog path={path ?? ''} libraryId={libraryId} {...dp} />
));

onClick?.();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { useCallback, useEffect, useMemo } from 'react';
import { Controller, get } from 'react-hook-form';
import { useNavigate } from 'react-router';
import { useDebouncedCallback } from 'use-debounce';
import {
extractInfoRSPCError,
Expand All @@ -9,7 +10,8 @@ import {
usePlausibleEvent,
useZodForm
} from '@sd/client';
import { Dialog, ErrorMessage, toast, useDialog, UseDialogProps, z } from '@sd/ui';
import { CheckBox, Dialog, ErrorMessage, Label, toast, useDialog, UseDialogProps, z } from '@sd/ui';
import { getExplorerStore, useExplorerStore } from '~/app/$libraryId/Explorer/store';
import Accordion from '~/components/Accordion';
import { useCallbackToWatchForm } from '~/hooks';
import { usePlatform } from '~/util/Platform';
Expand All @@ -34,13 +36,15 @@ const isRemoteErrorFormMessage = (message: unknown): message is RemoteErrorFormM
const schema = z.object({
path: z.string().min(1),
method: z.enum(Object.keys(REMOTE_ERROR_FORM_MESSAGE) as UnionToTuple<RemoteErrorFormMessage>),
indexerRulesIds: z.array(z.number())
indexerRulesIds: z.array(z.number()),
shouldRedirect: z.boolean()
});

type SchemaType = z.infer<typeof schema>;

export interface AddLocationDialog extends UseDialogProps {
path: string;
libraryId: string;
method?: RemoteErrorFormMessage;
}

Expand All @@ -56,6 +60,7 @@ export const AddLocationDialog = ({
const relinkLocation = useLibraryMutation('locations.relink');
const listIndexerRules = useLibraryQuery(['locations.indexer_rules.list']);
const addLocationToLibrary = useLibraryMutation('locations.addLibrary');
const explorerStore = useExplorerStore();

// This is required because indexRules is undefined on first render
const indexerRulesIds = useMemo(
Expand All @@ -65,7 +70,7 @@ export const AddLocationDialog = ({

const form = useZodForm({
schema,
defaultValues: { path, method, indexerRulesIds }
defaultValues: { path, method, indexerRulesIds, shouldRedirect: true }
});

useEffect(() => {
Expand All @@ -78,10 +83,12 @@ export const AddLocationDialog = ({
}, [form, path, indexerRulesIds]);

const addLocation = useCallback(
async ({ path, method, indexerRulesIds }: SchemaType, dryRun = false) => {
async ({ path, method, indexerRulesIds, shouldRedirect }: SchemaType, dryRun = false) => {
let id = null;

switch (method) {
case 'CREATE':
await createLocation.mutateAsync({
id = await createLocation.mutateAsync({
path,
dry_run: dryRun,
indexer_rules_ids: indexerRulesIds
Expand All @@ -91,7 +98,7 @@ export const AddLocationDialog = ({

break;
case 'NEED_RELINK':
if (!dryRun) await relinkLocation.mutateAsync(path);
if (!dryRun) id = await relinkLocation.mutateAsync(path);
// TODO: Update relinked location with new indexer rules, don't have a way to get location id yet though
// await updateLocation.mutateAsync({
// id: locationId,
Expand All @@ -104,7 +111,7 @@ export const AddLocationDialog = ({

break;
case 'ADD_LIBRARY':
await addLocationToLibrary.mutateAsync({
id = await addLocationToLibrary.mutateAsync({
path,
dry_run: dryRun,
indexer_rules_ids: indexerRulesIds
Expand All @@ -116,8 +123,14 @@ export const AddLocationDialog = ({
default:
throw new Error('Unimplemented custom remote error handling');
}
if (shouldRedirect) {
getExplorerStore().jobsToRedirect = [
{ locationId: id },
...explorerStore.jobsToRedirect
];
}
},
[createLocation, relinkLocation, addLocationToLibrary, submitPlausibleEvent]
[createLocation, relinkLocation, addLocationToLibrary, submitPlausibleEvent, explorerStore]
);

const handleAddError = useCallback(
Expand Down Expand Up @@ -206,27 +219,38 @@ export const AddLocationDialog = ({
: ''
}
>
<ErrorMessage name={REMOTE_ERROR_FORM_FIELD} variant="large" className="mb-4 mt-2" />

<LocationPathInputField {...form.register('path')} />

<input type="hidden" {...form.register('method')} />

<Accordion title="Advanced settings">
<Controller
name="indexerRulesIds"
render={({ field }) => (
<IndexerRuleEditor
field={field}
label="File indexing rules"
className="relative flex flex-col"
rulesContainerClass="grid grid-cols-2 gap-2"
ruleButtonClass="w-full"
/>
)}
control={form.control}
<div className="flex flex-col">
<ErrorMessage
name={REMOTE_ERROR_FORM_FIELD}
variant="large"
className="mb-4 mt-2"
/>
</Accordion>

<LocationPathInputField {...form.register('path')} />

<input type="hidden" {...form.register('method')} />

<div className="mb-4 flex">
<CheckBox {...form.register('shouldRedirect')} />
<Label className="mt-[3px] font-semibold">Open new location once added</Label>
</div>

<Accordion title="Advanced settings">
<Controller
name="indexerRulesIds"
render={({ field }) => (
<IndexerRuleEditor
field={field}
label="File indexing rules"
className="relative flex flex-col"
rulesContainerClass="grid grid-cols-2 gap-2"
ruleButtonClass="w-full"
/>
)}
control={form.control}
/>
</Accordion>
</div>
</Dialog>
);
};
9 changes: 4 additions & 5 deletions interface/app/index.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
import { useEffect, useMemo } from 'react';
import { useMemo } from 'react';
import { Navigate, Outlet, useMatches, type RouteObject } from 'react-router-dom';
import { currentLibraryCache, useCachedLibraries, useInvalidateQuery } from '@sd/client';
import { Dialogs, toast, Toaster } from '@sd/ui';
import { Dialogs, Toaster } from '@sd/ui';
import { RouterErrorBoundary } from '~/ErrorFallback';
import { useKeybindHandler, useTheme } from '~/hooks';
import { useKeybindHandler, useShouldRedirect, useTheme } from '~/hooks';

import libraryRoutes from './$libraryId';
import onboardingRoutes from './onboarding';
import { RootContext } from './RootContext';

import './style.scss';

import { usePlatform } from '..';

const Index = () => {
const libraries = useCachedLibraries();

Expand All @@ -31,6 +29,7 @@ const Wrapper = () => {
useKeybindHandler();
useInvalidateQuery();
useTheme();
useShouldRedirect();

const rawPath = useRawRoutePath();

Expand Down
1 change: 1 addition & 0 deletions interface/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,4 @@ export * from './useIsTextTruncated';
export * from './useKeyMatcher';
export * from './useKeyCopyCutPaste';
export * from './useMouseNavigate';
export * from './useShouldRedirect';
Loading

0 comments on commit cc72f54

Please sign in to comment.