@powerhousedao/codegen 0.49.2-dev.1 → 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.
- package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/components/CreateDocument.esm.t +49 -0
- package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/components/DriveExplorer.esm.t +214 -0
- package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/components/EditorContainer.esm.t +127 -0
- package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/components/FileItemsGrid.esm.t +44 -0
- package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/components/FolderItemsGrid.esm.t +96 -0
- package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/components/FolderTree.esm.t +85 -0
- package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/editor.esm.t +68 -0
- package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/hooks/useSelectedFolderChildren.esm.t +35 -0
- package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/hooks/useTransformedNodes.esm.t +35 -0
- package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/index.esm.t +20 -0
- package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/lib.esm.t +9 -0
- package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/lib.inject_export.esm.t +7 -0
- package/dist/src/codegen/.hygen/templates/powerhouse/generate-drive-editor/types/css.d.esm.t +8 -0
- package/dist/src/codegen/hygen.d.ts +3 -0
- package/dist/src/codegen/hygen.d.ts.map +1 -1
- package/dist/src/codegen/hygen.js +4 -0
- package/dist/src/codegen/index.d.ts +1 -0
- package/dist/src/codegen/index.d.ts.map +1 -1
- package/dist/src/codegen/index.js +6 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -2
|
@@ -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
|
+
}
|