@nymphjs/tilmeld-setup 1.0.0-beta.11 → 1.0.0-beta.111

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,570 +0,0 @@
1
- <div style="display: flex; align-items: center; padding: 12px;">
2
- <IconButton title="Back" on:click={() => dispatch('leave')}>
3
- <Icon component={Svg} viewBox="0 0 24 24">
4
- <path fill="currentColor" d={mdiArrowLeft} />
5
- </Icon>
6
- </IconButton>
7
- <h2 style="margin: 0px 12px 0px;" class="mdc-typography--headline5">
8
- Editing {entity.guid ? entity.name : 'New Group'}
9
- </h2>
10
- </div>
11
- {#if entity.user}
12
- {#await entity.user.$ready() then _user}
13
- <div style="padding: 12px;" class="mdc-typography--subtitle1">
14
- Generated primary group for {entity.user.name} ({entity.user.username})
15
- </div>
16
- {/await}
17
- {/if}
18
- {#if entity != null}
19
- {#if clientConfig == null || user == null}
20
- <section style="padding-top: 0;">
21
- <div style="display: flex; justify-content: center; align-items: center;">
22
- <CircularProgress style="height: 45px; width: 45px;" indeterminate />
23
- </div>
24
- </section>
25
- {:else}
26
- <TabBar
27
- tabs={['General', 'Parent', 'Abilities']}
28
- let:tab
29
- bind:active={activeTab}
30
- >
31
- <Tab {tab}>
32
- <Label>{tab}</Label>
33
- </Tab>
34
- </TabBar>
35
-
36
- <section>
37
- {#if activeTab === 'General'}
38
- <LayoutGrid style="padding: 0;">
39
- {#if entity.user != null}
40
- <LayoutCell span={12}>
41
- Some of these fields are not editable, since this group inherits
42
- the values from its user.
43
- </LayoutCell>
44
- {/if}
45
- <LayoutCell span={4}>
46
- <div class="mdc-typography--headline6">GUID</div>
47
- <code>{entity.guid}</code>
48
- </LayoutCell>
49
- <LayoutCell span={4}>
50
- <FormField>
51
- <Checkbox bind:checked={entity.enabled} />
52
- <span slot="label">Enabled (Able to give abilities)</span>
53
- </FormField>
54
- </LayoutCell>
55
- <LayoutCell span={4} style="text-align: end;">
56
- <a href="https://en.gravatar.com/" target="_blank" rel="noreferrer">
57
- <img src={avatar} alt="Avatar" title="Avatar by Gravatar" />
58
- </a>
59
- </LayoutCell>
60
- {#if !clientConfig.emailUsernames}
61
- <LayoutCell span={6}>
62
- <Textfield
63
- bind:value={entity.groupname}
64
- label="Groupname"
65
- type="text"
66
- style="width: 100%;"
67
- helperLine$style="width: 100%;"
68
- invalid={groupnameVerified === false}
69
- input$autocomplete="off"
70
- input$autocapitalize="off"
71
- input$spellcheck="false"
72
- disabled={entity.user != null}
73
- >
74
- <HelperText persistent slot="helper">
75
- {groupnameVerifiedMessage ?? ''}
76
- </HelperText>
77
- </Textfield>
78
- </LayoutCell>
79
- {/if}
80
- <LayoutCell span={clientConfig.emailUsernames ? 12 : 6}>
81
- <Textfield
82
- bind:value={entity.email}
83
- label="Email"
84
- type="email"
85
- style="width: 100%;"
86
- helperLine$style="width: 100%;"
87
- invalid={emailVerified === false}
88
- input$autocomplete="off"
89
- input$autocapitalize="off"
90
- input$spellcheck="false"
91
- disabled={entity.user != null}
92
- >
93
- <HelperText persistent slot="helper">
94
- {emailVerifiedMessage ?? ''}
95
- </HelperText>
96
- </Textfield>
97
- </LayoutCell>
98
- <LayoutCell span={12}>
99
- <Textfield
100
- bind:value={entity.name}
101
- label="Display Name"
102
- type="text"
103
- style="width: 100%;"
104
- input$autocomplete="off"
105
- disabled={entity.user != null}
106
- />
107
- </LayoutCell>
108
- <LayoutCell span={8}>
109
- <Textfield
110
- bind:value={entity.avatar}
111
- label="Avatar"
112
- type="text"
113
- style="width: 100%;"
114
- input$autocomplete="off"
115
- disabled={entity.user != null}
116
- />
117
- </LayoutCell>
118
- <LayoutCell span={4}>
119
- <Textfield
120
- bind:value={entity.phone}
121
- label="Phone"
122
- type="tel"
123
- style="width: 100%;"
124
- input$autocomplete="off"
125
- disabled={entity.user != null}
126
- />
127
- </LayoutCell>
128
- <LayoutCell span={12}>
129
- <FormField>
130
- <Checkbox bind:checked={entity.defaultPrimary} />
131
- <span slot="label"
132
- >Default primary group for newly registered users. <small
133
- class="form-text text-muted"
134
- >Setting this will unset any current default primary group.</small
135
- ></span
136
- >
137
- </FormField>
138
- </LayoutCell>
139
- <LayoutCell span={12}>
140
- <FormField>
141
- <Checkbox bind:checked={entity.defaultSecondary} />
142
- <span slot="label"
143
- >Default secondary group for newly registered{clientConfig.verifyEmail &&
144
- clientConfig.unverifiedAccess
145
- ? ', verified'
146
- : ''} users.</span
147
- >
148
- </FormField>
149
- </LayoutCell>
150
- {#if clientConfig.verifyEmail && clientConfig.unverifiedAccess}
151
- <LayoutCell span={12}>
152
- <FormField>
153
- <Checkbox bind:checked={entity.unverifiedSecondary} />
154
- <span slot="label"
155
- >Default secondary group for newly registered, unverified
156
- users.</span
157
- >
158
- </FormField>
159
- </LayoutCell>
160
- {/if}
161
- </LayoutGrid>
162
- {/if}
163
-
164
- {#if activeTab === 'Parent'}
165
- <h5 style="margin-top: 0;">Parent</h5>
166
-
167
- <Paper
168
- style="display: flex; justify-content: space-between; align-items: center;"
169
- >
170
- {#if !entity.parent}
171
- No parent
172
- {:else}
173
- <span
174
- >{entity.parent.name + ' (' + entity.parent.groupname + ')'}</span
175
- >
176
-
177
- <IconButton
178
- on:click={() => {
179
- delete entity.parent;
180
- entity = entity;
181
- }}
182
- >
183
- <Icon component={Svg} viewBox="0 0 24 24">
184
- <path fill="currentColor" d={mdiMinus} />
185
- </Icon>
186
- </IconButton>
187
- {/if}
188
- </Paper>
189
-
190
- <h6>Change Parent</h6>
191
-
192
- <div class="solo-search-container solo-container">
193
- <Paper class="solo-paper" elevation={1}>
194
- <Icon class="solo-icon" component={Svg} viewBox="0 0 24 24">
195
- <path fill="currentColor" d={mdiMagnify} />
196
- </Icon>
197
- <Input
198
- bind:value={parentSearch}
199
- on:keydown={parentSearchKeyDown}
200
- placeholder="Parent Search"
201
- class="solo-input"
202
- />
203
- </Paper>
204
- <IconButton
205
- on:click={searchParents}
206
- disabled={parentSearch === ''}
207
- class="solo-fab"
208
- title="Search"
209
- >
210
- <Icon component={Svg} viewBox="0 0 24 24">
211
- <path fill="currentColor" d={mdiArrowRight} />
212
- </Icon>
213
- </IconButton>
214
- </div>
215
-
216
- {#if parentsSearching}
217
- <div
218
- style="display: flex; justify-content: center; align-items: center;"
219
- >
220
- <CircularProgress
221
- style="height: 32px; width: 32px;"
222
- indeterminate
223
- />
224
- </div>
225
- {:else if parents != null}
226
- <DataTable table$aria-label="Parent list" style="width: 100%;">
227
- <Head>
228
- <Row>
229
- {#if !clientConfig.emailUsernames}
230
- <Cell>Groupname</Cell>
231
- {/if}
232
- <Cell>Name</Cell>
233
- <Cell>Email</Cell>
234
- <Cell>Enabled</Cell>
235
- </Row>
236
- </Head>
237
- <Body>
238
- {#each parents as curEntity (curEntity.guid)}
239
- <Row
240
- on:click={() => (entity.parent = curEntity)}
241
- style="cursor: pointer;"
242
- >
243
- {#if !clientConfig.emailUsernames}
244
- <Cell>{curEntity.groupname}</Cell>
245
- {/if}
246
- <Cell>{curEntity.name}</Cell>
247
- <Cell>{curEntity.email}</Cell>
248
- <Cell>{curEntity.enabled ? 'Yes' : 'No'}</Cell>
249
- </Row>
250
- {/each}
251
- </Body>
252
- </DataTable>
253
- {/if}
254
- {/if}
255
-
256
- {#if activeTab === 'Abilities'}
257
- <h5 style="margin-top: 0;">Abilities</h5>
258
-
259
- <List nonInteractive>
260
- {#if entity.abilities}
261
- {#each entity.abilities as ability, index (ability)}
262
- <Item>
263
- <Text>
264
- {ability}
265
- </Text>
266
- <Meta>
267
- <IconButton
268
- on:click={() => {
269
- entity.abilities?.splice(index, 1);
270
- entity = entity;
271
- }}
272
- >
273
- <Icon component={Svg} viewBox="0 0 24 24">
274
- <path fill="currentColor" d={mdiMinus} />
275
- </Icon>
276
- </IconButton>
277
- </Meta>
278
- </Item>
279
- {:else}
280
- <Item>
281
- <Text>No abilities</Text>
282
- </Item>
283
- {/each}
284
- {/if}
285
- </List>
286
-
287
- <h6>Add Ability</h6>
288
-
289
- <div style="display: flex; align-items: center; flex-wrap: wrap;">
290
- <Textfield
291
- bind:value={ability}
292
- label="Ability"
293
- type="text"
294
- style="width: 250px; max-width: 100%;"
295
- on:keydown={abilityKeyDown}
296
- />
297
- <IconButton on:click={addAbility}>
298
- <Icon component={Svg} viewBox="0 0 24 24">
299
- <path fill="currentColor" d={mdiPlus} />
300
- </Icon>
301
- </IconButton>
302
- </div>
303
- {/if}
304
-
305
- {#if failureMessage}
306
- <div class="tilmeld-failure">
307
- {failureMessage}
308
- </div>
309
- {/if}
310
-
311
- <div style="margin-top: 36px;">
312
- <Button variant="raised" on:click={saveEntity} disabled={saving}>
313
- <Label>Save Group</Label>
314
- </Button>
315
- {#if entity.guid}
316
- <Button on:click={deleteEntity} disabled={saving}>
317
- <Label>Delete</Label>
318
- </Button>
319
- {/if}
320
- {#if success}
321
- <span>Successfully saved!</span>
322
- {/if}
323
- </div>
324
- </section>
325
- {/if}
326
- {/if}
327
-
328
- <script lang="ts">
329
- import { createEventDispatcher, onMount } from 'svelte';
330
- import type {
331
- AdminGroupData,
332
- ClientConfig,
333
- CurrentUserData,
334
- } from '@nymphjs/tilmeld-client';
335
- import type {
336
- Group as GroupClass,
337
- User as UserClass,
338
- } from '@nymphjs/tilmeld-client';
339
- import queryParser from '@nymphjs/query-parser';
340
- import {
341
- mdiArrowLeft,
342
- mdiArrowRight,
343
- mdiMagnify,
344
- mdiMinus,
345
- mdiPlus,
346
- } from '@mdi/js';
347
- import CircularProgress from '@smui/circular-progress';
348
- import Tab from '@smui/tab';
349
- import TabBar from '@smui/tab-bar';
350
- import LayoutGrid, { Cell as LayoutCell } from '@smui/layout-grid';
351
- import FormField from '@smui/form-field';
352
- import Checkbox from '@smui/checkbox';
353
- import List, { Item, Text, Meta } from '@smui/list';
354
- import Paper from '@smui/paper';
355
- import DataTable, { Head, Body, Row, Cell } from '@smui/data-table';
356
- import Textfield, { Input } from '@smui/textfield';
357
- import HelperText from '@smui/textfield/helper-text';
358
- import IconButton from '@smui/icon-button';
359
- import Button from '@smui/button';
360
- import { Icon, Label, Svg } from '@smui/common';
361
-
362
- import { nymph, Group, User } from './nymph';
363
-
364
- const dispatch = createEventDispatcher();
365
-
366
- export let entity: GroupClass & AdminGroupData;
367
-
368
- let clientConfig: ClientConfig | undefined = undefined;
369
- let user: (UserClass & CurrentUserData) | undefined = undefined;
370
- let activeTab: 'General' | 'Parent' | 'Abilities' = 'General';
371
- let parentSearch = '';
372
- let ability = '';
373
- let avatar = 'https://secure.gravatar.com/avatar/?d=mm&s=40';
374
- let failureMessage: string | undefined = undefined;
375
- let groupnameTimer: NodeJS.Timeout | undefined = undefined;
376
- let groupnameVerified: boolean | undefined = undefined;
377
- let groupnameVerifiedMessage: string | undefined = undefined;
378
- let emailTimer: NodeJS.Timeout | undefined = undefined;
379
- let emailVerified: boolean | undefined = undefined;
380
- let emailVerifiedMessage: string | undefined = undefined;
381
- let saving = false;
382
- let success: boolean | undefined = undefined;
383
-
384
- onMount(async () => {
385
- user = (await User.current()) ?? undefined;
386
- });
387
- onMount(async () => {
388
- clientConfig = await User.getClientConfig();
389
- });
390
-
391
- readyEntity();
392
- function readyEntity() {
393
- // Make sure all fields are defined.
394
- if (entity.enabled == null) {
395
- entity.enabled = false;
396
- }
397
- if (entity.groupname == null) {
398
- entity.groupname = '';
399
- }
400
- if (entity.email == null) {
401
- entity.email = '';
402
- }
403
- if (entity.name == null) {
404
- entity.name = '';
405
- }
406
- if (entity.avatar == null) {
407
- entity.avatar = '';
408
- }
409
- if (entity.phone == null) {
410
- entity.phone = '';
411
- }
412
- if (entity.defaultPrimary == null) {
413
- entity.defaultPrimary = false;
414
- }
415
- if (entity.defaultSecondary == null) {
416
- entity.defaultSecondary = false;
417
- }
418
- if (entity.unverifiedSecondary == null) {
419
- entity.unverifiedSecondary = false;
420
- }
421
- entity.$getAvatar().then((value) => {
422
- avatar = value;
423
- });
424
- entity.$readyAll(1).then(() => {
425
- entity = entity;
426
- });
427
- }
428
-
429
- let parentsSearching = false;
430
- let parents: (GroupClass & AdminGroupData)[] | undefined = undefined;
431
- async function searchParents() {
432
- parentsSearching = true;
433
- failureMessage = undefined;
434
- if (parentSearch.trim() == '') {
435
- return;
436
- }
437
- try {
438
- const query = queryParser({
439
- query: parentSearch,
440
- entityClass: Group,
441
- defaultFields: ['groupname', 'name', 'email'],
442
- qrefMap: {
443
- User: {
444
- class: User,
445
- defaultFields: ['username', 'name', 'email'],
446
- },
447
- Group: {
448
- class: Group,
449
- defaultFields: ['groupname', 'name', 'email'],
450
- },
451
- },
452
- });
453
- parents = (await nymph.getEntities(...query)).filter((group) => {
454
- return !group.$is(entity) && !group.$is(entity.parent);
455
- });
456
- } catch (e: any) {
457
- failureMessage = e?.message;
458
- }
459
- parentsSearching = false;
460
- }
461
- function parentSearchKeyDown(event: CustomEvent | KeyboardEvent) {
462
- event = event as KeyboardEvent;
463
- if (event.key === 'Enter') searchParents();
464
- }
465
-
466
- let oldGroupname = entity.groupname;
467
- $: if (entity.groupname !== oldGroupname) {
468
- if (groupnameTimer) {
469
- clearTimeout(groupnameTimer);
470
- }
471
- groupnameTimer = setTimeout(async () => {
472
- if (entity.groupname === '') {
473
- groupnameVerified = undefined;
474
- groupnameVerifiedMessage = undefined;
475
- return;
476
- }
477
- try {
478
- const data = await entity.$checkGroupname();
479
- groupnameVerified = data.result;
480
- groupnameVerifiedMessage = data.message;
481
- } catch (e: any) {
482
- groupnameVerified = false;
483
- groupnameVerifiedMessage = e?.message;
484
- }
485
- }, 400);
486
- oldGroupname = entity.groupname;
487
- }
488
-
489
- let oldEmail = entity.email;
490
- $: if (entity.email !== oldEmail) {
491
- if (emailTimer) {
492
- clearTimeout(emailTimer);
493
- }
494
- emailTimer = setTimeout(async () => {
495
- if (entity.email === '') {
496
- emailVerified = undefined;
497
- emailVerifiedMessage = undefined;
498
- return;
499
- }
500
- try {
501
- const data = await entity.$checkEmail();
502
- emailVerified = data.result;
503
- emailVerifiedMessage = data.message;
504
- } catch (e: any) {
505
- emailVerified = false;
506
- emailVerifiedMessage = e?.message;
507
- }
508
- }, 400);
509
- oldEmail = entity.email;
510
- }
511
-
512
- function addAbility() {
513
- if (ability === '') {
514
- return;
515
- }
516
- failureMessage = undefined;
517
- if (ability === 'tilmeld/admin') {
518
- failureMessage = "Groups aren't allowed to be Tilmeld admins.";
519
- return;
520
- }
521
- if (ability === 'system/admin') {
522
- failureMessage = "Groups aren't allowed to be system admins.";
523
- return;
524
- }
525
- entity.abilities?.push(ability);
526
- ability = '';
527
- entity = entity;
528
- }
529
- function abilityKeyDown(event: CustomEvent | KeyboardEvent) {
530
- event = event as KeyboardEvent;
531
- if (event.key === 'Enter') addAbility();
532
- }
533
-
534
- async function saveEntity() {
535
- saving = true;
536
- failureMessage = undefined;
537
- try {
538
- if (await entity.$save()) {
539
- success = true;
540
- setTimeout(() => {
541
- success = undefined;
542
- }, 1000);
543
- readyEntity();
544
- } else {
545
- failureMessage = 'Error saving group.';
546
- }
547
- } catch (e: any) {
548
- console.log('error:', e);
549
- failureMessage = e?.message;
550
- }
551
- saving = false;
552
- }
553
-
554
- async function deleteEntity() {
555
- failureMessage = undefined;
556
- if (confirm('Are you sure you want to delete this?')) {
557
- saving = true;
558
- try {
559
- if (await entity.$delete()) {
560
- dispatch('leave');
561
- } else {
562
- failureMessage = 'An error occurred.';
563
- }
564
- } catch (e: any) {
565
- failureMessage = e?.message;
566
- }
567
- saving = false;
568
- }
569
- }
570
- </script>