Skip to content
Source
Sign inSign up

discardComposerDraft.ts

83 lines3.0 KB

1import * as Cause from "effect/Cause";
2import { AsyncResult } from "effect/reactivity";
3​
4import {
5 type ComposerThreadTarget,
6 composerDraftHasUserContent,
7 resolveComposerDraftKey,
8 useComposerDraftStore,
9} from "../composerDraftStore";
10import { showThreadUndoNotice } from "../hooks/showThreadUndoNotice";
11import * as ThreadUndo from "@t3tools/client-runtime/state/thread-undo";
12import { releaseDraftAttachments } from "./attachmentUploadQueue";
13​
14export function discardComposerDraft(target: ComposerThreadTarget): void {
15 const store = useComposerDraftStore.getState();
16 const key = resolveComposerDraftKey(store, target);
17 const draft = key === null ? undefined : store.draftsByThreadKey[key];
18 if (key === null || !draft) return;
19 const session = store.draftThreadsByThreadKey[key];
20 const logicalProjectKeys = Object.entries(store.logicalProjectDraftThreadKeyByLogicalProjectKey)
21 .filter(([, draftKey]) => draftKey === key)
22 .map(([logicalProjectKey]) => logicalProjectKey);
23 const discardsSession = typeof target === "string";
24​
25 const claim = ThreadUndo.begin("discard", key);
26 if (discardsSession) {
27 store.clearDraftThread(target);
28 } else {
29 store.clearComposerContent(target);
30 }
31​
32 const releaseDiscardedDraft = () => {
33 releaseDraftAttachments([...draft.images, ...draft.files]);
34 // Draft-session removal already revoked these previews.
35 if (!discardsSession) {
36 for (const image of draft.images) {
37 if (image.previewUrl.startsWith("blob:")) URL.revokeObjectURL(image.previewUrl);
38 }
39 }
40 };
41​
42 showThreadUndoNotice({
43 action: "Discarded",
44 claim,
45 failureTitle: "Failed to restore draft",
46 undo: async () => {
47 const current = useComposerDraftStore.getState().draftsByThreadKey[key];
48 if (current && composerDraftHasUserContent(current)) {
49 releaseDiscardedDraft();
50 return AsyncResult.failure(Cause.fail(new Error("The draft has new content.")));
51 }
52 useComposerDraftStore.setState((state) => {
53 const logicalProjectDraftThreadKeyByLogicalProjectKey = {
54 ...state.logicalProjectDraftThreadKeyByLogicalProjectKey,
55 };
56 for (const logicalProjectKey of logicalProjectKeys) {
57 logicalProjectDraftThreadKeyByLogicalProjectKey[logicalProjectKey] ??= key;
58 }
59 return {
60 draftsByThreadKey: {
61 ...state.draftsByThreadKey,
62 [key]: discardsSession
63 ? {
64 ...draft,
65 images: draft.images.map((image) =>
66 image.previewUrl.startsWith("blob:")
67 ? { ...image, previewUrl: URL.createObjectURL(image.file) }
68 : image,
69 ),
70 }
71 : draft,
72 },
73 draftThreadsByThreadKey: session
74 ? { ...state.draftThreadsByThreadKey, [key]: session }
75 : state.draftThreadsByThreadKey,
76 logicalProjectDraftThreadKeyByLogicalProjectKey,
77 };
78 });
79 return AsyncResult.success(undefined);
80 },
81 commit: releaseDiscardedDraft,
82 });