camox 0.0.0 → 0.1.2-alpha.2

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 (255) hide show
  1. package/LICENSE.md +110 -0
  2. package/dist/components/AuthGate.d.ts +7 -0
  3. package/dist/components/AuthGate.d.ts.map +1 -0
  4. package/dist/core/components/AddBlockControlBar.d.ts +9 -0
  5. package/dist/core/components/AddBlockControlBar.d.ts.map +1 -0
  6. package/dist/core/components/AddBlockControlBar.js +65 -0
  7. package/dist/core/components/lexical/InlineContentEditable.d.ts +7 -0
  8. package/dist/core/components/lexical/InlineContentEditable.d.ts.map +1 -0
  9. package/dist/core/components/lexical/InlineContentEditable.js +40 -0
  10. package/dist/core/components/lexical/InlineLexicalEditor.d.ts +12 -0
  11. package/dist/core/components/lexical/InlineLexicalEditor.d.ts.map +1 -0
  12. package/dist/core/components/lexical/InlineLexicalEditor.js +133 -0
  13. package/dist/core/components/lexical/InlineParagraphNode.d.ts +10 -0
  14. package/dist/core/components/lexical/InlineParagraphNode.d.ts.map +1 -0
  15. package/dist/core/components/lexical/InlineParagraphNode.js +34 -0
  16. package/dist/core/components/lexical/SelectionBroadcaster.d.ts +6 -0
  17. package/dist/core/components/lexical/SelectionBroadcaster.d.ts.map +1 -0
  18. package/dist/core/components/lexical/SelectionBroadcaster.js +62 -0
  19. package/dist/core/components/lexical/SidebarLexicalEditor.d.ts +9 -0
  20. package/dist/core/components/lexical/SidebarLexicalEditor.d.ts.map +1 -0
  21. package/dist/core/components/lexical/editorConfig.d.ts +4 -0
  22. package/dist/core/components/lexical/editorConfig.d.ts.map +1 -0
  23. package/dist/core/components/lexical/editorConfig.js +24 -0
  24. package/dist/core/createApp.d.ts +373 -0
  25. package/dist/core/createApp.d.ts.map +1 -0
  26. package/dist/core/createApp.js +40 -0
  27. package/dist/core/createBlock.d.ts +947 -0
  28. package/dist/core/createBlock.d.ts.map +1 -0
  29. package/dist/core/createBlock.js +873 -0
  30. package/dist/core/createLayout.d.ts +78 -0
  31. package/dist/core/createLayout.d.ts.map +1 -0
  32. package/dist/core/createLayout.js +73 -0
  33. package/dist/core/hooks/useFieldSelection.d.ts +11 -0
  34. package/dist/core/hooks/useFieldSelection.d.ts.map +1 -0
  35. package/dist/core/hooks/useFieldSelection.js +25 -0
  36. package/dist/core/hooks/useIsEditable.d.ts +2 -0
  37. package/dist/core/hooks/useIsEditable.d.ts.map +1 -0
  38. package/dist/core/hooks/useIsEditable.js +12 -0
  39. package/dist/core/hooks/useOverlayMessage.d.ts +10 -0
  40. package/dist/core/hooks/useOverlayMessage.d.ts.map +1 -0
  41. package/dist/core/hooks/useOverlayMessage.js +37 -0
  42. package/dist/core/lib/contentType.d.ts +165 -0
  43. package/dist/core/lib/contentType.d.ts.map +1 -0
  44. package/dist/core/lib/contentType.js +148 -0
  45. package/dist/core/lib/fieldTypes.d.ts +85 -0
  46. package/dist/core/lib/fieldTypes.d.ts.map +1 -0
  47. package/dist/core/lib/lexicalReact.d.ts +3 -0
  48. package/dist/core/lib/lexicalReact.d.ts.map +1 -0
  49. package/dist/core/lib/lexicalReact.js +24 -0
  50. package/dist/core/lib/lexicalState.d.ts +10 -0
  51. package/dist/core/lib/lexicalState.d.ts.map +1 -0
  52. package/dist/core/lib/lexicalState.js +38 -0
  53. package/dist/core/lib/modifierFormats.d.ts +9 -0
  54. package/dist/core/lib/modifierFormats.d.ts.map +1 -0
  55. package/dist/core/lib/modifierFormats.js +8 -0
  56. package/dist/core/lib/modifiers.d.ts +12 -0
  57. package/dist/core/lib/modifiers.d.ts.map +1 -0
  58. package/dist/core/lib/modifiers.js +27 -0
  59. package/dist/features/content/CamoxContent.d.ts +2 -0
  60. package/dist/features/content/CamoxContent.d.ts.map +1 -0
  61. package/dist/features/content/CamoxContent.js +100 -0
  62. package/dist/features/content/components/AssetCard.d.ts +10 -0
  63. package/dist/features/content/components/AssetCard.d.ts.map +1 -0
  64. package/dist/features/content/components/AssetCard.js +41 -0
  65. package/dist/features/content/components/AssetCardSkeleton.d.ts +2 -0
  66. package/dist/features/content/components/AssetCardSkeleton.d.ts.map +1 -0
  67. package/dist/features/content/components/AssetCardSkeleton.js +11 -0
  68. package/dist/features/content/components/ContentSidebar.d.ts +2 -0
  69. package/dist/features/content/components/ContentSidebar.d.ts.map +1 -0
  70. package/dist/features/content/components/ContentSidebar.js +15 -0
  71. package/dist/features/content/components/UploadDropZone.d.ts +9 -0
  72. package/dist/features/content/components/UploadDropZone.d.ts.map +1 -0
  73. package/dist/features/content/components/UploadDropZone.js +51 -0
  74. package/dist/features/content/components/UploadProgressDrawer.d.ts +11 -0
  75. package/dist/features/content/components/UploadProgressDrawer.d.ts.map +1 -0
  76. package/dist/features/content/components/UploadProgressDrawer.js +72 -0
  77. package/dist/features/preview/CamoxPreview.d.ts +124 -0
  78. package/dist/features/preview/CamoxPreview.d.ts.map +1 -0
  79. package/dist/features/preview/CamoxPreview.js +253 -0
  80. package/dist/features/preview/components/AddBlockSheet.d.ts +3 -0
  81. package/dist/features/preview/components/AddBlockSheet.d.ts.map +1 -0
  82. package/dist/features/preview/components/AddBlockSheet.js +121 -0
  83. package/dist/features/preview/components/AgentChatSheet.d.ts +3 -0
  84. package/dist/features/preview/components/AgentChatSheet.d.ts.map +1 -0
  85. package/dist/features/preview/components/AgentChatSheet.js +24 -0
  86. package/dist/features/preview/components/AssetFieldEditor.d.ts +18 -0
  87. package/dist/features/preview/components/AssetFieldEditor.d.ts.map +1 -0
  88. package/dist/features/preview/components/AssetFieldEditor.js +139 -0
  89. package/dist/features/preview/components/AssetLightbox.d.ts +9 -0
  90. package/dist/features/preview/components/AssetLightbox.d.ts.map +1 -0
  91. package/dist/features/preview/components/AssetLightbox.js +421 -0
  92. package/dist/features/preview/components/AssetPickerGrid.d.ts +11 -0
  93. package/dist/features/preview/components/AssetPickerGrid.d.ts.map +1 -0
  94. package/dist/features/preview/components/AssetPickerGrid.js +92 -0
  95. package/dist/features/preview/components/BlockActionsPopover.d.ts +15 -0
  96. package/dist/features/preview/components/BlockActionsPopover.d.ts.map +1 -0
  97. package/dist/features/preview/components/BlockActionsPopover.js +506 -0
  98. package/dist/features/preview/components/CreatePageSheet.d.ts +3 -0
  99. package/dist/features/preview/components/CreatePageSheet.d.ts.map +1 -0
  100. package/dist/features/preview/components/CreatePageSheet.js +159 -0
  101. package/dist/features/preview/components/DebouncedFieldEditor.d.ts +10 -0
  102. package/dist/features/preview/components/DebouncedFieldEditor.d.ts.map +1 -0
  103. package/dist/features/preview/components/DebouncedFieldEditor.js +51 -0
  104. package/dist/features/preview/components/EditPageSheet.d.ts +3 -0
  105. package/dist/features/preview/components/EditPageSheet.d.ts.map +1 -0
  106. package/dist/features/preview/components/EditPageSheet.js +352 -0
  107. package/dist/features/preview/components/ItemFieldsEditor.d.ts +29 -0
  108. package/dist/features/preview/components/ItemFieldsEditor.d.ts.map +1 -0
  109. package/dist/features/preview/components/ItemFieldsEditor.js +308 -0
  110. package/dist/features/preview/components/LinkFieldEditor.d.ts +8 -0
  111. package/dist/features/preview/components/LinkFieldEditor.d.ts.map +1 -0
  112. package/dist/features/preview/components/LinkFieldEditor.js +190 -0
  113. package/dist/features/preview/components/MultipleAssetFieldEditor.d.ts +10 -0
  114. package/dist/features/preview/components/MultipleAssetFieldEditor.d.ts.map +1 -0
  115. package/dist/features/preview/components/MultipleAssetFieldEditor.js +232 -0
  116. package/dist/features/preview/components/OverlayTracker.d.ts +6 -0
  117. package/dist/features/preview/components/OverlayTracker.d.ts.map +1 -0
  118. package/dist/features/preview/components/OverlayTracker.js +41 -0
  119. package/dist/features/preview/components/Overlays.d.ts +6 -0
  120. package/dist/features/preview/components/Overlays.d.ts.map +1 -0
  121. package/dist/features/preview/components/Overlays.js +58 -0
  122. package/dist/features/preview/components/PageContentSheet.d.ts +3 -0
  123. package/dist/features/preview/components/PageContentSheet.d.ts.map +1 -0
  124. package/dist/features/preview/components/PageContentSheet.js +492 -0
  125. package/dist/features/preview/components/PageLocationFieldset.d.ts +14 -0
  126. package/dist/features/preview/components/PageLocationFieldset.d.ts.map +1 -0
  127. package/dist/features/preview/components/PageLocationFieldset.js +77 -0
  128. package/dist/features/preview/components/PagePicker.d.ts +3 -0
  129. package/dist/features/preview/components/PagePicker.d.ts.map +1 -0
  130. package/dist/features/preview/components/PagePicker.js +185 -0
  131. package/dist/features/preview/components/PageTree.d.ts +3 -0
  132. package/dist/features/preview/components/PageTree.d.ts.map +1 -0
  133. package/dist/features/preview/components/PageTree.js +410 -0
  134. package/dist/features/preview/components/PeekedBlock.d.ts +6 -0
  135. package/dist/features/preview/components/PeekedBlock.d.ts.map +1 -0
  136. package/dist/features/preview/components/PeekedBlock.js +95 -0
  137. package/dist/features/preview/components/PreviewPanel.d.ts +12 -0
  138. package/dist/features/preview/components/PreviewPanel.d.ts.map +1 -0
  139. package/dist/features/preview/components/PreviewPanel.js +192 -0
  140. package/dist/features/preview/components/PreviewSideSheet.d.ts +13 -0
  141. package/dist/features/preview/components/PreviewSideSheet.d.ts.map +1 -0
  142. package/dist/features/preview/components/PreviewSideSheet.js +28 -0
  143. package/dist/features/preview/components/PreviewToolbar.d.ts +2 -0
  144. package/dist/features/preview/components/PreviewToolbar.d.ts.map +1 -0
  145. package/dist/features/preview/components/PreviewToolbar.js +79 -0
  146. package/dist/features/preview/components/RepeatableItemsList.d.ts +14 -0
  147. package/dist/features/preview/components/RepeatableItemsList.d.ts.map +1 -0
  148. package/dist/features/preview/components/RepeatableItemsList.js +366 -0
  149. package/dist/features/preview/components/ShikiMarkdown.d.ts +4 -0
  150. package/dist/features/preview/components/ShikiMarkdown.d.ts.map +1 -0
  151. package/dist/features/preview/components/ShikiMarkdown.js +37 -0
  152. package/dist/features/preview/components/TextFormatToolbar.d.ts +2 -0
  153. package/dist/features/preview/components/TextFormatToolbar.d.ts.map +1 -0
  154. package/dist/features/preview/components/TextFormatToolbar.js +73 -0
  155. package/dist/features/preview/components/UnlinkAssetButton.d.ts +9 -0
  156. package/dist/features/preview/components/UnlinkAssetButton.d.ts.map +1 -0
  157. package/dist/features/preview/components/UnlinkAssetButton.js +55 -0
  158. package/dist/features/preview/overlayConstants.d.ts +19 -0
  159. package/dist/features/preview/overlayConstants.d.ts.map +1 -0
  160. package/dist/features/preview/overlayConstants.js +21 -0
  161. package/dist/features/preview/overlayMessages.d.ts +62 -0
  162. package/dist/features/preview/overlayMessages.d.ts.map +1 -0
  163. package/dist/features/preview/overlayMessages.js +9 -0
  164. package/dist/features/preview/previewConstants.d.ts +2 -0
  165. package/dist/features/preview/previewConstants.d.ts.map +1 -0
  166. package/dist/features/preview/previewStore.d.ts +116 -0
  167. package/dist/features/preview/previewStore.d.ts.map +1 -0
  168. package/dist/features/preview/previewStore.js +321 -0
  169. package/dist/features/provider/CamoxProvider.d.ts +11 -0
  170. package/dist/features/provider/CamoxProvider.d.ts.map +1 -0
  171. package/dist/features/provider/CamoxProvider.js +73 -0
  172. package/dist/features/provider/actionsStore.d.ts +39 -0
  173. package/dist/features/provider/actionsStore.d.ts.map +1 -0
  174. package/dist/features/provider/actionsStore.js +35 -0
  175. package/dist/features/provider/components/CamoxAppContext.d.ts +371 -0
  176. package/dist/features/provider/components/CamoxAppContext.d.ts.map +1 -0
  177. package/dist/features/provider/components/CamoxAppContext.js +17 -0
  178. package/dist/features/provider/components/CommandPalette.d.ts +3 -0
  179. package/dist/features/provider/components/CommandPalette.d.ts.map +1 -0
  180. package/dist/features/provider/components/CommandPalette.js +127 -0
  181. package/dist/features/provider/useAdminShortcuts.d.ts +5 -0
  182. package/dist/features/provider/useAdminShortcuts.d.ts.map +1 -0
  183. package/dist/features/provider/useAdminShortcuts.js +83 -0
  184. package/dist/features/routes/ogRoute.d.ts +7 -0
  185. package/dist/features/routes/ogRoute.d.ts.map +1 -0
  186. package/dist/features/routes/ogRoute.js +19 -0
  187. package/dist/features/routes/pageRoute.d.ts +135 -0
  188. package/dist/features/routes/pageRoute.d.ts.map +1 -0
  189. package/dist/features/routes/pageRoute.js +112 -0
  190. package/dist/features/studio/CamoxStudio.d.ts +7 -0
  191. package/dist/features/studio/CamoxStudio.d.ts.map +1 -0
  192. package/dist/features/studio/CamoxStudio.js +24 -0
  193. package/dist/features/studio/components/Navbar.d.ts +4 -0
  194. package/dist/features/studio/components/Navbar.d.ts.map +1 -0
  195. package/dist/features/studio/components/Navbar.js +95 -0
  196. package/dist/features/studio/components/ProjectMenu.d.ts +2 -0
  197. package/dist/features/studio/components/ProjectMenu.d.ts.map +1 -0
  198. package/dist/features/studio/components/ProjectMenu.js +132 -0
  199. package/dist/features/studio/components/UserButton.d.ts +2 -0
  200. package/dist/features/studio/components/UserButton.d.ts.map +1 -0
  201. package/dist/features/studio/components/UserButton.js +96 -0
  202. package/dist/features/studio/studioStore.d.ts +17 -0
  203. package/dist/features/studio/studioStore.d.ts.map +1 -0
  204. package/dist/features/studio/studioStore.js +44 -0
  205. package/dist/features/studio/useTheme.d.ts +9 -0
  206. package/dist/features/studio/useTheme.d.ts.map +1 -0
  207. package/dist/features/studio/useTheme.js +98 -0
  208. package/dist/features/vite/appGeneration.d.ts +4 -0
  209. package/dist/features/vite/appGeneration.d.ts.map +1 -0
  210. package/dist/features/vite/appGeneration.js +67 -0
  211. package/dist/features/vite/blockBoilerplate.d.ts +3 -0
  212. package/dist/features/vite/blockBoilerplate.d.ts.map +1 -0
  213. package/dist/features/vite/blockBoilerplate.js +59 -0
  214. package/dist/features/vite/convexSync.d.ts +6 -0
  215. package/dist/features/vite/convexSync.d.ts.map +1 -0
  216. package/dist/features/vite/convexSync.js +98 -0
  217. package/dist/features/vite/definitionsSync.d.ts +11 -0
  218. package/dist/features/vite/definitionsSync.d.ts.map +1 -0
  219. package/dist/features/vite/definitionsSync.js +157 -0
  220. package/dist/features/vite/routeGeneration.d.ts +4 -0
  221. package/dist/features/vite/routeGeneration.d.ts.map +1 -0
  222. package/dist/features/vite/routeGeneration.js +194 -0
  223. package/dist/features/vite/skillGeneration.d.ts +4 -0
  224. package/dist/features/vite/skillGeneration.d.ts.map +1 -0
  225. package/dist/features/vite/skillGeneration.js +69 -0
  226. package/dist/features/vite/utils.d.ts +2 -0
  227. package/dist/features/vite/utils.d.ts.map +1 -0
  228. package/dist/features/vite/utils.js +10 -0
  229. package/dist/features/vite/vite.d.ts +18 -0
  230. package/dist/features/vite/vite.d.ts.map +1 -0
  231. package/dist/features/vite/vite.js +77 -0
  232. package/dist/hooks/use-file-upload.d.ts +22 -0
  233. package/dist/hooks/use-file-upload.d.ts.map +1 -0
  234. package/dist/hooks/use-marquee-selection.d.ts +17 -0
  235. package/dist/hooks/use-marquee-selection.d.ts.map +1 -0
  236. package/dist/lib/analytics-client.d.ts +3 -0
  237. package/dist/lib/analytics-client.d.ts.map +1 -0
  238. package/dist/lib/analytics.d.ts +3 -0
  239. package/dist/lib/analytics.d.ts.map +1 -0
  240. package/dist/lib/analytics.js +24 -0
  241. package/dist/lib/auth.d.ts +3683 -0
  242. package/dist/lib/auth.d.ts.map +1 -0
  243. package/dist/lib/convex-site.d.ts +3 -0
  244. package/dist/lib/convex-site.d.ts.map +1 -0
  245. package/dist/lib/utils.d.ts +40 -0
  246. package/dist/lib/utils.d.ts.map +1 -0
  247. package/dist/studio.css +2 -0
  248. package/package.json +123 -10
  249. package/server/api.d.ts +1 -0
  250. package/server/api.js +1 -0
  251. package/server/dataModel.d.ts +1 -0
  252. package/server/dataModel.js +1 -0
  253. package/skills/camox-block/SKILL.md +357 -0
  254. package/skills/camox-layout/SKILL.md +181 -0
  255. package/index.js +0 -3
@@ -0,0 +1,139 @@
1
+ import { AssetLightbox } from "./AssetLightbox.js";
2
+ import { AssetPickerGrid } from "./AssetPickerGrid.js";
3
+ import { UnlinkAssetButton } from "./UnlinkAssetButton.js";
4
+ import * as React from "react";
5
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
+ import { Button } from "@camox/ui/button";
7
+ import { FileIcon, Upload } from "lucide-react";
8
+ import { UploadDropZone } from "@/features/content/components/UploadDropZone";
9
+ import { UploadItemRow } from "@/features/content/components/UploadProgressDrawer";
10
+ import { useFileUpload } from "@/hooks/use-file-upload";
11
+ //#region src/features/preview/components/AssetFieldEditor.tsx
12
+ function assetLabel(isImage, multiple) {
13
+ if (isImage) return multiple ? "images" : "image";
14
+ return multiple ? "files" : "file";
15
+ }
16
+ var AssetActionButtons = ({ isImage, multiple, fileInputRef, onPickerOpen, onFilesSelected, uploads }) => /* @__PURE__ */ jsxs(Fragment, { children: [
17
+ /* @__PURE__ */ jsxs(Button, {
18
+ variant: "default",
19
+ className: "mx-auto flex w-full",
20
+ onClick: onPickerOpen,
21
+ children: ["Select existing ", assetLabel(isImage, multiple)]
22
+ }),
23
+ /* @__PURE__ */ jsxs(Button, {
24
+ variant: "secondary",
25
+ className: "mx-auto flex w-full",
26
+ onClick: () => fileInputRef.current?.click(),
27
+ children: [/* @__PURE__ */ jsx(Upload, { className: "h-4 w-4" }), "Upload new"]
28
+ }),
29
+ /* @__PURE__ */ jsx("p", {
30
+ className: "text-muted-foreground text-center text-xs",
31
+ children: "Or drag anywhere to upload"
32
+ }),
33
+ /* @__PURE__ */ jsx("input", {
34
+ type: "file",
35
+ ref: fileInputRef,
36
+ className: "hidden",
37
+ accept: isImage ? "image/*" : "*/*",
38
+ multiple,
39
+ onChange: (e) => {
40
+ if (e.target.files) onFilesSelected(e.target.files);
41
+ e.target.value = "";
42
+ }
43
+ }),
44
+ uploads.length > 0 && /* @__PURE__ */ jsx("div", { children: uploads.map((item) => /* @__PURE__ */ jsx(UploadItemRow, { item }, item.id)) })
45
+ ] });
46
+ var SingleAssetFieldEditor = ({ fieldName, assetType, currentData, onFieldChange }) => {
47
+ const asset = currentData[fieldName];
48
+ const hasAsset = !!asset?.url;
49
+ const [lightboxOpen, setLightboxOpen] = React.useState(false);
50
+ const [pickerOpen, setPickerOpen] = React.useState(false);
51
+ const isImage = assetType === "Image";
52
+ const fileInputRef = React.useRef(null);
53
+ const { uploads, uploadFiles } = useFileUpload({ onFileCommitted: (result) => {
54
+ onFieldChange(fieldName, {
55
+ url: result.url,
56
+ alt: "",
57
+ filename: result.filename,
58
+ mimeType: result.mimeType,
59
+ _fileId: result.fileId
60
+ });
61
+ } });
62
+ const handleDrop = React.useCallback((files) => {
63
+ const dt = new DataTransfer();
64
+ dt.items.add(files[0]);
65
+ uploadFiles(dt.files);
66
+ }, [uploadFiles]);
67
+ const handleSelectExisting = (file) => {
68
+ onFieldChange(fieldName, {
69
+ url: file.url,
70
+ alt: file.alt,
71
+ filename: file.filename,
72
+ mimeType: file.mimeType,
73
+ _fileId: file._id
74
+ });
75
+ setPickerOpen(false);
76
+ };
77
+ return /* @__PURE__ */ jsx(UploadDropZone, {
78
+ onDrop: handleDrop,
79
+ children: pickerOpen ? /* @__PURE__ */ jsx(AssetPickerGrid, {
80
+ assetType,
81
+ mode: "single",
82
+ onSelectSingle: handleSelectExisting,
83
+ onSelectMultiple: () => {},
84
+ onClose: () => setPickerOpen(false)
85
+ }) : /* @__PURE__ */ jsxs("div", {
86
+ className: "space-y-4 px-4 py-4",
87
+ children: [hasAsset && /* @__PURE__ */ jsxs("div", {
88
+ className: "text-foreground hover:bg-accent/75 flex max-w-full flex-row items-center gap-2 rounded-lg border-2 px-1 py-1",
89
+ children: [
90
+ /* @__PURE__ */ jsxs("button", {
91
+ type: "button",
92
+ className: "flex min-w-0 flex-1 cursor-zoom-in items-center gap-2",
93
+ onClick: () => setLightboxOpen(true),
94
+ children: [isImage ? /* @__PURE__ */ jsx("div", {
95
+ className: "border-border h-10 w-10 shrink-0 overflow-hidden rounded border",
96
+ children: /* @__PURE__ */ jsx("img", {
97
+ src: asset.url,
98
+ alt: asset.alt || asset.filename,
99
+ className: "h-full w-full object-cover"
100
+ })
101
+ }) : /* @__PURE__ */ jsx("div", {
102
+ className: "border-border bg-muted flex h-10 w-10 shrink-0 items-center justify-center overflow-hidden rounded border",
103
+ children: /* @__PURE__ */ jsx(FileIcon, { className: "text-muted-foreground h-5 w-5" })
104
+ }), /* @__PURE__ */ jsx("p", {
105
+ className: "flex-1 truncate text-left text-sm",
106
+ title: asset.filename,
107
+ children: asset.filename || "Untitled"
108
+ })]
109
+ }),
110
+ /* @__PURE__ */ jsx(UnlinkAssetButton, {
111
+ fileId: asset._fileId,
112
+ onUnlink: () => {
113
+ onFieldChange(fieldName, {
114
+ url: "",
115
+ alt: "",
116
+ filename: "",
117
+ mimeType: ""
118
+ });
119
+ }
120
+ }),
121
+ asset._fileId && /* @__PURE__ */ jsx(AssetLightbox, {
122
+ open: lightboxOpen,
123
+ onOpenChange: setLightboxOpen,
124
+ fileId: asset._fileId
125
+ })
126
+ ]
127
+ }), /* @__PURE__ */ jsx(AssetActionButtons, {
128
+ isImage,
129
+ multiple: false,
130
+ fileInputRef,
131
+ onPickerOpen: () => setPickerOpen(true),
132
+ onFilesSelected: handleDrop,
133
+ uploads
134
+ })]
135
+ })
136
+ });
137
+ };
138
+ //#endregion
139
+ export { AssetActionButtons, SingleAssetFieldEditor };
@@ -0,0 +1,9 @@
1
+ import { Id } from 'camox/server/dataModel';
2
+ interface AssetLightboxProps {
3
+ open: boolean;
4
+ onOpenChange: (open: boolean) => void;
5
+ fileId: Id<"files">;
6
+ }
7
+ declare const AssetLightbox: ({ open, onOpenChange, fileId }: AssetLightboxProps) => import("react/jsx-runtime").JSX.Element | null;
8
+ export { AssetLightbox };
9
+ //# sourceMappingURL=AssetLightbox.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AssetLightbox.d.ts","sourceRoot":"","sources":["../../../../src/features/preview/components/AssetLightbox.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,wBAAwB,CAAC;AAgDjD,UAAU,kBAAkB;IAC1B,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACtC,MAAM,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC;CACrB;AAED,QAAA,MAAM,aAAa,GAAI,gCAAgC,kBAAkB,mDAoWxE,CAAC;AAEF,OAAO,EAAE,aAAa,EAAE,CAAC"}
@@ -0,0 +1,421 @@
1
+ import { DebouncedFieldEditor } from "./DebouncedFieldEditor.js";
2
+ import { Label } from "@camox/ui/label";
3
+ import { toast } from "@camox/ui/toaster";
4
+ import { api } from "camox/server/api";
5
+ import { useMutation, useQuery } from "convex/react";
6
+ import { useCallback, useEffect, useRef, useState } from "react";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ import { Button } from "@camox/ui/button";
9
+ import { Check, Download, FileIcon, Link, Loader2, Trash2, X } from "lucide-react";
10
+ import { useConvexToken } from "@/lib/auth";
11
+ import { Tooltip, TooltipContent, TooltipTrigger } from "@camox/ui/tooltip";
12
+ import { Switch } from "@camox/ui/switch";
13
+ import { UploadDropZone } from "@/features/content/components/UploadDropZone";
14
+ import { ButtonGroup } from "@camox/ui/button-group";
15
+ import { Dialog, DialogContent, DialogTitle } from "@camox/ui/dialog";
16
+ import { FS_PREFIX, getSiteUrl } from "@/lib/convex-site";
17
+ //#region src/features/preview/components/AssetLightbox.tsx
18
+ function MetadataRow({ label, children }) {
19
+ return /* @__PURE__ */ jsxs("div", {
20
+ className: "flex items-baseline gap-2",
21
+ children: [
22
+ /* @__PURE__ */ jsx("span", {
23
+ className: "shrink-0",
24
+ children: label
25
+ }),
26
+ /* @__PURE__ */ jsx("span", { className: "border-border min-w-0 flex-1 border-b" }),
27
+ /* @__PURE__ */ jsx("span", {
28
+ className: "text-foreground shrink-0",
29
+ children
30
+ })
31
+ ]
32
+ });
33
+ }
34
+ function formatRelativeTime(epochMs) {
35
+ const now = Temporal.Now.instant();
36
+ const then = Temporal.Instant.fromEpochMilliseconds(epochMs);
37
+ const totalSeconds = now.since(then).total("seconds");
38
+ const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
39
+ if (totalSeconds < 60) return rtf.format(-Math.floor(totalSeconds), "second");
40
+ const totalMinutes = Math.floor(totalSeconds / 60);
41
+ if (totalMinutes < 60) return rtf.format(-totalMinutes, "minute");
42
+ const totalHours = Math.floor(totalMinutes / 60);
43
+ if (totalHours < 24) return rtf.format(-totalHours, "hour");
44
+ const totalDays = Math.floor(totalHours / 24);
45
+ if (totalDays < 30) return rtf.format(-totalDays, "day");
46
+ const totalMonths = Math.floor(totalDays / 30);
47
+ if (totalMonths < 12) return rtf.format(-totalMonths, "month");
48
+ return rtf.format(-Math.floor(totalDays / 365), "year");
49
+ }
50
+ function formatFileSize(bytes) {
51
+ if (bytes < 1024) return `${bytes} B`;
52
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
53
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
54
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
55
+ }
56
+ var AssetLightbox = ({ open, onOpenChange, fileId }) => {
57
+ const file = useQuery(api.files.getFile, { fileId });
58
+ const usageCount = useQuery(api.files.getFileUsageCount, { fileId });
59
+ const [uploadState, setUploadState] = useState(null);
60
+ const replaceInputRef = useRef(null);
61
+ const containerRef = useRef(null);
62
+ const [zoomed, setZoomed] = useState(false);
63
+ const [zoomedWidth, setZoomedWidth] = useState(null);
64
+ const clickFractionRef = useRef(null);
65
+ useEffect(() => {
66
+ if (!open) {
67
+ setZoomed(false);
68
+ setZoomedWidth(null);
69
+ }
70
+ }, [open]);
71
+ useEffect(() => {
72
+ if (!zoomed || !zoomedWidth || !containerRef.current || !clickFractionRef.current) return;
73
+ const container = containerRef.current;
74
+ const img = container.querySelector("img");
75
+ if (!img) return;
76
+ requestAnimationFrame(() => {
77
+ const frac = clickFractionRef.current;
78
+ const scrollX = img.offsetLeft + img.width * frac.x - container.clientWidth / 2;
79
+ const scrollY = img.offsetTop + img.height * frac.y - container.clientHeight / 2;
80
+ container.scrollTo(scrollX, scrollY);
81
+ clickFractionRef.current = null;
82
+ });
83
+ }, [zoomed, zoomedWidth]);
84
+ const updateFileFilename = useMutation(api.files.updateFileFilename);
85
+ const updateFileAlt = useMutation(api.files.updateFileAlt);
86
+ const deleteFile = useMutation(api.files.deleteFile);
87
+ const replaceFile = useMutation(api.files.replaceFile);
88
+ const setAiMetadata = useMutation(api.files.setAiMetadata);
89
+ const commitFile = useMutation(api.files.commitFile);
90
+ const getToken = useConvexToken();
91
+ const siteUrl = getSiteUrl();
92
+ const handleReplaceDrop = useCallback(async (files) => {
93
+ const droppedFile = files[0];
94
+ if (!droppedFile) return;
95
+ setUploadState({
96
+ status: "uploading",
97
+ progress: 0,
98
+ filename: droppedFile.name
99
+ });
100
+ try {
101
+ const token = await getToken();
102
+ const blobId = await new Promise((resolve, reject) => {
103
+ const xhr = new XMLHttpRequest();
104
+ xhr.upload.addEventListener("progress", (e) => {
105
+ if (e.lengthComputable) setUploadState((prev) => prev ? {
106
+ ...prev,
107
+ progress: Math.round(e.loaded / e.total * 100)
108
+ } : prev);
109
+ });
110
+ xhr.addEventListener("load", () => {
111
+ if (xhr.status >= 200 && xhr.status < 300) try {
112
+ resolve(JSON.parse(xhr.responseText).blobId);
113
+ } catch {
114
+ reject(/* @__PURE__ */ new Error("Invalid response from upload"));
115
+ }
116
+ else reject(/* @__PURE__ */ new Error(`Upload failed: ${xhr.status}`));
117
+ });
118
+ xhr.addEventListener("error", () => reject(/* @__PURE__ */ new Error("Upload failed")));
119
+ xhr.open("POST", `${siteUrl}${FS_PREFIX}/upload`);
120
+ xhr.setRequestHeader("Content-Type", droppedFile.type);
121
+ if (token) xhr.setRequestHeader("Authorization", `Bearer ${token}`);
122
+ xhr.send(droppedFile);
123
+ });
124
+ setUploadState((prev) => prev ? {
125
+ ...prev,
126
+ status: "committing",
127
+ progress: 100
128
+ } : prev);
129
+ const { fileId: newFileId } = await commitFile({
130
+ blobId,
131
+ filename: droppedFile.name,
132
+ contentType: droppedFile.type,
133
+ size: droppedFile.size,
134
+ siteUrl
135
+ });
136
+ await replaceFile({
137
+ oldFileId: fileId,
138
+ newFileId
139
+ });
140
+ setUploadState((prev) => prev ? {
141
+ ...prev,
142
+ status: "complete"
143
+ } : prev);
144
+ toast.success("File replaced");
145
+ setTimeout(() => {
146
+ setUploadState(null);
147
+ onOpenChange(false);
148
+ }, 600);
149
+ } catch (err) {
150
+ const message = err instanceof Error ? err.message : "Upload failed";
151
+ setUploadState((prev) => prev ? {
152
+ ...prev,
153
+ status: "error",
154
+ error: message
155
+ } : prev);
156
+ toast.error(message);
157
+ setTimeout(() => setUploadState(null), 3e3);
158
+ }
159
+ }, [
160
+ siteUrl,
161
+ commitFile,
162
+ replaceFile,
163
+ fileId,
164
+ onOpenChange,
165
+ getToken
166
+ ]);
167
+ const handleCopyUrl = async () => {
168
+ if (!file) return;
169
+ await navigator.clipboard.writeText(file.url);
170
+ toast("Link copied to clipboard");
171
+ };
172
+ const handleDownload = () => {
173
+ if (!file) return;
174
+ const a = document.createElement("a");
175
+ a.href = file.url;
176
+ a.download = file.filename || "file";
177
+ a.click();
178
+ };
179
+ const handleDelete = async () => {
180
+ await deleteFile({ fileId });
181
+ onOpenChange(false);
182
+ };
183
+ if (!file) return null;
184
+ const isImage = file.mimeType?.startsWith("image/");
185
+ return /* @__PURE__ */ jsx(Dialog, {
186
+ open,
187
+ onOpenChange,
188
+ children: /* @__PURE__ */ jsxs(DialogContent, {
189
+ className: "h-[90vh] max-h-[90vh] w-[90vw] max-w-[90vw] gap-0 overflow-hidden p-0 sm:max-w-[90vw]",
190
+ showCloseButton: false,
191
+ children: [
192
+ /* @__PURE__ */ jsx(DialogTitle, {
193
+ className: "sr-only",
194
+ children: file.alt || file.filename || "File preview"
195
+ }),
196
+ /* @__PURE__ */ jsx(Button, {
197
+ type: "button",
198
+ variant: "ghost",
199
+ size: "icon",
200
+ className: "absolute top-2 right-2 z-10",
201
+ onClick: () => onOpenChange(false),
202
+ children: /* @__PURE__ */ jsx(X, {})
203
+ }),
204
+ /* @__PURE__ */ jsxs("div", {
205
+ className: "flex h-full flex-row",
206
+ children: [/* @__PURE__ */ jsxs(UploadDropZone, {
207
+ label: "Drop file to replace",
208
+ onDrop: handleReplaceDrop,
209
+ className: "h-full min-w-0 flex-1",
210
+ children: [isImage ? /* @__PURE__ */ jsx("div", {
211
+ ref: containerRef,
212
+ className: `checkered absolute inset-0 ${zoomed ? "overflow-auto" : "flex items-center justify-center overflow-hidden p-6"}`,
213
+ onClick: (e) => {
214
+ const img = containerRef.current?.querySelector("img");
215
+ if (!img) return;
216
+ if (!zoomed) {
217
+ const rect = img.getBoundingClientRect();
218
+ clickFractionRef.current = {
219
+ x: (e.clientX - rect.left) / rect.width,
220
+ y: (e.clientY - rect.top) / rect.height
221
+ };
222
+ const container = containerRef.current;
223
+ const scaleForWidth = container.clientWidth * 2.5 / img.clientWidth;
224
+ const scaleForHeight = container.clientHeight * 2.5 / img.clientHeight;
225
+ setZoomedWidth(img.clientWidth * Math.max(scaleForWidth, scaleForHeight));
226
+ setZoomed(true);
227
+ } else {
228
+ setZoomed(false);
229
+ setZoomedWidth(null);
230
+ containerRef.current?.scrollTo(0, 0);
231
+ }
232
+ },
233
+ children: zoomed ? /* @__PURE__ */ jsx("div", {
234
+ className: "flex min-h-full items-center justify-center",
235
+ children: /* @__PURE__ */ jsx("img", {
236
+ src: file.url,
237
+ alt: file.alt || file.filename || "",
238
+ className: "cursor-zoom-out",
239
+ style: { width: zoomedWidth ?? void 0 },
240
+ draggable: false
241
+ })
242
+ }) : /* @__PURE__ */ jsx("img", {
243
+ src: file.url,
244
+ alt: file.alt || file.filename || "",
245
+ className: "max-h-full max-w-full cursor-zoom-in object-contain shadow-lg",
246
+ draggable: false
247
+ })
248
+ }) : /* @__PURE__ */ jsx("div", {
249
+ className: "bg-muted/30 flex h-full min-h-[70vh] items-center justify-center p-6",
250
+ children: /* @__PURE__ */ jsx(FileIcon, { className: "text-muted-foreground h-16 w-16" })
251
+ }), uploadState && /* @__PURE__ */ jsx("div", {
252
+ className: "bg-background/80 absolute inset-0 z-30 flex items-center justify-center backdrop-blur-sm",
253
+ children: /* @__PURE__ */ jsxs("div", {
254
+ className: "border-border bg-background w-64 rounded-lg border p-4 shadow-lg",
255
+ children: [
256
+ /* @__PURE__ */ jsxs("div", {
257
+ className: "flex items-center gap-2",
258
+ children: [/* @__PURE__ */ jsxs("div", {
259
+ className: "shrink-0",
260
+ children: [
261
+ uploadState.status === "uploading" && /* @__PURE__ */ jsx(Loader2, { className: "text-muted-foreground h-4 w-4 animate-spin" }),
262
+ uploadState.status === "committing" && /* @__PURE__ */ jsx(Loader2, { className: "text-primary h-4 w-4 animate-spin" }),
263
+ uploadState.status === "complete" && /* @__PURE__ */ jsx(Check, { className: "h-4 w-4 text-green-500" })
264
+ ]
265
+ }), /* @__PURE__ */ jsxs("div", {
266
+ className: "min-w-0 flex-1",
267
+ children: [/* @__PURE__ */ jsxs("p", {
268
+ className: "truncate text-sm font-medium",
269
+ children: [
270
+ uploadState.status === "uploading" && "Uploading…",
271
+ uploadState.status === "committing" && "Processing…",
272
+ uploadState.status === "complete" && "Replaced",
273
+ uploadState.status === "error" && "Upload failed"
274
+ ]
275
+ }), /* @__PURE__ */ jsx("p", {
276
+ className: "text-muted-foreground truncate text-xs",
277
+ children: uploadState.filename
278
+ })]
279
+ })]
280
+ }),
281
+ (uploadState.status === "uploading" || uploadState.status === "committing") && /* @__PURE__ */ jsx("div", {
282
+ className: "bg-muted mt-3 h-1.5 overflow-hidden rounded-full",
283
+ children: /* @__PURE__ */ jsx("div", {
284
+ className: "bg-primary h-full transition-all duration-200",
285
+ style: { width: `${uploadState.progress}%` }
286
+ })
287
+ }),
288
+ uploadState.status === "error" && uploadState.error && /* @__PURE__ */ jsx("p", {
289
+ className: "text-destructive mt-2 text-xs",
290
+ children: uploadState.error
291
+ })
292
+ ]
293
+ })
294
+ })]
295
+ }), /* @__PURE__ */ jsx("div", {
296
+ className: "border-border bg-background flex w-80 shrink-0 flex-col border-l",
297
+ children: /* @__PURE__ */ jsxs("div", {
298
+ className: "flex-1 space-y-4 overflow-y-auto p-4",
299
+ children: [
300
+ /* @__PURE__ */ jsxs(ButtonGroup, { children: [
301
+ /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
302
+ asChild: true,
303
+ children: /* @__PURE__ */ jsx(Button, {
304
+ type: "button",
305
+ variant: "outline",
306
+ size: "icon",
307
+ onClick: handleCopyUrl,
308
+ children: /* @__PURE__ */ jsx(Link, {})
309
+ })
310
+ }), /* @__PURE__ */ jsx(TooltipContent, { children: "Copy URL" })] }),
311
+ /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
312
+ asChild: true,
313
+ children: /* @__PURE__ */ jsx(Button, {
314
+ type: "button",
315
+ variant: "outline",
316
+ size: "icon",
317
+ onClick: handleDownload,
318
+ children: /* @__PURE__ */ jsx(Download, {})
319
+ })
320
+ }), /* @__PURE__ */ jsx(TooltipContent, { children: "Download" })] }),
321
+ /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
322
+ asChild: true,
323
+ children: /* @__PURE__ */ jsx(Button, {
324
+ type: "button",
325
+ variant: "outline",
326
+ size: "icon",
327
+ onClick: handleDelete,
328
+ children: /* @__PURE__ */ jsx(Trash2, {})
329
+ })
330
+ }), /* @__PURE__ */ jsx(TooltipContent, { children: "Delete" })] })
331
+ ] }),
332
+ /* @__PURE__ */ jsxs("div", {
333
+ className: "flex items-center gap-2",
334
+ children: [/* @__PURE__ */ jsx(Switch, {
335
+ id: "ai-metadata",
336
+ checked: file.aiMetadataEnabled !== false,
337
+ onCheckedChange: (checked) => setAiMetadata({
338
+ fileId,
339
+ enabled: checked
340
+ })
341
+ }), /* @__PURE__ */ jsx(Label, {
342
+ htmlFor: "ai-metadata",
343
+ children: "AI metadata"
344
+ })]
345
+ }),
346
+ /* @__PURE__ */ jsx(DebouncedFieldEditor, {
347
+ label: "File name",
348
+ placeholder: "File name...",
349
+ initialValue: file.filename,
350
+ disabled: file.aiMetadataEnabled !== false,
351
+ onSave: (value) => updateFileFilename({
352
+ fileId,
353
+ filename: value
354
+ })
355
+ }),
356
+ /* @__PURE__ */ jsx(DebouncedFieldEditor, {
357
+ label: "Alt text",
358
+ placeholder: "Describe this file...",
359
+ initialValue: file.alt,
360
+ disabled: file.aiMetadataEnabled !== false,
361
+ rows: 2,
362
+ onSave: (value) => updateFileAlt({
363
+ fileId,
364
+ alt: value
365
+ })
366
+ }),
367
+ /* @__PURE__ */ jsxs("div", {
368
+ className: "text-muted-foreground space-y-1 text-sm",
369
+ children: [
370
+ /* @__PURE__ */ jsx(MetadataRow, {
371
+ label: "Format",
372
+ children: file.mimeType.split("/").pop()?.toUpperCase() ?? "Unknown"
373
+ }),
374
+ /* @__PURE__ */ jsx(MetadataRow, {
375
+ label: "Size",
376
+ children: file.size != null ? formatFileSize(file.size) : "Unknown"
377
+ }),
378
+ /* @__PURE__ */ jsx(MetadataRow, {
379
+ label: "Created",
380
+ children: formatRelativeTime(file.createdAt)
381
+ }),
382
+ /* @__PURE__ */ jsx(MetadataRow, {
383
+ label: "Updated",
384
+ children: formatRelativeTime(file.updatedAt)
385
+ }),
386
+ /* @__PURE__ */ jsxs(MetadataRow, {
387
+ label: "Used in",
388
+ children: [
389
+ usageCount == null && "…",
390
+ usageCount === 0 && "No blocks",
391
+ usageCount != null && usageCount > 0 && `${usageCount} ${usageCount === 1 ? "block" : "blocks"}`
392
+ ]
393
+ })
394
+ ]
395
+ }),
396
+ /* @__PURE__ */ jsx("input", {
397
+ ref: replaceInputRef,
398
+ type: "file",
399
+ className: "hidden",
400
+ accept: isImage ? "image/*" : "*/*",
401
+ onChange: (e) => {
402
+ if (e.target.files) handleReplaceDrop(e.target.files);
403
+ e.target.value = "";
404
+ }
405
+ }),
406
+ /* @__PURE__ */ jsx(Button, {
407
+ type: "button",
408
+ variant: "secondary",
409
+ onClick: () => replaceInputRef.current?.click(),
410
+ children: isImage ? "Replace image" : "Replace file"
411
+ })
412
+ ]
413
+ })
414
+ })]
415
+ })
416
+ ]
417
+ })
418
+ });
419
+ };
420
+ //#endregion
421
+ export { AssetLightbox };
@@ -0,0 +1,11 @@
1
+ import { Doc } from 'camox/server/dataModel';
2
+ interface AssetPickerGridProps {
3
+ assetType: "Image" | "File";
4
+ mode: "single" | "multiple";
5
+ onSelectSingle: (file: Doc<"files">) => void;
6
+ onSelectMultiple: (files: Doc<"files">[]) => void;
7
+ onClose: () => void;
8
+ }
9
+ declare const AssetPickerGrid: ({ assetType, mode, onSelectSingle, onSelectMultiple, onClose, }: AssetPickerGridProps) => import("react/jsx-runtime").JSX.Element;
10
+ export { AssetPickerGrid };
11
+ //# sourceMappingURL=AssetPickerGrid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AssetPickerGrid.d.ts","sourceRoot":"","sources":["../../../../src/features/preview/components/AssetPickerGrid.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,wBAAwB,CAAC;AASlD,UAAU,oBAAoB;IAC5B,SAAS,EAAE,OAAO,GAAG,MAAM,CAAC;IAC5B,IAAI,EAAE,QAAQ,GAAG,UAAU,CAAC;IAC5B,cAAc,EAAE,CAAC,IAAI,EAAE,GAAG,CAAC,OAAO,CAAC,KAAK,IAAI,CAAC;IAC7C,gBAAgB,EAAE,CAAC,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC,EAAE,KAAK,IAAI,CAAC;IAClD,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,QAAA,MAAM,eAAe,GAAI,iEAMtB,oBAAoB,4CAoGtB,CAAC;AAEF,OAAO,EAAE,eAAe,EAAE,CAAC"}
@@ -0,0 +1,92 @@
1
+ import { AssetLightbox } from "./AssetLightbox.js";
2
+ import { api } from "camox/server/api";
3
+ import { useQuery } from "convex/react";
4
+ import * as React from "react";
5
+ import { jsx, jsxs } from "react/jsx-runtime";
6
+ import { Button } from "@camox/ui/button";
7
+ import { ArrowLeft } from "lucide-react";
8
+ import { Skeleton } from "@camox/ui/skeleton";
9
+ import { AssetCard } from "@/features/content/components/AssetCard";
10
+ //#region src/features/preview/components/AssetPickerGrid.tsx
11
+ var AssetPickerGrid = ({ assetType, mode, onSelectSingle, onSelectMultiple, onClose }) => {
12
+ const allFiles = useQuery(api.files.listFiles);
13
+ const [selectedIds, setSelectedIds] = React.useState(/* @__PURE__ */ new Set());
14
+ const [lightboxFile, setLightboxFile] = React.useState(null);
15
+ const isImage = assetType === "Image";
16
+ const files = React.useMemo(() => {
17
+ if (!allFiles) return void 0;
18
+ if (!isImage) return allFiles;
19
+ return allFiles.filter((f) => f.mimeType?.startsWith("image/"));
20
+ }, [allFiles, isImage]);
21
+ const toggleSelection = (fileId) => {
22
+ setSelectedIds((prev) => {
23
+ const next = new Set(prev);
24
+ if (next.has(fileId)) next.delete(fileId);
25
+ else next.add(fileId);
26
+ return next;
27
+ });
28
+ };
29
+ const handleConfirmMultiple = () => {
30
+ if (!files) return;
31
+ onSelectMultiple(files.filter((f) => selectedIds.has(f._id)));
32
+ };
33
+ return /* @__PURE__ */ jsxs("div", { children: [
34
+ /* @__PURE__ */ jsxs("div", {
35
+ className: "bg-background sticky top-0 z-10 flex items-center gap-2 px-4 py-4",
36
+ children: [/* @__PURE__ */ jsxs(Button, {
37
+ variant: "ghost",
38
+ size: "sm",
39
+ onClick: onClose,
40
+ children: [/* @__PURE__ */ jsx(ArrowLeft, { className: "h-4 w-4" }), mode === "multiple" ? `Select ${isImage ? "images" : "files"}` : `Select ${isImage ? "image" : "file"}`]
41
+ }), mode === "multiple" && /* @__PURE__ */ jsxs(Button, {
42
+ variant: "default",
43
+ size: "sm",
44
+ className: "ml-auto",
45
+ disabled: selectedIds.size === 0,
46
+ onClick: handleConfirmMultiple,
47
+ children: [
48
+ "Add selected (",
49
+ selectedIds.size,
50
+ ")"
51
+ ]
52
+ })]
53
+ }),
54
+ /* @__PURE__ */ jsxs("div", {
55
+ className: "px-4 pb-4",
56
+ children: [
57
+ files === void 0 && /* @__PURE__ */ jsx("div", {
58
+ className: "grid grid-cols-[repeat(auto-fill,minmax(140px,1fr))] gap-3",
59
+ children: Array.from({ length: 6 }).map((_, i) => /* @__PURE__ */ jsxs("div", {
60
+ className: "flex flex-col gap-1.5 rounded-lg p-2",
61
+ children: [/* @__PURE__ */ jsx(Skeleton, { className: "aspect-4/3 w-full rounded-md" }), /* @__PURE__ */ jsx(Skeleton, { className: "h-3.5 w-3/4 rounded" })]
62
+ }, i))
63
+ }),
64
+ files?.length === 0 && /* @__PURE__ */ jsx("p", {
65
+ className: "text-muted-foreground py-8 text-center text-sm",
66
+ children: "No assets yet"
67
+ }),
68
+ files && files.length > 0 && /* @__PURE__ */ jsx("div", {
69
+ className: "grid grid-cols-[repeat(auto-fill,minmax(140px,1fr))] gap-3",
70
+ children: files.map((file) => /* @__PURE__ */ jsx(AssetCard, {
71
+ file,
72
+ selected: selectedIds.has(file._id),
73
+ onSelect: () => {
74
+ if (mode === "single") onSelectSingle(file);
75
+ else toggleSelection(file._id);
76
+ },
77
+ onOpen: () => setLightboxFile(file)
78
+ }, file._id))
79
+ })
80
+ ]
81
+ }),
82
+ lightboxFile && /* @__PURE__ */ jsx(AssetLightbox, {
83
+ open: !!lightboxFile,
84
+ onOpenChange: (open) => {
85
+ if (!open) setLightboxFile(null);
86
+ },
87
+ fileId: lightboxFile._id
88
+ })
89
+ ] });
90
+ };
91
+ //#endregion
92
+ export { AssetPickerGrid };