@nymphjs/tilmeld-setup 1.0.0-beta.12 → 1.0.0-beta.121

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,14 @@
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
+ nonInteractive={!('href' in section)}
77
+ href={'href' in section ? `#${section.href}` : undefined}
78
+ activated={section.absolute
79
+ ? `${currentMatch.route.path}/` === section.href
80
+ : `${currentMatch.route.path}/`.startsWith(
81
+ section.href ?? '!',
82
+ )}
92
83
  style={section.indent
93
84
  ? 'margin-left: ' + section.indent * 25 + 'px;'
94
85
  : ''}
@@ -97,12 +88,12 @@
97
88
  </Item>
98
89
  {/if}
99
90
  {/each}
100
- {:else if user == null}
101
- <Item on:click={() => (drawerOpen = false)} activated>
91
+ {:else if $user == null}
92
+ <Item tag="span" onclick={() => (drawerOpen = false)} activated>
102
93
  <Text class="mdc-theme--on-secondary">Login</Text>
103
94
  </Item>
104
95
  {:else}
105
- <Item on:click={() => (drawerOpen = false)} activated>
96
+ <Item tag="span" onclick={() => (drawerOpen = false)} activated>
106
97
  <Text class="mdc-theme--on-secondary">Forbidden</Text>
107
98
  </Item>
108
99
  {/if}
@@ -116,83 +107,157 @@
116
107
  <AppContent class="tilmeld-app-content">
117
108
  <main class="tilmeld-main-content" bind:this={mainContent}>
118
109
  {#if tilmeldAdmin}
119
- {#if clientConfig}
120
- <svelte:component this={active} />
110
+ {#if $clientConfig}
111
+ <CurrentRoute {router} {params} {clientConfig} {user} />
121
112
  {:else}
122
113
  Loading...
123
114
  {/if}
124
- {:else if user == null}
115
+ {:else if $user === null}
125
116
  <section style="display: flex; justify-content: center;">
126
- <Login {User} {clientConfig} />
117
+ <Login
118
+ {User}
119
+ {clientConfig}
120
+ showExistingUserToggle={allowRegistration}
121
+ />
127
122
  </section>
123
+ {:else if $user === undefined}
124
+ Loading...
128
125
  {:else}
129
126
  <section>You don't have permission to access this app.</section>
130
127
  {/if}
131
- <Account bind:open={accountOpen} {User} {clientConfig} />
128
+ <Account bind:open={accountOpen} {User} {user} {clientConfig} />
132
129
  </main>
133
130
  </AppContent>
134
131
  </div>
135
132
 
136
133
  <script lang="ts">
137
- import { onMount, SvelteComponent } from 'svelte';
134
+ import type { Component } from 'svelte';
135
+ import { onMount } from 'svelte';
136
+ import type { Writable } from 'svelte/store';
137
+ import { writable } from 'svelte/store';
138
+ import Navigo from 'navigo';
138
139
  import type {
139
140
  User as UserClass,
140
141
  ClientConfig,
141
142
  CurrentUserData,
142
143
  } from '@nymphjs/tilmeld-client';
143
144
  import { Login, Account } from '@nymphjs/tilmeld-components';
144
- import { mdiGithub, mdiMastodon, mdiTwitter } from '@mdi/js';
145
+ import { mdiGit, mdiMastodon } from '@mdi/js';
145
146
  import TopAppBar, { Row, Section, Title } from '@smui/top-app-bar';
146
147
  import Drawer, { Content, Scrim, AppContent } from '@smui/drawer';
147
148
  import IconButton from '@smui/icon-button';
148
149
  import List, { Item, Text, Separator } from '@smui/list';
149
150
  import Menu from '@smui/menu';
150
- import { Icon, Svg } from '@smui/common';
151
+ import { Icon } from '@smui/common';
151
152
 
152
153
  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 Intro from './routes/Intro.svelte';
155
+ import Users from './routes/Users.svelte';
156
+ import UserEdit from './routes/UserEdit.svelte';
157
+ import Groups from './routes/Groups.svelte';
158
+ import GroupEdit from './routes/GroupEdit.svelte';
159
+ import NotFound from './routes/NotFound.svelte';
157
160
 
158
161
  const DEFAULT_AVATAR = 'https://secure.gravatar.com/avatar/?d=mm&s=40';
162
+ const allowRegistration = (window as any).allowRegistration;
159
163
 
160
164
  let mainContent: HTMLElement;
161
- let miniWindow = false;
162
- let drawerOpen = false;
165
+ let miniWindow = $state(false);
166
+ let drawerOpen = $state(false);
163
167
  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
- }
168
+ let clientConfig: Writable<ClientConfig | undefined> = writable();
169
+ let user: Writable<(UserClass & CurrentUserData) | null | undefined> =
170
+ writable();
171
+ let userAvatar: string = $state(DEFAULT_AVATAR);
172
+ let tilmeldAdmin: boolean | undefined = $state();
173
+ let accountOpen = $state(false);
175
174
 
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;
182
- }
175
+ $effect(() => {
176
+ if ($user) {
177
+ $user
178
+ .$gatekeeper('tilmeld/admin')
179
+ .then((value) => (tilmeldAdmin = value));
180
+ $user.$getAvatar().then((value) => (userAvatar = value));
181
+ } else {
182
+ tilmeldAdmin = undefined;
183
+ userAvatar = DEFAULT_AVATAR;
184
+ }
185
+ });
186
+
187
+ const router = new Navigo('/', { hash: true });
188
+ let CurrentRoute: Component<any> = $state(Intro);
189
+ let params: any = $state({});
190
+
191
+ router.hooks({
192
+ before(done, match) {
193
+ if (mainContent) {
194
+ drawerOpen = false;
195
+ mainContent.scrollTop = 0;
196
+ }
197
+
198
+ currentMatch = match;
199
+
200
+ done();
201
+ },
202
+ });
203
+
204
+ router.on({
205
+ '/': () => {
206
+ CurrentRoute = Intro;
207
+ params = {};
208
+ },
209
+ '/users/edit/:guid': ({ data }: any) => {
210
+ CurrentRoute = UserEdit;
211
+ params = data;
212
+ },
213
+ '/users/': () => {
214
+ CurrentRoute = Users;
215
+ params = {};
216
+ },
217
+ '/users/:query?': ({ data }: any) => {
218
+ CurrentRoute = Users;
219
+ params = data;
220
+ },
221
+ '/groups/edit/:guid': ({ data }: any) => {
222
+ CurrentRoute = GroupEdit;
223
+ params = data;
224
+ },
225
+ '/groups/': () => {
226
+ CurrentRoute = Groups;
227
+ params = {};
228
+ },
229
+ '/groups/:query?': ({ data }: any) => {
230
+ CurrentRoute = Groups;
231
+ params = data;
232
+ },
233
+ });
234
+
235
+ router.notFound(({ hashString }: any) => {
236
+ if (hashString === '') {
237
+ router.navigate('/', { historyAPIMethod: 'replaceState' });
238
+ } else {
239
+ CurrentRoute = NotFound;
240
+ params = {};
241
+ }
242
+ });
243
+
244
+ let currentMatch = $state(router.getCurrentLocation());
245
+ router.resolve();
183
246
 
184
247
  const sections: (
185
248
  | {
186
249
  name: string;
187
250
  indent: number;
188
- route?: typeof SvelteComponentDev;
189
- component?: SvelteComponent;
251
+ href?: string;
252
+ absolute?: boolean;
253
+ component?: ReturnType<Component<any>>;
190
254
  }
191
255
  | { name: string; separator: true }
192
256
  )[] = [
193
257
  {
194
258
  name: 'Introduction',
195
- route: Intro,
259
+ href: '/',
260
+ absolute: true,
196
261
  indent: 0,
197
262
  },
198
263
  {
@@ -201,31 +266,37 @@
201
266
  },
202
267
  {
203
268
  name: 'Users',
204
- route: Users,
269
+ href: 'users/',
205
270
  indent: 0,
206
271
  },
207
272
  {
208
273
  name: 'Groups',
209
- route: Groups,
274
+ href: 'groups/',
210
275
  indent: 0,
211
276
  },
212
277
  ];
213
278
 
214
279
  const onLogin = (currentUser: UserClass & CurrentUserData) => {
215
- user = currentUser;
280
+ $user = currentUser;
281
+ // Helps with admin and debugging.
282
+ (window as any).user = currentUser;
216
283
  };
217
284
  const onLogout = () => {
218
- user = undefined;
285
+ $user = null;
286
+ // Helps with admin and debugging.
287
+ (window as any).user = null;
219
288
  };
220
289
 
221
290
  onMount(setMiniWindow);
222
291
  onMount(async () => {
223
292
  User.on('login', onLogin);
224
293
  User.on('logout', onLogout);
225
- user = (await User.current()) ?? undefined;
226
- });
227
- onMount(async () => {
228
- clientConfig = await User.getClientConfig();
294
+
295
+ $clientConfig = await User.getClientConfig();
296
+
297
+ $user = await User.current();
298
+ // Helps with admin and debugging.
299
+ (window as any).user = $user;
229
300
  });
230
301
 
231
302
  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);