@powerhousedao/codegen 4.1.0-dev.19 → 4.1.0-dev.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/codegen/.hygen/templates/powerhouse/generate-document-model-module/customTest.esm.t +2 -2
- package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/components/CreateDocument.esm.t +10 -1
- package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/components/DriveExplorer.esm.t +292 -131
- package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/components/EditorContainer.esm.t +20 -3
- package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/components/FolderTree.esm.t +43 -25
- package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/editor.esm.t +37 -42
- package/dist/tsconfig.hygen.tsbuildinfo +1 -1
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +5 -5
- package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/components/FileItemsGrid.esm.t +0 -44
- package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/components/FolderItemsGrid.esm.t +0 -96
- package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/hooks/useSelectedFolderChildren.esm.t +0 -35
- package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/hooks/useTransformedNodes.esm.t +0 -35
package/dist/src/codegen/.hygen/templates/powerhouse/generate-document-model-module/customTest.esm.t
CHANGED
|
@@ -39,10 +39,10 @@ describe('<%= h.changeCase.pascal(module) %> Operations', () => {
|
|
|
39
39
|
);
|
|
40
40
|
|
|
41
41
|
expect(updatedDocument.operations.<%= action.scope %>).toHaveLength(1);
|
|
42
|
-
expect(updatedDocument.operations.<%= action.scope %>[0].type).toBe(
|
|
42
|
+
expect(updatedDocument.operations.<%= action.scope %>[0].action.type).toBe(
|
|
43
43
|
'<%= h.changeCase.constant(action.name) %>',
|
|
44
44
|
);
|
|
45
|
-
expect(updatedDocument.operations.<%= action.scope %>[0].input).toStrictEqual(input);
|
|
45
|
+
expect(updatedDocument.operations.<%= action.scope %>[0].action.input).toStrictEqual(input);
|
|
46
46
|
expect(updatedDocument.operations.<%= action.scope %>[0].index).toEqual(0);
|
|
47
47
|
});
|
|
48
48
|
<% }); _%>
|
|
@@ -10,6 +10,7 @@ interface CreateDocumentProps {
|
|
|
10
10
|
createDocument: (doc: DocumentModelModule) => void;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
+
// Helper function to extract document specification from different module formats
|
|
13
14
|
function getDocumentSpec(doc: DocumentModelModule) {
|
|
14
15
|
if ("documentModelState" in doc) {
|
|
15
16
|
return doc.documentModelState as DocumentModelModule["documentModel"];
|
|
@@ -18,28 +19,36 @@ function getDocumentSpec(doc: DocumentModelModule) {
|
|
|
18
19
|
return doc.documentModel;
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Document creation UI component.
|
|
24
|
+
* Displays available document types as clickable buttons.
|
|
25
|
+
* Customize available document types by filtering documentModels prop.
|
|
26
|
+
*/
|
|
21
27
|
export const CreateDocument: React.FC<CreateDocumentProps> = ({
|
|
22
28
|
documentModels,
|
|
23
29
|
createDocument,
|
|
24
30
|
}) => {
|
|
25
31
|
return (
|
|
26
32
|
<div className="px-6">
|
|
33
|
+
{/* Customize section title here */}
|
|
27
34
|
<h3 className="mb-3 mt-4 text-sm font-bold text-gray-600">
|
|
28
35
|
New document
|
|
29
36
|
</h3>
|
|
37
|
+
{/* Customize layout by changing flex-wrap, gap, or grid layout */}
|
|
30
38
|
<div className="flex w-full flex-wrap gap-4">
|
|
31
39
|
{documentModels?.map((doc) => {
|
|
32
40
|
const spec = getDocumentSpec(doc);
|
|
33
41
|
return (
|
|
34
42
|
<Button
|
|
35
43
|
key={spec.id}
|
|
36
|
-
color="light"
|
|
44
|
+
color="light" // Customize button appearance
|
|
37
45
|
size="small"
|
|
38
46
|
className="cursor-pointer"
|
|
39
47
|
title={spec.name}
|
|
40
48
|
aria-description={spec.description}
|
|
41
49
|
onClick={() => createDocument(doc)}
|
|
42
50
|
>
|
|
51
|
+
{/* Customize document type display format */}
|
|
43
52
|
<span className="text-sm">{spec.name}</span>
|
|
44
53
|
</Button>
|
|
45
54
|
);
|
|
@@ -2,88 +2,179 @@
|
|
|
2
2
|
to: "<%= rootDir %>/<%= h.changeCase.param(name) %>/components/DriveExplorer.tsx"
|
|
3
3
|
unless_exists: true
|
|
4
4
|
---
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
type
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
import {
|
|
6
|
+
FolderItem,
|
|
7
|
+
FileItem,
|
|
8
|
+
type SharingType,
|
|
9
|
+
Breadcrumbs,
|
|
10
|
+
useBreadcrumbs,
|
|
11
|
+
useDrop,
|
|
12
12
|
} from "@powerhousedao/design-system";
|
|
13
|
-
import { useCallback, useState, useRef,
|
|
14
|
-
import type { FileNode,
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
import { useCallback, useState, useRef, useMemo } from "react";
|
|
14
|
+
import type { FileNode, FolderNode, Node, GetDocumentOptions } from "document-drive";
|
|
15
|
+
import {
|
|
16
|
+
useNodes,
|
|
17
|
+
useSelectedDrive,
|
|
18
|
+
useSelectedFolder,
|
|
19
|
+
useFolderChildNodesForId,
|
|
20
|
+
useFileChildNodesForId,
|
|
21
|
+
useSetSelectedNode,
|
|
22
|
+
getDriveSharingType,
|
|
23
|
+
makeFolderNodeFromDrive,
|
|
24
|
+
} from "@powerhousedao/state";
|
|
20
25
|
import { EditorContainer } from "./EditorContainer.js";
|
|
26
|
+
import { FolderTree } from "./FolderTree.js";
|
|
21
27
|
import type { DocumentModelModule } from "document-model";
|
|
22
28
|
import { CreateDocumentModal } from "@powerhousedao/design-system";
|
|
23
29
|
import { CreateDocument } from "./CreateDocument.js";
|
|
24
|
-
import {
|
|
30
|
+
import {
|
|
31
|
+
type DriveEditorContext,
|
|
32
|
+
useDriveContext,
|
|
33
|
+
} from "@powerhousedao/reactor-browser";
|
|
25
34
|
|
|
26
35
|
interface DriveExplorerProps {
|
|
27
36
|
driveId: string;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
37
|
+
onAddFolder: (name: string, parent: Node | undefined) => Promise<FolderNode | undefined>;
|
|
38
|
+
onRenameNode: (newName: string, node: Node) => Promise<Node | undefined>;
|
|
39
|
+
onCopyNode: (src: Node, target: Node | undefined) => Promise<void>;
|
|
40
|
+
onOpenDocument: (node: FileNode) => void;
|
|
41
|
+
showDeleteNodeModal: (node: Node) => void;
|
|
33
42
|
context: DriveEditorContext;
|
|
34
43
|
}
|
|
35
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Main drive explorer component with sidebar navigation and content area.
|
|
47
|
+
* Layout: Left sidebar (folder tree) + Right content area (files/folders + document editor)
|
|
48
|
+
*/
|
|
36
49
|
export function DriveExplorer({
|
|
37
50
|
driveId,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
renameNode,
|
|
41
|
-
onAddFolder,
|
|
51
|
+
onRenameNode,
|
|
52
|
+
onAddFolder: onAddFolderProp,
|
|
42
53
|
onCopyNode,
|
|
54
|
+
onOpenDocument,
|
|
55
|
+
showDeleteNodeModal,
|
|
43
56
|
context,
|
|
44
57
|
}: DriveExplorerProps) {
|
|
45
|
-
const { getDocumentRevision } = context;
|
|
46
58
|
|
|
47
|
-
|
|
59
|
+
// === DOCUMENT EDITOR STATE ===
|
|
60
|
+
// Customize document opening/closing behavior here
|
|
48
61
|
const [activeDocumentId, setActiveDocumentId] = useState<
|
|
49
62
|
string | undefined
|
|
50
63
|
>();
|
|
51
64
|
const [openModal, setOpenModal] = useState(false);
|
|
52
65
|
const selectedDocumentModel = useRef<DocumentModelModule | null>(null);
|
|
53
|
-
|
|
66
|
+
|
|
67
|
+
// === DRIVE CONTEXT HOOKS ===
|
|
68
|
+
// Core drive operations and document models
|
|
69
|
+
const {
|
|
70
|
+
addDocument,
|
|
71
|
+
documentModels, // Modify this to filter available document types
|
|
72
|
+
getSyncStatusSync,
|
|
73
|
+
isAllowedToCreateDocuments,
|
|
74
|
+
onAddFile,
|
|
75
|
+
onAddFolder,
|
|
76
|
+
onAddAndSelectNewFolder,
|
|
77
|
+
onDuplicateNode,
|
|
78
|
+
onMoveNode,
|
|
79
|
+
} = useDriveContext();
|
|
80
|
+
|
|
81
|
+
// === STATE MANAGEMENT HOOKS ===
|
|
82
|
+
// Core state hooks for drive navigation
|
|
83
|
+
const nodes = useNodes(); // All nodes in the drive
|
|
84
|
+
const selectedDrive = useSelectedDrive(); // Current drive
|
|
85
|
+
const selectedFolder = useSelectedFolder(); // Currently selected folder
|
|
86
|
+
const setSelectedNode = useSetSelectedNode(); // Function to change selection
|
|
87
|
+
|
|
88
|
+
// === BREADCRUMB PATH CALCULATION ===
|
|
89
|
+
// Local fix for correct breadcrumb ordering (replaces useSelectedNodePath)
|
|
90
|
+
// TODO: Remove this when state package breadcrumb ordering is fixed
|
|
91
|
+
const selectedNodePath = useMemo(() => {
|
|
92
|
+
if (!nodes || !selectedDrive || !selectedFolder) return [];
|
|
93
|
+
const driveFolderNode = makeFolderNodeFromDrive(selectedDrive);
|
|
94
|
+
|
|
95
|
+
const path: Node[] = [];
|
|
96
|
+
let current = selectedFolder;
|
|
97
|
+
|
|
98
|
+
// Build path from current folder up to root
|
|
99
|
+
while (current) {
|
|
100
|
+
path.unshift(current);
|
|
101
|
+
if (!current.parentFolder) break;
|
|
102
|
+
current = nodes.find((n) => n.id === current.parentFolder) as FolderNode;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Add drive at the beginning
|
|
106
|
+
if (driveFolderNode) {
|
|
107
|
+
path.unshift(driveFolderNode);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return path;
|
|
111
|
+
}, [nodes, selectedDrive, selectedFolder]);
|
|
112
|
+
|
|
113
|
+
// === COMPUTED VALUES ===
|
|
114
|
+
const selectedNodeId = selectedFolder?.id;
|
|
115
|
+
const sharingType: SharingType = getDriveSharingType(selectedDrive) as SharingType;
|
|
116
|
+
const selectedDriveAsFolderNode = makeFolderNodeFromDrive(selectedDrive);
|
|
54
117
|
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
118
|
+
// === NAVIGATION SETUP ===
|
|
119
|
+
// Breadcrumbs for folder navigation
|
|
120
|
+
const { breadcrumbs, onBreadcrumbSelected } = useBreadcrumbs({
|
|
121
|
+
selectedNodePath,
|
|
122
|
+
setSelectedNode,
|
|
123
|
+
});
|
|
59
124
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
125
|
+
// Drag & drop functionality for file uploads
|
|
126
|
+
const { isDropTarget, dropProps } = useDrop({
|
|
127
|
+
node: selectedDriveAsFolderNode,
|
|
128
|
+
onAddFile,
|
|
129
|
+
onCopyNode,
|
|
130
|
+
onMoveNode,
|
|
131
|
+
});
|
|
66
132
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
133
|
+
// === FOLDER/FILE CHILDREN ===
|
|
134
|
+
// Get current folder's contents
|
|
135
|
+
const nodeIdForChildren = selectedNodeId === undefined ? null : selectedNodeId;
|
|
136
|
+
const folderChildren = useFolderChildNodesForId(nodeIdForChildren);
|
|
137
|
+
const fileChildren = useFileChildNodesForId(nodeIdForChildren);
|
|
138
|
+
|
|
139
|
+
// All folders for the sidebar tree view
|
|
140
|
+
const allFolders = nodes?.filter(n => n.kind === "folder") as FolderNode[] || [];
|
|
73
141
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
142
|
+
// === EVENT HANDLERS ===
|
|
143
|
+
// Customize document selection behavior here
|
|
144
|
+
const handleFileSelect = useCallback((nodeId: string | undefined) => {
|
|
145
|
+
if (!nodeId) return;
|
|
146
|
+
const fileNode = nodes?.find(n => n.id === nodeId) as FileNode;
|
|
147
|
+
if (fileNode) {
|
|
148
|
+
onOpenDocument(fileNode);
|
|
149
|
+
}
|
|
150
|
+
setActiveDocumentId(nodeId);
|
|
151
|
+
}, [nodes, onOpenDocument]);
|
|
78
152
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
153
|
+
// Handle folder creation with optional name parameter
|
|
154
|
+
const handleCreateFolder = useCallback(async (folderName?: string) => {
|
|
155
|
+
let name: string | undefined = folderName;
|
|
156
|
+
|
|
157
|
+
// If no name provided, prompt for it (for manual folder creation)
|
|
158
|
+
if (!name) {
|
|
159
|
+
const promptResult = prompt("Enter folder name:");
|
|
160
|
+
name = promptResult || undefined;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (name && name.trim()) {
|
|
164
|
+
try {
|
|
165
|
+
await onAddFolder(name.trim(), selectedFolder);
|
|
166
|
+
} catch (error) {
|
|
167
|
+
console.error("Failed to create folder:", error);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}, [onAddFolder, selectedFolder]);
|
|
82
171
|
|
|
172
|
+
// Close document editor and return to folder view
|
|
83
173
|
const handleEditorClose = useCallback(() => {
|
|
84
174
|
setActiveDocumentId(undefined);
|
|
85
175
|
}, []);
|
|
86
176
|
|
|
177
|
+
// Handle document creation from modal
|
|
87
178
|
const onCreateDocument = useCallback(
|
|
88
179
|
async (fileName: string) => {
|
|
89
180
|
setOpenModal(false);
|
|
@@ -91,58 +182,52 @@ export function DriveExplorer({
|
|
|
91
182
|
const documentModel = selectedDocumentModel.current;
|
|
92
183
|
if (!documentModel) return;
|
|
93
184
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
185
|
+
try {
|
|
186
|
+
const node = await addDocument(
|
|
187
|
+
driveId,
|
|
188
|
+
fileName,
|
|
189
|
+
documentModel.documentModel.id,
|
|
190
|
+
selectedNodeId,
|
|
191
|
+
);
|
|
100
192
|
|
|
101
|
-
|
|
102
|
-
|
|
193
|
+
selectedDocumentModel.current = null;
|
|
194
|
+
|
|
195
|
+
if (node) {
|
|
196
|
+
// Customize: Auto-open created document by uncommenting below
|
|
197
|
+
// setActiveDocumentId(node.id);
|
|
198
|
+
}
|
|
199
|
+
} catch (error) {
|
|
200
|
+
console.error("Failed to create document:", error);
|
|
201
|
+
}
|
|
103
202
|
},
|
|
104
|
-
[addDocument, driveId, selectedNodeId]
|
|
203
|
+
[addDocument, driveId, selectedNodeId],
|
|
105
204
|
);
|
|
106
205
|
|
|
206
|
+
// Handle document type selection for creation
|
|
107
207
|
const onSelectDocumentModel = useCallback(
|
|
108
208
|
(documentModel: DocumentModelModule) => {
|
|
109
209
|
selectedDocumentModel.current = documentModel;
|
|
110
210
|
setOpenModal(true);
|
|
111
211
|
},
|
|
112
|
-
[]
|
|
212
|
+
[],
|
|
113
213
|
);
|
|
114
214
|
|
|
215
|
+
// Document revision handling (placeholder implementation)
|
|
115
216
|
const onGetDocumentRevision = useCallback(
|
|
116
|
-
(
|
|
217
|
+
(_options?: GetDocumentOptions) => {
|
|
117
218
|
if (!activeDocumentId) return;
|
|
118
|
-
return
|
|
219
|
+
return undefined;
|
|
119
220
|
},
|
|
120
|
-
[
|
|
221
|
+
[activeDocumentId],
|
|
121
222
|
);
|
|
122
223
|
|
|
224
|
+
// === DOCUMENT EDITOR DATA ===
|
|
225
|
+
// Filter available document types here if needed
|
|
123
226
|
const filteredDocumentModels = documentModels;
|
|
124
227
|
|
|
125
|
-
//
|
|
126
|
-
const transformedNodes = useTransformedNodes(nodes, driveId);
|
|
127
|
-
|
|
128
|
-
// Separate folders and files
|
|
129
|
-
const folders = transformedNodes.filter(
|
|
130
|
-
(node): node is UiFolderNode => node.kind === "FOLDER"
|
|
131
|
-
);
|
|
132
|
-
const files = transformedNodes.filter(
|
|
133
|
-
(node): node is UiFileNode => node.kind === "FILE"
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
// Get children of selected folder using the custom hook
|
|
137
|
-
const selectedFolderChildren = useSelectedFolderChildren(
|
|
138
|
-
selectedNodeId,
|
|
139
|
-
folders,
|
|
140
|
-
files
|
|
141
|
-
);
|
|
142
|
-
|
|
143
|
-
// Get the active document info from nodes
|
|
228
|
+
// Get active document and its editor components
|
|
144
229
|
const activeDocument = activeDocumentId
|
|
145
|
-
?
|
|
230
|
+
? fileChildren.find((file) => file.id === activeDocumentId)
|
|
146
231
|
: undefined;
|
|
147
232
|
|
|
148
233
|
const documentModelModule = activeDocument
|
|
@@ -153,21 +238,33 @@ export function DriveExplorer({
|
|
|
153
238
|
? context.getEditor(activeDocument.documentType)
|
|
154
239
|
: null;
|
|
155
240
|
|
|
241
|
+
// === RENDER ===
|
|
156
242
|
return (
|
|
157
243
|
<div className="flex h-full">
|
|
158
|
-
{/*
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
<
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
244
|
+
{/* === LEFT SIDEBAR: Folder Navigation === */}
|
|
245
|
+
{/* Customize sidebar width by changing w-64 */}
|
|
246
|
+
<div className="w-64 border-r border-gray-200 bg-white overflow-y-auto">
|
|
247
|
+
<div className="p-4">
|
|
248
|
+
{/* Customize sidebar title here */}
|
|
249
|
+
<h2 className="text-lg font-semibold mb-4 text-gray-700">Drive Explorer</h2>
|
|
250
|
+
|
|
251
|
+
{/* Folder tree navigation component */}
|
|
252
|
+
<FolderTree
|
|
253
|
+
folders={allFolders}
|
|
254
|
+
selectedNodeId={selectedNodeId}
|
|
255
|
+
onSelectNode={setSelectedNode}
|
|
256
|
+
/>
|
|
257
|
+
</div>
|
|
166
258
|
</div>
|
|
167
259
|
|
|
168
|
-
{/*
|
|
169
|
-
<div
|
|
260
|
+
{/* === RIGHT CONTENT AREA: Files/Folders or Document Editor === */}
|
|
261
|
+
<div
|
|
262
|
+
className={`flex-1 p-4 overflow-y-auto ${isDropTarget ? "bg-blue-50 border-2 border-dashed border-blue-300" : ""}`}
|
|
263
|
+
{...dropProps}
|
|
264
|
+
>
|
|
265
|
+
{/* Conditional rendering: Document editor or folder contents */}
|
|
170
266
|
{activeDocument && documentModelModule && editorModule ? (
|
|
267
|
+
// Document editor view
|
|
171
268
|
<EditorContainer
|
|
172
269
|
context={{
|
|
173
270
|
...context,
|
|
@@ -182,52 +279,116 @@ export function DriveExplorer({
|
|
|
182
279
|
editorModule={editorModule}
|
|
183
280
|
/>
|
|
184
281
|
) : (
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
282
|
+
/* Folder contents view */
|
|
283
|
+
<div className="space-y-6">
|
|
284
|
+
{/* === HEADER SECTION === */}
|
|
285
|
+
<div className="space-y-3">
|
|
286
|
+
<div className="flex items-center justify-between">
|
|
287
|
+
{/* Folder title */}
|
|
288
|
+
<h2 className="text-lg font-semibold">
|
|
289
|
+
{selectedFolder ? `Contents of "${selectedFolder.name}"` : "Root Contents"}
|
|
290
|
+
</h2>
|
|
291
|
+
{/* Customize: Add more action buttons here */}
|
|
292
|
+
{isAllowedToCreateDocuments && (
|
|
293
|
+
<button
|
|
294
|
+
onClick={() => handleCreateFolder()}
|
|
295
|
+
className="px-3 py-1 bg-blue-500 text-white rounded text-sm hover:bg-blue-600"
|
|
296
|
+
>
|
|
297
|
+
+ New Folder
|
|
298
|
+
</button>
|
|
299
|
+
)}
|
|
300
|
+
</div>
|
|
301
|
+
|
|
302
|
+
{/* Navigation breadcrumbs */}
|
|
303
|
+
{breadcrumbs.length > 1 && (
|
|
304
|
+
<div className="border-b border-gray-200 pb-3">
|
|
305
|
+
<Breadcrumbs
|
|
306
|
+
breadcrumbs={breadcrumbs}
|
|
307
|
+
createEnabled={isAllowedToCreateDocuments}
|
|
308
|
+
onCreate={handleCreateFolder}
|
|
309
|
+
onBreadcrumbSelected={onBreadcrumbSelected}
|
|
310
|
+
/>
|
|
311
|
+
</div>
|
|
312
|
+
)}
|
|
313
|
+
</div>
|
|
210
314
|
|
|
211
|
-
{/*
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
315
|
+
{/* === FOLDERS SECTION === */}
|
|
316
|
+
{/* Customize grid layout by changing grid-cols-1 */}
|
|
317
|
+
{folderChildren.length > 0 && (
|
|
318
|
+
<div>
|
|
319
|
+
<h3 className="text-sm font-medium text-gray-500 mb-2">📁 Folders</h3>
|
|
320
|
+
<div className="grid grid-cols-1 gap-2">
|
|
321
|
+
{folderChildren.map((folderNode) => (
|
|
322
|
+
<FolderItem
|
|
323
|
+
key={folderNode.id}
|
|
324
|
+
folderNode={folderNode}
|
|
325
|
+
isAllowedToCreateDocuments={isAllowedToCreateDocuments}
|
|
326
|
+
sharingType={sharingType}
|
|
327
|
+
getSyncStatusSync={getSyncStatusSync}
|
|
328
|
+
setSelectedNode={setSelectedNode}
|
|
329
|
+
onAddFile={onAddFile}
|
|
330
|
+
onCopyNode={onCopyNode}
|
|
331
|
+
onMoveNode={onMoveNode}
|
|
332
|
+
onRenameNode={onRenameNode}
|
|
333
|
+
onDuplicateNode={onDuplicateNode}
|
|
334
|
+
onAddFolder={onAddFolderProp}
|
|
335
|
+
onAddAndSelectNewFolder={onAddAndSelectNewFolder}
|
|
336
|
+
showDeleteNodeModal={showDeleteNodeModal}
|
|
337
|
+
/>
|
|
338
|
+
))}
|
|
339
|
+
</div>
|
|
340
|
+
</div>
|
|
341
|
+
)}
|
|
342
|
+
|
|
343
|
+
{/* === FILES/DOCUMENTS SECTION === */}
|
|
344
|
+
{/* Customize grid layout by changing grid-cols-1 */}
|
|
345
|
+
{fileChildren.length > 0 && (
|
|
346
|
+
<div>
|
|
347
|
+
<h3 className="text-sm font-medium text-gray-500 mb-2">📄 Documents</h3>
|
|
348
|
+
<div className="grid grid-cols-1 gap-2">
|
|
349
|
+
{fileChildren.map((fileNode) => (
|
|
350
|
+
<FileItem
|
|
351
|
+
key={fileNode.id}
|
|
352
|
+
fileNode={fileNode}
|
|
353
|
+
isAllowedToCreateDocuments={isAllowedToCreateDocuments}
|
|
354
|
+
sharingType={sharingType}
|
|
355
|
+
getSyncStatusSync={getSyncStatusSync}
|
|
356
|
+
setSelectedNode={(nodeId) => handleFileSelect(nodeId)}
|
|
357
|
+
showDeleteNodeModal={showDeleteNodeModal}
|
|
358
|
+
onRenameNode={onRenameNode}
|
|
359
|
+
onDuplicateNode={onDuplicateNode}
|
|
360
|
+
onAddFile={onAddFile}
|
|
361
|
+
onCopyNode={onCopyNode}
|
|
362
|
+
onMoveNode={onMoveNode}
|
|
363
|
+
onAddFolder={onAddFolderProp}
|
|
364
|
+
onAddAndSelectNewFolder={onAddAndSelectNewFolder}
|
|
365
|
+
/>
|
|
366
|
+
))}
|
|
367
|
+
</div>
|
|
368
|
+
</div>
|
|
369
|
+
)}
|
|
370
|
+
|
|
371
|
+
{/* === EMPTY STATE === */}
|
|
372
|
+
{/* Customize empty state message and styling here */}
|
|
373
|
+
{folderChildren.length === 0 && fileChildren.length === 0 && (
|
|
374
|
+
<div className="text-center py-12 text-gray-500">
|
|
375
|
+
<p className="text-lg">📁 This folder is empty</p>
|
|
376
|
+
<p className="text-sm mt-2">Create your first document or folder below</p>
|
|
377
|
+
</div>
|
|
378
|
+
)}
|
|
220
379
|
|
|
221
|
-
{/*
|
|
380
|
+
{/* === DOCUMENT CREATION SECTION === */}
|
|
381
|
+
{/* Component for creating new documents */}
|
|
222
382
|
<CreateDocument
|
|
223
383
|
createDocument={onSelectDocumentModel}
|
|
224
384
|
documentModels={filteredDocumentModels}
|
|
225
385
|
/>
|
|
226
|
-
|
|
386
|
+
</div>
|
|
227
387
|
)}
|
|
228
388
|
</div>
|
|
229
389
|
|
|
230
|
-
{/*
|
|
390
|
+
{/* === DOCUMENT CREATION MODAL === */}
|
|
391
|
+
{/* Modal for entering document name after selecting type */}
|
|
231
392
|
<CreateDocumentModal
|
|
232
393
|
onContinue={onCreateDocument}
|
|
233
394
|
onOpenChange={(open) => setOpenModal(open)}
|
|
@@ -235,4 +396,4 @@ export function DriveExplorer({
|
|
|
235
396
|
/>
|
|
236
397
|
</div>
|
|
237
398
|
);
|
|
238
|
-
}
|
|
399
|
+
}
|
|
@@ -30,11 +30,17 @@ export interface EditorContainerProps {
|
|
|
30
30
|
documentType: string;
|
|
31
31
|
onClose: () => void;
|
|
32
32
|
title: string;
|
|
33
|
-
context: Omit<DriveEditorContext, "getDocumentRevision"> &
|
|
33
|
+
context: Omit<DriveEditorContext, "getDocumentRevision"> &
|
|
34
|
+
Pick<EditorContext, "getDocumentRevision">;
|
|
34
35
|
documentModelModule: DocumentModelModule<PHDocument>;
|
|
35
36
|
editorModule: EditorModule;
|
|
36
37
|
}
|
|
37
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Document editor container that wraps individual document editors.
|
|
41
|
+
* Handles document loading, toolbar, revision history, and dynamic editor loading.
|
|
42
|
+
* Customize toolbar actions and editor context here.
|
|
43
|
+
*/
|
|
38
44
|
export const EditorContainer: React.FC<EditorContainerProps> = (props) => {
|
|
39
45
|
const {
|
|
40
46
|
title,
|
|
@@ -47,12 +53,15 @@ export const EditorContainer: React.FC<EditorContainerProps> = (props) => {
|
|
|
47
53
|
documentModelModule,
|
|
48
54
|
} = props;
|
|
49
55
|
|
|
50
|
-
|
|
56
|
+
// UI state for revision history and timeline
|
|
57
|
+
const [selectedTimelineItem, setSelectedTimelineItem] =
|
|
58
|
+
useState<TimelineItem | null>(null);
|
|
51
59
|
const [showRevisionHistory, setShowRevisionHistory] = useState(false);
|
|
52
60
|
const { useDocumentEditorProps } = useDriveContext();
|
|
53
61
|
|
|
54
62
|
const user = context.user as User | undefined;
|
|
55
63
|
|
|
64
|
+
// Document data and editor state
|
|
56
65
|
const { dispatch, error, document } = useDocumentEditorProps({
|
|
57
66
|
documentId,
|
|
58
67
|
documentType,
|
|
@@ -61,12 +70,14 @@ export const EditorContainer: React.FC<EditorContainerProps> = (props) => {
|
|
|
61
70
|
user,
|
|
62
71
|
});
|
|
63
72
|
|
|
73
|
+
// Timeline data for revision history
|
|
64
74
|
const timelineItems = useTimelineItems(
|
|
65
75
|
documentId,
|
|
66
76
|
document?.header.createdAtUtcIso,
|
|
67
77
|
driveId,
|
|
68
78
|
);
|
|
69
79
|
|
|
80
|
+
// Document export functionality - customize export behavior here
|
|
70
81
|
const onExport = useCallback(async () => {
|
|
71
82
|
if (document) {
|
|
72
83
|
const ext = documentModelModule.documentModel.extension;
|
|
@@ -74,6 +85,7 @@ export const EditorContainer: React.FC<EditorContainerProps> = (props) => {
|
|
|
74
85
|
}
|
|
75
86
|
}, [document?.header.revision.global, document?.header.revision.local]);
|
|
76
87
|
|
|
88
|
+
// Loading state component
|
|
77
89
|
const loadingContent = (
|
|
78
90
|
<div className="flex-1 flex justify-center items-center h-full">
|
|
79
91
|
<DefaultEditorLoader />
|
|
@@ -82,9 +94,11 @@ export const EditorContainer: React.FC<EditorContainerProps> = (props) => {
|
|
|
82
94
|
|
|
83
95
|
if (!document) return loadingContent;
|
|
84
96
|
|
|
97
|
+
// Dynamically load the appropriate editor component for this document type
|
|
85
98
|
const EditorComponent = editorModule.Component as FC<EditorProps<PHDocument>>;
|
|
86
99
|
|
|
87
100
|
return showRevisionHistory ? (
|
|
101
|
+
// Revision history view
|
|
88
102
|
<RevisionHistory
|
|
89
103
|
documentId={documentId}
|
|
90
104
|
documentTitle={title}
|
|
@@ -94,17 +108,20 @@ export const EditorContainer: React.FC<EditorContainerProps> = (props) => {
|
|
|
94
108
|
onClose={() => setShowRevisionHistory(false)}
|
|
95
109
|
/>
|
|
96
110
|
) : (
|
|
111
|
+
// Main editor view
|
|
97
112
|
<Suspense fallback={loadingContent}>
|
|
113
|
+
{/* Document toolbar - customize available actions here */}
|
|
98
114
|
<DocumentToolbar
|
|
99
115
|
onClose={onClose}
|
|
100
116
|
onExport={onExport}
|
|
101
117
|
onShowRevisionHistory={() => setShowRevisionHistory(true)}
|
|
102
|
-
onSwitchboardLinkClick={() => {}}
|
|
118
|
+
onSwitchboardLinkClick={() => {}} // Customize switchboard integration
|
|
103
119
|
title={title}
|
|
104
120
|
timelineButtonVisible={editorModule.config.timelineEnabled}
|
|
105
121
|
timelineItems={timelineItems.data}
|
|
106
122
|
onTimelineItemClick={setSelectedTimelineItem}
|
|
107
123
|
/>
|
|
124
|
+
{/* Dynamic editor component based on document type */}
|
|
108
125
|
<EditorComponent
|
|
109
126
|
context={{
|
|
110
127
|
...context,
|