@axium/storage 0.5.4 → 0.6.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/lib/Usage.svelte +2 -2
- package/package.json +3 -2
- package/routes/files/usage/+page.svelte +93 -0
- package/routes/files/usage/+page.ts +14 -0
- package/styles/list.css +49 -0
package/lib/Usage.svelte
CHANGED
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
</script>
|
|
9
9
|
|
|
10
10
|
{#await info || getUserStorageInfo(userId) then info}
|
|
11
|
-
<
|
|
11
|
+
<a href="/files/usage">
|
|
12
12
|
<NumberBar
|
|
13
13
|
max={info.limits.user_size * 1_000_000}
|
|
14
14
|
value={info.usage.bytes}
|
|
15
15
|
text="Using {formatBytes(info.usage.bytes)} of {formatBytes(info.limits.user_size * 1_000_000)}"
|
|
16
16
|
--fill="#345"
|
|
17
17
|
/>
|
|
18
|
-
</
|
|
18
|
+
</a>
|
|
19
19
|
{:catch error}
|
|
20
20
|
<p>Couldn't load your uploads.</p>
|
|
21
21
|
<p>{error.message}</p>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axium/storage",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"author": "James Prevett <axium@jamespre.dev> (https://jamespre.dev)",
|
|
5
5
|
"description": "User file storage for Axium",
|
|
6
6
|
"funding": {
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
"files": [
|
|
32
32
|
"dist",
|
|
33
33
|
"lib",
|
|
34
|
-
"routes"
|
|
34
|
+
"routes",
|
|
35
|
+
"styles"
|
|
35
36
|
],
|
|
36
37
|
"scripts": {
|
|
37
38
|
"build": "tsc"
|
|
@@ -0,0 +1,93 @@
|
|
|
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
|
+
|
|
8
|
+
const { data } = $props();
|
|
9
|
+
const {
|
|
10
|
+
info: { limits },
|
|
11
|
+
session,
|
|
12
|
+
} = data;
|
|
13
|
+
|
|
14
|
+
const items = $state(data.info.items.filter(i => i.type != 'inode/directory').sort((a, b) => Math.sign(b.size - a.size)));
|
|
15
|
+
const usage = $state(data.info.usage);
|
|
16
|
+
|
|
17
|
+
let dialogs = $state<Record<string, HTMLDialogElement>>({});
|
|
18
|
+
let barText = $derived(`Using ${formatBytes(usage?.bytes)} of ${formatBytes(limits.user_size * 1_000_000)}`);
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<svelte:head>
|
|
22
|
+
<title>Your Storage Usage</title>
|
|
23
|
+
</svelte:head>
|
|
24
|
+
|
|
25
|
+
{#snippet action(name: string, i: string = 'pen')}
|
|
26
|
+
<button style:display="contents" onclick={() => dialogs[name].showModal()}>
|
|
27
|
+
<Icon {i} --size="16px" />
|
|
28
|
+
</button>
|
|
29
|
+
{/snippet}
|
|
30
|
+
|
|
31
|
+
<div class="flex-content">
|
|
32
|
+
<div class="list main">
|
|
33
|
+
<h2>Storage Usage</h2>
|
|
34
|
+
|
|
35
|
+
<p><NumberBar max={limits.user_size * 1_000_000} value={usage?.bytes} text={barText} --fill="#345" /></p>
|
|
36
|
+
|
|
37
|
+
{#each items as item}
|
|
38
|
+
<div class="item">
|
|
39
|
+
<Icon i={forMime(item.type)} />
|
|
40
|
+
<p>{item.name}</p>
|
|
41
|
+
<p>{item.type}</p>
|
|
42
|
+
<p>Owned by {item.userId === session?.userId ? 'You' : item.userId}</p>
|
|
43
|
+
<p>{formatBytes(item.size)}</p>
|
|
44
|
+
<p>Uploaded {item.modifiedAt.toLocaleString()}</p>
|
|
45
|
+
<span>{@render action('rename#' + item.id)}</span>
|
|
46
|
+
<span>{@render action('delete#' + item.id, 'trash')}</span>
|
|
47
|
+
</div>
|
|
48
|
+
<FormDialog
|
|
49
|
+
bind:dialog={dialogs['rename#' + item.id]}
|
|
50
|
+
submit={(data: StorageItemUpdate) => updateItemMetadata(item.id, data).then(n => (item.name = n.name))}
|
|
51
|
+
submitText="Update"
|
|
52
|
+
>
|
|
53
|
+
<div>
|
|
54
|
+
<label for="name">Name</label>
|
|
55
|
+
<input name="name" type="text" value={item.name || ''} required />
|
|
56
|
+
</div>
|
|
57
|
+
</FormDialog>
|
|
58
|
+
<FormDialog
|
|
59
|
+
bind:dialog={dialogs['delete#' + item.id]}
|
|
60
|
+
submit={async (data: StorageItemUpdate) => {
|
|
61
|
+
await deleteItem(item.id);
|
|
62
|
+
dialogs['delete#' + item.id].close();
|
|
63
|
+
items.splice(items.indexOf(item), 1);
|
|
64
|
+
}}
|
|
65
|
+
submitText="Delete"
|
|
66
|
+
submitDanger
|
|
67
|
+
>
|
|
68
|
+
<p>
|
|
69
|
+
Are you sure you want to delete this file?<br />
|
|
70
|
+
This action can't be undone.
|
|
71
|
+
</p>
|
|
72
|
+
</FormDialog>
|
|
73
|
+
{/each}
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
<style>
|
|
78
|
+
.list {
|
|
79
|
+
width: 80%;
|
|
80
|
+
padding-top: 4em;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.item {
|
|
84
|
+
display: grid;
|
|
85
|
+
align-items: center;
|
|
86
|
+
width: 100%;
|
|
87
|
+
gap: 1em;
|
|
88
|
+
text-wrap: nowrap;
|
|
89
|
+
border-top: 1px solid #8888;
|
|
90
|
+
padding-bottom: 1em;
|
|
91
|
+
grid-template-columns: 2em 1.5fr 1fr 1fr 5em 1fr 2em 2em;
|
|
92
|
+
}
|
|
93
|
+
</style>
|
|
@@ -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
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
.list {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
padding: 0.5em;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.list-item.list-header {
|
|
8
|
+
font-weight: bold;
|
|
9
|
+
border-bottom: 1px solid #bbc;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.list-item-container {
|
|
13
|
+
text-decoration: none;
|
|
14
|
+
color: inherit;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.list-item {
|
|
18
|
+
display: grid;
|
|
19
|
+
grid-template-columns: 1em 4fr 15em 5em repeat(3, 1em);
|
|
20
|
+
align-items: center;
|
|
21
|
+
gap: 0.5em;
|
|
22
|
+
padding: 0.5em 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.list-item:not(:last-child) {
|
|
26
|
+
border-bottom: 1px solid #bbc;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.list-item:not(.list-header):hover {
|
|
30
|
+
background-color: #7777;
|
|
31
|
+
}
|
|
32
|
+
p.list-empty {
|
|
33
|
+
text-align: center;
|
|
34
|
+
color: #888;
|
|
35
|
+
margin-top: 1em;
|
|
36
|
+
font-style: italic;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.action {
|
|
40
|
+
visibility: hidden;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.item:hover .action {
|
|
44
|
+
visibility: visible;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.action:hover {
|
|
48
|
+
cursor: pointer;
|
|
49
|
+
}
|