From d33427061281861c3310a6d7e773ff4857911b85 Mon Sep 17 00:00:00 2001 From: Daniil Sloboda Date: Mon, 9 Sep 2024 16:51:10 +0400 Subject: [PATCH] #1838 - Create custom top toolbar buttons (#5436) * Custom buttons draft * Fixed build and remove custom buttons to make sure tests are passing * Updated top toolbar snapshot --- ...isible-when-zoomed-in-1-chromium-linux.png | Bin 19280 -> 17453 bytes .../ketcher-core/src/application/ketcher.ts | 4 + .../menu/__snapshots__/Menu.test.tsx.snap | 20 ++--- .../Buttons/IconButton/IconButton.tsx | 34 ++------- .../Buttons/IconButton/IconButtonBase.tsx | 49 ++++++++++++ .../IconButton/IconButtonCustomIcon.tsx | 30 ++++++++ .../components/Buttons/IconButton/index.tsx | 1 + .../components/Buttons/IconButton/styles.ts | 17 ++++- .../components/Buttons/IconButton/types.ts | 19 ++++- .../script/builders/ketcher/CustomButtons.ts | 5 ++ .../script/builders/ketcher/KetcherBuilder.ts | 3 + packages/ketcher-react/src/script/index.ts | 4 + .../src/script/ui/state/index.js | 8 +- .../toolbars/TopToolbar/CustomButtons.tsx | 72 ++++++++++++++++++ .../TopToolbar/TopToolbar.container.ts | 3 + .../views/toolbars/TopToolbar/TopToolbar.tsx | 28 ++++++- .../TopToolbar/TopToolbarIconButton.ts | 15 +++- 17 files changed, 262 insertions(+), 50 deletions(-) create mode 100644 packages/ketcher-react/src/components/Buttons/IconButton/IconButtonBase.tsx create mode 100644 packages/ketcher-react/src/components/Buttons/IconButton/IconButtonCustomIcon.tsx create mode 100644 packages/ketcher-react/src/script/builders/ketcher/CustomButtons.ts create mode 100644 packages/ketcher-react/src/script/ui/views/toolbars/TopToolbar/CustomButtons.tsx diff --git a/ketcher-autotests/tests/User-Interface/Toolbar/toolbar-ui.spec.ts-snapshots/Open-Ketcher-Toolbars-on-the-right-and-bottom-visible-when-zoomed-in-1-chromium-linux.png b/ketcher-autotests/tests/User-Interface/Toolbar/toolbar-ui.spec.ts-snapshots/Open-Ketcher-Toolbars-on-the-right-and-bottom-visible-when-zoomed-in-1-chromium-linux.png index f05f9fba596105891929156a6ffeba154b5669cc..ec934085c8c6a6549c772bfad0aacdb67b2a055c 100644 GIT binary patch literal 17453 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 br*o`b%D1t&-BTK%NF!7gH5JN`51;)X=>utt literal 19280 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 KDN?cu{{H|mv-~jt diff --git a/packages/ketcher-core/src/application/ketcher.ts b/packages/ketcher-core/src/application/ketcher.ts index e82603fc2a..5b6597f776 100644 --- a/packages/ketcher-core/src/application/ketcher.ts +++ b/packages/ketcher-core/src/application/ketcher.ts @@ -499,4 +499,8 @@ export class Ketcher { this.structService = structService; this._indigo = new Indigo(structService); } + + public sendCustomAction(name: string) { + this.eventBus.emit('CUSTOM_BUTTON_PRESSED', name); + } } diff --git a/packages/ketcher-macromolecules/src/components/menu/__snapshots__/Menu.test.tsx.snap b/packages/ketcher-macromolecules/src/components/menu/__snapshots__/Menu.test.tsx.snap index 7bb07fccdc..25c58a54d4 100644 --- a/packages/ketcher-macromolecules/src/components/menu/__snapshots__/Menu.test.tsx.snap +++ b/packages/ketcher-macromolecules/src/components/menu/__snapshots__/Menu.test.tsx.snap @@ -16,7 +16,7 @@ Object { title="" > { - const combinedTitle = shortcut ? `${title} (${shortcut})` : title; - - if (isHidden) { - return null; - } - +export const IconButton = ({ iconName, ...props }: IIconButtonProps) => { return ( - + - + ); }; - -export default IconButton; diff --git a/packages/ketcher-react/src/components/Buttons/IconButton/IconButtonBase.tsx b/packages/ketcher-react/src/components/Buttons/IconButton/IconButtonBase.tsx new file mode 100644 index 0000000000..9d46f3a40e --- /dev/null +++ b/packages/ketcher-react/src/components/Buttons/IconButton/IconButtonBase.tsx @@ -0,0 +1,49 @@ +/**************************************************************************** + * Copyright 2021 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +import { StyledButton } from './styles'; +import { IIconButtonBaseProps } from './types'; + +export const IconButtonBase = ({ + onClick, + shortcut, + title, + className, + isActive = false, + isHidden = false, + disabled = false, + testId, + children, +}: IIconButtonBaseProps) => { + const combinedTitle = shortcut ? `${title} (${shortcut})` : title; + + if (isHidden) { + return null; + } + + return ( + + {children} + + ); +}; diff --git a/packages/ketcher-react/src/components/Buttons/IconButton/IconButtonCustomIcon.tsx b/packages/ketcher-react/src/components/Buttons/IconButton/IconButtonCustomIcon.tsx new file mode 100644 index 0000000000..07030b1874 --- /dev/null +++ b/packages/ketcher-react/src/components/Buttons/IconButton/IconButtonCustomIcon.tsx @@ -0,0 +1,30 @@ +/**************************************************************************** + * Copyright 2021 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + ***************************************************************************/ + +import { IIconButtonCustomIconProps } from './types'; +import { IconButtonBase } from './IconButtonBase'; +import { StyledCustomIcon } from './styles'; + +export const IconButtonCustomIcon = ({ + link, + ...props +}: IIconButtonCustomIconProps) => { + return ( + + + + ); +}; diff --git a/packages/ketcher-react/src/components/Buttons/IconButton/index.tsx b/packages/ketcher-react/src/components/Buttons/IconButton/index.tsx index f3b1235114..4fb116ae87 100644 --- a/packages/ketcher-react/src/components/Buttons/IconButton/index.tsx +++ b/packages/ketcher-react/src/components/Buttons/IconButton/index.tsx @@ -15,3 +15,4 @@ ***************************************************************************/ export { IconButton } from './IconButton'; +export { IconButtonCustomIcon } from './IconButtonCustomIcon'; diff --git a/packages/ketcher-react/src/components/Buttons/IconButton/styles.ts b/packages/ketcher-react/src/components/Buttons/IconButton/styles.ts index 468fc652d4..83ce8d107e 100644 --- a/packages/ketcher-react/src/components/Buttons/IconButton/styles.ts +++ b/packages/ketcher-react/src/components/Buttons/IconButton/styles.ts @@ -18,6 +18,7 @@ import styled from '@emotion/styled'; import Icon from '../../Icon/Icon'; import { style } from 'src/components/styles'; import { IStyledButtonProps } from './types'; +import { css } from '@emotion/react'; export const StyledButton = styled('button', { shouldForwardProp: (prop) => prop !== 'isActive', @@ -53,7 +54,15 @@ export const StyledButton = styled('button', { }, })); -export const StyledIcon = styled(Icon)({ - width: '100%', - height: '100%', -}); +const IconStyles = css` + width: 100%; + height: 100%; +`; + +export const StyledIcon = styled(Icon)` + ${IconStyles}; +`; + +export const StyledCustomIcon = styled('img')` + ${IconStyles}; +`; diff --git a/packages/ketcher-react/src/components/Buttons/IconButton/types.ts b/packages/ketcher-react/src/components/Buttons/IconButton/types.ts index e98249e456..45679b9c62 100644 --- a/packages/ketcher-react/src/components/Buttons/IconButton/types.ts +++ b/packages/ketcher-react/src/components/Buttons/IconButton/types.ts @@ -15,9 +15,9 @@ ***************************************************************************/ import { IconName } from '../../Icon/types'; +import { ReactNode } from 'react'; -export interface IIconButtonProps { - iconName: IconName; +export interface IIconButtonBaseProps { onClick: (e: React.MouseEvent) => void; title?: string; className?: string; @@ -26,6 +26,21 @@ export interface IIconButtonProps { isActive?: boolean; shortcut?: string; testId?: string; + children: ReactNode; +} + +type IIconButtonBasePropsWithoutChildren = Omit< + IIconButtonBaseProps, + 'children' +>; + +export interface IIconButtonProps extends IIconButtonBasePropsWithoutChildren { + iconName: IconName; +} + +export interface IIconButtonCustomIconProps + extends IIconButtonBasePropsWithoutChildren { + link: string; } export interface IStyledButtonProps { diff --git a/packages/ketcher-react/src/script/builders/ketcher/CustomButtons.ts b/packages/ketcher-react/src/script/builders/ketcher/CustomButtons.ts new file mode 100644 index 0000000000..1af98a8552 --- /dev/null +++ b/packages/ketcher-react/src/script/builders/ketcher/CustomButtons.ts @@ -0,0 +1,5 @@ +export interface CustomButton { + id: string; + imageLink: string; + title: string; +} diff --git a/packages/ketcher-react/src/script/builders/ketcher/KetcherBuilder.ts b/packages/ketcher-react/src/script/builders/ketcher/KetcherBuilder.ts index 29f665282f..a6515cca91 100644 --- a/packages/ketcher-react/src/script/builders/ketcher/KetcherBuilder.ts +++ b/packages/ketcher-react/src/script/builders/ketcher/KetcherBuilder.ts @@ -32,6 +32,7 @@ import { initApp } from '../../ui'; import { Root } from 'react-dom/client'; import { IndigoProvider } from 'src/script/providers'; import { STRUCT_SERVICE_INITIALIZED_EVENT } from '../../../constants'; +import { CustomButton } from './CustomButtons'; class KetcherBuilder { private structService: StructService | null; @@ -97,6 +98,7 @@ class KetcherBuilder { errorHandler: (message: string) => void, buttons?: ButtonsConfig, togglerComponent?: JSX.Element, + customButtons?: Array, ): Promise<{ setKetcher: (ketcher: Ketcher) => void; ketcherId: string; @@ -122,6 +124,7 @@ class KetcherBuilder { version: process.env.VERSION || '', buildDate: process.env.BUILD_DATE || '', buildNumber: process.env.BUILD_NUMBER || '', + customButtons: customButtons || [], }, structService!, resolve, diff --git a/packages/ketcher-react/src/script/index.ts b/packages/ketcher-react/src/script/index.ts index e98e0bd53a..fac264894e 100644 --- a/packages/ketcher-react/src/script/index.ts +++ b/packages/ketcher-react/src/script/index.ts @@ -18,6 +18,7 @@ import { Root } from 'react-dom/client'; import { ButtonsConfig, KetcherBuilder } from './builders'; import { StructServiceProvider } from 'ketcher-core'; +import { CustomButton } from './builders/ketcher/CustomButtons'; interface Config { element: HTMLDivElement | null; @@ -25,6 +26,7 @@ interface Config { staticResourcesUrl: string; structServiceProvider: StructServiceProvider; buttons?: ButtonsConfig; + customButtons?: Array; errorHandler: (message: string) => void; togglerComponent?: JSX.Element; } @@ -37,6 +39,7 @@ async function buildKetcherAsync({ buttons, errorHandler, togglerComponent, + customButtons, }: Config) { const builder = new KetcherBuilder(); @@ -50,6 +53,7 @@ async function buildKetcherAsync({ errorHandler, buttons, togglerComponent, + customButtons, ); const ketcher = builder.build(); diff --git a/packages/ketcher-react/src/script/ui/state/index.js b/packages/ketcher-react/src/script/ui/state/index.js index aec9b8ded1..fce88c61f8 100644 --- a/packages/ketcher-react/src/script/ui/state/index.js +++ b/packages/ketcher-react/src/script/ui/state/index.js @@ -101,14 +101,18 @@ function getRootReducer(setEditor) { } export default function (options, server, setEditor) { - const { buttons = {}, ...restOptions } = options; + const { buttons = {}, customButtons, ...restOptions } = options; // TODO: redux localStorage here const initState = { actionState: null, editor: null, modal: null, - options: Object.assign(initOptionsState, { app: restOptions, buttons }), + options: Object.assign(initOptionsState, { + app: restOptions, + buttons, + customButtons, + }), server: server || Promise.reject(new Error('Standalone mode!')), templates: initTmplsState, }; diff --git a/packages/ketcher-react/src/script/ui/views/toolbars/TopToolbar/CustomButtons.tsx b/packages/ketcher-react/src/script/ui/views/toolbars/TopToolbar/CustomButtons.tsx new file mode 100644 index 0000000000..413c297fae --- /dev/null +++ b/packages/ketcher-react/src/script/ui/views/toolbars/TopToolbar/CustomButtons.tsx @@ -0,0 +1,72 @@ +import { CustomButton } from '../../../../builders/ketcher/CustomButtons'; +import { Divider } from './Divider'; +import { TopToolbarCustomIconButton } from './TopToolbarIconButton'; +import { ElementWithDropdown } from './ElementWithDropdown'; + +interface CustomButtonElementProps { + customButton: CustomButton; + onClick: (name: string) => void; +} + +interface CustomButtonsProps { + customButtons: Array; + isCollapsed: boolean; + onCustomAction: (name: string) => void; +} + +const CustomButtonElement = ({ + customButton, + onClick, +}: CustomButtonElementProps) => ( + onClick(customButton.id)} + title={customButton.title} + /> +); + +export const CustomButtons = ({ + isCollapsed, + customButtons, + onCustomAction, +}: CustomButtonsProps) => { + if (customButtons.length === 0) { + return null; + } + + if (isCollapsed) { + return ( + <> + + + } + dropDownElements={customButtons.slice(1).map((customButton) => ( + + ))} + /> + + ); + } + + return ( + <> + + {customButtons.map((customButton) => ( + + ))} + + ); +}; diff --git a/packages/ketcher-react/src/script/ui/views/toolbars/TopToolbar/TopToolbar.container.ts b/packages/ketcher-react/src/script/ui/views/toolbars/TopToolbar/TopToolbar.container.ts index de72616562..fbfc0a441e 100644 --- a/packages/ketcher-react/src/script/ui/views/toolbars/TopToolbar/TopToolbar.container.ts +++ b/packages/ketcher-react/src/script/ui/views/toolbars/TopToolbar/TopToolbar.container.ts @@ -26,6 +26,8 @@ import { createSelector } from 'reselect'; const getActionState = (state) => state.actionState || {}; +const selectCustomButtons = (state) => state?.options?.customButtons || []; + const disabledButtonsSelector = createSelector( [getActionState], (actionState) => @@ -61,6 +63,7 @@ const mapStateToProps = (state: any) => { currentZoom: Math.round(state.actionState?.zoom?.selected * 100), disabledButtons: disabledButtonsSelector(state), hiddenButtons: hiddenButtonsSelector(state), + customButtons: selectCustomButtons(state), shortcuts, status: state.actionState || {}, opened: state.toolbar.opened, diff --git a/packages/ketcher-react/src/script/ui/views/toolbars/TopToolbar/TopToolbar.tsx b/packages/ketcher-react/src/script/ui/views/toolbars/TopToolbar/TopToolbar.tsx index bad8346138..1057f045a2 100644 --- a/packages/ketcher-react/src/script/ui/views/toolbars/TopToolbar/TopToolbar.tsx +++ b/packages/ketcher-react/src/script/ui/views/toolbars/TopToolbar/TopToolbar.tsx @@ -26,6 +26,10 @@ import { SystemControls } from './SystemControls'; import { ExternalFuncControls } from './ExternalFuncControls'; import { Divider } from './Divider'; import { TopToolbarIconButton } from './TopToolbarIconButton'; +import { CustomButtons } from './CustomButtons'; +import { ketcherProvider } from 'ketcher-core'; +import { useCallback, useMemo } from 'react'; +import { CustomButton } from '../../../../builders/ketcher/CustomButtons'; type VoidFunction = () => void; @@ -64,9 +68,11 @@ export interface PanelProps { onAbout: VoidFunction; onHelp: VoidFunction; togglerComponent?: JSX.Element; + customButtons: Array; } const collapseLimit = 650; +const CUSTOM_BUTTON_ADDITIONAL_WIDTH = 40; const ControlsPanel = styled('div')` display: flex; @@ -149,8 +155,23 @@ export const TopToolbar = ({ onAbout, onHelp, togglerComponent, + customButtons, }: PanelProps) => { const { ref: resizeRef, width = 50 } = useResizeObserver(); + const ketcher = ketcherProvider.getKetcher(); + + const onCustomAction = useCallback( + (name: string) => ketcher.sendCustomAction(name), + [ketcher], + ); + + const collapseLimitWithCustomButtons = useMemo(() => { + return ( + collapseLimit + customButtons.length * CUSTOM_BUTTON_ADDITIONAL_WIDTH + ); + }, [customButtons.length]); + + const isCollapsed = width < collapseLimitWithCustomButtons; return ( + diff --git a/packages/ketcher-react/src/script/ui/views/toolbars/TopToolbar/TopToolbarIconButton.ts b/packages/ketcher-react/src/script/ui/views/toolbars/TopToolbar/TopToolbarIconButton.ts index 26b1da257d..12716e9c00 100644 --- a/packages/ketcher-react/src/script/ui/views/toolbars/TopToolbar/TopToolbarIconButton.ts +++ b/packages/ketcher-react/src/script/ui/views/toolbars/TopToolbar/TopToolbarIconButton.ts @@ -15,11 +15,14 @@ ***************************************************************************/ import styled from '@emotion/styled'; -import { IconButton } from 'components'; +import { IconButton, IconButtonCustomIcon } from 'components'; +import { css } from '@emotion/react'; -export const TopToolbarIconButton = styled(IconButton)` +const TopToolbarIconStyles = css` border-radius: 4px; padding: 2px; + width: 28px; + height: 28px; @media only screen { @container (min-width: 1024px) { @@ -36,3 +39,11 @@ export const TopToolbarIconButton = styled(IconButton)` } } `; + +export const TopToolbarIconButton = styled(IconButton)` + ${TopToolbarIconStyles} +`; + +export const TopToolbarCustomIconButton = styled(IconButtonCustomIcon)` + ${TopToolbarIconStyles} +`;