@sierra-95/svelte-scaffold 1.0.8 → 1.0.10
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/Hooks/preview.d.ts +3 -1
- package/dist/Hooks/preview.js +11 -4
- package/dist/components/Core/Form/Input/FileInput/fileInput.svelte +61 -33
- package/dist/components/Core/Form/Input/FileInput/preview.svelte +28 -15
- package/dist/components/Core/Menus/DropdownContainer/dropdown.svelte +2 -2
- package/dist/components/Core/others/Previews/Audio/audio.svelte +1 -1
- package/dist/components/Core/others/Previews/Document/documents.svelte +14 -2
- package/dist/components/Core/others/Previews/GenericFile/genericFile.svelte +26 -0
- package/dist/components/Core/others/Previews/GenericFile/genericFile.svelte.d.ts +22 -0
- package/dist/components/Core/others/Previews/Image/image.svelte +1 -1
- package/dist/components/Core/others/Previews/Video/video.svelte +1 -1
- package/dist/components/Modules/Editor/Nodes/Images/images.svelte +1 -1
- package/dist/components/Modules/FilePicker/cloudStore.svelte +11 -7
- package/dist/components/Modules/FilePicker/controls.svelte +58 -13
- package/dist/components/Modules/FilePicker/controls.svelte.d.ts +4 -19
- package/dist/components/Modules/FilePicker/filePicker.svelte +3 -4
- package/dist/components/Modules/FilePicker/file_properties.svelte +33 -0
- package/dist/components/Modules/FilePicker/file_properties.svelte.d.ts +11 -0
- package/dist/components/Modules/FilePicker/previews.svelte +5 -1
- package/dist/components/Modules/Layout/main.svelte +1 -13
- package/dist/global.css +5 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +2 -1
- package/dist/stores/modules/fileInput.d.ts +14 -7
- package/dist/stores/modules/fileInput.js +2 -2
- package/package.json +1 -1
package/dist/Hooks/preview.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { FileInputStoreMediaItem } from '../index.js';
|
|
1
2
|
export declare function getPreviewUrlForMedia(file: File): string;
|
|
2
|
-
export declare function toggleSelectForMedia(
|
|
3
|
+
export declare function toggleSelectForMedia(item: FileInputStoreMediaItem, urlsOnly: boolean): void;
|
|
3
4
|
export declare function removeFileForMedia(index: number, e: Event): void;
|
|
5
|
+
export declare const DOCUMENT_MIME_TYPES: Set<string>;
|
package/dist/Hooks/preview.js
CHANGED
|
@@ -2,16 +2,16 @@ import { fileInputStore } from '../index.js';
|
|
|
2
2
|
export function getPreviewUrlForMedia(file) {
|
|
3
3
|
return URL.createObjectURL(file);
|
|
4
4
|
}
|
|
5
|
-
export function toggleSelectForMedia(
|
|
5
|
+
export function toggleSelectForMedia(item, urlsOnly) {
|
|
6
6
|
if (!urlsOnly)
|
|
7
7
|
return;
|
|
8
8
|
fileInputStore.update(state => {
|
|
9
|
-
const index = state.submissions.findIndex(
|
|
9
|
+
const index = state.submissions.findIndex(sub => sub.id === item.id);
|
|
10
10
|
return {
|
|
11
11
|
...state,
|
|
12
12
|
submissions: index === -1
|
|
13
|
-
? [...state.submissions,
|
|
14
|
-
: state.submissions.filter(
|
|
13
|
+
? [...state.submissions, item] // select full media
|
|
14
|
+
: state.submissions.filter(sub => sub.id !== item.id) // deselect
|
|
15
15
|
};
|
|
16
16
|
});
|
|
17
17
|
}
|
|
@@ -22,3 +22,10 @@ export function removeFileForMedia(index, e) {
|
|
|
22
22
|
selectedFiles: state.selectedFiles.filter((_, i) => i !== index)
|
|
23
23
|
}));
|
|
24
24
|
}
|
|
25
|
+
export const DOCUMENT_MIME_TYPES = new Set([
|
|
26
|
+
'application/pdf',
|
|
27
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
28
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
29
|
+
'application/vnd.ms-excel',
|
|
30
|
+
'text/plain'
|
|
31
|
+
]);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onDestroy } from 'svelte';
|
|
3
|
-
import {fileInputStore, Button, setToastMessage} from '../../../../../index.js'
|
|
3
|
+
import {fileInputStore, Button, setToastMessage, DOCUMENT_MIME_TYPES} from '../../../../../index.js'
|
|
4
4
|
import Preview from './preview.svelte';
|
|
5
5
|
|
|
6
6
|
const {
|
|
@@ -11,74 +11,97 @@
|
|
|
11
11
|
|
|
12
12
|
let fileInput: HTMLInputElement;
|
|
13
13
|
|
|
14
|
-
const typeMap: Record<'image' | 'audio' | 'video' | '
|
|
14
|
+
const typeMap: Record<'image' | 'audio' | 'video' | 'documents' | 'others', string[]> = {
|
|
15
15
|
image: ['image/'],
|
|
16
16
|
audio: ['audio/'],
|
|
17
17
|
video: ['video/'],
|
|
18
|
-
|
|
18
|
+
documents: Array.from(DOCUMENT_MIME_TYPES),
|
|
19
|
+
others: []
|
|
19
20
|
};
|
|
20
21
|
|
|
21
|
-
|
|
22
22
|
//Validate and add files
|
|
23
23
|
function isAllowedType(file: File): boolean {
|
|
24
|
-
return $fileInputStore.uploadType.some(type =>
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
return $fileInputStore.uploadType.some(type => {
|
|
25
|
+
if (type === 'others') return true;
|
|
26
|
+
const prefixes = typeMap[type];
|
|
27
|
+
return prefixes.some(prefix => file.type.startsWith(prefix));
|
|
28
|
+
});
|
|
27
29
|
}
|
|
28
30
|
|
|
29
|
-
function
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
function allowedTypes(){
|
|
32
|
+
const allowedTypes = $fileInputStore.uploadType
|
|
33
|
+
.filter(t => t !== 'others')
|
|
34
|
+
.map(t => {
|
|
35
|
+
if (t === 'documents') return 'Documents (PDF, Word, Excel, TXT)';
|
|
36
|
+
return t.charAt(0).toUpperCase() + t.slice(1);
|
|
37
|
+
})
|
|
38
|
+
.join(', ');
|
|
39
|
+
return allowedTypes;
|
|
40
|
+
}
|
|
34
41
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
42
|
+
function processFiles(files: FileList | File[]) {
|
|
43
|
+
const validFiles: File[] = [];
|
|
44
|
+
|
|
45
|
+
for (const file of files) {
|
|
46
|
+
if (!isAllowedType(file)) {
|
|
47
|
+
setToastMessage(
|
|
48
|
+
'error',
|
|
49
|
+
`"${file.name}" is not an allowed file type. Allowed types: ${allowedTypes()}`
|
|
50
|
+
);
|
|
51
|
+
continue; // skip invalid
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (file.size > $fileInputStore.sizeConstraint) {
|
|
55
|
+
setToastMessage(
|
|
56
|
+
'error',
|
|
57
|
+
`"${file.name}" exceeds the allowed file size`
|
|
58
|
+
);
|
|
59
|
+
continue; // skip oversized
|
|
60
|
+
}
|
|
61
|
+
validFiles.push(file);
|
|
38
62
|
}
|
|
39
63
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
64
|
+
if (validFiles.length > 0) {
|
|
65
|
+
fileInputStore.update(state => ({
|
|
66
|
+
...state,
|
|
67
|
+
selectedFiles: [...state.selectedFiles, ...validFiles]
|
|
68
|
+
}));
|
|
69
|
+
}
|
|
70
|
+
clearInput();
|
|
44
71
|
}
|
|
45
72
|
|
|
46
73
|
|
|
74
|
+
|
|
47
75
|
//Handle file selection via input
|
|
48
76
|
function handleFileChange(event: Event) {
|
|
49
77
|
const files = (event.target as HTMLInputElement).files;
|
|
50
78
|
if (!files || files.length === 0) return;
|
|
51
|
-
|
|
52
|
-
for (const file of files) {
|
|
53
|
-
validateAndAddFile(file);
|
|
54
|
-
}
|
|
79
|
+
processFiles(files);
|
|
55
80
|
}
|
|
56
81
|
|
|
57
82
|
//Handle drag and drop
|
|
58
83
|
function handleDrop(event: DragEvent) {
|
|
59
84
|
event.preventDefault();
|
|
60
85
|
if (!event.dataTransfer?.files.length) return;
|
|
61
|
-
|
|
62
|
-
for (const file of event.dataTransfer.files) {
|
|
63
|
-
validateAndAddFile(file);
|
|
64
|
-
}
|
|
65
|
-
|
|
86
|
+
processFiles(event.dataTransfer.files);
|
|
66
87
|
}
|
|
67
88
|
|
|
68
89
|
function handleDragOver(event: DragEvent) {
|
|
69
90
|
event.preventDefault();
|
|
70
91
|
}
|
|
71
92
|
|
|
93
|
+
function clearInput() {
|
|
94
|
+
if (fileInput) {
|
|
95
|
+
fileInput.value = '';
|
|
96
|
+
}
|
|
97
|
+
}
|
|
72
98
|
//Delete selected files
|
|
73
99
|
function handleClear() {
|
|
74
100
|
fileInputStore.update(state => ({
|
|
75
101
|
...state,
|
|
76
102
|
selectedFiles: []
|
|
77
103
|
}));
|
|
78
|
-
|
|
79
|
-
fileInput.value = '';
|
|
80
|
-
}
|
|
81
|
-
|
|
104
|
+
clearInput();
|
|
82
105
|
}
|
|
83
106
|
|
|
84
107
|
onDestroy(() => {
|
|
@@ -92,7 +115,12 @@
|
|
|
92
115
|
bind:this={fileInput}
|
|
93
116
|
type="file"
|
|
94
117
|
accept={$fileInputStore.uploadType
|
|
95
|
-
.map(t =>
|
|
118
|
+
.map(t => {
|
|
119
|
+
if (t === 'documents') return Array.from(DOCUMENT_MIME_TYPES).join(',');
|
|
120
|
+
if (t === 'others') return '';
|
|
121
|
+
return `${t}/*`;
|
|
122
|
+
})
|
|
123
|
+
.filter(Boolean)
|
|
96
124
|
.join(',')
|
|
97
125
|
}
|
|
98
126
|
hidden
|
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {fileInputStore, PreviewAudio,PreviewDocument,PreviewImage,PreviewVideo} from '../../../../../index.js';
|
|
2
|
+
import {fileInputStore, PreviewAudio,PreviewDocument,PreviewImage,PreviewVideo, PreviewGenericFile, DOCUMENT_MIME_TYPES} from '../../../../../index.js';
|
|
3
3
|
|
|
4
|
+
function getMediaCategory(file: File):
|
|
5
|
+
'Audio' | 'Documents' | 'Images' | 'Videos' | 'Others' {
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
)
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
7
|
+
if (file.type.startsWith('audio/')) return 'Audio';
|
|
8
|
+
if (file.type.startsWith('image/')) return 'Images';
|
|
9
|
+
if (file.type.startsWith('video/')) return 'Videos';
|
|
10
|
+
if (DOCUMENT_MIME_TYPES.has(file.type)) return 'Documents';
|
|
11
|
+
|
|
12
|
+
return 'Others';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
$: media = $fileInputStore.selectedFiles.reduce(
|
|
16
|
+
(acc, file) => {
|
|
17
|
+
acc[getMediaCategory(file)].push(file);
|
|
18
|
+
return acc;
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
Audio: [] as File[],
|
|
22
|
+
Documents: [] as File[],
|
|
23
|
+
Images: [] as File[],
|
|
24
|
+
Videos: [] as File[],
|
|
25
|
+
Others: [] as File[],
|
|
26
|
+
}
|
|
27
|
+
);
|
|
19
28
|
</script>
|
|
20
29
|
|
|
21
30
|
<div style="max-height: 300px; overflow-y: auto; padding: 0.5rem;">
|
|
@@ -35,4 +44,8 @@
|
|
|
35
44
|
<h3 style="margin: 1rem 0rem;">Videos</h3>
|
|
36
45
|
<PreviewVideo buttonTimes urlsOnly={false} {media}/>
|
|
37
46
|
{/if}
|
|
47
|
+
{#if media.Others.length > 0}
|
|
48
|
+
<h3 style="margin: 1rem 0rem;">Other Files</h3>
|
|
49
|
+
<PreviewGenericFile buttonTimes urlsOnly={false} {media}/>
|
|
50
|
+
{/if}
|
|
38
51
|
</div>
|
|
@@ -49,10 +49,10 @@
|
|
|
49
49
|
};
|
|
50
50
|
if (browser) {
|
|
51
51
|
onMount(() => {
|
|
52
|
-
document.addEventListener('
|
|
52
|
+
document.addEventListener('pointerdown', handleDocumentClick, true);
|
|
53
53
|
});
|
|
54
54
|
onDestroy(() => {
|
|
55
|
-
document.removeEventListener('
|
|
55
|
+
document.removeEventListener('pointerdown', handleDocumentClick, true);
|
|
56
56
|
});
|
|
57
57
|
}
|
|
58
58
|
</script>
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
{:else}
|
|
43
43
|
<div style="display: flex; flex-wrap:wrap; gap: 1rem">
|
|
44
44
|
{#each media.Audio as item (item.id || uuid())}
|
|
45
|
-
<div on:click={() => toggleSelectForMedia(item
|
|
45
|
+
<div on:click={() => toggleSelectForMedia(item, urlsOnly)} role="none" class="sierra-translate" style="position: relative; width: 200px;display: flex; gap: 0.5rem; padding: 0.5rem; box-shadow: var(--box-shadow-secondary); border-radius: 0.3rem; cursor: pointer;">
|
|
46
46
|
<button aria-label="Play or pause audio" on:click|stopPropagation={() => togglePlay(item.id)}>
|
|
47
47
|
<i class="fa {audioPlaying[item.id] ? 'fa-pause' : 'fa-play'}" aria-hidden="true"></i>
|
|
48
48
|
</button>
|
|
@@ -3,13 +3,25 @@
|
|
|
3
3
|
export let media;
|
|
4
4
|
export let buttonTimes = false;
|
|
5
5
|
export let urlsOnly = true;
|
|
6
|
+
const iconBaseUrl = 'https://files.michaelmachohi.com/svelte-scaffold/icons/';
|
|
7
|
+
const mimeTypeToIcon: Record<string, string> = {
|
|
8
|
+
'application/pdf': 'pdf.icon.png',
|
|
9
|
+
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'word.icon.png',
|
|
10
|
+
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'excel.icon.png',
|
|
11
|
+
'application/vnd.ms-excel': 'excel.icon.png',
|
|
12
|
+
'text/plain': 'txt.icon.png'
|
|
13
|
+
};
|
|
14
|
+
const getIconUrl = (mimeType: string) => {
|
|
15
|
+
return iconBaseUrl + (mimeTypeToIcon[mimeType] || 'generic.icon.png');
|
|
16
|
+
};
|
|
6
17
|
</script>
|
|
7
18
|
{#if media.Documents.length === 0}
|
|
8
19
|
<p>No documents available.</p>
|
|
9
20
|
{:else}
|
|
10
21
|
<div style="display: flex; flex-wrap: wrap; gap: 1rem;">
|
|
11
22
|
{#each media.Documents as item}
|
|
12
|
-
<div on:click={() => toggleSelectForMedia(item
|
|
23
|
+
<div on:click={() => toggleSelectForMedia(item, urlsOnly)} role="none" class="sierra-translate" style="position: relative; width: 70px; cursor: pointer;">
|
|
24
|
+
<img src={getIconUrl(item.type || item.mime_type)} alt="Document Icon" style="height: 70px;"/>
|
|
13
25
|
{#if buttonTimes}
|
|
14
26
|
<ButtonTimes vertical="bottom" onclick={(e: Event) => removeFileForMedia(
|
|
15
27
|
$fileInputStore.selectedFiles.indexOf(item), e
|
|
@@ -18,7 +30,7 @@
|
|
|
18
30
|
{#if $fileInputStore.submissions.some(sub => sub.url === item.url || sub.id === item.id)}
|
|
19
31
|
<ButtonSelect />
|
|
20
32
|
{/if}
|
|
21
|
-
<h3 class="sierra-text-
|
|
33
|
+
<h3 class="sierra-text-wrap">{item.original_name || item.name}</h3>
|
|
22
34
|
</div>
|
|
23
35
|
{/each}
|
|
24
36
|
</div>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import {ButtonTimes,ButtonSelect, fileInputStore, removeFileForMedia, toggleSelectForMedia} from '../../../../../index.js'
|
|
3
|
+
export let media;
|
|
4
|
+
export let buttonTimes = false;
|
|
5
|
+
export let urlsOnly = true;
|
|
6
|
+
</script>
|
|
7
|
+
{#if media.Others.length === 0}
|
|
8
|
+
<p>No other files available.</p>
|
|
9
|
+
{:else}
|
|
10
|
+
<div style="display: flex; flex-wrap: wrap; gap: 1rem;">
|
|
11
|
+
{#each media.Others as item}
|
|
12
|
+
<div on:click={() => toggleSelectForMedia(item, urlsOnly)} role="none" class="sierra-translate" style="position: relative; width: 70px; cursor: pointer;">
|
|
13
|
+
<img src="https://files.michaelmachohi.com/svelte-scaffold/icons/generic.icon.png" alt="Generic File Icon" style="height: 70px;"/>
|
|
14
|
+
{#if buttonTimes}
|
|
15
|
+
<ButtonTimes vertical="bottom" onclick={(e: Event) => removeFileForMedia(
|
|
16
|
+
$fileInputStore.selectedFiles.indexOf(item), e
|
|
17
|
+
)}/>
|
|
18
|
+
{/if}
|
|
19
|
+
{#if $fileInputStore.submissions.some(sub => sub.url === item.url || sub.id === item.id)}
|
|
20
|
+
<ButtonSelect />
|
|
21
|
+
{/if}
|
|
22
|
+
<h3 class="sierra-text-wrap">{item.original_name || item.name}</h3>
|
|
23
|
+
</div>
|
|
24
|
+
{/each}
|
|
25
|
+
</div>
|
|
26
|
+
{/if}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
interface $$__sveltets_2_IsomorphicComponent<Props extends Record<string, any> = any, Events extends Record<string, any> = any, Slots extends Record<string, any> = any, Exports = {}, Bindings = string> {
|
|
2
|
+
new (options: import('svelte').ComponentConstructorOptions<Props>): import('svelte').SvelteComponent<Props, Events, Slots> & {
|
|
3
|
+
$$bindings?: Bindings;
|
|
4
|
+
} & Exports;
|
|
5
|
+
(internal: unknown, props: Props & {
|
|
6
|
+
$$events?: Events;
|
|
7
|
+
$$slots?: Slots;
|
|
8
|
+
}): Exports & {
|
|
9
|
+
$set?: any;
|
|
10
|
+
$on?: any;
|
|
11
|
+
};
|
|
12
|
+
z_$$bindings?: Bindings;
|
|
13
|
+
}
|
|
14
|
+
declare const GenericFile: $$__sveltets_2_IsomorphicComponent<{
|
|
15
|
+
media: any;
|
|
16
|
+
buttonTimes?: boolean;
|
|
17
|
+
urlsOnly?: boolean;
|
|
18
|
+
}, {
|
|
19
|
+
[evt: string]: CustomEvent<any>;
|
|
20
|
+
}, {}, {}, string>;
|
|
21
|
+
type GenericFile = InstanceType<typeof GenericFile>;
|
|
22
|
+
export default GenericFile;
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
{:else}
|
|
11
11
|
<div style="display: flex; flex-wrap: wrap; gap: 1rem;">
|
|
12
12
|
{#each media.Images as item}
|
|
13
|
-
<div on:click={() => toggleSelectForMedia(item
|
|
13
|
+
<div on:click={() => toggleSelectForMedia(item, urlsOnly)} role="none" class="sierra-translate" style="position:relative; box-shadow: var(--box-shadow-secondary);width: 150px;border-radius: 0.3rem;cursor: pointer;">
|
|
14
14
|
<img src={urlsOnly ? item.url : getPreviewUrlForMedia(item)} alt={item} style="width: 100%; height: 100px; object-fit: cover;border-top-left-radius: 0.3rem; border-top-right-radius: 0.3rem;" />
|
|
15
15
|
<h3 style="margin: 0.5rem" class="sierra-text-ellipsis">{item.original_name || item.name}</h3>
|
|
16
16
|
{#if buttonTimes}
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
{:else}
|
|
11
11
|
<div style="display: flex; flex-wrap: wrap; gap: 1rem;">
|
|
12
12
|
{#each media.Videos as item}
|
|
13
|
-
<div on:click={() => toggleSelectForMedia(item
|
|
13
|
+
<div on:click={() => toggleSelectForMedia(item, urlsOnly)} role="none" class="sierra-translate" style="position:relative; box-shadow: var(--box-shadow-secondary);width: 150px;border-radius: 0.3rem; cursor: pointer;">
|
|
14
14
|
<video on:click|stopPropagation src={urlsOnly ? item.url : getPreviewUrlForMedia(item)} controls style="width: 100%; height: 100px; object-fit: cover; border-top-left-radius: 0.3rem; border-top-right-radius: 0.3rem;">
|
|
15
15
|
<track kind="captions" />
|
|
16
16
|
</video>
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
fileInputStore.update(store => ({
|
|
21
21
|
...store,
|
|
22
22
|
uploadType: ['image'],
|
|
23
|
-
disabledMenuItem: ['Music', 'Videos', 'Documents'],
|
|
23
|
+
disabledMenuItem: ['Music', 'Videos', 'Documents', 'Others'],
|
|
24
24
|
r2_key: $editorStore.r2_key,
|
|
25
25
|
serverGetUrl: $editorStore.serverGetUrl,
|
|
26
26
|
serverUploadUrl: $editorStore.serverUploadUrl,
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { onMount } from 'svelte';
|
|
3
|
-
import { fileInputStore, User,
|
|
3
|
+
import { fileInputStore, User, setToastMessage, buttonRipple, isMobile } from '../../../index.js';
|
|
4
4
|
import Previews from './previews.svelte';
|
|
5
5
|
|
|
6
6
|
export let processing: boolean;
|
|
7
|
-
const tabs: { name: 'Documents'| 'Music' | 'Pictures' | 'Videos'; icon: string }[] = [
|
|
7
|
+
const tabs: { name: 'Documents'| 'Music' | 'Pictures' | 'Videos'| 'Others'; icon: string }[] = [
|
|
8
8
|
{ name: 'Documents', icon: 'fa fa-file-o' },
|
|
9
9
|
{ name: 'Music', icon: 'fa fa-music' },
|
|
10
10
|
{ name: 'Pictures', icon: 'fa fa-file-image-o' },
|
|
11
|
-
{ name: 'Videos', icon: 'fa fa-film' }
|
|
11
|
+
{ name: 'Videos', icon: 'fa fa-film' },
|
|
12
|
+
{ name: 'Others', icon: 'fa fa-folder-o' }
|
|
12
13
|
];
|
|
13
14
|
|
|
14
15
|
$: if ($fileInputStore.disabledMenuItem?.includes($fileInputStore.activeMenu)) {
|
|
@@ -25,6 +26,7 @@
|
|
|
25
26
|
Documents: MediaItem[];
|
|
26
27
|
Images: MediaItem[];
|
|
27
28
|
Videos: MediaItem[];
|
|
29
|
+
Others: MediaItem[];
|
|
28
30
|
};
|
|
29
31
|
|
|
30
32
|
let media: MediaResponse | null = null;
|
|
@@ -39,7 +41,6 @@
|
|
|
39
41
|
try {
|
|
40
42
|
processing = true;
|
|
41
43
|
const params = new URLSearchParams({
|
|
42
|
-
isLoggedIn: String($isLoggedIn),
|
|
43
44
|
userId: $User.userId
|
|
44
45
|
});
|
|
45
46
|
|
|
@@ -75,7 +76,7 @@
|
|
|
75
76
|
}
|
|
76
77
|
</style>
|
|
77
78
|
<main id="sierra-cloud-store" style="display: flex; gap: 1rem; height: 100%; min-height: 300px;">
|
|
78
|
-
<nav style="display: flex; flex-direction: column; gap: 0.5rem; padding-top: 1rem; background-color: var(--background); border-bottom-left-radius:5px;">
|
|
79
|
+
<nav style="display: flex; flex-direction: column; gap: {$isMobile ? '1rem' : '0.5rem'}; padding-top: 1rem; background-color: var(--background); border-bottom-left-radius:5px;">
|
|
79
80
|
{#each tabs as tab}
|
|
80
81
|
<button
|
|
81
82
|
use:buttonRipple
|
|
@@ -85,11 +86,14 @@
|
|
|
85
86
|
activeMenu: tab.name
|
|
86
87
|
}))
|
|
87
88
|
}
|
|
88
|
-
style="
|
|
89
|
+
style="
|
|
90
|
+
width:100%; padding: 0.1rem 0.5rem;
|
|
91
|
+
{$fileInputStore.disabledMenuItem?.includes(tab.name) ? 'display: none;' : 'display: flex;align-items: center; gap: 0.5rem;'}
|
|
92
|
+
"
|
|
89
93
|
class={`sierra-text-ellipsis ${$fileInputStore.activeMenu === tab.name ? 'icon-active' : ''}`}
|
|
90
94
|
>
|
|
91
95
|
<i class={tab.icon} style="font-size: 10px; color: var(--icon-theme);"></i>
|
|
92
|
-
{tab.name}
|
|
96
|
+
{$isMobile ? '' : tab.name}
|
|
93
97
|
</button>
|
|
94
98
|
{/each}
|
|
95
99
|
</nav>
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {fileInputStore, LinearProgress, setToastMessage, modalStore, resetFileInputStore} from "../../../index.js";
|
|
2
|
+
import {fileInputStore, LinearProgress, setToastMessage, modalStore, resetFileInputStore, DropdownContainer, MenuItem, buttonRipple} from "../../../index.js";
|
|
3
3
|
import { get } from 'svelte/store';
|
|
4
|
-
|
|
4
|
+
import FileProperties from "./file_properties.svelte";
|
|
5
|
+
|
|
6
|
+
let {
|
|
7
|
+
processing,
|
|
8
|
+
} = $props();
|
|
5
9
|
|
|
6
10
|
const menuItems = [
|
|
7
11
|
{
|
|
@@ -21,7 +25,38 @@
|
|
|
21
25
|
uploadModalOpen: false
|
|
22
26
|
}));
|
|
23
27
|
}
|
|
28
|
+
|
|
29
|
+
const iconSize = '15px';
|
|
30
|
+
let showProperties = $state(false);
|
|
31
|
+
let openActionsMenu = $state(false);
|
|
32
|
+
let disableActions = $state(true);
|
|
33
|
+
$effect(() => {
|
|
34
|
+
if($fileInputStore.submissions.length > 0 && $fileInputStore.activeTab === 'cloud'){
|
|
35
|
+
disableActions = false;
|
|
36
|
+
} else {
|
|
37
|
+
disableActions = true;
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
function handleDownload() {
|
|
42
|
+
const store = get(fileInputStore);
|
|
43
|
+
if (!store.submissions || store.submissions.length === 0) return;
|
|
44
|
+
|
|
45
|
+
store.submissions.forEach(item => {
|
|
46
|
+
downloadFile(item.url, item.id);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function downloadFile(url: string, filename?: string) {
|
|
50
|
+
const a = document.createElement('a');
|
|
51
|
+
a.href = url;
|
|
52
|
+
if (filename) a.download = filename;
|
|
53
|
+
document.body.appendChild(a);
|
|
54
|
+
a.click();
|
|
55
|
+
document.body.removeChild(a);
|
|
56
|
+
}
|
|
57
|
+
|
|
24
58
|
function handleDelete() {
|
|
59
|
+
openActionsMenu = false;
|
|
25
60
|
modalStore.update(store => ({
|
|
26
61
|
...store,
|
|
27
62
|
open: true,
|
|
@@ -60,9 +95,9 @@
|
|
|
60
95
|
return;
|
|
61
96
|
}
|
|
62
97
|
|
|
63
|
-
data.forEach((item: { id: string;
|
|
98
|
+
data.forEach((item: { id: string; code: number }) => {
|
|
64
99
|
if (item.code === 404) {
|
|
65
|
-
setToastMessage('error', `Failed to delete file with ID: ${item.id}
|
|
100
|
+
setToastMessage('error', `Failed to delete file with ID: ${item.id}`);
|
|
66
101
|
}
|
|
67
102
|
});
|
|
68
103
|
processing = false;
|
|
@@ -99,24 +134,34 @@
|
|
|
99
134
|
color: var(--background);
|
|
100
135
|
}
|
|
101
136
|
</style>
|
|
137
|
+
|
|
102
138
|
<main id="file-picker-controls" style="display: flex; justify-content: space-between; gap:1rem;">
|
|
103
139
|
<div class="file-picker-controls">
|
|
104
140
|
<div style="display: flex; align-items: last baseline; gap:1rem;">
|
|
105
141
|
{#each menuItems as item, index (item.id)}
|
|
106
142
|
<button
|
|
107
143
|
class={$fileInputStore.activeTab === item.id ? 'active' : ''}
|
|
108
|
-
|
|
144
|
+
onclick={() => {fileInputStore.update(store => ({ ...store, activeTab: item.id }))}}
|
|
109
145
|
><span>{item.label}</span>
|
|
110
146
|
</button>
|
|
111
147
|
{/each}
|
|
112
|
-
</div>
|
|
113
|
-
<div style="display: flex; gap:
|
|
114
|
-
{
|
|
115
|
-
|
|
116
|
-
<button
|
|
117
|
-
|
|
118
|
-
|
|
148
|
+
</div>
|
|
149
|
+
<div style="display: flex; gap: 1rem; align-items: last baseline;">
|
|
150
|
+
<button hidden={$fileInputStore.manage || disableActions} title="Select" onclick={handleSelect} style="color: var(--primary-bg);" aria-label="Select submissions"><i class="fa fa-check-circle-o"></i></button>
|
|
151
|
+
{#snippet TriggerMenu()}
|
|
152
|
+
<button disabled={disableActions} use:buttonRipple class="w-10" aria-label="Ellipsis" onclick={() => (openActionsMenu = !openActionsMenu)}>
|
|
153
|
+
<i class="fa fa-bars"></i>
|
|
154
|
+
</button>
|
|
155
|
+
{/snippet}
|
|
156
|
+
<DropdownContainer bind:open={openActionsMenu} dropdownTrigger={TriggerMenu}>
|
|
157
|
+
<MenuItem onclick={() => { showProperties = true; openActionsMenu = false; }} icon="fa-info-circle" iconBg="var(--text)" iconSize={iconSize}>Properties</MenuItem>
|
|
158
|
+
<MenuItem onclick={handleDownload} icon="fa-download" iconBg="var(--text)" iconSize={iconSize}>Download</MenuItem>
|
|
159
|
+
<MenuItem onclick={handleDelete} icon="fa-trash-o" iconBg="var(--error-bg)" iconSize={iconSize}>Delete</MenuItem>
|
|
160
|
+
</DropdownContainer>
|
|
161
|
+
<button title="Cancel" onclick={resetFileInputStore} aria-label="Cancel"><i class="fa fa-times-circle"></i></button>
|
|
119
162
|
</div>
|
|
120
163
|
</div>
|
|
121
164
|
</main>
|
|
122
|
-
{#if processing}<LinearProgress />{/if}
|
|
165
|
+
{#if processing}<LinearProgress />{/if}
|
|
166
|
+
|
|
167
|
+
<FileProperties bind:showProperties />
|
|
@@ -1,20 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
(internal: unknown, props: Props & {
|
|
6
|
-
$$events?: Events;
|
|
7
|
-
$$slots?: Slots;
|
|
8
|
-
}): Exports & {
|
|
9
|
-
$set?: any;
|
|
10
|
-
$on?: any;
|
|
11
|
-
};
|
|
12
|
-
z_$$bindings?: Bindings;
|
|
13
|
-
}
|
|
14
|
-
declare const Controls: $$__sveltets_2_IsomorphicComponent<{
|
|
15
|
-
processing: boolean;
|
|
16
|
-
}, {
|
|
17
|
-
[evt: string]: CustomEvent<any>;
|
|
18
|
-
}, {}, {}, string>;
|
|
19
|
-
type Controls = InstanceType<typeof Controls>;
|
|
1
|
+
declare const Controls: import("svelte").Component<{
|
|
2
|
+
processing: any;
|
|
3
|
+
}, {}, "">;
|
|
4
|
+
type Controls = ReturnType<typeof Controls>;
|
|
20
5
|
export default Controls;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {Backdrop,Wrapper,fileInputStore, FileInput,
|
|
2
|
+
import {Backdrop,Wrapper,fileInputStore, FileInput, User, setToastMessage} from '../../../index.js'
|
|
3
3
|
import CloudStore from './cloudStore.svelte';
|
|
4
4
|
import Controls from './controls.svelte';
|
|
5
5
|
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
|
|
15
15
|
$fileInputStore.selectedFiles.forEach(file => formData.append('files', file));
|
|
16
16
|
formData.append('r2_key', $fileInputStore.r2_key);
|
|
17
|
-
formData.append('is_loggedin', $isLoggedIn.toString());
|
|
18
17
|
formData.append('userid', $User.userId);
|
|
19
18
|
|
|
20
19
|
const res = await fetch($fileInputStore.serverUploadUrl, {
|
|
@@ -29,9 +28,9 @@
|
|
|
29
28
|
return;
|
|
30
29
|
}
|
|
31
30
|
|
|
32
|
-
data.forEach((item: {
|
|
31
|
+
data.forEach((item: { original_name: string; code: number; }) => {
|
|
33
32
|
if (item.code === 500) {
|
|
34
|
-
setToastMessage('error', `Failed to upload file: ${item.original_name}
|
|
33
|
+
setToastMessage('error', `Failed to upload file: ${item.original_name}`);
|
|
35
34
|
}
|
|
36
35
|
});
|
|
37
36
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import {Wrapper, Backdrop, fileInputStore} from "../../../index.js";
|
|
3
|
+
let {
|
|
4
|
+
showProperties = $bindable(),
|
|
5
|
+
} = $props();
|
|
6
|
+
|
|
7
|
+
</script>
|
|
8
|
+
<style>
|
|
9
|
+
#file-picker-file-properties h3, span{
|
|
10
|
+
font-size: 14px;
|
|
11
|
+
}
|
|
12
|
+
#file-picker-file-properties span{
|
|
13
|
+
color: var(--text-secondary);
|
|
14
|
+
}
|
|
15
|
+
</style>
|
|
16
|
+
<Backdrop bind:open={showProperties}>
|
|
17
|
+
<Wrapper>
|
|
18
|
+
<div style="display: flex; justify-content: space-between">
|
|
19
|
+
<h3>Properties</h3>
|
|
20
|
+
<button title="Cancel" onclick={() => showProperties = false} aria-label="Cancel"><i class="fa fa-times-circle"></i></button>
|
|
21
|
+
</div>
|
|
22
|
+
<div style="display: flex; flex-direction: column; gap: 1rem; margin-top: 1rem; max-height: 70vh; overflow-y: auto;">
|
|
23
|
+
{#each $fileInputStore.submissions as item (item.id)}
|
|
24
|
+
<div id="file-picker-file-properties">
|
|
25
|
+
<h3>Name: <span>{item.original_name || 'Unnamed file'}</span></h3>
|
|
26
|
+
<h3>Type: <span>{item?.mime_type}</span></h3>
|
|
27
|
+
<h3>Size: <span>{item?.size_bytes}</span> bytes</h3>
|
|
28
|
+
<h3>Created: <span>{item?.created_at ? new Date(item.created_at).toLocaleString() : ''}</span></h3>
|
|
29
|
+
</div>
|
|
30
|
+
{/each}
|
|
31
|
+
</div>
|
|
32
|
+
</Wrapper>
|
|
33
|
+
</Backdrop>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export default FileProperties;
|
|
2
|
+
type FileProperties = {
|
|
3
|
+
$on?(type: string, callback: (e: any) => void): () => void;
|
|
4
|
+
$set?(props: Partial<$$ComponentProps>): void;
|
|
5
|
+
};
|
|
6
|
+
declare const FileProperties: import("svelte").Component<{
|
|
7
|
+
showProperties?: any;
|
|
8
|
+
}, {}, "showProperties">;
|
|
9
|
+
type $$ComponentProps = {
|
|
10
|
+
showProperties?: any;
|
|
11
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {PreviewAudio,PreviewDocument,PreviewImage,PreviewVideo, fileInputStore} from '../../../index.js';
|
|
2
|
+
import {PreviewAudio,PreviewDocument,PreviewImage,PreviewVideo, fileInputStore, PreviewGenericFile} from '../../../index.js';
|
|
3
3
|
|
|
4
4
|
export let media;
|
|
5
5
|
</script>
|
|
@@ -14,6 +14,10 @@
|
|
|
14
14
|
<PreviewImage {media} />
|
|
15
15
|
{:else if $fileInputStore.activeMenu === 'Videos'}
|
|
16
16
|
<PreviewVideo {media} />
|
|
17
|
+
{:else if $fileInputStore.activeMenu === 'Others'}
|
|
18
|
+
<PreviewGenericFile {media} />
|
|
17
19
|
{/if}
|
|
20
|
+
{:else}
|
|
21
|
+
<p>No media available.</p>
|
|
18
22
|
{/if}
|
|
19
23
|
</div>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script>
|
|
2
2
|
import './main.css';
|
|
3
3
|
import { onMount } from 'svelte';
|
|
4
|
-
import {LinearProgress, isLoading, isMobile, Modal, Toast, theme,FilePicker
|
|
4
|
+
import {LinearProgress, isLoading, isMobile, Modal, Toast, theme,FilePicker} from '../../../index.js'
|
|
5
5
|
import Header from './Header/header.svelte';
|
|
6
6
|
import Menu from './Menu/menu.svelte';
|
|
7
7
|
import Background from './background.svelte';
|
|
@@ -50,18 +50,6 @@
|
|
|
50
50
|
localStorage.setItem('theme', 'dark');
|
|
51
51
|
document.body.setAttribute('data-theme', 'dark');
|
|
52
52
|
}
|
|
53
|
-
|
|
54
|
-
const anonId = localStorage.getItem('anonymous_id');
|
|
55
|
-
if(!$isLoggedIn){
|
|
56
|
-
if (!anonId) {
|
|
57
|
-
const newId = crypto.randomUUID();
|
|
58
|
-
localStorage.setItem('anonymous_id', newId);
|
|
59
|
-
User.update(user => ({ ...user, userId: newId }));
|
|
60
|
-
}else if(anonId) {
|
|
61
|
-
User.update(user => ({ ...user, userId: anonId }) );
|
|
62
|
-
//console.log("Updated User store with user id:", $User);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
53
|
}
|
|
66
54
|
});
|
|
67
55
|
|
package/dist/global.css
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,7 @@ export { default as PreviewAudio } from './components/Core/others/Previews/Audio
|
|
|
17
17
|
export { default as PreviewImage } from './components/Core/others/Previews/Image/image.svelte';
|
|
18
18
|
export { default as PreviewVideo } from './components/Core/others/Previews/Video/video.svelte';
|
|
19
19
|
export { default as PreviewDocument } from './components/Core/others/Previews/Document/documents.svelte';
|
|
20
|
+
export { default as PreviewGenericFile } from './components/Core/others/Previews/GenericFile/genericFile.svelte';
|
|
20
21
|
export { default as Modal } from './components/Core/Alerts/Modal/modal.svelte';
|
|
21
22
|
export { default as Backdrop } from './components/Core/Alerts/Backdrop/backdrop.svelte';
|
|
22
23
|
export { default as Wrapper } from './components/Core/Alerts/Wrapper/wrapper.svelte';
|
|
@@ -37,7 +38,8 @@ export { User, resetUserStore } from './stores/core/user.js';
|
|
|
37
38
|
export { modalStore, resetModalStore } from './stores/core/modal.js';
|
|
38
39
|
export { editorStore, resetEditorStore } from './stores/modules/editor.js';
|
|
39
40
|
export { fileInputStore, resetFileInputStore } from './stores/modules/fileInput.js';
|
|
41
|
+
export type { FileInputStoreMediaItem } from './stores/modules/fileInput.js';
|
|
40
42
|
export { toastCarrier, setToastMessage, clearToastMessage } from './stores/modules/toast.js';
|
|
41
|
-
export { getPreviewUrlForMedia, toggleSelectForMedia, removeFileForMedia } from './Hooks/preview.js';
|
|
43
|
+
export { getPreviewUrlForMedia, toggleSelectForMedia, removeFileForMedia, DOCUMENT_MIME_TYPES } from './Hooks/preview.js';
|
|
42
44
|
export { validateLayoutMenuSections } from './Hooks/layout_menu.js';
|
|
43
45
|
export { buttonRipple } from './Hooks/button.js';
|
package/dist/index.js
CHANGED
|
@@ -22,6 +22,7 @@ export { default as PreviewAudio } from './components/Core/others/Previews/Audio
|
|
|
22
22
|
export { default as PreviewImage } from './components/Core/others/Previews/Image/image.svelte';
|
|
23
23
|
export { default as PreviewVideo } from './components/Core/others/Previews/Video/video.svelte';
|
|
24
24
|
export { default as PreviewDocument } from './components/Core/others/Previews/Document/documents.svelte';
|
|
25
|
+
export { default as PreviewGenericFile } from './components/Core/others/Previews/GenericFile/genericFile.svelte';
|
|
25
26
|
//Alerts
|
|
26
27
|
export { default as Modal } from './components/Core/Alerts/Modal/modal.svelte';
|
|
27
28
|
export { default as Backdrop } from './components/Core/Alerts/Backdrop/backdrop.svelte';
|
|
@@ -51,6 +52,6 @@ export { editorStore, resetEditorStore } from './stores/modules/editor.js';
|
|
|
51
52
|
export { fileInputStore, resetFileInputStore } from './stores/modules/fileInput.js';
|
|
52
53
|
export { toastCarrier, setToastMessage, clearToastMessage } from './stores/modules/toast.js';
|
|
53
54
|
//#######################HOOKS/UTILS########################
|
|
54
|
-
export { getPreviewUrlForMedia, toggleSelectForMedia, removeFileForMedia } from './Hooks/preview.js';
|
|
55
|
+
export { getPreviewUrlForMedia, toggleSelectForMedia, removeFileForMedia, DOCUMENT_MIME_TYPES } from './Hooks/preview.js';
|
|
55
56
|
export { validateLayoutMenuSections } from './Hooks/layout_menu.js';
|
|
56
57
|
export { buttonRipple } from './Hooks/button.js';
|
|
@@ -1,16 +1,23 @@
|
|
|
1
|
+
export type FileInputStoreMediaItem = {
|
|
2
|
+
id: string;
|
|
3
|
+
user_id?: string;
|
|
4
|
+
r2_key: string;
|
|
5
|
+
url: string;
|
|
6
|
+
created_at?: string;
|
|
7
|
+
original_name?: string;
|
|
8
|
+
mime_type?: string;
|
|
9
|
+
size_bytes?: number;
|
|
10
|
+
};
|
|
1
11
|
export type FileInputState = {
|
|
2
12
|
uploadModalOpen: boolean;
|
|
3
13
|
selectedFiles: File[];
|
|
4
14
|
sizeConstraint: number;
|
|
5
|
-
uploadType: Array<'image' | 'audio' | 'video' | '
|
|
15
|
+
uploadType: Array<'image' | 'audio' | 'video' | 'documents' | 'others'>;
|
|
6
16
|
activeTab: 'cloud' | 'upload';
|
|
7
|
-
activeMenu: 'Documents' | 'Music' | 'Pictures' | 'Videos';
|
|
8
|
-
disabledMenuItem: Array<'Music' | 'Documents' | 'Pictures' | 'Videos'> | null;
|
|
17
|
+
activeMenu: 'Documents' | 'Music' | 'Pictures' | 'Videos' | 'Others';
|
|
18
|
+
disabledMenuItem: Array<'Music' | 'Documents' | 'Pictures' | 'Videos' | 'Others'> | null;
|
|
9
19
|
manage: boolean;
|
|
10
|
-
submissions:
|
|
11
|
-
id: string;
|
|
12
|
-
url: string;
|
|
13
|
-
}[];
|
|
20
|
+
submissions: FileInputStoreMediaItem[];
|
|
14
21
|
submissionComplete: boolean;
|
|
15
22
|
r2_key: string;
|
|
16
23
|
serverGetUrl: string;
|
|
@@ -3,10 +3,10 @@ const defaultFileInputState = {
|
|
|
3
3
|
uploadModalOpen: false,
|
|
4
4
|
selectedFiles: [],
|
|
5
5
|
sizeConstraint: 10 * 1024 * 1024,
|
|
6
|
-
uploadType: ['image', 'audio', 'video', '
|
|
6
|
+
uploadType: ['image', 'audio', 'video', 'documents', 'others'],
|
|
7
7
|
// FilePicker
|
|
8
8
|
activeTab: 'cloud',
|
|
9
|
-
activeMenu: '
|
|
9
|
+
activeMenu: 'Documents',
|
|
10
10
|
disabledMenuItem: null,
|
|
11
11
|
manage: false,
|
|
12
12
|
submissions: [],
|