diff --git a/documentation/help.md b/documentation/help.md index 9034f05746..d2b6f2de84 100644 --- a/documentation/help.md +++ b/documentation/help.md @@ -11,7 +11,7 @@ reactions. The application operates in two modes - Standalone and Remote: - Remote version requires Indigo Service as a backend server to perform complex calculations (When the server is not responding you can continue to work in the application although some of the functions will be unavailable). **Ketcher** consists of the following elements: - + **Note** : Depending on the screen size, some tools on the _Tool palette_ can be displayed in expanded or collapsed forms. @@ -63,6 +63,7 @@ selects several structures. You can use the buttons of the _Main_ toolbar: ![](images/4_main_toolbarn.png 'Main Toolbar') + - **Clear Canvas** (1) button to start drawing a new molecule; this command clears the drawing area; @@ -101,31 +102,29 @@ You can use the buttons of the _Main_ toolbar: -- **Stereochemistry** button (16) to assign and display enhanced stereochemistry properties of the structure +- **3D Viewer** button (16) to open the structure in the three-dimensional -- **3D Viewer** button (17) to open the structure in the - three-dimensional Viewer; -- **Settings** button (18) to make some settings for molecular +- **Settings** button (17) to make some settings for molecular files: -- **Help** button (19) to view Help; +- **Help** button (18) to view Help; -- **About** button (20) to display version and copyright information +- **About** button (19) to display version and copyright information of the program. -- **Fullscreen mode** button (21) allows to initiate displaying Ketcher window in the fullscreen mode. +- **Fullscreen mode** button (20) allows to initiate displaying Ketcher window in the fullscreen mode. -- **Zoom panel** (22) displays the current zoom percentage. Click to expand the Zoom panel and use the following actions: **Zoom percentage** (23) to set the view manually, **Zoom in** (24) / **Zoom out** (25) to scale the view gradually, **Zoom 100%** (26) to enable the default zoom setting. +- **Zoom panel** (21) displays the current zoom percentage. Click to expand the Zoom panel and use the following actions: **Zoom percentage** (22) to set the view manually, **Zoom in** (23) / **Zoom out** (24) to scale the view gradually, **Zoom 100%** (25) to enable the default zoom setting. - + # 3D Viewer The structure appears in a modal window after clicking on the **3D -Viewer** button on the top panel: +Viewer** button on the top panel: @@ -377,12 +376,14 @@ type of selected elements: # Changing Structure Display -Use the _Rotate_ tool to change the structure +Use the _Rotate_ tool to change the structure display: - + + _Rotate Tool_ (1) +_Erase (Del)_ (4) This tool allows rotating objects. @@ -447,7 +448,7 @@ You can add templates (rings or other predefined structures) to the structure using the _Templates_ toolbar together with the _Custom Templates_ button located at the bottom: -
+

To add a ring to the molecule, select a ring from the toolbar and @@ -491,7 +492,7 @@ atom or bond preset in the program. **Note** : User is able to define the attachment atom and bond by clicking the Edit button for template structure in the Template Library. -The _Custom Templates_ button allows to view the list of templates available; both built-in and created +The _Custom Templates_ button allows to view the list of templates available; both built-in and created by user: @@ -516,7 +517,7 @@ Ketcher allows you to select and use Functional Groups to properly represent you Set of functional groups available is predefined and can't be changed right now. Explore the list of the Functional Groups available in the Templates library. Open it using the icon in the bottom toolbar:
-
+
Navigate to the Functional Groups tab to explore the FGs available: ![](images/FG_tab.png) @@ -636,114 +637,114 @@ it will appear in brackets after the description of the button. _NOTE: `Mod` key is `Command` on OSX and `Ctrl` on PC systems_ **General** -| Shortcut| Action | -| --- | --- | -| `Mod+Delete` | Clear Canvas | -| `Mod+o` | Open… | -| `Mod+s` | Save As… | -| `Mod+z` | Undo | -| `Mod+Shift+z, Mod+y` | Redo | -| `Mod+x` | Cut | -| `Mod+c` | Copy | -| `Mod+Shift+f` | Copy Image | -| `Mod+m` | Copy as MOL | -| `Mod+Shift+k` | Copy as KET | -| `Mod+v` | Paste | -| `Mod+a` | Select All | -| `Mod+Shift+a` | Deselect All | -| `Mod+d` | Select descriptors | +| Shortcut | Action | +| -------------------- | ------------------ | +| `Mod+Delete` | Clear Canvas | +| `Mod+o` | Open… | +| `Mod+s` | Save As… | +| `Mod+z` | Undo | +| `Mod+Shift+z, Mod+y` | Redo | +| `Mod+x` | Cut | +| `Mod+c` | Copy | +| `Mod+Shift+f` | Copy Image | +| `Mod+m` | Copy as MOL | +| `Mod+Shift+k` | Copy as KET | +| `Mod+v` | Paste | +| `Mod+a` | Select All | +| `Mod+Shift+a` | Deselect All | +| `Mod+d` | Select descriptors | **Server** -| Shortcut| Action | -| --- | --- | -| `Alt+a` | Aromatize | -| `Ctrl+Alt+a` | Dearomatize | -| `Mod+l` | Layout | -| `Mod+Shift+l` | Clean Up | -| `Mod+p` | Calculate CIP | -| `Alt+s` | Check structure | -| `Alt+c` | Calculate values | +| Shortcut | Action | +| ------------- | ---------------- | +| `Alt+a` | Aromatize | +| `Ctrl+Alt+a` | Dearomatize | +| `Mod+l` | Layout | +| `Mod+Shift+l` | Clean Up | +| `Mod+p` | Calculate CIP | +| `Alt+s` | Check structure | +| `Alt+c` | Calculate values | **Debug** -| Shortcut| Action | -| --- | --- | +| Shortcut | Action | +| ------------- | ------------ | | `Mod+Shift+r` | force-update | | `Alt+Shift+r` | qs-serialize | **Tools** -| Shortcut| Action | -| --- | --- | -| `Mod+h` | Hand tool | -| `Escape` | Rotate between: Lasso Selection, Rectangle Selection, Fragment Selection | -| `Delete, Backspace` | Erase | -| `Alt+e` | Stereochemistry | -| `+` | Charge Plus | -| `-` | Charge Minus | -| `Alt+r` | Rotate Tool | -| `Alt+h` | Horizontal Flip | -| `Alt+v` | Vertical Flip | -| `Mod+g` | Rotate between: S-Group, Data S-Group | -| `Mod+r` | Rotate between: R-Group Label Tool, Attachment Point Tool | -| `Mod+Shift+r, Mod+r` | R-Group Fragment Tool | -| `1` | Rotate between: Single Bond, Single Up Bond, Single Down Bond, Single Up/Down Bond | -| `2` | Rotate between: Double Bond, Double Cis/Trans Bond | -| `3` | Triple Bond | -| `4` | Aromatic Bond | -| `0` | Any Bond | -| `Alt+t` | Add text | +| Shortcut | Action | +| -------------------- | ---------------------------------------------------------------------------------- | +| `Mod+h` | Hand tool | +| `Escape` | Rotate between: Lasso Selection, Rectangle Selection, Fragment Selection | +| `Delete, Backspace` | Erase | +| `Alt+e` | Stereochemistry | +| `+` | Charge Plus | +| `-` | Charge Minus | +| `Alt+r` | Rotate Tool | +| `Alt+h` | Horizontal Flip | +| `Alt+v` | Vertical Flip | +| `Mod+g` | Rotate between: S-Group, Data S-Group | +| `Mod+r` | Rotate between: R-Group Label Tool, Attachment Point Tool | +| `Mod+Shift+r, Mod+r` | R-Group Fragment Tool | +| `1` | Rotate between: Single Bond, Single Up Bond, Single Down Bond, Single Up/Down Bond | +| `2` | Rotate between: Double Bond, Double Cis/Trans Bond | +| `3` | Triple Bond | +| `4` | Aromatic Bond | +| `0` | Any Bond | +| `Alt+t` | Add text | **Atoms** -| Shortcut| Action | -| --- | --- | -| `h` | Atom H | -| `c` | Atom C | -| `n` | Atom N | -| `o` | Atom O | -| `s` | Atom S | -| `p` | Atom P | -| `f` | Atom F | -| `b` | Atom Br | -| `i` | Atom I | -| `k` | Atom K | -| `Shift+c` | Atom Cl | -| `Shift+s` | Atom Si | -| `Shift+n` | Atom Na | -| `Shift+b` | Atom B | -| `r` | Pseudoatom | -| `d` | Deuterium | -| `a` | Any atom | -| `q` | Any heteroatom | -| `m` | Any metal | -| `x` | Any halogen | -| `/` | Display the Atom Properties dialog box | +| Shortcut | Action | +| --------- | -------------------------------------- | +| `h` | Atom H | +| `c` | Atom C | +| `n` | Atom N | +| `o` | Atom O | +| `s` | Atom S | +| `p` | Atom P | +| `f` | Atom F | +| `b` | Atom Br | +| `i` | Atom I | +| `k` | Atom K | +| `Shift+c` | Atom Cl | +| `Shift+s` | Atom Si | +| `Shift+n` | Atom Na | +| `Shift+b` | Atom B | +| `r` | Pseudoatom | +| `d` | Deuterium | +| `a` | Any atom | +| `q` | Any heteroatom | +| `m` | Any metal | +| `x` | Any halogen | +| `/` | Display the Atom Properties dialog box | **Bonds** -| Shortcut| Action | -| --- | --- | -| `/` | Open bond properties | +| Shortcut | Action | +| -------- | -------------------- | +| `/` | Open bond properties | **Zoom** -| Shortcut| Action | -| --- | --- | -| `Mod+_, Mod+-` | Zoom Out | -| `Mod+=, Mod++` | Zoom In | -| `Mod+Shift+0` | Zoom 100% | +| Shortcut | Action | +| -------------- | --------- | +| `Mod+_, Mod+-` | Zoom Out | +| `Mod+=, Mod++` | Zoom In | +| `Mod+Shift+0` | Zoom 100% | **Templates** -| Shortcut| Action | -| --- | --- | -| `Shift+t` | Custom Templates | -| `t` | Rotate between: Benzene, Cyclopentadiene, Cyclohexane, Cyclopentane, Cyclopropane, Cyclobutane, Cycloheptane, Cyclooctane | +| Shortcut | Action | +| --------- | ------------------------------------------------------------------------------------------------------------------------- | +| `Shift+t` | Structure Library | +| `t` | Rotate between: Benzene, Cyclopentadiene, Cyclohexane, Cyclopentane, Cyclopropane, Cyclobutane, Cycloheptane, Cyclooctane | **FunctionalGroups** -| Shortcut| Action | -| --- | --- | +| Shortcut | Action | +| --------- | ----------------- | | `Shift+f` | Functional Groups | **Help** -| Shortcut| Action | -| --- | --- | -| `?, &, Shift+/` | Help | +| Shortcut | Action | +| --------------- | ------ | +| `?, &, Shift+/` | Help | **Note** : Please, use`Ctrl+V`to paste the selected object in Google Chrome and Mozilla Firefox browsers. diff --git a/documentation/images/1_overview.png b/documentation/images/1_overview.png new file mode 100644 index 0000000000..162c885efc Binary files /dev/null and b/documentation/images/1_overview.png differ diff --git a/documentation/images/1_overviewn.png b/documentation/images/1_overviewn.png deleted file mode 100644 index c77f6ef1de..0000000000 Binary files a/documentation/images/1_overviewn.png and /dev/null differ diff --git a/documentation/images/31_datasgroup_icon.png b/documentation/images/31_datasgroup_icon.png index 501010cf87..3585ad0ea6 100644 Binary files a/documentation/images/31_datasgroup_icon.png and b/documentation/images/31_datasgroup_icon.png differ diff --git a/documentation/images/32_rotate_icon.png b/documentation/images/32_rotate_icon.png deleted file mode 100644 index 15fd3ee74d..0000000000 Binary files a/documentation/images/32_rotate_icon.png and /dev/null differ diff --git a/documentation/images/32_rotate_tool.png b/documentation/images/32_rotate_tool.png new file mode 100644 index 0000000000..6882633b1c Binary files /dev/null and b/documentation/images/32_rotate_tool.png differ diff --git a/documentation/images/32_rotate_tool_2.png b/documentation/images/32_rotate_tool_2.png new file mode 100644 index 0000000000..ec07c52799 Binary files /dev/null and b/documentation/images/32_rotate_tool_2.png differ diff --git a/documentation/images/32_rotate_tool_icon.png b/documentation/images/32_rotate_tool_icon.png new file mode 100644 index 0000000000..840beb9f23 Binary files /dev/null and b/documentation/images/32_rotate_tool_icon.png differ diff --git a/documentation/images/43-2.png b/documentation/images/43-2.png deleted file mode 100644 index e30dc35d9a..0000000000 Binary files a/documentation/images/43-2.png and /dev/null differ diff --git a/documentation/images/43.1_templates_iconn.png b/documentation/images/43.1_templates_iconn.png deleted file mode 100644 index d504ec39e5..0000000000 Binary files a/documentation/images/43.1_templates_iconn.png and /dev/null differ diff --git a/documentation/images/43.png b/documentation/images/43.png deleted file mode 100644 index 9c30113a9a..0000000000 Binary files a/documentation/images/43.png and /dev/null differ diff --git a/documentation/images/43_custom_templates_icon.png b/documentation/images/43_custom_templates_icon.png new file mode 100644 index 0000000000..c1a3502d16 Binary files /dev/null and b/documentation/images/43_custom_templates_icon.png differ diff --git a/documentation/images/43_templates_toolbar.png b/documentation/images/43_templates_toolbar.png new file mode 100644 index 0000000000..ee983bac70 Binary files /dev/null and b/documentation/images/43_templates_toolbar.png differ diff --git a/documentation/images/43_templates_toolbarn.png b/documentation/images/43_templates_toolbarn.png deleted file mode 100644 index 1822d667c0..0000000000 Binary files a/documentation/images/43_templates_toolbarn.png and /dev/null differ diff --git a/documentation/images/4_main_toolbar.png b/documentation/images/4_main_toolbar.png new file mode 100644 index 0000000000..65c47dfe3e Binary files /dev/null and b/documentation/images/4_main_toolbar.png differ diff --git a/documentation/images/4_main_toolbarn.png b/documentation/images/4_main_toolbarn.png deleted file mode 100644 index cd83c5e44c..0000000000 Binary files a/documentation/images/4_main_toolbarn.png and /dev/null differ diff --git a/documentation/images/8_1_miew_icon.png b/documentation/images/8_1_miew_icon.png deleted file mode 100644 index f2bf4508e8..0000000000 Binary files a/documentation/images/8_1_miew_icon.png and /dev/null differ diff --git a/documentation/images/8_zoom.png b/documentation/images/8_zoom.png new file mode 100644 index 0000000000..124f57566f Binary files /dev/null and b/documentation/images/8_zoom.png differ diff --git a/documentation/images/9_3D_viewer.png b/documentation/images/9_3D_viewer.png new file mode 100644 index 0000000000..532ffaba2b Binary files /dev/null and b/documentation/images/9_3D_viewer.png differ diff --git a/ketcher-autotests/tests/Templates/Functional-Groups/click-fg-on-canvas.spec.ts b/ketcher-autotests/tests/Templates/Functional-Groups/click-fg-on-canvas.spec.ts index 6c40ed29af..c1325b8891 100644 --- a/ketcher-autotests/tests/Templates/Functional-Groups/click-fg-on-canvas.spec.ts +++ b/ketcher-autotests/tests/Templates/Functional-Groups/click-fg-on-canvas.spec.ts @@ -7,13 +7,13 @@ import { moveMouseToTheMiddleOfTheScreen, selectAtomInToolbar, takeEditorScreenshot, - pressButton, AtomButton, selectFunctionalGroups, selectSaltsAndSolvents, FunctionalGroups, resetCurrentTool, SaltsAndSolvents, + selectTemplate, } from '@utils'; test.describe('Click Functional Group on canvas', () => { @@ -33,7 +33,7 @@ test.describe('Click Functional Group on canvas', () => { */ await selectAtomInToolbar(AtomButton.Nitrogen, page); await clickInTheMiddleOfTheScreen(page); - await pressButton(page, 'Custom Templates'); + await selectTemplate(page); await page.getByRole('tab', { name: 'Functional Groups' }).click(); await selectFunctionalGroups(FunctionalGroups.Boc, page); await clickInTheMiddleOfTheScreen(page); @@ -44,11 +44,11 @@ test.describe('Click Functional Group on canvas', () => { Test case: EPMLSOPKET-10106 Description: when clicking with an FG template on an FG it should replace it */ - await pressButton(page, 'Custom Templates'); + await selectTemplate(page); await page.getByRole('tab', { name: 'Functional Groups' }).click(); await selectFunctionalGroups(FunctionalGroups.Boc, page); await clickInTheMiddleOfTheScreen(page); - await pressButton(page, 'Custom Templates'); + await selectTemplate(page); await selectFunctionalGroups(FunctionalGroups.Cbz, page); await clickInTheMiddleOfTheScreen(page); }); @@ -58,12 +58,12 @@ test.describe('Click Functional Group on canvas', () => { Test case: EPMLSOPKET-10107 Description: when clicking with an FG template on a Salts and Solvents it should replace it */ - await pressButton(page, 'Custom Templates'); + await selectTemplate(page); await page.getByRole('tab', { name: 'Salts and Solvents' }).click(); await selectSaltsAndSolvents(SaltsAndSolvents.MethaneSulphonicAcid, page); await clickInTheMiddleOfTheScreen(page); - await pressButton(page, 'Custom Templates'); + await selectTemplate(page); await page.getByRole('tab', { name: 'Functional Groups' }).click(); await selectFunctionalGroups(FunctionalGroups.CCl3, page); await clickInTheMiddleOfTheScreen(page); @@ -84,7 +84,7 @@ test.describe('Click Functional Group on canvas', () => { await dragMouseTo(coordinatesWithShift, y, page); await resetCurrentTool(page); - await pressButton(page, 'Custom Templates'); + await selectTemplate(page); await page.getByRole('tab', { name: 'Functional Groups' }).click(); await selectFunctionalGroups(FunctionalGroups.CO2tBu, page); await clickInTheMiddleOfTheScreen(page); @@ -98,7 +98,7 @@ test.describe('Click Functional Group on canvas', () => { await selectAtomInToolbar(AtomButton.Oxygen, page); await clickInTheMiddleOfTheScreen(page); - await pressButton(page, 'Custom Templates'); + await selectTemplate(page); await page.getByRole('tab', { name: 'Functional Groups' }).click(); await selectFunctionalGroups(FunctionalGroups.Cbz, page); await moveMouseToTheMiddleOfTheScreen(page); @@ -106,7 +106,7 @@ test.describe('Click Functional Group on canvas', () => { const coordinatesWithShift = x + MAX_BOND_LENGTH; await dragMouseTo(coordinatesWithShift, y, page); - await pressButton(page, 'Custom Templates'); + await selectTemplate(page); await page.getByRole('tab', { name: 'Functional Groups' }).click(); await selectFunctionalGroups(FunctionalGroups.Ms, page); await page.mouse.click(coordinatesWithShift, y); diff --git a/ketcher-autotests/tests/utils/selectors/buttons.ts b/ketcher-autotests/tests/utils/selectors/buttons.ts index 9fefb4cda7..26c5d20e02 100644 --- a/ketcher-autotests/tests/utils/selectors/buttons.ts +++ b/ketcher-autotests/tests/utils/selectors/buttons.ts @@ -74,7 +74,7 @@ export enum RingButton { Cyclooctane = 'Cyclooctane', } -export const CustomTemplatesButton = 'Custom Templates'; +export const CustomTemplatesButton = 'Structure Library'; export type ToolbarButton = | AtomButton diff --git a/ketcher-autotests/tests/utils/selectors/templateModal.ts b/ketcher-autotests/tests/utils/selectors/templateModal.ts index 1aeef19db4..e685d76ef2 100644 --- a/ketcher-autotests/tests/utils/selectors/templateModal.ts +++ b/ketcher-autotests/tests/utils/selectors/templateModal.ts @@ -5,9 +5,9 @@ import { dragMouseTo, getCoordinatesOfTheMiddleOfTheScreen, moveMouseToTheMiddleOfTheScreen, - pressButton, selectLeftPanelButton, takeEditorScreenshot, + selectTemplate, } from '@utils'; export enum SaltsAndSolvents { @@ -102,7 +102,7 @@ export async function drawFGAndDrag( shift: number, page: Page ) { - await pressButton(page, 'Custom Templates'); + await selectTemplate(page); await page.getByRole('tab', { name: 'Functional Groups' }).click(); await selectFunctionalGroups(itemToChoose, page); await moveMouseToTheMiddleOfTheScreen(page); @@ -119,7 +119,7 @@ export async function drawSaltAndDrag( shift: number, page: Page ) { - await pressButton(page, 'Custom Templates'); + await selectTemplate(page); await page.getByRole('tab', { name: 'Salts and Solvents' }).click(); await selectSaltsAndSolvents(itemToChoose, page); await moveMouseToTheMiddleOfTheScreen(page); diff --git a/packages/ketcher-core/src/application/editor/actions/paste.ts b/packages/ketcher-core/src/application/editor/actions/paste.ts index b788c1a781..7b1a3a550b 100644 --- a/packages/ketcher-core/src/application/editor/actions/paste.ts +++ b/packages/ketcher-core/src/application/editor/actions/paste.ts @@ -103,6 +103,9 @@ export function fromPaste( const newsgid = restruct.molecule.sgroups.newId() const sgAtoms = sg.atoms.map((aid) => aidMap.get(aid)) const attachmentPoints = sg.cloneAttachmentPoints(aidMap) + if (sg.isNotContractible(pstruct)) { + sg.setAttr('expanded', true) + } const sgAction = fromSgroupAddition( restruct, sg.type, diff --git a/packages/ketcher-core/src/application/editor/actions/rotate.ts b/packages/ketcher-core/src/application/editor/actions/rotate.ts index 93fc329a7f..afaf776067 100644 --- a/packages/ketcher-core/src/application/editor/actions/rotate.ts +++ b/packages/ketcher-core/src/application/editor/actions/rotate.ts @@ -138,7 +138,7 @@ function fromTextFlip( // `pos`: a box (not bounding box) // `TextMove` is to move `position`, so we have to calculate the flipped `position` const textMiddleLeft = text.pos[0] - const textMiddleRight = text.pos[3] + const textMiddleRight = text.pos[2] const textCenter = Vec2.centre(textMiddleLeft, textMiddleRight) const difference = flipPointByCenter(textCenter, center, flipDirection) diff --git a/packages/ketcher-core/src/application/render/restruct/resgroup.js b/packages/ketcher-core/src/application/render/restruct/resgroup.js index d046b1f058..8a2b05be40 100644 --- a/packages/ketcher-core/src/application/render/restruct/resgroup.js +++ b/packages/ketcher-core/src/application/render/restruct/resgroup.js @@ -186,7 +186,7 @@ class ReSGroup extends ReObject { sGroupItem.hovering = this.getContractedSelectionContour(render).attr( options.hoverStyle ) - } else { + } else if (!this.selected) { sGroupItem.hovering = paper .path( 'M{0},{1}L{2},{3}L{4},{5}L{6},{7}L{0},{1}', diff --git a/packages/ketcher-core/src/application/render/restruct/restruct.ts b/packages/ketcher-core/src/application/render/restruct/restruct.ts index 22a32c901e..579b5b19f1 100644 --- a/packages/ketcher-core/src/application/render/restruct/restruct.ts +++ b/packages/ketcher-core/src/application/render/restruct/restruct.ts @@ -55,7 +55,7 @@ class ReStruct { reloops: ReLoop, simpleObjects: ReSimpleObject, texts: ReText - } + } as const public render: Render public molecule: Struct @@ -309,32 +309,84 @@ class ReStruct { }) } - getVBoxObj(selection?): Box2Abs { - selection = selection || {} + /** + * Why? + * we can't use just getVBoxObj and the center for atoms + * because this will lead incorrect center position for the move atom around it + * because of atom's vBox contain text label with is not constant after flip/rotate + * and this lead to unstable flip tool work + */ + // eslint-disable-next-line no-use-before-define + getSelectionRotationCenter(selection: SelectionMap): Vec2 | undefined { + let boundingBox: Box2Abs | null = null + + for (const atomId of selection.atoms ?? []) { + const atomPositionPoint = this.atoms.get(atomId)!.a.pp + const atomBox = new Box2Abs(atomPositionPoint, atomPositionPoint) + boundingBox = + boundingBox == null ? atomBox : Box2Abs.union(boundingBox, atomBox) + } + const selectionExceptAtoms = { ...selection } + delete selectionExceptAtoms.atoms + + const selectionExceptAtomsBoundingBox = + this.getBoundingBoxForSelection(selectionExceptAtoms) + if (selectionExceptAtomsBoundingBox) { + boundingBox = boundingBox + ? Box2Abs.union(boundingBox, selectionExceptAtomsBoundingBox) + : selectionExceptAtomsBoundingBox + } + + return boundingBox?.centre() + } + // eslint-disable-next-line no-use-before-define + getVBoxObj(selection?: SelectionMap): Box2Abs { if (isSelectionEmpty(selection)) { - Object.keys(ReStruct.maps).forEach((map) => { - selection[map] = Array.from(this[map].keys()) - }) + selection = this.getAllElementsAsSelectionMap() + } + + let boundingBox = this.getBoundingBoxForSelection(selection) + + if (boundingBox) { + const atomsIdsSelected: number[] = selection.atoms ?? [ + ...this.atoms.keys() + ] + const attachmentPointsVBox = + this.getAttachmentsPointsVBox(atomsIdsSelected) + if (attachmentPointsVBox) { + boundingBox = Box2Abs.union(boundingBox, attachmentPointsVBox) + } } - let vbox: Box2Abs | null = null + boundingBox = boundingBox || new Box2Abs(0, 0, 0, 0) + return boundingBox + } + + // eslint-disable-next-line no-use-before-define + private getAllElementsAsSelectionMap(): SelectionMap { + // eslint-disable-next-line no-use-before-define + const selection: SelectionMap = {} Object.keys(ReStruct.maps).forEach((map) => { - if (!selection[map]) return + selection[map] = Array.from(this[map].keys()) + }) + return selection + } - selection[map].forEach((id) => { - const box = this[map].get(id).getVBoxObj(this.render) - if (box) vbox = vbox ? Box2Abs.union(vbox, box) : box.clone() + // eslint-disable-next-line no-use-before-define + private getBoundingBoxForSelection(selection: SelectionMap): Box2Abs | null { + let boundingBox: Box2Abs | null = null + Object.keys(ReStruct.maps).forEach((elementKey) => { + selection[elementKey]?.forEach((elementId) => { + const box = this[elementKey].get(elementId).getVBoxObj(this.render) + if (box) { + boundingBox = boundingBox + ? Box2Abs.union(boundingBox, box) + : box.clone() + } }) }) - - const attachmentPointsVBox = this.getAttachmentsPointsVBox() - if (vbox != null && attachmentPointsVBox) { - vbox = Box2Abs.union(vbox, attachmentPointsVBox) - } - - vbox = vbox || new Box2Abs(0, 0, 0, 0) - return vbox + return boundingBox } translate(d): void { @@ -563,9 +615,10 @@ class ReStruct { }) } - private getAttachmentsPointsVBox(): Box2Abs | null { + private getAttachmentsPointsVBox(atomsIds: number[]): Box2Abs | null { let result: Box2Abs | null = null - for (const reAtom of this.atoms.values()) { + for (const atomId of atomsIds) { + const reAtom = this.atoms.get(atomId)! const bbox = reAtom.getVBoxObjOfAttachmentPoint(this.render) if (bbox) { result = result ? Box2Abs.union(result, bbox) : bbox @@ -708,7 +761,8 @@ class ReStruct { } } -function isSelectionEmpty(selection) { +// eslint-disable-next-line no-use-before-define +function isSelectionEmpty(selection?: SelectionMap): selection is undefined { if (!selection) return true const anySelection = Object.keys(ReStruct.maps).some( @@ -754,4 +808,8 @@ function isSelectionSvgObjectExists(item) { ) } +type SelectionMap = Partial< + Record +> + export default ReStruct diff --git a/packages/ketcher-core/src/application/render/restruct/retext.ts b/packages/ketcher-core/src/application/render/restruct/retext.ts index c141b1b2ed..a0231a6ee1 100644 --- a/packages/ketcher-core/src/application/render/restruct/retext.ts +++ b/packages/ketcher-core/src/application/render/restruct/retext.ts @@ -20,7 +20,7 @@ import { RawDraftContentState, RawDraftInlineStyleRange } from 'draft-js' -import { Text, TextCommand, Vec2 } from 'domain/entities' +import { Box2Abs, Text, TextCommand, Vec2 } from 'domain/entities' import { flatten, isEqual } from 'lodash/fp' import { LayerMap } from './generalEnumTypes' @@ -74,6 +74,11 @@ class ReText extends ReObject { return refPoints } + getVBoxObj(): Box2Abs { + const [leftTopPoint, _, rightBottomPoint] = this.getReferencePoints() + return new Box2Abs(leftTopPoint, rightBottomPoint) + } + hoverPath(render: any): any { const { p0, p1 } = this.getRelBox(this.paths) const topLeft = p0.sub(render.options.offset) diff --git a/packages/ketcher-react/src/components/styles/consts.ts b/packages/ketcher-react/src/components/styles/consts.ts index aece602ae9..2d226c598f 100644 --- a/packages/ketcher-react/src/components/styles/consts.ts +++ b/packages/ketcher-react/src/components/styles/consts.ts @@ -16,7 +16,7 @@ const color = { white: '#FFFFFF', - primaryWhite: 'EFF2F5', + primaryWhite: '#EFF2F5', green: '#167782', lightGreen: '#188794', graphite: '#333333', diff --git a/packages/ketcher-react/src/script/editor/Editor.ts b/packages/ketcher-react/src/script/editor/Editor.ts index 80a219352e..1c4a6747e8 100644 --- a/packages/ketcher-react/src/script/editor/Editor.ts +++ b/packages/ketcher-react/src/script/editor/Editor.ts @@ -629,9 +629,12 @@ function resetSelectionOnCanvasClick( function updateLastCursorPosition(editor: Editor, event) { const events = ['mousemove', 'click', 'mousedown', 'mouseup', 'mouseover'] if (events.includes(event.type)) { + const clientAreaBoundingBox = + editor.render.clientArea.getBoundingClientRect() + editor.lastCursorPosition = { - x: event.layerX, - y: event.layerY + x: event.pageX - clientAreaBoundingBox.x, + y: event.pageY - clientAreaBoundingBox.y } } } diff --git a/packages/ketcher-react/src/script/editor/tool/rotate.ts b/packages/ketcher-react/src/script/editor/tool/rotate.ts index d0df77063b..089f48a2e0 100644 --- a/packages/ketcher-react/src/script/editor/tool/rotate.ts +++ b/packages/ketcher-react/src/script/editor/tool/rotate.ts @@ -153,13 +153,12 @@ class RotateTool implements Tool { rxnArrows?.length || rxnPluses?.length) ) { - const selectionBoundingBox = editor.render.ctab.getVBoxObj({ + center = editor.render.ctab.getSelectionRotationCenter({ atoms: visibleAtoms, texts, rxnArrows, rxnPluses }) - center = selectionBoundingBox?.centre() } return [center, visibleAtoms] as const diff --git a/packages/ketcher-react/src/script/editor/utils/functionalGroupsTooltip.ts b/packages/ketcher-react/src/script/editor/utils/functionalGroupsTooltip.ts index 5b61f9abed..7af2cf981a 100644 --- a/packages/ketcher-react/src/script/editor/utils/functionalGroupsTooltip.ts +++ b/packages/ketcher-react/src/script/editor/utils/functionalGroupsTooltip.ts @@ -98,7 +98,10 @@ export function setFunctionalGroupsTooltip({ ) if (closestCollapsibleStructures && event) { const sGroup = editor.struct()?.sgroups.get(closestCollapsibleStructures.id) - if (sGroup && !sGroup.data.expanded && sGroup.hovering) { + const isSGroupPresent = sGroup?.hovering + const isShowingTooltip = + !sGroup?.data.expanded || SGroup.isDataSGroup(sGroup) + if (isSGroupPresent && isShowingTooltip) { const groupName = sGroup.data.name const groupStruct = makeStruct(editor, sGroup) groupStruct.name = groupName diff --git a/packages/ketcher-react/src/script/ui/action/templates.js b/packages/ketcher-react/src/script/ui/action/templates.js index 19061b95e5..2a5babdbb6 100644 --- a/packages/ketcher-react/src/script/ui/action/templates.js +++ b/packages/ketcher-react/src/script/ui/action/templates.js @@ -20,7 +20,7 @@ import isHidden from './isHidden' const templateLib = { 'template-lib': { shortcut: 'Shift+t', - title: 'Custom Templates', + title: 'Structure Library', action: { dialog: 'templates', prop: { tab: null } }, selected: (editor) => editor._tool.mode === 'classic', disabled: (editor, server, options) => !options.app.templates, diff --git a/packages/ketcher-react/src/script/ui/state/moveSelectedItems.ts b/packages/ketcher-react/src/script/ui/state/moveSelectedItems.ts index 2cfafc318a..14cbbb7a41 100644 --- a/packages/ketcher-react/src/script/ui/state/moveSelectedItems.ts +++ b/packages/ketcher-react/src/script/ui/state/moveSelectedItems.ts @@ -67,19 +67,19 @@ function isCloseToTheEdgeOfCanvas( const position = atom?.a.pp if (position && theMostTopAtom) { theMostTopAtom = - position.y > theMostTopAtom.a.pp.y ? atom : theMostTopAtom + position.y < theMostTopAtom.a.pp.y ? atom : theMostTopAtom } if (position && theMostBottomAtom) { theMostBottomAtom = - position.y < theMostBottomAtom.a.pp.y ? atom : theMostBottomAtom + position.y > theMostBottomAtom.a.pp.y ? atom : theMostBottomAtom } if (position && theMostRightAtom) { theMostRightAtom = - position.x > theMostRightAtom.a.pp.y ? atom : theMostRightAtom + position.x > theMostRightAtom.a.pp.x ? atom : theMostRightAtom } if (position && theMostLeftAtom) { theMostLeftAtom = - position.x < theMostLeftAtom.a.pp.y ? atom : theMostLeftAtom + position.x < theMostLeftAtom.a.pp.x ? atom : theMostLeftAtom } }) if ( diff --git a/packages/ketcher-react/src/script/ui/views/modal/components/document/Save/Save.jsx b/packages/ketcher-react/src/script/ui/views/modal/components/document/Save/Save.jsx index 188d3be967..ad7d342a94 100644 --- a/packages/ketcher-react/src/script/ui/views/modal/components/document/Save/Save.jsx +++ b/packages/ketcher-react/src/script/ui/views/modal/components/document/Save/Save.jsx @@ -17,6 +17,7 @@ import * as structFormat from '../../../../../data/convert/structConverter' import { Component, createRef } from 'react' +import { createSelector } from 'reselect' import Form, { Field } from '../../../../../component/form/form/form' import { FormatterFactory, @@ -443,10 +444,15 @@ class SaveDialog extends Component { } } +const getOptions = (state) => state.options +const serverSettingsSelector = createSelector([getOptions], (options) => + options.getServerSettings() +) + const mapStateToProps = (state) => ({ server: state.options.app.server ? state.server : null, struct: state.editor.struct(), - options: state.options.getServerSettings(), + options: serverSettingsSelector(state), formState: state.modal.form, moleculeErrors: state.modal.form.moleculeErrors, checkState: state.options.check, diff --git a/packages/ketcher-react/src/script/ui/views/modal/components/process/Miew/Miew.tsx b/packages/ketcher-react/src/script/ui/views/modal/components/process/Miew/Miew.tsx index f666d2ab36..f5f5c2caf5 100644 --- a/packages/ketcher-react/src/script/ui/views/modal/components/process/Miew/Miew.tsx +++ b/packages/ketcher-react/src/script/ui/views/modal/components/process/Miew/Miew.tsx @@ -28,6 +28,7 @@ import { connect } from 'react-redux' import { load } from '../../../../../state' import { pick } from 'lodash/fp' import { Miew as MiewAsType } from 'miew' +import { createSelector } from 'reselect' const Viewer = lazy(() => import('miew-react')) @@ -188,8 +189,13 @@ const MiewDialog = ({ ) } +const getOptionsSettings = (state) => state.options.settings +const miewOptionsSelector = createSelector([getOptionsSettings], (settings) => + createMiewOptions(pick(MIEW_OPTIONS, settings)) +) + const mapStateToProps = (state) => ({ - miewOpts: createMiewOptions(pick(MIEW_OPTIONS, state.options.settings)), + miewOpts: miewOptionsSelector(state), server: state.options.app.server ? state.server : null, struct: state.editor.struct(), miewTheme: state.options.settings.miewTheme diff --git a/packages/ketcher-react/src/script/ui/views/toolbars/BottomToolbar/BottomToolbar.container.ts b/packages/ketcher-react/src/script/ui/views/toolbars/BottomToolbar/BottomToolbar.container.ts index 2ba345e75d..7db01ecb14 100644 --- a/packages/ketcher-react/src/script/ui/views/toolbars/BottomToolbar/BottomToolbar.container.ts +++ b/packages/ketcher-react/src/script/ui/views/toolbars/BottomToolbar/BottomToolbar.container.ts @@ -27,13 +27,14 @@ import { onAction } from '../../../state' type StateProps = Omit type OwnProps = Pick +const disableableButtons = [] const mapStateToProps = (state): StateProps => ({ active: state.actionState && state.actionState.activeTool, status: state.actionState || {}, opened: state.toolbar.opened, indigoVerification: state.requestsStatuses.indigoVerification, - disableableButtons: [] + disableableButtons }) const mapDispatchToProps = (dispatch: Dispatch): BottomToolbarCallProps => ({ diff --git a/packages/ketcher-react/src/script/ui/views/toolbars/RightToolbar/RightToolbar.container.ts b/packages/ketcher-react/src/script/ui/views/toolbars/RightToolbar/RightToolbar.container.ts index 3ceaec2bef..0b7d130309 100644 --- a/packages/ketcher-react/src/script/ui/views/toolbars/RightToolbar/RightToolbar.container.ts +++ b/packages/ketcher-react/src/script/ui/views/toolbars/RightToolbar/RightToolbar.container.ts @@ -27,6 +27,7 @@ import { onAction } from '../../../state' type StateProps = Omit type OwnProps = Pick +const disableableButtons = [] const mapStateToProps = (state): StateProps => ({ active: state.actionState && state.actionState.activeTool, @@ -34,7 +35,7 @@ const mapStateToProps = (state): StateProps => ({ freqAtoms: state.toolbar.freqAtoms, opened: state.toolbar.opened, indigoVerification: state.requestsStatuses.indigoVerification, - disableableButtons: [] + disableableButtons }) const mapDispatchToProps = (dispatch: Dispatch): RightToolbarCallProps => ({ 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 2accc99f17..a05ac6be88 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 @@ -22,6 +22,37 @@ import { onAction } from '../../../state' import action from 'src/script/ui/action/index.js' import { shortcutStr } from '../shortcutStr' import { removeStructAction } from 'src/script/ui/state/shared' +import { createSelector } from 'reselect' + +const getActionState = (state) => state.actionState || {} + +const disabledButtonsSelector = createSelector( + [getActionState], + (actionState) => + Object.keys(actionState).reduce((acc: string[], item) => { + if (actionState[item]?.disabled) { + acc.push(item) + } + return acc + }, []) +) +const hiddenButtonsSelector = createSelector([getActionState], (actionState) => + Object.keys(actionState).reduce((acc: string[], item) => { + if (actionState[item]?.hidden) { + acc.push(item) + } + return acc + }, []) +) + +const disableableButtons = [ + 'layout', + 'clean', + 'arom', + 'dearom', + 'cip', + 'enhanced-stereo' +] const shortcuts = Object.keys(action).reduce((acc, key) => { if (action[key]?.shortcut) { @@ -33,44 +64,15 @@ const shortcuts = Object.keys(action).reduce((acc, key) => { }, {}) const mapStateToProps = (state: any) => { - const actionState = state.actionState || {} - - const disabledButtons = Object.keys(actionState).reduce( - (acc: string[], item) => { - if (actionState[item]?.disabled) { - acc.push(item) - } - return acc - }, - [] - ) - - const hiddenButtons = Object.keys(actionState).reduce( - (acc: string[], item) => { - if (actionState[item]?.hidden) { - acc.push(item) - } - return acc - }, - [] - ) - return { currentZoom: Math.round(state.actionState?.zoom?.selected * 100), - disabledButtons, - hiddenButtons, + disabledButtons: disabledButtonsSelector(state), + hiddenButtons: hiddenButtonsSelector(state), shortcuts, status: state.actionState || {}, opened: state.toolbar.opened, indigoVerification: state.requestsStatuses.indigoVerification, - disableableButtons: [ - 'layout', - 'clean', - 'arom', - 'dearom', - 'cip', - 'enhanced-stereo' - ] + disableableButtons } }