@openviking/react 0.1.6 → 0.1.7

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.
Files changed (94) hide show
  1. package/README.md +2 -0
  2. package/dist/2~0.js +6 -5
  3. package/dist/3c~0.js +10 -4
  4. package/dist/3~0.js +18 -8
  5. package/dist/ae~0.js +8 -1
  6. package/dist/ark-message-list-view~0.js +9 -4
  7. package/dist/a~0.js +12 -6
  8. package/dist/c7r~0.js +50 -0
  9. package/dist/ck~0.js +3 -0
  10. package/dist/client/paths.d.ts +6 -0
  11. package/dist/client/provider.d.ts +6 -1
  12. package/dist/components/agent-connect/style.css +222 -35
  13. package/dist/components/directory-tour/style.css +221 -21
  14. package/dist/components/directory-tour.js +1 -1
  15. package/dist/components/experience-dashboard/style.css +227 -12
  16. package/dist/components/experience-dashboard.js +1 -1
  17. package/dist/components/file-browser-entries/style.css +325 -80
  18. package/dist/components/file-preview/style.css +221 -76
  19. package/dist/components/file-tree/file-tree-toolbar.d.ts +3 -3
  20. package/dist/components/file-tree/locales/en-US.d.ts +3 -0
  21. package/dist/components/file-tree/locales/zh-CN.d.ts +3 -0
  22. package/dist/components/file-tree/style.css +311 -86
  23. package/dist/components/file-tree/types.d.ts +13 -4
  24. package/dist/components/scope-switcher/style.css +366 -37
  25. package/dist/components/scope-switcher.js +1 -1
  26. package/dist/components/style.css +3329 -640
  27. package/dist/components/tui/style.css +267 -35
  28. package/dist/components/viking-bot/interaction/style.css +143 -22
  29. package/dist/components/viking-bot/style.css +945 -295
  30. package/dist/components/wiki-dashboard/style.css +227 -12
  31. package/dist/c~0.js +16 -8
  32. package/dist/ep~0.js +345 -0
  33. package/dist/e~0.js +33 -328
  34. package/dist/fj~0.js +8 -4
  35. package/dist/fl~0.js +709 -0
  36. package/dist/f~0.js +302 -663
  37. package/dist/index.js +3 -3
  38. package/dist/lk~0.js +6 -3
  39. package/dist/l~0.js +1874 -437
  40. package/dist/modules/console/style.css +1471 -351
  41. package/dist/modules/file-browser/acl/acl-add-principal.d.ts +12 -0
  42. package/dist/modules/file-browser/acl/acl-candidate.d.ts +8 -0
  43. package/dist/modules/file-browser/acl/acl-client.d.ts +13 -0
  44. package/dist/modules/file-browser/acl/acl-confirmation.d.ts +6 -0
  45. package/dist/modules/file-browser/acl/acl-entry.d.ts +13 -0
  46. package/dist/modules/file-browser/acl/acl-labels.d.ts +47 -0
  47. package/dist/modules/file-browser/acl/acl-menu.d.ts +10 -0
  48. package/dist/modules/file-browser/acl/acl-modal.d.ts +17 -0
  49. package/dist/modules/file-browser/acl/acl-model.d.ts +17 -0
  50. package/dist/modules/file-browser/acl/acl-principal-tabs.d.ts +6 -0
  51. package/dist/modules/file-browser/acl/acl-tag.d.ts +7 -0
  52. package/dist/modules/file-browser/acl/locales/en-US.d.ts +44 -0
  53. package/dist/modules/file-browser/acl/locales/zh-CN.d.ts +44 -0
  54. package/dist/modules/file-browser/acl/use-acl-principals.d.ts +17 -0
  55. package/dist/modules/file-browser/acl/use-acl-report.d.ts +27 -0
  56. package/dist/modules/file-browser/acl-types.d.ts +86 -0
  57. package/dist/modules/file-browser/dashboard-model.d.ts +3 -0
  58. package/dist/modules/file-browser/file-browser-detail.d.ts +4 -1
  59. package/dist/modules/file-browser/file-browser-labels.d.ts +1 -1
  60. package/dist/modules/file-browser/file-browser-search.d.ts +20 -0
  61. package/dist/modules/file-browser/file-browser-toolbar.d.ts +3 -10
  62. package/dist/modules/file-browser/file-browser-tree-pane.d.ts +2 -6
  63. package/dist/modules/file-browser/file-browser.d.ts +1 -1
  64. package/dist/modules/file-browser/fs-directories.d.ts +1 -0
  65. package/dist/modules/file-browser/fs-model.d.ts +3 -1
  66. package/dist/modules/file-browser/index.d.ts +1 -0
  67. package/dist/modules/file-browser/locales/en-US.d.ts +5 -0
  68. package/dist/modules/file-browser/locales/zh-CN.d.ts +5 -0
  69. package/dist/modules/file-browser/style.css +2409 -603
  70. package/dist/modules/file-browser/types.d.ts +20 -20
  71. package/dist/modules/file-browser/use-file-browser-acl.d.ts +15 -0
  72. package/dist/modules/file-browser/use-file-browser.d.ts +5 -7
  73. package/dist/modules/file-browser/use-fs-tree.d.ts +1 -0
  74. package/dist/modules/scope-cascade/style.css +535 -37
  75. package/dist/modules/style.css +12227 -8757
  76. package/dist/n~0.js +60 -36
  77. package/dist/rt~0.js +107 -4
  78. package/dist/r~0.js +1 -1
  79. package/dist/shared/components/button/button.d.ts +20 -0
  80. package/dist/shared/components/input/input.d.ts +17 -0
  81. package/dist/shared/components/modal/locales/en-US.d.ts +4 -0
  82. package/dist/shared/components/modal/locales/zh-CN.d.ts +4 -0
  83. package/dist/shared/components/modal/modal.d.ts +2 -0
  84. package/dist/shared/components/modal/types.d.ts +18 -0
  85. package/dist/shared/components/overlay/lock-body-scroll.d.ts +1 -0
  86. package/dist/style.css +7776 -1571
  87. package/dist/s~0.js +16 -0
  88. package/dist/u~0.js +15 -6
  89. package/dist/viking-bot-composer~0.js +29 -11
  90. package/dist/viking-bot-interaction~0.js +2 -1
  91. package/package.json +3 -2
  92. package/dist/fi~0.js +0 -354
  93. package/dist/modules/file-browser/fs-selection.d.ts +0 -4
  94. package/dist/o~0.js +0 -106
package/dist/l~0.js CHANGED
@@ -1,16 +1,23 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { createElement, useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
3
+ import { createPortal } from "react-dom";
4
+ import { CSSTransition } from "react-transition-group";
3
5
  import { FilePreview as file_preview_FilePreview } from "./3~0.js";
4
- import { Tooltip } from "./rt~0.js";
6
+ import { button_Button } from "./e~0.js";
7
+ import { FileTree as file_tree_FileTree, FileTreeToolbar } from "./n~0.js";
5
8
  import { getFileTypeColor, getFileTypeIcon, WikiBookIcon, ExperienceBoardIcon, WikiConflictBoardIcon, FolderIcon, WikiBoardIcon, resolveFileTypeIconKey } from "./k~0.js";
6
- import { FileBrowserEntries } from "./2~0.js";
7
- import { FileTree as file_tree_FileTree } from "./n~0.js";
9
+ import { FileBrowserEntries, es2015, lockBodyScroll } from "./2~0.js";
10
+ import { removereact } from "./4~0.js";
11
+ import { useLocale, LocaleProvider } from "./28~0.js";
8
12
  import { resolveLocale } from "./nx~0.js";
9
- import { OV_PATHS } from "./ck~0.js";
10
- import { isOvClientError, useOvSignal, useOvClient } from "./ae~0.js";
11
- import { ExperienceDashboard } from "./f~0.js";
13
+ import { usePortalTheme } from "./9~0.js";
14
+ import { OV_PATHS, OV_PATH_BUILDERS } from "./ck~0.js";
15
+ import { useOvSignal, useOvClient, isOvClientError, useOvIdentity } from "./ae~0.js";
16
+ import { input_Input } from "./c7r~0.js";
17
+ import { ExperienceDashboard } from "./fl~0.js";
12
18
  import { WikiDashboard } from "./fj~0.js";
13
- import { useLocale, LocaleProvider } from "./28~0.js";
19
+ import { chevron_downreact } from "./s~0.js";
20
+ import { checkreact } from "./0~0.js";
14
21
  function clampResizableWidth(width, min, max) {
15
22
  const rounded = Math.round(width);
16
23
  const lower = Math.max(min, rounded);
@@ -122,11 +129,11 @@ function DownloadIcon() {
122
129
  ]
123
130
  });
124
131
  }
125
- function FileBrowserDetail({ tabs, activeKey, locale, labels, footer, viewLabels, onActiveKeyChange }) {
132
+ function FileBrowserDetail({ tabs, activeKey, locale, labels, footer, titleExtra, extraActions, viewLabels, onActiveKeyChange }) {
126
133
  return /*#__PURE__*/ jsx("div", {
127
134
  className: "ov-file-browser__detail",
128
135
  children: /*#__PURE__*/ jsx(file_preview_FilePreview, {
129
- tabs: decorateTabs(tabs, viewLabels, footer),
136
+ tabs: decorateTabs(tabs, viewLabels, footer, titleExtra, extraActions),
130
137
  activeKey: activeKey ?? void 0,
131
138
  locale: locale,
132
139
  labels: labels,
@@ -134,7 +141,7 @@ function FileBrowserDetail({ tabs, activeKey, locale, labels, footer, viewLabels
134
141
  })
135
142
  });
136
143
  }
137
- function decorateTabs(tabs, viewLabels, footer) {
144
+ function decorateTabs(tabs, viewLabels, footer, titleExtra, extraActions) {
138
145
  return tabs.map((tab)=>{
139
146
  if ('content' === tab.kind) return tab;
140
147
  return {
@@ -142,9 +149,16 @@ function decorateTabs(tabs, viewLabels, footer) {
142
149
  slots: {
143
150
  ...tab.slots,
144
151
  footer,
152
+ titleExtra: /*#__PURE__*/ jsxs(Fragment, {
153
+ children: [
154
+ tab.slots?.titleExtra,
155
+ titleExtra
156
+ ]
157
+ }),
145
158
  actions: (actions)=>/*#__PURE__*/ jsxs("div", {
146
159
  className: "ov-file-browser__detail-actions",
147
160
  children: [
161
+ extraActions,
148
162
  actions.canSwitchView ? /*#__PURE__*/ jsxs("div", {
149
163
  className: "ov-file-browser__segmented",
150
164
  role: "group",
@@ -170,16 +184,22 @@ function decorateTabs(tabs, viewLabels, footer) {
170
184
  "aria-hidden": "true",
171
185
  className: "ov-file-browser__detail-sep"
172
186
  }) : null,
173
- actions.canCopyContent ? /*#__PURE__*/ jsx("button", {
174
- type: "button",
187
+ actions.canCopyContent ? /*#__PURE__*/ jsx(button_Button, {
188
+ size: "small",
189
+ type: "text",
190
+ iconOnly: true,
191
+ htmlType: "button",
175
192
  className: "ov-file-browser__detail-icon",
176
193
  "aria-label": viewLabels.copyContent,
177
194
  title: viewLabels.copyContent,
178
195
  onClick: ()=>void actions.copyContent(),
179
196
  children: /*#__PURE__*/ jsx(CopyIcon, {})
180
197
  }) : null,
181
- actions.canDownload ? /*#__PURE__*/ jsx("button", {
182
- type: "button",
198
+ actions.canDownload ? /*#__PURE__*/ jsx(button_Button, {
199
+ size: "small",
200
+ type: "text",
201
+ iconOnly: true,
202
+ htmlType: "button",
183
203
  className: "ov-file-browser__detail-icon",
184
204
  "aria-label": viewLabels.download,
185
205
  title: viewLabels.download,
@@ -223,8 +243,10 @@ function FileBrowserFailure({ message, detail, retryLabel, onRetry }) {
223
243
  className: "ov-file-browser__failure-detail",
224
244
  children: detail
225
245
  }) : null,
226
- /*#__PURE__*/ jsx("button", {
227
- type: "button",
246
+ /*#__PURE__*/ jsx(button_Button, {
247
+ size: "small",
248
+ type: "default",
249
+ htmlType: "button",
228
250
  className: "ov-file-browser__failure-retry",
229
251
  onClick: onRetry,
230
252
  children: retryLabel
@@ -260,70 +282,20 @@ const dashboard_labels_dashboardLabels = (locale, labels)=>({
260
282
  ...'zh-CN' === locale ? zh : en,
261
283
  ...labels
262
284
  });
263
- const file_browser_icons_SIZE = 16;
264
- const strokeProps = {
265
- width: file_browser_icons_SIZE,
266
- height: file_browser_icons_SIZE,
267
- viewBox: '0 0 48 48',
268
- fill: 'none',
269
- stroke: 'currentColor',
270
- strokeWidth: 4,
271
- 'aria-hidden': true,
272
- focusable: 'false'
273
- };
274
- function SearchIcon() {
275
- return /*#__PURE__*/ jsx("svg", {
276
- ...strokeProps,
277
- children: /*#__PURE__*/ jsx("path", {
278
- d: "M33.072 33.071c6.248-6.248 6.248-16.379 0-22.627-6.249-6.249-16.38-6.249-22.628 0-6.248 6.248-6.248 16.379 0 22.627 6.248 6.248 16.38 6.248 22.628 0Zm0 0 8.485 8.485"
279
- })
280
- });
281
- }
282
- function RefreshIcon() {
283
- return /*#__PURE__*/ jsx("svg", {
284
- ...strokeProps,
285
- children: /*#__PURE__*/ jsx("path", {
286
- d: "M38.837 18C36.463 12.136 30.715 8 24 8 15.163 8 8 15.163 8 24s7.163 16 16 16c7.455 0 13.72-5.1 15.496-12M40 8v10H30"
287
- })
288
- });
289
- }
290
- function FileBrowserToolbar({ searchId, query, refreshing, canRefresh, placeholder, refreshLabel, onQueryChange, onRefresh }) {
291
- return /*#__PURE__*/ jsxs("div", {
285
+ function FileBrowserToolbar({ refreshing, canRefresh, placeholder, refreshLabel, onSearch, onRefresh }) {
286
+ return /*#__PURE__*/ jsx(FileTreeToolbar, {
292
287
  className: "ov-file-browser__toolbar",
293
- children: [
294
- /*#__PURE__*/ jsxs("label", {
295
- className: "ov-file-browser__search",
296
- htmlFor: searchId,
297
- children: [
298
- /*#__PURE__*/ jsx("span", {
299
- className: "ov-file-browser__search-icon",
300
- children: /*#__PURE__*/ jsx(SearchIcon, {})
301
- }),
302
- /*#__PURE__*/ jsx("input", {
303
- id: searchId,
304
- type: "search",
305
- className: "ov-file-browser__search-input",
306
- placeholder: placeholder,
307
- value: query,
308
- onChange: (event)=>onQueryChange(event.target.value)
309
- })
310
- ]
311
- }),
312
- /*#__PURE__*/ jsx(Tooltip, {
313
- content: refreshLabel,
314
- children: /*#__PURE__*/ jsx("button", {
315
- type: "button",
316
- className: [
317
- 'ov-file-browser__refresh',
318
- refreshing ? 'ov-file-browser__refresh--spinning' : ''
319
- ].filter(Boolean).join(' '),
320
- "aria-label": refreshLabel,
321
- disabled: !canRefresh,
322
- onClick: onRefresh,
323
- children: /*#__PURE__*/ jsx(RefreshIcon, {})
324
- })
325
- })
326
- ]
288
+ search: true,
289
+ refresh: {
290
+ disabled: !canRefresh
291
+ },
292
+ refreshing: refreshing,
293
+ text: {
294
+ search: placeholder,
295
+ refresh: refreshLabel
296
+ },
297
+ onSearch: onSearch,
298
+ onRefresh: onRefresh
327
299
  });
328
300
  }
329
301
  function DashboardIndicator({ dashboard, locale, labels, onOpen }) {
@@ -361,7 +333,7 @@ function directoryIcon() {
361
333
  className: "ov-file-tree__file-type-icon"
362
334
  });
363
335
  }
364
- function FileBrowserTreePane({ entries, locale, dashboardLabels, searchId, browser, treeLabels, searchPlaceholder, refreshLabel, searchLoading, searchEmpty, searchErrorLabel, retryLabel }) {
336
+ function FileBrowserTreePane({ entries, locale, dashboardLabels, onSearch, browser, treeLabels, searchPlaceholder, refreshLabel, retryLabel }) {
365
337
  return /*#__PURE__*/ jsxs(Fragment, {
366
338
  children: [
367
339
  /*#__PURE__*/ jsx(FileBrowserEntries, {
@@ -370,13 +342,11 @@ function FileBrowserTreePane({ entries, locale, dashboardLabels, searchId, brows
370
342
  className: "ov-file-browser__entries"
371
343
  }),
372
344
  /*#__PURE__*/ jsx(FileBrowserToolbar, {
373
- searchId: searchId,
374
- query: browser.query,
345
+ onSearch: onSearch,
375
346
  refreshing: browser.refreshing,
376
347
  canRefresh: browser.isReady,
377
348
  placeholder: searchPlaceholder,
378
349
  refreshLabel: refreshLabel,
379
- onQueryChange: browser.actions.setQuery,
380
350
  onRefresh: ()=>void browser.actions.refresh()
381
351
  }),
382
352
  /*#__PURE__*/ jsxs("div", {
@@ -394,41 +364,23 @@ function FileBrowserTreePane({ entries, locale, dashboardLabels, searchId, brows
394
364
  browser.dashboardError.message
395
365
  ]
396
366
  }),
397
- /*#__PURE__*/ jsx("button", {
398
- type: "button",
367
+ /*#__PURE__*/ jsx(button_Button, {
368
+ size: "small",
369
+ type: "default",
370
+ htmlType: "button",
399
371
  onClick: ()=>void browser.actions.refresh(),
400
372
  children: retryLabel
401
373
  })
402
374
  ]
403
375
  }) : null,
404
376
  /*#__PURE__*/ jsx(file_tree_FileTree, {
377
+ search: false,
378
+ refresh: false,
405
379
  locale: locale,
406
380
  rows: browser.rows,
407
381
  selectedId: browser.selectedId,
408
382
  isReady: browser.isReady,
409
383
  labels: treeLabels,
410
- emptyFallback: browser.searching ? browser.searchError ? /*#__PURE__*/ jsxs("div", {
411
- className: "ov-file-browser__hint ov-file-browser__hint--error",
412
- children: [
413
- /*#__PURE__*/ jsx("p", {
414
- className: "ov-file-browser__failure-title",
415
- children: searchErrorLabel
416
- }),
417
- /*#__PURE__*/ jsx("p", {
418
- className: "ov-file-browser__failure-detail",
419
- children: browser.searchError.message
420
- }),
421
- /*#__PURE__*/ jsx("button", {
422
- type: "button",
423
- className: "ov-file-browser__retry",
424
- onClick: ()=>void browser.actions.refresh(),
425
- children: retryLabel
426
- })
427
- ]
428
- }) : /*#__PURE__*/ jsx("p", {
429
- className: "ov-file-browser__hint",
430
- children: browser.searchLoading ? searchLoading : searchEmpty
431
- }) : void 0,
432
384
  renderRowIcon: (row)=>browser.dashboards.get(row.uri)?.isRoot ? /*#__PURE__*/ jsx(WikiBookIcon, {
433
385
  width: 14,
434
386
  height: 14
@@ -454,106 +406,138 @@ function FileBrowserTreePane({ entries, locale, dashboardLabels, searchId, brows
454
406
  ]
455
407
  });
456
408
  }
457
- const messages = {
458
- formatCharacterCount: (count)=>`${count.toLocaleString('en-US')} ${1 === count ? 'character' : 'characters'}`,
459
- empty: 'Select a file to preview',
460
- error: 'Failed to load the directory',
461
- retry: 'Retry',
462
- refresh: 'Refresh',
463
- searchPlaceholder: 'Search files ⌘+K...',
464
- searchEmpty: 'No matching resources',
465
- searchFailed: 'Failed to run the search',
466
- loading: 'Loading…',
467
- searchLoading: 'Searching…',
468
- previewMode: 'Preview',
469
- sourceMode: 'Source',
470
- copyPath: 'Copy path',
471
- copyContent: 'Copy content',
472
- download: 'Download',
473
- history: 'Version history',
474
- viewSwitch: 'Switch view',
475
- resizeTree: 'Resize the directory tree',
476
- l0Summary: 'L0 Summary',
477
- l1Overview: 'L1 Overview',
478
- summaryEmpty: 'No summary generated yet',
479
- summaryFailed: 'The summary could not be loaded.'
409
+ const en_US = {
410
+ closeLabel: 'Close dialog'
480
411
  };
481
- const en_US = messages;
482
- const zh_CN_messages = {
483
- formatCharacterCount: (count)=>`${count.toLocaleString('zh-CN')} 个字符`,
484
- empty: '选择一个文件查看内容',
485
- error: '目录加载失败',
486
- retry: '重试',
487
- refresh: '刷新',
488
- searchPlaceholder: '搜索文件 ⌘+K...',
489
- searchEmpty: '没有匹配的资源',
490
- searchFailed: '搜索失败',
491
- loading: '加载中…',
492
- searchLoading: '搜索中…',
493
- previewMode: '预览',
494
- sourceMode: '源码',
495
- copyPath: '复制路径',
496
- copyContent: '复制正文',
497
- download: '下载',
498
- history: '版本历史',
499
- viewSwitch: '切换视图',
500
- resizeTree: '调整目录树宽度',
501
- l0Summary: 'L0 摘要',
502
- l1Overview: 'L1 总览',
503
- summaryEmpty: '暂无生成内容',
504
- summaryFailed: '摘要加载失败。'
412
+ const zh_CN = {
413
+ closeLabel: '关闭弹窗'
505
414
  };
506
- const zh_CN = zh_CN_messages;
507
- const LOCALES = {
508
- 'en-US': en_US,
509
- 'zh-CN': zh_CN
415
+ const MOTION = {
416
+ appear: 'ov-modal--enter',
417
+ appearActive: 'ov-modal--enter-active',
418
+ enter: 'ov-modal--enter',
419
+ enterActive: 'ov-modal--enter-active',
420
+ exitActive: 'ov-modal--exit'
510
421
  };
511
- function resolveLabels(labels, locale) {
512
- return {
513
- ...resolveLocale(LOCALES, locale, labels),
514
- tree: labels?.tree,
515
- previewLabels: labels?.preview
516
- };
517
- }
518
- function resolvePreviewLabels(text) {
519
- return {
520
- loading: text.loading,
521
- copyPath: text.copyPath,
522
- download: text.download,
523
- ...text.previewLabels
524
- };
525
- }
526
- function resolveSummaryLabels(locale, labels) {
527
- const text = resolveLabels(labels, locale);
528
- return {
529
- l0: text.l0Summary,
530
- l1: text.l1Overview,
531
- failure: text.summaryFailed
532
- };
533
- }
534
- function useContainerWidth() {
535
- const ref = useRef(null);
536
- const [width, setWidth] = useState(0);
422
+ function Modal({ open, title, triggerRef, id, className, locale: localeProp, onClose, children, closeLabel }) {
423
+ const locale = useLocale(localeProp);
424
+ const text = resolveLocale({
425
+ 'en-US': en_US,
426
+ 'zh-CN': zh_CN
427
+ }, locale);
428
+ const label = closeLabel ?? text.closeLabel;
429
+ const generatedId = useId();
430
+ const panelRef = useRef(null);
431
+ const maskRef = useRef(null);
432
+ const [present, setPresent] = useState(open);
433
+ const [layer, setLayer] = useState(null);
434
+ usePortalTheme(Boolean(layer), triggerRef, layer);
537
435
  useEffect(()=>{
538
- const element = ref.current;
539
- if (!element) return;
540
- const update = (next)=>setWidth((current)=>current === next ? current : next);
541
- update(Math.round(element.getBoundingClientRect().width));
542
- const observer = new ResizeObserver(([entry])=>{
543
- if (entry) update(Math.round(entry.contentRect.width));
544
- });
545
- observer.observe(element);
546
- return ()=>observer.disconnect();
547
- }, []);
548
- return {
549
- ref,
550
- width
551
- };
436
+ if (layer) return lockBodyScroll(layer.ownerDocument.body);
437
+ }, [
438
+ layer
439
+ ]);
440
+ if ("u" < typeof document || !open && !present) return null;
441
+ return /*#__PURE__*/ createPortal(/*#__PURE__*/ jsxs("div", {
442
+ ref: setLayer,
443
+ role: "presentation",
444
+ lang: locale,
445
+ className: [
446
+ 'ov-modal',
447
+ className
448
+ ].filter(Boolean).join(' '),
449
+ onKeyDown: (event)=>{
450
+ if ('Escape' !== event.key || event.defaultPrevented) return;
451
+ if (!(event.target instanceof Node) || !event.currentTarget.contains(event.target)) return;
452
+ event.preventDefault();
453
+ event.stopPropagation();
454
+ onClose?.();
455
+ },
456
+ children: [
457
+ /*#__PURE__*/ jsx(CSSTransition, {
458
+ nodeRef: maskRef,
459
+ in: open,
460
+ timeout: 300,
461
+ classNames: MOTION,
462
+ appear: true,
463
+ mountOnEnter: true,
464
+ unmountOnExit: true,
465
+ children: /*#__PURE__*/ jsx("button", {
466
+ ref: maskRef,
467
+ type: "button",
468
+ className: "ov-modal__mask",
469
+ "aria-label": label,
470
+ "aria-hidden": "true",
471
+ tabIndex: -1,
472
+ onClick: onClose
473
+ })
474
+ }),
475
+ /*#__PURE__*/ jsx(CSSTransition, {
476
+ nodeRef: panelRef,
477
+ in: open,
478
+ timeout: 300,
479
+ classNames: MOTION,
480
+ appear: true,
481
+ mountOnEnter: true,
482
+ unmountOnExit: true,
483
+ onEnter: ()=>setPresent(true),
484
+ onExited: ()=>setPresent(false),
485
+ children: /*#__PURE__*/ jsx("div", {
486
+ ref: panelRef,
487
+ id: id ?? generatedId,
488
+ role: "dialog",
489
+ "aria-modal": "true",
490
+ "aria-label": title,
491
+ className: "ov-modal__panel",
492
+ children: /*#__PURE__*/ jsxs(es2015, {
493
+ className: "ov-modal__focus-lock",
494
+ returnFocus: {
495
+ preventScroll: true
496
+ },
497
+ crossFrame: false,
498
+ children: [
499
+ /*#__PURE__*/ jsx("button", {
500
+ type: "button",
501
+ className: "ov-modal__close",
502
+ "aria-label": label,
503
+ disabled: !onClose,
504
+ onClick: onClose,
505
+ children: /*#__PURE__*/ jsx(removereact, {
506
+ width: 16,
507
+ height: 16,
508
+ "aria-hidden": "true"
509
+ })
510
+ }),
511
+ /*#__PURE__*/ jsx("div", {
512
+ className: "ov-modal__body",
513
+ children: children
514
+ })
515
+ ]
516
+ })
517
+ })
518
+ })
519
+ ]
520
+ }), document.body);
552
521
  }
553
- function selectedRowId(uri, results) {
554
- if (!uri || null === results) return uri;
555
- const index = results.findIndex((entry)=>entry.uri === uri);
556
- return index < 0 ? uri : `${uri}#${index}`;
522
+ function mergeFindResult(result) {
523
+ const seen = new Set();
524
+ const entries = [];
525
+ for (const group of [
526
+ result?.memories,
527
+ result?.resources,
528
+ result?.skills
529
+ ])for (const item of group ?? []){
530
+ const uri = item?.uri;
531
+ if (!(!uri || seen.has(uri))) {
532
+ seen.add(uri);
533
+ entries.push({
534
+ uri,
535
+ isDir: false,
536
+ abstract: item.abstract?.trim() || void 0
537
+ });
538
+ }
539
+ }
540
+ return entries;
557
541
  }
558
542
  const emptyDir = ()=>({
559
543
  entries: [],
@@ -590,6 +574,9 @@ function toEntryRow(entry, depth, expanded, index, busy) {
590
574
  name: nameFromUri(entry.uri),
591
575
  type: isDir ? 'directory' : 'file',
592
576
  expanded: isDir ? expanded : void 0,
577
+ disabled: 'denied' === entry.access || void 0,
578
+ selectable: 'container' === entry.access ? false : void 0,
579
+ access: entry.access ?? ('read' === entry.permission ? 'read-only' : void 0),
593
580
  busy: busy || void 0
594
581
  };
595
582
  }
@@ -689,37 +676,302 @@ function flattenTree(uri, depth, dirs, expanded) {
689
676
  function isAbort(error) {
690
677
  return 'object' == typeof error && null !== error && 'AbortError' === error.name;
691
678
  }
692
- function toError(cause) {
693
- return cause instanceof Error ? cause : new Error(String(cause));
694
- }
695
- async function readDirectory(client, uri, options) {
696
- const read = (path)=>client.request({
697
- method: 'GET',
698
- path,
699
- query: {
700
- uri
679
+ const toError = (cause)=>cause instanceof Error ? cause : new Error(String(cause));
680
+ const LIMIT = 20;
681
+ function useFsSearch({ client, query, targetUri, generation = 0, requestOptions }) {
682
+ const [results, setResults] = useState(null);
683
+ const [loading, setLoading] = useState(false);
684
+ const [error, setError] = useState(null);
685
+ useEffect(()=>{
686
+ if (!client || !query) {
687
+ setResults(null);
688
+ setLoading(false);
689
+ setError(null);
690
+ return;
691
+ }
692
+ const controller = new AbortController();
693
+ setLoading(true);
694
+ setError(null);
695
+ client.request({
696
+ method: 'POST',
697
+ path: OV_PATHS.searchFind,
698
+ body: {
699
+ query,
700
+ target_uri: targetUri,
701
+ limit: LIMIT
701
702
  },
702
- options
703
- }).then((value)=>({
704
- ok: true,
705
- text: 'string' == typeof value ? value : ''
706
- })).catch((cause)=>({
707
- ok: false,
708
- cause
709
- }));
710
- const [abstractResult, overviewResult] = await Promise.all([
711
- read(OV_PATHS.contentAbstract),
712
- read(OV_PATHS.contentOverview)
703
+ options: {
704
+ ...requestOptions,
705
+ signal: controller.signal
706
+ }
707
+ }).then((result)=>{
708
+ if (controller.signal.aborted) return;
709
+ setResults(mergeFindResult(result));
710
+ setLoading(false);
711
+ }, (cause)=>{
712
+ if (isAbort(cause) || controller.signal.aborted) return;
713
+ setResults([]);
714
+ setError(toError(cause));
715
+ setLoading(false);
716
+ });
717
+ return ()=>controller.abort();
718
+ }, [
719
+ client,
720
+ query,
721
+ targetUri,
722
+ generation,
723
+ requestOptions
713
724
  ]);
714
- const aborted = [
715
- abstractResult,
716
- overviewResult
717
- ].find((result)=>!result.ok && isAbort(result.cause));
718
- if (aborted && !aborted.ok) throw aborted.cause;
719
- if (!abstractResult.ok && !overviewResult.ok) throw abstractResult.cause;
720
- const reason = (result)=>result.ok ? void 0 : result.cause instanceof Error ? result.cause.message : String(result.cause);
721
725
  return {
722
- abstract: abstractResult.ok ? abstractResult.text : '',
726
+ results,
727
+ loading,
728
+ error
729
+ };
730
+ }
731
+ function FileBrowserSearch({ open, rootUri, triggerRef, locale, text, requestOptions, onClose }) {
732
+ const client = useOvClient();
733
+ const [draft, setDraft] = useState('');
734
+ const [request, setRequest] = useState({
735
+ query: '',
736
+ generation: 0
737
+ });
738
+ const search = useFsSearch({
739
+ client,
740
+ query: open ? request.query : '',
741
+ targetUri: rootUri,
742
+ generation: request.generation,
743
+ requestOptions
744
+ });
745
+ const close = ()=>{
746
+ setDraft('');
747
+ setRequest({
748
+ query: '',
749
+ generation: 0
750
+ });
751
+ onClose();
752
+ };
753
+ return /*#__PURE__*/ jsxs(Modal, {
754
+ open: open,
755
+ title: text.searchTitle,
756
+ locale: locale,
757
+ triggerRef: triggerRef,
758
+ className: "ov-file-browser-search",
759
+ onClose: close,
760
+ children: [
761
+ /*#__PURE__*/ jsx("h2", {
762
+ className: "ov-file-browser-search__title",
763
+ children: text.searchTitle
764
+ }),
765
+ /*#__PURE__*/ jsx("search", {
766
+ children: /*#__PURE__*/ jsxs("form", {
767
+ className: "ov-file-browser-search__form",
768
+ onSubmit: (event)=>{
769
+ event.preventDefault();
770
+ const query = draft.trim();
771
+ if (!query || !client || search.loading) return;
772
+ setRequest((current)=>({
773
+ query,
774
+ generation: current.generation + 1
775
+ }));
776
+ },
777
+ children: [
778
+ /*#__PURE__*/ jsx(input_Input, {
779
+ "data-autofocus": true,
780
+ required: true,
781
+ type: "search",
782
+ name: "query",
783
+ "aria-label": text.searchTitle,
784
+ placeholder: text.searchPlaceholder,
785
+ value: draft,
786
+ onChange: (event)=>setDraft(event.target.value)
787
+ }),
788
+ /*#__PURE__*/ jsx(button_Button, {
789
+ type: "primary",
790
+ htmlType: "submit",
791
+ loading: search.loading,
792
+ disabled: !client,
793
+ children: text.searchAction
794
+ })
795
+ ]
796
+ })
797
+ }),
798
+ /*#__PURE__*/ jsx("div", {
799
+ className: "ov-file-browser-search__results",
800
+ "aria-busy": search.loading,
801
+ children: search.loading ? /*#__PURE__*/ jsx("p", {
802
+ role: "status",
803
+ children: text.searchLoading
804
+ }) : search.error ? /*#__PURE__*/ jsxs("div", {
805
+ role: "alert",
806
+ className: "ov-file-browser-search__error",
807
+ children: [
808
+ /*#__PURE__*/ jsx("p", {
809
+ children: text.searchFailed
810
+ }),
811
+ /*#__PURE__*/ jsx("p", {
812
+ children: search.error.message
813
+ })
814
+ ]
815
+ }) : null === search.results ? /*#__PURE__*/ jsx("p", {
816
+ role: "status",
817
+ children: text.searchHint
818
+ }) : 0 === search.results.length ? /*#__PURE__*/ jsx("p", {
819
+ role: "status",
820
+ children: text.searchEmpty
821
+ }) : /*#__PURE__*/ jsx("ul", {
822
+ className: "ov-file-browser-search__list",
823
+ children: search.results.map((entry)=>/*#__PURE__*/ jsxs("li", {
824
+ className: "ov-file-browser-search__result",
825
+ children: [
826
+ /*#__PURE__*/ jsx("span", {
827
+ className: "ov-file-browser-search__uri",
828
+ children: entry.uri
829
+ }),
830
+ entry.abstract ? /*#__PURE__*/ jsx("p", {
831
+ className: "ov-file-browser-search__summary",
832
+ children: entry.abstract
833
+ }) : null
834
+ ]
835
+ }, entry.uri))
836
+ })
837
+ })
838
+ ]
839
+ });
840
+ }
841
+ const messages = {
842
+ formatCharacterCount: (count)=>`${count.toLocaleString('en-US')} ${1 === count ? 'character' : 'characters'}`,
843
+ accessDenied: 'You do not have permission to view this node.',
844
+ traverseOnly: 'This directory can only be expanded to reach accessible children.',
845
+ empty: 'Select a file to preview',
846
+ error: 'Failed to load the directory',
847
+ retry: 'Retry',
848
+ refresh: 'Refresh',
849
+ searchPlaceholder: 'Search files ⌘+K...',
850
+ searchTitle: 'Search files',
851
+ searchAction: 'Search',
852
+ searchHint: 'Enter a query to search.',
853
+ searchEmpty: 'No matching resources',
854
+ searchFailed: 'Failed to run the search',
855
+ loading: 'Loading…',
856
+ searchLoading: 'Searching…',
857
+ previewMode: 'Preview',
858
+ sourceMode: 'Source',
859
+ copyPath: 'Copy path',
860
+ copyContent: 'Copy content',
861
+ download: 'Download',
862
+ history: 'Version history',
863
+ viewSwitch: 'Switch view',
864
+ resizeTree: 'Resize the directory tree',
865
+ l0Summary: 'L0 Summary',
866
+ l1Overview: 'L1 Overview',
867
+ summaryEmpty: 'No summary generated yet',
868
+ summaryFailed: 'The summary could not be loaded.'
869
+ };
870
+ const locales_en_US = messages;
871
+ const zh_CN_messages = {
872
+ formatCharacterCount: (count)=>`${count.toLocaleString('zh-CN')} 个字符`,
873
+ accessDenied: '无权查看此节点。',
874
+ traverseOnly: '此目录仅可展开以访问有权限的子节点。',
875
+ empty: '选择一个文件查看内容',
876
+ error: '目录加载失败',
877
+ retry: '重试',
878
+ refresh: '刷新',
879
+ searchPlaceholder: '搜索文件 ⌘+K...',
880
+ searchTitle: '搜索文件',
881
+ searchAction: '搜索',
882
+ searchHint: '输入关键词后搜索。',
883
+ searchEmpty: '没有匹配的资源',
884
+ searchFailed: '搜索失败',
885
+ loading: '加载中…',
886
+ searchLoading: '搜索中…',
887
+ previewMode: '预览',
888
+ sourceMode: '源码',
889
+ copyPath: '复制路径',
890
+ copyContent: '复制正文',
891
+ download: '下载',
892
+ history: '版本历史',
893
+ viewSwitch: '切换视图',
894
+ resizeTree: '调整目录树宽度',
895
+ l0Summary: 'L0 摘要',
896
+ l1Overview: 'L1 总览',
897
+ summaryEmpty: '暂无生成内容',
898
+ summaryFailed: '摘要加载失败。'
899
+ };
900
+ const locales_zh_CN = zh_CN_messages;
901
+ const LOCALES = {
902
+ 'en-US': locales_en_US,
903
+ 'zh-CN': locales_zh_CN
904
+ };
905
+ function resolveLabels(labels, locale) {
906
+ return {
907
+ ...resolveLocale(LOCALES, locale, labels),
908
+ tree: labels?.tree,
909
+ previewLabels: labels?.preview
910
+ };
911
+ }
912
+ function resolvePreviewLabels(text) {
913
+ return {
914
+ loading: text.loading,
915
+ copyPath: text.copyPath,
916
+ download: text.download,
917
+ ...text.previewLabels
918
+ };
919
+ }
920
+ function resolveSummaryLabels(locale, labels) {
921
+ const text = resolveLabels(labels, locale);
922
+ return {
923
+ l0: text.l0Summary,
924
+ l1: text.l1Overview,
925
+ failure: text.summaryFailed
926
+ };
927
+ }
928
+ function useContainerWidth() {
929
+ const ref = useRef(null);
930
+ const [width, setWidth] = useState(0);
931
+ useEffect(()=>{
932
+ const element = ref.current;
933
+ if (!element) return;
934
+ const update = (next)=>setWidth((current)=>current === next ? current : next);
935
+ update(Math.round(element.getBoundingClientRect().width));
936
+ const observer = new ResizeObserver(([entry])=>{
937
+ if (entry) update(Math.round(entry.contentRect.width));
938
+ });
939
+ observer.observe(element);
940
+ return ()=>observer.disconnect();
941
+ }, []);
942
+ return {
943
+ ref,
944
+ width
945
+ };
946
+ }
947
+ async function readDirectory(client, uri, options) {
948
+ const read = (path)=>client.request({
949
+ method: 'GET',
950
+ path,
951
+ query: {
952
+ uri
953
+ },
954
+ options
955
+ }).then((value)=>({
956
+ ok: true,
957
+ text: 'string' == typeof value ? value : ''
958
+ })).catch((cause)=>({
959
+ ok: false,
960
+ cause
961
+ }));
962
+ const [abstractResult, overviewResult] = await Promise.all([
963
+ read(OV_PATHS.contentAbstract),
964
+ read(OV_PATHS.contentOverview)
965
+ ]);
966
+ const aborted = [
967
+ abstractResult,
968
+ overviewResult
969
+ ].find((result)=>!result.ok && isAbort(result.cause));
970
+ if (aborted && !aborted.ok) throw aborted.cause;
971
+ if (!abstractResult.ok && !overviewResult.ok) throw abstractResult.cause;
972
+ const reason = (result)=>result.ok ? void 0 : result.cause instanceof Error ? result.cause.message : String(result.cause);
973
+ return {
974
+ abstract: abstractResult.ok ? abstractResult.text : '',
723
975
  overview: overviewResult.ok ? overviewResult.text : '',
724
976
  abstractError: reason(abstractResult),
725
977
  overviewError: reason(overviewResult)
@@ -1093,77 +1345,6 @@ function loadingTab(uri, isDirectory) {
1093
1345
  }
1094
1346
  };
1095
1347
  }
1096
- function mergeFindResult(result) {
1097
- const seen = new Set();
1098
- const entries = [];
1099
- for (const group of [
1100
- result?.memories,
1101
- result?.resources,
1102
- result?.skills
1103
- ])for (const item of group ?? []){
1104
- const uri = item?.uri;
1105
- if (!(!uri || seen.has(uri))) {
1106
- seen.add(uri);
1107
- entries.push({
1108
- uri,
1109
- isDir: false,
1110
- abstract: item.abstract?.trim() || void 0
1111
- });
1112
- }
1113
- }
1114
- return entries;
1115
- }
1116
- const LIMIT = 20;
1117
- function useFsSearch({ client, query, targetUri, generation = 0, requestOptions }) {
1118
- const [results, setResults] = useState(null);
1119
- const [loading, setLoading] = useState(false);
1120
- const [error, setError] = useState(null);
1121
- useEffect(()=>{
1122
- if (!client || !query) {
1123
- setResults(null);
1124
- setLoading(false);
1125
- setError(null);
1126
- return;
1127
- }
1128
- const controller = new AbortController();
1129
- setLoading(true);
1130
- setError(null);
1131
- client.request({
1132
- method: 'POST',
1133
- path: OV_PATHS.searchFind,
1134
- body: {
1135
- query,
1136
- target_uri: targetUri,
1137
- limit: LIMIT
1138
- },
1139
- options: {
1140
- ...requestOptions,
1141
- signal: controller.signal
1142
- }
1143
- }).then((result)=>{
1144
- if (controller.signal.aborted) return;
1145
- setResults(mergeFindResult(result));
1146
- setLoading(false);
1147
- }, (cause)=>{
1148
- if (isAbort(cause) || controller.signal.aborted) return;
1149
- setResults([]);
1150
- setError(toError(cause));
1151
- setLoading(false);
1152
- });
1153
- return ()=>controller.abort();
1154
- }, [
1155
- client,
1156
- query,
1157
- targetUri,
1158
- generation,
1159
- requestOptions
1160
- ]);
1161
- return {
1162
- results,
1163
- loading,
1164
- error
1165
- };
1166
- }
1167
1348
  const isHomeUri = (uri)=>'viking://~' === uri || uri.startsWith('viking://~/');
1168
1349
  async function readDashboardStat(client, uri, options) {
1169
1350
  const stat = await client.request({
@@ -1278,6 +1459,12 @@ function resolveDashboard(uri, rows, targets) {
1278
1459
  pendingConflicts: tagged ? 'has_conflict=true' === tagged : owner?.to === uri && void 0 !== owner.conflicts_num ? owner.conflicts_num > 0 : void 0
1279
1460
  };
1280
1461
  }
1462
+ function mergeDashboardTab(tabs, dashboard, blocked) {
1463
+ return blocked ? null : dashboard ? [
1464
+ ...tabs ?? [],
1465
+ dashboard
1466
+ ] : tabs;
1467
+ }
1281
1468
  async function readCompileTargets(client, rootUri, options) {
1282
1469
  rootUri = await resolveDashboardUri(client, rootUri, options);
1283
1470
  const targets = [];
@@ -1550,9 +1737,9 @@ function ExperienceDashboardPane({ uri, locale, requestOptions, refreshGeneratio
1550
1737
  const wiki_conflicts_prefix = '<!-- CONFLICTS_CONTENT';
1551
1738
  const manualResolutionBasis = '\u4eba\u5de5\u4fee\u590d';
1552
1739
  const timestamp = (value)=>'number' == typeof value && Number.isSafeInteger(value) && value >= 0 && Number.isFinite(new Date(1000 * value).getTime());
1553
- const locateWikiConflict = (document, matchText)=>{
1740
+ const locateWikiConflict = (document1, matchText)=>{
1554
1741
  const mask = (value)=>value.replace(/[^\r\n]/g, ' ');
1555
- const body = document.replace(/^(?:\uFEFF)?---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/, mask).replace(/<!--[\s\S]*?(?:-->|$)/g, mask);
1742
+ const body = document1.replace(/^(?:\uFEFF)?---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/, mask).replace(/<!--[\s\S]*?(?:-->|$)/g, mask);
1556
1743
  const start = matchText ? body.indexOf(matchText) : -1;
1557
1744
  if (start < 0) return {
1558
1745
  status: 'missing'
@@ -1564,7 +1751,7 @@ const locateWikiConflict = (document, matchText)=>{
1564
1751
  status: 'unique',
1565
1752
  start,
1566
1753
  end: start + matchText.length,
1567
- line: document.slice(0, start).split('\n').length
1754
+ line: document1.slice(0, start).split('\n').length
1568
1755
  };
1569
1756
  };
1570
1757
  const parseWikiFileConflicts = (content)=>{
@@ -1576,7 +1763,7 @@ const parseWikiFileConflicts = (content)=>{
1576
1763
  if (!footer.endsWith('-->')) return {
1577
1764
  status: 'invalid'
1578
1765
  };
1579
- const document = content.slice(0, start);
1766
+ const document1 = content.slice(0, start);
1580
1767
  const source = footer.slice(wiki_conflicts_prefix.length, -3).trim();
1581
1768
  let parsed;
1582
1769
  try {
@@ -1609,7 +1796,7 @@ const parseWikiFileConflicts = (content)=>{
1609
1796
  status: 'invalid'
1610
1797
  };
1611
1798
  const matchText = value.match_text;
1612
- const location = locateWikiConflict(document, matchText);
1799
+ const location = locateWikiConflict(document1, matchText);
1613
1800
  records.push({
1614
1801
  id: JSON.stringify([
1615
1802
  index,
@@ -1631,7 +1818,7 @@ const parseWikiFileConflicts = (content)=>{
1631
1818
  }
1632
1819
  return {
1633
1820
  status: 'available',
1634
- document,
1821
+ document: document1,
1635
1822
  records
1636
1823
  };
1637
1824
  };
@@ -1757,7 +1944,7 @@ function useBrowserDashboards({ rows, metadataRows, uri, rootUri, generation, lo
1757
1944
  uri: target
1758
1945
  } : void 0;
1759
1946
  };
1760
- const dashboards = new Map(rows.filter((row)=>'entry' === row.kind).flatMap((row)=>{
1947
+ const dashboards = new Map(rows.filter((row)=>'entry' === row.kind).filter((row)=>!row.disabled && false !== row.selectable).flatMap((row)=>{
1761
1948
  const dashboard = resolve(row.uri);
1762
1949
  return dashboard && (void 0 === dashboardKinds || dashboardKinds.includes(dashboard.kind)) ? [
1763
1950
  [
@@ -1849,10 +2036,17 @@ function useTreeMetadata(rootUri, dirs) {
1849
2036
  const metadataRows = useMemo(()=>Object.values(dirs).flatMap((dir)=>dir.entries.map((entry)=>toEntryRow(entry, 0, false))), [
1850
2037
  dirs
1851
2038
  ]);
2039
+ const accessOf = useCallback((uri)=>{
2040
+ const row = metadataRows.find((entry)=>entry.uri === uri);
2041
+ return row?.access === 'denied' || row?.access === 'container' ? row.access : void 0;
2042
+ }, [
2043
+ metadataRows
2044
+ ]);
1852
2045
  return {
1853
2046
  directoryUris,
1854
2047
  isKnownFile,
1855
- metadataRows
2048
+ metadataRows,
2049
+ accessOf
1856
2050
  };
1857
2051
  }
1858
2052
  const ids = new WeakMap();
@@ -2060,6 +2254,7 @@ function useFsTree({ client, rootUri, defaultExpandedUri, requestOptions }) {
2060
2254
  }
2061
2255
  },
2062
2256
  toggleExpand: async (row)=>{
2257
+ if (row.disabled) return;
2063
2258
  const isOpen = expandedRef.current.has(row.uri);
2064
2259
  setExpanded((prev)=>{
2065
2260
  const next = new Set(prev);
@@ -2096,19 +2291,18 @@ function useFsTree({ client, rootUri, defaultExpandedUri, requestOptions }) {
2096
2291
  dirs,
2097
2292
  expanded
2098
2293
  ]);
2099
- const metadata = useTreeMetadata(rootUri, dirs);
2100
2294
  return {
2101
2295
  rows,
2102
- ...metadata,
2296
+ ...useTreeMetadata(rootUri, dirs),
2103
2297
  isReady,
2104
2298
  error,
2105
2299
  ...state
2106
2300
  };
2107
2301
  }
2108
- function useFileBrowser({ rootUri, locale: localeProp, dashboardKinds, renderDashboard, dashboardLabels, controlled = false, summaryLabels, formatCharacterCount, defaultExpandedUri, currentUri, requestOptions, onUriChange }) {
2302
+ function useFileBrowser({ rootUri, locale: localeProp, dashboardKinds, renderDashboard, dashboardLabels, controlled = false, summaryLabels, accessLabels, formatCharacterCount, defaultExpandedUri, currentUri, requestOptions, onUriChange }) {
2109
2303
  const locale = useLocale(localeProp);
2304
+ const text = resolveLabels(accessLabels, locale);
2110
2305
  const client = useOvClient();
2111
- const [query, setQuery] = useState('');
2112
2306
  const [pendingUri, setPendingUri] = useState(currentUri);
2113
2307
  const [refreshing, setRefreshing] = useState(false);
2114
2308
  const [refreshGeneration, setRefreshGeneration] = useState(0);
@@ -2118,14 +2312,8 @@ function useFileBrowser({ rootUri, locale: localeProp, dashboardKinds, renderDas
2118
2312
  defaultExpandedUri,
2119
2313
  requestOptions
2120
2314
  });
2121
- const search = useFsSearch({
2122
- client,
2123
- query,
2124
- targetUri: rootUri,
2125
- generation: refreshGeneration,
2126
- requestOptions
2127
- });
2128
2315
  const activeUri = controlled ? currentUri : pendingUri;
2316
+ const blocked = tree.accessOf(activeUri);
2129
2317
  const resourceType = null === activeUri ? 'file' : tree.directoryUris.has(activeUri) ? 'directory' : tree.isKnownFile(activeUri) ? 'file' : 'unknown';
2130
2318
  const openTree = tree.open;
2131
2319
  const notifyUriChange = useRef(onUriChange);
@@ -2139,21 +2327,15 @@ function useFileBrowser({ rootUri, locale: localeProp, dashboardKinds, renderDas
2139
2327
  ]);
2140
2328
  const preview = useFsPreview({
2141
2329
  client,
2142
- uri: activeUri,
2330
+ uri: blocked ? null : activeUri,
2143
2331
  resourceType,
2144
2332
  labels: summaryLabels ?? resolveSummaryLabels(locale),
2145
- formatCharacterCount: formatCharacterCount ?? resolveLabels(void 0, locale).formatCharacterCount,
2333
+ formatCharacterCount: formatCharacterCount ?? text.formatCharacterCount,
2146
2334
  openParent,
2147
2335
  generation: refreshGeneration,
2148
2336
  requestOptions
2149
2337
  });
2150
- const rows = useMemo(()=>{
2151
- if (null === search.results) return tree.rows;
2152
- return search.results.map((entry, index)=>toEntryRow(entry, 0, false, index));
2153
- }, [
2154
- search.results,
2155
- tree.rows
2156
- ]);
2338
+ const rows = tree.rows;
2157
2339
  const openPath = tree.openPath;
2158
2340
  const openResource = useMemo(()=>async (uri)=>{
2159
2341
  setPendingUri(uri);
@@ -2167,7 +2349,7 @@ function useFileBrowser({ rootUri, locale: localeProp, dashboardKinds, renderDas
2167
2349
  const boards = useBrowserDashboards({
2168
2350
  rows,
2169
2351
  metadataRows: tree.metadataRows,
2170
- uri: activeUri,
2352
+ uri: blocked ? null : activeUri,
2171
2353
  rootUri,
2172
2354
  generation: refreshGeneration,
2173
2355
  locale,
@@ -2177,12 +2359,13 @@ function useFileBrowser({ rootUri, locale: localeProp, dashboardKinds, renderDas
2177
2359
  renderDashboard,
2178
2360
  dashboardLabels
2179
2361
  });
2180
- const selectedId = selectedRowId(activeUri, search.results);
2362
+ const selectedId = activeUri;
2181
2363
  const { open, toggleExpand, loadMore, retry, refresh } = tree;
2182
2364
  const { open: openDashboard, close: closeDashboard } = boards;
2183
2365
  const setActiveKey = preview.setActiveKey;
2184
2366
  const actions = useMemo(()=>({
2185
2367
  select: async (row)=>{
2368
+ if (row.disabled || false === row.selectable) return;
2186
2369
  closeDashboard();
2187
2370
  setPendingUri(row.uri);
2188
2371
  notifyUriChange.current(row.uri);
@@ -2213,8 +2396,7 @@ function useFileBrowser({ rootUri, locale: localeProp, dashboardKinds, renderDas
2213
2396
  closeDashboard();
2214
2397
  setActiveKey(key);
2215
2398
  }
2216
- },
2217
- setQuery
2399
+ }
2218
2400
  }), [
2219
2401
  setActiveKey,
2220
2402
  open,
@@ -2226,52 +2408,1284 @@ function useFileBrowser({ rootUri, locale: localeProp, dashboardKinds, renderDas
2226
2408
  closeDashboard,
2227
2409
  activeUri
2228
2410
  ]);
2411
+ const previewError = blocked ? new Error(text['denied' === blocked ? 'accessDenied' : 'traverseOnly']) : preview.error;
2229
2412
  return {
2230
2413
  rows,
2231
2414
  selectedId,
2232
2415
  currentUri: activeUri,
2233
2416
  isReady: tree.isReady,
2234
- error: tree.error ?? search.error ?? preview.error,
2417
+ error: tree.error ?? previewError,
2235
2418
  treeError: tree.error,
2236
- previewError: preview.error,
2237
- searchError: search.error,
2419
+ previewError,
2238
2420
  dashboards: boards.dashboards,
2239
2421
  dashboardError: boards.error,
2240
- previewTabs: boards.tab ? [
2241
- ...preview.tabs ?? [],
2242
- boards.tab
2243
- ] : preview.tabs,
2422
+ previewTabs: mergeDashboardTab(preview.tabs, boards.tab, Boolean(blocked)),
2244
2423
  previewKey: boards.isOpen && activeUri ? dashboardKey(activeUri) : preview.activeKey,
2245
2424
  previewFooter: preview.footer,
2246
- query,
2247
- searching: null !== search.results,
2248
- searchLoading: search.loading,
2249
2425
  refreshing,
2250
2426
  actions
2251
2427
  };
2252
2428
  }
2253
- function FileBrowser({ uri, defaultUri, rootUri = 'viking://', defaultExpandedUri, locale: localeProp, agentConnect, dashboardKinds, renderDashboard, onUriChange, onImportResources, renderImportResources, labels, requestOptions, className, style, ...rest }) {
2254
- const locale = useLocale(localeProp);
2255
- const [internalUri, setInternalUri] = useState(()=>defaultUri ?? null);
2256
- const [preferredTreeWidth, setPreferredTreeWidth] = useState(352);
2257
- const searchId = useId();
2258
- const isControlled = void 0 !== uri;
2259
- const currentUri = isControlled ? uri : internalUri;
2260
- const text = resolveLabels(labels, locale);
2261
- const { ref: layoutRef, width: layoutWidth } = useContainerWidth();
2262
- const treeMaxWidth = resolveTreeMaxWidth(layoutWidth);
2263
- const treeWidth = Math.min(preferredTreeWidth, treeMaxWidth);
2264
- const changeUri = useCallback((next)=>{
2265
- if (!isControlled) setInternalUri(next);
2266
- onUriChange?.(next);
2267
- }, [
2268
- isControlled,
2269
- onUriChange
2270
- ]);
2271
- const summaryLabels = useMemo(()=>({
2272
- l0: text.l0Summary,
2273
- l1: text.l1Overview,
2274
- failure: text.summaryFailed
2429
+ function AclPrincipalTabs({ type, text, onChange }) {
2430
+ return /*#__PURE__*/ jsx("div", {
2431
+ className: "ov-file-browser-acl__types",
2432
+ role: "group",
2433
+ "aria-label": text.addPrincipal,
2434
+ children: [
2435
+ 'user',
2436
+ 'group'
2437
+ ].map((value)=>/*#__PURE__*/ jsx(button_Button, {
2438
+ size: "small",
2439
+ type: type === value ? 'primary' : 'secondary',
2440
+ "aria-pressed": type === value,
2441
+ onClick: ()=>onChange(value),
2442
+ children: 'user' === value ? text.users : text.groups
2443
+ }, value))
2444
+ });
2445
+ }
2446
+ const file_browser_icons_SIZE = 16;
2447
+ const strokeProps = {
2448
+ width: file_browser_icons_SIZE,
2449
+ height: file_browser_icons_SIZE,
2450
+ viewBox: '0 0 48 48',
2451
+ fill: 'none',
2452
+ stroke: 'currentColor',
2453
+ strokeWidth: 4,
2454
+ 'aria-hidden': true,
2455
+ focusable: 'false'
2456
+ };
2457
+ function SearchIcon() {
2458
+ return /*#__PURE__*/ jsx("svg", {
2459
+ ...strokeProps,
2460
+ children: /*#__PURE__*/ jsx("path", {
2461
+ d: "M33.072 33.071c6.248-6.248 6.248-16.379 0-22.627-6.249-6.249-16.38-6.249-22.628 0-6.248 6.248-6.248 16.379 0 22.627 6.248 6.248 16.38 6.248 22.628 0Zm0 0 8.485 8.485"
2462
+ })
2463
+ });
2464
+ }
2465
+ const LEVELS = [
2466
+ 'read',
2467
+ 'write',
2468
+ 'manage'
2469
+ ];
2470
+ const RANK = {
2471
+ read: 1,
2472
+ write: 2,
2473
+ manage: 3
2474
+ };
2475
+ const isLevel = (value)=>LEVELS.includes(value);
2476
+ function isAclTarget(uri) {
2477
+ if (!uri?.startsWith('viking://resources/')) return false;
2478
+ const segments = uri.slice(19).split('/');
2479
+ return segments.every((part)=>part && '.' !== part && '..' !== part);
2480
+ }
2481
+ function normalizeEntries(entries) {
2482
+ const byPrincipal = new Map();
2483
+ for (const entry of entries){
2484
+ const principal = entry.principal.trim();
2485
+ if (!/^(user|group):[^\s:]+$/.test(principal) || 'group:*' === principal || !isLevel(entry.level)) throw new TypeError('Invalid ACL entry');
2486
+ const previous = byPrincipal.get(principal);
2487
+ if (!previous || RANK[entry.level] > RANK[previous.level]) byPrincipal.set(principal, {
2488
+ principal,
2489
+ level: entry.level
2490
+ });
2491
+ }
2492
+ return [
2493
+ ...byPrincipal.values()
2494
+ ].sort((a, b)=>a.principal.localeCompare(b.principal));
2495
+ }
2496
+ function parseReport(value, uri) {
2497
+ const report = value;
2498
+ if (!report || report.uri !== uri || ![
2499
+ 'none',
2500
+ 'inherit',
2501
+ 'restricted'
2502
+ ].includes(report.acl_mode)) throw new TypeError('Invalid ACL report');
2503
+ for (const key of [
2504
+ 'direct_entries',
2505
+ 'inherited_entries',
2506
+ 'effective_entries'
2507
+ ])if (!Array.isArray(report[key])) throw new TypeError('Invalid ACL entries');
2508
+ return {
2509
+ uri,
2510
+ acl_mode: report.acl_mode,
2511
+ direct_entries: normalizeEntries(report.direct_entries),
2512
+ inherited_entries: normalizeEntries(report.inherited_entries),
2513
+ effective_entries: normalizeEntries(report.effective_entries)
2514
+ };
2515
+ }
2516
+ function planChange(report, next) {
2517
+ const parent = new Map(report.inherited_entries.map((entry)=>[
2518
+ entry.principal,
2519
+ entry
2520
+ ]));
2521
+ const target = new Map(next.map((entry)=>[
2522
+ entry.principal,
2523
+ entry
2524
+ ]));
2525
+ const retained = [
2526
+ ...report.inherited_entries,
2527
+ ...report.effective_entries
2528
+ ].every((entry)=>{
2529
+ const value = target.get(entry.principal);
2530
+ return value && RANK[value.level] >= RANK[entry.level];
2531
+ });
2532
+ const entries = normalizeEntries(next);
2533
+ if ('restricted' === report.acl_mode) return {
2534
+ entries,
2535
+ mode: 'restricted',
2536
+ confirmation: false
2537
+ };
2538
+ if (!retained) return {
2539
+ entries,
2540
+ mode: 'restricted',
2541
+ confirmation: true
2542
+ };
2543
+ return {
2544
+ entries: entries.filter((entry)=>{
2545
+ const inherited = parent.get(entry.principal);
2546
+ return !inherited || RANK[entry.level] > RANK[inherited.level];
2547
+ }),
2548
+ mode: 'inherit',
2549
+ confirmation: false
2550
+ };
2551
+ }
2552
+ function restoreDiff(report) {
2553
+ const inherited = new Map(report.inherited_entries.map((entry)=>[
2554
+ entry.principal,
2555
+ RANK[entry.level]
2556
+ ]));
2557
+ return report.direct_entries.filter((entry)=>Math.max(inherited.get(entry.principal) ?? 0, entry.principal.startsWith('user:') ? inherited.get('user:*') ?? 0 : 0) < RANK[entry.level]);
2558
+ }
2559
+ const acl_locales_en_US = {
2560
+ title: 'Manage node permissions',
2561
+ manage: 'Manage permissions',
2562
+ read: 'Can read',
2563
+ write: 'Can edit',
2564
+ manageLevel: 'Can manage',
2565
+ readDescription: 'Can view content, but cannot edit or manage permissions.',
2566
+ writeDescription: 'Can view and edit content, but cannot manage permissions.',
2567
+ manageDescription: 'Can manage the permissions of this node.',
2568
+ effectivePermission: 'Current user’s effective permission',
2569
+ addPrincipal: 'Add a User or Group',
2570
+ search: 'Search users or groups',
2571
+ users: 'User',
2572
+ groups: 'Group',
2573
+ allUsers: 'All users',
2574
+ administrator: 'Account administrators',
2575
+ administratorDescription: 'Administrators retain implicit management access.',
2576
+ direct: 'Direct grant',
2577
+ inherited: 'Inherited from parent',
2578
+ remove: 'Remove permission',
2579
+ add: 'Add',
2580
+ cancel: 'Cancel',
2581
+ confirm: 'Confirm',
2582
+ close: 'Close permissions',
2583
+ retry: 'Retry',
2584
+ loadMore: 'Load more',
2585
+ loading: 'Loading permissions…',
2586
+ saving: 'Saving…',
2587
+ saved: 'Permissions saved',
2588
+ loadFailed: 'Could not load permissions',
2589
+ saveFailed: 'Could not save permissions',
2590
+ principalsFailed: 'Could not load users or groups',
2591
+ empty: 'No principals available to add',
2592
+ noMatches: 'No matching principals',
2593
+ restricted: 'This node does not inherit its parent’s permissions.',
2594
+ restore: 'Restore inheritance',
2595
+ stopTitle: 'Stop inheriting parent permissions?',
2596
+ stopDescription: 'This node will stop inheriting parent permissions and use the grants shown here.',
2597
+ restoreTitle: 'Restore parent permission inheritance?',
2598
+ restoreDescription: 'Direct grants on this node will be cleared and parent permissions inherited again.',
2599
+ restoreRemoved: 'These direct grants will no longer apply:',
2600
+ readOnly: 'Permission changes are disabled by the host.'
2601
+ };
2602
+ const acl_locales_zh_CN = {
2603
+ title: '管理节点权限',
2604
+ manage: '管理权限',
2605
+ read: '可阅读',
2606
+ write: '可编辑',
2607
+ manageLevel: '可管理',
2608
+ readDescription: '可查看节点内容,不能编辑或管理节点权限。',
2609
+ writeDescription: '可查看并编辑内容,不能管理节点权限。',
2610
+ manageDescription: '可管理该节点的权限配置。',
2611
+ effectivePermission: '当前 User 有效权限',
2612
+ addPrincipal: '添加 User 或 Group',
2613
+ search: '搜索用户或用户组',
2614
+ users: 'User',
2615
+ groups: 'Group',
2616
+ allUsers: '全部 User',
2617
+ administrator: '数据空间管理员',
2618
+ administratorDescription: '管理员始终保留隐式管理权限。',
2619
+ direct: '直接授权',
2620
+ inherited: '从父级目录继承权限',
2621
+ remove: '移除权限',
2622
+ add: '添加',
2623
+ cancel: '取消',
2624
+ confirm: '确定',
2625
+ close: '关闭权限管理',
2626
+ retry: '重试',
2627
+ loadMore: '加载更多',
2628
+ loading: '正在加载权限…',
2629
+ saving: '正在保存…',
2630
+ saved: '权限已保存',
2631
+ loadFailed: '权限加载失败',
2632
+ saveFailed: '权限保存失败',
2633
+ principalsFailed: '用户或用户组加载失败',
2634
+ empty: '暂无可添加成员',
2635
+ noMatches: '暂无匹配结果',
2636
+ restricted: '当前节点不继承父目录的权限。',
2637
+ restore: '恢复继承',
2638
+ stopTitle: '停止继承上级权限?',
2639
+ stopDescription: '确定后,此节点将不再继承父级目录的权限,并按当前设置的访问权限生效。',
2640
+ restoreTitle: '恢复继承上级权限?',
2641
+ restoreDescription: '将清空本节点的直接授权,重新跟随上级权限。',
2642
+ restoreRemoved: '以下直接授权将不再生效:',
2643
+ readOnly: '宿主已禁止修改权限。'
2644
+ };
2645
+ const aclLabels = (locale, labels)=>resolveLocale({
2646
+ 'en-US': acl_locales_en_US,
2647
+ 'zh-CN': acl_locales_zh_CN
2648
+ }, locale, labels);
2649
+ const levelLabel = (level, text)=>'manage' === level ? text.manageLevel : text[level];
2650
+ function AclMenu({ level, text, name, disabled, onChange, onRemove }) {
2651
+ const [open, setOpen] = useState(false);
2652
+ const root = useRef(null);
2653
+ const trigger = useRef(null);
2654
+ const menu = useRef(null);
2655
+ const id = useId();
2656
+ const close = ()=>{
2657
+ setOpen(false);
2658
+ trigger.current?.focus();
2659
+ };
2660
+ useEffect(()=>{
2661
+ if (!open) return;
2662
+ menu.current?.querySelector('button')?.focus();
2663
+ const outside = (event)=>{
2664
+ if (event.target instanceof Node && !root.current?.contains(event.target)) setOpen(false);
2665
+ };
2666
+ document.addEventListener('pointerdown', outside);
2667
+ return ()=>document.removeEventListener('pointerdown', outside);
2668
+ }, [
2669
+ open
2670
+ ]);
2671
+ return /*#__PURE__*/ jsxs("div", {
2672
+ className: "ov-file-browser-acl__menu-anchor",
2673
+ ref: root,
2674
+ onBlur: (event)=>{
2675
+ if (!event.currentTarget.contains(event.relatedTarget)) setOpen(false);
2676
+ },
2677
+ children: [
2678
+ /*#__PURE__*/ jsxs("button", {
2679
+ ref: trigger,
2680
+ type: "button",
2681
+ className: "ov-file-browser-acl__level",
2682
+ "aria-label": `${name}: ${levelLabel(level, text)}`,
2683
+ "aria-haspopup": "menu",
2684
+ "aria-expanded": open,
2685
+ "aria-controls": open ? id : void 0,
2686
+ disabled: disabled,
2687
+ onClick: ()=>setOpen(!open),
2688
+ children: [
2689
+ levelLabel(level, text),
2690
+ /*#__PURE__*/ jsx(chevron_downreact, {
2691
+ "aria-hidden": "true",
2692
+ width: 12,
2693
+ height: 12
2694
+ })
2695
+ ]
2696
+ }),
2697
+ open && !disabled ? /*#__PURE__*/ jsxs("div", {
2698
+ id: id,
2699
+ role: "menu",
2700
+ tabIndex: -1,
2701
+ "aria-label": name,
2702
+ ref: menu,
2703
+ className: "ov-file-browser-acl__menu",
2704
+ onKeyDown: (event)=>{
2705
+ if ('Escape' === event.key) {
2706
+ event.preventDefault();
2707
+ event.stopPropagation();
2708
+ close();
2709
+ }
2710
+ const buttons = [
2711
+ ...event.currentTarget.querySelectorAll('button')
2712
+ ];
2713
+ const index = buttons.indexOf(document.activeElement);
2714
+ const last = buttons.length - 1;
2715
+ const next = 'ArrowDown' === event.key ? (index + 1) % buttons.length : 'ArrowUp' === event.key ? (index + last) % buttons.length : 'Home' === event.key ? 0 : 'End' === event.key ? last : -1;
2716
+ if (next >= 0) {
2717
+ event.preventDefault();
2718
+ buttons[next]?.focus();
2719
+ }
2720
+ },
2721
+ children: [
2722
+ LEVELS.map((value)=>/*#__PURE__*/ jsxs("button", {
2723
+ type: "button",
2724
+ role: "menuitemradio",
2725
+ "aria-checked": level === value,
2726
+ onClick: ()=>{
2727
+ close();
2728
+ if (value !== level) onChange(value);
2729
+ },
2730
+ children: [
2731
+ /*#__PURE__*/ jsx("span", {
2732
+ className: "ov-file-browser-acl__check",
2733
+ children: level === value ? /*#__PURE__*/ jsx(checkreact, {
2734
+ width: 12,
2735
+ height: 12,
2736
+ "aria-hidden": "true"
2737
+ }) : null
2738
+ }),
2739
+ levelLabel(value, text)
2740
+ ]
2741
+ }, value)),
2742
+ onRemove ? /*#__PURE__*/ jsx("button", {
2743
+ type: "button",
2744
+ role: "menuitem",
2745
+ className: "ov-file-browser-acl__remove",
2746
+ onClick: ()=>{
2747
+ close();
2748
+ onRemove();
2749
+ },
2750
+ children: text.remove
2751
+ }) : null
2752
+ ]
2753
+ }) : null
2754
+ ]
2755
+ });
2756
+ }
2757
+ function AclAvatar({ principal }) {
2758
+ const [type, id] = principal.split(':');
2759
+ return /*#__PURE__*/ jsx("span", {
2760
+ "aria-hidden": "true",
2761
+ className: "ov-file-browser-acl__avatar",
2762
+ "data-group": 'group' === type || void 0,
2763
+ children: 'group' === type ? 'G' : id?.charAt(0).toUpperCase()
2764
+ });
2765
+ }
2766
+ function AclEntry({ entry, source, text, disabled, onChange, onRemove }) {
2767
+ const name = 'user:*' === entry.principal ? text.allUsers : entry.principal.slice(entry.principal.indexOf(':') + 1);
2768
+ return /*#__PURE__*/ jsxs("li", {
2769
+ className: "ov-file-browser-acl__entry",
2770
+ children: [
2771
+ /*#__PURE__*/ jsx(AclAvatar, {
2772
+ principal: entry.principal
2773
+ }),
2774
+ /*#__PURE__*/ jsxs("div", {
2775
+ className: "ov-file-browser-acl__identity",
2776
+ children: [
2777
+ /*#__PURE__*/ jsx("span", {
2778
+ className: "ov-file-browser-acl__name",
2779
+ children: name
2780
+ }),
2781
+ /*#__PURE__*/ jsx("span", {
2782
+ className: "ov-file-browser-acl__source",
2783
+ children: source
2784
+ })
2785
+ ]
2786
+ }),
2787
+ /*#__PURE__*/ jsx(AclMenu, {
2788
+ level: entry.level,
2789
+ text: text,
2790
+ name: entry.principal,
2791
+ disabled: disabled,
2792
+ onChange: onChange,
2793
+ onRemove: onRemove
2794
+ })
2795
+ ]
2796
+ });
2797
+ }
2798
+ function AclCandidate({ principal, level, text, onChange }) {
2799
+ const name = 'user:*' === principal ? text.allUsers : principal.slice(principal.indexOf(':') + 1);
2800
+ return /*#__PURE__*/ jsxs("div", {
2801
+ className: "ov-file-browser-acl__candidate",
2802
+ children: [
2803
+ /*#__PURE__*/ jsxs("label", {
2804
+ children: [
2805
+ /*#__PURE__*/ jsx("input", {
2806
+ type: "checkbox",
2807
+ checked: void 0 !== level,
2808
+ onChange: (event)=>onChange(event.target.checked ? 'read' : void 0)
2809
+ }),
2810
+ /*#__PURE__*/ jsx(AclAvatar, {
2811
+ principal: principal
2812
+ }),
2813
+ /*#__PURE__*/ jsx("span", {
2814
+ children: name
2815
+ })
2816
+ ]
2817
+ }),
2818
+ /*#__PURE__*/ jsx(AclMenu, {
2819
+ text: text,
2820
+ name: principal,
2821
+ level: level ?? 'read',
2822
+ onChange: onChange
2823
+ })
2824
+ ]
2825
+ });
2826
+ }
2827
+ const readAcl = async (client, uri, options)=>parseReport(await client.request({
2828
+ method: 'GET',
2829
+ path: OV_PATHS.acl,
2830
+ query: {
2831
+ uri
2832
+ },
2833
+ options
2834
+ }), uri);
2835
+ async function readPermission(client, uri, options) {
2836
+ const stat = await client.request({
2837
+ method: 'GET',
2838
+ path: OV_PATHS.fsStat,
2839
+ query: {
2840
+ uri
2841
+ },
2842
+ options
2843
+ });
2844
+ if (void 0 !== stat.permission) {
2845
+ if (!isLevel(stat.permission)) throw new TypeError('Unknown resource permission');
2846
+ return stat.permission;
2847
+ }
2848
+ try {
2849
+ await readAcl(client, uri, options);
2850
+ return 'manage';
2851
+ } catch (error) {
2852
+ if (isOvClientError(error) && 403 === error.statusCode) return;
2853
+ throw error;
2854
+ }
2855
+ }
2856
+ async function writeAcl(client, uri, options, change) {
2857
+ const value = await client.request({
2858
+ method: change ? 'PUT' : 'DELETE',
2859
+ path: OV_PATHS.acl,
2860
+ ...change ? {
2861
+ body: {
2862
+ uri,
2863
+ acl_mode: change.mode,
2864
+ entries: change.direct_entries
2865
+ }
2866
+ } : {
2867
+ query: {
2868
+ uri
2869
+ }
2870
+ },
2871
+ options
2872
+ });
2873
+ return parseReport(value, uri);
2874
+ }
2875
+ async function identityValue(value) {
2876
+ return ('function' == typeof value ? await value() : value)?.trim() ?? '';
2877
+ }
2878
+ async function listPrincipals(query, context, accountId) {
2879
+ const account = await identityValue(accountId);
2880
+ if (!account) throw new TypeError('Principal listing requires an account ID');
2881
+ if ('group' === query.type) {
2882
+ const groups = await context.client.request({
2883
+ method: 'GET',
2884
+ path: OV_PATH_BUILDERS.accountGroups(account),
2885
+ options: context.requestOptions
2886
+ });
2887
+ return {
2888
+ principals: groups.map((group)=>`group:${group.group_id}`),
2889
+ hasMore: false
2890
+ };
2891
+ }
2892
+ const result = await context.client.request({
2893
+ method: 'GET',
2894
+ path: OV_PATH_BUILDERS.accountUsers(account),
2895
+ query: {
2896
+ query: query.keyword,
2897
+ page: query.page,
2898
+ limit: 20,
2899
+ include_summary: true,
2900
+ include_credentials: false
2901
+ },
2902
+ options: context.requestOptions
2903
+ });
2904
+ return {
2905
+ principals: result.users.map((user)=>`user:${user.user_id}`),
2906
+ hasMore: 20 * query.page < result.total
2907
+ };
2908
+ }
2909
+ function useAclPrincipals({ open, type, keyword, context, accountId, options }) {
2910
+ const key = JSON.stringify([
2911
+ type,
2912
+ keyword,
2913
+ open
2914
+ ]);
2915
+ const [paging, setPaging] = useState({
2916
+ key,
2917
+ page: 1
2918
+ });
2919
+ const page = paging.key === key ? paging.page : 1;
2920
+ if (paging.key !== key) setPaging({
2921
+ key,
2922
+ page: 1
2923
+ });
2924
+ const [generation, setGeneration] = useState(0);
2925
+ const [state, setState] = useState({
2926
+ key: '',
2927
+ items: [],
2928
+ hasMore: false
2929
+ });
2930
+ const [loading, setLoading] = useState(false);
2931
+ const [error, setError] = useState();
2932
+ const load = options?.listPrincipals;
2933
+ useEffect(()=>{
2934
+ if (!open) return;
2935
+ const controller = new AbortController();
2936
+ setLoading(true);
2937
+ setError(void 0);
2938
+ const timer = setTimeout(()=>{
2939
+ const inherited = context.requestOptions.signal;
2940
+ const signal = inherited ? AbortSignal.any([
2941
+ controller.signal,
2942
+ inherited
2943
+ ]) : controller.signal;
2944
+ const request = {
2945
+ ...context,
2946
+ requestOptions: {
2947
+ ...context.requestOptions,
2948
+ signal
2949
+ }
2950
+ };
2951
+ const query = {
2952
+ type,
2953
+ keyword,
2954
+ page
2955
+ };
2956
+ (load ? load(query, request) : listPrincipals(query, request, accountId)).then((result)=>{
2957
+ if (signal.aborted) return;
2958
+ setState((previous)=>({
2959
+ key,
2960
+ hasMore: result.hasMore,
2961
+ items: [
2962
+ ...new Set([
2963
+ ...page > 1 && previous.key === key ? previous.items : [],
2964
+ ...result.principals
2965
+ ])
2966
+ ]
2967
+ }));
2968
+ setLoading(false);
2969
+ }, (cause)=>{
2970
+ if (!signal.aborted) {
2971
+ setError(toError(cause));
2972
+ setLoading(false);
2973
+ }
2974
+ });
2975
+ }, 200);
2976
+ return ()=>{
2977
+ clearTimeout(timer);
2978
+ controller.abort();
2979
+ };
2980
+ }, [
2981
+ open,
2982
+ type,
2983
+ keyword,
2984
+ key,
2985
+ page,
2986
+ generation,
2987
+ context,
2988
+ accountId,
2989
+ load
2990
+ ]);
2991
+ return {
2992
+ items: state.key === key ? state.items : [],
2993
+ loading,
2994
+ error,
2995
+ hasMore: state.key === key && state.hasMore,
2996
+ more: ()=>{
2997
+ if (!loading) setPaging({
2998
+ key,
2999
+ page: page + 1
3000
+ });
3001
+ },
3002
+ retry: ()=>setGeneration((value)=>value + 1)
3003
+ };
3004
+ }
3005
+ function AclAddPrincipal({ entries, text, disabled, context, accountId, options, onAdd }) {
3006
+ const [open, setOpen] = useState(false);
3007
+ const [keyword, setKeyword] = useState('');
3008
+ const [type, setType] = useState('user');
3009
+ const [pending, setPending] = useState({});
3010
+ const id = useId();
3011
+ const directory = useAclPrincipals({
3012
+ open: open && !disabled,
3013
+ type,
3014
+ keyword,
3015
+ context,
3016
+ accountId,
3017
+ options
3018
+ });
3019
+ const existing = new Set(entries.map((entry)=>entry.principal));
3020
+ const candidates = [
3021
+ ...new Set([
3022
+ ...'user' === type ? [
3023
+ 'user:*'
3024
+ ] : [],
3025
+ ...directory.items
3026
+ ])
3027
+ ].filter((principal)=>!existing.has(principal) && principal.startsWith(`${type}:`)).filter((principal)=>('user:*' === principal ? text.allUsers : principal.slice(principal.indexOf(':') + 1)).toLocaleLowerCase().includes(keyword.trim().toLocaleLowerCase()));
3028
+ const reset = ()=>{
3029
+ setOpen(false);
3030
+ setKeyword('');
3031
+ setPending({});
3032
+ };
3033
+ const selected = Object.entries(pending).filter(([principal])=>!existing.has(principal));
3034
+ return /*#__PURE__*/ jsxs("div", {
3035
+ className: "ov-file-browser-acl__add",
3036
+ children: [
3037
+ /*#__PURE__*/ jsx("label", {
3038
+ htmlFor: id,
3039
+ children: text.addPrincipal
3040
+ }),
3041
+ /*#__PURE__*/ jsx(input_Input, {
3042
+ prefix: /*#__PURE__*/ jsx(SearchIcon, {}),
3043
+ id: id,
3044
+ type: "search",
3045
+ role: "combobox",
3046
+ "aria-haspopup": "dialog",
3047
+ value: keyword,
3048
+ placeholder: text.search,
3049
+ disabled: disabled,
3050
+ "aria-expanded": open && !disabled,
3051
+ "aria-controls": open ? `${id}-options` : void 0,
3052
+ onFocus: ()=>setOpen(true),
3053
+ onChange: (event)=>{
3054
+ setKeyword(event.target.value);
3055
+ setOpen(true);
3056
+ }
3057
+ }),
3058
+ open && !disabled ? /*#__PURE__*/ jsxs("div", {
3059
+ id: `${id}-options`,
3060
+ className: "ov-file-browser-acl__picker",
3061
+ role: "dialog",
3062
+ "aria-label": text.addPrincipal,
3063
+ children: [
3064
+ /*#__PURE__*/ jsx(AclPrincipalTabs, {
3065
+ type: type,
3066
+ text: text,
3067
+ onChange: setType
3068
+ }),
3069
+ /*#__PURE__*/ jsxs("div", {
3070
+ className: "ov-file-browser-acl__candidates",
3071
+ "aria-busy": directory.loading,
3072
+ children: [
3073
+ candidates.map((principal)=>/*#__PURE__*/ jsx(AclCandidate, {
3074
+ principal: principal,
3075
+ level: pending[principal],
3076
+ text: text,
3077
+ onChange: (level)=>setPending((previous)=>{
3078
+ const next = {
3079
+ ...previous
3080
+ };
3081
+ if (level) next[principal] = level;
3082
+ else delete next[principal];
3083
+ return next;
3084
+ })
3085
+ }, principal)),
3086
+ directory.loading || candidates.length || directory.error ? null : /*#__PURE__*/ jsx("p", {
3087
+ className: "ov-file-browser-acl__hint",
3088
+ children: keyword.trim() ? text.noMatches : text.empty
3089
+ })
3090
+ ]
3091
+ }),
3092
+ directory.error ? /*#__PURE__*/ jsxs("div", {
3093
+ className: "ov-file-browser-acl__failure",
3094
+ role: "alert",
3095
+ children: [
3096
+ /*#__PURE__*/ jsx("p", {
3097
+ children: text.principalsFailed
3098
+ }),
3099
+ /*#__PURE__*/ jsx("p", {
3100
+ children: directory.error.message
3101
+ }),
3102
+ /*#__PURE__*/ jsx(button_Button, {
3103
+ size: "small",
3104
+ type: "default",
3105
+ htmlType: "button",
3106
+ onClick: directory.retry,
3107
+ children: text.retry
3108
+ })
3109
+ ]
3110
+ }) : null,
3111
+ directory.loading ? /*#__PURE__*/ jsx("p", {
3112
+ className: "ov-file-browser-acl__hint",
3113
+ children: text.loading
3114
+ }) : null,
3115
+ directory.hasMore ? /*#__PURE__*/ jsx(button_Button, {
3116
+ size: "small",
3117
+ type: "default",
3118
+ htmlType: "button",
3119
+ className: "ov-file-browser-acl__more",
3120
+ long: true,
3121
+ disabled: directory.loading,
3122
+ onClick: directory.more,
3123
+ children: text.loadMore
3124
+ }) : null,
3125
+ /*#__PURE__*/ jsxs("div", {
3126
+ className: "ov-file-browser-acl__buttons",
3127
+ children: [
3128
+ /*#__PURE__*/ jsx(button_Button, {
3129
+ size: "small",
3130
+ type: "default",
3131
+ htmlType: "button",
3132
+ onClick: reset,
3133
+ children: text.cancel
3134
+ }),
3135
+ /*#__PURE__*/ jsx(button_Button, {
3136
+ size: "small",
3137
+ type: "primary",
3138
+ htmlType: "button",
3139
+ className: "ov-file-browser-acl__primary",
3140
+ disabled: !selected.length,
3141
+ onClick: ()=>{
3142
+ onAdd(selected.map(([principal, level])=>({
3143
+ principal,
3144
+ level
3145
+ })));
3146
+ reset();
3147
+ },
3148
+ children: text.add
3149
+ })
3150
+ ]
3151
+ })
3152
+ ]
3153
+ }) : null
3154
+ ]
3155
+ });
3156
+ }
3157
+ function AclConfirmation({ model, text }) {
3158
+ const cancel = useRef(null);
3159
+ const titleId = useId();
3160
+ const descriptionId = useId();
3161
+ const restore = model.confirmation?.kind === 'restore';
3162
+ const removed = restore && model.report ? restoreDiff(model.report) : [];
3163
+ useEffect(()=>{
3164
+ cancel.current?.focus();
3165
+ }, []);
3166
+ return /*#__PURE__*/ jsxs("section", {
3167
+ className: "ov-file-browser-acl__confirmation",
3168
+ "aria-labelledby": titleId,
3169
+ "aria-describedby": descriptionId,
3170
+ children: [
3171
+ /*#__PURE__*/ jsx("h3", {
3172
+ id: titleId,
3173
+ children: restore ? text.restoreTitle : text.stopTitle
3174
+ }),
3175
+ /*#__PURE__*/ jsx("p", {
3176
+ id: descriptionId,
3177
+ children: restore ? text.restoreDescription : text.stopDescription
3178
+ }),
3179
+ removed.length ? /*#__PURE__*/ jsxs(Fragment, {
3180
+ children: [
3181
+ /*#__PURE__*/ jsx("p", {
3182
+ children: text.restoreRemoved
3183
+ }),
3184
+ /*#__PURE__*/ jsx("ul", {
3185
+ children: removed.map((entry)=>/*#__PURE__*/ jsx("li", {
3186
+ children: entry.principal
3187
+ }, entry.principal))
3188
+ })
3189
+ ]
3190
+ }) : null,
3191
+ /*#__PURE__*/ jsxs("div", {
3192
+ className: "ov-file-browser-acl__buttons",
3193
+ children: [
3194
+ /*#__PURE__*/ jsx(button_Button, {
3195
+ size: "small",
3196
+ type: "default",
3197
+ ref: cancel,
3198
+ htmlType: "button",
3199
+ disabled: 'saving' === model.phase,
3200
+ onClick: model.cancel,
3201
+ children: text.cancel
3202
+ }),
3203
+ /*#__PURE__*/ jsx(button_Button, {
3204
+ size: "small",
3205
+ type: "primary",
3206
+ htmlType: "button",
3207
+ className: "ov-file-browser-acl__primary",
3208
+ disabled: 'saving' === model.phase,
3209
+ onClick: model.confirm,
3210
+ children: 'saving' === model.phase ? text.saving : text.confirm
3211
+ })
3212
+ ]
3213
+ })
3214
+ ]
3215
+ });
3216
+ }
3217
+ function useAclReport({ uri, context, open, readOnly, onUpdated }) {
3218
+ const [report, setReport] = useState();
3219
+ const [confirmation, setConfirmation] = useState();
3220
+ const [error, setError] = useState();
3221
+ const [phase, setPhase] = useState('loading');
3222
+ const [saved, setSaved] = useState(false);
3223
+ const [generation, setGeneration] = useState(0);
3224
+ const pending = useRef(false);
3225
+ const lifetime = useRef(null);
3226
+ useEffect(()=>{
3227
+ if (!open) return;
3228
+ const controller = new AbortController();
3229
+ lifetime.current = controller;
3230
+ pending.current = false;
3231
+ setPhase('loading');
3232
+ setReport(void 0);
3233
+ setError(void 0);
3234
+ setConfirmation(void 0);
3235
+ setSaved(false);
3236
+ const { client, requestOptions } = context;
3237
+ const signal = requestOptions.signal ? AbortSignal.any([
3238
+ controller.signal,
3239
+ requestOptions.signal
3240
+ ]) : controller.signal;
3241
+ readAcl(client, uri, {
3242
+ ...requestOptions,
3243
+ signal
3244
+ }).then((value)=>{
3245
+ if (!signal.aborted) {
3246
+ setReport(value);
3247
+ setPhase('ready');
3248
+ }
3249
+ }, (cause)=>{
3250
+ if (!signal.aborted) {
3251
+ setError(toError(cause));
3252
+ setPhase('ready');
3253
+ }
3254
+ });
3255
+ return ()=>controller.abort();
3256
+ }, [
3257
+ uri,
3258
+ open,
3259
+ generation,
3260
+ context
3261
+ ]);
3262
+ async function save(change) {
3263
+ const controller = lifetime.current;
3264
+ if (pending.current || readOnly || !report || !controller || controller.signal.aborted) return;
3265
+ pending.current = true;
3266
+ setPhase('saving');
3267
+ setError(void 0);
3268
+ setSaved(false);
3269
+ const { client, requestOptions } = context;
3270
+ const signal = requestOptions.signal ? AbortSignal.any([
3271
+ controller.signal,
3272
+ requestOptions.signal
3273
+ ]) : controller.signal;
3274
+ try {
3275
+ const value = await writeAcl(client, uri, {
3276
+ ...requestOptions,
3277
+ signal
3278
+ }, change && {
3279
+ direct_entries: change.entries,
3280
+ mode: change.mode
3281
+ });
3282
+ if (signal.aborted) return;
3283
+ setReport(value);
3284
+ setConfirmation(void 0);
3285
+ setSaved(true);
3286
+ onUpdated(value);
3287
+ } catch (cause) {
3288
+ if (!signal.aborted) setError(toError(cause));
3289
+ } finally{
3290
+ if (!controller.signal.aborted && lifetime.current === controller) {
3291
+ pending.current = false;
3292
+ setPhase('ready');
3293
+ }
3294
+ }
3295
+ }
3296
+ function change(entries) {
3297
+ if (!report || pending.current || readOnly) return;
3298
+ const plan = planChange(report, normalizeEntries(entries));
3299
+ setSaved(false);
3300
+ setError(void 0);
3301
+ if (plan.confirmation) setConfirmation({
3302
+ kind: 'restrict',
3303
+ entries: plan.entries
3304
+ });
3305
+ else save(plan);
3306
+ }
3307
+ return {
3308
+ report,
3309
+ confirmation,
3310
+ error,
3311
+ phase,
3312
+ saved,
3313
+ retry: ()=>setGeneration((value)=>value + 1),
3314
+ change,
3315
+ restore: ()=>{
3316
+ if (!pending.current && !readOnly) setConfirmation({
3317
+ kind: 'restore'
3318
+ });
3319
+ },
3320
+ cancel: ()=>{
3321
+ if (!pending.current) {
3322
+ setConfirmation(void 0);
3323
+ setError(void 0);
3324
+ }
3325
+ },
3326
+ confirm: ()=>{
3327
+ if (confirmation?.kind === 'restore') save();
3328
+ else if (confirmation) save({
3329
+ entries: confirmation.entries,
3330
+ mode: 'restricted'
3331
+ });
3332
+ }
3333
+ };
3334
+ }
3335
+ function AclModal({ uri, open, text, locale, context, triggerRef, accountId, options, onClose, onUpdated }) {
3336
+ const id = useId();
3337
+ const heading = useRef(null);
3338
+ const model = useAclReport({
3339
+ uri,
3340
+ context,
3341
+ open,
3342
+ readOnly: Boolean(options?.readOnly),
3343
+ onUpdated
3344
+ });
3345
+ const disabled = Boolean(options?.readOnly) || 'ready' !== model.phase;
3346
+ const entries = model.report?.effective_entries ?? [];
3347
+ const direct = new Map(model.report?.direct_entries.map((entry)=>[
3348
+ entry.principal,
3349
+ entry.level
3350
+ ]));
3351
+ const wasConfirming = useRef(false);
3352
+ useEffect(()=>{
3353
+ if (wasConfirming.current && !model.confirmation) heading.current?.focus();
3354
+ wasConfirming.current = Boolean(model.confirmation);
3355
+ }, [
3356
+ model.confirmation
3357
+ ]);
3358
+ return /*#__PURE__*/ jsxs(Modal, {
3359
+ id: id,
3360
+ open: open,
3361
+ title: text.title,
3362
+ closeLabel: text.close,
3363
+ triggerRef: triggerRef,
3364
+ className: "ov-file-browser-acl",
3365
+ locale: locale,
3366
+ onClose: ()=>{
3367
+ if ('saving' === model.phase) return;
3368
+ if (model.confirmation) model.cancel();
3369
+ else onClose();
3370
+ },
3371
+ children: [
3372
+ /*#__PURE__*/ jsx("h2", {
3373
+ ref: heading,
3374
+ tabIndex: -1,
3375
+ className: "ov-file-browser-acl__title",
3376
+ children: text.title
3377
+ }),
3378
+ /*#__PURE__*/ jsx("p", {
3379
+ className: "ov-file-browser-acl__uri",
3380
+ children: uri
3381
+ }),
3382
+ model.error ? /*#__PURE__*/ jsxs("div", {
3383
+ className: "ov-file-browser-acl__failure",
3384
+ role: "alert",
3385
+ children: [
3386
+ /*#__PURE__*/ jsx("p", {
3387
+ children: model.report ? text.saveFailed : text.loadFailed
3388
+ }),
3389
+ /*#__PURE__*/ jsx("p", {
3390
+ children: model.error.message
3391
+ }),
3392
+ model.report ? null : /*#__PURE__*/ jsx(button_Button, {
3393
+ size: "small",
3394
+ type: "default",
3395
+ htmlType: "button",
3396
+ onClick: model.retry,
3397
+ children: text.retry
3398
+ })
3399
+ ]
3400
+ }) : null,
3401
+ /*#__PURE__*/ jsx("div", {
3402
+ role: "status",
3403
+ className: "ov-file-browser-acl__status",
3404
+ children: 'saving' === model.phase ? text.saving : 'loading' === model.phase ? text.loading : model.saved ? text.saved : ''
3405
+ }),
3406
+ model.confirmation ? /*#__PURE__*/ jsx(AclConfirmation, {
3407
+ model: model,
3408
+ text: text
3409
+ }) : model.report ? /*#__PURE__*/ jsxs(Fragment, {
3410
+ children: [
3411
+ 'restricted' === model.report.acl_mode ? /*#__PURE__*/ jsxs("div", {
3412
+ className: "ov-file-browser-acl__restricted",
3413
+ children: [
3414
+ /*#__PURE__*/ jsx("p", {
3415
+ children: text.restricted
3416
+ }),
3417
+ /*#__PURE__*/ jsx(button_Button, {
3418
+ size: "small",
3419
+ type: "default",
3420
+ htmlType: "button",
3421
+ disabled: disabled,
3422
+ onClick: model.restore,
3423
+ children: text.restore
3424
+ })
3425
+ ]
3426
+ }) : null,
3427
+ options?.readOnly ? /*#__PURE__*/ jsx("p", {
3428
+ className: "ov-file-browser-acl__hint",
3429
+ children: text.readOnly
3430
+ }) : null,
3431
+ /*#__PURE__*/ jsx(AclAddPrincipal, {
3432
+ entries: entries,
3433
+ text: text,
3434
+ disabled: disabled,
3435
+ context: context,
3436
+ accountId: accountId,
3437
+ options: options,
3438
+ onAdd: (added)=>model.change([
3439
+ ...entries,
3440
+ ...added
3441
+ ])
3442
+ }),
3443
+ /*#__PURE__*/ jsxs("ul", {
3444
+ className: "ov-file-browser-acl__entries",
3445
+ "aria-busy": 'saving' === model.phase,
3446
+ children: [
3447
+ /*#__PURE__*/ jsxs("li", {
3448
+ className: "ov-file-browser-acl__entry",
3449
+ children: [
3450
+ /*#__PURE__*/ jsx("span", {
3451
+ className: "ov-file-browser-acl__avatar",
3452
+ "aria-hidden": "true",
3453
+ children: "A"
3454
+ }),
3455
+ /*#__PURE__*/ jsxs("div", {
3456
+ className: "ov-file-browser-acl__identity",
3457
+ children: [
3458
+ /*#__PURE__*/ jsx("span", {
3459
+ className: "ov-file-browser-acl__name",
3460
+ children: text.administrator
3461
+ }),
3462
+ /*#__PURE__*/ jsx("span", {
3463
+ className: "ov-file-browser-acl__source",
3464
+ children: text.administratorDescription
3465
+ })
3466
+ ]
3467
+ }),
3468
+ /*#__PURE__*/ jsx("span", {
3469
+ className: "ov-file-browser-acl__fixed",
3470
+ children: levelLabel('manage', text)
3471
+ })
3472
+ ]
3473
+ }),
3474
+ entries.map((entry)=>/*#__PURE__*/ jsx(AclEntry, {
3475
+ entry: entry,
3476
+ text: text,
3477
+ disabled: disabled,
3478
+ source: direct.get(entry.principal) === entry.level ? text.direct : text.inherited,
3479
+ onChange: (level)=>model.change(entries.map((item)=>item.principal === entry.principal ? {
3480
+ ...item,
3481
+ level
3482
+ } : item)),
3483
+ onRemove: ()=>model.change(entries.filter((item)=>item.principal !== entry.principal))
3484
+ }, entry.principal))
3485
+ ]
3486
+ })
3487
+ ]
3488
+ }) : null
3489
+ ]
3490
+ });
3491
+ }
3492
+ function AclTag({ level, text, onManage }) {
3493
+ const [open, setOpen] = useState(false);
3494
+ const id = useId();
3495
+ const description = 'read' === level ? text.readDescription : 'write' === level ? text.writeDescription : text.manageDescription;
3496
+ return /*#__PURE__*/ jsxs("div", {
3497
+ className: "ov-file-browser-acl__tag-anchor",
3498
+ onBlur: (event)=>{
3499
+ if (!event.currentTarget.contains(event.relatedTarget)) setOpen(false);
3500
+ },
3501
+ children: [
3502
+ /*#__PURE__*/ jsx("button", {
3503
+ onKeyDown: (event)=>{
3504
+ if ('Escape' === event.key) {
3505
+ event.preventDefault();
3506
+ setOpen(false);
3507
+ }
3508
+ },
3509
+ type: "button",
3510
+ className: "ov-file-browser-acl__tag",
3511
+ "aria-expanded": open,
3512
+ "aria-controls": open ? id : void 0,
3513
+ onClick: ()=>setOpen(!open),
3514
+ children: levelLabel(level, text)
3515
+ }),
3516
+ open ? /*#__PURE__*/ jsxs("div", {
3517
+ id: id,
3518
+ className: "ov-file-browser-acl__tag-detail",
3519
+ children: [
3520
+ /*#__PURE__*/ jsx("p", {
3521
+ children: description
3522
+ }),
3523
+ /*#__PURE__*/ jsxs("p", {
3524
+ className: "ov-file-browser-acl__source",
3525
+ children: [
3526
+ text.effectivePermission,
3527
+ ": ",
3528
+ levelLabel(level, text)
3529
+ ]
3530
+ }),
3531
+ 'manage' === level ? /*#__PURE__*/ jsx(button_Button, {
3532
+ size: "small",
3533
+ type: "default",
3534
+ htmlType: "button",
3535
+ onClick: ()=>{
3536
+ setOpen(false);
3537
+ onManage();
3538
+ },
3539
+ children: text.manage
3540
+ }) : null
3541
+ ]
3542
+ }) : null
3543
+ ]
3544
+ });
3545
+ }
3546
+ function useFileBrowserAcl({ uri, acl, locale, labels, requestOptions, refresh }) {
3547
+ const client = useOvClient();
3548
+ const identity = useOvIdentity(requestOptions);
3549
+ const inheritedSignal = useOvSignal();
3550
+ const signal = requestOptions?.signal ?? inheritedSignal;
3551
+ const options = 'object' == typeof acl ? acl : void 0;
3552
+ const key = scopeKeyOf(client, requestOptions, uri ?? void 0);
3553
+ const [permission, setPermission] = useState();
3554
+ const [opened, setOpened] = useState();
3555
+ const [generation, setGeneration] = useState(0);
3556
+ const triggerRef = useRef(null);
3557
+ const { baseURL, apiKey, accountId, userId, resourceId } = requestOptions ?? {};
3558
+ const context = useMemo(()=>client ? {
3559
+ client,
3560
+ requestOptions: {
3561
+ baseURL,
3562
+ apiKey,
3563
+ accountId,
3564
+ userId,
3565
+ resourceId,
3566
+ signal
3567
+ }
3568
+ } : void 0, [
3569
+ client,
3570
+ baseURL,
3571
+ apiKey,
3572
+ accountId,
3573
+ userId,
3574
+ resourceId,
3575
+ signal
3576
+ ]);
3577
+ const enabled = Boolean(acl) && Boolean(client) && isAclTarget(uri);
3578
+ const resolver = options?.resolvePermission;
3579
+ useEffect(()=>{
3580
+ if (!enabled || !uri || !context) return;
3581
+ const controller = new AbortController();
3582
+ const composed = signal ? AbortSignal.any([
3583
+ controller.signal,
3584
+ signal
3585
+ ]) : controller.signal;
3586
+ const request = {
3587
+ ...context,
3588
+ requestOptions: {
3589
+ ...context.requestOptions,
3590
+ signal: composed
3591
+ }
3592
+ };
3593
+ const load = resolver ? resolver(uri, request) : readPermission(request.client, uri, request.requestOptions);
3594
+ load.then((level)=>{
3595
+ if (!composed.aborted) setPermission({
3596
+ key,
3597
+ level
3598
+ });
3599
+ }, ()=>{
3600
+ if (!composed.aborted) setPermission({
3601
+ key,
3602
+ error: true
3603
+ });
3604
+ });
3605
+ return ()=>controller.abort();
3606
+ }, [
3607
+ enabled,
3608
+ key,
3609
+ uri,
3610
+ resolver,
3611
+ signal,
3612
+ generation,
3613
+ context
3614
+ ]);
3615
+ const level = enabled && permission?.key === key ? permission.level : void 0;
3616
+ const text = aclLabels(locale, labels);
3617
+ return {
3618
+ titleExtra: enabled && permission?.key === key && permission.error ? /*#__PURE__*/ jsxs("span", {
3619
+ className: "ov-file-browser-acl__lookup-error",
3620
+ role: "status",
3621
+ children: [
3622
+ text.loadFailed,
3623
+ /*#__PURE__*/ jsx(button_Button, {
3624
+ size: "small",
3625
+ type: "default",
3626
+ htmlType: "button",
3627
+ onClick: ()=>setGeneration((value)=>value + 1),
3628
+ children: text.retry
3629
+ })
3630
+ ]
3631
+ }) : level ? /*#__PURE__*/ jsx(AclTag, {
3632
+ level: level,
3633
+ text: text,
3634
+ onManage: ()=>setOpened(key)
3635
+ }) : null,
3636
+ actions: 'manage' === level ? /*#__PURE__*/ jsx(button_Button, {
3637
+ size: "small",
3638
+ type: "default",
3639
+ ref: triggerRef,
3640
+ htmlType: "button",
3641
+ className: "ov-file-browser-acl__manage",
3642
+ onClick: ()=>setOpened(key),
3643
+ children: text.manage
3644
+ }) : null,
3645
+ drawer: enabled && uri && context && 'manage' === level ? /*#__PURE__*/ jsx(AclModal, {
3646
+ uri: uri,
3647
+ open: opened === key,
3648
+ text: text,
3649
+ locale: locale,
3650
+ triggerRef: triggerRef,
3651
+ context: context,
3652
+ accountId: identity.accountId,
3653
+ options: options,
3654
+ onClose: ()=>setOpened(void 0),
3655
+ onUpdated: (report)=>{
3656
+ setGeneration((value)=>value + 1);
3657
+ refresh();
3658
+ try {
3659
+ options?.onChange?.(report);
3660
+ } catch {
3661
+ return;
3662
+ }
3663
+ }
3664
+ }, key) : null
3665
+ };
3666
+ }
3667
+ function FileBrowser({ uri, defaultUri, rootUri = 'viking://', defaultExpandedUri, locale: localeProp, agentConnect, dashboardKinds, renderDashboard, acl, onUriChange, onImportResources, renderImportResources, labels, requestOptions, className, style, ...rest }) {
3668
+ const locale = useLocale(localeProp);
3669
+ const [searchOpen, setSearchOpen] = useState(false);
3670
+ const [internalUri, setInternalUri] = useState(()=>defaultUri ?? null);
3671
+ const [preferredTreeWidth, setPreferredTreeWidth] = useState(352);
3672
+ const isControlled = void 0 !== uri;
3673
+ const currentUri = isControlled ? uri : internalUri;
3674
+ const text = resolveLabels(labels, locale);
3675
+ const { ref: layoutRef, width: layoutWidth } = useContainerWidth();
3676
+ const treeMaxWidth = resolveTreeMaxWidth(layoutWidth);
3677
+ const treeWidth = Math.min(preferredTreeWidth, treeMaxWidth);
3678
+ const changeUri = useCallback((next)=>{
3679
+ if (!isControlled) setInternalUri(next);
3680
+ onUriChange?.(next);
3681
+ }, [
3682
+ isControlled,
3683
+ onUriChange
3684
+ ]);
3685
+ const summaryLabels = useMemo(()=>({
3686
+ l0: text.l0Summary,
3687
+ l1: text.l1Overview,
3688
+ failure: text.summaryFailed
2275
3689
  }), [
2276
3690
  text.l0Summary,
2277
3691
  text.l1Overview,
@@ -2290,86 +3704,109 @@ function FileBrowser({ uri, defaultUri, rootUri = 'viking://', defaultExpandedUr
2290
3704
  controlled: isControlled,
2291
3705
  summaryLabels,
2292
3706
  formatCharacterCount: text.formatCharacterCount,
3707
+ accessLabels: {
3708
+ accessDenied: text.accessDenied,
3709
+ traverseOnly: text.traverseOnly
3710
+ },
2293
3711
  defaultExpandedUri,
2294
3712
  currentUri,
2295
3713
  requestOptions,
2296
3714
  onUriChange: changeUri
2297
3715
  });
2298
- return /*#__PURE__*/ jsx(LocaleProvider, {
3716
+ const permissions = useFileBrowserAcl({
3717
+ uri: browser.currentUri,
3718
+ acl,
3719
+ locale,
3720
+ labels: labels?.acl,
3721
+ requestOptions,
3722
+ refresh: browser.actions.refresh
3723
+ });
3724
+ return /*#__PURE__*/ jsxs(LocaleProvider, {
2299
3725
  locale: locale,
2300
- children: /*#__PURE__*/ jsxs("div", {
2301
- ...rest,
2302
- ref: layoutRef,
2303
- className: [
2304
- 'ov-file-browser',
2305
- className
2306
- ].filter(Boolean).join(' '),
2307
- style: {
2308
- ...style,
2309
- '--ov-file-browser-tree-width': `${treeWidth}px`
2310
- },
2311
- children: [
2312
- /*#__PURE__*/ jsx("div", {
2313
- className: "ov-file-browser__tree",
2314
- children: /*#__PURE__*/ jsx(FileBrowserTreePane, {
2315
- entries: {
2316
- agentConnect,
2317
- onImportResources,
2318
- renderImportResources,
2319
- labels: labels?.entries
2320
- },
2321
- searchId: searchId,
2322
- browser: browser,
2323
- treeLabels: text.tree,
3726
+ children: [
3727
+ /*#__PURE__*/ jsxs("div", {
3728
+ ...rest,
3729
+ ref: layoutRef,
3730
+ className: [
3731
+ 'ov-file-browser',
3732
+ className
3733
+ ].filter(Boolean).join(' '),
3734
+ style: {
3735
+ ...style,
3736
+ '--ov-file-browser-tree-width': `${treeWidth}px`
3737
+ },
3738
+ children: [
3739
+ /*#__PURE__*/ jsx("div", {
3740
+ className: "ov-file-browser__tree",
3741
+ children: /*#__PURE__*/ jsx(FileBrowserTreePane, {
3742
+ entries: {
3743
+ agentConnect,
3744
+ onImportResources,
3745
+ renderImportResources,
3746
+ labels: labels?.entries
3747
+ },
3748
+ onSearch: ()=>setSearchOpen(true),
3749
+ browser: browser,
3750
+ treeLabels: text.tree,
3751
+ locale: locale,
3752
+ dashboardLabels: labels?.dashboard,
3753
+ searchPlaceholder: text.searchPlaceholder,
3754
+ refreshLabel: text.refresh,
3755
+ retryLabel: text.retry
3756
+ })
3757
+ }),
3758
+ /*#__PURE__*/ jsx(ResizableHandle, {
3759
+ label: text.resizeTree,
3760
+ min: 200,
3761
+ max: treeMaxWidth,
3762
+ value: treeWidth,
3763
+ onResize: setPreferredTreeWidth,
3764
+ className: "ov-file-browser__resizer"
3765
+ }),
3766
+ browser.previewTabs && browser.previewTabs.length > 0 ? /*#__PURE__*/ jsx(FileBrowserDetail, {
3767
+ tabs: browser.previewTabs,
3768
+ activeKey: browser.previewKey,
2324
3769
  locale: locale,
2325
- dashboardLabels: labels?.dashboard,
2326
- searchPlaceholder: text.searchPlaceholder,
2327
- refreshLabel: text.refresh,
2328
- searchLoading: text.searchLoading,
2329
- searchEmpty: text.searchEmpty,
2330
- searchErrorLabel: text.searchFailed,
2331
- retryLabel: text.retry
2332
- })
2333
- }),
2334
- /*#__PURE__*/ jsx(ResizableHandle, {
2335
- label: text.resizeTree,
2336
- min: 200,
2337
- max: treeMaxWidth,
2338
- value: treeWidth,
2339
- onResize: setPreferredTreeWidth,
2340
- className: "ov-file-browser__resizer"
2341
- }),
2342
- browser.previewTabs && browser.previewTabs.length > 0 ? /*#__PURE__*/ jsx(FileBrowserDetail, {
2343
- tabs: browser.previewTabs,
2344
- activeKey: browser.previewKey,
2345
- locale: locale,
2346
- labels: previewLabels,
2347
- footer: browser.previewFooter,
2348
- viewLabels: {
2349
- preview: text.previewMode,
2350
- source: text.sourceMode,
2351
- copyPath: text.copyPath,
2352
- copyContent: text.copyContent,
2353
- download: text.download,
2354
- viewSwitch: text.viewSwitch
2355
- },
2356
- onActiveKeyChange: browser.actions.setPreviewKey
2357
- }) : browser.currentUri && browser.previewError ? /*#__PURE__*/ jsx("div", {
2358
- className: "ov-file-browser__detail",
2359
- children: /*#__PURE__*/ jsx(FileBrowserFailure, {
2360
- message: text.error,
2361
- detail: browser.previewError.message,
2362
- retryLabel: text.retry,
2363
- onRetry: ()=>void browser.actions.refresh()
2364
- })
2365
- }) : /*#__PURE__*/ jsx("div", {
2366
- className: "ov-file-browser__detail",
2367
- children: /*#__PURE__*/ jsx(FileBrowserEmpty, {
2368
- label: text.empty
3770
+ labels: previewLabels,
3771
+ footer: browser.previewFooter,
3772
+ titleExtra: permissions.titleExtra,
3773
+ extraActions: permissions.actions,
3774
+ viewLabels: {
3775
+ preview: text.previewMode,
3776
+ source: text.sourceMode,
3777
+ copyPath: text.copyPath,
3778
+ copyContent: text.copyContent,
3779
+ download: text.download,
3780
+ viewSwitch: text.viewSwitch
3781
+ },
3782
+ onActiveKeyChange: browser.actions.setPreviewKey
3783
+ }) : browser.currentUri && browser.previewError ? /*#__PURE__*/ jsx("div", {
3784
+ className: "ov-file-browser__detail",
3785
+ children: /*#__PURE__*/ jsx(FileBrowserFailure, {
3786
+ message: text.error,
3787
+ detail: browser.previewError.message,
3788
+ retryLabel: text.retry,
3789
+ onRetry: ()=>void browser.actions.refresh()
3790
+ })
3791
+ }) : /*#__PURE__*/ jsx("div", {
3792
+ className: "ov-file-browser__detail",
3793
+ children: /*#__PURE__*/ jsx(FileBrowserEmpty, {
3794
+ label: text.empty
3795
+ })
2369
3796
  })
2370
- })
2371
- ]
2372
- })
3797
+ ]
3798
+ }),
3799
+ permissions.drawer,
3800
+ /*#__PURE__*/ jsx(FileBrowserSearch, {
3801
+ open: searchOpen,
3802
+ locale: locale,
3803
+ rootUri: rootUri,
3804
+ triggerRef: layoutRef,
3805
+ text: text,
3806
+ requestOptions: requestOptions,
3807
+ onClose: ()=>setSearchOpen(false)
3808
+ })
3809
+ ]
2373
3810
  });
2374
3811
  }
2375
3812
  export { FileBrowser };