@sierra-95/svelte-scaffold 1.2.5 → 1.2.6

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.
@@ -21,9 +21,9 @@
21
21
 
22
22
  async function deleteSubmissions() {
23
23
  const store = get(fileInputStore);
24
- const idsToDelete = store.submissions.map(item => item.id);
24
+ const itemsToDelete = store.submissions;
25
25
 
26
- if (!idsToDelete || idsToDelete.length === 0) return;
26
+ if (!itemsToDelete || itemsToDelete.length === 0) return;
27
27
  if (!$fileInputConfig.serverDeleteUrl) return;
28
28
 
29
29
  try {
@@ -33,7 +33,7 @@
33
33
  headers: {
34
34
  'Content-Type': 'application/json'
35
35
  },
36
- body: JSON.stringify(idsToDelete)
36
+ body: JSON.stringify(itemsToDelete)
37
37
  });
38
38
 
39
39
  const data = await response.json();
@@ -1,61 +1,111 @@
1
1
  <script lang="ts">
2
2
  import { get } from "svelte/store";
3
3
  import {MenuItem, fileInputStore, fileInputConfig, addToast} from "../../../index.js";
4
-
4
+ import type {FileInputStoreMediaItem} from '../../../index.js';
5
5
  let {
6
6
  openActionsMenu = $bindable(),
7
7
  } = $props();
8
8
 
9
+ let AuthorizedMediaItems = $state([]);
10
+
11
+ async function download(item: FileInputStoreMediaItem) {
12
+ try {
13
+ const res = await fetch($fileInputConfig.serverDownloadUrl + `?r2_key=${item.r2_key}`, {
14
+ method: 'GET'
15
+ });
16
+
17
+ if (!res.ok) {
18
+ addToast({
19
+ status: 'error',
20
+ message: await res.text() || 'Download failed'
21
+ });
22
+ return;
23
+ }
24
+
25
+ const blob = await res.blob();
26
+ const url = URL.createObjectURL(blob);
27
+ const a = document.createElement('a');
28
+ a.href = url;
29
+ a.download = item.original_name || 'download';
30
+
31
+ document.body.appendChild(a);
32
+ a.click();
33
+ a.remove();
34
+
35
+ URL.revokeObjectURL(url);
36
+
37
+ } catch (err) {
38
+ addToast({
39
+ status: 'error',
40
+ message: `Downloading ${item.original_name} failed: ${err}`
41
+ });
42
+ }
43
+ }
44
+
9
45
  async function handleDownload() {
10
- openActionsMenu = false;
11
- const store = get(fileInputStore);
12
- if (!store.submissions?.length) {
46
+ if (!AuthorizedMediaItems.length) {
13
47
  addToast({
14
48
  status: 'error',
15
- message: 'An error occurred while processing your request.'
49
+ message: 'No files to download'
16
50
  });
17
51
  return;
18
- };
52
+ }
19
53
  try {
20
54
  $fileInputStore.processing = true;
21
- for (const item of store.submissions) {
22
- if (!item.id) continue;
23
- await download(item.id);
55
+ for (const item of AuthorizedMediaItems) {
56
+ await download(item);
24
57
  }
25
58
  } finally {
26
59
  $fileInputStore.processing = false;
27
- fileInputStore.update(store => {
28
- store.submissions = [];
29
- return store;
30
- });
31
60
  }
32
61
  }
33
62
 
34
- async function download(id: string) {
63
+ async function fetchAuthorizedItems() {
64
+ openActionsMenu = false;
65
+ const store = get(fileInputStore);
66
+ if (!store.submissions?.length) {
67
+ addToast({
68
+ status: 'error',
69
+ message: 'An error occurred while processing your request.'
70
+ });
71
+ return;
72
+ }
35
73
  try {
36
- const downloadURL = $fileInputConfig.serverDownloadUrl + `?id=${id}`;
37
- const res = await fetch(downloadURL, {
38
- method: 'GET',
74
+ $fileInputStore.processing = true;
75
+ const ids = store.submissions
76
+ .map(item => item.id)
77
+ .filter(Boolean);
78
+
79
+ const res = await fetch($fileInputConfig.serverDownloadUrl, {
80
+ method: 'POST',
81
+ headers: {
82
+ 'Content-Type': 'application/json'
83
+ },
84
+ body: JSON.stringify(ids)
39
85
  });
40
86
 
41
87
  if (!res.ok) {
88
+ const message = await res.text();
42
89
  addToast({
43
90
  status: 'error',
44
- message: await res.json() || 'Download failed'
91
+ message: message || 'Download failed'
45
92
  });
46
93
  return;
47
94
  }
48
- const data = await res.json();
49
- const url = data.url;
50
- const a = document.createElement('a');
51
- a.href = url;
52
- a.click();
53
- a.remove();
95
+ AuthorizedMediaItems = await res.json();
96
+ await handleDownload();
97
+
54
98
  } catch (err) {
55
99
  addToast({
56
100
  status: 'error',
57
101
  message: `Download failed: ${err}`
58
102
  });
103
+ } finally {
104
+ $fileInputStore.processing = false;
105
+ fileInputStore.update(store => {
106
+ store.submissions = [];
107
+ return store;
108
+ });
59
109
  }
60
110
  }
61
111
  </script>
@@ -64,5 +114,5 @@
64
114
  icon="fa fa-download"
65
115
  iconGap="10px"
66
116
  iconSize="15px"
67
- onclick={handleDownload}>Download
117
+ onclick={fetchAuthorizedItems}>Download
68
118
  </MenuItem>
@@ -3,12 +3,20 @@
3
3
 
4
4
  async function handleUpload() {
5
5
  if( $fileInputStore.selectedFiles.length === 0 || !$fileInputConfig.serverUploadUrl) return;
6
-
6
+ if( !$fileInputConfig.user_id) {
7
+ addToast({
8
+ status: 'error',
9
+ message: 'User ID is required for upload',
10
+ persistent: true
11
+ });
12
+ return;
13
+ }
7
14
  try {
8
15
  $fileInputStore.processing = true;
9
16
  const formData = new FormData();
10
17
 
11
18
  $fileInputStore.selectedFiles.forEach(file => formData.append('files', file));
19
+ formData.append('user_id', $fileInputConfig.user_id);
12
20
 
13
21
  const res = await fetch($fileInputConfig.serverUploadUrl, {
14
22
  method: 'POST',
@@ -45,11 +53,39 @@
45
53
  $fileInputStore.processing = false;
46
54
  }
47
55
  }
56
+
57
+ async function handleStorageStatus(){
58
+ if (($fileInputConfig.usedBytes == null || $fileInputConfig.maxBytes == null)) {
59
+ addToast({
60
+ status: 'warning',
61
+ message: 'Unable to calculate storage usage. Please configure your cloud storage settings.',
62
+ persistent: true,
63
+ });
64
+ return;
65
+ }
66
+ const selectedSize = $fileInputStore.selectedFiles.reduce(
67
+ (acc, file) => acc + file.size,
68
+ 0
69
+ );
70
+ //console.log('Selected files total size:', selectedSize);
71
+ const projectedUsage = $fileInputConfig.usedBytes + selectedSize;
72
+ const usageRatio = projectedUsage / $fileInputConfig.maxBytes;
73
+
74
+ if (usageRatio >= 0.95) {
75
+ addToast({
76
+ status: 'error',
77
+ message: `Upload blocked: storage limit almost reached (${Math.round(usageRatio * 100)}%). Please delete some files or free up space.`,
78
+ persistent: true
79
+ });
80
+ return;
81
+ }
82
+ handleUpload();
83
+ }
48
84
  </script>
49
85
 
50
86
 
51
87
  <FileInput
52
88
  minHeight="400px"
53
89
  bind:processing={$fileInputStore.processing}
54
- onclick={handleUpload}
90
+ onclick={handleStorageStatus}
55
91
  />
@@ -29,6 +29,7 @@ export type FileInputConfig = {
29
29
  serverDownloadUrl: string;
30
30
  usedBytes: number;
31
31
  maxBytes: number;
32
+ user_id?: string;
32
33
  };
33
34
  export declare const fileInputConfig: import("svelte/store").Writable<FileInputConfig>;
34
35
  export declare const fileInputStore: import("svelte/store").Writable<FileInputState>;
@@ -20,7 +20,8 @@ const defaultFileInputConfig = {
20
20
  serverDeleteUrl: '',
21
21
  serverDownloadUrl: '',
22
22
  usedBytes: 0,
23
- maxBytes: 104857600
23
+ maxBytes: 104857600,
24
+ user_id: '',
24
25
  };
25
26
  export const fileInputConfig = writable({
26
27
  ...defaultFileInputConfig
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sierra-95/svelte-scaffold",
3
- "version": "1.2.5",
3
+ "version": "1.2.6",
4
4
  "scripts": {
5
5
  "dev": "vite dev",
6
6
  "build": "vite build && npm run prepack",