Rob Koch commited on
Commit
13a15e9
·
1 Parent(s): eb36ec6

copyPath and copyRelativePath for files and folders

Browse files
Files changed (1) hide show
  1. app/components/workbench/FileTree.tsx +73 -34
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 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">
 
 
 
170
  <span className="size-4 shrink-0"></span>
171
  <span>{children}</span>
172
  </ContextMenu.Item>
173
  );
174
  }
175
 
176
- function FolderContextMenu({ children }: FolderContextMenuProps) {
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>New file...</ContextMenuItem>
187
- <ContextMenuItem>New folder...</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
- <FolderContextMenu>
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
- </FolderContextMenu>
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({ file: { depth, name }, onClick, selected, unsavedChanges = false }: FileProps) {
 
 
 
 
 
 
 
233
  return (
234
- <NodeButton
235
- className={classNames('group', {
236
- 'bg-transparent hover:bg-bolt-elements-item-backgroundActive text-bolt-elements-item-contentDefault': !selected,
237
- 'bg-bolt-elements-item-backgroundAccent text-bolt-elements-item-contentAccent': selected,
238
- })}
239
- depth={depth}
240
- iconClasses={classNames('i-ph:file-duotone scale-98', {
241
- 'group-hover:text-bolt-elements-item-contentActive': !selected,
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 className="flex-1 truncate pr-2">{name}</div>
251
- {unsavedChanges && <span className="i-ph:circle-fill scale-68 shrink-0 text-orange-500" />}
252
- </div>
253
- </NodeButton>
 
 
 
 
 
 
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