@ticatec/uniface-element 0.2.0 → 0.2.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/README.md +320 -1
- package/README_CN.md +392 -0
- package/dist/accordion/Accordion.svelte +1 -1
- package/dist/action-bar/ActionBar.svelte +7 -1
- package/dist/attachment-files/FileRender.svelte +1 -1
- package/dist/attachment-files/FileUploadBar.svelte +7 -6
- package/dist/attachment-files/FileUploadPanel.svelte +2 -2
- package/dist/base-calendar/DateContext.js +5 -4
- package/dist/base-calendar/YearPickupPanel.svelte +4 -4
- package/dist/card/Card.svelte +1 -1
- package/dist/card/Card.svelte.d.ts +1 -1
- package/dist/cascade-options-select/CascadeOptionSelect.svelte +2 -3
- package/dist/cascade-options-select/CascadeOptionSelect.svelte.d.ts +46 -1
- package/dist/cascade-options-select/CascadePanel.svelte +1 -1
- package/dist/checkbox/CheckBox.svelte +1 -1
- package/dist/color-picker/ColorPicker.svelte +9 -9
- package/dist/color-picker/ColorPicker.svelte.d.ts +0 -2
- package/dist/color-picker/ColorPickerPanel.svelte +11 -15
- package/dist/color-picker/ColorPickerPanel.svelte.d.ts +1 -1
- package/dist/common/CommonPicker.svelte +2 -2
- package/dist/common/CommonPicker.svelte.d.ts +1 -1
- package/dist/common/MouseClickHandler.d.ts +1 -1
- package/dist/common/Popover.svelte +28 -6
- package/dist/common/Popover.svelte.d.ts +9 -3
- package/dist/common/uniface-utils.js +6 -4
- package/dist/common-editor/CommonEditor.svelte +1 -1
- package/dist/common-editor/CommonEditor.svelte.d.ts +1 -1
- package/dist/common-editor/NumberInput.svelte +1 -1
- package/dist/common-editor/NumberInput.svelte.d.ts +1 -1
- package/dist/concise-data-table/ConciseListTable.svelte +7 -2
- package/dist/concise-data-table/ConciseListTable.svelte.d.ts +1 -0
- package/dist/concise-data-table/DataRow.svelte +1 -1
- package/dist/concise-data-table/TableOptions.d.ts +1 -1
- package/dist/data-table/DataTable.svelte +1 -1
- package/dist/data-table/parts/ActionsPanel.svelte +4 -3
- package/dist/data-table/parts/ActionsRow.svelte +3 -3
- package/dist/data-table/parts/ContentPanel.svelte +3 -2
- package/dist/data-table/parts/FixedColumnsPanel.svelte +0 -1
- package/dist/data-table/parts/FixedColumnsPanel.svelte.d.ts +1 -2
- package/dist/data-table/parts/FixedHeaderPanel.svelte +2 -2
- package/dist/data-table/parts/FixedRow.svelte +2 -1
- package/dist/data-table/parts/FixedRow.svelte.d.ts +1 -1
- package/dist/data-table/parts/HrefCell.svelte +23 -2
- package/dist/data-table/parts/PopupMenu.svelte +25 -2
- package/dist/data-table/parts/PopupMenu.svelte.d.ts +8 -1
- package/dist/date-picker/DatePicker.svelte +0 -1
- package/dist/date-picker/DateTimePicker.svelte +3 -3
- package/dist/date-picker/ScrollBar.svelte +4 -7
- package/dist/date-range/DateRangeEditor.svelte +1 -1
- package/dist/dialog/CommonDialog.svelte +8 -5
- package/dist/dialog/CommonDialog.svelte.d.ts +2 -1
- package/dist/dialog/Dialog.svelte +37 -12
- package/dist/dialog/Dialog.svelte.d.ts +4 -3
- package/dist/dialog/DialogBoard.svelte +11 -8
- package/dist/dialog/DialogWrapper.svelte +10 -0
- package/dist/{common-editor/DateInput.svelte.d.ts → dialog/DialogWrapper.svelte.d.ts} +6 -13
- package/dist/dialog/Dialogs.js +1 -1
- package/dist/dialog/ModalResult.d.ts +5 -0
- package/dist/dialog/ModalResult.js +5 -0
- package/dist/drawer/Drawer.svelte +1 -1
- package/dist/form-field/FormField.svelte +2 -1
- package/dist/form-field/FormField.svelte.d.ts +1 -0
- package/dist/form-panel/flex-row-form/FormContainer.svelte +1 -1
- package/dist/form-panel/grid-form/EmptyCell.svelte +8 -7
- package/dist/form-panel/grid-form/EmptyCell.svelte.d.ts +2 -2
- package/dist/form-panel/grid-form/GridField.svelte +1 -1
- package/dist/i18nRes/i18nRes.d.ts +2 -0
- package/dist/i18nRes/i18nRes.js +44 -0
- package/dist/i18nRes/index.d.ts +2 -0
- package/dist/i18nRes/index.js +2 -0
- package/dist/image-files/ImageFilesField.svelte +1 -1
- package/dist/indicator/IndicatorBoard.svelte +1 -1
- package/dist/inline-cell-editor/InvalidEditor.svelte +1 -0
- package/dist/input-options-select/InputOptionsSelect.svelte +6 -6
- package/dist/input-options-select/InputOptionsSelect.svelte.d.ts +1 -1
- package/dist/layout/flex/FlexBlock.svelte +1 -1
- package/dist/lookup-editor/LookupEditor.svelte +1 -1
- package/dist/memo-editor/MemoEditor.svelte +2 -2
- package/dist/message-box/IMessageBox.d.ts +2 -4
- package/dist/message-box/IMessageBox.js +4 -6
- package/dist/message-box/MessageBoxBoard.svelte +1 -3
- package/dist/options-multi-select/OptionsMultiSelect.svelte +1 -1
- package/dist/prompts-text-editor/PromptsTextEditor.svelte +2 -2
- package/dist/prompts-text-editor/PromptsTextEditor.svelte.d.ts +1 -1
- package/dist/property-editor/PropertyEditor.svelte +3 -3
- package/dist/text-editor/PasswordEditor.svelte +1 -1
- package/dist/transfer/Transfer.svelte +17 -12
- package/dist/transfer/Transfer.svelte.d.ts +0 -1
- package/dist/unit-number-editor/UnitNumberEditor.svelte +2 -2
- package/docs/buttons/cn/ActionBar.md +459 -0
- package/docs/buttons/cn/Button.md +228 -0
- package/docs/buttons/cn/IconButton.md +294 -0
- package/docs/buttons/cn/README.md +600 -0
- package/docs/buttons/cn/TextButton.md +163 -0
- package/docs/buttons/en/ActionBar.md +459 -0
- package/docs/buttons/en/Button.md +228 -0
- package/docs/buttons/en/IconButton.md +294 -0
- package/docs/buttons/en/README.md +600 -0
- package/docs/buttons/en/TextButton.md +163 -0
- package/docs/containers/cn/accordion.md +622 -0
- package/docs/containers/cn/box.md +445 -0
- package/docs/containers/cn/card.md +404 -0
- package/docs/containers/cn/page.md +576 -0
- package/docs/containers/cn/tabs.md +686 -0
- package/docs/containers/en/accordion.md +622 -0
- package/docs/containers/en/box.md +445 -0
- package/docs/containers/en/card.md +404 -0
- package/docs/containers/en/page.md +576 -0
- package/docs/containers/en/tabs.md +686 -0
- package/docs/data-display/cn/concise-data-table.md +731 -0
- package/docs/data-display/cn/listbox.md +786 -0
- package/docs/data-display/cn/treeview.md +863 -0
- package/docs/data-display/en/concise-data-table.md +732 -0
- package/docs/data-display/en/listbox.md +786 -0
- package/docs/data-display/en/treeview.md +863 -0
- package/docs/data-table/cn/README.md +596 -0
- package/docs/data-table/en/README.md +596 -0
- package/docs/dialog/cn/README.md +767 -0
- package/docs/dialog/en/README.md +767 -0
- package/docs/form/cn/README.md +847 -0
- package/docs/form/en/README.md +847 -0
- package/docs/form-components/cn/README.md +495 -0
- package/docs/form-components/cn/attachmentfilesfield.md +757 -0
- package/docs/form-components/cn/cascade-options-select.md +374 -0
- package/docs/form-components/cn/checkbox.md +58 -0
- package/docs/form-components/cn/date-picker.md +138 -0
- package/docs/form-components/cn/datetime-picker.md +228 -0
- package/docs/form-components/cn/group-checkbox.md +405 -0
- package/docs/form-components/cn/group-radiobox.md +525 -0
- package/docs/form-components/cn/imagefilesfield.md +651 -0
- package/docs/form-components/cn/input-options-select.md +250 -0
- package/docs/form-components/cn/lookup-editor.md +406 -0
- package/docs/form-components/cn/memoeditor.md +409 -0
- package/docs/form-components/cn/number-editor.md +261 -0
- package/docs/form-components/cn/options-multi-select.md +177 -0
- package/docs/form-components/cn/options-select.md +83 -0
- package/docs/form-components/cn/passwordeditor.md +538 -0
- package/docs/form-components/cn/prompts-text-editor.md +241 -0
- package/docs/form-components/cn/radiobutton.md +84 -0
- package/docs/form-components/cn/switch.md +178 -0
- package/docs/form-components/cn/text-editor.md +142 -0
- package/docs/form-components/cn/time-editor.md +159 -0
- package/docs/form-components/cn/unit-number-editor.md +164 -0
- package/docs/form-components/en/README.md +463 -0
- package/docs/form-components/en/attachmentfilesfield.md +757 -0
- package/docs/form-components/en/cascade-options-select.md +358 -0
- package/docs/form-components/en/checkbox.md +59 -0
- package/docs/form-components/en/date-picker.md +138 -0
- package/docs/form-components/en/datetime-picker.md +228 -0
- package/docs/form-components/en/group-checkbox.md +395 -0
- package/docs/form-components/en/group-radiobox.md +498 -0
- package/docs/form-components/en/imagefilesfield.md +651 -0
- package/docs/form-components/en/input-options-select.md +239 -0
- package/docs/form-components/en/lookup-editor.md +389 -0
- package/docs/form-components/en/memoeditor.md +409 -0
- package/docs/form-components/en/number-editor.md +249 -0
- package/docs/form-components/en/options-multi-select.md +177 -0
- package/docs/form-components/en/options-select.md +83 -0
- package/docs/form-components/en/passwordeditor.md +538 -0
- package/docs/form-components/en/prompts-text-editor.md +241 -0
- package/docs/form-components/en/radiobutton.md +84 -0
- package/docs/form-components/en/switch.md +180 -0
- package/docs/form-components/en/text-editor.md +142 -0
- package/docs/form-components/en/time-editor.md +159 -0
- package/docs/form-components/en/unit-number-editor.md +164 -0
- package/docs/form-layout/cn/criteria-field.md +652 -0
- package/docs/form-layout/cn/flex-form.md +454 -0
- package/docs/form-layout/cn/flex-row-form.md +542 -0
- package/docs/form-layout/cn/form-field.md +923 -0
- package/docs/form-layout/cn/grid-form.md +676 -0
- package/docs/form-layout/en/criteria-field.md +652 -0
- package/docs/form-layout/en/flex-form.md +454 -0
- package/docs/form-layout/en/flex-row-form.md +542 -0
- package/docs/form-layout/en/form-field.md +923 -0
- package/docs/form-layout/en/grid-form.md +676 -0
- package/docs/global/cn/README.md +534 -0
- package/docs/global/en/README.md +534 -0
- package/docs/layout/cn/classiclayout.md +460 -0
- package/docs/layout/cn/headerlayout.md +524 -0
- package/docs/layout/cn/sidebarlayout.md +263 -0
- package/docs/layout/en/classiclayout.md +951 -0
- package/docs/layout/en/headerlayout.md +524 -0
- package/docs/layout/en/sidebarlayout.md +262 -0
- package/docs/misc/cn/README.md +1217 -0
- package/docs/misc/en/README.md +1217 -0
- package/docs/navigation/cn/breadcrumb.md +291 -0
- package/docs/navigation/cn/navigator.md +322 -0
- package/docs/navigation/cn/navigatormenu.md +512 -0
- package/docs/navigation/cn/progressbar.md +199 -0
- package/docs/navigation/cn/progressstepbar.md +299 -0
- package/docs/navigation/en/breadcrumb.md +291 -0
- package/docs/navigation/en/navigator.md +322 -0
- package/docs/navigation/en/navigatormenu.md +512 -0
- package/docs/navigation/en/progressbar.md +199 -0
- package/docs/navigation/en/progressstepbar.md +299 -0
- package/docs/search-filters/cn/daterange.md +1156 -0
- package/docs/search-filters/cn/numberrange.md +688 -0
- package/docs/search-filters/cn/searchbox.md +1582 -0
- package/docs/search-filters/en/daterange.md +1156 -0
- package/docs/search-filters/en/numberrange.md +688 -0
- package/docs/search-filters/en/searchbox.md +1582 -0
- package/package.json +53 -13
- package/dist/common-editor/DateInput.svelte +0 -27
- package/dist/i18n_resources/index.d.ts +0 -3
- package/dist/i18n_resources/index.js +0 -3
- /package/dist/{i18n_resources → i18nRes}/uniface_cn_resource.d.ts +0 -0
- /package/dist/{i18n_resources → i18nRes}/uniface_cn_resource.js +0 -0
- /package/dist/{i18n_resources → i18nRes}/uniface_en_resource.d.ts +0 -0
- /package/dist/{i18n_resources → i18nRes}/uniface_en_resource.js +0 -0
|
@@ -0,0 +1,651 @@
|
|
|
1
|
+
# ImageFilesField Component
|
|
2
|
+
|
|
3
|
+
The ImageFilesField component provides a specialized image upload and management interface with thumbnail previews, image preview functionality, and support for multiple image formats.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Image-Specific Upload**: Optimized for image file handling with automatic thumbnail generation
|
|
8
|
+
- **Image Preview**: Full-size image preview in modal dialog
|
|
9
|
+
- **Thumbnail Display**: Automatic thumbnail generation and display
|
|
10
|
+
- **Multiple Image Support**: Configurable maximum number of images
|
|
11
|
+
- **Image Format Support**: Support for common image formats (JPG, PNG, GIF, WebP, etc.)
|
|
12
|
+
- **Drag & Drop**: Intuitive drag-and-drop image upload
|
|
13
|
+
- **Progress Tracking**: Real-time upload progress with cancel capability
|
|
14
|
+
- **Display Modes**: Edit and view modes for different contexts
|
|
15
|
+
- **Variant Support**: Multiple styling variants (plain, outlined, filled)
|
|
16
|
+
- **Responsive Layout**: Adapts to different screen sizes
|
|
17
|
+
|
|
18
|
+
## Import
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import ImageFilesField from '@ticatec/uniface-element/ImageFilesField';
|
|
22
|
+
import type { ImageFile, FileUpload } from '@ticatec/uniface-element/ImageFilesField';
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Basic Usage
|
|
26
|
+
|
|
27
|
+
### Simple Image Upload Field
|
|
28
|
+
|
|
29
|
+
```svelte
|
|
30
|
+
<script>
|
|
31
|
+
import ImageFilesField from '@ticatec/uniface-element/ImageFilesField';
|
|
32
|
+
import type { ImageFile, FileUpload } from '@ticatec/uniface-element/ImageFilesField';
|
|
33
|
+
|
|
34
|
+
let images: ImageFile[] = [];
|
|
35
|
+
|
|
36
|
+
const uploadImage: FileUpload = (file, progressUpdate, onUploaded, errorHandler) => {
|
|
37
|
+
// Validate that it's an image file
|
|
38
|
+
if (!file.type.startsWith('image/')) {
|
|
39
|
+
errorHandler(new Error('Please select an image file'));
|
|
40
|
+
return { cancel: async () => true };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Simulate image upload with thumbnail generation
|
|
44
|
+
let uploadedBytes = 0;
|
|
45
|
+
const totalBytes = file.size;
|
|
46
|
+
|
|
47
|
+
const uploadInterval = setInterval(() => {
|
|
48
|
+
uploadedBytes += Math.min(1024 * 100, totalBytes - uploadedBytes);
|
|
49
|
+
progressUpdate(uploadedBytes);
|
|
50
|
+
|
|
51
|
+
if (uploadedBytes >= totalBytes) {
|
|
52
|
+
clearInterval(uploadInterval);
|
|
53
|
+
|
|
54
|
+
// Create thumbnail
|
|
55
|
+
const canvas = document.createElement('canvas');
|
|
56
|
+
const ctx = canvas.getContext('2d');
|
|
57
|
+
const img = new Image();
|
|
58
|
+
|
|
59
|
+
img.onload = () => {
|
|
60
|
+
canvas.width = 150;
|
|
61
|
+
canvas.height = 150;
|
|
62
|
+
ctx?.drawImage(img, 0, 0, 150, 150);
|
|
63
|
+
|
|
64
|
+
const thumbnailUrl = canvas.toDataURL();
|
|
65
|
+
const fullUrl = URL.createObjectURL(file);
|
|
66
|
+
|
|
67
|
+
onUploaded(fullUrl, thumbnailUrl);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
img.src = URL.createObjectURL(file);
|
|
71
|
+
}
|
|
72
|
+
}, 100);
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
cancel: async () => {
|
|
76
|
+
clearInterval(uploadInterval);
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
</script>
|
|
82
|
+
|
|
83
|
+
<ImageFilesField
|
|
84
|
+
bind:files={images}
|
|
85
|
+
{uploadImage}
|
|
86
|
+
maxFiles={5}
|
|
87
|
+
/>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Avatar Upload with Single Image
|
|
91
|
+
|
|
92
|
+
```svelte
|
|
93
|
+
<script>
|
|
94
|
+
import ImageFilesField from '@ticatec/uniface-element/ImageFilesField';
|
|
95
|
+
import type { ImageFile, FileUpload } from '@ticatec/uniface-element/ImageFilesField';
|
|
96
|
+
|
|
97
|
+
let avatar: ImageFile[] = [];
|
|
98
|
+
|
|
99
|
+
const uploadAvatar: FileUpload = (file, progressUpdate, onUploaded, errorHandler) => {
|
|
100
|
+
// Validate image dimensions and size
|
|
101
|
+
const img = new Image();
|
|
102
|
+
img.onload = () => {
|
|
103
|
+
if (img.width < 100 || img.height < 100) {
|
|
104
|
+
errorHandler(new Error('Image must be at least 100x100 pixels'));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (file.size > 2 * 1024 * 1024) {
|
|
109
|
+
errorHandler(new Error('Image size must be less than 2MB'));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Create square thumbnail
|
|
114
|
+
const canvas = document.createElement('canvas');
|
|
115
|
+
const ctx = canvas.getContext('2d');
|
|
116
|
+
const size = Math.min(img.width, img.height);
|
|
117
|
+
|
|
118
|
+
canvas.width = 100;
|
|
119
|
+
canvas.height = 100;
|
|
120
|
+
|
|
121
|
+
const sx = (img.width - size) / 2;
|
|
122
|
+
const sy = (img.height - size) / 2;
|
|
123
|
+
|
|
124
|
+
ctx?.drawImage(img, sx, sy, size, size, 0, 0, 100, 100);
|
|
125
|
+
|
|
126
|
+
const thumbnailUrl = canvas.toDataURL();
|
|
127
|
+
const fullUrl = URL.createObjectURL(file);
|
|
128
|
+
|
|
129
|
+
onUploaded(fullUrl, thumbnailUrl);
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
img.src = URL.createObjectURL(file);
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
cancel: async () => true
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const confirmRemoval = async (file: ImageFile) => {
|
|
140
|
+
return confirm('Remove avatar image?');
|
|
141
|
+
};
|
|
142
|
+
</script>
|
|
143
|
+
|
|
144
|
+
<div class="avatar-upload">
|
|
145
|
+
<label>Profile Picture</label>
|
|
146
|
+
<ImageFilesField
|
|
147
|
+
bind:files={avatar}
|
|
148
|
+
variant="outlined"
|
|
149
|
+
uploadFile={uploadAvatar}
|
|
150
|
+
removeFileConfirm={confirmRemoval}
|
|
151
|
+
maxFiles={1}
|
|
152
|
+
style="margin-top: 8px;"
|
|
153
|
+
/>
|
|
154
|
+
</div>
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
## API Reference
|
|
158
|
+
|
|
159
|
+
### Props
|
|
160
|
+
|
|
161
|
+
| Property | Type | Default | Description |
|
|
162
|
+
|----------|------|---------|-------------|
|
|
163
|
+
| `files` | `Array<ImageFile>` | `[]` | Array of image files |
|
|
164
|
+
| `variant` | `'' \| 'plain' \| 'outlined' \| 'filled'` | `''` | Visual variant |
|
|
165
|
+
| `disabled` | `boolean` | `false` | Whether the field is disabled |
|
|
166
|
+
| `readonly` | `boolean` | `false` | Whether the field is read-only |
|
|
167
|
+
| `style` | `string` | `''` | Additional CSS styles |
|
|
168
|
+
| `displayMode` | `DisplayMode` | `DisplayMode.Edit` | Display mode (Edit/View) |
|
|
169
|
+
| `removeFileConfirm` | `RemoveConfirm` | `null` | Optional confirmation function for file removal |
|
|
170
|
+
| `uploadFile` | `FileUpload` | required | Function to handle file uploads |
|
|
171
|
+
| `maxFiles` | `number` | `1` | Maximum number of images allowed |
|
|
172
|
+
| `text` | `string \| null` | `null` | Display text (auto-generated from file names) |
|
|
173
|
+
|
|
174
|
+
### Types
|
|
175
|
+
|
|
176
|
+
#### ImageFile Interface
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
interface ImageFile {
|
|
180
|
+
name: string; // Image file name
|
|
181
|
+
uri: string; // Full-size image URL or path
|
|
182
|
+
thumbnail?: string; // Thumbnail URL (optional)
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
#### FileUpload Function Type
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
type FileUpload = (
|
|
190
|
+
file: File,
|
|
191
|
+
progressUpdate: ProgressUpdate,
|
|
192
|
+
onUploaded: OnUploaded,
|
|
193
|
+
errorHandler: ErrorHandler
|
|
194
|
+
) => UploadProgress;
|
|
195
|
+
|
|
196
|
+
type ProgressUpdate = (uploadBytes: number) => void;
|
|
197
|
+
type OnUploaded = (url: string, thumbnail?: string) => void;
|
|
198
|
+
type ErrorHandler = (e: Error) => void;
|
|
199
|
+
|
|
200
|
+
interface UploadProgress {
|
|
201
|
+
cancel: () => Promise<boolean>;
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
#### RemoveConfirm Function Type
|
|
206
|
+
|
|
207
|
+
```typescript
|
|
208
|
+
type RemoveConfirm = (file: ImageFile) => Promise<boolean>;
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Advanced Examples
|
|
212
|
+
|
|
213
|
+
### Photo Gallery Upload
|
|
214
|
+
|
|
215
|
+
```svelte
|
|
216
|
+
<script>
|
|
217
|
+
import ImageFilesField, { DisplayMode } from '@ticatec/uniface-element/ImageFilesField';
|
|
218
|
+
import type { ImageFile, FileUpload } from '@ticatec/uniface-element/ImageFilesField';
|
|
219
|
+
|
|
220
|
+
let galleryImages: ImageFile[] = [];
|
|
221
|
+
let isEditMode = true;
|
|
222
|
+
|
|
223
|
+
const uploadToGallery: FileUpload = (file, progressUpdate, onUploaded, errorHandler) => {
|
|
224
|
+
// Validate image file
|
|
225
|
+
if (!file.type.startsWith('image/')) {
|
|
226
|
+
errorHandler(new Error('Only image files are allowed'));
|
|
227
|
+
return { cancel: async () => true };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Check file size (max 10MB)
|
|
231
|
+
if (file.size > 10 * 1024 * 1024) {
|
|
232
|
+
errorHandler(new Error('Image size cannot exceed 10MB'));
|
|
233
|
+
return { cancel: async () => true };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const img = new Image();
|
|
237
|
+
img.onload = async () => {
|
|
238
|
+
try {
|
|
239
|
+
// Create optimized thumbnail
|
|
240
|
+
const thumbnail = await createThumbnail(img, 200, 200);
|
|
241
|
+
|
|
242
|
+
// Simulate API upload
|
|
243
|
+
const formData = new FormData();
|
|
244
|
+
formData.append('image', file);
|
|
245
|
+
formData.append('gallery', 'main');
|
|
246
|
+
|
|
247
|
+
let uploadedBytes = 0;
|
|
248
|
+
const totalBytes = file.size;
|
|
249
|
+
|
|
250
|
+
const simulateUpload = setInterval(() => {
|
|
251
|
+
uploadedBytes += Math.min(1024 * 200, totalBytes - uploadedBytes);
|
|
252
|
+
progressUpdate(uploadedBytes);
|
|
253
|
+
|
|
254
|
+
if (uploadedBytes >= totalBytes) {
|
|
255
|
+
clearInterval(simulateUpload);
|
|
256
|
+
const fullUrl = URL.createObjectURL(file);
|
|
257
|
+
onUploaded(fullUrl, thumbnail);
|
|
258
|
+
}
|
|
259
|
+
}, 100);
|
|
260
|
+
|
|
261
|
+
} catch (error) {
|
|
262
|
+
errorHandler(new Error('Failed to process image'));
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
img.onerror = () => {
|
|
267
|
+
errorHandler(new Error('Invalid image file'));
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
img.src = URL.createObjectURL(file);
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
cancel: async () => true
|
|
274
|
+
};
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
const createThumbnail = (img: HTMLImageElement, maxWidth: number, maxHeight: number): Promise<string> => {
|
|
278
|
+
return new Promise((resolve) => {
|
|
279
|
+
const canvas = document.createElement('canvas');
|
|
280
|
+
const ctx = canvas.getContext('2d');
|
|
281
|
+
|
|
282
|
+
// Calculate dimensions maintaining aspect ratio
|
|
283
|
+
let { width, height } = img;
|
|
284
|
+
|
|
285
|
+
if (width > height) {
|
|
286
|
+
if (width > maxWidth) {
|
|
287
|
+
height = (height * maxWidth) / width;
|
|
288
|
+
width = maxWidth;
|
|
289
|
+
}
|
|
290
|
+
} else {
|
|
291
|
+
if (height > maxHeight) {
|
|
292
|
+
width = (width * maxHeight) / height;
|
|
293
|
+
height = maxHeight;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
canvas.width = width;
|
|
298
|
+
canvas.height = height;
|
|
299
|
+
|
|
300
|
+
ctx?.drawImage(img, 0, 0, width, height);
|
|
301
|
+
resolve(canvas.toDataURL('image/jpeg', 0.8));
|
|
302
|
+
});
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const confirmImageRemoval = async (image: ImageFile) => {
|
|
306
|
+
const result = await showConfirmDialog({
|
|
307
|
+
title: 'Remove Image',
|
|
308
|
+
message: `Remove "${image.name}" from gallery?`,
|
|
309
|
+
confirmText: 'Remove',
|
|
310
|
+
cancelText: 'Cancel'
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
return result;
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const showConfirmDialog = (options: any) => {
|
|
317
|
+
return new Promise<boolean>((resolve) => {
|
|
318
|
+
// Custom confirmation dialog implementation
|
|
319
|
+
const confirmed = confirm(`${options.title}: ${options.message}`);
|
|
320
|
+
resolve(confirmed);
|
|
321
|
+
});
|
|
322
|
+
};
|
|
323
|
+
</script>
|
|
324
|
+
|
|
325
|
+
<div class="photo-gallery">
|
|
326
|
+
<div class="gallery-header">
|
|
327
|
+
<h3>Photo Gallery</h3>
|
|
328
|
+
<div class="gallery-actions">
|
|
329
|
+
<button on:click={() => isEditMode = !isEditMode}>
|
|
330
|
+
{isEditMode ? 'View Mode' : 'Edit Mode'}
|
|
331
|
+
</button>
|
|
332
|
+
</div>
|
|
333
|
+
</div>
|
|
334
|
+
|
|
335
|
+
<ImageFilesField
|
|
336
|
+
bind:files={galleryImages}
|
|
337
|
+
variant="outlined"
|
|
338
|
+
displayMode={isEditMode ? DisplayMode.Edit : DisplayMode.View}
|
|
339
|
+
uploadFile={uploadToGallery}
|
|
340
|
+
removeFileConfirm={confirmImageRemoval}
|
|
341
|
+
maxFiles={20}
|
|
342
|
+
style="min-height: 200px; border-radius: 8px;"
|
|
343
|
+
/>
|
|
344
|
+
|
|
345
|
+
<div class="gallery-stats">
|
|
346
|
+
<p>Images: {galleryImages.length} / 20</p>
|
|
347
|
+
<p>Formats supported: JPG, PNG, GIF, WebP</p>
|
|
348
|
+
</div>
|
|
349
|
+
</div>
|
|
350
|
+
|
|
351
|
+
<style>
|
|
352
|
+
.photo-gallery {
|
|
353
|
+
max-width: 800px;
|
|
354
|
+
margin: 20px auto;
|
|
355
|
+
padding: 20px;
|
|
356
|
+
border: 1px solid #e0e0e0;
|
|
357
|
+
border-radius: 12px;
|
|
358
|
+
background: #fafafa;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.gallery-header {
|
|
362
|
+
display: flex;
|
|
363
|
+
justify-content: space-between;
|
|
364
|
+
align-items: center;
|
|
365
|
+
margin-bottom: 20px;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.gallery-stats {
|
|
369
|
+
margin-top: 15px;
|
|
370
|
+
padding: 10px;
|
|
371
|
+
background: white;
|
|
372
|
+
border-radius: 4px;
|
|
373
|
+
font-size: 0.9em;
|
|
374
|
+
color: #666;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.gallery-stats p {
|
|
378
|
+
margin: 2px 0;
|
|
379
|
+
}
|
|
380
|
+
</style>
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Product Image Management
|
|
384
|
+
|
|
385
|
+
```svelte
|
|
386
|
+
<script>
|
|
387
|
+
import ImageFilesField from '@ticatec/uniface-element/ImageFilesField';
|
|
388
|
+
import type { ImageFile, FileUpload } from '@ticatec/uniface-element/ImageFilesField';
|
|
389
|
+
|
|
390
|
+
let productImages: ImageFile[] = [];
|
|
391
|
+
let uploadErrors: string[] = [];
|
|
392
|
+
|
|
393
|
+
const uploadProductImage: FileUpload = (file, progressUpdate, onUploaded, errorHandler) => {
|
|
394
|
+
// Clear previous errors
|
|
395
|
+
uploadErrors = [];
|
|
396
|
+
|
|
397
|
+
// Validate image requirements for product
|
|
398
|
+
if (!file.type.startsWith('image/')) {
|
|
399
|
+
const error = 'Only image files are allowed for products';
|
|
400
|
+
uploadErrors = [...uploadErrors, error];
|
|
401
|
+
errorHandler(new Error(error));
|
|
402
|
+
return { cancel: async () => true };
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Check file size
|
|
406
|
+
if (file.size > 5 * 1024 * 1024) {
|
|
407
|
+
const error = 'Product images must be smaller than 5MB';
|
|
408
|
+
uploadErrors = [...uploadErrors, error];
|
|
409
|
+
errorHandler(new Error(error));
|
|
410
|
+
return { cancel: async () => true };
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const img = new Image();
|
|
414
|
+
img.onload = async () => {
|
|
415
|
+
// Validate dimensions for product images
|
|
416
|
+
if (img.width < 500 || img.height < 500) {
|
|
417
|
+
const error = 'Product images must be at least 500x500 pixels';
|
|
418
|
+
uploadErrors = [...uploadErrors, error];
|
|
419
|
+
errorHandler(new Error(error));
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
try {
|
|
424
|
+
// Create multiple thumbnail sizes for product
|
|
425
|
+
const thumbnailSmall = await createProductThumbnail(img, 150, 150);
|
|
426
|
+
const thumbnailMedium = await createProductThumbnail(img, 300, 300);
|
|
427
|
+
|
|
428
|
+
// Simulate upload with progress
|
|
429
|
+
let uploaded = 0;
|
|
430
|
+
const total = file.size;
|
|
431
|
+
|
|
432
|
+
const uploadInterval = setInterval(() => {
|
|
433
|
+
uploaded += Math.min(1024 * 150, total - uploaded);
|
|
434
|
+
progressUpdate(uploaded);
|
|
435
|
+
|
|
436
|
+
if (uploaded >= total) {
|
|
437
|
+
clearInterval(uploadInterval);
|
|
438
|
+
|
|
439
|
+
// Return full URL and thumbnail
|
|
440
|
+
const fullUrl = URL.createObjectURL(file);
|
|
441
|
+
onUploaded(fullUrl, thumbnailSmall);
|
|
442
|
+
}
|
|
443
|
+
}, 80);
|
|
444
|
+
|
|
445
|
+
} catch (error) {
|
|
446
|
+
const errorMsg = 'Failed to process product image';
|
|
447
|
+
uploadErrors = [...uploadErrors, errorMsg];
|
|
448
|
+
errorHandler(new Error(errorMsg));
|
|
449
|
+
}
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
img.onerror = () => {
|
|
453
|
+
const error = 'Invalid or corrupted image file';
|
|
454
|
+
uploadErrors = [...uploadErrors, error];
|
|
455
|
+
errorHandler(new Error(error));
|
|
456
|
+
};
|
|
457
|
+
|
|
458
|
+
img.src = URL.createObjectURL(file);
|
|
459
|
+
|
|
460
|
+
return {
|
|
461
|
+
cancel: async () => true
|
|
462
|
+
};
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
const createProductThumbnail = (img: HTMLImageElement, width: number, height: number): Promise<string> => {
|
|
466
|
+
return new Promise((resolve) => {
|
|
467
|
+
const canvas = document.createElement('canvas');
|
|
468
|
+
const ctx = canvas.getContext('2d');
|
|
469
|
+
|
|
470
|
+
canvas.width = width;
|
|
471
|
+
canvas.height = height;
|
|
472
|
+
|
|
473
|
+
// Center and crop the image to fill the square
|
|
474
|
+
const size = Math.min(img.width, img.height);
|
|
475
|
+
const sx = (img.width - size) / 2;
|
|
476
|
+
const sy = (img.height - size) / 2;
|
|
477
|
+
|
|
478
|
+
ctx?.drawImage(img, sx, sy, size, size, 0, 0, width, height);
|
|
479
|
+
resolve(canvas.toDataURL('image/jpeg', 0.9));
|
|
480
|
+
});
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
const confirmProductImageRemoval = async (image: ImageFile) => {
|
|
484
|
+
return confirm(`Remove product image "${image.name}"? This will affect product display.`);
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
const clearErrors = () => {
|
|
488
|
+
uploadErrors = [];
|
|
489
|
+
};
|
|
490
|
+
</script>
|
|
491
|
+
|
|
492
|
+
<div class="product-image-manager">
|
|
493
|
+
<div class="manager-header">
|
|
494
|
+
<h3>Product Images</h3>
|
|
495
|
+
<div class="requirements">
|
|
496
|
+
<h4>Image Requirements:</h4>
|
|
497
|
+
<ul>
|
|
498
|
+
<li>Minimum 500×500 pixels</li>
|
|
499
|
+
<li>Maximum 5MB file size</li>
|
|
500
|
+
<li>Formats: JPG, PNG, WebP</li>
|
|
501
|
+
<li>Up to 8 images per product</li>
|
|
502
|
+
</ul>
|
|
503
|
+
</div>
|
|
504
|
+
</div>
|
|
505
|
+
|
|
506
|
+
{#if uploadErrors.length > 0}
|
|
507
|
+
<div class="error-panel">
|
|
508
|
+
<h4>Upload Errors:</h4>
|
|
509
|
+
<ul>
|
|
510
|
+
{#each uploadErrors as error}
|
|
511
|
+
<li>{error}</li>
|
|
512
|
+
{/each}
|
|
513
|
+
</ul>
|
|
514
|
+
<button on:click={clearErrors}>Clear Errors</button>
|
|
515
|
+
</div>
|
|
516
|
+
{/if}
|
|
517
|
+
|
|
518
|
+
<ImageFilesField
|
|
519
|
+
bind:files={productImages}
|
|
520
|
+
variant="outlined"
|
|
521
|
+
uploadFile={uploadProductImage}
|
|
522
|
+
removeFileConfirm={confirmProductImageRemoval}
|
|
523
|
+
maxFiles={8}
|
|
524
|
+
style="margin: 20px 0; min-height: 180px;"
|
|
525
|
+
/>
|
|
526
|
+
|
|
527
|
+
<div class="image-summary">
|
|
528
|
+
<p><strong>Current images:</strong> {productImages.length} / 8</p>
|
|
529
|
+
{#if productImages.length > 0}
|
|
530
|
+
<p><strong>Image names:</strong></p>
|
|
531
|
+
<ul class="image-list">
|
|
532
|
+
{#each productImages as image}
|
|
533
|
+
<li>{image.name}</li>
|
|
534
|
+
{/each}
|
|
535
|
+
</ul>
|
|
536
|
+
{/if}
|
|
537
|
+
</div>
|
|
538
|
+
</div>
|
|
539
|
+
|
|
540
|
+
<style>
|
|
541
|
+
.product-image-manager {
|
|
542
|
+
max-width: 700px;
|
|
543
|
+
margin: 20px auto;
|
|
544
|
+
padding: 25px;
|
|
545
|
+
border: 1px solid #ddd;
|
|
546
|
+
border-radius: 8px;
|
|
547
|
+
background: white;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
.manager-header {
|
|
551
|
+
display: flex;
|
|
552
|
+
justify-content: space-between;
|
|
553
|
+
align-items: flex-start;
|
|
554
|
+
margin-bottom: 20px;
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
.requirements {
|
|
558
|
+
background: #f0f8ff;
|
|
559
|
+
padding: 15px;
|
|
560
|
+
border-radius: 6px;
|
|
561
|
+
max-width: 250px;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
.requirements h4 {
|
|
565
|
+
margin: 0 0 10px 0;
|
|
566
|
+
color: #1976d2;
|
|
567
|
+
font-size: 0.9em;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
.requirements ul {
|
|
571
|
+
margin: 0;
|
|
572
|
+
padding-left: 20px;
|
|
573
|
+
font-size: 0.85em;
|
|
574
|
+
color: #555;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
.error-panel {
|
|
578
|
+
background: #ffebee;
|
|
579
|
+
border: 1px solid #f44336;
|
|
580
|
+
padding: 15px;
|
|
581
|
+
border-radius: 4px;
|
|
582
|
+
margin-bottom: 20px;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
.error-panel h4 {
|
|
586
|
+
margin: 0 0 10px 0;
|
|
587
|
+
color: #d32f2f;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
.error-panel ul {
|
|
591
|
+
margin: 0 0 10px 0;
|
|
592
|
+
padding-left: 20px;
|
|
593
|
+
color: #d32f2f;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
.error-panel button {
|
|
597
|
+
background: #d32f2f;
|
|
598
|
+
color: white;
|
|
599
|
+
border: none;
|
|
600
|
+
padding: 5px 10px;
|
|
601
|
+
border-radius: 3px;
|
|
602
|
+
cursor: pointer;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
.image-summary {
|
|
606
|
+
background: #f5f5f5;
|
|
607
|
+
padding: 15px;
|
|
608
|
+
border-radius: 4px;
|
|
609
|
+
font-size: 0.9em;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
.image-list {
|
|
613
|
+
margin: 5px 0 0 0;
|
|
614
|
+
padding-left: 20px;
|
|
615
|
+
color: #666;
|
|
616
|
+
}
|
|
617
|
+
</style>
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
## Best Practices
|
|
621
|
+
|
|
622
|
+
1. **Image Validation**: Always validate image dimensions, file size, and format
|
|
623
|
+
2. **Thumbnail Generation**: Create thumbnails for better performance and UX
|
|
624
|
+
3. **Progress Feedback**: Show upload progress for large image files
|
|
625
|
+
4. **Error Handling**: Provide clear error messages for validation failures
|
|
626
|
+
5. **Preview Functionality**: Allow users to preview images before and after upload
|
|
627
|
+
6. **Aspect Ratio**: Consider maintaining or enforcing specific aspect ratios
|
|
628
|
+
7. **Optimization**: Compress images when appropriate to reduce file size
|
|
629
|
+
8. **Accessibility**: Provide alt text and proper ARIA labels for images
|
|
630
|
+
|
|
631
|
+
## Styling Notes
|
|
632
|
+
|
|
633
|
+
- The component displays images as thumbnails in a grid layout
|
|
634
|
+
- Image previews open in a modal overlay
|
|
635
|
+
- Upload progress is shown with progress bars
|
|
636
|
+
- Custom styles can be applied via the `style` prop
|
|
637
|
+
|
|
638
|
+
## Accessibility Considerations
|
|
639
|
+
|
|
640
|
+
- Images include appropriate alt attributes
|
|
641
|
+
- Keyboard navigation is supported for all interactive elements
|
|
642
|
+
- Screen readers can access image information
|
|
643
|
+
- Color contrast meets accessibility standards
|
|
644
|
+
- Focus management is handled properly in modals
|
|
645
|
+
|
|
646
|
+
## Browser Support
|
|
647
|
+
|
|
648
|
+
- Modern browsers with Canvas API support for thumbnail generation
|
|
649
|
+
- File API support required for file handling
|
|
650
|
+
- Drag & drop requires HTML5 support
|
|
651
|
+
- Image preview functionality works in all modern browsers
|