authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-07-29 01:46:59+02:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-07-29 01:46:59+02:00
log8fe8f9a9c1486528f689a7499c2eb78217d35caf
tree41d27cf3381a59bfcac5e9386e4bdc7593b87af7
parent936593c2583c6640a07fc310f97bd5d0c27a7c31
signature Signed by PGP key B5690EEEBB952194

fix(mobile): defer filesystem navigation (#4799)

Co-authored-by: codex <codex@users.noreply.github.com>

1 files changed, 86 insertions(+), 25 deletions(-)

apps/mobile/src/features/projects/AddProjectScreen.tsx+86-25
......@@ -11,6 +11,8 @@ import {
1111 type AddProjectRemoteSource,
1212} from "@t3tools/client-runtime/operations/projects";
1313import {
14 canPreloadBrowsePath,
15 createBrowseNavigationCoordinator,
1416 filterFilesystemBrowseEntries,
1517 getFilesystemBrowsePath,
1618} from "@t3tools/client-runtime/state/filesystem";
......@@ -43,7 +45,10 @@ import { useThemeColor } from "../../lib/useThemeColor";
4345import { uuidv4 } from "../../lib/uuid";
4446import { useAtomCommand } from "../../state/use-atom-command";
4547import { useAtomQueryRunner } from "../../state/use-atom-query-runner";
46import { useSavedRemoteConnections } from "../../state/use-remote-environment-registry";
48import {
49 useRemoteEnvironmentRuntime,
50 useSavedRemoteConnections,
51} from "../../state/use-remote-environment-registry";
4752
4853interface EnvironmentOption {
4954 readonly environmentId: EnvironmentId;
......@@ -221,6 +226,63 @@ function ProjectPathInput(props: {
221226 );
222227}
223228
229function useBrowsePathInput(environment: EnvironmentOption | null) {
230 const [pathInput, commitPathInput] = useState(() =>
231 getAddProjectInitialQuery(environment?.baseDirectory),
232 );
233 const environmentRuntime = useRemoteEnvironmentRuntime(environment?.environmentId ?? null);
234 const loadBrowsePath = useAtomQueryRunner(filesystemEnvironment.browse, {
235 reportFailure: false,
236 reportDefect: false,
237 });
238 const [browseNavigation] = useState(createBrowseNavigationCoordinator);
239 const [isBrowseNavigating, setIsBrowseNavigating] = useState(false);
240 const setPathInput = useCallback(
241 (path: string) => {
242 browseNavigation.invalidate();
243 setIsBrowseNavigating(false);
244 commitPathInput(path);
245 },
246 [browseNavigation],
247 );
248 const navigateToBrowsePath = useCallback(
249 async (path: string) => {
250 setIsBrowseNavigating(true);
251 const committed = await browseNavigation.run(
252 async () => {
253 if (environment && canPreloadBrowsePath(environmentRuntime?.connectionState)) {
254 await loadBrowsePath({
255 environmentId: environment.environmentId,
256 input: { partialPath: path },
257 });
258 }
259 },
260 () => commitPathInput(path),
261 );
262 if (committed) {
263 setIsBrowseNavigating(false);
264 }
265 return committed;
266 },
267 [browseNavigation, environment, environmentRuntime?.connectionState, loadBrowsePath],
268 );
269
270 useEffect(() => {
271 if (environment) {
272 setPathInput(getAddProjectInitialQuery(environment.baseDirectory));
273 }
274 }, [environment, setPathInput]);
275
276 useEffect(
277 () => () => {
278 browseNavigation.invalidate();
279 },
280 [browseNavigation],
281 );
282
283 return { isBrowseNavigating, pathInput, setPathInput, navigateToBrowsePath };
284}
285
224286function useEnvironmentOptions(): ReadonlyArray<EnvironmentOption> {
225287 const serverConfigByEnvironmentId = useServerConfigs();
226288 const { savedConnectionsById } = useSavedRemoteConnections();
......@@ -585,6 +647,7 @@ function FolderBrowser(props: {
585647 readonly environment: EnvironmentOption;
586648 readonly pathInput: string;
587649 readonly setPathInput: (path: string) => void;
650 readonly navigateToBrowsePath: (path: string) => Promise<boolean>;
588651}) {
589652 const accentColor = useThemeColor("--color-icon-muted");
590653 const browsePath = useMemo(
......@@ -633,7 +696,7 @@ function FolderBrowser(props: {
633696 right={null}
634697 onPress={() => {
635698 if (browsePath.parentPath) {
636 props.setPathInput(browsePath.parentPath);
699 void props.navigateToBrowsePath(browsePath.parentPath);
637700 }
638701 }}
639702 />
......@@ -650,7 +713,7 @@ function FolderBrowser(props: {
650713 browsePath.directoryPath.length > 0
651714 ? appendBrowsePathSegment(browsePath.directoryPath, entry.name)
652715 : ensureBrowseDirectoryPath(entry.fullPath);
653 props.setPathInput(nextPath);
716 void props.navigateToBrowsePath(nextPath);
654717 }}
655718 />
656719 ))}
......@@ -662,19 +725,13 @@ function FolderBrowser(props: {
662725export function AddProjectLocalFolderScreen(props: { readonly environmentId?: string | string[] }) {
663726 const environment = useEnvironmentFromParam(props.environmentId);
664727 const createProject = useCreateProject(environment);
665 const [pathInput, setPathInput] = useState(() =>
666 getAddProjectInitialQuery(environment?.baseDirectory),
667 );
728 const { isBrowseNavigating, navigateToBrowsePath, pathInput, setPathInput } =
729 useBrowsePathInput(environment);
668730 const [isSubmitting, setIsSubmitting] = useState(false);
669731 const [error, setError] = useState<string | null>(null);
670732
671 useEffect(() => {
672 if (!environment) return;
673 setPathInput(getAddProjectInitialQuery(environment.baseDirectory));
674 }, [environment]);
675
676733 const submitPath = useCallback(async () => {
677 if (!environment || isSubmitting) return;
734 if (!environment || isBrowseNavigating || isSubmitting) return;
678735 setError(null);
679736 const resolved = resolveAddProjectPath({
680737 rawPath: pathInput,
......@@ -692,7 +749,7 @@ export function AddProjectLocalFolderScreen(props: { readonly environmentId?: st
692749 setError(errorMessage(Cause.squash(result.cause)));
693750 }
694751 setIsSubmitting(false);
695 }, [createProject, environment, isSubmitting, pathInput]);
752 }, [createProject, environment, isBrowseNavigating, isSubmitting, pathInput]);
696753
697754 return (
698755 <AddProjectShell>
......@@ -706,12 +763,13 @@ export function AddProjectLocalFolderScreen(props: { readonly environmentId?: st
706763 />
707764 <PrimaryActionButton
708765 label="Add project"
709 disabled={isSubmitting}
766 disabled={isBrowseNavigating || isSubmitting}
710767 onPress={() => void submitPath()}
711768 loading={isSubmitting}
712769 />
713770 <FolderBrowser
714771 environment={environment}
772 navigateToBrowsePath={navigateToBrowsePath}
715773 pathInput={pathInput}
716774 setPathInput={setPathInput}
717775 />
......@@ -735,19 +793,13 @@ export function AddProjectDestinationScreen(props: {
735793 const createProject = useCreateProject(environment);
736794 const remoteUrl = stringParam(props.remoteUrl);
737795 const repositoryTitle = stringParam(props.repositoryTitle);
738 const [pathInput, setPathInput] = useState(() =>
739 getAddProjectInitialQuery(environment?.baseDirectory),
740 );
796 const { isBrowseNavigating, navigateToBrowsePath, pathInput, setPathInput } =
797 useBrowsePathInput(environment);
741798 const [isSubmitting, setIsSubmitting] = useState(false);
742799 const [error, setError] = useState<string | null>(null);
743800
744 useEffect(() => {
745 if (!environment) return;
746 setPathInput(getAddProjectInitialQuery(environment.baseDirectory));
747 }, [environment]);
748
749801 const submitPath = useCallback(async () => {
750 if (!environment || !remoteUrl || isSubmitting) return;
802 if (!environment || !remoteUrl || isBrowseNavigating || isSubmitting) return;
751803 setError(null);
752804 const resolved = resolveAddProjectPath({
753805 rawPath: pathInput,
......@@ -776,7 +828,15 @@ export function AddProjectDestinationScreen(props: {
776828 }
777829 }
778830 setIsSubmitting(false);
779 }, [cloneRepository, createProject, environment, isSubmitting, pathInput, remoteUrl]);
831 }, [
832 cloneRepository,
833 createProject,
834 environment,
835 isBrowseNavigating,
836 isSubmitting,
837 pathInput,
838 remoteUrl,
839 ]);
780840
781841 return (
782842 <AddProjectShell>
......@@ -798,12 +858,13 @@ export function AddProjectDestinationScreen(props: {
798858 />
799859 <PrimaryActionButton
800860 label="Clone project"
801 disabled={isSubmitting || !remoteUrl}
861 disabled={isBrowseNavigating || isSubmitting || !remoteUrl}
802862 onPress={() => void submitPath()}
803863 loading={isSubmitting}
804864 />
805865 <FolderBrowser
806866 environment={environment}
867 navigateToBrowsePath={navigateToBrowsePath}
807868 pathInput={pathInput}
808869 setPathInput={setPathInput}
809870 />