camox 0.77.0 → 0.79.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/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 +756 -0
- package/dist/features/memory/VoiceGroup.js +350 -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} +214 -218
- package/dist/features/studio/components/DraftsControl.js +174 -202
- package/dist/features/studio/components/DraftsSheet.js +102 -94
- 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/features/vite/vite.js +1 -0
- package/dist/lib/queries.js +11 -1
- package/dist/lib/studio-reads.d.ts +1 -0
- package/dist/lib/studio-reads.js +12 -0
- package/dist/package.js +1 -1
- package/dist/studio.css +1 -1
- package/package.json +5 -5
- package/LICENSE.md +0 -21
|
@@ -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 };
|
|
@@ -87,6 +87,7 @@ const CAMOX_OPTIMIZED_DEPS = [
|
|
|
87
87
|
"camox > @base-ui/react/popover",
|
|
88
88
|
"camox > @base-ui/react/select",
|
|
89
89
|
"camox > @base-ui/react/separator",
|
|
90
|
+
"camox > @base-ui/react/slider",
|
|
90
91
|
"camox > @base-ui/react/switch",
|
|
91
92
|
"camox > @base-ui/react/tabs",
|
|
92
93
|
"camox > @base-ui/react/toggle",
|
package/dist/lib/queries.js
CHANGED
|
@@ -80,6 +80,16 @@ const localeMutations = {
|
|
|
80
80
|
/** Adds a page or record deleted from the locale viewed back to it. */
|
|
81
81
|
addBack: () => getOrpc().locales.addBack.mutationOptions()
|
|
82
82
|
};
|
|
83
|
+
/**
|
|
84
|
+
* Correcting memory and saving the Voice: content writes, so on Live they start a draft (ADR 0049,
|
|
85
|
+
* ADR 0051).
|
|
86
|
+
*/
|
|
87
|
+
const memoryMutations = {
|
|
88
|
+
add: () => getOrpc().memories.add.mutationOptions(),
|
|
89
|
+
change: () => getOrpc().memories.change.mutationOptions(),
|
|
90
|
+
delete: () => getOrpc().memories.delete.mutationOptions(),
|
|
91
|
+
saveVoice: () => getOrpc().memories.saveVoice.mutationOptions()
|
|
92
|
+
};
|
|
83
93
|
const blockMutations = {
|
|
84
94
|
delete: () => getOrpc().blocks.delete.mutationOptions(),
|
|
85
95
|
deleteMany: () => getOrpc().blocks.deleteMany.mutationOptions(),
|
|
@@ -153,4 +163,4 @@ const fileMutations = {
|
|
|
153
163
|
};
|
|
154
164
|
|
|
155
165
|
//#endregion
|
|
156
|
-
export { blockMutations, collectionMutations, draftMutations, fileMutations, localeMutations, pageMutations, projectMutations, publicationMutations, repeatableItemMutations, specialistMutations };
|
|
166
|
+
export { blockMutations, collectionMutations, draftMutations, fileMutations, localeMutations, memoryMutations, pageMutations, projectMutations, publicationMutations, repeatableItemMutations, specialistMutations };
|
|
@@ -8,6 +8,7 @@ import "react";
|
|
|
8
8
|
import "@camox/api-contract/query-keys";
|
|
9
9
|
import "@camox/api-contract/specialists";
|
|
10
10
|
import { QueryClient } from "@tanstack/react-query";
|
|
11
|
+
import "@camox/api-contract/voice";
|
|
11
12
|
//#region src/lib/studio-reads.d.ts
|
|
12
13
|
type LayoutRead = Awaited<ReturnType<ApiClient["layouts"]["get"]>>;
|
|
13
14
|
/** A layout's structure: its blocks before and after the content of its pages. */
|
package/dist/lib/studio-reads.js
CHANGED
|
@@ -59,6 +59,18 @@ 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
|
+
}),
|
|
68
|
+
voice: entry({
|
|
69
|
+
key: ({ projectId }) => [...queryKeys.memories.voice, projectId],
|
|
70
|
+
everyLocale: true,
|
|
71
|
+
fetch: ({ api, signal }, { projectId }) => api.memories.voice({ projectId }, { signal }),
|
|
72
|
+
staleTime: Infinity
|
|
73
|
+
}),
|
|
62
74
|
missingFromLocale: entry({
|
|
63
75
|
key: ({ projectId }) => [
|
|
64
76
|
"camox",
|
package/dist/package.js
CHANGED