feat: add main features
Agents for developpement added + frontend add + backend added. Git viewer created + agent and template creator + layout and project creator
This commit is contained in:
167
frontend/src/features/git/useGit.ts
Normal file
167
frontend/src/features/git/useGit.ts
Normal file
@ -0,0 +1,167 @@
|
||||
/**
|
||||
* `useGit` — view-model hook for the Git feature (L8).
|
||||
*
|
||||
* Owns the git feature state for a given project and exposes the actions the
|
||||
* UI triggers. Consumes {@link GitGateway} exclusively through `useGateways().git`;
|
||||
* never touches `invoke()` or `@tauri-apps/api`.
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import type { GitBranches, GitCommit, GitFileStatus, GatewayError } from "@/domain";
|
||||
import { useGateways } from "@/app/di";
|
||||
|
||||
/** What the Git UI needs from this hook. */
|
||||
export interface GitViewModel {
|
||||
/** Files in the working tree (staged + unstaged). */
|
||||
files: GitFileStatus[];
|
||||
/** Branch info: list + current. */
|
||||
branches: GitBranches;
|
||||
/** Recent commit log. */
|
||||
log: GitCommit[];
|
||||
/** Whether a request is in flight. */
|
||||
busy: boolean;
|
||||
/** Last error message, or `null`. */
|
||||
error: string | null;
|
||||
/** Refresh all git state. */
|
||||
refresh: () => Promise<void>;
|
||||
/** Stage a file. */
|
||||
stage: (path: string) => Promise<void>;
|
||||
/** Unstage a file. */
|
||||
unstage: (path: string) => Promise<void>;
|
||||
/** Commit staged files with the given message. */
|
||||
commit: (message: string) => Promise<void>;
|
||||
/** Checkout (or create) a branch. */
|
||||
checkout: (branch: string) => Promise<void>;
|
||||
}
|
||||
|
||||
function describeError(e: unknown): string {
|
||||
if (e && typeof e === "object" && "message" in e) {
|
||||
return String((e as GatewayError).message);
|
||||
}
|
||||
return String(e);
|
||||
}
|
||||
|
||||
const EMPTY_BRANCHES: GitBranches = { branches: [], current: null };
|
||||
const LOG_LIMIT = 20;
|
||||
|
||||
export function useGit(projectId: string): GitViewModel {
|
||||
const { git } = useGateways();
|
||||
|
||||
const [files, setFiles] = useState<GitFileStatus[]>([]);
|
||||
const [branches, setBranches] = useState<GitBranches>(EMPTY_BRANCHES);
|
||||
const [log, setLog] = useState<GitCommit[]>([]);
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const refresh = useCallback(async () => {
|
||||
setBusy(true);
|
||||
setError(null);
|
||||
try {
|
||||
const [fileList, branchInfo, commitLog] = await Promise.all([
|
||||
git.status(projectId),
|
||||
git.branches(projectId),
|
||||
git.log(projectId, LOG_LIMIT),
|
||||
]);
|
||||
setFiles(fileList);
|
||||
setBranches(branchInfo);
|
||||
setLog(commitLog);
|
||||
} catch (e) {
|
||||
setError(describeError(e));
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
}, [git, projectId]);
|
||||
|
||||
useEffect(() => {
|
||||
void refresh();
|
||||
}, [refresh]);
|
||||
|
||||
const stage = useCallback(
|
||||
async (path: string) => {
|
||||
setBusy(true);
|
||||
setError(null);
|
||||
try {
|
||||
await git.stage(projectId, path);
|
||||
setFiles((prev) =>
|
||||
prev.map((f) => (f.path === path ? { ...f, staged: true } : f)),
|
||||
);
|
||||
} catch (e) {
|
||||
setError(describeError(e));
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
},
|
||||
[git, projectId],
|
||||
);
|
||||
|
||||
const unstage = useCallback(
|
||||
async (path: string) => {
|
||||
setBusy(true);
|
||||
setError(null);
|
||||
try {
|
||||
await git.unstage(projectId, path);
|
||||
setFiles((prev) =>
|
||||
prev.map((f) => (f.path === path ? { ...f, staged: false } : f)),
|
||||
);
|
||||
} catch (e) {
|
||||
setError(describeError(e));
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
},
|
||||
[git, projectId],
|
||||
);
|
||||
|
||||
const commit = useCallback(
|
||||
async (message: string) => {
|
||||
setBusy(true);
|
||||
setError(null);
|
||||
try {
|
||||
const newCommit = await git.commit(projectId, message);
|
||||
// Remove committed (staged) files from the list.
|
||||
setFiles((prev) => prev.filter((f) => !f.staged));
|
||||
setLog((prev) => [newCommit, ...prev].slice(0, LOG_LIMIT));
|
||||
} catch (e) {
|
||||
setError(describeError(e));
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
},
|
||||
[git, projectId],
|
||||
);
|
||||
|
||||
const checkout = useCallback(
|
||||
async (branch: string) => {
|
||||
setBusy(true);
|
||||
setError(null);
|
||||
try {
|
||||
await git.checkout(projectId, branch);
|
||||
setBranches((prev) => ({
|
||||
branches: prev.branches.includes(branch)
|
||||
? prev.branches
|
||||
: [...prev.branches, branch],
|
||||
current: branch,
|
||||
}));
|
||||
} catch (e) {
|
||||
setError(describeError(e));
|
||||
} finally {
|
||||
setBusy(false);
|
||||
}
|
||||
},
|
||||
[git, projectId],
|
||||
);
|
||||
|
||||
return {
|
||||
files,
|
||||
branches,
|
||||
log,
|
||||
busy,
|
||||
error,
|
||||
refresh,
|
||||
stage,
|
||||
unstage,
|
||||
commit,
|
||||
checkout,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user