@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.
- package/README.md +61 -295
- package/dist/fields/components/Signature.svelte +9 -5
- package/dist/fields2/FieldLabel.svelte +8 -0
- package/dist/fields2/FieldLabel.svelte.d.ts +20 -0
- package/dist/fields2/FieldMessage.svelte +16 -0
- package/dist/fields2/FieldMessage.svelte.d.ts +20 -0
- package/dist/fields2/Form.svelte +29 -0
- package/dist/fields2/Form.svelte.d.ts +23 -0
- package/dist/fields2/fileserver.d.ts +15 -0
- package/dist/fields2/fileserver.js +52 -0
- package/dist/fields2/form-field.svelte +10 -0
- package/dist/fields2/form-field.svelte.d.ts +20 -0
- package/dist/fields2/interfaces.d.ts +207 -0
- package/dist/fields2/interfaces.js +82 -0
- package/dist/fields2/sub/array-builder-field.svelte +110 -0
- package/dist/fields2/sub/array-builder-field.svelte.d.ts +27 -0
- package/dist/fields2/sub/checkbox-autocomplete.svelte +56 -0
- package/dist/fields2/sub/checkbox-autocomplete.svelte.d.ts +27 -0
- package/dist/fields2/sub/checkbox-field.svelte +41 -0
- package/dist/fields2/sub/checkbox-field.svelte.d.ts +28 -0
- package/dist/fields2/sub/code-field.svelte +146 -0
- package/dist/fields2/sub/code-field.svelte.d.ts +27 -0
- package/dist/fields2/sub/contact-selector-field.svelte +84 -0
- package/dist/fields2/sub/contact-selector-field.svelte.d.ts +30 -0
- package/dist/fields2/sub/currency-field.svelte +197 -0
- package/dist/fields2/sub/currency-field.svelte.d.ts +29 -0
- package/dist/fields2/sub/data-indication-field.svelte +19 -0
- package/dist/fields2/sub/data-indication-field.svelte.d.ts +23 -0
- package/dist/fields2/sub/date-field.svelte +31 -0
- package/dist/fields2/sub/date-field.svelte.d.ts +23 -0
- package/dist/fields2/sub/date-time-field.svelte +31 -0
- package/dist/fields2/sub/date-time-field.svelte.d.ts +23 -0
- package/dist/fields2/sub/email-field.svelte +40 -0
- package/dist/fields2/sub/email-field.svelte.d.ts +23 -0
- package/dist/fields2/sub/field-builder-field.svelte +525 -0
- package/dist/fields2/sub/field-builder-field.svelte.d.ts +23 -0
- package/dist/fields2/sub/file-field.svelte +150 -0
- package/dist/fields2/sub/file-field.svelte.d.ts +27 -0
- package/dist/fields2/sub/grid-field.svelte +54 -0
- package/dist/fields2/sub/grid-field.svelte.d.ts +30 -0
- package/dist/fields2/sub/heading-field.svelte +28 -0
- package/dist/fields2/sub/heading-field.svelte.d.ts +28 -0
- package/dist/fields2/sub/hidden-field.svelte +142 -0
- package/dist/fields2/sub/hidden-field.svelte.d.ts +37 -0
- package/dist/fields2/sub/hidden-location-field.svelte +23 -0
- package/dist/fields2/sub/hidden-location-field.svelte.d.ts +23 -0
- package/dist/fields2/sub/html-field.svelte +22 -0
- package/dist/fields2/sub/html-field.svelte.d.ts +27 -0
- package/dist/fields2/sub/json-editor-bound.svelte +17 -0
- package/dist/fields2/sub/json-editor-bound.svelte.d.ts +65 -0
- package/dist/fields2/sub/jsoneditor-field.svelte +23 -0
- package/dist/fields2/sub/jsoneditor-field.svelte.d.ts +27 -0
- package/dist/fields2/sub/map-field.svelte +144 -0
- package/dist/fields2/sub/map-field.svelte.d.ts +28 -0
- package/dist/fields2/sub/multi-checkbox-field.svelte +83 -0
- package/dist/fields2/sub/multi-checkbox-field.svelte.d.ts +34 -0
- package/dist/fields2/sub/multistep-field.svelte +70 -0
- package/dist/fields2/sub/multistep-field.svelte.d.ts +24 -0
- package/dist/fields2/sub/note-field.svelte +18 -0
- package/dist/fields2/sub/note-field.svelte.d.ts +23 -0
- package/dist/fields2/sub/number-field.svelte +77 -0
- package/dist/fields2/sub/number-field.svelte.d.ts +29 -0
- package/dist/fields2/sub/object-builder-field.svelte +123 -0
- package/dist/fields2/sub/object-builder-field.svelte.d.ts +28 -0
- package/dist/fields2/sub/qr-code-scanner-field.svelte +86 -0
- package/dist/fields2/sub/qr-code-scanner-field.svelte.d.ts +23 -0
- package/dist/fields2/sub/radio-field.svelte +69 -0
- package/dist/fields2/sub/radio-field.svelte.d.ts +30 -0
- package/dist/fields2/sub/screenrecorder-field.svelte +182 -0
- package/dist/fields2/sub/screenrecorder-field.svelte.d.ts +27 -0
- package/dist/fields2/sub/screenshot-field.svelte +165 -0
- package/dist/fields2/sub/screenshot-field.svelte.d.ts +26 -0
- package/dist/fields2/sub/scroll-and-read-display-field.svelte +92 -0
- package/dist/fields2/sub/scroll-and-read-display-field.svelte.d.ts +29 -0
- package/dist/fields2/sub/section-field.svelte +27 -0
- package/dist/fields2/sub/section-field.svelte.d.ts +26 -0
- package/dist/fields2/sub/select-field.svelte +138 -0
- package/dist/fields2/sub/select-field.svelte.d.ts +34 -0
- package/dist/fields2/sub/selectresource-field.svelte +69 -0
- package/dist/fields2/sub/selectresource-field.svelte.d.ts +29 -0
- package/dist/fields2/sub/signature-field.svelte +84 -0
- package/dist/fields2/sub/signature-field.svelte.d.ts +23 -0
- package/dist/fields2/sub/slider-field.svelte +28 -0
- package/dist/fields2/sub/slider-field.svelte.d.ts +28 -0
- package/dist/fields2/sub/smart-text-field.svelte +245 -0
- package/dist/fields2/sub/smart-text-field.svelte.d.ts +36 -0
- package/dist/fields2/sub/telephone-field.svelte +68 -0
- package/dist/fields2/sub/telephone-field.svelte.d.ts +26 -0
- package/dist/fields2/sub/text-field.svelte +46 -0
- package/dist/fields2/sub/text-field.svelte.d.ts +27 -0
- package/dist/fields2/sub/voicenote-field.svelte +161 -0
- package/dist/fields2/sub/voicenote-field.svelte.d.ts +26 -0
- package/dist/fields2/utils.d.ts +9 -0
- package/dist/fields2/utils.js +116 -0
- package/dist/fields2/validations/validate_field.d.ts +11 -0
- package/dist/fields2/validations/validate_field.js +121 -0
- package/package.json +6 -9
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
<script context="module">import {} from "../interfaces";
|
|
2
|
+
</script>
|
|
3
|
+
|
|
4
|
+
<script>import { Button } from "@stubber/ui/button";
|
|
5
|
+
import { snakeCase } from "lodash-es";
|
|
6
|
+
import FieldLabel from "../FieldLabel.svelte";
|
|
7
|
+
import FieldMessage from "../FieldMessage.svelte";
|
|
8
|
+
import {
|
|
9
|
+
append_attachment,
|
|
10
|
+
remove_attachment,
|
|
11
|
+
uploadFiles
|
|
12
|
+
} from "../fileserver";
|
|
13
|
+
export let fieldStore;
|
|
14
|
+
const file_name_prefix = snakeCase($fieldStore.label || "Screenshot");
|
|
15
|
+
let fileList = [];
|
|
16
|
+
let isRecording = false;
|
|
17
|
+
$: buttonLabel = isRecording ? "Cancel" : "Take screenshot";
|
|
18
|
+
let max_files_param = $fieldStore.params?.max_files;
|
|
19
|
+
$: max_files = isNaN(parseInt(max_files_param)) ? Infinity : parseInt(max_files_param);
|
|
20
|
+
$: limit_remaining = max_files - fileList.length;
|
|
21
|
+
const toggleRecording = () => {
|
|
22
|
+
isRecording = !isRecording;
|
|
23
|
+
if (isRecording) {
|
|
24
|
+
startRecording();
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
async function startRecording() {
|
|
28
|
+
if (limit_remaining <= 0) {
|
|
29
|
+
alert("You have reached the maximum number of files allowed");
|
|
30
|
+
isRecording = false;
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
let id = Math.random().toString(36).substring(7);
|
|
34
|
+
let filename = `${file_name_prefix}_${id}`;
|
|
35
|
+
if (!navigator.mediaDevices.getDisplayMedia) {
|
|
36
|
+
alert("Screen capture is not supported in this browser.");
|
|
37
|
+
isRecording = false;
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const stream = await navigator.mediaDevices.getDisplayMedia({
|
|
41
|
+
video: { mediaSource: "screen" }
|
|
42
|
+
});
|
|
43
|
+
const track = stream.getVideoTracks()[0];
|
|
44
|
+
const imageCapture = new ImageCapture(track);
|
|
45
|
+
const bitmap = await imageCapture.grabFrame();
|
|
46
|
+
track.stop();
|
|
47
|
+
const canvas = document.createElement("canvas");
|
|
48
|
+
canvas.width = bitmap.width;
|
|
49
|
+
canvas.height = bitmap.height;
|
|
50
|
+
const context = canvas.getContext("2d");
|
|
51
|
+
if (!context) {
|
|
52
|
+
isRecording = false;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
context.drawImage(bitmap, 0, 0, bitmap.width, bitmap.height);
|
|
56
|
+
const image = canvas.toDataURL();
|
|
57
|
+
const res = await fetch(image);
|
|
58
|
+
const buff = await res.arrayBuffer();
|
|
59
|
+
const file = new File([buff], `${filename}.png`, {
|
|
60
|
+
type: "image/png"
|
|
61
|
+
});
|
|
62
|
+
isRecording = false;
|
|
63
|
+
uploadFile(file, `${filename}.png`);
|
|
64
|
+
}
|
|
65
|
+
async function uploadFile(file, filename) {
|
|
66
|
+
fileList = [
|
|
67
|
+
...fileList,
|
|
68
|
+
{
|
|
69
|
+
file,
|
|
70
|
+
filename,
|
|
71
|
+
is_uploaded: false,
|
|
72
|
+
is_failed: false
|
|
73
|
+
}
|
|
74
|
+
];
|
|
75
|
+
const upload_res = await uploadFiles([file], $fieldStore.formDependencies);
|
|
76
|
+
const { uploaded_files = [], failed_files = [] } = upload_res || {};
|
|
77
|
+
const newly_uploaded = uploaded_files.find((f) => f.filename === filename) || {};
|
|
78
|
+
const is_uploaded = uploaded_files.length > 0;
|
|
79
|
+
const is_failed = failed_files.length > 0;
|
|
80
|
+
fileList = fileList.map((item) => {
|
|
81
|
+
if (item.filename === filename) {
|
|
82
|
+
return {
|
|
83
|
+
...item,
|
|
84
|
+
...newly_uploaded,
|
|
85
|
+
is_uploaded,
|
|
86
|
+
is_failed
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return item;
|
|
90
|
+
});
|
|
91
|
+
if (newly_uploaded.fileuuid) {
|
|
92
|
+
append_attachment(newly_uploaded, $fieldStore.attachmentsStore);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const update_file_list = (field_value) => {
|
|
96
|
+
if (Array.isArray(field_value)) {
|
|
97
|
+
fileList = field_value.map((f) => ({
|
|
98
|
+
file: f.file,
|
|
99
|
+
blob: f.blob,
|
|
100
|
+
fileuuid: f.fileuuid,
|
|
101
|
+
contentType: f.contentType,
|
|
102
|
+
originalname: f.originalname,
|
|
103
|
+
filename: f.filename,
|
|
104
|
+
is_uploaded: f.is_uploaded || f.fileuuid ? true : false,
|
|
105
|
+
is_failed: f.is_failed || false
|
|
106
|
+
}));
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
update_file_list($fieldStore.value);
|
|
110
|
+
$: update_field_value(fileList);
|
|
111
|
+
const update_field_value = (files) => {
|
|
112
|
+
const uploaded_files = files.filter((f) => f.is_uploaded && f.fileuuid);
|
|
113
|
+
$fieldStore.value = uploaded_files.map((f) => ({
|
|
114
|
+
filename: f.filename,
|
|
115
|
+
fileuuid: f.fileuuid,
|
|
116
|
+
contentType: f.contentType,
|
|
117
|
+
originalname: f.originalname
|
|
118
|
+
}));
|
|
119
|
+
};
|
|
120
|
+
function removeFile(item) {
|
|
121
|
+
fileList = fileList.filter((f) => f !== item);
|
|
122
|
+
remove_attachment(item.fileuuid, $fieldStore.attachmentsStore);
|
|
123
|
+
}
|
|
124
|
+
</script>
|
|
125
|
+
|
|
126
|
+
<FieldLabel {fieldStore} />
|
|
127
|
+
<div class="flex flex-col gap-2 items-start">
|
|
128
|
+
<Button variant={isRecording ? "destructive" : "default"} on:click={toggleRecording}>
|
|
129
|
+
{#if isRecording}
|
|
130
|
+
<i class="fa-solid fa-camera-slash" />
|
|
131
|
+
{:else}
|
|
132
|
+
<i class="fa-solid fa-camera" />
|
|
133
|
+
{/if}
|
|
134
|
+
{buttonLabel}
|
|
135
|
+
</Button>
|
|
136
|
+
<div class="flex flex-col gap-1 w-full">
|
|
137
|
+
{#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">
|
|
140
|
+
{#if item.is_uploaded}
|
|
141
|
+
<i class="fa fa-check text-success-500" />
|
|
142
|
+
{:else if item.is_failed}
|
|
143
|
+
<i class="fa-regular fa-triangle-exclamation text-danger-400" />
|
|
144
|
+
{:else}
|
|
145
|
+
<i class="fa fa-pulse fa-spinner text-surface-500" />
|
|
146
|
+
{/if}
|
|
147
|
+
</div>
|
|
148
|
+
|
|
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">
|
|
151
|
+
{item?.filename}
|
|
152
|
+
</p>
|
|
153
|
+
</div>
|
|
154
|
+
<Button
|
|
155
|
+
variant="destructive"
|
|
156
|
+
class="h-6 w-6 p-0 shrink-0"
|
|
157
|
+
on:click={() => removeFile(item)}
|
|
158
|
+
>
|
|
159
|
+
<i class="fa-solid fa-2xs fa-x" />
|
|
160
|
+
</Button>
|
|
161
|
+
</div>
|
|
162
|
+
{/each}
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
<FieldMessage {fieldStore} />
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { SvelteComponent } from "svelte";
|
|
2
|
+
import type { Writable } from "svelte/store";
|
|
3
|
+
import { type IBaseField, type IBuiltField } from "../interfaces";
|
|
4
|
+
interface IScreenshotFieldParams {
|
|
5
|
+
max_files?: number | string;
|
|
6
|
+
}
|
|
7
|
+
export interface IScreenshotField extends IBaseField<IScreenshotFieldParams> {
|
|
8
|
+
fieldtype: "screenshot";
|
|
9
|
+
}
|
|
10
|
+
declare const __propDef: {
|
|
11
|
+
props: {
|
|
12
|
+
fieldStore: Writable<IBuiltField<IScreenshotFieldParams>>;
|
|
13
|
+
};
|
|
14
|
+
events: {
|
|
15
|
+
[evt: string]: CustomEvent<any>;
|
|
16
|
+
};
|
|
17
|
+
slots: {};
|
|
18
|
+
exports?: {} | undefined;
|
|
19
|
+
bindings?: string | undefined;
|
|
20
|
+
};
|
|
21
|
+
export type ScreenshotFieldProps = typeof __propDef.props;
|
|
22
|
+
export type ScreenshotFieldEvents = typeof __propDef.events;
|
|
23
|
+
export type ScreenshotFieldSlots = typeof __propDef.slots;
|
|
24
|
+
export default class ScreenshotField extends SvelteComponent<ScreenshotFieldProps, ScreenshotFieldEvents, ScreenshotFieldSlots> {
|
|
25
|
+
}
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
<script context="module">import {} from "svelte/store";
|
|
2
|
+
import {} from "../interfaces";
|
|
3
|
+
export const scroll_and_read_display_field_param_spec = {
|
|
4
|
+
spec: {
|
|
5
|
+
fields: {
|
|
6
|
+
displaytext: {
|
|
7
|
+
fieldtype: "note"
|
|
8
|
+
},
|
|
9
|
+
checkedvalue: {
|
|
10
|
+
fieldtype: "jsoneditor"
|
|
11
|
+
},
|
|
12
|
+
uncheckedvalue: {
|
|
13
|
+
fieldtype: "jsoneditor"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<script>import { Checkbox } from "@stubber/ui/checkbox";
|
|
21
|
+
import { Label } from "@stubber/ui/label";
|
|
22
|
+
import { isEqual } from "lodash-es";
|
|
23
|
+
import FieldMessage from "../FieldMessage.svelte";
|
|
24
|
+
import { set_value_details } from "../utils";
|
|
25
|
+
import { onMount } from "svelte";
|
|
26
|
+
export let fieldStore;
|
|
27
|
+
let params = $fieldStore.params;
|
|
28
|
+
let yes_value = params?.checkedvalue ?? true;
|
|
29
|
+
let no_value = params?.uncheckedvalue ?? false;
|
|
30
|
+
let displaytext = params?.displaytext || "";
|
|
31
|
+
$: validation_result = $fieldStore.validation_result;
|
|
32
|
+
$: type = validation_result?.type;
|
|
33
|
+
$: is_error = type === "error";
|
|
34
|
+
let scrolled_to_bottom = false;
|
|
35
|
+
$: checked = isEqual($fieldStore.value, yes_value);
|
|
36
|
+
const handle_checked_change = (new_checked) => {
|
|
37
|
+
let new_value = new_checked ? yes_value : no_value;
|
|
38
|
+
$fieldStore.value = new_value;
|
|
39
|
+
};
|
|
40
|
+
let outer;
|
|
41
|
+
const handle_scroll = (event) => {
|
|
42
|
+
const target = event.target;
|
|
43
|
+
if (scrolled_to_bottom) return;
|
|
44
|
+
let scroll_percentage = Math.ceil(
|
|
45
|
+
target.scrollTop * 100 / (target.scrollHeight - target.clientHeight)
|
|
46
|
+
);
|
|
47
|
+
if (target.scrollHeight - target.clientHeight === 0) scroll_percentage = 100;
|
|
48
|
+
scrolled_to_bottom = scroll_percentage >= 99;
|
|
49
|
+
};
|
|
50
|
+
onMount(() => {
|
|
51
|
+
if (outer.scrollHeight <= outer.clientHeight) {
|
|
52
|
+
scrolled_to_bottom = true;
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
$: update_value_details($fieldStore.value);
|
|
56
|
+
const update_value_details = (new_value) => {
|
|
57
|
+
if (!new_value) return;
|
|
58
|
+
set_value_details($fieldStore, "label", $fieldStore.label);
|
|
59
|
+
};
|
|
60
|
+
</script>
|
|
61
|
+
|
|
62
|
+
<div class="flex flex-col w-full text-surface-900 my-2">
|
|
63
|
+
<!-- div with inner shadow -->
|
|
64
|
+
<div
|
|
65
|
+
bind:this={outer}
|
|
66
|
+
class="border max-h-[300px] overflow-y-scroll shadow-inner rounded-md"
|
|
67
|
+
on:scroll={handle_scroll}
|
|
68
|
+
>
|
|
69
|
+
<div class="p-6 px-2">
|
|
70
|
+
{@html displaytext}
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
<div
|
|
74
|
+
class="{!scrolled_to_bottom
|
|
75
|
+
? 'opacity-20'
|
|
76
|
+
: ''} flex space-x-3 relative mt-2 items-center {is_error
|
|
77
|
+
? 'border-b border-warning-500'
|
|
78
|
+
: ''}"
|
|
79
|
+
>
|
|
80
|
+
<Checkbox
|
|
81
|
+
disabled={!scrolled_to_bottom}
|
|
82
|
+
id="input_{$fieldStore.id}"
|
|
83
|
+
name={$fieldStore.id}
|
|
84
|
+
{checked}
|
|
85
|
+
onCheckedChange={handle_checked_change}
|
|
86
|
+
/>
|
|
87
|
+
<Label for="input_{$fieldStore.id}" class="block">
|
|
88
|
+
{$fieldStore.label}
|
|
89
|
+
</Label>
|
|
90
|
+
</div>
|
|
91
|
+
<FieldMessage {fieldStore} />
|
|
92
|
+
</div>
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { SvelteComponent } from "svelte";
|
|
2
|
+
import { type Writable } from "svelte/store";
|
|
3
|
+
import { type IBaseField, type IBuiltField, type IInitialForm } from "../interfaces";
|
|
4
|
+
interface IScrollAndReadDisplayFieldParams {
|
|
5
|
+
displaytext: string;
|
|
6
|
+
checkedvalue?: any;
|
|
7
|
+
uncheckedvalue?: any;
|
|
8
|
+
}
|
|
9
|
+
export declare const scroll_and_read_display_field_param_spec: IInitialForm;
|
|
10
|
+
export interface IScrollAndReadDisplayField extends IBaseField<IScrollAndReadDisplayFieldParams> {
|
|
11
|
+
fieldtype: "scrollandreaddisplay";
|
|
12
|
+
}
|
|
13
|
+
declare const __propDef: {
|
|
14
|
+
props: {
|
|
15
|
+
fieldStore: Writable<IBuiltField<IScrollAndReadDisplayFieldParams>>;
|
|
16
|
+
};
|
|
17
|
+
events: {
|
|
18
|
+
[evt: string]: CustomEvent<any>;
|
|
19
|
+
};
|
|
20
|
+
slots: {};
|
|
21
|
+
exports?: {} | undefined;
|
|
22
|
+
bindings?: string | undefined;
|
|
23
|
+
};
|
|
24
|
+
export type ScrollAndReadDisplayFieldProps = typeof __propDef.props;
|
|
25
|
+
export type ScrollAndReadDisplayFieldEvents = typeof __propDef.events;
|
|
26
|
+
export type ScrollAndReadDisplayFieldSlots = typeof __propDef.slots;
|
|
27
|
+
export default class ScrollAndReadDisplayField extends SvelteComponent<ScrollAndReadDisplayFieldProps, ScrollAndReadDisplayFieldEvents, ScrollAndReadDisplayFieldSlots> {
|
|
28
|
+
}
|
|
29
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<script context="module">import { get as get_store_value } from "svelte/store";
|
|
2
|
+
</script>
|
|
3
|
+
|
|
4
|
+
<script>import { Button } from "@stubber/ui/button";
|
|
5
|
+
import * as Collapsible from "@stubber/ui/collapsible";
|
|
6
|
+
import FieldLabel from "../FieldLabel.svelte";
|
|
7
|
+
import FieldMessage from "../FieldMessage.svelte";
|
|
8
|
+
import FormField from "../form-field.svelte";
|
|
9
|
+
export let fieldStore;
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
<Collapsible.Root open={true} class="w-full">
|
|
13
|
+
<Collapsible.Trigger asChild let:builder>
|
|
14
|
+
<Button builders={[builder]} variant="ghost" class="w-full justify-between pl-0">
|
|
15
|
+
<FieldLabel {fieldStore} />
|
|
16
|
+
<FieldMessage {fieldStore} />
|
|
17
|
+
|
|
18
|
+
<i class="fa fa-sort" />
|
|
19
|
+
</Button>
|
|
20
|
+
</Collapsible.Trigger>
|
|
21
|
+
|
|
22
|
+
<Collapsible.Content class="flex flex-col gap-y-1 pl-2">
|
|
23
|
+
{#each $fieldStore.sub_fields || [] as sub_field (get_store_value(sub_field).id)}
|
|
24
|
+
<FormField fieldStore={sub_field} />
|
|
25
|
+
{/each}
|
|
26
|
+
</Collapsible.Content>
|
|
27
|
+
</Collapsible.Root>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { SvelteComponent } from "svelte";
|
|
2
|
+
import type { Writable } from "svelte/store";
|
|
3
|
+
import type { IBaseField, IBuiltField, IField } from "../interfaces";
|
|
4
|
+
interface ISectionParams {
|
|
5
|
+
}
|
|
6
|
+
export interface ISectionField extends IBaseField<ISectionParams> {
|
|
7
|
+
fieldtype: "section";
|
|
8
|
+
fields: Record<string, IField>;
|
|
9
|
+
}
|
|
10
|
+
declare const __propDef: {
|
|
11
|
+
props: {
|
|
12
|
+
fieldStore: Writable<IBuiltField>;
|
|
13
|
+
};
|
|
14
|
+
events: {
|
|
15
|
+
[evt: string]: CustomEvent<any>;
|
|
16
|
+
};
|
|
17
|
+
slots: {};
|
|
18
|
+
exports?: {} | undefined;
|
|
19
|
+
bindings?: string | undefined;
|
|
20
|
+
};
|
|
21
|
+
export type SectionFieldProps = typeof __propDef.props;
|
|
22
|
+
export type SectionFieldEvents = typeof __propDef.events;
|
|
23
|
+
export type SectionFieldSlots = typeof __propDef.slots;
|
|
24
|
+
export default class SectionField extends SvelteComponent<SectionFieldProps, SectionFieldEvents, SectionFieldSlots> {
|
|
25
|
+
}
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
<script context="module">import { get } from "svelte/store";
|
|
2
|
+
export const select_field_param_spec = {
|
|
3
|
+
spec: {
|
|
4
|
+
fields: {
|
|
5
|
+
options: {
|
|
6
|
+
fieldtype: "arraybuilder",
|
|
7
|
+
params: {
|
|
8
|
+
new_entry_field: {
|
|
9
|
+
fieldtype: "section",
|
|
10
|
+
fields: {
|
|
11
|
+
label: {
|
|
12
|
+
fieldtype: "text"
|
|
13
|
+
},
|
|
14
|
+
value: {
|
|
15
|
+
fieldtype: "jsoneditor"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
</script>
|
|
25
|
+
|
|
26
|
+
<script>import { Button } from "@stubber/ui/button";
|
|
27
|
+
import * as Command from "@stubber/ui/command";
|
|
28
|
+
import * as Popover from "@stubber/ui/popover";
|
|
29
|
+
import { createState } from "cmdk-sv";
|
|
30
|
+
import { debounce, isEqual } from "lodash-es";
|
|
31
|
+
import { tick } from "svelte";
|
|
32
|
+
import FieldLabel from "../FieldLabel.svelte";
|
|
33
|
+
import FieldMessage from "../FieldMessage.svelte";
|
|
34
|
+
import { set_value_details } from "../utils";
|
|
35
|
+
export let fieldStore;
|
|
36
|
+
let open = false;
|
|
37
|
+
let loading = false;
|
|
38
|
+
let items = [];
|
|
39
|
+
const state = createState();
|
|
40
|
+
$: dropdownLabel = items.find((i) => isEqual(i.value, $fieldStore.value))?.label ?? $fieldStore.value?._default_label ?? get($fieldStore.formStore)[$fieldStore.data_path + "_label"] ?? "Select...";
|
|
41
|
+
$: update_value_details_label(dropdownLabel);
|
|
42
|
+
const update_value_details_label = (label) => {
|
|
43
|
+
if (!label || label === "Select...") return;
|
|
44
|
+
set_value_details($fieldStore, "label", label);
|
|
45
|
+
};
|
|
46
|
+
const map_field_options = (options) => {
|
|
47
|
+
if (!options || !Array.isArray(options)) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
return options.map((option, index) => {
|
|
51
|
+
const key = `${option.label}${index}`;
|
|
52
|
+
return {
|
|
53
|
+
label: option.label,
|
|
54
|
+
value: option.value,
|
|
55
|
+
key
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
const params = $fieldStore.params || {};
|
|
60
|
+
items = map_field_options(params?.options);
|
|
61
|
+
const get_selected_item_from_key = (key) => {
|
|
62
|
+
return items.find((item) => item.key === key);
|
|
63
|
+
};
|
|
64
|
+
function closeAndFocusTrigger(triggerId) {
|
|
65
|
+
open = false;
|
|
66
|
+
tick().then(() => {
|
|
67
|
+
document.getElementById(triggerId)?.focus();
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
let last_search = void 0;
|
|
71
|
+
const handle_search = async (search) => {
|
|
72
|
+
last_search = search;
|
|
73
|
+
if (params.load_options) {
|
|
74
|
+
loading = true;
|
|
75
|
+
const remote_results = await params.load_options($state.search);
|
|
76
|
+
loading = false;
|
|
77
|
+
items = map_field_options(remote_results);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
let debounced_handle_search = null;
|
|
81
|
+
if (params.load_options) {
|
|
82
|
+
debounced_handle_search = debounce(handle_search, 300);
|
|
83
|
+
}
|
|
84
|
+
$: if (params.load_options && $state.search !== void 0 && $state.search !== last_search) {
|
|
85
|
+
console.log("doing debounced search", !!debounced_handle_search);
|
|
86
|
+
if (debounced_handle_search) debounced_handle_search($state.search);
|
|
87
|
+
}
|
|
88
|
+
const handle_select = (item_key, trigger) => {
|
|
89
|
+
const selected_item = get_selected_item_from_key(item_key);
|
|
90
|
+
if (selected_item) {
|
|
91
|
+
$fieldStore.value = selected_item.value;
|
|
92
|
+
}
|
|
93
|
+
closeAndFocusTrigger(trigger);
|
|
94
|
+
};
|
|
95
|
+
</script>
|
|
96
|
+
|
|
97
|
+
<FieldLabel {fieldStore} />
|
|
98
|
+
<Popover.Root bind:open let:ids>
|
|
99
|
+
<Popover.Trigger asChild let:builder>
|
|
100
|
+
<Button
|
|
101
|
+
builders={[builder]}
|
|
102
|
+
variant="outline"
|
|
103
|
+
role="combobox"
|
|
104
|
+
aria-expanded={open}
|
|
105
|
+
class="w-full border-input bg-white bg-opacity-[15] ring-offset-background placeholder:text-muted-foreground focus-visible:ring-ring flex h-10 rounded-md border px-3 py-2 text-sm file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 justify-between"
|
|
106
|
+
style="box-shadow: inset 0px 16px 16px -16px rgba(0, 0, 0, 0.1333);"
|
|
107
|
+
>
|
|
108
|
+
{dropdownLabel}
|
|
109
|
+
<i class="fas fa-sort ml-2 h-4 w-4 shrink-0 opacity-50" />
|
|
110
|
+
</Button>
|
|
111
|
+
</Popover.Trigger>
|
|
112
|
+
<Popover.Content sameWidth class="p-0 z-[100]">
|
|
113
|
+
<Command.Root shouldFilter={!params.load_options} {state}>
|
|
114
|
+
<Command.Input class="border-none outline-none focus:ring-0" placeholder="Search..." />
|
|
115
|
+
<Command.List>
|
|
116
|
+
{#if loading}
|
|
117
|
+
<Command.Loading class="py-6 text-sm text-center">Loading…</Command.Loading>
|
|
118
|
+
{:else}
|
|
119
|
+
<Command.Empty>No results found.</Command.Empty>
|
|
120
|
+
|
|
121
|
+
{#each items as item}
|
|
122
|
+
<Command.Item
|
|
123
|
+
value={item.key}
|
|
124
|
+
onSelect={(item_key) => handle_select(item_key, ids.trigger)}
|
|
125
|
+
class={isEqual(item.value, $fieldStore.value) ? "bg-primary-100" : ""}
|
|
126
|
+
>
|
|
127
|
+
{item.label}
|
|
128
|
+
</Command.Item>
|
|
129
|
+
{/each}
|
|
130
|
+
{/if}
|
|
131
|
+
</Command.List>
|
|
132
|
+
</Command.Root>
|
|
133
|
+
</Popover.Content>
|
|
134
|
+
</Popover.Root>
|
|
135
|
+
<div class="flex items-center gap-2">
|
|
136
|
+
<FieldMessage {fieldStore} />
|
|
137
|
+
<slot />
|
|
138
|
+
</div>
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { SvelteComponent } from "svelte";
|
|
2
|
+
import { type Writable } from "svelte/store";
|
|
3
|
+
import type { IBaseField, IBuiltField, IInitialForm } from "../interfaces";
|
|
4
|
+
export interface ISelectFieldOption {
|
|
5
|
+
label: string;
|
|
6
|
+
value: any;
|
|
7
|
+
}
|
|
8
|
+
export interface ISelectFieldParams {
|
|
9
|
+
options?: ISelectFieldOption[];
|
|
10
|
+
load_options?: (search_term: string) => Promise<ISelectFieldOption[]>;
|
|
11
|
+
}
|
|
12
|
+
export declare const select_field_param_spec: IInitialForm;
|
|
13
|
+
export interface ISelectField extends IBaseField<ISelectFieldParams> {
|
|
14
|
+
fieldtype: "select";
|
|
15
|
+
}
|
|
16
|
+
declare const __propDef: {
|
|
17
|
+
props: {
|
|
18
|
+
fieldStore: Writable<IBuiltField<ISelectFieldParams>>;
|
|
19
|
+
};
|
|
20
|
+
events: {
|
|
21
|
+
[evt: string]: CustomEvent<any>;
|
|
22
|
+
};
|
|
23
|
+
slots: {
|
|
24
|
+
default: {};
|
|
25
|
+
};
|
|
26
|
+
exports?: {} | undefined;
|
|
27
|
+
bindings?: string | undefined;
|
|
28
|
+
};
|
|
29
|
+
export type SelectFieldProps = typeof __propDef.props;
|
|
30
|
+
export type SelectFieldEvents = typeof __propDef.events;
|
|
31
|
+
export type SelectFieldSlots = typeof __propDef.slots;
|
|
32
|
+
export default class SelectField extends SvelteComponent<SelectFieldProps, SelectFieldEvents, SelectFieldSlots> {
|
|
33
|
+
}
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
<script context="module">import { get } from "svelte/store";
|
|
2
|
+
export const select_resource_field_param_spec = {
|
|
3
|
+
spec: {
|
|
4
|
+
fields: {
|
|
5
|
+
resource_name: {
|
|
6
|
+
fieldtype: "text",
|
|
7
|
+
help: "The name of the resource to select from."
|
|
8
|
+
},
|
|
9
|
+
label: {
|
|
10
|
+
fieldtype: "text",
|
|
11
|
+
help: "The field in the resource to use as the label in the dropdown options."
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<script>import { cloneDeep, isEqual } from "lodash-es";
|
|
19
|
+
import SelectField, {
|
|
20
|
+
} from "./select-field.svelte";
|
|
21
|
+
export let fieldStore;
|
|
22
|
+
const load_options = async (search_term) => {
|
|
23
|
+
const resource_name = $fieldStore.params?.resource_name;
|
|
24
|
+
const dependencies = $fieldStore.formDependencies;
|
|
25
|
+
const clienthub = dependencies?.clienthub;
|
|
26
|
+
const stubber = dependencies?.stubber;
|
|
27
|
+
const socket = clienthub?.socket;
|
|
28
|
+
const stubref = stubber?.stubref;
|
|
29
|
+
const orguuid = stubber?.orguuid;
|
|
30
|
+
if (!clienthub || !socket) {
|
|
31
|
+
console.warn("Clienthub or socket not available");
|
|
32
|
+
return [];
|
|
33
|
+
}
|
|
34
|
+
if (!resource_name) {
|
|
35
|
+
console.warn("Resource name not specified in field params");
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
const details = {
|
|
39
|
+
resource_name
|
|
40
|
+
};
|
|
41
|
+
details.params = cloneDeep($fieldStore.params) || {};
|
|
42
|
+
details.params.orguuid = orguuid;
|
|
43
|
+
details.params.stubref = stubref;
|
|
44
|
+
details.params.input = search_term;
|
|
45
|
+
if (details.params.limit === void 0) {
|
|
46
|
+
details.params.limit = 50;
|
|
47
|
+
}
|
|
48
|
+
return new Promise((resolve, reject) => {
|
|
49
|
+
socket.emit("request", { type: "resource", details }, (res) => {
|
|
50
|
+
if (res.success) {
|
|
51
|
+
const resources = res.payload?.[resource_name] || [];
|
|
52
|
+
const result = resources.map((resource) => ({
|
|
53
|
+
value: resource,
|
|
54
|
+
label: resource[$fieldStore.params?.label || $fieldStore.label] ?? resource._default_label
|
|
55
|
+
}));
|
|
56
|
+
resolve(result);
|
|
57
|
+
} else {
|
|
58
|
+
console.error("Failed to load resource:", res);
|
|
59
|
+
resolve([]);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
};
|
|
64
|
+
if ($fieldStore.params) {
|
|
65
|
+
$fieldStore.params.load_options = load_options;
|
|
66
|
+
}
|
|
67
|
+
</script>
|
|
68
|
+
|
|
69
|
+
<SelectField {fieldStore} />
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { SvelteComponent } from "svelte";
|
|
2
|
+
import { type Writable } from "svelte/store";
|
|
3
|
+
import type { IBaseField, IBuiltField, IInitialForm } from "../interfaces";
|
|
4
|
+
export interface ISelectResourceFieldParams extends ISelectFieldParams {
|
|
5
|
+
resource_name: string;
|
|
6
|
+
label?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const select_resource_field_param_spec: IInitialForm;
|
|
9
|
+
export interface ISelectResourceField extends IBaseField<ISelectResourceFieldParams> {
|
|
10
|
+
fieldtype: "selectresource";
|
|
11
|
+
}
|
|
12
|
+
import { type ISelectFieldParams } from "./select-field.svelte";
|
|
13
|
+
declare const __propDef: {
|
|
14
|
+
props: {
|
|
15
|
+
fieldStore: Writable<IBuiltField<ISelectResourceFieldParams>>;
|
|
16
|
+
};
|
|
17
|
+
events: {
|
|
18
|
+
[evt: string]: CustomEvent<any>;
|
|
19
|
+
};
|
|
20
|
+
slots: {};
|
|
21
|
+
exports?: {} | undefined;
|
|
22
|
+
bindings?: string | undefined;
|
|
23
|
+
};
|
|
24
|
+
export type SelectresourceFieldProps = typeof __propDef.props;
|
|
25
|
+
export type SelectresourceFieldEvents = typeof __propDef.events;
|
|
26
|
+
export type SelectresourceFieldSlots = typeof __propDef.slots;
|
|
27
|
+
export default class SelectresourceField extends SvelteComponent<SelectresourceFieldProps, SelectresourceFieldEvents, SelectresourceFieldSlots> {
|
|
28
|
+
}
|
|
29
|
+
export {};
|