Skip to content
Source
Sign inSign up

preparingComposerImages.ts

83 lines2.8 KB

1import { create } from "zustand";
2​
3export interface PreparingComposerImage {
4 readonly id: string;
5 readonly name: string;
6 /** The uncompressed source, thumbnailed so the placeholder shows the image itself. */
7 readonly file: File;
8 /** Looks like an image the draft already has: it holds its slot but shows no tile. */
9 readonly hidden: boolean;
11​
12const NO_PREPARING_IMAGES: ReadonlyArray<PreparingComposerImage> = [];
13​
14/**
15 * Images accepted into a draft but still being compressed, keyed by attachment target. Large
16 * screenshots take a noticeable moment to re-encode, so the strip shows these right away and
17 * they hold their attachment slots until the real image joins the draft.
18 */
19const usePreparingComposerImageStore = create<{
20 readonly byTargetKey: Readonly<Record<string, ReadonlyArray<PreparingComposerImage>>>;
21}>(() => ({ byTargetKey: {} }));
22​
23export function usePreparingComposerImages(targetKey: string) {
24 return usePreparingComposerImageStore(
25 (state) => state.byTargetKey[targetKey] ?? NO_PREPARING_IMAGES,
26 );
28​
29export function getPreparingComposerImages(
30 targetKey: string,
31): ReadonlyArray<PreparingComposerImage> {
32 return usePreparingComposerImageStore.getState().byTargetKey[targetKey] ?? NO_PREPARING_IMAGES;
34​
35export function countPreparingComposerImages(targetKey: string): number {
36 return getPreparingComposerImages(targetKey).length;
38​
39export function addPreparingComposerImages(
40 targetKey: string,
41 images: ReadonlyArray<PreparingComposerImage>,
42): void {
43 usePreparingComposerImageStore.setState((state) => ({
44 byTargetKey: {
45 ...state.byTargetKey,
46 [targetKey]: [...(state.byTargetKey[targetKey] ?? NO_PREPARING_IMAGES), ...images],
47 },
48 }));
50​
51export function isPreparingComposerImage(targetKey: string, imageId: string): boolean {
52 return (
53 usePreparingComposerImageStore
54 .getState()
55 .byTargetKey[targetKey]?.some((image) => image.id === imageId) ?? false
56 );
58​
59/** Returns the ids that were still preparing; any other id was cancelled from the strip. */
60export function removePreparingComposerImages(
61 targetKey: string,
62 imageIds: ReadonlySet<string>,
63): ReadonlySet<string> {
64 const current =
65 usePreparingComposerImageStore.getState().byTargetKey[targetKey] ?? NO_PREPARING_IMAGES;
66 const removed = new Set(
67 current.filter((image) => imageIds.has(image.id)).map((image) => image.id),
68 );
69 if (removed.size === 0) return removed;
70 usePreparingComposerImageStore.setState((state) => {
71 const remaining = (state.byTargetKey[targetKey] ?? NO_PREPARING_IMAGES).filter(
72 (image) => !removed.has(image.id),
73 );
74 const byTargetKey = { ...state.byTargetKey };
75 if (remaining.length > 0) {
76 byTargetKey[targetKey] = remaining;
77 } else {
78 delete byTargetKey[targetKey];
79 }
80 return { byTargetKey };
81 });
82 return removed;