@vertesia/ui 0.78.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 (212) hide show
  1. package/lib/esm/core/components/SelectList.js +18 -13
  2. package/lib/esm/core/components/SelectList.js.map +1 -1
  3. package/lib/esm/core/components/SidePanel.js +1 -1
  4. package/lib/esm/core/components/SidePanel.js.map +1 -1
  5. package/lib/esm/core/components/shadcn/filters/filterBar.js +39 -12
  6. package/lib/esm/core/components/shadcn/filters/filterBar.js.map +1 -1
  7. package/lib/esm/core/components/shadcn/index.js +1 -0
  8. package/lib/esm/core/components/shadcn/index.js.map +1 -1
  9. package/lib/esm/core/components/shadcn/resizeable.js +15 -0
  10. package/lib/esm/core/components/shadcn/resizeable.js.map +1 -0
  11. package/lib/esm/core/components/shadcn/tabs.js +11 -6
  12. package/lib/esm/core/components/shadcn/tabs.js.map +1 -1
  13. package/lib/esm/core/components/table/index.js +1 -1
  14. package/lib/esm/core/components/table/index.js.map +1 -1
  15. package/lib/esm/features/facets/CollectionsFacetsNav.js +66 -0
  16. package/lib/esm/features/facets/CollectionsFacetsNav.js.map +1 -0
  17. package/lib/esm/features/facets/DocumentsFacetsNav.js +19 -7
  18. package/lib/esm/features/facets/DocumentsFacetsNav.js.map +1 -1
  19. package/lib/esm/features/facets/EnvironmentFacet.js +1 -1
  20. package/lib/esm/features/facets/EnvironmentFacet.js.map +1 -1
  21. package/lib/esm/features/facets/InteractionsFacetsNav.js +82 -0
  22. package/lib/esm/features/facets/InteractionsFacetsNav.js.map +1 -0
  23. package/lib/esm/features/facets/PromptsFacetsNav.js +80 -0
  24. package/lib/esm/features/facets/PromptsFacetsNav.js.map +1 -0
  25. package/lib/esm/features/facets/RunsFacetsNav.js +28 -6
  26. package/lib/esm/features/facets/RunsFacetsNav.js.map +1 -1
  27. package/lib/esm/features/facets/WorkflowExecutionsFacetsNav.js +7 -5
  28. package/lib/esm/features/facets/WorkflowExecutionsFacetsNav.js.map +1 -1
  29. package/lib/esm/features/facets/index.js +10 -8
  30. package/lib/esm/features/facets/index.js.map +1 -1
  31. package/lib/esm/features/facets/utils/SearchInterface.js +2 -0
  32. package/lib/esm/features/facets/utils/SearchInterface.js.map +1 -0
  33. package/lib/esm/features/facets/utils/StringFacet.js.map +1 -0
  34. package/lib/esm/features/facets/utils/StringListFacet.js.map +1 -0
  35. package/lib/esm/features/facets/utils/TypeFacet.js.map +1 -0
  36. package/lib/esm/features/facets/utils/VEnvironmentFacet.js.map +1 -0
  37. package/lib/esm/features/facets/utils/VInteractionFacet.js.map +1 -0
  38. package/lib/esm/features/facets/utils/VStringFacet.js.map +1 -0
  39. package/lib/esm/features/facets/{VTypeFacet.js → utils/VTypeFacet.js} +5 -3
  40. package/lib/esm/features/facets/utils/VTypeFacet.js.map +1 -0
  41. package/lib/esm/features/facets/{VUserFacet.js → utils/VUserFacet.js} +1 -1
  42. package/lib/esm/features/facets/utils/VUserFacet.js.map +1 -0
  43. package/lib/esm/features/facets/utils/utils.js.map +1 -0
  44. package/lib/esm/features/store/collections/EditCollectionView.js +14 -1
  45. package/lib/esm/features/store/collections/EditCollectionView.js.map +1 -1
  46. package/lib/esm/features/store/collections/SelectCollection.js +47 -18
  47. package/lib/esm/features/store/collections/SelectCollection.js.map +1 -1
  48. package/lib/esm/features/store/objects/DocumentSearchResults.js +9 -5
  49. package/lib/esm/features/store/objects/DocumentSearchResults.js.map +1 -1
  50. package/lib/esm/features/store/objects/components/ContentOverview.js +172 -78
  51. package/lib/esm/features/store/objects/components/ContentOverview.js.map +1 -1
  52. package/lib/esm/features/store/objects/components/DocumentIcon.js +6 -0
  53. package/lib/esm/features/store/objects/components/DocumentIcon.js.map +1 -1
  54. package/lib/esm/features/store/objects/layout/documentLayout.js +3 -4
  55. package/lib/esm/features/store/objects/layout/documentLayout.js.map +1 -1
  56. package/lib/esm/features/store/objects/selection/actions/AddToCollectionAction.js +2 -2
  57. package/lib/esm/features/store/objects/selection/actions/AddToCollectionAction.js.map +1 -1
  58. package/lib/esm/features/store/objects/upload/DocumentUploadModal.js +1 -1
  59. package/lib/esm/features/store/objects/upload/DocumentUploadModal.js.map +1 -1
  60. package/lib/esm/features/store/types/ObjectSchemaEditor.js +1 -1
  61. package/lib/esm/features/store/types/ObjectSchemaEditor.js.map +1 -1
  62. package/lib/esm/features/user/UserInfo.js +33 -1
  63. package/lib/esm/features/user/UserInfo.js.map +1 -1
  64. package/lib/esm/shell/login/UserInfo.js +1 -1
  65. package/lib/esm/shell/login/UserInfo.js.map +1 -1
  66. package/lib/esm/widgets/schema-editor/index.js +0 -1
  67. package/lib/esm/widgets/schema-editor/index.js.map +1 -1
  68. package/lib/tsconfig.tsbuildinfo +1 -1
  69. package/lib/types/core/components/SelectList.d.ts +2 -1
  70. package/lib/types/core/components/SelectList.d.ts.map +1 -1
  71. package/lib/types/core/components/SidePanel.d.ts.map +1 -1
  72. package/lib/types/core/components/shadcn/filters/filterBar.d.ts.map +1 -1
  73. package/lib/types/core/components/shadcn/index.d.ts +1 -0
  74. package/lib/types/core/components/shadcn/index.d.ts.map +1 -1
  75. package/lib/types/core/components/shadcn/resizeable.d.ts +9 -0
  76. package/lib/types/core/components/shadcn/resizeable.d.ts.map +1 -0
  77. package/lib/types/core/components/shadcn/tabs.d.ts +2 -1
  78. package/lib/types/core/components/shadcn/tabs.d.ts.map +1 -1
  79. package/lib/types/features/facets/CollectionsFacetsNav.d.ts +14 -0
  80. package/lib/types/features/facets/CollectionsFacetsNav.d.ts.map +1 -0
  81. package/lib/types/features/facets/DocumentsFacetsNav.d.ts +1 -1
  82. package/lib/types/features/facets/DocumentsFacetsNav.d.ts.map +1 -1
  83. package/lib/types/features/facets/InteractionsFacetsNav.d.ts +13 -0
  84. package/lib/types/features/facets/InteractionsFacetsNav.d.ts.map +1 -0
  85. package/lib/types/features/facets/PromptsFacetsNav.d.ts +15 -0
  86. package/lib/types/features/facets/PromptsFacetsNav.d.ts.map +1 -0
  87. package/lib/types/features/facets/RunsFacetsNav.d.ts +1 -1
  88. package/lib/types/features/facets/RunsFacetsNav.d.ts.map +1 -1
  89. package/lib/types/features/facets/WorkflowExecutionsFacetsNav.d.ts +1 -1
  90. package/lib/types/features/facets/WorkflowExecutionsFacetsNav.d.ts.map +1 -1
  91. package/lib/types/features/facets/index.d.ts +10 -8
  92. package/lib/types/features/facets/index.d.ts.map +1 -1
  93. package/lib/types/features/facets/{VFacetsNav.d.ts → utils/SearchInterface.d.ts} +1 -8
  94. package/lib/types/features/facets/utils/SearchInterface.d.ts.map +1 -0
  95. package/lib/types/features/facets/utils/StringFacet.d.ts.map +1 -0
  96. package/lib/types/features/facets/utils/StringListFacet.d.ts.map +1 -0
  97. package/lib/types/features/facets/utils/TypeFacet.d.ts.map +1 -0
  98. package/lib/types/features/facets/utils/VEnvironmentFacet.d.ts.map +1 -0
  99. package/lib/types/features/facets/utils/VInteractionFacet.d.ts.map +1 -0
  100. package/lib/types/features/facets/utils/VStringFacet.d.ts.map +1 -0
  101. package/lib/types/features/facets/utils/VTypeFacet.d.ts.map +1 -0
  102. package/lib/types/features/facets/utils/VUserFacet.d.ts.map +1 -0
  103. package/lib/types/features/facets/utils/utils.d.ts.map +1 -0
  104. package/lib/types/features/store/collections/EditCollectionView.d.ts.map +1 -1
  105. package/lib/types/features/store/collections/SelectCollection.d.ts +10 -8
  106. package/lib/types/features/store/collections/SelectCollection.d.ts.map +1 -1
  107. package/lib/types/features/store/objects/DocumentSearchResults.d.ts.map +1 -1
  108. package/lib/types/features/store/objects/components/ContentOverview.d.ts.map +1 -1
  109. package/lib/types/features/store/objects/components/DocumentIcon.d.ts +4 -0
  110. package/lib/types/features/store/objects/components/DocumentIcon.d.ts.map +1 -1
  111. package/lib/types/features/store/objects/layout/documentLayout.d.ts.map +1 -1
  112. package/lib/types/features/store/objects/search/DocumentSearchContext.d.ts +1 -3
  113. package/lib/types/features/store/objects/search/DocumentSearchContext.d.ts.map +1 -1
  114. package/lib/types/features/store/objects/upload/DocumentUploadModal.d.ts.map +1 -1
  115. package/lib/types/features/user/UserInfo.d.ts +12 -1
  116. package/lib/types/features/user/UserInfo.d.ts.map +1 -1
  117. package/lib/types/widgets/schema-editor/index.d.ts +0 -1
  118. package/lib/types/widgets/schema-editor/index.d.ts.map +1 -1
  119. package/lib/vertesia-ui-core.js +1 -1
  120. package/lib/vertesia-ui-core.js.map +1 -1
  121. package/lib/vertesia-ui-features.js +1 -1
  122. package/lib/vertesia-ui-features.js.map +1 -1
  123. package/lib/vertesia-ui-shell.js +1 -1
  124. package/lib/vertesia-ui-shell.js.map +1 -1
  125. package/lib/vertesia-ui-widgets.js +1 -1
  126. package/lib/vertesia-ui-widgets.js.map +1 -1
  127. package/package.json +5 -4
  128. package/src/core/components/SelectList.tsx +11 -1
  129. package/src/core/components/SidePanel.tsx +13 -10
  130. package/src/core/components/shadcn/filters/filterBar.tsx +46 -20
  131. package/src/core/components/shadcn/index.ts +1 -0
  132. package/src/core/components/shadcn/resizeable.tsx +54 -0
  133. package/src/core/components/shadcn/tabs.tsx +16 -6
  134. package/src/core/components/table/index.tsx +1 -1
  135. package/src/features/facets/CollectionsFacetsNav.tsx +94 -0
  136. package/src/features/facets/DocumentsFacetsNav.tsx +22 -11
  137. package/src/features/facets/EnvironmentFacet.tsx +1 -1
  138. package/src/features/facets/InteractionsFacetsNav.tsx +111 -0
  139. package/src/features/facets/PromptsFacetsNav.tsx +110 -0
  140. package/src/features/facets/RunsFacetsNav.tsx +40 -9
  141. package/src/features/facets/WorkflowExecutionsFacetsNav.tsx +10 -8
  142. package/src/features/facets/index.ts +11 -9
  143. package/src/features/facets/utils/SearchInterface.tsx +8 -0
  144. package/src/features/facets/{VTypeFacet.tsx → utils/VTypeFacet.tsx} +6 -3
  145. package/src/features/facets/{VUserFacet.tsx → utils/VUserFacet.tsx} +1 -1
  146. package/src/features/store/collections/EditCollectionView.tsx +14 -1
  147. package/src/features/store/collections/SelectCollection.tsx +160 -31
  148. package/src/features/store/objects/DocumentSearchResults.tsx +42 -37
  149. package/src/features/store/objects/components/ContentOverview.tsx +432 -261
  150. package/src/features/store/objects/components/DocumentIcon.tsx +31 -1
  151. package/src/features/store/objects/layout/documentLayout.tsx +3 -7
  152. package/src/features/store/objects/selection/actions/AddToCollectionAction.tsx +15 -8
  153. package/src/features/store/objects/upload/DocumentUploadModal.tsx +5 -6
  154. package/src/features/store/types/ObjectSchemaEditor.tsx +1 -1
  155. package/src/features/user/UserInfo.tsx +66 -3
  156. package/src/shell/login/UserInfo.tsx +1 -1
  157. package/src/widgets/schema-editor/index.ts +0 -1
  158. package/lib/esm/features/facets/FacetsNav.js +0 -8
  159. package/lib/esm/features/facets/FacetsNav.js.map +0 -1
  160. package/lib/esm/features/facets/StringFacet.js.map +0 -1
  161. package/lib/esm/features/facets/StringListFacet.js.map +0 -1
  162. package/lib/esm/features/facets/TypeFacet.js.map +0 -1
  163. package/lib/esm/features/facets/VEnvironmentFacet.js.map +0 -1
  164. package/lib/esm/features/facets/VFacetsNav.js +0 -48
  165. package/lib/esm/features/facets/VFacetsNav.js.map +0 -1
  166. package/lib/esm/features/facets/VInteractionFacet.js.map +0 -1
  167. package/lib/esm/features/facets/VStringFacet.js.map +0 -1
  168. package/lib/esm/features/facets/VTypeFacet.js.map +0 -1
  169. package/lib/esm/features/facets/VUserFacet.js.map +0 -1
  170. package/lib/esm/features/facets/utils.js.map +0 -1
  171. package/lib/esm/widgets/schema-editor/JSONSchemaEditorModal.js +0 -49
  172. package/lib/esm/widgets/schema-editor/JSONSchemaEditorModal.js.map +0 -1
  173. package/lib/types/features/facets/FacetsNav.d.ts +0 -7
  174. package/lib/types/features/facets/FacetsNav.d.ts.map +0 -1
  175. package/lib/types/features/facets/StringFacet.d.ts.map +0 -1
  176. package/lib/types/features/facets/StringListFacet.d.ts.map +0 -1
  177. package/lib/types/features/facets/TypeFacet.d.ts.map +0 -1
  178. package/lib/types/features/facets/VEnvironmentFacet.d.ts.map +0 -1
  179. package/lib/types/features/facets/VFacetsNav.d.ts.map +0 -1
  180. package/lib/types/features/facets/VInteractionFacet.d.ts.map +0 -1
  181. package/lib/types/features/facets/VStringFacet.d.ts.map +0 -1
  182. package/lib/types/features/facets/VTypeFacet.d.ts.map +0 -1
  183. package/lib/types/features/facets/VUserFacet.d.ts.map +0 -1
  184. package/lib/types/features/facets/utils.d.ts.map +0 -1
  185. package/lib/types/widgets/schema-editor/JSONSchemaEditorModal.d.ts +0 -10
  186. package/lib/types/widgets/schema-editor/JSONSchemaEditorModal.d.ts.map +0 -1
  187. package/src/features/facets/FacetsNav.tsx +0 -19
  188. package/src/features/facets/VFacetsNav.tsx +0 -81
  189. package/src/widgets/schema-editor/JSONSchemaEditorModal.tsx +0 -67
  190. /package/lib/esm/features/facets/{StringFacet.js → utils/StringFacet.js} +0 -0
  191. /package/lib/esm/features/facets/{StringListFacet.js → utils/StringListFacet.js} +0 -0
  192. /package/lib/esm/features/facets/{TypeFacet.js → utils/TypeFacet.js} +0 -0
  193. /package/lib/esm/features/facets/{VEnvironmentFacet.js → utils/VEnvironmentFacet.js} +0 -0
  194. /package/lib/esm/features/facets/{VInteractionFacet.js → utils/VInteractionFacet.js} +0 -0
  195. /package/lib/esm/features/facets/{VStringFacet.js → utils/VStringFacet.js} +0 -0
  196. /package/lib/esm/features/facets/{utils.js → utils/utils.js} +0 -0
  197. /package/lib/types/features/facets/{StringFacet.d.ts → utils/StringFacet.d.ts} +0 -0
  198. /package/lib/types/features/facets/{StringListFacet.d.ts → utils/StringListFacet.d.ts} +0 -0
  199. /package/lib/types/features/facets/{TypeFacet.d.ts → utils/TypeFacet.d.ts} +0 -0
  200. /package/lib/types/features/facets/{VEnvironmentFacet.d.ts → utils/VEnvironmentFacet.d.ts} +0 -0
  201. /package/lib/types/features/facets/{VInteractionFacet.d.ts → utils/VInteractionFacet.d.ts} +0 -0
  202. /package/lib/types/features/facets/{VStringFacet.d.ts → utils/VStringFacet.d.ts} +0 -0
  203. /package/lib/types/features/facets/{VTypeFacet.d.ts → utils/VTypeFacet.d.ts} +0 -0
  204. /package/lib/types/features/facets/{VUserFacet.d.ts → utils/VUserFacet.d.ts} +0 -0
  205. /package/lib/types/features/facets/{utils.d.ts → utils/utils.d.ts} +0 -0
  206. /package/src/features/facets/{StringFacet.tsx → utils/StringFacet.tsx} +0 -0
  207. /package/src/features/facets/{StringListFacet.tsx → utils/StringListFacet.tsx} +0 -0
  208. /package/src/features/facets/{TypeFacet.tsx → utils/TypeFacet.tsx} +0 -0
  209. /package/src/features/facets/{VEnvironmentFacet.tsx → utils/VEnvironmentFacet.tsx} +0 -0
  210. /package/src/features/facets/{VInteractionFacet.tsx → utils/VInteractionFacet.tsx} +0 -0
  211. /package/src/features/facets/{VStringFacet.tsx → utils/VStringFacet.tsx} +0 -0
  212. /package/src/features/facets/{utils.tsx → utils/utils.tsx} +0 -0
@@ -1,7 +1,7 @@
1
1
  import { useEffect, useState } from "react";
2
2
 
3
3
  import { useUserSession } from "@vertesia/ui/session";
4
- import { Button, Spinner, useToast } from "@vertesia/ui/core";
4
+ import { Button, ResizableHandle, ResizablePanel, ResizablePanelGroup, Spinner, useToast } from "@vertesia/ui/core";
5
5
  import { JSONDisplay, MarkdownRenderer } from "@vertesia/ui/widgets";
6
6
  import { ContentObject, ImageRenditionFormat } from "@vertesia/common";
7
7
  import { Copy, Download, SquarePen } from "lucide-react";
@@ -18,15 +18,51 @@ export function ContentOverview({
18
18
  loadText,
19
19
  refetch,
20
20
  }: ContentOverviewProps) {
21
- const { client, store } = useUserSession();
22
- const [isLoadingText, setIsLoadingText] = useState(false);
23
- const [text, setText] = useState<string | undefined>(object.text);
24
- const [imageUrl, setImageUrl] = useState<string>();
25
- const [isPropertiesModalOpen, setPropertiesModalOpen] = useState(false);
26
21
  const toast = useToast();
22
+
23
+ const handleCopyContent = async (
24
+ content: string,
25
+ type: "text" | "properties",
26
+ ) => {
27
+ try {
28
+ await navigator.clipboard.writeText(content);
29
+ toast({
30
+ status: "success",
31
+ title: `${type === "text" ? "Content" : "Properties"} copied`,
32
+ description: `Successfully copied ${type} to clipboard`,
33
+ duration: 2000,
34
+ });
35
+ } catch (err) {
36
+ console.error(`Failed to copy ${type}:`, err);
37
+ toast({
38
+ status: "error",
39
+ title: "Copy failed",
40
+ description: `Failed to copy ${type} to clipboard`,
41
+ duration: 5000,
42
+ });
43
+ }
44
+ };
45
+
46
+ return (
47
+ <>
48
+ <ResizablePanelGroup direction="horizontal" className="h-[calc(100vh-200px)]">
49
+ <ResizablePanel className="min-w-[100px]">
50
+ <PropertiesPanel object={object} refetch={refetch ?? (() => Promise.resolve())} handleCopyContent={handleCopyContent} />
51
+ </ResizablePanel>
52
+ <ResizableHandle withHandle />
53
+
54
+ <ResizablePanel className="min-w-[100px]">
55
+ <DataPanel object={object} loadText={loadText ?? false} handleCopyContent={handleCopyContent} />
56
+ </ResizablePanel>
57
+ </ResizablePanelGroup>
58
+
59
+ </>
60
+ );
61
+ }
62
+
63
+ function PropertiesPanel({ object, refetch, handleCopyContent }: { object: ContentObject, refetch: () => Promise<unknown>, handleCopyContent: (content: string, type: "text" | "properties") => Promise<void> }) {
27
64
  const [viewCode, setViewCode] = useState(false);
28
- const VIEW_JSON = "JSON";
29
- const VIEW_TEXT = "Preview";
65
+ const [isPropertiesModalOpen, setPropertiesModalOpen] = useState(false);
30
66
 
31
67
  const handleOpenPropertiesModal = () => {
32
68
  setPropertiesModalOpen(true);
@@ -36,6 +72,98 @@ export function ContentOverview({
36
72
  setPropertiesModalOpen(false);
37
73
  };
38
74
 
75
+ return (
76
+ <>
77
+ <div className="flex justify-between items-center px-2">
78
+ <div className="flex items-center gap-1 bg-muted mb-2 p-1 rounded">
79
+ <Button
80
+ variant={`${viewCode ? "ghost" : "primary"}`}
81
+ size="sm"
82
+ alt="Preview properties"
83
+ onClick={() => setViewCode(!viewCode)}
84
+ >
85
+ Properties
86
+ </Button>
87
+ <Button
88
+ variant={`${viewCode ? "primary" : "ghost"}`}
89
+ size="sm"
90
+ alt="View in JSON format"
91
+ onClick={() => setViewCode(!viewCode)}
92
+ >
93
+ JSON
94
+ </Button>
95
+ </div>
96
+ <div className="flex items-center gap-2">
97
+ {object.properties && (
98
+ <Button
99
+ variant="ghost"
100
+ size="sm"
101
+ title="Copy properties"
102
+ onClick={() =>
103
+ handleCopyContent(
104
+ JSON.stringify(
105
+ object.properties,
106
+ null,
107
+ 2,
108
+ ),
109
+ "properties",
110
+ )
111
+ }
112
+ >
113
+ <Copy className="size-4" />
114
+ </Button>
115
+ )}
116
+ <Button
117
+ variant="ghost"
118
+ size="sm"
119
+ onClick={handleOpenPropertiesModal}
120
+ title="Edit properties"
121
+ className="flex items-center gap-2"
122
+ >
123
+ <SquarePen className="size-4" />
124
+ </Button>
125
+ </div>
126
+ </div>
127
+
128
+ {
129
+ object.properties ? (
130
+ <div className="h-[calc(100vh-220px)] overflow-auto px-2">
131
+ <JSONDisplay
132
+ value={object.properties}
133
+ viewCode={viewCode}
134
+
135
+ />
136
+ </div>
137
+ ) : (
138
+ <div className="h-[calc(100vh-220px)] overflow-auto px-2">
139
+ <div>No properties defined</div>
140
+ </div>
141
+ )
142
+ }
143
+ {/* Properties Editor Modal */}
144
+ <PropertiesEditorModal
145
+ isOpen={isPropertiesModalOpen}
146
+ onClose={handleClosePropertiesModal}
147
+ object={object}
148
+ refetch={refetch}
149
+ />
150
+ </>
151
+ );
152
+ }
153
+
154
+ function DataPanel({ object, loadText, handleCopyContent }: { object: ContentObject, loadText: boolean, handleCopyContent: (content: string, type: "text" | "properties") => Promise<void> }) {
155
+ const { store } = useUserSession();
156
+
157
+ const content = object.content;
158
+ const isImage =
159
+ content && content.type && content.type.startsWith("image/");
160
+
161
+ const [viewImage, setViewImage] = useState(isImage);
162
+
163
+
164
+ const [text, setText] = useState<string | undefined>(object.text);
165
+ const [isLoadingText, setIsLoadingText] = useState<boolean>(false);
166
+
39
167
  useEffect(() => {
40
168
  if (loadText && !text) {
41
169
  setIsLoadingText(true);
@@ -53,28 +181,68 @@ export function ContentOverview({
53
181
  }
54
182
  }, [loadText]);
55
183
 
56
- const handleCopyContent = async (
57
- content: string,
58
- type: "text" | "properties",
59
- ) => {
60
- try {
61
- await navigator.clipboard.writeText(content);
62
- toast({
63
- status: "success",
64
- title: `${type === "text" ? "Content" : "Properties"} copied`,
65
- description: `Successfully copied ${type} to clipboard`,
66
- duration: 2000,
67
- });
68
- } catch (err) {
69
- console.error(`Failed to copy ${type}:`, err);
70
- toast({
71
- status: "error",
72
- title: "Copy failed",
73
- description: `Failed to copy ${type} to clipboard`,
74
- duration: 5000,
75
- });
76
- }
77
- };
184
+ return (
185
+ <>
186
+ <div className="flex justify-between items-center px-2">
187
+ <div className="flex items-center gap-1 bg-muted mb-2 p-1 rounded">
188
+ {isImage &&
189
+ <Button
190
+ variant={`${viewImage ? "primary" : "ghost"}`}
191
+ size="sm"
192
+ alt="View Image"
193
+ onClick={() => setViewImage(true)}
194
+ >
195
+ Image
196
+ </Button>
197
+ }
198
+ <Button
199
+ variant={`${viewImage ? "ghost" : "primary"}`}
200
+ size="sm"
201
+ alt="View Text"
202
+ onClick={() => setViewImage(false)}
203
+ >
204
+ Text
205
+ </Button>
206
+
207
+ </div>
208
+ {!viewImage && <TextActions object={object} text={text} handleCopyContent={handleCopyContent} />}
209
+ </div>
210
+ {
211
+ viewImage ? (
212
+ <ImagePanel object={object} />
213
+ ) : (
214
+ isLoadingText ? (
215
+ <div className="flex justify-center items-center h-[calc(100vh-260px)]">
216
+ <Spinner size="lg" />
217
+ </div>
218
+ ) : (
219
+ <TextPanel object={object} text={text} />
220
+ )
221
+ )
222
+ }
223
+ </>
224
+ );
225
+ }
226
+
227
+ function TextActions({ object, text, handleCopyContent }: { object: ContentObject, handleCopyContent: (content: string, type: "text" | "properties") => Promise<void>, text: string | undefined }) {
228
+ const { client } = useUserSession();
229
+ const toast = useToast();
230
+
231
+ const content = object.content;
232
+
233
+ const isMarkdownOrText =
234
+ content &&
235
+ content.type &&
236
+ (content.type === "text/markdown" || content.type === "text/plain");
237
+
238
+ // Check for markdown indicators, ignoring any HTML comments
239
+ const seemsMarkdown =
240
+ text &&
241
+ // Look for markdown indicators
242
+ (text.includes("\n#") ||
243
+ text.includes("\n*") ||
244
+ text.includes("\n+") ||
245
+ text.includes("!["));
78
246
 
79
247
  const handleExportDocument = async (format: "docx" | "pdf") => {
80
248
  try {
@@ -145,17 +313,45 @@ export function ContentOverview({
145
313
 
146
314
  const handleExportDocx = () => handleExportDocument("docx");
147
315
  const handleExportPdf = () => handleExportDocument("pdf");
316
+ return (
317
+ <div className="h-[41px] text-lg font-semibold flex justify-between items-center px-2">
318
+ <div className="flex items-center gap-2">
319
+ {text && (
320
+ <Button variant="ghost" size="sm" title="Copy text" className="flex items-center gap-2" onClick={() => handleCopyContent(text, "text")}>
321
+ <Copy className="size-4" />
322
+ </Button>
323
+ )}
324
+ {(isMarkdownOrText || seemsMarkdown) && text && (
325
+ <>
326
+ <Button
327
+ variant="ghost"
328
+ size="sm"
329
+ onClick={handleExportDocx}
330
+ className="flex items-center gap-2"
331
+ >
332
+ <Download className="size-4" />
333
+ DOCX
334
+ </Button>
335
+ <Button
336
+ variant="ghost"
337
+ size="sm"
338
+ onClick={handleExportPdf}
339
+ className="flex items-center gap-2"
340
+ >
341
+ <Download className="size-4" />
342
+ PDF
343
+ </Button>
344
+ </>
345
+ )}
346
+ </div>
347
+ </div>
348
+ );
349
+ }
350
+
351
+ function TextPanel({ object, text }: { object: ContentObject, text: string | undefined }) {
352
+ const toast = useToast();
353
+ const { client } = useUserSession();
148
354
 
149
- const content = object.content;
150
- const isImage =
151
- content &&
152
- content.source &&
153
- content.type &&
154
- content.type.startsWith("image/");
155
- const isMarkdownOrText =
156
- content &&
157
- content.type &&
158
- (content.type === "text/markdown" || content.type === "text/plain");
159
355
  // Check for markdown indicators, ignoring any HTML comments
160
356
  const seemsMarkdown =
161
357
  text &&
@@ -165,237 +361,212 @@ export function ContentOverview({
165
361
  text.includes("\n+") ||
166
362
  text.includes("!["));
167
363
 
168
- useEffect(() => {
169
- if (isImage) {
170
- client.objects
171
- .getRendition(object.id, {
172
- format: ImageRenditionFormat.jpeg,
173
- generate_if_missing: false,
174
- sign_url: true,
175
- })
176
- .then((r) => {
177
- if (r.status === "found") {
178
- return r.renditions?.length ? r.renditions[0] : null;
179
- } else {
180
- return object;
181
- }
182
- })
183
- .catch(() => {
184
- return object;
185
- })
186
- .then(() => {
187
- client.files
188
- .getDownloadUrl(object.content.source!)
189
- .then((r) => {
190
- setImageUrl(r.url);
191
- });
364
+ const handleExportDocument = async (format: "docx" | "pdf") => {
365
+ try {
366
+ // Request document rendition from the server
367
+ const response = await client.objects.getRendition(object.id, {
368
+ format: format as any, // We're extending the format type
369
+ generate_if_missing: true,
370
+ sign_url: true,
371
+ });
372
+
373
+ if (response.status === "generating") {
374
+ toast({
375
+ status: "info",
376
+ title: "Generating document",
377
+ description: `Please wait while we prepare your ${format.toUpperCase()} file...`,
378
+ duration: 5000,
192
379
  });
380
+
381
+ // Poll for completion
382
+ setTimeout(() => handleExportDocument(format), 3000);
383
+ return;
384
+ }
385
+
386
+ if (response.status === "failed") {
387
+ throw new Error("Document generation failed");
388
+ }
389
+
390
+ // Download the generated file or open in new window
391
+ if (response.renditions && response.renditions.length > 0) {
392
+ const downloadUrl = response.renditions[0];
393
+
394
+ if (format === 'pdf') {
395
+ // Open PDF in new window
396
+ window.open(downloadUrl, '_blank');
397
+ toast({
398
+ status: "success",
399
+ title: "PDF opened",
400
+ description: "PDF document opened in a new window",
401
+ duration: 2000,
402
+ });
403
+ } else {
404
+ // Download DOCX file
405
+ const link = document.createElement("a");
406
+ link.href = downloadUrl;
407
+ link.download = `${object.name || "document"}.${format}`;
408
+ document.body.appendChild(link);
409
+ link.click();
410
+ document.body.removeChild(link);
411
+
412
+ toast({
413
+ status: "success",
414
+ title: "Document exported",
415
+ description: `Successfully exported to ${format.toUpperCase()} format`,
416
+ duration: 2000,
417
+ });
418
+ }
419
+ }
420
+ } catch (err) {
421
+ console.error(`Failed to export document as ${format}:`, err);
422
+ toast({
423
+ status: "error",
424
+ title: "Export failed",
425
+ description: `Failed to export document to ${format.toUpperCase()} format`,
426
+ duration: 5000,
427
+ });
193
428
  }
194
- }, []);
429
+ };
195
430
 
196
431
  return (
197
- <div className="w-full">
198
- <div className="flex flex-col lg:flex-row lg:gap-8">
199
- <div className="w-full lg:w-1/2">
200
- <div className="h-[41px] text-lg font-semibold mb-4 border-b flex justify-between items-center">
201
- <div className="flex items-center gap-1">
202
- Properties
203
- <Button
204
- variant="outline"
205
- size="sm"
206
- alt={`${viewCode ? "Preview" : "View in JSON format"}`}
207
- onClick={() => setViewCode(!viewCode)}
208
- >
209
- {viewCode ? VIEW_TEXT : VIEW_JSON}
210
- </Button>
211
- </div>
212
- <div className="flex items-center gap-2">
213
- {object.properties && (
214
- <Button
215
- variant="ghost"
216
- size="sm"
217
- title="Copy properties"
218
- onClick={() =>
219
- handleCopyContent(
220
- JSON.stringify(
221
- object.properties,
222
- null,
223
- 2,
224
- ),
225
- "properties",
226
- )
227
- }
228
- >
229
- <Copy className="size-4" />
230
- </Button>
231
- )}
232
- <Button
233
- variant="ghost"
234
- size="sm"
235
- onClick={handleOpenPropertiesModal}
236
- title="Edit properties"
237
- className="flex items-center gap-2"
238
- >
239
- <SquarePen className="size-4" />
240
- </Button>
241
- </div>
242
- </div>
243
- {object.properties ? (
244
- <JSONDisplay
245
- value={object.properties}
246
- viewCode={viewCode}
247
- />
248
- ) : (
249
- <div>No properties defined</div>
250
- )}
251
- {isImage && (
252
- <div className="my-4">
253
- <div className="h-[41px] text-lg font-semibold mb-4 border-b flex justify-between items-center">
254
- <span className="py-1">Image</span>
255
- </div>
256
- {imageUrl ? (
257
- <img
258
- src={imageUrl}
259
- alt={object.name}
260
- className="w-full object-contain"
261
- />
262
- ) : (
263
- <Spinner size="md" />
264
- )}
265
- </div>
266
- )}
267
- </div>
268
-
269
- <div className="w-full lg:w-1/2 mt-4 lg:mt-0">
270
- <div className="h-[41px] text-lg font-semibold mb-4 border-b flex justify-between items-center">
271
- <span className="py-1">Text</span>
272
- <div className="flex items-center gap-2">
273
- {text && (
274
- <Button
275
- variant="ghost"
276
- size="sm"
277
- title="Copy text"
278
- onClick={() =>
279
- handleCopyContent(text, "text")
280
- }
281
- className="flex items-center gap-2"
282
- >
283
- <Copy className="h-4 w-4" />
284
- </Button>
285
- )}
286
- {(isMarkdownOrText || seemsMarkdown) && text && (
287
- <>
288
- <Button
289
- variant="ghost"
290
- size="sm"
291
- onClick={handleExportDocx}
292
- className="flex items-center gap-2"
293
- >
294
- <Download className="h-4 w-4" />
295
- DOCX
296
- </Button>
297
- <Button
298
- variant="ghost"
299
- size="sm"
300
- onClick={handleExportPdf}
301
- className="flex items-center gap-2"
302
- >
303
- <Download className="h-4 w-4" />
304
- PDF
305
- </Button>
306
- </>
307
- )}
308
- </div>
309
- </div>
310
- {isLoadingText && <Spinner size="md" />}
311
- {text ? (
312
- <div className="border shadow-xs rounded-xs max-w-7xl">
313
- {seemsMarkdown ? (
314
- <div className="vprose prose-sm p-1">
315
- <MarkdownRenderer
316
- components={{
317
- a: ({ node, ...props }: { node?: any; href?: string; children?: React.ReactNode }) => {
318
- const href = props.href || "";
319
- if (href.includes("/store/objects/")) {
320
- return (
321
- <NavLink
322
- topLevelNav
323
- href={href}
324
- className="text-info"
325
- >
326
- {props.children}
327
- </NavLink>
328
- );
432
+ text ? (
433
+ <>
434
+ <div className="max-w-7xl px-2 h-[calc(100vh-210px)] overflow-auto">
435
+ {seemsMarkdown ? (
436
+ <div className="vprose prose-sm p-1">
437
+ <MarkdownRenderer
438
+ components={{
439
+ a: ({ node, ...props }: { node?: any; href?: string; children?: React.ReactNode }) => {
440
+ const href = props.href || "";
441
+ if (href.includes("/store/objects/")) {
442
+ return (
443
+ <NavLink
444
+ topLevelNav
445
+ href={href}
446
+ className="text-info"
447
+ >
448
+ {props.children}
449
+ </NavLink>
450
+ );
451
+ }
452
+ return <a {...props} data-debug="test" target="_blank" rel="noopener noreferrer" />;
453
+ },
454
+ p: ({ node, ...props }: { node?: any; children?: React.ReactNode }) => (
455
+ <p {...props} className={`my-0`} />
456
+ ),
457
+ pre: ({ node, ...props }: { node?: any; children?: React.ReactNode }) => (
458
+ <pre {...props} className={`my-2 p-2 rounded`} />
459
+ ),
460
+ code: ({
461
+ node,
462
+ className,
463
+ children,
464
+ ...props
465
+ }: {
466
+ node?: any;
467
+ className?: string;
468
+ children?: React.ReactNode;
469
+ }) => {
470
+ const match = /language-(\w+)/.exec(className || "");
471
+ const isInline = !match;
472
+ return (
473
+ <code
474
+ {...props}
475
+ className={
476
+ isInline
477
+ ? `px-1.5 py-0.5 rounded`
478
+ : "text-muted"
329
479
  }
330
- return <a {...props} data-debug="test" target="_blank" rel="noopener noreferrer" />;
331
- },
332
- p: ({ node, ...props }: { node?: any; children?: React.ReactNode }) => (
333
- <p {...props} className={`my-0`} />
334
- ),
335
- pre: ({ node, ...props }: { node?: any; children?: React.ReactNode }) => (
336
- <pre {...props} className={`my-2 p-2 rounded`} />
337
- ),
338
- code: ({
339
- node,
340
- className,
341
- children,
342
- ...props
343
- }: {
344
- node?: any;
345
- className?: string;
346
- children?: React.ReactNode;
347
- }) => {
348
- const match = /language-(\w+)/.exec(className || "");
349
- const isInline = !match;
350
- return (
351
- <code
352
- {...props}
353
- className={
354
- isInline
355
- ? `px-1.5 py-0.5 rounded`
356
- : "text-muted"
357
- }
358
- >
359
- {children}
360
- </code>
361
- );
362
- },
363
- h1: ({ node, ...props }: { node?: any; children?: React.ReactNode }) => (
364
- <h1 {...props} className={`font-bold text-2xl my-2`} />
365
- ),
366
- h2: ({ node, ...props }: { node?: any; children?: React.ReactNode }) => (
367
- <h2 {...props} className={`font-bold text-xl my-2`} />
368
- ),
369
- h3: ({ node, ...props }: { node?: any; children?: React.ReactNode }) => (
370
- <h3 {...props} className={`font-bold text-lg my-2`} />
371
- ),
372
- li: ({ node, ...props }: { node?: any; children?: React.ReactNode }) => (
373
- <li {...props} />
374
- ),
375
- }}
376
- >
377
- {text}
378
- </MarkdownRenderer>
379
- </div>
380
- ) : (
381
- <pre className="text-wrap bg-muted text-muted p-2">
382
- {text}
383
- </pre>
384
- )}
480
+ >
481
+ {children}
482
+ </code>
483
+ );
484
+ },
485
+ h1: ({ node, ...props }: { node?: any; children?: React.ReactNode }) => (
486
+ <h1 {...props} className={`font-bold text-2xl my-2`} />
487
+ ),
488
+ h2: ({ node, ...props }: { node?: any; children?: React.ReactNode }) => (
489
+ <h2 {...props} className={`font-bold text-xl my-2`} />
490
+ ),
491
+ h3: ({ node, ...props }: { node?: any; children?: React.ReactNode }) => (
492
+ <h3 {...props} className={`font-bold text-lg my-2`} />
493
+ ),
494
+ li: ({ node, ...props }: { node?: any; children?: React.ReactNode }) => (
495
+ <li {...props} />
496
+ ),
497
+ }}
498
+ >
499
+ {text}
500
+ </MarkdownRenderer>
385
501
  </div>
386
502
  ) : (
387
- <div>No content</div>
503
+ <pre className="text-wrap bg-muted text-muted p-2">
504
+ {text}
505
+ </pre>
388
506
  )}
389
507
  </div>
508
+ </>
509
+ ) :
510
+ <div className="px-2">
511
+ <div>No content</div>
390
512
  </div>
513
+ );
514
+ }
391
515
 
392
- {/* Properties Editor Modal */}
393
- <PropertiesEditorModal
394
- isOpen={isPropertiesModalOpen}
395
- onClose={handleClosePropertiesModal}
396
- object={object}
397
- refetch={refetch}
398
- />
516
+ function ImagePanel({ object }: { object: ContentObject }) {
517
+ const { client } = useUserSession();
518
+ const [imageUrl, setImageUrl] = useState<string>();
519
+
520
+
521
+ const content = object.content;
522
+ const isImage =
523
+ content && content.type && content.type.startsWith("image/");
524
+
525
+ useEffect(() => {
526
+ if (isImage) {
527
+ const loadImage = async () => {
528
+ const webSupportedFormats = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml'];
529
+ const isOriginalWebSupported = content?.type && webSupportedFormats.includes(content.type);
530
+
531
+ try {
532
+ const rendition = await client.objects.getRendition(object.id, {
533
+ format: ImageRenditionFormat.jpeg,
534
+ generate_if_missing: false,
535
+ sign_url: true,
536
+ });
537
+
538
+ if (rendition.status === "found" && rendition.renditions?.length) {
539
+ // Use rendition URL directly
540
+ setImageUrl(rendition.renditions[0]);
541
+ } else if (isOriginalWebSupported) {
542
+ // Fall back to original file only if web-supported
543
+ const downloadUrl = await client.files.getDownloadUrl(object.content.source!);
544
+ setImageUrl(downloadUrl.url);
545
+ }
546
+ } catch (error) {
547
+ // Fall back to original file only if web-supported
548
+ if (isOriginalWebSupported) {
549
+ const downloadUrl = await client.files.getDownloadUrl(object.content.source!);
550
+ setImageUrl(downloadUrl.url);
551
+ }
552
+ }
553
+ };
554
+
555
+ loadImage();
556
+ }
557
+ }, []);
558
+
559
+ return (
560
+ <div className="mb-4 px-2">
561
+ {imageUrl ? (
562
+ <img
563
+ src={imageUrl}
564
+ alt={object.name}
565
+ className="w-full object-contain"
566
+ />
567
+ ) : (
568
+ <Spinner size="md" />
569
+ )}
399
570
  </div>
400
571
  );
401
- }
572
+ }