@cosmicdrift/kumiko-document-designer 0.388.0 → 0.390.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/feature.js +11 -3
  2. package/dist/index.d.ts +1 -1
  3. package/dist/index.js +1 -1
  4. package/dist/list-definition.d.ts +3 -1
  5. package/dist/list-definition.js +10 -3
  6. package/dist/web/client-plugin.js +6 -2
  7. package/dist/web/constants.d.ts +1 -0
  8. package/dist/web/constants.js +1 -0
  9. package/dist/web/i18n.js +1 -0
  10. package/dist/web/layout-editor-body.d.ts +3 -14
  11. package/dist/web/layout-editor-body.js +25 -172
  12. package/dist/web/layout-editor-center.d.ts +7 -0
  13. package/dist/web/layout-editor-center.js +23 -0
  14. package/dist/web/layout-editor-logic.d.ts +97 -0
  15. package/dist/web/layout-editor-logic.js +143 -0
  16. package/dist/web/layout-editor-paper.d.ts +4 -0
  17. package/dist/web/layout-editor-paper.js +24 -0
  18. package/dist/web/layout-editor.d.ts +3 -1
  19. package/dist/web/layout-editor.js +7 -3
  20. package/dist/web/mail-editor-body.d.ts +4 -16
  21. package/dist/web/mail-editor-body.js +27 -204
  22. package/dist/web/mail-editor-logic.d.ts +70 -0
  23. package/dist/web/mail-editor-logic.js +81 -0
  24. package/dist/web/mail-editor-model.d.ts +105 -0
  25. package/dist/web/mail-editor-model.js +114 -0
  26. package/dist/web/mail-editor-render.d.ts +11 -0
  27. package/dist/web/mail-editor-render.js +51 -0
  28. package/dist/web/mail-preview-render.d.ts +5 -0
  29. package/dist/web/mail-preview-render.js +22 -0
  30. package/dist/web/pdf-editor-actions.d.ts +27 -0
  31. package/dist/web/pdf-editor-actions.js +146 -0
  32. package/dist/web/pdf-editor-chrome.d.ts +6 -0
  33. package/dist/web/pdf-editor-chrome.js +60 -0
  34. package/dist/web/pdf-editor-context.d.ts +22 -0
  35. package/dist/web/pdf-editor-context.js +1 -0
  36. package/dist/web/pdf-editor-draft.d.ts +23 -0
  37. package/dist/web/pdf-editor-draft.js +33 -0
  38. package/dist/web/pdf-editor-panes.d.ts +6 -0
  39. package/dist/web/pdf-editor-panes.js +40 -0
  40. package/dist/web/pdf-editor-view.d.ts +52 -0
  41. package/dist/web/pdf-editor-view.js +89 -0
  42. package/dist/web/pdf-template-editor.d.ts +1 -1
  43. package/dist/web/pdf-template-editor.js +40 -342
  44. package/dist/web/use-layout-editor-state.d.ts +62 -0
  45. package/dist/web/use-layout-editor-state.js +155 -0
  46. package/dist/web/use-mail-editor-parts.d.ts +42 -0
  47. package/dist/web/use-mail-editor-parts.js +74 -0
  48. package/dist/web/use-mail-editor-state.d.ts +89 -0
  49. package/dist/web/use-mail-editor-state.js +55 -0
  50. package/dist/web/use-pdf-editor-state.d.ts +77 -0
  51. package/dist/web/use-pdf-editor-state.js +161 -0
  52. package/package.json +6 -6
  53. package/src/changes.json +30 -0
@@ -0,0 +1,62 @@
1
+ import { type EditorTab, type Region, type Translate } from "./layout-editor-logic.ts";
2
+ import type { BrandingView, LayoutKind, LayoutSourceView, LayoutValidation, TenantOverrideCounts } from "./layout-types.ts";
3
+ import type { OverviewRowView } from "./overview-row.ts";
4
+ export type LayoutEditorBodyProps = {
5
+ readonly kind: LayoutKind;
6
+ readonly slug: string;
7
+ readonly isSystemAdmin: boolean;
8
+ readonly source: LayoutSourceView;
9
+ readonly overviewRows: readonly OverviewRowView[];
10
+ readonly usedByCount: number;
11
+ readonly overrides: TenantOverrideCounts | null;
12
+ readonly onKindChange: (kind: LayoutKind) => void;
13
+ readonly onReload: () => Promise<void>;
14
+ };
15
+ export declare function useLayoutEditorState(props: LayoutEditorBodyProps, t: Translate): {
16
+ isMail: boolean;
17
+ showMjmlTab: boolean;
18
+ tab: EditorTab;
19
+ setTab: import("react").Dispatch<import("react").SetStateAction<EditorTab>>;
20
+ header: string;
21
+ setHeader: import("react").Dispatch<import("react").SetStateAction<string>>;
22
+ signature: string;
23
+ setSignature: import("react").Dispatch<import("react").SetStateAction<string>>;
24
+ footer: string;
25
+ setFooter: import("react").Dispatch<import("react").SetStateAction<string>>;
26
+ mjml: string;
27
+ setMjml: import("react").Dispatch<import("react").SetStateAction<string>>;
28
+ appearance: import("./layout-meta.ts").LayoutAppearance;
29
+ setAppearance: import("react").Dispatch<import("react").SetStateAction<import("./layout-meta.ts").LayoutAppearance>>;
30
+ activeRegion: Region;
31
+ setActiveRegion: import("react").Dispatch<import("react").SetStateAction<Region>>;
32
+ toolbarHost: HTMLElement | null;
33
+ setToolbarHost: import("react").Dispatch<import("react").SetStateAction<HTMLElement | null>>;
34
+ pendingKind: LayoutKind | null;
35
+ setPendingKind: import("react").Dispatch<import("react").SetStateAction<LayoutKind | null>>;
36
+ saveError: string | null;
37
+ setSaveError: import("react").Dispatch<import("react").SetStateAction<string | null>>;
38
+ branding: import("@cosmicdrift/kumiko-renderer").UseQueryResult<BrandingView>;
39
+ mjmlMode: boolean;
40
+ appearanceFromMjml: boolean;
41
+ dirty: boolean;
42
+ checked: LayoutValidation | null;
43
+ shownRow: import("./layout-types.ts").LayoutSourceRow | null;
44
+ userCount: number;
45
+ placeholders: readonly string[];
46
+ placeholderLabels: Readonly<Record<string, string>>;
47
+ requestKind: (next: LayoutKind) => void;
48
+ save: (publish: boolean) => Promise<void>;
49
+ busy: boolean;
50
+ canSave: boolean;
51
+ canPublish: boolean;
52
+ footerMessage: import("./layout-editor-logic.ts").FooterMessage;
53
+ accentColor: string;
54
+ showAccentLine: boolean;
55
+ blocks: ("align" | "button" | "divider" | "image" | "pageBreak" | "repeat" | "table")[];
56
+ resourcePreviews: Readonly<Record<string, {
57
+ readonly src: string;
58
+ readonly caption: string;
59
+ }>> | undefined;
60
+ discard: () => void;
61
+ };
62
+ export type LayoutEditorModel = ReturnType<typeof useLayoutEditorState>;
@@ -0,0 +1,155 @@
1
+ import { TemplateResolverHandlers, TemplateResolverQueries, } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
2
+ import { useMutation, useQuery } from "@cosmicdrift/kumiko-renderer";
3
+ import { useState } from "react";
4
+ import { BRANDING_QUERY_QN } from "./constants.js";
5
+ import { accentLineShown, appearanceFlags, footerMessageOf, initialTab, layoutBlocks, layoutContent, layoutDirty, layoutSaveFlags, resourcePreviewsOf, saveLayout, shownRowOf, } from "./layout-editor-logic.js";
6
+ import { appearanceOf } from "./layout-meta.js";
7
+ import { systemLabelMap } from "./mail-draft.js";
8
+ import { useDebouncedValue } from "./use-debounced-value.js";
9
+ // Server-side validation compiles the whole layout (mjml), so wait for a pause in typing.
10
+ const VALIDATE_DEBOUNCE_MS = 400;
11
+ export function useLayoutEditorState(props, t) {
12
+ const { kind, slug, isSystemAdmin, source, usedByCount, onKindChange, onReload } = props;
13
+ const isMail = kind === "mail-layout";
14
+ const showMjmlTab = isSystemAdmin && source.mjmlSource !== null;
15
+ const [tab, setTab] = useState(initialTab(showMjmlTab, source));
16
+ const [header, setHeader] = useState(source.regions.header);
17
+ const [signature, setSignature] = useState(source.regions.signature);
18
+ const [footer, setFooter] = useState(source.regions.footer);
19
+ const [mjml, setMjml] = useState(source.mjmlSource ?? "");
20
+ const storedAppearance = appearanceOf(source.meta);
21
+ const [appearance, setAppearance] = useState(storedAppearance);
22
+ const [activeRegion, setActiveRegion] = useState("header");
23
+ const [toolbarHost, setToolbarHost] = useState(null);
24
+ const [pendingKind, setPendingKind] = useState(null);
25
+ const [saveError, setSaveError] = useState(null);
26
+ const upsertTenant = useMutation(TemplateResolverHandlers.upsertTenant);
27
+ const upsertSystem = useMutation(TemplateResolverHandlers.upsertSystem);
28
+ const publishSystem = useMutation(TemplateResolverHandlers.publishSystem);
29
+ const branding = useQuery(BRANDING_QUERY_QN, {});
30
+ const mjmlMode = tab === "mjml";
31
+ const { appearanceFromMjml, appearanceEditable, appearanceValid } = appearanceFlags({
32
+ isMail,
33
+ mjmlMode,
34
+ source,
35
+ appearance,
36
+ });
37
+ const dirty = layoutDirty({
38
+ mjmlMode,
39
+ mjml,
40
+ header,
41
+ signature,
42
+ footer,
43
+ appearance,
44
+ storedAppearance,
45
+ appearanceEditable,
46
+ source,
47
+ });
48
+ const content = layoutContent({ mjmlMode, mjml, header, signature, footer, isMail });
49
+ const debouncedContent = useDebouncedValue(content, VALIDATE_DEBOUNCE_MS);
50
+ const validation = useQuery(TemplateResolverQueries.validate, {
51
+ slug,
52
+ kind,
53
+ contentFormat: mjmlMode ? "mjml" : "html",
54
+ content: debouncedContent,
55
+ locale: source.locale,
56
+ scope: isSystemAdmin ? "system" : "tenant",
57
+ });
58
+ const checked = validation.data;
59
+ const shownRow = shownRowOf(isSystemAdmin, source);
60
+ const userCount = usedByCount;
61
+ const placeholders = checked?.systemPlaceholders ?? [];
62
+ const placeholderLabels = systemLabelMap(t, placeholders);
63
+ const requestKind = (next) => {
64
+ // skip: the requested kind is already open
65
+ if (next === kind)
66
+ return;
67
+ if (dirty)
68
+ setPendingKind(next);
69
+ else
70
+ onKindChange(next);
71
+ };
72
+ const save = (publish) => saveLayout({
73
+ publish,
74
+ kind,
75
+ slug,
76
+ source,
77
+ content,
78
+ mjmlMode,
79
+ isMail,
80
+ isSystemAdmin,
81
+ appearanceEditable,
82
+ appearance,
83
+ t,
84
+ upsertTenant,
85
+ upsertSystem,
86
+ publishSystem,
87
+ setSaveError,
88
+ onReload,
89
+ });
90
+ const busy = upsertTenant.pending || upsertSystem.pending || publishSystem.pending;
91
+ const { canSave, canPublish } = layoutSaveFlags({
92
+ dirty,
93
+ checked,
94
+ appearanceValid,
95
+ busy,
96
+ shownRow,
97
+ });
98
+ const footerMessage = footerMessageOf({ t, checked, appearanceValid, isMail, userCount });
99
+ const accentColor = branding.data?.accentColor ?? "";
100
+ const showAccentLine = accentLineShown({ isMail, appearanceEditable, appearance, accentColor });
101
+ const blocks = layoutBlocks(isMail);
102
+ const resourcePreviews = resourcePreviewsOf(branding.data, isMail, t);
103
+ const discard = () => {
104
+ setHeader(source.regions.header);
105
+ setSignature(source.regions.signature);
106
+ setFooter(source.regions.footer);
107
+ setMjml(source.mjmlSource ?? "");
108
+ setAppearance(storedAppearance);
109
+ setSaveError(null);
110
+ };
111
+ return {
112
+ isMail,
113
+ showMjmlTab,
114
+ tab,
115
+ setTab,
116
+ header,
117
+ setHeader,
118
+ signature,
119
+ setSignature,
120
+ footer,
121
+ setFooter,
122
+ mjml,
123
+ setMjml,
124
+ appearance,
125
+ setAppearance,
126
+ activeRegion,
127
+ setActiveRegion,
128
+ toolbarHost,
129
+ setToolbarHost,
130
+ pendingKind,
131
+ setPendingKind,
132
+ saveError,
133
+ setSaveError,
134
+ branding,
135
+ mjmlMode,
136
+ appearanceFromMjml,
137
+ dirty,
138
+ checked,
139
+ shownRow,
140
+ userCount,
141
+ placeholders,
142
+ placeholderLabels,
143
+ requestKind,
144
+ save,
145
+ busy,
146
+ canSave,
147
+ canPublish,
148
+ footerMessage,
149
+ accentColor,
150
+ showAccentLine,
151
+ blocks,
152
+ resourcePreviews,
153
+ discard,
154
+ };
155
+ }
@@ -0,0 +1,42 @@
1
+ import type { EditorMode } from "./editor-header.tsx";
2
+ import type { ConfirmDialog } from "./mail-dialogs.tsx";
3
+ import type { MailEditorBodyProps } from "./mail-editor-logic.ts";
4
+ export declare function useMailUiState(row: MailEditorBodyProps["row"]): {
5
+ baseline: import("./mail-types.ts").MailDraft;
6
+ draft: import("./mail-types.ts").MailDraft;
7
+ setDraft: import("react").Dispatch<import("react").SetStateAction<import("./mail-types.ts").MailDraft>>;
8
+ settled: import("./mail-types.ts").MailDraft;
9
+ settle: (next: import("./mail-types.ts").MailDraft) => void;
10
+ mode: EditorMode;
11
+ setMode: import("react").Dispatch<import("react").SetStateAction<EditorMode>>;
12
+ creating: boolean;
13
+ setCreating: import("react").Dispatch<import("react").SetStateAction<boolean>>;
14
+ dialog: ConfirmDialog;
15
+ setDialog: import("react").Dispatch<import("react").SetStateAction<ConfirmDialog>>;
16
+ uploadOpen: boolean;
17
+ setUploadOpen: import("react").Dispatch<import("react").SetStateAction<boolean>>;
18
+ pdfOpen: boolean;
19
+ setPdfOpen: import("react").Dispatch<import("react").SetStateAction<boolean>>;
20
+ resourceError: string | null;
21
+ setResourceError: import("react").Dispatch<import("react").SetStateAction<string | null>>;
22
+ resetError: string | null;
23
+ setResetError: import("react").Dispatch<import("react").SetStateAction<string | null>>;
24
+ editorEpoch: number;
25
+ setEditorEpoch: import("react").Dispatch<import("react").SetStateAction<number>>;
26
+ includeAttachments: boolean;
27
+ setIncludeAttachments: import("react").Dispatch<import("react").SetStateAction<boolean>>;
28
+ prefixSubject: boolean;
29
+ setPrefixSubject: import("react").Dispatch<import("react").SetStateAction<boolean>>;
30
+ };
31
+ export type MailUiState = ReturnType<typeof useMailUiState>;
32
+ export type MailServices = ReturnType<typeof useMailServices>;
33
+ export declare function useMailServices(props: MailEditorBodyProps): {
34
+ insert: import("./use-insert-targets.ts").InsertTargets;
35
+ persistence: import("./use-mail-persistence.ts").MailPersistence;
36
+ testMail: import("./use-test-mail.ts").TestMail;
37
+ own: import("./use-own-address.ts").OwnAddress;
38
+ filesAvailable: boolean;
39
+ resourceRemove: import("@cosmicdrift/kumiko-renderer").UseMutationResult<unknown>;
40
+ layouts: import("./use-overview-rows.ts").OverviewRows;
41
+ pdfTemplates: import("./use-overview-rows.ts").OverviewRows;
42
+ };
@@ -0,0 +1,74 @@
1
+ import { TemplateResolverHandlers } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
2
+ import { useMutation } from "@cosmicdrift/kumiko-renderer";
3
+ import { useState } from "react";
4
+ import { loadedMailOf } from "./mail-draft.js";
5
+ import { useFilesCapability } from "./use-files-capability.js";
6
+ import { useInsertTargets } from "./use-insert-targets.js";
7
+ import { useMailPersistence } from "./use-mail-persistence.js";
8
+ import { useOverviewRows } from "./use-overview-rows.js";
9
+ import { useOwnAddress } from "./use-own-address.js";
10
+ import { useSettledValue } from "./use-settled-value.js";
11
+ import { useTestMail } from "./use-test-mail.js";
12
+ export function useMailUiState(row) {
13
+ const [baseline] = useState(() => loadedMailOf(row));
14
+ const [draft, setDraft] = useState(baseline);
15
+ const [settled, settle] = useSettledValue(baseline);
16
+ const [mode, setMode] = useState("edit");
17
+ const [creating, setCreating] = useState(false);
18
+ const [dialog, setDialog] = useState({ kind: "none" });
19
+ const [uploadOpen, setUploadOpen] = useState(false);
20
+ const [pdfOpen, setPdfOpen] = useState(false);
21
+ const [resourceError, setResourceError] = useState(null);
22
+ const [resetError, setResetError] = useState(null);
23
+ const [editorEpoch, setEditorEpoch] = useState(0);
24
+ const [includeAttachments, setIncludeAttachments] = useState(true);
25
+ const [prefixSubject, setPrefixSubject] = useState(true);
26
+ return {
27
+ baseline,
28
+ draft,
29
+ setDraft,
30
+ settled,
31
+ settle,
32
+ mode,
33
+ setMode,
34
+ creating,
35
+ setCreating,
36
+ dialog,
37
+ setDialog,
38
+ uploadOpen,
39
+ setUploadOpen,
40
+ pdfOpen,
41
+ setPdfOpen,
42
+ resourceError,
43
+ setResourceError,
44
+ resetError,
45
+ setResetError,
46
+ editorEpoch,
47
+ setEditorEpoch,
48
+ includeAttachments,
49
+ setIncludeAttachments,
50
+ prefixSubject,
51
+ setPrefixSubject,
52
+ };
53
+ }
54
+ export function useMailServices(props) {
55
+ const { slug, locale, title, isSystemAdmin, row } = props;
56
+ const insert = useInsertTargets();
57
+ const persistence = useMailPersistence({ slug, locale, title, isSystemAdmin, row });
58
+ const testMail = useTestMail();
59
+ const own = useOwnAddress();
60
+ const filesAvailable = useFilesCapability();
61
+ const resourceRemove = useMutation(TemplateResolverHandlers.resourceRemove);
62
+ const layouts = useOverviewRows("mail-layout");
63
+ const pdfTemplates = useOverviewRows("document-pdf");
64
+ return {
65
+ insert,
66
+ persistence,
67
+ testMail,
68
+ own,
69
+ filesAvailable,
70
+ resourceRemove,
71
+ layouts,
72
+ pdfTemplates,
73
+ };
74
+ }
@@ -0,0 +1,89 @@
1
+ import type { MailEditorBodyProps } from "./mail-editor-logic.ts";
2
+ import type { MailValidation, PickedValues } from "./mail-types.ts";
3
+ type Translate = (key: string, params?: Readonly<Record<string, unknown>>) => string;
4
+ export declare function useMailEditorState(props: MailEditorBodyProps, t: Translate): {
5
+ update: (patch: Partial<import("./mail-types.ts").MailDraft>) => void;
6
+ reset: () => void;
7
+ saveAndReload: (publish: boolean) => Promise<void>;
8
+ perform: (target: import("./mail-dialogs.tsx").LeaveTarget) => void;
9
+ leave: (target: import("./mail-dialogs.tsx").LeaveTarget) => void;
10
+ leaveSave: (target: import("./mail-dialogs.tsx").LeaveTarget) => Promise<void>;
11
+ removeResource: (key: string) => Promise<void>;
12
+ sendTestMail: () => void;
13
+ ui: {
14
+ baseline: import("./mail-types.ts").MailDraft;
15
+ draft: import("./mail-types.ts").MailDraft;
16
+ setDraft: import("react").Dispatch<import("react").SetStateAction<import("./mail-types.ts").MailDraft>>;
17
+ settled: import("./mail-types.ts").MailDraft;
18
+ settle: (next: import("./mail-types.ts").MailDraft) => void;
19
+ mode: import("./editor-header.tsx").EditorMode;
20
+ setMode: import("react").Dispatch<import("react").SetStateAction<import("./editor-header.tsx").EditorMode>>;
21
+ creating: boolean;
22
+ setCreating: import("react").Dispatch<import("react").SetStateAction<boolean>>;
23
+ dialog: import("./mail-dialogs.tsx").ConfirmDialog;
24
+ setDialog: import("react").Dispatch<import("react").SetStateAction<import("./mail-dialogs.tsx").ConfirmDialog>>;
25
+ uploadOpen: boolean;
26
+ setUploadOpen: import("react").Dispatch<import("react").SetStateAction<boolean>>;
27
+ pdfOpen: boolean;
28
+ setPdfOpen: import("react").Dispatch<import("react").SetStateAction<boolean>>;
29
+ resourceError: string | null;
30
+ setResourceError: import("react").Dispatch<import("react").SetStateAction<string | null>>;
31
+ resetError: string | null;
32
+ setResetError: import("react").Dispatch<import("react").SetStateAction<string | null>>;
33
+ editorEpoch: number;
34
+ setEditorEpoch: import("react").Dispatch<import("react").SetStateAction<number>>;
35
+ includeAttachments: boolean;
36
+ setIncludeAttachments: import("react").Dispatch<import("react").SetStateAction<boolean>>;
37
+ prefixSubject: boolean;
38
+ setPrefixSubject: import("react").Dispatch<import("react").SetStateAction<boolean>>;
39
+ };
40
+ services: {
41
+ insert: import("./use-insert-targets.ts").InsertTargets;
42
+ persistence: import("./use-mail-persistence.ts").MailPersistence;
43
+ testMail: import("./use-test-mail.ts").TestMail;
44
+ own: import("./use-own-address.ts").OwnAddress;
45
+ filesAvailable: boolean;
46
+ resourceRemove: import("@cosmicdrift/kumiko-renderer").UseMutationResult<unknown>;
47
+ layouts: import("./use-overview-rows.ts").OverviewRows;
48
+ pdfTemplates: import("./use-overview-rows.ts").OverviewRows;
49
+ };
50
+ validation: MailValidation | null;
51
+ picked: Readonly<Record<string, unknown>> | null;
52
+ setPicked: (next: PickedValues | null) => void;
53
+ pickedVariables: Readonly<Record<string, unknown>> | null;
54
+ variableSchema: Readonly<Record<string, unknown>>;
55
+ locked: boolean;
56
+ dirty: boolean;
57
+ busy: boolean;
58
+ canSave: boolean;
59
+ canPublish: boolean;
60
+ resources: readonly import("./mail-types.ts").LinkedResourceView[];
61
+ pdfTitles: Map<string, string>;
62
+ uploadTemplateId: string | null;
63
+ takenKeys: string[];
64
+ attachments: ({
65
+ readonly kind: "file";
66
+ readonly key: string;
67
+ readonly fileRefId: string;
68
+ readonly fileName: string;
69
+ } | {
70
+ readonly kind: "pdf";
71
+ readonly key: string;
72
+ readonly templateSlug: string;
73
+ readonly fileName: string;
74
+ })[];
75
+ resourcePreviews: {
76
+ [k: string]: {
77
+ src: string;
78
+ caption: string;
79
+ };
80
+ };
81
+ railValidation: MailValidation | null;
82
+ vocabulary: import("./mail-edit-center.tsx").EditorVocabulary;
83
+ footer: {
84
+ readonly tone: "bad" | "warn" | "ok";
85
+ readonly text: string;
86
+ } | null;
87
+ fieldErrors: Partial<Record<"previewText" | "subject", string>>;
88
+ };
89
+ export {};
@@ -0,0 +1,55 @@
1
+ import { TemplateResolverQueries } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
2
+ import { useQuery, useUnsavedInputSource } from "@cosmicdrift/kumiko-renderer";
3
+ import { useEffect, useRef, useState } from "react";
4
+ import { buildMeta, isDraftDirty, parseJsonRecord, pickDeclaredVariables } from "./mail-draft.js";
5
+ import { buildMailModel } from "./mail-editor-model.js";
6
+ import { useMailServices, useMailUiState } from "./use-mail-editor-parts.js";
7
+ export function useMailEditorState(props, t) {
8
+ const { slug, locale, isSystemAdmin, row } = props;
9
+ const ui = useMailUiState(row);
10
+ const [picked, setPicked] = useState(null);
11
+ const services = useMailServices(props);
12
+ const locked = row === null && !ui.creating;
13
+ const variableSchema = parseJsonRecord(row?.variableSchema);
14
+ const validation = useQuery(TemplateResolverQueries.validate, {
15
+ slug,
16
+ kind: "mail",
17
+ contentFormat: "html",
18
+ content: ui.settled.body,
19
+ meta: buildMeta(ui.settled),
20
+ variableSchema,
21
+ locale,
22
+ scope: isSystemAdmin ? "system" : "tenant",
23
+ }).data;
24
+ const pickedVariables = pickDeclaredVariables(picked, validation?.variables);
25
+ // A pick belongs to the variables it was made for: after the declared set changes it is dropped,
26
+ // so a re-added variable does not revive an old record value.
27
+ const declaredVariables = validation?.variables;
28
+ const declaredVariableNames = declaredVariables === undefined ? null : Object.keys(declaredVariables).sort().join("|");
29
+ const previousDeclaredNames = useRef(declaredVariableNames);
30
+ useEffect(() => {
31
+ // skip: validation has not loaded, so there is no declared set to compare
32
+ if (declaredVariableNames === null)
33
+ return;
34
+ if (previousDeclaredNames.current !== null &&
35
+ previousDeclaredNames.current !== declaredVariableNames) {
36
+ setPicked(null);
37
+ }
38
+ previousDeclaredNames.current = declaredVariableNames;
39
+ }, [declaredVariableNames]);
40
+ const dirty = isDraftDirty(ui.draft, ui.baseline);
41
+ useUnsavedInputSource(() => dirty);
42
+ return buildMailModel({
43
+ props,
44
+ t,
45
+ ui,
46
+ picked,
47
+ setPicked,
48
+ services,
49
+ validation,
50
+ variableSchema,
51
+ pickedVariables,
52
+ locked,
53
+ dirty,
54
+ });
55
+ }
@@ -0,0 +1,77 @@
1
+ import type { EditorMode } from "./editor-header.tsx";
2
+ import { type EditorState, type GeneratedPdf } from "./pdf-editor-draft.ts";
3
+ import { type PuppeteerStatus, type TemplateDetail, type TemplateListRow, type TemplateScope, type TenantOverrides, type ValidationResult } from "./types.ts";
4
+ type Translate = (key: string, params?: Readonly<Record<string, unknown>>) => string;
5
+ export declare function usePdfEditorState(input: {
6
+ readonly slug: string;
7
+ readonly requestedLocale: string | undefined;
8
+ }, t: Translate): {
9
+ host: {
10
+ insert: import("./use-insert-targets.ts").InsertTargets;
11
+ filesAvailable: boolean;
12
+ overview: import("./use-overview-rows.ts").OverviewRows;
13
+ scope: TemplateScope;
14
+ };
15
+ doc: {
16
+ rowsQuery: import("@cosmicdrift/kumiko-renderer").UseQueryResult<readonly TemplateListRow[]>;
17
+ locales: readonly string[];
18
+ locale: string;
19
+ row: TemplateListRow | undefined;
20
+ detailQuery: import("@cosmicdrift/kumiko-renderer").UseQueryResult<TemplateDetail | null>;
21
+ detail: TemplateDetail | null;
22
+ setState: import("react").Dispatch<import("react").SetStateAction<EditorState | null>>;
23
+ current: EditorState | null;
24
+ };
25
+ settleTimer: import("react").RefObject<NodeJS.Timeout | null>;
26
+ ui: {
27
+ overridesOpen: boolean;
28
+ setOverridesOpen: import("react").Dispatch<import("react").SetStateAction<boolean>>;
29
+ draftEpoch: number;
30
+ setDraftEpoch: import("react").Dispatch<import("react").SetStateAction<number>>;
31
+ mode: EditorMode;
32
+ setMode: import("react").Dispatch<import("react").SetStateAction<EditorMode>>;
33
+ busy: "pdf" | "publish" | "save" | null;
34
+ setBusy: import("react").Dispatch<import("react").SetStateAction<"pdf" | "publish" | "save" | null>>;
35
+ actionError: string | null;
36
+ setActionError: import("react").Dispatch<import("react").SetStateAction<string | null>>;
37
+ generated: GeneratedPdf | null;
38
+ setGenerated: import("react").Dispatch<import("react").SetStateAction<GeneratedPdf | null>>;
39
+ autoPrintedFor: import("react").RefObject<string | null>;
40
+ pendingLocale: string | null;
41
+ setPendingLocale: import("react").Dispatch<import("react").SetStateAction<string | null>>;
42
+ };
43
+ server: {
44
+ variableSchema: Record<string, unknown>;
45
+ validationQuery: import("@cosmicdrift/kumiko-renderer").UseQueryResult<ValidationResult>;
46
+ validation: ValidationResult | null;
47
+ layout: {
48
+ readonly slug: string;
49
+ readonly scope: TemplateScope;
50
+ readonly html: string;
51
+ readonly regions: import("./types.ts").LayoutRegionsHtml;
52
+ } | null;
53
+ layouts: import("./use-overview-rows.ts").OverviewRows;
54
+ brandingLogoFile: string;
55
+ statusQuery: import("@cosmicdrift/kumiko-renderer").UseQueryResult<PuppeteerStatus>;
56
+ showOrigin: boolean;
57
+ overridesQuery: import("@cosmicdrift/kumiko-renderer").UseQueryResult<TenantOverrides>;
58
+ };
59
+ vocabulary: {
60
+ variables: Readonly<Record<string, import("./mail-types.ts").MailVariable>>;
61
+ imageVariables: string[];
62
+ systemVariables: readonly string[];
63
+ systemLabels: Readonly<Record<string, string>>;
64
+ variableLabels: {
65
+ [x: string]: string;
66
+ };
67
+ variableNames: string[];
68
+ resourcePreviews: {
69
+ [k: string]: {
70
+ src: string;
71
+ caption: string;
72
+ };
73
+ };
74
+ };
75
+ };
76
+ export type PdfEditorState = ReturnType<typeof usePdfEditorState>;
77
+ export {};