first commit
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
|
||||
import type { JobResponse } from "../api/types";
|
||||
import type { OperationRecord, OperationStatus } from "../contracts/workspace";
|
||||
|
||||
export interface BeginOperationInput {
|
||||
intent: string;
|
||||
targetKind: OperationRecord["targetKind"];
|
||||
targetId: string;
|
||||
requester?: string;
|
||||
}
|
||||
|
||||
export interface OperationUpdate {
|
||||
status?: OperationStatus;
|
||||
job?: JobResponse;
|
||||
message?: string;
|
||||
errorReason?: string;
|
||||
diagnosticId?: string;
|
||||
}
|
||||
|
||||
export interface OperationTracker {
|
||||
operations: OperationRecord[];
|
||||
begin: (input: BeginOperationInput) => string;
|
||||
update: (operationId: string, update: OperationUpdate) => void;
|
||||
succeed: (operationId: string, message?: string, job?: JobResponse) => void;
|
||||
fail: (operationId: string, errorReason: string, diagnosticId?: string) => void;
|
||||
isPending: (targetId: string, intent?: string) => boolean;
|
||||
}
|
||||
|
||||
export function useOperationTracker(): OperationTracker {
|
||||
const [operations, setOperations] = useState<OperationRecord[]>([]);
|
||||
const sequence = useRef(0);
|
||||
|
||||
const begin = useCallback((input: BeginOperationInput): string => {
|
||||
sequence.current += 1;
|
||||
const now = new Date().toISOString();
|
||||
const record: OperationRecord = {
|
||||
id: `op-${Date.now()}-${sequence.current}`,
|
||||
intent: input.intent,
|
||||
targetKind: input.targetKind,
|
||||
targetId: input.targetId,
|
||||
requester: input.requester ?? "当前用户",
|
||||
status: "pending",
|
||||
createdAt: now,
|
||||
updatedAt: now
|
||||
};
|
||||
setOperations((current) => [record, ...current]);
|
||||
return record.id;
|
||||
}, []);
|
||||
|
||||
const update = useCallback((operationId: string, patch: OperationUpdate) => {
|
||||
setOperations((current) =>
|
||||
current.map((operation) =>
|
||||
operation.id === operationId
|
||||
? {
|
||||
...operation,
|
||||
status: patch.status ?? operation.status,
|
||||
jobId: patch.job?.id ?? operation.jobId,
|
||||
jobState: patch.job?.state ?? operation.jobState,
|
||||
message: patch.message ?? operation.message,
|
||||
errorReason: patch.errorReason ?? operation.errorReason,
|
||||
diagnosticId: patch.diagnosticId ?? operation.diagnosticId,
|
||||
updatedAt: new Date().toISOString()
|
||||
}
|
||||
: operation
|
||||
)
|
||||
);
|
||||
}, []);
|
||||
|
||||
const succeed = useCallback(
|
||||
(operationId: string, message?: string, job?: JobResponse) => {
|
||||
update(operationId, { status: "succeeded", message, job });
|
||||
},
|
||||
[update]
|
||||
);
|
||||
|
||||
const fail = useCallback(
|
||||
(operationId: string, errorReason: string, diagnosticId?: string) => {
|
||||
update(operationId, { status: "failed", errorReason, diagnosticId: diagnosticId ?? operationId });
|
||||
},
|
||||
[update]
|
||||
);
|
||||
|
||||
const isPending = useCallback(
|
||||
(targetId: string, intent?: string) =>
|
||||
operations.some(
|
||||
(operation) => operation.status === "pending" && operation.targetId === targetId && (intent === undefined || operation.intent === intent)
|
||||
),
|
||||
[operations]
|
||||
);
|
||||
|
||||
return { operations, begin, update, succeed, fail, isPending };
|
||||
}
|
||||
Reference in New Issue
Block a user