Skip to content

Commit c179bd4

Browse files
authored
fix(crud): expandable readonly document COMPASS-4635 (#6447)
* Make ReadonlyDocument expandable * Add extra gutter width on ReadonlyDocument * fixup! Add extra gutter width on ReadonlyDocument Passing extraGutterWidth through to children * fixup! Add extra gutter width on ReadonlyDocument Pass extraGutterWidth to calculateShowMoreToggleOffset in document * fixup! Add extra gutter width on ReadonlyDocument Remove unused readOnlySpacer CSS * fixup! Add extra gutter width on ReadonlyDocument Use existing element spacer
1 parent 9959dfe commit c179bd4

File tree

3 files changed

+122
-22
lines changed

3 files changed

+122
-22
lines changed

packages/compass-components/src/components/document-list/document.tsx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,14 @@ const HadronDocument: React.FunctionComponent<{
8585
editable?: boolean;
8686
editing?: boolean;
8787
onEditStart?: () => void;
88-
}> = ({ value: document, editable = false, editing = false, onEditStart }) => {
88+
extraGutterWidth?: number;
89+
}> = ({
90+
value: document,
91+
editable = false,
92+
editing = false,
93+
onEditStart,
94+
extraGutterWidth,
95+
}) => {
8996
const { elements, visibleElements } = useHadronDocument(document);
9097
const [autoFocus, setAutoFocus] = useState<{
9198
id: string;
@@ -113,8 +120,9 @@ const HadronDocument: React.FunctionComponent<{
113120
editable,
114121
level: 0,
115122
alignWithNestedExpandIcon: false,
123+
extraGutterWidth,
116124
}),
117-
[editable]
125+
[editable, extraGutterWidth]
118126
);
119127

120128
return (
@@ -147,6 +155,7 @@ const HadronDocument: React.FunctionComponent<{
147155
type: el.parent?.currentType === 'Array' ? 'value' : 'key',
148156
});
149157
}}
158+
extraGutterWidth={extraGutterWidth}
150159
></HadronElement>
151160
);
152161
})}

packages/compass-components/src/components/document-list/element.tsx

Lines changed: 24 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -363,36 +363,39 @@ const elementKeyDarkMode = css({
363363
color: palette.gray.light2,
364364
});
365365

366-
const calculateElementSpacerWidth = (editable: boolean, level: number) => {
367-
return (editable ? spacing[100] : 0) + spacing[400] * level;
366+
const calculateElementSpacerWidth = (
367+
editable: boolean,
368+
level: number,
369+
extra: number
370+
) => {
371+
return (editable ? spacing[100] : 0) + extra + spacing[400] * level;
368372
};
369373

370374
export const calculateShowMoreToggleOffset = ({
371375
editable,
372376
level,
373377
alignWithNestedExpandIcon,
378+
extraGutterWidth = 0,
374379
}: {
375380
editable: boolean;
376381
level: number;
377382
alignWithNestedExpandIcon: boolean;
383+
extraGutterWidth: number | undefined;
378384
}) => {
379-
// the base padding that we have on all elements rendered in the document
380-
const BASE_PADDING_LEFT = spacing[50];
381-
const OFFSET_WHEN_EDITABLE = editable
385+
const spacerWidth = calculateElementSpacerWidth(
386+
editable,
387+
level,
388+
extraGutterWidth
389+
);
390+
const editableOffset = editable
382391
? // space taken by element actions
383392
spacing[300] +
384393
// space and margin taken by line number element
385394
spacing[400] +
386-
spacing[100] +
387-
// element spacer width that we render
388-
calculateElementSpacerWidth(editable, level)
395+
spacing[100]
389396
: 0;
390-
const EXPAND_ICON_SIZE = spacing[400];
391-
return (
392-
BASE_PADDING_LEFT +
393-
OFFSET_WHEN_EDITABLE +
394-
(alignWithNestedExpandIcon ? EXPAND_ICON_SIZE : 0)
395-
);
397+
const expandIconSize = alignWithNestedExpandIcon ? spacing[400] : 0;
398+
return spacerWidth + editableOffset + expandIconSize;
396399
};
397400

398401
export const HadronElement: React.FunctionComponent<{
@@ -402,13 +405,15 @@ export const HadronElement: React.FunctionComponent<{
402405
onEditStart?: (id: string, field: 'key' | 'value' | 'type') => void;
403406
lineNumberSize: number;
404407
onAddElement(el: HadronElementType): void;
408+
extraGutterWidth?: number;
405409
}> = ({
406410
value: element,
407411
editable,
408412
editingEnabled,
409413
onEditStart,
410414
lineNumberSize,
411415
onAddElement,
416+
extraGutterWidth = 0,
412417
}) => {
413418
const darkMode = useDarkMode();
414419
const autoFocus = useAutoFocusContext();
@@ -445,8 +450,8 @@ export const HadronElement: React.FunctionComponent<{
445450
}, [lineNumberSize, editingEnabled]);
446451

447452
const elementSpacerWidth = useMemo(
448-
() => calculateElementSpacerWidth(editable, level),
449-
[editable, level]
453+
() => calculateElementSpacerWidth(editable, level, extraGutterWidth),
454+
[editable, level, extraGutterWidth]
450455
);
451456

452457
// To render the "Show more" toggle for the nested expandable elements we need
@@ -457,8 +462,9 @@ export const HadronElement: React.FunctionComponent<{
457462
editable,
458463
level,
459464
alignWithNestedExpandIcon: true,
465+
extraGutterWidth,
460466
}),
461-
[editable, level]
467+
[editable, level, extraGutterWidth]
462468
);
463469

464470
const isValid = key.valid && value.valid;
@@ -711,6 +717,7 @@ export const HadronElement: React.FunctionComponent<{
711717
onEditStart={onEditStart}
712718
lineNumberSize={lineNumberSize}
713719
onAddElement={onAddElement}
720+
extraGutterWidth={extraGutterWidth}
714721
></HadronElement>
715722
);
716723
})}

packages/compass-crud/src/components/readonly-document.tsx

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type Document from 'hadron-document';
55
import type { TypeCastMap } from 'hadron-type-checker';
66
import { withPreferences } from 'compass-preferences-model/provider';
77
import { getInsightsForDocument } from '../utils';
8+
import { DocumentEvents } from 'hadron-document';
89
type BSONObject = TypeCastMap['Object'];
910

1011
export const documentStyles = css({
@@ -30,10 +31,73 @@ export type ReadonlyDocumentProps = {
3031
showInsights?: boolean;
3132
};
3233

34+
type ReadonlyDocumentState = {
35+
expanded: boolean;
36+
};
37+
3338
/**
3439
* Component for a single readonly document in a list of documents.
3540
*/
36-
class ReadonlyDocument extends React.Component<ReadonlyDocumentProps> {
41+
class ReadonlyDocument extends React.Component<
42+
ReadonlyDocumentProps,
43+
ReadonlyDocumentState
44+
> {
45+
constructor(props: ReadonlyDocumentProps) {
46+
super(props);
47+
this.state = {
48+
expanded: props.doc.expanded,
49+
};
50+
}
51+
52+
/**
53+
* Subscribe to the update store on mount.
54+
*/
55+
componentDidMount() {
56+
this.subscribeToDocumentEvents(this.props.doc);
57+
}
58+
59+
/**
60+
* Refreshing the list updates the doc in the props so we should update the
61+
* document on the instance.
62+
*/
63+
componentDidUpdate(prevProps: ReadonlyDocumentProps) {
64+
if (prevProps.doc !== this.props.doc) {
65+
this.unsubscribeFromDocumentEvents(prevProps.doc);
66+
this.subscribeToDocumentEvents(this.props.doc);
67+
}
68+
}
69+
70+
/**
71+
* Unsubscribe from the update store on unmount.
72+
*/
73+
componentWillUnmount() {
74+
this.unsubscribeFromDocumentEvents(this.props.doc);
75+
}
76+
77+
/**
78+
* Subscribe to the document events.
79+
*/
80+
subscribeToDocumentEvents(doc: Document) {
81+
doc.on(DocumentEvents.Expanded, this.handleExpanded);
82+
doc.on(DocumentEvents.Collapsed, this.handleCollapsed);
83+
}
84+
85+
/**
86+
* Unsubscribe from the document events.
87+
*/
88+
unsubscribeFromDocumentEvents(doc: Document) {
89+
doc.on(DocumentEvents.Expanded, this.handleExpanded);
90+
doc.on(DocumentEvents.Collapsed, this.handleCollapsed);
91+
}
92+
93+
handleExpanded = () => {
94+
this.setState({ expanded: true });
95+
};
96+
97+
handleCollapsed = () => {
98+
this.setState({ expanded: false });
99+
};
100+
37101
handleClone = () => {
38102
const clonedDoc = this.props.doc.generateObject({
39103
excludeInternalFields: true,
@@ -48,13 +112,32 @@ class ReadonlyDocument extends React.Component<ReadonlyDocumentProps> {
48112
this.props.copyToClipboard?.(this.props.doc);
49113
};
50114

115+
/**
116+
* Handle clicking the expand all button.
117+
*/
118+
handleExpandAll = () => {
119+
const { doc } = this.props;
120+
// Update the doc directly - the components internal state will update via events
121+
if (doc.expanded) {
122+
doc.collapse();
123+
} else {
124+
doc.expand();
125+
}
126+
};
127+
51128
/**
52129
* Get the elements for the document.
53130
*
54131
* @returns {Array} The elements.
55132
*/
56133
renderElements() {
57-
return <DocumentList.Document value={this.props.doc} />;
134+
return (
135+
<DocumentList.Document
136+
value={this.props.doc}
137+
// Provide extra whitespace for the expand button
138+
extraGutterWidth={spacing[900]}
139+
/>
140+
);
58141
}
59142

60143
renderActions() {
@@ -64,6 +147,8 @@ class ReadonlyDocument extends React.Component<ReadonlyDocumentProps> {
64147
onClone={
65148
this.props.openInsertDocumentDialog ? this.handleClone : undefined
66149
}
150+
onExpand={this.handleExpandAll}
151+
expanded={this.state.expanded}
67152
insights={
68153
this.props.showInsights
69154
? getInsightsForDocument(this.props.doc)
@@ -94,7 +179,6 @@ class ReadonlyDocument extends React.Component<ReadonlyDocumentProps> {
94179
static propTypes = {
95180
copyToClipboard: PropTypes.func,
96181
doc: PropTypes.object.isRequired,
97-
expandAll: PropTypes.bool,
98182
openInsertDocumentDialog: PropTypes.func,
99183
showInsights: PropTypes.bool,
100184
};

0 commit comments

Comments
 (0)