@stubber/form-fields 2.1.2 → 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.
@@ -0,0 +1,4 @@
1
+ import type { IInitialForm } from "./interfaces";
2
+ import type { ISectionField } from "./sub/section-field.svelte";
3
+ export declare const file_creation_options_field_spec: ISectionField;
4
+ export declare const basic_files_field_param_spec: IInitialForm;
@@ -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: max_files_param_spec,
39
- screenshot: max_files_param_spec,
40
- voicenote: max_files_param_spec,
41
- signature: null,
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 {} from "../interfaces";
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(acceptedFiles, $fieldStore.formDependencies);
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";
@@ -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 { max, snakeCase } from "lodash-es";
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([file], $fieldStore.formDependencies);
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 items-start">
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 w-full">
138
+ <div class="flex w-full flex-col gap-1">
143
139
  {#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">
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="overflow-hidden relative max-w-[200px]">
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 py-1 pl-2 truncate border border-surface-200 rounded-sm">
166
- <p class="text-surface-800 text-fluid-md">
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 p-0 shrink-0"
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 IInitialForm } from "../interfaces";
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([file], $fieldStore.formDependencies);
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 items-start">
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 w-full">
141
+ <div class="flex w-full flex-col gap-1">
137
142
  {#each fileList as item}
138
- <div class="w-full flex flex-row items-center gap-1">
139
- <div class="flex items-center justify-center w-6 h-6 shrink-0">
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 py-1 pl-2 truncate border border-surface-200 rounded-sm">
150
- <p class="text-surface-800 text-fluid-md">
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 p-0 shrink-0"
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 {} from "../interfaces";
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([file], $fieldStore.formDependencies);
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
- export interface ISignatureField extends IBaseField<{}> {
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 { Textarea } from "@stubber/ui/textarea";
5
- import { Button } from "@stubber/ui/button";
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([file], $fieldStore.formDependencies);
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 items-start">
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 w-full">
129
+ <div class="flex w-full flex-col gap-1">
126
130
  {#each fileList as item}
127
- <div class="w-full flex flex-row items-center gap-1">
128
- <div class="flex items-center justify-center w-6 h-6 shrink-0">
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 w-full">
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 py-1 pl-2 truncate border border-surface-200 rounded-sm">
145
- <p class="text-surface-800 text-fluid-md">
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 p-0 shrink-0"
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";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stubber/form-fields",
3
- "version": "2.1.2",
3
+ "version": "2.2.0",
4
4
  "description": "An automatic form builder based on field specifications",
5
5
  "keywords": [
6
6
  "components",