@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.
@@ -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
- io.progress(0, Number(item.size));
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, Number(item.size));
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;
@@ -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 = join(remotePWD, 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
- .no-preview {
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;
@@ -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
- <audio src={item.dataURL} controls></audio>
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
- <video src={item.dataURL} controls>
100
- <track kind="captions" />
101
- </video>
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
- <div class="full-fill no-preview">
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="full-fill no-preview">
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="full-fill no-preview">
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.0",
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.20.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.1.0",
48
+ "ioium": "^1.2.0",
49
49
  "kysely": "^0.28.15",
50
50
  "mime": "^4.1.0",
51
51
  "utilium": "^3.1.0"