@stubber/form-fields 2.1.1 → 2.2.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/dist/form-fields/file_creation_options.d.ts +4 -0
- package/dist/form-fields/file_creation_options.js +72 -0
- package/dist/form-fields/fileserver.d.ts +2 -2
- package/dist/form-fields/fileserver.js +44 -2
- package/dist/form-fields/interfaces.d.ts +15 -0
- package/dist/form-fields/sub/field-builder-field.svelte +6 -5
- package/dist/form-fields/sub/file-field.svelte +13 -4
- package/dist/form-fields/sub/file-field.svelte.d.ts +2 -1
- package/dist/form-fields/sub/map-field.svelte +1 -1
- package/dist/form-fields/sub/screenrecorder-field.svelte +15 -19
- package/dist/form-fields/sub/screenrecorder-field.svelte.d.ts +2 -2
- package/dist/form-fields/sub/screenshot-field.svelte +13 -8
- package/dist/form-fields/sub/screenshot-field.svelte.d.ts +2 -1
- package/dist/form-fields/sub/signature-field.svelte +16 -2
- package/dist/form-fields/sub/signature-field.svelte.d.ts +7 -3
- package/dist/form-fields/sub/voicenote-field.svelte +16 -12
- package/dist/form-fields/sub/voicenote-field.svelte.d.ts +2 -1
- package/package.json +1 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
export const file_creation_options_field_spec = {
|
|
2
|
+
fieldtype: "section",
|
|
3
|
+
__order: 300,
|
|
4
|
+
fields: {
|
|
5
|
+
visibility: {
|
|
6
|
+
fieldtype: "select",
|
|
7
|
+
params: {
|
|
8
|
+
options: [
|
|
9
|
+
{
|
|
10
|
+
label: "private",
|
|
11
|
+
value: "private",
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
label: "public",
|
|
15
|
+
value: "public",
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
},
|
|
19
|
+
name: "visibility",
|
|
20
|
+
__key: "visibility",
|
|
21
|
+
__order: 0,
|
|
22
|
+
without_value_details: true,
|
|
23
|
+
},
|
|
24
|
+
folder: {
|
|
25
|
+
name: "folder",
|
|
26
|
+
__key: "folder",
|
|
27
|
+
__order: 1,
|
|
28
|
+
fieldtype: "text",
|
|
29
|
+
},
|
|
30
|
+
lifecycle: {
|
|
31
|
+
fieldtype: "section",
|
|
32
|
+
fields: {
|
|
33
|
+
archive: {
|
|
34
|
+
fieldtype: "section",
|
|
35
|
+
fields: {
|
|
36
|
+
after_creation_minutes: {
|
|
37
|
+
fieldtype: "number",
|
|
38
|
+
},
|
|
39
|
+
after_access_minutes: {
|
|
40
|
+
fieldtype: "number",
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
delete: {
|
|
45
|
+
fieldtype: "section",
|
|
46
|
+
fields: {
|
|
47
|
+
after_creation_minutes: {
|
|
48
|
+
fieldtype: "number",
|
|
49
|
+
},
|
|
50
|
+
after_access_minutes: {
|
|
51
|
+
fieldtype: "number",
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
name: "lifecycle",
|
|
57
|
+
__key: "lifecycle",
|
|
58
|
+
__order: 2,
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
export const basic_files_field_param_spec = {
|
|
63
|
+
spec: {
|
|
64
|
+
fields: {
|
|
65
|
+
max_files: {
|
|
66
|
+
fieldtype: "number",
|
|
67
|
+
__order: 0,
|
|
68
|
+
},
|
|
69
|
+
file_creation_options: file_creation_options_field_spec,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Writable } from "svelte/store";
|
|
2
|
-
import type { IFormDependencies } from "./interfaces";
|
|
3
|
-
export declare const uploadFiles: (files: File[], dependencies?: IFormDependencies) => Promise<{
|
|
2
|
+
import type { IFileCreationOptions, IFormDependencies } from "./interfaces";
|
|
3
|
+
export declare const uploadFiles: (files: File[], dependencies?: IFormDependencies, file_creation_options?: IFileCreationOptions) => Promise<{
|
|
4
4
|
uploaded_files: UploadedFile[];
|
|
5
5
|
failed_files: File[];
|
|
6
6
|
} | undefined>;
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { cloneDeep, isEmpty } from "lodash-es";
|
|
1
2
|
// export interface ExtendedFile extends File {
|
|
2
3
|
// path?: string; //for webkitdirectory, dont know svelte-file-dropzone has this apparently
|
|
3
4
|
// filename?: string; //from fileserver after upload
|
|
4
5
|
// }
|
|
5
|
-
export const uploadFiles = async (files, dependencies) => {
|
|
6
|
+
export const uploadFiles = async (files, dependencies, file_creation_options) => {
|
|
6
7
|
if (!dependencies?.file?.upload_url)
|
|
7
8
|
return;
|
|
8
9
|
const url = dependencies.file.upload_url;
|
|
@@ -12,14 +13,19 @@ export const uploadFiles = async (files, dependencies) => {
|
|
|
12
13
|
files.forEach((file) => {
|
|
13
14
|
filesForm.append(file.name, file);
|
|
14
15
|
});
|
|
16
|
+
const headers = {};
|
|
17
|
+
const merged_file_creation_options = merge_with_delete(cloneDeep(dependencies?.file_creation_options) ?? {}, file_creation_options ?? {});
|
|
18
|
+
if (!isEmpty(merged_file_creation_options)) {
|
|
19
|
+
headers["file-creation-options"] = JSON.stringify(merged_file_creation_options);
|
|
20
|
+
}
|
|
15
21
|
//send request
|
|
16
22
|
let resJSON;
|
|
17
23
|
try {
|
|
18
24
|
let res = await fetch(url, {
|
|
19
25
|
method: "POST",
|
|
20
26
|
body: filesForm,
|
|
27
|
+
headers,
|
|
21
28
|
});
|
|
22
|
-
// resJSON = await utils.rawResToJSON(res);
|
|
23
29
|
if (res.ok) {
|
|
24
30
|
resJSON = await res.json();
|
|
25
31
|
}
|
|
@@ -50,3 +56,39 @@ export const remove_attachment = (fileuuid, attachment_store) => {
|
|
|
50
56
|
return atts;
|
|
51
57
|
});
|
|
52
58
|
};
|
|
59
|
+
/**
|
|
60
|
+
* Converts a nested object into a flat object with dot notation keys.
|
|
61
|
+
*/
|
|
62
|
+
function flatten(obj, prefix = "") {
|
|
63
|
+
return Object.entries(obj).reduce((acc, [key, value]) => {
|
|
64
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
65
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
66
|
+
Object.assign(acc, flatten(value, path));
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
acc[path] = value;
|
|
70
|
+
}
|
|
71
|
+
return acc;
|
|
72
|
+
}, {});
|
|
73
|
+
}
|
|
74
|
+
function merge_with_delete(target, source) {
|
|
75
|
+
if (!source)
|
|
76
|
+
return target;
|
|
77
|
+
for (const [key, value] of Object.entries(source)) {
|
|
78
|
+
if (value === null) {
|
|
79
|
+
delete target[key];
|
|
80
|
+
}
|
|
81
|
+
else if (value &&
|
|
82
|
+
typeof value === "object" &&
|
|
83
|
+
!Array.isArray(value) &&
|
|
84
|
+
target[key] &&
|
|
85
|
+
typeof target[key] === "object" &&
|
|
86
|
+
!Array.isArray(target[key])) {
|
|
87
|
+
merge_with_delete(target[key], value);
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
target[key] = value;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return target;
|
|
94
|
+
}
|
|
@@ -128,6 +128,19 @@ export declare const fields: {
|
|
|
128
128
|
screenrecorder: typeof ScreenrecorderField;
|
|
129
129
|
code: typeof CodeField;
|
|
130
130
|
};
|
|
131
|
+
interface IPolicy {
|
|
132
|
+
after_creation_minutes?: number;
|
|
133
|
+
after_access_minutes?: number;
|
|
134
|
+
}
|
|
135
|
+
interface ILifecycle {
|
|
136
|
+
archive?: IPolicy;
|
|
137
|
+
delete?: IPolicy;
|
|
138
|
+
}
|
|
139
|
+
export interface IFileCreationOptions {
|
|
140
|
+
visibility?: "public" | "private";
|
|
141
|
+
folder?: string;
|
|
142
|
+
lifecycle?: ILifecycle;
|
|
143
|
+
}
|
|
131
144
|
export interface IFormDependencies {
|
|
132
145
|
file?: {
|
|
133
146
|
upload_url: string;
|
|
@@ -144,6 +157,7 @@ export interface IFormDependencies {
|
|
|
144
157
|
orguuid: string;
|
|
145
158
|
stubref?: string;
|
|
146
159
|
};
|
|
160
|
+
file_creation_options?: IFileCreationOptions;
|
|
147
161
|
[key: string]: any;
|
|
148
162
|
}
|
|
149
163
|
export interface IInitialForm {
|
|
@@ -205,3 +219,4 @@ export interface IValidationResult {
|
|
|
205
219
|
type: "error" | "info";
|
|
206
220
|
message: string;
|
|
207
221
|
}
|
|
222
|
+
export {};
|
|
@@ -35,10 +35,10 @@ const field_params_spec_lookup = {
|
|
|
35
35
|
objectbuilder: object_builder_field_param_spec,
|
|
36
36
|
qrcodescanner: null,
|
|
37
37
|
radio: radio_field_param_spec,
|
|
38
|
-
screenrecorder:
|
|
39
|
-
screenshot:
|
|
40
|
-
voicenote:
|
|
41
|
-
signature:
|
|
38
|
+
screenrecorder: basic_files_field_param_spec,
|
|
39
|
+
screenshot: basic_files_field_param_spec,
|
|
40
|
+
voicenote: basic_files_field_param_spec,
|
|
41
|
+
signature: signature_field_param_spec,
|
|
42
42
|
scrollandreaddisplay: scroll_and_read_display_field_param_spec,
|
|
43
43
|
section: null,
|
|
44
44
|
// todo: fields for section
|
|
@@ -80,11 +80,12 @@ import { multi_checkbox_field_param_spec } from "./multi-checkbox-field.svelte";
|
|
|
80
80
|
import { number_field_param_spec } from "./number-field.svelte";
|
|
81
81
|
import { object_builder_field_param_spec } from "./object-builder-field.svelte";
|
|
82
82
|
import { radio_field_param_spec } from "./radio-field.svelte";
|
|
83
|
-
import { max_files_param_spec } from "./screenrecorder-field.svelte";
|
|
84
83
|
import { scroll_and_read_display_field_param_spec } from "./scroll-and-read-display-field.svelte";
|
|
85
84
|
import { select_field_param_spec } from "./select-field.svelte";
|
|
86
85
|
import { select_resource_field_param_spec } from "./selectresource-field.svelte";
|
|
87
86
|
import { handle_editor_change_setter, handle_store_change } from "../../utils/json-editor-sync";
|
|
87
|
+
import { basic_files_field_param_spec } from "../file_creation_options";
|
|
88
|
+
import { signature_field_param_spec } from "./signature-field.svelte";
|
|
88
89
|
export let fieldStore;
|
|
89
90
|
let value = cloneDeep($fieldStore.value) || {
|
|
90
91
|
details: {},
|
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
<script context="module">import {
|
|
1
|
+
<script context="module">import {
|
|
2
|
+
} from "../interfaces";
|
|
2
3
|
export const file_field_param_spec = {
|
|
3
4
|
spec: {
|
|
4
5
|
fields: {
|
|
5
6
|
max_files: {
|
|
6
|
-
fieldtype: "number"
|
|
7
|
-
|
|
7
|
+
fieldtype: "number",
|
|
8
|
+
__order: 0
|
|
9
|
+
},
|
|
10
|
+
file_creation_options: file_creation_options_field_spec
|
|
8
11
|
}
|
|
9
12
|
}
|
|
10
13
|
};
|
|
@@ -16,6 +19,7 @@ import Dropzone from "svelte-file-dropzone";
|
|
|
16
19
|
import FieldLabel from "../FieldLabel.svelte";
|
|
17
20
|
import FieldMessage from "../FieldMessage.svelte";
|
|
18
21
|
import { append_attachment, remove_attachment, uploadFiles } from "../fileserver";
|
|
22
|
+
import { file_creation_options_field_spec } from "../file_creation_options";
|
|
19
23
|
export let fieldStore;
|
|
20
24
|
$: validation_result = $fieldStore.validation_result;
|
|
21
25
|
$: validation_type = validation_result?.type;
|
|
@@ -32,7 +36,11 @@ async function handle_files_select(e) {
|
|
|
32
36
|
f_id: Math.random().toString(36).substring(7)
|
|
33
37
|
}))
|
|
34
38
|
];
|
|
35
|
-
const upload_res = await uploadFiles(
|
|
39
|
+
const upload_res = await uploadFiles(
|
|
40
|
+
acceptedFiles,
|
|
41
|
+
$fieldStore.formDependencies,
|
|
42
|
+
file_creation_options
|
|
43
|
+
);
|
|
36
44
|
const { uploaded_files: newly_uploaded, failed_files: newly_failed } = upload_res || {
|
|
37
45
|
uploaded_files: [],
|
|
38
46
|
failed_files: []
|
|
@@ -80,6 +88,7 @@ $: all_files = [
|
|
|
80
88
|
];
|
|
81
89
|
let max_files = isNaN(Number($fieldStore.params?.max_files)) ? Infinity : Number($fieldStore.params?.max_files);
|
|
82
90
|
$: limit_remaining = max_files - selected_files.length - uploaded_files.length;
|
|
91
|
+
let file_creation_options = $fieldStore.params?.file_creation_options;
|
|
83
92
|
function remove_file(item) {
|
|
84
93
|
uploaded_files = uploaded_files?.filter((f) => f.f_id !== item.f_id);
|
|
85
94
|
failed_files = failed_files?.filter((f) => f.f_id !== item.f_id);
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { SvelteComponent } from "svelte";
|
|
2
2
|
import type { Writable } from "svelte/store";
|
|
3
|
-
import { type IBaseField, type IBuiltField, type IInitialForm } from "../interfaces";
|
|
3
|
+
import { type IBaseField, type IBuiltField, type IFileCreationOptions, type IInitialForm } from "../interfaces";
|
|
4
4
|
export interface IFileFieldParams {
|
|
5
5
|
max_files?: number;
|
|
6
|
+
file_creation_options?: IFileCreationOptions;
|
|
6
7
|
}
|
|
7
8
|
export interface IFileField extends IBaseField<IFileFieldParams> {
|
|
8
9
|
fieldtype: "file";
|
|
@@ -99,7 +99,7 @@ async function reverse_geocode(lat, lng) {
|
|
|
99
99
|
address.full_address = full_address;
|
|
100
100
|
}
|
|
101
101
|
if (cleaned_context) {
|
|
102
|
-
address["
|
|
102
|
+
address["details"] = cleaned_context;
|
|
103
103
|
}
|
|
104
104
|
$fieldStore.value = {
|
|
105
105
|
...$fieldStore.value,
|
|
@@ -1,17 +1,8 @@
|
|
|
1
1
|
<script context="module">import {} from "../interfaces";
|
|
2
|
-
export const max_files_param_spec = {
|
|
3
|
-
spec: {
|
|
4
|
-
fields: {
|
|
5
|
-
max_files: {
|
|
6
|
-
fieldtype: "number"
|
|
7
|
-
}
|
|
8
|
-
}
|
|
9
|
-
}
|
|
10
|
-
};
|
|
11
2
|
</script>
|
|
12
3
|
|
|
13
4
|
<script>import { Button } from "@stubber/ui/button";
|
|
14
|
-
import {
|
|
5
|
+
import { snakeCase } from "lodash-es";
|
|
15
6
|
import FieldLabel from "../FieldLabel.svelte";
|
|
16
7
|
import FieldMessage from "../FieldMessage.svelte";
|
|
17
8
|
import {
|
|
@@ -26,6 +17,7 @@ let mediaRecorder = null;
|
|
|
26
17
|
$: isRecording = mediaRecorder?.state === "recording";
|
|
27
18
|
$: buttonLabel = isRecording ? "Stop Recording" : "Start Recording";
|
|
28
19
|
let max_files_param = $fieldStore.params?.max_files;
|
|
20
|
+
let file_creation_options = $fieldStore.params?.file_creation_options;
|
|
29
21
|
$: max_files = isNaN(parseInt(max_files_param)) ? Infinity : parseInt(max_files_param);
|
|
30
22
|
$: limit_remaining = max_files - fileList.length;
|
|
31
23
|
const toggleRecording = () => {
|
|
@@ -78,7 +70,11 @@ async function uploadFile(file, blob, filename) {
|
|
|
78
70
|
is_failed: false
|
|
79
71
|
}
|
|
80
72
|
];
|
|
81
|
-
const upload_res = await uploadFiles(
|
|
73
|
+
const upload_res = await uploadFiles(
|
|
74
|
+
[file],
|
|
75
|
+
$fieldStore.formDependencies,
|
|
76
|
+
file_creation_options
|
|
77
|
+
);
|
|
82
78
|
const { uploaded_files = [], failed_files = [] } = upload_res || {};
|
|
83
79
|
const newly_uploaded = uploaded_files.find((f) => f.filename === filename) || {};
|
|
84
80
|
const is_uploaded = uploaded_files.length > 0;
|
|
@@ -130,7 +126,7 @@ function removeFile(item) {
|
|
|
130
126
|
</script>
|
|
131
127
|
|
|
132
128
|
<FieldLabel {fieldStore} />
|
|
133
|
-
<div class="flex flex-col gap-2
|
|
129
|
+
<div class="flex flex-col items-start gap-2">
|
|
134
130
|
<Button variant={isRecording ? "destructive" : "default"} on:click={toggleRecording}>
|
|
135
131
|
{#if isRecording}
|
|
136
132
|
<i class="fa-solid fa-video-slash" />
|
|
@@ -139,10 +135,10 @@ function removeFile(item) {
|
|
|
139
135
|
{/if}
|
|
140
136
|
{buttonLabel}
|
|
141
137
|
</Button>
|
|
142
|
-
<div class="flex flex-col gap-1
|
|
138
|
+
<div class="flex w-full flex-col gap-1">
|
|
143
139
|
{#each fileList as item}
|
|
144
|
-
<div class="w-full flex
|
|
145
|
-
<div class="flex
|
|
140
|
+
<div class="flex w-full flex-row items-center gap-1">
|
|
141
|
+
<div class="flex h-6 w-6 shrink-0 items-center justify-center">
|
|
146
142
|
{#if item.is_uploaded}
|
|
147
143
|
<i class="fa fa-check text-success-500" />
|
|
148
144
|
{:else if item.is_failed}
|
|
@@ -154,7 +150,7 @@ function removeFile(item) {
|
|
|
154
150
|
|
|
155
151
|
{#if item?.blob}
|
|
156
152
|
<div class="shrink p-2">
|
|
157
|
-
<div class="
|
|
153
|
+
<div class="relative max-w-[200px] overflow-hidden">
|
|
158
154
|
<!-- <audio controls src={window.URL.createObjectURL(item.blob)} /> -->
|
|
159
155
|
<video controls src={window.URL.createObjectURL(item.blob)} class="w-full">
|
|
160
156
|
<track kind="captions" />
|
|
@@ -162,15 +158,15 @@ function removeFile(item) {
|
|
|
162
158
|
</div>
|
|
163
159
|
</div>
|
|
164
160
|
{:else}
|
|
165
|
-
<div class="w-full shrink
|
|
166
|
-
<p class="text-
|
|
161
|
+
<div class="w-full shrink truncate rounded-sm border border-surface-200 py-1 pl-2">
|
|
162
|
+
<p class="text-fluid-md text-surface-800">
|
|
167
163
|
{item?.filename}
|
|
168
164
|
</p>
|
|
169
165
|
</div>
|
|
170
166
|
{/if}
|
|
171
167
|
<Button
|
|
172
168
|
variant="destructive"
|
|
173
|
-
class="h-6 w-6
|
|
169
|
+
class="h-6 w-6 shrink-0 p-0"
|
|
174
170
|
on:click={() => removeFile(item)}
|
|
175
171
|
>
|
|
176
172
|
<i class="fa-solid fa-2xs fa-x" />
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { SvelteComponent } from "svelte";
|
|
2
2
|
import type { Writable } from "svelte/store";
|
|
3
|
-
import { type IBaseField, type IBuiltField, type
|
|
3
|
+
import { type IBaseField, type IBuiltField, type IFileCreationOptions } from "../interfaces";
|
|
4
4
|
interface IScreenRecorderFieldParams {
|
|
5
5
|
max_files?: number | string;
|
|
6
|
+
file_creation_options?: IFileCreationOptions;
|
|
6
7
|
}
|
|
7
|
-
export declare const max_files_param_spec: IInitialForm;
|
|
8
8
|
export interface IScreenRecorderField extends IBaseField<IScreenRecorderFieldParams> {
|
|
9
9
|
fieldtype: "screenrecorder";
|
|
10
10
|
}
|
|
@@ -16,6 +16,7 @@ let fileList = [];
|
|
|
16
16
|
let isRecording = false;
|
|
17
17
|
$: buttonLabel = isRecording ? "Cancel" : "Take screenshot";
|
|
18
18
|
let max_files_param = $fieldStore.params?.max_files;
|
|
19
|
+
let file_creation_options = $fieldStore.params?.file_creation_options;
|
|
19
20
|
$: max_files = isNaN(parseInt(max_files_param)) ? Infinity : parseInt(max_files_param);
|
|
20
21
|
$: limit_remaining = max_files - fileList.length;
|
|
21
22
|
const toggleRecording = () => {
|
|
@@ -72,7 +73,11 @@ async function uploadFile(file, filename) {
|
|
|
72
73
|
is_failed: false
|
|
73
74
|
}
|
|
74
75
|
];
|
|
75
|
-
const upload_res = await uploadFiles(
|
|
76
|
+
const upload_res = await uploadFiles(
|
|
77
|
+
[file],
|
|
78
|
+
$fieldStore.formDependencies,
|
|
79
|
+
file_creation_options
|
|
80
|
+
);
|
|
76
81
|
const { uploaded_files = [], failed_files = [] } = upload_res || {};
|
|
77
82
|
const newly_uploaded = uploaded_files.find((f) => f.filename === filename) || {};
|
|
78
83
|
const is_uploaded = uploaded_files.length > 0;
|
|
@@ -124,7 +129,7 @@ function removeFile(item) {
|
|
|
124
129
|
</script>
|
|
125
130
|
|
|
126
131
|
<FieldLabel {fieldStore} />
|
|
127
|
-
<div class="flex flex-col gap-2
|
|
132
|
+
<div class="flex flex-col items-start gap-2">
|
|
128
133
|
<Button variant={isRecording ? "destructive" : "default"} on:click={toggleRecording}>
|
|
129
134
|
{#if isRecording}
|
|
130
135
|
<i class="fa-solid fa-camera-slash" />
|
|
@@ -133,10 +138,10 @@ function removeFile(item) {
|
|
|
133
138
|
{/if}
|
|
134
139
|
{buttonLabel}
|
|
135
140
|
</Button>
|
|
136
|
-
<div class="flex flex-col gap-1
|
|
141
|
+
<div class="flex w-full flex-col gap-1">
|
|
137
142
|
{#each fileList as item}
|
|
138
|
-
<div class="w-full flex
|
|
139
|
-
<div class="flex
|
|
143
|
+
<div class="flex w-full flex-row items-center gap-1">
|
|
144
|
+
<div class="flex h-6 w-6 shrink-0 items-center justify-center">
|
|
140
145
|
{#if item.is_uploaded}
|
|
141
146
|
<i class="fa fa-check text-success-500" />
|
|
142
147
|
{:else if item.is_failed}
|
|
@@ -146,14 +151,14 @@ function removeFile(item) {
|
|
|
146
151
|
{/if}
|
|
147
152
|
</div>
|
|
148
153
|
|
|
149
|
-
<div class="w-full shrink
|
|
150
|
-
<p class="text-
|
|
154
|
+
<div class="w-full shrink truncate rounded-sm border border-surface-200 py-1 pl-2">
|
|
155
|
+
<p class="text-fluid-md text-surface-800">
|
|
151
156
|
{item?.filename}
|
|
152
157
|
</p>
|
|
153
158
|
</div>
|
|
154
159
|
<Button
|
|
155
160
|
variant="destructive"
|
|
156
|
-
class="h-6 w-6
|
|
161
|
+
class="h-6 w-6 shrink-0 p-0"
|
|
157
162
|
on:click={() => removeFile(item)}
|
|
158
163
|
>
|
|
159
164
|
<i class="fa-solid fa-2xs fa-x" />
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { SvelteComponent } from "svelte";
|
|
2
2
|
import type { Writable } from "svelte/store";
|
|
3
|
-
import { type IBaseField, type IBuiltField } from "../interfaces";
|
|
3
|
+
import { type IBaseField, type IBuiltField, type IFileCreationOptions } from "../interfaces";
|
|
4
4
|
interface IScreenshotFieldParams {
|
|
5
5
|
max_files?: number | string;
|
|
6
|
+
file_creation_options?: IFileCreationOptions;
|
|
6
7
|
}
|
|
7
8
|
export interface IScreenshotField extends IBaseField<IScreenshotFieldParams> {
|
|
8
9
|
fieldtype: "screenshot";
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
<script context="module">import {
|
|
1
|
+
<script context="module">import {
|
|
2
|
+
} from "../interfaces";
|
|
3
|
+
export const signature_field_param_spec = {
|
|
4
|
+
spec: {
|
|
5
|
+
fields: {
|
|
6
|
+
file_creation_options: file_creation_options_field_spec
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
};
|
|
2
10
|
</script>
|
|
3
11
|
|
|
4
12
|
<script>import { Textarea } from "@stubber/ui/textarea";
|
|
@@ -9,10 +17,12 @@ import FieldMessage from "../FieldMessage.svelte";
|
|
|
9
17
|
import { onMount } from "svelte";
|
|
10
18
|
import { append_attachment, remove_attachment, uploadFiles } from "../fileserver";
|
|
11
19
|
import { debounce, snakeCase } from "lodash-es";
|
|
20
|
+
import { file_creation_options_field_spec } from "../file_creation_options";
|
|
12
21
|
export let fieldStore;
|
|
13
22
|
let canvasContainer;
|
|
14
23
|
let pad;
|
|
15
24
|
let signaturePad;
|
|
25
|
+
let file_creation_options = $fieldStore.params?.file_creation_options;
|
|
16
26
|
onMount(() => {
|
|
17
27
|
signaturePad = new SignaturePad(pad);
|
|
18
28
|
signaturePad.addEventListener("endStroke", handleStroke);
|
|
@@ -46,7 +56,11 @@ async function uploadFile() {
|
|
|
46
56
|
const fileURI = signaturePad.toDataURL();
|
|
47
57
|
const fileBlob = await (await fetch(fileURI)).blob();
|
|
48
58
|
const file = new File([fileBlob], `${snakeCase($fieldStore.label)}.png`, { type: "image/png" });
|
|
49
|
-
const upload_res = await uploadFiles(
|
|
59
|
+
const upload_res = await uploadFiles(
|
|
60
|
+
[file],
|
|
61
|
+
$fieldStore.formDependencies,
|
|
62
|
+
file_creation_options
|
|
63
|
+
);
|
|
50
64
|
const { uploaded_files = [] } = upload_res || {};
|
|
51
65
|
if (uploaded_files?.length) {
|
|
52
66
|
const prev_file = $fieldStore.value?.file;
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { SvelteComponent } from "svelte";
|
|
2
2
|
import type { Writable } from "svelte/store";
|
|
3
|
-
import { type IBaseField, type IBuiltField } from "../interfaces";
|
|
4
|
-
|
|
3
|
+
import { type IBaseField, type IBuiltField, type IFileCreationOptions, type IInitialForm } from "../interfaces";
|
|
4
|
+
interface ISignatureFieldParams {
|
|
5
|
+
file_creation_options?: IFileCreationOptions;
|
|
6
|
+
}
|
|
7
|
+
export interface ISignatureField extends IBaseField<ISignatureFieldParams> {
|
|
5
8
|
fieldtype: "signature";
|
|
6
9
|
}
|
|
10
|
+
export declare const signature_field_param_spec: IInitialForm;
|
|
7
11
|
declare const __propDef: {
|
|
8
12
|
props: {
|
|
9
|
-
fieldStore: Writable<IBuiltField
|
|
13
|
+
fieldStore: Writable<IBuiltField<ISignatureFieldParams>>;
|
|
10
14
|
};
|
|
11
15
|
events: {
|
|
12
16
|
[evt: string]: CustomEvent<any>;
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
<script context="module">import {} from "../interfaces";
|
|
2
2
|
</script>
|
|
3
3
|
|
|
4
|
-
<script>import {
|
|
5
|
-
import {
|
|
4
|
+
<script>import { Button } from "@stubber/ui/button";
|
|
5
|
+
import { snakeCase } from "lodash-es";
|
|
6
6
|
import FieldLabel from "../FieldLabel.svelte";
|
|
7
7
|
import FieldMessage from "../FieldMessage.svelte";
|
|
8
|
-
import { snakeCase } from "lodash-es";
|
|
9
8
|
import {
|
|
10
9
|
append_attachment,
|
|
11
10
|
remove_attachment,
|
|
@@ -19,6 +18,7 @@ let fileList = [];
|
|
|
19
18
|
$: isRecording = mediaRecorder?.state === "recording";
|
|
20
19
|
$: buttonLabel = isRecording ? "Stop Recording" : "Start Recording";
|
|
21
20
|
let max_files_param = $fieldStore.params?.max_files;
|
|
21
|
+
let file_creation_options = $fieldStore.params?.file_creation_options;
|
|
22
22
|
$: max_files = isNaN(parseInt(max_files_param)) ? Infinity : parseInt(max_files_param);
|
|
23
23
|
$: limit_remaining = max_files - fileList.length;
|
|
24
24
|
const toggleRecording = () => {
|
|
@@ -58,7 +58,11 @@ async function uploadFile(file, blob, filename) {
|
|
|
58
58
|
is_failed: false
|
|
59
59
|
}
|
|
60
60
|
];
|
|
61
|
-
const upload_res = await uploadFiles(
|
|
61
|
+
const upload_res = await uploadFiles(
|
|
62
|
+
[file],
|
|
63
|
+
$fieldStore.formDependencies,
|
|
64
|
+
file_creation_options
|
|
65
|
+
);
|
|
62
66
|
const { uploaded_files = [], failed_files = [] } = upload_res || {};
|
|
63
67
|
const newly_uploaded = uploaded_files.find((f) => f.filename === filename) || {};
|
|
64
68
|
const is_uploaded = uploaded_files.length > 0;
|
|
@@ -113,7 +117,7 @@ function removeFile(item) {
|
|
|
113
117
|
</script>
|
|
114
118
|
|
|
115
119
|
<FieldLabel {fieldStore} />
|
|
116
|
-
<div class="flex flex-col gap-2
|
|
120
|
+
<div class="flex flex-col items-start gap-2">
|
|
117
121
|
<Button variant={isRecording ? "destructive" : "default"} on:click={toggleRecording}>
|
|
118
122
|
{#if isRecording}
|
|
119
123
|
<i class="fa-solid fa-microphone-slash" />
|
|
@@ -122,10 +126,10 @@ function removeFile(item) {
|
|
|
122
126
|
{/if}
|
|
123
127
|
{buttonLabel}
|
|
124
128
|
</Button>
|
|
125
|
-
<div class="flex flex-col gap-1
|
|
129
|
+
<div class="flex w-full flex-col gap-1">
|
|
126
130
|
{#each fileList as item}
|
|
127
|
-
<div class="w-full flex
|
|
128
|
-
<div class="flex
|
|
131
|
+
<div class="flex w-full flex-row items-center gap-1">
|
|
132
|
+
<div class="flex h-6 w-6 shrink-0 items-center justify-center">
|
|
129
133
|
{#if item.is_uploaded}
|
|
130
134
|
<i class="fa fa-check text-success-500" />
|
|
131
135
|
{:else if item.is_failed}
|
|
@@ -135,21 +139,21 @@ function removeFile(item) {
|
|
|
135
139
|
{/if}
|
|
136
140
|
</div>
|
|
137
141
|
{#if item?.blob}
|
|
138
|
-
<div class="shrink p-2
|
|
142
|
+
<div class="w-full shrink p-2">
|
|
139
143
|
<div class="overflow-hidden">
|
|
140
144
|
<audio class="w-full" controls src={window.URL.createObjectURL(item.blob)} />
|
|
141
145
|
</div>
|
|
142
146
|
</div>
|
|
143
147
|
{:else}
|
|
144
|
-
<div class="w-full shrink
|
|
145
|
-
<p class="text-
|
|
148
|
+
<div class="w-full shrink truncate rounded-sm border border-surface-200 py-1 pl-2">
|
|
149
|
+
<p class="text-fluid-md text-surface-800">
|
|
146
150
|
{item?.filename}
|
|
147
151
|
</p>
|
|
148
152
|
</div>
|
|
149
153
|
{/if}
|
|
150
154
|
<Button
|
|
151
155
|
variant="destructive"
|
|
152
|
-
class="h-6 w-6
|
|
156
|
+
class="h-6 w-6 shrink-0 p-0"
|
|
153
157
|
on:click={() => removeFile(item)}
|
|
154
158
|
>
|
|
155
159
|
<i class="fa-solid fa-2xs fa-x" />
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { SvelteComponent } from "svelte";
|
|
2
2
|
import type { Writable } from "svelte/store";
|
|
3
|
-
import { type IBaseField, type IBuiltField } from "../interfaces";
|
|
3
|
+
import { type IBaseField, type IBuiltField, type IFileCreationOptions } from "../interfaces";
|
|
4
4
|
interface IVoicenoteFieldParams {
|
|
5
5
|
max_files?: number | string;
|
|
6
|
+
file_creation_options?: IFileCreationOptions;
|
|
6
7
|
}
|
|
7
8
|
export interface IVoicenoteField extends IBaseField<IVoicenoteFieldParams> {
|
|
8
9
|
fieldtype: "voicenote";
|