dsh-better-workspace 0.11.1 → 0.11.3
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/README.md +2 -0
- package/README_EN.md +2 -0
- package/dsh.plugin.json +1 -1
- package/package.json +1 -1
- package/src/client.js +658 -74
package/README.md
CHANGED
|
@@ -27,6 +27,8 @@ web/ ← 虚拟分组(不是真实目录,纯命名层级)
|
|
|
27
27
|
|
|
28
28
|
点「添加工作区」并选完文件夹后,弹出一个小对话框,一行选择工作区所属分组——直接输入(如 `web`)、从下拉里选已有层级(datalist 原生补全),留空就是根分组。确认后插件创建工作区并把分组前缀写进名称,树上自然落位。
|
|
29
29
|
|
|
30
|
+
**用哪种目录选择器,跟随宿主实际组装的后端**:webserver 只绑本机 loopback 时是宿主桌面上的**原生选择器**;绑到 `0.0.0.0`(局域网访问)、SSH 转发或宿主没有可用显示会话时,宿主只组装 `browse` 后端——它仅提供「列目录 / 新建目录」原语,`pick` 会被明确拒绝——插件这时改用**应用内目录浏览器**(Home 起点的面包屑、可编辑路径、行内新建文件夹、显示隐藏文件),选定目录后照样进同一个「所属分组」对话框。若确实想在这种绑定下走原生选择器,只能由用户显式钉死宿主的 native 后端包,而那会在**服务器桌面**上弹窗,局域网客户端根本够不着。
|
|
31
|
+
|
|
30
32
|
### 分组的增删
|
|
31
33
|
|
|
32
34
|
右键任意分组行即可:**新增子分组**(路径自动带上父分组前缀)、**新增子工作区**(选完目录后所属分组输入框自动预填)、重命名分组、删除空分组;标题栏不再放独立按钮。空分组持久保存在浏览器本地;一旦有工作区落进去,分组就由名称自然派生。
|
package/README_EN.md
CHANGED
|
@@ -25,6 +25,8 @@ web/ <- virtual folder (naming only, not a real directory)
|
|
|
25
25
|
|
|
26
26
|
After you pick a directory for a new workspace, a small dialog asks for the parent group — type one (`web`), pick an existing level from the datalist, or leave it empty for the root. The plugin creates the workspace and writes the prefix into its title, so it lands on the right branch of the tree.
|
|
27
27
|
|
|
28
|
+
**Which picker you get follows the backend the host actually composes.** A loopback-only webserver bind gives you the host's **native OS chooser**; an all-interfaces/LAN bind, an SSH launch, or a host without a usable display session composes the `browse` backend instead — listing and directory creation only, with `pick` refused by design — and the plugin then uses an **in-app directory browser** (Home-rooted breadcrumbs, editable path, inline new-folder row, hidden-file toggle) before the very same parent-group dialog. Forcing the native chooser under such a bind means pinning the host's native backend package yourself, and that dialog opens on the **server's** desktop, out of reach for LAN clients.
|
|
29
|
+
|
|
28
30
|
### Folder create/delete via context menu
|
|
29
31
|
|
|
30
32
|
Right-click any folder row: new subfolder (parent prefix pre-filled), new workspace here (the add-flow group field pre-fills with this folder), rename folder, delete empty folder — no standalone header button anymore. Explicit empty folders persist in the browser until workspaces live inside them.
|
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.3",
|
|
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.3",
|
|
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
|
@@ -116,6 +116,17 @@ window.__ModuleLoader__.load({
|
|
|
116
116
|
'flow.parent': '所属分组',
|
|
117
117
|
'flow.parentHint': '输入或下拉选择分组路径,留空表示根分组;多级用 / 分隔',
|
|
118
118
|
'flow.creating': '正在创建…',
|
|
119
|
+
'browse.title': '选择工作区目录',
|
|
120
|
+
'browse.home': '主目录',
|
|
121
|
+
'browse.up': '上一层',
|
|
122
|
+
'browse.newFolder': '新建文件夹',
|
|
123
|
+
'browse.folderName': '文件夹名称',
|
|
124
|
+
'browse.empty': '此文件夹没有子文件夹',
|
|
125
|
+
'browse.loading': '加载中…',
|
|
126
|
+
'browse.truncated': '文件夹过多,仅显示开头部分。',
|
|
127
|
+
'browse.showHidden': '显示隐藏文件',
|
|
128
|
+
'browse.editPath': '编辑路径',
|
|
129
|
+
'browse.select': '选择此文件夹',
|
|
119
130
|
'error.title': '出错了',
|
|
120
131
|
'cancel': '取消',
|
|
121
132
|
'create': '创建',
|
|
@@ -224,6 +235,17 @@ window.__ModuleLoader__.load({
|
|
|
224
235
|
'flow.parent': 'Parent group',
|
|
225
236
|
'flow.parentHint': 'Type or pick a group path; empty means root. Nest with /',
|
|
226
237
|
'flow.creating': 'Creating…',
|
|
238
|
+
'browse.title': 'Select Workspace Directory',
|
|
239
|
+
'browse.home': 'Home',
|
|
240
|
+
'browse.up': 'Up',
|
|
241
|
+
'browse.newFolder': 'New folder',
|
|
242
|
+
'browse.folderName': 'Folder name',
|
|
243
|
+
'browse.empty': 'No subfolders here',
|
|
244
|
+
'browse.loading': 'Loading…',
|
|
245
|
+
'browse.truncated': 'Too many folders to list; only the beginning is shown.',
|
|
246
|
+
'browse.showHidden': 'Show hidden files',
|
|
247
|
+
'browse.editPath': 'Edit path',
|
|
248
|
+
'browse.select': 'Use this folder',
|
|
227
249
|
'error.title': 'Something went wrong',
|
|
228
250
|
'cancel': 'Cancel',
|
|
229
251
|
'create': 'Create',
|
|
@@ -340,6 +362,17 @@ window.__ModuleLoader__.load({
|
|
|
340
362
|
'flow.parent': 'المجموعة التابعة لها',
|
|
341
363
|
'flow.parentHint': 'اكتب مسار المجموعة أو اختره؛ الفراغ يعني الجذر؛ تُفصل المستويات بـ /',
|
|
342
364
|
'flow.creating': 'جارٍ الإنشاء…',
|
|
365
|
+
'browse.title': 'اختيار مجلد مساحة العمل',
|
|
366
|
+
'browse.home': 'المجلد الرئيسي',
|
|
367
|
+
'browse.up': 'المستوى الأعلى',
|
|
368
|
+
'browse.newFolder': 'مجلد جديد',
|
|
369
|
+
'browse.folderName': 'اسم المجلد',
|
|
370
|
+
'browse.empty': 'لا توجد مجلدات فرعية هنا',
|
|
371
|
+
'browse.loading': 'جارٍ التحميل…',
|
|
372
|
+
'browse.truncated': 'المجلدات كثيرة جدًا؛ يُعرض الجزء الأول فقط.',
|
|
373
|
+
'browse.showHidden': 'إظهار الملفات المخفية',
|
|
374
|
+
'browse.editPath': 'تعديل المسار',
|
|
375
|
+
'browse.select': 'استخدام هذا المجلد',
|
|
343
376
|
'error.title': 'حدث خطأ ما',
|
|
344
377
|
'cancel': 'إلغاء',
|
|
345
378
|
'create': 'إنشاء',
|
|
@@ -448,6 +481,17 @@ window.__ModuleLoader__.load({
|
|
|
448
481
|
'flow.parent': 'Übergeordnete Gruppe',
|
|
449
482
|
'flow.parentHint': 'Gruppenpfad eingeben oder auswählen, leer bedeutet Wurzel; Ebenen mit / trennen',
|
|
450
483
|
'flow.creating': 'Wird erstellt…',
|
|
484
|
+
'browse.title': 'Arbeitsbereichsordner wählen',
|
|
485
|
+
'browse.home': 'Home',
|
|
486
|
+
'browse.up': 'Übergeordnet',
|
|
487
|
+
'browse.newFolder': 'Neuer Ordner',
|
|
488
|
+
'browse.folderName': 'Ordnername',
|
|
489
|
+
'browse.empty': 'Keine Unterordner vorhanden',
|
|
490
|
+
'browse.loading': 'Wird geladen…',
|
|
491
|
+
'browse.truncated': 'Zu viele Ordner; nur der Anfang wird angezeigt.',
|
|
492
|
+
'browse.showHidden': 'Versteckte Dateien anzeigen',
|
|
493
|
+
'browse.editPath': 'Pfad bearbeiten',
|
|
494
|
+
'browse.select': 'Diesen Ordner verwenden',
|
|
451
495
|
'error.title': 'Etwas ist schiefgelaufen',
|
|
452
496
|
'cancel': 'Abbrechen',
|
|
453
497
|
'create': 'Erstellen',
|
|
@@ -556,6 +600,17 @@ window.__ModuleLoader__.load({
|
|
|
556
600
|
'flow.parent': 'Groupe parent',
|
|
557
601
|
'flow.parentHint': 'Saisissez ou choisissez un chemin de groupe; vide = racine; niveaux séparés par /',
|
|
558
602
|
'flow.creating': 'Création…',
|
|
603
|
+
'browse.title': 'Choisir le dossier de l’espace de travail',
|
|
604
|
+
'browse.home': 'Dossier personnel',
|
|
605
|
+
'browse.up': 'Niveau supérieur',
|
|
606
|
+
'browse.newFolder': 'Nouveau dossier',
|
|
607
|
+
'browse.folderName': 'Nom du dossier',
|
|
608
|
+
'browse.empty': 'Aucun sous-dossier ici',
|
|
609
|
+
'browse.loading': 'Chargement…',
|
|
610
|
+
'browse.truncated': 'Trop de dossiers ; seul le début est affiché.',
|
|
611
|
+
'browse.showHidden': 'Afficher les fichiers cachés',
|
|
612
|
+
'browse.editPath': 'Modifier le chemin',
|
|
613
|
+
'browse.select': 'Utiliser ce dossier',
|
|
559
614
|
'error.title': 'Une erreur est survenue',
|
|
560
615
|
'cancel': 'Annuler',
|
|
561
616
|
'create': 'Créer',
|
|
@@ -664,6 +719,17 @@ window.__ModuleLoader__.load({
|
|
|
664
719
|
'flow.parent': 'संबंधित समूह',
|
|
665
720
|
'flow.parentHint': 'समूह पथ लिखें या चुनें; खाली का अर्थ मूल है; कई स्तर / से अलग करें',
|
|
666
721
|
'flow.creating': 'बनाया जा रहा है…',
|
|
722
|
+
'browse.title': 'कार्यस्थान फ़ोल्डर चुनें',
|
|
723
|
+
'browse.home': 'होम',
|
|
724
|
+
'browse.up': 'ऊपर',
|
|
725
|
+
'browse.newFolder': 'नया फ़ोल्डर',
|
|
726
|
+
'browse.folderName': 'फ़ोल्डर का नाम',
|
|
727
|
+
'browse.empty': 'यहाँ कोई उप-फ़ोल्डर नहीं',
|
|
728
|
+
'browse.loading': 'लोड हो रहा है…',
|
|
729
|
+
'browse.truncated': 'बहुत अधिक फ़ोल्डर; केवल शुरुआत दिखाई गई है।',
|
|
730
|
+
'browse.showHidden': 'छिपी फ़ाइलें दिखाएँ',
|
|
731
|
+
'browse.editPath': 'पथ संपादित करें',
|
|
732
|
+
'browse.select': 'यह फ़ोल्डर चुनें',
|
|
667
733
|
'error.title': 'कुछ गड़बड़ हो गई',
|
|
668
734
|
'cancel': 'रद्द करें',
|
|
669
735
|
'create': 'बनाएँ',
|
|
@@ -772,6 +838,17 @@ window.__ModuleLoader__.load({
|
|
|
772
838
|
'flow.parent': 'Grup induk',
|
|
773
839
|
'flow.parentHint': 'Ketik atau pilih jalur grup; kosong = akar; tingkat dipisah dengan /',
|
|
774
840
|
'flow.creating': 'Membuat…',
|
|
841
|
+
'browse.title': 'Pilih folder ruang kerja',
|
|
842
|
+
'browse.home': 'Beranda',
|
|
843
|
+
'browse.up': 'Naik',
|
|
844
|
+
'browse.newFolder': 'Folder baru',
|
|
845
|
+
'browse.folderName': 'Nama folder',
|
|
846
|
+
'browse.empty': 'Tidak ada subfolder di sini',
|
|
847
|
+
'browse.loading': 'Memuat…',
|
|
848
|
+
'browse.truncated': 'Terlalu banyak folder; hanya bagian awal yang ditampilkan.',
|
|
849
|
+
'browse.showHidden': 'Tampilkan file tersembunyi',
|
|
850
|
+
'browse.editPath': 'Edit jalur',
|
|
851
|
+
'browse.select': 'Gunakan folder ini',
|
|
775
852
|
'error.title': 'Terjadi kesalahan',
|
|
776
853
|
'cancel': 'Batal',
|
|
777
854
|
'create': 'Buat',
|
|
@@ -880,6 +957,17 @@ window.__ModuleLoader__.load({
|
|
|
880
957
|
'flow.parent': 'Gruppo di appartenenza',
|
|
881
958
|
'flow.parentHint': 'Digita o scegli un percorso di gruppo; vuoto = radice; livelli separati da /',
|
|
882
959
|
'flow.creating': 'Creazione…',
|
|
960
|
+
'browse.title': 'Scegli cartella dell’area di lavoro',
|
|
961
|
+
'browse.home': 'Home',
|
|
962
|
+
'browse.up': 'Livello superiore',
|
|
963
|
+
'browse.newFolder': 'Nuova cartella',
|
|
964
|
+
'browse.folderName': 'Nome cartella',
|
|
965
|
+
'browse.empty': 'Nessuna sottocartella qui',
|
|
966
|
+
'browse.loading': 'Caricamento…',
|
|
967
|
+
'browse.truncated': 'Troppe cartelle; è mostrato solo l’inizio.',
|
|
968
|
+
'browse.showHidden': 'Mostra file nascosti',
|
|
969
|
+
'browse.editPath': 'Modifica percorso',
|
|
970
|
+
'browse.select': 'Usa questa cartella',
|
|
883
971
|
'error.title': 'Si è verificato un errore',
|
|
884
972
|
'cancel': 'Annulla',
|
|
885
973
|
'create': 'Crea',
|
|
@@ -988,6 +1076,17 @@ window.__ModuleLoader__.load({
|
|
|
988
1076
|
'flow.parent': '所属グループ',
|
|
989
1077
|
'flow.parentHint': 'グループのパスを入力または選択します,空欄はルート,階層は / で区切ります',
|
|
990
1078
|
'flow.creating': '作成中…',
|
|
1079
|
+
'browse.title': 'ワークスペースのフォルダーを選択',
|
|
1080
|
+
'browse.home': 'ホーム',
|
|
1081
|
+
'browse.up': '上の階層',
|
|
1082
|
+
'browse.newFolder': '新しいフォルダー',
|
|
1083
|
+
'browse.folderName': 'フォルダー名',
|
|
1084
|
+
'browse.empty': 'ここにサブフォルダーはありません',
|
|
1085
|
+
'browse.loading': '読み込み中…',
|
|
1086
|
+
'browse.truncated': 'フォルダーが多すぎます。先頭のみ表示しています。',
|
|
1087
|
+
'browse.showHidden': '隠しファイルを表示',
|
|
1088
|
+
'browse.editPath': 'パスを編集',
|
|
1089
|
+
'browse.select': 'このフォルダーを選択',
|
|
991
1090
|
'error.title': 'エラーが発生しました',
|
|
992
1091
|
'cancel': 'キャンセル',
|
|
993
1092
|
'create': '作成',
|
|
@@ -1096,6 +1195,17 @@ window.__ModuleLoader__.load({
|
|
|
1096
1195
|
'flow.parent': '소속 그룹',
|
|
1097
1196
|
'flow.parentHint': '그룹 경로를 입력하거나 선택하세요, 비우면 루트, 여러 단계는 / 로 구분합니다',
|
|
1098
1197
|
'flow.creating': '만드는 중…',
|
|
1198
|
+
'browse.title': '워크스페이스 폴더 선택',
|
|
1199
|
+
'browse.home': '홈',
|
|
1200
|
+
'browse.up': '상위',
|
|
1201
|
+
'browse.newFolder': '새 폴더',
|
|
1202
|
+
'browse.folderName': '폴더 이름',
|
|
1203
|
+
'browse.empty': '여기에 하위 폴더가 없습니다',
|
|
1204
|
+
'browse.loading': '불러오는 중…',
|
|
1205
|
+
'browse.truncated': '폴더가 너무 많습니다. 앞부분만 표시합니다.',
|
|
1206
|
+
'browse.showHidden': '숨김 파일 표시',
|
|
1207
|
+
'browse.editPath': '경로 편집',
|
|
1208
|
+
'browse.select': '이 폴더 선택',
|
|
1099
1209
|
'error.title': '문제가 발생했습니다',
|
|
1100
1210
|
'cancel': '취소',
|
|
1101
1211
|
'create': '만들기',
|
|
@@ -1204,6 +1314,17 @@ window.__ModuleLoader__.load({
|
|
|
1204
1314
|
'flow.parent': 'Bovenliggende groep',
|
|
1205
1315
|
'flow.parentHint': 'Typ of kies een groeppad; leeg betekent hoofdmap; niveaus scheiden met /',
|
|
1206
1316
|
'flow.creating': 'Bezig met aanmaken…',
|
|
1317
|
+
'browse.title': 'Werkruimtemap kiezen',
|
|
1318
|
+
'browse.home': 'Home',
|
|
1319
|
+
'browse.up': 'Omhoog',
|
|
1320
|
+
'browse.newFolder': 'Nieuwe map',
|
|
1321
|
+
'browse.folderName': 'Mapnaam',
|
|
1322
|
+
'browse.empty': 'Geen submappen hier',
|
|
1323
|
+
'browse.loading': 'Laden…',
|
|
1324
|
+
'browse.truncated': 'Te veel mappen; alleen het begin wordt getoond.',
|
|
1325
|
+
'browse.showHidden': 'Verborgen bestanden tonen',
|
|
1326
|
+
'browse.editPath': 'Pad bewerken',
|
|
1327
|
+
'browse.select': 'Deze map gebruiken',
|
|
1207
1328
|
'error.title': 'Er is iets misgegaan',
|
|
1208
1329
|
'cancel': 'Annuleren',
|
|
1209
1330
|
'create': 'Aanmaken',
|
|
@@ -1312,6 +1433,17 @@ window.__ModuleLoader__.load({
|
|
|
1312
1433
|
'flow.parent': 'Grupa nadrzędna',
|
|
1313
1434
|
'flow.parentHint': 'Wpisz lub wybierz ścieżkę grupy; puste = główna; poziomy rozdziela /',
|
|
1314
1435
|
'flow.creating': 'Tworzenie…',
|
|
1436
|
+
'browse.title': 'Wybierz folder obszaru roboczego',
|
|
1437
|
+
'browse.home': 'Katalog domowy',
|
|
1438
|
+
'browse.up': 'W górę',
|
|
1439
|
+
'browse.newFolder': 'Nowy folder',
|
|
1440
|
+
'browse.folderName': 'Nazwa folderu',
|
|
1441
|
+
'browse.empty': 'Brak podfolderów',
|
|
1442
|
+
'browse.loading': 'Wczytywanie…',
|
|
1443
|
+
'browse.truncated': 'Zbyt wiele folderów; pokazano tylko początek.',
|
|
1444
|
+
'browse.showHidden': 'Pokaż ukryte pliki',
|
|
1445
|
+
'browse.editPath': 'Edytuj ścieżkę',
|
|
1446
|
+
'browse.select': 'Użyj tego folderu',
|
|
1315
1447
|
'error.title': 'Coś poszło nie tak',
|
|
1316
1448
|
'cancel': 'Anuluj',
|
|
1317
1449
|
'create': 'Utwórz',
|
|
@@ -1420,6 +1552,17 @@ window.__ModuleLoader__.load({
|
|
|
1420
1552
|
'flow.parent': 'Grupo pai',
|
|
1421
1553
|
'flow.parentHint': 'Digite ou escolha o caminho do grupo; vazio = raiz; níveis separados por /',
|
|
1422
1554
|
'flow.creating': 'Criando…',
|
|
1555
|
+
'browse.title': 'Escolher pasta da área de trabalho',
|
|
1556
|
+
'browse.home': 'Início',
|
|
1557
|
+
'browse.up': 'Acima',
|
|
1558
|
+
'browse.newFolder': 'Nova pasta',
|
|
1559
|
+
'browse.folderName': 'Nome da pasta',
|
|
1560
|
+
'browse.empty': 'Nenhuma subpasta aqui',
|
|
1561
|
+
'browse.loading': 'Carregando…',
|
|
1562
|
+
'browse.truncated': 'Pastas demais; apenas o início é exibido.',
|
|
1563
|
+
'browse.showHidden': 'Mostrar arquivos ocultos',
|
|
1564
|
+
'browse.editPath': 'Editar caminho',
|
|
1565
|
+
'browse.select': 'Usar esta pasta',
|
|
1423
1566
|
'error.title': 'Algo deu errado',
|
|
1424
1567
|
'cancel': 'Cancelar',
|
|
1425
1568
|
'create': 'Criar',
|
|
@@ -1528,6 +1671,17 @@ window.__ModuleLoader__.load({
|
|
|
1528
1671
|
'flow.parent': 'Родительская группа',
|
|
1529
1672
|
'flow.parentHint': 'Введите или выберите путь группы; пусто — корень; уровни разделяются /',
|
|
1530
1673
|
'flow.creating': 'Создание…',
|
|
1674
|
+
'browse.title': 'Выбор папки рабочей области',
|
|
1675
|
+
'browse.home': 'Домашняя папка',
|
|
1676
|
+
'browse.up': 'На уровень выше',
|
|
1677
|
+
'browse.newFolder': 'Новая папка',
|
|
1678
|
+
'browse.folderName': 'Имя папки',
|
|
1679
|
+
'browse.empty': 'Здесь нет вложенных папок',
|
|
1680
|
+
'browse.loading': 'Загрузка…',
|
|
1681
|
+
'browse.truncated': 'Слишком много папок; показано только начало.',
|
|
1682
|
+
'browse.showHidden': 'Показывать скрытые файлы',
|
|
1683
|
+
'browse.editPath': 'Изменить путь',
|
|
1684
|
+
'browse.select': 'Выбрать эту папку',
|
|
1531
1685
|
'error.title': 'Что-то пошло не так',
|
|
1532
1686
|
'cancel': 'Отмена',
|
|
1533
1687
|
'create': 'Создать',
|
|
@@ -1636,6 +1790,17 @@ window.__ModuleLoader__.load({
|
|
|
1636
1790
|
'flow.parent': 'Överordnad grupp',
|
|
1637
1791
|
'flow.parentHint': 'Skriv eller välj en gruppsökväg; tomt = rot; nivåer avgränsas med /',
|
|
1638
1792
|
'flow.creating': 'Skapar…',
|
|
1793
|
+
'browse.title': 'Välj arbetsyte-mapp',
|
|
1794
|
+
'browse.home': 'Hem',
|
|
1795
|
+
'browse.up': 'Upp',
|
|
1796
|
+
'browse.newFolder': 'Ny mapp',
|
|
1797
|
+
'browse.folderName': 'Mappnamn',
|
|
1798
|
+
'browse.empty': 'Inga undermappar här',
|
|
1799
|
+
'browse.loading': 'Laddar…',
|
|
1800
|
+
'browse.truncated': 'För många mappar; endast början visas.',
|
|
1801
|
+
'browse.showHidden': 'Visa dolda filer',
|
|
1802
|
+
'browse.editPath': 'Redigera sökväg',
|
|
1803
|
+
'browse.select': 'Använd den här mappen',
|
|
1639
1804
|
'error.title': 'Något gick fel',
|
|
1640
1805
|
'cancel': 'Avbryt',
|
|
1641
1806
|
'create': 'Skapa',
|
|
@@ -1744,6 +1909,17 @@ window.__ModuleLoader__.load({
|
|
|
1744
1909
|
'flow.parent': 'กลุ่มต้นทาง',
|
|
1745
1910
|
'flow.parentHint': 'พิมพ์หรือเลือกเส้นทางกลุ่ม เว้นว่างคือระดับราก หลายระดับคั่นด้วย /',
|
|
1746
1911
|
'flow.creating': 'กำลังสร้าง…',
|
|
1912
|
+
'browse.title': 'เลือกโฟลเดอร์เวิร์กสเปซ',
|
|
1913
|
+
'browse.home': 'โฮม',
|
|
1914
|
+
'browse.up': 'ขึ้นหนึ่งระดับ',
|
|
1915
|
+
'browse.newFolder': 'โฟลเดอร์ใหม่',
|
|
1916
|
+
'browse.folderName': 'ชื่อโฟลเดอร์',
|
|
1917
|
+
'browse.empty': 'ไม่มีโฟลเดอร์ย่อยที่นี่',
|
|
1918
|
+
'browse.loading': 'กำลังโหลด…',
|
|
1919
|
+
'browse.truncated': 'มีโฟลเดอร์มากเกินไป แสดงเฉพาะส่วนต้น',
|
|
1920
|
+
'browse.showHidden': 'แสดงไฟล์ที่ซ่อนอยู่',
|
|
1921
|
+
'browse.editPath': 'แก้ไขเส้นทาง',
|
|
1922
|
+
'browse.select': 'ใช้โฟลเดอร์นี้',
|
|
1747
1923
|
'error.title': 'เกิดข้อผิดพลาด',
|
|
1748
1924
|
'cancel': 'ยกเลิก',
|
|
1749
1925
|
'create': 'สร้าง',
|
|
@@ -1852,6 +2028,17 @@ window.__ModuleLoader__.load({
|
|
|
1852
2028
|
'flow.parent': 'Bağlı olduğu grup',
|
|
1853
2029
|
'flow.parentHint': 'Grup yolunu yazın veya seçin; boş = kök; düzeyler / ile ayrılır',
|
|
1854
2030
|
'flow.creating': 'Oluşturuluyor…',
|
|
2031
|
+
'browse.title': 'Çalışma alanı klasörünü seç',
|
|
2032
|
+
'browse.home': 'Ana dizin',
|
|
2033
|
+
'browse.up': 'Üst düzey',
|
|
2034
|
+
'browse.newFolder': 'Yeni klasör',
|
|
2035
|
+
'browse.folderName': 'Klasör adı',
|
|
2036
|
+
'browse.empty': 'Burada alt klasör yok',
|
|
2037
|
+
'browse.loading': 'Yükleniyor…',
|
|
2038
|
+
'browse.truncated': 'Çok fazla klasör var; yalnızca başlangıç gösteriliyor.',
|
|
2039
|
+
'browse.showHidden': 'Gizli dosyaları göster',
|
|
2040
|
+
'browse.editPath': 'Yolu düzenle',
|
|
2041
|
+
'browse.select': 'Bu klasörü kullan',
|
|
1855
2042
|
'error.title': 'Bir şeyler ters gitti',
|
|
1856
2043
|
'cancel': 'İptal',
|
|
1857
2044
|
'create': 'Oluştur',
|
|
@@ -1960,6 +2147,17 @@ window.__ModuleLoader__.load({
|
|
|
1960
2147
|
'flow.parent': 'Nhóm trực thuộc',
|
|
1961
2148
|
'flow.parentHint': 'Nhập hoặc chọn đường dẫn nhóm; để trống là gốc; nhiều tầng cách nhau bằng /',
|
|
1962
2149
|
'flow.creating': 'Đang tạo…',
|
|
2150
|
+
'browse.title': 'Chọn thư mục không gian làm việc',
|
|
2151
|
+
'browse.home': 'Thư mục chính',
|
|
2152
|
+
'browse.up': 'Lên một cấp',
|
|
2153
|
+
'browse.newFolder': 'Thư mục mới',
|
|
2154
|
+
'browse.folderName': 'Tên thư mục',
|
|
2155
|
+
'browse.empty': 'Không có thư mục con ở đây',
|
|
2156
|
+
'browse.loading': 'Đang tải…',
|
|
2157
|
+
'browse.truncated': 'Quá nhiều thư mục; chỉ hiển thị phần đầu.',
|
|
2158
|
+
'browse.showHidden': 'Hiện tệp ẩn',
|
|
2159
|
+
'browse.editPath': 'Sửa đường dẫn',
|
|
2160
|
+
'browse.select': 'Dùng thư mục này',
|
|
1963
2161
|
'error.title': 'Đã xảy ra lỗi',
|
|
1964
2162
|
'cancel': 'Hủy',
|
|
1965
2163
|
'create': 'Tạo',
|
|
@@ -2068,6 +2266,17 @@ window.__ModuleLoader__.load({
|
|
|
2068
2266
|
'flow.parent': '所屬分組',
|
|
2069
2267
|
'flow.parentHint': '輸入或者揀分組路徑,留空代表根分組;多級用 / 分隔',
|
|
2070
2268
|
'flow.creating': '建立緊…',
|
|
2269
|
+
'browse.title': '揀工作區資料夾',
|
|
2270
|
+
'browse.home': '主目錄',
|
|
2271
|
+
'browse.up': '上一層',
|
|
2272
|
+
'browse.newFolder': '新增資料夾',
|
|
2273
|
+
'browse.folderName': '資料夾名稱',
|
|
2274
|
+
'browse.empty': '呢個資料夾冇子資料夾',
|
|
2275
|
+
'browse.loading': '載入緊…',
|
|
2276
|
+
'browse.truncated': '資料夾太多,只顯示開頭部分。',
|
|
2277
|
+
'browse.showHidden': '顯示隱藏檔案',
|
|
2278
|
+
'browse.editPath': '編輯路徑',
|
|
2279
|
+
'browse.select': '揀呢個資料夾',
|
|
2071
2280
|
'error.title': '出咗錯',
|
|
2072
2281
|
'cancel': '取消',
|
|
2073
2282
|
'create': '建立',
|
|
@@ -2176,6 +2385,17 @@ window.__ModuleLoader__.load({
|
|
|
2176
2385
|
'flow.parent': '所屬分組',
|
|
2177
2386
|
'flow.parentHint': '輸入或者揀分組路徑,留空代表根分組;多級用 / 分隔',
|
|
2178
2387
|
'flow.creating': '建立緊…',
|
|
2388
|
+
'browse.title': '揀工作區資料夾',
|
|
2389
|
+
'browse.home': '主目錄',
|
|
2390
|
+
'browse.up': '上一層',
|
|
2391
|
+
'browse.newFolder': '新增資料夾',
|
|
2392
|
+
'browse.folderName': '資料夾名稱',
|
|
2393
|
+
'browse.empty': '呢個資料夾冇子資料夾',
|
|
2394
|
+
'browse.loading': '載入緊…',
|
|
2395
|
+
'browse.truncated': '資料夾太多,只顯示開頭部分。',
|
|
2396
|
+
'browse.showHidden': '顯示隱藏檔案',
|
|
2397
|
+
'browse.editPath': '編輯路徑',
|
|
2398
|
+
'browse.select': '揀呢個資料夾',
|
|
2179
2399
|
'error.title': '出咗錯',
|
|
2180
2400
|
'cancel': '取消',
|
|
2181
2401
|
'create': '建立',
|
|
@@ -2284,6 +2504,17 @@ window.__ModuleLoader__.load({
|
|
|
2284
2504
|
'flow.parent': '所屬群組',
|
|
2285
2505
|
'flow.parentHint': '輸入或下拉選擇群組路徑,留空表示根群組;多層用 / 分隔',
|
|
2286
2506
|
'flow.creating': '正在建立…',
|
|
2507
|
+
'browse.title': '選擇工作區資料夾',
|
|
2508
|
+
'browse.home': '主目錄',
|
|
2509
|
+
'browse.up': '上一層',
|
|
2510
|
+
'browse.newFolder': '新增資料夾',
|
|
2511
|
+
'browse.folderName': '資料夾名稱',
|
|
2512
|
+
'browse.empty': '此資料夾沒有子資料夾',
|
|
2513
|
+
'browse.loading': '載入中…',
|
|
2514
|
+
'browse.truncated': '資料夾過多,僅顯示開頭部分。',
|
|
2515
|
+
'browse.showHidden': '顯示隱藏檔案',
|
|
2516
|
+
'browse.editPath': '編輯路徑',
|
|
2517
|
+
'browse.select': '選擇此資料夾',
|
|
2287
2518
|
'error.title': '發生錯誤',
|
|
2288
2519
|
'cancel': '取消',
|
|
2289
2520
|
'create': '建立',
|
|
@@ -2699,16 +2930,16 @@ window.__ModuleLoader__.load({
|
|
|
2699
2930
|
'.bw-row-icon{flex:none;display:grid;place-items:center;color:var(--dsw-alias-label-tertiary,#9a9a9a)}',
|
|
2700
2931
|
'.bw-chevron{flex:none;display:grid;place-items:center;color:var(--dsw-alias-label-tertiary,#9a9a9a);transition:transform .15s ease}',
|
|
2701
2932
|
'.bw-chevron-open{transform:rotate(90deg)}',
|
|
2702
|
-
//
|
|
2703
|
-
//
|
|
2704
|
-
//
|
|
2933
|
+
// overflow:hidden (needed for the ellipsis) clips the outline: 3px of
|
|
2934
|
+
// padding covers the widest rim (2px at the top of the slider). margin
|
|
2935
|
+
// cancels it, so text still starts and truncates where it did.
|
|
2705
2936
|
'.bw-row-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding:3px;margin:-3px}',
|
|
2706
2937
|
'.bw-row-count{flex:none;font-size:11px;color:var(--dsw-alias-label-quaternary,#8a8a8a)}',
|
|
2707
2938
|
'.bw-row-time{flex:none;font-size:11px;color:var(--dsw-alias-label-quaternary,#8a8a8a)}',
|
|
2708
|
-
// The
|
|
2709
|
-
//
|
|
2710
|
-
//
|
|
2711
|
-
'.bw-row-count,.bw-row-time{text-
|
|
2939
|
+
// The outline is inherited from the row: the 11px meta column (session
|
|
2940
|
+
// count / relative time) opts out — stroked meta text outshines the title
|
|
2941
|
+
// it is supposed to support.
|
|
2942
|
+
'.bw-row-count,.bw-row-time{-webkit-text-stroke-width:0}',
|
|
2712
2943
|
'.bw-schedule-badge{flex:none;display:inline-flex;align-items:center;color:var(--dsw-alias-label-tertiary,#9a9a9a);margin:0 6px}',
|
|
2713
2944
|
'.bw-row-actions{flex:none;display:none;align-items:center;gap:2px}',
|
|
2714
2945
|
'.bw-row:hover .bw-row-actions{display:flex}',
|
|
@@ -2790,10 +3021,7 @@ window.__ModuleLoader__.load({
|
|
|
2790
3021
|
'.bw-pulse{animation:bw-breathe 1.8s ease-in-out infinite}',
|
|
2791
3022
|
'@keyframes bw-breathe{0%,100%{filter:drop-shadow(0 0 1px var(--bw-pulse-color));opacity:.55}50%{filter:drop-shadow(0 0 6px var(--bw-pulse-color));opacity:1}}',
|
|
2792
3023
|
'.bw-pulse-text{animation:bw-breathe-text 1.8s ease-in-out infinite}',
|
|
2793
|
-
|
|
2794
|
-
// in --bw-stroke-shadow (set by rowStyleOf, comma-terminated) and the empty
|
|
2795
|
-
// fallback keeps a row without an outline breathing exactly as before.
|
|
2796
|
-
'@keyframes bw-breathe-text{0%,100%{text-shadow:var(--bw-stroke-shadow,) 0 0 1px var(--bw-pulse-color);opacity:.65}50%{text-shadow:var(--bw-stroke-shadow,) 0 0 7px var(--bw-pulse-color);opacity:1}}',
|
|
3024
|
+
'@keyframes bw-breathe-text{0%,100%{text-shadow:0 0 1px var(--bw-pulse-color);opacity:.65}50%{text-shadow:0 0 7px var(--bw-pulse-color);opacity:1}}',
|
|
2797
3025
|
'.bw-sync-modes{display:flex;gap:6px;margin-top:8px}',
|
|
2798
3026
|
'.bw-sync-mode{flex:1;padding:5px 10px;border-radius:6px;border:1px solid var(--dsw-alias-border-l2,rgba(127,127,127,.3));background:transparent;color:var(--dsw-alias-label-secondary,#b8b8b8);cursor:pointer;font-size:12px}',
|
|
2799
3027
|
'.bw-sync-mode-on{border-color:var(--dsw-alias-brand-primary,#5b8def);color:var(--dsw-alias-label-primary,#e6e6e6);background:var(--dsw-alias-bg-layer-2,rgba(127,127,127,.12))}',
|
|
@@ -2801,6 +3029,38 @@ window.__ModuleLoader__.load({
|
|
|
2801
3029
|
'.bw-sync-btn{padding:5px 12px;border-radius:6px;border:1px solid var(--dsw-alias-border-l2,rgba(127,127,127,.3));background:transparent;color:var(--dsw-alias-label-secondary,#b8b8b8);cursor:pointer;font-size:12px}',
|
|
2802
3030
|
'.bw-sync-btn:disabled{opacity:.45;cursor:default}',
|
|
2803
3031
|
'.bw-sync-btn-primary{border-color:var(--dsw-alias-brand-primary,#5b8def);color:var(--dsw-alias-label-primary,#e6e6e6)}',
|
|
3032
|
+
/*
|
|
3033
|
+
* In-app directory browser (Hosts composing the browse backend). Every
|
|
3034
|
+
* surface rides the theme token chain like the rest of the plugin — the
|
|
3035
|
+
* list panel uses bg-layer-2 so a transparent/background plugin still
|
|
3036
|
+
* reads correctly; nothing may hard-code an opaque background.
|
|
3037
|
+
*/
|
|
3038
|
+
'.bw-browse-body{min-width:360px;max-width:460px}',
|
|
3039
|
+
'.bw-browse-bar{display:flex;align-items:center;gap:4px}',
|
|
3040
|
+
'.bw-browse-crumbs{display:flex;align-items:center;gap:2px;flex:1;min-width:0;overflow-x:auto}',
|
|
3041
|
+
'.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}',
|
|
3042
|
+
'.bw-browse-crumb:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-primary,#e6e6e6)}',
|
|
3043
|
+
'.bw-browse-crumb:disabled{opacity:.4;cursor:default}',
|
|
3044
|
+
'.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}',
|
|
3045
|
+
'.bw-browse-nav{font-size:13px;line-height:1}',
|
|
3046
|
+
'.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}',
|
|
3047
|
+
'.bw-browse-edit:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-primary,#e6e6e6)}',
|
|
3048
|
+
'.bw-browse-edit svg{width:14px;height:14px}',
|
|
3049
|
+
'.bw-browse-edit-row{display:flex;gap:6px;align-items:center}',
|
|
3050
|
+
'.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}',
|
|
3052
|
+
'.bw-browse-row:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12))}',
|
|
3053
|
+
'.bw-browse-row:focus-visible{outline:2px solid var(--dsw-alias-brand-primary,#5b8def);outline-offset:-2px}',
|
|
3054
|
+
'.bw-browse-row svg{width:15px;height:15px;flex:none;color:var(--dsw-alias-label-secondary,#b8b8b8)}',
|
|
3055
|
+
'.bw-browse-name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}',
|
|
3056
|
+
'.bw-browse-note{font-size:12px;color:var(--dsw-alias-label-tertiary,#9a9a9a);padding:10px 8px;text-align:center}',
|
|
3057
|
+
'.bw-browse-new-row{display:flex;gap:6px;align-items:center}',
|
|
3058
|
+
'.bw-browse-tools{display:flex;align-items:center;gap:6px;flex-wrap:wrap}',
|
|
3059
|
+
'.bw-browse-actions{display:flex;align-items:center;justify-content:flex-end;gap:8px}',
|
|
3060
|
+
'.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}',
|
|
3061
|
+
'.bw-browse-tool:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-primary,#e6e6e6)}',
|
|
3062
|
+
'.bw-browse-tool:disabled{opacity:.45;cursor:default}',
|
|
3063
|
+
'.bw-browse-tool-on{border-color:var(--dsw-alias-brand-primary,#5b8def);color:var(--dsw-alias-label-primary,#e6e6e6)}',
|
|
2804
3064
|
].join('')
|
|
2805
3065
|
|
|
2806
3066
|
const StyleNode = () => E('style', null, CSS_TEXT)
|
|
@@ -3034,6 +3294,65 @@ window.__ModuleLoader__.load({
|
|
|
3034
3294
|
},
|
|
3035
3295
|
})
|
|
3036
3296
|
|
|
3297
|
+
/* ==================== directory-picker capability ================== */
|
|
3298
|
+
|
|
3299
|
+
// The host composes exactly ONE directory-picking backend
|
|
3300
|
+
// (@deepseek-ai/dsh-host-directory-picker-auto): a loopback-only webserver
|
|
3301
|
+
// bind on a display-bearing host gets `native` — one OS chooser on the
|
|
3302
|
+
// HOST's own screen — while every other bind (all-interfaces/LAN, SSH,
|
|
3303
|
+
// headless) gets `browse`, whose wire verbs are list/createDirectory ONLY
|
|
3304
|
+
// and whose `pick` is refused BY DESIGN with `directory-picker/unavailable`
|
|
3305
|
+
// ("... needs the native capability; the composed picker serves browse").
|
|
3306
|
+
// A flow that hard-assumed the OS chooser therefore dies on every LAN bind
|
|
3307
|
+
// and no workspace can be added at all. Probe the composed backend once per
|
|
3308
|
+
// page and keep BOTH interactions: the OS chooser, and an in-app browser
|
|
3309
|
+
// driven by the browse primitives (see DirectoryBrowseDialog).
|
|
3310
|
+
const pickerState = { kind: 'unknown', probe: null, api: null }
|
|
3311
|
+
/**
|
|
3312
|
+
* True for the refusal a Host serving the OTHER capability throws: the wire
|
|
3313
|
+
* code when the caller still carries it, the Host's fixed sentence
|
|
3314
|
+
* otherwise (uiWorkspace.pickDirectory wraps the RPC failure in a plain
|
|
3315
|
+
* Error, so the code is gone by the time the flow sees it).
|
|
3316
|
+
*/
|
|
3317
|
+
const pickerRefusal = (error) => {
|
|
3318
|
+
if (error && typeof error === 'object' && error.rpcError
|
|
3319
|
+
&& error.rpcError.code === 'directory-picker/unavailable') return true
|
|
3320
|
+
const message = messageOf(error)
|
|
3321
|
+
return message.indexOf('needs the native capability') !== -1
|
|
3322
|
+
|| message.indexOf('directory-picker/unavailable') !== -1
|
|
3323
|
+
}
|
|
3324
|
+
/**
|
|
3325
|
+
* Resolve which backend this page's Host composes. `list` succeeds exactly
|
|
3326
|
+
* on `browse` and is refused with the capability code on `native`, so one
|
|
3327
|
+
* call answers both worlds without ever opening a chooser or creating a
|
|
3328
|
+
* directory. An unclassified failure (a carrier still connecting at boot)
|
|
3329
|
+
* is NOT cached: the next open re-probes, and the flow falls back to trying
|
|
3330
|
+
* the chooser itself.
|
|
3331
|
+
*/
|
|
3332
|
+
const pickerCapabilityNow = () => {
|
|
3333
|
+
if (pickerState.kind !== 'unknown') return Promise.resolve(pickerState.kind)
|
|
3334
|
+
if (pickerState.probe !== null) return pickerState.probe
|
|
3335
|
+
const api = pickerState.api
|
|
3336
|
+
if (!api || typeof api.listDirectory !== 'function') return Promise.resolve('unknown')
|
|
3337
|
+
const probe = Promise.resolve()
|
|
3338
|
+
.then(() => api.listDirectory(undefined, undefined))
|
|
3339
|
+
.then(
|
|
3340
|
+
() => 'browse',
|
|
3341
|
+
(error) => (pickerRefusal(error) ? 'native' : 'unknown'),
|
|
3342
|
+
)
|
|
3343
|
+
pickerState.probe = probe.then((kind) => {
|
|
3344
|
+
if (kind === 'unknown') { pickerState.probe = null; return kind }
|
|
3345
|
+
pickerState.kind = kind
|
|
3346
|
+
return kind
|
|
3347
|
+
})
|
|
3348
|
+
return pickerState.probe
|
|
3349
|
+
}
|
|
3350
|
+
/** Commit a browse verdict the flow discovered the hard way (a refused pick). */
|
|
3351
|
+
const markPickerBrowse = () => {
|
|
3352
|
+
pickerState.kind = 'browse'
|
|
3353
|
+
pickerState.probe = Promise.resolve('browse')
|
|
3354
|
+
}
|
|
3355
|
+
|
|
3037
3356
|
/* ============================ flow dialog ========================= */
|
|
3038
3357
|
|
|
3039
3358
|
const BTN = (props) => (
|
|
@@ -3109,16 +3428,251 @@ window.__ModuleLoader__.load({
|
|
|
3109
3428
|
}
|
|
3110
3429
|
|
|
3111
3430
|
/**
|
|
3112
|
-
* The
|
|
3113
|
-
*
|
|
3114
|
-
*
|
|
3115
|
-
*
|
|
3431
|
+
* The in-app directory browser: the interaction a `browse`-composed Host can
|
|
3432
|
+
* actually serve. It appears when the probe (or a refused pick) proves the
|
|
3433
|
+
* OS chooser is not this Host's backend — a LAN/all-interfaces bind, an SSH
|
|
3434
|
+
* launch, a headless host — where `pick` is refused by design and
|
|
3435
|
+
* list/createDirectory are the only wire verbs. One level at a time: a
|
|
3436
|
+
* clickable Home-rooted breadcrumb chain, an editable path, an inline
|
|
3437
|
+
* new-folder row and the hidden-entry toggle; the listed level (or any
|
|
3438
|
+
* ancestor crumb) is the pick. Browse failures stay inside the dialog — the
|
|
3439
|
+
* owner's error surface belongs to the pick/create conversation, and a
|
|
3440
|
+
* denied listing must never close the flow.
|
|
3441
|
+
*/
|
|
3442
|
+
function DirectoryBrowseDialog(props) {
|
|
3443
|
+
const { open, busy, listDirectory, createDirectory, onPick, onClose, t } = props
|
|
3444
|
+
const [listing, setListing] = React.useState(null)
|
|
3445
|
+
const [loading, setLoading] = React.useState(false)
|
|
3446
|
+
const [error, setError] = React.useState('')
|
|
3447
|
+
const [showHidden, setShowHidden] = React.useState(false)
|
|
3448
|
+
const [editing, setEditing] = React.useState(false)
|
|
3449
|
+
const [draft, setDraft] = React.useState('')
|
|
3450
|
+
const [creating, setCreating] = React.useState(false)
|
|
3451
|
+
const [createName, setCreateName] = React.useState('')
|
|
3452
|
+
const [createBusy, setCreateBusy] = React.useState(false)
|
|
3453
|
+
const [createError, setCreateError] = React.useState('')
|
|
3454
|
+
// Guards a superseded scan: a slow level must not overwrite a newer one.
|
|
3455
|
+
const seqRef = React.useRef(0)
|
|
3456
|
+
|
|
3457
|
+
const go = (path) => {
|
|
3458
|
+
const seq = seqRef.current + 1
|
|
3459
|
+
seqRef.current = seq
|
|
3460
|
+
setLoading(true)
|
|
3461
|
+
setError('')
|
|
3462
|
+
Promise.resolve()
|
|
3463
|
+
.then(() => listDirectory(path, undefined))
|
|
3464
|
+
.then((next) => {
|
|
3465
|
+
if (seqRef.current !== seq) return
|
|
3466
|
+
setListing(next || null)
|
|
3467
|
+
setLoading(false)
|
|
3468
|
+
})
|
|
3469
|
+
.catch((reason) => {
|
|
3470
|
+
if (seqRef.current !== seq) return
|
|
3471
|
+
setLoading(false)
|
|
3472
|
+
setError(messageOf(reason))
|
|
3473
|
+
})
|
|
3474
|
+
}
|
|
3475
|
+
|
|
3476
|
+
React.useEffect(() => {
|
|
3477
|
+
if (!open) return undefined
|
|
3478
|
+
seqRef.current += 1
|
|
3479
|
+
setListing(null)
|
|
3480
|
+
setError('')
|
|
3481
|
+
setEditing(false)
|
|
3482
|
+
setCreating(false)
|
|
3483
|
+
setCreateName('')
|
|
3484
|
+
setCreateBusy(false)
|
|
3485
|
+
setCreateError('')
|
|
3486
|
+
setShowHidden(false)
|
|
3487
|
+
go(undefined)
|
|
3488
|
+
return () => { seqRef.current += 1 }
|
|
3489
|
+
}, [open])
|
|
3490
|
+
|
|
3491
|
+
if (!open) return null
|
|
3492
|
+
|
|
3493
|
+
// Home-rooted chain: inside the host account's home subtree the display
|
|
3494
|
+
// starts at a localized Home crumb, outside it the full ancestry shows.
|
|
3495
|
+
const crumbs = (() => {
|
|
3496
|
+
const all = listing && Array.isArray(listing.crumbs) ? listing.crumbs : []
|
|
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 : ''
|
|
3505
|
+
const parentPath = crumbs.length > 1 ? crumbs[crumbs.length - 2].path : ''
|
|
3506
|
+
const entries = (listing && Array.isArray(listing.entries) ? listing.entries : [])
|
|
3507
|
+
.filter((entry) => showHidden || !entry.hidden)
|
|
3508
|
+
|
|
3509
|
+
const submitDraft = () => {
|
|
3510
|
+
const path = draft.trim()
|
|
3511
|
+
setEditing(false)
|
|
3512
|
+
if (path === '') { go(undefined); return }
|
|
3513
|
+
go(path)
|
|
3514
|
+
}
|
|
3515
|
+
const submitCreate = () => {
|
|
3516
|
+
const name = createName.trim()
|
|
3517
|
+
if (name === '' || createBusy || currentPath === '') return
|
|
3518
|
+
setCreateBusy(true)
|
|
3519
|
+
setCreateError('')
|
|
3520
|
+
Promise.resolve()
|
|
3521
|
+
.then(() => createDirectory(currentPath, name))
|
|
3522
|
+
.then(() => {
|
|
3523
|
+
setCreateBusy(false)
|
|
3524
|
+
setCreating(false)
|
|
3525
|
+
setCreateName('')
|
|
3526
|
+
go(currentPath)
|
|
3527
|
+
})
|
|
3528
|
+
.catch((reason) => {
|
|
3529
|
+
setCreateBusy(false)
|
|
3530
|
+
setCreateError(messageOf(reason))
|
|
3531
|
+
})
|
|
3532
|
+
}
|
|
3533
|
+
|
|
3534
|
+
const head = editing
|
|
3535
|
+
? E('div', { className: 'bw-browse-edit-row' },
|
|
3536
|
+
E('input', {
|
|
3537
|
+
className: 'bw-input',
|
|
3538
|
+
value: draft,
|
|
3539
|
+
autoFocus: true,
|
|
3540
|
+
spellCheck: false,
|
|
3541
|
+
placeholder: currentPath,
|
|
3542
|
+
onChange: (e) => setDraft(e.target.value),
|
|
3543
|
+
onKeyDown: (e) => {
|
|
3544
|
+
if (e.key === 'Enter') submitDraft()
|
|
3545
|
+
else if (e.key === 'Escape') setEditing(false)
|
|
3546
|
+
},
|
|
3547
|
+
}),
|
|
3548
|
+
E(BTN, { variant: 'outline', onClick: () => setEditing(false) }, t('cancel')),
|
|
3549
|
+
)
|
|
3550
|
+
: E('div', { className: 'bw-browse-bar' },
|
|
3551
|
+
E('button', {
|
|
3552
|
+
type: 'button',
|
|
3553
|
+
className: 'bw-browse-crumb bw-browse-nav',
|
|
3554
|
+
title: t('browse.up'),
|
|
3555
|
+
'aria-label': t('browse.up'),
|
|
3556
|
+
disabled: parentPath === '',
|
|
3557
|
+
onClick: () => { if (parentPath !== '') go(parentPath) },
|
|
3558
|
+
}, '↑'),
|
|
3559
|
+
E('div', { className: 'bw-browse-crumbs' },
|
|
3560
|
+
crumbs.map((crumb, index) => (index === crumbs.length - 1
|
|
3561
|
+
? E('span', { key: crumb.path, className: 'bw-browse-crumb bw-browse-current', title: crumb.path }, crumb.name)
|
|
3562
|
+
: E('button', {
|
|
3563
|
+
key: crumb.path,
|
|
3564
|
+
type: 'button',
|
|
3565
|
+
className: 'bw-browse-crumb',
|
|
3566
|
+
title: crumb.path,
|
|
3567
|
+
onClick: () => go(crumb.path),
|
|
3568
|
+
}, crumb.name))),
|
|
3569
|
+
),
|
|
3570
|
+
E('button', {
|
|
3571
|
+
type: 'button',
|
|
3572
|
+
className: 'bw-browse-edit',
|
|
3573
|
+
title: t('browse.editPath'),
|
|
3574
|
+
'aria-label': t('browse.editPath'),
|
|
3575
|
+
onClick: () => { setDraft(currentPath); setEditing(true) },
|
|
3576
|
+
}, icon('IconEditOutline16')),
|
|
3577
|
+
)
|
|
3578
|
+
|
|
3579
|
+
const rows = entries.map((entry) => E('button', {
|
|
3580
|
+
key: entry.path,
|
|
3581
|
+
type: 'button',
|
|
3582
|
+
className: 'bw-browse-row',
|
|
3583
|
+
title: entry.path,
|
|
3584
|
+
onClick: () => go(entry.path),
|
|
3585
|
+
}, icon('IconFolderClose16'), E('span', { className: 'bw-browse-name' }, entry.name)))
|
|
3586
|
+
|
|
3587
|
+
const body = loading
|
|
3588
|
+
? E('div', { className: 'bw-browse-note' }, t('browse.loading'))
|
|
3589
|
+
: rows.length > 0
|
|
3590
|
+
? rows
|
|
3591
|
+
: E('div', { className: 'bw-browse-note' }, t('browse.empty'))
|
|
3592
|
+
|
|
3593
|
+
const newRow = creating
|
|
3594
|
+
? E('div', { className: 'bw-browse-new-row' },
|
|
3595
|
+
E('input', {
|
|
3596
|
+
className: 'bw-input',
|
|
3597
|
+
value: createName,
|
|
3598
|
+
autoFocus: true,
|
|
3599
|
+
spellCheck: false,
|
|
3600
|
+
placeholder: t('browse.folderName'),
|
|
3601
|
+
disabled: createBusy,
|
|
3602
|
+
onChange: (e) => setCreateName(e.target.value),
|
|
3603
|
+
onKeyDown: (e) => {
|
|
3604
|
+
if (e.key === 'Enter') submitCreate()
|
|
3605
|
+
else if (e.key === 'Escape') { setCreating(false); setCreateError('') }
|
|
3606
|
+
},
|
|
3607
|
+
}),
|
|
3608
|
+
E(BTN, {
|
|
3609
|
+
variant: 'outline',
|
|
3610
|
+
disabled: createBusy,
|
|
3611
|
+
onClick: () => { setCreating(false); setCreateError('') },
|
|
3612
|
+
}, t('cancel')),
|
|
3613
|
+
E(BTN, { variant: 'primary', onClick: submitCreate, disabled: createBusy },
|
|
3614
|
+
createBusy ? t('flow.creating') : t('create')),
|
|
3615
|
+
)
|
|
3616
|
+
: null
|
|
3617
|
+
|
|
3618
|
+
// The dialog is deliberately narrower than the official browser, so the
|
|
3619
|
+
// two browsing tools sit on their own row above the list instead of
|
|
3620
|
+
// sharing the footer with the commit pair (they used to wrap there).
|
|
3621
|
+
const tools = E('div', { className: 'bw-browse-tools' },
|
|
3622
|
+
E('button', {
|
|
3623
|
+
type: 'button',
|
|
3624
|
+
className: cls('bw-browse-tool', showHidden && 'bw-browse-tool-on'),
|
|
3625
|
+
'aria-pressed': showHidden ? 'true' : 'false',
|
|
3626
|
+
onClick: () => setShowHidden((value) => !value),
|
|
3627
|
+
}, t('browse.showHidden')),
|
|
3628
|
+
E('button', {
|
|
3629
|
+
type: 'button',
|
|
3630
|
+
className: 'bw-browse-tool',
|
|
3631
|
+
disabled: creating || currentPath === '',
|
|
3632
|
+
onClick: () => { setCreating(true); setCreateName(''); setCreateError('') },
|
|
3633
|
+
}, t('browse.newFolder')),
|
|
3634
|
+
)
|
|
3635
|
+
|
|
3636
|
+
const footer = E('div', { className: 'bw-browse-actions' },
|
|
3637
|
+
E(BTN, { variant: 'outline', onClick: onClose, disabled: busy === true }, t('cancel')),
|
|
3638
|
+
E(BTN, {
|
|
3639
|
+
variant: 'primary',
|
|
3640
|
+
disabled: busy === true || currentPath === '',
|
|
3641
|
+
onClick: () => { if (currentPath !== '') onPick(currentPath) },
|
|
3642
|
+
}, t('browse.select')),
|
|
3643
|
+
)
|
|
3644
|
+
|
|
3645
|
+
return E(ui.Modal, {
|
|
3646
|
+
open: true,
|
|
3647
|
+
onClose: () => { if (busy !== true) onClose() },
|
|
3648
|
+
closeLabel: t('close'),
|
|
3649
|
+
title: t('browse.title'),
|
|
3650
|
+
footer,
|
|
3651
|
+
},
|
|
3652
|
+
E('div', { className: 'bw-modal-body bw-browse-body' },
|
|
3653
|
+
head,
|
|
3654
|
+
createError !== '' ? E('div', { className: 'bw-error-text', role: 'alert' }, createError) : null,
|
|
3655
|
+
tools,
|
|
3656
|
+
newRow,
|
|
3657
|
+
E('div', { className: 'bw-browse-list', role: 'list' }, body),
|
|
3658
|
+
listing && listing.truncated === true ? E('div', { className: 'bw-hint' }, t('browse.truncated')) : null,
|
|
3659
|
+
error !== '' ? E('div', { className: 'bw-error-text', role: 'alert' }, error) : null,
|
|
3660
|
+
),
|
|
3661
|
+
StyleNode())
|
|
3662
|
+
}
|
|
3663
|
+
|
|
3664
|
+
/**
|
|
3665
|
+
* The add-workspace picking interaction: whichever directory interaction the
|
|
3666
|
+
* Host composes (OS chooser or in-app browser), then a small parent-group
|
|
3667
|
+
* popup, then create + rename with the chosen prefix. Works as a
|
|
3668
|
+
* directoryFlow occupant (owner conversation props) and as the browser's
|
|
3669
|
+
* directly composed flow (same props, owner state lives above).
|
|
3116
3670
|
*/
|
|
3117
3671
|
function BetterFlow(props) {
|
|
3118
|
-
const { open, busy, onPicked, onCancel, onError, createWorkspace, renameWorkspace, pickDirectory, useWorkspaces, useStore, t } = props
|
|
3672
|
+
const { open, busy, onPicked, onCancel, onError, createWorkspace, renameWorkspace, pickDirectory, listDirectory, createDirectory, useWorkspaces, useStore, t } = props
|
|
3119
3673
|
const initialParent = props.initialParent || ''
|
|
3120
3674
|
const actions = props.actions
|
|
3121
|
-
const [phase, setPhase] = React.useState('idle') // idle | picking | picked | submitting
|
|
3675
|
+
const [phase, setPhase] = React.useState('idle') // idle | picking | browsing | picked | submitting
|
|
3122
3676
|
const [pickedPath, setPickedPath] = React.useState('')
|
|
3123
3677
|
const [parentInput, setParentInput] = React.useState('')
|
|
3124
3678
|
// All hooks run before any early return: the flow unmounts its dialog
|
|
@@ -3137,24 +3691,54 @@ window.__ModuleLoader__.load({
|
|
|
3137
3691
|
}
|
|
3138
3692
|
let alive = true
|
|
3139
3693
|
setPhase('picking')
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3694
|
+
// Which backend this Host composes decides the whole interaction: a
|
|
3695
|
+
// `browse` Host renders the in-app browser directly (its `pick` is
|
|
3696
|
+
// refused by design, so trying the chooser first would only surface an
|
|
3697
|
+
// error), a `native` Host opens the OS chooser, and an unresolved
|
|
3698
|
+
// verdict still tries the chooser so a silent probe never changes the
|
|
3699
|
+
// behavior this plugin had before the probe existed.
|
|
3700
|
+
pickerCapabilityNow()
|
|
3701
|
+
.then((kind) => {
|
|
3702
|
+
if (!alive) return undefined
|
|
3703
|
+
if (kind === 'browse') { setPhase('browsing'); return undefined }
|
|
3704
|
+
return Promise.resolve()
|
|
3705
|
+
.then(() => pickDirectory())
|
|
3706
|
+
.then((path) => {
|
|
3707
|
+
if (!alive) return
|
|
3708
|
+
if (!path) { onCancel(); return }
|
|
3709
|
+
setPickedPath(String(path))
|
|
3710
|
+
setParentInput(initialParent)
|
|
3711
|
+
setPhase('picked')
|
|
3712
|
+
})
|
|
3148
3713
|
})
|
|
3149
3714
|
.catch((reason) => {
|
|
3150
3715
|
if (!alive) return
|
|
3716
|
+
// A refusal is a backend fact, not a failure: this Host serves the
|
|
3717
|
+
// browse verbs, so switch interactions instead of erroring out.
|
|
3718
|
+
if (pickerRefusal(reason)) { markPickerBrowse(); setPhase('browsing'); return }
|
|
3151
3719
|
setPhase('idle')
|
|
3152
3720
|
onError(messageOf(reason))
|
|
3153
3721
|
})
|
|
3154
3722
|
return () => { alive = false }
|
|
3155
3723
|
}, [open])
|
|
3156
3724
|
|
|
3157
|
-
if (!open
|
|
3725
|
+
if (!open) return null
|
|
3726
|
+
if (phase === 'browsing') {
|
|
3727
|
+
return E(DirectoryBrowseDialog, {
|
|
3728
|
+
open: true,
|
|
3729
|
+
busy: busy === true,
|
|
3730
|
+
listDirectory,
|
|
3731
|
+
createDirectory,
|
|
3732
|
+
onPick: (path) => {
|
|
3733
|
+
setPickedPath(String(path))
|
|
3734
|
+
setParentInput(initialParent)
|
|
3735
|
+
setPhase('picked')
|
|
3736
|
+
},
|
|
3737
|
+
onClose: onCancel,
|
|
3738
|
+
t,
|
|
3739
|
+
})
|
|
3740
|
+
}
|
|
3741
|
+
if (phase !== 'picked' && phase !== 'submitting') return null
|
|
3158
3742
|
|
|
3159
3743
|
const folderOptions = (() => {
|
|
3160
3744
|
const set = new Set(storeFolders)
|
|
@@ -3259,7 +3843,9 @@ window.__ModuleLoader__.load({
|
|
|
3259
3843
|
// and returns the opposite pole; rows without a custom color inherit
|
|
3260
3844
|
// --bw-stroke-color, sampled from the live computed style (see the sampler
|
|
3261
3845
|
// in BetterBrowser) so a palette flip needs no React render.
|
|
3262
|
-
|
|
3846
|
+
// The slider is the rim the eye actually sees (0.5..2px). strokeStyleOf
|
|
3847
|
+
// paints twice that because paint-order hides the inner half of the band.
|
|
3848
|
+
const STROKE_MAX = 2
|
|
3263
3849
|
// The outline color defaults to a plain gray: it reads on light and dark
|
|
3264
3850
|
// backgrounds alike without shouting like pure black/white. "auto" derives
|
|
3265
3851
|
// the contrasting pole from the font color instead.
|
|
@@ -3334,33 +3920,26 @@ window.__ModuleLoader__.load({
|
|
|
3334
3920
|
}
|
|
3335
3921
|
return colorToRgb(picked) ? picked : DEFAULT_APPEARANCE.strokeColor
|
|
3336
3922
|
}
|
|
3337
|
-
//
|
|
3338
|
-
//
|
|
3339
|
-
//
|
|
3340
|
-
//
|
|
3341
|
-
//
|
|
3342
|
-
//
|
|
3343
|
-
// edge reads as pale and uneven — the "jagged" outline.
|
|
3923
|
+
// paint-order keeps the stroke UNDER the fill so the glyph does not thin out
|
|
3924
|
+
// — the whole point of an outer outline. The price is half the band:
|
|
3925
|
+
// -webkit-text-stroke centres it on the outline and the fill covers the inner
|
|
3926
|
+
// half, so the painted width must be TWICE the rim the slider promises.
|
|
3927
|
+
// Painting the configured value (0.10.x) left a 0.5px rim that antialiasing
|
|
3928
|
+
// blended away into a pale, uneven edge.
|
|
3344
3929
|
//
|
|
3345
|
-
//
|
|
3346
|
-
//
|
|
3347
|
-
//
|
|
3348
|
-
//
|
|
3349
|
-
//
|
|
3350
|
-
//
|
|
3351
|
-
const
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
}
|
|
3359
|
-
const strokeShadowsOf = (appearance) => {
|
|
3360
|
-
if (!appearance || appearance.stroke === false) return []
|
|
3361
|
-
const color = strokeColorOf(appearance)
|
|
3362
|
-
const offset = strokeOffsetOf(appearance)
|
|
3363
|
-
return STROKE_DIRECTIONS.map(([dx, dy]) => (dx * offset) + 'px ' + (dy * offset) + 'px ' + color)
|
|
3930
|
+
// 0.11.1 replaced this with eight offset glyph copies. It measured better and
|
|
3931
|
+
// looked worse: eight directions are only four diagonal samples, so every
|
|
3932
|
+
// slope and curve came out as a string of detached blocks instead of a filled
|
|
3933
|
+
// rim. A geometric stroke is continuous by construction — it really is a path
|
|
3934
|
+
// stroke — so the answer is to give it enough width to survive antialiasing,
|
|
3935
|
+
// not to fake it out of copies.
|
|
3936
|
+
const strokeStyleOf = (appearance) => {
|
|
3937
|
+
if (!appearance || appearance.stroke === false) return null
|
|
3938
|
+
return {
|
|
3939
|
+
WebkitTextStrokeWidth: clampStrokeWidth(appearance.strokeWidth) * 2 + 'px',
|
|
3940
|
+
WebkitTextStrokeColor: strokeColorOf(appearance),
|
|
3941
|
+
paintOrder: 'stroke fill',
|
|
3942
|
+
}
|
|
3364
3943
|
}
|
|
3365
3944
|
|
|
3366
3945
|
function FolderRow({ node, depth, expanded, onToggle, onContextMenu, dropInto, dragEvents, custStyle, iconMode, pulse, t }) {
|
|
@@ -3383,7 +3962,7 @@ window.__ModuleLoader__.load({
|
|
|
3383
3962
|
E('span', { className: cls('bw-chevron', expanded && 'bw-chevron-open') }, icon('IconTriangleRightFill14', 14)),
|
|
3384
3963
|
E('span', { className: 'bw-row-icon' }, iconChild),
|
|
3385
3964
|
// A row that already carries a custom label glow breathes in sync.
|
|
3386
|
-
E('span', { className: cls('bw-row-label', pulse && custStyle && custStyle
|
|
3965
|
+
E('span', { className: cls('bw-row-label', pulse && custStyle && custStyle.textShadow && 'bw-pulse-text') }, node.name),
|
|
3387
3966
|
total > 0 ? E('span', { className: 'bw-row-count' }, String(total)) : null,
|
|
3388
3967
|
)
|
|
3389
3968
|
}
|
|
@@ -3404,7 +3983,7 @@ window.__ModuleLoader__.load({
|
|
|
3404
3983
|
},
|
|
3405
3984
|
E('span', { className: cls('bw-chevron', sessionsOpen && 'bw-chevron-open') }, icon('IconTriangleRightFill14', 14)),
|
|
3406
3985
|
E('span', { className: 'bw-row-icon' }, iconChild),
|
|
3407
|
-
E('span', { className: cls('bw-row-label', pulse && custStyle && custStyle
|
|
3986
|
+
E('span', { className: cls('bw-row-label', pulse && custStyle && custStyle.textShadow && 'bw-pulse-text'), title: workspace.title || workspace.leaf }, workspace.leaf),
|
|
3408
3987
|
count > 0 ? E('span', { className: 'bw-row-count' }, String(count)) : null,
|
|
3409
3988
|
E('span', { className: 'bw-row-actions', onClick: (e) => e.stopPropagation() },
|
|
3410
3989
|
E('button', { type: 'button', className: 'bw-icon-btn', 'aria-label': t('session.new'), onClick: (e) => { e.stopPropagation(); onStart() } }, icon('IconPlusOutline16')),
|
|
@@ -3591,7 +4170,7 @@ window.__ModuleLoader__.load({
|
|
|
3591
4170
|
next[index] = n
|
|
3592
4171
|
patch({ color: rgbToHex(next[0], next[1], next[2]) })
|
|
3593
4172
|
}
|
|
3594
|
-
const previewShadows =
|
|
4173
|
+
const previewShadows = []
|
|
3595
4174
|
if (glow > 0 && color) previewShadows.push('0 0 ' + glow + 'px ' + color)
|
|
3596
4175
|
if (shadow) previewShadows.push('1px 1px 2px rgba(0,0,0,.85)')
|
|
3597
4176
|
// The preview must show the REAL outline: with no custom color the pole
|
|
@@ -3611,6 +4190,7 @@ window.__ModuleLoader__.load({
|
|
|
3611
4190
|
const poll = setInterval(sample, 1500)
|
|
3612
4191
|
return () => clearInterval(poll)
|
|
3613
4192
|
}, [color, stroke])
|
|
4193
|
+
const previewStroke = strokeStyleOf(appearance)
|
|
3614
4194
|
return E('div', { className: 'bw-appearance' },
|
|
3615
4195
|
E('div', { className: 'bw-field' },
|
|
3616
4196
|
t('custom.color'),
|
|
@@ -3751,6 +4331,7 @@ window.__ModuleLoader__.load({
|
|
|
3751
4331
|
color: color || undefined,
|
|
3752
4332
|
fontWeight: weight > 0 ? weight : undefined,
|
|
3753
4333
|
textShadow: previewShadows.length > 0 ? previewShadows.join(',') : undefined,
|
|
4334
|
+
...(previewStroke || null),
|
|
3754
4335
|
},
|
|
3755
4336
|
},
|
|
3756
4337
|
allowIcon ? E('span', { className: 'bw-preview-icon' }, iconOf(iconMode, true)) : null,
|
|
@@ -3960,7 +4541,8 @@ window.__ModuleLoader__.load({
|
|
|
3960
4541
|
useSessions, useSessionPendingInteraction, useWorkspaces,
|
|
3961
4542
|
useStore, actions,
|
|
3962
4543
|
startSession, open, renameSession, forkSession, renameWorkspace, deleteWorkspace,
|
|
3963
|
-
archiveSession, createWorkspace, pickDirectory,
|
|
4544
|
+
archiveSession, createWorkspace, pickDirectory, listDirectory, createDirectory,
|
|
4545
|
+
insertWorkspaceBefore, insertSessionBefore,
|
|
3964
4546
|
t,
|
|
3965
4547
|
} = props
|
|
3966
4548
|
|
|
@@ -4177,27 +4759,15 @@ window.__ModuleLoader__.load({
|
|
|
4177
4759
|
const glow = Number(appearance.glow) || 0
|
|
4178
4760
|
const weight = Number(appearance.weight) || 0
|
|
4179
4761
|
const shadow = appearance.shadow === true
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
const halos = []
|
|
4184
|
-
if (glow > 0 && color) halos.push('0 0 ' + glow + 'px ' + color)
|
|
4185
|
-
if (shadow) halos.push('1px 1px 2px rgba(0,0,0,.85)')
|
|
4186
|
-
const shadows = outline.concat(halos)
|
|
4762
|
+
const shadows = []
|
|
4763
|
+
if (glow > 0 && color) shadows.push('0 0 ' + glow + 'px ' + color)
|
|
4764
|
+
if (shadow) shadows.push('1px 1px 2px rgba(0,0,0,.85)')
|
|
4187
4765
|
const style = {}
|
|
4188
4766
|
if (color) style.color = color
|
|
4189
4767
|
if (weight > 0) style.fontWeight = weight
|
|
4190
4768
|
if (shadows.length > 0) style.textShadow = shadows.join(',')
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
// load-bearing — the pulse keyframes append the halo after it.
|
|
4194
|
-
if (outline.length > 0) style['--bw-stroke-shadow'] = outline.join(',') + ','
|
|
4195
|
-
// Meta text (session count / relative time) opts out of the rim but keeps
|
|
4196
|
-
// the halo, which it can only read from here.
|
|
4197
|
-
if (halos.length > 0) style['--bw-glow-shadow'] = halos.join(',')
|
|
4198
|
-
// The label breathes its text only when the row actually carries a halo;
|
|
4199
|
-
// a rim alone is not a reason to pulse.
|
|
4200
|
-
if (halos.length > 0) style['--bw-text-glow'] = '1'
|
|
4769
|
+
const stroke = strokeStyleOf(appearance)
|
|
4770
|
+
if (stroke) Object.assign(style, stroke)
|
|
4201
4771
|
return Object.keys(style).length > 0 ? style : null
|
|
4202
4772
|
}
|
|
4203
4773
|
const keyOf = (kind, payload) => {
|
|
@@ -5025,6 +5595,8 @@ window.__ModuleLoader__.load({
|
|
|
5025
5595
|
createWorkspace,
|
|
5026
5596
|
renameWorkspace,
|
|
5027
5597
|
pickDirectory,
|
|
5598
|
+
listDirectory,
|
|
5599
|
+
createDirectory,
|
|
5028
5600
|
useWorkspaces,
|
|
5029
5601
|
actions,
|
|
5030
5602
|
useStore,
|
|
@@ -5110,6 +5682,10 @@ window.__ModuleLoader__.load({
|
|
|
5110
5682
|
})
|
|
5111
5683
|
return
|
|
5112
5684
|
}
|
|
5685
|
+
// The capability probe talks to the composed backend through this
|
|
5686
|
+
// service (list is served by browse alone), so the state module needs
|
|
5687
|
+
// the handle before any flow can open.
|
|
5688
|
+
pickerState.api = uiWorkspace
|
|
5113
5689
|
|
|
5114
5690
|
if (ctx.locale && typeof ctx.locale.register === 'function') {
|
|
5115
5691
|
ctx.effect(() => {
|
|
@@ -5161,6 +5737,10 @@ window.__ModuleLoader__.load({
|
|
|
5161
5737
|
archiveSession: (sessionId) => uiWorkspace.archiveSession(sessionId),
|
|
5162
5738
|
createWorkspace: (input) => workspaces.create(input),
|
|
5163
5739
|
pickDirectory: () => uiWorkspace.pickDirectory(),
|
|
5740
|
+
// Browse-side primitives: the fallback interaction whenever the Host
|
|
5741
|
+
// composes the browse backend instead of the OS chooser.
|
|
5742
|
+
listDirectory: (path, signal) => uiWorkspace.listDirectory(path, signal),
|
|
5743
|
+
createDirectory: (path, name) => uiWorkspace.createDirectory(path, name),
|
|
5164
5744
|
hooks: {
|
|
5165
5745
|
directoryFlow: flowSource(slots, 'sidebar.workspaces.directoryFlow'),
|
|
5166
5746
|
},
|
|
@@ -5169,6 +5749,10 @@ window.__ModuleLoader__.load({
|
|
|
5169
5749
|
createWorkspace: (input) => workspaces.create(input),
|
|
5170
5750
|
renameWorkspace: (workspaceId, title) => workspaces.rename(workspaceId, title),
|
|
5171
5751
|
pickDirectory: () => uiWorkspace.pickDirectory(),
|
|
5752
|
+
// Same browse-side primitives as the sidebar flow: the two holes and
|
|
5753
|
+
// the inlined dialog must agree on which interaction they can run.
|
|
5754
|
+
listDirectory: (path, signal) => uiWorkspace.listDirectory(path, signal),
|
|
5755
|
+
createDirectory: (path, name) => uiWorkspace.createDirectory(path, name),
|
|
5172
5756
|
hooks: { directoryFlow: flowSource(slots, hole) },
|
|
5173
5757
|
})
|
|
5174
5758
|
|