@axium/storage 0.23.5 → 0.24.1

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/common.d.ts CHANGED
@@ -154,6 +154,14 @@ export declare const UserStorageOptions: z.ZodDefault<z.ZodObject<{
154
154
  }, z.core.$strip>>;
155
155
  export interface UserStorageOptions extends z.infer<typeof UserStorageOptions> {
156
156
  }
157
+ export declare const UserStoragePreferences: z.ZodObject<{
158
+ sort_folders_first: z.ZodDefault<z.ZodBoolean>;
159
+ }, z.core.$strip>;
160
+ declare module '@axium/core/apps' {
161
+ interface $AppPreferences {
162
+ files: typeof UserStoragePreferences;
163
+ }
164
+ }
157
165
  /**
158
166
  * Formats:
159
167
  *
package/dist/common.js CHANGED
@@ -1,4 +1,5 @@
1
- import { $API, AccessControl, serverConfigs } from '@axium/core';
1
+ import { $API, AccessControl, appPreferences, serverConfigs } from '@axium/core';
2
+ import { zKeys } from '@axium/core/locales';
2
3
  import * as z from 'zod';
3
4
  export const StorageItemSize = z.coerce.bigint().nonnegative();
4
5
  export const StorageItemName = z.string().nonempty().max(255);
@@ -68,6 +69,12 @@ export const UserStorageOptions = z
68
69
  })
69
70
  .partial()
70
71
  .default({});
72
+ export const UserStoragePreferences = z
73
+ .object({
74
+ sort_folders_first: z.boolean().default(true),
75
+ })
76
+ .register(zKeys, { prefix: 'storage.preferences' });
77
+ appPreferences.set('files', UserStoragePreferences);
71
78
  /**
72
79
  * Formats:
73
80
  *
package/lib/List.svelte CHANGED
@@ -1,5 +1,5 @@
1
1
  <script lang="ts">
2
- import { text } from '@axium/client';
2
+ import { getAppPreferences, text } from '@axium/client';
3
3
  import { closeOnBackGesture, contextMenu } from '@axium/client/attachments';
4
4
  import { AccessControlDialog, FormDialog, Icon } from '@axium/client/components';
5
5
  import { copy } from '@axium/client/gui';
@@ -9,9 +9,8 @@
9
9
  import { formatBytes } from '@axium/core/format';
10
10
  import { forMime as iconForMime } from '@axium/core/icons';
11
11
  import { getDirectoryMetadata, updateItemMetadata } from '@axium/storage/client';
12
- import { _downloadItem, copyShortURL, formatItemName } from '@axium/storage/client/frontend';
13
- import { StorageItemSorting, type StorageItemMetadata } from '@axium/storage/common';
14
- import { errorText } from 'ioium';
12
+ import { _downloadItem, copyShortURL } from '@axium/storage/client/frontend';
13
+ import { StorageItemSorting, UserStoragePreferences, type StorageItemMetadata } from '@axium/storage/common';
15
14
  import Preview from './Preview.svelte';
16
15
 
17
16
  let {
@@ -23,30 +22,30 @@
23
22
 
24
23
  let activeId = $state<string>();
25
24
  const activeItem = $derived(items.find(item => item.id === activeId));
26
- const activeItemName = $derived(formatItemName(activeItem));
27
25
  const dialogs = $state<Record<string, HTMLDialogElement>>({});
28
26
 
29
27
  const search = new URLSearchParams(location.search);
30
- let sort = $state<StorageItemSorting | null>();
31
- try {
32
- sort = StorageItemSorting.parse({
28
+ let sort = $state<StorageItemSorting | undefined>(
29
+ StorageItemSorting.safeParse({
33
30
  by: search.get('sortBy'),
34
31
  descending: search.has('descending'),
35
- });
36
- } catch (e) {
37
- console.log('Ignoring invalid sorting parameters', errorText(e));
38
- }
32
+ }).data
33
+ );
34
+
35
+ const { sort_folders_first } = user ? await getAppPreferences(user.id, 'files') : UserStoragePreferences.safeParse({}).data || {};
39
36
 
40
37
  const sortedItems = $derived(
41
- items.toSorted(
42
- sort
43
- ? (_a, _b) => {
44
- const [a, b] = sort?.descending ? [_b, _a] : [_a, _b];
45
- // @ts-expect-error 2362 — `Date`s have a `valueOf` and can be treated like numbers
46
- return sort.by == 'name' ? a.name.localeCompare(b.name) : a[sort.by] - b[sort.by];
47
- }
48
- : undefined
49
- )
38
+ items.toSorted((_a, _b) => {
39
+ if (!sort) {
40
+ const dirDiff = +(_b.type == 'inode/directory') - +(_a.type == 'inode/directory');
41
+ if (sort_folders_first && dirDiff) return dirDiff;
42
+ return _a.name.localeCompare(_b.name);
43
+ }
44
+
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
+ })
50
49
  );
51
50
 
52
51
  function removeActiveItem() {
@@ -76,7 +75,7 @@
76
75
  {#each [['name', 'storage.generic.name'], ['modifiedAt', 'storage.List.last_modified'], ['size', 'storage.List.size']] as const as [key, translation]}
77
76
  <span
78
77
  class="header-column"
79
- onclick={() => (sort = sort?.descending === false ? null : { by: key, descending: !sort?.descending })}
78
+ onclick={() => (sort = sort?.descending === false ? undefined : { by: key, descending: !sort?.descending })}
80
79
  >
81
80
  {#if sort?.by == key}
82
81
  <Icon i="sort-{sort.descending ? 'down' : 'up'}" />
package/locales/en.json CHANGED
@@ -24,7 +24,8 @@
24
24
  "tab": {
25
25
  "files": "Files",
26
26
  "trash": "Trash",
27
- "shared": "Shared"
27
+ "shared": "Shared",
28
+ "settings": "Settings"
28
29
  },
29
30
  "trash_page": {
30
31
  "title": "Files — Trash",
@@ -41,6 +42,9 @@
41
42
  "bar_text": "Using {used} of {total}",
42
43
  "bar_text_unlimited": "Using {used}",
43
44
  "empty": "You have not uploaded any files yet."
45
+ },
46
+ "settings": {
47
+ "title": "Files — Settings"
44
48
  }
45
49
  }
46
50
  },
@@ -65,6 +69,9 @@
65
69
  "trash_success": "Item trashed",
66
70
  "delete_confirm_named": "Are you sure you want to delete {name}?"
67
71
  },
72
+ "preferences": {
73
+ "sort_folders_first": "Sort folders before other files"
74
+ },
68
75
  "Add": {
69
76
  "content": "Content",
70
77
  "create": "Create",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axium/storage",
3
- "version": "0.23.5",
3
+ "version": "0.24.1",
4
4
  "author": "James Prevett <axium@jamespre.dev>",
5
5
  "description": "User file storage for Axium",
6
6
  "funding": {
@@ -40,8 +40,8 @@
40
40
  "build": "tsc"
41
41
  },
42
42
  "peerDependencies": {
43
- "@axium/client": ">=0.24.0",
44
- "@axium/core": ">=0.26.0",
43
+ "@axium/client": ">=0.26.0",
44
+ "@axium/core": ">=0.30.0",
45
45
  "@axium/server": ">=0.39.0",
46
46
  "@sveltejs/kit": "^2.27.3",
47
47
  "commander": "^14.0.0",
@@ -19,6 +19,12 @@ export async function load({ url, route, parent }) {
19
19
  },
20
20
  { name: text('page.files.tab.trash'), href: '/files/trash', icon: 'trash', active: route.id.endsWith('/files/trash') },
21
21
  { name: text('page.files.tab.shared'), href: '/files/shared', icon: 'user-group', active: route.id.endsWith('/files/shared') },
22
+ {
23
+ name: text('page.files.tab.settings'),
24
+ href: '/files/settings',
25
+ icon: 'gear-complex',
26
+ active: route.id.endsWith('/files/settings'),
27
+ },
22
28
  { href: '/files/usage', icon: 'chart-pie-simple', active: route.id.endsWith('/files/usage'), mobile: true },
23
29
  ] satisfies (
24
30
  | { name: string; href: LayoutRouteId; icon: string; active: boolean }
@@ -0,0 +1,13 @@
1
+ <script lang="ts">
2
+ import { text } from '@axium/client';
3
+ import { AppPreferences } from '@axium/client/components';
4
+ import { UserStoragePreferences } from '@axium/storage/common';
5
+
6
+ const { data } = $props();
7
+ </script>
8
+
9
+ <svelte:head>
10
+ <title>{text('page.files.settings.title')}</title>
11
+ </svelte:head>
12
+
13
+ <AppPreferences userId={data.session.userId} appId="files" schema={UserStoragePreferences} />