Skip to content

Commit

Permalink
#2216 S-Group pop-up tool tip is positioned so that it overlaps struc…
Browse files Browse the repository at this point in the history
…ture (#2289)

* Change Calculation of the position for InfoPanel component. For template structure calculation do not change at all.
For now it calculates according to the green rectangle that appears when user hovering over the element.
Function calculateMiddleCoordsForRect calculate middle coord for each side of the rect.
After that panel appears relativly to the middle point of side.

- created WrapperPositionedRelativeToRectangle component.
- moved calculateScrollOffsetX , calculateScrollOffsetY into helpers.ts to reduce code duplication
- created calculateMiddleCoordsForRect accepts array of arrays [[1,2], [3,4], [3,4]] => [{ x: 2, y: 3 }, { x: 3, y: 4}]
- added wrapperRef on div to get dinamically changed width / height of the element

* - fixed issue with selection tool

* - fixed eslint error

* - fixed comments

---------

Co-authored-by: Konstantin Zharich <kostantin_zharich@epam.com>
  • Loading branch information
Konstantin1996 and Konstantin Zharich authored Mar 8, 2023
1 parent a2c8470 commit 3e78f98
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import { useState, useEffect, useRef, FC } from 'react'
import { Scale, Vec2, Render, Struct, SGroup } from 'ketcher-core'

import StructRender from '../../../component/structrender'
import SGroupDataRender from './SGroupDataRender'
import { calculateScrollOffsetX, calculateScrollOffsetY } from './helpers'
import { functionGroupInfoSelector } from '../../../state/functionalGroups/selectors'
import { connect } from 'react-redux'
import clsx from 'clsx'
Expand Down Expand Up @@ -71,17 +73,12 @@ function getPanelPosition(
y = panelPosition.y - height - HOVER_PANEL_PADDING * 3
}
// adjust position to current scroll offset
const scrollOffsetX =
render?.options.offset?.x - render?.clientArea?.scrollLeft
const scrollOffsetY =
render?.options?.offset?.y - render?.clientArea?.scrollTop
x += scrollOffsetX
y += scrollOffsetY
x += calculateScrollOffsetX(render)
y += calculateScrollOffsetY(render)
}

return [new Vec2(x, y), new Vec2(width, height)]
}

interface InfoPanelProps {
clientX: number | undefined
clientY: number | undefined
Expand Down Expand Up @@ -148,15 +145,15 @@ const InfoPanel: FC<InfoPanelProps> = (props) => {
/>
</div>
) : (
<div
style={{
left: x + 'px',
top: y + 'px'
}}
className={clsx(classes.infoPanel, className)}
>
{sGroupData}
</div>
<SGroupDataRender
clientX={clientX}
clientY={clientY}
render={render}
groupStruct={groupStruct}
sGroup={sGroup}
sGroupData={sGroupData}
className={className}
/>
)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { useEffect, useRef, useState, FC } from 'react'
import { Vec2, Render, SGroup, Struct } from 'ketcher-core'
import clsx from 'clsx'
import classes from './InfoPanel.module.less'
import {
calculateMiddleCoordsForRect,
calculateScrollOffsetX,
calculateScrollOffsetY
} from './helpers'

const BAR_PANEL_SIZE = 32
const LEFT_PADDING_MULTIPLIER = 3

function getPanelPositionRelativeToRect(
clientX: number,
clientY: number,
sGroup: SGroup,
render: Render,
width: number,
height: number
): Vec2 | null {
const viewportLeftLimit = BAR_PANEL_SIZE * LEFT_PADDING_MULTIPLIER + width
const viewportBottomLimit =
render?.clientArea?.clientHeight - BAR_PANEL_SIZE - height
const viewportRightLimit =
render?.clientArea?.clientWidth - BAR_PANEL_SIZE - width

// [['M', 23, 43], ['L', 23, 24]] we should remove first elements => [[23,43], [23,24]]
const rectCoords: Array<Array<number>> = sGroup.hovering.attrs?.path?.map(
(line) => line.slice(1)
)

const [middleLeftSide, middleBottomSide, middleRightSide, middleTopSide] =
calculateMiddleCoordsForRect(rectCoords)

if (
!middleBottomSide?.x ||
!middleBottomSide?.y ||
!middleTopSide?.y ||
!middleLeftSide?.x ||
!middleLeftSide?.y ||
!middleRightSide?.x ||
!middleRightSide?.y
) {
return null
}

// Default position for panel is in the bottom;
let x = middleBottomSide.x - width / 2
let y = middleBottomSide.y

if (clientY > viewportBottomLimit) {
y = middleTopSide.y - height
}

if (clientX > viewportRightLimit) {
x = middleLeftSide.x - width
y = middleLeftSide.y - height / 2
}

if (clientX < viewportLeftLimit) {
x = middleRightSide.x
y = middleRightSide.y - height / 2
}

x += calculateScrollOffsetX(render)
y += calculateScrollOffsetY(render)

return new Vec2(x, y)
}

interface SGroupDataRenderProps {
clientX: number
clientY: number
render: Render
groupStruct: Struct
sGroup: SGroup
sGroupData: string | null
className?: string
}

const SGroupDataRender: FC<SGroupDataRenderProps> = (props) => {
const { clientX, clientY, render, className, sGroup, sGroupData } = props
const [wrapperHeight, setWrapperHeight] = useState(0)
const [wrapperWidth, setWrapperWidth] = useState(0)
const wrapperRef = useRef<HTMLDivElement>(null)

useEffect(() => {
if (wrapperRef.current) {
setWrapperHeight(wrapperRef.current.clientHeight)
setWrapperWidth(wrapperRef.current.clientWidth)
}
})

const panelCoordinate = getPanelPositionRelativeToRect(
clientX,
clientY,
sGroup,
render,
wrapperWidth,
wrapperHeight
)

return panelCoordinate ? (
<div
ref={wrapperRef}
style={{ left: panelCoordinate.x + 'px', top: panelCoordinate.y + 'px' }}
className={clsx(classes.infoPanel, className)}
>
{sGroupData}
</div>
) : null
}

export default SGroupDataRender
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Render, Point } from 'ketcher-core'

const X_COORD_INDEX = 0
const Y_COORD_INDEX = 1

export const calculateMiddleCoordsForRect = (
rectCoords: Array<Array<number>>
): Array<Point> | [] => {
if (!rectCoords) {
return []
}

const middleCoordForRectangleSides: Array<Point> = []

const previousPoint: Point = {
x: rectCoords[0][X_COORD_INDEX],
y: rectCoords[0][Y_COORD_INDEX]
}

for (let i = 1; i < rectCoords.length; i++) {
if (!previousPoint.x || !previousPoint.y) {
return []
}

const middleX = (previousPoint.x + rectCoords[i][X_COORD_INDEX]) / 2
const middleY = (previousPoint.y + rectCoords[i][Y_COORD_INDEX]) / 2
middleCoordForRectangleSides.push({ x: middleX, y: middleY })
previousPoint.x = rectCoords[i][X_COORD_INDEX]
previousPoint.y = rectCoords[i][Y_COORD_INDEX]
}

return middleCoordForRectangleSides
}

export const calculateScrollOffsetX = (render: Render) => {
return render?.options.offset?.x - render?.clientArea?.scrollLeft
}

export const calculateScrollOffsetY = (render: Render) => {
return render?.options?.offset?.y - render?.clientArea?.scrollTop
}

0 comments on commit 3e78f98

Please sign in to comment.