@axium/storage 0.22.0 → 0.22.2
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/dist/client/api.js +19 -13
- package/dist/client/cli/commands.js +6 -6
- package/dist/client/local.js +2 -2
- package/lib/Preview.css +12 -2
- package/lib/Preview.svelte +23 -12
- package/package.json +3 -3
package/dist/client/api.js
CHANGED
|
@@ -64,7 +64,6 @@ export async function uploadItem(file, opt = {}) {
|
|
|
64
64
|
if (!opt.name)
|
|
65
65
|
throw 'item name is required';
|
|
66
66
|
const content = await file.bytes();
|
|
67
|
-
opt.onProgress?.(0, content.length);
|
|
68
67
|
/** For big files, it takes a *really* long time to compute the hash, so we just don't do it ahead of time and leave it up to the server. */
|
|
69
68
|
const hash = content.length < uploadConfig.hashThreshold * 1_000_000 ? blake2b(content).toHex() : null;
|
|
70
69
|
const upload = await fetchAPI('PUT', 'storage', {
|
|
@@ -77,17 +76,21 @@ export async function uploadItem(file, opt = {}) {
|
|
|
77
76
|
if (upload.status == 'created')
|
|
78
77
|
return upload.item;
|
|
79
78
|
const chunkSize = Math.min(upload.max_transfer_size, globalThis.navigator?.connection ? conTypeToSpeed[globalThis.navigator.connection.effectiveType] : uploadConfig.uxChunkSize) * 1_000_000;
|
|
79
|
+
opt.onProgress?.(0, content.length);
|
|
80
80
|
let response;
|
|
81
81
|
for (let offset = 0; offset < content.length; offset += chunkSize) {
|
|
82
82
|
const size = Math.min(chunkSize, content.length - offset);
|
|
83
|
+
const headers = {
|
|
84
|
+
'x-upload': upload.token,
|
|
85
|
+
'x-offset': offset.toString(),
|
|
86
|
+
'content-length': size.toString(),
|
|
87
|
+
'content-type': 'application/octet-stream',
|
|
88
|
+
};
|
|
89
|
+
if (token)
|
|
90
|
+
headers.authorization = 'Bearer ' + token;
|
|
83
91
|
response = await fetch(rawStorage('chunk'), {
|
|
84
92
|
method: 'POST',
|
|
85
|
-
headers
|
|
86
|
-
'x-upload': upload.token,
|
|
87
|
-
'x-offset': offset.toString(),
|
|
88
|
-
'content-length': size.toString(),
|
|
89
|
-
'content-type': 'application/octet-stream',
|
|
90
|
-
},
|
|
93
|
+
headers,
|
|
91
94
|
body: content.slice(offset, offset + size),
|
|
92
95
|
}).catch(handleFetchFailed);
|
|
93
96
|
if (!response.ok)
|
|
@@ -110,14 +113,17 @@ export async function uploadItemStream(stream, opt) {
|
|
|
110
113
|
for (let offset = 0; offset < opt.size; offset += chunkSize) {
|
|
111
114
|
const size = Math.min(chunkSize, opt.size - offset);
|
|
112
115
|
let bytesReadForChunk = 0;
|
|
116
|
+
const headers = {
|
|
117
|
+
'x-upload': upload.token,
|
|
118
|
+
'x-offset': offset.toString(),
|
|
119
|
+
'content-length': size.toString(),
|
|
120
|
+
'content-type': 'application/octet-stream',
|
|
121
|
+
};
|
|
122
|
+
if (token)
|
|
123
|
+
headers.authorization = 'Bearer ' + token;
|
|
113
124
|
response = await fetch(rawStorage('chunk'), {
|
|
114
125
|
method: 'POST',
|
|
115
|
-
headers
|
|
116
|
-
'x-upload': upload.token,
|
|
117
|
-
'x-offset': offset.toString(),
|
|
118
|
-
'content-length': size.toString(),
|
|
119
|
-
'content-type': 'application/octet-stream',
|
|
120
|
-
},
|
|
126
|
+
headers,
|
|
121
127
|
body: new ReadableStream({
|
|
122
128
|
type: 'bytes',
|
|
123
129
|
async pull(controller) {
|
|
@@ -59,6 +59,7 @@ import { Readable } from 'node:stream';
|
|
|
59
59
|
import { colorItem, formatItems } from '../../node.js';
|
|
60
60
|
import * as api from '../api.js';
|
|
61
61
|
import { getDirectory, resolveItem, resolvePathWithParent, syncCache, writeCache } from '../local.js';
|
|
62
|
+
import { formatBytes } from '@axium/core';
|
|
62
63
|
export const ls = new Command('ls')
|
|
63
64
|
.alias('list')
|
|
64
65
|
.description('List the contents of a folder')
|
|
@@ -140,10 +141,9 @@ export const upload = new Command('upload')
|
|
|
140
141
|
size: stats.size,
|
|
141
142
|
type,
|
|
142
143
|
onProgress(uploaded, total) {
|
|
143
|
-
io.progress(uploaded, total, Math.round((uploaded / total) * 100) + '%');
|
|
144
|
+
io.progress(uploaded, total, Math.round((uploaded / total) * 100) + '%', `${formatBytes(BigInt(uploaded))}/${formatBytes(BigInt(total))}`);
|
|
144
145
|
},
|
|
145
146
|
});
|
|
146
|
-
io.done();
|
|
147
147
|
const { items } = await syncCache();
|
|
148
148
|
items.push(item);
|
|
149
149
|
writeCache();
|
|
@@ -169,18 +169,18 @@ export const download = new Command('download')
|
|
|
169
169
|
if (item.type == 'inode/directory')
|
|
170
170
|
throw "Can't download directories yet.";
|
|
171
171
|
localPath ||= item.name;
|
|
172
|
-
const _ = __addDisposableResource(env_2, io.start('Downloading to' + localPath), false);
|
|
172
|
+
const _ = __addDisposableResource(env_2, io.start('Downloading to ' + localPath), false);
|
|
173
173
|
const stream = await api.downloadItemStream(item.id);
|
|
174
|
-
|
|
174
|
+
const size = Number(item.size);
|
|
175
|
+
io.progress(0, size);
|
|
175
176
|
let downloaded = 0;
|
|
176
177
|
const fileStream = fs.createWriteStream(localPath);
|
|
177
178
|
for await (const chunk of stream) {
|
|
178
179
|
fileStream.write(chunk);
|
|
179
180
|
downloaded += chunk.length;
|
|
180
|
-
io.progress(downloaded,
|
|
181
|
+
io.progress(downloaded, size, Math.round((downloaded / size) * 100) + '%', `${formatBytes(BigInt(downloaded))}/${formatBytes(item.size)}`);
|
|
181
182
|
}
|
|
182
183
|
fileStream.end();
|
|
183
|
-
io.done();
|
|
184
184
|
}
|
|
185
185
|
catch (e_2) {
|
|
186
186
|
env_2.error = e_2;
|
package/dist/client/local.js
CHANGED
|
@@ -4,13 +4,13 @@ import { UserPublic } from '@axium/core';
|
|
|
4
4
|
import * as io from 'ioium/node';
|
|
5
5
|
import { ENOENT, ENOTDIR } from 'node:constants';
|
|
6
6
|
import { stat } from 'node:fs/promises';
|
|
7
|
-
import { join, parse } from 'node:path';
|
|
7
|
+
import { join, parse, resolve } from 'node:path';
|
|
8
8
|
import * as z from 'zod';
|
|
9
9
|
import { StorageItemMetadata } from '../common.js';
|
|
10
10
|
import { getUserStats, getUserStorage } from './api.js';
|
|
11
11
|
export let remotePWD = '/';
|
|
12
12
|
export function resolvePath(path) {
|
|
13
|
-
path =
|
|
13
|
+
path = resolve(remotePWD, path);
|
|
14
14
|
if (path != '/' && path.endsWith('/'))
|
|
15
15
|
path = path.slice(0, -1);
|
|
16
16
|
return path;
|
package/lib/Preview.css
CHANGED
|
@@ -58,7 +58,8 @@
|
|
|
58
58
|
top: 1em;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
.full-fill
|
|
61
|
+
.full-fill,
|
|
62
|
+
.preview-center {
|
|
62
63
|
position: absolute;
|
|
63
64
|
inset: 0;
|
|
64
65
|
width: 100%;
|
|
@@ -80,7 +81,8 @@
|
|
|
80
81
|
}
|
|
81
82
|
}
|
|
82
83
|
|
|
83
|
-
.
|
|
84
|
+
.preview-center,
|
|
85
|
+
.preview-audio-cover {
|
|
84
86
|
display: flex;
|
|
85
87
|
flex-direction: column;
|
|
86
88
|
gap: 1em;
|
|
@@ -88,6 +90,14 @@
|
|
|
88
90
|
justify-content: center;
|
|
89
91
|
}
|
|
90
92
|
|
|
93
|
+
/* Default cover */
|
|
94
|
+
.preview-audio-cover {
|
|
95
|
+
width: 512px;
|
|
96
|
+
height: 512px;
|
|
97
|
+
background-color: var(--bg-alt);
|
|
98
|
+
border-radius: 1em;
|
|
99
|
+
}
|
|
100
|
+
|
|
91
101
|
@media (width < 700px) {
|
|
92
102
|
.preview-top-bar {
|
|
93
103
|
flex-direction: column;
|
package/lib/Preview.svelte
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { text } from '@axium/client';
|
|
3
|
-
import { FormDialog, Icon, Popover } from '@axium/client/components';
|
|
3
|
+
import { Audio, FormDialog, Icon, Popover, Video } from '@axium/client/components';
|
|
4
4
|
import { toast } from '@axium/client/toast';
|
|
5
5
|
import type { AccessControllable } from '@axium/core';
|
|
6
|
-
import { downloadItem, getDirectoryMetadata, updateItemMetadata } from '@axium/storage/client';
|
|
6
|
+
import { downloadItem, downloadItemStream, getDirectoryMetadata, updateItemMetadata } from '@axium/storage/client';
|
|
7
7
|
import { openers, previews } from '@axium/storage/client/3rd-party';
|
|
8
8
|
import { copyShortURL } from '@axium/storage/client/frontend';
|
|
9
9
|
import type { StorageItemMetadata } from '@axium/storage/common';
|
|
@@ -90,15 +90,29 @@
|
|
|
90
90
|
</div>
|
|
91
91
|
</div>
|
|
92
92
|
{/if}
|
|
93
|
+
|
|
94
|
+
{#snippet loading()}
|
|
95
|
+
<div class="preview-center">
|
|
96
|
+
<Icon i="cloud-arrow-down" --size="50px" />
|
|
97
|
+
<span>{text('storage.Preview.loading')}</span>
|
|
98
|
+
</div>
|
|
99
|
+
{/snippet}
|
|
100
|
+
|
|
93
101
|
<div class={['preview-content', noTopBar && 'no-top-bar']}>
|
|
94
102
|
{#if item.type.startsWith('image/')}
|
|
95
103
|
<img src={item.dataURL} alt={item.name} />
|
|
96
104
|
{:else if item.type.startsWith('audio/')}
|
|
97
|
-
|
|
105
|
+
{#await downloadItemStream(item.id)}
|
|
106
|
+
{@render loading()}
|
|
107
|
+
{:then stream}
|
|
108
|
+
<Audio src={item.dataURL} {...item} metadataSource={stream} cover />
|
|
109
|
+
{/await}
|
|
98
110
|
{:else if item.type.startsWith('video/')}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
111
|
+
{#await downloadItemStream(item.id)}
|
|
112
|
+
{@render loading()}
|
|
113
|
+
{:then stream}
|
|
114
|
+
<Video src={item.dataURL} {...item} metadataSource={stream} />
|
|
115
|
+
{/await}
|
|
102
116
|
{:else if item.type == 'application/pdf'}
|
|
103
117
|
<object data={item.dataURL} type="application/pdf" width="100%" height="100%">
|
|
104
118
|
<embed src={item.dataURL} type="application/pdf" width="100%" height="100%" />
|
|
@@ -106,14 +120,11 @@
|
|
|
106
120
|
</object>
|
|
107
121
|
{:else if item.type.startsWith('text/')}
|
|
108
122
|
{#await downloadItem(item.id).then(b => b.text())}
|
|
109
|
-
|
|
110
|
-
<Icon i="cloud-arrow-down" --size="50px" />
|
|
111
|
-
<span>{text('storage.Preview.loading')}</span>
|
|
112
|
-
</div>
|
|
123
|
+
{@render loading()}
|
|
113
124
|
{:then content}
|
|
114
125
|
<pre class="full-fill preview-text">{content}</pre>
|
|
115
126
|
{:catch}
|
|
116
|
-
<div class="
|
|
127
|
+
<div class="preview-center">
|
|
117
128
|
<Icon i="cloud-exclamation" --size="50px" />
|
|
118
129
|
<span>{text('storage.Preview.error_loading')}</span>
|
|
119
130
|
</div>
|
|
@@ -121,7 +132,7 @@
|
|
|
121
132
|
{:else if previews.has(item.type)}
|
|
122
133
|
{@render previews.get(item.type)!(item)}
|
|
123
134
|
{:else}
|
|
124
|
-
<div class="
|
|
135
|
+
<div class="preview-center">
|
|
125
136
|
<Icon i="eye-slash" --size="50px" />
|
|
126
137
|
<span>{text('storage.Preview.preview_unavailable')}</span>
|
|
127
138
|
</div>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axium/storage",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.2",
|
|
4
4
|
"author": "James Prevett <axium@jamespre.dev>",
|
|
5
5
|
"description": "User file storage for Axium",
|
|
6
6
|
"funding": {
|
|
@@ -40,12 +40,12 @@
|
|
|
40
40
|
"build": "tsc"
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
|
-
"@axium/client": ">=0.
|
|
43
|
+
"@axium/client": ">=0.24.0",
|
|
44
44
|
"@axium/core": ">=0.26.0",
|
|
45
45
|
"@axium/server": ">=0.39.0",
|
|
46
46
|
"@sveltejs/kit": "^2.27.3",
|
|
47
47
|
"commander": "^14.0.0",
|
|
48
|
-
"ioium": "^1.
|
|
48
|
+
"ioium": "^1.2.0",
|
|
49
49
|
"kysely": "^0.28.15",
|
|
50
50
|
"mime": "^4.1.0",
|
|
51
51
|
"utilium": "^3.1.0"
|