@axium/storage 0.19.1 → 0.19.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/db.json CHANGED
@@ -79,6 +79,16 @@
79
79
  }
80
80
  }
81
81
  }
82
+ },
83
+ {
84
+ "delta": true,
85
+ "alter_tables": {
86
+ "storage": {
87
+ "add_constraints": {
88
+ "unique_name_parentId": { "type": "unique", "on": ["name", "parentId"] }
89
+ }
90
+ }
91
+ }
82
92
  }
83
93
  ],
84
94
  "wipe": ["storage", "acl.storage"],
package/dist/common.d.ts CHANGED
@@ -61,6 +61,8 @@ export declare const StorageItemSorting: z.ZodObject<{
61
61
  descending: z.ZodOptional<z.ZodBoolean>;
62
62
  by: z.ZodLiteral<"name" | "createdAt" | "modifiedAt" | "size">;
63
63
  }, z.core.$strip>;
64
+ export interface StorageItemSorting extends z.infer<typeof StorageItemSorting> {
65
+ }
64
66
  export declare const syncProtocolVersion = 0;
65
67
  export declare const StorageLimits: z.ZodObject<{
66
68
  item_size: z.ZodInt;
@@ -77,7 +77,14 @@ export async function createNewItem(init, userId, writeContent) {
77
77
  hash,
78
78
  })
79
79
  .returningAll()
80
- .executeTakeFirstOrThrow());
80
+ .executeTakeFirstOrThrow()
81
+ .catch(e => {
82
+ if (!(e instanceof Error))
83
+ throw e;
84
+ if (e.message.includes('unique_name_parentId') && e.message.includes('duplicate'))
85
+ error(409, 'A file with that name already exists in this folder.');
86
+ throw e;
87
+ }));
81
88
  const path = join(dataDir, item.id);
82
89
  if (existing)
83
90
  linkSync(join(dataDir, existing.id), path);
package/lib/List.svelte CHANGED
@@ -7,9 +7,10 @@
7
7
  import type { AccessControllable, UserPublic } from '@axium/core';
8
8
  import { formatBytes } from '@axium/core/format';
9
9
  import { forMime as iconForMime } from '@axium/core/icons';
10
+ import { errorText } from '@axium/core/io';
10
11
  import { getDirectoryMetadata, updateItemMetadata } from '@axium/storage/client';
11
12
  import { copyShortURL, formatItemName } from '@axium/storage/client/frontend';
12
- import type { StorageItemMetadata } from '@axium/storage/common';
13
+ import { StorageItemSorting, type StorageItemMetadata } from '@axium/storage/common';
13
14
  import Preview from './Preview.svelte';
14
15
 
15
16
  let {
@@ -23,6 +24,31 @@
23
24
  const activeItem = $derived(items[activeIndex]);
24
25
  const activeItemName = $derived(formatItemName(activeItem));
25
26
  const dialogs = $state<Record<string, HTMLDialogElement>>({});
27
+
28
+ const search = new URLSearchParams(location.search);
29
+ let sort = $state<StorageItemSorting | null>();
30
+ try {
31
+ sort = StorageItemSorting.parse({
32
+ by: search.get('sortBy'),
33
+ descending: search.has('descending'),
34
+ });
35
+ } catch (e) {
36
+ console.log('Ignoring invalid sorting parameters', errorText(e));
37
+ }
38
+
39
+ const sortedItems = $derived(
40
+ items
41
+ .map((item, i) => [item, i] as const)
42
+ .toSorted(
43
+ sort
44
+ ? ([_a], [_b]) => {
45
+ const [a, b] = sort?.descending ? [_b, _a] : [_a, _b];
46
+ // @ts-expect-error 2362 — `Date`s have a `valueOf` and can be treated like numbers
47
+ return sort.by == 'name' ? a.name.localeCompare(b.name) : a[sort.by] - b[sort.by];
48
+ }
49
+ : undefined
50
+ )
51
+ );
26
52
  </script>
27
53
 
28
54
  {#snippet action(name: string, icon: string, i: number, preview: boolean = false)}
@@ -40,11 +66,19 @@
40
66
  <div class="list">
41
67
  <div class="list-item list-header">
42
68
  <span></span>
43
- <span>{text('storage.generic.name')}</span>
44
- <span>{text('storage.List.last_modified')}</span>
45
- <span>{text('storage.List.size')}</span>
69
+ {#each [['name', 'storage.generic.name'], ['modifiedAt', 'storage.List.last_modified'], ['size', 'storage.List.size']] as const as [key, translation]}
70
+ <span
71
+ class="header-column"
72
+ onclick={() => (sort = sort?.descending === false ? null : { by: key, descending: !sort?.descending })}
73
+ >
74
+ {#if sort?.by == key}
75
+ <Icon i="sort-{sort.descending ? 'down' : 'up'}" />
76
+ {/if}
77
+ <span>{text(translation)}</span>
78
+ </span>
79
+ {/each}
46
80
  </div>
47
- {#each items as item, i (item.id)}
81
+ {#each sortedItems as [item, i] (item.id)}
48
82
  <div
49
83
  class="list-item"
50
84
  onclick={async () => {
@@ -167,6 +201,15 @@
167
201
  grid-template-columns: 1em 4fr 15em 5em repeat(4, 1em);
168
202
  }
169
203
 
204
+ .header-column {
205
+ display: inline-flex;
206
+ align-items: center;
207
+
208
+ &:hover {
209
+ cursor: pointer;
210
+ }
211
+ }
212
+
170
213
  @media (width < 700px) {
171
214
  .item-actions {
172
215
  display: none;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axium/storage",
3
- "version": "0.19.1",
3
+ "version": "0.19.2",
4
4
  "author": "James Prevett <axium@jamespre.dev>",
5
5
  "description": "User file storage for Axium",
6
6
  "funding": {