camox 0.76.1 → 0.78.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/core/lib/artifactTheme.js +43 -6
- package/dist/features/canvas/CamoxCanvas.js +187 -197
- package/dist/features/canvas/canvasCameraController.js +1 -1
- package/dist/features/canvas/canvasItems.js +40 -9
- package/dist/features/canvas/sidebar/ContentSidebarFrame.js +15 -11
- package/dist/features/canvas/useCanvasLayout.js +34 -0
- package/dist/features/memory/CamoxMemory.js +747 -0
- package/dist/features/preview/EditablePageExperience.js +2 -0
- package/dist/features/runtime/publicCache.d.ts +2 -0
- package/dist/features/runtime/publicCache.js +27 -4
- package/dist/features/runtime/runtime.d.ts +1 -1
- package/dist/features/runtime/runtime.js +34 -15
- package/dist/features/studio/FollowAgentProvider.js +1 -1
- package/dist/features/studio/components/AgentSidebar.js +22 -16
- package/dist/features/studio/components/DraftBar.js +167 -103
- package/dist/features/studio/components/{ReviewDraftDialog.js → DraftReview.js} +191 -218
- package/dist/features/studio/components/DraftsControl.js +174 -202
- package/dist/features/studio/components/DraftsSheet.js +102 -94
- package/dist/features/studio/components/LocalePicker.js +518 -134
- package/dist/features/studio/components/Navbar.js +121 -84
- package/dist/features/studio/components/PublishedTab.js +21 -20
- package/dist/features/studio/routes.js +3 -1
- package/dist/features/studio/studioStore.js +12 -9
- package/dist/features/studio/usePublishDraft.js +116 -0
- package/dist/features/studio/viewSwitch.js +10 -7
- package/dist/lib/locale-path.js +2 -2
- package/dist/lib/queries.js +12 -2
- package/dist/lib/studio-reads.js +6 -0
- package/dist/package.js +1 -1
- package/dist/studio.css +1 -1
- package/package.json +5 -5
- package/skills/camox/references/artifacts.md +7 -0
- package/LICENSE.md +0 -21
|
@@ -4,7 +4,7 @@ import { actionsStore } from "../../provider/actionsStore.js";
|
|
|
4
4
|
import { cn } from "../../../lib/utils.js";
|
|
5
5
|
import { usePageDestinations } from "../../../hooks/use-page-destinations.js";
|
|
6
6
|
import { useLocalePath, useLocalizedPath } from "../../../lib/view-locale.js";
|
|
7
|
-
import { STUDIO_BASE_PATH, STUDIO_CONTENT_PATH, STUDIO_SETTINGS_PATH } from "../routes.js";
|
|
7
|
+
import { STUDIO_BASE_PATH, STUDIO_CONTENT_PATH, STUDIO_MEMORY_PATH, STUDIO_SETTINGS_PATH } from "../routes.js";
|
|
8
8
|
import { studioStore } from "../studioStore.js";
|
|
9
9
|
import { UserButton } from "./UserButton.js";
|
|
10
10
|
import { ConnectAgent } from "./ConnectAgent.js";
|
|
@@ -16,28 +16,43 @@ import * as React from "react";
|
|
|
16
16
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
17
17
|
import { useSelector } from "@xstate/store-react";
|
|
18
18
|
import { buttonVariants } from "@camox/ui/button";
|
|
19
|
-
import { Database, Globe } from "lucide-react";
|
|
19
|
+
import { Brain, Database, Globe } from "lucide-react";
|
|
20
20
|
|
|
21
21
|
//#region src/features/studio/components/Navbar.tsx
|
|
22
|
-
const links = [
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
22
|
+
const links = [
|
|
23
|
+
{
|
|
24
|
+
to: "/",
|
|
25
|
+
title: "Website",
|
|
26
|
+
isWebsite: true,
|
|
27
|
+
children: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Globe, {}), "Website"] }),
|
|
28
|
+
icon: "Globe",
|
|
29
|
+
aliases: ["Website"]
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
to: STUDIO_MEMORY_PATH,
|
|
33
|
+
title: "Memory",
|
|
34
|
+
children: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Brain, {}), "Memory"] }),
|
|
35
|
+
icon: "Brain",
|
|
36
|
+
aliases: [
|
|
37
|
+
"Memory",
|
|
38
|
+
"Memories",
|
|
39
|
+
"Remember"
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
to: STUDIO_CONTENT_PATH,
|
|
44
|
+
title: "Content",
|
|
45
|
+
children: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Database, {}), "Content"] }),
|
|
46
|
+
icon: "FileText",
|
|
47
|
+
aliases: [
|
|
48
|
+
"Content",
|
|
49
|
+
"Pages",
|
|
50
|
+
"CMS"
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
];
|
|
40
54
|
const getNavbarLinks = () => links;
|
|
55
|
+
const WEBSITE_LINK = links.find((link) => link.isWebsite);
|
|
41
56
|
/**
|
|
42
57
|
* Where a navbar link goes. The Website goes back where it was last, Canvas included. In another
|
|
43
58
|
* locale since, a Studio location stays, while a page, whose path may differ there, goes home.
|
|
@@ -48,11 +63,25 @@ function navbarHref(link, last, locale, localized) {
|
|
|
48
63
|
if (last.path.startsWith(`${"/camox"}/`)) return localized(last.path);
|
|
49
64
|
return localized(link.to);
|
|
50
65
|
}
|
|
66
|
+
/** Where the Website link goes now: where the website was last, Canvas included. */
|
|
67
|
+
function useWebsiteHref() {
|
|
68
|
+
const $ = c(3);
|
|
69
|
+
const localized = useLocalizedPath();
|
|
70
|
+
const { locale } = useViewLocale();
|
|
71
|
+
let t0;
|
|
72
|
+
if ($[0] !== locale.tag || $[1] !== localized) {
|
|
73
|
+
t0 = () => navbarHref(WEBSITE_LINK, studioStore.getSnapshot().context.websiteLocation, locale.tag, localized);
|
|
74
|
+
$[0] = locale.tag;
|
|
75
|
+
$[1] = localized;
|
|
76
|
+
$[2] = t0;
|
|
77
|
+
} else t0 = $[2];
|
|
78
|
+
return t0;
|
|
79
|
+
}
|
|
51
80
|
/** Whether `pathname` is in the studio section at `sectionPath`. */
|
|
52
81
|
const isInSection = (sectionPath, pathname) => pathname === sectionPath || pathname.startsWith(`${sectionPath}/`);
|
|
53
82
|
const isSettingsNavbarPath = (pathname) => isInSection(STUDIO_SETTINGS_PATH, pathname);
|
|
54
83
|
const Navbar = (t0) => {
|
|
55
|
-
const $ = c(
|
|
84
|
+
const $ = c(28);
|
|
56
85
|
const { isWebsite: t1, recordPage } = t0;
|
|
57
86
|
const isWebsite = t1 === void 0 ? false : t1;
|
|
58
87
|
const pages = usePageDestinations();
|
|
@@ -68,43 +97,51 @@ const Navbar = (t0) => {
|
|
|
68
97
|
} else t2 = $[1];
|
|
69
98
|
const isSettings = t2;
|
|
70
99
|
let t3;
|
|
71
|
-
if ($[2]
|
|
72
|
-
t3 =
|
|
73
|
-
$[2] =
|
|
74
|
-
|
|
100
|
+
if ($[2] !== pathname) {
|
|
101
|
+
t3 = isInSection(STUDIO_MEMORY_PATH, pathname);
|
|
102
|
+
$[2] = pathname;
|
|
103
|
+
$[3] = t3;
|
|
104
|
+
} else t3 = $[3];
|
|
105
|
+
const isMemory = t3;
|
|
75
106
|
let t4;
|
|
76
|
-
if ($[
|
|
77
|
-
t4 = /* @__PURE__ */ jsx(
|
|
78
|
-
$[3] = isSettings;
|
|
107
|
+
if ($[4] === Symbol.for("react.memo_cache_sentinel")) {
|
|
108
|
+
t4 = /* @__PURE__ */ jsx(ProjectMenu, {});
|
|
79
109
|
$[4] = t4;
|
|
80
110
|
} else t4 = $[4];
|
|
81
111
|
let t5;
|
|
82
|
-
if ($[5] !== isSettings
|
|
83
|
-
t5 = /* @__PURE__ */ jsx(
|
|
112
|
+
if ($[5] !== isSettings) {
|
|
113
|
+
t5 = /* @__PURE__ */ jsx(DraftPicker, { disabled: isSettings });
|
|
114
|
+
$[5] = isSettings;
|
|
115
|
+
$[6] = t5;
|
|
116
|
+
} else t5 = $[6];
|
|
117
|
+
const t6 = isSettings || isMemory;
|
|
118
|
+
let t7;
|
|
119
|
+
if ($[7] !== recordPage || $[8] !== t6) {
|
|
120
|
+
t7 = /* @__PURE__ */ jsx(LocalePicker, {
|
|
84
121
|
recordPage,
|
|
85
|
-
disabled:
|
|
122
|
+
disabled: t6
|
|
86
123
|
});
|
|
87
|
-
$[
|
|
88
|
-
$[
|
|
89
|
-
$[
|
|
90
|
-
} else
|
|
91
|
-
let
|
|
92
|
-
if ($[
|
|
93
|
-
|
|
124
|
+
$[7] = recordPage;
|
|
125
|
+
$[8] = t6;
|
|
126
|
+
$[9] = t7;
|
|
127
|
+
} else t7 = $[9];
|
|
128
|
+
let t8;
|
|
129
|
+
if ($[10] !== t5 || $[11] !== t7) {
|
|
130
|
+
t8 = /* @__PURE__ */ jsxs("div", {
|
|
94
131
|
className: "flex items-center gap-1",
|
|
95
132
|
children: [
|
|
96
|
-
t3,
|
|
97
133
|
t4,
|
|
98
|
-
t5
|
|
134
|
+
t5,
|
|
135
|
+
t7
|
|
99
136
|
]
|
|
100
137
|
});
|
|
101
|
-
$[
|
|
102
|
-
$[
|
|
103
|
-
$[
|
|
104
|
-
} else
|
|
105
|
-
let
|
|
106
|
-
if ($[
|
|
107
|
-
|
|
138
|
+
$[10] = t5;
|
|
139
|
+
$[11] = t7;
|
|
140
|
+
$[12] = t8;
|
|
141
|
+
} else t8 = $[12];
|
|
142
|
+
let t9;
|
|
143
|
+
if ($[13] !== isWebsite || $[14] !== last || $[15] !== locale || $[16] !== localized || $[17] !== pages || $[18] !== pathname) {
|
|
144
|
+
t9 = getNavbarLinks().map((link) => {
|
|
108
145
|
const isActive = link.isWebsite ? isWebsite || !!pages?.some((page) => page.fullPath === pathname) : isInSection(link.to, pathname);
|
|
109
146
|
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(Link$1, {
|
|
110
147
|
to: navbarHref(link, last, locale.tag, localized),
|
|
@@ -114,51 +151,51 @@ const Navbar = (t0) => {
|
|
|
114
151
|
children: link.children
|
|
115
152
|
}) }, link.to);
|
|
116
153
|
});
|
|
117
|
-
$[
|
|
118
|
-
$[
|
|
119
|
-
$[
|
|
120
|
-
$[
|
|
121
|
-
$[
|
|
122
|
-
$[
|
|
123
|
-
$[
|
|
124
|
-
} else
|
|
125
|
-
let
|
|
126
|
-
if ($[
|
|
127
|
-
|
|
154
|
+
$[13] = isWebsite;
|
|
155
|
+
$[14] = last;
|
|
156
|
+
$[15] = locale;
|
|
157
|
+
$[16] = localized;
|
|
158
|
+
$[17] = pages;
|
|
159
|
+
$[18] = pathname;
|
|
160
|
+
$[19] = t9;
|
|
161
|
+
} else t9 = $[19];
|
|
162
|
+
let t10;
|
|
163
|
+
if ($[20] !== t9) {
|
|
164
|
+
t10 = /* @__PURE__ */ jsx("ul", {
|
|
128
165
|
className: "flex items-center gap-1",
|
|
129
|
-
children:
|
|
166
|
+
children: t9
|
|
130
167
|
});
|
|
131
|
-
$[
|
|
132
|
-
$[
|
|
133
|
-
} else
|
|
134
|
-
let
|
|
135
|
-
if ($[
|
|
136
|
-
|
|
168
|
+
$[20] = t9;
|
|
169
|
+
$[21] = t10;
|
|
170
|
+
} else t10 = $[21];
|
|
171
|
+
let t11;
|
|
172
|
+
if ($[22] !== t10 || $[23] !== t8) {
|
|
173
|
+
t11 = /* @__PURE__ */ jsxs("div", {
|
|
137
174
|
className: "flex flex-row gap-2",
|
|
138
|
-
children: [
|
|
175
|
+
children: [t8, t10]
|
|
139
176
|
});
|
|
140
|
-
$[
|
|
141
|
-
$[
|
|
142
|
-
$[
|
|
143
|
-
} else
|
|
144
|
-
let
|
|
145
|
-
if ($[
|
|
146
|
-
|
|
177
|
+
$[22] = t10;
|
|
178
|
+
$[23] = t8;
|
|
179
|
+
$[24] = t11;
|
|
180
|
+
} else t11 = $[24];
|
|
181
|
+
let t12;
|
|
182
|
+
if ($[25] === Symbol.for("react.memo_cache_sentinel")) {
|
|
183
|
+
t12 = /* @__PURE__ */ jsxs("div", {
|
|
147
184
|
className: "flex items-center gap-2",
|
|
148
185
|
children: [/* @__PURE__ */ jsx(ConnectAgent, {}), /* @__PURE__ */ jsx(UserButton, {})]
|
|
149
186
|
});
|
|
150
|
-
$[
|
|
151
|
-
} else
|
|
152
|
-
let
|
|
153
|
-
if ($[
|
|
154
|
-
|
|
187
|
+
$[25] = t12;
|
|
188
|
+
} else t12 = $[25];
|
|
189
|
+
let t13;
|
|
190
|
+
if ($[26] !== t11) {
|
|
191
|
+
t13 = /* @__PURE__ */ jsxs("nav", {
|
|
155
192
|
className: "bg-background relative flex items-center justify-between gap-4 border-b px-2 py-2",
|
|
156
|
-
children: [
|
|
193
|
+
children: [t11, t12]
|
|
157
194
|
});
|
|
158
|
-
$[
|
|
159
|
-
$[
|
|
160
|
-
} else
|
|
161
|
-
return
|
|
195
|
+
$[26] = t11;
|
|
196
|
+
$[27] = t13;
|
|
197
|
+
} else t13 = $[27];
|
|
198
|
+
return t13;
|
|
162
199
|
};
|
|
163
200
|
function useNavbarActions() {
|
|
164
201
|
const $ = c(5);
|
|
@@ -240,4 +277,4 @@ function _temp(state) {
|
|
|
240
277
|
}
|
|
241
278
|
|
|
242
279
|
//#endregion
|
|
243
|
-
export { Navbar, useNavbarActions };
|
|
280
|
+
export { Navbar, useNavbarActions, useWebsiteHref };
|
|
@@ -17,45 +17,46 @@ import { Spinner } from "@camox/ui/spinner";
|
|
|
17
17
|
|
|
18
18
|
//#region src/features/studio/components/PublishedTab.tsx
|
|
19
19
|
/**
|
|
20
|
-
* The Publications (ADR 0045), the latest first. Opening one shows its
|
|
21
|
-
* read-only: how a member finds out why Live changed. Reverting one takes
|
|
22
|
-
* draft it starts, once `onEntered` closed the sheet.
|
|
20
|
+
* The Publications (ADR 0045), the latest first, or the one `opening` names. Opening one shows its
|
|
21
|
+
* overview and its thread, read-only: how a member finds out why Live changed. Reverting one takes
|
|
22
|
+
* the member into the draft it starts, once `onEntered` closed the sheet.
|
|
23
23
|
*/
|
|
24
24
|
function PublishedTab(t0) {
|
|
25
25
|
const $ = c(7);
|
|
26
|
-
const { projectId, onEntered } = t0;
|
|
27
|
-
const
|
|
26
|
+
const { projectId, opening: t1, onEntered } = t0;
|
|
27
|
+
const opening = t1 === void 0 ? null : t1;
|
|
28
|
+
const [opened, setOpened] = React.useState(opening);
|
|
28
29
|
if (opened) {
|
|
29
|
-
let t1;
|
|
30
|
-
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
|
31
|
-
t1 = () => setOpened(null);
|
|
32
|
-
$[0] = t1;
|
|
33
|
-
} else t1 = $[0];
|
|
34
30
|
let t2;
|
|
31
|
+
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
|
32
|
+
t2 = () => setOpened(null);
|
|
33
|
+
$[0] = t2;
|
|
34
|
+
} else t2 = $[0];
|
|
35
|
+
let t3;
|
|
35
36
|
if ($[1] !== onEntered || $[2] !== opened || $[3] !== projectId) {
|
|
36
|
-
|
|
37
|
+
t3 = /* @__PURE__ */ jsx(PublicationView, {
|
|
37
38
|
projectId,
|
|
38
39
|
id: opened,
|
|
39
|
-
onBack:
|
|
40
|
+
onBack: t2,
|
|
40
41
|
onEntered
|
|
41
42
|
});
|
|
42
43
|
$[1] = onEntered;
|
|
43
44
|
$[2] = opened;
|
|
44
45
|
$[3] = projectId;
|
|
45
|
-
$[4] =
|
|
46
|
-
} else
|
|
47
|
-
return
|
|
46
|
+
$[4] = t3;
|
|
47
|
+
} else t3 = $[4];
|
|
48
|
+
return t3;
|
|
48
49
|
}
|
|
49
|
-
let
|
|
50
|
+
let t2;
|
|
50
51
|
if ($[5] !== projectId) {
|
|
51
|
-
|
|
52
|
+
t2 = /* @__PURE__ */ jsx(PublicationList, {
|
|
52
53
|
projectId,
|
|
53
54
|
onOpen: setOpened
|
|
54
55
|
});
|
|
55
56
|
$[5] = projectId;
|
|
56
|
-
$[6] =
|
|
57
|
-
} else
|
|
58
|
-
return
|
|
57
|
+
$[6] = t2;
|
|
58
|
+
} else t2 = $[6];
|
|
59
|
+
return t2;
|
|
59
60
|
}
|
|
60
61
|
function PublicationList(t0) {
|
|
61
62
|
const $ = c(16);
|
|
@@ -3,6 +3,8 @@ const STUDIO_ROUTE_SEGMENT = "camox";
|
|
|
3
3
|
const STUDIO_BASE_PATH = `/${STUDIO_ROUTE_SEGMENT}`;
|
|
4
4
|
const STUDIO_CONTENT_PATH = `${STUDIO_BASE_PATH}/content`;
|
|
5
5
|
const STUDIO_ASSETS_PATH = `${STUDIO_CONTENT_PATH}/assets`;
|
|
6
|
+
/** The Memory Section, where members see and correct what Camox's agent remembers. */
|
|
7
|
+
const STUDIO_MEMORY_PATH = `${STUDIO_BASE_PATH}/memory`;
|
|
6
8
|
/** The Settings Section, where members change Project Settings. */
|
|
7
9
|
const STUDIO_SETTINGS_PATH = `${STUDIO_BASE_PATH}/settings`;
|
|
8
10
|
const collectionContentPath = (collectionId) => `${STUDIO_CONTENT_PATH}/collections/${encodeURIComponent(collectionId)}`;
|
|
@@ -32,4 +34,4 @@ function matchCollectionContentPath(pathname) {
|
|
|
32
34
|
const STUDIO_CANVAS_PATH = `${STUDIO_BASE_PATH}/canvas`;
|
|
33
35
|
|
|
34
36
|
//#endregion
|
|
35
|
-
export { STUDIO_ASSETS_PATH, STUDIO_BASE_PATH, STUDIO_CANVAS_PATH, STUDIO_CONTENT_PATH, STUDIO_ROUTE_SEGMENT, STUDIO_SETTINGS_PATH, collectionContentPath, editCollectionItemPath, matchCollectionContentPath };
|
|
37
|
+
export { STUDIO_ASSETS_PATH, STUDIO_BASE_PATH, STUDIO_CANVAS_PATH, STUDIO_CONTENT_PATH, STUDIO_MEMORY_PATH, STUDIO_ROUTE_SEGMENT, STUDIO_SETTINGS_PATH, collectionContentPath, editCollectionItemPath, matchCollectionContentPath };
|
|
@@ -5,8 +5,9 @@ const studioStore = createStore({
|
|
|
5
5
|
context: {
|
|
6
6
|
isCommandPaletteOpen: false,
|
|
7
7
|
commandPalettePages: [],
|
|
8
|
-
|
|
8
|
+
isDraftReviewOpen: false,
|
|
9
9
|
isDraftsSheetOpen: false,
|
|
10
|
+
draftsSheetPublication: null,
|
|
10
11
|
localeDialog: null,
|
|
11
12
|
websiteLocation: null,
|
|
12
13
|
agentMessages: [],
|
|
@@ -46,28 +47,30 @@ const studioStore = createStore({
|
|
|
46
47
|
commandPalettePages: context.commandPalettePages.slice(0, -1)
|
|
47
48
|
};
|
|
48
49
|
},
|
|
49
|
-
|
|
50
|
+
openDraftReview: (context) => {
|
|
50
51
|
return {
|
|
51
52
|
...context,
|
|
52
|
-
|
|
53
|
+
isDraftReviewOpen: true
|
|
53
54
|
};
|
|
54
55
|
},
|
|
55
|
-
|
|
56
|
+
closeDraftReview: (context) => {
|
|
56
57
|
return {
|
|
57
58
|
...context,
|
|
58
|
-
|
|
59
|
+
isDraftReviewOpen: false
|
|
59
60
|
};
|
|
60
61
|
},
|
|
61
|
-
openDraftsSheet: (context) => {
|
|
62
|
+
openDraftsSheet: (context, event) => {
|
|
62
63
|
return {
|
|
63
64
|
...context,
|
|
64
|
-
isDraftsSheetOpen: true
|
|
65
|
+
isDraftsSheetOpen: true,
|
|
66
|
+
draftsSheetPublication: event.publicationId ?? null
|
|
65
67
|
};
|
|
66
68
|
},
|
|
67
69
|
closeDraftsSheet: (context) => {
|
|
68
70
|
return {
|
|
69
71
|
...context,
|
|
70
|
-
isDraftsSheetOpen: false
|
|
72
|
+
isDraftsSheetOpen: false,
|
|
73
|
+
draftsSheetPublication: null
|
|
71
74
|
};
|
|
72
75
|
},
|
|
73
76
|
openLocaleDialog: (context, event) => {
|
|
@@ -86,7 +89,7 @@ const studioStore = createStore({
|
|
|
86
89
|
const id = context.agentMessagesSent + 1;
|
|
87
90
|
return {
|
|
88
91
|
...context,
|
|
89
|
-
|
|
92
|
+
isDraftReviewOpen: false,
|
|
90
93
|
agentMessages: [...context.agentMessages, {
|
|
91
94
|
text: event.text,
|
|
92
95
|
id
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { reads, useReadQuery, useStudioCache } from "../../lib/studio-reads.js";
|
|
2
|
+
import { draftMutations } from "../../lib/queries.js";
|
|
3
|
+
import { publishToast, useLeaveDraft } from "./viewSwitch.js";
|
|
4
|
+
import * as React from "react";
|
|
5
|
+
import { PUBLISH_BLOCKED_ERROR } from "@camox/api-contract/drafts";
|
|
6
|
+
import { useMutation } from "@tanstack/react-query";
|
|
7
|
+
import { toast } from "@camox/ui/toaster";
|
|
8
|
+
|
|
9
|
+
//#region src/features/studio/usePublishDraft.ts
|
|
10
|
+
const PUBLISH_FAILED = "The publish couldn't finish on Camox's side, and Live is as it was. Try again.";
|
|
11
|
+
const PUBLISH_BLOCKED = "This draft has blockers to fix before it can publish.";
|
|
12
|
+
/**
|
|
13
|
+
* Why the last publish with code stopped, when it isn't a blocker, which the review shows
|
|
14
|
+
* instead.
|
|
15
|
+
*/
|
|
16
|
+
const PUBLISH_STOPPED = {
|
|
17
|
+
revisionChanged: "The draft's code changed while publishing, so the publish stopped and Live is as it was. Publish again.",
|
|
18
|
+
liveCodeMoved: "Live's code changed while publishing, and the draft took too long to take it in, so the publish stopped and Live is as it was. Try again.",
|
|
19
|
+
conflictsChanged: PUBLISH_FAILED,
|
|
20
|
+
failed: PUBLISH_FAILED
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Whether a publish request failed on Camox's side rather than being refused: a server error,
|
|
24
|
+
* or no answer at all. Either way the publish applied nothing.
|
|
25
|
+
*/
|
|
26
|
+
const failedOnCamoxSide = (error) => {
|
|
27
|
+
const status = error.status;
|
|
28
|
+
return typeof status !== "number" || status >= 500;
|
|
29
|
+
};
|
|
30
|
+
/** What Publish sends: the revision a draft with code shows, which publishing ships. */
|
|
31
|
+
const publishInput = (projectId, shown) => ({
|
|
32
|
+
projectId,
|
|
33
|
+
...shown.code?.revision && { revision: shown.code.revision }
|
|
34
|
+
});
|
|
35
|
+
/**
|
|
36
|
+
* Publishes the draft the studio views, at what its review reads now (ADR 0043). One toast
|
|
37
|
+
* follows the publish: it says it's publishing, then turns into how it ended. Once the draft
|
|
38
|
+
* ends, the studio switches to Live, and the toast says so. A draft with code publishes in the
|
|
39
|
+
* background: `publishing` stays true until it lands or stops. Blockers, found before publishing
|
|
40
|
+
* or stopping it, call `onBlocked`, for the review to show them.
|
|
41
|
+
*
|
|
42
|
+
* The review is read only to publish, and followed while a background publish runs: reading it
|
|
43
|
+
* hurries the draft's overview.
|
|
44
|
+
*/
|
|
45
|
+
function usePublishDraft({ projectId, draftId, onBlocked }) {
|
|
46
|
+
const read = reads.publishPreview({
|
|
47
|
+
projectId,
|
|
48
|
+
draftId
|
|
49
|
+
});
|
|
50
|
+
const cache = useStudioCache();
|
|
51
|
+
const leaveDraft = useLeaveDraft();
|
|
52
|
+
const id = publishToast(draftId);
|
|
53
|
+
const [following, setFollowing] = React.useState(false);
|
|
54
|
+
const preview = useReadQuery(read, { enabled: following });
|
|
55
|
+
const blocked = () => {
|
|
56
|
+
toast.error(PUBLISH_BLOCKED, { id });
|
|
57
|
+
onBlocked?.();
|
|
58
|
+
};
|
|
59
|
+
const publish = useMutation({
|
|
60
|
+
...draftMutations.publish(),
|
|
61
|
+
onSuccess: async (result) => {
|
|
62
|
+
if ("publishing" in result && result.publishing) {
|
|
63
|
+
setFollowing(true);
|
|
64
|
+
await cache.reload(read).catch(() => {});
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (!result.empty) return leaveDraft(result.ended);
|
|
68
|
+
toast.info("This draft has no changes, so there is nothing to publish.", { id });
|
|
69
|
+
cache.reload(read).catch(() => {});
|
|
70
|
+
},
|
|
71
|
+
onError: (error) => {
|
|
72
|
+
cache.reload(read).catch(() => {});
|
|
73
|
+
if (failedOnCamoxSide(error)) toast.error(PUBLISH_FAILED, { id });
|
|
74
|
+
else if (error.code === PUBLISH_BLOCKED_ERROR) blocked();
|
|
75
|
+
else toast.error(error.message, { id });
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
const [reading, setReading] = React.useState(false);
|
|
79
|
+
const start = async () => {
|
|
80
|
+
toast.loading("Publishing…", { id });
|
|
81
|
+
setReading(true);
|
|
82
|
+
let shown;
|
|
83
|
+
try {
|
|
84
|
+
shown = await cache.reload(read);
|
|
85
|
+
} catch {
|
|
86
|
+
toast.error(PUBLISH_FAILED, { id });
|
|
87
|
+
return;
|
|
88
|
+
} finally {
|
|
89
|
+
setReading(false);
|
|
90
|
+
}
|
|
91
|
+
if (shown.blockers.length > 0) return blocked();
|
|
92
|
+
publish.mutate(publishInput(projectId, shown));
|
|
93
|
+
};
|
|
94
|
+
const data = preview.data;
|
|
95
|
+
const backgroundPublish = !!data?.code?.publishing;
|
|
96
|
+
const [stopped, setStopped] = React.useState(null);
|
|
97
|
+
if (following && !publish.isPending && data && !backgroundPublish) {
|
|
98
|
+
setFollowing(false);
|
|
99
|
+
setStopped(data);
|
|
100
|
+
}
|
|
101
|
+
const reportStop = React.useEffectEvent((shown) => {
|
|
102
|
+
const reason = shown.code?.publishAborted;
|
|
103
|
+
if (shown.blockers.length > 0) return blocked();
|
|
104
|
+
toast.error((reason && PUBLISH_STOPPED[reason]) ?? "The publish couldn't finish on Camox's side, and Live is as it was. Try again.", { id });
|
|
105
|
+
});
|
|
106
|
+
React.useEffect(() => {
|
|
107
|
+
if (stopped) reportStop(stopped);
|
|
108
|
+
}, [stopped]);
|
|
109
|
+
return {
|
|
110
|
+
publish: start,
|
|
111
|
+
publishing: reading || publish.isPending || backgroundPublish
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
//#endregion
|
|
116
|
+
export { PUBLISH_FAILED, PUBLISH_STOPPED, usePublishDraft };
|
|
@@ -32,9 +32,14 @@ function useSwitchView() {
|
|
|
32
32
|
return t0;
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
|
+
* The toast of a draft's publish, from Publish to the studio showing Live: each toast with this
|
|
36
|
+
* id replaces the last.
|
|
37
|
+
*/
|
|
38
|
+
const publishToast = (draftId) => `publish:${draftId}`;
|
|
39
|
+
/**
|
|
35
40
|
* Switches the studio to Live once the draft it views ended or was archived, by this member, here
|
|
36
|
-
* or in another tab, replacing the draft's URL entry. An ended draft's
|
|
37
|
-
* working, so
|
|
41
|
+
* or in another tab, replacing the draft's URL entry. An ended draft's publish toast already
|
|
42
|
+
* showed it working, so it only turns into saying so once Live shows; an archived draft's toast shows meanwhile
|
|
38
43
|
* too. Called once the studio already left the draft, it does nothing.
|
|
39
44
|
*/
|
|
40
45
|
function useLeaveDraft() {
|
|
@@ -48,7 +53,8 @@ function useLeaveDraft() {
|
|
|
48
53
|
draftScope.leave(left);
|
|
49
54
|
if (!left) return;
|
|
50
55
|
if ("outcome" in left) {
|
|
51
|
-
|
|
56
|
+
const id = publishToast(left.draftId);
|
|
57
|
+
shown().then(() => toast.success(`Draft ${left.outcome}. You're now viewing Live.`, { id }), () => toast.error(SWITCH_FAILED, { id }));
|
|
52
58
|
return;
|
|
53
59
|
}
|
|
54
60
|
toastSwitch(shown(), {
|
|
@@ -62,9 +68,6 @@ function useLeaveDraft() {
|
|
|
62
68
|
} else t0 = $[2];
|
|
63
69
|
return t0;
|
|
64
70
|
}
|
|
65
|
-
function _temp() {
|
|
66
|
-
return toast.error(SWITCH_FAILED);
|
|
67
|
-
}
|
|
68
71
|
|
|
69
72
|
//#endregion
|
|
70
|
-
export { useLeaveDraft, useSwitchView };
|
|
73
|
+
export { publishToast, useLeaveDraft, useSwitchView };
|
package/dist/lib/locale-path.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LOCALE_HEADER } from "@camox/api-contract/drafts";
|
|
2
|
-
import { localizedPath, parseLocalePath } from "@camox/api-contract/locales";
|
|
2
|
+
import { localePrefix, localizedPath, parseLocalePath } from "@camox/api-contract/locales";
|
|
3
3
|
|
|
4
4
|
//#region src/lib/locale-path.ts
|
|
5
5
|
/**
|
|
@@ -24,4 +24,4 @@ function localeHeaders(locale) {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
//#endregion
|
|
27
|
-
export { UNNAMED_DEFAULT_LOCALE, localeHeaders, localizedPath, parseLocalePath };
|
|
27
|
+
export { UNNAMED_DEFAULT_LOCALE, localeHeaders, localePrefix, localizedPath, parseLocalePath };
|
package/dist/lib/queries.js
CHANGED
|
@@ -69,13 +69,23 @@ const specialistMutations = {
|
|
|
69
69
|
applyFix: () => getOrpc().specialists.applyFix.mutationOptions(),
|
|
70
70
|
dismissFinding: () => getOrpc().specialists.dismissFinding.mutationOptions()
|
|
71
71
|
};
|
|
72
|
-
/**
|
|
72
|
+
/**
|
|
73
|
+
* Adding and removing locales, and making one the default: content writes, so on Live they start
|
|
74
|
+
* a draft (ADR 0015).
|
|
75
|
+
*/
|
|
73
76
|
const localeMutations = {
|
|
74
77
|
add: () => getOrpc().locales.add.mutationOptions(),
|
|
75
78
|
remove: () => getOrpc().locales.remove.mutationOptions(),
|
|
79
|
+
setDefault: () => getOrpc().locales.setDefault.mutationOptions(),
|
|
76
80
|
/** Adds a page or record deleted from the locale viewed back to it. */
|
|
77
81
|
addBack: () => getOrpc().locales.addBack.mutationOptions()
|
|
78
82
|
};
|
|
83
|
+
/** Correcting memory: content writes, so on Live they start a draft (ADR 0049). */
|
|
84
|
+
const memoryMutations = {
|
|
85
|
+
add: () => getOrpc().memories.add.mutationOptions(),
|
|
86
|
+
change: () => getOrpc().memories.change.mutationOptions(),
|
|
87
|
+
delete: () => getOrpc().memories.delete.mutationOptions()
|
|
88
|
+
};
|
|
79
89
|
const blockMutations = {
|
|
80
90
|
delete: () => getOrpc().blocks.delete.mutationOptions(),
|
|
81
91
|
deleteMany: () => getOrpc().blocks.deleteMany.mutationOptions(),
|
|
@@ -149,4 +159,4 @@ const fileMutations = {
|
|
|
149
159
|
};
|
|
150
160
|
|
|
151
161
|
//#endregion
|
|
152
|
-
export { blockMutations, collectionMutations, draftMutations, fileMutations, localeMutations, pageMutations, projectMutations, publicationMutations, repeatableItemMutations, specialistMutations };
|
|
162
|
+
export { blockMutations, collectionMutations, draftMutations, fileMutations, localeMutations, memoryMutations, pageMutations, projectMutations, publicationMutations, repeatableItemMutations, specialistMutations };
|
package/dist/lib/studio-reads.js
CHANGED
|
@@ -59,6 +59,12 @@ const reads = {
|
|
|
59
59
|
fetch: ({ api, signal }, { projectId }) => api.locales.list({ projectId }, { signal }),
|
|
60
60
|
staleTime: Infinity
|
|
61
61
|
}),
|
|
62
|
+
memories: entry({
|
|
63
|
+
key: ({ projectId }) => [...queryKeys.memories.list, projectId],
|
|
64
|
+
everyLocale: true,
|
|
65
|
+
fetch: ({ api, signal }, { projectId }) => api.memories.list({ projectId }, { signal }),
|
|
66
|
+
staleTime: Infinity
|
|
67
|
+
}),
|
|
62
68
|
missingFromLocale: entry({
|
|
63
69
|
key: ({ projectId }) => [
|
|
64
70
|
"camox",
|
package/dist/package.js
CHANGED