|
1 |
| -<!-- Waiting for design on variant where a file is uploaded --> |
2 | 1 | <template>
|
3 |
| - <div class="flex flex-col gap-1"> |
4 |
| - <label v-if="widget.name" class="text-sm opacity-80">{{ |
| 2 | + <div class="flex flex-col gap-1 w-full"> |
| 3 | + <label v-if="widget.name" class="text-xs opacity-80">{{ |
5 | 4 | widget.name
|
6 | 5 | }}</label>
|
7 |
| - <FileUpload |
8 |
| - v-bind="filteredProps" |
9 |
| - :disabled="readonly" |
10 |
| - @upload="handleUpload" |
11 |
| - @select="handleSelect" |
12 |
| - @remove="handleRemove" |
13 |
| - @clear="handleClear" |
14 |
| - @error="handleError" |
15 |
| - /> |
| 6 | + <div class="flex flex-col items-center gap-2 w-full"> |
| 7 | + <!-- TODO: Given file type from node definition, change text here and display differently. --> |
| 8 | + <span class="text-xs opacity-60">{{ t('g.dropYourFileOr') }}</span> |
| 9 | + <div class="w-full"> |
| 10 | + <Button |
| 11 | + :label="getButtonLabel()" |
| 12 | + size="small" |
| 13 | + class="w-full text-xs" |
| 14 | + :disabled="readonly" |
| 15 | + @click="triggerFileInput" |
| 16 | + /> |
| 17 | + <input |
| 18 | + ref="fileInputRef" |
| 19 | + type="file" |
| 20 | + class="hidden" |
| 21 | + :accept="widget.options?.accept" |
| 22 | + :multiple="widget.options?.multiple" |
| 23 | + :disabled="readonly" |
| 24 | + @change="handleFileChange" |
| 25 | + /> |
| 26 | + </div> |
| 27 | + <!-- Display selected files --> |
| 28 | + <div v-if="selectedFiles.length > 0" class="w-full text-xs"> |
| 29 | + <div |
| 30 | + v-for="(file, index) in selectedFiles" |
| 31 | + :key="index" |
| 32 | + class="flex items-center justify-between p-1" |
| 33 | + > |
| 34 | + <span class="truncate flex-1">{{ file.name }}</span> |
| 35 | + <Button |
| 36 | + v-if="!readonly" |
| 37 | + icon="pi pi-times" |
| 38 | + size="small" |
| 39 | + text |
| 40 | + severity="secondary" |
| 41 | + @click="removeFile(index)" |
| 42 | + /> |
| 43 | + </div> |
| 44 | + </div> |
| 45 | + </div> |
16 | 46 | </div>
|
17 | 47 | </template>
|
18 | 48 |
|
19 | 49 | <script setup lang="ts">
|
20 |
| -import FileUpload from 'primevue/fileupload' |
21 |
| -import { computed } from 'vue' |
| 50 | +import Button from 'primevue/button' |
| 51 | +import { computed, ref, watch } from 'vue' |
| 52 | +import { useI18n } from 'vue-i18n' |
22 | 53 |
|
| 54 | +import { useWidgetValue } from '@/composables/graph/useWidgetValue' |
23 | 55 | import type { SimplifiedWidget } from '@/types/simplifiedWidget'
|
24 |
| -import { |
25 |
| - STANDARD_EXCLUDED_PROPS, |
26 |
| - filterWidgetProps |
27 |
| -} from '@/utils/widgetPropFilter' |
28 | 56 |
|
29 |
| -// FileUpload doesn't have a traditional v-model, it handles files through events |
| 57 | +const { t } = useI18n() |
| 58 | +
|
30 | 59 | const props = defineProps<{
|
31 | 60 | widget: SimplifiedWidget<File[] | null>
|
| 61 | + modelValue: File[] | null |
32 | 62 | readonly?: boolean
|
33 | 63 | }>()
|
34 | 64 |
|
35 |
| -const filteredProps = computed(() => |
36 |
| - filterWidgetProps(props.widget.options, STANDARD_EXCLUDED_PROPS) |
37 |
| -) |
| 65 | +const emit = defineEmits<{ |
| 66 | + 'update:modelValue': [value: File[] | null] |
| 67 | +}>() |
38 | 68 |
|
39 |
| -const handleUpload = (event: any) => { |
40 |
| - if (!props.readonly && props.widget.callback) { |
41 |
| - props.widget.callback(event.files) |
42 |
| - } |
43 |
| -} |
| 69 | +// Use the composable for consistent widget value handling |
| 70 | +const { localValue, onChange } = useWidgetValue({ |
| 71 | + widget: props.widget, |
| 72 | + modelValue: props.modelValue, |
| 73 | + defaultValue: null, |
| 74 | + emit |
| 75 | +}) |
44 | 76 |
|
45 |
| -const handleSelect = (event: any) => { |
46 |
| - if (!props.readonly && props.widget.callback) { |
47 |
| - props.widget.callback(event.files) |
| 77 | +const fileInputRef = ref<HTMLInputElement | null>(null) |
| 78 | +
|
| 79 | +// Use localValue from composable |
| 80 | +const selectedFiles = computed(() => localValue.value || []) |
| 81 | +
|
| 82 | +const getButtonLabel = () => { |
| 83 | + if (selectedFiles.value.length > 0) { |
| 84 | + return `${selectedFiles.value.length} file(s) selected` |
48 | 85 | }
|
| 86 | + return props.widget.options?.chooseLabel || t('g.searchbox.browse') |
49 | 87 | }
|
50 | 88 |
|
51 |
| -const handleRemove = (event: any) => { |
52 |
| - if (!props.readonly && props.widget.callback) { |
53 |
| - props.widget.callback(event.files) |
54 |
| - } |
| 89 | +const triggerFileInput = () => { |
| 90 | + fileInputRef.value?.click() |
55 | 91 | }
|
56 | 92 |
|
57 |
| -const handleClear = () => { |
58 |
| - if (!props.readonly && props.widget.callback) { |
59 |
| - props.widget.callback([]) |
| 93 | +const handleFileChange = (event: Event) => { |
| 94 | + const target = event.target as HTMLInputElement |
| 95 | + if (!props.readonly && target.files) { |
| 96 | + // Convert FileList to array |
| 97 | + const files = Array.from(target.files) |
| 98 | +
|
| 99 | + // Use the composable's onChange handler |
| 100 | + onChange(files) |
| 101 | +
|
| 102 | + // Reset input to allow selecting same file again |
| 103 | + target.value = '' |
60 | 104 | }
|
61 | 105 | }
|
62 | 106 |
|
63 |
| -const handleError = (event: any) => { |
64 |
| - // Could be extended to handle error reporting |
65 |
| - console.warn('File upload error:', event) |
| 107 | +const removeFile = (index: number) => { |
| 108 | + const newFiles = [...selectedFiles.value] |
| 109 | + newFiles.splice(index, 1) |
| 110 | +
|
| 111 | + const filesOrNull = newFiles.length > 0 ? newFiles : null |
| 112 | +
|
| 113 | + // Use the composable's onChange handler |
| 114 | + onChange(filesOrNull) |
66 | 115 | }
|
| 116 | +
|
| 117 | +// Clear file input when value is cleared externally |
| 118 | +watch(localValue, (newValue) => { |
| 119 | + if (!newValue || newValue.length === 0) { |
| 120 | + if (fileInputRef.value) { |
| 121 | + fileInputRef.value.value = '' |
| 122 | + } |
| 123 | + } |
| 124 | +}) |
67 | 125 | </script>
|
0 commit comments