Files
paseo/packages/app/src/hooks/use-preferred-editor.ts
T
Mohamed BoudraandKino Roy 27d6b3b7ce feat(desktop): remember project editors and support Xcode (#7898)
Keep editor choices local to each project and shared across its worktrees. Reuse the Xcode target contribution while preserving explicit editor selection.

Co-authored-by: Kino Roy <kinoroy@me.com>
2026-10-26 13:36:00 +07:00

94 lines
2.8 KiB
TypeScript

import { useMemo } from "react";
import AsyncStorage from "@react-native-async-storage/async-storage";
import { QueryClient, useQuery, useQueryClient } from "@tanstack/react-query";
import { z } from "zod";
import { readValidatedString, type ValidatedStorage } from "@/storage/validated-storage";
type EditorTargetId = string;
const PREFERRED_EDITOR_STORAGE_KEY = "@paseo:preferred-editor";
interface EditorPreferenceStorage extends ValidatedStorage {
setItem(key: string, value: string): Promise<void>;
}
interface ProjectEditorScope {
serverId: string;
projectId: string | null;
}
export function createPreferredEditorPreference({
scope,
queryClient,
storage,
}: {
scope: ProjectEditorScope;
queryClient: QueryClient;
storage: EditorPreferenceStorage;
}) {
const queryKey = ["preferred-editor", scope.serverId, scope.projectId];
const storageKey = `${PREFERRED_EDITOR_STORAGE_KEY}:${JSON.stringify([scope.serverId, scope.projectId])}`;
async function load() {
if (scope.projectId === null) return null;
const projectEditor = await readValidatedString(storage, storageKey, z.string().trim().min(1));
if (projectEditor !== null) return projectEditor;
return readValidatedString(storage, PREFERRED_EDITOR_STORAGE_KEY, z.string().trim().min(1));
}
return {
queryOptions: {
queryKey,
queryFn: load,
enabled: scope.projectId !== null,
staleTime: Infinity,
gcTime: Infinity,
},
async update(editorId: EditorTargetId | null) {
if (scope.projectId === null) return;
if (editorId) {
await storage.setItem(storageKey, editorId);
} else {
await storage.removeItem(storageKey);
}
queryClient.setQueryData(queryKey, await load());
},
};
}
export function resolvePreferredEditorId(
availableEditorIds: readonly EditorTargetId[],
storedEditorId: EditorTargetId | null | undefined,
): EditorTargetId | null {
if (storedEditorId === undefined) {
return null;
}
if (
storedEditorId &&
availableEditorIds.some((availableEditorId) => availableEditorId === storedEditorId)
) {
return storedEditorId;
}
return availableEditorIds[0] ?? null;
}
export function usePreferredEditor(scope: ProjectEditorScope) {
const queryClient = useQueryClient();
const { serverId, projectId } = scope;
const preference = useMemo(
() =>
createPreferredEditorPreference({
scope: { serverId, projectId },
queryClient,
storage: AsyncStorage,
}),
[queryClient, serverId, projectId],
);
const { data, isPending } = useQuery(preference.queryOptions);
const isLoading = projectId !== null && isPending;
return {
preferredEditorId: isLoading ? undefined : (data ?? null),
isLoading,
updatePreferredEditor: preference.update,
};
}