@axium/client 0.26.2 → 0.27.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/assets/styles.css +29 -0
- package/dist/access.js +11 -5
- package/dist/apps.js +4 -2
- package/dist/cache.d.ts +5 -3
- package/dist/cache.js +16 -2
- package/dist/cli/index.js +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/locales.d.ts +6 -4
- package/dist/user.js +11 -5
- package/lib/AppPreferences.svelte +11 -1
- package/lib/AppPreferencesDialog.svelte +21 -0
- package/lib/Popover.svelte +8 -1
- package/lib/index.ts +1 -0
- package/locales/en.json +6 -4
- package/package.json +1 -1
package/assets/styles.css
CHANGED
|
@@ -113,6 +113,35 @@ pre {
|
|
|
113
113
|
border-radius: 0.5em;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
+
details {
|
|
117
|
+
& > summary {
|
|
118
|
+
list-style: none;
|
|
119
|
+
cursor: pointer;
|
|
120
|
+
|
|
121
|
+
&::-webkit-details-marker {
|
|
122
|
+
display: none;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
&::before {
|
|
126
|
+
content: '';
|
|
127
|
+
display: inline-block;
|
|
128
|
+
width: 1em;
|
|
129
|
+
height: 1em;
|
|
130
|
+
background-color: currentColor;
|
|
131
|
+
mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 320 512'><path d='M278.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-160 160c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L210.7 256 73.4 118.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l160 160z'/></svg>");
|
|
132
|
+
mask-size: contain;
|
|
133
|
+
mask-repeat: no-repeat;
|
|
134
|
+
mask-position: center;
|
|
135
|
+
margin-right: 0.5em;
|
|
136
|
+
vertical-align: -0.125em;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
&:open > summary::before {
|
|
141
|
+
mask-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 448 512'><path d='M201.4 374.6c12.5 12.5 32.8 12.5 45.3 0l160-160c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L224 306.7 86.6 169.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3l160 160z'/></svg>");
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
116
145
|
dialog {
|
|
117
146
|
border-radius: 1em;
|
|
118
147
|
background: var(--bg-menu);
|
package/dist/access.js
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
|
+
import * as cache from './cache.js';
|
|
1
2
|
import { fetchAPI } from './requests.js';
|
|
2
|
-
import { useCache } from './cache.js';
|
|
3
3
|
export async function updateACL(itemType, itemId, target, permissions) {
|
|
4
|
-
|
|
4
|
+
const result = await fetchAPI('PATCH', 'acl/:itemType/:itemId', { target, permissions }, itemType, itemId);
|
|
5
|
+
cache.invalidate('acl', `${itemType}/${itemId}`);
|
|
6
|
+
return result;
|
|
5
7
|
}
|
|
6
8
|
export async function getACL(itemType, itemId) {
|
|
7
|
-
return await
|
|
9
|
+
return await cache.use('acl', `${itemType}/${itemId}`, () => fetchAPI('GET', 'acl/:itemType/:itemId', {}, itemType, itemId));
|
|
8
10
|
}
|
|
9
11
|
export async function addToACL(itemType, itemId, target) {
|
|
10
|
-
|
|
12
|
+
const result = await fetchAPI('PUT', 'acl/:itemType/:itemId', target, itemType, itemId);
|
|
13
|
+
cache.invalidate('acl', `${itemType}/${itemId}`);
|
|
14
|
+
return result;
|
|
11
15
|
}
|
|
12
16
|
export async function removeFromACL(itemType, itemId, target) {
|
|
13
|
-
|
|
17
|
+
const result = await fetchAPI('DELETE', 'acl/:itemType/:itemId', target, itemType, itemId);
|
|
18
|
+
cache.invalidate('acl', `${itemType}/${itemId}`);
|
|
19
|
+
return result;
|
|
14
20
|
}
|
package/dist/apps.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { appPreferences } from '@axium/core';
|
|
2
|
+
import * as cache from './cache.js';
|
|
2
3
|
import { fetchAPI } from './requests.js';
|
|
3
|
-
import { useCache } from './cache.js';
|
|
4
4
|
export async function getAppPreferences(userId, appId) {
|
|
5
5
|
const schema = appPreferences.get(appId);
|
|
6
6
|
if (!schema)
|
|
7
7
|
throw new Error(`Missing schema for "${appId}"`);
|
|
8
|
-
const pref = await
|
|
8
|
+
const pref = await cache.use('app_preferences', userId + ':' + appId, async () => {
|
|
9
9
|
const result = await fetchAPI('GET', 'users/:id/preferences/:appId', {}, userId, appId);
|
|
10
10
|
return schema.parse(result);
|
|
11
11
|
});
|
|
@@ -16,6 +16,7 @@ export async function setAppPreferences(userId, appId, preferences) {
|
|
|
16
16
|
if (!schema)
|
|
17
17
|
throw new Error(`Missing schema for "${appId}"`);
|
|
18
18
|
const result = await fetchAPI('POST', 'users/:id/preferences/:appId', preferences, userId, appId);
|
|
19
|
+
cache.update('app_preferences', userId + ':' + appId, result);
|
|
19
20
|
return schema.parse(result);
|
|
20
21
|
}
|
|
21
22
|
export async function clearAppPreferences(userId, appId) {
|
|
@@ -23,6 +24,7 @@ export async function clearAppPreferences(userId, appId) {
|
|
|
23
24
|
if (!schema)
|
|
24
25
|
throw new Error(`Missing schema for "${appId}"`);
|
|
25
26
|
const result = await fetchAPI('DELETE', 'users/:id/preferences/:appId', {}, userId, appId);
|
|
27
|
+
cache.invalidate('app_preferences', userId + ':' + appId);
|
|
26
28
|
return schema.parse(result);
|
|
27
29
|
}
|
|
28
30
|
export async function appPref(userId, appId, key) {
|
package/dist/cache.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
export interface
|
|
1
|
+
export interface Options {
|
|
2
2
|
/** Maximum number of items the cache can hold */
|
|
3
3
|
itemLimit: number;
|
|
4
4
|
}
|
|
5
5
|
/**
|
|
6
6
|
* Create a cache manually. Only useful when you want to specify different options.
|
|
7
7
|
*/
|
|
8
|
-
export declare function
|
|
8
|
+
export declare function create(cacheName: string, options?: Partial<Options>): void;
|
|
9
9
|
/**
|
|
10
10
|
* Use a cache for some arbitrary operation.
|
|
11
11
|
* This is primarily intended for de-duplicating API requests
|
|
@@ -15,4 +15,6 @@ export declare function createCache(cacheName: string, options?: Partial<CacheOp
|
|
|
15
15
|
* @remarks
|
|
16
16
|
* Note that a cache will automatically be created if it doesn't already exist
|
|
17
17
|
*/
|
|
18
|
-
export declare function
|
|
18
|
+
export declare function use<Key, Result>(cacheName: string, key: Key, miss: () => Result): Result;
|
|
19
|
+
export declare function invalidate(cacheName: string, key: any): void;
|
|
20
|
+
export declare function update(cacheName: string, key: any, value: any): void;
|
package/dist/cache.js
CHANGED
|
@@ -5,7 +5,7 @@ const defaultCacheOptions = {
|
|
|
5
5
|
/**
|
|
6
6
|
* Create a cache manually. Only useful when you want to specify different options.
|
|
7
7
|
*/
|
|
8
|
-
export function
|
|
8
|
+
export function create(cacheName, options = {}) {
|
|
9
9
|
if (_caches[cacheName])
|
|
10
10
|
throw new Error('Multiple caches with the same name are not allowed');
|
|
11
11
|
_caches[cacheName] = {
|
|
@@ -23,7 +23,7 @@ export function createCache(cacheName, options = {}) {
|
|
|
23
23
|
* @remarks
|
|
24
24
|
* Note that a cache will automatically be created if it doesn't already exist
|
|
25
25
|
*/
|
|
26
|
-
export function
|
|
26
|
+
export function use(cacheName, key, miss) {
|
|
27
27
|
const cache = (_caches[cacheName] ||= { items: new Map(), ...defaultCacheOptions });
|
|
28
28
|
const cached = cache.items.get(key);
|
|
29
29
|
if (cached)
|
|
@@ -32,3 +32,17 @@ export function useCache(cacheName, key, miss) {
|
|
|
32
32
|
cache.items.set(key, result);
|
|
33
33
|
return result;
|
|
34
34
|
}
|
|
35
|
+
export function invalidate(cacheName, key) {
|
|
36
|
+
const cache = _caches[cacheName];
|
|
37
|
+
if (!cache)
|
|
38
|
+
throw new ReferenceError("Can't invalidate key in non-existent cache: " + cacheName);
|
|
39
|
+
if (cache)
|
|
40
|
+
cache.items.delete(key);
|
|
41
|
+
}
|
|
42
|
+
export function update(cacheName, key, value) {
|
|
43
|
+
const cache = _caches[cacheName];
|
|
44
|
+
if (!cache)
|
|
45
|
+
throw new ReferenceError("Can't update key in non-existent cache: " + cacheName);
|
|
46
|
+
if (cache)
|
|
47
|
+
cache.items.set(key, value);
|
|
48
|
+
}
|
package/dist/cli/index.js
CHANGED
|
@@ -218,7 +218,7 @@ axiumPlugin
|
|
|
218
218
|
return;
|
|
219
219
|
}
|
|
220
220
|
if (!opt.long) {
|
|
221
|
-
console.log(
|
|
221
|
+
console.log(plugins.keys().toArray().join(', '));
|
|
222
222
|
return;
|
|
223
223
|
}
|
|
224
224
|
console.log(styleText('whiteBright', plugins.size + ' plugin(s) loaded:'));
|
package/dist/config.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as z from 'zod';
|
|
2
2
|
export declare const ClientConfig: z.ZodObject<{
|
|
3
|
-
token: z.ZodOptional<z.ZodNullable<z.
|
|
3
|
+
token: z.ZodOptional<z.ZodNullable<z.ZodBase64URL>>;
|
|
4
4
|
server: z.ZodOptional<z.ZodNullable<z.ZodURL>>;
|
|
5
5
|
cache: z.ZodOptional<z.ZodNullable<z.ZodObject<{
|
|
6
6
|
fetched: z.ZodInt;
|
package/dist/config.js
CHANGED
|
@@ -2,7 +2,7 @@ import { debug, warn } from 'ioium';
|
|
|
2
2
|
import { App, Session, User } from '@axium/core';
|
|
3
3
|
import * as z from 'zod';
|
|
4
4
|
export const ClientConfig = z.looseObject({
|
|
5
|
-
token: z.
|
|
5
|
+
token: z.base64url().nullish(),
|
|
6
6
|
server: z.url().nullish(),
|
|
7
7
|
// Cache to reduce server load:
|
|
8
8
|
cache: z
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/dist/locales.d.ts
CHANGED
|
@@ -19,6 +19,12 @@ declare let currentLoaded: {
|
|
|
19
19
|
readonly failed: "Couldn't load apps.";
|
|
20
20
|
readonly none: "No apps available.";
|
|
21
21
|
};
|
|
22
|
+
readonly AppPreferences: {
|
|
23
|
+
readonly title: "Preferences for {name}";
|
|
24
|
+
readonly save: "Save";
|
|
25
|
+
readonly toast_saved: "Preferences saved";
|
|
26
|
+
readonly dialog_toggle: "Settings";
|
|
27
|
+
};
|
|
22
28
|
readonly Discovery: {
|
|
23
29
|
readonly no_results: "No results";
|
|
24
30
|
};
|
|
@@ -30,10 +36,6 @@ declare let currentLoaded: {
|
|
|
30
36
|
readonly back: "Take me back";
|
|
31
37
|
readonly question: "Are you sure you want to log out?";
|
|
32
38
|
};
|
|
33
|
-
readonly AppPreferences: {
|
|
34
|
-
readonly title: "Preferences for {name}";
|
|
35
|
-
readonly save: "Save";
|
|
36
|
-
};
|
|
37
39
|
readonly Register: {
|
|
38
40
|
readonly email: "Email";
|
|
39
41
|
readonly login: "Login instead";
|
package/dist/user.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { startAuthentication, startRegistration } from '@simplewebauthn/browser';
|
|
2
2
|
import * as z from 'zod';
|
|
3
3
|
import { fetchAPI } from './requests.js';
|
|
4
|
-
import
|
|
4
|
+
import * as cache from './cache.js';
|
|
5
5
|
export async function login(userId) {
|
|
6
6
|
const options = await fetchAPI('PUT', 'users/:id/auth', { type: 'login' }, userId);
|
|
7
7
|
const response = await startAuthentication({ optionsJSON: options });
|
|
@@ -22,8 +22,10 @@ export async function loginByEmail(email) {
|
|
|
22
22
|
});
|
|
23
23
|
return await login(userId);
|
|
24
24
|
}
|
|
25
|
+
let _currentSession = null;
|
|
25
26
|
export async function getCurrentSession() {
|
|
26
|
-
|
|
27
|
+
_currentSession ||= await fetchAPI('GET', 'session');
|
|
28
|
+
return _currentSession;
|
|
27
29
|
}
|
|
28
30
|
export async function getSessions(userId) {
|
|
29
31
|
_checkId(userId);
|
|
@@ -62,11 +64,13 @@ function _checkId(userId) {
|
|
|
62
64
|
}
|
|
63
65
|
export async function userInfo(userId) {
|
|
64
66
|
_checkId(userId);
|
|
65
|
-
return await
|
|
67
|
+
return await cache.use('user', userId, () => fetchAPI('GET', 'users/:id', {}, userId));
|
|
66
68
|
}
|
|
67
69
|
export async function updateUser(userId, data) {
|
|
68
70
|
_checkId(userId);
|
|
69
|
-
|
|
71
|
+
const result = await fetchAPI('PATCH', 'users/:id', data, userId);
|
|
72
|
+
cache.update('user', userId, result);
|
|
73
|
+
return result;
|
|
70
74
|
}
|
|
71
75
|
export async function fullUserInfo(userId) {
|
|
72
76
|
_checkId(userId);
|
|
@@ -81,7 +85,9 @@ export async function deleteUser(userId, deletingId = userId) {
|
|
|
81
85
|
const options = await fetchAPI('PUT', 'users/:id/auth', { type: 'action' }, deletingId);
|
|
82
86
|
const response = await startAuthentication({ optionsJSON: options });
|
|
83
87
|
await fetchAPI('POST', 'users/:id/auth', response, deletingId);
|
|
84
|
-
|
|
88
|
+
const result = await fetchAPI('DELETE', 'users/:id', response, userId);
|
|
89
|
+
cache.invalidate('user', userId);
|
|
90
|
+
return result;
|
|
85
91
|
}
|
|
86
92
|
export async function emailVerificationEnabled(userId) {
|
|
87
93
|
_checkId(userId);
|
|
@@ -3,18 +3,28 @@
|
|
|
3
3
|
import { structurallyEqual } from 'utilium';
|
|
4
4
|
import type { ZodObject } from 'zod';
|
|
5
5
|
import ZodInput from './ZodInput.svelte';
|
|
6
|
+
import { toast } from './toast.js';
|
|
6
7
|
|
|
7
|
-
const {
|
|
8
|
+
const {
|
|
9
|
+
userId,
|
|
10
|
+
appId,
|
|
11
|
+
schema,
|
|
12
|
+
_parentDialog,
|
|
13
|
+
}: { userId: string; appId: string; schema: ZodObject; _parentDialog?: HTMLDialogElement } = $props();
|
|
8
14
|
|
|
9
15
|
let initialValue = $state(await getAppPreferences(userId, appId));
|
|
10
16
|
let currentValue = $state({ ...initialValue });
|
|
11
17
|
|
|
12
18
|
function cancel() {
|
|
13
19
|
currentValue = { ...initialValue };
|
|
20
|
+
_parentDialog?.close();
|
|
14
21
|
}
|
|
15
22
|
|
|
16
23
|
async function save() {
|
|
17
24
|
initialValue = await setAppPreferences(userId, appId, currentValue);
|
|
25
|
+
if (!_parentDialog) return;
|
|
26
|
+
_parentDialog.close();
|
|
27
|
+
toast('success', text('AppPreferences.toast_saved'));
|
|
18
28
|
}
|
|
19
29
|
</script>
|
|
20
30
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { ZodObject } from 'zod';
|
|
3
|
+
import AppPreferences from './AppPreferences.svelte';
|
|
4
|
+
import Icon from './Icon.svelte';
|
|
5
|
+
import { text } from '@axium/client';
|
|
6
|
+
|
|
7
|
+
const { userId, appId, schema }: { userId: string; appId: string; schema: ZodObject } = $props();
|
|
8
|
+
|
|
9
|
+
const id = 'AppPreferencesDialog:' + appId;
|
|
10
|
+
|
|
11
|
+
let _parentDialog = $state<HTMLDialogElement>();
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<button class="icon-text" command="show-modal" commandfor={id}>
|
|
15
|
+
<Icon i="gear-complex" />
|
|
16
|
+
<span>{text('AppPreferences.dialog_toggle')}</span>
|
|
17
|
+
</button>
|
|
18
|
+
|
|
19
|
+
<dialog bind:this={_parentDialog} {id}>
|
|
20
|
+
<AppPreferences {userId} {appId} {schema} {_parentDialog} />
|
|
21
|
+
</dialog>
|
package/lib/Popover.svelte
CHANGED
|
@@ -6,12 +6,15 @@
|
|
|
6
6
|
toggle,
|
|
7
7
|
showToggle,
|
|
8
8
|
popover,
|
|
9
|
+
cascadeHover = false,
|
|
9
10
|
...rest
|
|
10
11
|
}: {
|
|
11
12
|
children(): any;
|
|
12
13
|
toggle?(): any;
|
|
13
14
|
showToggle?: 'hover' | 'always';
|
|
14
15
|
popover?: 'auto' | 'hint' | 'manual';
|
|
16
|
+
/** If set, popover toggles for parent elements will also be shown on hover (only applies for `showToggle="hover"`) */
|
|
17
|
+
cascadeHover?: boolean;
|
|
15
18
|
} & HTMLAttributes<HTMLDivElement> = $props();
|
|
16
19
|
|
|
17
20
|
let popoverElement = $state<HTMLDivElement>();
|
|
@@ -27,7 +30,7 @@
|
|
|
27
30
|
{#if toggle}
|
|
28
31
|
{@render toggle()}
|
|
29
32
|
{:else}
|
|
30
|
-
<span class={[showToggle == 'hover' && 'toggle-hover']}>
|
|
33
|
+
<span class={[showToggle == 'hover' && 'toggle-hover', cascadeHover && 'cascade-hover']}>
|
|
31
34
|
<Icon i="ellipsis" />
|
|
32
35
|
</span>
|
|
33
36
|
{/if}
|
|
@@ -59,6 +62,10 @@
|
|
|
59
62
|
:global(:hover) > div > .toggle-hover {
|
|
60
63
|
visibility: visible;
|
|
61
64
|
}
|
|
65
|
+
|
|
66
|
+
:global(:hover:has(:hover > .Popover > .toggle-hover)) > div > .toggle-hover:not(.cascade-hover) {
|
|
67
|
+
visibility: hidden;
|
|
68
|
+
}
|
|
62
69
|
}
|
|
63
70
|
|
|
64
71
|
:popover-open {
|
package/lib/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { default as AccessControlDialog } from './AccessControlDialog.svelte';
|
|
2
2
|
export { default as AppMenu } from './AppMenu.svelte';
|
|
3
3
|
export { default as AppPreferences } from './AppPreferences.svelte';
|
|
4
|
+
export { default as AppPreferencesDialog } from './AppPreferencesDialog.svelte';
|
|
4
5
|
export { default as Audio } from './Audio.svelte';
|
|
5
6
|
export { default as ColorPicker } from './ColorPicker.svelte';
|
|
6
7
|
export { default as ClipboardCopy } from './ClipboardCopy.svelte';
|
package/locales/en.json
CHANGED
|
@@ -13,6 +13,12 @@
|
|
|
13
13
|
"failed": "Couldn't load apps.",
|
|
14
14
|
"none": "No apps available."
|
|
15
15
|
},
|
|
16
|
+
"AppPreferences": {
|
|
17
|
+
"title": "Preferences for {name}",
|
|
18
|
+
"save": "Save",
|
|
19
|
+
"toast_saved": "Preferences saved",
|
|
20
|
+
"dialog_toggle": "Settings"
|
|
21
|
+
},
|
|
16
22
|
"Discovery": {
|
|
17
23
|
"no_results": "No results"
|
|
18
24
|
},
|
|
@@ -24,10 +30,6 @@
|
|
|
24
30
|
"back": "Take me back",
|
|
25
31
|
"question": "Are you sure you want to log out?"
|
|
26
32
|
},
|
|
27
|
-
"AppPreferences": {
|
|
28
|
-
"title": "Preferences for {name}",
|
|
29
|
-
"save": "Save"
|
|
30
|
-
},
|
|
31
33
|
"Register": {
|
|
32
34
|
"email": "Email",
|
|
33
35
|
"login": "Login instead",
|