@qoretechnologies/reqraft 0.10.25 → 0.10.26
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 +52 -23
- package/design/COMPACT_ENGINE_REDESIGN.md +156 -0
- package/design/FORM_ENGINE_COMPACT_UX_PLAN.md +353 -0
- package/dist/components/FocusedEditing/index.d.ts +9 -1
- package/dist/components/FocusedEditing/index.d.ts.map +1 -1
- package/dist/components/FocusedEditing/index.js +2 -2
- package/dist/components/FocusedEditing/index.js.map +1 -1
- package/dist/components/composer/Composer.d.ts +108 -0
- package/dist/components/composer/Composer.d.ts.map +1 -0
- package/dist/components/composer/Composer.js +238 -0
- package/dist/components/composer/Composer.js.map +1 -0
- package/dist/components/composer/helpers.d.ts +21 -0
- package/dist/components/composer/helpers.d.ts.map +1 -0
- package/dist/components/composer/helpers.js +75 -0
- package/dist/components/composer/helpers.js.map +1 -0
- package/dist/components/composer/index.d.ts +3 -0
- package/dist/components/composer/index.d.ts.map +1 -0
- package/dist/components/composer/index.js +19 -0
- package/dist/components/composer/index.js.map +1 -0
- package/dist/components/form/engine/CompactRow.d.ts.map +1 -1
- package/dist/components/form/engine/CompactRow.js +307 -110
- 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 +123 -106
- package/dist/components/form/engine/CompactToolbar.js.map +1 -1
- package/dist/components/form/engine/FormEngine.d.ts +107 -11
- package/dist/components/form/engine/FormEngine.d.ts.map +1 -1
- package/dist/components/form/engine/FormEngine.js +618 -179
- package/dist/components/form/engine/FormEngine.js.map +1 -1
- package/dist/components/form/engine/compactRowContext.d.ts +9 -0
- package/dist/components/form/engine/compactRowContext.d.ts.map +1 -1
- package/dist/components/form/engine/compactRowContext.js.map +1 -1
- package/dist/components/form/engine/compactRowStyles.d.ts +7 -3
- package/dist/components/form/engine/compactRowStyles.d.ts.map +1 -1
- package/dist/components/form/engine/compactRowStyles.js +88 -51
- package/dist/components/form/engine/compactRowStyles.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/compactToolbarContext.js.map +1 -1
- package/dist/components/form/engine/optionActions.d.ts +28 -0
- package/dist/components/form/engine/optionActions.d.ts.map +1 -0
- package/dist/components/form/engine/optionActions.js +18 -0
- package/dist/components/form/engine/optionActions.js.map +1 -0
- package/dist/components/form/engine/readFirst.d.ts +63 -2
- package/dist/components/form/engine/readFirst.d.ts.map +1 -1
- package/dist/components/form/engine/readFirst.js +121 -6
- package/dist/components/form/engine/readFirst.js.map +1 -1
- package/dist/components/form/engine/rendererTypes.d.ts +35 -0
- package/dist/components/form/engine/rendererTypes.d.ts.map +1 -0
- package/dist/components/form/engine/rendererTypes.js +63 -0
- package/dist/components/form/engine/rendererTypes.js.map +1 -0
- package/dist/components/form/engine/variants/VariantCalmTable.d.ts +6 -0
- package/dist/components/form/engine/variants/VariantCalmTable.d.ts.map +1 -0
- package/dist/components/form/engine/variants/VariantCalmTable.js +94 -0
- package/dist/components/form/engine/variants/VariantCalmTable.js.map +1 -0
- package/dist/components/form/engine/variants/VariantCards.d.ts +6 -0
- package/dist/components/form/engine/variants/VariantCards.d.ts.map +1 -0
- package/dist/components/form/engine/variants/VariantCards.js +80 -0
- package/dist/components/form/engine/variants/VariantCards.js.map +1 -0
- package/dist/components/form/engine/variants/VariantFocus.d.ts +7 -0
- package/dist/components/form/engine/variants/VariantFocus.d.ts.map +1 -0
- package/dist/components/form/engine/variants/VariantFocus.js +138 -0
- package/dist/components/form/engine/variants/VariantFocus.js.map +1 -0
- package/dist/components/form/engine/variants/VariantMinimal.d.ts +6 -0
- package/dist/components/form/engine/variants/VariantMinimal.d.ts.map +1 -0
- package/dist/components/form/engine/variants/VariantMinimal.js +73 -0
- package/dist/components/form/engine/variants/VariantMinimal.js.map +1 -0
- package/dist/components/form/engine/variants/focusDemo.d.ts +13 -0
- package/dist/components/form/engine/variants/focusDemo.d.ts.map +1 -0
- package/dist/components/form/engine/variants/focusDemo.js +139 -0
- package/dist/components/form/engine/variants/focusDemo.js.map +1 -0
- package/dist/components/form/engine/variants/variantModel.d.ts +70 -0
- package/dist/components/form/engine/variants/variantModel.d.ts.map +1 -0
- package/dist/components/form/engine/variants/variantModel.js +133 -0
- package/dist/components/form/engine/variants/variantModel.js.map +1 -0
- package/dist/components/form/engine/variants/variantParts.d.ts +79 -0
- package/dist/components/form/engine/variants/variantParts.d.ts.map +1 -0
- package/dist/components/form/engine/variants/variantParts.js +191 -0
- package/dist/components/form/engine/variants/variantParts.js.map +1 -0
- package/dist/components/form/expressions/builder/index.d.ts.map +1 -1
- package/dist/components/form/expressions/builder/index.js +13 -2
- package/dist/components/form/expressions/builder/index.js.map +1 -1
- package/dist/components/form/expressions/useExpressions.d.ts.map +1 -1
- package/dist/components/form/expressions/useExpressions.js +4 -1
- package/dist/components/form/expressions/useExpressions.js.map +1 -1
- package/dist/components/form/fields/Field.d.ts.map +1 -1
- package/dist/components/form/fields/Field.js +5 -3
- package/dist/components/form/fields/Field.js.map +1 -1
- package/dist/components/form/fields/allowed-values/AllowedValues.d.ts +2 -1
- package/dist/components/form/fields/allowed-values/AllowedValues.d.ts.map +1 -1
- package/dist/components/form/fields/allowed-values/AllowedValues.js +8 -26
- package/dist/components/form/fields/allowed-values/AllowedValues.js.map +1 -1
- package/dist/components/form/fields/auto/AutoFormField.d.ts +3 -0
- package/dist/components/form/fields/auto/AutoFormField.d.ts.map +1 -1
- package/dist/components/form/fields/auto/AutoFormField.js +42 -8
- package/dist/components/form/fields/auto/AutoFormField.js.map +1 -1
- package/dist/components/form/fields/binary/Binary.d.ts +15 -0
- package/dist/components/form/fields/binary/Binary.d.ts.map +1 -0
- package/dist/components/form/fields/binary/Binary.js +74 -0
- package/dist/components/form/fields/binary/Binary.js.map +1 -0
- package/dist/components/form/fields/boolean/Boolean.js +1 -1
- package/dist/components/form/fields/boolean/Boolean.js.map +1 -1
- package/dist/components/form/fields/multi-select/MultiSelectFormField.d.ts.map +1 -1
- package/dist/components/form/fields/multi-select/MultiSelectFormField.js +5 -4
- package/dist/components/form/fields/multi-select/MultiSelectFormField.js.map +1 -1
- package/dist/components/form/fields/select/Select.d.ts.map +1 -1
- package/dist/components/form/fields/select/Select.js +29 -12
- package/dist/components/form/fields/select/Select.js.map +1 -1
- package/dist/components/form/fields/select/SelectCollection.d.ts +8 -2
- package/dist/components/form/fields/select/SelectCollection.d.ts.map +1 -1
- package/dist/components/form/fields/select/SelectCollection.js +52 -16
- package/dist/components/form/fields/select/SelectCollection.js.map +1 -1
- package/dist/components/form/index.d.ts +2 -0
- package/dist/components/form/index.d.ts.map +1 -1
- package/dist/components/form/index.js +2 -0
- package/dist/components/form/index.js.map +1 -1
- package/dist/components/imageLightbox/ImageLightbox.d.ts +21 -0
- package/dist/components/imageLightbox/ImageLightbox.d.ts.map +1 -0
- package/dist/components/imageLightbox/ImageLightbox.js +169 -0
- package/dist/components/imageLightbox/ImageLightbox.js.map +1 -0
- package/dist/components/imageLightbox/index.d.ts +2 -0
- package/dist/components/imageLightbox/index.d.ts.map +1 -0
- package/dist/components/imageLightbox/index.js +18 -0
- package/dist/components/imageLightbox/index.js.map +1 -0
- package/dist/components/searchFilterBar/SearchFilterBar.d.ts +33 -0
- package/dist/components/searchFilterBar/SearchFilterBar.d.ts.map +1 -0
- package/dist/components/searchFilterBar/SearchFilterBar.js +76 -0
- package/dist/components/searchFilterBar/SearchFilterBar.js.map +1 -0
- package/dist/components/searchFilterBar/index.d.ts +2 -0
- package/dist/components/searchFilterBar/index.d.ts.map +1 -0
- package/dist/components/searchFilterBar/index.js +18 -0
- package/dist/components/searchFilterBar/index.js.map +1 -0
- package/dist/components/supportTicket/AttachmentChips.d.ts +10 -0
- package/dist/components/supportTicket/AttachmentChips.d.ts.map +1 -0
- package/dist/components/supportTicket/AttachmentChips.js +122 -0
- package/dist/components/supportTicket/AttachmentChips.js.map +1 -0
- package/dist/components/supportTicket/InterfaceReferenceTags.d.ts +43 -0
- package/dist/components/supportTicket/InterfaceReferenceTags.d.ts.map +1 -0
- package/dist/components/supportTicket/InterfaceReferenceTags.js +31 -0
- package/dist/components/supportTicket/InterfaceReferenceTags.js.map +1 -0
- package/dist/components/supportTicket/ReferencePicker.d.ts +87 -0
- package/dist/components/supportTicket/ReferencePicker.d.ts.map +1 -0
- package/dist/components/supportTicket/ReferencePicker.js +295 -0
- package/dist/components/supportTicket/ReferencePicker.js.map +1 -0
- package/dist/components/supportTicket/TicketHeader.d.ts +86 -0
- package/dist/components/supportTicket/TicketHeader.d.ts.map +1 -0
- package/dist/components/supportTicket/TicketHeader.js +149 -0
- package/dist/components/supportTicket/TicketHeader.js.map +1 -0
- package/dist/components/supportTicket/TicketMetaTags.d.ts +32 -0
- package/dist/components/supportTicket/TicketMetaTags.d.ts.map +1 -0
- package/dist/components/supportTicket/TicketMetaTags.js +21 -0
- package/dist/components/supportTicket/TicketMetaTags.js.map +1 -0
- package/dist/components/supportTicket/TicketReplyBox.d.ts +63 -0
- package/dist/components/supportTicket/TicketReplyBox.d.ts.map +1 -0
- package/dist/components/supportTicket/TicketReplyBox.js +115 -0
- package/dist/components/supportTicket/TicketReplyBox.js.map +1 -0
- package/dist/components/supportTicket/attachmentAccess.d.ts +37 -0
- package/dist/components/supportTicket/attachmentAccess.d.ts.map +1 -0
- package/dist/components/supportTicket/attachmentAccess.js +132 -0
- package/dist/components/supportTicket/attachmentAccess.js.map +1 -0
- package/dist/components/supportTicket/fileToAttachment.d.ts +9 -0
- package/dist/components/supportTicket/fileToAttachment.d.ts.map +1 -0
- package/dist/components/supportTicket/fileToAttachment.js +28 -0
- package/dist/components/supportTicket/fileToAttachment.js.map +1 -0
- package/dist/components/supportTicket/index.d.ts +11 -0
- package/dist/components/supportTicket/index.d.ts.map +1 -0
- package/dist/components/supportTicket/index.js +27 -0
- package/dist/components/supportTicket/index.js.map +1 -0
- package/dist/components/supportTicket/meta.d.ts +71 -0
- package/dist/components/supportTicket/meta.d.ts.map +1 -0
- package/dist/components/supportTicket/meta.js +115 -0
- package/dist/components/supportTicket/meta.js.map +1 -0
- package/dist/components/supportTicket/ticketModel.d.ts +102 -0
- package/dist/components/supportTicket/ticketModel.d.ts.map +1 -0
- package/dist/components/supportTicket/ticketModel.js +3 -0
- package/dist/components/supportTicket/ticketModel.js.map +1 -0
- package/dist/components/ticketReferences/TicketReferences.d.ts +52 -0
- package/dist/components/ticketReferences/TicketReferences.d.ts.map +1 -0
- package/dist/components/ticketReferences/TicketReferences.js +492 -0
- package/dist/components/ticketReferences/TicketReferences.js.map +1 -0
- package/dist/components/ticketReferences/index.d.ts +3 -0
- package/dist/components/ticketReferences/index.d.ts.map +1 -0
- package/dist/components/ticketReferences/index.js +6 -0
- package/dist/components/ticketReferences/index.js.map +1 -0
- package/dist/components/ticketThread/TicketThread.d.ts +57 -0
- package/dist/components/ticketThread/TicketThread.d.ts.map +1 -0
- package/dist/components/ticketThread/TicketThread.js +299 -0
- package/dist/components/ticketThread/TicketThread.js.map +1 -0
- package/dist/components/ticketThread/index.d.ts +3 -0
- package/dist/components/ticketThread/index.d.ts.map +1 -0
- package/dist/components/ticketThread/index.js +7 -0
- package/dist/components/ticketThread/index.js.map +1 -0
- package/dist/helpers/common.d.ts +5 -0
- package/dist/helpers/common.d.ts.map +1 -1
- package/dist/helpers/common.js +20 -1
- package/dist/helpers/common.js.map +1 -1
- package/dist/helpers/scrollbar.d.ts +2 -0
- package/dist/helpers/scrollbar.d.ts.map +1 -0
- package/dist/helpers/scrollbar.js +25 -0
- package/dist/helpers/scrollbar.js.map +1 -0
- package/dist/index.d.ts +8 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/providers/StorageProvider.d.ts +13 -0
- package/dist/providers/StorageProvider.d.ts.map +1 -1
- package/dist/providers/StorageProvider.js +29 -5
- package/dist/providers/StorageProvider.js.map +1 -1
- package/package.json +6 -5
- package/src/components/FocusedEditing/index.tsx +17 -2
- package/src/components/composer/Composer.stories.tsx +238 -0
- package/src/components/composer/Composer.tsx +545 -0
- package/src/components/composer/helpers.ts +74 -0
- package/src/components/composer/index.ts +2 -0
- package/src/components/dpqlEditor/DpqlEditor.stories.tsx +74 -2
- package/src/components/form/engine/CompactRow.tsx +561 -283
- package/src/components/form/engine/CompactToolbar.tsx +216 -182
- package/src/components/form/engine/FormEngine.stories.tsx +1736 -162
- package/src/components/form/engine/FormEngine.tsx +886 -170
- package/src/components/form/engine/FormEngineRemote.stories.tsx +377 -0
- package/src/components/form/engine/_structuredData/StructuredDataView.stories.tsx +8 -0
- package/src/components/form/engine/compactRowContext.ts +14 -0
- package/src/components/form/engine/compactRowStyles.ts +273 -146
- package/src/components/form/engine/compactToolbarContext.ts +1 -0
- package/src/components/form/engine/optionActions.ts +40 -0
- package/src/components/form/engine/readFirst.ts +169 -10
- package/src/components/form/engine/rendererTypes.ts +55 -0
- package/src/components/form/engine/variants/FormEngineVariants.stories.tsx +159 -0
- package/src/components/form/engine/variants/VariantCalmTable.tsx +242 -0
- package/src/components/form/engine/variants/VariantCards.tsx +212 -0
- package/src/components/form/engine/variants/VariantFocus.tsx +382 -0
- package/src/components/form/engine/variants/VariantMinimal.tsx +170 -0
- package/src/components/form/engine/variants/focusDemo.ts +145 -0
- package/src/components/form/engine/variants/variantModel.ts +216 -0
- package/src/components/form/engine/variants/variantParts.tsx +313 -0
- package/src/components/form/expressions/ExpressionField.stories.tsx +84 -2
- package/src/components/form/expressions/builder/ExpressionBuilder.stories.tsx +213 -19
- package/src/components/form/expressions/builder/index.tsx +12 -1
- package/src/components/form/expressions/useExpressions.ts +4 -1
- package/src/components/form/fields/Field.stories.tsx +40 -0
- package/src/components/form/fields/Field.tsx +10 -1
- package/src/components/form/fields/allowed-values/AllowedValues.tsx +13 -36
- package/src/components/form/fields/array/ArrayAutoField.stories.tsx +64 -0
- package/src/components/form/fields/auto/AutoFormField.stories.tsx +146 -2
- package/src/components/form/fields/auto/AutoFormField.tsx +42 -1
- package/src/components/form/fields/binary/Binary.stories.tsx +122 -0
- package/src/components/form/fields/binary/Binary.tsx +81 -0
- package/src/components/form/fields/boolean/Boolean.stories.tsx +58 -0
- package/src/components/form/fields/boolean/Boolean.tsx +1 -1
- package/src/components/form/fields/byte-size/ByteSize.stories.tsx +24 -0
- package/src/components/form/fields/color/Color.stories.tsx +30 -0
- package/src/components/form/fields/cron/Cron.stories.tsx +8 -0
- package/src/components/form/fields/date/Date.stories.tsx +22 -0
- package/src/components/form/fields/file/File.stories.tsx +48 -0
- package/src/components/form/fields/long-string/LongString.stories.tsx +24 -0
- package/src/components/form/fields/markdown/Markdown.stories.tsx +8 -0
- package/src/components/form/fields/multi-select/MultiSelectFormField.tsx +14 -10
- package/src/components/form/fields/number/Number.stories.tsx +31 -0
- package/src/components/form/fields/object/Object.stories.tsx +88 -0
- package/src/components/form/fields/radio-group/RadioGroup.stories.tsx +24 -0
- package/src/components/form/fields/rich-text/RichText.stories.tsx +23 -0
- package/src/components/form/fields/schema-definition/SchemaDefinitionField.stories.tsx +72 -0
- package/src/components/form/fields/select/Select.stories.tsx +108 -0
- package/src/components/form/fields/select/Select.tsx +38 -12
- package/src/components/form/fields/select/SelectCollection.tsx +78 -17
- package/src/components/form/fields/string/String.stories.tsx +16 -0
- package/src/components/form/fields/template/TemplateField.stories.tsx +160 -0
- package/src/components/form/fields/url/Url.stories.tsx +24 -0
- package/src/components/form/index.tsx +2 -0
- package/src/components/imageLightbox/ImageLightbox.stories.tsx +238 -0
- package/src/components/imageLightbox/ImageLightbox.tsx +236 -0
- package/src/components/imageLightbox/index.ts +1 -0
- package/src/components/log/Log.stories.tsx +96 -0
- package/src/components/menu/Menu.stories.tsx +54 -0
- package/src/components/qonsoleSmartInput/QonsoleSmartInput.stories.tsx +83 -37
- package/src/components/searchFilterBar/SearchFilterBar.tsx +131 -0
- package/src/components/searchFilterBar/index.ts +1 -0
- package/src/components/smartEditor/SmartEditor.stories.tsx +32 -0
- package/src/components/supportTicket/AttachmentChips.stories.tsx +171 -0
- package/src/components/supportTicket/AttachmentChips.tsx +147 -0
- package/src/components/supportTicket/InterfaceReferenceTags.stories.tsx +171 -0
- package/src/components/supportTicket/InterfaceReferenceTags.tsx +96 -0
- package/src/components/supportTicket/ReferencePicker.stories.tsx +519 -0
- package/src/components/supportTicket/ReferencePicker.tsx +563 -0
- package/src/components/supportTicket/TicketConversation.stories.tsx +121 -0
- package/src/components/supportTicket/TicketHeader.stories.tsx +183 -0
- package/src/components/supportTicket/TicketHeader.tsx +247 -0
- package/src/components/supportTicket/TicketMetaTags.stories.tsx +103 -0
- package/src/components/supportTicket/TicketMetaTags.tsx +85 -0
- package/src/components/supportTicket/TicketReplyBox.stories.tsx +351 -0
- package/src/components/supportTicket/TicketReplyBox.tsx +146 -0
- package/src/components/supportTicket/attachmentAccess.ts +97 -0
- package/src/components/supportTicket/fileToAttachment.ts +24 -0
- package/src/components/supportTicket/index.ts +10 -0
- package/src/components/supportTicket/meta.ts +185 -0
- package/src/components/supportTicket/ticketModel.ts +131 -0
- package/src/components/ticketReferences/TicketReferences.stories.tsx +315 -0
- package/src/components/ticketReferences/TicketReferences.tsx +886 -0
- package/src/components/ticketReferences/index.ts +2 -0
- package/src/components/ticketThread/TicketThread.stories.tsx +828 -0
- package/src/components/ticketThread/TicketThread.tsx +704 -0
- package/src/components/ticketThread/index.ts +6 -0
- package/src/helpers/common.ts +19 -0
- package/src/helpers/scrollbar.ts +32 -0
- package/src/hooks/useFetch/useFetch.stories.tsx +30 -0
- package/src/hooks/useStorage/useStorage.stories.tsx +82 -0
- package/src/hooks/useWebSocket/useWebsocket.stories.tsx +127 -1
- package/src/index.tsx +8 -1
- package/src/providers/StorageProvider.tsx +30 -4
- package/src/stores/currentUser/currentUser.stories.tsx +49 -0
package/src/helpers/common.ts
CHANGED
|
@@ -174,6 +174,25 @@ export const splitByteSize = (value?: string): [number | undefined, string | und
|
|
|
174
174
|
return [amount?.[0] ? Number(amount[0]) : undefined, unit?.[0]];
|
|
175
175
|
};
|
|
176
176
|
|
|
177
|
+
/** Human-readable file size, e.g. `"12.3 KB"` — whole bytes below 1 KiB, one
|
|
178
|
+
* decimal above. Shared by the ticket thread / references attachment rows. */
|
|
179
|
+
export const formatBytes = (bytes: number): string => {
|
|
180
|
+
if (bytes < 1024) {
|
|
181
|
+
return `${bytes} B`;
|
|
182
|
+
}
|
|
183
|
+
const units = ['KB', 'MB', 'GB'];
|
|
184
|
+
let value = bytes / 1024;
|
|
185
|
+
let unit = 0;
|
|
186
|
+
while (value >= 1024 && unit < units.length - 1) {
|
|
187
|
+
value /= 1024;
|
|
188
|
+
unit += 1;
|
|
189
|
+
}
|
|
190
|
+
return `${value.toFixed(1)} ${units[unit]}`;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
/** True when a MIME content type is an image (`image/*`). */
|
|
194
|
+
export const isImage = (contentType: string): boolean => contentType.startsWith('image/');
|
|
195
|
+
|
|
177
196
|
// URL value splitting (IDE `urlField.tsx` behavior). Lives here rather than
|
|
178
197
|
// in the Url component so `validations.ts` can use it without pulling the
|
|
179
198
|
// component chain into non-DOM consumers (jest).
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { css } from 'styled-components';
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* The thin, unobtrusive scrollbar the Qorus surfaces use — a transparent track and a
|
|
5
|
+
* translucent rounded thumb — in place of the platform default, which reads as heavy
|
|
6
|
+
* chrome inside a dark panel. Drop it into any `styled` scroll container:
|
|
7
|
+
*
|
|
8
|
+
* const Scroll = styled.div`
|
|
9
|
+
* overflow-y: auto;
|
|
10
|
+
* ${thinScrollbar}
|
|
11
|
+
* `;
|
|
12
|
+
*
|
|
13
|
+
* webkit-only on purpose: setting `scrollbar-width` as well lets Chromium override this
|
|
14
|
+
* custom 6px with its own wider "thin", so the two disagree. Firefox keeps its native
|
|
15
|
+
* bar, which is already slim.
|
|
16
|
+
*/
|
|
17
|
+
export const thinScrollbar = css`
|
|
18
|
+
&::-webkit-scrollbar {
|
|
19
|
+
width: 6px;
|
|
20
|
+
height: 6px;
|
|
21
|
+
}
|
|
22
|
+
&::-webkit-scrollbar-track {
|
|
23
|
+
background: transparent;
|
|
24
|
+
}
|
|
25
|
+
&::-webkit-scrollbar-thumb {
|
|
26
|
+
background: rgba(255, 255, 255, 0.15);
|
|
27
|
+
border-radius: 3px;
|
|
28
|
+
}
|
|
29
|
+
&::-webkit-scrollbar-thumb:hover {
|
|
30
|
+
background: rgba(255, 255, 255, 0.28);
|
|
31
|
+
}
|
|
32
|
+
`;
|
|
@@ -35,6 +35,12 @@ export const Get: Story = {
|
|
|
35
35
|
method: 'GET',
|
|
36
36
|
},
|
|
37
37
|
parameters: {
|
|
38
|
+
docs: {
|
|
39
|
+
description: {
|
|
40
|
+
story:
|
|
41
|
+
'Renders a useFetch demo that issues a GET against /public/info on mount and displays the returned Qorus system info as a tree.',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
38
44
|
mockData: [
|
|
39
45
|
{
|
|
40
46
|
// An array of mock objects which will add in every story
|
|
@@ -66,6 +72,12 @@ export const Put: Story = {
|
|
|
66
72
|
method: 'PUT',
|
|
67
73
|
},
|
|
68
74
|
parameters: {
|
|
75
|
+
docs: {
|
|
76
|
+
description: {
|
|
77
|
+
story:
|
|
78
|
+
'Renders a useFetch demo that issues a PUT against /public/info on mount and displays the returned success status.',
|
|
79
|
+
},
|
|
80
|
+
},
|
|
69
81
|
mockData: [
|
|
70
82
|
{
|
|
71
83
|
// An array of mock objects which will add in every story
|
|
@@ -88,6 +100,12 @@ export const Post: Story = {
|
|
|
88
100
|
method: 'POST',
|
|
89
101
|
},
|
|
90
102
|
parameters: {
|
|
103
|
+
docs: {
|
|
104
|
+
description: {
|
|
105
|
+
story:
|
|
106
|
+
'Renders a useFetch demo that issues a POST against /public/info on mount and displays the returned success status.',
|
|
107
|
+
},
|
|
108
|
+
},
|
|
91
109
|
mockData: [
|
|
92
110
|
{
|
|
93
111
|
// An array of mock objects which will add in every story
|
|
@@ -110,6 +128,12 @@ export const Del: Story = {
|
|
|
110
128
|
method: 'DELETE',
|
|
111
129
|
},
|
|
112
130
|
parameters: {
|
|
131
|
+
docs: {
|
|
132
|
+
description: {
|
|
133
|
+
story:
|
|
134
|
+
'Renders a useFetch demo that issues a DELETE against /public/info on mount and displays the returned success status.',
|
|
135
|
+
},
|
|
136
|
+
},
|
|
113
137
|
mockData: [
|
|
114
138
|
{
|
|
115
139
|
// An array of mock objects which will add in every story
|
|
@@ -132,6 +156,12 @@ export const Error409: Story = {
|
|
|
132
156
|
method: 'POST',
|
|
133
157
|
},
|
|
134
158
|
parameters: {
|
|
159
|
+
docs: {
|
|
160
|
+
description: {
|
|
161
|
+
story:
|
|
162
|
+
'Renders a useFetch demo that issues a POST returning a 409 error — the hook exposes errorData and the Qorus stack trace is rendered in place of the response.',
|
|
163
|
+
},
|
|
164
|
+
},
|
|
135
165
|
mockData: [
|
|
136
166
|
{
|
|
137
167
|
// An array of mock objects which will add in every story
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ReqoreButton, ReqoreControlGroup, ReqoreP } from '@qoretechnologies/reqore';
|
|
2
2
|
import { StoryObj } from '@storybook/react-vite';
|
|
3
|
+
import { useRef } from 'react';
|
|
3
4
|
import { fireEvent, within } from 'storybook/test';
|
|
4
5
|
import { storiesStorageMock } from '../../../__tests__/ mock';
|
|
5
6
|
import { testsWaitForText } from '../../../__tests__/utils';
|
|
@@ -35,6 +36,12 @@ export const DefaultValue: Story = {
|
|
|
35
36
|
method: 'GET',
|
|
36
37
|
},
|
|
37
38
|
parameters: {
|
|
39
|
+
docs: {
|
|
40
|
+
description: {
|
|
41
|
+
story:
|
|
42
|
+
'Renders a useReqraftStorage demo where the server returns no stored value — the hook falls back to the default and displays it.',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
38
45
|
mockData: [
|
|
39
46
|
{
|
|
40
47
|
url: 'https://hq.qoretechnologies.com:8092/api/latest/users?action=current',
|
|
@@ -51,6 +58,12 @@ export const DefaultValue: Story = {
|
|
|
51
58
|
|
|
52
59
|
export const StorageValue: Story = {
|
|
53
60
|
parameters: {
|
|
61
|
+
docs: {
|
|
62
|
+
description: {
|
|
63
|
+
story:
|
|
64
|
+
'Renders a useReqraftStorage demo with a value already persisted on the server — the hook loads it and displays it in place of the default.',
|
|
65
|
+
},
|
|
66
|
+
},
|
|
54
67
|
mockData: [...storiesStorageMock],
|
|
55
68
|
},
|
|
56
69
|
play: async () => {
|
|
@@ -60,6 +73,15 @@ export const StorageValue: Story = {
|
|
|
60
73
|
|
|
61
74
|
export const ValueCanBeUpdated: Story = {
|
|
62
75
|
...StorageValue,
|
|
76
|
+
parameters: {
|
|
77
|
+
...StorageValue.parameters,
|
|
78
|
+
docs: {
|
|
79
|
+
description: {
|
|
80
|
+
story:
|
|
81
|
+
'Renders the useReqraftStorage demo with a stored value. When "Update storage" is clicked, the setter writes a new value and the display refreshes to match.',
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
63
85
|
play: async ({ canvasElement }) => {
|
|
64
86
|
const canvas = within(canvasElement);
|
|
65
87
|
|
|
@@ -72,8 +94,68 @@ export const ValueCanBeUpdated: Story = {
|
|
|
72
94
|
},
|
|
73
95
|
};
|
|
74
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Regression for the "stale updater clobbers other keys" bug: the storage
|
|
99
|
+
* updater must persist onto the LATEST storage blob, not the one captured in
|
|
100
|
+
* its closure. Long-lived callers (an imperative store subscription, a ref to
|
|
101
|
+
* the mount-time setter) hold an old updater; writing through it must not wipe
|
|
102
|
+
* keys other hooks wrote in the meantime.
|
|
103
|
+
*/
|
|
104
|
+
export const StaleUpdaterDoesNotClobberOtherKeys: Story = {
|
|
105
|
+
args: { reqraftOptions: { waitForStorage: true } },
|
|
106
|
+
parameters: {
|
|
107
|
+
docs: {
|
|
108
|
+
description: {
|
|
109
|
+
story:
|
|
110
|
+
"Renders two useReqraftStorage keys and captures the mount-time setter for the first one (a deliberately STALE updater). After a second key is written, writing through the stale updater must keep the second key intact — proving storage writes merge onto the latest blob rather than a closure copy. This is the regression test for page-visit tracking wiping other stored settings.",
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
mockData: [...storiesStorageMock],
|
|
114
|
+
},
|
|
115
|
+
render: () => {
|
|
116
|
+
const [someVal, setSomeVal] = useReqraftStorage<string>('some-path', 'default');
|
|
117
|
+
const [keyB] = useReqraftStorage<string>('key-b', 'b-default');
|
|
118
|
+
const [, setKeyB] = useReqraftStorage<string>('key-b', 'b-default');
|
|
119
|
+
// Capture the mount-time setter — it stays stale once other keys change.
|
|
120
|
+
const staleSetSomeVal = useRef(setSomeVal);
|
|
121
|
+
|
|
122
|
+
return (
|
|
123
|
+
<ReqoreControlGroup vertical>
|
|
124
|
+
<ReqoreP>{`some-path: ${someVal}`}</ReqoreP>
|
|
125
|
+
<ReqoreP>{`key-b: ${keyB}`}</ReqoreP>
|
|
126
|
+
<ReqoreButton onClick={() => setKeyB('b-written')}>Write B</ReqoreButton>
|
|
127
|
+
<ReqoreButton onClick={() => staleSetSomeVal.current('a-written')}>
|
|
128
|
+
Write A (stale)
|
|
129
|
+
</ReqoreButton>
|
|
130
|
+
</ReqoreControlGroup>
|
|
131
|
+
);
|
|
132
|
+
},
|
|
133
|
+
play: async ({ canvasElement }) => {
|
|
134
|
+
const canvas = within(canvasElement);
|
|
135
|
+
|
|
136
|
+
await testsWaitForText('some-path: This is a storage value');
|
|
137
|
+
// Write a second key through its own (fresh) updater.
|
|
138
|
+
await fireEvent.click(canvas.getByText('Write B'));
|
|
139
|
+
await testsWaitForText('key-b: b-written');
|
|
140
|
+
// Write the first key through the STALE mount-time updater.
|
|
141
|
+
await fireEvent.click(canvas.getByText('Write A (stale)'));
|
|
142
|
+
await testsWaitForText('some-path: a-written');
|
|
143
|
+
// The stale write must NOT have wiped the second key.
|
|
144
|
+
await testsWaitForText('key-b: b-written');
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
|
|
75
148
|
export const ValueCanBeRemoved: Story = {
|
|
76
149
|
...StorageValue,
|
|
150
|
+
parameters: {
|
|
151
|
+
...StorageValue.parameters,
|
|
152
|
+
docs: {
|
|
153
|
+
description: {
|
|
154
|
+
story:
|
|
155
|
+
'Renders the useReqraftStorage demo with a stored value. When "Remove value" is clicked, the remover clears storage and the hook falls back to the default value.',
|
|
156
|
+
},
|
|
157
|
+
},
|
|
158
|
+
},
|
|
77
159
|
play: async ({ canvasElement }) => {
|
|
78
160
|
const canvas = within(canvasElement);
|
|
79
161
|
|
|
@@ -106,8 +106,25 @@ const meta = {
|
|
|
106
106
|
export default meta;
|
|
107
107
|
export type Story = StoryObj<typeof meta>;
|
|
108
108
|
|
|
109
|
-
export const Default: Story = {
|
|
109
|
+
export const Default: Story = {
|
|
110
|
+
parameters: {
|
|
111
|
+
docs: {
|
|
112
|
+
description: {
|
|
113
|
+
story:
|
|
114
|
+
'Renders the useReqraftWebSocket demo with no lifecycle options — the socket stays CLOSED until the operator clicks Connect.',
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
},
|
|
118
|
+
};
|
|
110
119
|
export const OpenManually: Story = {
|
|
120
|
+
parameters: {
|
|
121
|
+
docs: {
|
|
122
|
+
description: {
|
|
123
|
+
story:
|
|
124
|
+
'Renders the useReqraftWebSocket demo and clicks the Connect action — the socket opens and the onOpen callback fires.',
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
},
|
|
111
128
|
play: async ({ args }) => {
|
|
112
129
|
await testsClickButton({ label: 'Connect' });
|
|
113
130
|
await testsWaitForText('Websocket Status: OPEN');
|
|
@@ -118,6 +135,14 @@ export const OpenOnMount: Story = {
|
|
|
118
135
|
args: {
|
|
119
136
|
openOnMount: true,
|
|
120
137
|
},
|
|
138
|
+
parameters: {
|
|
139
|
+
docs: {
|
|
140
|
+
description: {
|
|
141
|
+
story:
|
|
142
|
+
'Renders the useReqraftWebSocket demo with openOnMount enabled — the socket opens immediately after mount without any user action.',
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
},
|
|
121
146
|
play: async ({ args }) => {
|
|
122
147
|
await testsWaitForText('Websocket Status: OPEN');
|
|
123
148
|
await expect(args.onOpen).toHaveBeenCalled();
|
|
@@ -126,6 +151,15 @@ export const OpenOnMount: Story = {
|
|
|
126
151
|
|
|
127
152
|
export const CloseManually: Story = {
|
|
128
153
|
...OpenOnMount,
|
|
154
|
+
parameters: {
|
|
155
|
+
...OpenOnMount.parameters,
|
|
156
|
+
docs: {
|
|
157
|
+
description: {
|
|
158
|
+
story:
|
|
159
|
+
'Renders the useReqraftWebSocket demo opened on mount. When Disconnect is clicked, the socket transitions to CLOSED and the onClose callback fires.',
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
},
|
|
129
163
|
play: async ({ args, ...rest }) => {
|
|
130
164
|
await OpenOnMount.play({ args, ...rest });
|
|
131
165
|
await testsClickButton({ label: 'Disconnect' });
|
|
@@ -141,6 +175,14 @@ export const Reconnects: Story = {
|
|
|
141
175
|
maxReconnectTries: 5,
|
|
142
176
|
openOnMount: true,
|
|
143
177
|
},
|
|
178
|
+
parameters: {
|
|
179
|
+
docs: {
|
|
180
|
+
description: {
|
|
181
|
+
story:
|
|
182
|
+
'Renders the useReqraftWebSocket demo with reconnect enabled. When the server is killed, the socket transitions to CONNECTING and then back to OPEN once the server returns.',
|
|
183
|
+
},
|
|
184
|
+
},
|
|
185
|
+
},
|
|
144
186
|
play: async ({ args, ...rest }) => {
|
|
145
187
|
await OpenOnMount.play({ args, ...rest });
|
|
146
188
|
await testsClickButton({ label: 'Kill' });
|
|
@@ -159,6 +201,12 @@ export const ReconnectFails: Story = {
|
|
|
159
201
|
reconnectInterval: 500,
|
|
160
202
|
},
|
|
161
203
|
parameters: {
|
|
204
|
+
docs: {
|
|
205
|
+
description: {
|
|
206
|
+
story:
|
|
207
|
+
'Renders the useReqraftWebSocket demo with a low maxReconnectTries. When the server never returns, the socket exhausts its reconnect attempts and fires onReconnectFailed.',
|
|
208
|
+
},
|
|
209
|
+
},
|
|
162
210
|
jest: {
|
|
163
211
|
timeout: 60000,
|
|
164
212
|
},
|
|
@@ -178,6 +226,14 @@ export const SendMessage: Story = {
|
|
|
178
226
|
includeSentMessagesInState: true,
|
|
179
227
|
useState: true,
|
|
180
228
|
},
|
|
229
|
+
parameters: {
|
|
230
|
+
docs: {
|
|
231
|
+
description: {
|
|
232
|
+
story:
|
|
233
|
+
'Renders the useReqraftWebSocket demo with useState enabled. When Send is clicked, the message is dispatched and the echoed reply appears in the accumulated message list.',
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
},
|
|
181
237
|
play: async ({ args, ...rest }) => {
|
|
182
238
|
await OpenOnMount.play({ args, ...rest });
|
|
183
239
|
await testsClickButton({ label: 'Send' });
|
|
@@ -197,6 +253,14 @@ export const WithLogs: Story = {
|
|
|
197
253
|
useState: true,
|
|
198
254
|
reconnectInterval: 1500,
|
|
199
255
|
},
|
|
256
|
+
parameters: {
|
|
257
|
+
docs: {
|
|
258
|
+
description: {
|
|
259
|
+
story:
|
|
260
|
+
'Renders the useReqraftWebSocket demo with includeLogMessagesInState. Connect / disconnect / reconnect lifecycle events are captured as log entries alongside regular messages.',
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
},
|
|
200
264
|
play: async ({ args, ...rest }) => {
|
|
201
265
|
await Reconnects.play({ args, ...rest });
|
|
202
266
|
|
|
@@ -206,6 +270,15 @@ export const WithLogs: Story = {
|
|
|
206
270
|
|
|
207
271
|
export const ClearsMessages: Story = {
|
|
208
272
|
...SendMessage,
|
|
273
|
+
parameters: {
|
|
274
|
+
...SendMessage.parameters,
|
|
275
|
+
docs: {
|
|
276
|
+
description: {
|
|
277
|
+
story:
|
|
278
|
+
'Renders the useReqraftWebSocket demo after sending a message. When Clear is clicked, the accumulated message list is emptied.',
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
},
|
|
209
282
|
play: async ({ args, ...rest }) => {
|
|
210
283
|
await SendMessage.play({ args, ...rest });
|
|
211
284
|
await testsClickButton({ label: 'Clear' });
|
|
@@ -353,6 +426,14 @@ export const MultipleConnections: Story = {
|
|
|
353
426
|
includeLogMessagesInState: true,
|
|
354
427
|
useState: true,
|
|
355
428
|
},
|
|
429
|
+
parameters: {
|
|
430
|
+
docs: {
|
|
431
|
+
description: {
|
|
432
|
+
story:
|
|
433
|
+
'Renders three panels that each call useReqraftWebSocket against the same URL — proves the connections share one pooled underlying WebSocket while keeping independent message state.',
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
},
|
|
356
437
|
// @ts-expect-error customprops
|
|
357
438
|
render: (args: IUseReqraftWebSocketOptions) => {
|
|
358
439
|
const [conectionStatus, setConnectionStatus] = useState<string>('CLOSED');
|
|
@@ -405,6 +486,15 @@ export const MultipleConnectionsOpenOnMount: Story = {
|
|
|
405
486
|
...MultipleConnections.args,
|
|
406
487
|
openOnMount: true,
|
|
407
488
|
},
|
|
489
|
+
parameters: {
|
|
490
|
+
...MultipleConnections.parameters,
|
|
491
|
+
docs: {
|
|
492
|
+
description: {
|
|
493
|
+
story:
|
|
494
|
+
'Renders three pooled useReqraftWebSocket panels with openOnMount — all three transition to OPEN over the shared underlying socket without any user action.',
|
|
495
|
+
},
|
|
496
|
+
},
|
|
497
|
+
},
|
|
408
498
|
play: async ({ args }) => {
|
|
409
499
|
await testsWaitForText('First Connection Status: OPEN');
|
|
410
500
|
await testsWaitForText('Second Connection Status: OPEN');
|
|
@@ -420,6 +510,15 @@ export const MultipleConnectionsClosedAtOnce: Story = {
|
|
|
420
510
|
...MultipleConnections.args,
|
|
421
511
|
openOnMount: true,
|
|
422
512
|
},
|
|
513
|
+
parameters: {
|
|
514
|
+
...MultipleConnections.parameters,
|
|
515
|
+
docs: {
|
|
516
|
+
description: {
|
|
517
|
+
story:
|
|
518
|
+
'Renders three pooled useReqraftWebSocket panels. When Close All closes the shared socket, every consumer transitions to CLOSED at once and each panel logs its close handler.',
|
|
519
|
+
},
|
|
520
|
+
},
|
|
521
|
+
},
|
|
423
522
|
play: async (args) => {
|
|
424
523
|
await MultipleConnectionsOpenOnMount.play(args);
|
|
425
524
|
|
|
@@ -436,6 +535,15 @@ export const ConnectionIsClosedWhenAllUsersAreClosed: Story = {
|
|
|
436
535
|
...MultipleConnections.args,
|
|
437
536
|
openOnMount: true,
|
|
438
537
|
},
|
|
538
|
+
parameters: {
|
|
539
|
+
...MultipleConnections.parameters,
|
|
540
|
+
docs: {
|
|
541
|
+
description: {
|
|
542
|
+
story:
|
|
543
|
+
'Renders three pooled useReqraftWebSocket panels. Closing them one by one keeps the shared socket OPEN until the last consumer unmounts, then it closes.',
|
|
544
|
+
},
|
|
545
|
+
},
|
|
546
|
+
},
|
|
439
547
|
play: async (args) => {
|
|
440
548
|
await MultipleConnectionsOpenOnMount.play(args);
|
|
441
549
|
|
|
@@ -454,6 +562,15 @@ export const MultipleConnectionsHaveCustomHandlers: Story = {
|
|
|
454
562
|
...MultipleConnections.args,
|
|
455
563
|
openOnMount: true,
|
|
456
564
|
},
|
|
565
|
+
parameters: {
|
|
566
|
+
...MultipleConnections.parameters,
|
|
567
|
+
docs: {
|
|
568
|
+
description: {
|
|
569
|
+
story:
|
|
570
|
+
'Renders three pooled useReqraftWebSocket panels. Each panel attaches its own message handler, and sending a message triggers the handlers of only the connections still open.',
|
|
571
|
+
},
|
|
572
|
+
},
|
|
573
|
+
},
|
|
457
574
|
play: async (args) => {
|
|
458
575
|
const canvas = within(args.canvasElement);
|
|
459
576
|
|
|
@@ -480,6 +597,15 @@ export const MultipleConnectionsHaveCustomHandlers: Story = {
|
|
|
480
597
|
|
|
481
598
|
export const MultipleConnectionsCanBeDisconnectedAndReconnected: Story = {
|
|
482
599
|
...MultipleConnectionsHaveCustomHandlers,
|
|
600
|
+
parameters: {
|
|
601
|
+
...MultipleConnectionsHaveCustomHandlers.parameters,
|
|
602
|
+
docs: {
|
|
603
|
+
description: {
|
|
604
|
+
story:
|
|
605
|
+
'Renders three pooled useReqraftWebSocket panels after custom handlers have fired. Disconnecting one and reconnecting another proves individual consumers can toggle without disturbing peers.',
|
|
606
|
+
},
|
|
607
|
+
},
|
|
608
|
+
},
|
|
483
609
|
play: async (args) => {
|
|
484
610
|
await MultipleConnectionsHaveCustomHandlers.play(args);
|
|
485
611
|
|
package/src/index.tsx
CHANGED
|
@@ -10,6 +10,8 @@ export {
|
|
|
10
10
|
validateOptionWithRequiredGroups,
|
|
11
11
|
} from './helpers/validations';
|
|
12
12
|
export type { IValidationResult } from './helpers/validations';
|
|
13
|
+
export { thinScrollbar } from './helpers/scrollbar';
|
|
14
|
+
export * from './components/imageLightbox';
|
|
13
15
|
export { ReqraftMenu } from './components/menu/Menu';
|
|
14
16
|
export type {
|
|
15
17
|
IReqraftMenuItem,
|
|
@@ -33,7 +35,7 @@ export type {
|
|
|
33
35
|
ICurrentUser,
|
|
34
36
|
ICurrentUserStore,
|
|
35
37
|
} from './stores/currentUser/currentUser';
|
|
36
|
-
export { query } from './utils/fetch';
|
|
38
|
+
export { fetchConfig, query } from './utils/fetch';
|
|
37
39
|
export * from './utils/websocket';
|
|
38
40
|
export { ReqraftLspClient } from './utils/lspClient';
|
|
39
41
|
export type { IReqraftLspClientOptions } from './utils/lspClient';
|
|
@@ -52,6 +54,11 @@ export type {
|
|
|
52
54
|
ILspTextEdit,
|
|
53
55
|
TLspDocumentText,
|
|
54
56
|
} from './utils/lspClient.types';
|
|
57
|
+
export * from './components/composer';
|
|
55
58
|
export * from './components/smartEditor';
|
|
56
59
|
export * from './components/dpqlEditor';
|
|
57
60
|
export * from './components/qonsoleSmartInput';
|
|
61
|
+
export * from './components/supportTicket';
|
|
62
|
+
export * from './components/ticketThread';
|
|
63
|
+
export * from './components/ticketReferences';
|
|
64
|
+
export * from './components/searchFilterBar';
|
|
@@ -9,6 +9,23 @@ import { TReqraftStorageValue } from '../hooks/useStorage/useStorage';
|
|
|
9
9
|
import { currentUserStore } from '../stores/currentUser/currentUser';
|
|
10
10
|
import { IReqraftProviderProps } from './ReqraftProvider';
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Apply a single path write onto the current storage blob, seeding a fresh object
|
|
14
|
+
* when the user has no storage yet.
|
|
15
|
+
*
|
|
16
|
+
* `?? {}` is load-bearing: a user who has never written storage has
|
|
17
|
+
* `storage === undefined | null`, and `set(cloneDeep(undefined), …)` returns
|
|
18
|
+
* `undefined` (lodash won't set on a nullish target). Without the seed the FIRST
|
|
19
|
+
* write for such a user — e.g. accepting the cookie-consent banner — persists an
|
|
20
|
+
* empty body and silently drops the value, so the flag stays unset forever and
|
|
21
|
+
* the banner never closes. Exported for testing.
|
|
22
|
+
*/
|
|
23
|
+
export const applyStorageWrite = (
|
|
24
|
+
storage: TReqraftStorage | undefined,
|
|
25
|
+
path: string,
|
|
26
|
+
value: unknown
|
|
27
|
+
): TReqraftStorage => set(cloneDeep(storage) ?? {}, path, value);
|
|
28
|
+
|
|
12
29
|
export interface IReqraftStorageProviderProps
|
|
13
30
|
extends Pick<IReqraftProviderProps, 'waitForStorage'> {
|
|
14
31
|
children: ReactNode;
|
|
@@ -53,26 +70,35 @@ export const ReqraftUserProvider = ({ children, waitForStorage }: IReqraftStorag
|
|
|
53
70
|
includeAppPrefix: boolean = true
|
|
54
71
|
) {
|
|
55
72
|
const _path = includeAppPrefix ? `${appName}.${path}` : path;
|
|
56
|
-
|
|
73
|
+
// Base the write on the LATEST storage read straight from the store, not
|
|
74
|
+
// the blob captured in this callback's closure. A caller holding a stale
|
|
75
|
+
// updater — e.g. an imperative store subscription created on mount, before
|
|
76
|
+
// other keys had loaded — would otherwise persist an out-of-date blob and
|
|
77
|
+
// wipe every key written to storage since its closure was captured.
|
|
78
|
+
const latestStorage = currentUserStore.getState().currentUser?.storage;
|
|
79
|
+
const updatedStorage = applyStorageWrite(latestStorage, _path, value);
|
|
57
80
|
|
|
58
81
|
updateCurrentUserStorage(updatedStorage);
|
|
59
82
|
|
|
60
83
|
load({ body: { storage: updatedStorage } });
|
|
61
84
|
},
|
|
62
|
-
[appName,
|
|
85
|
+
[appName, load, updateCurrentUserStorage]
|
|
63
86
|
);
|
|
64
87
|
|
|
65
88
|
const removeStorageValue = useCallback(
|
|
66
89
|
function (path: string, includeAppPrefix: boolean = true) {
|
|
67
90
|
const _path = includeAppPrefix ? `${appName}.${path}` : path;
|
|
68
91
|
|
|
69
|
-
|
|
92
|
+
// Same as `updateStorage`: mutate the LATEST blob (never a stale closure
|
|
93
|
+
// copy), seeding a fresh object when there's no storage yet.
|
|
94
|
+
const latestStorage = currentUserStore.getState().currentUser?.storage;
|
|
95
|
+
const updatedStorage = applyStorageWrite(latestStorage, _path, null);
|
|
70
96
|
|
|
71
97
|
updateCurrentUserStorage(updatedStorage);
|
|
72
98
|
|
|
73
99
|
load({ body: { storage_path: _path } });
|
|
74
100
|
},
|
|
75
|
-
[appName,
|
|
101
|
+
[appName, load, updateCurrentUserStorage]
|
|
76
102
|
);
|
|
77
103
|
|
|
78
104
|
const contextValue = useMemo(
|
|
@@ -6,6 +6,38 @@ import { currentUserStore } from './currentUser';
|
|
|
6
6
|
|
|
7
7
|
const meta = {
|
|
8
8
|
title: 'Stores/Current User',
|
|
9
|
+
parameters: {
|
|
10
|
+
mockData: [
|
|
11
|
+
{
|
|
12
|
+
url: 'https://hq.qoretechnologies.com:8092/api/latest/users?action=current',
|
|
13
|
+
method: 'GET',
|
|
14
|
+
status: 200,
|
|
15
|
+
response: {
|
|
16
|
+
provider: 'local',
|
|
17
|
+
username: 'david',
|
|
18
|
+
name: 'David Nichols',
|
|
19
|
+
has_default: true,
|
|
20
|
+
roles: ['admin'],
|
|
21
|
+
permissions: ['USER-CONTROL'],
|
|
22
|
+
workflows: [],
|
|
23
|
+
services: [],
|
|
24
|
+
jobs: [],
|
|
25
|
+
mappers: [],
|
|
26
|
+
vmaps: [],
|
|
27
|
+
groups: [],
|
|
28
|
+
fsms: [],
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
],
|
|
32
|
+
},
|
|
33
|
+
beforeEach: () => {
|
|
34
|
+
currentUserStore.setState({
|
|
35
|
+
currentUser: undefined,
|
|
36
|
+
loading: false,
|
|
37
|
+
error: undefined,
|
|
38
|
+
errorData: undefined,
|
|
39
|
+
});
|
|
40
|
+
},
|
|
9
41
|
render: () => {
|
|
10
42
|
const { load, loading, currentUser = {}, errorData } = currentUserStore();
|
|
11
43
|
|
|
@@ -26,6 +58,14 @@ export default meta;
|
|
|
26
58
|
export type Story = StoryObj<typeof meta>;
|
|
27
59
|
|
|
28
60
|
export const CurrentUserCanBeLoaded: Story = {
|
|
61
|
+
parameters: {
|
|
62
|
+
docs: {
|
|
63
|
+
description: {
|
|
64
|
+
story:
|
|
65
|
+
'Renders a demo that reads currentUserStore — the store loads /users?action=current and the returned user object is displayed as a tree.',
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
29
69
|
play: async () => {
|
|
30
70
|
await testsWaitForText('"David Nichols"');
|
|
31
71
|
},
|
|
@@ -33,6 +73,15 @@ export const CurrentUserCanBeLoaded: Story = {
|
|
|
33
73
|
|
|
34
74
|
export const CurrentUserHasPermissions: Story = {
|
|
35
75
|
...CurrentUserCanBeLoaded,
|
|
76
|
+
parameters: {
|
|
77
|
+
...CurrentUserCanBeLoaded.parameters,
|
|
78
|
+
docs: {
|
|
79
|
+
description: {
|
|
80
|
+
story:
|
|
81
|
+
'Renders a demo that calls currentUserStore().hasAnyPermission — the affirmative message renders because the loaded user carries at least one of the requested permissions.',
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
36
85
|
render: () => {
|
|
37
86
|
const { hasAnyPermission } = currentUserStore();
|
|
38
87
|
|