dsh-explorer 0.0.0-stage → 0.1.1
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/LICENSE +22 -0
- package/README.md +106 -2
- package/cordis.patch.yml +5 -0
- package/icon.svg +7 -0
- package/lib/client.js +859 -0
- package/lib/index.js +854 -0
- package/package.json +91 -4
- package/src/client/ExplorerView.tsx +981 -0
- package/src/client/components/ExplorerContextMenu.tsx +157 -0
- package/src/client/components/ExplorerDeleteModal.tsx +124 -0
- package/src/client/components/ExplorerEmptyContextMenu.tsx +125 -0
- package/src/client/components/ExplorerHeader.tsx +81 -0
- package/src/client/components/ExplorerOptionsModal.tsx +230 -0
- package/src/client/components/ExplorerRow.tsx +258 -0
- package/src/client/components/ExplorerSearch.tsx +55 -0
- package/src/client/components/TreeNode.tsx +384 -0
- package/src/client/css-modules.d.ts +5 -0
- package/src/client/explorer.module.css +833 -0
- package/src/client/hooks/useKeyboardNav.ts +89 -0
- package/src/client/hooks/useLiveSync.ts +81 -0
- package/src/client/index.tsx +238 -0
- package/src/client/utils/api.ts +251 -0
- package/src/client/utils/conversation-draft.ts +644 -0
- package/src/client/utils/drag-drop.ts +209 -0
- package/src/client/utils/open-tab.ts +69 -0
- package/src/client/utils/options.ts +59 -0
- package/src/client/utils/vscode-theme.tsx +250 -0
- package/src/config.ts +11 -0
- package/src/fs-operations.ts +116 -0
- package/src/fs-search.ts +116 -0
- package/src/fs-tree.ts +155 -0
- package/src/fs-watch.ts +179 -0
- package/src/git-status.ts +167 -0
- package/src/index.ts +177 -0
- package/src/path-security.ts +22 -0
- package/src/session-path.ts +35 -0
- package/src/wire.ts +63 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from 'react'
|
|
2
|
+
import css from '../explorer.module.css'
|
|
3
|
+
import type { FsEntry } from '../utils/api.ts'
|
|
4
|
+
|
|
5
|
+
interface ContextMenuProps {
|
|
6
|
+
x: number
|
|
7
|
+
y: number
|
|
8
|
+
entry: FsEntry
|
|
9
|
+
relativePath: string
|
|
10
|
+
onClose: () => void
|
|
11
|
+
onOpenFile?: () => void
|
|
12
|
+
onInsertReference: () => void
|
|
13
|
+
onInsertSnippet: () => void
|
|
14
|
+
onCopyPath: () => void
|
|
15
|
+
onCopyRelativePath: () => void
|
|
16
|
+
onNewFile: () => void
|
|
17
|
+
onNewFolder: () => void
|
|
18
|
+
onRename: () => void
|
|
19
|
+
onDelete: () => void
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const ExplorerContextMenu: React.FC<ContextMenuProps> = ({
|
|
23
|
+
x,
|
|
24
|
+
y,
|
|
25
|
+
entry,
|
|
26
|
+
relativePath,
|
|
27
|
+
onClose,
|
|
28
|
+
onOpenFile,
|
|
29
|
+
onInsertReference,
|
|
30
|
+
onInsertSnippet,
|
|
31
|
+
onCopyPath,
|
|
32
|
+
onCopyRelativePath,
|
|
33
|
+
onNewFile,
|
|
34
|
+
onNewFolder,
|
|
35
|
+
onRename,
|
|
36
|
+
onDelete,
|
|
37
|
+
}) => {
|
|
38
|
+
const menuRef = useRef<HTMLDivElement>(null)
|
|
39
|
+
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
const handleOutsideClick = (e: MouseEvent) => {
|
|
42
|
+
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
|
|
43
|
+
onClose()
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
window.addEventListener('mousedown', handleOutsideClick)
|
|
47
|
+
return () => window.removeEventListener('mousedown', handleOutsideClick)
|
|
48
|
+
}, [onClose])
|
|
49
|
+
|
|
50
|
+
const top = Math.min(y, window.innerHeight - 260)
|
|
51
|
+
const left = Math.min(x, window.innerWidth - 200)
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div ref={menuRef} className={css.contextMenu} style={{ top, left }}>
|
|
55
|
+
{!entry.isDir && onOpenFile && (
|
|
56
|
+
<>
|
|
57
|
+
<div
|
|
58
|
+
className={css.menuItem}
|
|
59
|
+
onClick={() => {
|
|
60
|
+
onOpenFile()
|
|
61
|
+
onClose()
|
|
62
|
+
}}
|
|
63
|
+
>
|
|
64
|
+
<span>Open in New Tab</span>
|
|
65
|
+
<span className={css.menuShortcut}>Double-Click</span>
|
|
66
|
+
</div>
|
|
67
|
+
<div className={css.menuDivider} />
|
|
68
|
+
</>
|
|
69
|
+
)}
|
|
70
|
+
<div
|
|
71
|
+
className={css.menuItem}
|
|
72
|
+
onClick={() => {
|
|
73
|
+
onInsertReference()
|
|
74
|
+
onClose()
|
|
75
|
+
}}
|
|
76
|
+
>
|
|
77
|
+
<span>Insert Mention to Chat</span>
|
|
78
|
+
<span className={css.menuShortcut}>@</span>
|
|
79
|
+
</div>
|
|
80
|
+
{!entry.isDir && (
|
|
81
|
+
<div
|
|
82
|
+
className={css.menuItem}
|
|
83
|
+
onClick={() => {
|
|
84
|
+
onInsertSnippet()
|
|
85
|
+
onClose()
|
|
86
|
+
}}
|
|
87
|
+
>
|
|
88
|
+
<span>Insert Code to Chat</span>
|
|
89
|
+
<span className={css.menuShortcut}>Alt+Drop</span>
|
|
90
|
+
</div>
|
|
91
|
+
)}
|
|
92
|
+
<div className={css.menuDivider} />
|
|
93
|
+
{entry.isDir && (
|
|
94
|
+
<>
|
|
95
|
+
<div
|
|
96
|
+
className={css.menuItem}
|
|
97
|
+
onClick={() => {
|
|
98
|
+
onNewFile()
|
|
99
|
+
onClose()
|
|
100
|
+
}}
|
|
101
|
+
>
|
|
102
|
+
<span>New File...</span>
|
|
103
|
+
</div>
|
|
104
|
+
<div
|
|
105
|
+
className={css.menuItem}
|
|
106
|
+
onClick={() => {
|
|
107
|
+
onNewFolder()
|
|
108
|
+
onClose()
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
<span>New Folder...</span>
|
|
112
|
+
</div>
|
|
113
|
+
<div className={css.menuDivider} />
|
|
114
|
+
</>
|
|
115
|
+
)}
|
|
116
|
+
<div
|
|
117
|
+
className={css.menuItem}
|
|
118
|
+
onClick={() => {
|
|
119
|
+
onCopyRelativePath()
|
|
120
|
+
onClose()
|
|
121
|
+
}}
|
|
122
|
+
>
|
|
123
|
+
<span>Copy Relative Path</span>
|
|
124
|
+
</div>
|
|
125
|
+
<div
|
|
126
|
+
className={css.menuItem}
|
|
127
|
+
onClick={() => {
|
|
128
|
+
onCopyPath()
|
|
129
|
+
onClose()
|
|
130
|
+
}}
|
|
131
|
+
>
|
|
132
|
+
<span>Copy Path</span>
|
|
133
|
+
</div>
|
|
134
|
+
<div className={css.menuDivider} />
|
|
135
|
+
<div
|
|
136
|
+
className={css.menuItem}
|
|
137
|
+
onClick={() => {
|
|
138
|
+
onRename()
|
|
139
|
+
onClose()
|
|
140
|
+
}}
|
|
141
|
+
>
|
|
142
|
+
<span>Rename...</span>
|
|
143
|
+
<span className={css.menuShortcut}>F2</span>
|
|
144
|
+
</div>
|
|
145
|
+
<div
|
|
146
|
+
className={css.menuItem}
|
|
147
|
+
onClick={() => {
|
|
148
|
+
onDelete()
|
|
149
|
+
onClose()
|
|
150
|
+
}}
|
|
151
|
+
>
|
|
152
|
+
<span style={{ color: '#f44336' }}>Delete</span>
|
|
153
|
+
<span className={css.menuShortcut}>Del</span>
|
|
154
|
+
</div>
|
|
155
|
+
</div>
|
|
156
|
+
)
|
|
157
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react'
|
|
2
|
+
import css from '../explorer.module.css'
|
|
3
|
+
import type { FsEntry } from '../utils/api.ts'
|
|
4
|
+
import { renderFileIcon } from '../utils/vscode-theme.tsx'
|
|
5
|
+
|
|
6
|
+
interface ExplorerDeleteModalProps {
|
|
7
|
+
entry: FsEntry
|
|
8
|
+
relativePath: string
|
|
9
|
+
onConfirm: () => Promise<void>
|
|
10
|
+
onClose: () => void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const ExplorerDeleteModal: React.FC<ExplorerDeleteModalProps> = ({
|
|
14
|
+
entry,
|
|
15
|
+
relativePath,
|
|
16
|
+
onConfirm,
|
|
17
|
+
onClose,
|
|
18
|
+
}) => {
|
|
19
|
+
const [loading, setLoading] = useState(false)
|
|
20
|
+
const [error, setError] = useState<string | null>(null)
|
|
21
|
+
const cancelBtnRef = useRef<HTMLButtonElement>(null)
|
|
22
|
+
const modalRef = useRef<HTMLDivElement>(null)
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
cancelBtnRef.current?.focus()
|
|
26
|
+
|
|
27
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
28
|
+
if (e.key === 'Escape') {
|
|
29
|
+
e.preventDefault()
|
|
30
|
+
onClose()
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const handleMouseDown = (e: MouseEvent) => {
|
|
35
|
+
if (modalRef.current && !modalRef.current.contains(e.target as Node)) {
|
|
36
|
+
onClose()
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
document.addEventListener('keydown', handleKeyDown)
|
|
41
|
+
document.addEventListener('mousedown', handleMouseDown)
|
|
42
|
+
return () => {
|
|
43
|
+
document.removeEventListener('keydown', handleKeyDown)
|
|
44
|
+
document.removeEventListener('mousedown', handleMouseDown)
|
|
45
|
+
}
|
|
46
|
+
}, [onClose])
|
|
47
|
+
|
|
48
|
+
const handleDelete = async () => {
|
|
49
|
+
if (loading) return
|
|
50
|
+
setLoading(true)
|
|
51
|
+
setError(null)
|
|
52
|
+
try {
|
|
53
|
+
await onConfirm()
|
|
54
|
+
onClose()
|
|
55
|
+
} catch (err: any) {
|
|
56
|
+
setError(err?.message || 'Failed to delete')
|
|
57
|
+
setLoading(false)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div className={css.modalOverlay}>
|
|
63
|
+
<div ref={modalRef} className={css.modalCard} role="dialog" aria-modal="true">
|
|
64
|
+
<div className={css.modalHeader}>
|
|
65
|
+
<div className={css.modalTitleWrap}>
|
|
66
|
+
<svg width="15" height="15" viewBox="0 0 16 16" fill="currentColor">
|
|
67
|
+
<path d="M6.5 1h3a.5.5 0 0 1 .5.5v1H14a.5.5 0 0 1 0 1h-.6l-.8 11.2A2 2 0 0 1 10.6 16H5.4a2 2 0 0 1-2-1.8L2.6 3.5H2a.5.5 0 0 1 0-1h4v-1a.5.5 0 0 1 .5-.5zM7 2v.5h2V2H7zm-2.4 12.4a1 1 0 0 0 1 .6h5.2a1 1 0 0 0 1-.9L12.4 3.5H3.6l.8 10.9h.2zm2.9-9.9a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 .5-.5zm3 0a.5.5 0 0 1 .5.5v7a.5.5 0 0 1-1 0v-7a.5.5 0 0 1 .5-.5z" />
|
|
68
|
+
</svg>
|
|
69
|
+
<span>{entry.isDir ? 'Delete Folder' : 'Delete File'}</span>
|
|
70
|
+
</div>
|
|
71
|
+
<button className={css.optionsIconBtn} title="Close" onClick={onClose}>
|
|
72
|
+
<svg width="12" height="12" viewBox="0 0 16 16" fill="currentColor">
|
|
73
|
+
<path d="M4.646 4.646a.5.5 0 0 1 .708 0L8 7.293l2.646-2.647a.5.5 0 0 1 .708.708L8.707 8l2.647 2.646a.5.5 0 0 1-.708.708L8 8.707l-2.646 2.647a.5.5 0 0 1-.708-.708L7.293 8 4.646 5.354a.5.5 0 0 1 0-.708z" />
|
|
74
|
+
</svg>
|
|
75
|
+
</button>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
<div className={css.modalBody}>
|
|
79
|
+
<div>
|
|
80
|
+
Are you sure you want to permanently delete this {entry.isDir ? 'directory' : 'file'}?
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<div className={css.modalItemPreview}>
|
|
84
|
+
<span className={css.iconWrap}>
|
|
85
|
+
{renderFileIcon(entry.name, entry.isDir, false, 16)}
|
|
86
|
+
</span>
|
|
87
|
+
<div className={css.modalItemInfo}>
|
|
88
|
+
<div className={css.modalItemName}>{entry.name}</div>
|
|
89
|
+
<div className={css.modalItemPath}>{relativePath}</div>
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
{entry.isDir && (
|
|
94
|
+
<div className={css.modalWarning}>
|
|
95
|
+
Warning: All nested files and subdirectories inside this folder will be permanently removed.
|
|
96
|
+
</div>
|
|
97
|
+
)}
|
|
98
|
+
|
|
99
|
+
{error && <div className={css.modalErrorBanner}>{error}</div>}
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
<div className={css.modalFooter}>
|
|
103
|
+
<button
|
|
104
|
+
ref={cancelBtnRef}
|
|
105
|
+
type="button"
|
|
106
|
+
className={css.modalBtnCancel}
|
|
107
|
+
onClick={onClose}
|
|
108
|
+
disabled={loading}
|
|
109
|
+
>
|
|
110
|
+
Cancel
|
|
111
|
+
</button>
|
|
112
|
+
<button
|
|
113
|
+
type="button"
|
|
114
|
+
className={css.modalBtnDanger}
|
|
115
|
+
onClick={handleDelete}
|
|
116
|
+
disabled={loading}
|
|
117
|
+
>
|
|
118
|
+
{loading ? 'Deleting...' : 'Delete'}
|
|
119
|
+
</button>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
)
|
|
124
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import React, { useEffect, useRef } from 'react'
|
|
2
|
+
import css from '../explorer.module.css'
|
|
3
|
+
|
|
4
|
+
interface ExplorerEmptyContextMenuProps {
|
|
5
|
+
x: number
|
|
6
|
+
y: number
|
|
7
|
+
onClose: () => void
|
|
8
|
+
onNewFile: () => void
|
|
9
|
+
onNewFolder: () => void
|
|
10
|
+
onToggleSearch: () => void
|
|
11
|
+
onExpandAll: () => void
|
|
12
|
+
onCollapseAll: () => void
|
|
13
|
+
onRefresh: () => void
|
|
14
|
+
onOpenOptions: () => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const ExplorerEmptyContextMenu: React.FC<ExplorerEmptyContextMenuProps> = ({
|
|
18
|
+
x,
|
|
19
|
+
y,
|
|
20
|
+
onClose,
|
|
21
|
+
onNewFile,
|
|
22
|
+
onNewFolder,
|
|
23
|
+
onToggleSearch,
|
|
24
|
+
onExpandAll,
|
|
25
|
+
onCollapseAll,
|
|
26
|
+
onRefresh,
|
|
27
|
+
onOpenOptions,
|
|
28
|
+
}) => {
|
|
29
|
+
const menuRef = useRef<HTMLDivElement>(null)
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const handleOutsideClick = (e: MouseEvent) => {
|
|
33
|
+
if (menuRef.current && !menuRef.current.contains(e.target as Node)) {
|
|
34
|
+
onClose()
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
38
|
+
if (e.key === 'Escape') {
|
|
39
|
+
onClose()
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
window.addEventListener('mousedown', handleOutsideClick)
|
|
43
|
+
window.addEventListener('keydown', handleKeyDown)
|
|
44
|
+
return () => {
|
|
45
|
+
window.removeEventListener('mousedown', handleOutsideClick)
|
|
46
|
+
window.removeEventListener('keydown', handleKeyDown)
|
|
47
|
+
}
|
|
48
|
+
}, [onClose])
|
|
49
|
+
|
|
50
|
+
const top = Math.min(y, window.innerHeight - 250)
|
|
51
|
+
const left = Math.min(x, window.innerWidth - 200)
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div ref={menuRef} className={css.contextMenu} style={{ top, left }}>
|
|
55
|
+
<div
|
|
56
|
+
className={css.menuItem}
|
|
57
|
+
onClick={() => {
|
|
58
|
+
onNewFile()
|
|
59
|
+
onClose()
|
|
60
|
+
}}
|
|
61
|
+
>
|
|
62
|
+
<span>New File...</span>
|
|
63
|
+
</div>
|
|
64
|
+
<div
|
|
65
|
+
className={css.menuItem}
|
|
66
|
+
onClick={() => {
|
|
67
|
+
onNewFolder()
|
|
68
|
+
onClose()
|
|
69
|
+
}}
|
|
70
|
+
>
|
|
71
|
+
<span>New Folder...</span>
|
|
72
|
+
</div>
|
|
73
|
+
<div className={css.menuDivider} />
|
|
74
|
+
<div
|
|
75
|
+
className={css.menuItem}
|
|
76
|
+
onClick={() => {
|
|
77
|
+
onToggleSearch()
|
|
78
|
+
onClose()
|
|
79
|
+
}}
|
|
80
|
+
>
|
|
81
|
+
<span>Search Files...</span>
|
|
82
|
+
<span className={css.menuShortcut}>Ctrl+F</span>
|
|
83
|
+
</div>
|
|
84
|
+
<div className={css.menuDivider} />
|
|
85
|
+
<div
|
|
86
|
+
className={css.menuItem}
|
|
87
|
+
onClick={() => {
|
|
88
|
+
onExpandAll()
|
|
89
|
+
onClose()
|
|
90
|
+
}}
|
|
91
|
+
>
|
|
92
|
+
<span>Expand All</span>
|
|
93
|
+
</div>
|
|
94
|
+
<div
|
|
95
|
+
className={css.menuItem}
|
|
96
|
+
onClick={() => {
|
|
97
|
+
onCollapseAll()
|
|
98
|
+
onClose()
|
|
99
|
+
}}
|
|
100
|
+
>
|
|
101
|
+
<span>Collapse All</span>
|
|
102
|
+
</div>
|
|
103
|
+
<div className={css.menuDivider} />
|
|
104
|
+
<div
|
|
105
|
+
className={css.menuItem}
|
|
106
|
+
onClick={() => {
|
|
107
|
+
onRefresh()
|
|
108
|
+
onClose()
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
<span>Refresh</span>
|
|
112
|
+
</div>
|
|
113
|
+
<div className={css.menuDivider} />
|
|
114
|
+
<div
|
|
115
|
+
className={css.menuItem}
|
|
116
|
+
onClick={() => {
|
|
117
|
+
onOpenOptions()
|
|
118
|
+
onClose()
|
|
119
|
+
}}
|
|
120
|
+
>
|
|
121
|
+
<span>Display Preferences...</span>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
)
|
|
125
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import css from '../explorer.module.css'
|
|
3
|
+
|
|
4
|
+
interface ExplorerHeaderProps {
|
|
5
|
+
title: string
|
|
6
|
+
searchOpen: boolean
|
|
7
|
+
optionsOpen: boolean
|
|
8
|
+
onToggleSearch: () => void
|
|
9
|
+
onToggleOptions: () => void
|
|
10
|
+
onNewFile: () => void
|
|
11
|
+
onNewFolder: () => void
|
|
12
|
+
onRefresh: () => void
|
|
13
|
+
onExpandAll: () => void
|
|
14
|
+
onCollapseAll: () => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const ExplorerHeader: React.FC<ExplorerHeaderProps> = ({
|
|
18
|
+
title,
|
|
19
|
+
searchOpen,
|
|
20
|
+
optionsOpen,
|
|
21
|
+
onToggleSearch,
|
|
22
|
+
onToggleOptions,
|
|
23
|
+
onNewFile,
|
|
24
|
+
onNewFolder,
|
|
25
|
+
onRefresh,
|
|
26
|
+
onExpandAll,
|
|
27
|
+
onCollapseAll,
|
|
28
|
+
}) => {
|
|
29
|
+
return (
|
|
30
|
+
<div className={css.header}>
|
|
31
|
+
<span className={css.headerTitle} title={title}>
|
|
32
|
+
{title}
|
|
33
|
+
</span>
|
|
34
|
+
<div className={css.headerActions}>
|
|
35
|
+
<button
|
|
36
|
+
className={`${css.iconBtn} ${searchOpen ? css.iconBtnActive : ''}`}
|
|
37
|
+
title="Search files and folders"
|
|
38
|
+
onClick={onToggleSearch}
|
|
39
|
+
>
|
|
40
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
|
41
|
+
<path d="M11.742 10.344a6.5 6.5 0 1 0-1.397 1.398h-.001c.03.04.062.078.098.115l3.85 3.85a1 1 0 0 0 1.415-1.414l-3.85-3.85a1.007 1.007 0 0 0-.115-.1zM12 6.5a5.5 5.5 0 1 1-11 0 5.5 5.5 0 0 1 11 0z"/>
|
|
42
|
+
</svg>
|
|
43
|
+
</button>
|
|
44
|
+
<button className={css.iconBtn} title="New File" onClick={onNewFile}>
|
|
45
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
|
46
|
+
<path d="M9 1H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V6L9 1zm0 1.5L12.5 6H9V2.5zM8 8.5V11h1V8.5h2.5v-1H9V5H8v2.5H5.5v1H8z"/>
|
|
47
|
+
</svg>
|
|
48
|
+
</button>
|
|
49
|
+
<button className={css.iconBtn} title="New Folder" onClick={onNewFolder}>
|
|
50
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
|
51
|
+
<path d="M7 2l2 2h5a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h5zm2.5 6.5H8V6H7v2.5H4.5v1H7V12h1V9.5h2.5v-1z"/>
|
|
52
|
+
</svg>
|
|
53
|
+
</button>
|
|
54
|
+
<button className={css.iconBtn} title="Refresh" onClick={onRefresh}>
|
|
55
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
|
56
|
+
<path d="M13.65 2.35A7.958 7.958 0 0 0 8 0a8 8 0 1 0 8 8h-2a6 6 0 1 1-1.76-4.24l-2.24 2.24H16V0l-2.35 2.35z"/>
|
|
57
|
+
</svg>
|
|
58
|
+
</button>
|
|
59
|
+
<button className={css.iconBtn} title="Expand All" onClick={onExpandAll}>
|
|
60
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
|
61
|
+
<path d="M4.646 5.854a.5.5 0 0 1 0-.708l3-3a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8 3.207 5.354 5.854a.5.5 0 0 1-.708 0zm0 4.292a.5.5 0 0 0 0 .708l3 3a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8 12.793l-2.646-2.647a.5.5 0 0 0-.708 0z"/>
|
|
62
|
+
</svg>
|
|
63
|
+
</button>
|
|
64
|
+
<button className={css.iconBtn} title="Collapse All" onClick={onCollapseAll}>
|
|
65
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
|
66
|
+
<path d="M4.646 2.854a.5.5 0 0 0 0 .708l3 3a.5.5 0 0 0 .708 0l3-3a.5.5 0 0 0-.708-.708L8 5.293 5.354 2.854a.5.5 0 0 0-.708 0zm0 10.292a.5.5 0 0 1 0-.708l3-3a.5.5 0 0 1 .708 0l3 3a.5.5 0 0 1-.708.708L8 10.707l-2.646 2.647a.5.5 0 0 1-.708 0z"/>
|
|
67
|
+
</svg>
|
|
68
|
+
</button>
|
|
69
|
+
<button
|
|
70
|
+
className={`${css.iconBtn} ${optionsOpen ? css.iconBtnActive : ''}`}
|
|
71
|
+
title="Explorer display preferences"
|
|
72
|
+
onClick={onToggleOptions}
|
|
73
|
+
>
|
|
74
|
+
<svg width="16" height="16" viewBox="0 0 16 16" fill="currentColor">
|
|
75
|
+
<path d="M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 1 0-5.86 2.929 2.929 0 0 1 0 5.858z"/>
|
|
76
|
+
</svg>
|
|
77
|
+
</button>
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
)
|
|
81
|
+
}
|