@stubber/form-fields 2.2.6 → 3.0.0
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 +63 -25
- package/dist/form-fields/Autocomplete.svelte +261 -0
- package/dist/form-fields/Autocomplete.svelte.d.ts +43 -0
- package/dist/form-fields/CollapsibleSection.svelte +127 -0
- package/dist/form-fields/CollapsibleSection.svelte.d.ts +26 -0
- package/dist/form-fields/FieldLabel.svelte +18 -3
- package/dist/form-fields/FieldLabel.svelte.d.ts +1 -0
- package/dist/form-fields/FieldMessage.svelte +20 -9
- package/dist/form-fields/Form.svelte +19 -2
- package/dist/form-fields/Form.svelte.d.ts +2 -0
- package/dist/form-fields/UploadStatus.svelte +76 -0
- package/dist/form-fields/UploadStatus.svelte.d.ts +18 -0
- package/dist/form-fields/fields/_field-builder-reorder.svelte +35 -0
- package/dist/form-fields/fields/_field-builder-reorder.svelte.d.ts +22 -0
- package/dist/form-fields/{sub → fields}/array-builder-field.svelte +104 -38
- package/dist/form-fields/fields/checkbox-autocomplete.svelte +153 -0
- package/dist/form-fields/fields/checkbox-field.svelte +69 -0
- package/dist/form-fields/{sub → fields}/contact-selector-field.svelte +9 -6
- package/dist/form-fields/{sub → fields}/currency-field.svelte +108 -70
- package/dist/form-fields/fields/data-indication-field.svelte +39 -0
- package/dist/form-fields/fields/date-field.svelte +31 -0
- package/dist/form-fields/fields/date-time-field.svelte +31 -0
- package/dist/form-fields/{sub → fields}/email-field.svelte +25 -11
- package/dist/form-fields/fields/field-builder-field.svelte +568 -0
- package/dist/form-fields/fields/file-field.svelte +284 -0
- package/dist/form-fields/{sub → fields}/file-field.svelte.d.ts +2 -0
- package/dist/form-fields/fields/grid-field.svelte +152 -0
- package/dist/form-fields/{sub → fields}/heading-field.svelte +7 -4
- package/dist/form-fields/{sub → fields}/html-field.svelte +6 -4
- package/dist/form-fields/{sub → fields}/map-field.svelte +121 -78
- package/dist/form-fields/{sub → fields}/multi-checkbox-field.svelte +35 -20
- package/dist/form-fields/fields/multistep-field.svelte +135 -0
- package/dist/form-fields/fields/note-field.svelte +35 -0
- package/dist/form-fields/{sub → fields}/number-field.svelte +29 -14
- package/dist/form-fields/fields/object-builder-field.svelte +176 -0
- package/dist/form-fields/fields/qr-code-scanner-field.svelte +203 -0
- package/dist/form-fields/fields/radio-field.svelte +82 -0
- package/dist/form-fields/{sub → fields}/radio-field.svelte.d.ts +1 -1
- package/dist/form-fields/fields/screenrecorder-field.svelte +255 -0
- package/dist/form-fields/{sub → fields}/screenshot-field.svelte +101 -40
- package/dist/form-fields/{sub → fields}/scroll-and-read-display-field.svelte +43 -27
- package/dist/form-fields/fields/section-field.svelte +73 -0
- package/dist/form-fields/fields/select-field.svelte +104 -0
- package/dist/form-fields/{sub → fields}/signature-field.svelte +78 -22
- package/dist/form-fields/fields/slider-field.svelte +53 -0
- package/dist/form-fields/{sub → fields}/smart-text-field.svelte +62 -82
- package/dist/form-fields/fields/telephone-field.svelte +108 -0
- package/dist/form-fields/{sub → fields}/telephone-field.svelte.d.ts +1 -1
- package/dist/form-fields/{sub → fields}/text-field.svelte +28 -11
- package/dist/form-fields/{sub → fields}/voicenote-field.svelte +117 -46
- package/dist/form-fields/file_creation_options.d.ts +1 -1
- package/dist/form-fields/form-field.svelte +8 -1
- package/dist/form-fields/icons/ChevronDownIcon.svelte +21 -0
- package/dist/form-fields/icons/ChevronDownIcon.svelte.d.ts +20 -0
- package/dist/form-fields/icons/QrCodeScannerIcon.svelte +25 -0
- package/dist/form-fields/icons/QrCodeScannerIcon.svelte.d.ts +19 -0
- package/dist/form-fields/icons/ScreenRecordingIcon.svelte +21 -0
- package/dist/form-fields/icons/ScreenRecordingIcon.svelte.d.ts +19 -0
- package/dist/form-fields/icons/ScreenshotIcon.svelte +22 -0
- package/dist/form-fields/icons/ScreenshotIcon.svelte.d.ts +19 -0
- package/dist/form-fields/index.d.ts +5 -1
- package/dist/form-fields/index.js +3 -1
- package/dist/form-fields/interfaces.d.ts +63 -63
- package/dist/form-fields/interfaces.js +42 -39
- package/dist/form-fields/intl-tel-input.css +1 -0
- package/dist/form-fields/styles.d.ts +17 -0
- package/dist/form-fields/styles.js +4 -0
- package/package.json +2 -4
- package/dist/form-fields/sub/checkbox-autocomplete.svelte +0 -56
- package/dist/form-fields/sub/checkbox-field.svelte +0 -42
- package/dist/form-fields/sub/data-indication-field.svelte +0 -19
- package/dist/form-fields/sub/date-field.svelte +0 -31
- package/dist/form-fields/sub/date-time-field.svelte +0 -31
- package/dist/form-fields/sub/field-builder-field.svelte +0 -657
- package/dist/form-fields/sub/file-field.svelte +0 -159
- package/dist/form-fields/sub/grid-field.svelte +0 -69
- package/dist/form-fields/sub/multistep-field.svelte +0 -70
- package/dist/form-fields/sub/note-field.svelte +0 -18
- package/dist/form-fields/sub/object-builder-field.svelte +0 -134
- package/dist/form-fields/sub/qr-code-scanner-field.svelte +0 -84
- package/dist/form-fields/sub/radio-field.svelte +0 -69
- package/dist/form-fields/sub/screenrecorder-field.svelte +0 -178
- package/dist/form-fields/sub/section-field.svelte +0 -50
- package/dist/form-fields/sub/select-field.svelte +0 -150
- package/dist/form-fields/sub/slider-field.svelte +0 -28
- package/dist/form-fields/sub/telephone-field.svelte +0 -64
- /package/dist/form-fields/{sub → fields}/array-builder-field.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/checkbox-autocomplete.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/checkbox-field.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/code-field.svelte +0 -0
- /package/dist/form-fields/{sub → fields}/code-field.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/contact-selector-field.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/currency-field.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/data-indication-field.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/date-field.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/date-time-field.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/email-field.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/field-builder-field.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/grid-field.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/heading-field.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/hidden-field.svelte +0 -0
- /package/dist/form-fields/{sub → fields}/hidden-field.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/hidden-location-field.svelte +0 -0
- /package/dist/form-fields/{sub → fields}/hidden-location-field.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/html-field.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/jsoneditor-field.svelte +0 -0
- /package/dist/form-fields/{sub → fields}/jsoneditor-field.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/map-field.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/multi-checkbox-field.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/multistep-field.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/note-field.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/number-field.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/object-builder-field.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/qr-code-scanner-field.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/screenrecorder-field.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/screenshot-field.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/scroll-and-read-display-field.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/section-field.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/select-field.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/selectresource-field.svelte +0 -0
- /package/dist/form-fields/{sub → fields}/selectresource-field.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/signature-field.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/slider-field.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/smart-text-field.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/text-field.svelte.d.ts +0 -0
- /package/dist/form-fields/{sub → fields}/voicenote-field.svelte.d.ts +0 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
<script context="module">import {} from "../interfaces";
|
|
2
|
+
</script>
|
|
3
|
+
|
|
4
|
+
<script>import { getContext, onDestroy } from "svelte";
|
|
5
|
+
import FieldLabel from "../FieldLabel.svelte";
|
|
6
|
+
import FieldMessage from "../FieldMessage.svelte";
|
|
7
|
+
import UploadStatus from "../UploadStatus.svelte";
|
|
8
|
+
import {
|
|
9
|
+
append_attachment,
|
|
10
|
+
remove_attachment,
|
|
11
|
+
uploadFiles
|
|
12
|
+
} from "../fileserver";
|
|
13
|
+
import ScreenRecordingIcon from "../icons/ScreenRecordingIcon.svelte";
|
|
14
|
+
import { STYLES_CONTEXT, classNames } from "../styles";
|
|
15
|
+
export let fieldStore;
|
|
16
|
+
const styles = getContext(STYLES_CONTEXT);
|
|
17
|
+
let fileList = [];
|
|
18
|
+
let mediaRecorder = null;
|
|
19
|
+
let captureStreams = [];
|
|
20
|
+
$: isRecording = mediaRecorder?.state === "recording";
|
|
21
|
+
$: buttonLabel = isRecording ? "Stop Recording" : "Start Recording";
|
|
22
|
+
let max_files_param = $fieldStore.params?.max_files;
|
|
23
|
+
let file_creation_options = $fieldStore.params?.file_creation_options;
|
|
24
|
+
$: max_files = isNaN(parseInt(max_files_param)) ? Infinity : parseInt(max_files_param);
|
|
25
|
+
$: limit_remaining = max_files - fileList.length;
|
|
26
|
+
function stopCaptureStreams() {
|
|
27
|
+
captureStreams.forEach((stream) => stream.getTracks().forEach((track) => track.stop()));
|
|
28
|
+
captureStreams = [];
|
|
29
|
+
}
|
|
30
|
+
onDestroy(() => {
|
|
31
|
+
if (mediaRecorder?.state === "recording") {
|
|
32
|
+
mediaRecorder.onstop = null;
|
|
33
|
+
mediaRecorder.stop();
|
|
34
|
+
}
|
|
35
|
+
stopCaptureStreams();
|
|
36
|
+
fileList.forEach((item) => item.previewUrl && URL.revokeObjectURL(item.previewUrl));
|
|
37
|
+
});
|
|
38
|
+
const toggleRecording = () => {
|
|
39
|
+
if (mediaRecorder?.state === "recording") {
|
|
40
|
+
stopRecording();
|
|
41
|
+
} else {
|
|
42
|
+
startRecording();
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
async function startRecording() {
|
|
46
|
+
if (limit_remaining <= 0) {
|
|
47
|
+
alert("You have reached the maximum number of files allowed");
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (!navigator.mediaDevices.getDisplayMedia) {
|
|
51
|
+
alert("Screen capture is not supported in this browser.");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
const stream = await navigator.mediaDevices.getDisplayMedia({
|
|
55
|
+
audio: true,
|
|
56
|
+
video: true
|
|
57
|
+
});
|
|
58
|
+
const audiostream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
59
|
+
captureStreams = [stream, audiostream];
|
|
60
|
+
const combine = new MediaStream([...stream.getVideoTracks(), ...audiostream.getAudioTracks()]);
|
|
61
|
+
const recorder = new MediaRecorder(combine, { mimeType: "video/webm;codecs=vp9" });
|
|
62
|
+
mediaRecorder = recorder;
|
|
63
|
+
const media = [];
|
|
64
|
+
recorder.ondataavailable = (e) => media.push(e.data);
|
|
65
|
+
recorder.onstop = function() {
|
|
66
|
+
let id = Math.random().toString(36).substring(7);
|
|
67
|
+
let filename = `${$fieldStore.label}_${id}.webm`;
|
|
68
|
+
let blob = new Blob(media, { type: "video/webm;codecs=vp9" });
|
|
69
|
+
let file = new File([blob], filename, { type: "video/webm;codecs=vp9" });
|
|
70
|
+
uploadFile(file, blob, filename, URL.createObjectURL(blob));
|
|
71
|
+
stopCaptureStreams();
|
|
72
|
+
mediaRecorder = null;
|
|
73
|
+
};
|
|
74
|
+
recorder.start();
|
|
75
|
+
}
|
|
76
|
+
function stopRecording() {
|
|
77
|
+
mediaRecorder?.stop();
|
|
78
|
+
}
|
|
79
|
+
async function uploadFile(file, blob, filename, previewUrl) {
|
|
80
|
+
fileList = [
|
|
81
|
+
...fileList,
|
|
82
|
+
{
|
|
83
|
+
file,
|
|
84
|
+
blob,
|
|
85
|
+
filename,
|
|
86
|
+
previewUrl,
|
|
87
|
+
is_uploaded: false,
|
|
88
|
+
is_failed: false
|
|
89
|
+
}
|
|
90
|
+
];
|
|
91
|
+
const upload_res = await uploadFiles(
|
|
92
|
+
[file],
|
|
93
|
+
$fieldStore.formDependencies,
|
|
94
|
+
file_creation_options
|
|
95
|
+
);
|
|
96
|
+
const { uploaded_files = [], failed_files = [] } = upload_res || {};
|
|
97
|
+
const newly_uploaded = uploaded_files.find((f) => f.filename === filename) || {};
|
|
98
|
+
const is_uploaded = uploaded_files.length > 0;
|
|
99
|
+
const is_failed = failed_files.length > 0;
|
|
100
|
+
fileList = fileList.map((item) => {
|
|
101
|
+
if (item.filename === filename) {
|
|
102
|
+
return {
|
|
103
|
+
...item,
|
|
104
|
+
...newly_uploaded,
|
|
105
|
+
is_uploaded,
|
|
106
|
+
is_failed
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return item;
|
|
110
|
+
});
|
|
111
|
+
if (newly_uploaded.fileuuid) {
|
|
112
|
+
append_attachment(newly_uploaded, $fieldStore.attachmentsStore);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const update_file_list = (field_value) => {
|
|
116
|
+
if (Array.isArray(field_value)) {
|
|
117
|
+
fileList = field_value.map((f) => ({
|
|
118
|
+
file: f.file,
|
|
119
|
+
blob: f.blob,
|
|
120
|
+
fileuuid: f.fileuuid,
|
|
121
|
+
contentType: f.contentType,
|
|
122
|
+
originalname: f.originalname,
|
|
123
|
+
filename: f.filename,
|
|
124
|
+
previewUrl: f.previewUrl || (f.blob ? URL.createObjectURL(f.blob) : void 0),
|
|
125
|
+
is_uploaded: f.is_uploaded || f.fileuuid ? true : false,
|
|
126
|
+
is_failed: f.is_failed || false
|
|
127
|
+
}));
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
update_file_list($fieldStore.value);
|
|
131
|
+
$: update_field_value(fileList);
|
|
132
|
+
const update_field_value = (files) => {
|
|
133
|
+
const uploaded_files = files.filter((f) => f.is_uploaded && f.fileuuid);
|
|
134
|
+
$fieldStore.value = uploaded_files.map((f) => ({
|
|
135
|
+
filename: f.filename,
|
|
136
|
+
fileuuid: f.fileuuid,
|
|
137
|
+
contentType: f.contentType,
|
|
138
|
+
originalname: f.originalname
|
|
139
|
+
}));
|
|
140
|
+
};
|
|
141
|
+
function removeFile(item) {
|
|
142
|
+
fileList = fileList.filter((f) => f !== item);
|
|
143
|
+
if (item.previewUrl) URL.revokeObjectURL(item.previewUrl);
|
|
144
|
+
remove_attachment(item.fileuuid, $fieldStore.attachmentsStore);
|
|
145
|
+
}
|
|
146
|
+
</script>
|
|
147
|
+
|
|
148
|
+
<div class="screenrecorder-field">
|
|
149
|
+
<FieldLabel {fieldStore} />
|
|
150
|
+
<div
|
|
151
|
+
class="screenrecorder-control"
|
|
152
|
+
class:screenrecorder-control-invalid={$fieldStore.validation_result?.type === "error"}
|
|
153
|
+
aria-invalid={$fieldStore.validation_result?.type === "error" ? true : undefined}
|
|
154
|
+
>
|
|
155
|
+
<div class="screenrecorder-list" aria-live="polite">
|
|
156
|
+
{#each fileList as item}
|
|
157
|
+
<div class={classNames("screenrecorder-item", $styles.uploadItem)}>
|
|
158
|
+
<UploadStatus
|
|
159
|
+
status={item.is_uploaded ? "uploaded" : item.is_failed ? "failed" : "uploading"}
|
|
160
|
+
/>
|
|
161
|
+
{#if item.previewUrl}
|
|
162
|
+
<video class="screenrecorder-preview" controls src={item.previewUrl}>
|
|
163
|
+
<track kind="captions" />
|
|
164
|
+
</video>
|
|
165
|
+
{:else}
|
|
166
|
+
<p class="screenrecorder-filename">{item?.filename}</p>
|
|
167
|
+
{/if}
|
|
168
|
+
<button
|
|
169
|
+
type="button"
|
|
170
|
+
class={classNames("screenrecorder-remove", $styles.button)}
|
|
171
|
+
data-variant="destructive"
|
|
172
|
+
data-size="icon"
|
|
173
|
+
aria-label="Remove {item.filename}"
|
|
174
|
+
on:click={() => removeFile(item)}
|
|
175
|
+
>
|
|
176
|
+
<span aria-hidden="true">×</span>
|
|
177
|
+
</button>
|
|
178
|
+
</div>
|
|
179
|
+
{/each}
|
|
180
|
+
</div>
|
|
181
|
+
<button
|
|
182
|
+
id={$fieldStore.id}
|
|
183
|
+
type="button"
|
|
184
|
+
class={classNames("screenrecorder-record", $styles.button)}
|
|
185
|
+
data-variant="primary"
|
|
186
|
+
class:screenrecorder-record-active={isRecording}
|
|
187
|
+
aria-pressed={isRecording}
|
|
188
|
+
aria-describedby="message-{$fieldStore.id}"
|
|
189
|
+
on:click={toggleRecording}
|
|
190
|
+
>
|
|
191
|
+
<span class="screenrecorder-icon" aria-hidden="true">
|
|
192
|
+
{#if isRecording}
|
|
193
|
+
■
|
|
194
|
+
{:else}
|
|
195
|
+
<ScreenRecordingIcon />
|
|
196
|
+
{/if}
|
|
197
|
+
</span>
|
|
198
|
+
{buttonLabel}
|
|
199
|
+
</button>
|
|
200
|
+
</div>
|
|
201
|
+
<FieldMessage {fieldStore} />
|
|
202
|
+
</div>
|
|
203
|
+
|
|
204
|
+
<style>
|
|
205
|
+
.screenrecorder-field,
|
|
206
|
+
.screenrecorder-control,
|
|
207
|
+
.screenrecorder-list {
|
|
208
|
+
box-sizing: border-box;
|
|
209
|
+
width: 100%;
|
|
210
|
+
min-width: 0;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.screenrecorder-list {
|
|
214
|
+
display: flex;
|
|
215
|
+
flex-direction: column;
|
|
216
|
+
gap: 0.5rem;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
.screenrecorder-control {
|
|
220
|
+
display: flex;
|
|
221
|
+
flex-direction: column;
|
|
222
|
+
gap: 0.5rem;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
:where(.screenrecorder-item) {
|
|
226
|
+
display: flex;
|
|
227
|
+
align-items: center;
|
|
228
|
+
gap: 0.5rem;
|
|
229
|
+
min-width: 0;
|
|
230
|
+
padding: var(--form-upload-item-padding, 0.625rem 0.75rem);
|
|
231
|
+
border: var(--form-upload-item-border, 1px solid #e2e8f0);
|
|
232
|
+
border-radius: var(--form-upload-item-radius, 0.5rem);
|
|
233
|
+
background: var(--form-upload-item-background, #fff);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.screenrecorder-preview,
|
|
237
|
+
.screenrecorder-filename {
|
|
238
|
+
flex: 1;
|
|
239
|
+
min-width: 0;
|
|
240
|
+
max-width: 100%;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.screenrecorder-filename {
|
|
244
|
+
margin: 0;
|
|
245
|
+
overflow-wrap: anywhere;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
:where(.screenrecorder-record) {
|
|
249
|
+
align-self: flex-start;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
:where(.screenrecorder-remove) {
|
|
253
|
+
flex: none;
|
|
254
|
+
}
|
|
255
|
+
</style>
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
<script context="module">import {} from "../interfaces";
|
|
2
2
|
</script>
|
|
3
3
|
|
|
4
|
-
<script>import {
|
|
5
|
-
import {
|
|
4
|
+
<script>import { snakeCase } from "lodash-es";
|
|
5
|
+
import { getContext } from "svelte";
|
|
6
6
|
import FieldLabel from "../FieldLabel.svelte";
|
|
7
7
|
import FieldMessage from "../FieldMessage.svelte";
|
|
8
|
+
import UploadStatus from "../UploadStatus.svelte";
|
|
8
9
|
import {
|
|
9
10
|
append_attachment,
|
|
10
11
|
remove_attachment,
|
|
11
12
|
uploadFiles
|
|
12
13
|
} from "../fileserver";
|
|
14
|
+
import ScreenshotIcon from "../icons/ScreenshotIcon.svelte";
|
|
15
|
+
import { STYLES_CONTEXT, classNames } from "../styles";
|
|
13
16
|
export let fieldStore;
|
|
17
|
+
const styles = getContext(STYLES_CONTEXT);
|
|
14
18
|
const file_name_prefix = snakeCase($fieldStore.label || "Screenshot");
|
|
15
19
|
let fileList = [];
|
|
16
20
|
let isRecording = false;
|
|
@@ -39,7 +43,7 @@ async function startRecording() {
|
|
|
39
43
|
return;
|
|
40
44
|
}
|
|
41
45
|
const stream = await navigator.mediaDevices.getDisplayMedia({
|
|
42
|
-
video:
|
|
46
|
+
video: true
|
|
43
47
|
});
|
|
44
48
|
const track = stream.getVideoTracks()[0];
|
|
45
49
|
const imageCapture = new ImageCapture(track);
|
|
@@ -128,43 +132,100 @@ function removeFile(item) {
|
|
|
128
132
|
}
|
|
129
133
|
</script>
|
|
130
134
|
|
|
131
|
-
<
|
|
132
|
-
<
|
|
133
|
-
<
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
{:
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
135
|
+
<div class="screenshot-field">
|
|
136
|
+
<FieldLabel {fieldStore} />
|
|
137
|
+
<div
|
|
138
|
+
class="screenshot-control"
|
|
139
|
+
class:screenshot-control-invalid={$fieldStore.validation_result?.type === "error"}
|
|
140
|
+
aria-invalid={$fieldStore.validation_result?.type === "error" ? true : undefined}
|
|
141
|
+
>
|
|
142
|
+
<div class="screenshot-list" aria-live="polite">
|
|
143
|
+
{#each fileList as item}
|
|
144
|
+
<div class={classNames("screenshot-item", $styles.uploadItem)}>
|
|
145
|
+
<UploadStatus
|
|
146
|
+
status={item.is_uploaded ? "uploaded" : item.is_failed ? "failed" : "uploading"}
|
|
147
|
+
/>
|
|
148
|
+
<p class="screenshot-filename">{item?.filename}</p>
|
|
149
|
+
<button
|
|
150
|
+
type="button"
|
|
151
|
+
class={classNames("screenshot-remove", $styles.button)}
|
|
152
|
+
data-variant="destructive"
|
|
153
|
+
data-size="icon"
|
|
154
|
+
aria-label="Remove {item.filename}"
|
|
155
|
+
on:click={() => removeFile(item)}
|
|
156
|
+
>
|
|
157
|
+
<span aria-hidden="true">×</span>
|
|
158
|
+
</button>
|
|
152
159
|
</div>
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
160
|
+
{/each}
|
|
161
|
+
</div>
|
|
162
|
+
<button
|
|
163
|
+
id={$fieldStore.id}
|
|
164
|
+
type="button"
|
|
165
|
+
class={classNames("screenshot-capture", $styles.button)}
|
|
166
|
+
data-variant="primary"
|
|
167
|
+
class:screenshot-capture-active={isRecording}
|
|
168
|
+
aria-pressed={isRecording}
|
|
169
|
+
aria-describedby="message-{$fieldStore.id}"
|
|
170
|
+
on:click={toggleRecording}
|
|
171
|
+
>
|
|
172
|
+
<span class="screenshot-icon" aria-hidden="true">
|
|
173
|
+
{#if isRecording}
|
|
174
|
+
×
|
|
175
|
+
{:else}
|
|
176
|
+
<ScreenshotIcon />
|
|
177
|
+
{/if}
|
|
178
|
+
</span>
|
|
179
|
+
{buttonLabel}
|
|
180
|
+
</button>
|
|
168
181
|
</div>
|
|
182
|
+
<FieldMessage {fieldStore} />
|
|
169
183
|
</div>
|
|
170
|
-
|
|
184
|
+
|
|
185
|
+
<style>
|
|
186
|
+
.screenshot-field,
|
|
187
|
+
.screenshot-control,
|
|
188
|
+
.screenshot-list {
|
|
189
|
+
box-sizing: border-box;
|
|
190
|
+
width: 100%;
|
|
191
|
+
min-width: 0;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
.screenshot-list {
|
|
195
|
+
display: flex;
|
|
196
|
+
flex-direction: column;
|
|
197
|
+
gap: 0.5rem;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.screenshot-control {
|
|
201
|
+
display: flex;
|
|
202
|
+
flex-direction: column;
|
|
203
|
+
gap: 0.5rem;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
:where(.screenshot-item) {
|
|
207
|
+
display: flex;
|
|
208
|
+
align-items: center;
|
|
209
|
+
gap: 0.5rem;
|
|
210
|
+
min-width: 0;
|
|
211
|
+
padding: var(--form-upload-item-padding, 0.625rem 0.75rem);
|
|
212
|
+
border: var(--form-upload-item-border, 1px solid #e2e8f0);
|
|
213
|
+
border-radius: var(--form-upload-item-radius, 0.5rem);
|
|
214
|
+
background: var(--form-upload-item-background, #fff);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.screenshot-filename {
|
|
218
|
+
flex: 1;
|
|
219
|
+
min-width: 0;
|
|
220
|
+
margin: 0;
|
|
221
|
+
overflow-wrap: anywhere;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
:where(.screenshot-capture) {
|
|
225
|
+
align-self: flex-start;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
:where(.screenshot-remove) {
|
|
229
|
+
flex: none;
|
|
230
|
+
}
|
|
231
|
+
</style>
|
|
@@ -17,26 +17,23 @@ export const scroll_and_read_display_field_param_spec = {
|
|
|
17
17
|
};
|
|
18
18
|
</script>
|
|
19
19
|
|
|
20
|
-
<script>import {
|
|
21
|
-
import {
|
|
22
|
-
import
|
|
20
|
+
<script>import { isEqual } from "lodash-es";
|
|
21
|
+
import { getContext, onMount } from "svelte";
|
|
22
|
+
import FieldLabel from "../FieldLabel.svelte";
|
|
23
23
|
import FieldMessage from "../FieldMessage.svelte";
|
|
24
|
+
import { STYLES_CONTEXT, classNames } from "../styles";
|
|
24
25
|
import { set_value_details } from "../utils";
|
|
25
|
-
import { onMount } from "svelte";
|
|
26
|
-
import FieldExprToggle from "../FieldExprToggle.svelte";
|
|
27
26
|
export let fieldStore;
|
|
27
|
+
const styles = getContext(STYLES_CONTEXT);
|
|
28
28
|
let params = $fieldStore.params;
|
|
29
29
|
let yes_value = params?.checkedvalue ?? true;
|
|
30
30
|
let no_value = params?.uncheckedvalue ?? false;
|
|
31
31
|
let displaytext = params?.displaytext || "";
|
|
32
|
-
$: validation_result = $fieldStore.validation_result;
|
|
33
|
-
$: type = validation_result?.type;
|
|
34
|
-
$: is_error = type === "error";
|
|
35
32
|
let scrolled_to_bottom = false;
|
|
36
33
|
$: checked = isEqual($fieldStore.value, yes_value);
|
|
37
|
-
const handle_checked_change = (
|
|
38
|
-
|
|
39
|
-
$fieldStore.value =
|
|
34
|
+
const handle_checked_change = (event) => {
|
|
35
|
+
const new_checked = event.currentTarget.checked;
|
|
36
|
+
$fieldStore.value = new_checked ? yes_value : no_value;
|
|
40
37
|
};
|
|
41
38
|
let outer;
|
|
42
39
|
const handle_scroll = (event) => {
|
|
@@ -60,33 +57,52 @@ const update_value_details = (new_value) => {
|
|
|
60
57
|
};
|
|
61
58
|
</script>
|
|
62
59
|
|
|
63
|
-
<div class="
|
|
64
|
-
<!--
|
|
60
|
+
<div class="scroll-and-read-field">
|
|
61
|
+
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
|
65
62
|
<div
|
|
66
63
|
bind:this={outer}
|
|
67
|
-
class="
|
|
64
|
+
class="scroll-and-read-content"
|
|
65
|
+
role="region"
|
|
66
|
+
aria-label="{$fieldStore.label} content"
|
|
67
|
+
tabindex="0"
|
|
68
68
|
on:scroll={handle_scroll}
|
|
69
69
|
>
|
|
70
|
-
|
|
71
|
-
{@html displaytext}
|
|
72
|
-
</div>
|
|
70
|
+
{@html displaytext}
|
|
73
71
|
</div>
|
|
74
72
|
<div
|
|
75
|
-
class="
|
|
76
|
-
|
|
77
|
-
: ''} relative mt-2 flex items-center gap-x-2 {is_error ? 'border-b border-warning-500' : ''}"
|
|
73
|
+
class="scroll-and-read-confirmation"
|
|
74
|
+
aria-invalid={$fieldStore.validation_result?.type === "error" ? true : undefined}
|
|
78
75
|
>
|
|
79
|
-
<
|
|
76
|
+
<input
|
|
77
|
+
class={classNames("scroll-and-read-input", $styles.choice)}
|
|
80
78
|
disabled={!scrolled_to_bottom}
|
|
81
|
-
id=
|
|
79
|
+
id={$fieldStore.id}
|
|
82
80
|
name={$fieldStore.id}
|
|
81
|
+
type="checkbox"
|
|
83
82
|
{checked}
|
|
84
|
-
|
|
83
|
+
aria-invalid={$fieldStore.validation_result?.type === "error" ? true : undefined}
|
|
84
|
+
aria-describedby="message-{$fieldStore.id}"
|
|
85
|
+
on:change={handle_checked_change}
|
|
85
86
|
/>
|
|
86
|
-
<
|
|
87
|
-
{$fieldStore.label}
|
|
88
|
-
</Label>
|
|
89
|
-
<FieldExprToggle {fieldStore} />
|
|
87
|
+
<FieldLabel {fieldStore} with_margin={false} />
|
|
90
88
|
</div>
|
|
91
89
|
<FieldMessage {fieldStore} />
|
|
92
90
|
</div>
|
|
91
|
+
|
|
92
|
+
<style>
|
|
93
|
+
.scroll-and-read-field {
|
|
94
|
+
min-width: 0;
|
|
95
|
+
width: 100%;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.scroll-and-read-content {
|
|
99
|
+
max-height: 20rem;
|
|
100
|
+
overflow-y: auto;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.scroll-and-read-confirmation {
|
|
104
|
+
display: flex;
|
|
105
|
+
gap: 0.5rem;
|
|
106
|
+
align-items: center;
|
|
107
|
+
}
|
|
108
|
+
</style>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
<script context="module">import { get as get_store_value } from "svelte/store";
|
|
2
|
+
</script>
|
|
3
|
+
|
|
4
|
+
<script>import { getContext } from "svelte";
|
|
5
|
+
import FieldMessage from "../FieldMessage.svelte";
|
|
6
|
+
import FormField from "../form-field.svelte";
|
|
7
|
+
import FieldExprToggle from "../FieldExprToggle.svelte";
|
|
8
|
+
import CollapsibleSection from "../CollapsibleSection.svelte";
|
|
9
|
+
import { STYLES_CONTEXT, classNames } from "../styles";
|
|
10
|
+
import { validate_field } from "../validations/validate_field";
|
|
11
|
+
export let fieldStore;
|
|
12
|
+
const styles = getContext(STYLES_CONTEXT);
|
|
13
|
+
let open = true;
|
|
14
|
+
$: validate_sub_fields($fieldStore);
|
|
15
|
+
let section_has_errors = false;
|
|
16
|
+
const validate_sub_fields = async (field) => {
|
|
17
|
+
section_has_errors = false;
|
|
18
|
+
if (field.sub_fields && field.sub_fields.length > 0) {
|
|
19
|
+
for (const sub_field of field.sub_fields || []) {
|
|
20
|
+
const { errors } = await validate_field(field.formStore, sub_field);
|
|
21
|
+
if (errors.length > 0) {
|
|
22
|
+
section_has_errors = true;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<section
|
|
30
|
+
class={classNames("section-field", $styles.group)}
|
|
31
|
+
class:section-field-invalid={section_has_errors}
|
|
32
|
+
data-invalid={section_has_errors ? true : undefined}
|
|
33
|
+
aria-labelledby="label-{$fieldStore.id}"
|
|
34
|
+
>
|
|
35
|
+
<CollapsibleSection
|
|
36
|
+
title={$fieldStore.label}
|
|
37
|
+
bind:open
|
|
38
|
+
contentId="content-{$fieldStore.id}"
|
|
39
|
+
labelledBy="label-{$fieldStore.id}"
|
|
40
|
+
>
|
|
41
|
+
<div slot="heading" class="section-heading">
|
|
42
|
+
<span id="label-{$fieldStore.id}" class={classNames("section-label", $styles.label)}
|
|
43
|
+
>{$fieldStore.label}</span
|
|
44
|
+
>
|
|
45
|
+
<FieldMessage {fieldStore} />
|
|
46
|
+
</div>
|
|
47
|
+
<div slot="actions" class="section-actions">
|
|
48
|
+
<FieldExprToggle {fieldStore} />
|
|
49
|
+
</div>
|
|
50
|
+
{#each $fieldStore.sub_fields || [] as sub_field (get_store_value(sub_field).id)}
|
|
51
|
+
<FormField fieldStore={sub_field} />
|
|
52
|
+
{/each}
|
|
53
|
+
</CollapsibleSection>
|
|
54
|
+
</section>
|
|
55
|
+
|
|
56
|
+
<style>
|
|
57
|
+
.section-field {
|
|
58
|
+
box-sizing: border-box;
|
|
59
|
+
width: 100%;
|
|
60
|
+
min-width: 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.section-heading {
|
|
64
|
+
min-width: 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.section-actions {
|
|
68
|
+
display: flex;
|
|
69
|
+
align-items: center;
|
|
70
|
+
gap: 0.25rem;
|
|
71
|
+
flex: none;
|
|
72
|
+
}
|
|
73
|
+
</style>
|