Repository navigation
Expand file tree
/
Copy pathusePullRequestFilesViewed.ts
More file actions
85 lines (84 loc) · 3.75 KB
/
Copy pathusePullRequestFilesViewed.ts
File metadata and controls
85 lines (84 loc) · 3.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
import { usePullRequestConnected } from "./usePullRequestConnected";
import { RegistryContext, useAtomRefresh, useAtomValue } from "@effect/atom-react";
import { squashAtomCommandFailure } from "@t3tools/client-runtime/state/runtime";
import type { PullRequestDetail } from "@t3tools/contracts";
import { AsyncResult } from "effect/reactivity";
import { use, useState } from "react";
import { composerPullRequests } from "../../state/pull-requests";
import { useEnvironmentQuery } from "../../state/query";
import { useAtomCommand } from "../../state/use-atom-command";
import { pullRequestTarget, type PullRequestParams } from "./usePullRequestDiff";
import { pullRequestReviewKey } from "./pullRequestReviewDrafts";
import { viewedOverlay, type ViewedState } from "./pullRequestFilesViewed";
export function usePullRequestFilesViewed(params: PullRequestParams, pr: PullRequestDetail | null) {
const registry = use(RegistryContext);
const target = pullRequestTarget(params);
const scopeKey = pullRequestReviewKey(params);
const supported = Boolean(pr?.capabilities.viewedFiles);
const permission = useAtomValue(
composerPullRequests.setFilesViewed.permissionAtom(target.environmentId),
);
const connected = usePullRequestConnected(params.environmentId);
const allowed = connected && permission;
const query = useEnvironmentQuery(supported ? composerPullRequests.filesViewed(target) : null);
const refresh = useAtomRefresh(composerPullRequests.filesViewed(target));
const write = useAtomCommand(composerPullRequests.setFilesViewed, { reportFailure: false });
const [state, setState] = useState<ViewedState>({ key: scopeKey, presses: {}, error: null });
const presses = state.key === scopeKey ? state.presses : {};
const error = state.key === scopeKey ? state.error : null;
const overlay = viewedOverlay(presses, query.data);
const update = (change: (current: ViewedState) => ViewedState) =>
setState((current) =>
change(current.key === scopeKey ? current : { key: scopeKey, presses: {}, error: null }),
);
const serverViewed = (path: string) =>
query.data?.files.some((file) => file.path === path && file.state === "viewed") ?? false;
const isViewed = (path: string) => overlay[path] ?? serverViewed(path);
const toggle = async (path: string) => {
if (!supported || !allowed || !query.data) return;
const viewed = !isViewed(path);
const version = (presses[path]?.version ?? 0) + 1;
update((current) => ({
...current,
error: null,
presses: { ...current.presses, [path]: { viewed, version, awaitingReadAfter: undefined } },
}));
const response = await write({
...target,
input: { ...target.input, files: [{ path, viewed }] },
});
// The read that is current once the write lands still predates it; a newer one carries it.
const readAtWrite = AsyncResult.getOrElse(
registry.get(composerPullRequests.filesViewed(target)),
() => null,
);
setState((current) => {
if (current.key !== scopeKey || current.presses[path]?.version !== version) return current;
if (response._tag === "Success")
return {
...current,
presses: {
...current.presses,
[path]: { viewed, version, awaitingReadAfter: { read: readAtWrite } },
},
};
const rest = { ...current.presses };
delete rest[path];
const cause = squashAtomCommandFailure(response);
return {
...current,
presses: rest,
error: cause instanceof Error ? cause.message : "Could not update viewed files.",
};
});
refresh();
};
return {
supported,
canToggle: supported && allowed && Boolean(query.data),
isViewed,
toggle,
error: error ?? query.error,
truncated: query.data?.truncated,
};
}