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.
Files changed (32) hide show
  1. package/dist/features/canvas/CamoxCanvas.js +187 -197
  2. package/dist/features/canvas/canvasCameraController.js +1 -1
  3. package/dist/features/canvas/canvasItems.js +40 -9
  4. package/dist/features/canvas/sidebar/ContentSidebarFrame.js +15 -11
  5. package/dist/features/canvas/useCanvasLayout.js +34 -0
  6. package/dist/features/memory/CamoxMemory.js +756 -0
  7. package/dist/features/memory/VoiceGroup.js +350 -0
  8. package/dist/features/preview/EditablePageExperience.js +2 -0
  9. package/dist/features/runtime/publicCache.d.ts +2 -0
  10. package/dist/features/runtime/publicCache.js +27 -4
  11. package/dist/features/runtime/runtime.d.ts +1 -1
  12. package/dist/features/runtime/runtime.js +34 -15
  13. package/dist/features/studio/FollowAgentProvider.js +1 -1
  14. package/dist/features/studio/components/AgentSidebar.js +22 -16
  15. package/dist/features/studio/components/DraftBar.js +167 -103
  16. package/dist/features/studio/components/{ReviewDraftDialog.js → DraftReview.js} +214 -218
  17. package/dist/features/studio/components/DraftsControl.js +174 -202
  18. package/dist/features/studio/components/DraftsSheet.js +102 -94
  19. package/dist/features/studio/components/Navbar.js +121 -84
  20. package/dist/features/studio/components/PublishedTab.js +21 -20
  21. package/dist/features/studio/routes.js +3 -1
  22. package/dist/features/studio/studioStore.js +12 -9
  23. package/dist/features/studio/usePublishDraft.js +116 -0
  24. package/dist/features/studio/viewSwitch.js +10 -7
  25. package/dist/features/vite/vite.js +1 -0
  26. package/dist/lib/queries.js +11 -1
  27. package/dist/lib/studio-reads.d.ts +1 -0
  28. package/dist/lib/studio-reads.js +12 -0
  29. package/dist/package.js +1 -1
  30. package/dist/studio.css +1 -1
  31. package/package.json +5 -5
  32. 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 dialog already showed it
37
- * working, so its toast only says so once Live shows; an archived draft's toast shows meanwhile
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
- shown().then(() => toast.success(`Draft ${left.outcome}. You're now viewing Live.`), _temp);
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",
@@ -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. */
@@ -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
@@ -1,5 +1,5 @@
1
1
  //#region package.json
2
- var version = "0.77.0";
2
+ var version = "0.79.0";
3
3
 
4
4
  //#endregion
5
5
  export { version };