@isoftdata/svelte-user-configuration 2.3.2 → 2.4.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.
@@ -1,304 +1,312 @@
1
- <script lang="ts">
2
- import type { ButtonColors } from '@isoftdata/utility-bootstrap'
3
- import type { i18n } from 'i18next'
4
- import type { Merge } from 'type-fest'
5
- import type { IconName, SiteLabel, HTMLDivAttributes } from './'
6
- import type { Snippet } from 'svelte'
7
-
8
- import { getContext } from 'svelte'
9
- import camelCase from 'just-camel-case'
10
- import Icon from '@isoftdata/svelte-icon'
11
- import Button from '@isoftdata/svelte-button'
12
- import Select from '@isoftdata/svelte-select'
13
- import { getEventValueEnum } from '@isoftdata/browser-event'
14
- import { Table, Td, type Column } from '@isoftdata/svelte-table'
15
- import { translate as defaultTranslate } from '@isoftdata/utility-string'
16
- import { SvelteSet, SvelteMap } from 'svelte/reactivity'
17
- import { RadioButtonGroup } from '@isoftdata/svelte-checkbox'
18
- import Badge from '@isoftdata/svelte-badge'
19
-
20
- const { t: translate } = getContext<i18n>('i18next') || { t: defaultTranslate }
21
-
22
- type Scope = 'NONE' | 'SITE' | 'GLOBAL'
23
- type Permission = {
24
- id: number
25
- displayName: string
26
- category: string
27
- codeName: string
28
- }
29
- type ComputedPermission = Merge<
30
- Permission,
31
- { value: PermissionValue; groupValue?: PermissionValue | undefined; computedValue?: PermissionValue }
32
- >
33
- type ComputedPermissionColumns = Array<Column<ComputedPermission>>
34
- type PermissionValue = keyof typeof permissionValueList
35
- type PermissionValueMap = Map<number, PermissionValue>
36
-
37
- interface Props extends HTMLDivAttributes {
38
- permissions: Array<Permission>
39
- siteLabel?: SiteLabel
40
- enableSiteScope?: boolean
41
- permissionValueChange?:
42
- | ((change: { value: PermissionValue; permissionIds: Array<number> }) => void | Promise<void>)
43
- | undefined
44
- icon?: IconName
45
- cardHeaderTitle?: string
46
- permissionValueMap: PermissionValueMap
47
- groupPermissionValueMap?: PermissionValueMap | undefined
48
- children?: Snippet
49
- }
50
-
51
- let {
52
- permissions,
53
- siteLabel = 'Site',
54
- enableSiteScope = true,
55
- permissionValueChange = undefined,
56
- icon = 'user-lock',
57
- cardHeaderTitle = translate('common:permissions', 'Permissions'),
58
- permissionValueMap = $bindable(new SvelteMap()),
59
- groupPermissionValueMap = undefined,
60
- children,
61
- ...rest
62
- }: Props = $props()
63
-
64
- let selectedPermissionIds: SvelteSet<number> = $state(new SvelteSet())
65
- let selectedPermissionValue: string | null = $state(null)
66
-
67
- const permissionColumns: ComputedPermissionColumns = getColumns(groupPermissionValueMap)
68
-
69
- function getColumns(groupPermissionValueMap: PermissionValueMap | undefined): ComputedPermissionColumns {
70
- let columns: ComputedPermissionColumns = [
71
- {
72
- property: 'value',
73
- name: groupPermissionValueMap ? translate('common:user', 'User') : translate('common:permission', 'Permission'),
74
- width: '1rem',
75
- },
76
- { property: 'category', name: translate('common:category', 'Category') },
77
- { property: 'displayName', name: translate('common:name', 'Name') },
78
- ]
79
-
80
- if (groupPermissionValueMap) {
81
- columns.splice(
82
- 1,
83
- 0,
84
- {
85
- property: 'groupValue',
86
- name: translate('common:group', 'Group'),
87
- align: 'center',
88
- width: '1rem',
89
- minWidth: '25%',
90
- },
91
- {
92
- property: 'computedValue',
93
- name: translate('common:access', 'Access'),
94
- align: 'center',
95
- width: '1rem',
96
- minWidth: '25%',
97
- },
98
- )
99
- }
100
-
101
- return columns
102
- }
103
-
104
- function selectPermissions(permissionId: number) {
105
- selectedPermissionIds.has(permissionId)
106
- ? selectedPermissionIds.delete(permissionId)
107
- : selectedPermissionIds.add(permissionId)
108
- }
109
-
110
- function selectAndDeselectAllPermissions() {
111
- if (selectedPermissionIds.size === computedPermissions.length) {
112
- selectedPermissionIds.clear()
113
- selectedPermissionValue = null
114
- } else {
115
- selectedPermissionIds = new SvelteSet(permissions.map(permission => permission.id))
116
- }
117
- }
118
-
119
- async function updatePermissionValues(newValue: Scope, permissionIds: SvelteSet<number>) {
120
- const permissionIdsArray = Array.from(permissionIds)
121
- for (const id of permissionIdsArray) {
122
- permissionValueMap.set(id, newValue)
123
- }
124
- permissionValueMap = permissionValueMap
125
- await permissionValueChange?.({
126
- value: newValue,
127
- permissionIds: permissionIdsArray,
128
- })
129
- }
130
-
131
- function getComputedPermissions(
132
- permissions: Array<Permission>,
133
- permissionValueMap: PermissionValueMap,
134
- groupPermissionValueMap: PermissionValueMap | undefined,
135
- ): Array<ComputedPermission> {
136
- return permissions.map(permission => {
137
- const value = permissionValueMap.get(permission.id) ?? 'NONE'
138
- const groupValue = groupPermissionValueMap?.get(permission.id)
139
-
140
- return {
141
- ...permission,
142
- displayName: translate(`permissions:${camelCase(permission.codeName)}.displayName`, permission.displayName),
143
- category: translate(`permissions:categories.${camelCase(permission.category)}`, permission.category),
144
- value,
145
- groupValue,
146
- computedValue:
147
- (value === 'NONE' && groupValue) || (value === 'SITE' && groupValue === 'GLOBAL') ? groupValue : value,
148
- }
149
- })
150
- }
151
-
152
- const permissionValueList: Record<Scope, { label: string; value: Scope; color: ButtonColors }> = $derived({
153
- NONE: {
154
- label: enableSiteScope ? translate('common:permissionLevel.none', 'None') : translate('common:off', 'Off'),
155
- value: 'NONE',
156
- color: 'danger',
157
- },
158
- SITE: {
159
- label: translate(`common:permissionLevel.${siteLabel.toLowerCase()}`, siteLabel),
160
- value: 'SITE',
161
- color: 'primary',
162
- },
163
- GLOBAL: {
164
- label: enableSiteScope ? translate('common:permissionLevel.global', 'Global') : translate('common:on', 'On'),
165
- value: 'GLOBAL',
166
- color: 'success',
167
- },
168
- })
169
- const permissionValues = $derived(
170
- Object.values(permissionValueList).filter(permissionValue => enableSiteScope || permissionValue.value !== 'SITE'),
171
- )
172
- const computedPermissions = $derived(getComputedPermissions(permissions, permissionValueMap, groupPermissionValueMap))
173
- $effect(() => {
174
- if (
175
- !enableSiteScope &&
176
- computedPermissions.some(permission => [permission.value, permission.groupValue].includes('SITE'))
177
- ) {
178
- console.warn(
179
- 'PermissionList: Permissions with site scope are not supported when `enableSiteScope` is false. Please ensure you want to disable site scope, or update/remove these permissions.',
180
- )
181
- }
182
- })
183
- </script>
184
-
185
- <div
186
- class="card"
187
- {...rest}
188
- >
189
- <div class="card-header">
190
- <h5 class="mb-0">
191
- <Icon
192
- {icon}
193
- class="mr-1 me-1"
194
- />
195
- {cardHeaderTitle}
196
- </h5>
197
- </div>
198
- <div class="card-body">
199
- <Table
200
- class="mb-0"
201
- idProp="id"
202
- parentClass="mh-60vh overflow-y-auto"
203
- filterPlaceholder={translate('common:filterPermissions', 'Filter Permissions')}
204
- filterColumnClass="col-6 col-lg-4 align-self-end"
205
- rows={computedPermissions}
206
- columns={permissionColumns}
207
- responsive
208
- filterEnabled
209
- >
210
- {#snippet children({ row: permission })}
211
- <tr
212
- onclick={() => selectPermissions(permission.id)}
213
- class:table-primary={selectedPermissionIds.has(permission.id)}
214
- class="cursor-pointer"
215
- >
216
- <Td property="value">
217
- <div
218
- class="btn-group btn-group-toggle"
219
- data-toggle="buttons"
220
- >
221
- <RadioButtonGroup
222
- options={permissionValues}
223
- value={permission.value}
224
- unselectedColor="secondary"
225
- change={value => updatePermissionValues(value, new SvelteSet([permission.id]))}
226
- ></RadioButtonGroup>
227
- </div>
228
- </Td>
229
- {#if groupPermissionValueMap}
230
- <Td
231
- property="groupValue"
232
- class="px-4"
233
- >
234
- <Badge
235
- pill
236
- color={permissionValueList[permission.groupValue ?? 'NONE'].color}
237
- >{permissionValueList[permission.groupValue ?? 'NONE'].label}</Badge
238
- >
239
- </Td>
240
- <Td
241
- property="computedValue"
242
- class="px-4"
243
- >
244
- <Badge
245
- pill
246
- color={permissionValueList[permission.computedValue ?? 'NONE'].color}
247
- >{permissionValueList[permission.computedValue ?? 'NONE'].label}</Badge
248
- >
249
- </Td>
250
- {/if}
251
- <Td property="category">{permission.category}</Td>
252
- <Td property="displayName">{permission.displayName}</Td>
253
- </tr>
254
- {/snippet}
255
- </Table>
256
- {@render children?.()}
257
- </div>
258
- <div class="card-footer">
259
- <div class="d-flex justify-content-between align-items-end">
260
- <Select
261
- label="{translate('common:setSelectedTo', 'Set Selected To')}:"
262
- bind:value={selectedPermissionValue}
263
- showEmptyOption={selectedPermissionValue === null}
264
- emptyValue={null}
265
- emptyText={translate('common:selectValue', 'Select a Value')}
266
- labelParentClass="form-inline mr-2 me-2"
267
- labelClass="mr-2 me-2 p-0"
268
- disabled={selectedPermissionIds.size === 0}
269
- onchange={e => {
270
- const value = getEventValueEnum(e, 'NONE', 'SITE', 'GLOBAL')
271
- updatePermissionValues(value, selectedPermissionIds)
272
- selectedPermissionIds.clear()
273
- selectedPermissionValue = null
274
- }}
275
- >
276
- {#each Object.values(permissionValueList) as permission}
277
- {#if enableSiteScope || permission.value !== 'SITE'}
278
- <option value={permission.value}>{permission.label}</option>
279
- {/if}
280
- {/each}
281
- </Select>
282
- <Button
283
- class="mb-1"
284
- outline
285
- size="sm"
286
- iconClass="check-double"
287
- onclick={selectAndDeselectAllPermissions}
288
- >
289
- <span class:d-none={selectedPermissionIds.size !== computedPermissions.length}
290
- >{translate('common:deselectAll', 'Deselect All')}</span
291
- >
292
- <span class:d-none={selectedPermissionIds.size === computedPermissions.length}
293
- >{translate('common:selectAll', 'Select All')}</span
294
- >
295
- </Button>
296
- </div>
297
- </div>
298
- </div>
299
-
300
- <style>
301
- .cursor-pointer {
302
- cursor: pointer;
303
- }
304
- </style>
1
+ <script lang="ts">
2
+ import type { ButtonColors } from '@isoftdata/utility-bootstrap'
3
+ import type { i18n } from 'i18next'
4
+ import type { Merge } from 'type-fest'
5
+ import type { IconName, SiteLabel, HTMLDivAttributes } from './'
6
+ import type { Snippet } from 'svelte'
7
+
8
+ import { getContext } from 'svelte'
9
+ import camelCase from 'just-camel-case'
10
+ import Icon from '@isoftdata/svelte-icon'
11
+ import Button from '@isoftdata/svelte-button'
12
+ import Select from '@isoftdata/svelte-select'
13
+ import { getEventValueEnum } from '@isoftdata/browser-event'
14
+ import { Table, Td, type Column } from '@isoftdata/svelte-table'
15
+ import { translate as defaultTranslate } from '@isoftdata/utility-string'
16
+ import { SvelteSet, SvelteMap } from 'svelte/reactivity'
17
+ import { RadioButtonGroup } from '@isoftdata/svelte-checkbox'
18
+ import Badge from '@isoftdata/svelte-badge'
19
+
20
+ const { t: translate } = getContext<i18n>('i18next') || { t: defaultTranslate }
21
+
22
+ type Scope = 'NONE' | 'SITE' | 'GLOBAL'
23
+ type Permission = {
24
+ id: number
25
+ displayName: string
26
+ category: string
27
+ codeName: string
28
+ }
29
+ type ComputedPermission = Merge<
30
+ Permission,
31
+ { value: PermissionValue; groupValue?: PermissionValue | undefined; computedValue?: PermissionValue }
32
+ >
33
+ type ComputedPermissionColumns = Array<Column<ComputedPermission>>
34
+ type PermissionValue = keyof typeof permissionValueList
35
+ type PermissionValueMap = Map<number, PermissionValue>
36
+
37
+ interface Props extends HTMLDivAttributes {
38
+ permissions: Array<Permission>
39
+ siteLabel?: SiteLabel
40
+ enableSiteScope?: boolean
41
+ permissionValueChange?:
42
+ | ((change: { value: PermissionValue; permissionIds: Array<number> }) => void | Promise<void>)
43
+ | undefined
44
+ icon?: IconName
45
+ cardHeaderTitle?: string
46
+ permissionValueMap: PermissionValueMap
47
+ groupPermissionValueMap?: PermissionValueMap | undefined
48
+ children?: Snippet
49
+ permissionTranslationNamespace?: string
50
+ }
51
+
52
+ let {
53
+ permissions,
54
+ siteLabel = 'Site',
55
+ enableSiteScope = true,
56
+ permissionValueChange = undefined,
57
+ icon = 'user-lock',
58
+ cardHeaderTitle = translate('common:permissions', 'Permissions'),
59
+ permissionValueMap = $bindable(new SvelteMap()),
60
+ groupPermissionValueMap = undefined,
61
+ children,
62
+ permissionTranslationNamespace = 'permissions',
63
+ ...rest
64
+ }: Props = $props()
65
+
66
+ let selectedPermissionIds: SvelteSet<number> = $state(new SvelteSet())
67
+ let selectedPermissionValue: string | null = $state(null)
68
+
69
+ const permissionColumns: ComputedPermissionColumns = getColumns(groupPermissionValueMap)
70
+
71
+ function getColumns(groupPermissionValueMap: PermissionValueMap | undefined): ComputedPermissionColumns {
72
+ let columns: ComputedPermissionColumns = [
73
+ {
74
+ property: 'value',
75
+ name: groupPermissionValueMap ? translate('common:user', 'User') : translate('common:permission', 'Permission'),
76
+ width: '1rem',
77
+ },
78
+ { property: 'category', name: translate('common:category', 'Category') },
79
+ { property: 'displayName', name: translate('common:name', 'Name') },
80
+ ]
81
+
82
+ if (groupPermissionValueMap) {
83
+ columns.splice(
84
+ 1,
85
+ 0,
86
+ {
87
+ property: 'groupValue',
88
+ name: translate('common:group', 'Group'),
89
+ align: 'center',
90
+ width: '1rem',
91
+ minWidth: '25%',
92
+ },
93
+ {
94
+ property: 'computedValue',
95
+ name: translate('common:access', 'Access'),
96
+ align: 'center',
97
+ width: '1rem',
98
+ minWidth: '25%',
99
+ },
100
+ )
101
+ }
102
+
103
+ return columns
104
+ }
105
+
106
+ function selectPermissions(permissionId: number) {
107
+ selectedPermissionIds.has(permissionId)
108
+ ? selectedPermissionIds.delete(permissionId)
109
+ : selectedPermissionIds.add(permissionId)
110
+ }
111
+
112
+ function selectAndDeselectAllPermissions() {
113
+ if (selectedPermissionIds.size === computedPermissions.length) {
114
+ selectedPermissionIds.clear()
115
+ selectedPermissionValue = null
116
+ } else {
117
+ selectedPermissionIds = new SvelteSet(permissions.map(permission => permission.id))
118
+ }
119
+ }
120
+
121
+ async function updatePermissionValues(newValue: Scope, permissionIds: SvelteSet<number>) {
122
+ const permissionIdsArray = Array.from(permissionIds)
123
+ for (const id of permissionIdsArray) {
124
+ permissionValueMap.set(id, newValue)
125
+ }
126
+ permissionValueMap = permissionValueMap
127
+ await permissionValueChange?.({
128
+ value: newValue,
129
+ permissionIds: permissionIdsArray,
130
+ })
131
+ }
132
+
133
+ function getComputedPermissions(
134
+ permissions: Array<Permission>,
135
+ permissionValueMap: PermissionValueMap,
136
+ groupPermissionValueMap: PermissionValueMap | undefined,
137
+ ): Array<ComputedPermission> {
138
+ return permissions.map(permission => {
139
+ const value = permissionValueMap.get(permission.id) ?? 'NONE'
140
+ const groupValue = groupPermissionValueMap?.get(permission.id)
141
+
142
+ return {
143
+ ...permission,
144
+ displayName: translate(
145
+ `${permissionTranslationNamespace}:${camelCase(permission.codeName)}.displayName`,
146
+ permission.displayName,
147
+ ),
148
+ category: translate(
149
+ `${permissionTranslationNamespace}:categories.${camelCase(permission.category)}`,
150
+ permission.category,
151
+ ),
152
+ value,
153
+ groupValue,
154
+ computedValue:
155
+ (value === 'NONE' && groupValue) || (value === 'SITE' && groupValue === 'GLOBAL') ? groupValue : value,
156
+ }
157
+ })
158
+ }
159
+
160
+ const permissionValueList: Record<Scope, { label: string; value: Scope; color: ButtonColors }> = $derived({
161
+ NONE: {
162
+ label: enableSiteScope ? translate('common:permissionLevel.none', 'None') : translate('common:off', 'Off'),
163
+ value: 'NONE',
164
+ color: 'danger',
165
+ },
166
+ SITE: {
167
+ label: translate(`common:permissionLevel.${siteLabel.toLowerCase()}`, siteLabel),
168
+ value: 'SITE',
169
+ color: 'primary',
170
+ },
171
+ GLOBAL: {
172
+ label: enableSiteScope ? translate('common:permissionLevel.global', 'Global') : translate('common:on', 'On'),
173
+ value: 'GLOBAL',
174
+ color: 'success',
175
+ },
176
+ })
177
+ const permissionValues = $derived(
178
+ Object.values(permissionValueList).filter(permissionValue => enableSiteScope || permissionValue.value !== 'SITE'),
179
+ )
180
+ const computedPermissions = $derived(getComputedPermissions(permissions, permissionValueMap, groupPermissionValueMap))
181
+ $effect(() => {
182
+ if (
183
+ !enableSiteScope &&
184
+ computedPermissions.some(permission => [permission.value, permission.groupValue].includes('SITE'))
185
+ ) {
186
+ console.warn(
187
+ 'PermissionList: Permissions with site scope are not supported when `enableSiteScope` is false. Please ensure you want to disable site scope, or update/remove these permissions.',
188
+ )
189
+ }
190
+ })
191
+ </script>
192
+
193
+ <div
194
+ class="card"
195
+ {...rest}
196
+ >
197
+ <div class="card-header">
198
+ <h5 class="mb-0">
199
+ <Icon
200
+ {icon}
201
+ class="mr-1 me-1"
202
+ />
203
+ {cardHeaderTitle}
204
+ </h5>
205
+ </div>
206
+ <div class="card-body">
207
+ <Table
208
+ class="mb-0"
209
+ idProp="id"
210
+ parentClass="mh-60vh overflow-y-auto"
211
+ filterPlaceholder={translate('common:filterPermissions', 'Filter Permissions')}
212
+ filterColumnClass="col-6 col-lg-4 align-self-end"
213
+ rows={computedPermissions}
214
+ columns={permissionColumns}
215
+ responsive
216
+ filterEnabled
217
+ >
218
+ {#snippet children({ row: permission })}
219
+ <tr
220
+ onclick={() => selectPermissions(permission.id)}
221
+ class:table-primary={selectedPermissionIds.has(permission.id)}
222
+ class="cursor-pointer"
223
+ >
224
+ <Td property="value">
225
+ <div
226
+ class="btn-group btn-group-toggle"
227
+ data-toggle="buttons"
228
+ >
229
+ <RadioButtonGroup
230
+ options={permissionValues}
231
+ value={permission.value}
232
+ unselectedColor="secondary"
233
+ change={value => updatePermissionValues(value, new SvelteSet([permission.id]))}
234
+ ></RadioButtonGroup>
235
+ </div>
236
+ </Td>
237
+ {#if groupPermissionValueMap}
238
+ <Td
239
+ property="groupValue"
240
+ class="px-4"
241
+ >
242
+ <Badge
243
+ pill
244
+ color={permissionValueList[permission.groupValue ?? 'NONE'].color}
245
+ >{permissionValueList[permission.groupValue ?? 'NONE'].label}</Badge
246
+ >
247
+ </Td>
248
+ <Td
249
+ property="computedValue"
250
+ class="px-4"
251
+ >
252
+ <Badge
253
+ pill
254
+ color={permissionValueList[permission.computedValue ?? 'NONE'].color}
255
+ >{permissionValueList[permission.computedValue ?? 'NONE'].label}</Badge
256
+ >
257
+ </Td>
258
+ {/if}
259
+ <Td property="category">{permission.category}</Td>
260
+ <Td property="displayName">{permission.displayName}</Td>
261
+ </tr>
262
+ {/snippet}
263
+ </Table>
264
+ {@render children?.()}
265
+ </div>
266
+ <div class="card-footer">
267
+ <div class="d-flex justify-content-between align-items-end">
268
+ <Select
269
+ label="{translate('common:setSelectedTo', 'Set Selected To')}:"
270
+ bind:value={selectedPermissionValue}
271
+ showEmptyOption={selectedPermissionValue === null}
272
+ emptyValue={null}
273
+ emptyText={translate('common:selectValue', 'Select a Value')}
274
+ labelParentClass="form-inline mr-2 me-2"
275
+ labelClass="mr-2 me-2 p-0"
276
+ disabled={selectedPermissionIds.size === 0}
277
+ onchange={e => {
278
+ const value = getEventValueEnum(e, 'NONE', 'SITE', 'GLOBAL')
279
+ updatePermissionValues(value, selectedPermissionIds)
280
+ selectedPermissionIds.clear()
281
+ selectedPermissionValue = null
282
+ }}
283
+ >
284
+ {#each Object.values(permissionValueList) as permission}
285
+ {#if enableSiteScope || permission.value !== 'SITE'}
286
+ <option value={permission.value}>{permission.label}</option>
287
+ {/if}
288
+ {/each}
289
+ </Select>
290
+ <Button
291
+ class="mb-1"
292
+ outline
293
+ size="sm"
294
+ iconClass="check-double"
295
+ onclick={selectAndDeselectAllPermissions}
296
+ >
297
+ <span class:d-none={selectedPermissionIds.size !== computedPermissions.length}
298
+ >{translate('common:deselectAll', 'Deselect All')}</span
299
+ >
300
+ <span class:d-none={selectedPermissionIds.size === computedPermissions.length}
301
+ >{translate('common:selectAll', 'Select All')}</span
302
+ >
303
+ </Button>
304
+ </div>
305
+ </div>
306
+ </div>
307
+
308
+ <style>
309
+ .cursor-pointer {
310
+ cursor: pointer;
311
+ }
312
+ </style>
@@ -18,6 +18,7 @@ declare const PermissionList: import("svelte").Component<HTMLDivAttributes & {
18
18
  permissionValueMap: Map<number, "NONE" | "SITE" | "GLOBAL">;
19
19
  groupPermissionValueMap?: Map<number, "NONE" | "SITE" | "GLOBAL"> | undefined;
20
20
  children?: Snippet;
21
+ permissionTranslationNamespace?: string;
21
22
  }, {}, "permissionValueMap">;
22
23
  type PermissionList = ReturnType<typeof PermissionList>;
23
24
  export default PermissionList;