@backmatter/studio 0.0.6 → 0.0.7

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 (42) hide show
  1. package/dist/chunks/{EmbedPublishPage-DazYJLaD.js → EmbedPublishPage-88VsmOhi.js} +1 -1
  2. package/dist/chunks/{UiKitPage-DyVyBp0I.js → UiKitPage-X4hkFL2d.js} +3 -3
  3. package/dist/chunks/{WorkspaceRoutes-CPvkgvxe.js → WorkspaceRoutes-DXzdWqN3.js} +20287 -18407
  4. package/dist/chunks/{dashboard-6aSS8oyj.js → dashboard-DCHRI0WD.js} +465 -443
  5. package/dist/chunks/{index-DgTNL4t3.js → index-BS0-Bvbk.js} +2 -2
  6. package/dist/chunks/{status-bar-action-RNBujSgk.js → status-bar-action-wj1KZ51m.js} +5512 -5382
  7. package/dist/dashboard.js +2 -2
  8. package/dist/index.js +4 -4
  9. package/dist/site-widget.js +2 -2
  10. package/dist/studio.css +450 -62
  11. package/package.json +3 -2
  12. package/src/App.tsx +2 -0
  13. package/src/WorkspaceRoutes.tsx +2 -0
  14. package/src/api.ts +80 -0
  15. package/src/chat/ChatMessage.tsx +10 -3
  16. package/src/chat/Composer.tsx +18 -18
  17. package/src/chat/chat-helpers.ts +58 -1
  18. package/src/chrome/AppSidebar.tsx +31 -156
  19. package/src/chrome/CommandPalette.tsx +211 -74
  20. package/src/chrome/ContentExplorerSidebar.tsx +1174 -0
  21. package/src/chrome/UserMenu.tsx +18 -3
  22. package/src/chrome/WorkspaceNavContext.tsx +1 -0
  23. package/src/chrome/command-palette-icons.tsx +152 -0
  24. package/src/chrome/content-path-tree.ts +95 -0
  25. package/src/chrome/content-repo-path.ts +52 -0
  26. package/src/chrome/explorer-icons.tsx +89 -0
  27. package/src/chrome/folder-create-context.ts +147 -0
  28. package/src/pages/BlockSettingsDialog.tsx +4 -0
  29. package/src/pages/CliAuthorizePage.tsx +43 -0
  30. package/src/pages/DocEditorPage.tsx +33 -4
  31. package/src/pages/MediaFieldInput.tsx +139 -0
  32. package/src/pages/MediaGalleryPage.tsx +752 -0
  33. package/src/pages/MediaLightbox.tsx +460 -0
  34. package/src/pages/SettingsUsagePage.tsx +19 -0
  35. package/src/pages/TypeDocsPage.tsx +2 -210
  36. package/src/pages/WorkspaceLayout.tsx +6 -1
  37. package/src/pages/media-grid.ts +190 -0
  38. package/src/pages/preview-url.ts +13 -2
  39. package/src/replica/useReplica.ts +9 -0
  40. package/src/ui/ConfirmDialog.tsx +1 -1
  41. package/src/workspace/permissions.ts +9 -0
  42. package/src/workspace-path.ts +13 -0
@@ -1,4 +1,5 @@
1
1
  // packages/studio/src/pages/DocEditorPage.tsx
2
+ import { Snake } from "loading-dev";
2
3
  import { useEffect, useMemo, useRef, useState } from "react";
3
4
  import { useOutletContext, useParams, useSearchParams, Link, useNavigate } from "react-router";
4
5
  import * as stylex from "@stylexjs/stylex";
@@ -17,6 +18,7 @@ import {
17
18
  type SchemaField,
18
19
  } from "../api";
19
20
  import { resolveFieldInput, useStudioDefinition } from "../define-studio";
21
+ import { MediaFieldInput } from "./MediaFieldInput";
20
22
  import { useSetEditorActions } from "../chrome/EditorActionsContext";
21
23
  import {
22
24
  DOC_CHANGED_EVENT,
@@ -730,8 +732,13 @@ export function DocEditorPage() {
730
732
 
731
733
  if (loading) {
732
734
  return (
733
- <div {...stylex.props(styles.pad)}>
734
- <Text tone="muted">Loading document…</Text>
735
+ <div {...stylex.props(styles.root)}>
736
+ <div {...stylex.props(styles.loading)} aria-busy aria-live="polite">
737
+ <span {...stylex.props(styles.loadingSpinner)} aria-hidden>
738
+ <Snake size={20} />
739
+ </span>
740
+ <Text tone="muted" size="sm">Loading document…</Text>
741
+ </div>
735
742
  </div>
736
743
  );
737
744
  }
@@ -1013,6 +1020,16 @@ export function DocEditorPage() {
1013
1020
  setFrontmatter((prev) => ({ ...prev, [name]: next }));
1014
1021
  }}
1015
1022
  />
1023
+ ) : kind === "media" ? (
1024
+ <MediaFieldInput
1025
+ name={name}
1026
+ value={frontmatter[name]}
1027
+ readOnly={aiEditing || parseFailed}
1028
+ onChange={(next) => {
1029
+ markEdited();
1030
+ setFrontmatter((prev) => ({ ...prev, [name]: next }));
1031
+ }}
1032
+ />
1016
1033
  ) : composite ? (
1017
1034
  <JsonField
1018
1035
  kind={kind === "array" ? "array" : "object"}
@@ -1073,14 +1090,26 @@ export function DocEditorPage() {
1073
1090
 
1074
1091
  const styles = stylex.create({
1075
1092
  root: {
1093
+ position: "relative",
1076
1094
  display: "flex",
1077
1095
  minHeight: 0,
1078
1096
  flex: 1,
1079
1097
  flexDirection: "column",
1080
1098
  overflow: "hidden",
1081
1099
  },
1082
- pad: {
1083
- padding: space.xxl,
1100
+ loading: {
1101
+ position: "absolute",
1102
+ inset: 0,
1103
+ display: "flex",
1104
+ flexDirection: "column",
1105
+ alignItems: "center",
1106
+ justifyContent: "center",
1107
+ gap: space.md,
1108
+ backgroundColor: colors.bgApp,
1109
+ },
1110
+ loadingSpinner: {
1111
+ display: "flex",
1112
+ color: colors.textMuted,
1084
1113
  },
1085
1114
  split: {
1086
1115
  display: "flex",
@@ -0,0 +1,139 @@
1
+ // packages/studio/src/pages/MediaFieldInput.tsx
2
+ import { useState } from "react";
3
+ import { Dialog } from "@base-ui/react/dialog";
4
+ import { mediaIdFromUrl, mediaVariantUrl } from "@backmatter/core";
5
+ import * as stylex from "@stylexjs/stylex";
6
+ import { colors, radius, space, type } from "../styles/tokens.stylex";
7
+ import { Button } from "../ui/Button";
8
+ import { cx, isTransitioning } from "../ui/popup";
9
+ import { MediaGalleryPage } from "./MediaGalleryPage";
10
+
11
+ const CDN_ORIGIN = "https://cdn.backmatter.app";
12
+
13
+ export function MediaFieldInput({
14
+ value,
15
+ readOnly,
16
+ onChange,
17
+ }: {
18
+ name: string;
19
+ value: unknown;
20
+ readOnly?: boolean;
21
+ onChange: (value: unknown) => void;
22
+ }) {
23
+ const [open, setOpen] = useState(false);
24
+ const url = typeof value === "string" ? value : "";
25
+ const id = url ? mediaIdFromUrl(url) : null;
26
+ const thumb = id ? mediaVariantUrl(CDN_ORIGIN, id, 400) : null;
27
+
28
+ return (
29
+ <div {...stylex.props(styles.row)}>
30
+ {thumb ? (
31
+ <img src={thumb} alt="" {...stylex.props(styles.thumb)} />
32
+ ) : (
33
+ <div {...stylex.props(styles.placeholder)}>No image</div>
34
+ )}
35
+ {readOnly ? null : (
36
+ <Button variant="secondary" onClick={() => setOpen(true)}>
37
+ Choose
38
+ </Button>
39
+ )}
40
+ {readOnly || !url ? null : (
41
+ <Button variant="ghost" onClick={() => onChange("")}>
42
+ Clear
43
+ </Button>
44
+ )}
45
+ <Dialog.Root open={open} onOpenChange={setOpen}>
46
+ <Dialog.Portal>
47
+ <Dialog.Backdrop
48
+ className={(state) =>
49
+ cx(styles.backdrop, isTransitioning(state.transitionStatus) && styles.backdropHidden)
50
+ }
51
+ />
52
+ <Dialog.Popup
53
+ className={(state) =>
54
+ cx(styles.popup, isTransitioning(state.transitionStatus) && styles.popupHidden)
55
+ }
56
+ >
57
+ <Dialog.Title className={cx(styles.title)}>Choose media</Dialog.Title>
58
+ <div {...stylex.props(styles.gallery)}>
59
+ <MediaGalleryPage
60
+ pick
61
+ onPick={(next) => {
62
+ onChange(next);
63
+ setOpen(false);
64
+ }}
65
+ />
66
+ </div>
67
+ </Dialog.Popup>
68
+ </Dialog.Portal>
69
+ </Dialog.Root>
70
+ </div>
71
+ );
72
+ }
73
+
74
+ const fade = stylex.keyframes({
75
+ from: { opacity: 0 },
76
+ to: { opacity: 1 },
77
+ });
78
+
79
+ const styles = stylex.create({
80
+ row: {
81
+ display: "flex",
82
+ alignItems: "center",
83
+ gap: space.md,
84
+ },
85
+ thumb: {
86
+ width: "4.5rem",
87
+ height: "4.5rem",
88
+ objectFit: "cover",
89
+ borderRadius: radius.md,
90
+ backgroundColor: colors.bgPanel,
91
+ },
92
+ placeholder: {
93
+ width: "4.5rem",
94
+ height: "4.5rem",
95
+ display: "flex",
96
+ alignItems: "center",
97
+ justifyContent: "center",
98
+ borderRadius: radius.md,
99
+ backgroundColor: colors.bgPanel,
100
+ color: colors.textMuted,
101
+ fontSize: type.xs,
102
+ },
103
+ backdrop: {
104
+ position: "fixed",
105
+ inset: 0,
106
+ backgroundColor: colors.overlay,
107
+ animationName: fade,
108
+ animationDuration: "120ms",
109
+ },
110
+ backdropHidden: {
111
+ opacity: 0,
112
+ },
113
+ popup: {
114
+ position: "fixed",
115
+ top: "8vh",
116
+ left: "50%",
117
+ transform: "translateX(-50%)",
118
+ width: "min(960px, calc(100vw - 32px))",
119
+ height: "min(720px, 80vh)",
120
+ display: "flex",
121
+ flexDirection: "column",
122
+ backgroundColor: colors.bgSurface,
123
+ borderRadius: radius.md,
124
+ overflow: "hidden",
125
+ },
126
+ popupHidden: {
127
+ opacity: 0,
128
+ },
129
+ title: {
130
+ margin: 0,
131
+ padding: space.xl,
132
+ paddingBottom: 0,
133
+ fontSize: type.lg,
134
+ },
135
+ gallery: {
136
+ flex: 1,
137
+ minHeight: 0,
138
+ },
139
+ });