@nymphjs/tilmeld-setup 1.0.0-beta.13 → 1.0.0-beta.130

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,12 +1,12 @@
1
- <svelte:window on:resize={setMiniWindow} />
1
+ <svelte:window onresize={setMiniWindow} />
2
+
2
3
  <TopAppBar variant="static" class="tilmeld-top-app-bar">
3
4
  <Row>
4
5
  <Section>
5
6
  {#if miniWindow}
6
- <IconButton
7
- class="material-icons"
8
- on:click={() => (drawerOpen = !drawerOpen)}>menu</IconButton
9
- >
7
+ <IconButton onclick={() => (drawerOpen = !drawerOpen)}>
8
+ <Icon class="material-icons">menu</Icon>
9
+ </IconButton>
10
10
  {/if}
11
11
  <Title style={miniWindow ? 'padding-left: 0;' : ''}>
12
12
  {miniWindow ? 'Tilmeld Setup' : 'Tilmeld Setup App'}
@@ -14,13 +14,13 @@
14
14
  </Section>
15
15
  <Section align="end" toolbar style="color: var(--mdc-on-surface, #000);">
16
16
  <IconButton
17
- href="https://github.com/sciactive/nymphjs"
17
+ href="https://forge.sciactive.com/sciactive/nymphjs"
18
18
  target="_blank"
19
19
  rel="noreferrer"
20
- title="Nymph/Tilmeld on GitHub"
20
+ title="Nymph/Tilmeld on SciActive Forge"
21
21
  >
22
- <Icon component={Svg} viewBox="0 0 24 24">
23
- <path fill="currentColor" d={mdiGithub} />
22
+ <Icon tag="svg" viewBox="0 0 24 24">
23
+ <path fill="currentColor" d={mdiGit} />
24
24
  </Icon>
25
25
  </IconButton>
26
26
  <IconButton
@@ -29,34 +29,24 @@
29
29
  rel="noreferrer"
30
30
  title="SciActive on Mastodon"
31
31
  >
32
- <Icon component={Svg} viewBox="0 0 24 24">
32
+ <Icon tag="svg" viewBox="0 0 24 24">
33
33
  <path fill="currentColor" d={mdiMastodon} />
34
34
  </Icon>
35
35
  </IconButton>
36
- <IconButton
37
- href="https://twitter.com/SciActive"
38
- target="_blank"
39
- rel="noreferrer"
40
- title="SciActive on Twitter"
41
- >
42
- <Icon component={Svg} viewBox="0 0 24 24">
43
- <path fill="currentColor" d={mdiTwitter} />
44
- </Icon>
45
- </IconButton>
46
36
  <div>
47
37
  <IconButton
48
38
  title="Account"
49
- on:click={() => user != null && accountMenu.setOpen(true)}
39
+ onclick={() => $user != null && accountMenu.setOpen(true)}
50
40
  >
51
41
  <Icon tag="img" src={userAvatar} />
52
42
  </IconButton>
53
43
  <Menu bind:this={accountMenu} anchorCorner="BOTTOM_LEFT">
54
44
  <List>
55
- <Item on:SMUI:action={() => (accountOpen = true)}>
45
+ <Item onSMUIAction={() => (accountOpen = true)}>
56
46
  <Text>Account Details</Text>
57
47
  </Item>
58
48
  <Separator />
59
- <Item on:SMUI:action={() => user?.$logout()}>
49
+ <Item onSMUIAction={() => $user?.$logout()}>
60
50
  <Text>Logout</Text>
61
51
  </Item>
62
52
  </List>
@@ -74,7 +64,7 @@
74
64
  : 'hide-initial-small'}"
75
65
  >
76
66
  <Content>
77
- <List>
67
+ <List tag="div">
78
68
  {#if tilmeldAdmin}
79
69
  {#each sections as section (section.name)}
80
70
  {#if 'separator' in section}
@@ -82,13 +72,13 @@
82
72
  {:else}
83
73
  <Item
84
74
  bind:this={section.component}
85
- nonInteractive={!('route' in section)}
86
- on:click={() => {
87
- if ('route' in section) {
88
- active = section.route ?? Intro;
89
- }
90
- }}
91
- activated={active === section.route}
75
+ tag="a"
76
+ use={[link]}
77
+ nonInteractive={!('href' in section)}
78
+ href={'href' in section ? section.href : undefined}
79
+ activated={section.absolute
80
+ ? `${currentRoute}` === section.href
81
+ : `${currentRoute}/`.startsWith(section.href ?? '!')}
92
82
  style={section.indent
93
83
  ? 'margin-left: ' + section.indent * 25 + 'px;'
94
84
  : ''}
@@ -97,12 +87,12 @@
97
87
  </Item>
98
88
  {/if}
99
89
  {/each}
100
- {:else if user == null}
101
- <Item on:click={() => (drawerOpen = false)} activated>
90
+ {:else if $user == null}
91
+ <Item tag="span" onclick={() => (drawerOpen = false)} activated>
102
92
  <Text class="mdc-theme--on-secondary">Login</Text>
103
93
  </Item>
104
94
  {:else}
105
- <Item on:click={() => (drawerOpen = false)} activated>
95
+ <Item tag="span" onclick={() => (drawerOpen = false)} activated>
106
96
  <Text class="mdc-theme--on-secondary">Forbidden</Text>
107
97
  </Item>
108
98
  {/if}
@@ -116,83 +106,124 @@
116
106
  <AppContent class="tilmeld-app-content">
117
107
  <main class="tilmeld-main-content" bind:this={mainContent}>
118
108
  {#if tilmeldAdmin}
119
- {#if clientConfig}
120
- <svelte:component this={active} />
109
+ {#if $clientConfig}
110
+ {#key currentLocation}
111
+ <Router {routes} {onRouteLoaded} />
112
+ {/key}
121
113
  {:else}
122
114
  Loading...
123
115
  {/if}
124
- {:else if user == null}
116
+ {:else if $user === null}
125
117
  <section style="display: flex; justify-content: center;">
126
- <Login {User} {clientConfig} />
118
+ <Login
119
+ {User}
120
+ {clientConfig}
121
+ showExistingUserToggle={allowRegistration}
122
+ />
127
123
  </section>
124
+ {:else if $user === undefined}
125
+ Loading...
128
126
  {:else}
129
127
  <section>You don't have permission to access this app.</section>
130
128
  {/if}
131
- <Account bind:open={accountOpen} {User} {clientConfig} />
129
+ <Account bind:open={accountOpen} {User} {user} {clientConfig} />
132
130
  </main>
133
131
  </AppContent>
134
132
  </div>
135
133
 
136
134
  <script lang="ts">
137
- import { onMount, SvelteComponent } from 'svelte';
135
+ import type { Component } from 'svelte';
136
+ import { onMount, setContext } from 'svelte';
137
+ import type { Writable } from 'svelte/store';
138
+ import { writable } from 'svelte/store';
139
+ import Router, { link, type RouteDetailLoaded } from 'svelte-spa-router';
138
140
  import type {
139
141
  User as UserClass,
140
142
  ClientConfig,
141
143
  CurrentUserData,
142
144
  } from '@nymphjs/tilmeld-client';
143
145
  import { Login, Account } from '@nymphjs/tilmeld-components';
144
- import { mdiGithub, mdiMastodon, mdiTwitter } from '@mdi/js';
146
+ import { mdiGit, mdiMastodon } from '@mdi/js';
145
147
  import TopAppBar, { Row, Section, Title } from '@smui/top-app-bar';
146
148
  import Drawer, { Content, Scrim, AppContent } from '@smui/drawer';
147
149
  import IconButton from '@smui/icon-button';
148
150
  import List, { Item, Text, Separator } from '@smui/list';
149
151
  import Menu from '@smui/menu';
150
- import { Icon, Svg } from '@smui/common';
152
+ import { Icon } from '@smui/common';
151
153
 
152
- import { User } from './nymph';
153
- import Intro from './Intro.svelte';
154
- import Users from './Users.svelte';
155
- import Groups from './Groups.svelte';
156
- import type { SvelteComponentDev } from 'svelte/internal';
154
+ import { User } from './nymph.js';
155
+ import Intro from './routes/Intro.svelte';
156
+ import Users from './routes/Users.svelte';
157
+ import UserEdit from './routes/UserEdit.svelte';
158
+ import Groups from './routes/Groups.svelte';
159
+ import GroupEdit from './routes/GroupEdit.svelte';
160
+ import NotFound from './routes/NotFound.svelte';
157
161
 
158
162
  const DEFAULT_AVATAR = 'https://secure.gravatar.com/avatar/?d=mm&s=40';
163
+ const allowRegistration = (window as any).allowRegistration;
159
164
 
160
165
  let mainContent: HTMLElement;
161
- let miniWindow = false;
162
- let drawerOpen = false;
166
+ let miniWindow = $state(false);
167
+ let drawerOpen = $state(false);
163
168
  let accountMenu: any;
164
- let active: typeof SvelteComponentDev = Intro;
165
- let clientConfig: ClientConfig;
166
- let user: (UserClass & CurrentUserData) | undefined = undefined;
167
- let userAvatar: string = DEFAULT_AVATAR;
168
- let tilmeldAdmin: boolean | undefined = undefined;
169
- let accountOpen = false;
170
-
171
- $: if (active && mainContent) {
172
- drawerOpen = false;
173
- mainContent.scrollTop = 0;
174
- }
169
+ let clientConfig: Writable<ClientConfig | undefined> = writable();
170
+ let user: Writable<(UserClass & CurrentUserData) | null | undefined> =
171
+ writable();
172
+ let userAvatar: string = $state(DEFAULT_AVATAR);
173
+ let tilmeldAdmin: boolean | undefined = $state();
174
+ let accountOpen = $state(false);
175
+
176
+ setContext('clientConfigStore', clientConfig);
177
+ setContext('userStore', user);
175
178
 
176
- $: if (user) {
177
- user.$gatekeeper('tilmeld/admin').then((value) => (tilmeldAdmin = value));
178
- user.$getAvatar().then((value) => (userAvatar = value));
179
- } else {
180
- tilmeldAdmin = undefined;
181
- userAvatar = DEFAULT_AVATAR;
179
+ $effect(() => {
180
+ if ($user) {
181
+ $user
182
+ .$gatekeeper('tilmeld/admin')
183
+ .then((value) => (tilmeldAdmin = value));
184
+ $user.$getAvatar().then((value) => (userAvatar = value));
185
+ } else {
186
+ tilmeldAdmin = undefined;
187
+ userAvatar = DEFAULT_AVATAR;
188
+ }
189
+ });
190
+
191
+ let currentRoute = $state('/');
192
+ let currentLocation = $state('/');
193
+
194
+ const routes = {
195
+ '/': Intro,
196
+ '/users/edit/:guid': UserEdit,
197
+ '/users/:query?': Users,
198
+ '/groups/edit/:guid': GroupEdit,
199
+ '/groups/:query?': Groups,
200
+ '*': NotFound,
201
+ };
202
+
203
+ function onRouteLoaded(detail: RouteDetailLoaded) {
204
+ if (mainContent) {
205
+ drawerOpen = false;
206
+ mainContent.scrollTop = 0;
207
+ }
208
+
209
+ currentRoute = `${detail?.route ?? '/'}`;
210
+ currentLocation = `${detail?.location ?? '/'}`;
182
211
  }
183
212
 
184
213
  const sections: (
185
214
  | {
186
215
  name: string;
187
216
  indent: number;
188
- route?: typeof SvelteComponentDev;
189
- component?: SvelteComponent;
217
+ href?: string;
218
+ absolute?: boolean;
219
+ component?: ReturnType<Component<any>>;
190
220
  }
191
221
  | { name: string; separator: true }
192
222
  )[] = [
193
223
  {
194
224
  name: 'Introduction',
195
- route: Intro,
225
+ href: '/',
226
+ absolute: true,
196
227
  indent: 0,
197
228
  },
198
229
  {
@@ -201,31 +232,37 @@
201
232
  },
202
233
  {
203
234
  name: 'Users',
204
- route: Users,
235
+ href: '/users/',
205
236
  indent: 0,
206
237
  },
207
238
  {
208
239
  name: 'Groups',
209
- route: Groups,
240
+ href: '/groups/',
210
241
  indent: 0,
211
242
  },
212
243
  ];
213
244
 
214
245
  const onLogin = (currentUser: UserClass & CurrentUserData) => {
215
- user = currentUser;
246
+ $user = currentUser;
247
+ // Helps with admin and debugging.
248
+ (window as any).user = currentUser;
216
249
  };
217
250
  const onLogout = () => {
218
- user = undefined;
251
+ $user = null;
252
+ // Helps with admin and debugging.
253
+ (window as any).user = null;
219
254
  };
220
255
 
221
256
  onMount(setMiniWindow);
222
257
  onMount(async () => {
223
258
  User.on('login', onLogin);
224
259
  User.on('logout', onLogout);
225
- user = (await User.current()) ?? undefined;
226
- });
227
- onMount(async () => {
228
- clientConfig = await User.getClientConfig();
260
+
261
+ $clientConfig = await User.getClientConfig();
262
+
263
+ $user = await User.current();
264
+ // Helps with admin and debugging.
265
+ (window as any).user = $user;
229
266
  });
230
267
 
231
268
  function setMiniWindow() {
package/app/src/nymph.ts CHANGED
@@ -6,7 +6,7 @@ import {
6
6
  } from '@nymphjs/tilmeld-client';
7
7
 
8
8
  const nymph = new Nymph(
9
- (window as unknown as { nymphOptions: NymphOptions }).nymphOptions
9
+ (window as unknown as { nymphOptions: NymphOptions }).nymphOptions,
10
10
  );
11
11
  const User = nymph.addEntityClass(UserClass);
12
12
  const Group = nymph.addEntityClass(GroupClass);