From f40805bf4afd787f8a8226e593f90515c7aea532 Mon Sep 17 00:00:00 2001 From: Jessica McInchak Date: Fri, 26 Apr 2024 16:50:40 +0200 Subject: [PATCH 1/5] i think it works? --- .../src/pages/FlowEditor/index.tsx | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/editor.planx.uk/src/pages/FlowEditor/index.tsx b/editor.planx.uk/src/pages/FlowEditor/index.tsx index d46cbc7508..2939ddb4f1 100644 --- a/editor.planx.uk/src/pages/FlowEditor/index.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/index.tsx @@ -10,7 +10,7 @@ import { formatOps } from "@planx/graph"; import { OT } from "@planx/graph/types"; import DelayedLoadingIndicator from "components/DelayedLoadingIndicator"; import { formatDistanceToNow } from "date-fns"; -import React, { useRef } from "react"; +import React, { useRef, useState } from "react"; import { rootFlowPath } from "../../routes/utils"; import Flow from "./components/Flow"; @@ -111,6 +111,8 @@ export const LastEdited = () => { }; export const EditHistory = () => { + const [focusedOpIndex, setFocusedOpIndex] = useState(); + const [flowId, flow, canUserEditTeam, undoOperation] = useStore((state) => [ state.id, state.flow, @@ -152,6 +154,19 @@ export const EditHistory = () => { // Handle missing operations (e.g. non-production data) if (!loading && !data?.operations) return null; + const handleUndo = (i: number) => { + // Get all operations _since_ & including the selected one + const operationsToUndo = data?.operations?.slice(0, i + 1); + + // Make a flattened list, with the latest operations first + const operationsData: Array = []; + operationsToUndo?.map((op) => operationsData.unshift(op?.data)); + const flattenedOperationsData: OT.Op[] = operationsData?.flat(1); + + // Undo all + undoOperation(flattenedOperationsData); + }; + return ( {loading && !data ? ( @@ -162,7 +177,13 @@ export const EditHistory = () => { key={`container-${op.id}`} marginBottom={2} padding={2} - sx={{ background: (theme) => theme.palette.grey[200] }} + sx={{ + background: (theme) => theme.palette.grey[200], + borderLeft: (theme) => + focusedOpIndex && i <= focusedOpIndex + ? `5px solid ${theme.palette.primary.main}` + : `none`, + }} > { {formatLastEditDate(op.createdAt)} - {i === 0 && ( + { undoOperation(op.data)} + onClick={() => handleUndo(i)} + onMouseEnter={() => setFocusedOpIndex(i)} + onMouseLeave={() => setFocusedOpIndex(undefined)} disabled={!canUserEditTeam} color="primary" > - )} + } {op.data && ( From 64726be75e83eec9f928f5ebf1de0b82f9bed16e Mon Sep 17 00:00:00 2001 From: Jessica McInchak Date: Fri, 26 Apr 2024 17:32:34 +0200 Subject: [PATCH 2/5] styling tweak to correctly highlight i=0 --- editor.planx.uk/src/pages/FlowEditor/index.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/editor.planx.uk/src/pages/FlowEditor/index.tsx b/editor.planx.uk/src/pages/FlowEditor/index.tsx index 2939ddb4f1..2dc22240b2 100644 --- a/editor.planx.uk/src/pages/FlowEditor/index.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/index.tsx @@ -111,7 +111,9 @@ export const LastEdited = () => { }; export const EditHistory = () => { - const [focusedOpIndex, setFocusedOpIndex] = useState(); + const [focusedOpIndex, setFocusedOpIndex] = useState( + undefined, + ); const [flowId, flow, canUserEditTeam, undoOperation] = useStore((state) => [ state.id, @@ -180,7 +182,7 @@ export const EditHistory = () => { sx={{ background: (theme) => theme.palette.grey[200], borderLeft: (theme) => - focusedOpIndex && i <= focusedOpIndex + focusedOpIndex !== undefined && i <= focusedOpIndex ? `5px solid ${theme.palette.primary.main}` : `none`, }} From 542a6ec54d0b7456362bbf3d6b56fdcf9e50e6f1 Mon Sep 17 00:00:00 2001 From: Jessica McInchak Date: Tue, 30 Apr 2024 13:43:03 +0200 Subject: [PATCH 3/5] style tweak --- editor.planx.uk/src/pages/FlowEditor/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/editor.planx.uk/src/pages/FlowEditor/index.tsx b/editor.planx.uk/src/pages/FlowEditor/index.tsx index 2dc22240b2..4a05b8e64e 100644 --- a/editor.planx.uk/src/pages/FlowEditor/index.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/index.tsx @@ -184,7 +184,7 @@ export const EditHistory = () => { borderLeft: (theme) => focusedOpIndex !== undefined && i <= focusedOpIndex ? `5px solid ${theme.palette.primary.main}` - : `none`, + : `5px solid ${theme.palette.grey[200]}`, // looks like 'none', but avoids content shifting on focus }} > Date: Tue, 30 Apr 2024 14:12:07 +0200 Subject: [PATCH 4/5] try @mui/lab Timeline --- editor.planx.uk/package.json | 1 + editor.planx.uk/pnpm-lock.yaml | 91 +++++++++++- .../src/pages/FlowEditor/index.tsx | 135 +++++++++++------- 3 files changed, 171 insertions(+), 56 deletions(-) diff --git a/editor.planx.uk/package.json b/editor.planx.uk/package.json index e70d64ea7e..eabf541348 100644 --- a/editor.planx.uk/package.json +++ b/editor.planx.uk/package.json @@ -9,6 +9,7 @@ "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@mui/icons-material": "^5.15.2", + "@mui/lab": "5.0.0-alpha.170", "@mui/material": "^5.15.2", "@mui/utils": "^5.15.2", "@opensystemslab/map": "^0.8.1", diff --git a/editor.planx.uk/pnpm-lock.yaml b/editor.planx.uk/pnpm-lock.yaml index e04ca25390..ed7527aa8f 100644 --- a/editor.planx.uk/pnpm-lock.yaml +++ b/editor.planx.uk/pnpm-lock.yaml @@ -28,6 +28,9 @@ dependencies: '@mui/icons-material': specifier: ^5.15.2 version: 5.15.2(@mui/material@5.15.2)(@types/react@18.2.45)(react@18.2.0) + '@mui/lab': + specifier: 5.0.0-alpha.170 + version: 5.0.0-alpha.170(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@mui/material@5.15.2)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) '@mui/material': specifier: ^5.15.2 version: 5.15.2(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) @@ -4788,7 +4791,30 @@ packages: '@babel/runtime': 7.24.1 '@floating-ui/react-dom': 2.0.8(react-dom@18.2.0)(react@18.2.0) '@mui/types': 7.2.14(@types/react@18.2.45) - '@mui/utils': 5.15.2(@types/react@18.2.45)(react@18.2.0) + '@mui/utils': 5.15.14(@types/react@18.2.45)(react@18.2.0) + '@popperjs/core': 2.11.8 + '@types/react': 18.2.45 + clsx: 2.1.0 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@mui/base@5.0.0-beta.40(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.1 + '@floating-ui/react-dom': 2.0.8(react-dom@18.2.0)(react@18.2.0) + '@mui/types': 7.2.14(@types/react@18.2.45) + '@mui/utils': 5.15.14(@types/react@18.2.45)(react@18.2.0) '@popperjs/core': 2.11.8 '@types/react': 18.2.45 clsx: 2.1.0 @@ -4818,6 +4844,39 @@ packages: react: 18.2.0 dev: false + /@mui/lab@5.0.0-alpha.170(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@mui/material@5.15.2)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-0bDVECGmrNjd3+bLdcLiwYZ0O4HP5j5WSQm5DV6iA/Z9kr8O6AnvZ1bv9ImQbbX7Gj3pX4o43EKwCutj3EQxQg==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@mui/material': '>=5.15.0' + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.1 + '@emotion/react': 11.11.1(@types/react@18.2.45)(react@18.2.0) + '@emotion/styled': 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.45)(react@18.2.0) + '@mui/base': 5.0.0-beta.40(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@mui/material': 5.15.2(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0) + '@mui/system': 5.15.15(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.45)(react@18.2.0) + '@mui/types': 7.2.14(@types/react@18.2.45) + '@mui/utils': 5.15.14(@types/react@18.2.45)(react@18.2.0) + '@types/react': 18.2.45 + clsx: 2.1.0 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@mui/material@5.15.2(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.45)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-JnoIrpNmEHG5uC1IyEdgsnDiaiuCZnUIh7f9oeAr87AvBmNiEJPbo7XrD7kBTFWwp+b97rQ12QdSs9CLhT2n/A==} engines: {node: '>=12.0.0'} @@ -5011,6 +5070,36 @@ packages: react: 18.2.0 dev: false + /@mui/system@5.15.15(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.45)(react@18.2.0): + resolution: {integrity: sha512-aulox6N1dnu5PABsfxVGOZffDVmlxPOVgj56HrUnJE8MCSh8lOvvkd47cebIVQQYAjpwieXQXiDPj5pwM40jTQ==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.1 + '@emotion/react': 11.11.1(@types/react@18.2.45)(react@18.2.0) + '@emotion/styled': 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.45)(react@18.2.0) + '@mui/private-theming': 5.15.14(@types/react@18.2.45)(react@18.2.0) + '@mui/styled-engine': 5.15.14(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0) + '@mui/types': 7.2.14(@types/react@18.2.45) + '@mui/utils': 5.15.14(@types/react@18.2.45)(react@18.2.0) + '@types/react': 18.2.45 + clsx: 2.1.0 + csstype: 3.1.3 + prop-types: 15.8.1 + react: 18.2.0 + dev: false + /@mui/types@7.2.14(@types/react@18.2.45): resolution: {integrity: sha512-MZsBZ4q4HfzBsywtXgM1Ksj6HDThtiwmOKUXH1pKYISI9gAVXCNHNpo7TlGoGrBaYWZTdNoirIN7JsQcQUjmQQ==} peerDependencies: diff --git a/editor.planx.uk/src/pages/FlowEditor/index.tsx b/editor.planx.uk/src/pages/FlowEditor/index.tsx index 4a05b8e64e..39a5fc7bd2 100644 --- a/editor.planx.uk/src/pages/FlowEditor/index.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/index.tsx @@ -3,6 +3,12 @@ import "./floweditor.scss"; import { gql, useSubscription } from "@apollo/client"; import UndoOutlined from "@mui/icons-material/UndoOutlined"; +import Timeline from "@mui/lab/Timeline"; +import TimelineConnector from "@mui/lab/TimelineConnector"; +import TimelineContent from "@mui/lab/TimelineContent"; +import TimelineDot from "@mui/lab/TimelineDot"; +import TimelineItem, { timelineItemClasses } from "@mui/lab/TimelineItem"; +import TimelineSeparator from "@mui/lab/TimelineSeparator"; import Box from "@mui/material/Box"; import IconButton from "@mui/material/IconButton"; import Typography from "@mui/material/Typography"; @@ -174,63 +180,82 @@ export const EditHistory = () => { {loading && !data ? ( ) : ( - data?.operations?.map((op: Operation, i: number) => ( - theme.palette.grey[200], - borderLeft: (theme) => - focusedOpIndex !== undefined && i <= focusedOpIndex - ? `5px solid ${theme.palette.primary.main}` - : `5px solid ${theme.palette.grey[200]}`, // looks like 'none', but avoids content shifting on focus - }} - > - - - - {`${ - op.actor - ? `Edited by ${op.actor?.firstName} ${op.actor?.lastName}` - : `Created flow` - }`} - - - {formatLastEditDate(op.createdAt)} - - - { - handleUndo(i)} - onMouseEnter={() => setFocusedOpIndex(i)} - onMouseLeave={() => setFocusedOpIndex(undefined)} - disabled={!canUserEditTeam} - color="primary" + + {data?.operations?.map((op: Operation, i: number) => ( + + + + {i < data.operations.length - 1 && ( + + focusedOpIndex !== undefined && i <= focusedOpIndex + ? theme.palette.primary.main + : undefined, + }} + /> + )} + + + - - - } - - {op.data && ( - - {[...new Set(formatOps(flow, op.data))].map( - (formattedOp, i) => ( -
  • {formattedOp}
  • - ), + + + {`${ + op.actor + ? `Edited by ${op.actor?.firstName} ${op.actor?.lastName}` + : `Created flow` + }`} + + + {formatLastEditDate(op.createdAt)} + + + { + handleUndo(i)} + onMouseEnter={() => setFocusedOpIndex(i)} + onMouseLeave={() => setFocusedOpIndex(undefined)} + disabled={!canUserEditTeam} + color="primary" + > + + + } +
    + {op.data && ( + + {[...new Set(formatOps(flow, op.data))].map( + (formattedOp, i) => ( +
  • {formattedOp}
  • + ), + )} +
    )} -
    - )} - - )) + + + ))} + )} ); From 0a118ab047ea5b6e73b377799e6bb16efb731ada Mon Sep 17 00:00:00 2001 From: Jessica McInchak Date: Wed, 1 May 2024 09:43:56 +0200 Subject: [PATCH 5/5] invert colors, don't show restore on first item per ian's suggestion --- .../src/pages/FlowEditor/index.tsx | 51 +++++++++++-------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/editor.planx.uk/src/pages/FlowEditor/index.tsx b/editor.planx.uk/src/pages/FlowEditor/index.tsx index 39a5fc7bd2..2469932001 100644 --- a/editor.planx.uk/src/pages/FlowEditor/index.tsx +++ b/editor.planx.uk/src/pages/FlowEditor/index.tsx @@ -2,7 +2,7 @@ import "./components/Settings"; import "./floweditor.scss"; import { gql, useSubscription } from "@apollo/client"; -import UndoOutlined from "@mui/icons-material/UndoOutlined"; +import RestoreOutlined from "@mui/icons-material/RestoreOutlined"; import Timeline from "@mui/lab/Timeline"; import TimelineConnector from "@mui/lab/TimelineConnector"; import TimelineContent from "@mui/lab/TimelineContent"; @@ -175,6 +175,10 @@ export const EditHistory = () => { undoOperation(flattenedOperationsData); }; + const inFocus = (i: number): boolean => { + return focusedOpIndex !== undefined && i < focusedOpIndex; + }; + return ( {loading && !data ? ( @@ -191,20 +195,12 @@ export const EditHistory = () => { {data?.operations?.map((op: Operation, i: number) => ( - + {i < data.operations.length - 1 && ( - focusedOpIndex !== undefined && i <= focusedOpIndex - ? theme.palette.primary.main - : undefined, + inFocus(i) ? undefined : theme.palette.primary.main, }} /> )} @@ -218,33 +214,46 @@ export const EditHistory = () => { }} > - + {`${ op.actor ? `Edited by ${op.actor?.firstName} ${op.actor?.lastName}` : `Created flow` }`} - + {formatLastEditDate(op.createdAt)} - { + {i > 0 && op.actor && ( handleUndo(i)} onMouseEnter={() => setFocusedOpIndex(i)} onMouseLeave={() => setFocusedOpIndex(undefined)} - disabled={!canUserEditTeam} - color="primary" > - + - } + )} {op.data && ( - + {[...new Set(formatOps(flow, op.data))].map( (formattedOp, i) => (
  • {formattedOp}