@svadmin/ui 0.72.0 → 0.74.0
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 +62 -22
- package/THIRD_PARTY_NOTICES.md +52 -0
- package/dist/app.css +8488 -6297
- package/dist/app.theme.css +8514 -6374
- package/dist/class-properties.generated.d.ts +1 -0
- package/dist/class-properties.generated.js +2 -0
- package/dist/classnames.d.ts +8 -0
- package/dist/classnames.js +26 -0
- package/dist/component-registry.d.ts +14977 -0
- package/dist/component-registry.js +216 -0
- package/dist/components/AboutSettings.svelte +7 -1
- package/dist/components/AboutSettings.svelte.d.ts +4 -1
- package/dist/components/ActivityFeed.svelte +9 -3
- package/dist/components/ActivityFeed.svelte.d.ts +3 -0
- package/dist/components/AdminApp.svelte +19 -10
- package/dist/components/AdminApp.svelte.d.ts +6 -0
- package/dist/components/ApiSettings.svelte +41 -5
- package/dist/components/ApprovalCenter.svelte +283 -0
- package/dist/components/ApprovalCenter.svelte.d.ts +10 -0
- package/dist/components/AuditLogViewer.svelte +3 -1
- package/dist/components/AutoForm.svelte +53 -9
- package/dist/components/AutoForm.svelte.d.ts +3 -0
- package/dist/components/AutoTable.svelte +846 -43
- package/dist/components/AutoTable.svelte.d.ts +22 -1
- package/dist/components/BoundRecordDetailDrawer.svelte +17 -2
- package/dist/components/BoundRecordDetailDrawer.svelte.d.ts +4 -0
- package/dist/components/Breadcrumbs.svelte +11 -1
- package/dist/components/CancelTaskButton.svelte +12 -42
- package/dist/components/ColorPicker.svelte +73 -0
- package/dist/components/ColorPicker.svelte.d.ts +15 -0
- package/dist/components/ComboboxField.svelte +143 -34
- package/dist/components/ComboboxField.svelte.d.ts +4 -2
- package/dist/components/CreatePage.svelte +4 -0
- package/dist/components/CreatePage.svelte.d.ts +2 -0
- package/dist/components/DashboardView.svelte +99 -0
- package/dist/components/DashboardView.svelte.d.ts +14 -0
- package/dist/components/DatePicker.svelte +10 -0
- package/dist/components/DatePicker.svelte.d.ts +6 -0
- package/dist/components/DateRangeInput.svelte +155 -0
- package/dist/components/DateRangeInput.svelte.d.ts +35 -0
- package/dist/components/DateRangePicker.svelte +10 -0
- package/dist/components/DateRangePicker.svelte.d.ts +6 -0
- package/dist/components/DateTimeInput.svelte +134 -0
- package/dist/components/DateTimeInput.svelte.d.ts +27 -0
- package/dist/components/DateTimePicker.svelte +10 -0
- package/dist/components/DateTimePicker.svelte.d.ts +6 -0
- package/dist/components/DevTools.svelte +36 -28
- package/dist/components/DevframePanel.svelte +123 -0
- package/dist/components/DevframePanel.svelte.d.ts +3 -0
- package/dist/components/EditPage.svelte +4 -0
- package/dist/components/EditPage.svelte.d.ts +2 -0
- package/dist/components/ErrorSummary.svelte +52 -0
- package/dist/components/ErrorSummary.svelte.d.ts +14 -0
- package/dist/components/FieldRenderer.svelte +172 -99
- package/dist/components/FileUpload.svelte +412 -0
- package/dist/components/FileUpload.svelte.d.ts +43 -0
- package/dist/components/FilterBuilder.svelte +244 -265
- package/dist/components/FilterBuilder.svelte.d.ts +3 -0
- package/dist/components/FilterCollectionInput.svelte +80 -0
- package/dist/components/FilterCollectionInput.svelte.d.ts +10 -0
- package/dist/components/GanttChart.svelte +66 -14
- package/dist/components/GanttChart.svelte.d.ts +6 -0
- package/dist/components/Header.svelte +18 -2
- package/dist/components/ImportWizard.svelte +509 -100
- package/dist/components/ImportWizard.svelte.d.ts +11 -0
- package/dist/components/JsonSchemaForm.svelte +322 -98
- package/dist/components/JsonSchemaForm.svelte.d.ts +18 -1
- package/dist/components/KanbanBoard.svelte +195 -40
- package/dist/components/KanbanBoard.svelte.d.ts +9 -2
- package/dist/components/Layout.svelte +4 -3
- package/dist/components/LazyAutoForm.svelte +28 -0
- package/dist/components/LazyAutoForm.svelte.d.ts +8 -0
- package/dist/components/LazyAutoTable.svelte +28 -0
- package/dist/components/LazyAutoTable.svelte.d.ts +8 -0
- package/dist/components/LazySettingsPage.svelte +28 -0
- package/dist/components/LazySettingsPage.svelte.d.ts +8 -0
- package/dist/components/LazyShowPage.svelte +28 -0
- package/dist/components/LazyShowPage.svelte.d.ts +8 -0
- package/dist/components/LazyTaskQueueDrawer.svelte.d.ts +1 -1
- package/dist/components/ListPage.svelte +9 -1
- package/dist/components/ListPage.svelte.d.ts +5 -0
- package/dist/components/LoginPage.svelte +2 -2
- package/dist/components/MoneyInput.svelte +25 -0
- package/dist/components/MoneyInput.svelte.d.ts +20 -0
- package/dist/components/MultiTabKeepAlive.svelte +11 -6
- package/dist/components/NotificationsSettings.svelte +69 -2
- package/dist/components/NotificationsSettings.svelte.d.ts +25 -1
- package/dist/components/NumberInput.svelte +25 -0
- package/dist/components/NumberInput.svelte.d.ts +19 -0
- package/dist/components/NumericInput.svelte +110 -0
- package/dist/components/NumericInput.svelte.d.ts +23 -0
- package/dist/components/PercentInput.svelte +25 -0
- package/dist/components/PercentInput.svelte.d.ts +20 -0
- package/dist/components/PermissionMatrix.svelte +20 -1
- package/dist/components/PivotExportButton.svelte +157 -0
- package/dist/components/PivotExportButton.svelte.d.ts +16 -0
- package/dist/components/PivotTable.svelte +229 -74
- package/dist/components/PivotTable.svelte.d.ts +19 -2
- package/dist/components/QuickEditDrawer.svelte +4 -1
- package/dist/components/QuickEditDrawer.svelte.d.ts +2 -0
- package/dist/components/RadioGroup.svelte +121 -0
- package/dist/components/RadioGroup.svelte.d.ts +43 -0
- package/dist/components/RangeSlider.svelte +127 -0
- package/dist/components/RangeSlider.svelte.d.ts +22 -0
- package/dist/components/Rate.svelte +121 -0
- package/dist/components/Rate.svelte.d.ts +16 -0
- package/dist/components/RecordDetailDrawer.svelte +6 -2
- package/dist/components/RecordDetailDrawer.svelte.d.ts +4 -0
- package/dist/components/RelationPicker.svelte +341 -0
- package/dist/components/RelationPicker.svelte.d.ts +26 -0
- package/dist/components/ResourceOperationsPage.svelte +25 -19
- package/dist/components/ResourceOperationsPage.svelte.d.ts +5 -1
- package/dist/components/RetryTaskButton.svelte +12 -42
- package/dist/components/SecuritySettings.svelte +29 -5
- package/dist/components/SegmentedControl.svelte +145 -0
- package/dist/components/SegmentedControl.svelte.d.ts +38 -0
- package/dist/components/SettingsPage.svelte +3 -1
- package/dist/components/SettingsPage.svelte.d.ts +1 -0
- package/dist/components/ShowInferencer.svelte +4 -2
- package/dist/components/ShowInferencer.svelte.d.ts +2 -0
- package/dist/components/ShowPage.svelte +25 -6
- package/dist/components/ShowPage.svelte.d.ts +5 -0
- package/dist/components/Sidebar.svelte +5 -5
- package/dist/components/Slider.svelte +100 -0
- package/dist/components/Slider.svelte.d.ts +19 -0
- package/dist/components/SpreadsheetView.svelte +1037 -124
- package/dist/components/SpreadsheetView.svelte.d.ts +19 -0
- package/dist/components/StepsForm.svelte +5 -5
- package/dist/components/SvarAutoTable.svelte +39 -0
- package/dist/components/SvarAutoTable.svelte.d.ts +13 -0
- package/dist/components/SvarDataGrid.svelte +362 -0
- package/dist/components/SvarDataGrid.svelte.d.ts +57 -0
- package/dist/components/SvarGridCell.svelte +10 -0
- package/dist/components/SvarGridCell.svelte.d.ts +7 -0
- package/dist/components/SvarResourceRead.svelte +50 -0
- package/dist/components/SvarResourceRead.svelte.d.ts +23 -0
- package/dist/components/SvarResourceTable.svelte +63 -0
- package/dist/components/SvarResourceTable.svelte.d.ts +42 -0
- package/dist/components/SvarResourceTableView.svelte +364 -0
- package/dist/components/SvarResourceTableView.svelte.d.ts +4 -0
- package/dist/components/TagsInput.svelte +178 -0
- package/dist/components/TagsInput.svelte.d.ts +17 -0
- package/dist/components/TaskQueueDrawer.svelte +72 -16
- package/dist/components/TimePicker.svelte +10 -0
- package/dist/components/TimePicker.svelte.d.ts +6 -0
- package/dist/components/TreeSelect.svelte +440 -46
- package/dist/components/TreeSelect.svelte.d.ts +6 -1
- package/dist/components/TreeTable.svelte +484 -82
- package/dist/components/TreeTable.svelte.d.ts +24 -1
- package/dist/components/TwoFactorAuthPage.svelte +85 -48
- package/dist/components/TwoFactorAuthPage.svelte.d.ts +12 -1
- package/dist/components/VirtualTable.svelte +116 -20
- package/dist/components/VirtualTable.svelte.d.ts +5 -0
- package/dist/components/account/CompanyProfilePage.svelte +74 -38
- package/dist/components/account/GetStartedPage.svelte +2 -1
- package/dist/components/account/ImportMembersPage.svelte +22 -167
- package/dist/components/account/ImportMembersPage.svelte.d.ts +4 -1
- package/dist/components/account/MemberDirectory.svelte +156 -0
- package/dist/components/account/MemberDirectory.svelte.d.ts +19 -0
- package/dist/components/account/MembersStarterPage.svelte +9 -55
- package/dist/components/account/MembersStarterPage.svelte.d.ts +5 -1
- package/dist/components/account/SecurityLogPage.svelte +4 -1
- package/dist/components/account/SettingsPlainPage.svelte +7 -38
- package/dist/components/account/SettingsPlainPage.svelte.d.ts +4 -1
- package/dist/components/account/SettingsSidebarPage.svelte +7 -20
- package/dist/components/account/TeamMembersPage.svelte +4 -20
- package/dist/components/account/TeamMembersPage.svelte.d.ts +5 -1
- package/dist/components/account/UserProfilePage.svelte +3 -25
- package/dist/components/account/UserProfilePage.svelte.d.ts +17 -2
- package/dist/components/auto-table-grid.d.ts +26 -0
- package/dist/components/auto-table-grid.js +1 -0
- package/dist/components/buttons/ExportButton.svelte +56 -12
- package/dist/components/buttons/ExportButton.svelte.d.ts +11 -0
- package/dist/components/content/ContentPageHeader.svelte +12 -9
- package/dist/components/content/ContentPageShell.svelte +6 -9
- package/dist/components/content/FileBrowser.svelte +35 -0
- package/dist/components/content/FileBrowser.svelte.d.ts +14 -0
- package/dist/components/content/MetricBlock.svelte +10 -13
- package/dist/components/content/NetworkTable.svelte +3 -3
- package/dist/components/content/NetworkTable.svelte.d.ts +1 -0
- package/dist/components/content/PageToolbar.svelte +6 -10
- package/dist/components/content/ReferenceNotice.svelte +5 -0
- package/dist/components/content/ReferenceNotice.svelte.d.ts +18 -0
- package/dist/components/content/SectionHeader.svelte +8 -13
- package/dist/components/content/SettingsFieldRow.svelte +9 -23
- package/dist/components/content/SettingsGroup.svelte +11 -27
- package/dist/components/content/StatusBadge.svelte +3 -8
- package/dist/components/content/WorkspaceLayout.svelte +8 -12
- package/dist/components/enterprise/filter-tree.d.ts +48 -0
- package/dist/components/enterprise/filter-tree.js +224 -0
- package/dist/components/enterprise/json-schema-form.d.ts +39 -0
- package/dist/components/enterprise/json-schema-form.js +195 -0
- package/dist/components/enterprise/recursive-schema-form.d.ts +30 -0
- package/dist/components/enterprise/recursive-schema-form.js +196 -0
- package/dist/components/enterprise/spreadsheet-formula.d.ts +12 -0
- package/dist/components/enterprise/spreadsheet-formula.js +245 -0
- package/dist/components/export-reference.d.ts +2 -0
- package/dist/components/export-reference.js +9 -0
- package/dist/components/fieldComponentMap.js +2 -0
- package/dist/components/fields/DateField.svelte +12 -12
- package/dist/components/fields/date-display.d.ts +6 -0
- package/dist/components/fields/date-display.js +24 -0
- package/dist/components/fields/date-field-value.d.ts +2 -0
- package/dist/components/fields/date-field-value.js +13 -0
- package/dist/components/json-schema-form-state.d.ts +37 -0
- package/dist/components/json-schema-form-state.js +168 -0
- package/dist/components/network/TeamCrewTablePage.svelte +6 -1
- package/dist/components/network/UserCardsNFTPage.svelte +2 -0
- package/dist/components/profile/PublicProfilePage.svelte +2 -0
- package/dist/components/record-detail.svelte.d.ts +3 -0
- package/dist/components/record-detail.svelte.js +10 -4
- package/dist/components/saved-list-views.d.ts +74 -1
- package/dist/components/saved-list-views.js +191 -3
- package/dist/components/svar-grid-cells.d.ts +14 -0
- package/dist/components/svar-grid-cells.js +2 -0
- package/dist/components/svar-grid-contract.d.ts +80 -0
- package/dist/components/svar-grid-contract.js +159 -0
- package/dist/components/svar-grid-interactions.d.ts +54 -0
- package/dist/components/svar-grid-interactions.js +174 -0
- package/dist/components/svar-grid-loading.d.ts +40 -0
- package/dist/components/svar-grid-loading.js +180 -0
- package/dist/components/svar-grid-operations.d.ts +37 -0
- package/dist/components/svar-grid-operations.js +186 -0
- package/dist/components/svar-grid-panes.d.ts +34 -0
- package/dist/components/svar-grid-panes.js +66 -0
- package/dist/components/svar-grid-resource-loading.d.ts +18 -0
- package/dist/components/svar-grid-resource-loading.js +68 -0
- package/dist/components/svar-grid-resource-session.svelte.d.ts +29 -0
- package/dist/components/svar-grid-resource-session.svelte.js +61 -0
- package/dist/components/table-contract.d.ts +12 -1
- package/dist/components/task-action.svelte.d.ts +13 -0
- package/dist/components/task-action.svelte.js +73 -0
- package/dist/components/ui/DesignSystemShowcase.svelte +164 -0
- package/dist/components/ui/DesignSystemShowcase.svelte.d.ts +3 -0
- package/dist/components/ui/badge/badge-variants.d.ts +3 -2
- package/dist/components/ui/badge/badge-variants.js +2 -1
- package/dist/components/ui/badge/badge.svelte +3 -0
- package/dist/components/ui/badge/badge.svelte.d.ts +1 -0
- package/dist/components/ui/button/button-variants.d.ts +5 -4
- package/dist/components/ui/button/button-variants.js +7 -2
- package/dist/components/ui/checkbox/checkbox.svelte +5 -1
- package/dist/components/ui/checkbox/checkbox.svelte.d.ts +5 -1
- package/dist/components/ui/input/input.svelte +8 -6
- package/dist/components/ui/input/input.svelte.d.ts +4 -1
- package/dist/components/ui/select/select.svelte +4 -1
- package/dist/components/ui/select/select.svelte.d.ts +2 -1
- package/dist/components/ui/textarea/textarea.svelte +2 -1
- package/dist/components.css +3110 -0
- package/dist/date-input-policy.d.ts +4 -0
- package/dist/date-input-policy.js +35 -0
- package/dist/design-contract.d.ts +13 -0
- package/dist/design-contract.js +11 -0
- package/dist/design-system-components.d.ts +100 -0
- package/dist/design-system-components.js +101 -0
- package/dist/design-system.d.ts +15 -0
- package/dist/design-system.js +7 -0
- package/dist/devtools-bridge.d.ts +80 -0
- package/dist/devtools-bridge.js +92 -0
- package/dist/index.d.ts +50 -1
- package/dist/index.js +32 -2
- package/dist/page-chrome.css +12 -49
- package/dist/recipes.d.ts +960 -0
- package/dist/recipes.js +264 -0
- package/dist/rendering/index.d.ts +87 -0
- package/dist/rendering/index.js +117 -0
- package/dist/styles/aliases.css +49 -0
- package/dist/styles/compatibility.epilogue.css +384 -0
- package/dist/styles/compatibility.prelude.css +5513 -0
- package/dist/styles/conditional-states.css +28 -0
- package/dist/styles/conditional-utilities.css +22 -0
- package/dist/tree-table-model.d.ts +24 -0
- package/dist/tree-table-model.js +111 -0
- package/dist/utils.d.ts +1 -1
- package/dist/utils.js +1 -1
- package/package.json +40 -10
- package/styles-compatibility.json +12 -0
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { bindResourceRendering, type ResourceRendering } from '../rendering/index.js';
|
|
3
|
+
import type { AutoTableGridState } from './auto-table-grid.js';
|
|
2
4
|
import { definedOptions } from '@svadmin/core/options';
|
|
3
5
|
|
|
4
6
|
import { onDestroy, tick, untrack } from 'svelte';
|
|
@@ -23,6 +25,7 @@
|
|
|
23
25
|
import { createAtom, useSelector } from '@tanstack/svelte-store';
|
|
24
26
|
import {
|
|
25
27
|
column_getCanSort,
|
|
28
|
+
column_getSize,
|
|
26
29
|
column_getIsSorted,
|
|
27
30
|
column_toggleSorting,
|
|
28
31
|
header_getSize,
|
|
@@ -37,9 +40,16 @@
|
|
|
37
40
|
cell_getValue,
|
|
38
41
|
} from '@tanstack/table-core/static-functions';
|
|
39
42
|
|
|
40
|
-
import { captureAdminContext, DeleteManyPartialError, getAdminOptions, getContractFormFields, useNavigation, useParsed, useResourceContract, useList, useDeleteMany, downloadData } from '@svadmin/core';
|
|
43
|
+
import { captureAdminContext, captureAuthSession, useGetIdentity, DeleteManyPartialError, getAdminOptions, getContractFormFields, useNavigation, useParsed, useResourceContract, useList, useDeleteMany, downloadData, type ExportFormat, type TaskProvider } from '@svadmin/core';
|
|
41
44
|
import { decodeBaseRecord, snapshotPlainData, parseContractRouteId, formatContractRouteId } from '@svadmin/core/schema';
|
|
42
|
-
import {
|
|
45
|
+
import {
|
|
46
|
+
checkedTableRows,
|
|
47
|
+
copyTableRecord,
|
|
48
|
+
tableExportRows,
|
|
49
|
+
tableRowKey,
|
|
50
|
+
type BatchSelection,
|
|
51
|
+
type TableRecord,
|
|
52
|
+
} from './table-contract';
|
|
43
53
|
import type {
|
|
44
54
|
BaseRecord,
|
|
45
55
|
FieldDefinition,
|
|
@@ -57,6 +67,11 @@
|
|
|
57
67
|
cloneSavedListViewState,
|
|
58
68
|
columnOrderStorageKey,
|
|
59
69
|
columnVisibilityStorageKey,
|
|
70
|
+
decodeRemoteSavedListViews,
|
|
71
|
+
decodeSavedListViewMutationResult,
|
|
72
|
+
decodeSavedListViewRemoveResult,
|
|
73
|
+
decodeSavedListViewAccess,
|
|
74
|
+
decodeSavedListViewSubjects,
|
|
60
75
|
legacyActiveSavedListViewStorageKey,
|
|
61
76
|
legacyColumnOrderStorageKey,
|
|
62
77
|
legacyColumnVisibilityStorageKey,
|
|
@@ -67,7 +82,10 @@
|
|
|
67
82
|
serializeSavedListViews,
|
|
68
83
|
type ListPreferenceScope,
|
|
69
84
|
type SavedListView,
|
|
85
|
+
type SavedListViewAccess,
|
|
86
|
+
type SavedListViewSubject,
|
|
70
87
|
type SavedListViewState,
|
|
88
|
+
type SavedListViewProvider,
|
|
71
89
|
} from './saved-list-views.js';
|
|
72
90
|
|
|
73
91
|
import { fade } from 'svelte/transition';
|
|
@@ -83,7 +101,7 @@
|
|
|
83
101
|
import * as ContextMenu from './ui/context-menu/index.js';
|
|
84
102
|
import * as Select from './ui/select/index.js';
|
|
85
103
|
import {
|
|
86
|
-
Plus, Pencil, Trash2,
|
|
104
|
+
Plus, Pencil, Trash2, Star,
|
|
87
105
|
Search, Download, ChevronDown, ChevronUp, SlidersHorizontal, Filter as FilterIcon,
|
|
88
106
|
Eye, Copy, RefreshCw, Rows, X, Bookmark, Check
|
|
89
107
|
} from '@lucide/svelte';
|
|
@@ -94,6 +112,7 @@
|
|
|
94
112
|
import CanAccess from './CanAccess.svelte';
|
|
95
113
|
import TooltipButton from './TooltipButton.svelte';
|
|
96
114
|
import InlineEdit from './InlineEdit.svelte';
|
|
115
|
+
import ExportButton from './buttons/ExportButton.svelte';
|
|
97
116
|
import FieldDisplay from './FieldDisplay.svelte';
|
|
98
117
|
import DraggableHeader from './DraggableHeader.svelte';
|
|
99
118
|
import DataState from './content/DataState.svelte';
|
|
@@ -104,7 +123,12 @@
|
|
|
104
123
|
// ─── Props with Snippet composability ─────────────────────────
|
|
105
124
|
interface Props {
|
|
106
125
|
resourceName: string;
|
|
126
|
+
rendering?: ResourceRendering | undefined;
|
|
127
|
+
/** 可选主体渲染器:不替换资源、权限、工具栏和持久化逻辑。 */
|
|
128
|
+
gridBody?: Snippet<[AutoTableGridState]>;
|
|
107
129
|
selectable?: boolean;
|
|
130
|
+
/** 仅在业务回调支持按查询执行服务端命令时启用。 */
|
|
131
|
+
allowSelectAllMatching?: boolean;
|
|
108
132
|
density?: 'compact' | 'comfortable';
|
|
109
133
|
showHeader?: boolean;
|
|
110
134
|
title?: string;
|
|
@@ -127,16 +151,29 @@
|
|
|
127
151
|
pagination?: { current: number; pageSize: number };
|
|
128
152
|
/** Externally controlled sorters */
|
|
129
153
|
sorters?: Sort[];
|
|
130
|
-
/**
|
|
131
|
-
batchActions?: Snippet<[{ selectedIds: (string | number)[] }]>;
|
|
154
|
+
/** 批量操作必须按 selection.scope 区分明确 ID 与全部查询结果。 */
|
|
155
|
+
batchActions?: Snippet<[{ selectedIds: (string | number)[]; selection: BatchSelection }]>;
|
|
132
156
|
/** Summary row rendered at table footer */
|
|
133
157
|
summary?: Snippet<[{ data: BaseRecord[]; total: number; visibleColumnsCount: number }]>;
|
|
134
158
|
deleteVariables?: unknown;
|
|
159
|
+
/** 团队/系统视图;写入需要 Provider 能力和显式可写标记。 */
|
|
160
|
+
savedViewProvider?: SavedListViewProvider;
|
|
161
|
+
/** 显式允许唯一的远程默认视图在无 URL/个人活动视图时初始化页面。 */
|
|
162
|
+
applyRemoteDefaultView?: boolean;
|
|
163
|
+
/** 提供任务名后按当前筛选/排序提交全量导出;未提供时仍导出当前已加载页。 */
|
|
164
|
+
exportTaskName?: string;
|
|
165
|
+
exportTaskProvider?: TaskProvider;
|
|
166
|
+
exportTaskIdempotencyKey?: string;
|
|
167
|
+
exportFormat?: ExportFormat;
|
|
168
|
+
exportMaxItemCount?: number;
|
|
135
169
|
}
|
|
136
170
|
|
|
137
171
|
let {
|
|
138
172
|
resourceName,
|
|
173
|
+
rendering,
|
|
174
|
+
gridBody,
|
|
139
175
|
selectable = true,
|
|
176
|
+
allowSelectAllMatching = false,
|
|
140
177
|
density = 'comfortable',
|
|
141
178
|
showHeader = true,
|
|
142
179
|
title,
|
|
@@ -154,25 +191,43 @@
|
|
|
154
191
|
pagination: externalPagination,
|
|
155
192
|
sorters: externalSorters,
|
|
156
193
|
deleteVariables,
|
|
194
|
+
savedViewProvider,
|
|
195
|
+
applyRemoteDefaultView = false,
|
|
196
|
+
exportTaskName,
|
|
197
|
+
exportTaskProvider,
|
|
198
|
+
exportTaskIdempotencyKey,
|
|
199
|
+
exportFormat = 'csv',
|
|
200
|
+
exportMaxItemCount,
|
|
157
201
|
}: Props = $props();
|
|
158
202
|
|
|
159
203
|
let densityOverride = $state<'compact' | 'comfortable' | undefined>(undefined);
|
|
204
|
+
let allMatchingSelected = $state(false);
|
|
205
|
+
const excludedMatchingIds = new SvelteMap<string, string | number>();
|
|
160
206
|
const currentDensity = $derived(densityOverride ?? density);
|
|
161
207
|
const adminContext = captureAdminContext();
|
|
162
208
|
const parsed = useParsed();
|
|
163
209
|
const navigation = useNavigation();
|
|
164
210
|
|
|
165
211
|
const binding = useResourceContract(() => resourceName);
|
|
212
|
+
const activeRendering = $derived(bindResourceRendering(rendering, binding.resource));
|
|
166
213
|
const resource = $derived(adminContext.getResource(resourceName));
|
|
167
214
|
const primaryKey = $derived(resource.primaryKey ?? 'id');
|
|
168
215
|
const listPermission = useCan(() => ({ resource: resourceName, action: 'list' }));
|
|
169
216
|
const canRead = $derived(listPermission.allowed);
|
|
217
|
+
const preferenceIdentity = useGetIdentity();
|
|
218
|
+
const preferenceSession = $derived(captureAuthSession(adminContext.authProvider));
|
|
219
|
+
const preferenceIdentityReady = $derived(!adminContext.authProvider || (
|
|
220
|
+
preferenceSession.available && !preferenceIdentity.isLoading && !preferenceIdentity.error
|
|
221
|
+
&& typeof preferenceIdentity.data?.id === 'string' && !!preferenceIdentity.data.id.trim()
|
|
222
|
+
));
|
|
170
223
|
const listPreferenceScope = $derived.by(() => {
|
|
171
|
-
const matcher = adminContext.queryKeyMatcher(resourceName);
|
|
172
224
|
return definedOptions({
|
|
173
225
|
resourceName,
|
|
174
|
-
providerName:
|
|
175
|
-
tenantIdentity:
|
|
226
|
+
providerName: binding.dataProviderName,
|
|
227
|
+
tenantIdentity: adminContext.queryKeyMatcher(resourceName).tenant,
|
|
228
|
+
identityKey: adminContext.authProvider
|
|
229
|
+
? preferenceIdentityReady ? `id:${preferenceIdentity.data?.id}` : `unresolved:${preferenceSession.cacheKey}`
|
|
230
|
+
: undefined,
|
|
176
231
|
});
|
|
177
232
|
});
|
|
178
233
|
|
|
@@ -186,6 +241,7 @@
|
|
|
186
241
|
}
|
|
187
242
|
|
|
188
243
|
function readScopedPreference(scope: ListPreferenceScope, scopedKey: string, legacyKey: string): string | null {
|
|
244
|
+
if (!preferenceIdentityReady) return null;
|
|
189
245
|
const scoped = readLocalPreference(scopedKey);
|
|
190
246
|
if (scoped !== null || !canMigrateLegacyListPreferences(scope)) return scoped;
|
|
191
247
|
const legacy = readLocalPreference(legacyKey);
|
|
@@ -213,6 +269,19 @@
|
|
|
213
269
|
savedViewColumnIds,
|
|
214
270
|
));
|
|
215
271
|
let savedViews = $state<SavedListView[]>(storedSavedViews);
|
|
272
|
+
let remoteSavedViews = $state<SavedListView[]>([]);
|
|
273
|
+
let remoteViewsLoading = $state(false);
|
|
274
|
+
let remoteViewsFailed = $state(false);
|
|
275
|
+
let remoteViewsReload = $state(0);
|
|
276
|
+
let remoteDefaultEligible = true;
|
|
277
|
+
const availableSavedViews = $derived.by(() => {
|
|
278
|
+
const ids = new Set<string>();
|
|
279
|
+
return [...remoteSavedViews, ...savedViews].filter(view => {
|
|
280
|
+
if (ids.has(view.id)) return false;
|
|
281
|
+
ids.add(view.id);
|
|
282
|
+
return true;
|
|
283
|
+
});
|
|
284
|
+
});
|
|
216
285
|
const storedActiveSavedViewId = untrack(() => {
|
|
217
286
|
const candidate = readScopedPreference(
|
|
218
287
|
listPreferenceScope,
|
|
@@ -223,6 +292,16 @@
|
|
|
223
292
|
});
|
|
224
293
|
let savedViewName = $state('');
|
|
225
294
|
let savedViewsOpen = $state(false);
|
|
295
|
+
let savedViewSource = $state<'local' | 'team' | 'system'>('local');
|
|
296
|
+
let savedViewMutationPending = $state(false);
|
|
297
|
+
let savedViewMutationError = $state<'failure' | 'conflict' | undefined>();
|
|
298
|
+
const accessDrafts = new SvelteMap<string, SavedListViewAccess>();
|
|
299
|
+
const accessSubjects = new SvelteMap<string, SavedListViewSubject[]>();
|
|
300
|
+
const accessSubjectsLoading = new SvelteMap<string, boolean>();
|
|
301
|
+
const accessSubjectsFailed = new SvelteMap<string, boolean>();
|
|
302
|
+
const accessSubjectQueries = new SvelteMap<string, string>();
|
|
303
|
+
const accessSubjectRequests = new Map<string, object>();
|
|
304
|
+
let savedViewMutationEpoch = 0;
|
|
226
305
|
|
|
227
306
|
const hasExplicitURLState = untrack(() => (
|
|
228
307
|
urlState.page !== undefined
|
|
@@ -232,7 +311,7 @@
|
|
|
232
311
|
|| urlState.filters !== undefined
|
|
233
312
|
));
|
|
234
313
|
let activeSavedViewId = $state<string | undefined>(hasExplicitURLState ? undefined : storedActiveSavedViewId);
|
|
235
|
-
const activeSavedViewName = $derived(
|
|
314
|
+
const activeSavedViewName = $derived(availableSavedViews.find((view) => view.id === activeSavedViewId)?.name);
|
|
236
315
|
const initialSavedView = untrack(() => (
|
|
237
316
|
!hasExplicitURLState && storedActiveSavedViewId
|
|
238
317
|
? storedSavedViews.find((view) => view.id === storedActiveSavedViewId)
|
|
@@ -362,6 +441,7 @@
|
|
|
362
441
|
|
|
363
442
|
function scheduleSearch(event: Event) {
|
|
364
443
|
if (!(event.currentTarget instanceof HTMLInputElement)) return;
|
|
444
|
+
markSavedViewDirty();
|
|
365
445
|
searchText = event.currentTarget.value;
|
|
366
446
|
if (searchDebounceTimer) clearTimeout(searchDebounceTimer);
|
|
367
447
|
searchDebounceTimer = setTimeout(() => {
|
|
@@ -522,7 +602,7 @@
|
|
|
522
602
|
|
|
523
603
|
// ─── Data fetching ────────────────────────────────────────────
|
|
524
604
|
const listResult = useList({
|
|
525
|
-
get resource() { return binding.resource; },
|
|
605
|
+
get resource() { void activeRendering; return binding.resource; },
|
|
526
606
|
get dataProviderName() { return binding.dataProviderName; },
|
|
527
607
|
get queryOptions() { return { enabled: canRead }; },
|
|
528
608
|
get pagination() { return queryPagination; },
|
|
@@ -530,10 +610,25 @@
|
|
|
530
610
|
get filters() { return queryFilters; },
|
|
531
611
|
});
|
|
532
612
|
const query = listResult;
|
|
533
|
-
const pageRecords =
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
613
|
+
const pageRecords = {
|
|
614
|
+
get ok(): boolean {
|
|
615
|
+
if (!canRead) return true;
|
|
616
|
+
try {
|
|
617
|
+
checkedTableRows(activeRendering ? activeRendering.records(query.data?.data ?? []) : query.data?.data ?? []);
|
|
618
|
+
return true;
|
|
619
|
+
} catch {
|
|
620
|
+
return false;
|
|
621
|
+
}
|
|
622
|
+
},
|
|
623
|
+
get data(): TableRecord[] {
|
|
624
|
+
if (!canRead) return [];
|
|
625
|
+
try {
|
|
626
|
+
return checkedTableRows(activeRendering ? activeRendering.records(query.data?.data ?? []) : query.data?.data ?? []);
|
|
627
|
+
} catch {
|
|
628
|
+
return [];
|
|
629
|
+
}
|
|
630
|
+
},
|
|
631
|
+
};
|
|
537
632
|
let deleteRequest = $state<{ ids: (string | number)[]; batch: boolean } | null>(null);
|
|
538
633
|
const deletePermission = useCan(() => definedOptions({
|
|
539
634
|
resource: resourceName, action: 'delete',
|
|
@@ -543,7 +638,7 @@
|
|
|
543
638
|
}));
|
|
544
639
|
const deleteAllowed = $derived(canRead && resource.canDelete !== false && deleteRequest !== null && deletePermission.allowed);
|
|
545
640
|
const deleteManyResult = useDeleteMany({
|
|
546
|
-
get resource() { return binding.resource; },
|
|
641
|
+
get resource() { void activeRendering; return binding.resource; },
|
|
547
642
|
get enabled() { return deleteAllowed; },
|
|
548
643
|
});
|
|
549
644
|
const deleteManyMutation = deleteManyResult.mutation;
|
|
@@ -598,7 +693,7 @@
|
|
|
598
693
|
const selectedIdValueByKey = new SvelteMap<string, string | number>();
|
|
599
694
|
|
|
600
695
|
function preferenceScopeIsLoaded(): boolean {
|
|
601
|
-
return loadedPreferenceScopeId === listPreferenceScopeId(listPreferenceScope);
|
|
696
|
+
return preferenceIdentityReady && loadedPreferenceScopeId === listPreferenceScopeId(listPreferenceScope);
|
|
602
697
|
}
|
|
603
698
|
|
|
604
699
|
function rowIsExpanded(rowId: string): boolean {
|
|
@@ -607,7 +702,7 @@
|
|
|
607
702
|
}
|
|
608
703
|
|
|
609
704
|
function rowIsSelected(rowId: string): boolean {
|
|
610
|
-
return rowSelection[rowId] === true;
|
|
705
|
+
return allMatchingSelected ? !excludedMatchingIds.has(rowId) : rowSelection[rowId] === true;
|
|
611
706
|
}
|
|
612
707
|
|
|
613
708
|
function rowIdValue(row: Row<TableFeatures, TableRecord>): string | number {
|
|
@@ -616,6 +711,11 @@
|
|
|
616
711
|
|
|
617
712
|
function toggleRowSelection(row: Row<TableFeatures, TableRecord>): void {
|
|
618
713
|
if (confirmPending || !canRead) return;
|
|
714
|
+
if (allMatchingSelected) {
|
|
715
|
+
if (excludedMatchingIds.has(row.id)) excludedMatchingIds.delete(row.id);
|
|
716
|
+
else excludedMatchingIds.set(row.id, rowIdValue(row));
|
|
717
|
+
return;
|
|
718
|
+
}
|
|
619
719
|
const wasSelected = rowIsSelected(row.id);
|
|
620
720
|
if (!wasSelected) {
|
|
621
721
|
selectedIdValueByKey.set(row.id, rowIdValue(row));
|
|
@@ -670,6 +770,7 @@
|
|
|
670
770
|
}
|
|
671
771
|
|
|
672
772
|
function markSavedViewDirty(): void {
|
|
773
|
+
remoteDefaultEligible = false;
|
|
673
774
|
if (!activeSavedViewId) return;
|
|
674
775
|
activeSavedViewId = undefined;
|
|
675
776
|
persistActiveSavedView();
|
|
@@ -690,6 +791,7 @@
|
|
|
690
791
|
}
|
|
691
792
|
|
|
692
793
|
function applySavedView(view: SavedListView): void {
|
|
794
|
+
remoteDefaultEligible = false;
|
|
693
795
|
if (searchDebounceTimer) {
|
|
694
796
|
clearTimeout(searchDebounceTimer);
|
|
695
797
|
searchDebounceTimer = undefined;
|
|
@@ -724,6 +826,8 @@
|
|
|
724
826
|
columnOrderAtom.set(state.columnOrder);
|
|
725
827
|
persistColumnOrder(state.columnOrder);
|
|
726
828
|
rowSelection = {};
|
|
829
|
+
allMatchingSelected = false;
|
|
830
|
+
excludedMatchingIds.clear();
|
|
727
831
|
selectedIdValueByKey.clear();
|
|
728
832
|
}
|
|
729
833
|
|
|
@@ -751,8 +855,12 @@
|
|
|
751
855
|
const scopeId = listPreferenceScopeId(scope);
|
|
752
856
|
if (scopeId === loadedPreferenceScopeId) return;
|
|
753
857
|
|
|
858
|
+
remoteDefaultEligible = true;
|
|
754
859
|
const preferences = readScopedSavedViewPreferences(scope);
|
|
860
|
+
savedViewName = '';
|
|
861
|
+
savedViewsOpen = false;
|
|
755
862
|
savedViews = preferences.savedViews;
|
|
863
|
+
remoteSavedViews = [];
|
|
756
864
|
activeSavedViewId = preferences.activeSavedViewId;
|
|
757
865
|
|
|
758
866
|
if (!hasExplicitURLState && preferences.activeSavedView) {
|
|
@@ -762,13 +870,68 @@
|
|
|
762
870
|
columnVisibilityAtom.set(preferences.columnVisibility);
|
|
763
871
|
columnOrderAtom.set(preferences.columnOrder);
|
|
764
872
|
rowSelection = {};
|
|
873
|
+
allMatchingSelected = false;
|
|
874
|
+
excludedMatchingIds.clear();
|
|
765
875
|
selectedIdValueByKey.clear();
|
|
766
876
|
}
|
|
767
877
|
|
|
768
878
|
loadedPreferenceScopeId = scopeId;
|
|
769
879
|
});
|
|
770
880
|
|
|
881
|
+
let remoteViewEpoch = 0;
|
|
882
|
+
$effect(() => {
|
|
883
|
+
const provider = savedViewProvider;
|
|
884
|
+
const scope = listPreferenceScope;
|
|
885
|
+
const columns = savedViewColumnIds;
|
|
886
|
+
void remoteViewsReload;
|
|
887
|
+
remoteSavedViews = [];
|
|
888
|
+
remoteViewsLoading = false;
|
|
889
|
+
remoteViewsFailed = false;
|
|
890
|
+
accessDrafts.clear();
|
|
891
|
+
accessSubjects.clear();
|
|
892
|
+
accessSubjectsLoading.clear();
|
|
893
|
+
accessSubjectsFailed.clear();
|
|
894
|
+
accessSubjectQueries.clear();
|
|
895
|
+
accessSubjectRequests.clear();
|
|
896
|
+
savedViewMutationEpoch += 1;
|
|
897
|
+
savedViewMutationPending = false;
|
|
898
|
+
savedViewMutationError = undefined;
|
|
899
|
+
savedViewSource = 'local';
|
|
900
|
+
if (!provider || !preferenceScopeIsLoaded()) {
|
|
901
|
+
remoteSavedViews = [];
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
904
|
+
const epoch = ++remoteViewEpoch;
|
|
905
|
+
remoteViewsLoading = true;
|
|
906
|
+
void Promise.resolve().then(() => provider.list({ ...scope })).then(value => {
|
|
907
|
+
if (epoch !== remoteViewEpoch || !preferenceScopeIsLoaded()) return;
|
|
908
|
+
remoteSavedViews = decodeRemoteSavedListViews(value, columns, !!(provider.save || provider.remove || provider.setDefault || provider.updateAccess));
|
|
909
|
+
if (applyRemoteDefaultView && remoteDefaultEligible) {
|
|
910
|
+
const defaults = remoteSavedViews.filter(view => view.default === true);
|
|
911
|
+
if (!hasExplicitURLState && !activeSavedViewId && !externalPagination && !externalSorters
|
|
912
|
+
&& defaults.length === 1 && defaults[0]) {
|
|
913
|
+
applySavedView(defaults[0]);
|
|
914
|
+
remoteDefaultEligible = false;
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
}).catch(() => {
|
|
918
|
+
if (epoch === remoteViewEpoch) {
|
|
919
|
+
remoteSavedViews = [];
|
|
920
|
+
remoteViewsFailed = true;
|
|
921
|
+
}
|
|
922
|
+
}).finally(() => {
|
|
923
|
+
if (epoch === remoteViewEpoch) remoteViewsLoading = false;
|
|
924
|
+
});
|
|
925
|
+
return () => {
|
|
926
|
+
remoteViewEpoch += 1;
|
|
927
|
+
savedViewMutationEpoch += 1;
|
|
928
|
+
accessSubjectRequests.clear();
|
|
929
|
+
};
|
|
930
|
+
});
|
|
931
|
+
|
|
771
932
|
function saveCurrentView(): void {
|
|
933
|
+
if (!preferenceScopeIsLoaded() || savedViewMutationPending) return;
|
|
934
|
+
remoteDefaultEligible = false;
|
|
772
935
|
const name = savedViewName.trim().slice(0, 60);
|
|
773
936
|
if (!name) return;
|
|
774
937
|
if (searchDebounceTimer) {
|
|
@@ -776,6 +939,16 @@
|
|
|
776
939
|
searchDebounceTimer = undefined;
|
|
777
940
|
appliedSearchText = searchText;
|
|
778
941
|
}
|
|
942
|
+
if (savedViewSource !== 'local') {
|
|
943
|
+
const existing = remoteSavedViews.find(view => view.source === savedViewSource
|
|
944
|
+
&& view.name.toLocaleLowerCase() === name.toLocaleLowerCase());
|
|
945
|
+
if (!savedViewProvider?.save || (existing && (existing.readOnly || !existing.version))) return;
|
|
946
|
+
void mutateRemoteView({
|
|
947
|
+
id: existing?.id ?? crypto.randomUUID(), name, state: getCurrentSavedViewState(),
|
|
948
|
+
source: savedViewSource, expectedVersion: existing?.version ?? null,
|
|
949
|
+
});
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
779
952
|
const existing = savedViews.find((view) => view.name.toLocaleLowerCase() === name.toLocaleLowerCase());
|
|
780
953
|
const view: SavedListView = {
|
|
781
954
|
id: existing?.id ?? `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
@@ -792,12 +965,184 @@
|
|
|
792
965
|
}
|
|
793
966
|
|
|
794
967
|
function deleteSavedView(id: string): void {
|
|
968
|
+
if (!preferenceScopeIsLoaded() || savedViewMutationPending) return;
|
|
969
|
+
const remote = remoteSavedViews.find(view => view.id === id);
|
|
970
|
+
if (remote) {
|
|
971
|
+
if (!savedViewProvider?.remove || remote.readOnly || !remote.version) return;
|
|
972
|
+
void mutateRemoteView({
|
|
973
|
+
id, expectedVersion: remote.version, source: remote.source === 'system' ? 'system' : 'team',
|
|
974
|
+
name: remote.name, state: remote.state,
|
|
975
|
+
}, true);
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
795
978
|
savedViews = savedViews.filter((view) => view.id !== id);
|
|
796
979
|
if (activeSavedViewId === id) activeSavedViewId = undefined;
|
|
797
980
|
persistSavedViews();
|
|
798
981
|
persistActiveSavedView();
|
|
799
982
|
}
|
|
800
983
|
|
|
984
|
+
async function mutateRemoteView(
|
|
985
|
+
mutation: import('./saved-list-views').SavedListViewMutation, remove = false,
|
|
986
|
+
): Promise<void> {
|
|
987
|
+
const provider = savedViewProvider;
|
|
988
|
+
if (!provider || savedViewMutationPending || !preferenceScopeIsLoaded()
|
|
989
|
+
|| (remove && (mutation.expectedVersion === null || !provider.remove))
|
|
990
|
+
|| (!remove && !provider.save)) return;
|
|
991
|
+
const scope = { ...listPreferenceScope };
|
|
992
|
+
const scopeId = listPreferenceScopeId(scope);
|
|
993
|
+
const columns = savedViewColumnIds;
|
|
994
|
+
const epoch = ++savedViewMutationEpoch;
|
|
995
|
+
const current = () => epoch === savedViewMutationEpoch && provider === savedViewProvider
|
|
996
|
+
&& scopeId === listPreferenceScopeId(listPreferenceScope) && preferenceScopeIsLoaded();
|
|
997
|
+
savedViewMutationPending = true;
|
|
998
|
+
savedViewMutationError = undefined;
|
|
999
|
+
try {
|
|
1000
|
+
const result = remove
|
|
1001
|
+
? await provider.remove?.(scope, { id: mutation.id, expectedVersion: mutation.expectedVersion ?? 0 })
|
|
1002
|
+
: await provider.save?.(scope, { ...mutation, state: cloneSavedListViewState(mutation.state) });
|
|
1003
|
+
if (!current()) return;
|
|
1004
|
+
if (remove && mutation.expectedVersion !== null && decodeSavedListViewRemoveResult(result, {
|
|
1005
|
+
id: mutation.id, expectedVersion: mutation.expectedVersion,
|
|
1006
|
+
})) {
|
|
1007
|
+
remoteSavedViews = remoteSavedViews.filter(view => view.id !== mutation.id);
|
|
1008
|
+
if (activeSavedViewId === mutation.id) activeSavedViewId = undefined;
|
|
1009
|
+
persistActiveSavedView();
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
const decoded = decodeSavedListViewMutationResult(result, columns, mutation);
|
|
1013
|
+
if (!decoded || (remove && decoded.ok)) {
|
|
1014
|
+
savedViewMutationError = 'failure';
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
if (!decoded.ok) {
|
|
1018
|
+
savedViewMutationError = 'conflict';
|
|
1019
|
+
// 保留本地查询;用户必须重新读取后再决定如何修改。
|
|
1020
|
+
return;
|
|
1021
|
+
}
|
|
1022
|
+
const view = { ...decoded.view, readOnly: decoded.view.readOnly !== false };
|
|
1023
|
+
remoteSavedViews = [view, ...remoteSavedViews.filter(candidate => candidate.id !== view.id)];
|
|
1024
|
+
savedViewName = '';
|
|
1025
|
+
} catch {
|
|
1026
|
+
if (current()) savedViewMutationError = 'failure';
|
|
1027
|
+
} finally {
|
|
1028
|
+
if (current()) savedViewMutationPending = false;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
function setRemoteDefault(view: SavedListView): void {
|
|
1033
|
+
const provider = savedViewProvider;
|
|
1034
|
+
if (!provider?.setDefault || savedViewMutationPending || view.readOnly || !view.version
|
|
1035
|
+
|| savedViewMutationError === 'conflict' || !preferenceScopeIsLoaded()
|
|
1036
|
+
|| (view.source !== 'team' && view.source !== 'system')) return;
|
|
1037
|
+
const source = view.source;
|
|
1038
|
+
const expectedVersion = view.version;
|
|
1039
|
+
const defaultValue = view.default !== true;
|
|
1040
|
+
remoteDefaultEligible = false;
|
|
1041
|
+
const scope = { ...listPreferenceScope };
|
|
1042
|
+
const scopeId = listPreferenceScopeId(scope);
|
|
1043
|
+
const epoch = ++savedViewMutationEpoch;
|
|
1044
|
+
const current = () => epoch === savedViewMutationEpoch && provider === savedViewProvider
|
|
1045
|
+
&& scopeId === listPreferenceScopeId(listPreferenceScope) && preferenceScopeIsLoaded();
|
|
1046
|
+
savedViewMutationPending = true;
|
|
1047
|
+
savedViewMutationError = undefined;
|
|
1048
|
+
void Promise.resolve().then(() => provider.setDefault?.(scope, {
|
|
1049
|
+
id: view.id, source, expectedVersion, default: defaultValue,
|
|
1050
|
+
})).then(result => {
|
|
1051
|
+
if (!current()) return;
|
|
1052
|
+
const decoded = decodeSavedListViewMutationResult(result, savedViewColumnIds, {
|
|
1053
|
+
id: view.id, source, expectedVersion,
|
|
1054
|
+
});
|
|
1055
|
+
if (!decoded) {
|
|
1056
|
+
savedViewMutationError = 'failure';
|
|
1057
|
+
} else if (!decoded.ok) {
|
|
1058
|
+
savedViewMutationError = 'conflict';
|
|
1059
|
+
} else if (decoded.view.default !== defaultValue) {
|
|
1060
|
+
savedViewMutationError = 'failure';
|
|
1061
|
+
} else {
|
|
1062
|
+
// 默认标记会同时改变同一作用域的其他视图,成功后重新读取完整集合。
|
|
1063
|
+
remoteViewsReload += 1;
|
|
1064
|
+
}
|
|
1065
|
+
}).catch(() => {
|
|
1066
|
+
if (current()) savedViewMutationError = 'failure';
|
|
1067
|
+
}).finally(() => {
|
|
1068
|
+
if (current()) savedViewMutationPending = false;
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
function saveRemoteAccess(view: SavedListView): void {
|
|
1073
|
+
const provider = savedViewProvider;
|
|
1074
|
+
const draft = accessDrafts.get(view.id);
|
|
1075
|
+
if (!provider?.updateAccess || !draft || view.readOnly !== false || !view.version
|
|
1076
|
+
|| savedViewMutationPending || savedViewMutationError === 'conflict' || !preferenceScopeIsLoaded()
|
|
1077
|
+
|| (view.source !== 'team' && view.source !== 'system')) return;
|
|
1078
|
+
const access = decodeSavedListViewAccess({
|
|
1079
|
+
mode: draft.mode,
|
|
1080
|
+
subjectIds: draft.mode === 'restricted' ? draft.subjectIds : [],
|
|
1081
|
+
});
|
|
1082
|
+
if (!access) { savedViewMutationError = 'failure'; return; }
|
|
1083
|
+
const source = view.source;
|
|
1084
|
+
const expectedVersion = view.version;
|
|
1085
|
+
const scope = { ...listPreferenceScope };
|
|
1086
|
+
const scopeId = listPreferenceScopeId(scope);
|
|
1087
|
+
const epoch = ++savedViewMutationEpoch;
|
|
1088
|
+
const current = () => epoch === savedViewMutationEpoch && provider === savedViewProvider
|
|
1089
|
+
&& scopeId === listPreferenceScopeId(listPreferenceScope) && preferenceScopeIsLoaded();
|
|
1090
|
+
remoteDefaultEligible = false;
|
|
1091
|
+
savedViewMutationPending = true;
|
|
1092
|
+
savedViewMutationError = undefined;
|
|
1093
|
+
void Promise.resolve().then(() => {
|
|
1094
|
+
if (!current()) return;
|
|
1095
|
+
return provider.updateAccess?.(scope, {
|
|
1096
|
+
id: view.id, source, expectedVersion, access: { ...access, subjectIds: [...access.subjectIds] },
|
|
1097
|
+
});
|
|
1098
|
+
}).then(result => {
|
|
1099
|
+
if (!current()) return;
|
|
1100
|
+
const decoded = decodeSavedListViewMutationResult(result, savedViewColumnIds, {
|
|
1101
|
+
id: view.id, source, expectedVersion,
|
|
1102
|
+
});
|
|
1103
|
+
if (!decoded) savedViewMutationError = 'failure';
|
|
1104
|
+
else if (!decoded.ok) savedViewMutationError = 'conflict';
|
|
1105
|
+
else if (decoded.view.access?.mode !== access.mode
|
|
1106
|
+
|| decoded.view.access.subjectIds.length !== access.subjectIds.length
|
|
1107
|
+
|| !decoded.view.access.subjectIds.every(id => access.subjectIds.includes(id))) {
|
|
1108
|
+
savedViewMutationError = 'failure';
|
|
1109
|
+
} else remoteViewsReload += 1;
|
|
1110
|
+
}).catch(() => {
|
|
1111
|
+
if (current()) savedViewMutationError = 'failure';
|
|
1112
|
+
}).finally(() => {
|
|
1113
|
+
if (current()) savedViewMutationPending = false;
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
function loadAccessSubjects(view: SavedListView, query = ''): void {
|
|
1118
|
+
const provider = savedViewProvider;
|
|
1119
|
+
if (!provider?.listAccessSubjects || view.readOnly !== false
|
|
1120
|
+
|| view.source === 'local' || !preferenceScopeIsLoaded()) return;
|
|
1121
|
+
const scope = { ...listPreferenceScope };
|
|
1122
|
+
const scopeId = listPreferenceScopeId(scope);
|
|
1123
|
+
const request = {};
|
|
1124
|
+
accessSubjectRequests.set(view.id, request);
|
|
1125
|
+
const current = () => accessSubjectRequests.get(view.id) === request
|
|
1126
|
+
&& scopeId === listPreferenceScopeId(listPreferenceScope)
|
|
1127
|
+
&& provider === savedViewProvider && preferenceScopeIsLoaded();
|
|
1128
|
+
accessSubjectsLoading.set(view.id, true);
|
|
1129
|
+
accessSubjectsFailed.delete(view.id);
|
|
1130
|
+
accessSubjects.delete(view.id);
|
|
1131
|
+
void Promise.resolve().then(() => {
|
|
1132
|
+
if (current()) return provider.listAccessSubjects?.(scope, { query: query.trim(), limit: 50 });
|
|
1133
|
+
})
|
|
1134
|
+
.then(result => {
|
|
1135
|
+
if (!current()) return;
|
|
1136
|
+
const subjects = decodeSavedListViewSubjects(result);
|
|
1137
|
+
if (!subjects) accessSubjectsFailed.set(view.id, true);
|
|
1138
|
+
else accessSubjects.set(view.id, subjects);
|
|
1139
|
+
})
|
|
1140
|
+
.catch(() => {
|
|
1141
|
+
if (current()) accessSubjectsFailed.set(view.id, true);
|
|
1142
|
+
})
|
|
1143
|
+
.finally(() => { if (current()) accessSubjectsLoading.delete(view.id); });
|
|
1144
|
+
}
|
|
1145
|
+
|
|
801
1146
|
function setColumnVisibility(columnId: string, visible: boolean): void {
|
|
802
1147
|
markSavedViewDirty();
|
|
803
1148
|
columnVisibilityAtom.set({ ...tableColumnVisibility.current, [columnId]: visible });
|
|
@@ -915,6 +1260,10 @@
|
|
|
915
1260
|
|
|
916
1261
|
function toggleAllRowsSelection(): void {
|
|
917
1262
|
if (confirmPending || !canRead) return;
|
|
1263
|
+
if (allMatchingSelected) {
|
|
1264
|
+
clearSelection();
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
918
1267
|
const willSelect = !table_getIsAllRowsSelected(tbl);
|
|
919
1268
|
for (const row of tableView.rows) {
|
|
920
1269
|
if (willSelect) {
|
|
@@ -933,7 +1282,58 @@
|
|
|
933
1282
|
return id === undefined ? [] : [id];
|
|
934
1283
|
})
|
|
935
1284
|
);
|
|
936
|
-
const
|
|
1285
|
+
const matchingTotal = $derived(query.data?.total ?? 0);
|
|
1286
|
+
const selectedCount = $derived(allMatchingSelected
|
|
1287
|
+
? Math.max(0, matchingTotal - excludedMatchingIds.size)
|
|
1288
|
+
: selectedIds.length);
|
|
1289
|
+
const selectionLabel = $derived(i18n.t(
|
|
1290
|
+
allMatchingSelected ? 'common.allMatchingSelected' : 'common.selectedCount',
|
|
1291
|
+
{ count: selectedCount },
|
|
1292
|
+
));
|
|
1293
|
+
const selectionReady = $derived(
|
|
1294
|
+
canRead && selectable && pageRecords.ok && !query.isError && !query.isFetching
|
|
1295
|
+
);
|
|
1296
|
+
const canSelectAllMatching = $derived(
|
|
1297
|
+
allowSelectAllMatching && !!batchActions && selectionReady
|
|
1298
|
+
&& Number.isSafeInteger(matchingTotal) && matchingTotal > 0
|
|
1299
|
+
);
|
|
1300
|
+
function batchSelectionSnapshot(): BatchSelection {
|
|
1301
|
+
if (allMatchingSelected) return {
|
|
1302
|
+
scope: 'all', filters: queryFilters.map(cloneFilter),
|
|
1303
|
+
sorters: querySorters.map(sorter => ({ ...sorter })), total: matchingTotal,
|
|
1304
|
+
excludedIds: [...excludedMatchingIds.values()],
|
|
1305
|
+
};
|
|
1306
|
+
return {
|
|
1307
|
+
scope: 'selected', ids: [...selectedIds],
|
|
1308
|
+
currentPageIds: tableView.rows.filter(row => rowSelection[row.id] === true).map(rowIdValue),
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
const selectionCriteria = $derived(JSON.stringify(queryFilters));
|
|
1312
|
+
let previousSelectionCriteria = untrack(() => selectionCriteria);
|
|
1313
|
+
$effect.pre(() => {
|
|
1314
|
+
const criteria = selectionCriteria;
|
|
1315
|
+
if (criteria !== previousSelectionCriteria) {
|
|
1316
|
+
allMatchingSelected = false;
|
|
1317
|
+
excludedMatchingIds.clear();
|
|
1318
|
+
rowSelection = {};
|
|
1319
|
+
selectedIdValueByKey.clear();
|
|
1320
|
+
if (deleteRequest?.batch && !confirmPending) {
|
|
1321
|
+
confirmOpen = false;
|
|
1322
|
+
deleteRequest = null;
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
previousSelectionCriteria = criteria;
|
|
1326
|
+
});
|
|
1327
|
+
$effect.pre(() => {
|
|
1328
|
+
if (!selectable || !allowSelectAllMatching || !batchActions) {
|
|
1329
|
+
allMatchingSelected = false;
|
|
1330
|
+
excludedMatchingIds.clear();
|
|
1331
|
+
}
|
|
1332
|
+
if (!selectable) {
|
|
1333
|
+
rowSelection = {};
|
|
1334
|
+
selectedIdValueByKey.clear();
|
|
1335
|
+
}
|
|
1336
|
+
});
|
|
937
1337
|
const batchDeletePerm = useCan(() => ({
|
|
938
1338
|
resource: resourceName,
|
|
939
1339
|
action: 'delete',
|
|
@@ -941,16 +1341,26 @@
|
|
|
941
1341
|
queryOptions: { enabled: acEnabled && canDelete && selectedIds.length > 0 },
|
|
942
1342
|
}));
|
|
943
1343
|
const canBatchDelete = $derived(
|
|
944
|
-
canDelete && (!acEnabled || batchDeletePerm.isLoading || batchDeletePerm.allowed)
|
|
1344
|
+
!allMatchingSelected && canDelete && (!acEnabled || batchDeletePerm.isLoading || batchDeletePerm.allowed)
|
|
945
1345
|
);
|
|
946
1346
|
|
|
947
1347
|
function clearSelection(): void {
|
|
948
1348
|
if (!deleteManyMutation.isPending) {
|
|
1349
|
+
allMatchingSelected = false;
|
|
1350
|
+
excludedMatchingIds.clear();
|
|
949
1351
|
selectedIdValueByKey.clear();
|
|
950
1352
|
table_resetRowSelection(tbl, true);
|
|
951
1353
|
}
|
|
952
1354
|
}
|
|
953
1355
|
|
|
1356
|
+
function selectAllMatching(): void {
|
|
1357
|
+
if (!canSelectAllMatching || confirmPending || confirmOpen) return;
|
|
1358
|
+
selectedIdValueByKey.clear();
|
|
1359
|
+
excludedMatchingIds.clear();
|
|
1360
|
+
rowSelection = {};
|
|
1361
|
+
allMatchingSelected = true;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
954
1364
|
function isColumnVisible(columnId: string): boolean {
|
|
955
1365
|
return tableColumnVisibility.current[columnId] !== false;
|
|
956
1366
|
}
|
|
@@ -1025,6 +1435,19 @@
|
|
|
1025
1435
|
variables: deleteVariables,
|
|
1026
1436
|
});
|
|
1027
1437
|
let previousTableScope: typeof tableScope | undefined;
|
|
1438
|
+
function tableScopesEqual(left: typeof tableScope, right: typeof tableScope): boolean {
|
|
1439
|
+
return left.contract === right.contract
|
|
1440
|
+
&& left.resourceName === right.resourceName
|
|
1441
|
+
&& left.provider === right.provider
|
|
1442
|
+
&& left.meta === right.meta
|
|
1443
|
+
&& left.tenant === right.tenant
|
|
1444
|
+
&& left.auth === right.auth
|
|
1445
|
+
&& left.router === right.router
|
|
1446
|
+
&& left.permissionProvider === right.permissionProvider
|
|
1447
|
+
&& left.canRead === right.canRead
|
|
1448
|
+
&& left.canDelete === right.canDelete
|
|
1449
|
+
&& left.variables === right.variables;
|
|
1450
|
+
}
|
|
1028
1451
|
const detailState = $derived.by(() => {
|
|
1029
1452
|
const route = parsed.params['detail'];
|
|
1030
1453
|
if (route === undefined || !canRead) return { id: undefined, invalid: false };
|
|
@@ -1035,12 +1458,14 @@
|
|
|
1035
1458
|
const detailOpen = $derived(detailRecordId != null);
|
|
1036
1459
|
$effect.pre(() => {
|
|
1037
1460
|
const scope = tableScope;
|
|
1038
|
-
if (previousTableScope && previousTableScope
|
|
1461
|
+
if (previousTableScope && !tableScopesEqual(previousTableScope, scope)) {
|
|
1039
1462
|
activeDelete = undefined;
|
|
1040
1463
|
confirmOpen = false;
|
|
1041
1464
|
confirmPending = false;
|
|
1042
1465
|
deleteRequest = null;
|
|
1043
1466
|
operationError = null;
|
|
1467
|
+
allMatchingSelected = false;
|
|
1468
|
+
excludedMatchingIds.clear();
|
|
1044
1469
|
selectedIdValueByKey.clear();
|
|
1045
1470
|
rowSelection = {};
|
|
1046
1471
|
expandedAtom.set({});
|
|
@@ -1177,8 +1602,205 @@
|
|
|
1177
1602
|
function refreshList() {
|
|
1178
1603
|
if (canRead && !query.isFetching) void listResult.refetch();
|
|
1179
1604
|
}
|
|
1605
|
+
const gridRowIndex = $derived(new Map(tableView.rows.map(row => [rowIdValue(row), row])));
|
|
1606
|
+
const gridColumns = $derived.by(() => {
|
|
1607
|
+
void tableColumnOrder.current; void tableColumnVisibility.current;
|
|
1608
|
+
return table_getAllLeafColumns(tbl).filter(column => isColumnVisible(column.id)).map(column => ({
|
|
1609
|
+
key: column.id,
|
|
1610
|
+
label: column.id === '_actions' ? i18n.t('common.actions') : column.id === '_select' ? i18n.t('common.selectAll') : column.id === '_expand' ? i18n.t('common.expand') : visibleFields.find(field => field.key === column.id)?.label ?? column.id,
|
|
1611
|
+
width: Math.max(40, Math.min(4096, column_getSize(column))),
|
|
1612
|
+
sortable: column_getCanSort(column),
|
|
1613
|
+
}));
|
|
1614
|
+
});
|
|
1615
|
+
function changeGridSort(next: readonly Sort[]): void {
|
|
1616
|
+
if (!canRead || query.isFetching || deleteManyMutation.isPending) return;
|
|
1617
|
+
markSavedViewDirty();
|
|
1618
|
+
const accepted = next.filter(sort => gridColumns.some(column => column.key === sort.field && column.sortable));
|
|
1619
|
+
sorters = accepted.map(sort => ({ ...sort }));
|
|
1620
|
+
sortingAtom.set(accepted.map(sort => ({ id: sort.field, desc: sort.order === 'desc' })));
|
|
1621
|
+
pagination = { ...pagination, current: 1 };
|
|
1622
|
+
}
|
|
1180
1623
|
</script>
|
|
1181
1624
|
|
|
1625
|
+
{#snippet renderGridCell({ id, field }: { id: string | number; field: string })}
|
|
1626
|
+
{@const row = gridRowIndex.get(id)}
|
|
1627
|
+
{#if row}
|
|
1628
|
+
{@const record = copyTableRecord(row.original)}
|
|
1629
|
+
{@const cell = row_getVisibleCells(row).find(item => item.column.id === field)}
|
|
1630
|
+
{#if cell}
|
|
1631
|
+
<ContextMenu.Root>
|
|
1632
|
+
<ContextMenu.Trigger>
|
|
1633
|
+
{#snippet child({ props })}
|
|
1634
|
+
<div {...props}>
|
|
1635
|
+
{#if cell.column.id === '_select'}
|
|
1636
|
+
{#if batchActions}
|
|
1637
|
+
<Checkbox
|
|
1638
|
+
aria-label={i18n.t('common.selectRow', { id })}
|
|
1639
|
+
checked={rowIsSelected(row.id)}
|
|
1640
|
+
onCheckedChange={() => toggleRowSelection(row)}
|
|
1641
|
+
/>
|
|
1642
|
+
{:else}
|
|
1643
|
+
<CanAccess resource={resourceName} action="delete" params={{ id }}>
|
|
1644
|
+
<Checkbox
|
|
1645
|
+
aria-label={i18n.t('common.selectRow', { id })}
|
|
1646
|
+
checked={rowIsSelected(row.id)}
|
|
1647
|
+
onCheckedChange={() => toggleRowSelection(row)}
|
|
1648
|
+
/>
|
|
1649
|
+
</CanAccess>
|
|
1650
|
+
{/if}
|
|
1651
|
+
{:else if cell.column.id === '_expand'}
|
|
1652
|
+
<TooltipButton tooltip={rowIsExpanded(row.id) ? i18n.t('common.collapse') : i18n.t('common.expand')} variant="ghost" size="icon" class="svadmin-u-d0a52b312f7d svadmin-u-cbbf90f9a828" onclick={() => toggleRowExpanded(row.id)}>
|
|
1653
|
+
{#if rowIsExpanded(row.id)}
|
|
1654
|
+
<ChevronUp class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
|
|
1655
|
+
{:else}
|
|
1656
|
+
<ChevronDown class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" />
|
|
1657
|
+
{/if}
|
|
1658
|
+
</TooltipButton>
|
|
1659
|
+
{:else if cell.column.id === '_actions'}
|
|
1660
|
+
<div class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-77c08e015d14 svadmin-u-44ee8ba0a421">
|
|
1661
|
+
{#if rowActions}
|
|
1662
|
+
{@render rowActions({ record, id })}
|
|
1663
|
+
{:else}
|
|
1664
|
+
{@render defaultRowActions(id)}
|
|
1665
|
+
{/if}
|
|
1666
|
+
</div>
|
|
1667
|
+
{:else}
|
|
1668
|
+
{@const field = visibleFields.find(f => f.key === cell.column.id)}
|
|
1669
|
+
{@const customColumn = field ? customColumns?.[field.key] : undefined}
|
|
1670
|
+
{#if customColumn}
|
|
1671
|
+
{@render customColumn({ value: clonePlainValue(cell_getValue(cell)), record: copyTableRecord(record) })}
|
|
1672
|
+
{:else if defaultCellRenderer && field}
|
|
1673
|
+
{@render defaultCellRenderer({ field, value: clonePlainValue(cell_getValue(cell)), record: copyTableRecord(record) })}
|
|
1674
|
+
{:else if canEdit && field && field.showInEdit !== false && writableFields.has(field.key) && field.key !== primaryKey && field.key !== 'id' && ['text', 'number', 'email', 'url'].includes(field.type)}
|
|
1675
|
+
<CanAccess resource={resourceName} action="edit" params={{ id }}>
|
|
1676
|
+
<InlineEdit
|
|
1677
|
+
{resourceName}
|
|
1678
|
+
recordId={id}
|
|
1679
|
+
{field}
|
|
1680
|
+
value={cell_getValue(cell)}
|
|
1681
|
+
/>
|
|
1682
|
+
{#snippet fallback()}
|
|
1683
|
+
<FieldDisplay type={field.type} value={cell_getValue(cell)} options={field.options} />
|
|
1684
|
+
{/snippet}
|
|
1685
|
+
</CanAccess>
|
|
1686
|
+
{:else if field?.key === 'id'}
|
|
1687
|
+
<span class="svadmin-u-0214b4b355d1 svadmin-u-f283ea9bea0e svadmin-u-0e65706bcccd svadmin-u-359090c2d529" title={String(cell_getValue(cell) ?? '—')}>{cell_getValue(cell) ?? '—'}</span>
|
|
1688
|
+
{:else if field}
|
|
1689
|
+
<FieldDisplay type={field.type} value={cell_getValue(cell)} options={field.options} resourceName={field.resource} />
|
|
1690
|
+
{/if}
|
|
1691
|
+
{/if}
|
|
1692
|
+
</div>
|
|
1693
|
+
{/snippet}
|
|
1694
|
+
</ContextMenu.Trigger>
|
|
1695
|
+
<ContextMenu.Content class="svadmin-u-74b2435a1d40">
|
|
1696
|
+
{#if canEdit}
|
|
1697
|
+
<CanAccess resource={resourceName} action="edit" params={{ id }}>
|
|
1698
|
+
<ContextMenu.Item onclick={() => navigation.edit(resourceName, id)} class="svadmin-u-77a2a20e90d4">
|
|
1699
|
+
<Pencil class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" /> {i18n.t('common.edit')}
|
|
1700
|
+
</ContextMenu.Item>
|
|
1701
|
+
</CanAccess>
|
|
1702
|
+
{/if}
|
|
1703
|
+
{#if canShow}
|
|
1704
|
+
<CanAccess resource={resourceName} action="show" params={{ id }}>
|
|
1705
|
+
<ContextMenu.Item onclick={() => openDetail(id)} class="svadmin-u-77a2a20e90d4">
|
|
1706
|
+
<Eye class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" /> {i18n.t('common.detail')}
|
|
1707
|
+
</ContextMenu.Item>
|
|
1708
|
+
</CanAccess>
|
|
1709
|
+
{/if}
|
|
1710
|
+
<ContextMenu.Item onclick={() => navigator.clipboard?.writeText(String(id))} class="svadmin-u-77a2a20e90d4">
|
|
1711
|
+
<Copy class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" /> {i18n.t('common.copyId')}
|
|
1712
|
+
</ContextMenu.Item>
|
|
1713
|
+
{#if canDelete}
|
|
1714
|
+
<CanAccess resource={resourceName} action="delete" params={{ id }}>
|
|
1715
|
+
<ContextMenu.Separator />
|
|
1716
|
+
<ContextMenu.Item onclick={() => confirmDelete(id)} class="svadmin-u-77a2a20e90d4 svadmin-u-811148b13d1e">
|
|
1717
|
+
<Trash2 class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" /> {i18n.t('common.delete')}
|
|
1718
|
+
</ContextMenu.Item>
|
|
1719
|
+
</CanAccess>
|
|
1720
|
+
{/if}
|
|
1721
|
+
</ContextMenu.Content>
|
|
1722
|
+
</ContextMenu.Root>
|
|
1723
|
+
{/if}
|
|
1724
|
+
{/if}
|
|
1725
|
+
{/snippet}
|
|
1726
|
+
|
|
1727
|
+
{#snippet renderGridControls()}
|
|
1728
|
+
<Table.Root density={currentDensity}>
|
|
1729
|
+
<Table.Header>
|
|
1730
|
+
{#each tableView.headerGroups as headerGroup, _i (_i)}
|
|
1731
|
+
{@const visibleHeaders = headerGroup.headers.filter((header: Header<TableFeatures, TableRecord, unknown>) => isColumnVisible(header.column.id))}
|
|
1732
|
+
<DraggableHeader
|
|
1733
|
+
columns={visibleHeaders.map((header) => ({ id: header.column.id, header }))}
|
|
1734
|
+
onReorder={setColumnOrder}
|
|
1735
|
+
>
|
|
1736
|
+
{#snippet header(col, _index, dragProps)}
|
|
1737
|
+
{@const header = col.header}
|
|
1738
|
+
<Table.Head
|
|
1739
|
+
{...dragProps}
|
|
1740
|
+
class={cn('svadmin-u-65fdbade2025 svadmin-u-18049387f0af svadmin-u-b247a17a0d75 svadmin-u-2689f3958069 svadmin-u-d9256981a032 svadmin-u-bfa603190748 svadmin-u-f6e31b39b8e4', dragProps.class)}
|
|
1741
|
+
style={header_getSize(header) != null && header_getSize(header) !== 150 ? `width:${header_getSize(header)}px` : undefined}
|
|
1742
|
+
>
|
|
1743
|
+
{#if header.id === '_select'}
|
|
1744
|
+
{#if batchActions}
|
|
1745
|
+
<Checkbox
|
|
1746
|
+
aria-label={i18n.t('common.selectAll')}
|
|
1747
|
+
checked={table_getIsAllRowsSelected(tbl)}
|
|
1748
|
+
onCheckedChange={toggleAllRowsSelection}
|
|
1749
|
+
/>
|
|
1750
|
+
{:else if !acEnabled || deleteResourcePermission.isLoading || deleteResourcePermission.allowed}
|
|
1751
|
+
<Checkbox
|
|
1752
|
+
aria-label={i18n.t('common.selectAll')}
|
|
1753
|
+
checked={table_getIsAllRowsSelected(tbl)}
|
|
1754
|
+
onCheckedChange={toggleAllRowsSelection}
|
|
1755
|
+
/>
|
|
1756
|
+
{/if}
|
|
1757
|
+
{:else if header.id === '_expand'}
|
|
1758
|
+
<!-- empty -->
|
|
1759
|
+
{:else if header.id === '_actions'}
|
|
1760
|
+
<span class="svadmin-u-308fc069e46e svadmin-u-0214b4b355d1">{i18n.t('common.actions')}</span>
|
|
1761
|
+
{:else if column_getCanSort(header.column)}
|
|
1762
|
+
<Button
|
|
1763
|
+
variant="ghost"
|
|
1764
|
+
size="sm"
|
|
1765
|
+
class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-44ee8ba0a421 svadmin-u-ea7b2e9e070e svadmin-u-8ffe7a29179a svadmin-u-b8f0a08ece1e svadmin-u-660d2effb880 svadmin-u-d5eab218aa34 uppercase svadmin-u-8baf13a3e9d7 svadmin-u-7357df2b2e0c svadmin-u-e83a7042bc91"
|
|
1766
|
+
onclick={() => toggleColumnSort(header.column)}
|
|
1767
|
+
>
|
|
1768
|
+
{visibleFields.find(f => f.key === header.id)?.label ?? header.id}
|
|
1769
|
+
<span class="svadmin-u-359090c2d529 svadmin-u-0b8c506a0596">
|
|
1770
|
+
{#if column_getIsSorted(header.column) === 'asc'}↑
|
|
1771
|
+
{:else if column_getIsSorted(header.column) === 'desc'}↓
|
|
1772
|
+
{:else}⇅
|
|
1773
|
+
{/if}
|
|
1774
|
+
</span>
|
|
1775
|
+
</Button>
|
|
1776
|
+
{:else}
|
|
1777
|
+
{visibleFields.find(f => f.key === header.id)?.label ?? header.id}
|
|
1778
|
+
{/if}
|
|
1779
|
+
</Table.Head>
|
|
1780
|
+
{/snippet}
|
|
1781
|
+
</DraggableHeader>
|
|
1782
|
+
{/each}
|
|
1783
|
+
</Table.Header>
|
|
1784
|
+
</Table.Root>
|
|
1785
|
+
{/snippet}
|
|
1786
|
+
|
|
1787
|
+
{#snippet renderGridAfter()}
|
|
1788
|
+
<Table.Root density={currentDensity}>
|
|
1789
|
+
<Table.Body>
|
|
1790
|
+
{#each tableView.rows as row (row.id)}
|
|
1791
|
+
{#if expandedRowRender && rowIsExpanded(row.id)}
|
|
1792
|
+
<Table.Row><Table.Cell colspan={Math.max(1, gridColumns.length)}>
|
|
1793
|
+
{@render expandedRowRender({ record: copyTableRecord(row.original) })}
|
|
1794
|
+
</Table.Cell></Table.Row>
|
|
1795
|
+
{/if}
|
|
1796
|
+
{/each}
|
|
1797
|
+
</Table.Body>
|
|
1798
|
+
{#if summary}<Table.Footer>
|
|
1799
|
+
{@render summary({ data: pageRecords.data.map(copyTableRecord), total: query.data?.total ?? 0, visibleColumnsCount: gridColumns.length })}
|
|
1800
|
+
</Table.Footer>{/if}
|
|
1801
|
+
</Table.Root>
|
|
1802
|
+
{/snippet}
|
|
1803
|
+
|
|
1182
1804
|
{#snippet defaultRowActions(id: string | number)}
|
|
1183
1805
|
<RecordRowActions
|
|
1184
1806
|
{resourceName} {id} {canShow} {canEdit} {canDelete}
|
|
@@ -1208,7 +1830,7 @@
|
|
|
1208
1830
|
</DataState>
|
|
1209
1831
|
{/snippet}
|
|
1210
1832
|
|
|
1211
|
-
<div class="svadmin-u-6ed543e2fbbb">
|
|
1833
|
+
<div class="svadmin-u-6ed543e2fbbb" data-svadmin-rendering-resource={activeRendering?.resource.name} data-svadmin-rendering-kind={activeRendering ? 'table' : undefined}>
|
|
1212
1834
|
{#if operationError}<p role="alert">{operationError}</p>{/if}
|
|
1213
1835
|
{#if detailState.invalid}<p role="alert">{i18n.t('common.operationFailed')}</p>{/if}
|
|
1214
1836
|
{#if showHeader}
|
|
@@ -1217,9 +1839,21 @@
|
|
|
1217
1839
|
<h1 class="svadmin-u-42536e69e639 svadmin-u-998e0b29fe9e svadmin-u-e83a7042bc91 svadmin-u-d4108abe6359">{title ?? resource.label}</h1>
|
|
1218
1840
|
<div class="svadmin-u-60fbb7713999 svadmin-u-1eb5c6df38c1 svadmin-u-3960ffc248d9 svadmin-u-77a2a20e90d4">
|
|
1219
1841
|
{#if canExport}
|
|
1220
|
-
|
|
1221
|
-
<
|
|
1222
|
-
|
|
1842
|
+
{#if exportTaskName}
|
|
1843
|
+
<ExportButton
|
|
1844
|
+
resource={resourceName}
|
|
1845
|
+
taskName={exportTaskName}
|
|
1846
|
+
{...definedOptions({ taskProvider: exportTaskProvider, taskIdempotencyKey: exportTaskIdempotencyKey, maxItemCount: exportMaxItemCount })}
|
|
1847
|
+
format={exportFormat}
|
|
1848
|
+
filters={activeFilters}
|
|
1849
|
+
sorters={sorters}
|
|
1850
|
+
accessControl={{ enabled: acEnabled, hideIfUnauthorized: true }}
|
|
1851
|
+
/>
|
|
1852
|
+
{:else}
|
|
1853
|
+
<Button variant="outline" size="sm" onclick={exportCSV}>
|
|
1854
|
+
<Download class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" data-icon="inline-start" /> {i18n.t("common.export")}
|
|
1855
|
+
</Button>
|
|
1856
|
+
{/if}
|
|
1223
1857
|
{/if}
|
|
1224
1858
|
{#if headerActions}
|
|
1225
1859
|
{@render headerActions()}
|
|
@@ -1234,21 +1868,27 @@
|
|
|
1234
1868
|
{/if}
|
|
1235
1869
|
|
|
1236
1870
|
<!-- Selection Banner (Enterprise Batch Actions) -->
|
|
1237
|
-
{#if selectedCount > 0}
|
|
1871
|
+
{#if selectedCount > 0 || allMatchingSelected}
|
|
1238
1872
|
<div
|
|
1239
1873
|
class="svadmin-u-60fbb7713999 svadmin-u-8dddea0773ed svadmin-u-1004c0c3954c svadmin-u-ca6bcd4b6f3f svadmin-u-a6afccfc915b svadmin-u-375dc44df6e9 svadmin-u-e0d9cc7f0647 svadmin-u-03b4dd7f172b svadmin-u-5f22e64f2282 svadmin-u-fc7473ca09eb svadmin-u-d4108abe6359 svadmin-u-40137e897961 fade-in svadmin-u-625a4c3fbeb2 svadmin-u-259ce51fc8f3 svadmin-u-020ba687fa12 svadmin-u-9f76a62f4f44 svadmin-u-3b9871a0bf93"
|
|
1240
|
-
aria-label={
|
|
1874
|
+
aria-label={selectionLabel}
|
|
1241
1875
|
data-svadmin-batch-toolbar
|
|
1242
1876
|
>
|
|
1243
1877
|
<div class="svadmin-u-60fbb7713999 svadmin-u-7e0b7cdf1a94 svadmin-u-3960ffc248d9 svadmin-u-77a2a20e90d4 svadmin-u-fc7473ca09eb">
|
|
1244
|
-
<span class="svadmin-u-2689f3958069 svadmin-u-d4108abe6359 svadmin-u-3032cae0badb">{
|
|
1878
|
+
<span class="svadmin-u-2689f3958069 svadmin-u-d4108abe6359 svadmin-u-3032cae0badb">{selectionLabel}</span>
|
|
1245
1879
|
{#if deleteManyMutation.isPending}
|
|
1246
1880
|
<span class="svadmin-u-bfa603190748" role="status">{i18n.t("common.processing")}</span>
|
|
1247
1881
|
{/if}
|
|
1248
1882
|
</div>
|
|
1249
1883
|
<div class="svadmin-u-60fbb7713999 svadmin-u-1eb5c6df38c1 svadmin-u-3960ffc248d9 svadmin-u-77a2a20e90d4">
|
|
1250
|
-
{#if batchActions}
|
|
1251
|
-
{@render batchActions({ selectedIds })}
|
|
1884
|
+
{#if batchActions && selectionReady && selectedCount > 0}
|
|
1885
|
+
{@render batchActions({ selectedIds: [...selectedIds], selection: batchSelectionSnapshot() })}
|
|
1886
|
+
{/if}
|
|
1887
|
+
{#if !allMatchingSelected && canSelectAllMatching && selectedCount < matchingTotal}
|
|
1888
|
+
<Button variant="outline" size="sm" disabled={confirmPending || confirmOpen} onclick={selectAllMatching}>
|
|
1889
|
+
<Rows class="svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c" data-icon="inline-start" aria-hidden="true" />
|
|
1890
|
+
{i18n.t('common.selectAllMatching', { count: matchingTotal })}
|
|
1891
|
+
</Button>
|
|
1252
1892
|
{/if}
|
|
1253
1893
|
{#if canBatchDelete}
|
|
1254
1894
|
<Button
|
|
@@ -1351,9 +1991,22 @@
|
|
|
1351
1991
|
<!-- Right: Table Controls (Density, Columns, Saved Views, Refresh, Export if !showHeader) -->
|
|
1352
1992
|
<div class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-58284b4ea568 svadmin-u-012fbd121f37">
|
|
1353
1993
|
{#if !showHeader && canExport}
|
|
1354
|
-
|
|
1355
|
-
<
|
|
1356
|
-
|
|
1994
|
+
{#if exportTaskName}
|
|
1995
|
+
<ExportButton
|
|
1996
|
+
resource={resourceName}
|
|
1997
|
+
taskName={exportTaskName}
|
|
1998
|
+
{...definedOptions({ taskProvider: exportTaskProvider, taskIdempotencyKey: exportTaskIdempotencyKey, maxItemCount: exportMaxItemCount })}
|
|
1999
|
+
format={exportFormat}
|
|
2000
|
+
filters={activeFilters}
|
|
2001
|
+
sorters={sorters}
|
|
2002
|
+
hideText
|
|
2003
|
+
accessControl={{ enabled: acEnabled, hideIfUnauthorized: true }}
|
|
2004
|
+
/>
|
|
2005
|
+
{:else}
|
|
2006
|
+
<TooltipButton tooltip={i18n.t("common.export")} variant="outline" size="sm" class="svadmin-u-e7a768f922d2 svadmin-u-0b91436debbd" onclick={exportCSV}>
|
|
2007
|
+
<Download class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" aria-hidden="true" />
|
|
2008
|
+
</TooltipButton>
|
|
2009
|
+
{/if}
|
|
1357
2010
|
{/if}
|
|
1358
2011
|
|
|
1359
2012
|
{#if showDensitySwitcher}
|
|
@@ -1401,7 +2054,7 @@
|
|
|
1401
2054
|
<Popover.Root bind:open={savedViewsOpen}>
|
|
1402
2055
|
<Popover.Trigger>
|
|
1403
2056
|
{#snippet child({ props })}
|
|
1404
|
-
<Button variant="outline" size="sm" class="svadmin-u-e7a768f922d2 svadmin-u-0b91436debbd" {...props} aria-label={i18n.t("common.savedViews")}>
|
|
2057
|
+
<Button variant="outline" size="sm" class="svadmin-u-e7a768f922d2 svadmin-u-0b91436debbd" {...props} disabled={!preferenceIdentityReady} aria-label={i18n.t("common.savedViews")}>
|
|
1405
2058
|
<Bookmark class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" data-icon="inline-start" /> {activeSavedViewName ?? i18n.t("common.savedViews")}
|
|
1406
2059
|
</Button>
|
|
1407
2060
|
{/snippet}
|
|
@@ -1412,6 +2065,34 @@
|
|
|
1412
2065
|
<h4 class="svadmin-u-2689f3958069 svadmin-u-fc7473ca09eb">{i18n.t("common.savedViews")}</h4>
|
|
1413
2066
|
<p class="svadmin-u-b6b02c0ebef6 svadmin-u-359090c2d529 svadmin-u-bfa603190748">{i18n.t("common.savedViewsHint")}</p>
|
|
1414
2067
|
</div>
|
|
2068
|
+
{#if remoteViewsLoading}
|
|
2069
|
+
<p role="status">{i18n.t('common.loading')}</p>
|
|
2070
|
+
{:else if remoteViewsFailed}
|
|
2071
|
+
<p role="alert">{i18n.t('common.operationFailed')}</p>
|
|
2072
|
+
<Button type="button" variant="outline" size="sm" onclick={() => remoteViewsReload += 1}>
|
|
2073
|
+
{i18n.t('common.retry')}
|
|
2074
|
+
</Button>
|
|
2075
|
+
{/if}
|
|
2076
|
+
{#if savedViewMutationPending}
|
|
2077
|
+
<p role="status">{i18n.t('common.loading')}</p>
|
|
2078
|
+
{:else if savedViewMutationError}
|
|
2079
|
+
<p role="alert">{i18n.t(savedViewMutationError === 'conflict' ? 'common.viewConflict' : 'common.operationFailed')}</p>
|
|
2080
|
+
<Button type="button" variant="outline" size="sm" onclick={() => remoteViewsReload += 1}>
|
|
2081
|
+
{i18n.t('common.refresh')}
|
|
2082
|
+
</Button>
|
|
2083
|
+
{/if}
|
|
2084
|
+
{#if savedViewProvider?.save}
|
|
2085
|
+
<Select.Root aria-label={i18n.t('common.viewSource')} value={savedViewSource} disabled={savedViewMutationPending}
|
|
2086
|
+
onchange={(event: Event) => {
|
|
2087
|
+
if (!(event.currentTarget instanceof HTMLSelectElement)) return;
|
|
2088
|
+
const value = event.currentTarget.value;
|
|
2089
|
+
if (value === 'local' || value === 'team' || value === 'system') savedViewSource = value;
|
|
2090
|
+
}}>
|
|
2091
|
+
<option value="local">{i18n.t('common.personalView')}</option>
|
|
2092
|
+
<option value="team">{i18n.t('common.teamView')}</option>
|
|
2093
|
+
<option value="system">{i18n.t('common.systemView')}</option>
|
|
2094
|
+
</Select.Root>
|
|
2095
|
+
{/if}
|
|
1415
2096
|
<div class="svadmin-u-da7c36cd8867">
|
|
1416
2097
|
<label class="svadmin-u-359090c2d529 svadmin-u-bfa603190748" for="saved-list-view">{i18n.t("common.currentView")}</label>
|
|
1417
2098
|
<Select.Root
|
|
@@ -1421,16 +2102,17 @@
|
|
|
1421
2102
|
onchange={(event: Event) => {
|
|
1422
2103
|
if (!(event.currentTarget instanceof HTMLSelectElement)) return;
|
|
1423
2104
|
const id = event.currentTarget.value;
|
|
1424
|
-
const view =
|
|
2105
|
+
const view = availableSavedViews.find((candidate) => candidate.id === id);
|
|
1425
2106
|
if (view) applySavedView(view);
|
|
1426
2107
|
else {
|
|
2108
|
+
remoteDefaultEligible = false;
|
|
1427
2109
|
activeSavedViewId = undefined;
|
|
1428
2110
|
persistActiveSavedView();
|
|
1429
2111
|
}
|
|
1430
2112
|
}}
|
|
1431
2113
|
>
|
|
1432
2114
|
<option value="">{i18n.t("common.currentView")}</option>
|
|
1433
|
-
{#each
|
|
2115
|
+
{#each availableSavedViews as view (view.id)}
|
|
1434
2116
|
<option value={view.id}>{view.name}</option>
|
|
1435
2117
|
{/each}
|
|
1436
2118
|
</Select.Root>
|
|
@@ -1441,22 +2123,132 @@
|
|
|
1441
2123
|
placeholder={i18n.t("common.viewName")}
|
|
1442
2124
|
maxlength={60}
|
|
1443
2125
|
bind:value={savedViewName}
|
|
2126
|
+
disabled={savedViewMutationPending}
|
|
1444
2127
|
class="svadmin-u-e7a768f922d2"
|
|
1445
2128
|
/>
|
|
1446
|
-
<Button size="sm" class="svadmin-u-012fbd121f37" disabled={!savedViewName.trim()} onclick={saveCurrentView}>
|
|
2129
|
+
<Button size="sm" class="svadmin-u-012fbd121f37" disabled={!savedViewName.trim() || savedViewMutationPending || savedViewMutationError === 'conflict'} onclick={saveCurrentView}>
|
|
1447
2130
|
<Check class="svadmin-u-11e59c6d5f6b svadmin-u-dc7972ebf3f3" data-icon="inline-start" /> {i18n.t("common.saveView")}
|
|
1448
2131
|
</Button>
|
|
1449
2132
|
</div>
|
|
1450
|
-
{#if
|
|
2133
|
+
{#if availableSavedViews.length > 0}
|
|
1451
2134
|
<div class="svadmin-u-da7c36cd8867 svadmin-u-b950dda299d3 svadmin-u-18049387f0af svadmin-u-f46b61a9b310">
|
|
1452
|
-
{#each
|
|
2135
|
+
{#each availableSavedViews as view (view.id)}
|
|
1453
2136
|
<div class="svadmin-u-60fbb7713999 svadmin-u-3960ffc248d9 svadmin-u-8ef2268efbbc svadmin-u-77a2a20e90d4 svadmin-u-fc7473ca09eb">
|
|
1454
2137
|
<span class="svadmin-u-7e0b7cdf1a94 svadmin-u-f283ea9bea0e">{view.name}</span>
|
|
2138
|
+
{#if (view.source === 'team' || view.source === 'system') && savedViewProvider?.setDefault}
|
|
2139
|
+
<button
|
|
2140
|
+
type="button"
|
|
2141
|
+
class="svadmin-u-52083e7da442 svadmin-u-012fbd121f37 svadmin-u-bfa603190748"
|
|
2142
|
+
aria-label={`${i18n.t(view.default ? 'common.unsetDefaultView' : 'common.setDefaultView')} ${view.name}`}
|
|
2143
|
+
aria-pressed={view.default === true}
|
|
2144
|
+
title={i18n.t(view.default ? 'common.unsetDefaultView' : 'common.setDefaultView')}
|
|
2145
|
+
disabled={view.readOnly === true || !view.version || savedViewMutationPending || savedViewMutationError === 'conflict'}
|
|
2146
|
+
onclick={() => setRemoteDefault(view)}
|
|
2147
|
+
>
|
|
2148
|
+
<Star class={view.default ? "svadmin-u-e83a7042bc91" : ""} fill={view.default ? "currentColor" : "none"} />
|
|
2149
|
+
</button>
|
|
2150
|
+
{/if}
|
|
2151
|
+
{#if (view.source === 'team' || view.source === 'system') && savedViewProvider?.updateAccess}
|
|
2152
|
+
{@const draft = accessDrafts.get(view.id)}
|
|
2153
|
+
{@const accessMode = draft?.mode ?? view.access?.mode ?? ''}
|
|
2154
|
+
<Select.Root
|
|
2155
|
+
aria-label={`${i18n.t('common.viewAccess')} ${view.name}`}
|
|
2156
|
+
value={accessMode}
|
|
2157
|
+
disabled={view.readOnly !== false || !view.version || savedViewMutationPending || savedViewMutationError === 'conflict'}
|
|
2158
|
+
onchange={(event: Event) => {
|
|
2159
|
+
if (!(event.currentTarget instanceof HTMLSelectElement)) return;
|
|
2160
|
+
const mode = event.currentTarget.value;
|
|
2161
|
+
if (mode === 'team' || mode === 'organization' || mode === 'restricted') {
|
|
2162
|
+
accessDrafts.set(view.id, {
|
|
2163
|
+
mode,
|
|
2164
|
+
subjectIds: draft?.subjectIds ?? view.access?.subjectIds ?? [],
|
|
2165
|
+
});
|
|
2166
|
+
if (mode === 'restricted') loadAccessSubjects(view);
|
|
2167
|
+
}
|
|
2168
|
+
}}
|
|
2169
|
+
>
|
|
2170
|
+
<option value="" disabled>{i18n.t('common.selectOption')}</option>
|
|
2171
|
+
<option value="team">{i18n.t('common.teamView')}</option>
|
|
2172
|
+
<option value="organization">{i18n.t('common.accessOrganization')}</option>
|
|
2173
|
+
<option value="restricted">{i18n.t('common.accessRestricted')}</option>
|
|
2174
|
+
</Select.Root>
|
|
2175
|
+
{#if accessMode === 'restricted'}
|
|
2176
|
+
{#if savedViewProvider.listAccessSubjects}
|
|
2177
|
+
{@const selected = draft?.subjectIds ?? view.access?.subjectIds ?? []}
|
|
2178
|
+
<div>
|
|
2179
|
+
<Input
|
|
2180
|
+
aria-label={`${i18n.t('common.search')} ${view.name}`}
|
|
2181
|
+
value={accessSubjectQueries.get(view.id) ?? ''}
|
|
2182
|
+
disabled={view.readOnly !== false || savedViewMutationPending}
|
|
2183
|
+
oninput={(event: Event) => {
|
|
2184
|
+
if (!(event.currentTarget instanceof HTMLInputElement)) return;
|
|
2185
|
+
accessSubjectQueries.set(view.id, event.currentTarget.value);
|
|
2186
|
+
loadAccessSubjects(view, event.currentTarget.value);
|
|
2187
|
+
}}
|
|
2188
|
+
/>
|
|
2189
|
+
<Button type="button" size="sm" variant="outline"
|
|
2190
|
+
aria-label={`${i18n.t('common.refresh')} ${view.name}`}
|
|
2191
|
+
disabled={view.readOnly !== false || savedViewMutationPending}
|
|
2192
|
+
onclick={() => loadAccessSubjects(view, accessSubjectQueries.get(view.id) ?? '')}>
|
|
2193
|
+
<RefreshCw />
|
|
2194
|
+
</Button>
|
|
2195
|
+
{#if accessSubjectsLoading.get(view.id)}
|
|
2196
|
+
<span role="status">{i18n.t('common.loading')}</span>
|
|
2197
|
+
{:else if accessSubjectsFailed.get(view.id)}
|
|
2198
|
+
<span role="alert">{i18n.t('common.operationFailed')}</span>
|
|
2199
|
+
{:else if accessSubjects.has(view.id) && !accessSubjects.get(view.id)?.length}
|
|
2200
|
+
<span role="status">{i18n.t('common.noData')}</span>
|
|
2201
|
+
{/if}
|
|
2202
|
+
{#each selected as id (id)}
|
|
2203
|
+
<label>
|
|
2204
|
+
<input type="checkbox" checked
|
|
2205
|
+
disabled={view.readOnly !== false || savedViewMutationPending || savedViewMutationError === 'conflict'}
|
|
2206
|
+
onchange={() => accessDrafts.set(view.id, {
|
|
2207
|
+
mode: 'restricted', subjectIds: selected.filter(candidate => candidate !== id),
|
|
2208
|
+
})} />
|
|
2209
|
+
{accessSubjects.get(view.id)?.find(subject => subject.id === id)?.label ?? id}
|
|
2210
|
+
</label>
|
|
2211
|
+
{/each}
|
|
2212
|
+
{#each (accessSubjects.get(view.id) ?? []).filter(subject => !selected.includes(subject.id)) as subject (subject.id)}
|
|
2213
|
+
<label>
|
|
2214
|
+
<input type="checkbox"
|
|
2215
|
+
disabled={view.readOnly !== false || savedViewMutationPending || savedViewMutationError === 'conflict' || selected.length >= 200}
|
|
2216
|
+
onchange={() => accessDrafts.set(view.id, {
|
|
2217
|
+
mode: 'restricted', subjectIds: [...selected, subject.id],
|
|
2218
|
+
})} />
|
|
2219
|
+
{subject.label}
|
|
2220
|
+
</label>
|
|
2221
|
+
{/each}
|
|
2222
|
+
</div>
|
|
2223
|
+
{:else}
|
|
2224
|
+
<Input
|
|
2225
|
+
aria-label={`${i18n.t('common.accessSubjects')} ${view.name}`}
|
|
2226
|
+
value={(draft?.subjectIds ?? view.access?.subjectIds ?? []).join(', ')}
|
|
2227
|
+
disabled={view.readOnly !== false || !view.version || savedViewMutationPending || savedViewMutationError === 'conflict'}
|
|
2228
|
+
oninput={(event: Event) => {
|
|
2229
|
+
if (event.currentTarget instanceof HTMLInputElement) {
|
|
2230
|
+
accessDrafts.set(view.id, { mode: 'restricted', subjectIds: event.currentTarget.value.split(',').map(id => id.trim()) });
|
|
2231
|
+
}
|
|
2232
|
+
}}
|
|
2233
|
+
/>
|
|
2234
|
+
{/if}
|
|
2235
|
+
{/if}
|
|
2236
|
+
<Button type="button" size="sm"
|
|
2237
|
+
aria-label={`${i18n.t('common.saveAccess')} ${view.name}`}
|
|
2238
|
+
disabled={!draft || view.readOnly !== false || !view.version
|
|
2239
|
+
|| savedViewMutationPending || savedViewMutationError === 'conflict'
|
|
2240
|
+
|| (draft.mode === 'restricted' && !decodeSavedListViewAccess(draft))}
|
|
2241
|
+
onclick={() => saveRemoteAccess(view)}>
|
|
2242
|
+
{i18n.t('common.saveAccess')}
|
|
2243
|
+
</Button>
|
|
2244
|
+
{/if}
|
|
1455
2245
|
<button
|
|
1456
2246
|
type="button"
|
|
1457
2247
|
class="svadmin-u-52083e7da442 svadmin-u-cc46d0fa277d svadmin-u-012fbd121f37 svadmin-u-3960ffc248d9 svadmin-u-86843cf1e227 svadmin-u-421ac2be5045 svadmin-u-bfa603190748 svadmin-u-8e551981c8d7 svadmin-u-ea7b2e9e070e svadmin-u-f10f771f87e9 svadmin-u-793c80e97ffb svadmin-u-9c1295a6914a"
|
|
1458
2248
|
aria-label="{i18n.t("common.delete")} {view.name}"
|
|
1459
2249
|
title="{i18n.t("common.delete")} {view.name}"
|
|
2250
|
+
disabled={view.readOnly === true || savedViewMutationPending || savedViewMutationError === 'conflict'
|
|
2251
|
+
|| (!!view.source && !savedViewProvider?.remove)}
|
|
1460
2252
|
onclick={() => deleteSavedView(view.id)}
|
|
1461
2253
|
>
|
|
1462
2254
|
<Trash2 class="svadmin-u-783b0d9d1e2c" />
|
|
@@ -1589,6 +2381,12 @@
|
|
|
1589
2381
|
description={i18n.t('common.operationFailed')}
|
|
1590
2382
|
retry={refreshList}
|
|
1591
2383
|
/>
|
|
2384
|
+
{:else if gridBody && pageRecords.data.length > 0}
|
|
2385
|
+
{@render gridBody({ records: pageRecords.data.map(copyTableRecord), columns: gridColumns, primaryKey: 'id',
|
|
2386
|
+
sorters: querySorters, density: currentDensity, locale: i18n.locale, label: resource.label,
|
|
2387
|
+
scopeKey: JSON.stringify([resourceName, queryPagination, querySorters, queryFilters, adminContext.tenantCacheKey?.__svadminTenant]),
|
|
2388
|
+
disabled: query.isFetching || deleteManyMutation.isPending,
|
|
2389
|
+
onSortChange: changeGridSort, cell: renderGridCell, controls: renderGridControls, after: renderGridAfter })}
|
|
1592
2390
|
{:else}
|
|
1593
2391
|
<div in:fade={{ duration: 150 }}>
|
|
1594
2392
|
<!-- Desktop Table (hidden on mobile) -->
|
|
@@ -1611,14 +2409,14 @@
|
|
|
1611
2409
|
{#if header.id === '_select'}
|
|
1612
2410
|
{#if batchActions}
|
|
1613
2411
|
<Checkbox
|
|
1614
|
-
aria-label={i18n.t('common.
|
|
1615
|
-
checked={table_getIsAllRowsSelected(tbl)}
|
|
2412
|
+
aria-label={i18n.t(allMatchingSelected ? 'common.clearSelection' : 'common.selectAllOnPage')}
|
|
2413
|
+
checked={allMatchingSelected || table_getIsAllRowsSelected(tbl)}
|
|
1616
2414
|
onCheckedChange={toggleAllRowsSelection}
|
|
1617
2415
|
/>
|
|
1618
2416
|
{:else if !acEnabled || deleteResourcePermission.isLoading || deleteResourcePermission.allowed}
|
|
1619
2417
|
<Checkbox
|
|
1620
|
-
aria-label={i18n.t('common.
|
|
1621
|
-
checked={table_getIsAllRowsSelected(tbl)}
|
|
2418
|
+
aria-label={i18n.t(allMatchingSelected ? 'common.clearSelection' : 'common.selectAllOnPage')}
|
|
2419
|
+
checked={allMatchingSelected || table_getIsAllRowsSelected(tbl)}
|
|
1622
2420
|
onCheckedChange={toggleAllRowsSelection}
|
|
1623
2421
|
/>
|
|
1624
2422
|
{/if}
|
|
@@ -1665,6 +2463,7 @@
|
|
|
1665
2463
|
<Checkbox
|
|
1666
2464
|
aria-label={i18n.t('common.selectRow', { id })}
|
|
1667
2465
|
checked={rowIsSelected(row.id)}
|
|
2466
|
+
disabled={confirmPending}
|
|
1668
2467
|
onCheckedChange={() => toggleRowSelection(row)}
|
|
1669
2468
|
/>
|
|
1670
2469
|
{:else}
|
|
@@ -1672,6 +2471,7 @@
|
|
|
1672
2471
|
<Checkbox
|
|
1673
2472
|
aria-label={i18n.t('common.selectRow', { id })}
|
|
1674
2473
|
checked={rowIsSelected(row.id)}
|
|
2474
|
+
disabled={confirmPending}
|
|
1675
2475
|
onCheckedChange={() => toggleRowSelection(row)}
|
|
1676
2476
|
/>
|
|
1677
2477
|
</CanAccess>
|
|
@@ -1793,6 +2593,7 @@
|
|
|
1793
2593
|
<Checkbox
|
|
1794
2594
|
aria-label={i18n.t('common.selectRow', { id })}
|
|
1795
2595
|
checked={rowIsSelected(row.id)}
|
|
2596
|
+
disabled={confirmPending}
|
|
1796
2597
|
onCheckedChange={() => toggleRowSelection(row)}
|
|
1797
2598
|
/>
|
|
1798
2599
|
{:else}
|
|
@@ -1800,6 +2601,7 @@
|
|
|
1800
2601
|
<Checkbox
|
|
1801
2602
|
aria-label={i18n.t('common.selectRow', { id })}
|
|
1802
2603
|
checked={rowIsSelected(row.id)}
|
|
2604
|
+
disabled={confirmPending}
|
|
1803
2605
|
onCheckedChange={() => toggleRowSelection(row)}
|
|
1804
2606
|
/>
|
|
1805
2607
|
</CanAccess>
|
|
@@ -1921,6 +2723,7 @@
|
|
|
1921
2723
|
|
|
1922
2724
|
{#if detailRecordId != null}
|
|
1923
2725
|
<RecordDetailDrawer
|
|
2726
|
+
rendering={activeRendering}
|
|
1924
2727
|
resourceName={resourceName}
|
|
1925
2728
|
open={detailOpen}
|
|
1926
2729
|
recordId={detailRecordId}
|
|
@@ -1930,6 +2733,6 @@
|
|
|
1930
2733
|
|
|
1931
2734
|
{#if quickEditId != null && canRead && canEdit}
|
|
1932
2735
|
{#key quickEditId}
|
|
1933
|
-
<QuickEditDrawer {resourceName} recordId={quickEditId} onClose={() => quickEditId = undefined} />
|
|
2736
|
+
<QuickEditDrawer rendering={activeRendering} {resourceName} recordId={quickEditId} onClose={() => quickEditId = undefined} />
|
|
1934
2737
|
{/key}
|
|
1935
2738
|
{/if}
|