Rob Koch
commited on
Commit
·
13a15e9
1
Parent(s):
eb36ec6
copyPath and copyRelativePath for files and folders
Browse files
app/components/workbench/FileTree.tsx
CHANGED
@@ -111,6 +111,22 @@ export const FileTree = memo(
|
|
111 |
});
|
112 |
};
|
113 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
114 |
return (
|
115 |
<div className={classNames('text-sm', className, 'overflow-y-auto')}>
|
116 |
{filteredFileList.map((fileOrFolder) => {
|
@@ -122,6 +138,12 @@ export const FileTree = memo(
|
|
122 |
selected={selectedFile === fileOrFolder.fullPath}
|
123 |
file={fileOrFolder}
|
124 |
unsavedChanges={unsavedFiles?.has(fileOrFolder.fullPath)}
|
|
|
|
|
|
|
|
|
|
|
|
|
125 |
onClick={() => {
|
126 |
onFileSelect?.(fileOrFolder.fullPath);
|
127 |
}}
|
@@ -135,6 +157,12 @@ export const FileTree = memo(
|
|
135 |
folder={fileOrFolder}
|
136 |
selected={allowFolderSelection && selectedFile === fileOrFolder.fullPath}
|
137 |
collapsed={collapsedFolders.has(fileOrFolder.fullPath)}
|
|
|
|
|
|
|
|
|
|
|
|
|
138 |
onClick={() => {
|
139 |
toggleCollapseState(fileOrFolder.fullPath);
|
140 |
}}
|
@@ -157,23 +185,30 @@ interface FolderProps {
|
|
157 |
folder: FolderNode;
|
158 |
collapsed: boolean;
|
159 |
selected?: boolean;
|
|
|
|
|
160 |
onClick: () => void;
|
161 |
}
|
162 |
|
163 |
interface FolderContextMenuProps {
|
|
|
|
|
164 |
children: ReactNode;
|
165 |
}
|
166 |
|
167 |
-
function ContextMenuItem({ children }: { children: ReactNode }) {
|
168 |
return (
|
169 |
-
<ContextMenu.Item
|
|
|
|
|
|
|
170 |
<span className="size-4 shrink-0"></span>
|
171 |
<span>{children}</span>
|
172 |
</ContextMenu.Item>
|
173 |
);
|
174 |
}
|
175 |
|
176 |
-
function
|
177 |
return (
|
178 |
<ContextMenu.Root>
|
179 |
<ContextMenu.Trigger>{children}</ContextMenu.Trigger>
|
@@ -183,16 +218,8 @@ function FolderContextMenu({ children }: FolderContextMenuProps) {
|
|
183 |
className="border border-bolt-elements-borderColor rounded-md z-context-menu bg-bolt-elements-background-depth-1 dark:bg-bolt-elements-background-depth-2 data-[state=open]:animate-in animate-duration-100 data-[state=open]:fade-in-0 data-[state=open]:zoom-in-98 w-56"
|
184 |
>
|
185 |
<ContextMenu.Group className="p-1 border-b-px border-solid border-bolt-elements-borderColor">
|
186 |
-
<ContextMenuItem>
|
187 |
-
<ContextMenuItem>
|
188 |
-
</ContextMenu.Group>
|
189 |
-
<ContextMenu.Group className="p-1 border-b-px border-solid border-bolt-elements-borderColor">
|
190 |
-
<ContextMenuItem>Copy path</ContextMenuItem>
|
191 |
-
<ContextMenuItem>Copy relative path</ContextMenuItem>
|
192 |
-
</ContextMenu.Group>
|
193 |
-
<ContextMenu.Group className="p-1 border-b-px border-solid border-bolt-elements-borderColor">
|
194 |
-
<ContextMenuItem>Rename...</ContextMenuItem>
|
195 |
-
<ContextMenuItem>Delete</ContextMenuItem>
|
196 |
</ContextMenu.Group>
|
197 |
</ContextMenu.Content>
|
198 |
</ContextMenu.Portal>
|
@@ -200,9 +227,9 @@ function FolderContextMenu({ children }: FolderContextMenuProps) {
|
|
200 |
);
|
201 |
}
|
202 |
|
203 |
-
function Folder({ folder, collapsed, selected = false, onClick }: FolderProps) {
|
204 |
return (
|
205 |
-
<
|
206 |
<NodeButton
|
207 |
className={classNames('group', {
|
208 |
'bg-transparent text-bolt-elements-item-contentDefault hover:text-bolt-elements-item-contentActive hover:bg-bolt-elements-item-backgroundActive':
|
@@ -218,7 +245,7 @@ function Folder({ folder, collapsed, selected = false, onClick }: FolderProps) {
|
|
218 |
>
|
219 |
{folder.name}
|
220 |
</NodeButton>
|
221 |
-
</
|
222 |
);
|
223 |
}
|
224 |
|
@@ -226,31 +253,43 @@ interface FileProps {
|
|
226 |
file: FileNode;
|
227 |
selected: boolean;
|
228 |
unsavedChanges?: boolean;
|
|
|
|
|
229 |
onClick: () => void;
|
230 |
}
|
231 |
|
232 |
-
function File({
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
233 |
return (
|
234 |
-
<
|
235 |
-
|
236 |
-
'
|
237 |
-
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
onClick={onClick}
|
244 |
-
>
|
245 |
-
<div
|
246 |
-
className={classNames('flex items-center', {
|
247 |
'group-hover:text-bolt-elements-item-contentActive': !selected,
|
248 |
})}
|
|
|
249 |
>
|
250 |
-
<div
|
251 |
-
|
252 |
-
|
253 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
254 |
);
|
255 |
}
|
256 |
|
|
|
111 |
});
|
112 |
};
|
113 |
|
114 |
+
const onCopyPath = (fileOrFolder: FileNode | FolderNode) => {
|
115 |
+
try {
|
116 |
+
navigator.clipboard.writeText(fileOrFolder.fullPath);
|
117 |
+
} catch (error) {
|
118 |
+
logger.error(error);
|
119 |
+
}
|
120 |
+
};
|
121 |
+
|
122 |
+
const onCopyRelativePath = (fileOrFolder: FileNode | FolderNode) => {
|
123 |
+
try {
|
124 |
+
navigator.clipboard.writeText(fileOrFolder.fullPath.substring((rootFolder || '').length));
|
125 |
+
} catch (error) {
|
126 |
+
logger.error(error);
|
127 |
+
}
|
128 |
+
};
|
129 |
+
|
130 |
return (
|
131 |
<div className={classNames('text-sm', className, 'overflow-y-auto')}>
|
132 |
{filteredFileList.map((fileOrFolder) => {
|
|
|
138 |
selected={selectedFile === fileOrFolder.fullPath}
|
139 |
file={fileOrFolder}
|
140 |
unsavedChanges={unsavedFiles?.has(fileOrFolder.fullPath)}
|
141 |
+
onCopyPath={() => {
|
142 |
+
onCopyPath(fileOrFolder);
|
143 |
+
}}
|
144 |
+
onCopyRelativePath={() => {
|
145 |
+
onCopyRelativePath(fileOrFolder);
|
146 |
+
}}
|
147 |
onClick={() => {
|
148 |
onFileSelect?.(fileOrFolder.fullPath);
|
149 |
}}
|
|
|
157 |
folder={fileOrFolder}
|
158 |
selected={allowFolderSelection && selectedFile === fileOrFolder.fullPath}
|
159 |
collapsed={collapsedFolders.has(fileOrFolder.fullPath)}
|
160 |
+
onCopyPath={() => {
|
161 |
+
onCopyPath(fileOrFolder);
|
162 |
+
}}
|
163 |
+
onCopyRelativePath={() => {
|
164 |
+
onCopyRelativePath(fileOrFolder);
|
165 |
+
}}
|
166 |
onClick={() => {
|
167 |
toggleCollapseState(fileOrFolder.fullPath);
|
168 |
}}
|
|
|
185 |
folder: FolderNode;
|
186 |
collapsed: boolean;
|
187 |
selected?: boolean;
|
188 |
+
onCopyPath: () => void;
|
189 |
+
onCopyRelativePath: () => void;
|
190 |
onClick: () => void;
|
191 |
}
|
192 |
|
193 |
interface FolderContextMenuProps {
|
194 |
+
onCopyPath?: () => void;
|
195 |
+
onCopyRelativePath?: () => void;
|
196 |
children: ReactNode;
|
197 |
}
|
198 |
|
199 |
+
function ContextMenuItem({ onSelect, children }: { onSelect?: () => void; children: ReactNode }) {
|
200 |
return (
|
201 |
+
<ContextMenu.Item
|
202 |
+
onSelect={onSelect}
|
203 |
+
className="flex items-center gap-2 px-2 py-1.5 outline-0 text-sm text-bolt-elements-textPrimary cursor-pointer ws-nowrap text-bolt-elements-item-contentDefault hover:text-bolt-elements-item-contentActive hover:bg-bolt-elements-item-backgroundActive rounded-md"
|
204 |
+
>
|
205 |
<span className="size-4 shrink-0"></span>
|
206 |
<span>{children}</span>
|
207 |
</ContextMenu.Item>
|
208 |
);
|
209 |
}
|
210 |
|
211 |
+
function FileContextMenu({ onCopyPath, onCopyRelativePath, children }: FolderContextMenuProps) {
|
212 |
return (
|
213 |
<ContextMenu.Root>
|
214 |
<ContextMenu.Trigger>{children}</ContextMenu.Trigger>
|
|
|
218 |
className="border border-bolt-elements-borderColor rounded-md z-context-menu bg-bolt-elements-background-depth-1 dark:bg-bolt-elements-background-depth-2 data-[state=open]:animate-in animate-duration-100 data-[state=open]:fade-in-0 data-[state=open]:zoom-in-98 w-56"
|
219 |
>
|
220 |
<ContextMenu.Group className="p-1 border-b-px border-solid border-bolt-elements-borderColor">
|
221 |
+
<ContextMenuItem onSelect={onCopyPath}>Copy path</ContextMenuItem>
|
222 |
+
<ContextMenuItem onSelect={onCopyRelativePath}>Copy relative path</ContextMenuItem>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
223 |
</ContextMenu.Group>
|
224 |
</ContextMenu.Content>
|
225 |
</ContextMenu.Portal>
|
|
|
227 |
);
|
228 |
}
|
229 |
|
230 |
+
function Folder({ folder, collapsed, selected = false, onCopyPath, onCopyRelativePath, onClick }: FolderProps) {
|
231 |
return (
|
232 |
+
<FileContextMenu onCopyPath={onCopyPath} onCopyRelativePath={onCopyRelativePath}>
|
233 |
<NodeButton
|
234 |
className={classNames('group', {
|
235 |
'bg-transparent text-bolt-elements-item-contentDefault hover:text-bolt-elements-item-contentActive hover:bg-bolt-elements-item-backgroundActive':
|
|
|
245 |
>
|
246 |
{folder.name}
|
247 |
</NodeButton>
|
248 |
+
</FileContextMenu>
|
249 |
);
|
250 |
}
|
251 |
|
|
|
253 |
file: FileNode;
|
254 |
selected: boolean;
|
255 |
unsavedChanges?: boolean;
|
256 |
+
onCopyPath: () => void;
|
257 |
+
onCopyRelativePath: () => void;
|
258 |
onClick: () => void;
|
259 |
}
|
260 |
|
261 |
+
function File({
|
262 |
+
file: { depth, name },
|
263 |
+
onClick,
|
264 |
+
onCopyPath,
|
265 |
+
onCopyRelativePath,
|
266 |
+
selected,
|
267 |
+
unsavedChanges = false,
|
268 |
+
}: FileProps) {
|
269 |
return (
|
270 |
+
<FileContextMenu onCopyPath={onCopyPath} onCopyRelativePath={onCopyRelativePath}>
|
271 |
+
<NodeButton
|
272 |
+
className={classNames('group', {
|
273 |
+
'bg-transparent hover:bg-bolt-elements-item-backgroundActive text-bolt-elements-item-contentDefault':
|
274 |
+
!selected,
|
275 |
+
'bg-bolt-elements-item-backgroundAccent text-bolt-elements-item-contentAccent': selected,
|
276 |
+
})}
|
277 |
+
depth={depth}
|
278 |
+
iconClasses={classNames('i-ph:file-duotone scale-98', {
|
|
|
|
|
|
|
|
|
279 |
'group-hover:text-bolt-elements-item-contentActive': !selected,
|
280 |
})}
|
281 |
+
onClick={onClick}
|
282 |
>
|
283 |
+
<div
|
284 |
+
className={classNames('flex items-center', {
|
285 |
+
'group-hover:text-bolt-elements-item-contentActive': !selected,
|
286 |
+
})}
|
287 |
+
>
|
288 |
+
<div className="flex-1 truncate pr-2">{name}</div>
|
289 |
+
{unsavedChanges && <span className="i-ph:circle-fill scale-68 shrink-0 text-orange-500" />}
|
290 |
+
</div>
|
291 |
+
</NodeButton>
|
292 |
+
</FileContextMenu>
|
293 |
);
|
294 |
}
|
295 |
|