@servicetitan/dte-pdf-editor 1.92.1 → 1.92.2
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/components/field-config-panel/field-sidebar.d.ts.map +1 -1
- package/dist/components/field-config-panel/field-sidebar.js +8 -4
- package/dist/components/field-config-panel/field-sidebar.js.map +1 -1
- package/dist/components/field-sidebar/calculated-field-type-list.d.ts.map +1 -1
- package/dist/components/field-sidebar/calculated-field-type-list.js +1 -1
- package/dist/components/field-sidebar/calculated-field-type-list.js.map +1 -1
- package/dist/components/field-sidebar/data-model-field-type-list.d.ts +15 -1
- package/dist/components/field-sidebar/data-model-field-type-list.d.ts.map +1 -1
- package/dist/components/field-sidebar/data-model-field-type-list.js +54 -10
- package/dist/components/field-sidebar/data-model-field-type-list.js.map +1 -1
- package/dist/components/field-sidebar/e-sign-field-type-list.d.ts.map +1 -1
- package/dist/components/field-sidebar/e-sign-field-type-list.js +1 -1
- package/dist/components/field-sidebar/e-sign-field-type-list.js.map +1 -1
- package/dist/components/field-sidebar/field-sidebar.d.ts +7 -0
- package/dist/components/field-sidebar/field-sidebar.d.ts.map +1 -1
- package/dist/components/field-sidebar/field-sidebar.js +8 -11
- package/dist/components/field-sidebar/field-sidebar.js.map +1 -1
- package/dist/components/field-sidebar/field-type.d.ts +0 -2
- package/dist/components/field-sidebar/field-type.d.ts.map +1 -1
- package/dist/components/field-sidebar/field-type.js +2 -2
- package/dist/components/field-sidebar/field-type.js.map +1 -1
- package/dist/components/field-sidebar/fillable-field-type-list.d.ts.map +1 -1
- package/dist/components/field-sidebar/fillable-field-type-list.js +1 -1
- package/dist/components/field-sidebar/fillable-field-type-list.js.map +1 -1
- package/dist/components/field-sidebar/form-fields-type-list.d.ts +28 -5
- package/dist/components/field-sidebar/form-fields-type-list.d.ts.map +1 -1
- package/dist/components/field-sidebar/form-fields-type-list.js +27 -6
- package/dist/components/field-sidebar/form-fields-type-list.js.map +1 -1
- package/dist/components/field-sidebar/generic-field-type-list.d.ts.map +1 -1
- package/dist/components/field-sidebar/generic-field-type-list.js +1 -1
- package/dist/components/field-sidebar/generic-field-type-list.js.map +1 -1
- package/dist/components/pdf-canvas/canvas-controls.d.ts +15 -9
- package/dist/components/pdf-canvas/canvas-controls.d.ts.map +1 -1
- package/dist/components/pdf-canvas/canvas-controls.js +14 -4
- package/dist/components/pdf-canvas/canvas-controls.js.map +1 -1
- package/dist/components/pdf-canvas/pdf-canvas.d.ts +2 -6
- package/dist/components/pdf-canvas/pdf-canvas.d.ts.map +1 -1
- package/dist/components/pdf-canvas/pdf-canvas.js +5 -11
- package/dist/components/pdf-canvas/pdf-canvas.js.map +1 -1
- package/dist/components/pdf-editor/pdf-editor.d.ts +0 -2
- package/dist/components/pdf-editor/pdf-editor.d.ts.map +1 -1
- package/dist/components/pdf-editor/pdf-editor.js +4 -3
- package/dist/components/pdf-editor/pdf-editor.js.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-fields-overlay.js +1 -4
- package/dist/components/pdf-fields-overlay/pdf-fields-overlay.js.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-e-sign.d.ts.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-e-sign.js +2 -3
- package/dist/components/pdf-fields-overlay/pdf-overlay-field-e-sign.js.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-overlay-field.d.ts.map +1 -1
- package/dist/components/pdf-fields-overlay/pdf-overlay-field.js +2 -4
- package/dist/components/pdf-fields-overlay/pdf-overlay-field.js.map +1 -1
- package/dist/components/pdf-view/pdf-view-e-sign.d.ts.map +1 -1
- package/dist/components/pdf-view/pdf-view-e-sign.js +2 -3
- package/dist/components/pdf-view/pdf-view-e-sign.js.map +1 -1
- package/dist/components/pdf-view/pdf-view-field-container.d.ts.map +1 -1
- package/dist/components/pdf-view/pdf-view-field-container.js +3 -10
- package/dist/components/pdf-view/pdf-view-field-container.js.map +1 -1
- package/dist/components/pdf-view/pdf-view-fillable.js +1 -1
- package/dist/components/pdf-view/pdf-view-fillable.js.map +1 -1
- package/dist/components/pdf-view/pdf-view.d.ts +0 -5
- package/dist/components/pdf-view/pdf-view.d.ts.map +1 -1
- package/dist/components/pdf-view/pdf-view.js +24 -51
- package/dist/components/pdf-view/pdf-view.js.map +1 -1
- package/dist/constants/field.constants.d.ts.map +1 -1
- package/dist/constants/field.constants.js +6 -27
- package/dist/constants/field.constants.js.map +1 -1
- package/dist/constants/menu-group.d.ts +5 -0
- package/dist/constants/menu-group.d.ts.map +1 -1
- package/dist/constants/menu-group.js +5 -2
- package/dist/constants/menu-group.js.map +1 -1
- package/dist/hooks/index.d.ts +0 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +0 -1
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/useFormFields.d.ts +3 -1
- package/dist/hooks/useFormFields.d.ts.map +1 -1
- package/dist/hooks/useFormFields.js +20 -3
- package/dist/hooks/useFormFields.js.map +1 -1
- package/dist/hooks/useZoom.d.ts +0 -2
- package/dist/hooks/useZoom.d.ts.map +1 -1
- package/dist/hooks/useZoom.js +6 -18
- package/dist/hooks/useZoom.js.map +1 -1
- package/dist/interface/types.d.ts +0 -2
- package/dist/interface/types.d.ts.map +1 -1
- package/dist/interface/types.js.map +1 -1
- package/dist/styles/field-type.css +5 -0
- package/dist/styles/formula-modal.css +17 -0
- package/dist/styles/pdf-canvas.css +15 -91
- package/dist/styles/pdf-editor.css +0 -1
- package/dist/styles/pdf-field-overlay.css +78 -109
- package/dist/styles/variables.css +0 -3
- package/dist/utils/field/get-navigable-fields.utils.js +1 -1
- package/dist/utils/field/get-navigable-fields.utils.js.map +1 -1
- package/dist/utils/field/index.d.ts +0 -1
- package/dist/utils/field/index.d.ts.map +1 -1
- package/dist/utils/field/index.js +0 -1
- package/dist/utils/field/index.js.map +1 -1
- package/dist/utils/pdf/index.d.ts +0 -1
- package/dist/utils/pdf/index.d.ts.map +1 -1
- package/dist/utils/pdf/index.js +0 -1
- package/dist/utils/pdf/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/field-config-panel/field-sidebar.tsx +24 -8
- package/src/components/field-sidebar/calculated-field-type-list.tsx +0 -1
- package/src/components/field-sidebar/data-model-field-type-list.tsx +143 -29
- package/src/components/field-sidebar/e-sign-field-type-list.tsx +0 -1
- package/src/components/field-sidebar/field-sidebar.tsx +19 -18
- package/src/components/field-sidebar/field-type.tsx +3 -13
- package/src/components/field-sidebar/fillable-field-type-list.tsx +0 -1
- package/src/components/field-sidebar/form-fields-type-list.tsx +137 -95
- package/src/components/field-sidebar/generic-field-type-list.tsx +0 -1
- package/src/components/pdf-canvas/canvas-controls.tsx +128 -30
- package/src/components/pdf-canvas/pdf-canvas.tsx +8 -18
- package/src/components/pdf-editor/pdf-editor.tsx +5 -7
- package/src/components/pdf-fields-overlay/pdf-fields-overlay.tsx +1 -1
- package/src/components/pdf-fields-overlay/pdf-overlay-field-e-sign.tsx +6 -8
- package/src/components/pdf-fields-overlay/pdf-overlay-field.tsx +1 -17
- package/src/components/pdf-view/pdf-view-e-sign.tsx +6 -8
- package/src/components/pdf-view/pdf-view-field-container.tsx +1 -21
- package/src/components/pdf-view/pdf-view-fillable.tsx +1 -1
- package/src/components/pdf-view/pdf-view.tsx +95 -156
- package/src/constants/field.constants.ts +6 -28
- package/src/constants/menu-group.ts +5 -2
- package/src/hooks/index.ts +0 -1
- package/src/hooks/useFormFields.ts +24 -2
- package/src/hooks/useZoom.ts +8 -20
- package/src/interface/types.ts +0 -3
- package/src/styles/field-type.css +5 -0
- package/src/styles/formula-modal.css +17 -0
- package/src/styles/pdf-canvas.css +15 -91
- package/src/styles/pdf-editor.css +0 -1
- package/src/styles/pdf-field-overlay.css +78 -109
- package/src/styles/variables.css +0 -3
- package/src/utils/field/get-navigable-fields.utils.ts +1 -1
- package/src/utils/field/index.ts +0 -1
- package/src/utils/pdf/index.ts +0 -1
- package/dist/hooks/usePinchZoom.d.ts +0 -14
- package/dist/hooks/usePinchZoom.d.ts.map +0 -1
- package/dist/hooks/usePinchZoom.js +0 -58
- package/dist/hooks/usePinchZoom.js.map +0 -1
- package/dist/utils/field/get-field-type-option.utils.d.ts +0 -3
- package/dist/utils/field/get-field-type-option.utils.d.ts.map +0 -1
- package/dist/utils/field/get-field-type-option.utils.js +0 -10
- package/dist/utils/field/get-field-type-option.utils.js.map +0 -1
- package/dist/utils/pdf/pinch-zoom.utils.d.ts +0 -6
- package/dist/utils/pdf/pinch-zoom.utils.d.ts.map +0 -1
- package/dist/utils/pdf/pinch-zoom.utils.js +0 -18
- package/dist/utils/pdf/pinch-zoom.utils.js.map +0 -1
- package/src/hooks/usePinchZoom.ts +0 -86
- package/src/utils/field/__tests__/get-field-type-option.utils.test.ts +0 -53
- package/src/utils/field/get-field-type-option.utils.ts +0 -16
- package/src/utils/pdf/__tests__/pinch-zoom.utils.test.ts +0 -58
- package/src/utils/pdf/pinch-zoom.utils.ts +0 -30
|
@@ -1,37 +1,111 @@
|
|
|
1
1
|
import { Flex, SearchField, Text } from '@servicetitan/anvil2';
|
|
2
|
-
import { FC, Fragment, useMemo, useState } from 'react';
|
|
3
|
-
import {
|
|
2
|
+
import { FC, Fragment, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
type DataModelFieldGroup,
|
|
5
|
+
type FieldTypeOption,
|
|
6
|
+
type FormInfo,
|
|
7
|
+
} from '../../interface/types';
|
|
4
8
|
import { getFieldSampleData } from '../../utils';
|
|
5
9
|
import { FieldType } from './field-type';
|
|
10
|
+
import { FormFieldsPanel, FormsList } from './form-fields-type-list';
|
|
6
11
|
|
|
7
12
|
interface DataModelFieldTypeListProps {
|
|
8
13
|
dataModelGroups?: DataModelFieldGroup[];
|
|
14
|
+
/**
|
|
15
|
+
* Forms offered as a group after the data model groups, already narrowed to the ones that have
|
|
16
|
+
* fields. Omitted (or empty, with nothing loading) hides the group; `onSelectForm` must be
|
|
17
|
+
* provided for the group to render.
|
|
18
|
+
*/
|
|
19
|
+
forms?: FormInfo[];
|
|
20
|
+
/** The form whose fields are shown in place of the group list; null shows the group list. */
|
|
21
|
+
selectedForm?: FormInfo | null;
|
|
22
|
+
/** Draggable options for `selectedForm`. */
|
|
23
|
+
formFieldOptions?: FieldTypeOption[];
|
|
24
|
+
/** Placeholder rows to draw while it is still unknown which forms have fields. */
|
|
25
|
+
loadingFormsCount?: number;
|
|
26
|
+
isLoadingFormFields?: boolean;
|
|
27
|
+
onSelectForm?(form: FormInfo | null): void;
|
|
9
28
|
onDragStart(fieldOption: FieldTypeOption): void;
|
|
10
29
|
onDragEnd(): void;
|
|
11
30
|
}
|
|
12
31
|
|
|
32
|
+
const matchesSearch = (label: string, searchLower: string) =>
|
|
33
|
+
label.toLowerCase().includes(searchLower);
|
|
34
|
+
|
|
13
35
|
export const DataModelFieldTypeList: FC<DataModelFieldTypeListProps> = ({
|
|
14
36
|
dataModelGroups = [],
|
|
37
|
+
formFieldOptions = [],
|
|
38
|
+
forms,
|
|
39
|
+
isLoadingFormFields = false,
|
|
40
|
+
loadingFormsCount = 0,
|
|
15
41
|
onDragEnd,
|
|
16
42
|
onDragStart,
|
|
43
|
+
onSelectForm,
|
|
44
|
+
selectedForm = null,
|
|
17
45
|
}) => {
|
|
18
46
|
const [searchText, setSearchText] = useState('');
|
|
47
|
+
const searchLower = searchText.trim().toLowerCase();
|
|
48
|
+
const showForms = (!!forms?.length || loadingFormsCount > 0) && !!onSelectForm;
|
|
19
49
|
|
|
20
50
|
const searchedFieldGroups = useMemo(() => {
|
|
21
|
-
if (!
|
|
51
|
+
if (!searchLower) {
|
|
22
52
|
return dataModelGroups;
|
|
23
53
|
}
|
|
24
54
|
|
|
25
|
-
const searchLower = searchText.toLowerCase();
|
|
26
55
|
return dataModelGroups
|
|
27
56
|
.map(group => ({
|
|
28
57
|
...group,
|
|
29
|
-
fields: group.fields.filter(({ label }) =>
|
|
30
|
-
label.toLowerCase().includes(searchLower),
|
|
31
|
-
),
|
|
58
|
+
fields: group.fields.filter(({ label }) => matchesSearch(label, searchLower)),
|
|
32
59
|
}))
|
|
33
60
|
.filter(group => group.fields.length > 0);
|
|
34
|
-
}, [dataModelGroups,
|
|
61
|
+
}, [dataModelGroups, searchLower]);
|
|
62
|
+
|
|
63
|
+
const searchedForms = useMemo(() => {
|
|
64
|
+
if (!forms?.length) {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
if (!searchLower) {
|
|
68
|
+
return forms;
|
|
69
|
+
}
|
|
70
|
+
return forms.filter(form => matchesSearch(form.name, searchLower));
|
|
71
|
+
}, [forms, searchLower]);
|
|
72
|
+
|
|
73
|
+
const searchedFormFieldOptions = useMemo(() => {
|
|
74
|
+
if (!searchLower) {
|
|
75
|
+
return formFieldOptions;
|
|
76
|
+
}
|
|
77
|
+
return formFieldOptions.filter(option => matchesSearch(option.label, searchLower));
|
|
78
|
+
}, [formFieldOptions, searchLower]);
|
|
79
|
+
|
|
80
|
+
const isFormDetail = showForms && !!selectedForm;
|
|
81
|
+
|
|
82
|
+
/*
|
|
83
|
+
* Both slide panels share one scroller. The Forms group sits below every data model group, so
|
|
84
|
+
* a form is usually picked with the list scrolled far down — and the fields panel, being much
|
|
85
|
+
* shorter, would then be scrolled clean past its own content. Start the fields panel at the
|
|
86
|
+
* top and hand the list back the offset it was left at.
|
|
87
|
+
*
|
|
88
|
+
* The offset is tracked as the user scrolls rather than read when the panel switches: by then
|
|
89
|
+
* the list has already collapsed (see formula-modal.css) and the browser has clamped the
|
|
90
|
+
* scroller back to 0.
|
|
91
|
+
*/
|
|
92
|
+
const scrollerRef = useRef<HTMLDivElement>(null);
|
|
93
|
+
const listScrollTopRef = useRef(0);
|
|
94
|
+
|
|
95
|
+
useLayoutEffect(() => {
|
|
96
|
+
const scroller = scrollerRef.current;
|
|
97
|
+
if (!scroller) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
scroller.scrollTop = isFormDetail ? 0 : listScrollTopRef.current;
|
|
101
|
+
}, [isFormDetail]);
|
|
102
|
+
|
|
103
|
+
const handleScroll = () => {
|
|
104
|
+
if (!isFormDetail && scrollerRef.current) {
|
|
105
|
+
listScrollTopRef.current = scrollerRef.current.scrollTop;
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
|
|
35
109
|
return (
|
|
36
110
|
<Fragment>
|
|
37
111
|
<SearchField
|
|
@@ -40,30 +114,70 @@ export const DataModelFieldTypeList: FC<DataModelFieldTypeListProps> = ({
|
|
|
40
114
|
className="dte-field-sidebar-search"
|
|
41
115
|
onChange={e => setSearchText(e.target.value ?? '')}
|
|
42
116
|
/>
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
<
|
|
54
|
-
|
|
117
|
+
<div
|
|
118
|
+
className="dte-formula-sidebar-slide-outer"
|
|
119
|
+
ref={scrollerRef}
|
|
120
|
+
onScroll={handleScroll}
|
|
121
|
+
>
|
|
122
|
+
<div
|
|
123
|
+
className={`dte-formula-sidebar-slide-track${isFormDetail ? ' --detail' : ''}`}
|
|
124
|
+
>
|
|
125
|
+
<div className="dte-formula-sidebar-slide-panel">
|
|
126
|
+
{searchedFieldGroups.map(group => (
|
|
127
|
+
<div key={group.groupName} className="dte-field-type-group">
|
|
128
|
+
<Flex
|
|
129
|
+
alignItems="center"
|
|
130
|
+
gap={1}
|
|
131
|
+
className="dte-field-type-group-header"
|
|
132
|
+
>
|
|
133
|
+
<Text variant="headline" el="h6" size="small">
|
|
134
|
+
{group.groupName}
|
|
135
|
+
</Text>
|
|
136
|
+
</Flex>
|
|
137
|
+
{group.fields.map(fieldOption => {
|
|
138
|
+
const key = `${fieldOption.type}-${group.groupName}-${fieldOption.path}`;
|
|
139
|
+
return (
|
|
140
|
+
<FieldType
|
|
141
|
+
key={key}
|
|
142
|
+
onDragEnd={onDragEnd}
|
|
143
|
+
onDragStart={() => onDragStart(fieldOption)}
|
|
144
|
+
label={fieldOption.label}
|
|
145
|
+
sampleData={getFieldSampleData(
|
|
146
|
+
fieldOption.sampleData,
|
|
147
|
+
fieldOption.fieldType,
|
|
148
|
+
)}
|
|
149
|
+
/>
|
|
150
|
+
);
|
|
151
|
+
})}
|
|
152
|
+
</div>
|
|
153
|
+
))}
|
|
154
|
+
{showForms && (searchedForms.length > 0 || !searchLower) && (
|
|
155
|
+
<FormsList
|
|
156
|
+
forms={searchedForms}
|
|
157
|
+
loadingCount={loadingFormsCount}
|
|
158
|
+
onSelectForm={onSelectForm}
|
|
159
|
+
/>
|
|
160
|
+
)}
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
<div className="dte-formula-sidebar-slide-panel --form-fields">
|
|
164
|
+
{isFormDetail && (
|
|
165
|
+
<FormFieldsPanel
|
|
166
|
+
form={selectedForm}
|
|
167
|
+
fieldOptions={searchedFormFieldOptions}
|
|
168
|
+
hasHiddenFields={
|
|
169
|
+
formFieldOptions.length > 0 &&
|
|
170
|
+
searchedFormFieldOptions.length === 0
|
|
171
|
+
}
|
|
172
|
+
isLoading={isLoadingFormFields}
|
|
173
|
+
onBack={() => onSelectForm(null)}
|
|
55
174
|
onDragEnd={onDragEnd}
|
|
56
|
-
onDragStart={
|
|
57
|
-
label={fieldOption.label}
|
|
58
|
-
sampleData={getFieldSampleData(
|
|
59
|
-
fieldOption.sampleData,
|
|
60
|
-
fieldOption.fieldType,
|
|
61
|
-
)}
|
|
175
|
+
onDragStart={onDragStart}
|
|
62
176
|
/>
|
|
63
|
-
)
|
|
64
|
-
|
|
177
|
+
)}
|
|
178
|
+
</div>
|
|
65
179
|
</div>
|
|
66
|
-
|
|
180
|
+
</div>
|
|
67
181
|
</Fragment>
|
|
68
182
|
);
|
|
69
183
|
};
|
|
@@ -13,7 +13,6 @@ export const ESignFieldTypeList: FC<ESignFieldTypeListProps> = ({ onDragEnd, onD
|
|
|
13
13
|
<div className="dte-field-type-items-wrapper">
|
|
14
14
|
{E_SIGN_FIELD_TYPES.map(fieldOption => (
|
|
15
15
|
<FieldType
|
|
16
|
-
icon={fieldOption.icon}
|
|
17
16
|
key={fieldOption.subType}
|
|
18
17
|
label={fieldOption.label}
|
|
19
18
|
onDragEnd={onDragEnd}
|
|
@@ -14,15 +14,21 @@ import { DataModelFieldTypeList } from './data-model-field-type-list';
|
|
|
14
14
|
import { ESignFieldTypeList } from './e-sign-field-type-list';
|
|
15
15
|
import { FieldMenuGroup } from './field-menu-group';
|
|
16
16
|
import { FillableFieldTypeList } from './fillable-field-type-list';
|
|
17
|
-
import { FormFieldsTypeList } from './form-fields-type-list';
|
|
18
17
|
import { GenericFieldTypeList } from './generic-field-type-list';
|
|
19
18
|
|
|
20
19
|
interface FieldSidebarProps {
|
|
21
20
|
dataModelGroups?: DataModelFieldGroup[];
|
|
21
|
+
/**
|
|
22
|
+
* Forms listed as a group inside the Merge Tags tab (alongside the data model groups).
|
|
23
|
+
* Hidden when empty, when `onSelectForm` is missing, or when `hideFields` names
|
|
24
|
+
* `FieldTypeEnum.forms`.
|
|
25
|
+
*/
|
|
22
26
|
forms?: FormInfo[];
|
|
23
27
|
formFieldOptions?: FieldTypeOption[];
|
|
24
28
|
hideFields?: FieldTypeEnum[];
|
|
25
29
|
selectedForm?: FormInfo | null;
|
|
30
|
+
/** Placeholder rows to draw while it is still unknown which forms have fields. */
|
|
31
|
+
loadingFormsCount?: number;
|
|
26
32
|
isLoadingFormFields?: boolean;
|
|
27
33
|
onDragEnd(): void;
|
|
28
34
|
onDragStart(fieldOption: FieldTypeOption): void;
|
|
@@ -35,6 +41,7 @@ export const FieldSidebar: FC<FieldSidebarProps> = ({
|
|
|
35
41
|
forms,
|
|
36
42
|
hideFields,
|
|
37
43
|
isLoadingFormFields = false,
|
|
44
|
+
loadingFormsCount = 0,
|
|
38
45
|
onDragEnd,
|
|
39
46
|
onDragStart,
|
|
40
47
|
onSelectForm,
|
|
@@ -49,15 +56,14 @@ export const FieldSidebar: FC<FieldSidebarProps> = ({
|
|
|
49
56
|
};
|
|
50
57
|
|
|
51
58
|
const filteredMenuGroups = useMemo(() => {
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
groups = groups.filter(menuGroup => !hideFields.includes(menuGroup.key));
|
|
59
|
+
if (!hideFields?.length) {
|
|
60
|
+
return menuGroups;
|
|
55
61
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
return menuGroups.filter(menuGroup => !hideFields.includes(menuGroup.key));
|
|
63
|
+
}, [hideFields]);
|
|
64
|
+
|
|
65
|
+
// The list itself hides the group when there are no forms or no selection handler.
|
|
66
|
+
const visibleForms = hideFields?.includes(FieldTypeEnum.forms) ? undefined : forms;
|
|
61
67
|
|
|
62
68
|
return (
|
|
63
69
|
<Flex className="dte-field-sidebar-container skeleton-item">
|
|
@@ -85,17 +91,12 @@ export const FieldSidebar: FC<FieldSidebarProps> = ({
|
|
|
85
91
|
{activeFieldType === FieldTypeEnum.dataModel && (
|
|
86
92
|
<DataModelFieldTypeList
|
|
87
93
|
dataModelGroups={dataModelGroups}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
)}
|
|
92
|
-
{activeFieldType === FieldTypeEnum.forms && forms && onSelectForm && (
|
|
93
|
-
<FormFieldsTypeList
|
|
94
|
-
fieldOptions={formFieldOptions}
|
|
95
|
-
forms={forms}
|
|
94
|
+
forms={visibleForms}
|
|
95
|
+
formFieldOptions={formFieldOptions}
|
|
96
|
+
loadingFormsCount={visibleForms ? loadingFormsCount : 0}
|
|
96
97
|
isLoadingFormFields={isLoadingFormFields}
|
|
97
|
-
onDragEnd={onDragEnd}
|
|
98
98
|
onDragStart={onDragStart}
|
|
99
|
+
onDragEnd={onDragEnd}
|
|
99
100
|
onSelectForm={onSelectForm}
|
|
100
101
|
selectedForm={selectedForm}
|
|
101
102
|
/>
|
|
@@ -1,22 +1,15 @@
|
|
|
1
|
-
import { Flex, Icon,
|
|
1
|
+
import { Flex, Icon, Text } from '@servicetitan/anvil2';
|
|
2
2
|
import IconDragIndicator from '@servicetitan/anvil2/assets/icons/material/round/drag_indicator.svg';
|
|
3
3
|
import { FC } from 'react';
|
|
4
4
|
|
|
5
5
|
interface FieldTypeProps {
|
|
6
|
-
icon?: Svg;
|
|
7
6
|
label: string;
|
|
8
7
|
sampleData?: string | number | null;
|
|
9
8
|
onDragEnd(): void;
|
|
10
9
|
onDragStart(): void;
|
|
11
10
|
}
|
|
12
11
|
|
|
13
|
-
export const FieldType: FC<FieldTypeProps> = ({
|
|
14
|
-
icon,
|
|
15
|
-
label,
|
|
16
|
-
onDragEnd,
|
|
17
|
-
onDragStart,
|
|
18
|
-
sampleData,
|
|
19
|
-
}) => {
|
|
12
|
+
export const FieldType: FC<FieldTypeProps> = ({ label, onDragEnd, onDragStart, sampleData }) => {
|
|
20
13
|
return (
|
|
21
14
|
<div
|
|
22
15
|
draggable
|
|
@@ -31,10 +24,7 @@ export const FieldType: FC<FieldTypeProps> = ({
|
|
|
31
24
|
>
|
|
32
25
|
<Icon svg={IconDragIndicator} size="medium" />
|
|
33
26
|
<Flex direction="column" style={{ overflow: 'hidden' }}>
|
|
34
|
-
<
|
|
35
|
-
{icon && <Icon svg={icon} size="medium" />}
|
|
36
|
-
<Text size="small">{label}</Text>
|
|
37
|
-
</Flex>
|
|
27
|
+
<Text size="small">{label}</Text>
|
|
38
28
|
{sampleData && (
|
|
39
29
|
<Text
|
|
40
30
|
size="small"
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Button, Flex, Text } from '@servicetitan/anvil2';
|
|
1
|
+
import { Button, Flex, Skeleton, Text } from '@servicetitan/anvil2';
|
|
2
2
|
import IconArrowBack from '@servicetitan/anvil2/assets/icons/material/round/keyboard_arrow_left.svg';
|
|
3
3
|
import IconArrowForward from '@servicetitan/anvil2/assets/icons/material/round/keyboard_arrow_right.svg';
|
|
4
4
|
import { FC, Fragment } from 'react';
|
|
@@ -6,112 +6,154 @@ import { type FieldTypeOption, type FormInfo } from '../../interface/types';
|
|
|
6
6
|
import { getFieldSampleData } from '../../utils';
|
|
7
7
|
import { FieldType } from './field-type';
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
/** Heading of the Forms group inside the Merge Tags tab. */
|
|
10
|
+
export const FORMS_GROUP_NAME = 'Forms';
|
|
11
|
+
|
|
12
|
+
/** A form's field count is unknown until it answers, so the placeholder settles on a short list. */
|
|
13
|
+
const FIELD_SKELETON_ROWS = 4;
|
|
14
|
+
|
|
15
|
+
/** Placeholder rows, shaped like the real ones, for a list whose contents are still in flight. */
|
|
16
|
+
const SkeletonRows: FC<{ count: number; className: string }> = ({ className, count }) => {
|
|
17
|
+
return (
|
|
18
|
+
<Fragment>
|
|
19
|
+
{Array.from({ length: count }, (_, index) => (
|
|
20
|
+
<div key={index} className={`${className} --skeleton`} aria-hidden>
|
|
21
|
+
<Skeleton.Text />
|
|
22
|
+
</div>
|
|
23
|
+
))}
|
|
24
|
+
</Fragment>
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export interface FormsListProps {
|
|
10
29
|
forms: FormInfo[];
|
|
11
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Rows to draw as placeholders instead of the list. Forms are only listed once they are known
|
|
32
|
+
* to have fields, so until the fields arrive there is nothing truthful to show.
|
|
33
|
+
*/
|
|
34
|
+
loadingCount?: number;
|
|
35
|
+
/** Rendered when the (possibly search-filtered) list is empty. */
|
|
36
|
+
emptyText?: string;
|
|
37
|
+
onSelectForm(form: FormInfo): void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Root level of the Forms group: one clickable row per form. Selecting a form hands off to
|
|
42
|
+
* `FormFieldsPanel`, which lists that form's draggable fields.
|
|
43
|
+
*/
|
|
44
|
+
export const FormsList: FC<FormsListProps> = ({
|
|
45
|
+
emptyText = 'No forms configured.',
|
|
46
|
+
forms,
|
|
47
|
+
loadingCount = 0,
|
|
48
|
+
onSelectForm,
|
|
49
|
+
}) => {
|
|
50
|
+
return (
|
|
51
|
+
<div className="dte-field-type-group">
|
|
52
|
+
<Flex alignItems="center" gap={1} className="dte-field-type-group-header">
|
|
53
|
+
<Text variant="headline" el="h6" size="small">
|
|
54
|
+
{FORMS_GROUP_NAME}
|
|
55
|
+
</Text>
|
|
56
|
+
</Flex>
|
|
57
|
+
{loadingCount > 0 ? (
|
|
58
|
+
<SkeletonRows count={loadingCount} className="dte-formula-field-list-item" />
|
|
59
|
+
) : forms.length > 0 ? (
|
|
60
|
+
<ul className="dte-formula-field-list" role="listbox" aria-label={FORMS_GROUP_NAME}>
|
|
61
|
+
{forms.map(form => (
|
|
62
|
+
<li
|
|
63
|
+
key={form.id}
|
|
64
|
+
role="option"
|
|
65
|
+
className="dte-formula-field-list-item"
|
|
66
|
+
onMouseDown={e => {
|
|
67
|
+
e.preventDefault();
|
|
68
|
+
onSelectForm(form);
|
|
69
|
+
}}
|
|
70
|
+
>
|
|
71
|
+
<Flex justifyContent="space-between" alignItems="center">
|
|
72
|
+
{/*
|
|
73
|
+
* Same Text as the data model rows use (via FieldType): a bare
|
|
74
|
+
* span would inherit the host page's font instead of Anvil2's,
|
|
75
|
+
* so the Forms rows would not match the groups above them.
|
|
76
|
+
*/}
|
|
77
|
+
<Text size="small" className="dte-text-ellipsis" title={form.name}>
|
|
78
|
+
{form.name}
|
|
79
|
+
</Text>
|
|
80
|
+
<IconArrowForward />
|
|
81
|
+
</Flex>
|
|
82
|
+
</li>
|
|
83
|
+
))}
|
|
84
|
+
</ul>
|
|
85
|
+
) : (
|
|
86
|
+
<Text size="small" subdued variant="body">
|
|
87
|
+
{emptyText}
|
|
88
|
+
</Text>
|
|
89
|
+
)}
|
|
90
|
+
</div>
|
|
91
|
+
);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export interface FormFieldsPanelProps {
|
|
95
|
+
form: FormInfo;
|
|
12
96
|
fieldOptions: FieldTypeOption[];
|
|
13
|
-
|
|
14
|
-
|
|
97
|
+
isLoading?: boolean;
|
|
98
|
+
/** Rendered when the form has fields but none match the current search. */
|
|
99
|
+
hasHiddenFields?: boolean;
|
|
100
|
+
onBack(): void;
|
|
15
101
|
onDragEnd(): void;
|
|
16
102
|
onDragStart(fieldOption: FieldTypeOption): void;
|
|
17
103
|
}
|
|
18
104
|
|
|
19
|
-
|
|
105
|
+
/**
|
|
106
|
+
* Detail level of the Forms group: a back button naming the form, then its fields as the same
|
|
107
|
+
* draggable rows the data model groups use, so a form field drops onto the PDF like any merge tag.
|
|
108
|
+
*/
|
|
109
|
+
export const FormFieldsPanel: FC<FormFieldsPanelProps> = ({
|
|
20
110
|
fieldOptions,
|
|
21
|
-
|
|
22
|
-
|
|
111
|
+
form,
|
|
112
|
+
hasHiddenFields = false,
|
|
113
|
+
isLoading = false,
|
|
114
|
+
onBack,
|
|
23
115
|
onDragEnd,
|
|
24
116
|
onDragStart,
|
|
25
|
-
onSelectForm,
|
|
26
|
-
selectedForm,
|
|
27
117
|
}) => {
|
|
28
118
|
return (
|
|
29
|
-
<
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
119
|
+
<Fragment>
|
|
120
|
+
<Button
|
|
121
|
+
style={{ width: '100%' }}
|
|
122
|
+
appearance="ghost"
|
|
123
|
+
size="small"
|
|
124
|
+
icon={IconArrowBack}
|
|
125
|
+
title={form.name}
|
|
126
|
+
onClick={onBack}
|
|
127
|
+
>
|
|
128
|
+
<Text
|
|
129
|
+
size="medium"
|
|
130
|
+
className="dte-text-ellipsis"
|
|
131
|
+
style={{ fontWeight: 'bold', width: '100%' }}
|
|
33
132
|
>
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
</li>
|
|
58
|
-
))}
|
|
59
|
-
</ul>
|
|
60
|
-
) : (
|
|
61
|
-
<Text size="small" subdued variant="body">
|
|
62
|
-
No forms configured.
|
|
63
|
-
</Text>
|
|
64
|
-
)}
|
|
65
|
-
</div>
|
|
66
|
-
|
|
67
|
-
<div className="dte-formula-sidebar-slide-panel --form-fields">
|
|
68
|
-
{selectedForm ? (
|
|
69
|
-
<Fragment>
|
|
70
|
-
<Button
|
|
71
|
-
style={{ width: '100%' }}
|
|
72
|
-
appearance="ghost"
|
|
73
|
-
size="small"
|
|
74
|
-
icon={IconArrowBack}
|
|
75
|
-
title={selectedForm.name}
|
|
76
|
-
onClick={() => onSelectForm(null)}
|
|
77
|
-
>
|
|
78
|
-
<Text
|
|
79
|
-
size="medium"
|
|
80
|
-
className="dte-text-ellipsis"
|
|
81
|
-
style={{ fontWeight: 'bold', width: '100%' }}
|
|
82
|
-
>
|
|
83
|
-
{selectedForm.name}
|
|
84
|
-
</Text>
|
|
85
|
-
</Button>
|
|
86
|
-
<div className="dte-field-type-group">
|
|
87
|
-
{isLoadingFormFields ? (
|
|
88
|
-
<Text size="small" subdued variant="body">
|
|
89
|
-
Loading fields…
|
|
90
|
-
</Text>
|
|
91
|
-
) : fieldOptions.length === 0 ? (
|
|
92
|
-
<Text size="small" subdued variant="body">
|
|
93
|
-
No fields on this form.
|
|
94
|
-
</Text>
|
|
95
|
-
) : (
|
|
96
|
-
fieldOptions.map(option => (
|
|
97
|
-
<FieldType
|
|
98
|
-
key={option.path}
|
|
99
|
-
label={option.label}
|
|
100
|
-
sampleData={getFieldSampleData(
|
|
101
|
-
option.sampleData,
|
|
102
|
-
option.fieldType,
|
|
103
|
-
)}
|
|
104
|
-
onDragEnd={onDragEnd}
|
|
105
|
-
onDragStart={() => onDragStart(option)}
|
|
106
|
-
/>
|
|
107
|
-
))
|
|
108
|
-
)}
|
|
109
|
-
</div>
|
|
110
|
-
</Fragment>
|
|
111
|
-
) : null}
|
|
112
|
-
</div>
|
|
113
|
-
</div>
|
|
133
|
+
{form.name}
|
|
134
|
+
</Text>
|
|
135
|
+
</Button>
|
|
136
|
+
<div className="dte-field-type-group">
|
|
137
|
+
{isLoading ? (
|
|
138
|
+
<SkeletonRows count={FIELD_SKELETON_ROWS} className="dte-field-type-item" />
|
|
139
|
+
) : fieldOptions.length === 0 ? (
|
|
140
|
+
<Text size="small" subdued variant="body">
|
|
141
|
+
{hasHiddenFields
|
|
142
|
+
? 'No fields match the search.'
|
|
143
|
+
: 'No fields on this form.'}
|
|
144
|
+
</Text>
|
|
145
|
+
) : (
|
|
146
|
+
fieldOptions.map(option => (
|
|
147
|
+
<FieldType
|
|
148
|
+
key={option.path}
|
|
149
|
+
label={option.label}
|
|
150
|
+
sampleData={getFieldSampleData(option.sampleData, option.fieldType)}
|
|
151
|
+
onDragEnd={onDragEnd}
|
|
152
|
+
onDragStart={() => onDragStart(option)}
|
|
153
|
+
/>
|
|
154
|
+
))
|
|
155
|
+
)}
|
|
114
156
|
</div>
|
|
115
|
-
</
|
|
157
|
+
</Fragment>
|
|
116
158
|
);
|
|
117
159
|
};
|