diff --git a/apps/mobile/src/features/projects/AddProjectScreen.tsx b/apps/mobile/src/features/projects/AddProjectScreen.tsx index bd6932a98b1..eef40382b24 100644 --- a/apps/mobile/src/features/projects/AddProjectScreen.tsx +++ b/apps/mobile/src/features/projects/AddProjectScreen.tsx @@ -11,6 +11,8 @@ import { type AddProjectRemoteSource, } from "@t3tools/client-runtime/operations/projects"; import { + canPreloadBrowsePath, + createBrowseNavigationCoordinator, filterFilesystemBrowseEntries, getFilesystemBrowsePath, } from "@t3tools/client-runtime/state/filesystem"; @@ -43,7 +45,10 @@ import { useThemeColor } from "../../lib/useThemeColor"; import { uuidv4 } from "../../lib/uuid"; import { useAtomCommand } from "../../state/use-atom-command"; import { useAtomQueryRunner } from "../../state/use-atom-query-runner"; -import { useSavedRemoteConnections } from "../../state/use-remote-environment-registry"; +import { + useRemoteEnvironmentRuntime, + useSavedRemoteConnections, +} from "../../state/use-remote-environment-registry"; interface EnvironmentOption { readonly environmentId: EnvironmentId; @@ -221,6 +226,63 @@ function ProjectPathInput(props: { ); } +function useBrowsePathInput(environment: EnvironmentOption | null) { + const [pathInput, commitPathInput] = useState(() => + getAddProjectInitialQuery(environment?.baseDirectory), + ); + const environmentRuntime = useRemoteEnvironmentRuntime(environment?.environmentId ?? null); + const loadBrowsePath = useAtomQueryRunner(filesystemEnvironment.browse, { + reportFailure: false, + reportDefect: false, + }); + const [browseNavigation] = useState(createBrowseNavigationCoordinator); + const [isBrowseNavigating, setIsBrowseNavigating] = useState(false); + const setPathInput = useCallback( + (path: string) => { + browseNavigation.invalidate(); + setIsBrowseNavigating(false); + commitPathInput(path); + }, + [browseNavigation], + ); + const navigateToBrowsePath = useCallback( + async (path: string) => { + setIsBrowseNavigating(true); + const committed = await browseNavigation.run( + async () => { + if (environment && canPreloadBrowsePath(environmentRuntime?.connectionState)) { + await loadBrowsePath({ + environmentId: environment.environmentId, + input: { partialPath: path }, + }); + } + }, + () => commitPathInput(path), + ); + if (committed) { + setIsBrowseNavigating(false); + } + return committed; + }, + [browseNavigation, environment, environmentRuntime?.connectionState, loadBrowsePath], + ); + + useEffect(() => { + if (environment) { + setPathInput(getAddProjectInitialQuery(environment.baseDirectory)); + } + }, [environment, setPathInput]); + + useEffect( + () => () => { + browseNavigation.invalidate(); + }, + [browseNavigation], + ); + + return { isBrowseNavigating, pathInput, setPathInput, navigateToBrowsePath }; +} + function useEnvironmentOptions(): ReadonlyArray { const serverConfigByEnvironmentId = useServerConfigs(); const { savedConnectionsById } = useSavedRemoteConnections(); @@ -585,6 +647,7 @@ function FolderBrowser(props: { readonly environment: EnvironmentOption; readonly pathInput: string; readonly setPathInput: (path: string) => void; + readonly navigateToBrowsePath: (path: string) => Promise; }) { const accentColor = useThemeColor("--color-icon-muted"); const browsePath = useMemo( @@ -633,7 +696,7 @@ function FolderBrowser(props: { right={null} onPress={() => { if (browsePath.parentPath) { - props.setPathInput(browsePath.parentPath); + void props.navigateToBrowsePath(browsePath.parentPath); } }} /> @@ -650,7 +713,7 @@ function FolderBrowser(props: { browsePath.directoryPath.length > 0 ? appendBrowsePathSegment(browsePath.directoryPath, entry.name) : ensureBrowseDirectoryPath(entry.fullPath); - props.setPathInput(nextPath); + void props.navigateToBrowsePath(nextPath); }} /> ))} @@ -662,19 +725,13 @@ function FolderBrowser(props: { export function AddProjectLocalFolderScreen(props: { readonly environmentId?: string | string[] }) { const environment = useEnvironmentFromParam(props.environmentId); const createProject = useCreateProject(environment); - const [pathInput, setPathInput] = useState(() => - getAddProjectInitialQuery(environment?.baseDirectory), - ); + const { isBrowseNavigating, navigateToBrowsePath, pathInput, setPathInput } = + useBrowsePathInput(environment); const [isSubmitting, setIsSubmitting] = useState(false); const [error, setError] = useState(null); - useEffect(() => { - if (!environment) return; - setPathInput(getAddProjectInitialQuery(environment.baseDirectory)); - }, [environment]); - const submitPath = useCallback(async () => { - if (!environment || isSubmitting) return; + if (!environment || isBrowseNavigating || isSubmitting) return; setError(null); const resolved = resolveAddProjectPath({ rawPath: pathInput, @@ -692,7 +749,7 @@ export function AddProjectLocalFolderScreen(props: { readonly environmentId?: st setError(errorMessage(Cause.squash(result.cause))); } setIsSubmitting(false); - }, [createProject, environment, isSubmitting, pathInput]); + }, [createProject, environment, isBrowseNavigating, isSubmitting, pathInput]); return ( @@ -706,12 +763,13 @@ export function AddProjectLocalFolderScreen(props: { readonly environmentId?: st /> void submitPath()} loading={isSubmitting} /> @@ -735,19 +793,13 @@ export function AddProjectDestinationScreen(props: { const createProject = useCreateProject(environment); const remoteUrl = stringParam(props.remoteUrl); const repositoryTitle = stringParam(props.repositoryTitle); - const [pathInput, setPathInput] = useState(() => - getAddProjectInitialQuery(environment?.baseDirectory), - ); + const { isBrowseNavigating, navigateToBrowsePath, pathInput, setPathInput } = + useBrowsePathInput(environment); const [isSubmitting, setIsSubmitting] = useState(false); const [error, setError] = useState(null); - useEffect(() => { - if (!environment) return; - setPathInput(getAddProjectInitialQuery(environment.baseDirectory)); - }, [environment]); - const submitPath = useCallback(async () => { - if (!environment || !remoteUrl || isSubmitting) return; + if (!environment || !remoteUrl || isBrowseNavigating || isSubmitting) return; setError(null); const resolved = resolveAddProjectPath({ rawPath: pathInput, @@ -776,7 +828,15 @@ export function AddProjectDestinationScreen(props: { } } setIsSubmitting(false); - }, [cloneRepository, createProject, environment, isSubmitting, pathInput, remoteUrl]); + }, [ + cloneRepository, + createProject, + environment, + isBrowseNavigating, + isSubmitting, + pathInput, + remoteUrl, + ]); return ( @@ -798,12 +858,13 @@ export function AddProjectDestinationScreen(props: { /> void submitPath()} loading={isSubmitting} />