@axium/storage 0.16.7 → 0.17.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/db.json +12 -0
- package/dist/polyfills.d.ts +2 -2
- package/dist/server/db.d.ts +3 -17
- package/lib/List.svelte +9 -164
- package/lib/Preview.svelte +238 -0
- package/lib/index.ts +6 -5
- package/package.json +1 -1
- package/routes/files/+layout.svelte +2 -2
- package/routes/files/+page.svelte +3 -3
- package/routes/files/[id]/+page.svelte +33 -7
- package/routes/files/shared/+page.svelte +2 -2
- package/routes/files/usage/+page.svelte +2 -2
- package/routes/+layout.ts +0 -1
package/db.json
CHANGED
package/dist/polyfills.d.ts
CHANGED
|
@@ -17,12 +17,12 @@ declare global {
|
|
|
17
17
|
* @throws {SyntaxError} If the input string contains characters outside the specified alphabet, or if the last
|
|
18
18
|
* chunk is inconsistent with the `lastChunkHandling` option.
|
|
19
19
|
*/
|
|
20
|
-
fromBase64: (string: string) => Uint8Array
|
|
20
|
+
fromBase64: (string: string) => Uint8Array<ArrayBuffer>;
|
|
21
21
|
/**
|
|
22
22
|
* Creates a new `Uint8Array` from a base16-encoded string.
|
|
23
23
|
* @returns A new `Uint8Array` instance.
|
|
24
24
|
*/
|
|
25
|
-
fromHex: (string: string) => Uint8Array
|
|
25
|
+
fromHex: (string: string) => Uint8Array<ArrayBuffer>;
|
|
26
26
|
}
|
|
27
27
|
interface Uint8Array {
|
|
28
28
|
/**
|
package/dist/server/db.d.ts
CHANGED
|
@@ -1,24 +1,10 @@
|
|
|
1
1
|
import { type Schema } from '@axium/server/database';
|
|
2
|
-
import type {
|
|
2
|
+
import type { Selectable } from 'kysely';
|
|
3
|
+
import type schema from '../../db.json';
|
|
3
4
|
import type { StorageItemMetadata, StorageStats } from '../common.js';
|
|
4
5
|
import '../polyfills.js';
|
|
5
6
|
declare module '@axium/server/database' {
|
|
6
|
-
interface Schema {
|
|
7
|
-
storage: {
|
|
8
|
-
createdAt: Generated<Date>;
|
|
9
|
-
hash: Uint8Array | null;
|
|
10
|
-
id: Generated<string>;
|
|
11
|
-
immutable: Generated<boolean>;
|
|
12
|
-
modifiedAt: Generated<Date>;
|
|
13
|
-
name: string;
|
|
14
|
-
parentId: string | null;
|
|
15
|
-
size: number;
|
|
16
|
-
trashedAt: Date | null;
|
|
17
|
-
type: string;
|
|
18
|
-
userId: string;
|
|
19
|
-
metadata: Generated<Record<string, unknown>>;
|
|
20
|
-
};
|
|
21
|
-
'acl.storage': DBAccessControl & DBBool<'read' | 'write' | 'manage' | 'download' | 'comment'>;
|
|
7
|
+
interface Schema extends FromSchemaFile<typeof schema> {
|
|
22
8
|
}
|
|
23
9
|
}
|
|
24
10
|
/**
|
package/lib/List.svelte
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { AccessControlDialog, FormDialog, Icon
|
|
2
|
+
import { AccessControlDialog, FormDialog, Icon } from '@axium/client/components';
|
|
3
3
|
import '@axium/client/styles/list';
|
|
4
4
|
import type { AccessControllable, UserPublic } from '@axium/core';
|
|
5
5
|
import { formatBytes } from '@axium/core/format';
|
|
6
6
|
import { forMime as iconForMime } from '@axium/core/icons';
|
|
7
|
-
import {
|
|
8
|
-
import { downloadItem, getDirectoryMetadata, updateItemMetadata } from '@axium/storage/client';
|
|
9
|
-
import { openers, previews } from '@axium/storage/client/3rd-party';
|
|
7
|
+
import { getDirectoryMetadata, updateItemMetadata } from '@axium/storage/client';
|
|
10
8
|
import type { StorageItemMetadata } from '@axium/storage/common';
|
|
9
|
+
import Preview from './Preview.svelte';
|
|
11
10
|
|
|
12
11
|
let {
|
|
13
12
|
items = $bindable(),
|
|
@@ -95,73 +94,12 @@
|
|
|
95
94
|
|
|
96
95
|
<dialog bind:this={dialogs.preview} class="preview">
|
|
97
96
|
{#if activeItem}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
{
|
|
103
|
-
|
|
104
|
-
<div class="openers">
|
|
105
|
-
<span>Open with <a href={first.openURL(activeItem)} target="_blank">{first.name}</a></span>
|
|
106
|
-
{#if others.length}
|
|
107
|
-
<Popover>
|
|
108
|
-
{#snippet toggle()}
|
|
109
|
-
<span class="popover-toggle"><Icon i="caret-down" /></span>
|
|
110
|
-
{/snippet}
|
|
111
|
-
{#each others as opener}
|
|
112
|
-
<a href={opener.openURL(activeItem)} target="_blank">{opener.name}</a>
|
|
113
|
-
{/each}
|
|
114
|
-
</Popover>
|
|
115
|
-
{/if}
|
|
116
|
-
</div>
|
|
117
|
-
{/if}
|
|
118
|
-
<div class="actions">
|
|
119
|
-
{@render action('rename', 'pencil', i, true)}
|
|
120
|
-
{@render action('share:' + activeItem.id, 'user-group', i, true)}
|
|
121
|
-
{@render action('download', 'download', i, true)}
|
|
122
|
-
{@render action('trash', 'trash', i, true)}
|
|
123
|
-
<span class="mobile-hide" onclick={() => dialogs.preview.close()}>
|
|
124
|
-
<Icon i="xmark" --size="20px" />
|
|
125
|
-
</span>
|
|
126
|
-
</div>
|
|
127
|
-
</div>
|
|
128
|
-
<div class="content">
|
|
129
|
-
{#if type.startsWith('image/')}
|
|
130
|
-
<img src={dataURL} alt={activeItem.name} width="100%" />
|
|
131
|
-
{:else if type.startsWith('audio/')}
|
|
132
|
-
<audio src={dataURL} controls></audio>
|
|
133
|
-
{:else if type.startsWith('video/')}
|
|
134
|
-
<video src={dataURL} controls width="100%">
|
|
135
|
-
<track kind="captions" />
|
|
136
|
-
</video>
|
|
137
|
-
{:else if type == 'application/pdf'}
|
|
138
|
-
<object data={dataURL} type="application/pdf" width="100%" height="100%">
|
|
139
|
-
<embed src={dataURL} type="application/pdf" width="100%" height="100%" />
|
|
140
|
-
<p>PDF not displayed? <a href={dataURL} download={activeItem.name}>Download</a></p>
|
|
141
|
-
</object>
|
|
142
|
-
{:else if type.startsWith('text/')}
|
|
143
|
-
{#await downloadItem(activeItem.id).then(b => b.text())}
|
|
144
|
-
<div class="full-fill no-preview">
|
|
145
|
-
<Icon i="cloud-arrow-down" --size="50px" />
|
|
146
|
-
<span>Loading</span>
|
|
147
|
-
</div>
|
|
148
|
-
{:then content}
|
|
149
|
-
<pre class="full-fill preview-text">{content}</pre>
|
|
150
|
-
{:catch}
|
|
151
|
-
<div class="full-fill no-preview">
|
|
152
|
-
<Icon i="cloud-exclamation" --size="50px" />
|
|
153
|
-
<span>Error loading preview. You might not have permission to view this file.</span>
|
|
154
|
-
</div>
|
|
155
|
-
{/await}
|
|
156
|
-
{:else if previews.has(type)}
|
|
157
|
-
{@render previews.get(type)!(activeItem)}
|
|
158
|
-
{:else}
|
|
159
|
-
<div class="full-fill no-preview">
|
|
160
|
-
<Icon i="eye-slash" --size="50px" />
|
|
161
|
-
<span>Preview not available</span>
|
|
162
|
-
</div>
|
|
163
|
-
{/if}
|
|
164
|
-
</div>
|
|
97
|
+
<Preview
|
|
98
|
+
item={activeItem}
|
|
99
|
+
previewDialog={dialogs.preview}
|
|
100
|
+
shareDialog={dialogs['share:' + activeItem.id]}
|
|
101
|
+
onDelete={() => items.splice(activeIndex, 1)}
|
|
102
|
+
/>
|
|
165
103
|
{/if}
|
|
166
104
|
</dialog>
|
|
167
105
|
|
|
@@ -218,98 +156,5 @@
|
|
|
218
156
|
border: none;
|
|
219
157
|
padding: 1em;
|
|
220
158
|
word-wrap: normal;
|
|
221
|
-
anchor-scope: --preview-openers;
|
|
222
|
-
|
|
223
|
-
.preview-action:hover {
|
|
224
|
-
cursor: pointer;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
.preview-top-bar {
|
|
228
|
-
display: flex;
|
|
229
|
-
align-items: center;
|
|
230
|
-
gap: 1em;
|
|
231
|
-
justify-content: space-between;
|
|
232
|
-
padding: 0;
|
|
233
|
-
position: absolute;
|
|
234
|
-
inset: 0.5em 1em 0;
|
|
235
|
-
height: fit-content;
|
|
236
|
-
|
|
237
|
-
> div {
|
|
238
|
-
display: flex;
|
|
239
|
-
gap: 1em;
|
|
240
|
-
align-items: center;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
.openers {
|
|
245
|
-
padding: 1em;
|
|
246
|
-
border: 1px solid var(--border-accent);
|
|
247
|
-
border-radius: 1em;
|
|
248
|
-
height: 2em;
|
|
249
|
-
anchor-name: --preview-openers;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
.openers :global([popover]) {
|
|
253
|
-
inset: anchor(bottom) anchor(right) auto anchor(left);
|
|
254
|
-
position-anchor: --preview-openers;
|
|
255
|
-
width: anchor-size(width);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
.actions {
|
|
259
|
-
right: 0;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
.content {
|
|
263
|
-
position: absolute;
|
|
264
|
-
inset: 3em 10em 0;
|
|
265
|
-
|
|
266
|
-
.full-fill {
|
|
267
|
-
position: absolute;
|
|
268
|
-
inset: 0;
|
|
269
|
-
width: 100%;
|
|
270
|
-
height: 100%;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
.preview-text {
|
|
274
|
-
white-space: pre-wrap;
|
|
275
|
-
overflow-y: scroll;
|
|
276
|
-
line-height: 1.6;
|
|
277
|
-
background-color: var(--bg-menu);
|
|
278
|
-
font-family: monospace;
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
.no-preview {
|
|
283
|
-
display: flex;
|
|
284
|
-
flex-direction: column;
|
|
285
|
-
gap: 1em;
|
|
286
|
-
align-items: center;
|
|
287
|
-
justify-content: center;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
@media (width < 700px) {
|
|
291
|
-
.preview-top-bar {
|
|
292
|
-
flex-direction: column;
|
|
293
|
-
|
|
294
|
-
.actions {
|
|
295
|
-
justify-content: space-around;
|
|
296
|
-
width: 100%;
|
|
297
|
-
|
|
298
|
-
.preview-action {
|
|
299
|
-
padding: 1em;
|
|
300
|
-
flex: 1 1 0;
|
|
301
|
-
border-radius: 1em;
|
|
302
|
-
border: 1px solid var(--border-accent);
|
|
303
|
-
padding: 1em;
|
|
304
|
-
justify-content: center;
|
|
305
|
-
display: flex;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
.content {
|
|
311
|
-
inset: 10em 1em 0;
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
159
|
}
|
|
315
160
|
</style>
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { FormDialog, Icon, Popover } from '@axium/client/components';
|
|
3
|
+
import type { AccessControllable } from '@axium/core';
|
|
4
|
+
import { downloadItem, getDirectoryMetadata, updateItemMetadata } from '@axium/storage/client';
|
|
5
|
+
import { openers, previews } from '@axium/storage/client/3rd-party';
|
|
6
|
+
import type { StorageItemMetadata } from '@axium/storage/common';
|
|
7
|
+
|
|
8
|
+
const {
|
|
9
|
+
item,
|
|
10
|
+
shareDialog,
|
|
11
|
+
previewDialog,
|
|
12
|
+
onDelete = () => {},
|
|
13
|
+
}: {
|
|
14
|
+
item: StorageItemMetadata & AccessControllable;
|
|
15
|
+
shareDialog?: HTMLDialogElement;
|
|
16
|
+
previewDialog?: HTMLDialogElement;
|
|
17
|
+
onDelete?(): unknown;
|
|
18
|
+
} = $props();
|
|
19
|
+
|
|
20
|
+
const itemOpeners = openers.filter(opener => opener.types.includes(item.type));
|
|
21
|
+
|
|
22
|
+
let dialogs = $state<Record<string, HTMLDialogElement>>({});
|
|
23
|
+
</script>
|
|
24
|
+
|
|
25
|
+
{#snippet action(name: string, icon: string)}
|
|
26
|
+
<span class="icon-text preview-action" onclick={() => dialogs[name].showModal()}>
|
|
27
|
+
<Icon i={icon} --size="18px" />
|
|
28
|
+
</span>
|
|
29
|
+
{/snippet}
|
|
30
|
+
|
|
31
|
+
<div class="preview-top-bar">
|
|
32
|
+
<div class="title">{item.name}</div>
|
|
33
|
+
{#if itemOpeners.length}
|
|
34
|
+
{@const [first, ...others] = itemOpeners}
|
|
35
|
+
<div class="openers">
|
|
36
|
+
<span>Open with <a href={first.openURL(item)} target="_blank">{first.name}</a></span>
|
|
37
|
+
{#if others.length}
|
|
38
|
+
<Popover>
|
|
39
|
+
{#snippet toggle()}
|
|
40
|
+
<span class="popover-toggle"><Icon i="caret-down" /></span>
|
|
41
|
+
{/snippet}
|
|
42
|
+
{#each others as opener}
|
|
43
|
+
<a href={opener.openURL(item)} target="_blank">{opener.name}</a>
|
|
44
|
+
{/each}
|
|
45
|
+
</Popover>
|
|
46
|
+
{/if}
|
|
47
|
+
</div>
|
|
48
|
+
{/if}
|
|
49
|
+
<div class="actions">
|
|
50
|
+
{@render action('rename', 'pencil')}
|
|
51
|
+
{#if shareDialog}
|
|
52
|
+
<span class="icon-text preview-action" onclick={() => shareDialog.showModal()}>
|
|
53
|
+
<Icon i="user-group" --size="18px" />
|
|
54
|
+
</span>
|
|
55
|
+
{/if}
|
|
56
|
+
{@render action('download', 'download')}
|
|
57
|
+
{@render action('trash', 'trash')}
|
|
58
|
+
{#if previewDialog}
|
|
59
|
+
<span class="mobile-hide" onclick={() => previewDialog.close()}>
|
|
60
|
+
<Icon i="xmark" --size="20px" />
|
|
61
|
+
</span>
|
|
62
|
+
{/if}
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
<div class="preview-content">
|
|
66
|
+
{#if item.type.startsWith('image/')}
|
|
67
|
+
<img src={item.dataURL} alt={item.name} width="100%" />
|
|
68
|
+
{:else if item.type.startsWith('audio/')}
|
|
69
|
+
<audio src={item.dataURL} controls></audio>
|
|
70
|
+
{:else if item.type.startsWith('video/')}
|
|
71
|
+
<video src={item.dataURL} controls width="100%">
|
|
72
|
+
<track kind="captions" />
|
|
73
|
+
</video>
|
|
74
|
+
{:else if item.type == 'application/pdf'}
|
|
75
|
+
<object data={item.dataURL} type="application/pdf" width="100%" height="100%">
|
|
76
|
+
<embed src={item.dataURL} type="application/pdf" width="100%" height="100%" />
|
|
77
|
+
<p>PDF not displayed? <a href={item.dataURL} download={item.name}>Download</a></p>
|
|
78
|
+
</object>
|
|
79
|
+
{:else if item.type.startsWith('text/')}
|
|
80
|
+
{#await downloadItem(item.id).then(b => b.text())}
|
|
81
|
+
<div class="full-fill no-preview">
|
|
82
|
+
<Icon i="cloud-arrow-down" --size="50px" />
|
|
83
|
+
<span>Loading</span>
|
|
84
|
+
</div>
|
|
85
|
+
{:then content}
|
|
86
|
+
<pre class="full-fill preview-text">{content}</pre>
|
|
87
|
+
{:catch}
|
|
88
|
+
<div class="full-fill no-preview">
|
|
89
|
+
<Icon i="cloud-exclamation" --size="50px" />
|
|
90
|
+
<span>Error loading preview. You might not have permission to view this file.</span>
|
|
91
|
+
</div>
|
|
92
|
+
{/await}
|
|
93
|
+
{:else if previews.has(item.type)}
|
|
94
|
+
{@render previews.get(item.type)!(item)}
|
|
95
|
+
{:else}
|
|
96
|
+
<div class="full-fill no-preview">
|
|
97
|
+
<Icon i="eye-slash" --size="50px" />
|
|
98
|
+
<span>Preview not available</span>
|
|
99
|
+
</div>
|
|
100
|
+
{/if}
|
|
101
|
+
</div>
|
|
102
|
+
|
|
103
|
+
<FormDialog
|
|
104
|
+
bind:dialog={dialogs.rename}
|
|
105
|
+
submitText="Rename"
|
|
106
|
+
submit={async (data: { name: string }) => {
|
|
107
|
+
await updateItemMetadata(item.id, data);
|
|
108
|
+
item.name = data.name;
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
<div>
|
|
112
|
+
<label for="name">Name</label>
|
|
113
|
+
<input name="name" type="text" required value={item.name} />
|
|
114
|
+
</div>
|
|
115
|
+
</FormDialog>
|
|
116
|
+
<FormDialog
|
|
117
|
+
bind:dialog={dialogs.trash}
|
|
118
|
+
submitText="Trash"
|
|
119
|
+
submitDanger
|
|
120
|
+
submit={async () => {
|
|
121
|
+
if (!item) throw 'No item is selected';
|
|
122
|
+
await updateItemMetadata(item.id, { trash: true });
|
|
123
|
+
onDelete();
|
|
124
|
+
}}
|
|
125
|
+
>
|
|
126
|
+
<p>Are you sure you want to trash this?</p>
|
|
127
|
+
</FormDialog>
|
|
128
|
+
<FormDialog
|
|
129
|
+
bind:dialog={dialogs.download}
|
|
130
|
+
submitText="Download"
|
|
131
|
+
submit={async () => {
|
|
132
|
+
if (item!.type == 'inode/directory') {
|
|
133
|
+
/** @todo ZIP support */
|
|
134
|
+
const children = await getDirectoryMetadata(item.id);
|
|
135
|
+
for (const child of children) open(child.dataURL, '_blank');
|
|
136
|
+
} else open(item!.dataURL, '_blank');
|
|
137
|
+
}}
|
|
138
|
+
>
|
|
139
|
+
<p>Are you sure you want to download this?</p>
|
|
140
|
+
</FormDialog>
|
|
141
|
+
|
|
142
|
+
<style>
|
|
143
|
+
:host {
|
|
144
|
+
anchor-scope: --preview-openers;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.preview-action:hover {
|
|
148
|
+
cursor: pointer;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.preview-top-bar {
|
|
152
|
+
display: flex;
|
|
153
|
+
align-items: center;
|
|
154
|
+
gap: 1em;
|
|
155
|
+
justify-content: space-between;
|
|
156
|
+
padding: 0;
|
|
157
|
+
position: absolute;
|
|
158
|
+
inset: 0.5em 1em 0;
|
|
159
|
+
height: fit-content;
|
|
160
|
+
|
|
161
|
+
> div {
|
|
162
|
+
display: flex;
|
|
163
|
+
gap: 1em;
|
|
164
|
+
align-items: center;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.openers {
|
|
169
|
+
padding: 1em;
|
|
170
|
+
border: 1px solid var(--border-accent);
|
|
171
|
+
border-radius: 1em;
|
|
172
|
+
height: 2em;
|
|
173
|
+
anchor-name: --preview-openers;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.openers :global([popover]) {
|
|
177
|
+
inset: anchor(bottom) anchor(right) auto anchor(left);
|
|
178
|
+
position-anchor: --preview-openers;
|
|
179
|
+
width: anchor-size(width);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.actions {
|
|
183
|
+
right: 0;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.preview-content {
|
|
187
|
+
position: absolute;
|
|
188
|
+
inset: 3em 10em 0;
|
|
189
|
+
|
|
190
|
+
.full-fill {
|
|
191
|
+
position: absolute;
|
|
192
|
+
inset: 0;
|
|
193
|
+
width: 100%;
|
|
194
|
+
height: 100%;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.preview-text {
|
|
198
|
+
white-space: pre-wrap;
|
|
199
|
+
overflow-y: scroll;
|
|
200
|
+
line-height: 1.6;
|
|
201
|
+
background-color: var(--bg-menu);
|
|
202
|
+
font-family: monospace;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.no-preview {
|
|
207
|
+
display: flex;
|
|
208
|
+
flex-direction: column;
|
|
209
|
+
gap: 1em;
|
|
210
|
+
align-items: center;
|
|
211
|
+
justify-content: center;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
@media (width < 700px) {
|
|
215
|
+
.preview-top-bar {
|
|
216
|
+
flex-direction: column;
|
|
217
|
+
|
|
218
|
+
.actions {
|
|
219
|
+
justify-content: space-around;
|
|
220
|
+
width: 100%;
|
|
221
|
+
|
|
222
|
+
.preview-action {
|
|
223
|
+
padding: 1em;
|
|
224
|
+
flex: 1 1 0;
|
|
225
|
+
border-radius: 1em;
|
|
226
|
+
border: 1px solid var(--border-accent);
|
|
227
|
+
padding: 1em;
|
|
228
|
+
justify-content: center;
|
|
229
|
+
display: flex;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.preview-content {
|
|
235
|
+
inset: 10em 1em 0;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
</style>
|
package/lib/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
export { default as
|
|
2
|
-
export { default as
|
|
3
|
-
export { default as
|
|
4
|
-
export { default as
|
|
5
|
-
export { default as
|
|
1
|
+
export { default as Add } from './Add.svelte';
|
|
2
|
+
export { default as List } from './List.svelte';
|
|
3
|
+
export { default as Preview } from './Preview.svelte';
|
|
4
|
+
export { default as Sidebar } from './Sidebar.svelte';
|
|
5
|
+
export { default as SidebarItem } from './SidebarItem.svelte';
|
|
6
|
+
export { default as Usage } from './Usage.svelte';
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import SidebarLayout from '@axium/client/components/SidebarLayout';
|
|
3
|
-
import {
|
|
3
|
+
import { Usage } from '@axium/storage/components';
|
|
4
4
|
|
|
5
5
|
let { children, data } = $props();
|
|
6
6
|
</script>
|
|
7
7
|
|
|
8
8
|
<SidebarLayout tabs={data.tabs}>
|
|
9
9
|
{#snippet bottom()}
|
|
10
|
-
<
|
|
10
|
+
<Usage userId={data.session?.userId} />
|
|
11
11
|
{/snippet}
|
|
12
12
|
|
|
13
13
|
{@render children()}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { Add, List } from '@axium/storage/components';
|
|
3
3
|
|
|
4
4
|
const { data } = $props();
|
|
5
5
|
let items = $state(data.items!);
|
|
@@ -9,5 +9,5 @@
|
|
|
9
9
|
<title>Files</title>
|
|
10
10
|
</svelte:head>
|
|
11
11
|
|
|
12
|
-
<
|
|
13
|
-
<
|
|
12
|
+
<List appMode bind:items user={data.session?.user} />
|
|
13
|
+
<Add onAdd={item => items.push(item)} />
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { Icon } from '@axium/client/components';
|
|
3
|
-
import {
|
|
2
|
+
import { AccessControlDialog, Icon } from '@axium/client/components';
|
|
3
|
+
import { Add, List, Preview } from '@axium/storage/components';
|
|
4
4
|
import type { PageProps } from './$types';
|
|
5
5
|
import { updateItemMetadata } from '@axium/storage/client';
|
|
6
6
|
|
|
7
7
|
const { data }: PageProps = $props();
|
|
8
8
|
|
|
9
9
|
let items = $state(data.items!);
|
|
10
|
-
const item = $
|
|
10
|
+
const item = $derived(data.item);
|
|
11
|
+
const user = $derived(data.session?.user);
|
|
12
|
+
let shareDialog = $state<HTMLDialogElement>();
|
|
13
|
+
|
|
14
|
+
const parentHref = $derived('/files' + (item.parentId ? '/' + item.parentId : ''));
|
|
11
15
|
</script>
|
|
12
16
|
|
|
13
17
|
<svelte:head>
|
|
@@ -29,13 +33,35 @@
|
|
|
29
33
|
class="icon-text"
|
|
30
34
|
onclick={e => {
|
|
31
35
|
e.preventDefault();
|
|
32
|
-
location.href =
|
|
36
|
+
location.href = parentHref;
|
|
33
37
|
}}
|
|
34
38
|
>
|
|
35
39
|
<Icon i="folder-arrow-up" /> Back
|
|
36
40
|
</button>
|
|
37
|
-
<
|
|
38
|
-
<
|
|
41
|
+
<List appMode bind:items user={data.session?.user} />
|
|
42
|
+
<Add parentId={item.id} onAdd={item => items.push(item)} />
|
|
39
43
|
{:else}
|
|
40
|
-
<
|
|
44
|
+
<div class="preview-container">
|
|
45
|
+
<AccessControlDialog
|
|
46
|
+
bind:dialog={shareDialog}
|
|
47
|
+
{item}
|
|
48
|
+
itemType="storage"
|
|
49
|
+
editable={(item.acl?.find(
|
|
50
|
+
a =>
|
|
51
|
+
a.userId == user?.id ||
|
|
52
|
+
(a.role && user?.roles.includes(a.role)) ||
|
|
53
|
+
(a.tag && user?.tags?.includes(a.tag)) ||
|
|
54
|
+
(!a.userId && !a.role && !a.tag)
|
|
55
|
+
)?.manage as boolean | undefined) ?? true}
|
|
56
|
+
/>
|
|
57
|
+
<Preview {item} {shareDialog} onDelete={() => (location.href = parentHref)} />
|
|
58
|
+
</div>
|
|
41
59
|
{/if}
|
|
60
|
+
|
|
61
|
+
<style>
|
|
62
|
+
.preview-container {
|
|
63
|
+
position: relative;
|
|
64
|
+
width: 100%;
|
|
65
|
+
height: 100%;
|
|
66
|
+
}
|
|
67
|
+
</style>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { List } from '@axium/storage/components';
|
|
3
3
|
|
|
4
4
|
const { data } = $props();
|
|
5
5
|
let items = $state(data.items!);
|
|
@@ -9,4 +9,4 @@
|
|
|
9
9
|
<title>Files - Shared With You</title>
|
|
10
10
|
</svelte:head>
|
|
11
11
|
|
|
12
|
-
<
|
|
12
|
+
<List appMode bind:items emptyText="No items have been shared with you." user={data.session?.user} />
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { NumberBar } from '@axium/client/components';
|
|
3
3
|
import '@axium/client/styles/list';
|
|
4
4
|
import { formatBytes } from '@axium/core/format';
|
|
5
|
-
import {
|
|
5
|
+
import { List } from '@axium/storage/components';
|
|
6
6
|
|
|
7
7
|
const { data } = $props();
|
|
8
8
|
const { limits } = data.info;
|
|
@@ -21,4 +21,4 @@
|
|
|
21
21
|
|
|
22
22
|
<p><NumberBar max={limits.user_size * 1_000_000} value={usedBytes} text={barText} /></p>
|
|
23
23
|
|
|
24
|
-
<
|
|
24
|
+
<List bind:items emptyText="You have not uploaded any files yet." user={data.session?.user} />
|
package/routes/+layout.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import '@axium/storage/common';
|