@qoretechnologies/reqraft 0.10.53 → 0.10.54-pr.115.gaf0d7a7
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/.claude/CLAUDE.md +25 -0
- package/.tasks/INDEX.md +1 -0
- package/.tasks/STORY_NETWORK_ISOLATION.md +98 -0
- package/README.md +1 -1
- package/design/FORM_ENGINE_EXPRESSIONS.md +110 -0
- package/design/FORM_ENGINE_FIELD_MIGRATION.md +155 -1
- package/design/RENDER_EXPRESSION_TRANSPORT.md +76 -10
- package/design/SMART_EDITOR_UX.md +21 -0
- package/dist/components/dpqlEditor/DpqlEditor.d.ts.map +1 -1
- package/dist/components/dpqlEditor/DpqlEditor.js +3 -3
- package/dist/components/dpqlEditor/DpqlEditor.js.map +1 -1
- package/dist/components/dpqlEditor/dpqlHelpers.d.ts +6 -0
- package/dist/components/dpqlEditor/dpqlHelpers.d.ts.map +1 -1
- package/dist/components/dpqlEditor/dpqlHelpers.js +21 -2
- package/dist/components/dpqlEditor/dpqlHelpers.js.map +1 -1
- package/dist/components/dpqlEditor/dpqlProbe.d.ts +37 -0
- package/dist/components/dpqlEditor/dpqlProbe.d.ts.map +1 -0
- package/dist/components/dpqlEditor/dpqlProbe.js +203 -0
- package/dist/components/dpqlEditor/dpqlProbe.js.map +1 -0
- package/dist/components/dpqlEditor/dpqlTags.d.ts +8 -0
- package/dist/components/dpqlEditor/dpqlTags.d.ts.map +1 -1
- package/dist/components/dpqlEditor/dpqlTags.js +5 -1
- package/dist/components/dpqlEditor/dpqlTags.js.map +1 -1
- package/dist/components/dpqlEditor/index.d.ts +4 -0
- package/dist/components/dpqlEditor/index.d.ts.map +1 -1
- package/dist/components/dpqlEditor/index.js +5 -1
- package/dist/components/dpqlEditor/index.js.map +1 -1
- package/dist/components/dpqlEditor/types.d.ts +42 -2
- package/dist/components/dpqlEditor/types.d.ts.map +1 -1
- package/dist/components/dpqlEditor/types.js +0 -4
- package/dist/components/dpqlEditor/types.js.map +1 -1
- package/dist/components/dpqlEditor/useDpqlProbe.d.ts +4 -0
- package/dist/components/dpqlEditor/useDpqlProbe.d.ts.map +1 -0
- package/dist/components/dpqlEditor/useDpqlProbe.js +29 -0
- package/dist/components/dpqlEditor/useDpqlProbe.js.map +1 -0
- package/dist/components/dpqlEditor/useDpqlSession.d.ts +5 -2
- package/dist/components/dpqlEditor/useDpqlSession.d.ts.map +1 -1
- package/dist/components/dpqlEditor/useDpqlSession.js +8 -2
- package/dist/components/dpqlEditor/useDpqlSession.js.map +1 -1
- package/dist/components/form/engine/CompactRow.d.ts.map +1 -1
- package/dist/components/form/engine/CompactRow.js +192 -45
- package/dist/components/form/engine/CompactRow.js.map +1 -1
- package/dist/components/form/engine/CompactToolbar.d.ts.map +1 -1
- package/dist/components/form/engine/CompactToolbar.js +5 -2
- package/dist/components/form/engine/CompactToolbar.js.map +1 -1
- package/dist/components/form/engine/FormEngine.d.ts +160 -12
- package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
- package/dist/components/form/engine/FormEngine.js +380 -78
- package/dist/components/form/engine/FormEngine.js.map +1 -1
- package/dist/components/form/engine/FormFieldsSkeleton.d.ts +48 -0
- package/dist/components/form/engine/FormFieldsSkeleton.d.ts.map +1 -0
- package/dist/components/form/engine/FormFieldsSkeleton.js +38 -0
- package/dist/components/form/engine/FormFieldsSkeleton.js.map +1 -0
- package/dist/components/form/engine/OptionFieldMessages.d.ts +17 -2
- package/dist/components/form/engine/OptionFieldMessages.d.ts.map +1 -1
- package/dist/components/form/engine/OptionFieldMessages.js +73 -12
- package/dist/components/form/engine/OptionFieldMessages.js.map +1 -1
- package/dist/components/form/engine/_structuredData/SchemaDataView.d.ts.map +1 -1
- package/dist/components/form/engine/_structuredData/SchemaDataView.js +7 -1
- package/dist/components/form/engine/_structuredData/SchemaDataView.js.map +1 -1
- package/dist/components/form/engine/compactToolbarContext.d.ts +1 -0
- package/dist/components/form/engine/compactToolbarContext.d.ts.map +1 -1
- package/dist/components/form/engine/readFirst.d.ts +42 -1
- package/dist/components/form/engine/readFirst.d.ts.map +1 -1
- package/dist/components/form/engine/readFirst.js +74 -7
- package/dist/components/form/engine/readFirst.js.map +1 -1
- package/dist/components/form/engine/rendererTypes.d.ts +13 -0
- package/dist/components/form/engine/rendererTypes.d.ts.map +1 -1
- package/dist/components/form/engine/rendererTypes.js.map +1 -1
- package/dist/components/form/engine/rowMenuContext.d.ts +78 -0
- package/dist/components/form/engine/rowMenuContext.d.ts.map +1 -0
- package/dist/components/form/engine/rowMenuContext.js +103 -0
- package/dist/components/form/engine/rowMenuContext.js.map +1 -0
- package/dist/components/form/engine/typeChoices.d.ts +42 -0
- package/dist/components/form/engine/typeChoices.d.ts.map +1 -0
- package/dist/components/form/engine/typeChoices.js +31 -0
- package/dist/components/form/engine/typeChoices.js.map +1 -0
- package/dist/components/form/expressions/DpqlRendering.d.ts +26 -0
- package/dist/components/form/expressions/DpqlRendering.d.ts.map +1 -0
- package/dist/components/form/expressions/DpqlRendering.js +53 -0
- package/dist/components/form/expressions/DpqlRendering.js.map +1 -0
- package/dist/components/form/expressions/ExpressionField.d.ts +7 -1
- package/dist/components/form/expressions/ExpressionField.d.ts.map +1 -1
- package/dist/components/form/expressions/ExpressionField.js +203 -45
- package/dist/components/form/expressions/ExpressionField.js.map +1 -1
- package/dist/components/form/expressions/argumentPresence.d.ts +97 -0
- package/dist/components/form/expressions/argumentPresence.d.ts.map +1 -0
- package/dist/components/form/expressions/argumentPresence.js +203 -0
- package/dist/components/form/expressions/argumentPresence.js.map +1 -0
- package/dist/components/form/expressions/builder/argumentLabel.d.ts.map +1 -1
- package/dist/components/form/expressions/builder/argumentLabel.js +9 -4
- package/dist/components/form/expressions/builder/argumentLabel.js.map +1 -1
- package/dist/components/form/expressions/builder/index.d.ts +15 -2
- package/dist/components/form/expressions/builder/index.d.ts.map +1 -1
- package/dist/components/form/expressions/builder/index.js +20 -58
- package/dist/components/form/expressions/builder/index.js.map +1 -1
- package/dist/components/form/expressions/builder/renderTemplate.d.ts +4 -1
- package/dist/components/form/expressions/builder/renderTemplate.d.ts.map +1 -1
- package/dist/components/form/expressions/builder/renderTemplate.js +10 -67
- package/dist/components/form/expressions/builder/renderTemplate.js.map +1 -1
- package/dist/components/form/expressions/dpqlMockLanguage.d.ts +76 -0
- package/dist/components/form/expressions/dpqlMockLanguage.d.ts.map +1 -0
- package/dist/components/form/expressions/dpqlMockLanguage.js +525 -0
- package/dist/components/form/expressions/dpqlMockLanguage.js.map +1 -0
- package/dist/components/form/expressions/dpqlMockLsp.d.ts +31 -2
- package/dist/components/form/expressions/dpqlMockLsp.d.ts.map +1 -1
- package/dist/components/form/expressions/dpqlMockLsp.js +86 -80
- package/dist/components/form/expressions/dpqlMockLsp.js.map +1 -1
- package/dist/components/form/expressions/mockExpressions.d.ts.map +1 -1
- package/dist/components/form/expressions/mockExpressions.js +8 -4
- package/dist/components/form/expressions/mockExpressions.js.map +1 -1
- package/dist/components/form/expressions/renderExpressionToText.d.ts +1 -1
- package/dist/components/form/expressions/renderExpressionToText.d.ts.map +1 -1
- package/dist/components/form/expressions/renderExpressionToText.js +95 -19
- package/dist/components/form/expressions/renderExpressionToText.js.map +1 -1
- package/dist/components/form/expressions/types.d.ts +7 -0
- package/dist/components/form/expressions/types.d.ts.map +1 -1
- package/dist/components/form/expressions/useRenderExpression.d.ts +7 -16
- package/dist/components/form/expressions/useRenderExpression.d.ts.map +1 -1
- package/dist/components/form/expressions/useRenderExpression.js +50 -126
- package/dist/components/form/expressions/useRenderExpression.js.map +1 -1
- package/dist/components/form/fields/Field.d.ts.map +1 -1
- package/dist/components/form/fields/Field.js +3 -1
- package/dist/components/form/fields/Field.js.map +1 -1
- package/dist/components/form/fields/allowed-values/AllowedValues.d.ts +11 -1
- package/dist/components/form/fields/allowed-values/AllowedValues.d.ts.map +1 -1
- package/dist/components/form/fields/allowed-values/AllowedValues.js +14 -2
- package/dist/components/form/fields/allowed-values/AllowedValues.js.map +1 -1
- package/dist/components/form/fields/auto/AutoFormField.d.ts.map +1 -1
- package/dist/components/form/fields/auto/AutoFormField.js +91 -28
- package/dist/components/form/fields/auto/AutoFormField.js.map +1 -1
- package/dist/components/form/fields/date/Date.d.ts +1 -1
- package/dist/components/form/fields/date/Date.d.ts.map +1 -1
- package/dist/components/form/fields/date/Date.js +16 -2
- package/dist/components/form/fields/date/Date.js.map +1 -1
- package/dist/components/form/fields/long-string/LongString.d.ts.map +1 -1
- package/dist/components/form/fields/long-string/LongString.js +14 -2
- package/dist/components/form/fields/long-string/LongString.js.map +1 -1
- package/dist/components/form/fields/rich-text/RichText.d.ts +35 -2
- package/dist/components/form/fields/rich-text/RichText.d.ts.map +1 -1
- package/dist/components/form/fields/rich-text/RichText.js +153 -12
- package/dist/components/form/fields/rich-text/RichText.js.map +1 -1
- package/dist/components/form/fields/template/ReadOnlyTemplateTag.d.ts +2 -1
- package/dist/components/form/fields/template/ReadOnlyTemplateTag.d.ts.map +1 -1
- package/dist/components/form/fields/template/ReadOnlyTemplateTag.js +3 -2
- package/dist/components/form/fields/template/ReadOnlyTemplateTag.js.map +1 -1
- package/dist/components/form/fields/template/TemplateField.d.ts +34 -2
- package/dist/components/form/fields/template/TemplateField.d.ts.map +1 -1
- package/dist/components/form/fields/template/TemplateField.js +604 -58
- package/dist/components/form/fields/template/TemplateField.js.map +1 -1
- package/dist/components/form/index.d.ts +1 -0
- package/dist/components/form/index.d.ts.map +1 -1
- package/dist/components/form/index.js +1 -0
- package/dist/components/form/index.js.map +1 -1
- package/dist/components/smartEditor/SmartEditor.d.ts.map +1 -1
- package/dist/components/smartEditor/SmartEditor.js +29 -8
- package/dist/components/smartEditor/SmartEditor.js.map +1 -1
- package/dist/components/smartEditor/__fixtures__/mockLspServer.d.ts +8 -0
- package/dist/components/smartEditor/__fixtures__/mockLspServer.d.ts.map +1 -1
- package/dist/components/smartEditor/__fixtures__/mockLspServer.js +53 -10
- package/dist/components/smartEditor/__fixtures__/mockLspServer.js.map +1 -1
- package/dist/components/smartEditor/useLspSession.d.ts +5 -1
- package/dist/components/smartEditor/useLspSession.d.ts.map +1 -1
- package/dist/components/smartEditor/useLspSession.js +16 -2
- package/dist/components/smartEditor/useLspSession.js.map +1 -1
- package/dist/helpers/dpqlDetection.d.ts +58 -0
- package/dist/helpers/dpqlDetection.d.ts.map +1 -0
- package/dist/helpers/dpqlDetection.js +164 -0
- package/dist/helpers/dpqlDetection.js.map +1 -0
- package/dist/helpers/optionUiTypes.d.ts +19 -3
- package/dist/helpers/optionUiTypes.d.ts.map +1 -1
- package/dist/helpers/optionUiTypes.js +20 -1
- package/dist/helpers/optionUiTypes.js.map +1 -1
- package/dist/helpers/options.d.ts +37 -0
- package/dist/helpers/options.d.ts.map +1 -1
- package/dist/helpers/options.js +78 -1
- package/dist/helpers/options.js.map +1 -1
- package/dist/helpers/templateItems.d.ts +47 -0
- package/dist/helpers/templateItems.d.ts.map +1 -0
- package/dist/helpers/templateItems.js +88 -0
- package/dist/helpers/templateItems.js.map +1 -0
- package/dist/helpers/templateText.d.ts +37 -0
- package/dist/helpers/templateText.d.ts.map +1 -0
- package/dist/helpers/templateText.js +64 -0
- package/dist/helpers/templateText.js.map +1 -0
- package/dist/helpers/templates.d.ts +19 -11
- package/dist/helpers/templates.d.ts.map +1 -1
- package/dist/helpers/templates.js +46 -29
- package/dist/helpers/templates.js.map +1 -1
- package/dist/helpers/validations.d.ts.map +1 -1
- package/dist/helpers/validations.js +56 -9
- package/dist/helpers/validations.js.map +1 -1
- package/dist/hooks/useArgSchema.d.ts +2 -1
- package/dist/hooks/useArgSchema.d.ts.map +1 -1
- package/dist/hooks/useFetch/useFetch.d.ts +3 -1
- package/dist/hooks/useFetch/useFetch.d.ts.map +1 -1
- package/dist/hooks/useFetch/useFetch.js +47 -9
- package/dist/hooks/useFetch/useFetch.js.map +1 -1
- package/dist/hooks/useTemplates.d.ts +20 -0
- package/dist/hooks/useTemplates.d.ts.map +1 -1
- package/dist/hooks/useTemplates.js +34 -2
- package/dist/hooks/useTemplates.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/stories/storyNetwork.d.ts +36 -0
- package/dist/stories/storyNetwork.d.ts.map +1 -0
- package/dist/stories/storyNetwork.js +82 -0
- package/dist/stories/storyNetwork.js.map +1 -0
- package/dist/utils/fetch.d.ts +151 -1
- package/dist/utils/fetch.d.ts.map +1 -1
- package/dist/utils/fetch.js +256 -44
- package/dist/utils/fetch.js.map +1 -1
- package/dist/utils/lspClient.d.ts +6 -0
- package/dist/utils/lspClient.d.ts.map +1 -1
- package/dist/utils/lspClient.js +62 -11
- package/dist/utils/lspClient.js.map +1 -1
- package/dist/utils/websocket.d.ts +6 -0
- package/dist/utils/websocket.d.ts.map +1 -1
- package/dist/utils/websocket.js +15 -7
- package/dist/utils/websocket.js.map +1 -1
- package/package.json +2 -2
- package/src/components/dpqlEditor/DpqlEditor.stories.tsx +3 -119
- package/src/components/dpqlEditor/DpqlEditor.tsx +5 -2
- package/src/components/dpqlEditor/dpqlHelpers.ts +20 -1
- package/src/components/dpqlEditor/dpqlProbe.ts +165 -0
- package/src/components/dpqlEditor/dpqlTags.ts +13 -0
- package/src/components/dpqlEditor/index.ts +4 -0
- package/src/components/dpqlEditor/types.ts +38 -2
- package/src/components/dpqlEditor/useDpqlProbe.ts +33 -0
- package/src/components/dpqlEditor/useDpqlSession.ts +20 -4
- package/src/components/form/engine/CompactRow.tsx +240 -51
- package/src/components/form/engine/CompactToolbar.tsx +10 -3
- package/src/components/form/engine/FormEngine.stories.tsx +431 -124
- package/src/components/form/engine/FormEngine.tsx +508 -90
- package/src/components/form/engine/FormFieldsSkeleton.tsx +76 -0
- package/src/components/form/engine/OptionFieldMessages.tsx +103 -12
- package/src/components/form/engine/_structuredData/SchemaDataView.tsx +11 -0
- package/src/components/form/engine/compactToolbarContext.ts +1 -1
- package/src/components/form/engine/readFirst.ts +86 -7
- package/src/components/form/engine/rendererTypes.ts +12 -0
- package/src/components/form/engine/rowMenuContext.ts +159 -0
- package/src/components/form/engine/typeChoices.ts +61 -0
- package/src/components/form/expressions/DpqlRendering.tsx +85 -0
- package/src/components/form/expressions/ExpressionField.stories.tsx +826 -48
- package/src/components/form/expressions/ExpressionField.tsx +266 -54
- package/src/components/form/expressions/argumentPresence.ts +200 -0
- package/src/components/form/expressions/builder/ExpressionBuilder.stories.tsx +31 -14
- package/src/components/form/expressions/builder/argumentLabel.tsx +10 -4
- package/src/components/form/expressions/builder/index.tsx +39 -77
- package/src/components/form/expressions/builder/renderTemplate.tsx +23 -56
- package/src/components/form/expressions/dpqlMockLanguage.ts +565 -0
- package/src/components/form/expressions/dpqlMockLsp.ts +109 -83
- package/src/components/form/expressions/mockExpressions.ts +8 -4
- package/src/components/form/expressions/renderExpressionToText.ts +112 -22
- package/src/components/form/expressions/types.ts +7 -0
- package/src/components/form/expressions/useRenderExpression.ts +43 -127
- package/src/components/form/fields/Field.tsx +3 -2
- package/src/components/form/fields/allowed-values/AllowedValues.tsx +13 -2
- package/src/components/form/fields/auto/AutoFormField.stories.tsx +85 -18
- package/src/components/form/fields/auto/AutoFormField.tsx +90 -20
- package/src/components/form/fields/date/Date.tsx +17 -1
- package/src/components/form/fields/long-string/LongString.tsx +15 -3
- package/src/components/form/fields/rich-text/RichText.tsx +235 -30
- package/src/components/form/fields/template/ReadOnlyTemplateTag.tsx +4 -2
- package/src/components/form/fields/template/TemplateField.stories.tsx +93 -14
- package/src/components/form/fields/template/TemplateField.tsx +823 -129
- package/src/components/form/index.tsx +1 -0
- package/src/components/log/Log.stories.tsx +2 -1
- package/src/components/smartEditor/SmartEditor.tsx +41 -9
- package/src/components/smartEditor/__fixtures__/mockLspServer.ts +52 -3
- package/src/components/smartEditor/useLspSession.ts +21 -3
- package/src/helpers/dpqlDetection.ts +209 -0
- package/src/helpers/optionUiTypes.ts +22 -0
- package/src/helpers/options.ts +70 -0
- package/src/helpers/templateItems.ts +81 -0
- package/src/helpers/templateText.ts +92 -0
- package/src/helpers/templates.ts +47 -37
- package/src/helpers/validations.ts +59 -9
- package/src/hooks/useFetch/useFetch.stories.tsx +6 -5
- package/src/hooks/useFetch/useFetch.tsx +53 -7
- package/src/hooks/useStorage/useStorage.stories.tsx +2 -1
- package/src/hooks/useTemplates.ts +41 -0
- package/src/hooks/useWebSocket/useWebsocket.stories.tsx +2 -1
- package/src/index.tsx +14 -1
- package/src/stores/currentUser/currentUser.stories.tsx +2 -1
- package/src/stories/storyNetwork.ts +92 -0
- package/src/utils/fetch.ts +311 -21
- package/src/utils/lspClient.ts +68 -11
- package/src/utils/websocket.ts +20 -9
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
// Copyright 2026 Qore Technologies, s.r.o.
|
|
2
|
+
// A string holding template references, as the nodes a rich-text editor draws.
|
|
3
|
+
//
|
|
4
|
+
// A field whose value is a plain string can still hold references —
|
|
5
|
+
// `Dear $local:name` — and those should read as the names they were chosen by,
|
|
6
|
+
// not as their spelling. These convert between the stored string and the
|
|
7
|
+
// editor's paragraphs, where each reference is a chip, so the editor can draw
|
|
8
|
+
// chips while the field keeps storing exactly the string it always stored.
|
|
9
|
+
import { IReqoreFormTemplates } from '@qoretechnologies/reqore/dist/components/Textarea';
|
|
10
|
+
import { getTagLabel } from '../components/dpqlEditor/dpqlHelpers';
|
|
11
|
+
import { describeTemplateReference, TEMPLATE_TOKEN_SOURCE } from './templates';
|
|
12
|
+
|
|
13
|
+
/* Stated here rather than borrowed from Reqore's editor props: those resolve
|
|
14
|
+
to Reqore's `CustomElement`, which it does not export, so the published
|
|
15
|
+
declarations could not name this module's types (TS4023 on `yarn build`). */
|
|
16
|
+
|
|
17
|
+
/** A run of text. */
|
|
18
|
+
export interface ITemplateTextLeaf {
|
|
19
|
+
text: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** A template reference, drawn as a chip. */
|
|
23
|
+
export interface ITemplateTextTag {
|
|
24
|
+
type: 'tag';
|
|
25
|
+
value: string;
|
|
26
|
+
label: string;
|
|
27
|
+
metadata?: unknown;
|
|
28
|
+
children: ITemplateTextLeaf[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** One line of the string. */
|
|
32
|
+
export interface ITemplateTextParagraph {
|
|
33
|
+
type: 'paragraph';
|
|
34
|
+
children: Array<ITemplateTextLeaf | ITemplateTextTag>;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* What a reference chip says: the name the catalogue gives the reference, else
|
|
39
|
+
* the reference as the DPQL Text view labels it (`local: name`), so the same
|
|
40
|
+
* reference reads alike in the Visual builder and the Text view.
|
|
41
|
+
*/
|
|
42
|
+
export const templateChipLabel = (templates: IReqoreFormTemplates | undefined, value: string): string => {
|
|
43
|
+
const { label, item } = describeTemplateReference(templates, value);
|
|
44
|
+
return item ? label : getTagLabel(value);
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* `text` as paragraphs, one per line, with each template reference a chip.
|
|
49
|
+
*
|
|
50
|
+
* A chip is an inline VOID and holds no text of its own, so a cursor can only
|
|
51
|
+
* sit in the text nodes around it; one is kept on each side even when empty.
|
|
52
|
+
* The chip's label is `templateChipLabel`.
|
|
53
|
+
*/
|
|
54
|
+
export const templateTextToNodes = (
|
|
55
|
+
text: string,
|
|
56
|
+
templates?: IReqoreFormTemplates
|
|
57
|
+
): ITemplateTextParagraph[] =>
|
|
58
|
+
(text ?? '').split('\n').map((line) => {
|
|
59
|
+
const children: ITemplateTextParagraph['children'] = [];
|
|
60
|
+
// A fresh expression per line: a shared /g instance carries `lastIndex`.
|
|
61
|
+
const pattern = new RegExp(TEMPLATE_TOKEN_SOURCE, 'g');
|
|
62
|
+
let last = 0;
|
|
63
|
+
let match: RegExpExecArray | null;
|
|
64
|
+
while ((match = pattern.exec(line)) !== null) {
|
|
65
|
+
const { item } = describeTemplateReference(templates, match[0]);
|
|
66
|
+
children.push({ text: line.slice(last, match.index) });
|
|
67
|
+
children.push({
|
|
68
|
+
type: 'tag',
|
|
69
|
+
value: match[0],
|
|
70
|
+
label: templateChipLabel(templates, match[0]),
|
|
71
|
+
metadata: item?.metadata,
|
|
72
|
+
children: [{ text: '' }],
|
|
73
|
+
});
|
|
74
|
+
last = match.index + match[0].length;
|
|
75
|
+
}
|
|
76
|
+
children.push({ text: line.slice(last) });
|
|
77
|
+
return { type: 'paragraph' as const, children };
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
/** The string the paragraphs stand for: each chip written as its reference. */
|
|
81
|
+
export const templateNodesToText = (nodes?: ReadonlyArray<{ children?: unknown[] }>): string =>
|
|
82
|
+
(nodes ?? [])
|
|
83
|
+
.map((paragraph: any) =>
|
|
84
|
+
(paragraph?.children ?? [])
|
|
85
|
+
.map((node: any) =>
|
|
86
|
+
typeof node?.text === 'string' ? node.text
|
|
87
|
+
: node?.type === 'tag' ? String(node.value ?? '')
|
|
88
|
+
: ''
|
|
89
|
+
)
|
|
90
|
+
.join('')
|
|
91
|
+
)
|
|
92
|
+
.join('\n');
|
package/src/helpers/templates.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { modalStore } from '@qoretechnologies/reqore';
|
|
2
|
+
import { TReqoreTooltipProp } from '@qoretechnologies/reqore/dist/types/global';
|
|
2
3
|
import { IReqoreFormTemplates } from '@qoretechnologies/reqore/dist/components/Textarea';
|
|
3
4
|
import {
|
|
4
5
|
TReqoreDropdownItem,
|
|
@@ -20,6 +21,24 @@ import { areQorusTypesCompatible } from './expressions';
|
|
|
20
21
|
*/
|
|
21
22
|
export const TEMPLATE_EXAMPLE_PREVIEW_LENGTH = 150;
|
|
22
23
|
|
|
24
|
+
/**
|
|
25
|
+
* How template picker entries are ordered: by the label a reader actually sees.
|
|
26
|
+
*
|
|
27
|
+
* The server sends groups and their items in whatever order it assembled them —
|
|
28
|
+
* for the test vocabulary that is step order, for an app's actions it is the
|
|
29
|
+
* connector's own. Neither is an order a reader can predict, and a picker
|
|
30
|
+
* holding 29 items is navigated by looking for a NAME.
|
|
31
|
+
*
|
|
32
|
+
* `localeCompare` with `numeric` so `item2` sorts before `item10`, and
|
|
33
|
+
* `sensitivity: 'base'` so case and accents do not split otherwise-adjacent
|
|
34
|
+
* neighbours.
|
|
35
|
+
*/
|
|
36
|
+
const byLabel = (a: { label?: unknown }, b: { label?: unknown }): number =>
|
|
37
|
+
String(a?.label ?? '').localeCompare(String(b?.label ?? ''), undefined, {
|
|
38
|
+
numeric: true,
|
|
39
|
+
sensitivity: 'base',
|
|
40
|
+
});
|
|
41
|
+
|
|
23
42
|
/**
|
|
24
43
|
* Shared template string utilities.
|
|
25
44
|
* Used by both validations and TemplateField.
|
|
@@ -258,43 +277,34 @@ export const describeTemplateReference = (
|
|
|
258
277
|
return resolved.item ? resolved : { label: getTemplateReferencePath(value) };
|
|
259
278
|
};
|
|
260
279
|
|
|
261
|
-
const EMBEDDED_TEMPLATE_TOKEN = new RegExp(TEMPLATE_TOKEN_SOURCE, 'g');
|
|
262
|
-
|
|
263
|
-
export type TTemplateTextSegment =
|
|
264
|
-
| { kind: 'text'; text: string }
|
|
265
|
-
| { kind: 'token'; text: string };
|
|
266
|
-
|
|
267
280
|
/**
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
*
|
|
281
|
+
* What a chosen template's hover should say.
|
|
282
|
+
*
|
|
283
|
+
* The reference itself — `$._case.title`, `$data:{dc_ai_reply.choices[0]…}` —
|
|
284
|
+
* is an implementation detail of the grammar, and it was what every template
|
|
285
|
+
* chip put in its tooltip. An author hovering a chip wants to know WHAT VALUE
|
|
286
|
+
* this is, and the catalogue already carries that: the entry's `description`
|
|
287
|
+
* is the prose the picker lists it under, written from the schema that declares
|
|
288
|
+
* it, with its type appended.
|
|
289
|
+
*
|
|
290
|
+
* So the hover reads as a small card — the value's name as the title, its
|
|
291
|
+
* description as the body — and the path stays where it is genuinely needed:
|
|
292
|
+
* the field's own help, which teaches the grammar with a worked example, and
|
|
293
|
+
* the picker, for the walks no list can hold.
|
|
294
|
+
*
|
|
295
|
+
* Falls back to the resolved label (itself the stripped path when the catalogue
|
|
296
|
+
* explains nothing), because a surface with no catalogue must still say what
|
|
297
|
+
* the value is rather than nothing at all.
|
|
271
298
|
*/
|
|
272
|
-
export const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
// calls and would skip tokens on the second string it is given.
|
|
282
|
-
const pattern = new RegExp(EMBEDDED_TEMPLATE_TOKEN.source, 'g');
|
|
283
|
-
let match: RegExpExecArray | null;
|
|
284
|
-
|
|
285
|
-
while ((match = pattern.exec(text)) !== null) {
|
|
286
|
-
if (match.index > lastIndex) {
|
|
287
|
-
segments.push({ kind: 'text', text: text.slice(lastIndex, match.index) });
|
|
288
|
-
}
|
|
289
|
-
segments.push({ kind: 'token', text: match[0] });
|
|
290
|
-
lastIndex = match.index + match[0].length;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
if (lastIndex < text.length) {
|
|
294
|
-
segments.push({ kind: 'text', text: text.slice(lastIndex) });
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
return segments;
|
|
299
|
+
export const templateTooltip = (
|
|
300
|
+
templates: IReqoreFormTemplates | undefined,
|
|
301
|
+
value: string
|
|
302
|
+
): TReqoreTooltipProp => {
|
|
303
|
+
const { label, item } = describeTemplateReference(templates, value);
|
|
304
|
+
const description = (item as { description?: unknown } | undefined)?.description;
|
|
305
|
+
return typeof description === 'string' && description.trim() ?
|
|
306
|
+
{ title: label, content: description, maxWidth: '360px' }
|
|
307
|
+
: label || value;
|
|
298
308
|
};
|
|
299
309
|
|
|
300
310
|
// Ported verbatim from qorus-ide `helpers/functions.tsx` (FIELD_STACK_REPORT
|
|
@@ -506,7 +516,7 @@ export const buildTemplates = (
|
|
|
506
516
|
|
|
507
517
|
return item;
|
|
508
518
|
}
|
|
509
|
-
);
|
|
519
|
+
).sort(byLabel);
|
|
510
520
|
|
|
511
521
|
return {
|
|
512
522
|
items: Object.values(templates).map(
|
|
@@ -544,6 +554,6 @@ export const buildTemplates = (
|
|
|
544
554
|
data_role
|
|
545
555
|
),
|
|
546
556
|
})
|
|
547
|
-
),
|
|
557
|
+
).sort(byLabel),
|
|
548
558
|
} as IReqoreFormTemplates;
|
|
549
559
|
};
|
|
@@ -1064,14 +1064,39 @@ export const _validateField = (
|
|
|
1064
1064
|
}
|
|
1065
1065
|
}
|
|
1066
1066
|
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1067
|
+
/* A FALSY value is not an ABSENT one. `null`, `0`, `false` and `""` are
|
|
1068
|
+
all values an author may hold in an `auto`/`any` field — a test
|
|
1069
|
+
assertion expecting a count of zero, a flag that is off, or a blank
|
|
1070
|
+
string — and the truthiness gate that used to stand here refused every
|
|
1071
|
+
one of them as *"Value is empty"*. It surfaced on an assertion's
|
|
1072
|
+
Expected Value holding `null`: opening it reported *"Value for argument
|
|
1073
|
+
1 ("any") is invalid: Value is empty"* on the value the author had
|
|
1074
|
+
deliberately chosen. Only `undefined` is actually empty.
|
|
1075
|
+
|
|
1076
|
+
`null` and `""` are answered here rather than delegated, for two
|
|
1077
|
+
different reasons: `getTypeFromValue(null)` answers `auto`, which would
|
|
1078
|
+
re-enter this very branch, and the `string` validator rejects `""`
|
|
1079
|
+
because that is the rule for a REQUIRED text field — a different
|
|
1080
|
+
question from whether an untyped field may hold a blank string. */
|
|
1081
|
+
if (parsedData === undefined) {
|
|
1082
|
+
return invalidResult('Value is empty');
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
if (parsedData === null || parsedData === '') {
|
|
1086
|
+
return validResult();
|
|
1072
1087
|
}
|
|
1073
1088
|
|
|
1074
|
-
|
|
1089
|
+
/* Validate what was PARSED, against the type detected from the same
|
|
1090
|
+
parse. Validating the raw `value` instead made the two disagree
|
|
1091
|
+
whenever parsing changed the shape: typing `1` into an `auto` field
|
|
1092
|
+
parsed to the number 1, detected `int`, and then validated the STRING
|
|
1093
|
+
"1" against it — "Value must be an integer", on a value that is one.
|
|
1094
|
+
The author saw their field go invalid on the first character they
|
|
1095
|
+
typed. */
|
|
1096
|
+
return withContext(
|
|
1097
|
+
validateFieldWithResult(getTypeFromValue(parsedData), parsedData),
|
|
1098
|
+
'Auto-detected type is invalid'
|
|
1099
|
+
);
|
|
1075
1100
|
}
|
|
1076
1101
|
case 'processor': {
|
|
1077
1102
|
if (!value || !value['processor-input-type'] || !value['processor-output-type']) {
|
|
@@ -1512,6 +1537,31 @@ export const _validateField = (
|
|
|
1512
1537
|
const argDefinition = expressionDefinition.varargs
|
|
1513
1538
|
? expressionDefinition.args[0]
|
|
1514
1539
|
: expressionDefinition.args[index];
|
|
1540
|
+
/* The argument's name for the message.
|
|
1541
|
+
|
|
1542
|
+
`display_name` is what a catalogue MAY carry; the served one carries
|
|
1543
|
+
`name`, so every message about an argument read "argument 1
|
|
1544
|
+
("undefined") is invalid" and named a field that does not exist. */
|
|
1545
|
+
const argLabel =
|
|
1546
|
+
argDefinition?.display_name ?? argDefinition?.name ?? `argument ${index + 1}`;
|
|
1547
|
+
|
|
1548
|
+
/* An explicit null is a VALUE, and DPQL says so: `null` is a literal
|
|
1549
|
+
that parses, serializes and round-trips like any other.
|
|
1550
|
+
|
|
1551
|
+
Treated as a missing one, it made "this returns no value"
|
|
1552
|
+
unsayable. Typing `null` into the text editor produces the literal
|
|
1553
|
+
expression `{exp: "value", args: [null]}`; the visual view then
|
|
1554
|
+
called it invalid and refused to render a summary, and a raw null
|
|
1555
|
+
argument crashed the validator outright on `argValue.type`. Reported
|
|
1556
|
+
from the live IDE by an author with no other way to assert that a
|
|
1557
|
+
service method returns nothing.
|
|
1558
|
+
|
|
1559
|
+
Only an EXPLICIT null counts. An argument that is simply absent, or
|
|
1560
|
+
an envelope holding `undefined`, is still missing — which is the
|
|
1561
|
+
distinction the author is making when they write it. */
|
|
1562
|
+
if (argValue === null || (isObject(argValue) && (argValue as any).value === null)) {
|
|
1563
|
+
continue;
|
|
1564
|
+
}
|
|
1515
1565
|
|
|
1516
1566
|
if (!argValue?.value && !argDefinition?.required) {
|
|
1517
1567
|
continue;
|
|
@@ -1526,14 +1576,14 @@ export const _validateField = (
|
|
|
1526
1576
|
if (!result.isValid) {
|
|
1527
1577
|
return withContext(
|
|
1528
1578
|
result,
|
|
1529
|
-
`Sub-expression for argument ${index + 1} ("${
|
|
1579
|
+
`Sub-expression for argument ${index + 1} ("${argLabel}") is invalid`
|
|
1530
1580
|
);
|
|
1531
1581
|
}
|
|
1532
1582
|
|
|
1533
1583
|
continue;
|
|
1534
1584
|
}
|
|
1535
1585
|
|
|
1536
|
-
const result = validateFieldWithResult(argValue
|
|
1586
|
+
const result = validateFieldWithResult(argValue?.type, argValue?.value, {
|
|
1537
1587
|
expressions,
|
|
1538
1588
|
allowed_values: argDefinition?.allowed_values,
|
|
1539
1589
|
element_allowed_values: argDefinition?.element_allowed_values,
|
|
@@ -1543,7 +1593,7 @@ export const _validateField = (
|
|
|
1543
1593
|
if (!result.isValid) {
|
|
1544
1594
|
return withContext(
|
|
1545
1595
|
result,
|
|
1546
|
-
`Value for argument ${index + 1} ("${
|
|
1596
|
+
`Value for argument ${index + 1} ("${argLabel}") is invalid`
|
|
1547
1597
|
);
|
|
1548
1598
|
}
|
|
1549
1599
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ReqoreSpinner, ReqoreTree } from '@qoretechnologies/reqore';
|
|
2
2
|
import { StoryObj } from '@storybook/react-vite';
|
|
3
3
|
import { testsWaitForText } from '../../../__tests__/utils';
|
|
4
|
+
import { storyApiUrl } from '../../stories/storyNetwork';
|
|
4
5
|
import { StoryMeta } from '../../types';
|
|
5
6
|
import { useFetch } from './useFetch';
|
|
6
7
|
|
|
@@ -44,7 +45,7 @@ export const Get: Story = {
|
|
|
44
45
|
mockData: [
|
|
45
46
|
{
|
|
46
47
|
// An array of mock objects which will add in every story
|
|
47
|
-
url: '
|
|
48
|
+
url: storyApiUrl('public/info'),
|
|
48
49
|
method: 'GET',
|
|
49
50
|
status: 200,
|
|
50
51
|
delay: 200,
|
|
@@ -81,7 +82,7 @@ export const Put: Story = {
|
|
|
81
82
|
mockData: [
|
|
82
83
|
{
|
|
83
84
|
// An array of mock objects which will add in every story
|
|
84
|
-
url: '
|
|
85
|
+
url: storyApiUrl('public/info'),
|
|
85
86
|
method: 'PUT',
|
|
86
87
|
status: 200,
|
|
87
88
|
response: {
|
|
@@ -109,7 +110,7 @@ export const Post: Story = {
|
|
|
109
110
|
mockData: [
|
|
110
111
|
{
|
|
111
112
|
// An array of mock objects which will add in every story
|
|
112
|
-
url: '
|
|
113
|
+
url: storyApiUrl('public/info'),
|
|
113
114
|
method: 'POST',
|
|
114
115
|
status: 200,
|
|
115
116
|
response: {
|
|
@@ -137,7 +138,7 @@ export const Del: Story = {
|
|
|
137
138
|
mockData: [
|
|
138
139
|
{
|
|
139
140
|
// An array of mock objects which will add in every story
|
|
140
|
-
url: '
|
|
141
|
+
url: storyApiUrl('public/info'),
|
|
141
142
|
method: 'DELETE',
|
|
142
143
|
status: 200,
|
|
143
144
|
response: {
|
|
@@ -165,7 +166,7 @@ export const Error409: Story = {
|
|
|
165
166
|
mockData: [
|
|
166
167
|
{
|
|
167
168
|
// An array of mock objects which will add in every story
|
|
168
|
-
url: '
|
|
169
|
+
url: storyApiUrl('public/info'),
|
|
169
170
|
method: 'POST',
|
|
170
171
|
status: 409,
|
|
171
172
|
response: {
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { QueryClient, useQueryClient } from '@tanstack/react-query';
|
|
1
2
|
import { useCallback, useMemo, useState } from 'react';
|
|
2
3
|
import { useEffectOnce } from 'react-use';
|
|
3
4
|
import { useContextSelector } from 'use-context-selector';
|
|
4
5
|
import { FetchContext } from '../../contexts/FetchContext';
|
|
5
|
-
import {
|
|
6
|
+
import { ReqraftQueryClient } from '../../providers/ReqraftProvider';
|
|
7
|
+
import { IReqraftQueryConfig, reqraftCacheKey } from '../../utils/fetch';
|
|
6
8
|
|
|
7
9
|
export interface IReqraftUseFetch<T> {
|
|
8
10
|
data: T | undefined;
|
|
@@ -41,8 +43,40 @@ export function useFetch<T>({
|
|
|
41
43
|
}
|
|
42
44
|
});
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
/* An answer we ALREADY HAVE is not a loading state.
|
|
47
|
+
`loading` started at `loadOnMount` unconditionally, so every consumer that
|
|
48
|
+
mounted reported "loading" for at least one async turn even when the
|
|
49
|
+
response was sitting in the cache — and `FormEngine` renders its
|
|
50
|
+
`options-loading-skeleton` whenever any of its sources says that. So each
|
|
51
|
+
form appearing on a page flashed a skeleton whether or not anything was
|
|
52
|
+
actually fetched, which is what the repeated skeletons on an IDE page are:
|
|
53
|
+
measured on a fully warm SECOND visit to one page, three skeleton waves
|
|
54
|
+
against two requests, the last wave with no request anywhere near it.
|
|
55
|
+
Seeded from the query cache instead, so a resource already in hand renders
|
|
56
|
+
immediately. */
|
|
57
|
+
/* The provider is OPTIONAL here. `useQueryClient` THROWS when no
|
|
58
|
+
`QueryClientProvider` is above it, and plenty of consumers mount a form
|
|
59
|
+
with only a `FetchContext` — requiring one turned a rendering optimisation
|
|
60
|
+
into a hard dependency and took 131 tests down with it. `useContext` runs
|
|
61
|
+
before that throw, so hook order is unaffected and catching is safe. The
|
|
62
|
+
fallback is the same client `query` itself defaults to, so a cache hit is
|
|
63
|
+
found either way. */
|
|
64
|
+
let queryClient: QueryClient;
|
|
65
|
+
try {
|
|
66
|
+
queryClient = useQueryClient();
|
|
67
|
+
} catch {
|
|
68
|
+
queryClient = ReqraftQueryClient;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const cached = queryClient.getQueryData([
|
|
72
|
+
reqraftCacheKey({ url, method, body, noApiPrefix }),
|
|
73
|
+
]) as { data?: T; ok?: boolean } | undefined;
|
|
74
|
+
const hasCachedAnswer = !!cached && cached.ok !== false && cached.data !== undefined;
|
|
75
|
+
|
|
76
|
+
const [loading, setLoading] = useState(!!loadOnMount && !hasCachedAnswer);
|
|
77
|
+
const [data, setData] = useState<T | undefined>(
|
|
78
|
+
hasCachedAnswer ? (cached?.data as T) : defaultData
|
|
79
|
+
);
|
|
46
80
|
const [response, setResponse] = useState<Response | undefined>();
|
|
47
81
|
const [error, setError] = useState<Error | undefined>();
|
|
48
82
|
const [errorData, setErrorData] = useState<any>();
|
|
@@ -51,13 +85,23 @@ export function useFetch<T>({
|
|
|
51
85
|
async ({
|
|
52
86
|
body: customBody,
|
|
53
87
|
mergeBodies,
|
|
54
|
-
|
|
55
|
-
|
|
88
|
+
silent,
|
|
89
|
+
}: {
|
|
90
|
+
body?: Record<string | number, any>;
|
|
91
|
+
mergeBodies?: boolean;
|
|
92
|
+
/** Revalidate without announcing a loading state — see the note above. */
|
|
93
|
+
silent?: boolean;
|
|
94
|
+
} = {}) => {
|
|
95
|
+
if (!silent) {
|
|
96
|
+
setLoading(true);
|
|
97
|
+
}
|
|
56
98
|
|
|
57
99
|
const _body = mergeBodies ? { ...body, ...customBody } : customBody || body;
|
|
58
100
|
const response = await query<T>({ url, body: _body, cache, noApiPrefix });
|
|
59
101
|
|
|
60
|
-
|
|
102
|
+
if (!silent) {
|
|
103
|
+
setLoading(false);
|
|
104
|
+
}
|
|
61
105
|
|
|
62
106
|
if (response.ok) {
|
|
63
107
|
setError(undefined);
|
|
@@ -75,7 +119,9 @@ export function useFetch<T>({
|
|
|
75
119
|
|
|
76
120
|
useEffectOnce(() => {
|
|
77
121
|
if (loadOnMount) {
|
|
78
|
-
|
|
122
|
+
// Still revalidates when the entry is cached — `query` honours its own
|
|
123
|
+
// staleness — but silently, so a cached answer never flashes a skeleton.
|
|
124
|
+
load({ silent: hasCachedAnswer });
|
|
79
125
|
}
|
|
80
126
|
});
|
|
81
127
|
|
|
@@ -4,6 +4,7 @@ import { useRef } from 'react';
|
|
|
4
4
|
import { fireEvent, within } from 'storybook/test';
|
|
5
5
|
import { storiesStorageMock } from '../../../__tests__/ mock';
|
|
6
6
|
import { testsWaitForText } from '../../../__tests__/utils';
|
|
7
|
+
import { storyApiUrl } from '../../stories/storyNetwork';
|
|
7
8
|
import { StoryMeta } from '../../types';
|
|
8
9
|
import { useReqraftStorage } from './useStorage';
|
|
9
10
|
|
|
@@ -44,7 +45,7 @@ export const DefaultValue: Story = {
|
|
|
44
45
|
},
|
|
45
46
|
mockData: [
|
|
46
47
|
{
|
|
47
|
-
url: '
|
|
48
|
+
url: storyApiUrl('users?action=current'),
|
|
48
49
|
method: 'GET',
|
|
49
50
|
status: 200,
|
|
50
51
|
response: {},
|
|
@@ -13,6 +13,39 @@ export interface IUseTemplates {
|
|
|
13
13
|
|
|
14
14
|
export const TemplatesCache = new Map<string, IReqoreFormTemplates>();
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Hand this hook a `getContextData` payload the host has already fetched.
|
|
18
|
+
*
|
|
19
|
+
* The host and this library keep SEPARATE caches over one API, so a page that
|
|
20
|
+
* has already paid for the context data pays again here — and, worse, waits for
|
|
21
|
+
* it: `FormEngine` will not render a field before its templates exist, because
|
|
22
|
+
* an any-typed field with no templates falls back to the type picker and asks
|
|
23
|
+
* the author to choose `string`/`int`/`hash` for a value they already captured.
|
|
24
|
+
*
|
|
25
|
+
* Measured on the live alert rule: three form sections were placeholders from
|
|
26
|
+
* 1459ms to 2039ms and, when they cleared, 193 elements that had been on screen
|
|
27
|
+
* for ~590ms dropped 67px, because the placeholder is not the height of the
|
|
28
|
+
* form it stands in for. Seeded, the form has its templates on its FIRST render
|
|
29
|
+
* and there is no placeholder to swap out at all.
|
|
30
|
+
*
|
|
31
|
+
* Same shape as `seedQueryCache` for the type catalogue, and deliberately
|
|
32
|
+
* first-write-wins: a real fetch that has already answered is not overwritten.
|
|
33
|
+
*/
|
|
34
|
+
export const seedTemplatesCache = (
|
|
35
|
+
interfaceContext: string,
|
|
36
|
+
payload: ITemplatesPayload
|
|
37
|
+
): void => {
|
|
38
|
+
if (!interfaceContext || TemplatesCache.has(interfaceContext)) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const templates = buildTemplates(payload);
|
|
43
|
+
|
|
44
|
+
if (templates) {
|
|
45
|
+
TemplatesCache.set(interfaceContext, templates);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
16
49
|
/**
|
|
17
50
|
* Templates for form fields. Ported from qorus-ide `useTemplates` with one
|
|
18
51
|
* deliberate difference: fetching `system/getContextData` is **opt-in** via
|
|
@@ -29,6 +62,14 @@ export const useTemplates = (
|
|
|
29
62
|
|
|
30
63
|
const globalTemplates = useAsyncRetry(async () => {
|
|
31
64
|
if (shouldFetch) {
|
|
65
|
+
/* Already answered — by a previous mount or by the host seeding it. The
|
|
66
|
+
cache was consulted for `loading` and for the value but not here, so a
|
|
67
|
+
seeded page still issued the request it had been handed the answer to. */
|
|
68
|
+
const cached = TemplatesCache.get(interfaceContext!);
|
|
69
|
+
if (cached) {
|
|
70
|
+
return cached;
|
|
71
|
+
}
|
|
72
|
+
|
|
32
73
|
const serverTemplates = await query<ITemplatesPayload>({
|
|
33
74
|
url: `system/getContextData?interface_context=${interfaceContext}`,
|
|
34
75
|
method: 'PUT',
|
|
@@ -6,6 +6,7 @@ import { Server } from 'mock-socket';
|
|
|
6
6
|
import { useEffect, useState } from 'react';
|
|
7
7
|
import { useMount } from 'react-use';
|
|
8
8
|
import { sleep, testsClickButton, testsWaitForText } from '../../../__tests__/utils';
|
|
9
|
+
import { storySocketUrl } from '../../stories/storyNetwork';
|
|
9
10
|
import { StoryMeta } from '../../types';
|
|
10
11
|
import { ReqraftWebSocketsManager } from '../../utils/websocket';
|
|
11
12
|
import { IUseReqraftWebSocketOptions, useReqraftWebSocket } from './useWebSocket';
|
|
@@ -40,7 +41,7 @@ const CompWithHook = (args: IUseReqraftWebSocketOptions) => {
|
|
|
40
41
|
const meta = {
|
|
41
42
|
title: 'Hooks/useWebSocket',
|
|
42
43
|
async beforeEach({ parameters }: { parameters: Record<string, any> }) {
|
|
43
|
-
const url =
|
|
44
|
+
const url = storySocketUrl('log-test');
|
|
44
45
|
let server = new Server(url);
|
|
45
46
|
let killTimeout: NodeJS.Timeout;
|
|
46
47
|
/** Set while the server is dead: every connection that lands is closed. */
|
package/src/index.tsx
CHANGED
|
@@ -36,7 +36,20 @@ export type {
|
|
|
36
36
|
ICurrentUser,
|
|
37
37
|
ICurrentUserStore,
|
|
38
38
|
} from './stores/currentUser/currentUser';
|
|
39
|
-
export {
|
|
39
|
+
export {
|
|
40
|
+
fetchConfig,
|
|
41
|
+
query,
|
|
42
|
+
queryRaw,
|
|
43
|
+
reqraftApiPath,
|
|
44
|
+
reqraftCacheKey,
|
|
45
|
+
setupFetch,
|
|
46
|
+
} from './utils/fetch';
|
|
47
|
+
export type {
|
|
48
|
+
IReqraftFetchConfig,
|
|
49
|
+
IReqraftFetchResponse,
|
|
50
|
+
IReqraftQueryConfig,
|
|
51
|
+
IReqraftRawResponse,
|
|
52
|
+
} from './utils/fetch';
|
|
40
53
|
export * from './utils/websocket';
|
|
41
54
|
export { ReqraftLspClient } from './utils/lspClient';
|
|
42
55
|
export type { IReqraftLspClientOptions } from './utils/lspClient';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ReqoreP, ReqoreSpinner, ReqoreTree } from '@qoretechnologies/reqore';
|
|
2
2
|
import { StoryObj } from '@storybook/react-vite';
|
|
3
3
|
import { testsWaitForText } from '../../../__tests__/utils';
|
|
4
|
+
import { storyApiUrl } from '../../stories/storyNetwork';
|
|
4
5
|
import { StoryMeta } from '../../types';
|
|
5
6
|
import { currentUserStore } from './currentUser';
|
|
6
7
|
|
|
@@ -9,7 +10,7 @@ const meta = {
|
|
|
9
10
|
parameters: {
|
|
10
11
|
mockData: [
|
|
11
12
|
{
|
|
12
|
-
url: '
|
|
13
|
+
url: storyApiUrl('users?action=current'),
|
|
13
14
|
method: 'GET',
|
|
14
15
|
status: 200,
|
|
15
16
|
response: {
|