Skip to content

Commit

Permalink
fix(crud): expandable readonly document COMPASS-4635 (#6447)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
kraenhansen authored Nov 8, 2024
1 parent 9959dfe commit c179bd4
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,14 @@ const HadronDocument: React.FunctionComponent<{
editable?: boolean;
editing?: boolean;
onEditStart?: () => void;
}> = ({ value: document, editable = false, editing = false, onEditStart }) => {
extraGutterWidth?: number;
}> = ({
value: document,
editable = false,
editing = false,
onEditStart,
extraGutterWidth,
}) => {
const { elements, visibleElements } = useHadronDocument(document);
const [autoFocus, setAutoFocus] = useState<{
id: string;
Expand Down Expand Up @@ -113,8 +120,9 @@ const HadronDocument: React.FunctionComponent<{
editable,
level: 0,
alignWithNestedExpandIcon: false,
extraGutterWidth,
}),
[editable]
[editable, extraGutterWidth]
);

return (
Expand Down Expand Up @@ -147,6 +155,7 @@ const HadronDocument: React.FunctionComponent<{
type: el.parent?.currentType === 'Array' ? 'value' : 'key',
});
}}
extraGutterWidth={extraGutterWidth}
></HadronElement>
);
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -363,36 +363,39 @@ const elementKeyDarkMode = css({
color: palette.gray.light2,
});

const calculateElementSpacerWidth = (editable: boolean, level: number) => {
return (editable ? spacing[100] : 0) + spacing[400] * level;
const calculateElementSpacerWidth = (
editable: boolean,
level: number,
extra: number
) => {
return (editable ? spacing[100] : 0) + extra + spacing[400] * level;
};

export const calculateShowMoreToggleOffset = ({
editable,
level,
alignWithNestedExpandIcon,
extraGutterWidth = 0,
}: {
editable: boolean;
level: number;
alignWithNestedExpandIcon: boolean;
extraGutterWidth: number | undefined;
}) => {
// the base padding that we have on all elements rendered in the document
const BASE_PADDING_LEFT = spacing[50];
const OFFSET_WHEN_EDITABLE = editable
const spacerWidth = calculateElementSpacerWidth(
editable,
level,
extraGutterWidth
);
const editableOffset = editable
? // space taken by element actions
spacing[300] +
// space and margin taken by line number element
spacing[400] +
spacing[100] +
// element spacer width that we render
calculateElementSpacerWidth(editable, level)
spacing[100]
: 0;
const EXPAND_ICON_SIZE = spacing[400];
return (
BASE_PADDING_LEFT +
OFFSET_WHEN_EDITABLE +
(alignWithNestedExpandIcon ? EXPAND_ICON_SIZE : 0)
);
const expandIconSize = alignWithNestedExpandIcon ? spacing[400] : 0;
return spacerWidth + editableOffset + expandIconSize;
};

export const HadronElement: React.FunctionComponent<{
Expand All @@ -402,13 +405,15 @@ export const HadronElement: React.FunctionComponent<{
onEditStart?: (id: string, field: 'key' | 'value' | 'type') => void;
lineNumberSize: number;
onAddElement(el: HadronElementType): void;
extraGutterWidth?: number;
}> = ({
value: element,
editable,
editingEnabled,
onEditStart,
lineNumberSize,
onAddElement,
extraGutterWidth = 0,
}) => {
const darkMode = useDarkMode();
const autoFocus = useAutoFocusContext();
Expand Down Expand Up @@ -445,8 +450,8 @@ export const HadronElement: React.FunctionComponent<{
}, [lineNumberSize, editingEnabled]);

const elementSpacerWidth = useMemo(
() => calculateElementSpacerWidth(editable, level),
[editable, level]
() => calculateElementSpacerWidth(editable, level, extraGutterWidth),
[editable, level, extraGutterWidth]
);

// To render the "Show more" toggle for the nested expandable elements we need
Expand All @@ -457,8 +462,9 @@ export const HadronElement: React.FunctionComponent<{
editable,
level,
alignWithNestedExpandIcon: true,
extraGutterWidth,
}),
[editable, level]
[editable, level, extraGutterWidth]
);

const isValid = key.valid && value.valid;
Expand Down Expand Up @@ -711,6 +717,7 @@ export const HadronElement: React.FunctionComponent<{
onEditStart={onEditStart}
lineNumberSize={lineNumberSize}
onAddElement={onAddElement}
extraGutterWidth={extraGutterWidth}
></HadronElement>
);
})}
Expand Down
90 changes: 87 additions & 3 deletions packages/compass-crud/src/components/readonly-document.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type Document from 'hadron-document';
import type { TypeCastMap } from 'hadron-type-checker';
import { withPreferences } from 'compass-preferences-model/provider';
import { getInsightsForDocument } from '../utils';
import { DocumentEvents } from 'hadron-document';
type BSONObject = TypeCastMap['Object'];

export const documentStyles = css({
Expand All @@ -30,10 +31,73 @@ export type ReadonlyDocumentProps = {
showInsights?: boolean;
};

type ReadonlyDocumentState = {
expanded: boolean;
};

/**
* Component for a single readonly document in a list of documents.
*/
class ReadonlyDocument extends React.Component<ReadonlyDocumentProps> {
class ReadonlyDocument extends React.Component<
ReadonlyDocumentProps,
ReadonlyDocumentState
> {
constructor(props: ReadonlyDocumentProps) {
super(props);
this.state = {
expanded: props.doc.expanded,
};
}

/**
* Subscribe to the update store on mount.
*/
componentDidMount() {
this.subscribeToDocumentEvents(this.props.doc);
}

/**
* Refreshing the list updates the doc in the props so we should update the
* document on the instance.
*/
componentDidUpdate(prevProps: ReadonlyDocumentProps) {
if (prevProps.doc !== this.props.doc) {
this.unsubscribeFromDocumentEvents(prevProps.doc);
this.subscribeToDocumentEvents(this.props.doc);
}
}

/**
* Unsubscribe from the update store on unmount.
*/
componentWillUnmount() {
this.unsubscribeFromDocumentEvents(this.props.doc);
}

/**
* Subscribe to the document events.
*/
subscribeToDocumentEvents(doc: Document) {
doc.on(DocumentEvents.Expanded, this.handleExpanded);
doc.on(DocumentEvents.Collapsed, this.handleCollapsed);
}

/**
* Unsubscribe from the document events.
*/
unsubscribeFromDocumentEvents(doc: Document) {
doc.on(DocumentEvents.Expanded, this.handleExpanded);
doc.on(DocumentEvents.Collapsed, this.handleCollapsed);
}

handleExpanded = () => {
this.setState({ expanded: true });
};

handleCollapsed = () => {
this.setState({ expanded: false });
};

handleClone = () => {
const clonedDoc = this.props.doc.generateObject({
excludeInternalFields: true,
Expand All @@ -48,13 +112,32 @@ class ReadonlyDocument extends React.Component<ReadonlyDocumentProps> {
this.props.copyToClipboard?.(this.props.doc);
};

/**
* Handle clicking the expand all button.
*/
handleExpandAll = () => {
const { doc } = this.props;
// Update the doc directly - the components internal state will update via events
if (doc.expanded) {
doc.collapse();
} else {
doc.expand();
}
};

/**
* Get the elements for the document.
*
* @returns {Array} The elements.
*/
renderElements() {
return <DocumentList.Document value={this.props.doc} />;
return (
<DocumentList.Document
value={this.props.doc}
// Provide extra whitespace for the expand button
extraGutterWidth={spacing[900]}
/>
);
}

renderActions() {
Expand All @@ -64,6 +147,8 @@ class ReadonlyDocument extends React.Component<ReadonlyDocumentProps> {
onClone={
this.props.openInsertDocumentDialog ? this.handleClone : undefined
}
onExpand={this.handleExpandAll}
expanded={this.state.expanded}
insights={
this.props.showInsights
? getInsightsForDocument(this.props.doc)
Expand Down Expand Up @@ -94,7 +179,6 @@ class ReadonlyDocument extends React.Component<ReadonlyDocumentProps> {
static propTypes = {
copyToClipboard: PropTypes.func,
doc: PropTypes.object.isRequired,
expandAll: PropTypes.bool,
openInsertDocumentDialog: PropTypes.func,
showInsights: PropTypes.bool,
};
Expand Down

0 comments on commit c179bd4

Please sign in to comment.