dsh-better-workspace 0.12.0 → 0.12.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/dsh.plugin.json +1 -1
- package/package.json +1 -1
- package/src/client.js +870 -25
package/dsh.plugin.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-external/dsh-better-workspace",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"main": "./src/index.js",
|
|
5
5
|
"description": "侧边栏工作区两层层级树:工作区按磁盘目录嵌套(官方 workspace-tree 语义),名称中的 / 即虚拟分组(web/前端 · web/后端);添加工作区由官方目录流承载。Two-layer 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.12.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "侧边栏工作区两层层级树:磁盘目录嵌套(官方 workspace-tree 语义)+ 名称 / 虚拟分组,添加工作区走官方目录流。Two-layer workspace tree for the DSH sidebar: disk-path nesting plus \"/\"-name groups; add-workspace rides the official directory flow.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
package/src/client.js
CHANGED
|
@@ -112,6 +112,21 @@ window.__ModuleLoader__.load({
|
|
|
112
112
|
'sync.empty': '另一端暂无数据可获取',
|
|
113
113
|
'sync.off': '当前环境不支持同步(需要宿主设置服务)',
|
|
114
114
|
'sync.loading': '正在连接宿主设置…',
|
|
115
|
+
'flow.creating': '正在创建…',
|
|
116
|
+
'browse.title': '选择工作区目录',
|
|
117
|
+
'browse.home': '主目录',
|
|
118
|
+
'browse.up': '上一层',
|
|
119
|
+
'browse.newFolder': '新建文件夹',
|
|
120
|
+
'browse.folderName': '文件夹名称',
|
|
121
|
+
'browse.empty': '此文件夹没有子文件夹',
|
|
122
|
+
'browse.loading': '加载中…',
|
|
123
|
+
'browse.truncated': '文件夹过多,仅显示开头部分。',
|
|
124
|
+
'browse.showHidden': '显示隐藏文件',
|
|
125
|
+
'browse.editPath': '编辑路径',
|
|
126
|
+
'browse.select': '选择此文件夹',
|
|
127
|
+
'browse.enter': '进入',
|
|
128
|
+
'browse.drives': '盘符',
|
|
129
|
+
'browse.selectNamed': '选择「{name}」',
|
|
115
130
|
'error.title': '出错了',
|
|
116
131
|
'cancel': '取消',
|
|
117
132
|
'create': '创建',
|
|
@@ -207,6 +222,21 @@ window.__ModuleLoader__.load({
|
|
|
207
222
|
'sync.empty': 'No data on the other surface yet',
|
|
208
223
|
'sync.off': 'Sync unavailable here (requires the host settings service)',
|
|
209
224
|
'sync.loading': 'Connecting to host settings…',
|
|
225
|
+
'flow.creating': 'Creating…',
|
|
226
|
+
'browse.title': 'Select Workspace Directory',
|
|
227
|
+
'browse.home': 'Home',
|
|
228
|
+
'browse.up': 'Up',
|
|
229
|
+
'browse.newFolder': 'New folder',
|
|
230
|
+
'browse.folderName': 'Folder name',
|
|
231
|
+
'browse.empty': 'No subfolders here',
|
|
232
|
+
'browse.loading': 'Loading…',
|
|
233
|
+
'browse.truncated': 'Too many folders to list; only the beginning is shown.',
|
|
234
|
+
'browse.showHidden': 'Show hidden files',
|
|
235
|
+
'browse.editPath': 'Edit path',
|
|
236
|
+
'browse.select': 'Use this folder',
|
|
237
|
+
'browse.enter': 'Open',
|
|
238
|
+
'browse.drives': 'Drives',
|
|
239
|
+
'browse.selectNamed': 'Use "{name}"',
|
|
210
240
|
'error.title': 'Something went wrong',
|
|
211
241
|
'cancel': 'Cancel',
|
|
212
242
|
'create': 'Create',
|
|
@@ -310,6 +340,21 @@ window.__ModuleLoader__.load({
|
|
|
310
340
|
'sync.empty': 'لا توجد بيانات لدى الطرف الآخر بعد',
|
|
311
341
|
'sync.off': 'المزامنة غير متاحة هنا (تحتاج خدمة إعدادات المضيف)',
|
|
312
342
|
'sync.loading': 'الاتصال بإعدادات المضيف…',
|
|
343
|
+
'flow.creating': 'جارٍ الإنشاء…',
|
|
344
|
+
'browse.title': 'اختيار مجلد مساحة العمل',
|
|
345
|
+
'browse.home': 'المجلد الرئيسي',
|
|
346
|
+
'browse.up': 'المستوى الأعلى',
|
|
347
|
+
'browse.newFolder': 'مجلد جديد',
|
|
348
|
+
'browse.folderName': 'اسم المجلد',
|
|
349
|
+
'browse.empty': 'لا توجد مجلدات فرعية هنا',
|
|
350
|
+
'browse.loading': 'جارٍ التحميل…',
|
|
351
|
+
'browse.truncated': 'المجلدات كثيرة جدًا؛ يُعرض الجزء الأول فقط.',
|
|
352
|
+
'browse.showHidden': 'إظهار الملفات المخفية',
|
|
353
|
+
'browse.editPath': 'تعديل المسار',
|
|
354
|
+
'browse.select': 'استخدام هذا المجلد',
|
|
355
|
+
'browse.enter': 'فتح',
|
|
356
|
+
'browse.drives': 'الأقراص',
|
|
357
|
+
'browse.selectNamed': 'استخدام "{name}"',
|
|
313
358
|
'error.title': 'حدث خطأ ما',
|
|
314
359
|
'cancel': 'إلغاء',
|
|
315
360
|
'create': 'إنشاء',
|
|
@@ -405,6 +450,21 @@ window.__ModuleLoader__.load({
|
|
|
405
450
|
'sync.empty': 'Auf der anderen Seite liegen noch keine Daten',
|
|
406
451
|
'sync.off': 'Hier nicht verfügbar (erfordert den Host-Einstellungsdienst)',
|
|
407
452
|
'sync.loading': 'Verbindung zum Host-Einstellungsspeicher…',
|
|
453
|
+
'flow.creating': 'Wird erstellt…',
|
|
454
|
+
'browse.title': 'Arbeitsbereichsordner wählen',
|
|
455
|
+
'browse.home': 'Home',
|
|
456
|
+
'browse.up': 'Übergeordnet',
|
|
457
|
+
'browse.newFolder': 'Neuer Ordner',
|
|
458
|
+
'browse.folderName': 'Ordnername',
|
|
459
|
+
'browse.empty': 'Keine Unterordner vorhanden',
|
|
460
|
+
'browse.loading': 'Wird geladen…',
|
|
461
|
+
'browse.truncated': 'Zu viele Ordner; nur der Anfang wird angezeigt.',
|
|
462
|
+
'browse.showHidden': 'Versteckte Dateien anzeigen',
|
|
463
|
+
'browse.editPath': 'Pfad bearbeiten',
|
|
464
|
+
'browse.select': 'Diesen Ordner verwenden',
|
|
465
|
+
'browse.enter': 'Öffnen',
|
|
466
|
+
'browse.drives': 'Laufwerke',
|
|
467
|
+
'browse.selectNamed': '"{name}" verwenden',
|
|
408
468
|
'error.title': 'Etwas ist schiefgelaufen',
|
|
409
469
|
'cancel': 'Abbrechen',
|
|
410
470
|
'create': 'Erstellen',
|
|
@@ -500,6 +560,21 @@ window.__ModuleLoader__.load({
|
|
|
500
560
|
'sync.empty': 'Aucune donnée à récupérer de l\'autre côté',
|
|
501
561
|
'sync.off': 'Synchronisation indisponible ici (service de réglages de l\'hôte requis)',
|
|
502
562
|
'sync.loading': 'Connexion aux réglages de l\'hôte…',
|
|
563
|
+
'flow.creating': 'Création…',
|
|
564
|
+
'browse.title': 'Choisir le dossier de l’espace de travail',
|
|
565
|
+
'browse.home': 'Dossier personnel',
|
|
566
|
+
'browse.up': 'Niveau supérieur',
|
|
567
|
+
'browse.newFolder': 'Nouveau dossier',
|
|
568
|
+
'browse.folderName': 'Nom du dossier',
|
|
569
|
+
'browse.empty': 'Aucun sous-dossier ici',
|
|
570
|
+
'browse.loading': 'Chargement…',
|
|
571
|
+
'browse.truncated': 'Trop de dossiers ; seul le début est affiché.',
|
|
572
|
+
'browse.showHidden': 'Afficher les fichiers cachés',
|
|
573
|
+
'browse.editPath': 'Modifier le chemin',
|
|
574
|
+
'browse.select': 'Utiliser ce dossier',
|
|
575
|
+
'browse.enter': 'Ouvrir',
|
|
576
|
+
'browse.drives': 'Lecteurs',
|
|
577
|
+
'browse.selectNamed': 'Utiliser « {name} »',
|
|
503
578
|
'error.title': 'Une erreur est survenue',
|
|
504
579
|
'cancel': 'Annuler',
|
|
505
580
|
'create': 'Créer',
|
|
@@ -595,6 +670,21 @@ window.__ModuleLoader__.load({
|
|
|
595
670
|
'sync.empty': 'दूसरे सिरे पर अभी कोई डेटा नहीं',
|
|
596
671
|
'sync.off': 'यहाँ समन्वयन उपलब्ध नहीं (होस्ट सेटिंग सेवा चाहिए)',
|
|
597
672
|
'sync.loading': 'होस्ट सेटिंग से जुड़ रहे हैं…',
|
|
673
|
+
'flow.creating': 'बनाया जा रहा है…',
|
|
674
|
+
'browse.title': 'कार्यस्थान फ़ोल्डर चुनें',
|
|
675
|
+
'browse.home': 'होम',
|
|
676
|
+
'browse.up': 'ऊपर',
|
|
677
|
+
'browse.newFolder': 'नया फ़ोल्डर',
|
|
678
|
+
'browse.folderName': 'फ़ोल्डर का नाम',
|
|
679
|
+
'browse.empty': 'यहाँ कोई उप-फ़ोल्डर नहीं',
|
|
680
|
+
'browse.loading': 'लोड हो रहा है…',
|
|
681
|
+
'browse.truncated': 'बहुत अधिक फ़ोल्डर; केवल शुरुआत दिखाई गई है।',
|
|
682
|
+
'browse.showHidden': 'छिपी फ़ाइलें दिखाएँ',
|
|
683
|
+
'browse.editPath': 'पथ संपादित करें',
|
|
684
|
+
'browse.select': 'यह फ़ोल्डर चुनें',
|
|
685
|
+
'browse.enter': 'खोलें',
|
|
686
|
+
'browse.drives': 'ड्राइव',
|
|
687
|
+
'browse.selectNamed': '"{name}" चुनें',
|
|
598
688
|
'error.title': 'कुछ गड़बड़ हो गई',
|
|
599
689
|
'cancel': 'रद्द करें',
|
|
600
690
|
'create': 'बनाएँ',
|
|
@@ -690,6 +780,21 @@ window.__ModuleLoader__.load({
|
|
|
690
780
|
'sync.empty': 'Sisi lain belum punya data',
|
|
691
781
|
'sync.off': 'Sinkronisasi tidak tersedia di sini (perlu layanan pengaturan host)',
|
|
692
782
|
'sync.loading': 'Menyambung ke pengaturan host…',
|
|
783
|
+
'flow.creating': 'Membuat…',
|
|
784
|
+
'browse.title': 'Pilih folder ruang kerja',
|
|
785
|
+
'browse.home': 'Beranda',
|
|
786
|
+
'browse.up': 'Naik',
|
|
787
|
+
'browse.newFolder': 'Folder baru',
|
|
788
|
+
'browse.folderName': 'Nama folder',
|
|
789
|
+
'browse.empty': 'Tidak ada subfolder di sini',
|
|
790
|
+
'browse.loading': 'Memuat…',
|
|
791
|
+
'browse.truncated': 'Terlalu banyak folder; hanya bagian awal yang ditampilkan.',
|
|
792
|
+
'browse.showHidden': 'Tampilkan file tersembunyi',
|
|
793
|
+
'browse.editPath': 'Edit jalur',
|
|
794
|
+
'browse.select': 'Gunakan folder ini',
|
|
795
|
+
'browse.enter': 'Buka',
|
|
796
|
+
'browse.drives': 'Drive',
|
|
797
|
+
'browse.selectNamed': 'Gunakan "{name}"',
|
|
693
798
|
'error.title': 'Terjadi kesalahan',
|
|
694
799
|
'cancel': 'Batal',
|
|
695
800
|
'create': 'Buat',
|
|
@@ -785,6 +890,21 @@ window.__ModuleLoader__.load({
|
|
|
785
890
|
'sync.empty': 'Nessun dato da scaricare dall\'altro lato',
|
|
786
891
|
'sync.off': 'Sincronizzazione non disponibile qui (serve il servizio impostazioni dell\'host)',
|
|
787
892
|
'sync.loading': 'Connessione alle impostazioni dell\'host…',
|
|
893
|
+
'flow.creating': 'Creazione…',
|
|
894
|
+
'browse.title': 'Scegli cartella dell’area di lavoro',
|
|
895
|
+
'browse.home': 'Home',
|
|
896
|
+
'browse.up': 'Livello superiore',
|
|
897
|
+
'browse.newFolder': 'Nuova cartella',
|
|
898
|
+
'browse.folderName': 'Nome cartella',
|
|
899
|
+
'browse.empty': 'Nessuna sottocartella qui',
|
|
900
|
+
'browse.loading': 'Caricamento…',
|
|
901
|
+
'browse.truncated': 'Troppe cartelle; è mostrato solo l’inizio.',
|
|
902
|
+
'browse.showHidden': 'Mostra file nascosti',
|
|
903
|
+
'browse.editPath': 'Modifica percorso',
|
|
904
|
+
'browse.select': 'Usa questa cartella',
|
|
905
|
+
'browse.enter': 'Apri',
|
|
906
|
+
'browse.drives': 'Unità',
|
|
907
|
+
'browse.selectNamed': 'Usa "{name}"',
|
|
788
908
|
'error.title': 'Si è verificato un errore',
|
|
789
909
|
'cancel': 'Annulla',
|
|
790
910
|
'create': 'Crea',
|
|
@@ -880,6 +1000,21 @@ window.__ModuleLoader__.load({
|
|
|
880
1000
|
'sync.empty': 'もう一方に取得できるデータがありません',
|
|
881
1001
|
'sync.off': 'この環境では同期できません(ホスト設定サービスが必要)',
|
|
882
1002
|
'sync.loading': 'ホスト設定に接続中…',
|
|
1003
|
+
'flow.creating': '作成中…',
|
|
1004
|
+
'browse.title': 'ワークスペースのフォルダーを選択',
|
|
1005
|
+
'browse.home': 'ホーム',
|
|
1006
|
+
'browse.up': '上の階層',
|
|
1007
|
+
'browse.newFolder': '新しいフォルダー',
|
|
1008
|
+
'browse.folderName': 'フォルダー名',
|
|
1009
|
+
'browse.empty': 'ここにサブフォルダーはありません',
|
|
1010
|
+
'browse.loading': '読み込み中…',
|
|
1011
|
+
'browse.truncated': 'フォルダーが多すぎます。先頭のみ表示しています。',
|
|
1012
|
+
'browse.showHidden': '隠しファイルを表示',
|
|
1013
|
+
'browse.editPath': 'パスを編集',
|
|
1014
|
+
'browse.select': 'このフォルダーを選択',
|
|
1015
|
+
'browse.enter': '開く',
|
|
1016
|
+
'browse.drives': 'ドライブ',
|
|
1017
|
+
'browse.selectNamed': '「{name}」を選択',
|
|
883
1018
|
'error.title': 'エラーが発生しました',
|
|
884
1019
|
'cancel': 'キャンセル',
|
|
885
1020
|
'create': '作成',
|
|
@@ -975,6 +1110,21 @@ window.__ModuleLoader__.load({
|
|
|
975
1110
|
'sync.empty': '반대쪽에 가져올 데이터가 없습니다',
|
|
976
1111
|
'sync.off': '이 환경에서는 동기화할 수 없습니다(호스트 설정 서비스 필요)',
|
|
977
1112
|
'sync.loading': '호스트 설정에 연결하는 중…',
|
|
1113
|
+
'flow.creating': '만드는 중…',
|
|
1114
|
+
'browse.title': '워크스페이스 폴더 선택',
|
|
1115
|
+
'browse.home': '홈',
|
|
1116
|
+
'browse.up': '상위',
|
|
1117
|
+
'browse.newFolder': '새 폴더',
|
|
1118
|
+
'browse.folderName': '폴더 이름',
|
|
1119
|
+
'browse.empty': '여기에 하위 폴더가 없습니다',
|
|
1120
|
+
'browse.loading': '불러오는 중…',
|
|
1121
|
+
'browse.truncated': '폴더가 너무 많습니다. 앞부분만 표시합니다.',
|
|
1122
|
+
'browse.showHidden': '숨김 파일 표시',
|
|
1123
|
+
'browse.editPath': '경로 편집',
|
|
1124
|
+
'browse.select': '이 폴더 선택',
|
|
1125
|
+
'browse.enter': '열기',
|
|
1126
|
+
'browse.drives': '드라이브',
|
|
1127
|
+
'browse.selectNamed': '"{name}" 선택',
|
|
978
1128
|
'error.title': '문제가 발생했습니다',
|
|
979
1129
|
'cancel': '취소',
|
|
980
1130
|
'create': '만들기',
|
|
@@ -1070,6 +1220,21 @@ window.__ModuleLoader__.load({
|
|
|
1070
1220
|
'sync.empty': 'Aan de andere kant staan nog geen gegevens',
|
|
1071
1221
|
'sync.off': 'Synchronisatie is hier niet beschikbaar (hostinstellingenservice nodig)',
|
|
1072
1222
|
'sync.loading': 'Verbinden met hostinstellingen…',
|
|
1223
|
+
'flow.creating': 'Bezig met aanmaken…',
|
|
1224
|
+
'browse.title': 'Werkruimtemap kiezen',
|
|
1225
|
+
'browse.home': 'Home',
|
|
1226
|
+
'browse.up': 'Omhoog',
|
|
1227
|
+
'browse.newFolder': 'Nieuwe map',
|
|
1228
|
+
'browse.folderName': 'Mapnaam',
|
|
1229
|
+
'browse.empty': 'Geen submappen hier',
|
|
1230
|
+
'browse.loading': 'Laden…',
|
|
1231
|
+
'browse.truncated': 'Te veel mappen; alleen het begin wordt getoond.',
|
|
1232
|
+
'browse.showHidden': 'Verborgen bestanden tonen',
|
|
1233
|
+
'browse.editPath': 'Pad bewerken',
|
|
1234
|
+
'browse.select': 'Deze map gebruiken',
|
|
1235
|
+
'browse.enter': 'Openen',
|
|
1236
|
+
'browse.drives': 'Stations',
|
|
1237
|
+
'browse.selectNamed': '"{name}" gebruiken',
|
|
1073
1238
|
'error.title': 'Er is iets misgegaan',
|
|
1074
1239
|
'cancel': 'Annuleren',
|
|
1075
1240
|
'create': 'Aanmaken',
|
|
@@ -1165,6 +1330,21 @@ window.__ModuleLoader__.load({
|
|
|
1165
1330
|
'sync.empty': 'Druga strona nie ma jeszcze danych',
|
|
1166
1331
|
'sync.off': 'Synchronizacja niedostępna (wymaga usługi ustawień hosta)',
|
|
1167
1332
|
'sync.loading': 'Łączenie z ustawieniami hosta…',
|
|
1333
|
+
'flow.creating': 'Tworzenie…',
|
|
1334
|
+
'browse.title': 'Wybierz folder obszaru roboczego',
|
|
1335
|
+
'browse.home': 'Katalog domowy',
|
|
1336
|
+
'browse.up': 'W górę',
|
|
1337
|
+
'browse.newFolder': 'Nowy folder',
|
|
1338
|
+
'browse.folderName': 'Nazwa folderu',
|
|
1339
|
+
'browse.empty': 'Brak podfolderów',
|
|
1340
|
+
'browse.loading': 'Wczytywanie…',
|
|
1341
|
+
'browse.truncated': 'Zbyt wiele folderów; pokazano tylko początek.',
|
|
1342
|
+
'browse.showHidden': 'Pokaż ukryte pliki',
|
|
1343
|
+
'browse.editPath': 'Edytuj ścieżkę',
|
|
1344
|
+
'browse.select': 'Użyj tego folderu',
|
|
1345
|
+
'browse.enter': 'Otwórz',
|
|
1346
|
+
'browse.drives': 'Dyski',
|
|
1347
|
+
'browse.selectNamed': 'Użyj "{name}"',
|
|
1168
1348
|
'error.title': 'Coś poszło nie tak',
|
|
1169
1349
|
'cancel': 'Anuluj',
|
|
1170
1350
|
'create': 'Utwórz',
|
|
@@ -1260,6 +1440,21 @@ window.__ModuleLoader__.load({
|
|
|
1260
1440
|
'sync.empty': 'O outro lado ainda não tem dados',
|
|
1261
1441
|
'sync.off': 'Sincronização indisponível aqui (requer o serviço de configurações do host)',
|
|
1262
1442
|
'sync.loading': 'Conectando às configurações do host…',
|
|
1443
|
+
'flow.creating': 'Criando…',
|
|
1444
|
+
'browse.title': 'Escolher pasta da área de trabalho',
|
|
1445
|
+
'browse.home': 'Início',
|
|
1446
|
+
'browse.up': 'Acima',
|
|
1447
|
+
'browse.newFolder': 'Nova pasta',
|
|
1448
|
+
'browse.folderName': 'Nome da pasta',
|
|
1449
|
+
'browse.empty': 'Nenhuma subpasta aqui',
|
|
1450
|
+
'browse.loading': 'Carregando…',
|
|
1451
|
+
'browse.truncated': 'Pastas demais; apenas o início é exibido.',
|
|
1452
|
+
'browse.showHidden': 'Mostrar arquivos ocultos',
|
|
1453
|
+
'browse.editPath': 'Editar caminho',
|
|
1454
|
+
'browse.select': 'Usar esta pasta',
|
|
1455
|
+
'browse.enter': 'Abrir',
|
|
1456
|
+
'browse.drives': 'Unidades',
|
|
1457
|
+
'browse.selectNamed': 'Usar "{name}"',
|
|
1263
1458
|
'error.title': 'Algo deu errado',
|
|
1264
1459
|
'cancel': 'Cancelar',
|
|
1265
1460
|
'create': 'Criar',
|
|
@@ -1355,6 +1550,21 @@ window.__ModuleLoader__.load({
|
|
|
1355
1550
|
'sync.empty': 'На другой стороне пока нет данных',
|
|
1356
1551
|
'sync.off': 'Синхронизация недоступна (нужна служба настроек хоста)',
|
|
1357
1552
|
'sync.loading': 'Подключение к настройкам хоста…',
|
|
1553
|
+
'flow.creating': 'Создание…',
|
|
1554
|
+
'browse.title': 'Выбор папки рабочей области',
|
|
1555
|
+
'browse.home': 'Домашняя папка',
|
|
1556
|
+
'browse.up': 'На уровень выше',
|
|
1557
|
+
'browse.newFolder': 'Новая папка',
|
|
1558
|
+
'browse.folderName': 'Имя папки',
|
|
1559
|
+
'browse.empty': 'Здесь нет вложенных папок',
|
|
1560
|
+
'browse.loading': 'Загрузка…',
|
|
1561
|
+
'browse.truncated': 'Слишком много папок; показано только начало.',
|
|
1562
|
+
'browse.showHidden': 'Показывать скрытые файлы',
|
|
1563
|
+
'browse.editPath': 'Изменить путь',
|
|
1564
|
+
'browse.select': 'Выбрать эту папку',
|
|
1565
|
+
'browse.enter': 'Открыть',
|
|
1566
|
+
'browse.drives': 'Диски',
|
|
1567
|
+
'browse.selectNamed': 'Выбрать «{name}»',
|
|
1358
1568
|
'error.title': 'Что-то пошло не так',
|
|
1359
1569
|
'cancel': 'Отмена',
|
|
1360
1570
|
'create': 'Создать',
|
|
@@ -1450,6 +1660,21 @@ window.__ModuleLoader__.load({
|
|
|
1450
1660
|
'sync.empty': 'Andra sidan har inga data ännu',
|
|
1451
1661
|
'sync.off': 'Synk är inte tillgänglig här (kräver värduppläggets inställningstjänst)',
|
|
1452
1662
|
'sync.loading': 'Ansluter till värduppläggets inställningar…',
|
|
1663
|
+
'flow.creating': 'Skapar…',
|
|
1664
|
+
'browse.title': 'Välj arbetsyte-mapp',
|
|
1665
|
+
'browse.home': 'Hem',
|
|
1666
|
+
'browse.up': 'Upp',
|
|
1667
|
+
'browse.newFolder': 'Ny mapp',
|
|
1668
|
+
'browse.folderName': 'Mappnamn',
|
|
1669
|
+
'browse.empty': 'Inga undermappar här',
|
|
1670
|
+
'browse.loading': 'Laddar…',
|
|
1671
|
+
'browse.truncated': 'För många mappar; endast början visas.',
|
|
1672
|
+
'browse.showHidden': 'Visa dolda filer',
|
|
1673
|
+
'browse.editPath': 'Redigera sökväg',
|
|
1674
|
+
'browse.select': 'Använd den här mappen',
|
|
1675
|
+
'browse.enter': 'Öppna',
|
|
1676
|
+
'browse.drives': 'Enheter',
|
|
1677
|
+
'browse.selectNamed': 'Använd "{name}"',
|
|
1453
1678
|
'error.title': 'Något gick fel',
|
|
1454
1679
|
'cancel': 'Avbryt',
|
|
1455
1680
|
'create': 'Skapa',
|
|
@@ -1545,6 +1770,21 @@ window.__ModuleLoader__.load({
|
|
|
1545
1770
|
'sync.empty': 'อีกฝั่งยังไม่มีข้อมูลให้ดึง',
|
|
1546
1771
|
'sync.off': 'สภาพแวดล้อมนี้ไม่รองรับการซิงก์ (ต้องมีบริการการตั้งค่าของโฮสต์)',
|
|
1547
1772
|
'sync.loading': 'กำลังเชื่อมต่อการตั้งค่าโฮสต์…',
|
|
1773
|
+
'flow.creating': 'กำลังสร้าง…',
|
|
1774
|
+
'browse.title': 'เลือกโฟลเดอร์เวิร์กสเปซ',
|
|
1775
|
+
'browse.home': 'โฮม',
|
|
1776
|
+
'browse.up': 'ขึ้นหนึ่งระดับ',
|
|
1777
|
+
'browse.newFolder': 'โฟลเดอร์ใหม่',
|
|
1778
|
+
'browse.folderName': 'ชื่อโฟลเดอร์',
|
|
1779
|
+
'browse.empty': 'ไม่มีโฟลเดอร์ย่อยที่นี่',
|
|
1780
|
+
'browse.loading': 'กำลังโหลด…',
|
|
1781
|
+
'browse.truncated': 'มีโฟลเดอร์มากเกินไป แสดงเฉพาะส่วนต้น',
|
|
1782
|
+
'browse.showHidden': 'แสดงไฟล์ที่ซ่อนอยู่',
|
|
1783
|
+
'browse.editPath': 'แก้ไขเส้นทาง',
|
|
1784
|
+
'browse.select': 'ใช้โฟลเดอร์นี้',
|
|
1785
|
+
'browse.enter': 'เปิด',
|
|
1786
|
+
'browse.drives': 'ไดรฟ์',
|
|
1787
|
+
'browse.selectNamed': 'ใช้ "{name}"',
|
|
1548
1788
|
'error.title': 'เกิดข้อผิดพลาด',
|
|
1549
1789
|
'cancel': 'ยกเลิก',
|
|
1550
1790
|
'create': 'สร้าง',
|
|
@@ -1640,6 +1880,21 @@ window.__ModuleLoader__.load({
|
|
|
1640
1880
|
'sync.empty': 'Diğer tarafta alınacak veri yok',
|
|
1641
1881
|
'sync.off': 'Bu ortamda eşitleme yok (ana makine ayar servisi gerekir)',
|
|
1642
1882
|
'sync.loading': 'Ana makine ayarlarına bağlanılıyor…',
|
|
1883
|
+
'flow.creating': 'Oluşturuluyor…',
|
|
1884
|
+
'browse.title': 'Çalışma alanı klasörünü seç',
|
|
1885
|
+
'browse.home': 'Ana dizin',
|
|
1886
|
+
'browse.up': 'Üst düzey',
|
|
1887
|
+
'browse.newFolder': 'Yeni klasör',
|
|
1888
|
+
'browse.folderName': 'Klasör adı',
|
|
1889
|
+
'browse.empty': 'Burada alt klasör yok',
|
|
1890
|
+
'browse.loading': 'Yükleniyor…',
|
|
1891
|
+
'browse.truncated': 'Çok fazla klasör var; yalnızca başlangıç gösteriliyor.',
|
|
1892
|
+
'browse.showHidden': 'Gizli dosyaları göster',
|
|
1893
|
+
'browse.editPath': 'Yolu düzenle',
|
|
1894
|
+
'browse.select': 'Bu klasörü kullan',
|
|
1895
|
+
'browse.enter': 'Aç',
|
|
1896
|
+
'browse.drives': 'Sürücüler',
|
|
1897
|
+
'browse.selectNamed': '"{name}" kullan',
|
|
1643
1898
|
'error.title': 'Bir şeyler ters gitti',
|
|
1644
1899
|
'cancel': 'İptal',
|
|
1645
1900
|
'create': 'Oluştur',
|
|
@@ -1735,6 +1990,21 @@ window.__ModuleLoader__.load({
|
|
|
1735
1990
|
'sync.empty': 'Bên kia chưa có dữ liệu để lấy',
|
|
1736
1991
|
'sync.off': 'Môi trường này không hỗ trợ đồng bộ (cần dịch vụ cài đặt của máy chủ)',
|
|
1737
1992
|
'sync.loading': 'Đang kết nối cài đặt máy chủ…',
|
|
1993
|
+
'flow.creating': 'Đang tạo…',
|
|
1994
|
+
'browse.title': 'Chọn thư mục không gian làm việc',
|
|
1995
|
+
'browse.home': 'Thư mục chính',
|
|
1996
|
+
'browse.up': 'Lên một cấp',
|
|
1997
|
+
'browse.newFolder': 'Thư mục mới',
|
|
1998
|
+
'browse.folderName': 'Tên thư mục',
|
|
1999
|
+
'browse.empty': 'Không có thư mục con ở đây',
|
|
2000
|
+
'browse.loading': 'Đang tải…',
|
|
2001
|
+
'browse.truncated': 'Quá nhiều thư mục; chỉ hiển thị phần đầu.',
|
|
2002
|
+
'browse.showHidden': 'Hiện tệp ẩn',
|
|
2003
|
+
'browse.editPath': 'Sửa đường dẫn',
|
|
2004
|
+
'browse.select': 'Dùng thư mục này',
|
|
2005
|
+
'browse.enter': 'Mở',
|
|
2006
|
+
'browse.drives': 'Ổ đĩa',
|
|
2007
|
+
'browse.selectNamed': 'Dùng "{name}"',
|
|
1738
2008
|
'error.title': 'Đã xảy ra lỗi',
|
|
1739
2009
|
'cancel': 'Hủy',
|
|
1740
2010
|
'create': 'Tạo',
|
|
@@ -1830,6 +2100,21 @@ window.__ModuleLoader__.load({
|
|
|
1830
2100
|
'sync.empty': '另一端暫時未有資料可以獲取',
|
|
1831
2101
|
'sync.off': '呢個環境唔支援同步(需要宿主設定服務)',
|
|
1832
2102
|
'sync.loading': '連接緊宿主設定…',
|
|
2103
|
+
'flow.creating': '建立緊…',
|
|
2104
|
+
'browse.title': '揀工作區資料夾',
|
|
2105
|
+
'browse.home': '主目錄',
|
|
2106
|
+
'browse.up': '上一層',
|
|
2107
|
+
'browse.newFolder': '新增資料夾',
|
|
2108
|
+
'browse.folderName': '資料夾名稱',
|
|
2109
|
+
'browse.empty': '呢個資料夾冇子資料夾',
|
|
2110
|
+
'browse.loading': '載入緊…',
|
|
2111
|
+
'browse.truncated': '資料夾太多,只顯示開頭部分。',
|
|
2112
|
+
'browse.showHidden': '顯示隱藏檔案',
|
|
2113
|
+
'browse.editPath': '編輯路徑',
|
|
2114
|
+
'browse.select': '揀呢個資料夾',
|
|
2115
|
+
'browse.enter': '入去',
|
|
2116
|
+
'browse.drives': '磁碟',
|
|
2117
|
+
'browse.selectNamed': '揀「{name}」',
|
|
1833
2118
|
'error.title': '出咗錯',
|
|
1834
2119
|
'cancel': '取消',
|
|
1835
2120
|
'create': '建立',
|
|
@@ -1925,6 +2210,21 @@ window.__ModuleLoader__.load({
|
|
|
1925
2210
|
'sync.empty': '另一端暫時未有資料可以獲取',
|
|
1926
2211
|
'sync.off': '呢個環境唔支援同步(需要宿主設定服務)',
|
|
1927
2212
|
'sync.loading': '連接緊宿主設定…',
|
|
2213
|
+
'flow.creating': '建立緊…',
|
|
2214
|
+
'browse.title': '揀工作區資料夾',
|
|
2215
|
+
'browse.home': '主目錄',
|
|
2216
|
+
'browse.up': '上一層',
|
|
2217
|
+
'browse.newFolder': '新增資料夾',
|
|
2218
|
+
'browse.folderName': '資料夾名稱',
|
|
2219
|
+
'browse.empty': '呢個資料夾冇子資料夾',
|
|
2220
|
+
'browse.loading': '載入緊…',
|
|
2221
|
+
'browse.truncated': '資料夾太多,只顯示開頭部分。',
|
|
2222
|
+
'browse.showHidden': '顯示隱藏檔案',
|
|
2223
|
+
'browse.editPath': '編輯路徑',
|
|
2224
|
+
'browse.select': '揀呢個資料夾',
|
|
2225
|
+
'browse.enter': '入去',
|
|
2226
|
+
'browse.drives': '磁碟',
|
|
2227
|
+
'browse.selectNamed': '揀「{name}」',
|
|
1928
2228
|
'error.title': '出咗錯',
|
|
1929
2229
|
'cancel': '取消',
|
|
1930
2230
|
'create': '建立',
|
|
@@ -2020,6 +2320,21 @@ window.__ModuleLoader__.load({
|
|
|
2020
2320
|
'sync.empty': '另一端尚無資料可取得',
|
|
2021
2321
|
'sync.off': '目前環境不支援同步(需要宿主設定服務)',
|
|
2022
2322
|
'sync.loading': '正在連線宿主設定…',
|
|
2323
|
+
'flow.creating': '正在建立…',
|
|
2324
|
+
'browse.title': '選擇工作區資料夾',
|
|
2325
|
+
'browse.home': '主目錄',
|
|
2326
|
+
'browse.up': '上一層',
|
|
2327
|
+
'browse.newFolder': '新增資料夾',
|
|
2328
|
+
'browse.folderName': '資料夾名稱',
|
|
2329
|
+
'browse.empty': '此資料夾沒有子資料夾',
|
|
2330
|
+
'browse.loading': '載入中…',
|
|
2331
|
+
'browse.truncated': '資料夾過多,僅顯示開頭部分。',
|
|
2332
|
+
'browse.showHidden': '顯示隱藏檔案',
|
|
2333
|
+
'browse.editPath': '編輯路徑',
|
|
2334
|
+
'browse.select': '選擇此資料夾',
|
|
2335
|
+
'browse.enter': '進入',
|
|
2336
|
+
'browse.drives': '磁碟',
|
|
2337
|
+
'browse.selectNamed': '選擇「{name}」',
|
|
2023
2338
|
'error.title': '發生錯誤',
|
|
2024
2339
|
'cancel': '取消',
|
|
2025
2340
|
'create': '建立',
|
|
@@ -2570,7 +2885,40 @@ window.__ModuleLoader__.load({
|
|
|
2570
2885
|
'.bw-btn-primary{background:var(--dsw-alias-brand-primary,#5b8def);border-color:transparent;color:var(--dsw-alias-brand-text,#fff)}',
|
|
2571
2886
|
'.bw-btn-primary:hover{background:var(--dsw-alias-button-primary-hover,var(--dsw-alias-brand-primary,#5b8def))}',
|
|
2572
2887
|
'.bw-btn:disabled{opacity:.5;cursor:default}',
|
|
2573
|
-
|
|
2888
|
+
'.bw-browse-body{min-width:360px;max-width:460px}',
|
|
2889
|
+
'.bw-browse-bar{display:flex;align-items:center;gap:4px}',
|
|
2890
|
+
'.bw-browse-crumbs{display:flex;align-items:center;gap:2px;flex:1;min-width:0;overflow-x:auto}',
|
|
2891
|
+
'.bw-browse-crumb{border:none;background:transparent;color:var(--dsw-alias-label-secondary,#b8b8b8);font:inherit;font-size:12px;padding:2px 5px;border-radius:4px;cursor:pointer;white-space:nowrap;max-width:150px;overflow:hidden;text-overflow:ellipsis;flex:none}',
|
|
2892
|
+
'.bw-browse-crumb:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-primary,#e6e6e6)}',
|
|
2893
|
+
'.bw-browse-crumb:disabled{opacity:.4;cursor:default}',
|
|
2894
|
+
'.bw-browse-current{color:var(--dsw-alias-label-primary,#e6e6e6);font-size:12px;padding:2px 5px;max-width:190px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
|
|
2895
|
+
'.bw-browse-nav{font-size:13px;line-height:1}',
|
|
2896
|
+
'.bw-browse-edit{flex:none;display:grid;place-items:center;width:24px;height:24px;padding:0;border:none;border-radius:5px;background:transparent;color:var(--dsw-alias-label-tertiary,#9a9a9a);cursor:pointer}',
|
|
2897
|
+
'.bw-browse-edit:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-primary,#e6e6e6)}',
|
|
2898
|
+
'.bw-browse-edit svg{width:14px;height:14px}',
|
|
2899
|
+
'.bw-browse-edit-row{display:flex;gap:6px;align-items:center}',
|
|
2900
|
+
'.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))}',
|
|
2901
|
+
'.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}',
|
|
2902
|
+
'.bw-browse-row:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12))}',
|
|
2903
|
+
'.bw-browse-row:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5b8def);outline-offset:-2px}',
|
|
2904
|
+
'.bw-browse-row svg{width:15px;height:15px;flex:none;color:var(--dsw-alias-label-secondary,#b8b8b8)}',
|
|
2905
|
+
'.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)}',
|
|
2906
|
+
'.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}',
|
|
2907
|
+
'.bw-browse-open:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.16));color:var(--dsw-alias-label-primary,#e6e6e6)}',
|
|
2908
|
+
'.bw-browse-drives{display:flex;align-items:center;gap:4px;margin-left:auto}',
|
|
2909
|
+
'.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}',
|
|
2910
|
+
'.bw-browse-drive:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-primary,#e6e6e6)}',
|
|
2911
|
+
'.bw-browse-drive-on{border-color:var(--dsw-alias-brand-primary,#5b8def);color:var(--dsw-alias-label-primary,#e6e6e6)}',
|
|
2912
|
+
'.bw-browse-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
|
|
2913
|
+
'.bw-browse-note{font-size:12px;color:var(--dsw-alias-label-tertiary,#9a9a9a);padding:10px 8px;text-align:center}',
|
|
2914
|
+
'.bw-browse-new-row{display:flex;gap:6px;align-items:center}',
|
|
2915
|
+
'.bw-browse-tools{display:flex;align-items:center;gap:6px;flex-wrap:wrap}',
|
|
2916
|
+
'.bw-browse-actions{display:flex;align-items:center;justify-content:flex-end;gap:8px}',
|
|
2917
|
+
'.bw-browse-tool{border:1px solid var(--dsw-alias-border-l1,rgba(127,127,127,.25));background:transparent;color:var(--dsw-alias-label-secondary,#b8b8b8);font:inherit;font-size:12px;padding:4px 10px;border-radius:6px;cursor:pointer;white-space:nowrap}',
|
|
2918
|
+
'.bw-browse-tool:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-primary,#e6e6e6)}',
|
|
2919
|
+
'.bw-browse-tool:disabled{opacity:.45;cursor:default}',
|
|
2920
|
+
'.bw-browse-tool-on{border-color:var(--dsw-alias-brand-primary,#5b8def);color:var(--dsw-alias-label-primary,#e6e6e6)}',
|
|
2921
|
+
'.bw-error-text{font-size:12.5px;color:var(--dsw-alias-state-error-primary,#f85149);word-break:break-all;max-width:380px}',
|
|
2574
2922
|
'.bw-dialog-input-row{display:flex;gap:6px;align-items:center}',
|
|
2575
2923
|
'.bw-glow-row{display:flex;align-items:center;gap:10px}',
|
|
2576
2924
|
'.bw-slider{flex:1;accent-color:var(--dsw-alias-brand-primary,#5b8def);height:22px}',
|
|
@@ -2790,6 +3138,65 @@ window.__ModuleLoader__.load({
|
|
|
2790
3138
|
},
|
|
2791
3139
|
})
|
|
2792
3140
|
|
|
3141
|
+
/* ==================== directory-picker capability ================== */
|
|
3142
|
+
|
|
3143
|
+
// The host composes exactly ONE directory-picking backend
|
|
3144
|
+
// (@deepseek-ai/dsh-host-directory-picker-auto): a loopback-only webserver
|
|
3145
|
+
// bind on a display-bearing host gets `native` — one OS chooser on the
|
|
3146
|
+
// HOST's own screen — while every other bind (all-interfaces/LAN, SSH,
|
|
3147
|
+
// headless) gets `browse`, whose wire verbs are list/createDirectory ONLY
|
|
3148
|
+
// and whose `pick` is refused BY DESIGN with `directory-picker/unavailable`
|
|
3149
|
+
// ("... needs the native capability; the composed picker serves browse").
|
|
3150
|
+
// A flow that hard-assumed the OS chooser therefore dies on every LAN bind
|
|
3151
|
+
// and no workspace can be added at all. Probe the composed backend once per
|
|
3152
|
+
// page and keep BOTH interactions: the OS chooser, and an in-app browser
|
|
3153
|
+
// driven by the browse primitives (see DirectoryBrowseDialog).
|
|
3154
|
+
const pickerState = { kind: 'unknown', probe: null, api: null }
|
|
3155
|
+
/**
|
|
3156
|
+
* True for the refusal a Host serving the OTHER capability throws: the wire
|
|
3157
|
+
* code when the caller still carries it, the Host's fixed sentence
|
|
3158
|
+
* otherwise (uiWorkspace.pickDirectory wraps the RPC failure in a plain
|
|
3159
|
+
* Error, so the code is gone by the time the flow sees it).
|
|
3160
|
+
*/
|
|
3161
|
+
const pickerRefusal = (error) => {
|
|
3162
|
+
if (error && typeof error === 'object' && error.rpcError
|
|
3163
|
+
&& error.rpcError.code === 'directory-picker/unavailable') return true
|
|
3164
|
+
const message = messageOf(error)
|
|
3165
|
+
return message.indexOf('needs the native capability') !== -1
|
|
3166
|
+
|| message.indexOf('directory-picker/unavailable') !== -1
|
|
3167
|
+
}
|
|
3168
|
+
/**
|
|
3169
|
+
* Resolve which backend this page's Host composes. `list` succeeds exactly
|
|
3170
|
+
* on `browse` and is refused with the capability code on `native`, so one
|
|
3171
|
+
* call answers both worlds without ever opening a chooser or creating a
|
|
3172
|
+
* directory. An unclassified failure (a carrier still connecting at boot)
|
|
3173
|
+
* is NOT cached: the next open re-probes, and the flow falls back to trying
|
|
3174
|
+
* the chooser itself.
|
|
3175
|
+
*/
|
|
3176
|
+
const pickerCapabilityNow = () => {
|
|
3177
|
+
if (pickerState.kind !== 'unknown') return Promise.resolve(pickerState.kind)
|
|
3178
|
+
if (pickerState.probe !== null) return pickerState.probe
|
|
3179
|
+
const api = pickerState.api
|
|
3180
|
+
if (!api || typeof api.listDirectory !== 'function') return Promise.resolve('unknown')
|
|
3181
|
+
const probe = Promise.resolve()
|
|
3182
|
+
.then(() => api.listDirectory(undefined, undefined))
|
|
3183
|
+
.then(
|
|
3184
|
+
() => 'browse',
|
|
3185
|
+
(error) => (pickerRefusal(error) ? 'native' : 'unknown'),
|
|
3186
|
+
)
|
|
3187
|
+
pickerState.probe = probe.then((kind) => {
|
|
3188
|
+
if (kind === 'unknown') { pickerState.probe = null; return kind }
|
|
3189
|
+
pickerState.kind = kind
|
|
3190
|
+
return kind
|
|
3191
|
+
})
|
|
3192
|
+
return pickerState.probe
|
|
3193
|
+
}
|
|
3194
|
+
/** Commit a browse verdict the flow discovered the hard way (a refused pick). */
|
|
3195
|
+
const markPickerBrowse = () => {
|
|
3196
|
+
pickerState.kind = 'browse'
|
|
3197
|
+
pickerState.probe = Promise.resolve('browse')
|
|
3198
|
+
}
|
|
3199
|
+
|
|
2793
3200
|
/* ============================ flow dialog ========================= */
|
|
2794
3201
|
|
|
2795
3202
|
const BTN = (props) => (
|
|
@@ -2864,6 +3271,406 @@ window.__ModuleLoader__.load({
|
|
|
2864
3271
|
}
|
|
2865
3272
|
}
|
|
2866
3273
|
|
|
3274
|
+
/**
|
|
3275
|
+
* The in-app directory browser: the interaction a `browse`-composed Host can
|
|
3276
|
+
* actually serve. It appears when the probe (or a refused pick) proves the
|
|
3277
|
+
* OS chooser is not this Host's backend — a LAN/all-interfaces bind, an SSH
|
|
3278
|
+
* launch, a headless host — where `pick` is refused by design and
|
|
3279
|
+
* list/createDirectory are the only wire verbs. One level at a time: a
|
|
3280
|
+
* clickable Home-rooted breadcrumb chain, an editable path, an inline
|
|
3281
|
+
* new-folder row and the hidden-entry toggle; the listed level (or any
|
|
3282
|
+
* ancestor crumb) is the pick. Browse failures stay inside the dialog — the
|
|
3283
|
+
* owner's error surface belongs to the pick/create conversation, and a
|
|
3284
|
+
* denied listing must never close the flow.
|
|
3285
|
+
*/
|
|
3286
|
+
/**
|
|
3287
|
+
* Page-lifetime cache of the Windows drive letters this Host actually
|
|
3288
|
+
* serves. The browse API can only list a path, so the first open probes the
|
|
3289
|
+
* usual letters once (six cheap listings, a missing drive fails silently)
|
|
3290
|
+
* and every later open renders the chips straight from here.
|
|
3291
|
+
*/
|
|
3292
|
+
const browseDrives = { probed: false, probing: false, list: [] }
|
|
3293
|
+
|
|
3294
|
+
/**
|
|
3295
|
+
* Run the one wire verb that actually puts a folder on disk and hand back
|
|
3296
|
+
* the created path. This is deliberately a module-level seam the smoke suite
|
|
3297
|
+
* drives for real: v0.11.4 folded the call into the success handler and
|
|
3298
|
+
* dropped it, so the form closed on a resolved `undefined` — no request, no
|
|
3299
|
+
* error, no folder. A string-level assertion cannot see that; calling it can.
|
|
3300
|
+
*/
|
|
3301
|
+
const createFolderIn = (createDirectory, path, name) => Promise.resolve()
|
|
3302
|
+
.then(() => createDirectory(path, name))
|
|
3303
|
+
|
|
3304
|
+
function DirectoryBrowseDialog(props) {
|
|
3305
|
+
const { open, busy, listDirectory, createDirectory, onPick, onClose, t } = props
|
|
3306
|
+
const [listing, setListing] = React.useState(null)
|
|
3307
|
+
const [loading, setLoading] = React.useState(false)
|
|
3308
|
+
const [error, setError] = React.useState('')
|
|
3309
|
+
const [showHidden, setShowHidden] = React.useState(false)
|
|
3310
|
+
const [editing, setEditing] = React.useState(false)
|
|
3311
|
+
const [draft, setDraft] = React.useState('')
|
|
3312
|
+
const [creating, setCreating] = React.useState(false)
|
|
3313
|
+
const [createName, setCreateName] = React.useState('')
|
|
3314
|
+
const [createBusy, setCreateBusy] = React.useState(false)
|
|
3315
|
+
const [createError, setCreateError] = React.useState('')
|
|
3316
|
+
const [selected, setSelected] = React.useState('')
|
|
3317
|
+
const [drives, setDrives] = React.useState(browseDrives.list)
|
|
3318
|
+
// Guards a superseded scan: a slow level must not overwrite a newer one.
|
|
3319
|
+
const seqRef = React.useRef(0)
|
|
3320
|
+
const crumbsRef = React.useRef(null)
|
|
3321
|
+
|
|
3322
|
+
const go = (path, keepSelected) => {
|
|
3323
|
+
const seq = seqRef.current + 1
|
|
3324
|
+
seqRef.current = seq
|
|
3325
|
+
setLoading(true)
|
|
3326
|
+
setError('')
|
|
3327
|
+
if (keepSelected !== true) setSelected('')
|
|
3328
|
+
Promise.resolve()
|
|
3329
|
+
.then(() => listDirectory(path, undefined))
|
|
3330
|
+
.then((next) => {
|
|
3331
|
+
if (seqRef.current !== seq) return
|
|
3332
|
+
setListing(next || null)
|
|
3333
|
+
setLoading(false)
|
|
3334
|
+
})
|
|
3335
|
+
.catch((reason) => {
|
|
3336
|
+
if (seqRef.current !== seq) return
|
|
3337
|
+
setLoading(false)
|
|
3338
|
+
setError(messageOf(reason))
|
|
3339
|
+
})
|
|
3340
|
+
}
|
|
3341
|
+
|
|
3342
|
+
React.useEffect(() => {
|
|
3343
|
+
if (!open) return undefined
|
|
3344
|
+
seqRef.current += 1
|
|
3345
|
+
setListing(null)
|
|
3346
|
+
setError('')
|
|
3347
|
+
setEditing(false)
|
|
3348
|
+
setCreating(false)
|
|
3349
|
+
setCreateName('')
|
|
3350
|
+
setCreateBusy(false)
|
|
3351
|
+
setCreateError('')
|
|
3352
|
+
setShowHidden(false)
|
|
3353
|
+
setSelected('')
|
|
3354
|
+
go(undefined)
|
|
3355
|
+
return () => { seqRef.current += 1 }
|
|
3356
|
+
}, [open])
|
|
3357
|
+
|
|
3358
|
+
const currentPath = listing && typeof listing.path === 'string' ? listing.path : ''
|
|
3359
|
+
const homePath = listing && typeof listing.home === 'string' ? listing.home : ''
|
|
3360
|
+
const windowsHost = homePath.indexOf('\\') !== -1
|
|
3361
|
+
const hasListing = listing !== null
|
|
3362
|
+
|
|
3363
|
+
// Windows drives are separate filesystem roots and the browse API can only
|
|
3364
|
+
// list a path, so probe the usual letters once per page (cached above) and
|
|
3365
|
+
// offer the ones that answered. A drive nobody probed is still reachable
|
|
3366
|
+
// through the path editor, which is the only general answer anyway.
|
|
3367
|
+
React.useEffect(() => {
|
|
3368
|
+
if (!open || !hasListing || !windowsHost) return undefined
|
|
3369
|
+
if (browseDrives.probed || browseDrives.probing) {
|
|
3370
|
+
if (browseDrives.list.length > 0) setDrives(browseDrives.list)
|
|
3371
|
+
return undefined
|
|
3372
|
+
}
|
|
3373
|
+
browseDrives.probing = true
|
|
3374
|
+
let alive = true
|
|
3375
|
+
const letters = ['C:', 'D:', 'E:', 'F:', 'G:', 'H:']
|
|
3376
|
+
Promise.all(letters.map((letter) => Promise.resolve()
|
|
3377
|
+
.then(() => listDirectory(letter + '\\', undefined))
|
|
3378
|
+
.then(() => letter, () => null)))
|
|
3379
|
+
.then((found) => {
|
|
3380
|
+
browseDrives.list = found.filter(Boolean)
|
|
3381
|
+
browseDrives.probed = true
|
|
3382
|
+
browseDrives.probing = false
|
|
3383
|
+
if (alive) setDrives(browseDrives.list)
|
|
3384
|
+
}, () => { browseDrives.probing = false })
|
|
3385
|
+
return () => { alive = false }
|
|
3386
|
+
}, [open, hasListing, windowsHost])
|
|
3387
|
+
|
|
3388
|
+
// A deep path must show its TAIL — that is where you are — so the chain
|
|
3389
|
+
// keeps its right edge in view instead of cutting the last crumb off.
|
|
3390
|
+
React.useEffect(() => {
|
|
3391
|
+
const node = crumbsRef.current
|
|
3392
|
+
if (node) node.scrollLeft = node.scrollWidth
|
|
3393
|
+
}, [listing])
|
|
3394
|
+
|
|
3395
|
+
if (!open) return null
|
|
3396
|
+
|
|
3397
|
+
// The whole ancestry from the filesystem ROOT: rooting the chain at Home
|
|
3398
|
+
// (the official dialog's choice) hides where the listed level actually is,
|
|
3399
|
+
// and the drive crumb answers "which disk am I on" at a glance.
|
|
3400
|
+
const crumbs = listing && Array.isArray(listing.crumbs) ? listing.crumbs : []
|
|
3401
|
+
const parentPath = crumbs.length > 1 ? crumbs[crumbs.length - 2].path : ''
|
|
3402
|
+
const entries = (listing && Array.isArray(listing.entries) ? listing.entries : [])
|
|
3403
|
+
.filter((entry) => showHidden || !entry.hidden)
|
|
3404
|
+
const selectedEntry = entries.find((entry) => entry.path === selected) || null
|
|
3405
|
+
const targetPath = selectedEntry ? selectedEntry.path : currentPath
|
|
3406
|
+
|
|
3407
|
+
const submitDraft = () => {
|
|
3408
|
+
const path = draft.trim()
|
|
3409
|
+
setEditing(false)
|
|
3410
|
+
if (path === '') { go(undefined); return }
|
|
3411
|
+
go(path)
|
|
3412
|
+
}
|
|
3413
|
+
const submitCreate = () => {
|
|
3414
|
+
const name = createName.trim()
|
|
3415
|
+
if (name === '' || createBusy || currentPath === '') return
|
|
3416
|
+
setCreateBusy(true)
|
|
3417
|
+
setCreateError('')
|
|
3418
|
+
createFolderIn(createDirectory, currentPath, name)
|
|
3419
|
+
.then((created) => {
|
|
3420
|
+
setCreateBusy(false)
|
|
3421
|
+
setCreating(false)
|
|
3422
|
+
setCreateName('')
|
|
3423
|
+
// Land on the folder just made: it is almost always the one the
|
|
3424
|
+
// operator wants to adopt next.
|
|
3425
|
+
if (typeof created === 'string' && created !== '') setSelected(created)
|
|
3426
|
+
go(currentPath, true)
|
|
3427
|
+
})
|
|
3428
|
+
.catch((reason) => {
|
|
3429
|
+
setCreateBusy(false)
|
|
3430
|
+
setCreateError(messageOf(reason))
|
|
3431
|
+
})
|
|
3432
|
+
}
|
|
3433
|
+
|
|
3434
|
+
const head = editing
|
|
3435
|
+
? E('div', { className: 'bw-browse-edit-row' },
|
|
3436
|
+
E('input', {
|
|
3437
|
+
className: 'bw-input',
|
|
3438
|
+
value: draft,
|
|
3439
|
+
autoFocus: true,
|
|
3440
|
+
spellCheck: false,
|
|
3441
|
+
placeholder: currentPath,
|
|
3442
|
+
onChange: (e) => setDraft(e.target.value),
|
|
3443
|
+
onKeyDown: (e) => {
|
|
3444
|
+
if (e.key === 'Enter') submitDraft()
|
|
3445
|
+
else if (e.key === 'Escape') setEditing(false)
|
|
3446
|
+
},
|
|
3447
|
+
}),
|
|
3448
|
+
E(BTN, { variant: 'outline', onClick: () => setEditing(false) }, t('cancel')),
|
|
3449
|
+
)
|
|
3450
|
+
: E('div', { className: 'bw-browse-bar' },
|
|
3451
|
+
E('button', {
|
|
3452
|
+
type: 'button',
|
|
3453
|
+
className: 'bw-browse-crumb bw-browse-nav',
|
|
3454
|
+
title: t('browse.up'),
|
|
3455
|
+
'aria-label': t('browse.up'),
|
|
3456
|
+
disabled: parentPath === '',
|
|
3457
|
+
onClick: () => { if (parentPath !== '') go(parentPath) },
|
|
3458
|
+
}, '↑'),
|
|
3459
|
+
E('div', { className: 'bw-browse-crumbs', ref: crumbsRef },
|
|
3460
|
+
crumbs.map((crumb, index) => (index === crumbs.length - 1
|
|
3461
|
+
? E('span', { key: crumb.path, className: 'bw-browse-crumb bw-browse-current', title: crumb.path }, crumb.name)
|
|
3462
|
+
: E('button', {
|
|
3463
|
+
key: crumb.path,
|
|
3464
|
+
type: 'button',
|
|
3465
|
+
className: 'bw-browse-crumb',
|
|
3466
|
+
title: crumb.path,
|
|
3467
|
+
onClick: () => go(crumb.path),
|
|
3468
|
+
}, crumb.name))),
|
|
3469
|
+
),
|
|
3470
|
+
E('button', {
|
|
3471
|
+
type: 'button',
|
|
3472
|
+
className: 'bw-browse-edit',
|
|
3473
|
+
title: t('browse.editPath'),
|
|
3474
|
+
'aria-label': t('browse.editPath'),
|
|
3475
|
+
onClick: () => { setDraft(currentPath); setEditing(true) },
|
|
3476
|
+
}, icon('IconEditOutline16')),
|
|
3477
|
+
)
|
|
3478
|
+
|
|
3479
|
+
// One click SELECTS a row (the footer button then adopts it — no need to
|
|
3480
|
+
// step inside just to pick a folder), a double click or the row's chevron
|
|
3481
|
+
// enters it. Touch needs the chevron: a double tap is not a gesture.
|
|
3482
|
+
const rows = entries.map((entry) => E('div', {
|
|
3483
|
+
key: entry.path,
|
|
3484
|
+
className: cls('bw-browse-row', selected === entry.path && 'bw-browse-row-on'),
|
|
3485
|
+
title: entry.path,
|
|
3486
|
+
role: 'button',
|
|
3487
|
+
tabIndex: 0,
|
|
3488
|
+
onClick: () => setSelected((current) => (current === entry.path ? '' : entry.path)),
|
|
3489
|
+
onDoubleClick: () => go(entry.path),
|
|
3490
|
+
onKeyDown: (event) => {
|
|
3491
|
+
if (event.key === 'Enter') go(entry.path)
|
|
3492
|
+
else if (event.key === ' ') { event.preventDefault(); setSelected((current) => (current === entry.path ? '' : entry.path)) }
|
|
3493
|
+
},
|
|
3494
|
+
},
|
|
3495
|
+
icon('IconFolderClose16'),
|
|
3496
|
+
E('span', { className: 'bw-browse-name' }, entry.name),
|
|
3497
|
+
E('button', {
|
|
3498
|
+
type: 'button',
|
|
3499
|
+
className: 'bw-browse-open',
|
|
3500
|
+
title: t('browse.enter'),
|
|
3501
|
+
'aria-label': t('browse.enter'),
|
|
3502
|
+
onClick: (event) => { event.stopPropagation(); go(entry.path) },
|
|
3503
|
+
}, '›'),
|
|
3504
|
+
))
|
|
3505
|
+
|
|
3506
|
+
const body = loading
|
|
3507
|
+
? E('div', { className: 'bw-browse-note' }, t('browse.loading'))
|
|
3508
|
+
: rows.length > 0
|
|
3509
|
+
? rows
|
|
3510
|
+
: E('div', { className: 'bw-browse-note' }, t('browse.empty'))
|
|
3511
|
+
|
|
3512
|
+
const newRow = creating
|
|
3513
|
+
? E('div', { className: 'bw-browse-new-row' },
|
|
3514
|
+
E('input', {
|
|
3515
|
+
className: 'bw-input',
|
|
3516
|
+
value: createName,
|
|
3517
|
+
autoFocus: true,
|
|
3518
|
+
spellCheck: false,
|
|
3519
|
+
placeholder: t('browse.folderName'),
|
|
3520
|
+
disabled: createBusy,
|
|
3521
|
+
onChange: (e) => setCreateName(e.target.value),
|
|
3522
|
+
onKeyDown: (e) => {
|
|
3523
|
+
if (e.key === 'Enter') submitCreate()
|
|
3524
|
+
else if (e.key === 'Escape') { setCreating(false); setCreateError('') }
|
|
3525
|
+
},
|
|
3526
|
+
}),
|
|
3527
|
+
E(BTN, {
|
|
3528
|
+
variant: 'outline',
|
|
3529
|
+
disabled: createBusy,
|
|
3530
|
+
onClick: () => { setCreating(false); setCreateError('') },
|
|
3531
|
+
}, t('cancel')),
|
|
3532
|
+
E(BTN, { variant: 'primary', onClick: submitCreate, disabled: createBusy },
|
|
3533
|
+
createBusy ? t('flow.creating') : t('create')),
|
|
3534
|
+
)
|
|
3535
|
+
: null
|
|
3536
|
+
|
|
3537
|
+
// The dialog is deliberately narrower than the official browser, so the
|
|
3538
|
+
// two browsing tools sit on their own row above the list instead of
|
|
3539
|
+
// sharing the footer with the commit pair (they used to wrap there).
|
|
3540
|
+
const tools = E('div', { className: 'bw-browse-tools' },
|
|
3541
|
+
E('button', {
|
|
3542
|
+
type: 'button',
|
|
3543
|
+
className: cls('bw-browse-tool', currentPath !== '' && currentPath === homePath && 'bw-browse-tool-on'),
|
|
3544
|
+
disabled: homePath === '' || currentPath === homePath,
|
|
3545
|
+
onClick: () => { if (homePath !== '') go(homePath) },
|
|
3546
|
+
}, t('browse.home')),
|
|
3547
|
+
E('button', {
|
|
3548
|
+
type: 'button',
|
|
3549
|
+
className: cls('bw-browse-tool', showHidden && 'bw-browse-tool-on'),
|
|
3550
|
+
'aria-pressed': showHidden ? 'true' : 'false',
|
|
3551
|
+
onClick: () => setShowHidden((value) => !value),
|
|
3552
|
+
}, t('browse.showHidden')),
|
|
3553
|
+
E('button', {
|
|
3554
|
+
type: 'button',
|
|
3555
|
+
className: 'bw-browse-tool',
|
|
3556
|
+
disabled: creating || currentPath === '',
|
|
3557
|
+
onClick: () => { setCreating(true); setCreateName(''); setCreateError('') },
|
|
3558
|
+
}, t('browse.newFolder')),
|
|
3559
|
+
drives.length > 1
|
|
3560
|
+
? E('div', {
|
|
3561
|
+
className: 'bw-browse-drives',
|
|
3562
|
+
title: t('browse.drives'),
|
|
3563
|
+
role: 'group',
|
|
3564
|
+
'aria-label': t('browse.drives'),
|
|
3565
|
+
}, drives.map((drive) => E('button', {
|
|
3566
|
+
key: drive,
|
|
3567
|
+
type: 'button',
|
|
3568
|
+
className: cls('bw-browse-drive', currentPath.slice(0, 2).toUpperCase() === drive && 'bw-browse-drive-on'),
|
|
3569
|
+
title: drive + '\\',
|
|
3570
|
+
onClick: () => go(drive + '\\'),
|
|
3571
|
+
}, drive)))
|
|
3572
|
+
: null,
|
|
3573
|
+
)
|
|
3574
|
+
|
|
3575
|
+
const footer = E('div', { className: 'bw-browse-actions' },
|
|
3576
|
+
E(BTN, { variant: 'outline', onClick: onClose, disabled: busy === true }, t('cancel')),
|
|
3577
|
+
E(BTN, {
|
|
3578
|
+
variant: 'primary',
|
|
3579
|
+
disabled: busy === true || targetPath === '',
|
|
3580
|
+
onClick: () => { if (targetPath !== '') onPick(targetPath) },
|
|
3581
|
+
}, selectedEntry !== null ? t('browse.selectNamed', { name: selectedEntry.name }) : t('browse.select')),
|
|
3582
|
+
)
|
|
3583
|
+
|
|
3584
|
+
return E(ui.Modal, {
|
|
3585
|
+
open: true,
|
|
3586
|
+
onClose: () => { if (busy !== true) onClose() },
|
|
3587
|
+
closeLabel: t('close'),
|
|
3588
|
+
title: t('browse.title'),
|
|
3589
|
+
footer,
|
|
3590
|
+
},
|
|
3591
|
+
E('div', { className: 'bw-modal-body bw-browse-body' },
|
|
3592
|
+
head,
|
|
3593
|
+
createError !== '' ? E('div', { className: 'bw-error-text', role: 'alert' }, createError) : null,
|
|
3594
|
+
tools,
|
|
3595
|
+
newRow,
|
|
3596
|
+
E('div', { className: 'bw-browse-list', role: 'list' }, body),
|
|
3597
|
+
listing && listing.truncated === true ? E('div', { className: 'bw-hint' }, t('browse.truncated')) : null,
|
|
3598
|
+
error !== '' ? E('div', { className: 'bw-error-text', role: 'alert' }, error) : null,
|
|
3599
|
+
),
|
|
3600
|
+
StyleNode())
|
|
3601
|
+
}
|
|
3602
|
+
|
|
3603
|
+
/**
|
|
3604
|
+
* The add-workspace picking interaction as the sidebar hole's OCCUPANT
|
|
3605
|
+
* (v0.12.1): the official flow cannot be rendered here — a child-hole
|
|
3606
|
+
* declaration is exclusive and the shipped browser entry, though it
|
|
3607
|
+
* loses the render race, still owns 'sidebar.workspaces.directoryFlow's
|
|
3608
|
+
* declaration, so re-declaring it throws and renderSlot authorization
|
|
3609
|
+
* is runtime-enforced. This flow therefore probes the composed backend
|
|
3610
|
+
* (v0.11.3 discipline): a native Host gets its OS chooser through the
|
|
3611
|
+
* SAME official host service (uiWorkspace.pickDirectory), a browse Host
|
|
3612
|
+
* gets the in-app DirectoryBrowseDialog (the v0.11.4/0.11.5 interaction
|
|
3613
|
+
* this plugin polished). A picked path is adopted by the owner with
|
|
3614
|
+
* createWorkspace + startSession — no parent-group popup (groups derive
|
|
3615
|
+
* from names; rename or drag to group). The conversation hero flow stays
|
|
3616
|
+
* purely official: this plugin does NOT occupy that hole.
|
|
3617
|
+
*/
|
|
3618
|
+
function BetterFlow(props) {
|
|
3619
|
+
const { open, busy, onPicked, onCancel, onError, pickDirectory, listDirectory, createDirectory, t } = props
|
|
3620
|
+
const [phase, setPhase] = React.useState('idle') // idle | picking | browsing
|
|
3621
|
+
// All hooks run before any early return: the flow unmounts while
|
|
3622
|
+
// closed, but its hook sequence must stay stable.
|
|
3623
|
+
React.useEffect(() => {
|
|
3624
|
+
if (!open) {
|
|
3625
|
+
setPhase('idle')
|
|
3626
|
+
return
|
|
3627
|
+
}
|
|
3628
|
+
let alive = true
|
|
3629
|
+
setPhase('picking')
|
|
3630
|
+
// Which backend this Host composes decides the interaction: browse
|
|
3631
|
+
// renders the in-app browser directly (its pick is refused by
|
|
3632
|
+
// design, so trying the chooser first would only surface an error);
|
|
3633
|
+
// native opens the OS chooser; an unresolved verdict still tries the
|
|
3634
|
+
// chooser so a silent probe never changes the fallback.
|
|
3635
|
+
pickerCapabilityNow()
|
|
3636
|
+
.then((kind) => {
|
|
3637
|
+
if (!alive) return undefined
|
|
3638
|
+
if (kind === 'browse') { setPhase('browsing'); return undefined }
|
|
3639
|
+
return Promise.resolve()
|
|
3640
|
+
.then(() => pickDirectory())
|
|
3641
|
+
.then((path) => {
|
|
3642
|
+
if (!alive) return
|
|
3643
|
+
if (!path) { onCancel(); return }
|
|
3644
|
+
onPicked(String(path))
|
|
3645
|
+
})
|
|
3646
|
+
})
|
|
3647
|
+
.catch((reason) => {
|
|
3648
|
+
if (!alive) return
|
|
3649
|
+
// A refusal is a backend fact, not a failure: this Host serves
|
|
3650
|
+
// the browse verbs, so switch interactions instead of erroring.
|
|
3651
|
+
if (pickerRefusal(reason)) { markPickerBrowse(); setPhase('browsing'); return }
|
|
3652
|
+
setPhase('idle')
|
|
3653
|
+
onError(messageOf(reason))
|
|
3654
|
+
})
|
|
3655
|
+
return () => { alive = false }
|
|
3656
|
+
}, [open])
|
|
3657
|
+
|
|
3658
|
+
if (!open) return null
|
|
3659
|
+
if (phase === 'browsing') {
|
|
3660
|
+
return E(DirectoryBrowseDialog, {
|
|
3661
|
+
open: true,
|
|
3662
|
+
busy: busy === true,
|
|
3663
|
+
listDirectory,
|
|
3664
|
+
createDirectory,
|
|
3665
|
+
onPick: (path) => onPicked(String(path)),
|
|
3666
|
+
onClose: onCancel,
|
|
3667
|
+
t,
|
|
3668
|
+
})
|
|
3669
|
+
}
|
|
3670
|
+
return null
|
|
3671
|
+
}
|
|
3672
|
+
|
|
3673
|
+
|
|
2867
3674
|
/* ============================== rows ============================== */
|
|
2868
3675
|
|
|
2869
3676
|
/** Folder glyph variants from the primitives family (solid / outline / hidden). */
|
|
@@ -3614,7 +4421,8 @@ window.__ModuleLoader__.load({
|
|
|
3614
4421
|
startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace,
|
|
3615
4422
|
archiveSession, createWorkspace,
|
|
3616
4423
|
insertWorkspaceBefore, insertSessionBefore,
|
|
3617
|
-
useDirectoryFlow,
|
|
4424
|
+
useDirectoryFlow,
|
|
4425
|
+
pickDirectory, listDirectory, createDirectory,
|
|
3618
4426
|
t,
|
|
3619
4427
|
} = props
|
|
3620
4428
|
|
|
@@ -3712,10 +4520,9 @@ window.__ModuleLoader__.load({
|
|
|
3712
4520
|
const [searchOpen, setSearchOpen] = React.useState(false)
|
|
3713
4521
|
const [flowOpen, setFlowOpen] = React.useState(false)
|
|
3714
4522
|
const [flowBusy, setFlowBusy] = React.useState(false) // createWorkspace in flight after a pick
|
|
3715
|
-
// Occupancy of this surface's directory-flow hole (
|
|
3716
|
-
//
|
|
3717
|
-
//
|
|
3718
|
-
// occupant that unloads mid-interaction withdraws an open flow.
|
|
4523
|
+
// Occupancy of this surface's directory-flow hole (our own picking
|
|
4524
|
+
// flow fills it): gates the add affordance, and an occupant that
|
|
4525
|
+
// unloads mid-interaction withdraws an open flow.
|
|
3719
4526
|
const flowAvailable = typeof useDirectoryFlow === 'function' ? useDirectoryFlow(occupied => occupied) : false
|
|
3720
4527
|
React.useEffect(() => {
|
|
3721
4528
|
if (flowOpen && !flowAvailable) setFlowOpen(false)
|
|
@@ -4609,7 +5416,8 @@ window.__ModuleLoader__.load({
|
|
|
4609
5416
|
|
|
4610
5417
|
// The owner side of the flow conversation: adopt keeps the flow open
|
|
4611
5418
|
// (busy) until the Host answers, then startSession — exactly what the
|
|
4612
|
-
// shipped browser does after the same pick.
|
|
5419
|
+
// shipped browser does after the same pick. Groups never ride this
|
|
5420
|
+
// flow (v0.12.0+): rename or drag to group instead.
|
|
4613
5421
|
const adoptDirectory = (path) => Promise.resolve()
|
|
4614
5422
|
.then(() => createWorkspace({ path: String(path) }))
|
|
4615
5423
|
.then((workspace) => {
|
|
@@ -4649,10 +5457,19 @@ window.__ModuleLoader__.load({
|
|
|
4649
5457
|
flowAvailable ? E('button', { type: 'button', className: 'bw-icon-btn', 'aria-label': t('add'), onClick: () => setFlowOpen(true) }, icon('IconProjectAddOutline16')) : null,
|
|
4650
5458
|
),
|
|
4651
5459
|
E('div', { ref: treeRef, className: 'bw-tree', role: 'tree', 'aria-label': t('title') }, bodyRows),
|
|
4652
|
-
//
|
|
4653
|
-
//
|
|
4654
|
-
|
|
4655
|
-
|
|
5460
|
+
// Our own occupant serves the sidebar hole (direct composition, the
|
|
5461
|
+
// same props conversation the official owner share defines).
|
|
5462
|
+
E(BetterFlow, {
|
|
5463
|
+
open: flowOpen,
|
|
5464
|
+
busy: flowBusy,
|
|
5465
|
+
onPicked: flowOwner.onPicked,
|
|
5466
|
+
onCancel: flowOwner.onCancel,
|
|
5467
|
+
onError: flowOwner.onError,
|
|
5468
|
+
pickDirectory,
|
|
5469
|
+
listDirectory,
|
|
5470
|
+
createDirectory,
|
|
5471
|
+
t,
|
|
5472
|
+
}),
|
|
4656
5473
|
dialogElement,
|
|
4657
5474
|
ctx !== null ? E('div', {
|
|
4658
5475
|
className: 'bw-ctx-overlay',
|
|
@@ -4733,6 +5550,11 @@ window.__ModuleLoader__.load({
|
|
|
4733
5550
|
})
|
|
4734
5551
|
return
|
|
4735
5552
|
}
|
|
5553
|
+
// The capability probe talks to the composed backend through this
|
|
5554
|
+
// service (list is served by browse alone), so the state module needs
|
|
5555
|
+
// the handle before any flow can open.
|
|
5556
|
+
pickerState.api = uiWorkspace
|
|
5557
|
+
|
|
4736
5558
|
if (ctx.locale && typeof ctx.locale.register === 'function') {
|
|
4737
5559
|
ctx.effect(() => {
|
|
4738
5560
|
try {
|
|
@@ -4782,14 +5604,26 @@ window.__ModuleLoader__.load({
|
|
|
4782
5604
|
: undefined,
|
|
4783
5605
|
archiveSession: (sessionId) => uiWorkspace.archiveSession(sessionId),
|
|
4784
5606
|
createWorkspace: (input) => workspaces.create(input),
|
|
4785
|
-
|
|
4786
|
-
//
|
|
4787
|
-
// the
|
|
5607
|
+
pickDirectory: () => uiWorkspace.pickDirectory(),
|
|
5608
|
+
// Browse-side primitives: the in-app dialog needs them whenever the
|
|
5609
|
+
// Host composes the browse backend instead of the OS chooser.
|
|
5610
|
+
listDirectory: (path, signal) => uiWorkspace.listDirectory(path, signal),
|
|
5611
|
+
createDirectory: (path, name) => uiWorkspace.createDirectory(path, name),
|
|
4788
5612
|
hooks: {
|
|
4789
5613
|
directoryFlow: flowSource(slots, 'sidebar.workspaces.directoryFlow'),
|
|
4790
5614
|
},
|
|
4791
5615
|
})
|
|
4792
5616
|
|
|
5617
|
+
// Occupant inject face for the sidebar hole (v0.12.1): the picking
|
|
5618
|
+
// primitives BetterFlow needs. The conversation hero hole is
|
|
5619
|
+
// deliberately NOT occupied — that flow stays purely official.
|
|
5620
|
+
const flowInjected = () => ({
|
|
5621
|
+
pickDirectory: () => uiWorkspace.pickDirectory(),
|
|
5622
|
+
listDirectory: (path, signal) => uiWorkspace.listDirectory(path, signal),
|
|
5623
|
+
createDirectory: (path, name) => uiWorkspace.createDirectory(path, name),
|
|
5624
|
+
hooks: { directoryFlow: flowSource(slots, 'sidebar.workspaces.directoryFlow') },
|
|
5625
|
+
})
|
|
5626
|
+
|
|
4793
5627
|
// Registration helper: a thrown register (semantics drift, vanishing
|
|
4794
5628
|
// hole declaration mid-transition) degrades this one seat, never the
|
|
4795
5629
|
// plugin fiber — the whole web boot is all-or-nothing.
|
|
@@ -4802,11 +5636,14 @@ window.__ModuleLoader__.load({
|
|
|
4802
5636
|
}
|
|
4803
5637
|
}
|
|
4804
5638
|
|
|
4805
|
-
// The
|
|
4806
|
-
//
|
|
4807
|
-
//
|
|
4808
|
-
//
|
|
4809
|
-
//
|
|
5639
|
+
// The SIDEBAR hole is occupied by our own picking flow at priority -1:
|
|
5640
|
+
// rendering the official occupant is impossible here (a child-hole
|
|
5641
|
+
// declaration is exclusive — the shipped browser entry keeps owning
|
|
5642
|
+
// 'sidebar.workspaces.directoryFlow' even after losing the render race,
|
|
5643
|
+
// and re-declaring throws), so the flow probes the backend itself — the
|
|
5644
|
+
// OS chooser through the same official host service on native Hosts,
|
|
5645
|
+
// the polished in-app dialog on browse Hosts. The HERO hole stays
|
|
5646
|
+
// untouched: the conversation empty-state flow is purely official.
|
|
4810
5647
|
|
|
4811
5648
|
// One shared store handle: the browser and the settings page must see the
|
|
4812
5649
|
// same persisted state (expansion, folder list, prefs, styling).
|
|
@@ -4831,6 +5668,15 @@ window.__ModuleLoader__.load({
|
|
|
4831
5668
|
prefsScopeRef = null
|
|
4832
5669
|
}
|
|
4833
5670
|
|
|
5671
|
+
// The sidebar hole occupation itself (v0.12.1): priority -1 shadows
|
|
5672
|
+
// the official composed picker inside the hole — the hero hole keeps
|
|
5673
|
+
// the official occupant, so the conversation flow stays stock.
|
|
5674
|
+
slots.inject('sidebar.workspaces.directoryFlow', guarded(
|
|
5675
|
+
'sidebar.workspaces.directoryFlow',
|
|
5676
|
+
{ name: 'sidebar.workspaces.directoryFlow', inject: flowInjected, locale: NS, priority: -1 },
|
|
5677
|
+
BetterFlow,
|
|
5678
|
+
))
|
|
5679
|
+
|
|
4834
5680
|
// Settings → Plugins card only (the tab dispatches the intersection of
|
|
4835
5681
|
// served namespaces — registered host-side — and settings.plugin.item
|
|
4836
5682
|
// cards). The old left-nav settings.section entry was removed: the
|
|
@@ -4863,17 +5709,16 @@ window.__ModuleLoader__.load({
|
|
|
4863
5709
|
))
|
|
4864
5710
|
|
|
4865
5711
|
// the browser itself — lowest priority renders, shadowing the shipped
|
|
4866
|
-
// entry.
|
|
4867
|
-
//
|
|
4868
|
-
//
|
|
4869
|
-
//
|
|
4870
|
-
//
|
|
5712
|
+
// entry. NO children declaration here (v0.12.1): the child-hole
|
|
5713
|
+
// declaration is exclusive and the shipped entry already owns it —
|
|
5714
|
+
// re-declaring would throw and silently unregister this seat (the
|
|
5715
|
+
// v0.12.0 regression). The add flow rides the sidebar-hole occupation
|
|
5716
|
+
// registered above instead.
|
|
4871
5717
|
slots.inject('sidebar.workspaces', guarded(
|
|
4872
5718
|
'sidebar.workspaces',
|
|
4873
5719
|
{
|
|
4874
5720
|
name: 'sidebar.workspaces',
|
|
4875
5721
|
priority: -1,
|
|
4876
|
-
children: { 'sidebar.workspaces.directoryFlow': { kind: 'single', scope: 'root' } },
|
|
4877
5722
|
store: viewStore,
|
|
4878
5723
|
inject: browserInjected,
|
|
4879
5724
|
locale: NS,
|