@stubber/form-fields 1.4.5 → 1.5.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.
Files changed (97) hide show
  1. package/README.md +61 -295
  2. package/dist/fields/components/Signature.svelte +9 -5
  3. package/dist/fields2/FieldLabel.svelte +8 -0
  4. package/dist/fields2/FieldLabel.svelte.d.ts +20 -0
  5. package/dist/fields2/FieldMessage.svelte +16 -0
  6. package/dist/fields2/FieldMessage.svelte.d.ts +20 -0
  7. package/dist/fields2/Form.svelte +29 -0
  8. package/dist/fields2/Form.svelte.d.ts +23 -0
  9. package/dist/fields2/fileserver.d.ts +15 -0
  10. package/dist/fields2/fileserver.js +52 -0
  11. package/dist/fields2/form-field.svelte +10 -0
  12. package/dist/fields2/form-field.svelte.d.ts +20 -0
  13. package/dist/fields2/interfaces.d.ts +207 -0
  14. package/dist/fields2/interfaces.js +82 -0
  15. package/dist/fields2/sub/array-builder-field.svelte +110 -0
  16. package/dist/fields2/sub/array-builder-field.svelte.d.ts +27 -0
  17. package/dist/fields2/sub/checkbox-autocomplete.svelte +56 -0
  18. package/dist/fields2/sub/checkbox-autocomplete.svelte.d.ts +27 -0
  19. package/dist/fields2/sub/checkbox-field.svelte +41 -0
  20. package/dist/fields2/sub/checkbox-field.svelte.d.ts +28 -0
  21. package/dist/fields2/sub/code-field.svelte +146 -0
  22. package/dist/fields2/sub/code-field.svelte.d.ts +27 -0
  23. package/dist/fields2/sub/contact-selector-field.svelte +84 -0
  24. package/dist/fields2/sub/contact-selector-field.svelte.d.ts +30 -0
  25. package/dist/fields2/sub/currency-field.svelte +197 -0
  26. package/dist/fields2/sub/currency-field.svelte.d.ts +29 -0
  27. package/dist/fields2/sub/data-indication-field.svelte +19 -0
  28. package/dist/fields2/sub/data-indication-field.svelte.d.ts +23 -0
  29. package/dist/fields2/sub/date-field.svelte +31 -0
  30. package/dist/fields2/sub/date-field.svelte.d.ts +23 -0
  31. package/dist/fields2/sub/date-time-field.svelte +31 -0
  32. package/dist/fields2/sub/date-time-field.svelte.d.ts +23 -0
  33. package/dist/fields2/sub/email-field.svelte +40 -0
  34. package/dist/fields2/sub/email-field.svelte.d.ts +23 -0
  35. package/dist/fields2/sub/field-builder-field.svelte +525 -0
  36. package/dist/fields2/sub/field-builder-field.svelte.d.ts +23 -0
  37. package/dist/fields2/sub/file-field.svelte +150 -0
  38. package/dist/fields2/sub/file-field.svelte.d.ts +27 -0
  39. package/dist/fields2/sub/grid-field.svelte +54 -0
  40. package/dist/fields2/sub/grid-field.svelte.d.ts +30 -0
  41. package/dist/fields2/sub/heading-field.svelte +28 -0
  42. package/dist/fields2/sub/heading-field.svelte.d.ts +28 -0
  43. package/dist/fields2/sub/hidden-field.svelte +142 -0
  44. package/dist/fields2/sub/hidden-field.svelte.d.ts +37 -0
  45. package/dist/fields2/sub/hidden-location-field.svelte +23 -0
  46. package/dist/fields2/sub/hidden-location-field.svelte.d.ts +23 -0
  47. package/dist/fields2/sub/html-field.svelte +22 -0
  48. package/dist/fields2/sub/html-field.svelte.d.ts +27 -0
  49. package/dist/fields2/sub/json-editor-bound.svelte +17 -0
  50. package/dist/fields2/sub/json-editor-bound.svelte.d.ts +65 -0
  51. package/dist/fields2/sub/jsoneditor-field.svelte +23 -0
  52. package/dist/fields2/sub/jsoneditor-field.svelte.d.ts +27 -0
  53. package/dist/fields2/sub/map-field.svelte +144 -0
  54. package/dist/fields2/sub/map-field.svelte.d.ts +28 -0
  55. package/dist/fields2/sub/multi-checkbox-field.svelte +83 -0
  56. package/dist/fields2/sub/multi-checkbox-field.svelte.d.ts +34 -0
  57. package/dist/fields2/sub/multistep-field.svelte +70 -0
  58. package/dist/fields2/sub/multistep-field.svelte.d.ts +24 -0
  59. package/dist/fields2/sub/note-field.svelte +18 -0
  60. package/dist/fields2/sub/note-field.svelte.d.ts +23 -0
  61. package/dist/fields2/sub/number-field.svelte +77 -0
  62. package/dist/fields2/sub/number-field.svelte.d.ts +29 -0
  63. package/dist/fields2/sub/object-builder-field.svelte +123 -0
  64. package/dist/fields2/sub/object-builder-field.svelte.d.ts +28 -0
  65. package/dist/fields2/sub/qr-code-scanner-field.svelte +86 -0
  66. package/dist/fields2/sub/qr-code-scanner-field.svelte.d.ts +23 -0
  67. package/dist/fields2/sub/radio-field.svelte +69 -0
  68. package/dist/fields2/sub/radio-field.svelte.d.ts +30 -0
  69. package/dist/fields2/sub/screenrecorder-field.svelte +182 -0
  70. package/dist/fields2/sub/screenrecorder-field.svelte.d.ts +27 -0
  71. package/dist/fields2/sub/screenshot-field.svelte +165 -0
  72. package/dist/fields2/sub/screenshot-field.svelte.d.ts +26 -0
  73. package/dist/fields2/sub/scroll-and-read-display-field.svelte +92 -0
  74. package/dist/fields2/sub/scroll-and-read-display-field.svelte.d.ts +29 -0
  75. package/dist/fields2/sub/section-field.svelte +27 -0
  76. package/dist/fields2/sub/section-field.svelte.d.ts +26 -0
  77. package/dist/fields2/sub/select-field.svelte +138 -0
  78. package/dist/fields2/sub/select-field.svelte.d.ts +34 -0
  79. package/dist/fields2/sub/selectresource-field.svelte +69 -0
  80. package/dist/fields2/sub/selectresource-field.svelte.d.ts +29 -0
  81. package/dist/fields2/sub/signature-field.svelte +84 -0
  82. package/dist/fields2/sub/signature-field.svelte.d.ts +23 -0
  83. package/dist/fields2/sub/slider-field.svelte +28 -0
  84. package/dist/fields2/sub/slider-field.svelte.d.ts +28 -0
  85. package/dist/fields2/sub/smart-text-field.svelte +245 -0
  86. package/dist/fields2/sub/smart-text-field.svelte.d.ts +36 -0
  87. package/dist/fields2/sub/telephone-field.svelte +68 -0
  88. package/dist/fields2/sub/telephone-field.svelte.d.ts +26 -0
  89. package/dist/fields2/sub/text-field.svelte +46 -0
  90. package/dist/fields2/sub/text-field.svelte.d.ts +27 -0
  91. package/dist/fields2/sub/voicenote-field.svelte +161 -0
  92. package/dist/fields2/sub/voicenote-field.svelte.d.ts +26 -0
  93. package/dist/fields2/utils.d.ts +9 -0
  94. package/dist/fields2/utils.js +116 -0
  95. package/dist/fields2/validations/validate_field.d.ts +11 -0
  96. package/dist/fields2/validations/validate_field.js +121 -0
  97. package/package.json +6 -9
@@ -0,0 +1,123 @@
1
+ <script context="module">import {} from "svelte/store";
2
+ import {} from "../interfaces";
3
+ export const object_builder_field_param_spec = {
4
+ spec: {
5
+ fields: {
6
+ value_field_spec: {
7
+ fieldtype: "fieldbuilder",
8
+ help: "Field specification for the object values",
9
+ params: {}
10
+ }
11
+ }
12
+ }
13
+ };
14
+ </script>
15
+
16
+ <script>import * as Collapsible from "@stubber/ui/collapsible";
17
+ import { Button } from "@stubber/ui/button";
18
+ import { Input } from "@stubber/ui/input";
19
+ import { Separator } from "@stubber/ui/separator";
20
+ import { cloneDeep } from "lodash-es";
21
+ import FieldLabel from "../FieldLabel.svelte";
22
+ import FieldMessage from "../FieldMessage.svelte";
23
+ import FormField from "../form-field.svelte";
24
+ import { build_field } from "../utils";
25
+ export let fieldStore;
26
+ let key_field_spec = $fieldStore.params?.key_field_spec;
27
+ let value_field_spec = $fieldStore.params?.value_field_spec;
28
+ const update_field_value = (new_value_inner_value) => {
29
+ console.log("updated value", new_value_inner_value);
30
+ const new_value_object = {};
31
+ new_value_inner_value.forEach(({ key, value }) => {
32
+ new_value_object[key] = value;
33
+ });
34
+ };
35
+ const handle_key_input = (old_key, e) => {
36
+ const target = e.target;
37
+ const new_key = make_key_unique(target.value);
38
+ if (new_key === old_key) return;
39
+ const prev_value = $fieldStore.value || {};
40
+ const new_value = {};
41
+ for (const [key, value] of Object.entries(prev_value)) {
42
+ if (key === old_key) {
43
+ new_value[new_key] = value;
44
+ } else {
45
+ new_value[key] = value;
46
+ }
47
+ }
48
+ $fieldStore.value = new_value;
49
+ };
50
+ const add_value = () => {
51
+ const unique_key = make_key_unique("");
52
+ const field_value = cloneDeep($fieldStore.value || {});
53
+ field_value[unique_key] = null;
54
+ $fieldStore.value = field_value;
55
+ };
56
+ const remove_value = (key) => {
57
+ const field_value = cloneDeep($fieldStore.value || {});
58
+ delete field_value[key];
59
+ $fieldStore.value = field_value;
60
+ };
61
+ const make_key_unique = (key) => {
62
+ let unique_key = key;
63
+ let counter = 1;
64
+ const keys = Object.keys($fieldStore.value || {});
65
+ while (keys.includes(unique_key)) {
66
+ unique_key = `${key}_${counter}`;
67
+ counter++;
68
+ }
69
+ return unique_key;
70
+ };
71
+ </script>
72
+
73
+ <Collapsible.Root open={true} class="w-full">
74
+ <Collapsible.Trigger asChild let:builder>
75
+ <Button builders={[builder]} variant="ghost" class="w-full justify-between pl-0">
76
+ <FieldLabel {fieldStore} />
77
+ <FieldMessage {fieldStore} />
78
+
79
+ <i class="fa fa-sort" />
80
+ </Button>
81
+ </Collapsible.Trigger>
82
+ <Collapsible.Content class="flex flex-col gap-y-1 pl-2">
83
+ {#each Object.keys($fieldStore.value || {}) as key}
84
+ <Collapsible.Root open={true}>
85
+ <div class="flex flex-row items-center gap-2">
86
+ <Collapsible.Trigger let:builder class="">
87
+ {#if builder["data-state"] === "open"}
88
+ <i class="fa fa-chevron-down" />
89
+ {:else}
90
+ <i class="fa fa-chevron-right w-4" />
91
+ {/if}
92
+ </Collapsible.Trigger>
93
+ <div class="flex w-full items-center gap-2">
94
+ <Input
95
+ class="w-full"
96
+ placeholder="Key"
97
+ value={key}
98
+ on:input={(e) => handle_key_input(key, e)}
99
+ />
100
+ <Button variant="destructive" class="h-6 w-6 p-2" on:click={() => remove_value(key)}>
101
+ <i class="fa fa-trash" />
102
+ </Button>
103
+ </div>
104
+ </div>
105
+ <Collapsible.Content class="pl-6 mt-2">
106
+ {@const built_value_field = build_field(
107
+ $fieldStore.formStore,
108
+ $fieldStore.attachmentsStore,
109
+ $fieldStore.formDependencies,
110
+ key,
111
+ value_field_spec || { fieldtype: "text", title: "Value", hide_label: true },
112
+ $fieldStore.data_path
113
+ )}
114
+ <FormField fieldStore={built_value_field} />
115
+ </Collapsible.Content>
116
+ </Collapsible.Root>
117
+ <!-- <Separator class="my-2" /> -->
118
+ {/each}
119
+ <Button size="icon" variant="outline" on:click={add_value}>
120
+ <i class="fa fa-plus" />
121
+ </Button>
122
+ </Collapsible.Content>
123
+ </Collapsible.Root>
@@ -0,0 +1,28 @@
1
+ import { SvelteComponent } from "svelte";
2
+ import { type Writable } from "svelte/store";
3
+ import { type IBaseField, type IBuiltField, type IField, type IInitialForm } from "../interfaces";
4
+ interface IObjectBuilderFieldParams {
5
+ key_field_spec?: IField;
6
+ value_field_spec?: IField;
7
+ }
8
+ export declare const object_builder_field_param_spec: IInitialForm;
9
+ export interface IObjectBuilderField extends IBaseField<IObjectBuilderFieldParams> {
10
+ fieldtype: "objectbuilder";
11
+ }
12
+ declare const __propDef: {
13
+ props: {
14
+ fieldStore: Writable<IBuiltField<IObjectBuilderFieldParams>>;
15
+ };
16
+ events: {
17
+ [evt: string]: CustomEvent<any>;
18
+ };
19
+ slots: {};
20
+ exports?: {} | undefined;
21
+ bindings?: string | undefined;
22
+ };
23
+ export type ObjectBuilderFieldProps = typeof __propDef.props;
24
+ export type ObjectBuilderFieldEvents = typeof __propDef.events;
25
+ export type ObjectBuilderFieldSlots = typeof __propDef.slots;
26
+ export default class ObjectBuilderField extends SvelteComponent<ObjectBuilderFieldProps, ObjectBuilderFieldEvents, ObjectBuilderFieldSlots> {
27
+ }
28
+ export {};
@@ -0,0 +1,86 @@
1
+ <script context="module">import {} from "../interfaces";
2
+ </script>
3
+
4
+ <script>import { Textarea } from "@stubber/ui/textarea";
5
+ import { Input } from "@stubber/ui/input";
6
+ import { Button } from "@stubber/ui/button";
7
+ import * as Dialog from "@stubber/ui/dialog";
8
+ import FieldLabel from "../FieldLabel.svelte";
9
+ import FieldMessage from "../FieldMessage.svelte";
10
+ import { Html5Qrcode } from "html5-qrcode";
11
+ export let fieldStore;
12
+ let qr_code_lib;
13
+ let is_cameras_available = false;
14
+ let scan_success = false;
15
+ let scanned_text = "";
16
+ const open_scanner_modal = (is_open) => {
17
+ if (is_open) {
18
+ setup_scanner();
19
+ } else {
20
+ qr_code_lib?.stop();
21
+ }
22
+ };
23
+ const setup_scanner = async () => {
24
+ let cameras = await Html5Qrcode.getCameras();
25
+ if (!cameras) {
26
+ is_cameras_available = false;
27
+ return;
28
+ }
29
+ is_cameras_available = true;
30
+ let cameraId = cameras[0].id;
31
+ qr_code_lib = new Html5Qrcode("reader");
32
+ qr_code_lib.start(cameraId, { fps: 2 }, on_scan_success, on_scan_failure);
33
+ };
34
+ function on_scan_success(decodedText, decodedResult) {
35
+ scanned_text = decodedText;
36
+ scan_success = true;
37
+ }
38
+ function on_scan_failure(errorString) {
39
+ }
40
+ let open = false;
41
+ function confirm_scan() {
42
+ $fieldStore.value = scanned_text;
43
+ open = false;
44
+ }
45
+ </script>
46
+
47
+ <FieldLabel {fieldStore} />
48
+ <Dialog.Root bind:open onOpenChange={open_scanner_modal}>
49
+ <Dialog.Trigger asChild let:builder>
50
+ <Button builders={[builder]} class="flex">
51
+ <i class="fa-solid fa-qrcode" />
52
+ Scan Code
53
+ </Button>
54
+ </Dialog.Trigger>
55
+ <Dialog.Content>
56
+ <Dialog.Header>
57
+ <Dialog.Title>Scan Code</Dialog.Title>
58
+ <div class="flex flex-col w-full gap-2">
59
+ <div id="reader" class="w-[250px] sm:w-[310px] flex flex-col items-center" />
60
+ {#if is_cameras_available}
61
+ {#if scan_success}
62
+ <div class="flex items-center space-x-2">
63
+ <i class="fas fa-circle text-success-400 fa-2xs" />
64
+ <p class="text-label text-surface-800">{scanned_text}</p>
65
+ </div>
66
+ {:else}
67
+ <div class="flex items-center space-x-2">
68
+ <i class="fas fa-circle text-surface-200 fa-2xs" />
69
+ <p class="text-label text-surface-800">Searching for code ...</p>
70
+ </div>
71
+ {/if}
72
+ <div class="flex">
73
+ <div class="flex ml-auto">
74
+ <Button disabled={!scan_success} on:click={confirm_scan}>Confirm</Button>
75
+ </div>
76
+ </div>
77
+ {:else}
78
+ <div class="w-[250px] sm:w-[310px] aspect-square flex items-center justify-center">
79
+ <p class="text-surface-800 text-label">No camera found</p>
80
+ </div>
81
+ {/if}
82
+ </div>
83
+ </Dialog.Header>
84
+ </Dialog.Content>
85
+ </Dialog.Root>
86
+ <FieldMessage {fieldStore} />
@@ -0,0 +1,23 @@
1
+ import { SvelteComponent } from "svelte";
2
+ import type { Writable } from "svelte/store";
3
+ import { type IBaseField, type IBuiltField } from "../interfaces";
4
+ export interface IQRCodeScannerField extends IBaseField<{}> {
5
+ fieldtype: "qrcodescanner";
6
+ }
7
+ declare const __propDef: {
8
+ props: {
9
+ fieldStore: Writable<IBuiltField>;
10
+ };
11
+ events: {
12
+ [evt: string]: CustomEvent<any>;
13
+ };
14
+ slots: {};
15
+ exports?: {} | undefined;
16
+ bindings?: string | undefined;
17
+ };
18
+ export type QrCodeScannerFieldProps = typeof __propDef.props;
19
+ export type QrCodeScannerFieldEvents = typeof __propDef.events;
20
+ export type QrCodeScannerFieldSlots = typeof __propDef.slots;
21
+ export default class QrCodeScannerField extends SvelteComponent<QrCodeScannerFieldProps, QrCodeScannerFieldEvents, QrCodeScannerFieldSlots> {
22
+ }
23
+ export {};
@@ -0,0 +1,69 @@
1
+ <script context="module">import { get } from "svelte/store";
2
+ import {} from "../interfaces";
3
+ export const radio_field_param_spec = {
4
+ spec: {
5
+ fields: {
6
+ options: {
7
+ fieldtype: "arraybuilder",
8
+ params: {
9
+ new_entry_field: {
10
+ fieldtype: "section",
11
+ fields: {
12
+ label: {
13
+ fieldtype: "text"
14
+ },
15
+ value: {
16
+ fieldtype: "jsoneditor"
17
+ }
18
+ }
19
+ }
20
+ }
21
+ }
22
+ }
23
+ }
24
+ };
25
+ </script>
26
+
27
+ <script>import { Label } from "@stubber/ui/label";
28
+ import * as RadioGroup from "@stubber/ui/radio-group";
29
+ import { isEqual } from "lodash-es";
30
+ import FieldLabel from "../FieldLabel.svelte";
31
+ import FieldMessage from "../FieldMessage.svelte";
32
+ import { set_value_details } from "../utils";
33
+ export let fieldStore;
34
+ let options = $fieldStore.params?.options || [];
35
+ let mapped_options = options.map((option, index) => {
36
+ const key = `${option.label}${index}`;
37
+ return {
38
+ label: option.label || index,
39
+ value: option.value || option.label || index,
40
+ key
41
+ };
42
+ });
43
+ $: selected_option = mapped_options.find((o) => isEqual(o.value, $fieldStore.value));
44
+ $: update_value_details(selected_option);
45
+ const update_value_details = (newly_set_option) => {
46
+ set_value_details($fieldStore, "label", newly_set_option?.label);
47
+ };
48
+ const handle_radio_change = (new_key) => {
49
+ const selected_option2 = mapped_options.find((o) => o.key === new_key);
50
+ if (selected_option2) {
51
+ $fieldStore.value = selected_option2.value;
52
+ }
53
+ };
54
+ </script>
55
+
56
+ <FieldLabel {fieldStore} />
57
+ <RadioGroup.Root
58
+ value={selected_option?.key}
59
+ onValueChange={handle_radio_change}
60
+ class="border rounded-md px-2 py-1"
61
+ >
62
+ {#each mapped_options as option}
63
+ <div class="flex items-center space-x-2">
64
+ <RadioGroup.Item value={option.key} id={option.key} />
65
+ <Label for={option.key}>{option.label}</Label>
66
+ </div>
67
+ {/each}
68
+ </RadioGroup.Root>
69
+ <FieldMessage {fieldStore} />
@@ -0,0 +1,30 @@
1
+ import { SvelteComponent } from "svelte";
2
+ import { type Writable } from "svelte/store";
3
+ import { type IBaseField, type IBuiltField, type IInitialForm } from "../interfaces";
4
+ export interface IRadioFieldParams {
5
+ options: {
6
+ label?: string;
7
+ value?: any;
8
+ }[];
9
+ }
10
+ export declare const radio_field_param_spec: IInitialForm;
11
+ export interface IRadioField extends IBaseField<IRadioFieldParams> {
12
+ fieldtype: "radio";
13
+ }
14
+ declare const __propDef: {
15
+ props: {
16
+ fieldStore: Writable<IBuiltField<IRadioFieldParams>>;
17
+ };
18
+ events: {
19
+ [evt: string]: CustomEvent<any>;
20
+ };
21
+ slots: {};
22
+ exports?: {} | undefined;
23
+ bindings?: string | undefined;
24
+ };
25
+ export type RadioFieldProps = typeof __propDef.props;
26
+ export type RadioFieldEvents = typeof __propDef.events;
27
+ export type RadioFieldSlots = typeof __propDef.slots;
28
+ export default class RadioField extends SvelteComponent<RadioFieldProps, RadioFieldEvents, RadioFieldSlots> {
29
+ }
30
+ export {};
@@ -0,0 +1,182 @@
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
+ </script>
12
+
13
+ <script>import { Button } from "@stubber/ui/button";
14
+ import { max, snakeCase } from "lodash-es";
15
+ import FieldLabel from "../FieldLabel.svelte";
16
+ import FieldMessage from "../FieldMessage.svelte";
17
+ import {
18
+ append_attachment,
19
+ remove_attachment,
20
+ uploadFiles
21
+ } from "../fileserver";
22
+ export let fieldStore;
23
+ const file_name_prefix = snakeCase($fieldStore.label || "Screenshot");
24
+ let fileList = [];
25
+ let mediaRecorder = null;
26
+ $: isRecording = mediaRecorder?.state === "recording";
27
+ $: buttonLabel = isRecording ? "Stop Recording" : "Start Recording";
28
+ let max_files_param = $fieldStore.params?.max_files;
29
+ $: max_files = isNaN(parseInt(max_files_param)) ? Infinity : parseInt(max_files_param);
30
+ $: limit_remaining = max_files - fileList.length;
31
+ const toggleRecording = () => {
32
+ if (mediaRecorder?.state === "recording") {
33
+ stopRecording();
34
+ } else {
35
+ startRecording();
36
+ }
37
+ };
38
+ async function startRecording() {
39
+ if (limit_remaining <= 0) {
40
+ alert("You have reached the maximum number of files allowed");
41
+ return;
42
+ }
43
+ if (!navigator.mediaDevices.getDisplayMedia) {
44
+ alert("Screen capture is not supported in this browser.");
45
+ return;
46
+ }
47
+ const stream = await navigator.mediaDevices.getDisplayMedia({
48
+ audio: true,
49
+ // @ts-ignore
50
+ video: { mediaSource: "screen" }
51
+ });
52
+ const audiostream = await navigator.mediaDevices.getUserMedia({ audio: true });
53
+ const combine = new MediaStream([...stream.getVideoTracks(), ...audiostream.getAudioTracks()]);
54
+ mediaRecorder = new MediaRecorder(combine, { mimeType: "video/webm;codecs=vp9" });
55
+ const media = [];
56
+ mediaRecorder.ondataavailable = (e) => media.push(e.data);
57
+ mediaRecorder.onstop = function() {
58
+ let id = Math.random().toString(36).substring(7);
59
+ let filename = `${$fieldStore.label}_${id}.webm`;
60
+ let blob = new Blob(media, { type: "video/webm;codecs=vp9" });
61
+ let file = new File([blob], filename, { type: "video/webm;codecs=vp9" });
62
+ uploadFile(file, blob, filename);
63
+ mediaRecorder = null;
64
+ };
65
+ mediaRecorder.start();
66
+ }
67
+ function stopRecording() {
68
+ mediaRecorder?.stop();
69
+ }
70
+ async function uploadFile(file, blob, filename) {
71
+ fileList = [
72
+ ...fileList,
73
+ {
74
+ file,
75
+ blob,
76
+ filename,
77
+ is_uploaded: false,
78
+ is_failed: false
79
+ }
80
+ ];
81
+ const upload_res = await uploadFiles([file], $fieldStore.formDependencies);
82
+ const { uploaded_files = [], failed_files = [] } = upload_res || {};
83
+ const newly_uploaded = uploaded_files.find((f) => f.filename === filename) || {};
84
+ const is_uploaded = uploaded_files.length > 0;
85
+ const is_failed = failed_files.length > 0;
86
+ fileList = fileList.map((item) => {
87
+ if (item.filename === filename) {
88
+ return {
89
+ ...item,
90
+ ...newly_uploaded,
91
+ is_uploaded,
92
+ is_failed
93
+ };
94
+ }
95
+ return item;
96
+ });
97
+ if (newly_uploaded.fileuuid) {
98
+ append_attachment(newly_uploaded, $fieldStore.attachmentsStore);
99
+ }
100
+ }
101
+ const update_file_list = (field_value) => {
102
+ if (Array.isArray(field_value)) {
103
+ fileList = field_value.map((f) => ({
104
+ file: f.file,
105
+ blob: f.blob,
106
+ fileuuid: f.fileuuid,
107
+ contentType: f.contentType,
108
+ originalname: f.originalname,
109
+ filename: f.filename,
110
+ is_uploaded: f.is_uploaded || f.fileuuid ? true : false,
111
+ is_failed: f.is_failed || false
112
+ }));
113
+ }
114
+ };
115
+ update_file_list($fieldStore.value);
116
+ $: update_field_value(fileList);
117
+ const update_field_value = (files) => {
118
+ const uploaded_files = files.filter((f) => f.is_uploaded && f.fileuuid);
119
+ $fieldStore.value = uploaded_files.map((f) => ({
120
+ filename: f.filename,
121
+ fileuuid: f.fileuuid,
122
+ contentType: f.contentType,
123
+ originalname: f.originalname
124
+ }));
125
+ };
126
+ function removeFile(item) {
127
+ fileList = fileList.filter((f) => f !== item);
128
+ remove_attachment(item.fileuuid, $fieldStore.attachmentsStore);
129
+ }
130
+ </script>
131
+
132
+ <FieldLabel {fieldStore} />
133
+ <div class="flex flex-col gap-2 items-start">
134
+ <Button variant={isRecording ? "destructive" : "default"} on:click={toggleRecording}>
135
+ {#if isRecording}
136
+ <i class="fa-solid fa-video-slash" />
137
+ {:else}
138
+ <i class="fa-solid fa-video" />
139
+ {/if}
140
+ {buttonLabel}
141
+ </Button>
142
+ <div class="flex flex-col gap-1 w-full">
143
+ {#each fileList as item}
144
+ <div class="w-full flex flex-row items-center gap-1">
145
+ <div class="flex items-center justify-center w-6 h-6 shrink-0">
146
+ {#if item.is_uploaded}
147
+ <i class="fa fa-check text-success-500" />
148
+ {:else if item.is_failed}
149
+ <i class="fa-regular fa-triangle-exclamation text-danger-400" />
150
+ {:else}
151
+ <i class="fa fa-pulse fa-spinner text-surface-500" />
152
+ {/if}
153
+ </div>
154
+
155
+ {#if item?.blob}
156
+ <div class="shrink p-2">
157
+ <div class="overflow-hidden relative max-w-[200px]">
158
+ <!-- <audio controls src={window.URL.createObjectURL(item.blob)} /> -->
159
+ <video controls src={window.URL.createObjectURL(item.blob)} class="w-full">
160
+ <track kind="captions" />
161
+ </video>
162
+ </div>
163
+ </div>
164
+ {:else}
165
+ <div class="w-full shrink py-1 pl-2 truncate border border-surface-200 rounded-sm">
166
+ <p class="text-surface-800 text-fluid-md">
167
+ {item?.filename}
168
+ </p>
169
+ </div>
170
+ {/if}
171
+ <Button
172
+ variant="destructive"
173
+ class="h-6 w-6 p-0 shrink-0"
174
+ on:click={() => removeFile(item)}
175
+ >
176
+ <i class="fa-solid fa-2xs fa-x" />
177
+ </Button>
178
+ </div>
179
+ {/each}
180
+ </div>
181
+ </div>
182
+ <FieldMessage {fieldStore} />
@@ -0,0 +1,27 @@
1
+ import { SvelteComponent } from "svelte";
2
+ import type { Writable } from "svelte/store";
3
+ import { type IBaseField, type IBuiltField, type IInitialForm } from "../interfaces";
4
+ interface IScreenRecorderFieldParams {
5
+ max_files?: number | string;
6
+ }
7
+ export declare const max_files_param_spec: IInitialForm;
8
+ export interface IScreenRecorderField extends IBaseField<IScreenRecorderFieldParams> {
9
+ fieldtype: "screenrecorder";
10
+ }
11
+ declare const __propDef: {
12
+ props: {
13
+ fieldStore: Writable<IBuiltField<IScreenRecorderFieldParams>>;
14
+ };
15
+ events: {
16
+ [evt: string]: CustomEvent<any>;
17
+ };
18
+ slots: {};
19
+ exports?: {} | undefined;
20
+ bindings?: string | undefined;
21
+ };
22
+ export type ScreenrecorderFieldProps = typeof __propDef.props;
23
+ export type ScreenrecorderFieldEvents = typeof __propDef.events;
24
+ export type ScreenrecorderFieldSlots = typeof __propDef.slots;
25
+ export default class ScreenrecorderField extends SvelteComponent<ScreenrecorderFieldProps, ScreenrecorderFieldEvents, ScreenrecorderFieldSlots> {
26
+ }
27
+ export {};