@rebasepro/admin 0.13.0 → 0.13.1-canary.g394d868
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{CollectionEditorDialog-CjXkWZ64.js → CollectionEditorDialog-D78xsXgL.js} +3 -3
- package/dist/{CollectionEditorDialog-CjXkWZ64.js.map → CollectionEditorDialog-D78xsXgL.js.map} +1 -1
- package/dist/{PropertyEditView-W8I3YV89.js → PropertyEditView-B88EC-1f.js} +2 -2
- package/dist/{PropertyEditView-W8I3YV89.js.map → PropertyEditView-B88EC-1f.js.map} +1 -1
- package/dist/{RouterCollectionsStudioView-BsLTNoqX.js → RouterCollectionsStudioView-B1Ws7h9l.js} +4 -4
- package/dist/{RouterCollectionsStudioView-BsLTNoqX.js.map → RouterCollectionsStudioView-B1Ws7h9l.js.map} +1 -1
- package/dist/collection_editor/serializable_types.d.ts +1 -0
- package/dist/collection_editor_ui.js +4 -4
- package/dist/components/AdditionalFieldValue.d.ts +25 -0
- package/dist/components/CollectionViewBinding/CollectionViewBinding.d.ts +16 -0
- package/dist/components/CollectionViewBinding/SlotValue.d.ts +63 -0
- package/dist/components/CollectionViewBinding/SplitListCloseButton.d.ts +17 -4
- package/dist/components/CollectionViewBinding/SplitListShowButton.d.ts +12 -0
- package/dist/components/CollectionViewBinding/usePreviewSlots.d.ts +55 -5
- package/dist/components/DetailViewBinding.d.ts +15 -0
- package/dist/components/DrawerNavigationGroup.d.ts +9 -6
- package/dist/components/DrawerNavigationItem.d.ts +16 -6
- package/dist/components/EditViewBinding.d.ts +30 -1
- package/dist/components/EntityDisplayHeader.d.ts +53 -0
- package/dist/components/EntityIdentityBar.d.ts +43 -1
- package/dist/components/EntityViewBinding.d.ts +60 -6
- package/dist/components/RebaseLayout.d.ts +1 -1
- package/dist/components/RebaseShell.d.ts +1 -1
- package/dist/components/RelationSelector.d.ts +8 -1
- package/dist/components/UserSelector.d.ts +8 -1
- package/dist/components/app/Scaffold.d.ts +9 -3
- package/dist/components/index.d.ts +2 -0
- package/dist/{export-J79rMoNj.js → export-BZhdWhkB.js} +2 -2
- package/dist/{export-J79rMoNj.js.map → export-BZhdWhkB.js.map} +1 -1
- package/dist/form/components/FieldBlock.d.ts +58 -4
- package/dist/form/components/FormRail.d.ts +13 -0
- package/dist/form/components/FormSections.d.ts +15 -1
- package/dist/form/components/useRailVisible.d.ts +5 -4
- package/dist/form/form_utils.d.ts +38 -1
- package/dist/{history-D7L_JlLO.js → history-BPpt68CL.js} +4 -4
- package/dist/{history-D7L_JlLO.js.map → history-BPpt68CL.js.map} +1 -1
- package/dist/hooks/useEntityDisplay.d.ts +102 -0
- package/dist/hooks/useEntityDisplayTitle.d.ts +18 -9
- package/dist/{import-BvYl--U4.js → import-gRfemegK.js} +3 -3
- package/dist/{import-BvYl--U4.js.map → import-gRfemegK.js.map} +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +152 -57
- package/dist/index.js.map +1 -1
- package/dist/preview/compact_text.d.ts +23 -0
- package/dist/preview/components/BooleanPreview.d.ts +3 -1
- package/dist/preview/property_previews/ArrayOfStringsPreview.d.ts +1 -1
- package/dist/preview/property_previews/ArrayOneOfPreview.d.ts +1 -1
- package/dist/preview/property_previews/ArrayPropertyPreview.d.ts +1 -1
- package/dist/preview/property_previews/MapPropertyPreview.d.ts +1 -1
- package/dist/preview/property_previews/StringPropertyPreview.d.ts +1 -1
- package/dist/routes/RebaseRoute.d.ts +1 -1
- package/dist/types/components/PropertyPreviewProps.d.ts +19 -0
- package/dist/util/number_format.d.ts +14 -0
- package/dist/util/previews.d.ts +9 -2
- package/dist/{util-GiwPgxnL.js → util-BHq3VOjH.js} +9080 -7861
- package/dist/util-BHq3VOjH.js.map +1 -0
- package/package.json +9 -9
- package/src/collection_editor/serializable_types.ts +1 -0
- package/src/components/AdditionalFieldValue.tsx +68 -0
- package/src/components/CollectionTableBinding/CollectionTableBinding.tsx +7 -8
- package/src/components/CollectionTableBinding/PropertyTableCell.tsx +2 -2
- package/src/components/CollectionTableBinding/column_utils.tsx +15 -6
- package/src/components/CollectionTableBinding/internal/popup_field/PopupFormField.tsx +2 -2
- package/src/components/CollectionViewBinding/BoardCardBinding.tsx +26 -27
- package/src/components/CollectionViewBinding/CollectionListViewBinding.tsx +172 -112
- package/src/components/CollectionViewBinding/CollectionViewActions.tsx +1 -1
- package/src/components/CollectionViewBinding/CollectionViewBinding.tsx +38 -8
- package/src/components/CollectionViewBinding/EntityCardBinding.tsx +23 -36
- package/src/components/CollectionViewBinding/SlotValue.tsx +156 -0
- package/src/components/CollectionViewBinding/SplitListCloseButton.tsx +23 -7
- package/src/components/CollectionViewBinding/SplitListShowButton.tsx +28 -0
- package/src/components/CollectionViewBinding/SplitListView.tsx +33 -2
- package/src/components/CollectionViewBinding/useBoardDataController.tsx +12 -3
- package/src/components/CollectionViewBinding/usePreviewSlots.ts +177 -54
- package/src/components/DefaultDrawer.tsx +19 -23
- package/src/components/DetailViewBinding.tsx +87 -55
- package/src/components/DrawerNavigationGroup.tsx +16 -39
- package/src/components/DrawerNavigationItem.tsx +44 -16
- package/src/components/EditViewBinding.tsx +211 -41
- package/src/components/EntityDisplayHeader.tsx +170 -0
- package/src/components/EntityIdentityBar.tsx +149 -23
- package/src/components/EntityPreviewBinding.tsx +58 -18
- package/src/components/EntityViewBinding.tsx +342 -41
- package/src/components/InlineEntityPreview.tsx +10 -10
- package/src/components/RebaseLayout.tsx +2 -2
- package/src/components/RebaseShell.tsx +2 -2
- package/src/components/ReferenceTable/SelectionTableBinding.tsx +4 -2
- package/src/components/RelationSelector.tsx +15 -4
- package/src/components/SideDialogs.tsx +15 -3
- package/src/components/SidePanelBinding.tsx +122 -66
- package/src/components/UserSelector.tsx +15 -4
- package/src/components/app/Scaffold.tsx +112 -10
- package/src/components/index.ts +3 -0
- package/src/form/EntityForm.tsx +64 -52
- package/src/form/EntityFormBinding.tsx +24 -11
- package/src/form/PropertyFieldBinding.tsx +2 -2
- package/src/form/components/FieldBlock.tsx +80 -10
- package/src/form/components/FormRail.tsx +19 -2
- package/src/form/components/FormSections.tsx +32 -5
- package/src/form/components/StorageUploadProgress.tsx +9 -3
- package/src/form/components/useRailVisible.tsx +5 -4
- package/src/form/field_bindings/ArrayCustomShapedFieldBinding.tsx +2 -2
- package/src/form/field_bindings/BinaryFieldBinding.tsx +10 -5
- package/src/form/field_bindings/BlockFieldBinding.tsx +3 -3
- package/src/form/field_bindings/GeopointFieldBinding.tsx +23 -16
- package/src/form/field_bindings/MapFieldBinding.tsx +2 -2
- package/src/form/field_bindings/MarkdownEditorFieldBinding.tsx +13 -7
- package/src/form/field_bindings/MultiSelectFieldBinding.tsx +7 -1
- package/src/form/field_bindings/ReadOnlyFieldBinding.tsx +7 -1
- package/src/form/field_bindings/ReferenceFieldBinding.tsx +1 -1
- package/src/form/field_bindings/RelationFieldBinding.tsx +5 -2
- package/src/form/field_bindings/RepeatFieldBinding.tsx +3 -3
- package/src/form/field_bindings/StorageUploadFieldBinding.tsx +2 -2
- package/src/form/field_bindings/UserSelectFieldBinding.tsx +4 -1
- package/src/form/field_bindings/VectorFieldBinding.tsx +6 -4
- package/src/form/form_utils.ts +78 -0
- package/src/hooks/navigation/useBuildCollectionRegistryController.tsx +29 -5
- package/src/hooks/useEntityDisplay.tsx +262 -0
- package/src/hooks/useEntityDisplayTitle.tsx +27 -38
- package/src/index.ts +9 -1
- package/src/preview/PropertyPreview.tsx +25 -5
- package/src/preview/compact_text.ts +68 -0
- package/src/preview/components/BooleanPreview.tsx +5 -2
- package/src/preview/components/StorageThumbnail.tsx +5 -2
- package/src/preview/property_previews/ArrayOfRelationsPreview.tsx +6 -3
- package/src/preview/property_previews/ArrayOfStringsPreview.tsx +26 -1
- package/src/preview/property_previews/ArrayOneOfPreview.tsx +10 -1
- package/src/preview/property_previews/ArrayPropertyPreview.tsx +10 -1
- package/src/preview/property_previews/MapPropertyPreview.tsx +52 -1
- package/src/preview/property_previews/NumberPropertyPreview.tsx +9 -1
- package/src/preview/property_previews/StringPropertyPreview.tsx +8 -5
- package/src/routes/RebaseRoute.tsx +62 -4
- package/src/types/components/PropertyPreviewProps.tsx +21 -0
- package/src/util/number_format.ts +46 -0
- package/src/util/previews.ts +8 -55
- package/src/util/property_utils.tsx +6 -1
- package/dist/util-GiwPgxnL.js.map +0 -1
|
@@ -4,7 +4,7 @@ import { deepEqual as equal } from "fast-equals"
|
|
|
4
4
|
|
|
5
5
|
import { EntityReference, EntityRelation } from "@rebasepro/types";
|
|
6
6
|
import type { PropertyPreviewProps } from "../types/components/PropertyPreviewProps";
|
|
7
|
-
import { resolveProperty, normalizeToEntityRelation } from "@rebasepro/common";
|
|
7
|
+
import { resolveProperty, normalizeToEntityRelation, getRelationTargetPath } from "@rebasepro/common";
|
|
8
8
|
import { useAuthController, useCustomizationController, resolveComponentRef } from "@rebasepro/app";
|
|
9
9
|
import { EmptyValue } from "./components/EmptyValue";
|
|
10
10
|
import { UrlComponentPreview } from "./components/UrlComponentPreview";
|
|
@@ -27,6 +27,7 @@ import { ErrorView } from "@rebasepro/app";
|
|
|
27
27
|
import { RelationPreview } from "./components/RelationPreview";
|
|
28
28
|
import { InlineEntityListPreview } from "./components/InlineEntityListPreview";
|
|
29
29
|
import { useIsNestedEntityPreview } from "../components/EntityPreviewNesting";
|
|
30
|
+
import { markdownToPlainText } from "./compact_text";
|
|
30
31
|
import { UserPreview } from "./components/UserPreview";
|
|
31
32
|
|
|
32
33
|
/**
|
|
@@ -50,6 +51,13 @@ export const PropertyPreview = React.memo(function PropertyPreview<P extends Pro
|
|
|
50
51
|
fill
|
|
51
52
|
} = props;
|
|
52
53
|
|
|
54
|
+
// A preview rendered inside an entity preview fills one line of a card and
|
|
55
|
+
// has no room to grow — see {@link PropertyPreviewProps.compact}. The card
|
|
56
|
+
// is the thing that knows this, and it says so by opening a nesting
|
|
57
|
+
// boundary, so the depth is the signal rather than a prop every caller
|
|
58
|
+
// between here and there would have to forward.
|
|
59
|
+
const compact = props.compact ?? (nested || Boolean(props.textOnly));
|
|
60
|
+
|
|
53
61
|
const property = resolveProperty({
|
|
54
62
|
propertyKey,
|
|
55
63
|
property: inputProperty,
|
|
@@ -103,7 +111,9 @@ export const PropertyPreview = React.memo(function PropertyPreview<P extends Pro
|
|
|
103
111
|
fill={fill}
|
|
104
112
|
previewType={stringProperty.admin?.urlPreview}/>;
|
|
105
113
|
} else if (stringProperty.admin?.markdown) {
|
|
106
|
-
content =
|
|
114
|
+
content = compact
|
|
115
|
+
? <span className={"text-sm"}>{markdownToPlainText(value)}</span>
|
|
116
|
+
: <Markdown source={value} size={"small"}/>;
|
|
107
117
|
} else if (stringProperty.userSelect) {
|
|
108
118
|
content = <UserPreview
|
|
109
119
|
value={value}
|
|
@@ -113,6 +123,7 @@ export const PropertyPreview = React.memo(function PropertyPreview<P extends Pro
|
|
|
113
123
|
/>;
|
|
114
124
|
} else {
|
|
115
125
|
content = <StringPropertyPreview {...props}
|
|
126
|
+
compact={compact}
|
|
116
127
|
property={stringProperty}
|
|
117
128
|
value={value}/>;
|
|
118
129
|
}
|
|
@@ -129,6 +140,7 @@ export const PropertyPreview = React.memo(function PropertyPreview<P extends Pro
|
|
|
129
140
|
if (arrayProperty.of) {
|
|
130
141
|
if (Array.isArray(arrayProperty.of)) {
|
|
131
142
|
content = <ArrayPropertyPreview {...props}
|
|
143
|
+
compact={compact}
|
|
132
144
|
value={value}
|
|
133
145
|
property={arrayProperty}/>;
|
|
134
146
|
} else if (arrayProperty.of.type === "reference") {
|
|
@@ -153,6 +165,7 @@ export const PropertyPreview = React.memo(function PropertyPreview<P extends Pro
|
|
|
153
165
|
} else {
|
|
154
166
|
content = <ArrayOfStringsPreview
|
|
155
167
|
{...props}
|
|
168
|
+
compact={compact}
|
|
156
169
|
property={property as ArrayProperty}
|
|
157
170
|
value={value as string[]}/>;
|
|
158
171
|
}
|
|
@@ -163,11 +176,13 @@ export const PropertyPreview = React.memo(function PropertyPreview<P extends Pro
|
|
|
163
176
|
property={property as ArrayProperty}/>;
|
|
164
177
|
} else {
|
|
165
178
|
content = <ArrayPropertyPreview {...props}
|
|
179
|
+
compact={compact}
|
|
166
180
|
value={value}
|
|
167
181
|
property={property as ArrayProperty}/>;
|
|
168
182
|
}
|
|
169
183
|
} else if (arrayProperty.oneOf) {
|
|
170
184
|
content = <ArrayOneOfPreview {...props}
|
|
185
|
+
compact={compact}
|
|
171
186
|
value={value}
|
|
172
187
|
property={property as ArrayProperty}/>;
|
|
173
188
|
}
|
|
@@ -178,6 +193,7 @@ export const PropertyPreview = React.memo(function PropertyPreview<P extends Pro
|
|
|
178
193
|
if (typeof value === "object") {
|
|
179
194
|
content =
|
|
180
195
|
<MapPropertyPreview {...props}
|
|
196
|
+
compact={compact}
|
|
181
197
|
value={value as Record<string, any>}
|
|
182
198
|
property={property as MapProperty}/>;
|
|
183
199
|
} else {
|
|
@@ -218,12 +234,16 @@ export const PropertyPreview = React.memo(function PropertyPreview<P extends Pro
|
|
|
218
234
|
}
|
|
219
235
|
|
|
220
236
|
} else if (property.type === "relation") {
|
|
237
|
+
// A relation column arrives either hydrated or as the bare foreign key,
|
|
238
|
+
// depending on the fetch path. The declared target resolves the latter;
|
|
239
|
+
// see `normalizeToEntityRelation`.
|
|
240
|
+
const relationTargetPath = getRelationTargetPath(property);
|
|
221
241
|
if (!value) {
|
|
222
242
|
content = <EmptyValue/>;
|
|
223
243
|
} else if (Array.isArray(value)) {
|
|
224
244
|
// Many-cardinality relation: value is an array of EntityRelation (or plain objects)
|
|
225
245
|
const relations = (value as unknown[])
|
|
226
|
-
.map(item => normalizeToEntityRelation(item, "relation"))
|
|
246
|
+
.map(item => normalizeToEntityRelation(item, "relation", relationTargetPath))
|
|
227
247
|
.filter(Boolean) as EntityRelation[];
|
|
228
248
|
const renderRelation = (entityRelation: EntityRelation, index: number) => <RelationPreview
|
|
229
249
|
key={`preview_rel_${propertyKey}_${index}`}
|
|
@@ -252,7 +272,7 @@ export const PropertyPreview = React.memo(function PropertyPreview<P extends Pro
|
|
|
252
272
|
);
|
|
253
273
|
} else {
|
|
254
274
|
// Single-cardinality relation
|
|
255
|
-
const relationValue = normalizeToEntityRelation(value, "relation");
|
|
275
|
+
const relationValue = normalizeToEntityRelation(value, "relation", relationTargetPath);
|
|
256
276
|
if (relationValue) {
|
|
257
277
|
content = <RelationPreview
|
|
258
278
|
disabled={!property.relation}
|
|
@@ -270,7 +290,7 @@ export const PropertyPreview = React.memo(function PropertyPreview<P extends Pro
|
|
|
270
290
|
|
|
271
291
|
} else if (property.type === "boolean") {
|
|
272
292
|
if (typeof value === "boolean") {
|
|
273
|
-
content = <BooleanPreview value={value} size={size} property={property}/>;
|
|
293
|
+
content = <BooleanPreview value={value} size={size} property={property} hideLabel={props.hideLabel}/>;
|
|
274
294
|
} else {
|
|
275
295
|
content = buildWrongValueType(propertyKey, property.type, value);
|
|
276
296
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turning block content into the one line a compact preview has room for.
|
|
3
|
+
*
|
|
4
|
+
* A preview slot inside a card is a single line that cannot grow. Rendering
|
|
5
|
+
* Markdown there does not produce a small version of the document — it
|
|
6
|
+
* produces the whole document, headings and lists included, inside a 44px box.
|
|
7
|
+
* These helpers take the opening line instead, which is what a person reads
|
|
8
|
+
* off a card anyway.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** Longer than any card line; the cap only keeps the DOM node small. */
|
|
12
|
+
const DEFAULT_MAX_LENGTH = 280;
|
|
13
|
+
|
|
14
|
+
const FENCED_CODE = /```[\s\S]*?```|~~~[\s\S]*?~~~/g;
|
|
15
|
+
const HTML_TAG = /<[^>]*>/g;
|
|
16
|
+
const IMAGE = /!\[([^\]]*)\]\([^)]*\)/g;
|
|
17
|
+
const LINK = /\[([^\]]*)\]\([^)]*\)/g;
|
|
18
|
+
const REFERENCE_LINK = /\[([^\]]*)\]\[[^\]]*\]/g;
|
|
19
|
+
const HEADING = /^\s{0,3}#{1,6}\s+/gm;
|
|
20
|
+
const BLOCKQUOTE = /^\s{0,3}>\s?/gm;
|
|
21
|
+
const LIST_MARKER = /^\s*(?:[-*+]|\d+[.)])\s+/gm;
|
|
22
|
+
const THEMATIC_BREAK = /^\s{0,3}(?:[-*_]\s*){3,}$/gm;
|
|
23
|
+
const TABLE_PIPE = /[|]/g;
|
|
24
|
+
const EMPHASIS = /(\*\*|__|\*|_|~~|`)/g;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The readable text of a Markdown document, as one line.
|
|
28
|
+
*
|
|
29
|
+
* Deliberately a lexical strip rather than a parse: this runs once per preview
|
|
30
|
+
* cell in a table that can hold hundreds, and the result is truncated to a line
|
|
31
|
+
* regardless, so the cost of being exactly right is not worth paying. Anything
|
|
32
|
+
* it fails to strip degrades to a stray `*`, never to a broken layout.
|
|
33
|
+
*/
|
|
34
|
+
export function markdownToPlainText(source: string, maxLength: number = DEFAULT_MAX_LENGTH): string {
|
|
35
|
+
if (!source) return "";
|
|
36
|
+
|
|
37
|
+
const text = source
|
|
38
|
+
.replace(FENCED_CODE, " ")
|
|
39
|
+
.replace(IMAGE, "$1")
|
|
40
|
+
.replace(LINK, "$1")
|
|
41
|
+
.replace(REFERENCE_LINK, "$1")
|
|
42
|
+
.replace(THEMATIC_BREAK, " ")
|
|
43
|
+
.replace(HEADING, "")
|
|
44
|
+
.replace(BLOCKQUOTE, "")
|
|
45
|
+
.replace(LIST_MARKER, "")
|
|
46
|
+
.replace(HTML_TAG, "")
|
|
47
|
+
.replace(TABLE_PIPE, " ")
|
|
48
|
+
.replace(EMPHASIS, "");
|
|
49
|
+
|
|
50
|
+
return collapseToSingleLine(text, maxLength);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Whitespace — including the newlines that would otherwise let a `truncate`
|
|
55
|
+
* container grow — collapsed to single spaces, capped to `maxLength`.
|
|
56
|
+
*/
|
|
57
|
+
export function collapseToSingleLine(source: string, maxLength: number = DEFAULT_MAX_LENGTH): string {
|
|
58
|
+
if (!source) return "";
|
|
59
|
+
const collapsed = source.replace(/\s+/g, " ").trim();
|
|
60
|
+
if (collapsed.length <= maxLength) return collapsed;
|
|
61
|
+
// Cut at a word boundary when there is one nearby, so the line does not end
|
|
62
|
+
// mid-word for the sake of ten characters.
|
|
63
|
+
const cut = collapsed.slice(0, maxLength);
|
|
64
|
+
// The cut already landed between words: keep the last one whole.
|
|
65
|
+
if (collapsed[maxLength] === " ") return cut.trimEnd() + "…";
|
|
66
|
+
const lastSpace = cut.lastIndexOf(" ");
|
|
67
|
+
return (lastSpace > maxLength * 0.6 ? cut.slice(0, lastSpace) : cut).trimEnd() + "…";
|
|
68
|
+
}
|
|
@@ -8,18 +8,21 @@ import { PreviewSize } from "../../types/components/PropertyPreviewProps";
|
|
|
8
8
|
export function BooleanPreview({
|
|
9
9
|
value,
|
|
10
10
|
size,
|
|
11
|
-
property
|
|
11
|
+
property,
|
|
12
|
+
hideLabel
|
|
12
13
|
}: {
|
|
13
14
|
value: boolean,
|
|
14
15
|
size: PreviewSize,
|
|
15
16
|
property: Property,
|
|
17
|
+
/** The caller already shows the name — see `PropertyPreviewProps.hideLabel`. */
|
|
18
|
+
hideLabel?: boolean,
|
|
16
19
|
}): React.ReactElement {
|
|
17
20
|
return <div className={"flex flex-row gap-2 items-center"}>
|
|
18
21
|
<Checkbox checked={value}
|
|
19
22
|
padding={false}
|
|
20
23
|
size={size}
|
|
21
24
|
color={"secondary"}/>
|
|
22
|
-
{property.name && <span
|
|
25
|
+
{!hideLabel && property.name && <span
|
|
23
26
|
className={cls("text-text-secondary dark:text-text-secondary-dark", size === "small" ? "text-sm" : "")}>{property.name}</span>}
|
|
24
27
|
</div>;
|
|
25
28
|
}
|
|
@@ -2,7 +2,7 @@ import React, { useEffect } from "react";
|
|
|
2
2
|
|
|
3
3
|
import { renderSkeletonImageThumbnail } from "../property_previews/SkeletonPropertyComponent";
|
|
4
4
|
import { UrlComponentPreview } from "./UrlComponentPreview";
|
|
5
|
-
import { ErrorView, useStorageSource, useStorageSources } from "@rebasepro/app";
|
|
5
|
+
import { ErrorView, useStorageSource, useStorageSources, useTranslation } from "@rebasepro/app";
|
|
6
6
|
import { resolveStorageSource } from "@rebasepro/common";
|
|
7
7
|
import { DownloadConfig, FileType } from "@rebasepro/types";
|
|
8
8
|
import type { PreviewSize } from "../../types/components/PropertyPreviewProps";
|
|
@@ -42,6 +42,7 @@ export function StorageThumbnailInternal({
|
|
|
42
42
|
storageSourceKey
|
|
43
43
|
}: StorageThumbnailProps) {
|
|
44
44
|
|
|
45
|
+
const { t } = useTranslation();
|
|
45
46
|
const [error, setError] = React.useState<Error | undefined>(undefined);
|
|
46
47
|
const defaultStorage = useStorageSource();
|
|
47
48
|
const storageSources = useStorageSources();
|
|
@@ -85,7 +86,9 @@ export function StorageThumbnailInternal({
|
|
|
85
86
|
: (filetype?.startsWith("audio") ? "audio" : "file"));
|
|
86
87
|
|
|
87
88
|
if (downloadConfig?.fileNotFound)
|
|
88
|
-
|
|
89
|
+
// `file_not_found` is translated into seven locales and this rendered
|
|
90
|
+
// the English literal, so a German panel said "File not found".
|
|
91
|
+
return <ErrorView error={t("file_not_found")}></ErrorView>
|
|
89
92
|
|
|
90
93
|
return downloadConfig?.url
|
|
91
94
|
? <UrlComponentPreview previewType={previewType}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ArrayProperty, RelationProperty } from "@rebasepro/types";
|
|
2
2
|
import { EntityRelation } from "@rebasepro/types";
|
|
3
3
|
import type { PropertyPreviewProps } from "../../types/components/PropertyPreviewProps";
|
|
4
|
-
import { normalizeToEntityRelation } from "@rebasepro/common";
|
|
4
|
+
import { normalizeToEntityRelation, getRelationTargetPath } from "@rebasepro/common";
|
|
5
5
|
import { RelationPreview } from "../components/RelationPreview";
|
|
6
6
|
import { useIsNestedEntityPreview } from "../../components/EntityPreviewNesting";
|
|
7
7
|
import { InlineEntityListPreview } from "../components/InlineEntityListPreview";
|
|
@@ -27,11 +27,14 @@ export function ArrayOfRelationsPreview({
|
|
|
27
27
|
throw Error("Picked wrong preview component ArrayOfRelationsPreview");
|
|
28
28
|
|
|
29
29
|
const ofProperty = property.of as RelationProperty;
|
|
30
|
+
// Elements arrive hydrated or as bare foreign keys depending on the fetch
|
|
31
|
+
// path; the declared target resolves the latter rather than dropping them.
|
|
32
|
+
const relationTargetPath = getRelationTargetPath(ofProperty);
|
|
30
33
|
|
|
31
34
|
// See ArrayOfReferencesPreview: nested lists stay on one wrapping line.
|
|
32
35
|
if (nested || textOnly) {
|
|
33
36
|
const relations = ((value ?? []) as unknown[])
|
|
34
|
-
.map(relation => normalizeToEntityRelation(relation))
|
|
37
|
+
.map(relation => normalizeToEntityRelation(relation, "relation", relationTargetPath))
|
|
35
38
|
.filter((relation): relation is EntityRelation => Boolean(relation));
|
|
36
39
|
return <InlineEntityListPreview
|
|
37
40
|
items={relations}
|
|
@@ -51,7 +54,7 @@ export function ArrayOfRelationsPreview({
|
|
|
51
54
|
<div className="flex flex-col w-full gap-0.5">
|
|
52
55
|
{value ?
|
|
53
56
|
(value as unknown[]).map((relation: unknown, index: number) => {
|
|
54
|
-
const entityRelation = normalizeToEntityRelation(relation);
|
|
57
|
+
const entityRelation = normalizeToEntityRelation(relation, "relation", relationTargetPath);
|
|
55
58
|
|
|
56
59
|
if (!entityRelation) return null;
|
|
57
60
|
|
|
@@ -12,7 +12,8 @@ export function ArrayOfStringsPreview({
|
|
|
12
12
|
value,
|
|
13
13
|
property: property,
|
|
14
14
|
// entity,
|
|
15
|
-
size
|
|
15
|
+
size,
|
|
16
|
+
compact
|
|
16
17
|
}: PropertyPreviewProps<ArrayProperty>) {
|
|
17
18
|
|
|
18
19
|
if (Array.isArray(property.of)) {
|
|
@@ -27,6 +28,30 @@ export function ArrayOfStringsPreview({
|
|
|
27
28
|
const stringProperty = property.of as StringProperty;
|
|
28
29
|
const arrayValues = value as string[];
|
|
29
30
|
|
|
31
|
+
// One line, so the items run along it separated by a dot rather than
|
|
32
|
+
// stacking. Each still renders through the string preview: a value that is
|
|
33
|
+
// a tag stays a tag, it just sits beside its neighbours.
|
|
34
|
+
if (compact) {
|
|
35
|
+
return (
|
|
36
|
+
<span className="inline-flex items-center gap-1 min-w-0 truncate">
|
|
37
|
+
{arrayValues &&
|
|
38
|
+
arrayValues.map((v, index: number) =>
|
|
39
|
+
<React.Fragment key={`preview_array_strings_${propertyKey}_${index}`}>
|
|
40
|
+
{index > 0 && <span className="opacity-40"
|
|
41
|
+
aria-hidden={true}>·</span>}
|
|
42
|
+
<ErrorBoundary>
|
|
43
|
+
<StringPropertyPreview propertyKey={propertyKey}
|
|
44
|
+
property={stringProperty}
|
|
45
|
+
value={v}
|
|
46
|
+
compact={true}
|
|
47
|
+
size={size}/>
|
|
48
|
+
</ErrorBoundary>
|
|
49
|
+
</React.Fragment>
|
|
50
|
+
)}
|
|
51
|
+
</span>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
30
55
|
return (
|
|
31
56
|
<div className="flex flex-col gap-2">
|
|
32
57
|
{arrayValues &&
|
|
@@ -14,7 +14,8 @@ export function ArrayOneOfPreview({
|
|
|
14
14
|
propertyKey,
|
|
15
15
|
value,
|
|
16
16
|
property: property,
|
|
17
|
-
size
|
|
17
|
+
size,
|
|
18
|
+
compact
|
|
18
19
|
// entity
|
|
19
20
|
}: PropertyPreviewProps<ArrayProperty>) {
|
|
20
21
|
|
|
@@ -37,6 +38,14 @@ export function ArrayOneOfPreview({
|
|
|
37
38
|
|
|
38
39
|
if (!values) return null;
|
|
39
40
|
|
|
41
|
+
// As in {@link ArrayPropertyPreview}: blocks stacked vertically, so a
|
|
42
|
+
// single line can only report the count.
|
|
43
|
+
if (compact) {
|
|
44
|
+
return <span className={"text-sm truncate"}>
|
|
45
|
+
{values.length === 1 ? "1 block" : `${values.length} blocks`}
|
|
46
|
+
</span>;
|
|
47
|
+
}
|
|
48
|
+
|
|
40
49
|
const childSize: PreviewSize = size === "medium" ? "medium" : "small";
|
|
41
50
|
|
|
42
51
|
const typeField = property.oneOf.typeField ?? DEFAULT_ONE_OF_TYPE;
|
|
@@ -15,7 +15,8 @@ export function ArrayPropertyPreview({
|
|
|
15
15
|
propertyKey,
|
|
16
16
|
value,
|
|
17
17
|
property: property,
|
|
18
|
-
size
|
|
18
|
+
size,
|
|
19
|
+
compact
|
|
19
20
|
}: PropertyPreviewProps<ArrayProperty>) {
|
|
20
21
|
|
|
21
22
|
if (property.type !== "array")
|
|
@@ -38,6 +39,14 @@ export function ArrayPropertyPreview({
|
|
|
38
39
|
|
|
39
40
|
if (!values) return null;
|
|
40
41
|
|
|
42
|
+
// Each entry is a block of its own — a bordered row, sometimes a whole
|
|
43
|
+
// sub-table. Stating how many there are is the only honest one-liner.
|
|
44
|
+
if (compact) {
|
|
45
|
+
return <span className={"text-sm truncate"}>
|
|
46
|
+
{values.length === 1 ? "1 item" : `${values.length} items`}
|
|
47
|
+
</span>;
|
|
48
|
+
}
|
|
49
|
+
|
|
41
50
|
const childSize: PreviewSize = size === "medium" ? "medium" : "small";
|
|
42
51
|
|
|
43
52
|
return (
|
|
@@ -14,7 +14,8 @@ export function MapPropertyPreview({
|
|
|
14
14
|
propertyKey,
|
|
15
15
|
value,
|
|
16
16
|
property,
|
|
17
|
-
size
|
|
17
|
+
size,
|
|
18
|
+
compact
|
|
18
19
|
}: PropertyPreviewProps<MapProperty>) {
|
|
19
20
|
|
|
20
21
|
if (property.type !== "map") {
|
|
@@ -23,6 +24,13 @@ export function MapPropertyPreview({
|
|
|
23
24
|
|
|
24
25
|
const mapProperty = property as MapProperty;
|
|
25
26
|
|
|
27
|
+
// A map is a table — a row per sub-property — and a card line has room for
|
|
28
|
+
// neither the rows nor the borders between them.
|
|
29
|
+
if (compact) {
|
|
30
|
+
return <CompactMapPreview property={mapProperty}
|
|
31
|
+
value={value as Record<string, unknown> | undefined}/>;
|
|
32
|
+
}
|
|
33
|
+
|
|
26
34
|
if (!mapProperty.properties || Object.keys(mapProperty.properties ?? {}).length === 0) {
|
|
27
35
|
return (
|
|
28
36
|
<KeyValuePreview value={value}/>
|
|
@@ -105,6 +113,49 @@ export function MapPropertyPreview({
|
|
|
105
113
|
|
|
106
114
|
}
|
|
107
115
|
|
|
116
|
+
/** How many leaf values a one-line map summary shows before it gives up. */
|
|
117
|
+
const COMPACT_MAP_ENTRIES = 3;
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* A map in one line: its first few leaf values, labelled, joined by dots.
|
|
121
|
+
*
|
|
122
|
+
* Nested maps and arrays are skipped rather than flattened — a line that says
|
|
123
|
+
* `[object Object]` is worse than a line that says nothing about that field —
|
|
124
|
+
* and a map with no leaves at all falls back to stating its size, which is at
|
|
125
|
+
* least true.
|
|
126
|
+
*/
|
|
127
|
+
function CompactMapPreview({
|
|
128
|
+
property,
|
|
129
|
+
value
|
|
130
|
+
}: { property: MapProperty, value: Record<string, unknown> | undefined }) {
|
|
131
|
+
|
|
132
|
+
if (!value || typeof value !== "object") return <EmptyValue/>;
|
|
133
|
+
|
|
134
|
+
const declared = property.properties;
|
|
135
|
+
const keys = declared ? Object.keys(declared) : Object.keys(value);
|
|
136
|
+
|
|
137
|
+
const parts: string[] = [];
|
|
138
|
+
for (const key of keys) {
|
|
139
|
+
if (parts.length >= COMPACT_MAP_ENTRIES) break;
|
|
140
|
+
const childValue = value[key];
|
|
141
|
+
if (childValue === undefined || childValue === null || childValue === "") continue;
|
|
142
|
+
if (typeof childValue === "object") continue;
|
|
143
|
+
const label = declared?.[key]?.name ?? key;
|
|
144
|
+
parts.push(`${label}: ${String(childValue)}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (parts.length === 0) {
|
|
148
|
+
const count = Object.keys(value).length;
|
|
149
|
+
return <Typography variant={"caption"}
|
|
150
|
+
color={"secondary"}
|
|
151
|
+
className={"truncate"}>
|
|
152
|
+
{count === 1 ? "1 field" : `${count} fields`}
|
|
153
|
+
</Typography>;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return <span className={"truncate text-sm"}>{parts.join(" · ")}</span>;
|
|
157
|
+
}
|
|
158
|
+
|
|
108
159
|
export function KeyValuePreview({ value }: { value: any }) {
|
|
109
160
|
if (typeof value !== "object") return null;
|
|
110
161
|
if (!value) return <EmptyValue/>;
|
|
@@ -4,6 +4,8 @@ import React from "react";
|
|
|
4
4
|
import { EnumValuesChip } from "../components/EnumValuesChip";
|
|
5
5
|
import type { PropertyPreviewProps } from "../../types/components/PropertyPreviewProps";
|
|
6
6
|
import { enumToObjectEntries } from "@rebasepro/common";
|
|
7
|
+
import { useCustomizationController } from "@rebasepro/app";
|
|
8
|
+
import { formatNumber } from "../../util/number_format";
|
|
7
9
|
|
|
8
10
|
/**
|
|
9
11
|
* @group Preview components
|
|
@@ -14,6 +16,7 @@ export function NumberPropertyPreview({
|
|
|
14
16
|
size
|
|
15
17
|
}: PropertyPreviewProps<NumberProperty>): React.ReactElement {
|
|
16
18
|
|
|
19
|
+
const customizationController = useCustomizationController();
|
|
17
20
|
const numValue = value as number;
|
|
18
21
|
|
|
19
22
|
if (property.enum) {
|
|
@@ -26,6 +29,11 @@ export function NumberPropertyPreview({
|
|
|
26
29
|
enumValues={enumValues}
|
|
27
30
|
size={size !== "medium" ? "small" : "medium"}/>;
|
|
28
31
|
} else {
|
|
29
|
-
|
|
32
|
+
// `admin.format` only — an undeclared number renders as the number that
|
|
33
|
+
// is in the database, which is the only thing we can honestly claim to
|
|
34
|
+
// know about it.
|
|
35
|
+
return <span className={size === "small" ? "text-sm" : ""}>
|
|
36
|
+
{formatNumber(numValue, property.admin?.format, customizationController?.locale)}
|
|
37
|
+
</span>;
|
|
30
38
|
}
|
|
31
39
|
}
|
|
@@ -7,6 +7,7 @@ import type { PropertyPreviewProps } from "../../types/components/PropertyPrevie
|
|
|
7
7
|
import { UrlComponentPreview } from "../components/UrlComponentPreview";
|
|
8
8
|
import { ErrorBoundary } from "@rebasepro/ui";
|
|
9
9
|
import { Chip, cls, getColorSchemeForSeed } from "@rebasepro/ui";
|
|
10
|
+
import { collapseToSingleLine } from "../compact_text";
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* @group Preview components
|
|
@@ -16,7 +17,8 @@ export function StringPropertyPreview({
|
|
|
16
17
|
value,
|
|
17
18
|
property,
|
|
18
19
|
size,
|
|
19
|
-
textOnly
|
|
20
|
+
textOnly,
|
|
21
|
+
compact
|
|
20
22
|
}: PropertyPreviewProps<StringProperty>): React.ReactElement {
|
|
21
23
|
|
|
22
24
|
const strValue = value as string;
|
|
@@ -45,10 +47,11 @@ export function StringPropertyPreview({
|
|
|
45
47
|
);
|
|
46
48
|
} else {
|
|
47
49
|
if (!strValue) return <></>;
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
// A title/subtitle (textOnly) or a card line (compact) is one line, and
|
|
51
|
+
// `truncate` only clamps text that has no newlines of its own to break
|
|
52
|
+
// on — so the newlines come out here rather than in CSS.
|
|
53
|
+
if (textOnly || compact) {
|
|
54
|
+
const singleLine = collapseToSingleLine(strValue);
|
|
52
55
|
return size === "small"
|
|
53
56
|
? <span className={"text-sm"}>{singleLine}</span>
|
|
54
57
|
: <>{singleLine}</>;
|
|
@@ -6,10 +6,10 @@ import { DetailViewBinding } from "../components/DetailViewBinding";
|
|
|
6
6
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
7
7
|
import { useNavigate } from "react-router";
|
|
8
8
|
import { CollectionViewBinding } from "../components/CollectionViewBinding/CollectionViewBinding";
|
|
9
|
-
import { NotFoundPage, useUserConfigurationPersistence, useComponentOverride, useNavigationBlocker } from "@rebasepro/app";
|
|
9
|
+
import { ErrorView, NotFoundPage, useUserConfigurationPersistence, useComponentOverride, useNavigationBlocker } from "@rebasepro/app";
|
|
10
10
|
import { resolveOpenEntityMode, resolveViewMode } from "../util/view_mode";
|
|
11
11
|
import { UnsavedChangesDialog } from "@rebasepro/app";
|
|
12
|
-
import { CircularProgressCenter } from "@rebasepro/ui";
|
|
12
|
+
import { CenteredView, CircularProgressCenter } from "@rebasepro/ui";
|
|
13
13
|
import { NavigationViewCollectionInternal, NavigationViewEntityCustomInternal, NavigationViewInternal } from "@rebasepro/app";
|
|
14
14
|
import { getNavigationEntriesFromPath } from "@rebasepro/app";
|
|
15
15
|
import { toArray } from "@rebasepro/utils";
|
|
@@ -94,7 +94,7 @@ export function RebaseRoute() {
|
|
|
94
94
|
if (!collectionRegistry.initialised) {
|
|
95
95
|
return <CircularProgressCenter/>;
|
|
96
96
|
}
|
|
97
|
-
return
|
|
97
|
+
return <UnresolvedCollectionView path={navigationEntries[0].slug}/>;
|
|
98
98
|
}
|
|
99
99
|
return <ResolvedCollectionView
|
|
100
100
|
key={`collection_view_${collection.slug}`}
|
|
@@ -117,7 +117,7 @@ export function RebaseRoute() {
|
|
|
117
117
|
if (!collectionRegistry.initialised) {
|
|
118
118
|
return <CircularProgressCenter/>;
|
|
119
119
|
}
|
|
120
|
-
return
|
|
120
|
+
return <UnresolvedCollectionView path={firstEntry.slug}/>;
|
|
121
121
|
}
|
|
122
122
|
return <ResolvedCollectionView
|
|
123
123
|
key={`collection_view_${collection.slug}`}
|
|
@@ -233,6 +233,7 @@ function EntityFullScreenRoute({
|
|
|
233
233
|
const urlController = useUrlController();
|
|
234
234
|
const navigate = useNavigate();
|
|
235
235
|
const location = useLocation();
|
|
236
|
+
const userConfigPersistence = useUserConfigurationPersistence();
|
|
236
237
|
|
|
237
238
|
// defaultValues may be carried via location.state when openNewDocument() is called
|
|
238
239
|
// for full-screen mode. We read it once on mount — after that, the form owns its state.
|
|
@@ -340,6 +341,30 @@ function EntityFullScreenRoute({
|
|
|
340
341
|
// Determine if this is a detail-view-first collection showing the view page
|
|
341
342
|
const isDetailMode = collection.defaultEntityAction === "view" && !isNew && !isCopy && entityId && !isEditRoute;
|
|
342
343
|
|
|
344
|
+
// A record is full screen either because its collection has no list to show
|
|
345
|
+
// beside it, or because the user folded that list away. Only the second is
|
|
346
|
+
// reversible, and it is reversible by dropping `#full` alone: this is the
|
|
347
|
+
// same URL the split route reads, so the same conditions decide it.
|
|
348
|
+
const splitListAvailable = !isNew && !isCopy && entityId !== undefined &&
|
|
349
|
+
navigationEntries[0]?.type === "collection" &&
|
|
350
|
+
(navigationEntries.length === 2 || navigationEntries.length === 3) &&
|
|
351
|
+
resolveOpenEntityMode({
|
|
352
|
+
collection,
|
|
353
|
+
viewMode: resolveViewMode({
|
|
354
|
+
collection,
|
|
355
|
+
search: location.search,
|
|
356
|
+
savedViewMode: userConfigPersistence?.getCollectionConfig(collection.slug)?.defaultViewMode as ViewMode | undefined
|
|
357
|
+
})
|
|
358
|
+
}) === "split";
|
|
359
|
+
|
|
360
|
+
const showList = splitListAvailable
|
|
361
|
+
? () => navigate({
|
|
362
|
+
pathname,
|
|
363
|
+
search: location.search,
|
|
364
|
+
hash: ""
|
|
365
|
+
})
|
|
366
|
+
: undefined;
|
|
367
|
+
|
|
343
368
|
if (isDetailMode) {
|
|
344
369
|
return <>
|
|
345
370
|
<DetailViewBinding
|
|
@@ -349,6 +374,7 @@ function EntityFullScreenRoute({
|
|
|
349
374
|
layout={"full_screen"}
|
|
350
375
|
path={collectionPath}
|
|
351
376
|
selectedTab={selectedTab ?? undefined}
|
|
377
|
+
onShowList={showList}
|
|
352
378
|
onEditClick={() => {
|
|
353
379
|
const editUrl = urlController.buildUrlCollectionPath(`${collectionPath}/${entityId}`) + "/edit";
|
|
354
380
|
navigate(editUrl + hash);
|
|
@@ -377,6 +403,7 @@ function EntityFullScreenRoute({
|
|
|
377
403
|
path={collectionPath}
|
|
378
404
|
copy={isCopy}
|
|
379
405
|
selectedTab={selectedTab ?? undefined}
|
|
406
|
+
onShowList={showList}
|
|
380
407
|
defaultValues={isNew ? defaultValues : undefined}
|
|
381
408
|
onValuesModified={(modified) => blocked.current = modified}
|
|
382
409
|
navigateBack={() => {
|
|
@@ -415,3 +442,34 @@ function EntityFullScreenRoute({
|
|
|
415
442
|
body={"You have unsaved changes in this entity."}/>
|
|
416
443
|
</>;
|
|
417
444
|
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Shown when a URL names a collection the registry cannot resolve.
|
|
448
|
+
*
|
|
449
|
+
* This used to be `return null`, which renders an empty pane inside the app
|
|
450
|
+
* chrome: the sidebar, the nav highlight and the breadcrumb all still work,
|
|
451
|
+
* because those read the collections array directly, while the view itself is
|
|
452
|
+
* blank. Nothing is thrown, and the only trace is a `console.debug` — so the
|
|
453
|
+
* panel looks like it lost its data rather than like it failed to find the
|
|
454
|
+
* collection, and there is no string to search for.
|
|
455
|
+
*
|
|
456
|
+
* A slug containing slashes went unresolvable this way and cost an afternoon to
|
|
457
|
+
* find. The lookup is fixed; this is the part that made it expensive.
|
|
458
|
+
*/
|
|
459
|
+
function UnresolvedCollectionView({ path }: { path: string }) {
|
|
460
|
+
// Warn, not debug: this is a route that renders nothing useful, and it
|
|
461
|
+
// should be visible at the console's default level.
|
|
462
|
+
useEffect(() => {
|
|
463
|
+
console.warn(
|
|
464
|
+
`[rebase] No collection is registered for "${path}", so this route has nothing to render. ` +
|
|
465
|
+
"Check that the collection is in the collections array and that its slug matches the URL."
|
|
466
|
+
);
|
|
467
|
+
}, [path]);
|
|
468
|
+
|
|
469
|
+
return <CenteredView>
|
|
470
|
+
<ErrorView
|
|
471
|
+
title={"Collection not found"}
|
|
472
|
+
error={`Nothing is registered for "${path}".`}
|
|
473
|
+
tooltip={"The URL names a collection the panel does not know about. It may have been renamed or removed from the collections array, or the slug may not match the path."}/>
|
|
474
|
+
</CenteredView>;
|
|
475
|
+
}
|
|
@@ -61,4 +61,25 @@ export interface PropertyPreviewProps<P extends Property | Property, CustomProps
|
|
|
61
61
|
*/
|
|
62
62
|
textOnly?: boolean;
|
|
63
63
|
|
|
64
|
+
/**
|
|
65
|
+
* The preview has one line and cannot grow — it fills a slot in a card, a
|
|
66
|
+
* chip, or a table cell of fixed height.
|
|
67
|
+
*
|
|
68
|
+
* Properties whose natural rendering is a block (Markdown, multi-line text,
|
|
69
|
+
* maps, arrays) render a one-line stand-in instead: an opening excerpt, or
|
|
70
|
+
* a count. Left unset, {@link PropertyPreview} infers it from the entity
|
|
71
|
+
* preview nesting depth, which is where the constraint actually comes from;
|
|
72
|
+
* pass it explicitly only to force the compact form somewhere the nesting
|
|
73
|
+
* does not say so.
|
|
74
|
+
*/
|
|
75
|
+
compact?: boolean;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The caller has already labelled this value, so the preview must not
|
|
79
|
+
* repeat the property name. A boolean is the one that does: it renders its
|
|
80
|
+
* name beside the checkbox, which reads as a caption in a table cell and as
|
|
81
|
+
* a stutter under a field label — "VIP" over a checkbox saying "VIP".
|
|
82
|
+
*/
|
|
83
|
+
hideLabel?: boolean;
|
|
84
|
+
|
|
64
85
|
}
|