@cosmicdrift/kumiko-document-designer 0.388.0 → 0.389.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.
package/dist/feature.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { access, defineFeature, i18nKey, } from "@cosmicdrift/kumiko-framework/engine";
2
2
  import { TEMPLATE_FACETS, templateColumns, templateRowActions } from "./list-definition.js";
3
- import { DOCUMENT_DESIGNER_FEATURE, LAYOUT_EDITOR_SCREEN_ID, MAIL_EDITOR_SCREEN_ID, PDF_TEMPLATE_EDITOR_SCREEN_ID, PLATFORM_TEMPLATES_NAV_ID, PLATFORM_TEMPLATES_SCREEN_ID, TEMPLATES_NAV_ID, TEMPLATES_SCREEN_ID, } from "./web/constants.js";
3
+ import { DOCUMENT_DESIGNER_FEATURE, LAYOUT_EDITOR_SCREEN_ID, MAIL_EDITOR_SCREEN_ID, PDF_TEMPLATE_EDITOR_SCREEN_ID, PLATFORM_LAYOUT_EDITOR_SCREEN_ID, PLATFORM_TEMPLATES_NAV_ID, PLATFORM_TEMPLATES_SCREEN_ID, TEMPLATES_NAV_ID, TEMPLATES_SCREEN_ID, } from "./web/constants.js";
4
4
  import { DOCUMENT_DESIGNER_I18N } from "./web/i18n.js";
5
5
  const OVERVIEW_QUERY = "template-resolver:query:overview";
6
6
  export function createDocumentDesignerFeature(options = {}) {
@@ -20,7 +20,7 @@ export function createDocumentDesignerFeature(options = {}) {
20
20
  query: OVERVIEW_QUERY,
21
21
  columns: templateColumns({ platform: false }),
22
22
  facets: [...TEMPLATE_FACETS],
23
- rowActions: templateRowActions(),
23
+ rowActions: templateRowActions({ platform: false }),
24
24
  searchable: true,
25
25
  searchPlaceholder: i18nKey("documentDesigner.search"),
26
26
  pagination: "pages",
@@ -41,7 +41,7 @@ export function createDocumentDesignerFeature(options = {}) {
41
41
  query: OVERVIEW_QUERY,
42
42
  columns: templateColumns({ platform: true }),
43
43
  facets: [...TEMPLATE_FACETS],
44
- rowActions: templateRowActions(),
44
+ rowActions: templateRowActions({ platform: true }),
45
45
  searchable: true,
46
46
  searchPlaceholder: i18nKey("documentDesigner.search"),
47
47
  pagination: "pages",
@@ -64,6 +64,14 @@ export function createDocumentDesignerFeature(options = {}) {
64
64
  dormant: true,
65
65
  access: { roles: access.roles(...editorRoles) },
66
66
  });
67
+ r.screen({
68
+ id: PLATFORM_LAYOUT_EDITOR_SCREEN_ID,
69
+ type: "custom",
70
+ renderer: { react: { __component: PLATFORM_LAYOUT_EDITOR_SCREEN_ID } },
71
+ description: "documentDesigner.screen.layoutEditor.description",
72
+ dormant: true,
73
+ access: { roles: access.systemAdmin },
74
+ });
67
75
  r.screen({
68
76
  id: PDF_TEMPLATE_EDITOR_SCREEN_ID,
69
77
  type: "custom",
package/dist/index.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export { createDocumentDesignerFeature, type DocumentDesignerOptions } from "./feature.ts";
2
- export { DEFAULT_TEMPLATE_LOCALES, DOCUMENT_DESIGNER_FEATURE, LAYOUT_EDITOR_SCREEN_ID, MAIL_EDITOR_SCREEN_ID, PDF_TEMPLATE_EDITOR_SCREEN_ID, PLATFORM_TEMPLATES_NAV_ID, PLATFORM_TEMPLATES_SCREEN_ID, TEMPLATE_EDITOR_SCREENS, TEMPLATES_NAV_ID, TEMPLATES_SCREEN_ID, type TemplateEditorKind, } from "./web/constants.ts";
2
+ export { DEFAULT_TEMPLATE_LOCALES, DOCUMENT_DESIGNER_FEATURE, LAYOUT_EDITOR_SCREEN_ID, MAIL_EDITOR_SCREEN_ID, PDF_TEMPLATE_EDITOR_SCREEN_ID, PLATFORM_LAYOUT_EDITOR_SCREEN_ID, PLATFORM_TEMPLATES_NAV_ID, PLATFORM_TEMPLATES_SCREEN_ID, TEMPLATE_EDITOR_SCREENS, TEMPLATES_NAV_ID, TEMPLATES_SCREEN_ID, type TemplateEditorKind, } from "./web/constants.ts";
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  export { createDocumentDesignerFeature } from "./feature.js";
2
- export { DEFAULT_TEMPLATE_LOCALES, DOCUMENT_DESIGNER_FEATURE, LAYOUT_EDITOR_SCREEN_ID, MAIL_EDITOR_SCREEN_ID, PDF_TEMPLATE_EDITOR_SCREEN_ID, PLATFORM_TEMPLATES_NAV_ID, PLATFORM_TEMPLATES_SCREEN_ID, TEMPLATE_EDITOR_SCREENS, TEMPLATES_NAV_ID, TEMPLATES_SCREEN_ID, } from "./web/constants.js";
2
+ export { DEFAULT_TEMPLATE_LOCALES, DOCUMENT_DESIGNER_FEATURE, LAYOUT_EDITOR_SCREEN_ID, MAIL_EDITOR_SCREEN_ID, PDF_TEMPLATE_EDITOR_SCREEN_ID, PLATFORM_LAYOUT_EDITOR_SCREEN_ID, PLATFORM_TEMPLATES_NAV_ID, PLATFORM_TEMPLATES_SCREEN_ID, TEMPLATE_EDITOR_SCREENS, TEMPLATES_NAV_ID, TEMPLATES_SCREEN_ID, } from "./web/constants.js";
@@ -3,4 +3,6 @@ export declare function templateColumns(options: {
3
3
  readonly platform: boolean;
4
4
  }): ListColumnSpec[];
5
5
  export declare const TEMPLATE_FACETS: readonly ListFacetSpec[];
6
- export declare function templateRowActions(): RowAction[];
6
+ export declare function templateRowActions(options: {
7
+ readonly platform: boolean;
8
+ }): RowAction[];
@@ -1,6 +1,6 @@
1
1
  import { TemplateResolverHandlers } from "@cosmicdrift/kumiko-bundled-features/template-resolver";
2
2
  import { i18nKey, } from "@cosmicdrift/kumiko-framework/engine";
3
- import { DocumentDesignerCells, TEMPLATE_EDITOR_SCREENS, } from "./web/constants.js";
3
+ import { DocumentDesignerCells, PLATFORM_LAYOUT_EDITOR_SCREEN_ID, TEMPLATE_EDITOR_SCREENS, } from "./web/constants.js";
4
4
  function cell(component) {
5
5
  return { react: { __component: component } };
6
6
  }
@@ -85,6 +85,9 @@ export const TEMPLATE_FACETS = [
85
85
  ],
86
86
  },
87
87
  ];
88
+ function isLayoutKind(kind) {
89
+ return kind === "mail-layout" || kind === "pdf-layout";
90
+ }
88
91
  const EDITABLE_KINDS = [
89
92
  "mail",
90
93
  "document-pdf",
@@ -101,9 +104,13 @@ const RESET_LOCKED_OVERRIDE_ACTION = {
101
104
  style: "danger",
102
105
  visible: { field: "canResetLockedOverride", eq: true },
103
106
  };
104
- export function templateRowActions() {
107
+ // The screen the row is opened from fixes the layout scope: the platform overview edits the system
108
+ // layout, the tenant overview always the tenant's own, even for a SystemAdmin on a system-origin row.
109
+ export function templateRowActions(options) {
105
110
  const editActions = EDITABLE_KINDS.flatMap((kind) => {
106
- const screen = TEMPLATE_EDITOR_SCREENS[kind];
111
+ const screen = options.platform && isLayoutKind(kind)
112
+ ? PLATFORM_LAYOUT_EDITOR_SCREEN_ID
113
+ : TEMPLATE_EDITOR_SCREENS[kind];
107
114
  if (screen === undefined)
108
115
  return [];
109
116
  return [
@@ -4,14 +4,17 @@ import { mergeTranslations, translationsByLocaleFromKeys, useNav, } from "@cosmi
4
4
  import { LoadingState } from "@cosmicdrift/kumiko-renderer/web";
5
5
  import { lazy, Suspense } from "react";
6
6
  import { createLocalesCell, KindCell, ModifiedCell, OriginCell, OverriddenByCell, StatusCell, TitleCell, UsedByCell, } from "./cells.js";
7
- import { DEFAULT_TEMPLATE_LOCALES, DOCUMENT_DESIGNER_FEATURE, DocumentDesignerCells, LAYOUT_EDITOR_SCREEN_ID, MAIL_EDITOR_SCREEN_ID, PDF_TEMPLATE_EDITOR_SCREEN_ID, } from "./constants.js";
7
+ import { DEFAULT_TEMPLATE_LOCALES, DOCUMENT_DESIGNER_FEATURE, DocumentDesignerCells, LAYOUT_EDITOR_SCREEN_ID, MAIL_EDITOR_SCREEN_ID, PDF_TEMPLATE_EDITOR_SCREEN_ID, PLATFORM_LAYOUT_EDITOR_SCREEN_ID, } from "./constants.js";
8
8
  import { documentDesignerTranslations } from "./default-translations.js";
9
9
  import { DOCUMENT_DESIGNER_I18N } from "./i18n.js";
10
10
  // The editors pull tiptap, MJML preview and the page canvas; load them on first mount.
11
11
  const LazyLayoutEditor = lazy(() => import("./layout-editor.js").then((module) => ({ default: module.LayoutEditor })));
12
12
  const LazyPdfTemplateEditor = lazy(() => import("./pdf-template-editor.js").then((module) => ({ default: module.PdfTemplateEditor })));
13
13
  function LayoutEditorScreen() {
14
- return (_jsx(Suspense, { fallback: _jsx(LoadingState, {}), children: _jsx(LazyLayoutEditor, {}) }));
14
+ return (_jsx(Suspense, { fallback: _jsx(LoadingState, {}), children: _jsx(LazyLayoutEditor, { scope: "tenant" }) }));
15
+ }
16
+ function PlatformLayoutEditorScreen() {
17
+ return (_jsx(Suspense, { fallback: _jsx(LoadingState, {}), children: _jsx(LazyLayoutEditor, { scope: "system" }) }));
15
18
  }
16
19
  function createLazyMailEditor(appLocales) {
17
20
  const LazyMailEditor = lazy(() => import("./mail-editor.js").then((module) => ({
@@ -47,6 +50,7 @@ export function documentDesignerClient(options = {}) {
47
50
  },
48
51
  components: {
49
52
  [LAYOUT_EDITOR_SCREEN_ID]: LayoutEditorScreen,
53
+ [PLATFORM_LAYOUT_EDITOR_SCREEN_ID]: PlatformLayoutEditorScreen,
50
54
  [PDF_TEMPLATE_EDITOR_SCREEN_ID]: PdfTemplateEditorScreen,
51
55
  [MAIL_EDITOR_SCREEN_ID]: createLazyMailEditor(options.appLocales ?? DEFAULT_TEMPLATE_LOCALES),
52
56
  },
@@ -2,6 +2,7 @@ export declare const DOCUMENT_DESIGNER_FEATURE: "document-designer";
2
2
  export declare const TEMPLATES_SCREEN_ID: "templates";
3
3
  export declare const PLATFORM_TEMPLATES_SCREEN_ID: "platform-templates";
4
4
  export declare const LAYOUT_EDITOR_SCREEN_ID: "layout-editor";
5
+ export declare const PLATFORM_LAYOUT_EDITOR_SCREEN_ID: "platform-layout-editor";
5
6
  export declare const PDF_TEMPLATE_EDITOR_SCREEN_ID: "pdf-template-editor";
6
7
  export declare const MAIL_EDITOR_SCREEN_ID: "mail-editor";
7
8
  export declare const TEMPLATES_NAV_ID: "templates";
@@ -3,6 +3,7 @@ export const DOCUMENT_DESIGNER_FEATURE = "document-designer";
3
3
  export const TEMPLATES_SCREEN_ID = "templates";
4
4
  export const PLATFORM_TEMPLATES_SCREEN_ID = "platform-templates";
5
5
  export const LAYOUT_EDITOR_SCREEN_ID = "layout-editor";
6
+ export const PLATFORM_LAYOUT_EDITOR_SCREEN_ID = "platform-layout-editor";
6
7
  export const PDF_TEMPLATE_EDITOR_SCREEN_ID = "pdf-template-editor";
7
8
  export const MAIL_EDITOR_SCREEN_ID = "mail-editor";
8
9
  export const TEMPLATES_NAV_ID = "templates";
package/dist/web/i18n.js CHANGED
@@ -3,6 +3,7 @@ export const DOCUMENT_DESIGNER_I18N = {
3
3
  "screen:templates.title": { en: "Templates", de: "Vorlagen" },
4
4
  "screen:platform-templates.title": { en: "System templates", de: "System-Vorlagen" },
5
5
  "screen:layout-editor.title": { en: "Layout", de: "Layout" },
6
+ "screen:platform-layout-editor.title": { en: "System layout", de: "System-Layout" },
6
7
  "screen:pdf-template-editor.title": { en: "PDF template", de: "PDF-Vorlage" },
7
8
  "document-designer:nav.templates": { en: "Templates", de: "Vorlagen" },
8
9
  "document-designer:nav.platformTemplates": { en: "System templates", de: "System-Vorlagen" },
@@ -1,2 +1,4 @@
1
1
  import { type ReactNode } from "react";
2
- export declare function LayoutEditor(): ReactNode;
2
+ export declare function LayoutEditor({ scope, }: {
3
+ readonly scope?: "tenant" | "system";
4
+ }): ReactNode;
@@ -11,7 +11,11 @@ function parseLayoutKind(raw) {
11
11
  return raw === "pdf-layout" ? "pdf-layout" : "mail-layout";
12
12
  }
13
13
  function LayoutEditorLoader({ kind, slug, isSystemAdmin, onKindChange, }) {
14
- const source = useQuery(TemplateResolverQueries.layoutSource, { kind, slug });
14
+ const source = useQuery(TemplateResolverQueries.layoutSource, {
15
+ kind,
16
+ slug,
17
+ scope: isSystemAdmin ? "system" : "tenant",
18
+ });
15
19
  const overview = useQuery(TemplateResolverQueries.overview, {
16
20
  limit: 200,
17
21
  layoutSlug: slug,
@@ -38,10 +42,10 @@ function LayoutEditorLoader({ kind, slug, isSystemAdmin, onKindChange, }) {
38
42
  const bodyKey = [kind, slug, data.tenant?.id, data.tenant?.version, data.system?.version].join("|");
39
43
  return (_jsx(LayoutEditorBody, { kind: kind, slug: slug, isSystemAdmin: isSystemAdmin, source: data, overviewRows: rows, usedByCount: overview.data?.total ?? rows.length, overrides: isSystemAdmin ? overrides.data : null, onKindChange: onKindChange, onReload: reload }, bodyKey));
40
44
  }
41
- export function LayoutEditor() {
45
+ export function LayoutEditor({ scope = "tenant", }) {
42
46
  const nav = useNav();
43
47
  const roles = useUserRoles();
44
48
  const [kind, setKind] = useState(() => parseLayoutKind(nav.searchParams["kind"]));
45
49
  const slug = nav.searchParams["slug"] ?? DEFAULT_LAYOUT_SLUG;
46
- return (_jsx(LayoutEditorLoader, { kind: kind, slug: slug, isSystemAdmin: roles?.includes("SystemAdmin") === true, onKindChange: setKind }, kind));
50
+ return (_jsx(LayoutEditorLoader, { kind: kind, slug: slug, isSystemAdmin: scope === "system" && roles?.includes("SystemAdmin") === true, onKindChange: setKind }, kind));
47
51
  }
@@ -1,7 +1,7 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { TemplateResolverHandlers, TemplateResolverQueries, } from "@cosmicdrift/kumiko-bundled-features/template-resolver/web";
3
3
  import { dispatcherErrorText, useMutation, usePrimitives, useQuery, useTranslation, useUnsavedInputSource, } from "@cosmicdrift/kumiko-renderer";
4
- import { useState } from "react";
4
+ import { useEffect, useRef, useState } from "react";
5
5
  import { EditorHeader, EditorLocaleSwitch, EditorModeSwitch, originOf, } from "./editor-header.js";
6
6
  import { EditorBody, EditorFooter, EditorFrame } from "./editor-shell.js";
7
7
  import { fileUrl } from "./file-name.js";
@@ -84,6 +84,20 @@ export function MailEditorBody(props) {
84
84
  scope: isSystemAdmin ? "system" : "tenant",
85
85
  }).data;
86
86
  const pickedVariables = pickDeclaredVariables(picked, validation?.variables);
87
+ // A pick belongs to the variables it was made for: after the declared set changes it is dropped,
88
+ // so a re-added variable does not revive an old record value.
89
+ const declaredVariables = validation?.variables;
90
+ const declaredVariableNames = declaredVariables === undefined ? null : Object.keys(declaredVariables).sort().join("|");
91
+ const previousDeclaredNames = useRef(declaredVariableNames);
92
+ useEffect(() => {
93
+ if (declaredVariableNames === null)
94
+ return;
95
+ if (previousDeclaredNames.current !== null &&
96
+ previousDeclaredNames.current !== declaredVariableNames) {
97
+ setPicked(null);
98
+ }
99
+ previousDeclaredNames.current = declaredVariableNames;
100
+ }, [declaredVariableNames]);
87
101
  const update = (patch) => {
88
102
  const next = { ...draft, ...patch };
89
103
  setDraft(next);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-document-designer",
3
- "version": "0.388.0",
3
+ "version": "0.389.0",
4
4
  "description": "Document designer for Kumiko: template overview, layout editor (mail layout, PDF letterhead) and the PDF template editor.",
5
5
  "license": "MIT",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -20,11 +20,11 @@
20
20
  "./web/styles.css": "./src/web/styles.css"
21
21
  },
22
22
  "dependencies": {
23
- "@cosmicdrift/kumiko-bundled-features": "0.388.0",
24
- "@cosmicdrift/kumiko-framework": "0.388.0",
25
- "@cosmicdrift/kumiko-renderer": "0.388.0",
26
- "@cosmicdrift/kumiko-renderer-puppeteer-client": "0.388.0",
27
- "@cosmicdrift/kumiko-types": "0.388.0",
23
+ "@cosmicdrift/kumiko-bundled-features": "0.389.0",
24
+ "@cosmicdrift/kumiko-framework": "0.389.0",
25
+ "@cosmicdrift/kumiko-renderer": "0.389.0",
26
+ "@cosmicdrift/kumiko-renderer-puppeteer-client": "0.389.0",
27
+ "@cosmicdrift/kumiko-types": "0.389.0",
28
28
  "react": "^19.2.6"
29
29
  },
30
30
  "devDependencies": {
package/src/changes.json CHANGED
@@ -1,4 +1,16 @@
1
1
  [
2
+ {
3
+ "version": "0.389.0",
4
+ "type": "fix",
5
+ "title": "layout editor scope follows the entry screen",
6
+ "detail": "The tenant overview opens layout rows in the tenant editor even for a SystemAdmin, so a system-origin row there loads and saves the tenant layout through layout-source with scope tenant and upsert-tenant. The platform overview opens the new platform-layout-editor screen (PLATFORM_LAYOUT_EDITOR_SCREEN_ID) for the system layout. layout-source takes an optional scope and answers 403 to scope system for non-SystemAdmins. templateRowActions now takes { platform }."
7
+ },
8
+ {
9
+ "version": "0.389.0",
10
+ "type": "fix",
11
+ "title": "mail editor resets the record pick when variables change",
12
+ "detail": "The picked record values are cleared as soon as the set of declared variables changes, so a removed or renamed variable no longer leaves a stale pick behind and a re-added variable does not revive an old value."
13
+ },
2
14
  {
3
15
  "version": "0.388.0",
4
16
  "type": "fix",