@axium/storage 0.5.5 → 0.6.1
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/lib/List.svelte +13 -11
- package/lib/Usage.svelte +8 -6
- package/package.json +2 -2
- package/routes/files/+layout.svelte +1 -4
- package/routes/files/[id]/+page.svelte +2 -1
- package/routes/files/trash/+page.svelte +40 -20
- package/routes/files/usage/+page.svelte +34 -0
- package/routes/files/usage/+page.ts +14 -0
- package/styles/list.css +10 -10
package/lib/List.svelte
CHANGED
|
@@ -6,7 +6,11 @@
|
|
|
6
6
|
import type { StorageItemMetadata } from '@axium/storage/common';
|
|
7
7
|
import '../styles/list.css';
|
|
8
8
|
|
|
9
|
-
let {
|
|
9
|
+
let {
|
|
10
|
+
items = $bindable([]),
|
|
11
|
+
appMode,
|
|
12
|
+
emptyText = 'Folder is empty.',
|
|
13
|
+
}: { appMode?: boolean; items: StorageItemMetadata[]; emptyText?: string } = $props();
|
|
10
14
|
|
|
11
15
|
let activeIndex = $state<number>(-1);
|
|
12
16
|
let activeItem = $derived(activeIndex == -1 ? null : items[activeIndex]);
|
|
@@ -40,7 +44,7 @@
|
|
|
40
44
|
<dfn title={item.type}><Icon i={iconForMime(item.type)} /></dfn>
|
|
41
45
|
<span class="name">{item.name}</span>
|
|
42
46
|
<span>{item.modifiedAt.toLocaleString()}</span>
|
|
43
|
-
<span>{formatBytes(item.size)}</span>
|
|
47
|
+
<span>{item.type == 'inode/directory' ? '—' : formatBytes(item.size)}</span>
|
|
44
48
|
{@render action('rename', 'pencil', i)}
|
|
45
49
|
{@render action('download', 'download', i)}
|
|
46
50
|
{@render action('trash', 'trash', i)}
|
|
@@ -61,7 +65,7 @@
|
|
|
61
65
|
{@render _item(item, i)}
|
|
62
66
|
{/if}
|
|
63
67
|
{:else}
|
|
64
|
-
<p class="list-empty">
|
|
68
|
+
<p class="list-empty">{emptyText}</p>
|
|
65
69
|
{/each}
|
|
66
70
|
</div>
|
|
67
71
|
|
|
@@ -69,8 +73,9 @@
|
|
|
69
73
|
bind:dialog={dialogs.rename}
|
|
70
74
|
submitText="Rename"
|
|
71
75
|
submit={async (data: { name: string }) => {
|
|
72
|
-
|
|
73
|
-
activeItem
|
|
76
|
+
if (!activeItem) throw 'No item is selected';
|
|
77
|
+
await updateItemMetadata(activeItem.id, data);
|
|
78
|
+
activeItem.name = data.name;
|
|
74
79
|
}}
|
|
75
80
|
>
|
|
76
81
|
<div>
|
|
@@ -83,8 +88,9 @@
|
|
|
83
88
|
submitText="Trash"
|
|
84
89
|
submitDanger
|
|
85
90
|
submit={async () => {
|
|
86
|
-
|
|
87
|
-
|
|
91
|
+
if (!activeItem) throw 'No item is selected';
|
|
92
|
+
await updateItemMetadata(activeItem.id, { trash: true });
|
|
93
|
+
items.splice(activeIndex, 1);
|
|
88
94
|
}}
|
|
89
95
|
>
|
|
90
96
|
<p>Are you sure you want to trash {@render _itemName()}?</p>
|
|
@@ -107,10 +113,6 @@
|
|
|
107
113
|
|
|
108
114
|
<style>
|
|
109
115
|
.list-item {
|
|
110
|
-
display: grid;
|
|
111
116
|
grid-template-columns: 1em 4fr 15em 5em repeat(3, 1em);
|
|
112
|
-
align-items: center;
|
|
113
|
-
gap: 0.5em;
|
|
114
|
-
padding: 0.5em 0;
|
|
115
117
|
}
|
|
116
118
|
</style>
|
package/lib/Usage.svelte
CHANGED
|
@@ -9,12 +9,14 @@
|
|
|
9
9
|
|
|
10
10
|
{#await info || getUserStorageInfo(userId) then info}
|
|
11
11
|
<p>
|
|
12
|
-
<
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
12
|
+
<a href="/files/usage">
|
|
13
|
+
<NumberBar
|
|
14
|
+
max={info.limits.user_size * 1_000_000}
|
|
15
|
+
value={info.usage.bytes}
|
|
16
|
+
text="Using {formatBytes(info.usage.bytes)} of {formatBytes(info.limits.user_size * 1_000_000)}"
|
|
17
|
+
--fill="#345"
|
|
18
|
+
/>
|
|
19
|
+
</a>
|
|
18
20
|
</p>
|
|
19
21
|
{:catch error}
|
|
20
22
|
<p>Couldn't load your uploads.</p>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axium/storage",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"author": "James Prevett <axium@jamespre.dev> (https://jamespre.dev)",
|
|
5
5
|
"description": "User file storage for Axium",
|
|
6
6
|
"funding": {
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"@axium/client": ">=0.1.0",
|
|
42
42
|
"@axium/core": ">=0.5.0",
|
|
43
|
-
"@axium/server": ">=0.
|
|
43
|
+
"@axium/server": ">=0.20.2",
|
|
44
44
|
"@sveltejs/kit": "^2.27.3",
|
|
45
45
|
"utilium": "^2.3.8"
|
|
46
46
|
},
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<div class="app">
|
|
11
11
|
<div class="sidebar">
|
|
12
12
|
{#each data.tabs as { href, name, icon: i, active }}
|
|
13
|
-
<a {href} class={['item', active && 'active']}><Icon {i} /> {capitalize(name)}</a>
|
|
13
|
+
<a {href} class={['item', 'icon-text', active && 'active']}><Icon {i} /> {capitalize(name)}</a>
|
|
14
14
|
{/each}
|
|
15
15
|
|
|
16
16
|
<div class="usage">
|
|
@@ -42,9 +42,6 @@
|
|
|
42
42
|
.item {
|
|
43
43
|
padding: 0.3em 0.5em;
|
|
44
44
|
border-radius: 0.25em 1em 1em 0.25em;
|
|
45
|
-
display: inline-flex;
|
|
46
|
-
align-items: center;
|
|
47
|
-
gap: 1em;
|
|
48
45
|
}
|
|
49
46
|
|
|
50
47
|
.item:hover {
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
const { data }: PageProps = $props();
|
|
8
8
|
|
|
9
|
+
let items = $state(data.items!);
|
|
9
10
|
const item = $state(data.item);
|
|
10
11
|
</script>
|
|
11
12
|
|
|
@@ -24,7 +25,7 @@
|
|
|
24
25
|
<Icon i="trash-can-undo" /> Restore
|
|
25
26
|
</button>
|
|
26
27
|
{:else if item.type == 'inode/directory'}
|
|
27
|
-
<StorageList appMode bind:items
|
|
28
|
+
<StorageList appMode bind:items />
|
|
28
29
|
{:else}
|
|
29
30
|
<p>No preview available.</p>
|
|
30
31
|
{/if}
|
|
@@ -2,16 +2,26 @@
|
|
|
2
2
|
import { formatBytes } from '@axium/core/format';
|
|
3
3
|
import { forMime as iconForMime } from '@axium/core/icons';
|
|
4
4
|
import { FormDialog, Icon } from '@axium/server/components';
|
|
5
|
-
import { deleteItem } from '@axium/storage/client';
|
|
5
|
+
import { deleteItem, updateItemMetadata } from '@axium/storage/client';
|
|
6
6
|
import '@axium/storage/styles/list';
|
|
7
7
|
import type { PageProps } from './$types';
|
|
8
8
|
|
|
9
9
|
const { data }: PageProps = $props();
|
|
10
10
|
let items = $state(data.items);
|
|
11
|
-
let
|
|
11
|
+
let restoreDialog = $state<HTMLDialogElement>()!;
|
|
12
|
+
let deleteDialog = $state<HTMLDialogElement>()!;
|
|
12
13
|
|
|
13
14
|
let activeIndex = $state<number>(-1);
|
|
14
|
-
const activeItem = $derived(items[activeIndex]);
|
|
15
|
+
const activeItem = $derived(activeIndex == -1 ? null : items[activeIndex]);
|
|
16
|
+
|
|
17
|
+
function action(index: number, dialog: () => HTMLDialogElement) {
|
|
18
|
+
return (e: Event) => {
|
|
19
|
+
e.stopPropagation();
|
|
20
|
+
e.preventDefault();
|
|
21
|
+
activeIndex = index;
|
|
22
|
+
dialog().showModal();
|
|
23
|
+
};
|
|
24
|
+
}
|
|
15
25
|
</script>
|
|
16
26
|
|
|
17
27
|
<svelte:head>
|
|
@@ -31,16 +41,11 @@
|
|
|
31
41
|
<span class="name">{item.name}</span>
|
|
32
42
|
<span>{item.modifiedAt.toLocaleString()}</span>
|
|
33
43
|
<span>{formatBytes(item.size)}</span>
|
|
34
|
-
<span
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
activeIndex = i;
|
|
40
|
-
dialog?.showModal();
|
|
41
|
-
}}
|
|
42
|
-
>
|
|
43
|
-
<Icon i="trash" --size="14px" --fill="#c44" />
|
|
44
|
+
<span class="action" onclick={action(i, () => restoreDialog)}>
|
|
45
|
+
<Icon i="rotate-left" --size="14px" />
|
|
46
|
+
</span>
|
|
47
|
+
<span class="action" onclick={action(i, () => deleteDialog)}>
|
|
48
|
+
<Icon i="trash-can-xmark" --size="14px" --fill="#c44" />
|
|
44
49
|
</span>
|
|
45
50
|
</div>
|
|
46
51
|
{:else}
|
|
@@ -48,25 +53,40 @@
|
|
|
48
53
|
{/each}
|
|
49
54
|
</div>
|
|
50
55
|
|
|
56
|
+
{#snippet _name()}
|
|
57
|
+
{#if activeItem?.name}<strong>{activeItem.name.length > 23 ? activeItem.name.slice(0, 20) + '...' : activeItem.name}</strong>
|
|
58
|
+
{:else}this
|
|
59
|
+
{/if}
|
|
60
|
+
{/snippet}
|
|
61
|
+
|
|
62
|
+
<FormDialog
|
|
63
|
+
bind:dialog={restoreDialog}
|
|
64
|
+
submitText="Restore"
|
|
65
|
+
submit={async () => {
|
|
66
|
+
if (!activeItem) throw 'No item is selected';
|
|
67
|
+
await updateItemMetadata(activeItem.id, { trash: false });
|
|
68
|
+
items.splice(activeIndex, 1);
|
|
69
|
+
}}
|
|
70
|
+
>
|
|
71
|
+
<p>Restore {@render _name()}?</p>
|
|
72
|
+
</FormDialog>
|
|
51
73
|
<FormDialog
|
|
52
|
-
bind:dialog
|
|
74
|
+
bind:dialog={deleteDialog}
|
|
53
75
|
submitText="Delete"
|
|
54
76
|
submitDanger
|
|
55
77
|
submit={async () => {
|
|
78
|
+
if (!activeItem) throw 'No item is selected';
|
|
56
79
|
await deleteItem(activeItem.id);
|
|
57
|
-
|
|
80
|
+
items.splice(activeIndex, 1);
|
|
58
81
|
}}
|
|
59
82
|
>
|
|
60
83
|
<p>
|
|
61
|
-
Are you sure you want to permanently delete
|
|
62
|
-
{#if activeItem?.name}<strong>{activeItem.name.length > 23 ? activeItem.name.slice(0, 20) + '...' : activeItem.name}</strong>
|
|
63
|
-
{:else}this
|
|
64
|
-
{/if}?
|
|
84
|
+
Are you sure you want to permanently delete {@render _name()}?
|
|
65
85
|
</p>
|
|
66
86
|
</FormDialog>
|
|
67
87
|
|
|
68
88
|
<style>
|
|
69
89
|
.list-item {
|
|
70
|
-
grid-template-columns: 1em 4fr 15em 5em 1em
|
|
90
|
+
grid-template-columns: 1em 4fr 15em 5em 1em 1em;
|
|
71
91
|
}
|
|
72
92
|
</style>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { formatBytes } from '@axium/core/format';
|
|
3
|
+
import { forMime } from '@axium/core/icons';
|
|
4
|
+
import { FormDialog, Icon, NumberBar } from '@axium/server/components';
|
|
5
|
+
import { deleteItem, updateItemMetadata } from '@axium/storage/client';
|
|
6
|
+
import type { StorageItemUpdate } from '@axium/storage/common';
|
|
7
|
+
import { StorageList } from '@axium/storage/components';
|
|
8
|
+
import '@axium/storage/styles/list';
|
|
9
|
+
|
|
10
|
+
const { data } = $props();
|
|
11
|
+
const { limits } = data.info;
|
|
12
|
+
|
|
13
|
+
let items = $state(data.info.items.filter(i => i.type != 'inode/directory').sort((a, b) => Math.sign(b.size - a.size)));
|
|
14
|
+
const usage = $state(data.info.usage);
|
|
15
|
+
|
|
16
|
+
let dialogs = $state<Record<string, HTMLDialogElement>>({});
|
|
17
|
+
let barText = $derived(`Using ${formatBytes(usage?.bytes)} of ${formatBytes(limits.user_size * 1_000_000)}`);
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<svelte:head>
|
|
21
|
+
<title>Your Storage Usage</title>
|
|
22
|
+
</svelte:head>
|
|
23
|
+
|
|
24
|
+
{#snippet action(name: string, i: string = 'pen')}
|
|
25
|
+
<span class="action" onclick={() => dialogs[name].showModal()}>
|
|
26
|
+
<Icon {i} --size="16px" />
|
|
27
|
+
</span>
|
|
28
|
+
{/snippet}
|
|
29
|
+
|
|
30
|
+
<h2>Storage Usage</h2>
|
|
31
|
+
|
|
32
|
+
<p><NumberBar max={limits.user_size * 1_000_000} value={usage?.bytes} text={barText} --fill="#345" /></p>
|
|
33
|
+
|
|
34
|
+
<StorageList bind:items emptyText="You have not uploaded any files yet." />
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { getUserStorage } from '@axium/storage/client';
|
|
2
|
+
import { redirect } from '@sveltejs/kit';
|
|
3
|
+
|
|
4
|
+
export const ssr = false;
|
|
5
|
+
|
|
6
|
+
export async function load({ parent }) {
|
|
7
|
+
const { session } = await parent();
|
|
8
|
+
|
|
9
|
+
if (!session) redirect(307, '/login?after=/files/usage');
|
|
10
|
+
|
|
11
|
+
const info = await getUserStorage(session.userId);
|
|
12
|
+
|
|
13
|
+
return { info };
|
|
14
|
+
}
|
package/styles/list.css
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
padding: 0.5em;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
.list-
|
|
7
|
+
.list-header {
|
|
8
8
|
font-weight: bold;
|
|
9
|
-
border-bottom:
|
|
9
|
+
border-bottom: 1.5px solid #bbc;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
.list-item-container {
|
|
@@ -18,17 +18,20 @@
|
|
|
18
18
|
display: grid;
|
|
19
19
|
grid-template-columns: 1em 4fr 15em 5em repeat(3, 1em);
|
|
20
20
|
align-items: center;
|
|
21
|
-
gap:
|
|
22
|
-
padding: 0.5em
|
|
21
|
+
gap: 1em;
|
|
22
|
+
padding: 0.5em;
|
|
23
|
+
overflow: hidden;
|
|
24
|
+
text-wrap: nowrap;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
|
-
.list-item:not(:
|
|
26
|
-
border-
|
|
27
|
+
.list-item:not(.list-header, :first-child) {
|
|
28
|
+
border-top: 1px solid #bbc;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
.list-item:not(.list-header):hover {
|
|
30
32
|
background-color: #7777;
|
|
31
33
|
}
|
|
34
|
+
|
|
32
35
|
p.list-empty {
|
|
33
36
|
text-align: center;
|
|
34
37
|
color: #888;
|
|
@@ -40,10 +43,7 @@ p.list-empty {
|
|
|
40
43
|
visibility: hidden;
|
|
41
44
|
}
|
|
42
45
|
|
|
43
|
-
.item:hover .action {
|
|
46
|
+
.list-item:hover .action {
|
|
44
47
|
visibility: visible;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
.action:hover {
|
|
48
48
|
cursor: pointer;
|
|
49
49
|
}
|