@axium/storage 0.19.0 → 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/Add.svelte CHANGED
@@ -32,7 +32,7 @@
32
32
 
33
33
  <Popover>
34
34
  {#snippet toggle()}
35
- <button class="icon-text"><Icon i="plus" />{text('storage.Add.text')}</button>
35
+ <button class="icon-text StorageAdd"><Icon i="plus" />{text('storage.Add.text')}</button>
36
36
  {/snippet}
37
37
 
38
38
  <span class="menu-item" onclick={() => uploadDialog.showModal()}><Icon i="upload" />{text('storage.Add.upload')}</span>
package/lib/List.svelte CHANGED
@@ -1,16 +1,17 @@
1
1
  <script lang="ts">
2
2
  import { text } from '@axium/client';
3
- import { contextMenu } from '@axium/client/attachments';
3
+ import { closeOnBackGesture, contextMenu } from '@axium/client/attachments';
4
4
  import { AccessControlDialog, FormDialog, Icon } from '@axium/client/components';
5
+ import { copy } from '@axium/client/gui';
5
6
  import '@axium/client/styles/list';
6
7
  import type { AccessControllable, UserPublic } from '@axium/core';
7
8
  import { formatBytes } from '@axium/core/format';
8
9
  import { forMime as iconForMime } from '@axium/core/icons';
10
+ import { errorText } from '@axium/core/io';
9
11
  import { getDirectoryMetadata, updateItemMetadata } from '@axium/storage/client';
10
12
  import { copyShortURL, formatItemName } from '@axium/storage/client/frontend';
11
- import type { StorageItemMetadata } from '@axium/storage/common';
13
+ import { StorageItemSorting, type StorageItemMetadata } from '@axium/storage/common';
12
14
  import Preview from './Preview.svelte';
13
- import { copy } from '@axium/client/gui';
14
15
 
15
16
  let {
16
17
  items = $bindable(),
@@ -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 () => {
@@ -107,7 +141,7 @@
107
141
  {/each}
108
142
  </div>
109
143
 
110
- <dialog bind:this={dialogs.preview} class="preview" onclick={e => e.stopPropagation()}>
144
+ <dialog bind:this={dialogs.preview} class="preview" onclick={e => e.stopPropagation()} {@attach closeOnBackGesture}>
111
145
  {#if activeItem}
112
146
  <Preview
113
147
  item={activeItem}
@@ -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.0",
3
+ "version": "0.19.2",
4
4
  "author": "James Prevett <axium@jamespre.dev>",
5
5
  "description": "User file storage for Axium",
6
6
  "funding": {
@@ -40,7 +40,7 @@
40
40
  "build": "tsc"
41
41
  },
42
42
  "peerDependencies": {
43
- "@axium/client": ">=0.18.0",
43
+ "@axium/client": ">=0.19.0",
44
44
  "@axium/core": ">=0.19.0",
45
45
  "@axium/server": ">=0.39.0",
46
46
  "@sveltejs/kit": "^2.27.3",
@@ -131,8 +131,12 @@
131
131
  align-items: center;
132
132
  }
133
133
 
134
- .parents a::before {
135
- content: ' / ';
136
- color: #888;
134
+ .parents {
135
+ margin-top: 0;
136
+
137
+ a::before {
138
+ content: ' / ';
139
+ color: #888;
140
+ }
137
141
  }
138
142
  </style>