@openviking/react 0.1.5 → 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.
- package/README.md +15 -11
- package/dist/2~0.js +6 -5
- package/dist/37~0.js +27 -0
- package/dist/3c~0.js +24 -12
- package/dist/3~0.js +19 -9
- package/dist/ae~0.js +8 -1
- package/dist/ark-message-list-view~0.js +9 -4
- package/dist/a~0.js +13 -7
- package/dist/c7r~0.js +50 -0
- package/dist/ck~0.js +7 -0
- package/dist/client/paths.d.ts +10 -0
- package/dist/client/provider.d.ts +6 -1
- package/dist/components/agent-connect/style.css +268 -37
- package/dist/components/directory-tour/style.css +265 -21
- package/dist/components/directory-tour.js +1 -1
- package/dist/components/experience-dashboard/dashboard-tabs.d.ts +11 -0
- package/dist/components/experience-dashboard/experience-dashboard.d.ts +2 -0
- package/dist/components/experience-dashboard/experience-impact.d.ts +11 -0
- package/dist/components/experience-dashboard/experience-list.d.ts +9 -0
- package/dist/components/experience-dashboard/experience-pagination.d.ts +8 -0
- package/dist/components/experience-dashboard/experience-tests.d.ts +6 -0
- package/dist/components/experience-dashboard/experience-trajectories.d.ts +6 -0
- package/dist/components/experience-dashboard/index.d.ts +2 -0
- package/dist/components/experience-dashboard/labels.d.ts +2 -0
- package/dist/components/experience-dashboard/style.css +914 -0
- package/dist/components/experience-dashboard/style.js +0 -0
- package/dist/components/experience-dashboard/types.d.ts +106 -0
- package/dist/components/experience-dashboard.js +1 -0
- package/dist/components/file-browser-entries/style.css +463 -86
- package/dist/components/file-preview/style.css +267 -78
- package/dist/components/file-tree/file-tree-toolbar.d.ts +3 -3
- package/dist/components/file-tree/locales/en-US.d.ts +3 -0
- package/dist/components/file-tree/locales/zh-CN.d.ts +3 -0
- package/dist/components/file-tree/style.css +372 -88
- package/dist/components/file-tree/types.d.ts +13 -4
- package/dist/components/scope-switcher/style.css +412 -39
- package/dist/components/style.css +9326 -4553
- package/dist/components/tui/style.css +385 -67
- package/dist/components/viking-bot/interaction/style.css +143 -22
- package/dist/components/viking-bot/style.css +1075 -278
- package/dist/components/wiki-dashboard/index.d.ts +2 -0
- package/dist/components/wiki-dashboard/labels.d.ts +2 -0
- package/dist/components/wiki-dashboard/style.css +956 -0
- package/dist/components/wiki-dashboard/style.js +0 -0
- package/dist/components/wiki-dashboard/types.d.ts +92 -0
- package/dist/components/wiki-dashboard/wiki-action-button.d.ts +9 -0
- package/dist/components/wiki-dashboard/wiki-conflict-card.d.ts +15 -0
- package/dist/components/wiki-dashboard/wiki-dashboard.d.ts +2 -0
- package/dist/components/wiki-dashboard/wiki-file-icon.d.ts +3 -0
- package/dist/components/wiki-dashboard/wiki-severity-filter.d.ts +8 -0
- package/dist/components/wiki-dashboard.js +1 -0
- package/dist/c~0.js +16 -8
- package/dist/ep~0.js +345 -0
- package/dist/e~0.js +33 -328
- package/dist/fj~0.js +507 -0
- package/dist/fl~0.js +709 -0
- package/dist/f~0.js +12 -27
- package/dist/icons/index.d.ts +28 -0
- package/dist/icons.js +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -1
- package/dist/k~0.js +277 -1
- package/dist/lk~0.js +56 -14
- package/dist/l~0.js +2786 -446
- package/dist/modules/console/index.d.ts +1 -2
- package/dist/modules/console/style.css +1787 -346
- package/dist/modules/console.js +1 -1
- package/dist/modules/file-browser/acl/acl-add-principal.d.ts +12 -0
- package/dist/modules/file-browser/acl/acl-candidate.d.ts +8 -0
- package/dist/modules/file-browser/acl/acl-client.d.ts +13 -0
- package/dist/modules/file-browser/acl/acl-confirmation.d.ts +6 -0
- package/dist/modules/file-browser/acl/acl-entry.d.ts +13 -0
- package/dist/modules/file-browser/acl/acl-labels.d.ts +47 -0
- package/dist/modules/file-browser/acl/acl-menu.d.ts +10 -0
- package/dist/modules/file-browser/acl/acl-modal.d.ts +17 -0
- package/dist/modules/file-browser/acl/acl-model.d.ts +17 -0
- package/dist/modules/file-browser/acl/acl-principal-tabs.d.ts +6 -0
- package/dist/modules/file-browser/acl/acl-tag.d.ts +7 -0
- package/dist/modules/file-browser/acl/locales/en-US.d.ts +44 -0
- package/dist/modules/file-browser/acl/locales/zh-CN.d.ts +44 -0
- package/dist/modules/file-browser/acl/use-acl-principals.d.ts +17 -0
- package/dist/modules/file-browser/acl/use-acl-report.d.ts +27 -0
- package/dist/modules/file-browser/acl-types.d.ts +86 -0
- package/dist/modules/file-browser/dashboard-data.d.ts +9 -0
- package/dist/modules/file-browser/dashboard-indicator.d.ts +8 -0
- package/dist/modules/file-browser/dashboard-labels.d.ts +9 -0
- package/dist/modules/file-browser/dashboard-model.d.ts +17 -0
- package/dist/modules/file-browser/dashboard-resource.d.ts +12 -0
- package/dist/modules/file-browser/experience-dashboard-pane.d.ts +8 -0
- package/dist/modules/file-browser/experience-data.d.ts +31 -0
- package/dist/modules/file-browser/file-browser-detail.d.ts +4 -1
- package/dist/modules/file-browser/file-browser-labels.d.ts +1 -1
- package/dist/modules/file-browser/file-browser-search.d.ts +20 -0
- package/dist/modules/file-browser/file-browser-toolbar.d.ts +3 -10
- package/dist/modules/file-browser/file-browser-tree-pane.d.ts +4 -7
- package/dist/modules/file-browser/file-browser.d.ts +1 -1
- package/dist/modules/file-browser/fs-directories.d.ts +9 -0
- package/dist/modules/file-browser/fs-lines.d.ts +36 -0
- package/dist/modules/file-browser/fs-memory-fields.d.ts +65 -0
- package/dist/modules/file-browser/fs-model.d.ts +15 -3
- package/dist/modules/file-browser/fs-read-file.d.ts +14 -0
- package/dist/modules/file-browser/index.d.ts +3 -2
- package/dist/modules/file-browser/locales/en-US.d.ts +5 -0
- package/dist/modules/file-browser/locales/zh-CN.d.ts +5 -0
- package/dist/modules/file-browser/style.css +4033 -483
- package/dist/modules/file-browser/types.d.ts +82 -20
- package/dist/modules/file-browser/use-browser-dashboards.d.ts +31 -0
- package/dist/modules/file-browser/use-dashboard-query.d.ts +9 -0
- package/dist/modules/file-browser/use-experience-dashboard.d.ts +27 -0
- package/dist/modules/file-browser/use-file-browser-acl.d.ts +15 -0
- package/dist/modules/file-browser/use-file-browser.d.ts +6 -51
- package/dist/modules/file-browser/use-fs-tree.d.ts +3 -0
- package/dist/modules/file-browser/use-wiki-dashboard.d.ts +17 -0
- package/dist/modules/file-browser/wiki-conflicts.d.ts +12 -0
- package/dist/modules/file-browser/wiki-dashboard-pane.d.ts +14 -0
- package/dist/modules/file-browser.js +1 -1
- package/dist/modules/scope-cascade/controller.d.ts +2 -2
- package/dist/modules/scope-cascade/index.d.ts +1 -2
- package/dist/modules/scope-cascade/scope-cascade.d.ts +1 -1
- package/dist/modules/scope-cascade/style.css +581 -39
- package/dist/modules/scope-cascade/types.d.ts +8 -3
- package/dist/modules/scope-cascade/use-scope-cascade.d.ts +2 -2
- package/dist/modules/scope-cascade.js +1 -1
- package/dist/modules/style.css +13589 -7966
- package/dist/n~0.js +73 -165
- package/dist/rt~0.js +255 -0
- package/dist/r~0.js +18 -39
- package/dist/shared/components/button/button.d.ts +20 -0
- package/dist/shared/components/input/input.d.ts +17 -0
- package/dist/shared/components/modal/locales/en-US.d.ts +4 -0
- package/dist/shared/components/modal/locales/zh-CN.d.ts +4 -0
- package/dist/shared/components/modal/modal.d.ts +2 -0
- package/dist/shared/components/modal/types.d.ts +18 -0
- package/dist/shared/components/overlay/lock-body-scroll.d.ts +1 -0
- package/dist/shared/components/popover/popover.d.ts +1 -1
- package/dist/shared/components/popover/types.d.ts +7 -1
- package/dist/shared/dashboard/dashboard-state.d.ts +12 -0
- package/dist/static/image/dashboard-background.png +0 -0
- package/dist/style.css +16088 -5602
- package/dist/s~0.js +16 -0
- package/dist/u~0.js +15 -6
- package/dist/viking-bot-composer~0.js +30 -12
- package/dist/viking-bot-interaction~0.js +2 -1
- package/package.json +14 -3
- package/dist/o~0.js +0 -106
package/dist/l~0.js
CHANGED
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
|
|
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 {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import { OV_PATHS } from "./ck~0.js";
|
|
6
|
+
import { button_Button } from "./e~0.js";
|
|
7
|
+
import { FileTree as file_tree_FileTree, FileTreeToolbar } from "./n~0.js";
|
|
8
|
+
import { getFileTypeColor, getFileTypeIcon, WikiBookIcon, ExperienceBoardIcon, WikiConflictBoardIcon, FolderIcon, WikiBoardIcon, resolveFileTypeIconKey } from "./k~0.js";
|
|
9
|
+
import { FileBrowserEntries, es2015, lockBodyScroll } from "./2~0.js";
|
|
10
|
+
import { removereact } from "./4~0.js";
|
|
10
11
|
import { useLocale, LocaleProvider } from "./28~0.js";
|
|
11
|
-
import {
|
|
12
|
+
import { resolveLocale } from "./nx~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";
|
|
18
|
+
import { WikiDashboard } from "./fj~0.js";
|
|
19
|
+
import { chevron_downreact } from "./s~0.js";
|
|
20
|
+
import { checkreact } from "./0~0.js";
|
|
12
21
|
function clampResizableWidth(width, min, max) {
|
|
13
22
|
const rounded = Math.round(width);
|
|
14
23
|
const lower = Math.max(min, rounded);
|
|
@@ -120,11 +129,11 @@ function DownloadIcon() {
|
|
|
120
129
|
]
|
|
121
130
|
});
|
|
122
131
|
}
|
|
123
|
-
function FileBrowserDetail({ tabs, activeKey, locale, labels, footer, viewLabels, onActiveKeyChange }) {
|
|
132
|
+
function FileBrowserDetail({ tabs, activeKey, locale, labels, footer, titleExtra, extraActions, viewLabels, onActiveKeyChange }) {
|
|
124
133
|
return /*#__PURE__*/ jsx("div", {
|
|
125
134
|
className: "ov-file-browser__detail",
|
|
126
135
|
children: /*#__PURE__*/ jsx(file_preview_FilePreview, {
|
|
127
|
-
tabs: decorateTabs(tabs, viewLabels, footer),
|
|
136
|
+
tabs: decorateTabs(tabs, viewLabels, footer, titleExtra, extraActions),
|
|
128
137
|
activeKey: activeKey ?? void 0,
|
|
129
138
|
locale: locale,
|
|
130
139
|
labels: labels,
|
|
@@ -132,7 +141,7 @@ function FileBrowserDetail({ tabs, activeKey, locale, labels, footer, viewLabels
|
|
|
132
141
|
})
|
|
133
142
|
});
|
|
134
143
|
}
|
|
135
|
-
function decorateTabs(tabs, viewLabels, footer) {
|
|
144
|
+
function decorateTabs(tabs, viewLabels, footer, titleExtra, extraActions) {
|
|
136
145
|
return tabs.map((tab)=>{
|
|
137
146
|
if ('content' === tab.kind) return tab;
|
|
138
147
|
return {
|
|
@@ -140,9 +149,16 @@ function decorateTabs(tabs, viewLabels, footer) {
|
|
|
140
149
|
slots: {
|
|
141
150
|
...tab.slots,
|
|
142
151
|
footer,
|
|
152
|
+
titleExtra: /*#__PURE__*/ jsxs(Fragment, {
|
|
153
|
+
children: [
|
|
154
|
+
tab.slots?.titleExtra,
|
|
155
|
+
titleExtra
|
|
156
|
+
]
|
|
157
|
+
}),
|
|
143
158
|
actions: (actions)=>/*#__PURE__*/ jsxs("div", {
|
|
144
159
|
className: "ov-file-browser__detail-actions",
|
|
145
160
|
children: [
|
|
161
|
+
extraActions,
|
|
146
162
|
actions.canSwitchView ? /*#__PURE__*/ jsxs("div", {
|
|
147
163
|
className: "ov-file-browser__segmented",
|
|
148
164
|
role: "group",
|
|
@@ -168,16 +184,22 @@ function decorateTabs(tabs, viewLabels, footer) {
|
|
|
168
184
|
"aria-hidden": "true",
|
|
169
185
|
className: "ov-file-browser__detail-sep"
|
|
170
186
|
}) : null,
|
|
171
|
-
actions.canCopyContent ? /*#__PURE__*/ jsx(
|
|
172
|
-
|
|
187
|
+
actions.canCopyContent ? /*#__PURE__*/ jsx(button_Button, {
|
|
188
|
+
size: "small",
|
|
189
|
+
type: "text",
|
|
190
|
+
iconOnly: true,
|
|
191
|
+
htmlType: "button",
|
|
173
192
|
className: "ov-file-browser__detail-icon",
|
|
174
193
|
"aria-label": viewLabels.copyContent,
|
|
175
194
|
title: viewLabels.copyContent,
|
|
176
195
|
onClick: ()=>void actions.copyContent(),
|
|
177
196
|
children: /*#__PURE__*/ jsx(CopyIcon, {})
|
|
178
197
|
}) : null,
|
|
179
|
-
actions.canDownload ? /*#__PURE__*/ jsx(
|
|
180
|
-
|
|
198
|
+
actions.canDownload ? /*#__PURE__*/ jsx(button_Button, {
|
|
199
|
+
size: "small",
|
|
200
|
+
type: "text",
|
|
201
|
+
iconOnly: true,
|
|
202
|
+
htmlType: "button",
|
|
181
203
|
className: "ov-file-browser__detail-icon",
|
|
182
204
|
"aria-label": viewLabels.download,
|
|
183
205
|
title: viewLabels.download,
|
|
@@ -221,8 +243,10 @@ function FileBrowserFailure({ message, detail, retryLabel, onRetry }) {
|
|
|
221
243
|
className: "ov-file-browser__failure-detail",
|
|
222
244
|
children: detail
|
|
223
245
|
}) : null,
|
|
224
|
-
/*#__PURE__*/ jsx(
|
|
225
|
-
|
|
246
|
+
/*#__PURE__*/ jsx(button_Button, {
|
|
247
|
+
size: "small",
|
|
248
|
+
type: "default",
|
|
249
|
+
htmlType: "button",
|
|
226
250
|
className: "ov-file-browser__failure-retry",
|
|
227
251
|
onClick: onRetry,
|
|
228
252
|
children: retryLabel
|
|
@@ -238,70 +262,59 @@ function resolveTreeMaxWidth(containerWidth) {
|
|
|
238
262
|
if (!containerWidth) return TREE_MAX_WIDTH;
|
|
239
263
|
return Math.max(TREE_MIN_WIDTH, Math.min(TREE_MAX_WIDTH, containerWidth - DETAIL_MIN_WIDTH - RESIZE_FOOTPRINT));
|
|
240
264
|
}
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
strokeWidth: 4,
|
|
249
|
-
'aria-hidden': true,
|
|
250
|
-
focusable: 'false'
|
|
265
|
+
const en = {
|
|
266
|
+
experienceTitle: 'Experience dashboard',
|
|
267
|
+
wikiTitle: 'Dashboard',
|
|
268
|
+
openExperience: 'Open experience dashboard',
|
|
269
|
+
openWiki: 'Open Wiki dashboard',
|
|
270
|
+
wikiConflicts: 'View pending Wiki conflicts',
|
|
271
|
+
failed: 'Could not load dashboard information'
|
|
251
272
|
};
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
273
|
+
const zh = {
|
|
274
|
+
experienceTitle: '经验看板',
|
|
275
|
+
wikiTitle: '看板',
|
|
276
|
+
openExperience: '打开经验看板',
|
|
277
|
+
openWiki: '打开 Wiki 看板',
|
|
278
|
+
wikiConflicts: '查看 Wiki 待确认冲突',
|
|
279
|
+
failed: '看板信息加载失败'
|
|
280
|
+
};
|
|
281
|
+
const dashboard_labels_dashboardLabels = (locale, labels)=>({
|
|
282
|
+
...'zh-CN' === locale ? zh : en,
|
|
283
|
+
...labels
|
|
258
284
|
});
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
}
|
|
285
|
+
function FileBrowserToolbar({ refreshing, canRefresh, placeholder, refreshLabel, onSearch, onRefresh }) {
|
|
286
|
+
return /*#__PURE__*/ jsx(FileTreeToolbar, {
|
|
287
|
+
className: "ov-file-browser__toolbar",
|
|
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
|
|
266
299
|
});
|
|
267
300
|
}
|
|
268
|
-
function
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
value: query,
|
|
286
|
-
onChange: (event)=>onQueryChange(event.target.value)
|
|
287
|
-
})
|
|
288
|
-
]
|
|
289
|
-
}),
|
|
290
|
-
/*#__PURE__*/ jsx(Tooltip, {
|
|
291
|
-
content: refreshLabel,
|
|
292
|
-
children: /*#__PURE__*/ jsx("button", {
|
|
293
|
-
type: "button",
|
|
294
|
-
className: [
|
|
295
|
-
'ov-file-browser__refresh',
|
|
296
|
-
refreshing ? 'ov-file-browser__refresh--spinning' : ''
|
|
297
|
-
].filter(Boolean).join(' '),
|
|
298
|
-
"aria-label": refreshLabel,
|
|
299
|
-
disabled: !canRefresh,
|
|
300
|
-
onClick: onRefresh,
|
|
301
|
-
children: /*#__PURE__*/ jsx(RefreshIcon, {})
|
|
302
|
-
})
|
|
303
|
-
})
|
|
304
|
-
]
|
|
301
|
+
function DashboardIndicator({ dashboard, locale, labels, onOpen }) {
|
|
302
|
+
const text = dashboard_labels_dashboardLabels(locale, labels);
|
|
303
|
+
const label = 'experience' === dashboard.kind ? text.openExperience : dashboard.pendingConflicts ? text.wikiConflicts : text.openWiki;
|
|
304
|
+
const Icon = 'experience' === dashboard.kind ? ExperienceBoardIcon : dashboard.pendingConflicts ? WikiConflictBoardIcon : WikiBoardIcon;
|
|
305
|
+
return /*#__PURE__*/ jsx("button", {
|
|
306
|
+
type: "button",
|
|
307
|
+
className: `ov-file-browser__dashboard${dashboard.pendingConflicts ? ' ov-file-browser__dashboard--pending' : ''}`,
|
|
308
|
+
"aria-label": label,
|
|
309
|
+
title: label,
|
|
310
|
+
onClick: (event)=>{
|
|
311
|
+
event.stopPropagation();
|
|
312
|
+
onOpen(dashboard.uri);
|
|
313
|
+
},
|
|
314
|
+
children: /*#__PURE__*/ jsx(Icon, {
|
|
315
|
+
width: 12,
|
|
316
|
+
height: 12
|
|
317
|
+
})
|
|
305
318
|
});
|
|
306
319
|
}
|
|
307
320
|
function fileTypeIcon(name) {
|
|
@@ -320,7 +333,7 @@ function directoryIcon() {
|
|
|
320
333
|
className: "ov-file-tree__file-type-icon"
|
|
321
334
|
});
|
|
322
335
|
}
|
|
323
|
-
function FileBrowserTreePane({ entries, locale,
|
|
336
|
+
function FileBrowserTreePane({ entries, locale, dashboardLabels, onSearch, browser, treeLabels, searchPlaceholder, refreshLabel, retryLabel }) {
|
|
324
337
|
return /*#__PURE__*/ jsxs(Fragment, {
|
|
325
338
|
children: [
|
|
326
339
|
/*#__PURE__*/ jsx(FileBrowserEntries, {
|
|
@@ -329,152 +342,202 @@ function FileBrowserTreePane({ entries, locale, searchId, browser, treeLabels, s
|
|
|
329
342
|
className: "ov-file-browser__entries"
|
|
330
343
|
}),
|
|
331
344
|
/*#__PURE__*/ jsx(FileBrowserToolbar, {
|
|
332
|
-
|
|
333
|
-
query: browser.query,
|
|
345
|
+
onSearch: onSearch,
|
|
334
346
|
refreshing: browser.refreshing,
|
|
335
347
|
canRefresh: browser.isReady,
|
|
336
348
|
placeholder: searchPlaceholder,
|
|
337
349
|
refreshLabel: refreshLabel,
|
|
338
|
-
onQueryChange: browser.actions.setQuery,
|
|
339
350
|
onRefresh: ()=>void browser.actions.refresh()
|
|
340
351
|
}),
|
|
341
|
-
/*#__PURE__*/
|
|
352
|
+
/*#__PURE__*/ jsxs("div", {
|
|
342
353
|
className: "ov-file-browser__tree-scroll",
|
|
343
|
-
children:
|
|
344
|
-
|
|
345
|
-
rows: browser.rows,
|
|
346
|
-
selectedId: browser.selectedId,
|
|
347
|
-
isReady: browser.isReady,
|
|
348
|
-
labels: treeLabels,
|
|
349
|
-
emptyFallback: browser.searching ? browser.searchError ? /*#__PURE__*/ jsxs("div", {
|
|
354
|
+
children: [
|
|
355
|
+
browser.dashboardError ? /*#__PURE__*/ jsxs("div", {
|
|
350
356
|
className: "ov-file-browser__hint ov-file-browser__hint--error",
|
|
357
|
+
"aria-live": "polite",
|
|
351
358
|
children: [
|
|
352
|
-
/*#__PURE__*/
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
+
/*#__PURE__*/ jsxs("p", {
|
|
360
|
+
children: [
|
|
361
|
+
dashboard_labels_dashboardLabels(locale, dashboardLabels).failed,
|
|
362
|
+
":",
|
|
363
|
+
' ',
|
|
364
|
+
browser.dashboardError.message
|
|
365
|
+
]
|
|
359
366
|
}),
|
|
360
|
-
/*#__PURE__*/ jsx(
|
|
361
|
-
|
|
362
|
-
|
|
367
|
+
/*#__PURE__*/ jsx(button_Button, {
|
|
368
|
+
size: "small",
|
|
369
|
+
type: "default",
|
|
370
|
+
htmlType: "button",
|
|
363
371
|
onClick: ()=>void browser.actions.refresh(),
|
|
364
372
|
children: retryLabel
|
|
365
373
|
})
|
|
366
374
|
]
|
|
367
|
-
}) :
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
375
|
+
}) : null,
|
|
376
|
+
/*#__PURE__*/ jsx(file_tree_FileTree, {
|
|
377
|
+
search: false,
|
|
378
|
+
refresh: false,
|
|
379
|
+
locale: locale,
|
|
380
|
+
rows: browser.rows,
|
|
381
|
+
selectedId: browser.selectedId,
|
|
382
|
+
isReady: browser.isReady,
|
|
383
|
+
labels: treeLabels,
|
|
384
|
+
renderRowIcon: (row)=>browser.dashboards.get(row.uri)?.isRoot ? /*#__PURE__*/ jsx(WikiBookIcon, {
|
|
385
|
+
width: 14,
|
|
386
|
+
height: 14
|
|
387
|
+
}) : fileTypeIcon(row.name),
|
|
388
|
+
renderDashboard: (row)=>{
|
|
389
|
+
const dashboard = browser.dashboards.get(row.uri);
|
|
390
|
+
return dashboard ? /*#__PURE__*/ jsx(DashboardIndicator, {
|
|
391
|
+
dashboard: dashboard,
|
|
392
|
+
locale: locale,
|
|
393
|
+
labels: dashboardLabels,
|
|
394
|
+
onOpen: browser.actions.openDashboard
|
|
395
|
+
}) : null;
|
|
396
|
+
},
|
|
397
|
+
operations: {
|
|
398
|
+
select: browser.actions.select,
|
|
399
|
+
toggleExpand: browser.actions.toggleExpand,
|
|
400
|
+
loadMore: browser.actions.loadMore,
|
|
401
|
+
retry: browser.actions.retry
|
|
402
|
+
}
|
|
403
|
+
})
|
|
404
|
+
]
|
|
379
405
|
})
|
|
380
406
|
]
|
|
381
407
|
});
|
|
382
408
|
}
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
empty: 'Select a file to preview',
|
|
386
|
-
error: 'Failed to load the directory',
|
|
387
|
-
retry: 'Retry',
|
|
388
|
-
refresh: 'Refresh',
|
|
389
|
-
searchPlaceholder: 'Search files ⌘+K...',
|
|
390
|
-
searchEmpty: 'No matching resources',
|
|
391
|
-
searchFailed: 'Failed to run the search',
|
|
392
|
-
loading: 'Loading…',
|
|
393
|
-
searchLoading: 'Searching…',
|
|
394
|
-
previewMode: 'Preview',
|
|
395
|
-
sourceMode: 'Source',
|
|
396
|
-
copyPath: 'Copy path',
|
|
397
|
-
copyContent: 'Copy content',
|
|
398
|
-
download: 'Download',
|
|
399
|
-
history: 'Version history',
|
|
400
|
-
viewSwitch: 'Switch view',
|
|
401
|
-
resizeTree: 'Resize the directory tree',
|
|
402
|
-
l0Summary: 'L0 Summary',
|
|
403
|
-
l1Overview: 'L1 Overview',
|
|
404
|
-
summaryEmpty: 'No summary generated yet',
|
|
405
|
-
summaryFailed: 'The summary could not be loaded.'
|
|
409
|
+
const en_US = {
|
|
410
|
+
closeLabel: 'Close dialog'
|
|
406
411
|
};
|
|
407
|
-
const
|
|
408
|
-
|
|
409
|
-
formatCharacterCount: (count)=>`${count.toLocaleString('zh-CN')} 个字符`,
|
|
410
|
-
empty: '选择一个文件查看内容',
|
|
411
|
-
error: '目录加载失败',
|
|
412
|
-
retry: '重试',
|
|
413
|
-
refresh: '刷新',
|
|
414
|
-
searchPlaceholder: '搜索文件 ⌘+K...',
|
|
415
|
-
searchEmpty: '没有匹配的资源',
|
|
416
|
-
searchFailed: '搜索失败',
|
|
417
|
-
loading: '加载中…',
|
|
418
|
-
searchLoading: '搜索中…',
|
|
419
|
-
previewMode: '预览',
|
|
420
|
-
sourceMode: '源码',
|
|
421
|
-
copyPath: '复制路径',
|
|
422
|
-
copyContent: '复制正文',
|
|
423
|
-
download: '下载',
|
|
424
|
-
history: '版本历史',
|
|
425
|
-
viewSwitch: '切换视图',
|
|
426
|
-
resizeTree: '调整目录树宽度',
|
|
427
|
-
l0Summary: 'L0 摘要',
|
|
428
|
-
l1Overview: 'L1 总览',
|
|
429
|
-
summaryEmpty: '暂无生成内容',
|
|
430
|
-
summaryFailed: '摘要加载失败。'
|
|
412
|
+
const zh_CN = {
|
|
413
|
+
closeLabel: '关闭弹窗'
|
|
431
414
|
};
|
|
432
|
-
const
|
|
433
|
-
|
|
434
|
-
'
|
|
435
|
-
'
|
|
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'
|
|
436
421
|
};
|
|
437
|
-
function
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
};
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
};
|
|
451
|
-
}
|
|
452
|
-
function resolveSummaryLabels(locale, labels) {
|
|
453
|
-
const text = resolveLabels(labels, locale);
|
|
454
|
-
return {
|
|
455
|
-
l0: text.l0Summary,
|
|
456
|
-
l1: text.l1Overview,
|
|
457
|
-
failure: text.summaryFailed
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
function useContainerWidth() {
|
|
461
|
-
const ref = useRef(null);
|
|
462
|
-
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);
|
|
463
435
|
useEffect(()=>{
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
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);
|
|
521
|
+
}
|
|
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;
|
|
478
541
|
}
|
|
479
542
|
const emptyDir = ()=>({
|
|
480
543
|
entries: [],
|
|
@@ -503,6 +566,7 @@ function nameFromUri(uri) {
|
|
|
503
566
|
function toEntryRow(entry, depth, expanded, index, busy) {
|
|
504
567
|
const isDir = true === entry.isDir;
|
|
505
568
|
return {
|
|
569
|
+
tags: entry.tags,
|
|
506
570
|
kind: 'entry',
|
|
507
571
|
id: void 0 === index ? entry.uri : `${entry.uri}#${index}`,
|
|
508
572
|
depth,
|
|
@@ -510,10 +574,13 @@ function toEntryRow(entry, depth, expanded, index, busy) {
|
|
|
510
574
|
name: nameFromUri(entry.uri),
|
|
511
575
|
type: isDir ? 'directory' : 'file',
|
|
512
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),
|
|
513
580
|
busy: busy || void 0
|
|
514
581
|
};
|
|
515
582
|
}
|
|
516
|
-
function toFileTab(entry, content, maxLines, icon, openParent) {
|
|
583
|
+
function toFileTab(entry, content, maxLines, icon, openParent, truncated = content.split('\n').length >= maxLines) {
|
|
517
584
|
const name = nameFromUri(entry.uri);
|
|
518
585
|
return {
|
|
519
586
|
kind: 'file',
|
|
@@ -527,7 +594,7 @@ function toFileTab(entry, content, maxLines, icon, openParent) {
|
|
|
527
594
|
content,
|
|
528
595
|
fileName: name,
|
|
529
596
|
path: entry.uri,
|
|
530
|
-
truncated
|
|
597
|
+
truncated
|
|
531
598
|
}
|
|
532
599
|
};
|
|
533
600
|
}
|
|
@@ -609,18 +676,316 @@ function flattenTree(uri, depth, dirs, expanded) {
|
|
|
609
676
|
function isAbort(error) {
|
|
610
677
|
return 'object' == typeof error && null !== error && 'AbortError' === error.name;
|
|
611
678
|
}
|
|
612
|
-
|
|
613
|
-
|
|
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
|
|
702
|
+
},
|
|
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
|
|
724
|
+
]);
|
|
725
|
+
return {
|
|
726
|
+
results,
|
|
727
|
+
loading,
|
|
728
|
+
error
|
|
729
|
+
};
|
|
614
730
|
}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
'
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
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 : '',
|
|
975
|
+
overview: overviewResult.ok ? overviewResult.text : '',
|
|
976
|
+
abstractError: reason(abstractResult),
|
|
977
|
+
overviewError: reason(overviewResult)
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
const BINARY_EXTENSIONS = new Set([
|
|
981
|
+
'png',
|
|
982
|
+
'jpg',
|
|
983
|
+
'jpeg',
|
|
984
|
+
'gif',
|
|
985
|
+
'webp',
|
|
986
|
+
'avif',
|
|
987
|
+
'bmp',
|
|
988
|
+
'ico',
|
|
624
989
|
'svg',
|
|
625
990
|
'pdf',
|
|
626
991
|
'doc',
|
|
@@ -695,38 +1060,152 @@ function toBinaryTab(uri, name, file, icon, openParent) {
|
|
|
695
1060
|
}
|
|
696
1061
|
};
|
|
697
1062
|
}
|
|
698
|
-
|
|
699
|
-
|
|
1063
|
+
const LINE_BREAK_CODES = new Set([
|
|
1064
|
+
0x0a,
|
|
1065
|
+
0x0b,
|
|
1066
|
+
0x0c,
|
|
1067
|
+
0x0d,
|
|
1068
|
+
0x1c,
|
|
1069
|
+
0x1d,
|
|
1070
|
+
0x1e,
|
|
1071
|
+
0x85,
|
|
1072
|
+
0x2028,
|
|
1073
|
+
0x2029
|
|
1074
|
+
]);
|
|
1075
|
+
function splitLines(text) {
|
|
1076
|
+
if (!text) return [];
|
|
1077
|
+
const lines = [];
|
|
1078
|
+
let start = 0;
|
|
1079
|
+
for(let index = 0; index < text.length; index++){
|
|
1080
|
+
if (!LINE_BREAK_CODES.has(text.charCodeAt(index))) continue;
|
|
1081
|
+
const paired = 0x0d === text.charCodeAt(index) && 0x0a === text.charCodeAt(index + 1);
|
|
1082
|
+
const end = index + (paired ? 2 : 1);
|
|
1083
|
+
lines.push(text.slice(start, end));
|
|
1084
|
+
start = end;
|
|
1085
|
+
index = end - 1;
|
|
1086
|
+
}
|
|
1087
|
+
if (start < text.length) lines.push(text.slice(start));
|
|
1088
|
+
return lines;
|
|
1089
|
+
}
|
|
1090
|
+
function joinLines(lines) {
|
|
1091
|
+
return lines.join('');
|
|
1092
|
+
}
|
|
1093
|
+
const PY_WHITESPACE_CODES = [
|
|
1094
|
+
0x09,
|
|
1095
|
+
0x0a,
|
|
1096
|
+
0x0b,
|
|
1097
|
+
0x0c,
|
|
1098
|
+
0x0d,
|
|
1099
|
+
0x1c,
|
|
1100
|
+
0x1d,
|
|
1101
|
+
0x1e,
|
|
1102
|
+
0x1f,
|
|
1103
|
+
0x20,
|
|
1104
|
+
0x85,
|
|
1105
|
+
0xa0,
|
|
1106
|
+
0x1680,
|
|
1107
|
+
0x2000,
|
|
1108
|
+
0x2001,
|
|
1109
|
+
0x2002,
|
|
1110
|
+
0x2003,
|
|
1111
|
+
0x2004,
|
|
1112
|
+
0x2005,
|
|
1113
|
+
0x2006,
|
|
1114
|
+
0x2007,
|
|
1115
|
+
0x2008,
|
|
1116
|
+
0x2009,
|
|
1117
|
+
0x200a,
|
|
1118
|
+
0x2028,
|
|
1119
|
+
0x2029,
|
|
1120
|
+
0x202f,
|
|
1121
|
+
0x205f,
|
|
1122
|
+
0x3000
|
|
1123
|
+
];
|
|
1124
|
+
const PY_WHITESPACE = String.fromCharCode(...PY_WHITESPACE_CODES);
|
|
1125
|
+
const PY_WORD = '\\p{L}\\p{N}_';
|
|
1126
|
+
const MEMORY_FIELDS_COMMENT = new RegExp(`<!--[${PY_WHITESPACE}]*MEMORY_FIELDS(?![${PY_WORD}])[\\s\\S]*?[${PY_WHITESPACE}]*-->`, 'gu');
|
|
1127
|
+
function isPythonBlank(text) {
|
|
1128
|
+
for (const char of text)if (!PY_WHITESPACE.includes(char)) return false;
|
|
1129
|
+
return true;
|
|
1130
|
+
}
|
|
1131
|
+
function trimPythonEnd(text) {
|
|
1132
|
+
let end = text.length;
|
|
1133
|
+
while(end > 0 && PY_WHITESPACE.includes(text[end - 1] ?? ''))end--;
|
|
1134
|
+
return text.slice(0, end);
|
|
1135
|
+
}
|
|
1136
|
+
function uriParts(uri) {
|
|
1137
|
+
const [beforeQuery = ''] = uri.split('?');
|
|
1138
|
+
const path = beforeQuery.replace(/\/+$/, '');
|
|
1139
|
+
if (!path.startsWith('viking://')) return [];
|
|
1140
|
+
return path.slice(9).split('/').filter(Boolean);
|
|
1141
|
+
}
|
|
1142
|
+
function isMemoryUri(uri) {
|
|
1143
|
+
const parts = uriParts(uri);
|
|
1144
|
+
if (parts.length < 3) return false;
|
|
1145
|
+
if ('user' !== parts[0] && '~' !== parts[0]) return false;
|
|
1146
|
+
const scoped = '~' === parts[0] ? [
|
|
1147
|
+
'user',
|
|
1148
|
+
'~',
|
|
1149
|
+
...parts.slice(1)
|
|
1150
|
+
] : parts;
|
|
1151
|
+
if ('memories' === scoped[2]) return true;
|
|
1152
|
+
return scoped.length >= 5 && 'peers' === scoped[2] && 'memories' === scoped[4];
|
|
1153
|
+
}
|
|
1154
|
+
function stripMemoryFieldsTrailer(text) {
|
|
1155
|
+
let visible = text;
|
|
1156
|
+
for(;;){
|
|
1157
|
+
let start = -1;
|
|
1158
|
+
MEMORY_FIELDS_COMMENT.lastIndex = 0;
|
|
1159
|
+
let match;
|
|
1160
|
+
while(null !== (match = MEMORY_FIELDS_COMMENT.exec(visible))){
|
|
1161
|
+
const after = visible.slice(match.index + match[0].length);
|
|
1162
|
+
if (isPythonBlank(after)) start = match.index;
|
|
1163
|
+
}
|
|
1164
|
+
if (start < 0) return visible;
|
|
1165
|
+
visible = trimPythonEnd(visible.slice(0, start));
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
const MAX_LINES = 2000;
|
|
1169
|
+
async function readFile(client, uri, options, openParent) {
|
|
1170
|
+
const name = nameFromUri(uri);
|
|
1171
|
+
const icon = fileTypeIcon(name);
|
|
1172
|
+
if (isBinaryFile(name)) {
|
|
1173
|
+
const blob = await client.request({
|
|
700
1174
|
method: 'GET',
|
|
701
|
-
path,
|
|
1175
|
+
path: OV_PATHS.contentDownload,
|
|
702
1176
|
query: {
|
|
703
1177
|
uri
|
|
704
1178
|
},
|
|
1179
|
+
responseType: 'blob',
|
|
705
1180
|
options
|
|
706
|
-
})
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
const
|
|
724
|
-
return {
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
1181
|
+
});
|
|
1182
|
+
const typed = new Blob([
|
|
1183
|
+
blob
|
|
1184
|
+
], {
|
|
1185
|
+
type: mimeTypeOf(name)
|
|
1186
|
+
});
|
|
1187
|
+
return toBinaryTab(uri, name, typed, icon, openParent);
|
|
1188
|
+
}
|
|
1189
|
+
const read = (limit)=>client.request({
|
|
1190
|
+
method: 'GET',
|
|
1191
|
+
path: OV_PATHS.contentRead,
|
|
1192
|
+
query: {
|
|
1193
|
+
uri,
|
|
1194
|
+
limit
|
|
1195
|
+
},
|
|
1196
|
+
options
|
|
1197
|
+
}).then((value)=>'string' == typeof value ? value : '');
|
|
1198
|
+
const first = await read(MAX_LINES);
|
|
1199
|
+
if (!isMemoryUri(uri)) return toFileTab({
|
|
1200
|
+
uri
|
|
1201
|
+
}, first, MAX_LINES, icon, openParent, splitLines(first).length >= MAX_LINES);
|
|
1202
|
+
const full = splitLines(first).length < MAX_LINES ? first : await read(-1);
|
|
1203
|
+
const lines = splitLines(stripMemoryFieldsTrailer(full));
|
|
1204
|
+
const truncated = lines.length > MAX_LINES;
|
|
1205
|
+
const visible = joinLines(truncated ? lines.slice(0, MAX_LINES) : lines);
|
|
1206
|
+
return toFileTab({
|
|
1207
|
+
uri
|
|
1208
|
+
}, visible, MAX_LINES, icon, openParent, truncated);
|
|
730
1209
|
}
|
|
731
1210
|
async function resolveIsDirectory(client, uri, options) {
|
|
732
1211
|
try {
|
|
@@ -748,7 +1227,6 @@ function abortError() {
|
|
|
748
1227
|
error.name = 'AbortError';
|
|
749
1228
|
return error;
|
|
750
1229
|
}
|
|
751
|
-
const MAX_LINES = 2000;
|
|
752
1230
|
function useFsPreview({ client, uri, resourceType = 'file', labels = resolveSummaryLabels('en-US'), formatCharacterCount = resolveLabels(void 0, 'en-US').formatCharacterCount, openParent, generation = 0, requestOptions }) {
|
|
753
1231
|
const [directory, setDirectory] = useState(null);
|
|
754
1232
|
const [tabs, setTabs] = useState(null);
|
|
@@ -867,108 +1345,663 @@ function loadingTab(uri, isDirectory) {
|
|
|
867
1345
|
}
|
|
868
1346
|
};
|
|
869
1347
|
}
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
const
|
|
873
|
-
if (isBinaryFile(name)) {
|
|
874
|
-
const blob = await client.request({
|
|
875
|
-
method: 'GET',
|
|
876
|
-
path: OV_PATHS.contentDownload,
|
|
877
|
-
query: {
|
|
878
|
-
uri
|
|
879
|
-
},
|
|
880
|
-
responseType: 'blob',
|
|
881
|
-
options
|
|
882
|
-
});
|
|
883
|
-
const typed = new Blob([
|
|
884
|
-
blob
|
|
885
|
-
], {
|
|
886
|
-
type: mimeTypeOf(name)
|
|
887
|
-
});
|
|
888
|
-
return toBinaryTab(uri, name, typed, icon, openParent);
|
|
889
|
-
}
|
|
890
|
-
const content = await client.request({
|
|
1348
|
+
const isHomeUri = (uri)=>'viking://~' === uri || uri.startsWith('viking://~/');
|
|
1349
|
+
async function readDashboardStat(client, uri, options) {
|
|
1350
|
+
const stat = await client.request({
|
|
891
1351
|
method: 'GET',
|
|
892
|
-
path: OV_PATHS.
|
|
1352
|
+
path: OV_PATHS.fsStat,
|
|
893
1353
|
query: {
|
|
894
|
-
uri
|
|
895
|
-
limit: MAX_LINES
|
|
1354
|
+
uri
|
|
896
1355
|
},
|
|
897
1356
|
options
|
|
898
1357
|
});
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
1358
|
+
const canonical = stat?.uri ?? uri;
|
|
1359
|
+
if ('boolean' != typeof stat?.isDir || 'string' != typeof canonical || !canonical || isHomeUri(canonical)) throw new TypeError('Invalid canonical resource stat');
|
|
1360
|
+
return {
|
|
1361
|
+
uri: canonical,
|
|
1362
|
+
isDir: stat.isDir
|
|
1363
|
+
};
|
|
902
1364
|
}
|
|
903
|
-
function
|
|
904
|
-
|
|
905
|
-
const entries = [];
|
|
906
|
-
for (const group of [
|
|
907
|
-
result?.memories,
|
|
908
|
-
result?.resources,
|
|
909
|
-
result?.skills
|
|
910
|
-
])for (const item of group ?? []){
|
|
911
|
-
const uri = item?.uri;
|
|
912
|
-
if (!(!uri || seen.has(uri))) {
|
|
913
|
-
seen.add(uri);
|
|
914
|
-
entries.push({
|
|
915
|
-
uri,
|
|
916
|
-
isDir: false,
|
|
917
|
-
abstract: item.abstract?.trim() || void 0
|
|
918
|
-
});
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
return entries;
|
|
1365
|
+
async function resolveDashboardUri(client, uri, options) {
|
|
1366
|
+
return isHomeUri(uri) ? (await readDashboardStat(client, uri, options)).uri : uri;
|
|
922
1367
|
}
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
1368
|
+
async function readDashboardHint(client, uri, options) {
|
|
1369
|
+
const result = await client.request({
|
|
1370
|
+
method: 'GET',
|
|
1371
|
+
path: OV_PATHS.fsAttrs,
|
|
1372
|
+
query: {
|
|
1373
|
+
uri
|
|
1374
|
+
},
|
|
1375
|
+
options
|
|
1376
|
+
});
|
|
1377
|
+
return {
|
|
1378
|
+
uri: await resolveDashboardUri(client, result?.uri ?? uri, options),
|
|
1379
|
+
tags: result?.attrs?.tags ?? []
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
const EXPERIENCE_PAGE_SIZE = 20;
|
|
1383
|
+
const isExperienceDirectory = (uri)=>/^viking:\/\/(?:user\/[^/]+|~)\/memories\/experiences\/?$/.test(uri);
|
|
1384
|
+
const isExperienceFile = (uri)=>/^viking:\/\/(?:user\/[^/]+|~)\/memories\/experiences\/[^/]+\.md$/i.test(uri);
|
|
1385
|
+
function experienceDateRange(days, now = new Date()) {
|
|
1386
|
+
if (!days) return {};
|
|
1387
|
+
const start = new Date(now);
|
|
1388
|
+
start.setUTCDate(start.getUTCDate() - days + 1);
|
|
1389
|
+
return {
|
|
1390
|
+
start_date: start.toISOString().slice(0, 10),
|
|
1391
|
+
end_date: now.toISOString().slice(0, 10)
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1394
|
+
async function readExperiences(client, uri, page, options) {
|
|
1395
|
+
uri = await resolveDashboardUri(client, uri, options);
|
|
1396
|
+
const result = await client.requestEnvelope({
|
|
1397
|
+
method: 'GET',
|
|
1398
|
+
path: OV_PATHS.fsLs,
|
|
1399
|
+
query: {
|
|
1400
|
+
uri,
|
|
1401
|
+
output: 'original',
|
|
1402
|
+
limit: EXPERIENCE_PAGE_SIZE,
|
|
1403
|
+
offset: page * EXPERIENCE_PAGE_SIZE,
|
|
1404
|
+
sort_by: 'mtime',
|
|
1405
|
+
sort_order: 'desc'
|
|
1406
|
+
},
|
|
1407
|
+
options
|
|
1408
|
+
});
|
|
1409
|
+
if (!Array.isArray(result.result)) throw new TypeError('Invalid Experience listing');
|
|
1410
|
+
const prefix = `${uri.replace(/\/+$/, '')}/`;
|
|
1411
|
+
return {
|
|
1412
|
+
items: result.result.filter((item)=>!item.isDir && item.uri.startsWith(prefix) && isExperienceFile(item.uri)),
|
|
1413
|
+
hasMore: result.hasMore
|
|
1414
|
+
};
|
|
1415
|
+
}
|
|
1416
|
+
async function readExperienceSources(client, uri, options) {
|
|
1417
|
+
uri = await resolveDashboardUri(client, uri, options);
|
|
1418
|
+
const result = await client.request({
|
|
1419
|
+
method: 'GET',
|
|
1420
|
+
path: OV_PATHS.fsAttrs,
|
|
1421
|
+
query: {
|
|
1422
|
+
uri
|
|
1423
|
+
},
|
|
1424
|
+
options
|
|
1425
|
+
});
|
|
1426
|
+
const links = result?.attrs?.memory?.links;
|
|
1427
|
+
if (void 0 !== links && !Array.isArray(links)) throw new TypeError('Invalid Experience links');
|
|
1428
|
+
const prefix = uri.slice(0, uri.indexOf('/memories/experiences/')) + '/memories/trajectories/';
|
|
1429
|
+
const seen = new Set();
|
|
1430
|
+
return (links ?? []).flatMap((link)=>{
|
|
1431
|
+
if ('derived_from' !== link.link_type || 'string' != typeof link.to_uri || !link.to_uri.startsWith(prefix) || seen.has(link.to_uri)) return [];
|
|
1432
|
+
seen.add(link.to_uri);
|
|
1433
|
+
return [
|
|
1434
|
+
{
|
|
1435
|
+
uri: link.to_uri,
|
|
1436
|
+
name: link.to_uri.split('/').pop() ?? link.to_uri,
|
|
1437
|
+
description: link.description ?? link.match_text
|
|
1438
|
+
}
|
|
1439
|
+
];
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
const dashboardKey = (uri)=>`dashboard:${uri}`;
|
|
1443
|
+
const within = (uri, root)=>uri === root || uri.startsWith(`${root.replace(/\/+$/, '')}/`);
|
|
1444
|
+
function resolveDashboard(uri, rows, targets) {
|
|
1445
|
+
if (isExperienceDirectory(uri) || isExperienceFile(uri)) return {
|
|
1446
|
+
kind: 'experience',
|
|
1447
|
+
uri
|
|
1448
|
+
};
|
|
1449
|
+
const entries = rows.filter((row)=>'entry' === row.kind);
|
|
1450
|
+
const row = entries.find((item)=>item.uri === uri);
|
|
1451
|
+
const hasWiki = entries.some((item)=>item.tags?.includes('compile_target=true') && within(uri, item.uri));
|
|
1452
|
+
const owner = targets.filter((item)=>within(uri, item.to)).sort((a, b)=>b.to.length - a.to.length)[0];
|
|
1453
|
+
if (!hasWiki && !owner) return;
|
|
1454
|
+
const tagged = row?.tags?.find((tag)=>tag.startsWith('has_conflict='));
|
|
1455
|
+
return {
|
|
1456
|
+
kind: 'wiki',
|
|
1457
|
+
uri,
|
|
1458
|
+
isRoot: Boolean(row?.tags?.includes('compile_target=true') || owner?.to === uri),
|
|
1459
|
+
pendingConflicts: tagged ? 'has_conflict=true' === tagged : owner?.to === uri && void 0 !== owner.conflicts_num ? owner.conflicts_num > 0 : void 0
|
|
1460
|
+
};
|
|
1461
|
+
}
|
|
1462
|
+
function mergeDashboardTab(tabs, dashboard, blocked) {
|
|
1463
|
+
return blocked ? null : dashboard ? [
|
|
1464
|
+
...tabs ?? [],
|
|
1465
|
+
dashboard
|
|
1466
|
+
] : tabs;
|
|
1467
|
+
}
|
|
1468
|
+
async function readCompileTargets(client, rootUri, options) {
|
|
1469
|
+
rootUri = await resolveDashboardUri(client, rootUri, options);
|
|
1470
|
+
const targets = [];
|
|
1471
|
+
let cursor;
|
|
1472
|
+
const seen = new Set();
|
|
1473
|
+
do {
|
|
1474
|
+
const result = await client.request({
|
|
1475
|
+
method: 'GET',
|
|
1476
|
+
path: OV_PATHS.compileConfigs,
|
|
1477
|
+
query: {
|
|
1478
|
+
limit: 100,
|
|
1479
|
+
cursor
|
|
1480
|
+
},
|
|
1481
|
+
options
|
|
1482
|
+
});
|
|
1483
|
+
if (!Array.isArray(result?.targets)) throw new TypeError('Invalid Compile target registry');
|
|
1484
|
+
targets.push(...result.targets.filter((item)=>'string' == typeof item.to && 'memory' !== item.compile_type && false !== item.initialized && (within(item.to, rootUri) || within(rootUri, item.to))));
|
|
1485
|
+
cursor = result.next_cursor ?? void 0;
|
|
1486
|
+
if (cursor && seen.has(cursor)) throw new TypeError('Compile target cursor did not advance');
|
|
1487
|
+
if (cursor) seen.add(cursor);
|
|
1488
|
+
}while (cursor)
|
|
1489
|
+
return targets;
|
|
1490
|
+
}
|
|
1491
|
+
function useDashboardQuery(load, requestOptions, enabled = true, refreshGeneration = 0) {
|
|
1492
|
+
const client = useOvClient();
|
|
1493
|
+
const inheritedSignal = useOvSignal();
|
|
1494
|
+
const { baseURL, apiKey, accountId, userId, resourceId, signal } = requestOptions ?? {};
|
|
1495
|
+
const [generation, setGeneration] = useState(0);
|
|
1496
|
+
const [state, setState] = useState({
|
|
1497
|
+
owner: [],
|
|
1498
|
+
error: null
|
|
1499
|
+
});
|
|
1500
|
+
const owner = useMemo(()=>[
|
|
1501
|
+
client,
|
|
1502
|
+
load,
|
|
1503
|
+
baseURL,
|
|
1504
|
+
apiKey,
|
|
1505
|
+
accountId,
|
|
1506
|
+
userId,
|
|
1507
|
+
resourceId,
|
|
1508
|
+
signal,
|
|
1509
|
+
inheritedSignal,
|
|
1510
|
+
generation,
|
|
1511
|
+
enabled,
|
|
1512
|
+
refreshGeneration
|
|
1513
|
+
], [
|
|
1514
|
+
client,
|
|
1515
|
+
load,
|
|
1516
|
+
baseURL,
|
|
1517
|
+
apiKey,
|
|
1518
|
+
accountId,
|
|
1519
|
+
userId,
|
|
1520
|
+
resourceId,
|
|
1521
|
+
signal,
|
|
1522
|
+
inheritedSignal,
|
|
1523
|
+
generation,
|
|
1524
|
+
enabled,
|
|
1525
|
+
refreshGeneration
|
|
1526
|
+
]);
|
|
1527
|
+
const current = owner.every((value, i)=>value === state.owner[i]);
|
|
928
1528
|
useEffect(()=>{
|
|
929
|
-
if (!
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
}
|
|
1529
|
+
if (!enabled) return;
|
|
1530
|
+
if (!client) return void setState({
|
|
1531
|
+
owner,
|
|
1532
|
+
error: new Error('OpenVikingProvider is required')
|
|
1533
|
+
});
|
|
935
1534
|
const controller = new AbortController();
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
1535
|
+
const signals = [
|
|
1536
|
+
controller.signal,
|
|
1537
|
+
signal,
|
|
1538
|
+
inheritedSignal
|
|
1539
|
+
].filter((value)=>Boolean(value));
|
|
1540
|
+
const options = {
|
|
1541
|
+
baseURL,
|
|
1542
|
+
apiKey,
|
|
1543
|
+
accountId,
|
|
1544
|
+
userId,
|
|
1545
|
+
resourceId,
|
|
1546
|
+
signal: AbortSignal.any(signals)
|
|
1547
|
+
};
|
|
1548
|
+
load(client, options).then((data)=>{
|
|
1549
|
+
if (!options.signal.aborted) setState({
|
|
1550
|
+
owner,
|
|
1551
|
+
data,
|
|
1552
|
+
error: null
|
|
1553
|
+
});
|
|
954
1554
|
}, (cause)=>{
|
|
955
|
-
if (
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
1555
|
+
if (!options.signal.aborted) setState({
|
|
1556
|
+
owner,
|
|
1557
|
+
error: cause instanceof Error ? cause : new Error(String(cause))
|
|
1558
|
+
});
|
|
959
1559
|
});
|
|
960
1560
|
return ()=>controller.abort();
|
|
961
1561
|
}, [
|
|
962
1562
|
client,
|
|
963
|
-
|
|
964
|
-
|
|
1563
|
+
load,
|
|
1564
|
+
baseURL,
|
|
1565
|
+
apiKey,
|
|
1566
|
+
accountId,
|
|
1567
|
+
userId,
|
|
1568
|
+
resourceId,
|
|
1569
|
+
signal,
|
|
1570
|
+
inheritedSignal,
|
|
965
1571
|
generation,
|
|
966
|
-
|
|
1572
|
+
enabled,
|
|
1573
|
+
owner
|
|
967
1574
|
]);
|
|
968
1575
|
return {
|
|
969
|
-
|
|
970
|
-
loading,
|
|
971
|
-
error
|
|
1576
|
+
data: current ? state.data : void 0,
|
|
1577
|
+
loading: enabled && !current,
|
|
1578
|
+
error: current ? state.error : null,
|
|
1579
|
+
retry: ()=>setGeneration((value)=>value + 1)
|
|
1580
|
+
};
|
|
1581
|
+
}
|
|
1582
|
+
function useExperienceDashboard({ uri, requestOptions, enabled = true, refreshGeneration = 0 }) {
|
|
1583
|
+
const client = useOvClient();
|
|
1584
|
+
const signal = useOvSignal();
|
|
1585
|
+
const owner = useMemo(()=>[
|
|
1586
|
+
uri,
|
|
1587
|
+
client,
|
|
1588
|
+
signal,
|
|
1589
|
+
requestOptions?.baseURL,
|
|
1590
|
+
requestOptions?.apiKey,
|
|
1591
|
+
requestOptions?.accountId,
|
|
1592
|
+
requestOptions?.userId,
|
|
1593
|
+
requestOptions?.resourceId
|
|
1594
|
+
], [
|
|
1595
|
+
uri,
|
|
1596
|
+
client,
|
|
1597
|
+
signal,
|
|
1598
|
+
requestOptions?.baseURL,
|
|
1599
|
+
requestOptions?.apiKey,
|
|
1600
|
+
requestOptions?.accountId,
|
|
1601
|
+
requestOptions?.userId,
|
|
1602
|
+
requestOptions?.resourceId
|
|
1603
|
+
]);
|
|
1604
|
+
const [state, setState] = useState({
|
|
1605
|
+
owner,
|
|
1606
|
+
page: 0,
|
|
1607
|
+
days: 7,
|
|
1608
|
+
tab: 'impact'
|
|
1609
|
+
});
|
|
1610
|
+
const current = state.owner === owner ? state : {
|
|
1611
|
+
owner,
|
|
1612
|
+
page: 0,
|
|
1613
|
+
days: 7,
|
|
1614
|
+
tab: 'impact'
|
|
1615
|
+
};
|
|
1616
|
+
const { page, days, tab } = current;
|
|
1617
|
+
const setPage = (value)=>setState({
|
|
1618
|
+
...current,
|
|
1619
|
+
page: Math.max(0, value)
|
|
1620
|
+
});
|
|
1621
|
+
const setDays = (value)=>setState({
|
|
1622
|
+
...current,
|
|
1623
|
+
days: value,
|
|
1624
|
+
page: 0
|
|
1625
|
+
});
|
|
1626
|
+
const setTab = (value)=>setState({
|
|
1627
|
+
...current,
|
|
1628
|
+
tab: value,
|
|
1629
|
+
page: 0
|
|
1630
|
+
});
|
|
1631
|
+
const directory = isExperienceDirectory(uri);
|
|
1632
|
+
const loadList = useCallback((client, options)=>readExperiences(client, uri, page, options), [
|
|
1633
|
+
uri,
|
|
1634
|
+
page
|
|
1635
|
+
]);
|
|
1636
|
+
const loadSources = useCallback((client, options)=>readExperienceSources(client, uri, options), [
|
|
1637
|
+
uri
|
|
1638
|
+
]);
|
|
1639
|
+
const loadTrajectories = useCallback(async (client, options)=>{
|
|
1640
|
+
const result = await client.request({
|
|
1641
|
+
method: 'GET',
|
|
1642
|
+
path: OV_PATHS.experienceTrajectories,
|
|
1643
|
+
query: {
|
|
1644
|
+
experience_uri: uri,
|
|
1645
|
+
offset: 20 * page,
|
|
1646
|
+
limit: 20,
|
|
1647
|
+
...experienceDateRange(days)
|
|
1648
|
+
},
|
|
1649
|
+
options
|
|
1650
|
+
});
|
|
1651
|
+
if (!Array.isArray(result?.items) || !Number.isSafeInteger(result.total) || result.total < 0 || result.items.some((item)=>'string' != typeof item?.uri || 'string' != typeof item.name)) throw new TypeError('Invalid Experience trajectories');
|
|
1652
|
+
return result;
|
|
1653
|
+
}, [
|
|
1654
|
+
uri,
|
|
1655
|
+
page,
|
|
1656
|
+
days
|
|
1657
|
+
]);
|
|
1658
|
+
const loadOutcomes = useCallback(async (client, options)=>{
|
|
1659
|
+
const result = await client.request({
|
|
1660
|
+
method: 'GET',
|
|
1661
|
+
path: OV_PATHS.experienceOutcomes,
|
|
1662
|
+
query: {
|
|
1663
|
+
experience_uri: uri,
|
|
1664
|
+
...experienceDateRange(days)
|
|
1665
|
+
},
|
|
1666
|
+
options
|
|
1667
|
+
});
|
|
1668
|
+
if (!Array.isArray(result?.outcome_distribution) || result.outcome_distribution.some((entry)=>![
|
|
1669
|
+
'success',
|
|
1670
|
+
'partial',
|
|
1671
|
+
'unfinished',
|
|
1672
|
+
'failure',
|
|
1673
|
+
'unknown'
|
|
1674
|
+
].includes(entry?.outcome) || !Number.isSafeInteger(entry.count) || entry.count < 0)) throw new TypeError('Invalid Experience outcomes');
|
|
1675
|
+
return result.outcome_distribution;
|
|
1676
|
+
}, [
|
|
1677
|
+
uri,
|
|
1678
|
+
days
|
|
1679
|
+
]);
|
|
1680
|
+
const list = useDashboardQuery(loadList, requestOptions, enabled && directory, refreshGeneration);
|
|
1681
|
+
const sources = useDashboardQuery(loadSources, requestOptions, enabled && !directory && 'source' === tab, refreshGeneration);
|
|
1682
|
+
const trajectories = useDashboardQuery(loadTrajectories, requestOptions, enabled && !directory && 'impact' === tab, refreshGeneration);
|
|
1683
|
+
const outcomes = useDashboardQuery(loadOutcomes, requestOptions, enabled && !directory && 'impact' === tab, refreshGeneration);
|
|
1684
|
+
return {
|
|
1685
|
+
list,
|
|
1686
|
+
sources,
|
|
1687
|
+
trajectories,
|
|
1688
|
+
outcomes,
|
|
1689
|
+
page,
|
|
1690
|
+
setPage,
|
|
1691
|
+
days,
|
|
1692
|
+
setDays,
|
|
1693
|
+
tab,
|
|
1694
|
+
setTab
|
|
1695
|
+
};
|
|
1696
|
+
}
|
|
1697
|
+
function ExperienceDashboardPane({ uri, locale, requestOptions, refreshGeneration, onOpenUri }) {
|
|
1698
|
+
const browser = useExperienceDashboard({
|
|
1699
|
+
uri,
|
|
1700
|
+
requestOptions,
|
|
1701
|
+
refreshGeneration
|
|
1702
|
+
});
|
|
1703
|
+
const directory = isExperienceDirectory(uri);
|
|
1704
|
+
const status = (query)=>({
|
|
1705
|
+
loading: query.loading,
|
|
1706
|
+
error: query.error,
|
|
1707
|
+
onRetry: query.retry
|
|
1708
|
+
});
|
|
1709
|
+
return /*#__PURE__*/ jsx(ExperienceDashboard, {
|
|
1710
|
+
uri: uri,
|
|
1711
|
+
kind: directory ? 'directory' : 'file',
|
|
1712
|
+
locale: locale,
|
|
1713
|
+
onOpenUri: onOpenUri,
|
|
1714
|
+
data: {
|
|
1715
|
+
experiences: browser.list.data?.items,
|
|
1716
|
+
outcomes: browser.outcomes.data,
|
|
1717
|
+
trajectories: browser.trajectories.data?.items,
|
|
1718
|
+
trajectoryTotal: browser.trajectories.data?.total,
|
|
1719
|
+
sources: browser.sources.data
|
|
1720
|
+
},
|
|
1721
|
+
tab: browser.tab,
|
|
1722
|
+
days: browser.days,
|
|
1723
|
+
page: browser.page,
|
|
1724
|
+
startIndex: 20 * browser.page,
|
|
1725
|
+
hasMore: Boolean(directory ? browser.list.data?.hasMore : 'impact' === browser.tab && browser.trajectories.data?.has_more),
|
|
1726
|
+
states: {
|
|
1727
|
+
list: status(browser.list),
|
|
1728
|
+
sources: status(browser.sources),
|
|
1729
|
+
outcomes: status(browser.outcomes),
|
|
1730
|
+
trajectories: status(browser.trajectories)
|
|
1731
|
+
},
|
|
1732
|
+
onTabChange: browser.setTab,
|
|
1733
|
+
onDaysChange: browser.setDays,
|
|
1734
|
+
onPageChange: browser.setPage
|
|
1735
|
+
});
|
|
1736
|
+
}
|
|
1737
|
+
const wiki_conflicts_prefix = '<!-- CONFLICTS_CONTENT';
|
|
1738
|
+
const manualResolutionBasis = '\u4eba\u5de5\u4fee\u590d';
|
|
1739
|
+
const timestamp = (value)=>'number' == typeof value && Number.isSafeInteger(value) && value >= 0 && Number.isFinite(new Date(1000 * value).getTime());
|
|
1740
|
+
const locateWikiConflict = (document1, matchText)=>{
|
|
1741
|
+
const mask = (value)=>value.replace(/[^\r\n]/g, ' ');
|
|
1742
|
+
const body = document1.replace(/^(?:\uFEFF)?---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/, mask).replace(/<!--[\s\S]*?(?:-->|$)/g, mask);
|
|
1743
|
+
const start = matchText ? body.indexOf(matchText) : -1;
|
|
1744
|
+
if (start < 0) return {
|
|
1745
|
+
status: 'missing'
|
|
1746
|
+
};
|
|
1747
|
+
if (body.indexOf(matchText, start + 1) >= 0) return {
|
|
1748
|
+
status: 'ambiguous'
|
|
1749
|
+
};
|
|
1750
|
+
return {
|
|
1751
|
+
status: 'unique',
|
|
1752
|
+
start,
|
|
1753
|
+
end: start + matchText.length,
|
|
1754
|
+
line: document1.slice(0, start).split('\n').length
|
|
1755
|
+
};
|
|
1756
|
+
};
|
|
1757
|
+
const parseWikiFileConflicts = (content)=>{
|
|
1758
|
+
const start = content.lastIndexOf(wiki_conflicts_prefix);
|
|
1759
|
+
if (start < 0) return {
|
|
1760
|
+
status: 'missing'
|
|
1761
|
+
};
|
|
1762
|
+
const footer = content.slice(start).trimEnd();
|
|
1763
|
+
if (!footer.endsWith('-->')) return {
|
|
1764
|
+
status: 'invalid'
|
|
1765
|
+
};
|
|
1766
|
+
const document1 = content.slice(0, start);
|
|
1767
|
+
const source = footer.slice(wiki_conflicts_prefix.length, -3).trim();
|
|
1768
|
+
let parsed;
|
|
1769
|
+
try {
|
|
1770
|
+
parsed = source ? JSON.parse(`[${source}]`) : [];
|
|
1771
|
+
} catch {
|
|
1772
|
+
return {
|
|
1773
|
+
status: 'invalid'
|
|
1774
|
+
};
|
|
1775
|
+
}
|
|
1776
|
+
if (!Array.isArray(parsed)) return {
|
|
1777
|
+
status: 'invalid'
|
|
1778
|
+
};
|
|
1779
|
+
const records = [];
|
|
1780
|
+
for (const [index, raw] of parsed.entries()){
|
|
1781
|
+
if (!raw || 'object' != typeof raw || Array.isArray(raw)) return {
|
|
1782
|
+
status: 'invalid'
|
|
1783
|
+
};
|
|
1784
|
+
const value = raw;
|
|
1785
|
+
if ('string' != typeof value.match_text || !value.match_text || 'string' != typeof value.problem_description || 'string' != typeof value.reason || ![
|
|
1786
|
+
'high',
|
|
1787
|
+
'medium',
|
|
1788
|
+
'low'
|
|
1789
|
+
].includes(value.severity) || ![
|
|
1790
|
+
'unfixed',
|
|
1791
|
+
'fixed'
|
|
1792
|
+
].includes(value.state) || !timestamp(value.begin_time) || 'string' != typeof value.fix_plan || 'string' != typeof value.resolution_basis) return {
|
|
1793
|
+
status: 'invalid'
|
|
1794
|
+
};
|
|
1795
|
+
if ('fixed' === value.state ? !timestamp(value.fix_time) || value.fix_time < value.begin_time : null !== value.fix_time) return {
|
|
1796
|
+
status: 'invalid'
|
|
1797
|
+
};
|
|
1798
|
+
const matchText = value.match_text;
|
|
1799
|
+
const location = locateWikiConflict(document1, matchText);
|
|
1800
|
+
records.push({
|
|
1801
|
+
id: JSON.stringify([
|
|
1802
|
+
index,
|
|
1803
|
+
value.begin_time,
|
|
1804
|
+
matchText
|
|
1805
|
+
]),
|
|
1806
|
+
matchText,
|
|
1807
|
+
description: value.problem_description,
|
|
1808
|
+
reason: value.reason,
|
|
1809
|
+
severity: value.severity,
|
|
1810
|
+
state: value.state,
|
|
1811
|
+
beginTime: value.begin_time,
|
|
1812
|
+
fixTime: value.fix_time,
|
|
1813
|
+
fixPlan: value.fix_plan,
|
|
1814
|
+
resolutionBasis: value.resolution_basis,
|
|
1815
|
+
resolutionSource: 'fixed' === value.state && value.resolution_basis.trim() ? value.resolution_basis.trim() === manualResolutionBasis ? 'manual' : 'automatic' : void 0,
|
|
1816
|
+
location
|
|
1817
|
+
});
|
|
1818
|
+
}
|
|
1819
|
+
return {
|
|
1820
|
+
status: 'available',
|
|
1821
|
+
document: document1,
|
|
1822
|
+
records
|
|
1823
|
+
};
|
|
1824
|
+
};
|
|
1825
|
+
async function readWikiDashboard(client, uri, options) {
|
|
1826
|
+
const stat = await readDashboardStat(client, uri, options);
|
|
1827
|
+
uri = stat.uri;
|
|
1828
|
+
if (!stat.isDir) {
|
|
1829
|
+
const result = await client.request({
|
|
1830
|
+
method: 'GET',
|
|
1831
|
+
path: OV_PATHS.contentRead,
|
|
1832
|
+
query: {
|
|
1833
|
+
uri,
|
|
1834
|
+
limit: -1
|
|
1835
|
+
},
|
|
1836
|
+
options
|
|
1837
|
+
});
|
|
1838
|
+
if ('string' != typeof result) throw new TypeError('Invalid Wiki content response');
|
|
1839
|
+
const parsed = parseWikiFileConflicts(result);
|
|
1840
|
+
if ('invalid' === parsed.status) throw new TypeError('Invalid CONFLICTS_CONTENT metadata');
|
|
1841
|
+
return {
|
|
1842
|
+
kind: 'file',
|
|
1843
|
+
data: {
|
|
1844
|
+
path: uri,
|
|
1845
|
+
conflicts: 'available' === parsed.status ? parsed.records : []
|
|
1846
|
+
}
|
|
1847
|
+
};
|
|
1848
|
+
}
|
|
1849
|
+
const page = await client.requestEnvelope({
|
|
1850
|
+
method: 'GET',
|
|
1851
|
+
path: OV_PATHS.fsLs,
|
|
1852
|
+
query: {
|
|
1853
|
+
uri,
|
|
1854
|
+
recursive: true,
|
|
1855
|
+
output: 'agent',
|
|
1856
|
+
tags: [
|
|
1857
|
+
'has_conflict=true'
|
|
1858
|
+
],
|
|
1859
|
+
limit: 100,
|
|
1860
|
+
sort_by: 'mtime',
|
|
1861
|
+
sort_order: 'desc'
|
|
1862
|
+
},
|
|
1863
|
+
options
|
|
1864
|
+
});
|
|
1865
|
+
if (!Array.isArray(page.result)) throw new TypeError('Invalid Wiki conflict listing');
|
|
1866
|
+
const prefix = `${uri.replace(/\/+$/, '')}/`;
|
|
1867
|
+
const files = page.result.filter((entry)=>!entry.isDir && entry.uri.startsWith(prefix)).map((entry)=>entry.uri);
|
|
1868
|
+
return {
|
|
1869
|
+
kind: 'directory',
|
|
1870
|
+
data: {
|
|
1871
|
+
files,
|
|
1872
|
+
conflictFileCount: files.length,
|
|
1873
|
+
limited: page.hasMore || files.length >= 100
|
|
1874
|
+
}
|
|
1875
|
+
};
|
|
1876
|
+
}
|
|
1877
|
+
function useWikiDashboard({ uri, requestOptions, enabled = true, refreshGeneration = 0 }) {
|
|
1878
|
+
const load = useCallback((client, options)=>readWikiDashboard(client, uri, options), [
|
|
1879
|
+
uri
|
|
1880
|
+
]);
|
|
1881
|
+
return useDashboardQuery(load, requestOptions, enabled, refreshGeneration);
|
|
1882
|
+
}
|
|
1883
|
+
function WikiDashboardPane({ uri, kind, requestOptions, refreshGeneration, onOpenUri, onOpenDashboard, onEdit, onDiscuss, ...props }) {
|
|
1884
|
+
const query = useWikiDashboard({
|
|
1885
|
+
uri,
|
|
1886
|
+
requestOptions,
|
|
1887
|
+
refreshGeneration
|
|
1888
|
+
});
|
|
1889
|
+
const common = {
|
|
1890
|
+
...props,
|
|
1891
|
+
loading: query.loading,
|
|
1892
|
+
error: query.error,
|
|
1893
|
+
onRetry: query.retry
|
|
1894
|
+
};
|
|
1895
|
+
if ((query.data?.kind ?? kind) === 'file') return /*#__PURE__*/ createElement(WikiDashboard, {
|
|
1896
|
+
...common,
|
|
1897
|
+
key: uri,
|
|
1898
|
+
kind: "file",
|
|
1899
|
+
data: query.data?.kind === 'file' ? query.data.data : void 0,
|
|
1900
|
+
onOpenUri: onOpenUri && (()=>onOpenUri(uri)),
|
|
1901
|
+
onEdit: onEdit && ((conflict)=>onEdit(uri, conflict)),
|
|
1902
|
+
onDiscuss: onDiscuss && ((conflict)=>onDiscuss(uri, conflict))
|
|
1903
|
+
});
|
|
1904
|
+
return /*#__PURE__*/ createElement(WikiDashboard, {
|
|
1905
|
+
...common,
|
|
1906
|
+
key: uri,
|
|
1907
|
+
kind: "directory",
|
|
1908
|
+
data: query.data?.kind === 'directory' ? query.data.data : void 0,
|
|
1909
|
+
onOpenUri: onOpenUri,
|
|
1910
|
+
onOpenDashboard: onOpenDashboard,
|
|
1911
|
+
onEdit: onEdit
|
|
1912
|
+
});
|
|
1913
|
+
}
|
|
1914
|
+
function useBrowserDashboards({ rows, metadataRows, uri, rootUri, generation, locale, requestOptions, onOpenUri, dashboardKinds, renderDashboard, dashboardLabels }) {
|
|
1915
|
+
const [opened, setOpened] = useState(null);
|
|
1916
|
+
const load = useCallback((client, options)=>readCompileTargets(client, rootUri, options), [
|
|
1917
|
+
rootUri
|
|
1918
|
+
]);
|
|
1919
|
+
const wikiEnabled = void 0 === dashboardKinds || dashboardKinds.includes('wiki');
|
|
1920
|
+
const catalog = useDashboardQuery(load, requestOptions, wikiEnabled, generation);
|
|
1921
|
+
const loadHint = useCallback(async (client, options)=>uri ? readDashboardHint(client, uri, options) : void 0, [
|
|
1922
|
+
uri
|
|
1923
|
+
]);
|
|
1924
|
+
const hint = useDashboardQuery(loadHint, requestOptions, wikiEnabled && Boolean(uri), generation);
|
|
1925
|
+
const canonicalUri = hint.data?.uri ?? uri;
|
|
1926
|
+
const knownRows = canonicalUri && hint.data?.tags.includes('compile_target=true') ? [
|
|
1927
|
+
...metadataRows,
|
|
1928
|
+
{
|
|
1929
|
+
kind: 'entry',
|
|
1930
|
+
uri: canonicalUri,
|
|
1931
|
+
id: canonicalUri,
|
|
1932
|
+
name: canonicalUri,
|
|
1933
|
+
depth: 0,
|
|
1934
|
+
type: 'directory',
|
|
1935
|
+
tags: hint.data.tags
|
|
1936
|
+
}
|
|
1937
|
+
] : metadataRows;
|
|
1938
|
+
const unsupported = isOvClientError(catalog.error) && 404 === catalog.error.statusCode;
|
|
1939
|
+
const targets = catalog.data ?? [];
|
|
1940
|
+
const resolve = (target)=>{
|
|
1941
|
+
const match = resolveDashboard(target === uri ? canonicalUri ?? target : target, knownRows, targets);
|
|
1942
|
+
return match ? {
|
|
1943
|
+
...match,
|
|
1944
|
+
uri: target
|
|
1945
|
+
} : void 0;
|
|
1946
|
+
};
|
|
1947
|
+
const dashboards = new Map(rows.filter((row)=>'entry' === row.kind).filter((row)=>!row.disabled && false !== row.selectable).flatMap((row)=>{
|
|
1948
|
+
const dashboard = resolve(row.uri);
|
|
1949
|
+
return dashboard && (void 0 === dashboardKinds || dashboardKinds.includes(dashboard.kind)) ? [
|
|
1950
|
+
[
|
|
1951
|
+
row.uri,
|
|
1952
|
+
dashboard
|
|
1953
|
+
]
|
|
1954
|
+
] : [];
|
|
1955
|
+
}));
|
|
1956
|
+
const resolved = uri ? resolve(uri) : void 0;
|
|
1957
|
+
const current = resolved && (void 0 === dashboardKinds || dashboardKinds.includes(resolved.kind)) ? resolved : void 0;
|
|
1958
|
+
const open = useCallback((target)=>{
|
|
1959
|
+
setOpened(target);
|
|
1960
|
+
onOpenUri(target);
|
|
1961
|
+
}, [
|
|
1962
|
+
onOpenUri
|
|
1963
|
+
]);
|
|
1964
|
+
const close = useCallback(()=>setOpened(null), []);
|
|
1965
|
+
const text = dashboard_labels_dashboardLabels(locale, dashboardLabels);
|
|
1966
|
+
const title = current?.kind === 'experience' ? text.experienceTitle : text.wikiTitle;
|
|
1967
|
+
const tab = current && uri ? {
|
|
1968
|
+
key: dashboardKey(uri),
|
|
1969
|
+
title,
|
|
1970
|
+
icon: createElement('experience' === current.kind ? ExperienceBoardIcon : WikiBoardIcon, {
|
|
1971
|
+
width: 14,
|
|
1972
|
+
height: 14
|
|
1973
|
+
}),
|
|
1974
|
+
kind: 'content',
|
|
1975
|
+
content: renderDashboard?.(current) ?? ('experience' === current.kind ? createElement(ExperienceDashboardPane, {
|
|
1976
|
+
uri,
|
|
1977
|
+
locale,
|
|
1978
|
+
requestOptions,
|
|
1979
|
+
refreshGeneration: generation,
|
|
1980
|
+
onOpenUri: (target)=>{
|
|
1981
|
+
setOpened(null);
|
|
1982
|
+
onOpenUri(target);
|
|
1983
|
+
}
|
|
1984
|
+
}) : createElement(WikiDashboardPane, {
|
|
1985
|
+
uri,
|
|
1986
|
+
kind: knownRows.some((row)=>'entry' === row.kind && row.uri === canonicalUri && 'file' === row.type) ? 'file' : 'directory',
|
|
1987
|
+
locale,
|
|
1988
|
+
requestOptions,
|
|
1989
|
+
refreshGeneration: generation,
|
|
1990
|
+
onOpenUri: (target)=>{
|
|
1991
|
+
setOpened(null);
|
|
1992
|
+
onOpenUri(target);
|
|
1993
|
+
},
|
|
1994
|
+
onOpenDashboard: open
|
|
1995
|
+
}))
|
|
1996
|
+
} : void 0;
|
|
1997
|
+
return {
|
|
1998
|
+
dashboards,
|
|
1999
|
+
tab,
|
|
2000
|
+
isOpen: null !== uri && opened === uri && Boolean(current),
|
|
2001
|
+
open,
|
|
2002
|
+
close,
|
|
2003
|
+
error: unsupported ? null : catalog.error,
|
|
2004
|
+
retry: catalog.retry
|
|
972
2005
|
};
|
|
973
2006
|
}
|
|
974
2007
|
function knownDirectories(rootUri, dirs) {
|
|
@@ -994,6 +2027,28 @@ function useKnownFile(dirs) {
|
|
|
994
2027
|
files
|
|
995
2028
|
]);
|
|
996
2029
|
}
|
|
2030
|
+
function useTreeMetadata(rootUri, dirs) {
|
|
2031
|
+
const directoryUris = useMemo(()=>knownDirectories(rootUri, dirs), [
|
|
2032
|
+
rootUri,
|
|
2033
|
+
dirs
|
|
2034
|
+
]);
|
|
2035
|
+
const isKnownFile = useKnownFile(dirs);
|
|
2036
|
+
const metadataRows = useMemo(()=>Object.values(dirs).flatMap((dir)=>dir.entries.map((entry)=>toEntryRow(entry, 0, false))), [
|
|
2037
|
+
dirs
|
|
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
|
+
]);
|
|
2045
|
+
return {
|
|
2046
|
+
directoryUris,
|
|
2047
|
+
isKnownFile,
|
|
2048
|
+
metadataRows,
|
|
2049
|
+
accessOf
|
|
2050
|
+
};
|
|
2051
|
+
}
|
|
997
2052
|
const ids = new WeakMap();
|
|
998
2053
|
let nextId = 1;
|
|
999
2054
|
function idOf(value) {
|
|
@@ -1064,6 +2119,7 @@ async function loadPage(client, uri, offset, signal, requestOptions) {
|
|
|
1064
2119
|
query: {
|
|
1065
2120
|
uri,
|
|
1066
2121
|
output: 'agent',
|
|
2122
|
+
include_tags: true,
|
|
1067
2123
|
node_limit: PAGE_SIZE,
|
|
1068
2124
|
offset
|
|
1069
2125
|
},
|
|
@@ -1189,7 +2245,16 @@ function useFsTree({ client, rootUri, defaultExpandedUri, requestOptions }) {
|
|
|
1189
2245
|
setExpanded((prev)=>new Set(prev).add(uri));
|
|
1190
2246
|
if (!dirsRef.current[uri]) await fetchPage(uri, 0);
|
|
1191
2247
|
},
|
|
2248
|
+
openPath: async (uri)=>{
|
|
2249
|
+
const current = scope.current();
|
|
2250
|
+
for (const parent of expandChain(rootUri, uri)){
|
|
2251
|
+
if (current.controller.signal.aborted || scope.current().key !== current.key) return;
|
|
2252
|
+
setExpanded((prev)=>new Set(prev).add(parent));
|
|
2253
|
+
if (!dirsRef.current[parent]) await fetchPage(parent, 0);
|
|
2254
|
+
}
|
|
2255
|
+
},
|
|
1192
2256
|
toggleExpand: async (row)=>{
|
|
2257
|
+
if (row.disabled) return;
|
|
1193
2258
|
const isOpen = expandedRef.current.has(row.uri);
|
|
1194
2259
|
setExpanded((prev)=>{
|
|
1195
2260
|
const next = new Set(prev);
|
|
@@ -1226,24 +2291,18 @@ function useFsTree({ client, rootUri, defaultExpandedUri, requestOptions }) {
|
|
|
1226
2291
|
dirs,
|
|
1227
2292
|
expanded
|
|
1228
2293
|
]);
|
|
1229
|
-
const directoryUris = useMemo(()=>knownDirectories(rootUri, dirs), [
|
|
1230
|
-
rootUri,
|
|
1231
|
-
dirs
|
|
1232
|
-
]);
|
|
1233
|
-
const isKnownFile = useKnownFile(dirs);
|
|
1234
2294
|
return {
|
|
1235
2295
|
rows,
|
|
2296
|
+
...useTreeMetadata(rootUri, dirs),
|
|
1236
2297
|
isReady,
|
|
1237
2298
|
error,
|
|
1238
|
-
directoryUris,
|
|
1239
|
-
isKnownFile,
|
|
1240
2299
|
...state
|
|
1241
2300
|
};
|
|
1242
2301
|
}
|
|
1243
|
-
function useFileBrowser({ rootUri, locale: localeProp, 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 }) {
|
|
1244
2303
|
const locale = useLocale(localeProp);
|
|
2304
|
+
const text = resolveLabels(accessLabels, locale);
|
|
1245
2305
|
const client = useOvClient();
|
|
1246
|
-
const [query, setQuery] = useState('');
|
|
1247
2306
|
const [pendingUri, setPendingUri] = useState(currentUri);
|
|
1248
2307
|
const [refreshing, setRefreshing] = useState(false);
|
|
1249
2308
|
const [refreshGeneration, setRefreshGeneration] = useState(0);
|
|
@@ -1253,14 +2312,8 @@ function useFileBrowser({ rootUri, locale: localeProp, controlled = false, summa
|
|
|
1253
2312
|
defaultExpandedUri,
|
|
1254
2313
|
requestOptions
|
|
1255
2314
|
});
|
|
1256
|
-
const search = useFsSearch({
|
|
1257
|
-
client,
|
|
1258
|
-
query,
|
|
1259
|
-
targetUri: rootUri,
|
|
1260
|
-
generation: refreshGeneration,
|
|
1261
|
-
requestOptions
|
|
1262
|
-
});
|
|
1263
2315
|
const activeUri = controlled ? currentUri : pendingUri;
|
|
2316
|
+
const blocked = tree.accessOf(activeUri);
|
|
1264
2317
|
const resourceType = null === activeUri ? 'file' : tree.directoryUris.has(activeUri) ? 'directory' : tree.isKnownFile(activeUri) ? 'file' : 'unknown';
|
|
1265
2318
|
const openTree = tree.open;
|
|
1266
2319
|
const notifyUriChange = useRef(onUriChange);
|
|
@@ -1274,32 +2327,46 @@ function useFileBrowser({ rootUri, locale: localeProp, controlled = false, summa
|
|
|
1274
2327
|
]);
|
|
1275
2328
|
const preview = useFsPreview({
|
|
1276
2329
|
client,
|
|
1277
|
-
uri: activeUri,
|
|
2330
|
+
uri: blocked ? null : activeUri,
|
|
1278
2331
|
resourceType,
|
|
1279
2332
|
labels: summaryLabels ?? resolveSummaryLabels(locale),
|
|
1280
|
-
formatCharacterCount: formatCharacterCount ??
|
|
2333
|
+
formatCharacterCount: formatCharacterCount ?? text.formatCharacterCount,
|
|
1281
2334
|
openParent,
|
|
1282
2335
|
generation: refreshGeneration,
|
|
1283
2336
|
requestOptions
|
|
1284
2337
|
});
|
|
1285
|
-
const rows =
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
return index < 0 ? activeUri : `${activeUri}#${index}`;
|
|
1296
|
-
}, [
|
|
1297
|
-
activeUri,
|
|
1298
|
-
search.results
|
|
2338
|
+
const rows = tree.rows;
|
|
2339
|
+
const openPath = tree.openPath;
|
|
2340
|
+
const openResource = useMemo(()=>async (uri)=>{
|
|
2341
|
+
setPendingUri(uri);
|
|
2342
|
+
notifyUriChange.current(uri);
|
|
2343
|
+
const directory = tree.directoryUris.has(uri) ? uri : uri.slice(0, uri.lastIndexOf('/'));
|
|
2344
|
+
await openPath(directory);
|
|
2345
|
+
}, [
|
|
2346
|
+
openPath,
|
|
2347
|
+
tree.directoryUris
|
|
1299
2348
|
]);
|
|
2349
|
+
const boards = useBrowserDashboards({
|
|
2350
|
+
rows,
|
|
2351
|
+
metadataRows: tree.metadataRows,
|
|
2352
|
+
uri: blocked ? null : activeUri,
|
|
2353
|
+
rootUri,
|
|
2354
|
+
generation: refreshGeneration,
|
|
2355
|
+
locale,
|
|
2356
|
+
requestOptions,
|
|
2357
|
+
onOpenUri: openResource,
|
|
2358
|
+
dashboardKinds,
|
|
2359
|
+
renderDashboard,
|
|
2360
|
+
dashboardLabels
|
|
2361
|
+
});
|
|
2362
|
+
const selectedId = activeUri;
|
|
1300
2363
|
const { open, toggleExpand, loadMore, retry, refresh } = tree;
|
|
2364
|
+
const { open: openDashboard, close: closeDashboard } = boards;
|
|
2365
|
+
const setActiveKey = preview.setActiveKey;
|
|
1301
2366
|
const actions = useMemo(()=>({
|
|
1302
2367
|
select: async (row)=>{
|
|
2368
|
+
if (row.disabled || false === row.selectable) return;
|
|
2369
|
+
closeDashboard();
|
|
1303
2370
|
setPendingUri(row.uri);
|
|
1304
2371
|
notifyUriChange.current(row.uri);
|
|
1305
2372
|
if ('directory' === row.type) await open(row.uri);
|
|
@@ -1322,40 +2389,1286 @@ function useFileBrowser({ rootUri, locale: localeProp, controlled = false, summa
|
|
|
1322
2389
|
setRefreshing(false);
|
|
1323
2390
|
}
|
|
1324
2391
|
},
|
|
1325
|
-
|
|
1326
|
-
|
|
2392
|
+
openDashboard: openDashboard,
|
|
2393
|
+
setPreviewKey: (key)=>{
|
|
2394
|
+
if (activeUri && key === dashboardKey(activeUri)) openDashboard(activeUri);
|
|
2395
|
+
else {
|
|
2396
|
+
closeDashboard();
|
|
2397
|
+
setActiveKey(key);
|
|
2398
|
+
}
|
|
2399
|
+
}
|
|
1327
2400
|
}), [
|
|
1328
|
-
|
|
2401
|
+
setActiveKey,
|
|
1329
2402
|
open,
|
|
1330
2403
|
toggleExpand,
|
|
1331
2404
|
loadMore,
|
|
1332
2405
|
retry,
|
|
1333
|
-
refresh
|
|
2406
|
+
refresh,
|
|
2407
|
+
openDashboard,
|
|
2408
|
+
closeDashboard,
|
|
2409
|
+
activeUri
|
|
1334
2410
|
]);
|
|
2411
|
+
const previewError = blocked ? new Error(text['denied' === blocked ? 'accessDenied' : 'traverseOnly']) : preview.error;
|
|
1335
2412
|
return {
|
|
1336
2413
|
rows,
|
|
1337
2414
|
selectedId,
|
|
1338
2415
|
currentUri: activeUri,
|
|
1339
2416
|
isReady: tree.isReady,
|
|
1340
|
-
error: tree.error ??
|
|
2417
|
+
error: tree.error ?? previewError,
|
|
1341
2418
|
treeError: tree.error,
|
|
1342
|
-
previewError
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
2419
|
+
previewError,
|
|
2420
|
+
dashboards: boards.dashboards,
|
|
2421
|
+
dashboardError: boards.error,
|
|
2422
|
+
previewTabs: mergeDashboardTab(preview.tabs, boards.tab, Boolean(blocked)),
|
|
2423
|
+
previewKey: boards.isOpen && activeUri ? dashboardKey(activeUri) : preview.activeKey,
|
|
1346
2424
|
previewFooter: preview.footer,
|
|
1347
|
-
query,
|
|
1348
|
-
searching: null !== search.results,
|
|
1349
|
-
searchLoading: search.loading,
|
|
1350
2425
|
refreshing,
|
|
1351
2426
|
actions
|
|
1352
2427
|
};
|
|
1353
2428
|
}
|
|
1354
|
-
function
|
|
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 }) {
|
|
1355
3668
|
const locale = useLocale(localeProp);
|
|
3669
|
+
const [searchOpen, setSearchOpen] = useState(false);
|
|
1356
3670
|
const [internalUri, setInternalUri] = useState(()=>defaultUri ?? null);
|
|
1357
3671
|
const [preferredTreeWidth, setPreferredTreeWidth] = useState(352);
|
|
1358
|
-
const searchId = useId();
|
|
1359
3672
|
const isControlled = void 0 !== uri;
|
|
1360
3673
|
const currentUri = isControlled ? uri : internalUri;
|
|
1361
3674
|
const text = resolveLabels(labels, locale);
|
|
@@ -1385,88 +3698,115 @@ function FileBrowser({ uri, defaultUri, rootUri = 'viking://', defaultExpandedUr
|
|
|
1385
3698
|
const browser = useFileBrowser({
|
|
1386
3699
|
rootUri,
|
|
1387
3700
|
locale,
|
|
3701
|
+
dashboardKinds,
|
|
3702
|
+
renderDashboard,
|
|
3703
|
+
dashboardLabels: labels?.dashboard,
|
|
1388
3704
|
controlled: isControlled,
|
|
1389
3705
|
summaryLabels,
|
|
1390
3706
|
formatCharacterCount: text.formatCharacterCount,
|
|
3707
|
+
accessLabels: {
|
|
3708
|
+
accessDenied: text.accessDenied,
|
|
3709
|
+
traverseOnly: text.traverseOnly
|
|
3710
|
+
},
|
|
1391
3711
|
defaultExpandedUri,
|
|
1392
3712
|
currentUri,
|
|
1393
3713
|
requestOptions,
|
|
1394
3714
|
onUriChange: changeUri
|
|
1395
3715
|
});
|
|
1396
|
-
|
|
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, {
|
|
1397
3725
|
locale: locale,
|
|
1398
|
-
children:
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
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,
|
|
1422
3769
|
locale: locale,
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
copyContent: text.copyContent,
|
|
1450
|
-
download: text.download,
|
|
1451
|
-
viewSwitch: text.viewSwitch
|
|
1452
|
-
},
|
|
1453
|
-
onActiveKeyChange: browser.actions.setPreviewKey
|
|
1454
|
-
}) : browser.currentUri && browser.previewError ? /*#__PURE__*/ jsx("div", {
|
|
1455
|
-
className: "ov-file-browser__detail",
|
|
1456
|
-
children: /*#__PURE__*/ jsx(FileBrowserFailure, {
|
|
1457
|
-
message: text.error,
|
|
1458
|
-
detail: browser.previewError.message,
|
|
1459
|
-
retryLabel: text.retry,
|
|
1460
|
-
onRetry: ()=>void browser.actions.refresh()
|
|
1461
|
-
})
|
|
1462
|
-
}) : /*#__PURE__*/ jsx("div", {
|
|
1463
|
-
className: "ov-file-browser__detail",
|
|
1464
|
-
children: /*#__PURE__*/ jsx(FileBrowserEmpty, {
|
|
1465
|
-
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
|
+
})
|
|
1466
3796
|
})
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
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
|
+
]
|
|
1470
3810
|
});
|
|
1471
3811
|
}
|
|
1472
|
-
export { FileBrowser
|
|
3812
|
+
export { FileBrowser };
|