Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -659,8 +659,10 @@ export type CodeEditorToolbarProps = {
// Omit the ref as we have our own ref type, which is completely different
export type BasicCodeEditorProps = Partial<Omit<PfCodeEditorProps, 'ref'>>;

export type CodeEditorProps = Omit<BasicCodeEditorProps, 'code'> &
export type CodeEditorProps = Omit<BasicCodeEditorProps, 'code' | 'shortcutsPopoverProps'> &
CodeEditorToolbarProps & {
/** Additional props to override the default popover properties */
shortcutsPopoverProps?: Partial<PfCodeEditorProps['shortcutsPopoverProps']>;
Comment on lines 662 to 665
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@vojtechszocs This is the API change. previously, shortcutsPopoverProps did nothing, because our props take priority.

now, when shortcutsPopoverProps is passed to our CodeEditor, it will be spread into the our shortcutsPopoverProps that we ultimately pass to the PatternFly CodeEditor's shortcutsPopoverProps.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So after these changes plugins utilizing CodeEditor are affected only in a positive way, as we have made the shortcutsPopoverProps customizable now. Only case where this affects plugins is if they used the shortcutsPopoverProps without realizing its has no effect, but now they will function.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

exactly!

/** Code displayed in code editor. */
value?: string;
/** Minimum editor height in valid CSS height values. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,8 @@
"View document outline": "View document outline",
"View property descriptions": "View property descriptions",
"Save": "Save",
"Hide sidebar": "Hide sidebar",
"Show sidebar": "Show sidebar",
"Restricted access": "Restricted access",
"You don't have access to this section due to cluster policy": "You don't have access to this section due to cluster policy",
"Error details": "Error details",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const CodeEditor = React.forwardRef<CodeEditorRef, CodeEditorProps>((props, ref)
const [monacoRef, setMonacoRef] = React.useState<CodeEditorRef['monaco'] | null>(null);
const [usesValue] = React.useState<boolean>(value !== undefined);

const shortcutPopover = useShortcutPopover();
const shortcutPopover = useShortcutPopover(props.shortcutsPopoverProps);

const editorDidMount: EditorDidMount = React.useCallback(
(editor, monaco) => {
Expand Down Expand Up @@ -55,7 +55,7 @@ const CodeEditor = React.forwardRef<CodeEditorRef, CodeEditorProps>((props, ref)
// do not render toolbar if the component is null
const ToolbarLinks = React.useMemo(() => {
return showShortcuts || toolbarLinks?.length ? (
<CodeEditorToolbar toolbarLinks={toolbarLinks} showShortcuts={showShortcuts} />
<CodeEditorToolbar toolbarLinks={toolbarLinks} />
) : undefined;
}, [toolbarLinks, showShortcuts]);

Expand All @@ -80,11 +80,11 @@ const CodeEditor = React.forwardRef<CodeEditorRef, CodeEditorProps>((props, ref)
<div style={{ minHeight }} className="ocs-yaml-editor">
<BasicCodeEditor
{...props}
language={props?.language ?? Language.yaml}
language={props.language ?? Language.yaml}
code={value}
options={{ ...defaultEditorOptions, ...props?.options }}
options={{ ...defaultEditorOptions, ...props.options }}
onEditorDidMount={editorDidMount}
isFullHeight={props?.isFullHeight ?? true}
isFullHeight={props.isFullHeight ?? true}
customControls={ToolbarLinks ?? undefined}
shortcutsPopoverProps={showShortcuts ? shortcutPopover : undefined}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,49 +1,40 @@
import * as React from 'react';
import { Button, Flex, FlexItem } from '@patternfly/react-core';
import { Button, Tooltip } from '@patternfly/react-core';
import { MagicIcon } from '@patternfly/react-icons';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { action } from 'typesafe-actions';
import { CodeEditorToolbarProps } from '@console/dynamic-plugin-sdk';
import { ActionType } from '@console/internal/reducers/ols';
import { useOLSConfig } from '../../hooks/ols-hook';
import { useOLSConfig } from '@console/shared/src/hooks/ols-hook';
import { useIsFullscreen } from '@console/shared/src/hooks/useFullscreen';

export const AskOpenShiftLightspeedButton: React.FC = () => {
export const AskOpenShiftLightspeedButton: React.FCC = () => {
const { t } = useTranslation('console-shared');
const openOLS = () => action(ActionType.OpenOLS);
const showLightspeedButton = useOLSConfig();
const dispatch = useDispatch();
const isFullscreen = useIsFullscreen();

return showLightspeedButton ? (
<Button
variant="secondary"
size="sm"
onClick={() => {
dispatch(openOLS());
}}
icon={<MagicIcon />}
>
{t('console-shared~Ask OpenShift Lightspeed')}
</Button>
<Tooltip content={t('Ask OpenShift Lightspeed')}>
<Button
isDisabled={isFullscreen}
variant="plain"
onClick={() => dispatch(openOLS())}
aria-label={t('Ask OpenShift Lightspeed')}
icon={<MagicIcon />}
/>
</Tooltip>
) : null;
};

export const CodeEditorToolbar: React.FC<CodeEditorToolbarProps> = ({
showShortcuts,
toolbarLinks,
}) => {
if (!showShortcuts && !toolbarLinks?.length) return null;
export const CodeEditorToolbar: React.FCC<CodeEditorToolbarProps> = ({ toolbarLinks }) => {
if (!toolbarLinks?.length) return null;

return (
<>
<AskOpenShiftLightspeedButton />

<Flex className="pf-v6-u-ml-xs" alignItems={{ default: 'alignItemsCenter' }}>
{toolbarLinks &&
toolbarLinks.map((link, index) => (
// eslint-disable-next-line react/no-array-index-key
<FlexItem key={`${index}`}>{link}</FlexItem>
))}
</Flex>
{toolbarLinks}
</>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,36 @@
import { useMemo } from 'react';
import { PopoverProps } from '@patternfly/react-core';
import { useTranslation } from 'react-i18next';
import { ShortcutTable, Shortcut } from '../shortcuts';
import { isMac } from '../shortcuts/Shortcut';

export const useShortcutPopover = (onHideShortcuts?: () => {}): PopoverProps => {
export const useShortcutPopover = (shortcutsPopoverProps?: Partial<PopoverProps>): PopoverProps => {
const { t } = useTranslation('console-shared');

return {
'aria-label': t('Shortcuts'),
bodyContent: (
<ShortcutTable>
<Shortcut keyName="F1">{t('View all editor shortcuts')}</Shortcut>
<Shortcut ctrl keyName="space">
{t('Activate auto complete')}
</Shortcut>
<Shortcut ctrl shift={isMac} keyName="m">
{t('Toggle Tab action between insert Tab character and move focus out of editor')}
</Shortcut>
<Shortcut ctrlCmd shift keyName="o">
{t('View document outline')}
</Shortcut>
<Shortcut hover>{t('View property descriptions')}</Shortcut>
<Shortcut ctrlCmd keyName="s">
{t('Save')}
</Shortcut>
</ShortcutTable>
),
maxWidth: '25rem',
distance: 18,
onHide: onHideShortcuts,
};
return useMemo((): PopoverProps => {
return {
'aria-label': t('Shortcuts'),
bodyContent: (
<ShortcutTable>
<Shortcut keyName="F1">{t('View all editor shortcuts')}</Shortcut>
<Shortcut ctrl keyName="space">
{t('Activate auto complete')}
</Shortcut>
<Shortcut ctrl shift={isMac} keyName="m">
{t('Toggle Tab action between insert Tab character and move focus out of editor')}
</Shortcut>
<Shortcut ctrlCmd shift keyName="o">
{t('View document outline')}
</Shortcut>
<Shortcut hover>{t('View property descriptions')}</Shortcut>
<Shortcut ctrlCmd keyName="s">
{t('Save')}
</Shortcut>
</ShortcutTable>
),
maxWidth: '25rem',
distance: 18,
...shortcutsPopoverProps,
};
}, [t, shortcutsPopoverProps]);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { CodeEditorControl, CodeEditorControlProps } from '@patternfly/react-code-editor';
import { createIcon } from '@patternfly/react-icons/dist/esm/createIcon';
import { useTranslation } from 'react-i18next';

export const SidebarOffIcon = createIcon({
name: 'SidebarOffIcon',
width: 512,
height: 512,
svgPath:
'M463.98 31.958H48.02C21.5 31.958 0 53.458 0 79.98v352.042c0 26.52 21.5 48.02 48.02 48.02h415.96c26.52 0 48.02-21.5 48.02-48.02V79.979c0-26.52-21.5-48.02-48.02-48.02zm-239.938 384H64V96.042h159.958v320ZM448 315.398v100.56H288.042V96.042H448zm-29.867-78.473h-98.3a8.426 8.426 0 0 0-8.45 8.45v21.25a8.426 8.426 0 0 0 8.45 8.45h98.3a8.426 8.426 0 0 0 8.45-8.45v-21.25a8.426 8.426 0 0 0-8.45-8.45zm0-84.83h-98.3a8.426 8.426 0 0 0-8.45 8.449v21.25a8.426 8.426 0 0 0 8.45 8.45h98.3a8.426 8.426 0 0 0 8.45-8.45v-21.25a8.426 8.426 0 0 0-8.45-8.45zm0 169.662h-98.3a8.426 8.426 0 0 0-8.45 8.45v21.25a8.426 8.426 0 0 0 8.45 8.449h98.3a8.426 8.426 0 0 0 8.45-8.45v-21.25a8.426 8.426 0 0 0-8.45-8.45z',
});

export const SidebarOnIcon = createIcon({
name: 'SidebarOnIcon',
width: 512,
height: 512,
svgPath:
'M463.98 31.958H48.02C21.5 31.958 0 53.458 0 79.98v352.042c0 26.52 21.5 48.02 48.02 48.02h415.96c26.52 0 48.02-21.5 48.02-48.02V79.979c0-26.52-21.5-48.02-48.02-48.02zm-239.938 384H64V96.042h159.958v320ZM448 315.398v100.56H288.042V96.042H448zM248.638 66.929v369.443h214.84V66.928Zm177.945 284.528a8.426 8.426 0 0 1-8.45 8.45h-98.3a8.426 8.426 0 0 1-8.45-8.45v-21.25a8.426 8.426 0 0 1 8.45-8.45h98.3a8.426 8.426 0 0 1 8.45 8.45zm0-84.831a8.426 8.426 0 0 1-8.45 8.45h-98.3a8.426 8.426 0 0 1-8.45-8.45v-21.25a8.426 8.426 0 0 1 8.45-8.45h98.3a8.426 8.426 0 0 1 8.45 8.45zm0-84.832a8.426 8.426 0 0 1-8.45 8.45h-98.3a8.426 8.426 0 0 1-8.45-8.45v-21.25a8.426 8.426 0 0 1 8.45-8.449h98.3a8.426 8.426 0 0 1 8.45 8.45z',
});

interface ToggleSidebarButtonProps extends Partial<CodeEditorControlProps> {
isSidebarOpen: boolean;
toggleSidebar: () => void;
/** Adds a div with `flex-grow: 1` so that the button is aligned to the end of the toolbar */
alignToEnd?: boolean;
}

export const ToggleSidebarButton: React.FCC<ToggleSidebarButtonProps> = ({
isSidebarOpen,
toggleSidebar,
alignToEnd = false,
...props
}) => {
const { t } = useTranslation('console-shared');

return (
<>
{alignToEnd && <div style={{ flexGrow: 1 }} />}
<CodeEditorControl
aria-label={isSidebarOpen ? t('Hide sidebar') : t('Show sidebar')}
onClick={toggleSidebar}
icon={isSidebarOpen ? <SidebarOnIcon /> : <SidebarOffIcon />}
tooltipProps={{ content: isSidebarOpen ? t('Hide sidebar') : t('Show sidebar') }}
{...props}
/>
</>
);
};
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Button } from '@patternfly/react-core';
import { shallow, ShallowWrapper } from 'enzyme';
import { render, screen, fireEvent } from '@testing-library/react';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { ActionType } from '@console/internal/reducers/ols';
import { useOLSConfig } from '../../../hooks/ols-hook';
import { AskOpenShiftLightspeedButton, CodeEditorToolbar } from '../CodeEditorToolbar';
import '@testing-library/jest-dom';

jest.mock('react-i18next', () => ({
useTranslation: jest.fn(),
Expand All @@ -19,7 +19,6 @@ jest.mock('../../../hooks/ols-hook', () => ({
}));

describe('CodeEditorToolbar', () => {
let wrapper: ShallowWrapper;
const mockDispatch = jest.fn();

beforeEach(() => {
Expand All @@ -29,32 +28,32 @@ describe('CodeEditorToolbar', () => {
});

it('should render null when showShortcuts is false and toolbarLinks is empty', () => {
wrapper = shallow(<CodeEditorToolbar />);
expect(wrapper.isEmptyRender()).toBe(true);
const { container } = render(<CodeEditorToolbar />);
expect(container.firstChild).toBeNull();
});

it('should render toolbar with custom links when toolbarLinks are provided', () => {
const toolbarLinks = [<div key="custom">Custom Link</div>];
wrapper = shallow(<CodeEditorToolbar toolbarLinks={toolbarLinks} />);
expect(wrapper.contains(<div>Custom Link</div>)).toBe(true);
render(<CodeEditorToolbar toolbarLinks={[<div key="custom">Custom Link</div>]} />);
expect(screen.getByText('Custom Link')).toBeInTheDocument();
});

it('should render "Ask OpenShift Lightspeed" button when showLightspeedButton is true', () => {
(useOLSConfig as jest.Mock).mockReturnValue(true);
wrapper = shallow(<AskOpenShiftLightspeedButton />);
expect(wrapper.find(Button).prop('children')).toBe('console-shared~Ask OpenShift Lightspeed');
render(<AskOpenShiftLightspeedButton />);
expect(screen.getByRole('button')).toBeInTheDocument();
});

it('should not render "Ask OpenShift Lightspeed" button when showLightspeedButton is false', () => {
(useOLSConfig as jest.Mock).mockReturnValue(false);
wrapper = shallow(<AskOpenShiftLightspeedButton />);
expect(wrapper.find(Button).exists()).toBe(false);
render(<AskOpenShiftLightspeedButton />);
expect(screen.queryByRole('button')).not.toBeInTheDocument();
});

it('should dispatch OpenOLS action when "Ask OpenShift Lightspeed" button is clicked', () => {
(useOLSConfig as jest.Mock).mockReturnValue(true);
wrapper = shallow(<AskOpenShiftLightspeedButton />);
wrapper.find(Button).simulate('click');
render(<AskOpenShiftLightspeedButton />);
const button = screen.getByRole('button');
fireEvent.click(button);
expect(mockDispatch).toHaveBeenCalledWith({ type: ActionType.OpenOLS });
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import * as React from 'react';
import { Switch } from '@patternfly/react-core';
import { css } from '@patternfly/react-styles';
import { FormikValues, useField, useFormikContext } from 'formik';
import { isEmpty } from 'lodash';
Expand All @@ -15,6 +14,7 @@ import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watc
import { ConsoleYAMLSampleModel } from '@console/internal/models';
import { getYAMLTemplates } from '@console/internal/models/yaml-templates';
import { definitionFor, K8sResourceCommon, referenceForModel } from '@console/internal/module/k8s';
import { ToggleSidebarButton } from '@console/shared/src/components/editor/ToggleSidebarButton';
import { getResourceSidebarSamples } from '../../utils';
import { CodeEditorFieldProps } from './field-types';

Expand Down Expand Up @@ -99,13 +99,13 @@ const CodeEditorField: React.FC<CodeEditorFieldProps> = ({
language={language}
toolbarLinks={[
hasSidebarContent && (
<Switch
label={t('public~Sidebar')}
<ToggleSidebarButton
key="toggle-sidebar"
id="showSidebar"
isChecked={sidebarOpen}
data-checked-state={sidebarOpen}
onChange={() => setSidebarOpen(!sidebarOpen)}
hasCheckIcon
isSidebarOpen={sidebarOpen}
toggleSidebar={() => setSidebarOpen(!sidebarOpen)}
alignToEnd
className="pf-v6-u-mr-xs"
/>
),
]}
Expand Down
2 changes: 2 additions & 0 deletions frontend/packages/console-shared/src/constants/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ export const COLUMN_MANAGEMENT_LOCAL_STORAGE_KEY = `${STORAGE_PREFIX}/table-colu
export const LOG_WRAP_LINES_USERSETTINGS_KEY = `${USERSETTINGS_PREFIX}.log.wrapLines`;
export const SHOW_YAML_EDITOR_TOOLTIPS_USER_SETTING_KEY = `${USERSETTINGS_PREFIX}.showYAMLEditorTooltips`;
export const SHOW_YAML_EDITOR_TOOLTIPS_LOCAL_STORAGE_KEY = `${STORAGE_PREFIX}/showYAMLEditorTooltips`;
export const SHOW_YAML_EDITOR_STICKY_SCROLL_USER_SETTING_KEY = `${USERSETTINGS_PREFIX}.showYAMLEditorStickyScroll`;
export const SHOW_YAML_EDITOR_STICKY_SCROLL_LOCAL_STORAGE_KEY = `${STORAGE_PREFIX}/showYAMLEditorStickyScroll`;
export const SHOW_FULL_LOG_USERSETTINGS_KEY = `${USERSETTINGS_PREFIX}.show.full.log`;
// Bootstrap user for OpenShift 4.0 clusters (kube:admin)
export const KUBE_ADMIN_USERNAMES = ['kube:admin'];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react';
import { K8sResourceCommon } from '@console/internal/module/k8s';
import { usePostFormSubmitAction } from '../hooks/post-form-submit-action';

type WithPostFormSubmissionCallbackProps<R> = {
export type WithPostFormSubmissionCallbackProps<R> = {
postFormSubmissionCallback: (arg: R) => Promise<R>;
};

Expand Down
Loading