dsh-better-workspace 0.11.3 → 0.11.4
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/dsh.plugin.json +1 -1
- package/package.json +1 -1
- package/src/client.js +181 -26
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-external/dsh-better-workspace",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.4",
|
|
4
4
|
"main": "./src/index.js",
|
|
5
5
|
"description": "侧边栏工作区层级树:名称中的 / 即虚拟分组(web/前端 · web/后端),添加工作区的目录流附所属分组弹窗,重命名即时重排层级,可新建空分组。Hierarchy tree for the DSH sidebar workspace list.",
|
|
6
6
|
"engines": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-better-workspace",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.4",
|
|
4
4
|
"description": "DSH web plugin: a hierarchical workspace tree for the sidebar. Workspace titles containing \"/\" group into virtual folders (web/前端 · web/后端); the add-workspace directory flow gains a parent-group popup; renames re-derive the tree; a new-folder button creates empty levels.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
package/src/client.js
CHANGED
|
@@ -127,6 +127,9 @@ window.__ModuleLoader__.load({
|
|
|
127
127
|
'browse.showHidden': '显示隐藏文件',
|
|
128
128
|
'browse.editPath': '编辑路径',
|
|
129
129
|
'browse.select': '选择此文件夹',
|
|
130
|
+
'browse.enter': '进入',
|
|
131
|
+
'browse.drives': '盘符',
|
|
132
|
+
'browse.selectNamed': '选择「{name}」',
|
|
130
133
|
'error.title': '出错了',
|
|
131
134
|
'cancel': '取消',
|
|
132
135
|
'create': '创建',
|
|
@@ -246,6 +249,9 @@ window.__ModuleLoader__.load({
|
|
|
246
249
|
'browse.showHidden': 'Show hidden files',
|
|
247
250
|
'browse.editPath': 'Edit path',
|
|
248
251
|
'browse.select': 'Use this folder',
|
|
252
|
+
'browse.enter': 'Open',
|
|
253
|
+
'browse.drives': 'Drives',
|
|
254
|
+
'browse.selectNamed': 'Use "{name}"',
|
|
249
255
|
'error.title': 'Something went wrong',
|
|
250
256
|
'cancel': 'Cancel',
|
|
251
257
|
'create': 'Create',
|
|
@@ -373,6 +379,9 @@ window.__ModuleLoader__.load({
|
|
|
373
379
|
'browse.showHidden': 'إظهار الملفات المخفية',
|
|
374
380
|
'browse.editPath': 'تعديل المسار',
|
|
375
381
|
'browse.select': 'استخدام هذا المجلد',
|
|
382
|
+
'browse.enter': 'فتح',
|
|
383
|
+
'browse.drives': 'الأقراص',
|
|
384
|
+
'browse.selectNamed': 'استخدام "{name}"',
|
|
376
385
|
'error.title': 'حدث خطأ ما',
|
|
377
386
|
'cancel': 'إلغاء',
|
|
378
387
|
'create': 'إنشاء',
|
|
@@ -492,6 +501,9 @@ window.__ModuleLoader__.load({
|
|
|
492
501
|
'browse.showHidden': 'Versteckte Dateien anzeigen',
|
|
493
502
|
'browse.editPath': 'Pfad bearbeiten',
|
|
494
503
|
'browse.select': 'Diesen Ordner verwenden',
|
|
504
|
+
'browse.enter': 'Öffnen',
|
|
505
|
+
'browse.drives': 'Laufwerke',
|
|
506
|
+
'browse.selectNamed': '"{name}" verwenden',
|
|
495
507
|
'error.title': 'Etwas ist schiefgelaufen',
|
|
496
508
|
'cancel': 'Abbrechen',
|
|
497
509
|
'create': 'Erstellen',
|
|
@@ -611,6 +623,9 @@ window.__ModuleLoader__.load({
|
|
|
611
623
|
'browse.showHidden': 'Afficher les fichiers cachés',
|
|
612
624
|
'browse.editPath': 'Modifier le chemin',
|
|
613
625
|
'browse.select': 'Utiliser ce dossier',
|
|
626
|
+
'browse.enter': 'Ouvrir',
|
|
627
|
+
'browse.drives': 'Lecteurs',
|
|
628
|
+
'browse.selectNamed': 'Utiliser « {name} »',
|
|
614
629
|
'error.title': 'Une erreur est survenue',
|
|
615
630
|
'cancel': 'Annuler',
|
|
616
631
|
'create': 'Créer',
|
|
@@ -730,6 +745,9 @@ window.__ModuleLoader__.load({
|
|
|
730
745
|
'browse.showHidden': 'छिपी फ़ाइलें दिखाएँ',
|
|
731
746
|
'browse.editPath': 'पथ संपादित करें',
|
|
732
747
|
'browse.select': 'यह फ़ोल्डर चुनें',
|
|
748
|
+
'browse.enter': 'खोलें',
|
|
749
|
+
'browse.drives': 'ड्राइव',
|
|
750
|
+
'browse.selectNamed': '"{name}" चुनें',
|
|
733
751
|
'error.title': 'कुछ गड़बड़ हो गई',
|
|
734
752
|
'cancel': 'रद्द करें',
|
|
735
753
|
'create': 'बनाएँ',
|
|
@@ -849,6 +867,9 @@ window.__ModuleLoader__.load({
|
|
|
849
867
|
'browse.showHidden': 'Tampilkan file tersembunyi',
|
|
850
868
|
'browse.editPath': 'Edit jalur',
|
|
851
869
|
'browse.select': 'Gunakan folder ini',
|
|
870
|
+
'browse.enter': 'Buka',
|
|
871
|
+
'browse.drives': 'Drive',
|
|
872
|
+
'browse.selectNamed': 'Gunakan "{name}"',
|
|
852
873
|
'error.title': 'Terjadi kesalahan',
|
|
853
874
|
'cancel': 'Batal',
|
|
854
875
|
'create': 'Buat',
|
|
@@ -968,6 +989,9 @@ window.__ModuleLoader__.load({
|
|
|
968
989
|
'browse.showHidden': 'Mostra file nascosti',
|
|
969
990
|
'browse.editPath': 'Modifica percorso',
|
|
970
991
|
'browse.select': 'Usa questa cartella',
|
|
992
|
+
'browse.enter': 'Apri',
|
|
993
|
+
'browse.drives': 'Unità',
|
|
994
|
+
'browse.selectNamed': 'Usa "{name}"',
|
|
971
995
|
'error.title': 'Si è verificato un errore',
|
|
972
996
|
'cancel': 'Annulla',
|
|
973
997
|
'create': 'Crea',
|
|
@@ -1087,6 +1111,9 @@ window.__ModuleLoader__.load({
|
|
|
1087
1111
|
'browse.showHidden': '隠しファイルを表示',
|
|
1088
1112
|
'browse.editPath': 'パスを編集',
|
|
1089
1113
|
'browse.select': 'このフォルダーを選択',
|
|
1114
|
+
'browse.enter': '開く',
|
|
1115
|
+
'browse.drives': 'ドライブ',
|
|
1116
|
+
'browse.selectNamed': '「{name}」を選択',
|
|
1090
1117
|
'error.title': 'エラーが発生しました',
|
|
1091
1118
|
'cancel': 'キャンセル',
|
|
1092
1119
|
'create': '作成',
|
|
@@ -1206,6 +1233,9 @@ window.__ModuleLoader__.load({
|
|
|
1206
1233
|
'browse.showHidden': '숨김 파일 표시',
|
|
1207
1234
|
'browse.editPath': '경로 편집',
|
|
1208
1235
|
'browse.select': '이 폴더 선택',
|
|
1236
|
+
'browse.enter': '열기',
|
|
1237
|
+
'browse.drives': '드라이브',
|
|
1238
|
+
'browse.selectNamed': '"{name}" 선택',
|
|
1209
1239
|
'error.title': '문제가 발생했습니다',
|
|
1210
1240
|
'cancel': '취소',
|
|
1211
1241
|
'create': '만들기',
|
|
@@ -1325,6 +1355,9 @@ window.__ModuleLoader__.load({
|
|
|
1325
1355
|
'browse.showHidden': 'Verborgen bestanden tonen',
|
|
1326
1356
|
'browse.editPath': 'Pad bewerken',
|
|
1327
1357
|
'browse.select': 'Deze map gebruiken',
|
|
1358
|
+
'browse.enter': 'Openen',
|
|
1359
|
+
'browse.drives': 'Stations',
|
|
1360
|
+
'browse.selectNamed': '"{name}" gebruiken',
|
|
1328
1361
|
'error.title': 'Er is iets misgegaan',
|
|
1329
1362
|
'cancel': 'Annuleren',
|
|
1330
1363
|
'create': 'Aanmaken',
|
|
@@ -1444,6 +1477,9 @@ window.__ModuleLoader__.load({
|
|
|
1444
1477
|
'browse.showHidden': 'Pokaż ukryte pliki',
|
|
1445
1478
|
'browse.editPath': 'Edytuj ścieżkę',
|
|
1446
1479
|
'browse.select': 'Użyj tego folderu',
|
|
1480
|
+
'browse.enter': 'Otwórz',
|
|
1481
|
+
'browse.drives': 'Dyski',
|
|
1482
|
+
'browse.selectNamed': 'Użyj "{name}"',
|
|
1447
1483
|
'error.title': 'Coś poszło nie tak',
|
|
1448
1484
|
'cancel': 'Anuluj',
|
|
1449
1485
|
'create': 'Utwórz',
|
|
@@ -1563,6 +1599,9 @@ window.__ModuleLoader__.load({
|
|
|
1563
1599
|
'browse.showHidden': 'Mostrar arquivos ocultos',
|
|
1564
1600
|
'browse.editPath': 'Editar caminho',
|
|
1565
1601
|
'browse.select': 'Usar esta pasta',
|
|
1602
|
+
'browse.enter': 'Abrir',
|
|
1603
|
+
'browse.drives': 'Unidades',
|
|
1604
|
+
'browse.selectNamed': 'Usar "{name}"',
|
|
1566
1605
|
'error.title': 'Algo deu errado',
|
|
1567
1606
|
'cancel': 'Cancelar',
|
|
1568
1607
|
'create': 'Criar',
|
|
@@ -1682,6 +1721,9 @@ window.__ModuleLoader__.load({
|
|
|
1682
1721
|
'browse.showHidden': 'Показывать скрытые файлы',
|
|
1683
1722
|
'browse.editPath': 'Изменить путь',
|
|
1684
1723
|
'browse.select': 'Выбрать эту папку',
|
|
1724
|
+
'browse.enter': 'Открыть',
|
|
1725
|
+
'browse.drives': 'Диски',
|
|
1726
|
+
'browse.selectNamed': 'Выбрать «{name}»',
|
|
1685
1727
|
'error.title': 'Что-то пошло не так',
|
|
1686
1728
|
'cancel': 'Отмена',
|
|
1687
1729
|
'create': 'Создать',
|
|
@@ -1801,6 +1843,9 @@ window.__ModuleLoader__.load({
|
|
|
1801
1843
|
'browse.showHidden': 'Visa dolda filer',
|
|
1802
1844
|
'browse.editPath': 'Redigera sökväg',
|
|
1803
1845
|
'browse.select': 'Använd den här mappen',
|
|
1846
|
+
'browse.enter': 'Öppna',
|
|
1847
|
+
'browse.drives': 'Enheter',
|
|
1848
|
+
'browse.selectNamed': 'Använd "{name}"',
|
|
1804
1849
|
'error.title': 'Något gick fel',
|
|
1805
1850
|
'cancel': 'Avbryt',
|
|
1806
1851
|
'create': 'Skapa',
|
|
@@ -1920,6 +1965,9 @@ window.__ModuleLoader__.load({
|
|
|
1920
1965
|
'browse.showHidden': 'แสดงไฟล์ที่ซ่อนอยู่',
|
|
1921
1966
|
'browse.editPath': 'แก้ไขเส้นทาง',
|
|
1922
1967
|
'browse.select': 'ใช้โฟลเดอร์นี้',
|
|
1968
|
+
'browse.enter': 'เปิด',
|
|
1969
|
+
'browse.drives': 'ไดรฟ์',
|
|
1970
|
+
'browse.selectNamed': 'ใช้ "{name}"',
|
|
1923
1971
|
'error.title': 'เกิดข้อผิดพลาด',
|
|
1924
1972
|
'cancel': 'ยกเลิก',
|
|
1925
1973
|
'create': 'สร้าง',
|
|
@@ -2039,6 +2087,9 @@ window.__ModuleLoader__.load({
|
|
|
2039
2087
|
'browse.showHidden': 'Gizli dosyaları göster',
|
|
2040
2088
|
'browse.editPath': 'Yolu düzenle',
|
|
2041
2089
|
'browse.select': 'Bu klasörü kullan',
|
|
2090
|
+
'browse.enter': 'Aç',
|
|
2091
|
+
'browse.drives': 'Sürücüler',
|
|
2092
|
+
'browse.selectNamed': '"{name}" kullan',
|
|
2042
2093
|
'error.title': 'Bir şeyler ters gitti',
|
|
2043
2094
|
'cancel': 'İptal',
|
|
2044
2095
|
'create': 'Oluştur',
|
|
@@ -2158,6 +2209,9 @@ window.__ModuleLoader__.load({
|
|
|
2158
2209
|
'browse.showHidden': 'Hiện tệp ẩn',
|
|
2159
2210
|
'browse.editPath': 'Sửa đường dẫn',
|
|
2160
2211
|
'browse.select': 'Dùng thư mục này',
|
|
2212
|
+
'browse.enter': 'Mở',
|
|
2213
|
+
'browse.drives': 'Ổ đĩa',
|
|
2214
|
+
'browse.selectNamed': 'Dùng "{name}"',
|
|
2161
2215
|
'error.title': 'Đã xảy ra lỗi',
|
|
2162
2216
|
'cancel': 'Hủy',
|
|
2163
2217
|
'create': 'Tạo',
|
|
@@ -2277,6 +2331,9 @@ window.__ModuleLoader__.load({
|
|
|
2277
2331
|
'browse.showHidden': '顯示隱藏檔案',
|
|
2278
2332
|
'browse.editPath': '編輯路徑',
|
|
2279
2333
|
'browse.select': '揀呢個資料夾',
|
|
2334
|
+
'browse.enter': '入去',
|
|
2335
|
+
'browse.drives': '磁碟',
|
|
2336
|
+
'browse.selectNamed': '揀「{name}」',
|
|
2280
2337
|
'error.title': '出咗錯',
|
|
2281
2338
|
'cancel': '取消',
|
|
2282
2339
|
'create': '建立',
|
|
@@ -2396,6 +2453,9 @@ window.__ModuleLoader__.load({
|
|
|
2396
2453
|
'browse.showHidden': '顯示隱藏檔案',
|
|
2397
2454
|
'browse.editPath': '編輯路徑',
|
|
2398
2455
|
'browse.select': '揀呢個資料夾',
|
|
2456
|
+
'browse.enter': '入去',
|
|
2457
|
+
'browse.drives': '磁碟',
|
|
2458
|
+
'browse.selectNamed': '揀「{name}」',
|
|
2399
2459
|
'error.title': '出咗錯',
|
|
2400
2460
|
'cancel': '取消',
|
|
2401
2461
|
'create': '建立',
|
|
@@ -2515,6 +2575,9 @@ window.__ModuleLoader__.load({
|
|
|
2515
2575
|
'browse.showHidden': '顯示隱藏檔案',
|
|
2516
2576
|
'browse.editPath': '編輯路徑',
|
|
2517
2577
|
'browse.select': '選擇此資料夾',
|
|
2578
|
+
'browse.enter': '進入',
|
|
2579
|
+
'browse.drives': '磁碟',
|
|
2580
|
+
'browse.selectNamed': '選擇「{name}」',
|
|
2518
2581
|
'error.title': '發生錯誤',
|
|
2519
2582
|
'cancel': '取消',
|
|
2520
2583
|
'create': '建立',
|
|
@@ -3048,10 +3111,17 @@ window.__ModuleLoader__.load({
|
|
|
3048
3111
|
'.bw-browse-edit svg{width:14px;height:14px}',
|
|
3049
3112
|
'.bw-browse-edit-row{display:flex;gap:6px;align-items:center}',
|
|
3050
3113
|
'.bw-browse-list{display:flex;flex-direction:column;gap:1px;height:240px;overflow:auto;padding:4px;box-sizing:border-box;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.22));border-radius:8px;background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.06))}',
|
|
3051
|
-
'.bw-browse-row{display:flex;align-items:center;gap:8px;width:100%;flex:none;text-align:left;border:none;background:transparent;color:var(--dsw-alias-label-primary,#e6e6e6);font:inherit;font-size:12.5px;padding:5px 8px;border-radius:6px;cursor:pointer}',
|
|
3114
|
+
'.bw-browse-row{display:flex;align-items:center;gap:8px;width:100%;flex:none;text-align:left;border:none;background:transparent;color:var(--dsw-alias-label-primary,#e6e6e6);font:inherit;font-size:12.5px;padding:5px 8px;border-radius:6px;cursor:pointer;user-select:none}',
|
|
3052
3115
|
'.bw-browse-row:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12))}',
|
|
3053
3116
|
'.bw-browse-row:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5b8def);outline-offset:-2px}',
|
|
3054
3117
|
'.bw-browse-row svg{width:15px;height:15px;flex:none;color:var(--dsw-alias-label-secondary,#b8b8b8)}',
|
|
3118
|
+
'.bw-browse-row-on{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));box-shadow:inset 0 0 0 1px var(--dsw-alias-brand-primary,#5b8def)}',
|
|
3119
|
+
'.bw-browse-open{flex:none;width:22px;height:22px;padding:0;border:none;border-radius:5px;background:transparent;color:var(--dsw-alias-label-tertiary,#9a9a9a);font:inherit;font-size:15px;line-height:1;cursor:pointer}',
|
|
3120
|
+
'.bw-browse-open:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.16));color:var(--dsw-alias-label-primary,#e6e6e6)}',
|
|
3121
|
+
'.bw-browse-drives{display:flex;align-items:center;gap:4px;margin-left:auto}',
|
|
3122
|
+
'.bw-browse-drive{min-width:30px;padding:3px 6px;border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.25));border-radius:5px;background:transparent;color:var(--dsw-alias-label-secondary,#b8b8b8);font:inherit;font-size:11.5px;cursor:pointer}',
|
|
3123
|
+
'.bw-browse-drive:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-primary,#e6e6e6)}',
|
|
3124
|
+
'.bw-browse-drive-on{border-color:var(--dsw-alias-brand-primary,#5b8def);color:var(--dsw-alias-label-primary,#e6e6e6)}',
|
|
3055
3125
|
'.bw-browse-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
|
|
3056
3126
|
'.bw-browse-note{font-size:12px;color:var(--dsw-alias-label-tertiary,#9a9a9a);padding:10px 8px;text-align:center}',
|
|
3057
3127
|
'.bw-browse-new-row{display:flex;gap:6px;align-items:center}',
|
|
@@ -3439,6 +3509,14 @@ window.__ModuleLoader__.load({
|
|
|
3439
3509
|
* owner's error surface belongs to the pick/create conversation, and a
|
|
3440
3510
|
* denied listing must never close the flow.
|
|
3441
3511
|
*/
|
|
3512
|
+
/**
|
|
3513
|
+
* Page-lifetime cache of the Windows drive letters this Host actually
|
|
3514
|
+
* serves. The browse API can only list a path, so the first open probes the
|
|
3515
|
+
* usual letters once (six cheap listings, a missing drive fails silently)
|
|
3516
|
+
* and every later open renders the chips straight from here.
|
|
3517
|
+
*/
|
|
3518
|
+
const browseDrives = { probed: false, probing: false, list: [] }
|
|
3519
|
+
|
|
3442
3520
|
function DirectoryBrowseDialog(props) {
|
|
3443
3521
|
const { open, busy, listDirectory, createDirectory, onPick, onClose, t } = props
|
|
3444
3522
|
const [listing, setListing] = React.useState(null)
|
|
@@ -3451,14 +3529,18 @@ window.__ModuleLoader__.load({
|
|
|
3451
3529
|
const [createName, setCreateName] = React.useState('')
|
|
3452
3530
|
const [createBusy, setCreateBusy] = React.useState(false)
|
|
3453
3531
|
const [createError, setCreateError] = React.useState('')
|
|
3532
|
+
const [selected, setSelected] = React.useState('')
|
|
3533
|
+
const [drives, setDrives] = React.useState(browseDrives.list)
|
|
3454
3534
|
// Guards a superseded scan: a slow level must not overwrite a newer one.
|
|
3455
3535
|
const seqRef = React.useRef(0)
|
|
3536
|
+
const crumbsRef = React.useRef(null)
|
|
3456
3537
|
|
|
3457
|
-
const go = (path) => {
|
|
3538
|
+
const go = (path, keepSelected) => {
|
|
3458
3539
|
const seq = seqRef.current + 1
|
|
3459
3540
|
seqRef.current = seq
|
|
3460
3541
|
setLoading(true)
|
|
3461
3542
|
setError('')
|
|
3543
|
+
if (keepSelected !== true) setSelected('')
|
|
3462
3544
|
Promise.resolve()
|
|
3463
3545
|
.then(() => listDirectory(path, undefined))
|
|
3464
3546
|
.then((next) => {
|
|
@@ -3484,27 +3566,59 @@ window.__ModuleLoader__.load({
|
|
|
3484
3566
|
setCreateBusy(false)
|
|
3485
3567
|
setCreateError('')
|
|
3486
3568
|
setShowHidden(false)
|
|
3569
|
+
setSelected('')
|
|
3487
3570
|
go(undefined)
|
|
3488
3571
|
return () => { seqRef.current += 1 }
|
|
3489
3572
|
}, [open])
|
|
3490
3573
|
|
|
3574
|
+
const currentPath = listing && typeof listing.path === 'string' ? listing.path : ''
|
|
3575
|
+
const homePath = listing && typeof listing.home === 'string' ? listing.home : ''
|
|
3576
|
+
const windowsHost = homePath.indexOf('\\') !== -1
|
|
3577
|
+
const hasListing = listing !== null
|
|
3578
|
+
|
|
3579
|
+
// Windows drives are separate filesystem roots and the browse API can only
|
|
3580
|
+
// list a path, so probe the usual letters once per page (cached above) and
|
|
3581
|
+
// offer the ones that answered. A drive nobody probed is still reachable
|
|
3582
|
+
// through the path editor, which is the only general answer anyway.
|
|
3583
|
+
React.useEffect(() => {
|
|
3584
|
+
if (!open || !hasListing || !windowsHost) return undefined
|
|
3585
|
+
if (browseDrives.probed || browseDrives.probing) {
|
|
3586
|
+
if (browseDrives.list.length > 0) setDrives(browseDrives.list)
|
|
3587
|
+
return undefined
|
|
3588
|
+
}
|
|
3589
|
+
browseDrives.probing = true
|
|
3590
|
+
let alive = true
|
|
3591
|
+
const letters = ['C:', 'D:', 'E:', 'F:', 'G:', 'H:']
|
|
3592
|
+
Promise.all(letters.map((letter) => Promise.resolve()
|
|
3593
|
+
.then(() => listDirectory(letter + '\\', undefined))
|
|
3594
|
+
.then(() => letter, () => null)))
|
|
3595
|
+
.then((found) => {
|
|
3596
|
+
browseDrives.list = found.filter(Boolean)
|
|
3597
|
+
browseDrives.probed = true
|
|
3598
|
+
browseDrives.probing = false
|
|
3599
|
+
if (alive) setDrives(browseDrives.list)
|
|
3600
|
+
}, () => { browseDrives.probing = false })
|
|
3601
|
+
return () => { alive = false }
|
|
3602
|
+
}, [open, hasListing, windowsHost])
|
|
3603
|
+
|
|
3604
|
+
// A deep path must show its TAIL — that is where you are — so the chain
|
|
3605
|
+
// keeps its right edge in view instead of cutting the last crumb off.
|
|
3606
|
+
React.useEffect(() => {
|
|
3607
|
+
const node = crumbsRef.current
|
|
3608
|
+
if (node) node.scrollLeft = node.scrollWidth
|
|
3609
|
+
}, [listing])
|
|
3610
|
+
|
|
3491
3611
|
if (!open) return null
|
|
3492
3612
|
|
|
3493
|
-
//
|
|
3494
|
-
//
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
const home = listing && typeof listing.home === 'string' ? listing.home : ''
|
|
3498
|
-
const at = home === '' ? -1 : all.findIndex((crumb) => crumb.path === home)
|
|
3499
|
-
const tail = at === -1 ? all : all.slice(at)
|
|
3500
|
-
return tail.map((crumb, index) => (index === 0 && at !== -1
|
|
3501
|
-
? { path: crumb.path, name: t('browse.home') }
|
|
3502
|
-
: crumb))
|
|
3503
|
-
})()
|
|
3504
|
-
const currentPath = listing && typeof listing.path === 'string' ? listing.path : ''
|
|
3613
|
+
// The whole ancestry from the filesystem ROOT: rooting the chain at Home
|
|
3614
|
+
// (the official dialog's choice) hides where the listed level actually is,
|
|
3615
|
+
// and the drive crumb answers "which disk am I on" at a glance.
|
|
3616
|
+
const crumbs = listing && Array.isArray(listing.crumbs) ? listing.crumbs : []
|
|
3505
3617
|
const parentPath = crumbs.length > 1 ? crumbs[crumbs.length - 2].path : ''
|
|
3506
3618
|
const entries = (listing && Array.isArray(listing.entries) ? listing.entries : [])
|
|
3507
3619
|
.filter((entry) => showHidden || !entry.hidden)
|
|
3620
|
+
const selectedEntry = entries.find((entry) => entry.path === selected) || null
|
|
3621
|
+
const targetPath = selectedEntry ? selectedEntry.path : currentPath
|
|
3508
3622
|
|
|
3509
3623
|
const submitDraft = () => {
|
|
3510
3624
|
const path = draft.trim()
|
|
@@ -3518,12 +3632,14 @@ window.__ModuleLoader__.load({
|
|
|
3518
3632
|
setCreateBusy(true)
|
|
3519
3633
|
setCreateError('')
|
|
3520
3634
|
Promise.resolve()
|
|
3521
|
-
.then(() =>
|
|
3522
|
-
.then(() => {
|
|
3635
|
+
.then((created) => {
|
|
3523
3636
|
setCreateBusy(false)
|
|
3524
3637
|
setCreating(false)
|
|
3525
3638
|
setCreateName('')
|
|
3526
|
-
|
|
3639
|
+
// Land on the folder just made: it is almost always the one the
|
|
3640
|
+
// operator wants to adopt next.
|
|
3641
|
+
if (typeof created === 'string' && created !== '') setSelected(created)
|
|
3642
|
+
go(currentPath, true)
|
|
3527
3643
|
})
|
|
3528
3644
|
.catch((reason) => {
|
|
3529
3645
|
setCreateBusy(false)
|
|
@@ -3556,7 +3672,7 @@ window.__ModuleLoader__.load({
|
|
|
3556
3672
|
disabled: parentPath === '',
|
|
3557
3673
|
onClick: () => { if (parentPath !== '') go(parentPath) },
|
|
3558
3674
|
}, '↑'),
|
|
3559
|
-
E('div', { className: 'bw-browse-crumbs' },
|
|
3675
|
+
E('div', { className: 'bw-browse-crumbs', ref: crumbsRef },
|
|
3560
3676
|
crumbs.map((crumb, index) => (index === crumbs.length - 1
|
|
3561
3677
|
? E('span', { key: crumb.path, className: 'bw-browse-crumb bw-browse-current', title: crumb.path }, crumb.name)
|
|
3562
3678
|
: E('button', {
|
|
@@ -3576,13 +3692,32 @@ window.__ModuleLoader__.load({
|
|
|
3576
3692
|
}, icon('IconEditOutline16')),
|
|
3577
3693
|
)
|
|
3578
3694
|
|
|
3579
|
-
|
|
3695
|
+
// One click SELECTS a row (the footer button then adopts it — no need to
|
|
3696
|
+
// step inside just to pick a folder), a double click or the row's chevron
|
|
3697
|
+
// enters it. Touch needs the chevron: a double tap is not a gesture.
|
|
3698
|
+
const rows = entries.map((entry) => E('div', {
|
|
3580
3699
|
key: entry.path,
|
|
3581
|
-
|
|
3582
|
-
className: 'bw-browse-row',
|
|
3700
|
+
className: cls('bw-browse-row', selected === entry.path && 'bw-browse-row-on'),
|
|
3583
3701
|
title: entry.path,
|
|
3584
|
-
|
|
3585
|
-
|
|
3702
|
+
role: 'button',
|
|
3703
|
+
tabIndex: 0,
|
|
3704
|
+
onClick: () => setSelected((current) => (current === entry.path ? '' : entry.path)),
|
|
3705
|
+
onDoubleClick: () => go(entry.path),
|
|
3706
|
+
onKeyDown: (event) => {
|
|
3707
|
+
if (event.key === 'Enter') go(entry.path)
|
|
3708
|
+
else if (event.key === ' ') { event.preventDefault(); setSelected((current) => (current === entry.path ? '' : entry.path)) }
|
|
3709
|
+
},
|
|
3710
|
+
},
|
|
3711
|
+
icon('IconFolderClose16'),
|
|
3712
|
+
E('span', { className: 'bw-browse-name' }, entry.name),
|
|
3713
|
+
E('button', {
|
|
3714
|
+
type: 'button',
|
|
3715
|
+
className: 'bw-browse-open',
|
|
3716
|
+
title: t('browse.enter'),
|
|
3717
|
+
'aria-label': t('browse.enter'),
|
|
3718
|
+
onClick: (event) => { event.stopPropagation(); go(entry.path) },
|
|
3719
|
+
}, '›'),
|
|
3720
|
+
))
|
|
3586
3721
|
|
|
3587
3722
|
const body = loading
|
|
3588
3723
|
? E('div', { className: 'bw-browse-note' }, t('browse.loading'))
|
|
@@ -3619,6 +3754,12 @@ window.__ModuleLoader__.load({
|
|
|
3619
3754
|
// two browsing tools sit on their own row above the list instead of
|
|
3620
3755
|
// sharing the footer with the commit pair (they used to wrap there).
|
|
3621
3756
|
const tools = E('div', { className: 'bw-browse-tools' },
|
|
3757
|
+
E('button', {
|
|
3758
|
+
type: 'button',
|
|
3759
|
+
className: cls('bw-browse-tool', currentPath !== '' && currentPath === homePath && 'bw-browse-tool-on'),
|
|
3760
|
+
disabled: homePath === '' || currentPath === homePath,
|
|
3761
|
+
onClick: () => { if (homePath !== '') go(homePath) },
|
|
3762
|
+
}, t('browse.home')),
|
|
3622
3763
|
E('button', {
|
|
3623
3764
|
type: 'button',
|
|
3624
3765
|
className: cls('bw-browse-tool', showHidden && 'bw-browse-tool-on'),
|
|
@@ -3631,15 +3772,29 @@ window.__ModuleLoader__.load({
|
|
|
3631
3772
|
disabled: creating || currentPath === '',
|
|
3632
3773
|
onClick: () => { setCreating(true); setCreateName(''); setCreateError('') },
|
|
3633
3774
|
}, t('browse.newFolder')),
|
|
3775
|
+
drives.length > 1
|
|
3776
|
+
? E('div', {
|
|
3777
|
+
className: 'bw-browse-drives',
|
|
3778
|
+
title: t('browse.drives'),
|
|
3779
|
+
role: 'group',
|
|
3780
|
+
'aria-label': t('browse.drives'),
|
|
3781
|
+
}, drives.map((drive) => E('button', {
|
|
3782
|
+
key: drive,
|
|
3783
|
+
type: 'button',
|
|
3784
|
+
className: cls('bw-browse-drive', currentPath.slice(0, 2).toUpperCase() === drive && 'bw-browse-drive-on'),
|
|
3785
|
+
title: drive + '\\',
|
|
3786
|
+
onClick: () => go(drive + '\\'),
|
|
3787
|
+
}, drive)))
|
|
3788
|
+
: null,
|
|
3634
3789
|
)
|
|
3635
3790
|
|
|
3636
3791
|
const footer = E('div', { className: 'bw-browse-actions' },
|
|
3637
3792
|
E(BTN, { variant: 'outline', onClick: onClose, disabled: busy === true }, t('cancel')),
|
|
3638
3793
|
E(BTN, {
|
|
3639
3794
|
variant: 'primary',
|
|
3640
|
-
disabled: busy === true ||
|
|
3641
|
-
onClick: () => { if (
|
|
3642
|
-
}, t('browse.select')),
|
|
3795
|
+
disabled: busy === true || targetPath === '',
|
|
3796
|
+
onClick: () => { if (targetPath !== '') onPick(targetPath) },
|
|
3797
|
+
}, selectedEntry !== null ? t('browse.selectNamed', { name: selectedEntry.name }) : t('browse.select')),
|
|
3643
3798
|
)
|
|
3644
3799
|
|
|
3645
3800
|
return E(ui.Modal, {
|