From 216642b5a4328c2212ddee446e122cd6db432e7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kr=C3=A6n=20Hansen?= Date: Mon, 4 Nov 2024 14:37:36 +0100 Subject: [PATCH] Refactor JSON editor action container into its own component --- .../src/{actions.tsx => action-button.tsx} | 9 +- .../compass-editor/src/actions-container.tsx | 87 ++++++++++++++++ packages/compass-editor/src/editor.tsx | 99 +++---------------- packages/compass-editor/src/index.ts | 12 +-- packages/compass-editor/src/types.ts | 13 +++ 5 files changed, 125 insertions(+), 95 deletions(-) rename packages/compass-editor/src/{actions.tsx => action-button.tsx} (93%) create mode 100644 packages/compass-editor/src/actions-container.tsx diff --git a/packages/compass-editor/src/actions.tsx b/packages/compass-editor/src/action-button.tsx similarity index 93% rename from packages/compass-editor/src/actions.tsx rename to packages/compass-editor/src/action-button.tsx index 49e0b25eef5..86ccbbbfcd7 100644 --- a/packages/compass-editor/src/actions.tsx +++ b/packages/compass-editor/src/action-button.tsx @@ -1,7 +1,14 @@ import { cx } from '@mongodb-js/compass-components'; import { css } from '@mongodb-js/compass-components'; -import { Button, Icon } from '@mongodb-js/compass-components'; +import { Button, Icon, type IconGlyph } from '@mongodb-js/compass-components'; import React, { useCallback, useEffect, useState } from 'react'; +import type { EditorView } from '@codemirror/view'; + +export type Action = { + icon: IconGlyph; + label: string; + action: (editor: EditorView) => boolean | void; +}; const actionButtonStyle = css({ flex: 'none', diff --git a/packages/compass-editor/src/actions-container.tsx b/packages/compass-editor/src/actions-container.tsx new file mode 100644 index 00000000000..99380abdfab --- /dev/null +++ b/packages/compass-editor/src/actions-container.tsx @@ -0,0 +1,87 @@ +import React, { type RefObject, useMemo } from 'react'; + +import { type Action, ActionButton, FormatIcon } from './action-button'; +import type { EditorRef } from './types'; +import { css, cx, spacing } from '@mongodb-js/compass-components'; + +type ActionsContainerProps = { + copyable: boolean; + formattable: boolean; + customActions?: Action[]; + className?: string; + editorRef: RefObject; +}; + +const actionsContainerStyle = css({ + position: 'absolute', + top: spacing[100], + right: spacing[100], + // left: spacing[100], + display: 'none', + gap: spacing[200], +}); + +export const ActionsContainer = ({ + copyable, + formattable, + customActions, + className, + editorRef, +}: ActionsContainerProps) => { + const actions = useMemo(() => { + return [ + copyable && ( + { + return editorRef.current?.copyAll() ?? false; + }} + > + ), + formattable && ( + + } + onClick={() => { + return editorRef.current?.prettify() ?? false; + }} + > + ), + ...(customActions ?? []).map((action) => { + return ( + { + if (!editorRef.current?.editor) { + return false; + } + return action.action(editorRef.current.editor); + }} + > + ); + }), + ]; + }, [copyable, formattable, customActions, editorRef]); + + return ( +
+ {actions} +
+ ); +}; diff --git a/packages/compass-editor/src/editor.tsx b/packages/compass-editor/src/editor.tsx index e366503c54a..7c5dd396f56 100644 --- a/packages/compass-editor/src/editor.tsx +++ b/packages/compass-editor/src/editor.tsx @@ -2,7 +2,6 @@ import React, { useCallback, useEffect, useLayoutEffect, - useMemo, useImperativeHandle, useRef, useState, @@ -50,7 +49,6 @@ import { snippetCompletion, startCompletion, } from '@codemirror/autocomplete'; -import type { IconGlyph } from '@mongodb-js/compass-components'; import { css, cx, @@ -74,7 +72,9 @@ import { tags as t } from '@lezer/highlight'; import { rgba } from 'polished'; import { prettify as _prettify } from './prettify'; -import { ActionButton, FormatIcon } from './actions'; +import type { Action } from './action-button'; +import { ActionsContainer } from './actions-container'; +import type { EditorRef } from './types'; // TODO(COMPASS-8453): Re-enable this once the linked tickets are resolved // https://github.com/codemirror/dev/issues/1458 @@ -697,19 +697,6 @@ function useCodemirrorExtensionCompartment( return initialExtensionRef.current; } -export type EditorRef = { - foldAll: () => boolean; - unfoldAll: () => boolean; - copyAll: () => boolean; - prettify: () => boolean; - applySnippet: (template: string) => boolean; - focus: () => boolean; - cursorDocEnd: () => boolean; - startCompletion: () => boolean; - readonly editorContents: string | null; - readonly editor: EditorView | null; -}; - const BaseEditor = React.forwardRef(function BaseEditor( { initialText: _initialText, @@ -1406,20 +1393,6 @@ const multilineEditorContainerDarkModeStyle = css({ backgroundColor: editorPalette.dark.backgroundColor, }); -const actionsContainerStyle = css({ - position: 'absolute', - top: spacing[1], - right: spacing[2], - display: 'none', - gap: spacing[2], -}); - -export type Action = { - icon: IconGlyph; - label: string; - action: (editor: EditorView) => boolean | void; -}; - type MultilineEditorProps = EditorProps & { customActions?: Action[]; copyable?: boolean; @@ -1485,50 +1458,8 @@ const MultilineEditor = React.forwardRef( [] ); - const actions = useMemo(() => { - return [ - copyable && ( - { - return editorRef.current?.copyAll() ?? false; - }} - > - ), - formattable && ( - - } - onClick={() => { - return editorRef.current?.prettify() ?? false; - }} - > - ), - ...(customActions ?? []).map((action) => { - return ( - { - if (!editorRef.current?.editor) { - return false; - } - return action.action(editorRef.current.editor); - }} - > - ); - }), - ]; - }, [copyable, formattable, customActions]); + const hasCustomActions = customActions && customActions.length > 0; + const hasActions = copyable || formattable || hasCustomActions; return ( // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions @@ -1537,7 +1468,7 @@ const MultilineEditor = React.forwardRef( className={cx( multilineEditorContainerStyle, darkMode && multilineEditorContainerDarkModeStyle, - !!actions.length && multilineEditorContainerWithActionsStyle, + hasActions && multilineEditorContainerWithActionsStyle, className )} // We want folks to be able to click into the container element @@ -1559,16 +1490,14 @@ const MultilineEditor = React.forwardRef( minLines={10} {...props} > - {actions.length > 0 && ( -
- {actions} -
+ {hasActions && ( + )} ); diff --git a/packages/compass-editor/src/index.ts b/packages/compass-editor/src/index.ts index f9a6a1876f8..5bc81f249f6 100644 --- a/packages/compass-editor/src/index.ts +++ b/packages/compass-editor/src/index.ts @@ -1,4 +1,4 @@ -export type { CompletionWithServerInfo } from './types'; +export type { CompletionWithServerInfo, EditorRef } from './types'; export { prettify } from './prettify'; export type { FormatOptions } from './prettify'; export { @@ -7,14 +7,8 @@ export { setCodemirrorEditorValue, getCodemirrorEditorValue, } from './editor'; -export type { - EditorView, - Command, - Annotation, - Action, - EditorRef, - Completer, -} from './editor'; +export type { EditorView, Command, Annotation, Completer } from './editor'; +export type { Action } from './action-button'; export { createDocumentAutocompleter } from './codemirror/document-autocompleter'; export { createValidationAutocompleter } from './codemirror/validation-autocompleter'; export { createQueryAutocompleter } from './codemirror/query-autocompleter'; diff --git a/packages/compass-editor/src/types.ts b/packages/compass-editor/src/types.ts index 039c5d6d514..4ba26c2f414 100644 --- a/packages/compass-editor/src/types.ts +++ b/packages/compass-editor/src/types.ts @@ -14,3 +14,16 @@ export type CompletionWithServerInfo = { /** Optional completion description */ description?: string; }; + +export type EditorRef = { + foldAll: () => boolean; + unfoldAll: () => boolean; + copyAll: () => boolean; + prettify: () => boolean; + applySnippet: (template: string) => boolean; + focus: () => boolean; + cursorDocEnd: () => boolean; + startCompletion: () => boolean; + readonly editorContents: string | null; + readonly editor: EditorView | null; +};