From 04f8b139b079b42afa1b04ebb8db8874a997ac40 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 4 Aug 2022 14:46:41 +0000 Subject: [PATCH 1/2] chore(deps): update dependency chart.js to v3.9.1 --- frontend/package.json | 2 +- frontend/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 9fd0ac4b8d..09402e1479 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -63,7 +63,7 @@ "@types/react-timeago": "4.1.3", "@types/semver": "7.3.10", "@vitejs/plugin-react": "1.3.2", - "chart.js": "3.8.2", + "chart.js": "3.9.1", "chartjs-adapter-date-fns": "2.0.0", "classnames": "2.3.1", "copy-to-clipboard": "3.3.2", diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 8df3b9e5cc..b5e76cda3c 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2919,10 +2919,10 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== -chart.js@3.8.2: - version "3.8.2" - resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.8.2.tgz#e3ebb88f7072780eec4183a788a990f4a58ba7a1" - integrity sha512-7rqSlHWMUKFyBDOJvmFGW2lxULtcwaPLegDjX/Nu5j6QybY+GCiQkEY+6cqHw62S5tcwXMD8Y+H5OBGoR7d+ZQ== +chart.js@3.9.1: + version "3.9.1" + resolved "https://registry.yarnpkg.com/chart.js/-/chart.js-3.9.1.tgz#3abf2c775169c4c71217a107163ac708515924b8" + integrity sha512-Ro2JbLmvg83gXF5F4sniaQ+lTbSv18E+TIf2cOeiH1Iqd2PGFOtem+DUufMZsCJwFE7ywPOpfXFBwRTGq7dh6w== chartjs-adapter-date-fns@2.0.0: version "2.0.0" From 6eb3922741606dfa6c0588ae91d8e36cbacb5280 Mon Sep 17 00:00:00 2001 From: Tymoteusz Czech <2625371+Tymek@users.noreply.github.com> Date: Fri, 5 Aug 2022 09:54:15 +0200 Subject: [PATCH 2/2] Improve drag and drop on strategies (#1203) * initial drag and drop improvements * prevent oscillation when dragging strategies by handle --- .../common/SegmentItem/SegmentItem.tsx | 2 +- .../FeatureStrategySegmentList.tsx | 4 +- .../EnvironmentAccordionBody.tsx | 120 +++++++++++++----- .../StrategyDraggableItem.tsx | 30 ++++- .../StrategyItem/StrategyItem.tsx | 20 ++- 5 files changed, 130 insertions(+), 46 deletions(-) diff --git a/frontend/src/component/common/SegmentItem/SegmentItem.tsx b/frontend/src/component/common/SegmentItem/SegmentItem.tsx index 5b0745cb79..be5f240b6b 100644 --- a/frontend/src/component/common/SegmentItem/SegmentItem.tsx +++ b/frontend/src/component/common/SegmentItem/SegmentItem.tsx @@ -65,7 +65,7 @@ export const SegmentItem: VFC = ({ condition={segment!.constraints?.length > 0} show={ } diff --git a/frontend/src/component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentList.tsx b/frontend/src/component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentList.tsx index 9a31f86f22..ac04eaf5b2 100644 --- a/frontend/src/component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentList.tsx +++ b/frontend/src/component/feature/FeatureStrategy/FeatureStrategySegment/FeatureStrategySegmentList.tsx @@ -50,9 +50,7 @@ export const FeatureStrategySegmentList = ({ ( - - )} + show={() => } /> ); diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/EnvironmentAccordionBody.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/EnvironmentAccordionBody.tsx index 8c51528caf..4f738d3406 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/EnvironmentAccordionBody.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/EnvironmentAccordionBody.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { DragEventHandler, RefObject, useEffect, useState } from 'react'; import { Alert } from '@mui/material'; import useFeatureStrategyApi from 'hooks/api/actions/useFeatureStrategyApi/useFeatureStrategyApi'; import { formatUnknownError } from 'utils/formatUnknownError'; @@ -27,10 +27,14 @@ const EnvironmentAccordionBody = ({ const { setStrategiesSortOrder } = useFeatureStrategyApi(); const { setToastData, setToastApiError } = useToast(); const { refetchFeature } = useFeature(projectId, featureId); - const [strategies, setStrategies] = useState( featureEnvironment?.strategies || [] ); + const [dragItem, setDragItem] = useState<{ + id: string; + index: number; + height: number; + } | null>(null); const { classes: styles } = useStyles(); useEffect(() => { // Use state to enable drag and drop, but switch to API output when it arrives @@ -41,37 +45,88 @@ const EnvironmentAccordionBody = ({ return null; } - const onDragAndDrop = async ( - from: number, - to: number, - dropped?: boolean - ) => { - if (from !== to && dropped) { - const newStrategies = [...strategies]; - const movedStrategy = newStrategies.splice(from, 1)[0]; - newStrategies.splice(to, 0, movedStrategy); - setStrategies(newStrategies); - try { - await setStrategiesSortOrder( - projectId, - featureId, - featureEnvironment.name, - [...newStrategies].map((strategy, sortOrder) => ({ - id: strategy.id, - sortOrder, - })) - ); - refetchFeature(); - setToastData({ - title: 'Order of strategies updated', - type: 'success', - }); - } catch (error: unknown) { - setToastApiError(formatUnknownError(error)); - } + const onReorder = async (payload: { id: string; sortOrder: number }[]) => { + try { + await setStrategiesSortOrder( + projectId, + featureId, + featureEnvironment.name, + payload + ); + refetchFeature(); + setToastData({ + title: 'Order of strategies updated', + type: 'success', + }); + } catch (error: unknown) { + setToastApiError(formatUnknownError(error)); } }; + const onDragStartRef = + ( + ref: RefObject, + index: number + ): DragEventHandler => + event => { + setDragItem({ + id: strategies[index].id, + index, + height: ref.current?.offsetHeight || 0, + }); + + if (ref?.current) { + event.dataTransfer.effectAllowed = 'move'; + event.dataTransfer.setData('text/html', ref.current.outerHTML); + event.dataTransfer.setDragImage(ref.current, 20, 20); + } + }; + + const onDragOver = + (targetId: string) => + ( + ref: RefObject, + targetIndex: number + ): DragEventHandler => + event => { + if (dragItem === null || ref.current === null) return; + if (dragItem.index === targetIndex || targetId === dragItem.id) + return; + + const { top, bottom } = ref.current.getBoundingClientRect(); + const overTargetTop = event.clientY - top < dragItem.height; + const overTargetBottom = bottom - event.clientY < dragItem.height; + const draggingUp = dragItem.index > targetIndex; + + // prevent oscillating by only reordering if there is sufficient space + if ( + (overTargetTop && draggingUp) || + (overTargetBottom && !draggingUp) + ) { + const newStrategies = [...strategies]; + const movedStrategy = newStrategies.splice( + dragItem.index, + 1 + )[0]; + newStrategies.splice(targetIndex, 0, movedStrategy); + setStrategies(newStrategies); + setDragItem({ + ...dragItem, + index: targetIndex, + }); + } + }; + + const onDragEnd = () => { + setDragItem(null); + onReorder( + strategies.map((strategy, sortOrder) => ({ + id: strategy.id, + sortOrder, + })) + ); + }; + return (
@@ -91,11 +146,14 @@ const EnvironmentAccordionBody = ({ {strategies.map((strategy, index) => ( ))} diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyDraggableItem.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyDraggableItem.tsx index be568736a8..efbb434d97 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyDraggableItem.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyDraggableItem.tsx @@ -1,9 +1,9 @@ import { Box, styled } from '@mui/material'; import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender'; import { StrategySeparator } from 'component/common/StrategySeparator/StrategySeparator'; -import { MoveListItem, useDragItem } from 'hooks/useDragItem'; import { IFeatureEnvironment } from 'interfaces/featureToggle'; import { IFeatureStrategy } from 'interfaces/strategy'; +import { DragEventHandler, RefObject, useRef } from 'react'; import { StrategyItem } from './StrategyItem/StrategyItem'; interface IStrategyDraggableItemProps { @@ -11,7 +11,16 @@ interface IStrategyDraggableItemProps { environmentName: string; index: number; otherEnvironments?: IFeatureEnvironment['name'][]; - onDragAndDrop: MoveListItem; + isDragging?: boolean; + onDragStartRef: ( + ref: RefObject, + index: number + ) => DragEventHandler; + onDragOver: ( + ref: RefObject, + index: number + ) => DragEventHandler; + onDragEnd: () => void; } const StyledIndexLabel = styled('div')(({ theme }) => ({ @@ -31,12 +40,20 @@ export const StrategyDraggableItem = ({ index, environmentName, otherEnvironments, - onDragAndDrop, + isDragging, + onDragStartRef, + onDragOver, + onDragEnd, }: IStrategyDraggableItemProps) => { - const ref = useDragItem(index, onDragAndDrop); + const ref = useRef(null); return ( - + 0} show={} @@ -47,7 +64,8 @@ export const StrategyDraggableItem = ({ strategy={strategy} environmentId={environmentName} otherEnvironments={otherEnvironments} - isDraggable + onDragStart={onDragStartRef(ref, index)} + onDragEnd={onDragEnd} /> diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyItem.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyItem.tsx index e4b0bfcfb9..bcd9fa88a6 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyItem.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/EnvironmentAccordionBody/StrategyDraggableItem/StrategyItem/StrategyItem.tsx @@ -1,3 +1,4 @@ +import { DragEventHandler } from 'react'; import { DragIndicator, Edit } from '@mui/icons-material'; import { styled, useTheme, IconButton } from '@mui/material'; import { Link } from 'react-router-dom'; @@ -22,7 +23,8 @@ import { useStyles } from './StrategyItem.styles'; interface IStrategyItemProps { environmentId: string; strategy: IFeatureStrategy; - isDraggable?: boolean; + onDragStart?: DragEventHandler; + onDragEnd?: DragEventHandler; otherEnvironments?: IFeatureEnvironment['name'][]; } @@ -35,7 +37,8 @@ const DragIcon = styled(IconButton)(({ theme }) => ({ export const StrategyItem = ({ environmentId, strategy, - isDraggable, + onDragStart, + onDragEnd, otherEnvironments, }: IStrategyItemProps) => { const projectId = useRequiredPathParam('projectId'); @@ -55,13 +58,20 @@ export const StrategyItem = ({
( - +