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 |
|