@powerhousedao/codegen 0.49.2-dev.0 → 0.49.2-dev.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 (22) hide show
  1. package/dist/src/codegen/.hygen/templates/powerhouse/generate-documents-subgraph/resolvers.esm.t +1 -1
  2. package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/components/CreateDocument.esm.t +49 -0
  3. package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/components/DriveExplorer.esm.t +214 -0
  4. package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/components/EditorContainer.esm.t +127 -0
  5. package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/components/FileItemsGrid.esm.t +44 -0
  6. package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/components/FolderItemsGrid.esm.t +96 -0
  7. package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/components/FolderTree.esm.t +85 -0
  8. package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/editor.esm.t +68 -0
  9. package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/hooks/useSelectedFolderChildren.esm.t +35 -0
  10. package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/hooks/useTransformedNodes.esm.t +35 -0
  11. package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/index.esm.t +20 -0
  12. package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/lib.esm.t +9 -0
  13. package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/lib.inject_export.esm.t +7 -0
  14. package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/types/css.d.esm.t +8 -0
  15. package/dist/src/codegen/hygen.d.ts +3 -0
  16. package/dist/src/codegen/hygen.d.ts.map +1 -1
  17. package/dist/src/codegen/hygen.js +4 -0
  18. package/dist/src/codegen/index.d.ts +1 -0
  19. package/dist/src/codegen/index.d.ts.map +1 -1
  20. package/dist/src/codegen/index.js +6 -1
  21. package/dist/tsconfig.tsbuildinfo +1 -1
  22. package/package.json +3 -2
@@ -29,7 +29,7 @@ export const getResolvers = (subgraph: Subgraph) => {
29
29
  id: docId,
30
30
  ...doc,
31
31
  state: doc.state.global,
32
- stateJson: JSON.stringify(doc.state.global),
32
+ stateJSON: doc.state.global,
33
33
  __typename: typeName,
34
34
  };
35
35
 
@@ -0,0 +1,49 @@
1
+ ---
2
+ to: "<%= rootDir %>/<%= h.changeCase.param(name) %>/components/CreateDocument.tsx"
3
+ unless_exists: true
4
+ ---
5
+ import { Button } from "@powerhousedao/design-system";
6
+ import { type DocumentModelModule } from "document-model";
7
+
8
+ interface CreateDocumentProps {
9
+ documentModels?: DocumentModelModule[];
10
+ createDocument: (doc: DocumentModelModule) => void;
11
+ }
12
+
13
+ function getDocumentSpec(doc: DocumentModelModule) {
14
+ if ("documentModelState" in doc) {
15
+ return doc.documentModelState as DocumentModelModule["documentModel"];
16
+ }
17
+
18
+ return doc.documentModel;
19
+ }
20
+
21
+ export const CreateDocument: React.FC<CreateDocumentProps> = ({
22
+ documentModels,
23
+ createDocument,
24
+ }) => {
25
+ return (
26
+ <div className="px-6">
27
+ <h3 className="mb-3 mt-4 text-sm font-bold text-gray-600">
28
+ New document
29
+ </h3>
30
+ <div className="flex w-full flex-wrap gap-4">
31
+ {documentModels?.map((doc) => {
32
+ const spec = getDocumentSpec(doc);
33
+ return (
34
+ <Button
35
+ key={spec.id}
36
+ color="light"
37
+ size="small"
38
+ className="cursor-pointer"
39
+ aria-details={spec.description}
40
+ onClick={() => createDocument(doc)}
41
+ >
42
+ <span className="text-sm">{spec.name}</span>
43
+ </Button>
44
+ );
45
+ })}
46
+ </div>
47
+ </div>
48
+ );
49
+ };
@@ -0,0 +1,214 @@
1
+ ---
2
+ to: "<%= rootDir %>/<%= h.changeCase.param(name) %>/components/DriveExplorer.tsx"
3
+ unless_exists: true
4
+ ---
5
+ import {
6
+ type BaseUiFileNode,
7
+ type BaseUiFolderNode,
8
+ type BaseUiNode,
9
+ type UiFileNode,
10
+ type UiFolderNode,
11
+ type UiNode,
12
+ } from "@powerhousedao/design-system";
13
+ import { useCallback, useState, useRef, useEffect } from "react";
14
+ import type { FileNode, Node } from "document-drive";
15
+ import { FileItemsGrid } from "./FileItemsGrid.js";
16
+ import { FolderItemsGrid } from "./FolderItemsGrid.js";
17
+ import { FolderTree } from "./FolderTree.js";
18
+ import { useTransformedNodes } from "../hooks/useTransformedNodes.js";
19
+ import { useSelectedFolderChildren } from "../hooks/useSelectedFolderChildren.js";
20
+ import { EditorContainer } from "./EditorContainer.js";
21
+ import type { EditorContext, DocumentModelModule } from "document-model";
22
+ import { CreateDocumentModal } from "@powerhousedao/design-system";
23
+ import { CreateDocument } from "./CreateDocument.js";
24
+ import { useDriveContext } from "@powerhousedao/reactor-browser";
25
+
26
+ interface DriveExplorerProps {
27
+ driveId: string;
28
+ nodes: Node[];
29
+ onAddFolder: (name: string, parentFolder?: string) => void;
30
+ onDeleteNode: (nodeId: string) => void;
31
+ renameNode: (nodeId: string, name: string) => void;
32
+ onCopyNode: (nodeId: string, targetName: string, parentId?: string) => void;
33
+ context: EditorContext;
34
+ }
35
+
36
+ export function DriveExplorer({
37
+ driveId,
38
+ nodes,
39
+ onDeleteNode,
40
+ renameNode,
41
+ onAddFolder,
42
+ onCopyNode,
43
+ context,
44
+ }: DriveExplorerProps) {
45
+ const [selectedNodeId, setSelectedNodeId] = useState<string | undefined>();
46
+ const [activeDocumentId, setActiveDocumentId] = useState<
47
+ string | undefined
48
+ >();
49
+ const [openModal, setOpenModal] = useState(false);
50
+ const selectedDocumentModel = useRef<DocumentModelModule | null>(null);
51
+ const { addDocument, documentModels } = useDriveContext();
52
+
53
+ // Dummy functions to satisfy component types
54
+ const dummyDuplicateNode = useCallback((node: BaseUiNode) => {
55
+ console.log("Duplicate node:", node);
56
+ }, []);
57
+
58
+ const dummyAddFile = useCallback(
59
+ async (file: File, parentNode: BaseUiNode | null) => {
60
+ console.log("Add file:", file, parentNode);
61
+ },
62
+ []
63
+ );
64
+
65
+ const dummyMoveNode = useCallback(
66
+ async (uiNode: BaseUiNode, targetNode: BaseUiNode) => {
67
+ console.log("Move node:", uiNode, targetNode);
68
+ },
69
+ []
70
+ );
71
+
72
+ const handleNodeSelect = useCallback((node: BaseUiFolderNode) => {
73
+ console.log("Selected node:", node);
74
+ setSelectedNodeId(node.id);
75
+ }, []);
76
+
77
+ const handleFileSelect = useCallback((node: BaseUiFileNode) => {
78
+ setActiveDocumentId(node.id);
79
+ }, []);
80
+
81
+ const handleEditorClose = useCallback(() => {
82
+ setActiveDocumentId(undefined);
83
+ }, []);
84
+
85
+ const onCreateDocument = useCallback(
86
+ async (fileName: string) => {
87
+ setOpenModal(false);
88
+
89
+ const documentModel = selectedDocumentModel.current;
90
+ if (!documentModel) return;
91
+
92
+ const node = await addDocument(
93
+ driveId,
94
+ fileName,
95
+ documentModel.documentModel.id
96
+ );
97
+
98
+ selectedDocumentModel.current = null;
99
+ setActiveDocumentId(node.id);
100
+ },
101
+ [addDocument, driveId]
102
+ );
103
+
104
+ const onSelectDocumentModel = useCallback(
105
+ (documentModel: DocumentModelModule) => {
106
+ selectedDocumentModel.current = documentModel;
107
+ setOpenModal(true);
108
+ },
109
+ []
110
+ );
111
+
112
+ const filteredDocumentModels = documentModels;
113
+
114
+ // Transform nodes using the custom hook
115
+ const transformedNodes = useTransformedNodes(nodes, driveId);
116
+
117
+ // Separate folders and files
118
+ const folders = transformedNodes.filter(
119
+ (node): node is UiFolderNode => node.kind === "FOLDER"
120
+ );
121
+ const files = transformedNodes.filter(
122
+ (node): node is UiFileNode => node.kind === "FILE"
123
+ );
124
+
125
+ // Get children of selected folder using the custom hook
126
+ const selectedFolderChildren = useSelectedFolderChildren(
127
+ selectedNodeId,
128
+ folders,
129
+ files
130
+ );
131
+
132
+ // Get the active document info from nodes
133
+ const activeDocument = activeDocumentId
134
+ ? files.find((file) => file.id === activeDocumentId)
135
+ : undefined;
136
+
137
+ return (
138
+ <div className="flex h-full">
139
+ {/* Sidebar */}
140
+ <div className="w-64 border-r border-gray-200 p-4 overflow-y-auto">
141
+ <h2 className="text-lg font-semibold mb-4">Folders</h2>
142
+ <FolderTree
143
+ folders={folders}
144
+ selectedNodeId={selectedNodeId}
145
+ onSelectNode={handleNodeSelect}
146
+ />
147
+ </div>
148
+
149
+ {/* Main Content */}
150
+ <div className="flex-1 p-4 overflow-y-auto">
151
+ {activeDocument ? (
152
+ <EditorContainer
153
+ context={context}
154
+ documentId={activeDocumentId!}
155
+ documentType={activeDocument.documentType}
156
+ driveId={driveId}
157
+ onClose={handleEditorClose}
158
+ title={activeDocument.name}
159
+ />
160
+ ) : (
161
+ <>
162
+ <h2 className="text-lg font-semibold mb-4">Contents</h2>
163
+
164
+ {/* Folders Section */}
165
+ <FolderItemsGrid
166
+ folders={selectedFolderChildren.folders}
167
+ onSelectNode={handleNodeSelect}
168
+ onRenameNode={renameNode}
169
+ onDuplicateNode={(uiNode) =>
170
+ onCopyNode(
171
+ uiNode.id,
172
+ "Copy of " + uiNode.name,
173
+ uiNode.parentFolder
174
+ )
175
+ }
176
+ onDeleteNode={onDeleteNode}
177
+ onAddFile={dummyAddFile}
178
+ onCopyNode={async (uiNode, targetNode) =>
179
+ onCopyNode(uiNode.id, "Copy of " + uiNode.name, targetNode.id)
180
+ }
181
+ onMoveNode={dummyMoveNode}
182
+ isAllowedToCreateDocuments={true}
183
+ onAddFolder={onAddFolder}
184
+ parentFolderId={selectedNodeId}
185
+ />
186
+
187
+ {/* Files Section */}
188
+ <FileItemsGrid
189
+ files={selectedFolderChildren.files}
190
+ onSelectNode={handleFileSelect}
191
+ onRenameNode={renameNode}
192
+ onDuplicateNode={dummyDuplicateNode}
193
+ onDeleteNode={onDeleteNode}
194
+ isAllowedToCreateDocuments={true}
195
+ />
196
+
197
+ {/* Create Document Section */}
198
+ <CreateDocument
199
+ createDocument={onSelectDocumentModel}
200
+ documentModels={filteredDocumentModels}
201
+ />
202
+ </>
203
+ )}
204
+ </div>
205
+
206
+ {/* Create Document Modal */}
207
+ <CreateDocumentModal
208
+ onContinue={onCreateDocument}
209
+ onOpenChange={(open) => setOpenModal(open)}
210
+ open={openModal}
211
+ />
212
+ </div>
213
+ );
214
+ }
@@ -0,0 +1,127 @@
1
+ ---
2
+ to: "<%= rootDir %>/<%= h.changeCase.param(name) %>/components/EditorContainer.tsx"
3
+ unless_exists: true
4
+ ---
5
+ import {
6
+ useDriveContext,
7
+ exportDocument,
8
+ type User,
9
+ } from "@powerhousedao/reactor-browser";
10
+ import {
11
+ documentModelDocumentModelModule,
12
+ type DocumentModelModule,
13
+ type EditorContext,
14
+ type EditorProps,
15
+ type PHDocument,
16
+ } from "document-model";
17
+ import {
18
+ DocumentToolbar,
19
+ RevisionHistory,
20
+ DefaultEditorLoader,
21
+ } from "@powerhousedao/design-system";
22
+ import { useState, Suspense, type FC, useCallback, lazy } from "react";
23
+
24
+ export interface EditorContainerProps {
25
+ driveId: string;
26
+ documentId: string;
27
+ documentType: string;
28
+ onClose: () => void;
29
+ title: string;
30
+ context: EditorContext;
31
+ }
32
+
33
+ const documentModelsMap = {
34
+ [documentModelDocumentModelModule.documentModel.id]: documentModelDocumentModelModule,
35
+ };
36
+
37
+ const documentEditorMap = {
38
+ [documentModelDocumentModelModule.documentModel.id]: lazy(() =>
39
+ import('@powerhousedao/builder-tools/style.css').then(() =>
40
+ import("@powerhousedao/builder-tools/document-model-editor").then((m) => ({
41
+ default: m.documentModelEditorModule.Component,
42
+ }))
43
+ )
44
+ ),
45
+ } as const;
46
+
47
+ function getDocumentModel(documentType: string) {
48
+ return documentModelsMap[documentType];
49
+ }
50
+
51
+ function getDocumentEditor(documentType: string) {
52
+ return documentEditorMap[documentType];
53
+ }
54
+
55
+ export const EditorContainer: React.FC<EditorContainerProps> = (props) => {
56
+ const { driveId, documentId, documentType, onClose, title, context } = props;
57
+
58
+ const [showRevisionHistory, setShowRevisionHistory] = useState(false);
59
+ const { useDocumentEditorProps } = useDriveContext();
60
+ const user = context.user as User | undefined;
61
+
62
+ const documentModelModule = getDocumentModel(
63
+ documentType,
64
+ ) as DocumentModelModule<PHDocument>;
65
+
66
+ const { dispatch, error, document } = useDocumentEditorProps({
67
+ documentId,
68
+ documentType,
69
+ driveId,
70
+ documentModelModule,
71
+ user,
72
+ });
73
+
74
+ const onExport = useCallback(async () => {
75
+ if (document) {
76
+ const ext = documentModelModule.documentModel.extension;
77
+ await exportDocument(document, title, ext);
78
+ }
79
+ }, [document?.revision.global, document?.revision.local]);
80
+
81
+ const loadingContent = (
82
+ <div className="flex-1 flex justify-center items-center h-full">
83
+ <DefaultEditorLoader />
84
+ </div>
85
+ );
86
+
87
+ if (!document) return loadingContent;
88
+
89
+ const Editor = getDocumentEditor(documentType);
90
+
91
+ if (!Editor) {
92
+ console.error("No editor found for document type:", documentType);
93
+ return (
94
+ <div className="flex-1">
95
+ No editor found for document type: {documentType}
96
+ </div>
97
+ );
98
+ }
99
+ const EditorComponent = Editor as FC<EditorProps<PHDocument>>;
100
+
101
+ return showRevisionHistory ? (
102
+ <RevisionHistory
103
+ documentId={documentId}
104
+ documentTitle={title}
105
+ globalOperations={document.operations.global}
106
+ key={documentId}
107
+ localOperations={document.operations.local}
108
+ onClose={() => setShowRevisionHistory(false)}
109
+ />
110
+ ) : (
111
+ <Suspense fallback={loadingContent}>
112
+ <DocumentToolbar
113
+ onClose={onClose}
114
+ onExport={onExport}
115
+ onShowRevisionHistory={() => setShowRevisionHistory(true)}
116
+ onSwitchboardLinkClick={() => {}}
117
+ title={title}
118
+ />
119
+ <EditorComponent
120
+ context={context}
121
+ dispatch={dispatch}
122
+ document={document}
123
+ error={error}
124
+ />
125
+ </Suspense>
126
+ );
127
+ };
@@ -0,0 +1,44 @@
1
+ ---
2
+ to: "<%= rootDir %>/<%= h.changeCase.param(name) %>/components/FileItemsGrid.tsx"
3
+ unless_exists: true
4
+ ---
5
+ import { FileItem, type UiFileNode, type BaseUiFileNode } from "@powerhousedao/design-system";
6
+
7
+ interface FileItemsGridProps {
8
+ files: UiFileNode[];
9
+ onSelectNode: (node: BaseUiFileNode) => void;
10
+ onRenameNode: (nodeId: string, name: string) => void;
11
+ onDuplicateNode: (node: BaseUiFileNode) => void;
12
+ onDeleteNode: (nodeId: string) => void;
13
+ isAllowedToCreateDocuments: boolean;
14
+ }
15
+
16
+ export function FileItemsGrid({
17
+ files,
18
+ onSelectNode,
19
+ onRenameNode,
20
+ onDuplicateNode,
21
+ onDeleteNode,
22
+ isAllowedToCreateDocuments,
23
+ }: FileItemsGridProps) {
24
+ if (files.length === 0) return null;
25
+
26
+ return (
27
+ <div>
28
+ <h3 className="text-sm font-medium text-gray-500 mb-2">Files</h3>
29
+ <div className="flex flex-wrap gap-2">
30
+ {files.map((file) => (
31
+ <FileItem
32
+ key={file.id}
33
+ uiNode={file}
34
+ onSelectNode={onSelectNode}
35
+ onRenameNode={(name) => onRenameNode(file.id, name)}
36
+ onDuplicateNode={onDuplicateNode}
37
+ onDeleteNode={() => onDeleteNode(file.id)}
38
+ isAllowedToCreateDocuments={isAllowedToCreateDocuments}
39
+ />
40
+ ))}
41
+ </div>
42
+ </div>
43
+ );
44
+ }
@@ -0,0 +1,96 @@
1
+ ---
2
+ to: "<%= rootDir %>/<%= h.changeCase.param(name) %>/components/FolderItemsGrid.tsx"
3
+ unless_exists: true
4
+ ---
5
+ import {
6
+ FolderItem,
7
+ type UiFolderNode,
8
+ type UiNode,
9
+ type BaseUiFolderNode,
10
+ type BaseUiNode,
11
+ } from "@powerhousedao/design-system";
12
+ import { useState } from "react";
13
+
14
+ interface FolderItemsGridProps {
15
+ folders: UiFolderNode[];
16
+ onSelectNode: (node: BaseUiFolderNode) => void;
17
+ onRenameNode: (nodeId: string, name: string) => void;
18
+ onDuplicateNode: (node: BaseUiFolderNode) => void;
19
+ onDeleteNode: (nodeId: string) => void;
20
+ onAddFile: (file: File, parentNode: BaseUiNode | null) => Promise<void>;
21
+ onCopyNode: (uiNode: BaseUiNode, targetNode: BaseUiNode) => Promise<void>;
22
+ onMoveNode: (uiNode: BaseUiNode, targetNode: BaseUiNode) => Promise<void>;
23
+ isAllowedToCreateDocuments: boolean;
24
+ onAddFolder: (name: string, parentFolder?: string) => void;
25
+ parentFolderId?: string;
26
+ }
27
+
28
+ export function FolderItemsGrid({
29
+ folders,
30
+ onSelectNode,
31
+ onRenameNode,
32
+ onDuplicateNode,
33
+ onDeleteNode,
34
+ onAddFile,
35
+ onCopyNode,
36
+ onMoveNode,
37
+ isAllowedToCreateDocuments,
38
+ onAddFolder,
39
+ parentFolderId,
40
+ }: FolderItemsGridProps) {
41
+ const [newFolderName, setNewFolderName] = useState("");
42
+
43
+ const handleSubmit = (e: React.FormEvent) => {
44
+ e.preventDefault();
45
+ if (newFolderName.trim()) {
46
+ onAddFolder(newFolderName.trim(), parentFolderId);
47
+ setNewFolderName("");
48
+ }
49
+ };
50
+
51
+ return (
52
+ <div className="mb-6">
53
+ <div className="flex items-center gap-4 mb-2">
54
+ <h3 className="text-sm font-medium text-gray-500">Folders</h3>
55
+
56
+ {/* New Folder Input */}
57
+ <form onSubmit={handleSubmit} className="w-48">
58
+ <div className="relative">
59
+ <input
60
+ type="text"
61
+ value={newFolderName}
62
+ onChange={(e) => setNewFolderName(e.target.value)}
63
+ placeholder="Create new folder..."
64
+ className="w-full px-3 py-1.5 pr-8 text-sm border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
65
+ />
66
+ <button
67
+ type="submit"
68
+ className="absolute right-2 top-1/2 -translate-y-1/2 text-gray-400 hover:text-gray-600 focus:outline-none text-sm"
69
+ >
70
+ +
71
+ </button>
72
+ </div>
73
+ </form>
74
+ </div>
75
+
76
+ {folders.length > 0 && (
77
+ <div className="flex flex-wrap gap-2">
78
+ {folders.map((folder) => (
79
+ <FolderItem
80
+ key={folder.id}
81
+ uiNode={folder}
82
+ onSelectNode={onSelectNode}
83
+ onRenameNode={(name) => onRenameNode(folder.id, name)}
84
+ onDuplicateNode={onDuplicateNode}
85
+ onDeleteNode={() => onDeleteNode(folder.id)}
86
+ onAddFile={onAddFile}
87
+ onCopyNode={onCopyNode}
88
+ onMoveNode={onMoveNode}
89
+ isAllowedToCreateDocuments={isAllowedToCreateDocuments}
90
+ />
91
+ ))}
92
+ </div>
93
+ )}
94
+ </div>
95
+ );
96
+ }
@@ -0,0 +1,85 @@
1
+ ---
2
+ to: "<%= rootDir %>/<%= h.changeCase.param(name) %>/components/FolderTree.tsx"
3
+ unless_exists: true
4
+ ---
5
+ import { useState } from 'react';
6
+ import type { UiFolderNode } from "@powerhousedao/design-system";
7
+
8
+ interface FolderTreeProps {
9
+ folders: UiFolderNode[];
10
+ selectedNodeId?: string;
11
+ onSelectNode: (node: UiFolderNode) => void;
12
+ }
13
+
14
+ export function FolderTree({ folders, selectedNodeId, onSelectNode }: FolderTreeProps) {
15
+ const [expandedFolders, setExpandedFolders] = useState<Set<string>>(new Set());
16
+
17
+ const toggleFolder = (folderId: string) => {
18
+ setExpandedFolders(prev => {
19
+ const next = new Set(prev);
20
+ if (next.has(folderId)) {
21
+ next.delete(folderId);
22
+ } else {
23
+ next.add(folderId);
24
+ }
25
+ return next;
26
+ });
27
+ };
28
+
29
+ const renderFolder = (folder: UiFolderNode, level: number = 0) => {
30
+ const hasChildren = folders.some(f => f.parentFolder === folder.id);
31
+ const isExpanded = expandedFolders.has(folder.id);
32
+ const isSelected = selectedNodeId === folder.id;
33
+
34
+ return (
35
+ <div key={folder.id}>
36
+ <div
37
+ className={`flex items-center py-1 px-2 cursor-pointer hover:bg-gray-100 rounded ${
38
+ isSelected ? 'bg-gray-100' : ''
39
+ }`}
40
+ style={{ paddingLeft: `${level * 16 + 8}px` }}
41
+ onClick={() => onSelectNode(folder)}
42
+ >
43
+ {hasChildren && (
44
+ <button
45
+ className="w-4 h-4 mr-1 flex items-center justify-center"
46
+ onClick={(e) => {
47
+ e.stopPropagation();
48
+ toggleFolder(folder.id);
49
+ }}
50
+ >
51
+ {isExpanded ? '▼' : '▶'}
52
+ </button>
53
+ )}
54
+ <span className="text-sm">{folder.name}</span>
55
+ </div>
56
+ {isExpanded && hasChildren && (
57
+ <div>
58
+ {folders
59
+ .filter(f => f.parentFolder === folder.id)
60
+ .map(child => renderFolder(child, level + 1))}
61
+ </div>
62
+ )}
63
+ </div>
64
+ );
65
+ };
66
+
67
+ return (
68
+ <div className="space-y-1">
69
+ {/* Root Directory Option */}
70
+ <div
71
+ className={`flex items-center py-1 px-2 cursor-pointer hover:bg-gray-100 rounded ${
72
+ !selectedNodeId ? 'bg-gray-100' : ''
73
+ }`}
74
+ onClick={() => onSelectNode({ id: '', name: 'Root', kind: 'FOLDER' } as UiFolderNode)}
75
+ >
76
+ <span className="text-sm font-medium">Root</span>
77
+ </div>
78
+
79
+ {/* Folder Tree */}
80
+ {folders
81
+ .filter(folder => !folder.parentFolder)
82
+ .map(folder => renderFolder(folder))}
83
+ </div>
84
+ );
85
+ }
@@ -0,0 +1,68 @@
1
+ ---
2
+ to: "<%= rootDir %>/<%= h.changeCase.param(name) %>/editor.tsx"
3
+ unless_exists: true
4
+ ---
5
+ import { type EditorProps, hashKey } from "document-model";
6
+ import { type DocumentDriveDocument, addFolder, deleteNode, updateNode, generateNodesCopy, copyNode } from "document-drive";
7
+ import { WagmiContext } from "@powerhousedao/design-system";
8
+ import { DriveExplorer } from "./components/DriveExplorer.js";
9
+ import { useCallback } from "react";
10
+
11
+ export type IProps = EditorProps<DocumentDriveDocument>;
12
+
13
+ export default function Editor(props: IProps) {
14
+ const { dispatch, context } = props;
15
+
16
+ const onAddFolder = useCallback((name: string, parentFolder?: string) => {
17
+ dispatch(addFolder({
18
+ id: hashKey(),
19
+ name,
20
+ parentFolder,
21
+ }));
22
+ }, [dispatch]);
23
+
24
+ const onDeleteNode = useCallback((nodeId: string) => {
25
+ dispatch(deleteNode({ id: nodeId }));
26
+ }, [dispatch]);
27
+
28
+ const renameNode = useCallback((nodeId: string, name: string) => {
29
+ dispatch(updateNode({ id: nodeId, name }));
30
+ }, [dispatch]);
31
+
32
+ const onCopyNode = useCallback((nodeId: string, targetName: string, parentId?: string) => {
33
+ const generateId = () => hashKey();
34
+
35
+ const copyNodesInput = generateNodesCopy({
36
+ srcId: nodeId,
37
+ targetParentFolder: parentId,
38
+ targetName,
39
+ }, generateId, props.document.state.global.nodes);
40
+
41
+ const copyNodesAction = copyNodesInput.map(input => {
42
+ return copyNode(input);
43
+ });
44
+
45
+ for (const copyNodeAction of copyNodesAction) {
46
+ dispatch(copyNodeAction);
47
+ }
48
+ }, [dispatch, props.document.state.global.nodes]);
49
+
50
+ return (
51
+ <div
52
+ className="new-drive-explorer"
53
+ style={{ height: "100%" }}
54
+ >
55
+ <WagmiContext>
56
+ <DriveExplorer
57
+ driveId={props.document.state.global.id}
58
+ nodes={props.document.state.global.nodes}
59
+ onAddFolder={onAddFolder}
60
+ onDeleteNode={onDeleteNode}
61
+ renameNode={renameNode}
62
+ onCopyNode={onCopyNode}
63
+ context={context}
64
+ />
65
+ </WagmiContext>
66
+ </div>
67
+ );
68
+ }