diff --git a/components/pageblocks/ProblemRowReact.js b/components/pageblocks/ProblemRowReact.js index f31d99e..0d28a22 100644 --- a/components/pageblocks/ProblemRowReact.js +++ b/components/pageblocks/ProblemRowReact.js @@ -15,6 +15,8 @@ import { TextField } from '@mui/material'; import { Button, Stack, Box } from "@mui/material"; import { Folder as FolderIcon } from '@mui/icons-material'; import { Download as DownloadIcon } from '@mui/icons-material'; +import { DragIndicator as DragIndicatorIcon } from '@mui/icons-material'; +import { IconButton } from '@mui/material'; import PopoverTooltipClick from '../widgets/PopoverTooltipClick'; import ProblemFilterMenu from '../widgets/ProblemFilterMenu'; @@ -38,7 +40,7 @@ const COMPLEXITY_CLASS_ORDER = ["P", "NPComplete", "NPHard", "NPIntermediate", " /** * Creates an accordion that has a nested autocomplete search bar, as well as an editable problem instance textbox */ -export default function ProblemRowReact({ url, problemName, setProblemName, problemNameMap, setProblemInstance }) { +export default function ProblemRowReact({ url, problemName, setProblemName, problemNameMap, setProblemInstance, dragHandleProps }) { const problemInfo = useProblemInfo(url, problemName); const { problemIndex, reductionGraph } = useProblemIndex(url); const { @@ -166,28 +168,28 @@ export default function ProblemRowReact({ url, problemName, setProblemName, prob const tip = problemName ? { - header: problemInfo.problemName ?? "", - formalDef: problemInfo.formalDefinition ?? "", - // It makes description clean - info: problemInfo.problemDefinition ?? "", - classification: [ - { label: "Complexity class", value: problemInfo.complexityClass || "Unclassified" }, - ], - // Source shown on its own line here - source: - problemInfo.source || - (Array.isArray(problemInfo.citations) ? problemInfo.citations.join("; ") : "") || - "", - // Contributors - credit: - Array.isArray(problemInfo.contributors) && problemInfo.contributors.length - ? problemInfo.contributors.join(", ") - : "", - // Popover builds Wikipedia URL - componentLink: problemInfo.problemLink || "", - sourceLink: problemInfo.sourceLink || "", - isMathDef: true, // only this file adds the flag - } + header: problemInfo.problemName ?? "", + formalDef: problemInfo.formalDefinition ?? "", + // It makes description clean + info: problemInfo.problemDefinition ?? "", + classification: [ + { label: "Complexity class", value: problemInfo.complexityClass || "Unclassified" }, + ], + // Source shown on its own line here + source: + problemInfo.source || + (Array.isArray(problemInfo.citations) ? problemInfo.citations.join("; ") : "") || + "", + // Contributors + credit: + Array.isArray(problemInfo.contributors) && problemInfo.contributors.length + ? problemInfo.contributors.join(", ") + : "", + // Popover builds Wikipedia URL + componentLink: problemInfo.problemLink || "", + sourceLink: problemInfo.sourceLink || "", + isMathDef: true, // only this file adds the flag + } : TOOLTIP; return ( @@ -217,7 +219,24 @@ export default function ProblemRowReact({ url, problemName, setProblemName, prob setSelectedSolverComplexityBuckets={setSelectedSolverComplexityBuckets} clearFilters={clearFilters} />{" "} - + + {dragHandleProps && ( + + + + )} diff --git a/components/pageblocks/ReduceToRowReact.js b/components/pageblocks/ReduceToRowReact.js index 688db6f..fda2c1e 100644 --- a/components/pageblocks/ReduceToRowReact.js +++ b/components/pageblocks/ReduceToRowReact.js @@ -15,6 +15,8 @@ import 'bootstrap/dist/css/bootstrap.min.css' import { Card } from 'react-bootstrap' import { Button } from '@mui/material' import { Download as DownloadIcon } from '@mui/icons-material'; +import { DragIndicator as DragIndicatorIcon } from '@mui/icons-material'; +import { IconButton } from '@mui/material'; import { requestReducedInstanceFromPath } from '../redux' import { useProblemInfo, useReducerInfo } from '../hooks/ProblemProvider' @@ -56,6 +58,7 @@ export default function ReduceToRowReact({ setChosenReductionType, reducedInstance, setReducedInstance, + dragHandleProps, }) { const reduceToInfo = useProblemInfo(url, chosenReduceTo); const reducerInfo = useReducerInfo(url, chosenReductionType); @@ -176,6 +179,23 @@ export default function ReduceToRowReact({ : TOOLTIP2 } > + {dragHandleProps && ( + + + + )} diff --git a/components/pageblocks/SolveRowReact.js b/components/pageblocks/SolveRowReact.js index 2fb5f6c..c115a3e 100644 --- a/components/pageblocks/SolveRowReact.js +++ b/components/pageblocks/SolveRowReact.js @@ -13,6 +13,8 @@ import { useContext } from "react"; import "bootstrap/dist/css/bootstrap.min.css"; import { Button } from "@mui/material"; import { Download as DownloadIcon } from '@mui/icons-material'; +import { DragIndicator as DragIndicatorIcon } from '@mui/icons-material'; +import { IconButton } from '@mui/material'; import { requestSolvedInstance } from "../redux"; import PopoverTooltipClick from "../widgets/PopoverTooltipClick"; @@ -45,6 +47,7 @@ export default function SolveRowReact({ solverNameMap, problemNameMap, chosenReduceTo, + dragHandleProps, }) { const solverInfo = useSolverInfo(url, chosenSolver); @@ -114,6 +117,23 @@ export default function SolveRowReact({ }} />{" "} + {dragHandleProps && ( + + + + )} diff --git a/components/pageblocks/VerifyRowReact.js b/components/pageblocks/VerifyRowReact.js index a15fc5a..8352c6c 100644 --- a/components/pageblocks/VerifyRowReact.js +++ b/components/pageblocks/VerifyRowReact.js @@ -13,6 +13,8 @@ import { useContext, useEffect, useState } from 'react'; import 'bootstrap/dist/css/bootstrap.min.css' import { FormControl } from 'react-bootstrap' import { Button } from '@mui/material' +import { DragIndicator as DragIndicatorIcon } from '@mui/icons-material'; +import { IconButton } from '@mui/material'; import { requestVerifiedInstance, requestIsCertificateValid } from '../redux'; import PopoverTooltipClick from '../widgets/PopoverTooltipClick'; @@ -34,6 +36,7 @@ export default function VerifyRowReact({ setChosenVerifier, verifierOptions, verifierNameMap, + dragHandleProps, }) { const [certificate, setCertificate] = useState(""); const [verifyResult, setVerifyResult] = useState(""); @@ -98,6 +101,23 @@ export default function VerifyRowReact({ : TOOLTIP } > + {dragHandleProps && ( + + + + )} diff --git a/components/pageblocks/VisualizeRowReact.js b/components/pageblocks/VisualizeRowReact.js index fccd002..8137e92 100644 --- a/components/pageblocks/VisualizeRowReact.js +++ b/components/pageblocks/VisualizeRowReact.js @@ -22,6 +22,7 @@ import { FastForward, } from "@mui/icons-material"; import RefreshIcon from "@mui/icons-material/Refresh"; +import { DragIndicator as DragIndicatorIcon } from '@mui/icons-material'; import Link from "next/link"; // <-- IMPORTANT for Quantum button import PopoverTooltipClick from "../widgets/PopoverTooltipClick"; @@ -71,6 +72,7 @@ export default function VisualizeRowReact({ VisualizationOptions, defaultVisualizationMap, visualizationTypeMap, + dragHandleProps, }) { const visualizationInfo = useVisualizationInfo(url, chosenVisualization); @@ -371,6 +373,23 @@ export default function VisualizeRowReact({ /> + {dragHandleProps && ( + + + + )} diff --git a/package-lock.json b/package-lock.json index 2892fa0..ac417f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,9 @@ "version": "0.1.0", "hasInstallScript": true, "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", "@fontsource/roboto": "^5.3.0", @@ -601,6 +604,59 @@ "node": ">=18" } }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.1.1.tgz", + "integrity": "sha512-2P+YgaXF+gRsIihwwY1gCsQSYnu9Zyj2py8kY5fFvUM1qm2WA2u639R6YNVfU4GWr+ZM5mqEsfHZZLoRONbemw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", + "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", + "license": "MIT", + "dependencies": { + "@dnd-kit/accessibility": "^3.1.1", + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/sortable": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-10.0.0.tgz", + "integrity": "sha512-+xqhmIIzvAYMGfBYYnbKuNicfSsk4RksY2XdmJhT+HAC01nix6fHCztU68jooFiMUB01Ky3F0FyOvhG/BZrWkg==", + "license": "MIT", + "dependencies": { + "@dnd-kit/utilities": "^3.2.2", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.3.0", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.2.tgz", + "integrity": "sha512-+MKAJEOfaBe5SmV6t34p80MMKhjvUz0vRrvVJbPT0WElzaOJ/1xs+D+KDv+tD/NE5ujfrChEcshd4fLn0wpiqg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/@emnapi/core": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", diff --git a/package.json b/package.json index 2bb01d4..2766403 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,9 @@ "postinstall": "next telemetry disable" }, "dependencies": { + "@dnd-kit/core": "^6.3.1", + "@dnd-kit/sortable": "^10.0.0", + "@dnd-kit/utilities": "^3.2.2", "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", "@fontsource/roboto": "^5.3.0", diff --git a/pages/index.js b/pages/index.js index 7bcadd9..da5190f 100644 --- a/pages/index.js +++ b/pages/index.js @@ -26,12 +26,28 @@ import { } from "@mui/material"; import { Container } from "react-bootstrap"; import { useProblemProvider } from "../components/hooks/ProblemProvider"; -import { useEffect, memo } from "react"; +import { useEffect, memo, useState } from "react"; // CHANGED: added useState for row order import { useUnload } from "../components/eventHandlers/handleUnload"; import ShareButton from "../components/widgets/ShareButton"; import TourLauncher from "../components/tour/TourLauncher"; import { useHandleParameters } from "../components/eventHandlers/handleParameters"; +import { + DndContext, + closestCenter, + PointerSensor, + TouchSensor, + useSensor, + useSensors, +} from "@dnd-kit/core"; +import { + SortableContext, + verticalListSortingStrategy, + useSortable, + arrayMove, +} from "@dnd-kit/sortable"; +import { CSS } from "@dnd-kit/utilities"; + const SHOW_QUANTUM_VIS = false; //Flag to show a quantum circuit visualizer (sandbox feature) const ProblemRowMemo = memo(ProblemRowReact); const ReduceToRowMemo = memo(ReduceToRowReact); @@ -40,6 +56,48 @@ const SolveRowMemo = memo(SolveRowReact); const VerifyRowMemo = memo(VerifyRowReact); const reduxBaseUrl = '/api/redux/'; + +function SortableRow({ id, children }) { + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + isDragging, + } = useSortable({ id }); + + const style = { + transform: CSS.Transform.toString(transform), + transition, + position: "relative", + opacity: isDragging ? 0.6 : 1, + }; + + const tourIds = { + reduce: "reduce-row", + solve: "solve-row", + verify: "verify-row", + }; + + const tourId = tourIds[id]; + + const childrenWithProps = React.cloneElement(children, { + dragHandleProps: { attributes, listeners }, + }); + + return ( +
+ {childrenWithProps} +
+ ); +} + /** * Generates the actual page contents * @@ -79,7 +137,55 @@ function MainPageContent() { const { problem, solver, verifier, reducer, visualization } = useProblemProvider(reduxBaseUrl); - //useUnload(problem, solver, verifier, reducer); + const [rowOrder, setRowOrder] = useState([ + "problem", + "reduce", + "visualize", + "solve", + "verify", + ]); + + // PointerSensor covers mouse; TouchSensor adds mobile/tablet support + const sensors = useSensors( + useSensor(PointerSensor), + useSensor(TouchSensor) + ); + + const rowMap = { + problem: , + reduce: , + visualize: ( + + ), + solve: ( + + ), + verify: , + }; + + // Replaces the old handleDragStart/handleDragOver/handleDrop trio. + function handleDragEnd(event) { + const { active, over } = event; + if (over && active.id !== over.id) { + setRowOrder((items) => { + const oldIndex = items.indexOf(active.id); + const newIndex = items.indexOf(over.id); + return arrayMove(items, oldIndex, newIndex); + }); + } + } return ( <> @@ -98,37 +204,22 @@ function MainPageContent() { /> - -
- -
-
- -
- -
- -
- -
- -
- -
- -
+ + + {rowOrder.map((key) => ( + + {rowMap[key]} + + ))} + + @@ -162,4 +253,4 @@ export default function MainPage() { ); -} +} \ No newline at end of file