diff --git a/Tools/Constants.js b/Tools/Constants.js index 6cdc9a76..98aa87e6 100644 --- a/Tools/Constants.js +++ b/Tools/Constants.js @@ -1,16 +1,12 @@ const Constants = { - - ProblemTypes: { - Sat3: "SAT3", - Clique: "CLIQUE", - GraphColoring: "GRAPHCOLORING", - VertexCover: "VERTEXCOVER", - Arcset: "ARCSET", - Knapsack: "KNAPSACK" - } - - - -} - -export default Constants \ No newline at end of file + ProblemTypes: { + Sat3: "SAT3", + Clique: "CLIQUE", + GraphColoring: "GRAPHCOLORING", + VertexCover: "VERTEXCOVER", + Arcset: "ARCSET", + Knapsack: "KNAPSACK", + }, +}; + +export default Constants; diff --git a/Tools/ProblemInstanceParser.js b/Tools/ProblemInstanceParser.js index fae5d93c..d2184a68 100644 --- a/Tools/ProblemInstanceParser.js +++ b/Tools/ProblemInstanceParser.js @@ -3,69 +3,83 @@ import Constants from "./Constants"; class ProblemInstanceParser { - constructor() { - - } - - //Breaks parsing logic up by problem type, calls function based on type. - parse(problemType, problemInstance) { - let parsedOutput = {} + constructor() {} - if (problemType === Constants.ProblemTypes.Clique || problemType === Constants.ProblemTypes.VertexCover || problemType === Constants.ProblemTypes.GraphColoring) { - parsedOutput = this.parseUndirectedGraph(problemInstance); - } - else if (problemType === Constants.ProblemTypes.Sat3) { - parsedOutput = this.parseSat3(problemInstance); - } - - else if (problemType === Constants.ProblemTypes.Arcset) { - parsedOutput = this.parseDirectedGraph(problemInstance); - } - else { - parsedOutput = { - test: true, - input: problemInstance, - regex: "There is no regex string for this problem, parsing is likely not enabled", - type: problemType, - exampleStr: "There is no example string for this problem, click on the problem info box for more problem information" - - } - } + //Breaks parsing logic up by problem type, calls function based on type. + parse(problemType, problemInstance) { + let parsedOutput = {}; - return parsedOutput + if ( + problemType === Constants.ProblemTypes.Clique || + problemType === Constants.ProblemTypes.VertexCover || + problemType === Constants.ProblemTypes.GraphColoring + ) { + parsedOutput = this.parseUndirectedGraph(problemInstance); + } else if (problemType === Constants.ProblemTypes.Sat3) { + parsedOutput = this.parseSat3(problemInstance); + } else if (problemType === Constants.ProblemTypes.Arcset) { + parsedOutput = this.parseDirectedGraph(problemInstance); + } else { + parsedOutput = { + test: true, + input: problemInstance, + regex: "There is no regex string for this problem, parsing is likely not enabled", + type: problemType, + exampleStr: + "There is no example string for this problem, click on the problem info box for more problem information", + }; } - - parseSat3(instance) { + return parsedOutput; + } + parseSat3(instance) { + const type = "Sat3BooleanExp"; + // const sat3Format = /^(\((!)*\w+\|(!)*\w+\|(!)*\w+\))((&)(\((!)*\w+\|(!)*\w+\|(!)*\w+\)))*$/g + const sat3Format = + /^(\((!)*[^\W_]+\|(!)*[^\W_]+\|(!)*[^\W_]+\))((&)(\((!)*[^\W_]+\|(!)*[^\W_]+\|(!)*[^\W_]+\)))*$/g; - const type = "Sat3BooleanExp" - // const sat3Format = /^(\((!)*\w+\|(!)*\w+\|(!)*\w+\))((&)(\((!)*\w+\|(!)*\w+\|(!)*\w+\)))*$/g - const sat3Format = /^(\((!)*[^\W_]+\|(!)*[^\W_]+\|(!)*[^\W_]+\))((&)(\((!)*[^\W_]+\|(!)*[^\W_]+\|(!)*[^\W_]+\)))*$/g + //[^\W_] + const satRegex = new RegExp(sat3Format); + const bool = satRegex.test(instance); + return { + test: bool, + input: instance, + regex: sat3Format, + type: type, + exampleStr: "(x1|!x2|x3)&(!x1|x3|x1)&(x2|!x3|x1)", + }; + } - //[^\W_] - const satRegex = new RegExp(sat3Format) - const bool = satRegex.test(instance); - return {test:bool,input:instance,regex:sat3Format,type:type,exampleStr:"(x1|!x2|x3)&(!x1|x3|x1)&(x2|!x3|x1)"} - } - - - parseUndirectedGraph(instance) { - const type = "UndirectedGraph" - const undirectedGraphFormat = /\(\({([\w!]+)(,([\w!]+))*},{\{([\w!]+),([\w!]+)\}(,\{([\w!]+),([\w!]+)\})*}\),\d+\)$/g; //checks for undirected graph format, implicitly regex - const graphReg = new RegExp(undirectedGraphFormat); - const bool = graphReg.test(instance) - return {test:bool,input:instance,regex:undirectedGraphFormat,type:type,exampleStr:"(({a,b,c},{{a,b},{b,c}}),2)"} - } + parseUndirectedGraph(instance) { + const type = "UndirectedGraph"; + const undirectedGraphFormat = + /\(\({([\w!]+)(,([\w!]+))*},{\{([\w!]+),([\w!]+)\}(,\{([\w!]+),([\w!]+)\})*}\),\d+\)$/g; //checks for undirected graph format, implicitly regex + const graphReg = new RegExp(undirectedGraphFormat); + const bool = graphReg.test(instance); + return { + test: bool, + input: instance, + regex: undirectedGraphFormat, + type: type, + exampleStr: "(({a,b,c},{{a,b},{b,c}}),2)", + }; + } - parseDirectedGraph(instance) { - const type = "DirectedGraph" - const directedGraphFormat = /\(\({(([\w!]+)+(,([\w!]+))*)},{(\(([\w!]+),([\w!]+)\)(,\(([\w!]+),([\w!]+)\))*)*}\),\d+\)$/g - const graphReg = new RegExp(directedGraphFormat); - const bool = graphReg.test(instance); - return {test:bool,input:instance,regex:directedGraphFormat,type:type,exampleStr:"(({a,b,c},{(a,b),(b,c),(c,a)}),3)"} - } - + parseDirectedGraph(instance) { + const type = "DirectedGraph"; + const directedGraphFormat = + /\(\({(([\w!]+)(,([\w!]+))*)},{(\(([\w!]+),([\w!]+)\)(,\(([\w!]+),([\w!]+)\))*)*}\),\d+\)$/g; + const graphReg = new RegExp(directedGraphFormat); + const bool = graphReg.test(instance); + return { + test: bool, + input: instance, + regex: directedGraphFormat, + type: type, + exampleStr: "(({a,b,c},{(a,b),(b,c),(c,a)}),3)", + }; + } } -export default ProblemInstanceParser \ No newline at end of file +export default ProblemInstanceParser; diff --git a/biome.json b/biome.json index d5eabff0..d6a160dd 100644 --- a/biome.json +++ b/biome.json @@ -1,5 +1,5 @@ { - "$schema": "https://biomejs.dev/schemas/2.5.3/schema.json", + "$schema": "https://biomejs.dev/schemas/2.5.8/schema.json", "vcs": { "enabled": true, "clientKind": "git", @@ -7,14 +7,7 @@ }, "files": { "ignoreUnknown": false, - "includes": [ - "**", - "!public/**", - "!package-lock.json", - "!.next/**", - "!out/**", - "!build/**" - ] + "includes": ["**", "!public/**", "!package-lock.json", "!.next/**", "!out/**", "!build/**"] }, "formatter": { "enabled": true, diff --git a/components/ContributorCard.js b/components/ContributorCard.js index 315995fb..2361f53c 100644 --- a/components/ContributorCard.js +++ b/components/ContributorCard.js @@ -2,14 +2,16 @@ import React from "react"; export default function ContributorCard({ name, role, contributions, github, image }) { return ( -
+
diff --git a/components/Quantum/QuantumCircuitVisualizer.js b/components/Quantum/QuantumCircuitVisualizer.js index eb607d30..8a0c491e 100644 --- a/components/Quantum/QuantumCircuitVisualizer.js +++ b/components/Quantum/QuantumCircuitVisualizer.js @@ -1,6 +1,7 @@ // components/Quantum/QuantumCircuitVisualizer.jsx -import React, { useEffect, useRef, useState } from "react"; + import * as d3 from "d3"; +import React, { useEffect, useRef, useState } from "react"; import { getMaxTime } from "./circuitUtils"; export default function QuantumCircuitVisualizer({ circuit }) { @@ -68,7 +69,7 @@ export default function QuantumCircuitVisualizer({ circuit }) { (g) => g.qubit !== prev.qubits - 1 && g.control !== prev.qubits - 1 && - g.target !== prev.qubits - 1 + g.target !== prev.qubits - 1, ), }; }); @@ -128,7 +129,11 @@ export default function QuantumCircuitVisualizer({ circuit }) { [...Array(c.qubits).keys()].forEach((q) => { const y = 60 + q * spacingY; - svg.append("text").attr("x", 20).attr("y", y + 5).text(`q${q}`); + svg + .append("text") + .attr("x", 20) + .attr("y", y + 5) + .text(`q${q}`); svg .append("line") @@ -210,9 +215,7 @@ export default function QuantumCircuitVisualizer({ circuit }) {

Quantum Circuit Visualizer

- + Time:{" "} - setGateTime(e.target.value)} - /> + setGateTime(e.target.value)} /> {gateType !== "cnot" ? ( @@ -288,4 +287,4 @@ export default function QuantumCircuitVisualizer({ circuit }) {
); -} \ No newline at end of file +} diff --git a/components/Quantum/circuitUtils.js b/components/Quantum/circuitUtils.js index f0831e48..6a173fd5 100644 --- a/components/Quantum/circuitUtils.js +++ b/components/Quantum/circuitUtils.js @@ -2,4 +2,4 @@ export function getMaxTime(circuit) { if (!circuit?.gates || circuit.gates.length === 0) return 0; return Math.max(...circuit.gates.map((g) => g.t)); -} \ No newline at end of file +} diff --git a/components/Visualization/Graphvisualization.js b/components/Visualization/Graphvisualization.js index af3ae3e0..311178e5 100644 --- a/components/Visualization/Graphvisualization.js +++ b/components/Visualization/Graphvisualization.js @@ -1,22 +1,17 @@ /** * GraphVisualization.js - * + * * This component generates an example Graph component using the Graphviz library. - * + * * @author Daniel Igbokwe */ - - import dynamic from "next/dynamic"; const Graphviz = dynamic(() => import("./GraphvizWrapper"), { ssr: false }); function Page(props) { - return ( - - - ); + return ; } export default Page; diff --git a/components/Visualization/GraphvizWrapper.js b/components/Visualization/GraphvizWrapper.js index 0c96debb..91668e38 100644 --- a/components/Visualization/GraphvizWrapper.js +++ b/components/Visualization/GraphvizWrapper.js @@ -1,5 +1,5 @@ -import { useEffect, useRef } from "react"; import { graphviz } from "d3-graphviz"; +import { useEffect, useRef } from "react"; export default function GraphvizWrapper({ dot, options = {} }) { const ref = useRef(null); diff --git a/components/Visualization/QuantumCircuitVis.js b/components/Visualization/QuantumCircuitVis.js index 794fe074..51983c8a 100644 --- a/components/Visualization/QuantumCircuitVis.js +++ b/components/Visualization/QuantumCircuitVis.js @@ -1,11 +1,8 @@ -import React, { useEffect, useMemo, useRef, useState } from "react"; import Script from "next/script"; +import React, { useEffect, useMemo, useRef, useState } from "react"; import { openqasmToQText } from "./openqasmToQText"; -const QuantumCircuitVis = ({ - problemData, - useSolutionCircuit = false -}) => { +const QuantumCircuitVis = ({ problemData, useSolutionCircuit = false }) => { const [qReady, setQReady] = useState(false); const containerRef = useRef(null); @@ -20,7 +17,7 @@ const QuantumCircuitVis = ({ (useSolution && (f?.solution?.openQasm || f?.solution?.qasm)) || f?.openQasm || f?.qasm || - f?.openqasm + f?.openqasm, ); if (candidate) data = candidate; } @@ -58,11 +55,7 @@ const QuantumCircuitVis = ({ // At this point, data is either an object or undefined const fromSolution = data?.solution?.openQasm || data?.solution?.qasm; const fromMain = - data?.openQasm || - data?.qasm || - data?.openqasm || - data?.circuitQasm || - data?.qasmText; + data?.openQasm || data?.qasm || data?.openqasm || data?.circuitQasm || data?.qasmText; if (useSolution && fromSolution) return fromSolution; if (fromMain) return fromMain; @@ -86,12 +79,7 @@ const QuantumCircuitVis = ({ const getField = (obj) => { if (!obj || typeof obj !== "object") return ""; return ( - obj.solution ?? - obj.solutionText ?? - obj.solution_string ?? - obj.answer ?? - obj.result ?? - "" + obj.solution ?? obj.solutionText ?? obj.solution_string ?? obj.answer ?? obj.result ?? "" ); }; @@ -209,8 +197,7 @@ const QuantumCircuitVis = ({ } } catch (err) { console.error("Error rendering Q.js circuit:", err); - containerRef.current.textContent = - "Error rendering circuit: " + err.message; + containerRef.current.textContent = "Error rendering circuit: " + err.message; } }, [qReady, qText, openQasm]); @@ -264,11 +251,7 @@ const QuantumCircuitVis = ({

Quantum Circuit (Q.js format)

You can still copy this text into{" "} - + the Q.js playground , but the live circuit is rendered below. @@ -297,9 +280,7 @@ const QuantumCircuitVis = ({ overflowX: "auto", }} /> -

- Solution: {solutionText || "Not provided"} -

+

Solution: {solutionText || "Not provided"}

); }; diff --git a/components/Visualization/ReducedVisualization.js b/components/Visualization/ReducedVisualization.js index a6e85056..31bb7e67 100644 --- a/components/Visualization/ReducedVisualization.js +++ b/components/Visualization/ReducedVisualization.js @@ -1,47 +1,43 @@ /** * ReducedVisualization.js - * + * * This component generates an example split view with two graphs using the Graphviz library and the React split component. - * + * * @author Daniel Igbokwe */ - import { Container } from "@mui/material"; import Split from "react-split"; - - -export default function visualize(props){ - - - return( - - - {/*
+export default function visualize(props) { + return ( + + {/*
{props.instanceVisualization}
*/} -
- - {props.instanceVisualization !== null ?
- {props.instanceVisualization} - -
: null} -
+
+ + {props.instanceVisualization !== null ? ( +
+ {props.instanceVisualization}
- {/*
+ ) : null} + +
+ {/*
{props.instanceVisualization}
*/} -
- - {props.reducedVisualization !== null ?
- {props.reducedVisualization} - -
: null} -
+
+ + {props.reducedVisualization !== null ? ( +
+ {props.reducedVisualization}
- - ) -} \ No newline at end of file + ) : null} +
+
+ + ); +} diff --git a/components/Visualization/constants/VisColors.js b/components/Visualization/constants/VisColors.js index 3a42f2ad..1bbb8d76 100644 --- a/components/Visualization/constants/VisColors.js +++ b/components/Visualization/constants/VisColors.js @@ -1,21 +1,21 @@ const VisColors = { - ElementHighlight : "#f69240", - ClauseHighlight : "#989898", - Background : "#abc", - Solution : "#00e676", - SolutionAlt : "#E600E3", - Edges : "#aaa", + ElementHighlight: "#f69240", + ClauseHighlight: "#989898", + Background: "#abc", + Solution: "#00e676", + SolutionAlt: "#E600E3", + Edges: "#aaa", - // Paul Tol's color-blindess palette - muted - Rose : '#CC6677', - Indigo: '#332288', - Sand : '#DDCC77', - Green : '#117733', - Cyan : '#88CCEE', - Wine : '#882255', - Teal : '#44AA99', - Olive : '#999933', - Purple : '#AA4499', -} + // Paul Tol's color-blindess palette - muted + Rose: "#CC6677", + Indigo: "#332288", + Sand: "#DDCC77", + Green: "#117733", + Cyan: "#88CCEE", + Wine: "#882255", + Teal: "#44AA99", + Olive: "#999933", + Purple: "#AA4499", +}; -export default VisColors; \ No newline at end of file +export default VisColors; diff --git a/components/Visualization/constants/VisColorsArray.js b/components/Visualization/constants/VisColorsArray.js index 9450e7ae..eb9caab4 100644 --- a/components/Visualization/constants/VisColorsArray.js +++ b/components/Visualization/constants/VisColorsArray.js @@ -1,25 +1,25 @@ const VisColorsArray = [ - { key: "ElementHighlight", value: "#f69240" }, - { key: "ClauseHighlight", value: "#989898" }, - { key: "Background", value: "#abc" }, - { key: "Solution", value: "#00e676" }, - { key: "SolutionAlt", value: "#E600E3" }, - { key: "Edges", value: "#aaa" }, - { key: "Rose", value: "#CC6677" }, - { key: "Indigo", value: "#332288" }, - { key: "Sand", value: "#DDCC77" }, - { key: "Green", value: "#117733" }, - { key: "Cyan", value: "#88CCEE" }, - { key: "Wine", value: "#882255" }, - { key: "Teal", value: "#44AA99" }, - { key: "Olive", value: "#999933" }, - { key: "Purple", value: "#AA4499" }, - { key: "Red", value: "#FF0000" }, + { key: "ElementHighlight", value: "#f69240" }, + { key: "ClauseHighlight", value: "#989898" }, + { key: "Background", value: "#abc" }, + { key: "Solution", value: "#00e676" }, + { key: "SolutionAlt", value: "#E600E3" }, + { key: "Edges", value: "#aaa" }, + { key: "Rose", value: "#CC6677" }, + { key: "Indigo", value: "#332288" }, + { key: "Sand", value: "#DDCC77" }, + { key: "Green", value: "#117733" }, + { key: "Cyan", value: "#88CCEE" }, + { key: "Wine", value: "#882255" }, + { key: "Teal", value: "#44AA99" }, + { key: "Olive", value: "#999933" }, + { key: "Purple", value: "#AA4499" }, + { key: "Red", value: "#FF0000" }, ]; const getColorByKey = (key) => { - const colorObj = VisColorsArray.find(color => color.key === key); - return colorObj ? colorObj.value : null; + const colorObj = VisColorsArray.find((color) => color.key === key); + return colorObj ? colorObj.value : null; }; -export { VisColorsArray, getColorByKey }; \ No newline at end of file +export { getColorByKey, VisColorsArray }; diff --git a/components/Visualization/openqasmToQText.js b/components/Visualization/openqasmToQText.js index 1a12e4bd..5bfa77ee 100644 --- a/components/Visualization/openqasmToQText.js +++ b/components/Visualization/openqasmToQText.js @@ -1,9 +1,7 @@ // components/Visualization/openqasmToQText.js // All gate *symbols* that actually exist in Q.Gate.constants -const QJS_SYMBOLS = new Set([ - "I", "*", "M", "H", "X", "Y", "Z", "P", "T", "B", "S", "√S", "Q" -]); +const QJS_SYMBOLS = new Set(["I", "*", "M", "H", "X", "Y", "Z", "P", "T", "B", "S", "√S", "Q"]); // Map from OpenQASM gate name -> Q.js gate symbol // (multi-qubit vs single-qubit is determined by how many qubits we apply it to) @@ -24,10 +22,9 @@ const QASM_TO_QJS_SYMBOL = { gate_q_4505047632: "Q", gate_q_4505045840: "Q", gate_q_4505048912: "Q", - gate_q_4505050448: "Q" + gate_q_4505050448: "Q", }; - // Helper: look up a Q.js symbol for a QASM gate name function lookupSymbol(qasmName) { const key = qasmName.toLowerCase(); @@ -91,11 +88,7 @@ export function openqasmToQText(qasm) { // ------------------------------------------------------------------ // Measurement: measure q[i] -> c[j]; // ------------------------------------------------------------------ - if ( - (m = line.match( - /^measure\s+q\[(\d+)\]\s*->\s*[a-zA-Z_]\w*\[(\d+)\];/i - )) - ) { + if ((m = line.match(/^measure\s+q\[(\d+)\]\s*->\s*[a-zA-Z_]\w*\[(\d+)\];/i))) { const qIndex = parseInt(m[1], 10); time++; ensureAllRowsHaveMoment(time); @@ -107,9 +100,7 @@ export function openqasmToQText(qasm) { // Parameterised gates like "p(pi/8) q[0];" // Currently we *skip* all of these, but log them. // ------------------------------------------------------------------ - if ( - /^([a-zA-Z][a-zA-Z0-9_]*)\s*\(.*\)\s+q\[\d+\]/.test(line) - ) { + if (/^([a-zA-Z][a-zA-Z0-9_]*)\s*\(.*\)\s+q\[\d+\]/.test(line)) { console.warn("Skipping parameterised gate (not supported in Q.js text):", line); continue; } @@ -117,11 +108,7 @@ export function openqasmToQText(qasm) { // ------------------------------------------------------------------ // 1-qubit gate: name q[i]; // ------------------------------------------------------------------ - if ( - (m = line.match( - /^([a-zA-Z][a-zA-Z0-9_]*)\s+q\[(\d+)\];$/ - )) - ) { + if ((m = line.match(/^([a-zA-Z][a-zA-Z0-9_]*)\s+q\[(\d+)\];$/))) { const gateName = m[1]; const qIndex = parseInt(m[2], 10); const symbol = lookupSymbol(gateName); @@ -141,11 +128,7 @@ export function openqasmToQText(qasm) { // SPECIAL CASE: auto-generated Grover Q gates // gate_Q_12345678 q[0],q[1],q[2],q[3]; // ------------------------------------------------------------------ - if ( - (m = line.match( - /^(gate_Q_[A-Za-z0-9_]+)\s+((?:q\[\d+\]\s*,\s*)*q\[\d+\]);$/ - )) - ) { + if ((m = line.match(/^(gate_Q_[A-Za-z0-9_]+)\s+((?:q\[\d+\]\s*,\s*)*q\[\d+\]);$/))) { const qubits = parseQubitList(m[2]); // [0,1,2,3,...] const symbol = "Q"; // our custom multi-qubit Grover gate symbol @@ -161,16 +144,11 @@ export function openqasmToQText(qasm) { continue; // we handled this line, skip to next one } - // ------------------------------------------------------------------ // Multi-qubit gate: name q[a],q[b],q[c],... // ------------------------------------------------------------------ // name q[a],q[b],q[c],... - if ( - (m = line.match( - /^([a-zA-Z][a-zA-Z0-9_]*)\s+((?:q\[\d+\]\s*,\s*)*q\[\d+\]);$/ - )) - ) { + if ((m = line.match(/^([a-zA-Z][a-zA-Z0-9_]*)\s+((?:q\[\d+\]\s*,\s*)*q\[\d+\]);$/))) { const gateName = m[1]; const qubits = parseQubitList(m[2]); const symbol = lookupSymbol(gateName); @@ -211,7 +189,5 @@ export function openqasmToQText(qasm) { } // Convert table into Q.js text format - return rows - .map((row) => row.join("-")) - .join("\n"); + return rows.map((row) => row.join("-")).join("\n"); } diff --git a/components/Visualization/svgs/DynamicTableSvgReact.js b/components/Visualization/svgs/DynamicTableSvgReact.js index e39f3c6d..dee25ba4 100644 --- a/components/Visualization/svgs/DynamicTableSvgReact.js +++ b/components/Visualization/svgs/DynamicTableSvgReact.js @@ -1,90 +1,94 @@ import React from "react"; import { getColorByKey } from "../constants/VisColorsArray"; -export default function DynamicTableSvgReact({problemData}) -{ - if(!problemData || !problemData.rows || !problemData.columns) - return null; +export default function DynamicTableSvgReact({ problemData }) { + if (!problemData || !problemData.rows || !problemData.columns) return null; - const {title, columns, rows} = problemData; + const { title, columns, rows } = problemData; - return ( -
- {title && ( -
- {title} -
- )} - {/* Capped height with a sticky header: traces (a DFA run, a long Dijkstra table) + return ( +
+ {title && ( +
+ {title} +
+ )} + {/* Capped height with a sticky header: traces (a DFA run, a long Dijkstra table) can be far taller than the visualization pane, and scrolling one out of view loses the column labels that make the rows readable. */} -
- - - - {columns.map(col => ( - - ))} - - - - {rows.map((row, rowIndex) => ( - - {columns.map(col => { - const cellColor = row.cellColors?.[col.key]; - return ( - - ); - })} - - ))} - -
- {col.label} -
- {row.cells?.[col.key] ?? "-"} -
-
-
- ); +
+ + + + {columns.map((col) => ( + + ))} + + + + {rows.map((row, rowIndex) => ( + + {columns.map((col) => { + const cellColor = row.cellColors?.[col.key]; + return ( + + ); + })} + + ))} + +
+ {col.label} +
+ {row.cells?.[col.key] ?? "-"} +
+
+
+ ); } const thStyle = { - border: "1px solid #ccc", - padding: "8px 16px", - textAlign: "center", - fontWeight: "bold" + border: "1px solid #ccc", + padding: "8px 16px", + textAlign: "center", + fontWeight: "bold", }; const tdStyle = { - border: "1px solid #ccc", - padding: "8px 16px", - textAlign: "center" + border: "1px solid #ccc", + padding: "8px 16px", + textAlign: "center", }; diff --git a/components/Visualization/svgs/LaTeXGraphSvgReact.js b/components/Visualization/svgs/LaTeXGraphSvgReact.js index 077d63aa..c0b43a72 100644 --- a/components/Visualization/svgs/LaTeXGraphSvgReact.js +++ b/components/Visualization/svgs/LaTeXGraphSvgReact.js @@ -1,18 +1,22 @@ import React, { useEffect, useState } from "react"; function escapeLatexText(str) { - return String(str).replace(/[\\{}%$&#_^~]/g, (c) => ({ - "\\": "\\textbackslash{}", - "{": "\\{", - "}": "\\}", - "%": "\\%", - "$": "\\$", - "&": "\\&", - "#": "\\#", - "_": "\\_", - "^": "\\^{}", - "~": "\\~{}", - }[c])); + return String(str).replace( + /[\\{}%$&#_^~]/g, + (c) => + ({ + "\\": "\\textbackslash{}", + "{": "\\{", + "}": "\\}", + "%": "\\%", + $: "\\$", + "&": "\\&", + "#": "\\#", + _: "\\_", + "^": "\\^{}", + "~": "\\~{}", + })[c], + ); } function safeNodeId(id) { @@ -101,8 +105,7 @@ function LaTeXGraphSvgReact({ problemData }) { } } - let nodeDefs = - "\\begin{scope}[every node/.style={circle,draw,line width=1.2pt}]\n"; + let nodeDefs = "\\begin{scope}[every node/.style={circle,draw,line width=1.2pt}]\n"; nodes.forEach((node) => { const id = safeNodeId(node.id); @@ -154,16 +157,13 @@ function LaTeXGraphSvgReact({ problemData }) { ? ` node[midway, fill=white, inner sep=2pt] {${escapeLatexText(link.weight)}}` : ""; - const loopWeight = - link.weighted === true - ? ` node {${escapeLatexText(link.weight)}}` - : ""; + const loopWeight = link.weighted === true ? ` node {${escapeLatexText(link.weight)}}` : ""; if (src === tgt) { const node = nodes.find((n) => n.id === src); // FIX 2: Added parentheses to fix operator precedence bug. const nearbyEdges = links.filter( - (l) => l.source === src || (l.target === src && l.source !== src) + (l) => l.source === src || (l.target === src && l.source !== src), ); let counts = { right: 0, left: 0, above: 0, below: 0 }; @@ -196,9 +196,7 @@ function LaTeXGraphSvgReact({ problemData }) { usedEdges[canonicalKey] = (usedEdges[canonicalKey] || 0) + 1; const bend = - usedEdges[canonicalKey] > 1 - ? `bend right=${20 * usedEdges[canonicalKey]}` - : ""; + usedEdges[canonicalKey] > 1 ? `bend right=${20 * usedEdges[canonicalKey]}` : ""; const options = [arrow, bend, style].filter(Boolean).join(","); @@ -209,8 +207,7 @@ function LaTeXGraphSvgReact({ problemData }) { edgeDefs += "\\end{scope}\n"; - const tikz = - `\\begin{tikzpicture}\n${nodeDefs}${edgeDefs}\\end{tikzpicture}`; + const tikz = `\\begin{tikzpicture}\n${nodeDefs}${edgeDefs}\\end{tikzpicture}`; try { const response = await fetch("/api/render-tikz", { @@ -267,4 +264,4 @@ function LaTeXGraphSvgReact({ problemData }) { ); } -export default LaTeXGraphSvgReact; \ No newline at end of file +export default LaTeXGraphSvgReact; diff --git a/components/Visualization/svgs/No_Viz_SVG.js b/components/Visualization/svgs/No_Viz_SVG.js index e4b5e250..b99cae04 100644 --- a/components/Visualization/svgs/No_Viz_SVG.js +++ b/components/Visualization/svgs/No_Viz_SVG.js @@ -1,73 +1,69 @@ -import React, { useContext } from 'react'; -import Box from '@mui/material/Box'; -import ErrorOutlineIcon from '@mui/icons-material/ErrorOutlined'; -import { Typography,Card } from '@mui/material'; -import '@fontsource/roboto/300.css'; -import '@fontsource/roboto/400.css'; -import '@fontsource/roboto/500.css'; -import '@fontsource/roboto/700.css'; - +import ErrorOutlineIcon from "@mui/icons-material/ErrorOutlined"; +import { Card, Typography } from "@mui/material"; +import Box from "@mui/material/Box"; +import React, { useContext } from "react"; +import "@fontsource/roboto/300.css"; +import "@fontsource/roboto/400.css"; +import "@fontsource/roboto/500.css"; +import "@fontsource/roboto/700.css"; export function No_Viz_Svg({ niceProblemName }) { - return ( - - - - - - + return ( + + + - - {/*

No visualization is Currently implemented!

+ {/*

No visualization is Currently implemented!

No visualization is Currently Implemented!

*/} - - The {niceProblemName} visualization has not been implemented yet - -
- -
- ) + + The {niceProblemName} visualization has not been implemented yet + +
+
+ ); } - - export function No_Reduction_Viz_Svg({ niceReductionName }) { - return ( - - - - - The {niceReductionName ?? "chosen reduction"} visualization has not been implemented yet - - - ) + return ( + + + + + The {niceReductionName ?? "chosen reduction"} visualization has not been implemented yet + + + + ); } /** @@ -77,34 +73,35 @@ export function No_Reduction_Viz_Svg({ niceReductionName }) { * / `niceReductionName` are shown as extra context when available. */ export function No_Renderable_Viz_Svg({ niceProblemName, niceReductionName, visualizationType }) { - const contextName = niceReductionName ?? niceProblemName; - return ( - - - - - {contextName ? `${contextName} declares` : "This visualization declares"} type - "{visualizationType || "unknown"}", which this interface can't render. - - - - ) + const contextName = niceReductionName ?? niceProblemName; + return ( + + + + + {contextName ? `${contextName} declares` : "This visualization declares"} type " + {visualizationType || "unknown"}", which this interface can't render. + + + + ); } /** @@ -114,33 +111,33 @@ export function No_Renderable_Viz_Svg({ niceProblemName, niceReductionName, visu * bugs behind a missing-feature message. */ export function Viz_Render_Error_Svg({ niceProblemName, niceReductionName, visualizationType }) { - const contextName = niceReductionName ?? niceProblemName; - return ( - - - - - {contextName ? `The ${contextName}` : "This"} visualization - (type "{visualizationType || "unknown"}") failed to render. Check the console - for details. - - - - ) -} \ No newline at end of file + const contextName = niceReductionName ?? niceProblemName; + return ( + + + + + {contextName ? `The ${contextName}` : "This"} visualization (type " + {visualizationType || "unknown"}") failed to render. Check the console for details. + + + + ); +} diff --git a/components/Visualization/svgs/PumpSchedulingSvgReact.js b/components/Visualization/svgs/PumpSchedulingSvgReact.js index c2b275f9..aa306dba 100644 --- a/components/Visualization/svgs/PumpSchedulingSvgReact.js +++ b/components/Visualization/svgs/PumpSchedulingSvgReact.js @@ -1,113 +1,153 @@ import React from "react"; export default function PumpSchedulingSvgReact({ problemData }) { - if (!problemData) return null; + if (!problemData) return null; - const { action, metrics, state } = problemData; - const pumps = state?.pumps ?? []; - const fillRatio = Math.min(1, Math.max(0, metrics?.tankFillRatio ?? 0)); - const fillPct = Math.round(fillRatio * 100); + const { action, metrics, state } = problemData; + const pumps = state?.pumps ?? []; + const fillRatio = Math.min(1, Math.max(0, metrics?.tankFillRatio ?? 0)); + const fillPct = Math.round(fillRatio * 100); - const peakColor = metrics?.isPeakHour ? "#e53935" : "#43a047"; - const peakLabel = metrics?.isPeakHour ? "Peak" : "Off-Peak"; + const peakColor = metrics?.isPeakHour ? "#e53935" : "#43a047"; + const peakLabel = metrics?.isPeakHour ? "Peak" : "Off-Peak"; - return ( -
- - {/* Hour / tariff badge */} -
- - Hour {metrics?.hour ?? "—"} - - - {peakLabel} - -
+ return ( +
+ {/* Hour / tariff badge */} +
+ + Hour {metrics?.hour ?? "—"} + + + {peakLabel} + +
- {/* Action description */} - {action && ( -
- {action} -
- )} + {/* Action description */} + {action && ( +
+ {action} +
+ )} - {/* Pump states */} -
-
Pump States
-
- {pumps.map((pump) => ( -
-
{pump.name}
-
{pump.isOn ? "ON" : "OFF"}
- {pump.isOn && ( -
- {pump.flowGph} gph · {pump.powerKw} kW -
- )} -
- ))} + {/* Pump states */} +
+
Pump States
+
+ {pumps.map((pump) => ( +
+
{pump.name}
+
{pump.isOn ? "ON" : "OFF"}
+ {pump.isOn && ( +
+ {pump.flowGph} gph · {pump.powerKw} kW
+ )}
+ ))} +
+
- {/* Tank level bar */} -
-
- Tank Level — {metrics?.tankLevel?.toLocaleString() ?? "—"} / {metrics?.tankCapacity?.toLocaleString() ?? "—"} gal ({fillPct}%) -
-
-
0.9 ? "#e65100" : "#1565c0", - transition: "width 0.3s", - }} /> -
-
+ {/* Tank level bar */} +
+
+ Tank Level — {metrics?.tankLevel?.toLocaleString() ?? "—"} /{" "} + {metrics?.tankCapacity?.toLocaleString() ?? "—"} gal ({fillPct}%) +
+
+
0.9 ? "#e65100" : "#1565c0", + transition: "width 0.3s", + }} + /> +
+
- {/* Flow / demand */} -
- - - -
+ {/* Flow / demand */} +
+ + + +
- {/* Cost */} -
- - -
-
- ); + {/* Cost */} +
+ + +
+
+ ); } function Metric({ label, value, accent }) { - return ( -
-
{label}
-
{value}
-
- ); + return ( +
+
{label}
+
{value}
+
+ ); } diff --git a/components/Visualization/svgs/StandardCircuitSvgReact.js b/components/Visualization/svgs/StandardCircuitSvgReact.js index 07b1cc96..d930627e 100644 --- a/components/Visualization/svgs/StandardCircuitSvgReact.js +++ b/components/Visualization/svgs/StandardCircuitSvgReact.js @@ -1,5 +1,5 @@ -import { useEffect, useRef } from "react"; import * as d3 from "d3"; +import { useEffect, useRef } from "react"; import { getColorByKey } from "../constants/VisColorsArray"; const CIRCUIT_WIDTH = 700; @@ -61,19 +61,25 @@ export default function StandardCircuitSvgReact({ const qubits = parsedData.qubits ?? ["q0", "q1"]; const classical = parsedData.classical ?? []; - const chosenCircuit = useSolutionCircuit && Array.isArray(parsedData.solutionCircuit) && parsedData.solutionCircuit.length - ? parsedData.solutionCircuit - : parsedData.gates ?? []; + const chosenCircuit = + useSolutionCircuit && + Array.isArray(parsedData.solutionCircuit) && + parsedData.solutionCircuit.length + ? parsedData.solutionCircuit + : (parsedData.gates ?? []); const gates = chosenCircuit; const timestepsRaw = gates.map((g, idx) => g.time ?? idx); - const timesteps = (timestepsRaw.length ? Array.from(new Set(timestepsRaw)) : [0, 1]).sort((a, b) => a - b); + const timesteps = (timestepsRaw.length ? Array.from(new Set(timestepsRaw)) : [0, 1]).sort( + (a, b) => a - b, + ); const qubitSpacing = 64; const classicalSpacing = 18; const qubitToClassicalGap = classical.length ? 44 : 0; - const xExtent = margin.left + margin.right + Math.max(timesteps.length - 1, 1) * 80 + GATE_WIDTH * 2; + const xExtent = + margin.left + margin.right + Math.max(timesteps.length - 1, 1) * 80 + GATE_WIDTH * 2; const width = Math.max(CIRCUIT_WIDTH, xExtent); const qubitBand = Math.max(qubits.length - 1, 0) * qubitSpacing; @@ -81,7 +87,7 @@ export default function StandardCircuitSvgReact({ const classicalHeight = classical.length ? (classical.length - 1) * classicalSpacing : 0; const height = Math.max( CIRCUIT_HEIGHT, - classicalStart + classicalHeight + margin.bottom + (classical.length ? 30 : 10) + classicalStart + classicalHeight + margin.bottom + (classical.length ? 30 : 10), ); const xScale = d3 @@ -242,11 +248,13 @@ export default function StandardCircuitSvgReact({ const gateType = (g.type || g.label || "").toLowerCase(); const paletteEntry = gatePalette[gateType]; if (paletteEntry) gateTypesUsed.add(paletteEntry.label); - const targets = Array.isArray(g.targets) ? g.targets : (g.target ? [g.target] : []); + const targets = Array.isArray(g.targets) ? g.targets : g.target ? [g.target] : []; const x = xScale(g.time ?? i); if (x == null) return; - const targetIndices = targets.map((t) => (typeof t === "number" ? t : qubits.indexOf(String(t)))); + const targetIndices = targets.map((t) => + typeof t === "number" ? t : qubits.indexOf(String(t)), + ); if (gateType === "cx" && targetIndices.length >= 2) { const controlIdx = targetIndices[0]; @@ -308,7 +316,7 @@ export default function StandardCircuitSvgReact({ const yMax = yScale(maxIdx); if (yMin != null && yMax != null) { const blockGroup = svg.append("g").attr("id", g.id ? `id${g.id}` : null); - const blockHeight = (yMax - yMin) + GATE_HEIGHT; + const blockHeight = yMax - yMin + GATE_HEIGHT; const fill = paletteEntry?.fill || getColorByKey("Background"); blockGroup .append("rect") @@ -325,14 +333,14 @@ export default function StandardCircuitSvgReact({ blockGroup .append("text") .attr("x", x) - .attr("y", yMin + (blockHeight / 2)) + .attr("y", yMin + blockHeight / 2) .attr("text-anchor", "middle") .attr("font-size", 12) .attr("font-weight", "bold") .text((paletteEntry?.label || g.label || g.type || "?").toUpperCase()); - const blockTitle = `${(paletteEntry?.label || g.label || g.type || "?")} on ${targetIndices.map(qubitName).join(", ")}`; - const blockSuffix = g.name || g.id ? ` (${g.name || g.id})` : ""; - addTitle(blockGroup, `${blockTitle}${blockSuffix}`); + const blockTitle = `${paletteEntry?.label || g.label || g.type || "?"} on ${targetIndices.map(qubitName).join(", ")}`; + const blockSuffix = g.name || g.id ? ` (${g.name || g.id})` : ""; + addTitle(blockGroup, `${blockTitle}${blockSuffix}`); targetIndices.forEach((ti) => { const yTarget = yScale(ti); @@ -359,19 +367,29 @@ export default function StandardCircuitSvgReact({ const group = svg.append("g").attr("id", g.id ? `id${g.id}` : null); // meter icon at measX - group.append("path") + group + .append("path") .attr("d", `M ${measX - 10} ${y - 6} Q ${measX} ${y + 10} ${measX + 10} ${y - 6}`) - .attr("fill", "none").attr("stroke", edgeColor).attr("stroke-width", 2); - group.append("line") - .attr("x1", measX - 10).attr("x2", measX + 10) - .attr("y1", y - 6).attr("y2", y - 6) - .attr("stroke", edgeColor).attr("stroke-width", 2); - group.append("circle") - .attr("cx", measX).attr("cy", y).attr("r", 3).attr("fill", edgeColor); - group.append("text") - .attr("x", measX).attr("y", y - 12) - .attr("text-anchor", "middle").attr("font-size", 12) - .attr("font-weight", "bold").text("M"); + .attr("fill", "none") + .attr("stroke", edgeColor) + .attr("stroke-width", 2); + group + .append("line") + .attr("x1", measX - 10) + .attr("x2", measX + 10) + .attr("y1", y - 6) + .attr("y2", y - 6) + .attr("stroke", edgeColor) + .attr("stroke-width", 2); + group.append("circle").attr("cx", measX).attr("cy", y).attr("r", 3).attr("fill", edgeColor); + group + .append("text") + .attr("x", measX) + .attr("y", y - 12) + .attr("text-anchor", "middle") + .attr("font-size", 12) + .attr("font-weight", "bold") + .text("M"); if (Array.isArray(g.classical) && g.classical.length && classical.length) { const classicalIdx = classical.indexOf(g.classical[0]); @@ -383,17 +401,25 @@ export default function StandardCircuitSvgReact({ })(); // dotted stem to the bus - group.append("line") - .attr("x1", measX).attr("x2", measX) - .attr("y1", y).attr("y2", classicalYBase) - .attr("stroke", edgeColor).attr("stroke-width", 2.5) + group + .append("line") + .attr("x1", measX) + .attr("x2", measX) + .attr("y1", y) + .attr("y2", classicalYBase) + .attr("stroke", edgeColor) + .attr("stroke-width", 2.5) .attr("stroke-dasharray", "4,3"); // marker at the bus (label handled in bus ticks) - group.append("circle") - .attr("cx", measX).attr("cy", classicalYBase) - .attr("r", 4).attr("fill", edgeColor) - .attr("stroke", getColorByKey("Background")).attr("stroke-width", 1); + group + .append("circle") + .attr("cx", measX) + .attr("cy", classicalYBase) + .attr("r", 4) + .attr("fill", edgeColor) + .attr("stroke", getColorByKey("Background")) + .attr("stroke-width", 1); if (!measurementAnchors.has(classicalIdx)) { measurementAnchors.set(classicalIdx, { @@ -414,7 +440,12 @@ export default function StandardCircuitSvgReact({ const label = g.label ?? g.type ?? "?"; const gateLabel = paletteEntry?.label || label; const fill = paletteEntry?.fill || getColorByKey("Background"); - const rotationParam = Array.isArray(g.params) && g.params.length ? `(${g.params.join(",")})` : (g.theta ? `(${g.theta})` : ""); + const rotationParam = + Array.isArray(g.params) && g.params.length + ? `(${g.params.join(",")})` + : g.theta + ? `(${g.theta})` + : ""; const displayLabel = ["rz", "ry", "rx"].includes(gateType) ? `${gateLabel}${rotationParam}` : gateLabel; @@ -441,7 +472,9 @@ export default function StandardCircuitSvgReact({ .attr("font-weight", "bold") .text(displayLabel.toUpperCase()); const titleName = gateTitleMap[gateType] || gateLabel || displayLabel; - const titleWithParams = ["rz", "ry", "rx"].includes(gateType) ? `${titleName}${rotationParam}` : titleName; + const titleWithParams = ["rz", "ry", "rx"].includes(gateType) + ? `${titleName}${rotationParam}` + : titleName; const gateTitle = `${titleWithParams} on ${qubitName(targetIdx)}`; const gateSuffix = g.name || g.id ? ` (${g.name || g.id})` : ""; addTitle(gateGroup, `${gateTitle}${gateSuffix}`); @@ -510,7 +543,16 @@ export default function StandardCircuitSvgReact({ offsetX += 18 + label.length * 7; }); } - }, [parsedData, useSolutionCircuit, gatePalette, gateTitleMap, margin.bottom, margin.left, margin.right, margin.top]); + }, [ + parsedData, + useSolutionCircuit, + gatePalette, + gateTitleMap, + margin.bottom, + margin.left, + margin.right, + margin.top, + ]); const oracle = parsedData?.metadata?.oracleType; const solution = parsedData?.metadata?.solution; @@ -520,10 +562,12 @@ export default function StandardCircuitSvgReact({ const additionalMetadata = parsedData?.metadata ? Object.entries(parsedData.metadata).filter( - ([key]) => !["oracleType", "solution", "solutionBits", "iterations", "secretString"].includes(key) - ) + ([key]) => + !["oracleType", "solution", "solutionBits", "iterations", "secretString"].includes(key), + ) : []; - const hasSolutionMetadata = shouldShowSolution && (solution || solutionBits || additionalMetadata.length > 0); + const hasSolutionMetadata = + shouldShowSolution && (solution || solutionBits || additionalMetadata.length > 0); const showMetadataPanel = oracle || typeof iterations !== "undefined" || hasSolutionMetadata; return ( @@ -543,37 +587,38 @@ export default function StandardCircuitSvgReact({ >
- {showMetadataPanel && ( -
- {oracle && ( -
- Oracle (ground truth): {oracle} -
- )} - {shouldShowSolution && solution && ( -
- Solution (measured result): {solution} -
- )} - {shouldShowSolution && solutionBits &&
Solution bits: {solutionBits}
} - {typeof iterations !== "undefined" &&
Iterations: {iterations}
} - {shouldShowSolution && additionalMetadata.map(([k, v]) => ( + {showMetadataPanel && ( +
+ {oracle && ( +
+ Oracle (ground truth): {oracle} +
+ )} + {shouldShowSolution && solution && ( +
+ Solution (measured result): {solution} +
+ )} + {shouldShowSolution && solutionBits &&
Solution bits: {solutionBits}
} + {typeof iterations !== "undefined" &&
Iterations: {iterations}
} + {shouldShowSolution && + additionalMetadata.map(([k, v]) => (
{k}: {String(v)}
))} -
- )} +
+ )} ); } @@ -582,14 +627,21 @@ function parseCircuitData(data) { if (!data) return null; if (typeof data === "string") { - try { data = JSON.parse(data); } catch { return null; } + try { + data = JSON.parse(data); + } catch { + return null; + } } if (data && typeof data === "object" && typeof data.payload === "string") { - try { data = JSON.parse(data.payload); } catch { /* keep original */ } + try { + data = JSON.parse(data.payload); + } catch { + /* keep original */ + } } - if (data && typeof data === "object" && data.d3 && typeof data.d3 === "object") { const d3 = data.d3; @@ -609,14 +661,18 @@ function parseCircuitData(data) { }; } - // If the payload is a string, try to parse it as JSON - if (data && typeof data === "object" && typeof data.circuit === "string") { - const s = data.circuit.trim(); - if (s.startsWith("{") || s.startsWith("[")) { - try { return JSON.parse(s); } catch { /* ignore */ } + // If the payload is a string, try to parse it as JSON + if (data && typeof data === "object" && typeof data.circuit === "string") { + const s = data.circuit.trim(); + if (s.startsWith("{") || s.startsWith("[")) { + try { + return JSON.parse(s); + } catch { + /* ignore */ } } + } - // Already the direct D3 payload shape - return data; + // Already the direct D3 payload shape + return data; } diff --git a/components/Visualization/svgs/StandardGraphSvgReact.js b/components/Visualization/svgs/StandardGraphSvgReact.js index 78dc73cd..7432f7cf 100644 --- a/components/Visualization/svgs/StandardGraphSvgReact.js +++ b/components/Visualization/svgs/StandardGraphSvgReact.js @@ -1,7 +1,7 @@ // ForceGraphReact.js import * as d3 from "d3"; import { useEffect, useRef, useState } from "react"; -import { getColorByKey } from '../constants/VisColorsArray'; +import { getColorByKey } from "../constants/VisColorsArray"; const GRAPH_MARGIN = { top: 200, right: 30, bottom: 30, left: 200 }; @@ -17,13 +17,16 @@ function ForceGraph({ w, h, charge, problemData, gadgetMap }) { if (d3.select("#highlightGadgets").property("checked")) { d3.selectAll("#id" + nodeId.replace("!", "NOT")) .attr("fill", getColorByKey("ElementHighlight")) - .attr("stroke", getColorByKey("ElementHighlight")) + .attr("stroke", getColorByKey("ElementHighlight")); } if (!gadgetMap || gadgetMap.length === 0) return; - gadgetMap.forEach(item => { - if ((item.reductionFromIds.includes(nodeId) || item.reductionToIds.includes(nodeId)) && item.color === "ElementHighlight") { - [...item.reductionFromIds, ...item.reductionToIds].forEach(id => { + gadgetMap.forEach((item) => { + if ( + (item.reductionFromIds.includes(nodeId) || item.reductionToIds.includes(nodeId)) && + item.color === "ElementHighlight" + ) { + [...item.reductionFromIds, ...item.reductionToIds].forEach((id) => { d3.selectAll("#id" + id.replace("!", "NOT")) .attr("fill", getColorByKey("ElementHighlight")) .attr("stroke", getColorByKey("ElementHighlight")); @@ -40,8 +43,8 @@ function ForceGraph({ w, h, charge, problemData, gadgetMap }) { if (!gadgetMap || gadgetMap.length === 0) return; // Reset all gadgets in gadgetMap - gadgetMap.forEach(item => { - [...item.reductionFromIds, ...item.reductionToIds].forEach(id => { + gadgetMap.forEach((item) => { + [...item.reductionFromIds, ...item.reductionToIds].forEach((id) => { d3.selectAll("#id" + id.replace("!", "NOT")) .attr("fill", getColorByKey("Background")) .attr("stroke", getColorByKey("Background")); @@ -58,9 +61,9 @@ function ForceGraph({ w, h, charge, problemData, gadgetMap }) { .attr("stroke", getColorByKey("ClauseHighlight")); if (!gadgetMap || gadgetMap.length === 0) return; - gadgetMap.forEach(item => { - if ((item.reductionFromIds.includes(nodeId) || item.reductionToIds.includes(nodeId))) { - [...item.reductionFromIds, ...item.reductionToIds].forEach(id => { + gadgetMap.forEach((item) => { + if (item.reductionFromIds.includes(nodeId) || item.reductionToIds.includes(nodeId)) { + [...item.reductionFromIds, ...item.reductionToIds].forEach((id) => { d3.selectAll("#id" + id.replace("!", "NOT")) .attr("fill", getColorByKey("ClauseHighlight")) .attr("stroke", getColorByKey("ClauseHighlight")); @@ -76,8 +79,8 @@ function ForceGraph({ w, h, charge, problemData, gadgetMap }) { function clearClusters(nodeId, gadgetMap) { if (!gadgetMap || gadgetMap.length === 0) return; // Reset all clusters in gadgetMap - gadgetMap.forEach(item => { - [...item.reductionFromIds, ...item.reductionToIds].forEach(id => { + gadgetMap.forEach((item) => { + [...item.reductionFromIds, ...item.reductionToIds].forEach((id) => { d3.selectAll("#id" + id.replace("!", "NOT")) .attr("fill", getColorByKey("Background")) .attr("stroke", getColorByKey("Background")); @@ -88,7 +91,6 @@ function ForceGraph({ w, h, charge, problemData, gadgetMap }) { }); } - useEffect(() => { if (!problemData) return; @@ -101,7 +103,8 @@ function ForceGraph({ w, h, charge, problemData, gadgetMap }) { d3.select(ref.current).selectChildren().remove(); - const svg = d3.select(ref.current) + const svg = d3 + .select(ref.current) .append("svg") .attr("preserveAspectRatio", "xMinYMin meet") .attr("viewBox", `0 0 ${width} ${height}`) @@ -112,7 +115,8 @@ function ForceGraph({ w, h, charge, problemData, gadgetMap }) { problemData.links?.forEach((d, i) => { if (d.directed) { - const marker = defs.append("marker") + const marker = defs + .append("marker") .attr("id", `arrow-${i}`) .attr("viewBox", "0 -5 10 10") .attr("refX", 26) @@ -121,9 +125,7 @@ function ForceGraph({ w, h, charge, problemData, gadgetMap }) { .attr("markerHeight", 6) .attr("orient", "auto"); - marker.append("path") - .attr("d", "M0,-5L10,0L0,5") - .attr("fill", getColorByKey("Edges")); + marker.append("path").attr("d", "M0,-5L10,0L0,5").attr("fill", getColorByKey("Edges")); d.markerId = `arrow-${i}`; } @@ -132,7 +134,7 @@ function ForceGraph({ w, h, charge, problemData, gadgetMap }) { // Helper: parse the delay attribute (string from backend) into a positive // number of milliseconds. Returns 0 for missing / non-numeric / non-positive // values, which means "render at final color immediately, no animation." - const getDelayMs = d => { + const getDelayMs = (d) => { const n = parseInt(d?.delay, 10); return Number.isFinite(n) && n > 0 ? n : 0; }; @@ -142,30 +144,33 @@ function ForceGraph({ w, h, charge, problemData, gadgetMap }) { // color and transition to its final color after the delay. This produces a // staggered animation for visualizations like Topological Sort that color // edges in waves by topological rank. - const link = svg.selectAll("line") + const link = svg + .selectAll("line") .data(data.links) .join("line") - .attr("marker-end", d => d.directed ? `url(#${d.markerId})` : null) - .style("stroke", d => getDelayMs(d) > 0 - ? getColorByKey("Edges") - : getColorByKey(d.color || "Edges")) + .attr("marker-end", (d) => (d.directed ? `url(#${d.markerId})` : null)) + .style("stroke", (d) => + getDelayMs(d) > 0 ? getColorByKey("Edges") : getColorByKey(d.color || "Edges"), + ) .style("stroke-width", "2px") - .style("stroke-dasharray", d => d.dashed ? "5,5" : "none"); + .style("stroke-dasharray", (d) => (d.dashed ? "5,5" : "none")); - link.filter(d => getDelayMs(d) > 0 && d.color) + link + .filter((d) => getDelayMs(d) > 0 && d.color) .transition() - .delay(d => getDelayMs(d)) + .delay((d) => getDelayMs(d)) .duration(FADE_DURATION_MS) - .style("stroke", d => getColorByKey(d.color)); + .style("stroke", (d) => getColorByKey(d.color)); // Draw arrowhead markers for directed edges. Match the link's animation so // the arrow color stays in sync with its line. - problemData.links?.forEach(d => { + problemData.links?.forEach((d) => { if (d.directed) { const markerPath = d3.select(`#${d.markerId} path`); const delayMs = getDelayMs(d); if (delayMs > 0 && d.color) { - markerPath.attr("fill", getColorByKey("Edges")) + markerPath + .attr("fill", getColorByKey("Edges")) .transition() .delay(delayMs) .duration(FADE_DURATION_MS) @@ -179,17 +184,18 @@ function ForceGraph({ w, h, charge, problemData, gadgetMap }) { // Draw nodes. Same delay-aware pattern as links: nodes with a positive // delay start at the background color and animate to their final color // after the delay. - const node = svg.selectAll("circle") + const node = svg + .selectAll("circle") .data(data.nodes) .join("circle") .attr("r", 20) - .attr("id", d => "id" + d.id.replace("!", "NOT")) - .attr("class", d => "node" + d.id.replace("!", "NOT")) - .attr("fill", d => getDelayMs(d) > 0 - ? getColorByKey("Background") - : getColorByKey(d.color || "Background")) - .attr("stroke", d => d.outline ? getColorByKey(d.outline) : null) - .attr("stroke-width", d => d.outline ? 2 : 0) + .attr("id", (d) => "id" + d.id.replace("!", "NOT")) + .attr("class", (d) => "node" + d.id.replace("!", "NOT")) + .attr("fill", (d) => + getDelayMs(d) > 0 ? getColorByKey("Background") : getColorByKey(d.color || "Background"), + ) + .attr("stroke", (d) => (d.outline ? getColorByKey(d.outline) : null)) + .attr("stroke-width", (d) => (d.outline ? 2 : 0)) .on("mouseover", (event, d) => { if (d3.select("#highlightGadgets").property("checked")) { highlightCluster(d.id, gadgetMap); @@ -203,15 +209,17 @@ function ForceGraph({ w, h, charge, problemData, gadgetMap }) { } }); - node.filter(d => getDelayMs(d) > 0 && d.color) + node + .filter((d) => getDelayMs(d) > 0 && d.color) .transition() - .delay(d => getDelayMs(d)) + .delay((d) => getDelayMs(d)) .duration(FADE_DURATION_MS) - .attr("fill", d => getColorByKey(d.color)); + .attr("fill", (d) => getColorByKey(d.color)); // Draw edge weight labels for weighted graphs (MaxCut, MinSTCut, etc.) - const linkLabel = svg.selectAll(".link-label") - .data(data.links.filter(d => d.weighted)) + const linkLabel = svg + .selectAll(".link-label") + .data(data.links.filter((d) => d.weighted)) .enter() .append("text") .attr("class", "link-label") @@ -219,61 +227,71 @@ function ForceGraph({ w, h, charge, problemData, gadgetMap }) { .attr("font-size", "11px") .attr("text-anchor", "middle") .style("pointer-events", "none") - .text(d => d.weight); + .text((d) => d.weight); // Draw labels - const text = svg.selectAll("text:not(.link-label)") + const text = svg + .selectAll("text:not(.link-label)") .data(data.nodes) .enter() .append("text") .attr("fill", "black") .attr("font-size", "12px") .attr("text-anchor", "middle") - .text(d => d.name) + .text((d) => d.name) .style("pointer-events", "none"); // disables pointer events for labels // Scale for link distances based on weight - const weights = data.links.map(d => d.weight); + const weights = data.links.map((d) => d.weight); const minWeight = d3.min(weights); const maxWeight = d3.max(weights); - const scale = (minWeight === maxWeight) - ? () => 1 - : d3.scaleLinear().domain([minWeight, maxWeight]).range([1, 4]).clamp(true); + const scale = + minWeight === maxWeight + ? () => 1 + : d3.scaleLinear().domain([minWeight, maxWeight]).range([1, 4]).clamp(true); // Force simulation - const simulation = d3.forceSimulation(problemData.nodes) - .force("link", d3.forceLink(problemData.links) - .id(d => d.name) - .distance(d => scale(d.weight) * Math.abs(charge) * 1.5)) + const simulation = d3 + .forceSimulation(problemData.nodes) + .force( + "link", + d3 + .forceLink(problemData.links) + .id((d) => d.name) + .distance((d) => scale(d.weight) * Math.abs(charge) * 1.5), + ) .force("charge", d3.forceManyBody().strength(charge * 8)) .force("x", d3.forceX()) .force("y", d3.forceY()) - .force("collide", d3.forceCollide().radius(d => d.r * 2).iterations(10)) + .force( + "collide", + d3 + .forceCollide() + .radius((d) => d.r * 2) + .iterations(10), + ) .on("tick", ticked); function ticked() { link - .attr("x1", d => d.source.x) - .attr("y1", d => d.source.y) - .attr("x2", d => d.target.x) - .attr("y2", d => d.target.y); + .attr("x1", (d) => d.source.x) + .attr("y1", (d) => d.source.y) + .attr("x2", (d) => d.target.x) + .attr("y2", (d) => d.target.y); - node - .attr("cx", d => d.x) - .attr("cy", d => d.y); + node.attr("cx", (d) => d.x).attr("cy", (d) => d.y); linkLabel - .attr("x", d => (d.source.x + d.target.x) / 2) - .attr("y", d => (d.source.y + d.target.y) / 2) + .attr("x", (d) => (d.source.x + d.target.x) / 2) + .attr("y", (d) => (d.source.y + d.target.y) / 2) .attr("dy", -4); text - .attr("x", d => d.x) - .attr("y", d => d.y) + .attr("x", (d) => d.x) + .attr("y", (d) => d.y) .attr("dy", 5) - .text(d => d.name); + .text((d) => d.name); } - }, [problemData, charge, gadgetMap, height, width, margin.left, margin.top]); return ( @@ -294,7 +312,5 @@ function ForceGraph({ w, h, charge, problemData, gadgetMap }) { export default function StandardGraphSvgReact(props) { const [charge, setCharge] = useState(-50); - return ( - - ); + return ; } diff --git a/components/Visualization/svgs/StandardSATSvgReact.js b/components/Visualization/svgs/StandardSATSvgReact.js index 9cc61347..8daa00aa 100644 --- a/components/Visualization/svgs/StandardSATSvgReact.js +++ b/components/Visualization/svgs/StandardSATSvgReact.js @@ -1,337 +1,352 @@ -import React from 'react' -import * as d3 from 'd3' -import { getColorByKey } from '../constants/VisColorsArray'; +import * as d3 from "d3"; import dynamic from "next/dynamic"; -import { useRef, useState, useEffect, useContext } from 'react'; +import React, { useContext, useEffect, useRef, useState } from "react"; +import { getColorByKey } from "../constants/VisColorsArray"; /// StandardSATSvgReact.js /// This is a wrapper for the boolean visualization instance. It allows us to use the visualization as a react component, and also disables -/// server side rendering due to compilation issues with rendering a d3 svg before the entire page is rendered. +/// server side rendering due to compilation issues with rendering a d3 svg before the entire page is rendered. function StandardSATSvgReact(props) { - const ref = useRef(null); - useEffect(() => { - - try { - if (props.problemData) { - getSets(ref.current, props.problemData, props.gadgetMap, props.gadgetsOn); - } - - } - catch (error) { console.log("VISUALIZATION FAILED") }; - - }, [props.problemData, props.gadgetMap, props.gadgetsOn]) - - - return ( - - ) + const ref = useRef(null); + useEffect(() => { + try { + if (props.problemData) { + getSets(ref.current, props.problemData, props.gadgetMap, props.gadgetsOn); + } + } catch (error) { + console.log("VISUALIZATION FAILED"); + } + }, [props.problemData, props.gadgetMap, props.gadgetsOn]); + + return ( + + ); } function getSets(ref, data, gadgetMap, gadgetsOn) { - const margin = { top: 200, right: 30, bottom: 30, left: 200 }, - width = 700 - margin.left - margin.right, - height = 700 - margin.top - margin.bottom; - - // clear previous content - d3.select(ref).selectAll("*").remove(); - - // create main svg container - const svg = d3.select(ref) - .append("svg") - .attr("preserveAspectRatio", "xMinYMin meet") - .attr("viewBox", `0 0 ${width} ${height}`) - .attr("class", "all"); - - let x = 20; - let y = 100; - - const clauses = data.clauses; - - for (let i = 0; i < clauses.length; i++) { - let c = new clause(clauses[i].id, svg, x, y, clauses[i].literals, 13, gadgetMap, gadgetsOn); - c.show(); - x += c.width + 8; - if (i < clauses.length - 1) { - svg.append("text") - .attr("x", x) - .attr("y", y) - .attr("text-anchor", "left") - .attr("dominant-baseline", "middle") - .attr("font-size", "15px") - .attr("font-family", "'Courier New', Courier, monospace") - .text("\u2227"); - x += 16; - } - if (x >= width - c.width) { - x = 20; - y += 50 - } + const margin = { top: 200, right: 30, bottom: 30, left: 200 }, + width = 700 - margin.left - margin.right, + height = 700 - margin.top - margin.bottom; + + // clear previous content + d3.select(ref).selectAll("*").remove(); + + // create main svg container + const svg = d3 + .select(ref) + .append("svg") + .attr("preserveAspectRatio", "xMinYMin meet") + .attr("viewBox", `0 0 ${width} ${height}`) + .attr("class", "all"); + + let x = 20; + let y = 100; + + const clauses = data.clauses; + + for (let i = 0; i < clauses.length; i++) { + let c = new clause(clauses[i].id, svg, x, y, clauses[i].literals, 13, gadgetMap, gadgetsOn); + c.show(); + x += c.width + 8; + if (i < clauses.length - 1) { + svg + .append("text") + .attr("x", x) + .attr("y", y) + .attr("text-anchor", "left") + .attr("dominant-baseline", "middle") + .attr("font-size", "15px") + .attr("font-family", "'Courier New', Courier, monospace") + .text("\u2227"); + x += 16; } + if (x >= width - c.width) { + x = 20; + y += 50; + } + } - d3.selectAll(".true") - .attr("fill", getColorByKey("ElementHighlight")) - .attr("stroke", getColorByKey("ElementHighlight")); + d3.selectAll(".true") + .attr("fill", getColorByKey("ElementHighlight")) + .attr("stroke", getColorByKey("ElementHighlight")); - d3.select(ref).selectChildren()._groups[0]?.slice(1).map((child) => d3.select(child).remove()) + d3.select(ref) + .selectChildren() + ._groups[0]?.slice(1) + .map((child) => d3.select(child).remove()); } function showCluster(clusterClass, gadgetMap) { - if (!d3.select("#highlightGadgets").property("checked")) return; - - d3.selectAll("." + clusterClass) - .attr("fill", getColorByKey("ClauseHighlight")) - .attr("stroke", getColorByKey("ClauseHighlight")); - - const cleanElement = clusterClass.replace(/^class/, ""); - - // Optionally highlight linked elements via gadgetMap - if (Array.isArray(gadgetMap)) { - gadgetMap.forEach(item => { - if ((item.reductionFromIds.includes(cleanElement) || item.reductionToIds.includes(cleanElement)) && item.color === "ClauseHighlight") { - [...item.reductionFromIds, ...item.reductionToIds].forEach(id => { - d3.selectAll("#id" + id.replace("!", "NOT")) - .attr("fill", getColorByKey("ClauseHighlight")) - .attr("stroke", getColorByKey("ClauseHighlight")); - d3.selectAll(".class" + id.replace("!", "NOT")) - .attr("fill", getColorByKey("ClauseHighlight")) - .attr("stroke", getColorByKey("ClauseHighlight")); - }); - } + if (!d3.select("#highlightGadgets").property("checked")) return; + + d3.selectAll("." + clusterClass) + .attr("fill", getColorByKey("ClauseHighlight")) + .attr("stroke", getColorByKey("ClauseHighlight")); + + const cleanElement = clusterClass.replace(/^class/, ""); + + // Optionally highlight linked elements via gadgetMap + if (Array.isArray(gadgetMap)) { + gadgetMap.forEach((item) => { + if ( + (item.reductionFromIds.includes(cleanElement) || + item.reductionToIds.includes(cleanElement)) && + item.color === "ClauseHighlight" + ) { + [...item.reductionFromIds, ...item.reductionToIds].forEach((id) => { + d3.selectAll("#id" + id.replace("!", "NOT")) + .attr("fill", getColorByKey("ClauseHighlight")) + .attr("stroke", getColorByKey("ClauseHighlight")); + d3.selectAll(".class" + id.replace("!", "NOT")) + .attr("fill", getColorByKey("ClauseHighlight")) + .attr("stroke", getColorByKey("ClauseHighlight")); }); - } + } + }); + } } function showElement(element, gadgetMap) { - if (d3.select("#highlightGadgets").property("checked")) { - d3.selectAll("#" + element) + if (d3.select("#highlightGadgets").property("checked")) { + d3.selectAll("#" + element) + .attr("fill", getColorByKey("ElementHighlight")) + .attr("stroke", getColorByKey("ElementHighlight")); + + if (!gadgetMap) return; + + const cleanElement = element.replace(/^id/, ""); + + gadgetMap.forEach((item) => { + // Check if the element is in reductionFromIds or reductionToIds + if ( + (item.reductionFromIds.includes(cleanElement) || + item.reductionToIds.includes(cleanElement)) && + item.color === "ElementHighlight" + ) { + // Highlight all IDs in reductionFromIds + item.reductionFromIds.forEach((id) => { + d3.selectAll("#id" + id.replace("!", "NOT")) .attr("fill", getColorByKey("ElementHighlight")) - .attr("stroke", getColorByKey("ElementHighlight")) - - if (!gadgetMap) return; - - const cleanElement = element.replace(/^id/, ""); - - gadgetMap.forEach(item => { - // Check if the element is in reductionFromIds or reductionToIds - if ((item.reductionFromIds.includes(cleanElement) || item.reductionToIds.includes(cleanElement)) && item.color === "ElementHighlight") { - - // Highlight all IDs in reductionFromIds - item.reductionFromIds.forEach(id => { - d3.selectAll("#id" + id.replace("!", "NOT")) - .attr("fill", getColorByKey("ElementHighlight")) - .attr("stroke", getColorByKey("ElementHighlight")); - }); - - // Highlight all IDs in reductionToIds - item.reductionToIds.forEach(id => { - d3.selectAll("#id" + id.replace("!", "NOT")) - .attr("fill", getColorByKey("ElementHighlight")) - .attr("stroke", getColorByKey("ElementHighlight")); - }); - } + .attr("stroke", getColorByKey("ElementHighlight")); }); - } + + // Highlight all IDs in reductionToIds + item.reductionToIds.forEach((id) => { + d3.selectAll("#id" + id.replace("!", "NOT")) + .attr("fill", getColorByKey("ElementHighlight")) + .attr("stroke", getColorByKey("ElementHighlight")); + }); + } + }); + } } function clear(gadgetMap) { - // Reset all elements highlighted directly - d3.selectAll("[id^='id']").attr("fill", getColorByKey("Background")) - .attr("stroke", getColorByKey("Background")); - - // Reset all gadgets - d3.selectAll(".gadget").attr("fill", getColorByKey("Background")) - .attr("stroke", getColorByKey("Background")); - - // Reset linked elements from gadgetMap - if (Array.isArray(gadgetMap)) { - gadgetMap.forEach(item => { - if (item.color === "ElementHighlight") { - item.reductionFromIds.forEach(id => { - d3.selectAll("#id" + id.replace("!", "NOT")) - .attr("fill", getColorByKey("Background")) - .attr("stroke", getColorByKey("Background")); - }); - item.reductionToIds.forEach(id => { - d3.selectAll("#id" + id.replace("!", "NOT")) - .attr("fill", getColorByKey("Background")) - .attr("stroke", getColorByKey("Background")); - }); - } + // Reset all elements highlighted directly + d3.selectAll("[id^='id']") + .attr("fill", getColorByKey("Background")) + .attr("stroke", getColorByKey("Background")); + + // Reset all gadgets + d3.selectAll(".gadget") + .attr("fill", getColorByKey("Background")) + .attr("stroke", getColorByKey("Background")); + + // Reset linked elements from gadgetMap + if (Array.isArray(gadgetMap)) { + gadgetMap.forEach((item) => { + if (item.color === "ElementHighlight") { + item.reductionFromIds.forEach((id) => { + d3.selectAll("#id" + id.replace("!", "NOT")) + .attr("fill", getColorByKey("Background")) + .attr("stroke", getColorByKey("Background")); }); - } + item.reductionToIds.forEach((id) => { + d3.selectAll("#id" + id.replace("!", "NOT")) + .attr("fill", getColorByKey("Background")) + .attr("stroke", getColorByKey("Background")); + }); + } + }); + } } class literal { - constructor(id, className, name, svg, x, y, size = 25, gadgetMap, color, gadgetsOn) { - this.id = "id" + id; - this.className = className; - this.name = name; - this.svg = svg; - this.x = x; - this.y = y; - this.size = size; - this.gadgetMap = gadgetMap; - this.color = color; - this.gadgetsOn = gadgetsOn; - } - show(c = this.className, e = this.id) { - this.svg.append("rect") - .attr("x", this.x) - .attr("y", this.y - this.size / 2) - .attr("fill", getColorByKey(this.color.trim()) || getColorByKey("Background")) - .attr("height", this.size) - .attr("width", this.size * this.name.length - 7)//subtracting 7 since the stroke length is 7. - .attr("id", this.id) - .attr("class", this.className + " gadget " + this.name.replace("!", "NOT")) - .attr("stroke-linejoin", "round") - .attr("stroke-width", "7px") - .on("mouseover", () => { - if (this.gadgetsOn) { - showCluster(c, this.gadgetMap); - showElement(e, this.gadgetMap); - } - }) - .on("mouseout", () => { - if (this.gadgetsOn) { - clear(); - } - }); - this.svg.append("text") - .attr("class", this.name) - .attr("x", this.x) - .attr("y", this.y) - .attr("text-anchor", "left") - .attr("dominant-baseline", "middle") - .attr("font-size", this.size + "px") - .attr("font-family", "'Courier New', Courier, monospace") - .text(this.name) - .on("mouseover", () => { - if (this.gadgetsOn) { - showCluster(c, this.gadgetMap); - showElement(e, this.gadgetMap); - } - }) - .on("mouseout", () => { - if (this.gadgetsOn) { - clear(); - } - }) - .style("pointer-events", "none"); - - } + constructor(id, className, name, svg, x, y, size = 25, gadgetMap, color, gadgetsOn) { + this.id = "id" + id; + this.className = className; + this.name = name; + this.svg = svg; + this.x = x; + this.y = y; + this.size = size; + this.gadgetMap = gadgetMap; + this.color = color; + this.gadgetsOn = gadgetsOn; + } + show(c = this.className, e = this.id) { + this.svg + .append("rect") + .attr("x", this.x) + .attr("y", this.y - this.size / 2) + .attr("fill", getColorByKey(this.color.trim()) || getColorByKey("Background")) + .attr("height", this.size) + .attr("width", this.size * this.name.length - 7) //subtracting 7 since the stroke length is 7. + .attr("id", this.id) + .attr("class", this.className + " gadget " + this.name.replace("!", "NOT")) + .attr("stroke-linejoin", "round") + .attr("stroke-width", "7px") + .on("mouseover", () => { + if (this.gadgetsOn) { + showCluster(c, this.gadgetMap); + showElement(e, this.gadgetMap); + } + }) + .on("mouseout", () => { + if (this.gadgetsOn) { + clear(); + } + }); + this.svg + .append("text") + .attr("class", this.name) + .attr("x", this.x) + .attr("y", this.y) + .attr("text-anchor", "left") + .attr("dominant-baseline", "middle") + .attr("font-size", this.size + "px") + .attr("font-family", "'Courier New', Courier, monospace") + .text(this.name) + .on("mouseover", () => { + if (this.gadgetsOn) { + showCluster(c, this.gadgetMap); + showElement(e, this.gadgetMap); + } + }) + .on("mouseout", () => { + if (this.gadgetsOn) { + clear(); + } + }) + .style("pointer-events", "none"); + } } class clause { - constructor(className, svg, x, y, literals, size = 20, gadgetMap, gadgetsOn) { - this.className = "class" + className; - this.svg = svg; - this.x = x; - this.y = y; - this.size = size; - this.literalsIDs = []; - this.literals = literals; - this.width = 0; - this.gadgetMap = gadgetMap; - this.gadgetsOn = gadgetsOn; + constructor(className, svg, x, y, literals, size = 20, gadgetMap, gadgetsOn) { + this.className = "class" + className; + this.svg = svg; + this.x = x; + this.y = y; + this.size = size; + this.literalsIDs = []; + this.literals = literals; + this.width = 0; + this.gadgetMap = gadgetMap; + this.gadgetsOn = gadgetsOn; + } + show(c = this.className) { + // starting offset + let offsetX = this.x + this.size; + + // opening parenthesis + this.svg + .append("text") + .attr("x", this.x) + .attr("y", this.y) + .attr("text-anchor", "left") + .attr("dominant-baseline", "middle") + .attr("font-size", this.size + "px") + .text("(") + .style("pointer-events", "none"); + + // draw each literal and OR symbol + for (let i = 0; i < this.literals.length; i++) { + const lit = new literal( + this.literals[i].id, + this.className, + this.literals[i].literal, + this.svg, + offsetX, + this.y, + this.size, + this.gadgetMap, + this.literals[i].color, + this.gadgetsOn, + ); + lit.show(); + + // literal width exactly + const litWidth = lit.size * lit.name.length - 7; + + // update offsetX to the right edge of the literal + offsetX += litWidth; + + if (i < this.literals.length - 1) { + // small gap between literal and OR symbol + const gap = 4; + this.svg + .append("text") + .attr("x", offsetX + gap) + .attr("y", this.y) + .attr("text-anchor", "left") + .attr("dominant-baseline", "middle") + .attr("font-size", this.size + "px") + .attr("font-family", "'Courier New', Courier, monospace") + .text("\u2228") + .style("pointer-events", "none"); + + // move offsetX past the OR symbol plus some spacing + offsetX += this.size + gap; + } else { + // last literal, move offsetX past a bit for closing parenthesis + offsetX += this.size / 2; + } } - show(c = this.className) { - // starting offset - let offsetX = this.x + this.size; - - // opening parenthesis - this.svg.append("text") - .attr("x", this.x) - .attr("y", this.y) - .attr("text-anchor", "left") - .attr("dominant-baseline", "middle") - .attr("font-size", this.size + "px") - .text("(") - .style("pointer-events", "none"); - - // draw each literal and OR symbol - for (let i = 0; i < this.literals.length; i++) { - const lit = new literal( - this.literals[i].id, - this.className, - this.literals[i].literal, - this.svg, - offsetX, - this.y, - this.size, - this.gadgetMap, - this.literals[i].color, - this.gadgetsOn, - ); - lit.show(); - - // literal width exactly - const litWidth = lit.size * lit.name.length - 7; - - // update offsetX to the right edge of the literal - offsetX += litWidth; - - if (i < this.literals.length - 1) { - // small gap between literal and OR symbol - const gap = 4; - this.svg.append("text") - .attr("x", offsetX + gap) - .attr("y", this.y) - .attr("text-anchor", "left") - .attr("dominant-baseline", "middle") - .attr("font-size", this.size + "px") - .attr("font-family", "'Courier New', Courier, monospace") - .text("\u2228") - .style("pointer-events", "none"); - - // move offsetX past the OR symbol plus some spacing - offsetX += this.size + gap; - } else { - // last literal, move offsetX past a bit for closing parenthesis - offsetX += this.size / 2; - } - } - - - // closing parenthesis - this.svg.append("text") - .attr("x", offsetX) - .attr("y", this.y) - .attr("text-anchor", "left") - .attr("dominant-baseline", "middle") - .attr("font-size", this.size + "px") - .attr("font-family", "'Courier New', Courier, monospace") - .text(")") - .style("pointer-events", "none"); - // compute width for background rect - this.width = offsetX - this.x + this.size / 2; - - this.svg.append("rect") - .attr("x", this.x) - .attr("y", this.y - this.size) - .attr("fill", getColorByKey("Background")) - .attr("stroke", getColorByKey("Background")) - .attr("height", this.size * 2) - .attr("width", this.width) - .attr("class", this.className + " gadget") - .attr("stroke-linejoin", "round") - .attr("stroke-width", "7px") - .lower() // send behind text - .on("mouseover", () => { - if(this.gadgetsOn) showCluster(c, this.gadgetMap) } - ) - .on("mouseout", () => { - if(this.gadgetsOn) clear() } - ); - } + // closing parenthesis + this.svg + .append("text") + .attr("x", offsetX) + .attr("y", this.y) + .attr("text-anchor", "left") + .attr("dominant-baseline", "middle") + .attr("font-size", this.size + "px") + .attr("font-family", "'Courier New', Courier, monospace") + .text(")") + .style("pointer-events", "none"); + + // compute width for background rect + this.width = offsetX - this.x + this.size / 2; + + this.svg + .append("rect") + .attr("x", this.x) + .attr("y", this.y - this.size) + .attr("fill", getColorByKey("Background")) + .attr("stroke", getColorByKey("Background")) + .attr("height", this.size * 2) + .attr("width", this.width) + .attr("class", this.className + " gadget") + .attr("stroke-linejoin", "round") + .attr("stroke-width", "7px") + .lower() // send behind text + .on("mouseover", () => { + if (this.gadgetsOn) showCluster(c, this.gadgetMap); + }) + .on("mouseout", () => { + if (this.gadgetsOn) clear(); + }); + } } - export default dynamic(() => Promise.resolve(StandardSATSvgReact), { - ssr: false -}) \ No newline at end of file + ssr: false, +}); diff --git a/components/Visualization/svgs/StandardSetSvgReact.js b/components/Visualization/svgs/StandardSetSvgReact.js index c5a68dff..56acce9a 100644 --- a/components/Visualization/svgs/StandardSetSvgReact.js +++ b/components/Visualization/svgs/StandardSetSvgReact.js @@ -1,308 +1,349 @@ -import React, { useRef, useEffect } from 'react' -import * as d3 from 'd3' +import * as d3 from "d3"; import dynamic from "next/dynamic"; -import { getColorByKey } from '../constants/VisColorsArray'; +import React, { useEffect, useRef } from "react"; +import { getColorByKey } from "../constants/VisColorsArray"; function StandardSetSvgReact(props) { - const ref = useRef(null); - - useEffect(() => { - try { - if (props.problemData) { - globalY = 100; - getSets(ref.current, props.problemData, props.gadgetMap, props.gadgetsOn); - } - } catch (error) { console.log("VISUALIZATION FAILED", error) }; - }, [props.problemData, props.gadgetMap, props.gadgetsOn]); - - return ( - - ) + const ref = useRef(null); + + useEffect(() => { + try { + if (props.problemData) { + globalY = 100; + getSets(ref.current, props.problemData, props.gadgetMap, props.gadgetsOn); + } + } catch (error) { + console.log("VISUALIZATION FAILED", error); + } + }, [props.problemData, props.gadgetMap, props.gadgetsOn]); + + return ( + + ); } function getSets(ref, data, gadgetMap, gadgetsOn) { - const margin = { top: 200, right: 30, bottom: 30, left: 200 }, - width = 700 - margin.left - margin.right, - height = 700 - margin.top - margin.bottom; + const margin = { top: 200, right: 30, bottom: 30, left: 200 }, + width = 700 - margin.left - margin.right, + height = 700 - margin.top - margin.bottom; - d3.select(ref).selectAll("*").remove(); + d3.select(ref).selectAll("*").remove(); - const svg = d3.select(ref) - .append("svg") - .attr("preserveAspectRatio", "xMinYMin meet") - .attr("viewBox", `0 0 ${width} ${height}`) - .attr("class", "all"); + const svg = d3 + .select(ref) + .append("svg") + .attr("preserveAspectRatio", "xMinYMin meet") + .attr("viewBox", `0 0 ${width} ${height}`) + .attr("class", "all"); - let x = 20; + let x = 20; - recursiveSets(data.data.list, svg, gadgetMap, gadgetsOn, x, width); + recursiveSets(data.data.list, svg, gadgetMap, gadgetsOn, x, width); - d3.selectAll(".true") - .attr("fill", getColorByKey("ElementHighlight")) - .attr("stroke", getColorByKey("ElementHighlight")); + d3.selectAll(".true") + .attr("fill", getColorByKey("ElementHighlight")) + .attr("stroke", getColorByKey("ElementHighlight")); - d3.select(ref).selectChildren()._groups[0]?.slice(1).map(child => d3.select(child).remove()) + d3.select(ref) + .selectChildren() + ._groups[0]?.slice(1) + .map((child) => d3.select(child).remove()); } function asciiToHex(str) { - return Array.from(str).map(c => c.charCodeAt(0).toString(16).padStart(2, '0')).join(''); + return Array.from(str) + .map((c) => c.charCodeAt(0).toString(16).padStart(2, "0")) + .join(""); } let globalY = 100; // start Y function recursiveSets(sets, svg, gadgetMap, gadgetsOn, x, maxWidth) { - for (let i = 0; i < sets.length; i++) { - // Wrap line if needed - if (x >= maxWidth - 50) { - x = 20; - globalY += 50; - } - - const s = new CustomSet( - sets[i].id, - svg, - x, - globalY, - sets[i].list || [sets[i]], - 13, - gadgetMap, - gadgetsOn, - sets[i].isOrdered, - sets[i].isValue || false, - sets[i].color - ); + for (let i = 0; i < sets.length; i++) { + // Wrap line if needed + if (x >= maxWidth - 50) { + x = 20; + globalY += 50; + } - x = s.show(); // x after the set including its rectangle - - // Only add comma between sets - if (i < sets.length - 1) { - const commaGap = 8; - svg.append("text") - .attr("x", x + commaGap) - .attr("y", globalY) - .attr("text-anchor", "left") - .attr("dominant-baseline", "middle") - .attr("font-size", "15px") - .text(",") - .style("pointer-events", "none"); - - x += commaGap + 10; // move x past comma for next set - } + const s = new CustomSet( + sets[i].id, + svg, + x, + globalY, + sets[i].list || [sets[i]], + 13, + gadgetMap, + gadgetsOn, + sets[i].isOrdered, + sets[i].isValue || false, + sets[i].color, + ); + + x = s.show(); // x after the set including its rectangle + + // Only add comma between sets + if (i < sets.length - 1) { + const commaGap = 8; + svg + .append("text") + .attr("x", x + commaGap) + .attr("y", globalY) + .attr("text-anchor", "left") + .attr("dominant-baseline", "middle") + .attr("font-size", "15px") + .text(",") + .style("pointer-events", "none"); + + x += commaGap + 10; // move x past comma for next set } - return x; + } + return x; } - function showCluster(clusterClass, gadgetMap) { - if (!d3.select("#highlightGadgets").property("checked")) return; - - d3.selectAll("." + clusterClass) - .attr("fill", getColorByKey("ClauseHighlight")) - .attr("stroke", getColorByKey("ClauseHighlight")); - - const cleanElement = asciiToHex(clusterClass.replace(/^class/, "")); - - if (Array.isArray(gadgetMap)) { - gadgetMap.forEach(item => { - if ((item.reductionFromIds.includes(cleanElement) || item.reductionToIds.includes(cleanElement)) && item.color === "ClauseHighlight") { - [...item.reductionFromIds, ...item.reductionToIds].forEach(id => { - d3.selectAll("#id" + id.replace("!", "NOT")) - .attr("fill", getColorByKey("ClauseHighlight")) - .attr("stroke", getColorByKey("ClauseHighlight")); - }); - } + if (!d3.select("#highlightGadgets").property("checked")) return; + + d3.selectAll("." + clusterClass) + .attr("fill", getColorByKey("ClauseHighlight")) + .attr("stroke", getColorByKey("ClauseHighlight")); + + const cleanElement = asciiToHex(clusterClass.replace(/^class/, "")); + + if (Array.isArray(gadgetMap)) { + gadgetMap.forEach((item) => { + if ( + (item.reductionFromIds.includes(cleanElement) || + item.reductionToIds.includes(cleanElement)) && + item.color === "ClauseHighlight" + ) { + [...item.reductionFromIds, ...item.reductionToIds].forEach((id) => { + d3.selectAll("#id" + id.replace("!", "NOT")) + .attr("fill", getColorByKey("ClauseHighlight")) + .attr("stroke", getColorByKey("ClauseHighlight")); }); - } + } + }); + } } function showElement(element, gadgetMap) { - if (!d3.select("#highlightGadgets").property("checked")) return; - if (!gadgetMap) return; - - d3.selectAll("#" + element) - .attr("fill", getColorByKey("ElementHighlight")) - .attr("stroke", getColorByKey("ElementHighlight")) - - const cleanElement = asciiToHex(element.replace(/^id/, "")); - gadgetMap.forEach(item => { - if ((item.reductionFromIds.includes(cleanElement) || item.reductionToIds.includes(cleanElement)) && item.color === "ElementHighlight") { - [...item.reductionFromIds, ...item.reductionToIds].forEach(id => { - d3.selectAll("#id" + id.replace("!", "NOT")) - .attr("fill", getColorByKey("ElementHighlight")) - .attr("stroke", getColorByKey("ElementHighlight")); - }); - } - }); + if (!d3.select("#highlightGadgets").property("checked")) return; + if (!gadgetMap) return; + + d3.selectAll("#" + element) + .attr("fill", getColorByKey("ElementHighlight")) + .attr("stroke", getColorByKey("ElementHighlight")); + + const cleanElement = asciiToHex(element.replace(/^id/, "")); + gadgetMap.forEach((item) => { + if ( + (item.reductionFromIds.includes(cleanElement) || + item.reductionToIds.includes(cleanElement)) && + item.color === "ElementHighlight" + ) { + [...item.reductionFromIds, ...item.reductionToIds].forEach((id) => { + d3.selectAll("#id" + id.replace("!", "NOT")) + .attr("fill", getColorByKey("ElementHighlight")) + .attr("stroke", getColorByKey("ElementHighlight")); + }); + } + }); } function clear(gadgetMap) { - d3.selectAll("[id^='id']").attr("fill", getColorByKey("Background")) - .attr("stroke", getColorByKey("Background")); - d3.selectAll(".gadget").attr("fill", getColorByKey("Background")) - .attr("stroke", getColorByKey("Background")); - - if (Array.isArray(gadgetMap)) { - gadgetMap.forEach(item => { - if (item.color === "ElementHighlight") { - [...item.reductionFromIds, ...item.reductionToIds].forEach(id => { - d3.selectAll("#id" + id.replace("!", "NOT")) - .attr("fill", getColorByKey("Background")) - .attr("stroke", getColorByKey("Background")); - }); - } + d3.selectAll("[id^='id']") + .attr("fill", getColorByKey("Background")) + .attr("stroke", getColorByKey("Background")); + d3.selectAll(".gadget") + .attr("fill", getColorByKey("Background")) + .attr("stroke", getColorByKey("Background")); + + if (Array.isArray(gadgetMap)) { + gadgetMap.forEach((item) => { + if (item.color === "ElementHighlight") { + [...item.reductionFromIds, ...item.reductionToIds].forEach((id) => { + d3.selectAll("#id" + id.replace("!", "NOT")) + .attr("fill", getColorByKey("Background")) + .attr("stroke", getColorByKey("Background")); }); - } + } + }); + } } class element { - constructor(id, className, name, svg, x, y, size = 25, gadgetMap, color, gadgetsOn) { - this.id = "id" + asciiToHex(id); - this.className = className; - this.name = name; - this.svg = svg; - this.x = x; - this.y = y; - this.size = size; - this.gadgetMap = gadgetMap; - this.color = color; - this.gadgetsOn = gadgetsOn; - } - show(c = this.className, e = this.id) { - this.svg.append("rect") - .attr("x", this.x) - .attr("y", this.y - this.size / 2) - .attr("fill", getColorByKey(this.color.trim()) || getColorByKey("Background")) - .attr("height", this.size) - .attr("width", this.size * this.name.length - 7) - .attr("id", this.id) - .attr("class", this.className + " gadget " + this.name.replace("!", "NOT")) - .attr("stroke-linejoin", "round") - .attr("stroke-width", "7px") - .on("mouseover", () => { - if (this.gadgetsOn) { - showCluster(c, this.gadgetMap); - showElement(e, this.gadgetMap); - } - }) - .on("mouseout", () => { - if (this.gadgetsOn) clear(); - }); - this.svg.append("text") - .attr("class", this.name) - .attr("x", this.x) - .attr("y", this.y) - .attr("text-anchor", "left") - .attr("dominant-baseline", "middle") - .attr("font-size", this.size + "px") - .text(this.name) - .style("pointer-events", "none"); - } + constructor(id, className, name, svg, x, y, size = 25, gadgetMap, color, gadgetsOn) { + this.id = "id" + asciiToHex(id); + this.className = className; + this.name = name; + this.svg = svg; + this.x = x; + this.y = y; + this.size = size; + this.gadgetMap = gadgetMap; + this.color = color; + this.gadgetsOn = gadgetsOn; + } + show(c = this.className, e = this.id) { + this.svg + .append("rect") + .attr("x", this.x) + .attr("y", this.y - this.size / 2) + .attr("fill", getColorByKey(this.color.trim()) || getColorByKey("Background")) + .attr("height", this.size) + .attr("width", this.size * this.name.length - 7) + .attr("id", this.id) + .attr("class", this.className + " gadget " + this.name.replace("!", "NOT")) + .attr("stroke-linejoin", "round") + .attr("stroke-width", "7px") + .on("mouseover", () => { + if (this.gadgetsOn) { + showCluster(c, this.gadgetMap); + showElement(e, this.gadgetMap); + } + }) + .on("mouseout", () => { + if (this.gadgetsOn) clear(); + }); + this.svg + .append("text") + .attr("class", this.name) + .attr("x", this.x) + .attr("y", this.y) + .attr("text-anchor", "left") + .attr("dominant-baseline", "middle") + .attr("font-size", this.size + "px") + .text(this.name) + .style("pointer-events", "none"); + } } class CustomSet { - constructor(className, svg, x, y, elements, size = 20, gadgetMap, gadgetsOn, isOrdered = false, isValue, color) { - this.className = "class" + asciiToHex(className); - this.svg = svg; - this.x = x; - this.y = y; - this.size = size; - this.elements = elements; - this.width = 0; - this.gadgetMap = gadgetMap; - this.gadgetsOn = gadgetsOn; - this.isOrdered = isOrdered; - this.isValue = isValue; - this.color = color; - } - - show(c = this.className) { - let offsetX = this.x + this.size; - - this.svg.append("text") - .attr("x", this.x) - .attr("y", this.y) - .attr("text-anchor", "left") - .attr("dominant-baseline", "middle") - .attr("font-size", this.size + "px") - .text(!this.isValue ? (this.isOrdered ? "(" : "{") : "") - .style("pointer-events", "none"); - - let hasNestedSets = false; - - this.elements.forEach((el, i) => { - if (!el.isValue && el.list) { - hasNestedSets = true; - offsetX = recursiveSets([el], this.svg, this.gadgetMap, this.gadgetsOn, offsetX, 700); - if (i < this.elements.length - 1) offsetX += 8; - } - else { - const e = new element( - el.id, - this.className, - el.value, - this.svg, - offsetX, - this.y, - this.size, - this.gadgetMap, - el.color, - this.gadgetsOn - ); - - e.show(); - offsetX += e.size * e.name.length - 7; - } - if (i < this.elements.length - 1) { - const gap = 8; - this.svg.append("text") - .attr("x", offsetX + gap) - .attr("y", globalY) - .attr("text-anchor", "left") - .attr("dominant-baseline", "middle") - .attr("font-size", this.size + "px") - .text(",") - .style("pointer-events", "none"); - offsetX += this.size + gap; - } else { - offsetX += this.size; - } - }); + constructor( + className, + svg, + x, + y, + elements, + size = 20, + gadgetMap, + gadgetsOn, + isOrdered = false, + isValue, + color, + ) { + this.className = "class" + asciiToHex(className); + this.svg = svg; + this.x = x; + this.y = y; + this.size = size; + this.elements = elements; + this.width = 0; + this.gadgetMap = gadgetMap; + this.gadgetsOn = gadgetsOn; + this.isOrdered = isOrdered; + this.isValue = isValue; + this.color = color; + } + + show(c = this.className) { + let offsetX = this.x + this.size; + + this.svg + .append("text") + .attr("x", this.x) + .attr("y", this.y) + .attr("text-anchor", "left") + .attr("dominant-baseline", "middle") + .attr("font-size", this.size + "px") + .text(!this.isValue ? (this.isOrdered ? "(" : "{") : "") + .style("pointer-events", "none"); + + let hasNestedSets = false; + + this.elements.forEach((el, i) => { + if (!el.isValue && el.list) { + hasNestedSets = true; + offsetX = recursiveSets([el], this.svg, this.gadgetMap, this.gadgetsOn, offsetX, 700); + if (i < this.elements.length - 1) offsetX += 8; + } else { + const e = new element( + el.id, + this.className, + el.value, + this.svg, + offsetX, + this.y, + this.size, + this.gadgetMap, + el.color, + this.gadgetsOn, + ); - this.svg.append("text") - .attr("x", offsetX) - .attr("y", globalY) - .attr("text-anchor", "left") - .attr("dominant-baseline", "middle") - .attr("font-size", this.size + "px") - .text(!this.isValue ? (this.isOrdered ? ")" : "}") : "") - .style("pointer-events", "none"); - - this.width = offsetX - this.x + this.size / 2; - - if (!hasNestedSets) { - this.svg.append("rect") - .attr("x", this.x) - .attr("y", this.y - this.size) - .attr("fill", getColorByKey(this.color?.trim() || "Background")) - .attr("stroke", getColorByKey(this.color?.trim() || "Background")) - .attr("height", this.size * 2) - .attr("width", this.width) - .attr("class", this.className + " gadget") - .attr("stroke-linejoin", "round") - .attr("stroke-width", "7px") - .lower() - .on("mouseover", () => { if (this.gadgetsOn) showCluster(c, this.gadgetMap); }) - .on("mouseout", () => { if (this.gadgetsOn) clear(); }); - } + e.show(); + offsetX += e.size * e.name.length - 7; + } + if (i < this.elements.length - 1) { + const gap = 8; + this.svg + .append("text") + .attr("x", offsetX + gap) + .attr("y", globalY) + .attr("text-anchor", "left") + .attr("dominant-baseline", "middle") + .attr("font-size", this.size + "px") + .text(",") + .style("pointer-events", "none"); + offsetX += this.size + gap; + } else { + offsetX += this.size; + } + }); - return offsetX; + this.svg + .append("text") + .attr("x", offsetX) + .attr("y", globalY) + .attr("text-anchor", "left") + .attr("dominant-baseline", "middle") + .attr("font-size", this.size + "px") + .text(!this.isValue ? (this.isOrdered ? ")" : "}") : "") + .style("pointer-events", "none"); + + this.width = offsetX - this.x + this.size / 2; + + if (!hasNestedSets) { + this.svg + .append("rect") + .attr("x", this.x) + .attr("y", this.y - this.size) + .attr("fill", getColorByKey(this.color?.trim() || "Background")) + .attr("stroke", getColorByKey(this.color?.trim() || "Background")) + .attr("height", this.size * 2) + .attr("width", this.width) + .attr("class", this.className + " gadget") + .attr("stroke-linejoin", "round") + .attr("stroke-width", "7px") + .lower() + .on("mouseover", () => { + if (this.gadgetsOn) showCluster(c, this.gadgetMap); + }) + .on("mouseout", () => { + if (this.gadgetsOn) clear(); + }); } + + return offsetX; + } } export default dynamic(() => Promise.resolve(StandardSetSvgReact), { ssr: false }); diff --git a/components/eventHandlers/handleParameters.js b/components/eventHandlers/handleParameters.js index 53cc8681..33857099 100644 --- a/components/eventHandlers/handleParameters.js +++ b/components/eventHandlers/handleParameters.js @@ -1,5 +1,5 @@ -import { useRouter } from 'next/router'; -import { useEffect } from 'react'; +import { useRouter } from "next/router"; +import { useEffect } from "react"; export const useHandleParameters = () => { const router = useRouter(); @@ -7,7 +7,7 @@ export const useHandleParameters = () => { useEffect(() => { // Capture parameters from the URL const params = new URLSearchParams(window.location.search); - const allowedKeys = ['problem', 'instance', 'solver', 'reduceTo', 'reductionType', 'verifier']; + const allowedKeys = ["problem", "instance", "solver", "reduceTo", "reductionType", "verifier"]; const data = {}; params.forEach((value, key) => { @@ -24,7 +24,7 @@ export const useHandleParameters = () => { // Remove parameters from the URL router.replace(window.location.pathname, undefined, { shallow: true }); - document.title = 'Redux'; + document.title = "Redux"; // eslint-disable-next-line react-hooks/exhaustive-deps }, []); // Intentionally mount-only: URL params should be read and cleaned exactly once on load. -}; \ No newline at end of file +}; diff --git a/components/eventHandlers/handleUnload.js b/components/eventHandlers/handleUnload.js index 55452c6b..d1787a50 100644 --- a/components/eventHandlers/handleUnload.js +++ b/components/eventHandlers/handleUnload.js @@ -1,24 +1,24 @@ -import { useEffect } from 'react'; +import { useEffect } from "react"; export const useUnload = (problem, solver, verifier, reducer) => { - useEffect(() => { - const handleBeforeUnload = () => { - const data = { - problem: problem.problemName ?? "", - instance: problem.problemInstance ?? "", - solver: solver.chosenSolver ?? "", - reduceTo: reducer.chosenReduceTo ?? "", - reductionType: reducer.chosenReductionType ?? "", - verifier: verifier.chosenVerifier ?? "", - }; - // localStorage.setItem('problemData', JSON.stringify(data)); - }; + useEffect(() => { + const handleBeforeUnload = () => { + const data = { + problem: problem.problemName ?? "", + instance: problem.problemInstance ?? "", + solver: solver.chosenSolver ?? "", + reduceTo: reducer.chosenReduceTo ?? "", + reductionType: reducer.chosenReductionType ?? "", + verifier: verifier.chosenVerifier ?? "", + }; + // localStorage.setItem('problemData', JSON.stringify(data)); + }; - window.addEventListener('beforeunload', handleBeforeUnload); + window.addEventListener("beforeunload", handleBeforeUnload); - // Cleanup the event listener on component unmount - return () => { - window.removeEventListener('beforeunload', handleBeforeUnload); - }; - }, [problem, solver, verifier, reducer]); // Dependencies array -}; \ No newline at end of file + // Cleanup the event listener on component unmount + return () => { + window.removeEventListener("beforeunload", handleBeforeUnload); + }; + }, [problem, solver, verifier, reducer]); // Dependencies array +}; diff --git a/components/hooks/ProblemProvider/Problem.js b/components/hooks/ProblemProvider/Problem.js index 5283b8a8..b8c58ec0 100644 --- a/components/hooks/ProblemProvider/Problem.js +++ b/components/hooks/ProblemProvider/Problem.js @@ -1,5 +1,5 @@ -import { requestAllProblems, requestAllInfo } from "../../redux"; -import { useEffect, useState, useMemo } from "react"; +import { useEffect, useMemo, useState } from "react"; +import { requestAllInfo, requestAllProblems } from "../../redux"; // For initial startup defaults const DEFAULT_PROBLEM_NAME = "SAT3"; @@ -17,7 +17,7 @@ export function useProblemInfo(url, problemName) { const [problemInfo, setProblemInfo] = useState({}); useEffect(() => { - if(!problemName) return; + if (!problemName) return; (async () => { const allInfo = (await requestAllInfo(url)) ?? {}; setProblemInfo(allInfo[problemName] ?? {}); @@ -64,12 +64,11 @@ function useProblemName(problemNameMap) { useEffect(() => { const storedData = null; - - if(storedData) { + + if (storedData) { const allData = JSON.parse(storedData); setProblemName(allData.problem); - } - else if(problemNameMap.has(DEFAULT_PROBLEM_NAME)) { + } else if (problemNameMap.has(DEFAULT_PROBLEM_NAME)) { setProblemName(DEFAULT_PROBLEM_NAME); } }, [problemNameMap]); @@ -78,8 +77,10 @@ function useProblemName(problemNameMap) { } function useProblemNameMap(problemInfoMap = new Map()) { - return [useMemo( - () => new Map([...problemInfoMap].map(([name, info]) => [name, info?.problemName || name])), - [problemInfoMap] - )]; + return [ + useMemo( + () => new Map([...problemInfoMap].map(([name, info]) => [name, info?.problemName || name])), + [problemInfoMap], + ), + ]; } diff --git a/components/hooks/ProblemProvider/Reducer.js b/components/hooks/ProblemProvider/Reducer.js index 1704580d..8cde2726 100644 --- a/components/hooks/ProblemProvider/Reducer.js +++ b/components/hooks/ProblemProvider/Reducer.js @@ -1,6 +1,12 @@ +import React, { useEffect, useRef, useState } from "react"; +import { + requestInfo, + requestReducedInstanceFromPath, + requestReductionInfo, + requestReductionOptions, + requestReductions, +} from "../../redux"; import { useGenericInfo } from "../ProblemProvider"; -import { requestReductionOptions, requestReductionInfo, requestReductions, requestReducedInstanceFromPath, requestInfo } from "../../redux"; -import React, { useEffect, useState, useRef } from "react"; // For initial startup defaults const DEFAULT_SAT3_CHOSEN_REDUCE_TO = "CLIQUE"; @@ -11,23 +17,26 @@ const DEFAULT_VERTEXCOVER_CHOSEN_REDUCTION_TYPE = "sipserReduceToVC"; export function useReducer(url, problemName, problemInstance) { const state = {}; [state.reduceToOptions] = useReduceToOptions(url, problemName); - [state.chosenReduceTo, state.setChosenReduceTo] = useChosenReduceTo(problemName, state.reduceToOptions); + [state.chosenReduceTo, state.setChosenReduceTo] = useChosenReduceTo( + problemName, + state.reduceToOptions, + ); [state.reductionNameMap] = useReductionNameMap(url, problemName, state.chosenReduceTo); [state.reductionTypeOptions] = useReductionTypeOptions(url, problemName, state.chosenReduceTo); [state.chosenReductionType, state.setChosenReductionType] = useChosenReductionType( problemName, state.chosenReduceTo, - state.reductionTypeOptions + state.reductionTypeOptions, ); [state.reducedInstance, state.setReducedInstance] = useReducedInstance( url, problemInstance, state.chosenReduceTo, - state.chosenReductionType + state.chosenReductionType, ); [state.reductionVisualization, state.setReductionVisualization] = useReductionVisualization( url, - state.chosenReduceTo + state.chosenReduceTo, ); return state; } @@ -37,7 +46,9 @@ export function useReducerInfo(url, reducer) { useEffect(() => { (async () => { - setGenericInfo(!reducer ? {} : (await requestReductionInfo(url, (reducer ?? "").split("-")[0])) ?? {}); + setGenericInfo( + !reducer ? {} : ((await requestReductionInfo(url, (reducer ?? "").split("-")[0])) ?? {}), + ); })(); }, [reducer, url]); @@ -57,8 +68,9 @@ function useReducedInstance(url, problemInstance, chosenReduceTo, chosenReductio (async () => { setReducedInstance( chosenReductionType && problemInstance - ? (await requestReducedInstanceFromPath(url, chosenReductionType, problemInstance)) ?? "" - : "" + ? ((await requestReducedInstanceFromPath(url, chosenReductionType, problemInstance)) ?? + "") + : "", ); })(); }, [chosenReductionType, problemInstance, url]); @@ -90,7 +102,7 @@ function useReduceToOptions(url, problemName) { useEffect(() => { (async () => { setReduceToOptions( - (problemName ? (await requestReductionOptions(url, problemName)) ?? [] : []).sort() + (problemName ? ((await requestReductionOptions(url, problemName)) ?? []) : []).sort(), ); })(); }, [problemName, url]); @@ -114,9 +126,9 @@ function useReductionTypeOptions(url, problemName, chosenReduceTo) { (async () => { setReductionTypeOptions( (problemName && chosenReduceTo - ? (await requestPreparedReductions(url, problemName, chosenReduceTo)) ?? [] + ? ((await requestPreparedReductions(url, problemName, chosenReduceTo)) ?? []) : [] - ).sort() + ).sort(), ); })(); }, [chosenReduceTo, url, problemName]); @@ -133,7 +145,7 @@ function useChosenReductionType(problemName, chosenReduceTo, reductionTypeOption }, [problemName, chosenReduceTo]); useEffect(() => { - if(reductionTypeOptions.length === 0) return; + if (reductionTypeOptions.length === 0) return; const storedData = null; @@ -143,12 +155,15 @@ function useChosenReductionType(problemName, chosenReduceTo, reductionTypeOption const allData = JSON.parse(storedData); setChosenReductionType(allData.reductionType); isFirstRender.current = false; - if(allData.reductionType !== "") return; + if (allData.reductionType !== "") return; } isFirstRender.current = false; } - if (chosenReduceTo === "CLIQUE" && reductionTypeOptions.includes(DEFAULT_CLIQUE_CHOSEN_REDUCTION_TYPE)) { + if ( + chosenReduceTo === "CLIQUE" && + reductionTypeOptions.includes(DEFAULT_CLIQUE_CHOSEN_REDUCTION_TYPE) + ) { setChosenReductionType(DEFAULT_CLIQUE_CHOSEN_REDUCTION_TYPE); } else if ( chosenReduceTo === "VERTEXCOVER" && @@ -172,7 +187,7 @@ function useChosenReduceTo(problemName, reduceToOptions) { }, [problemName]); useEffect(() => { - if(reduceToOptions.length === 0) return; + if (reduceToOptions.length === 0) return; const storedData = null; if (isFirstRender.current) { @@ -181,24 +196,27 @@ function useChosenReduceTo(problemName, reduceToOptions) { const allData = JSON.parse(storedData); setChosenReduceTo(allData.reduceTo); isFirstRender.current = false; - if(allData.reduceTo !== "") return; + if (allData.reduceTo !== "") return; } isFirstRender.current = false; - } + } if (problemName === "SAT3" && reduceToOptions.includes(DEFAULT_SAT3_CHOSEN_REDUCE_TO)) { setChosenReduceTo(DEFAULT_SAT3_CHOSEN_REDUCE_TO); - } else if (problemName === "CLIQUE" && reduceToOptions.includes(DEFAULT_CLIQUE_CHOSEN_REDUCE_TO)) { + } else if ( + problemName === "CLIQUE" && + reduceToOptions.includes(DEFAULT_CLIQUE_CHOSEN_REDUCE_TO) + ) { setChosenReduceTo(DEFAULT_CLIQUE_CHOSEN_REDUCE_TO); } else { setChosenReduceTo(!reduceToOptions.length ? "" : reduceToOptions[0]); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [reduceToOptions]); // problemName intentionally omitted: it's a follower of reduceToOptions. - // When problemName changes, reduceToOptions recomputes and re-fires this - // effect with the current problemName already in scope. Adding problemName - // directly would fire this effect while reduceToOptions still holds stale - // values from the previous problem, setting a wrong default. + // When problemName changes, reduceToOptions recomputes and re-fires this + // effect with the current problemName already in scope. Adding problemName + // directly would fire this effect while reduceToOptions still holds stale + // values from the previous problem, setting a wrong default. return [chosenReduceTo, setChosenReduceTo]; } diff --git a/components/hooks/ProblemProvider/Solver.js b/components/hooks/ProblemProvider/Solver.js index d17a393e..48295232 100644 --- a/components/hooks/ProblemProvider/Solver.js +++ b/components/hooks/ProblemProvider/Solver.js @@ -1,15 +1,21 @@ +import React, { useEffect, useRef, useState } from "react"; +import { requestAllInfo, requestAllSolvers } from "../../redux"; import { useGenericInfo } from "../ProblemProvider"; -import { requestAllSolvers, requestAllInfo } from "../../redux"; -import React, { useEffect, useState, useRef } from "react"; export function useSolver(url, problemName, problemNameMap, problemInfoMap, problemInstance) { const state = {}; /// Maps each problem name to its default solver name. [state.defaultSolverMap] = useDefaultSolverMap(url, problemInfoMap); [state.solverOptions] = useSolverOptions(url, problemName); - [state.chosenSolver, state.setChosenSolver] = useChosenSolver(problemName, state.defaultSolverMap); + [state.chosenSolver, state.setChosenSolver] = useChosenSolver( + problemName, + state.defaultSolverMap, + ); [state.solverNameMap] = useSolverNameMap(url, problemNameMap); - [state.solvedInstance, state.setSolvedInstance] = useSolvedInstance(problemInstance, state.chosenSolver); + [state.solvedInstance, state.setSolvedInstance] = useSolvedInstance( + problemInstance, + state.chosenSolver, + ); return state; } @@ -72,29 +78,29 @@ function useDefaultSolverMap(url, problemInfoMap) { const [defaultSolverMap, setDefaultSolverMap] = useState(new Map()); useEffect(() => { - const problems = [...problemInfoMap.keys()]; - const defaultSolverNames = [...problemInfoMap.values()] - .map((info) => info?.defaultSolver?.solverName) - .filter(Boolean); - - (async () => { - const allSolvers = (await requestAllSolvers(url)) ?? {}; - const allInfo = (await requestAllInfo(url)) ?? {}; - - let map = new Map(); - for (const problem of problems) { - const solvers = allSolvers[problem] ?? []; - for (const s of solvers) { - const solver = s.split(" ")[0]; - const info = allInfo[solver]; - if (info && defaultSolverNames.includes(info.solverName)) { - map.set(problem, s); + const problems = [...problemInfoMap.keys()]; + const defaultSolverNames = [...problemInfoMap.values()] + .map((info) => info?.defaultSolver?.solverName) + .filter(Boolean); + + (async () => { + const allSolvers = (await requestAllSolvers(url)) ?? {}; + const allInfo = (await requestAllInfo(url)) ?? {}; + + let map = new Map(); + for (const problem of problems) { + const solvers = allSolvers[problem] ?? []; + for (const s of solvers) { + const solver = s.split(" ")[0]; + const info = allInfo[solver]; + if (info && defaultSolverNames.includes(info.solverName)) { + map.set(problem, s); + } } } - } - setDefaultSolverMap(map); - })(); -}, [url, problemInfoMap]); + setDefaultSolverMap(map); + })(); + }, [url, problemInfoMap]); return [defaultSolverMap, setDefaultSolverMap]; } @@ -136,7 +142,6 @@ function useChosenSolver(problemName, defaultSolverMap) { } setChosenSolver(solverVar); - }, [problemName, defaultSolverMap]); return [chosenSolver, setChosenSolver]; diff --git a/components/hooks/ProblemProvider/Verifier.js b/components/hooks/ProblemProvider/Verifier.js index 6c33d2b1..505c2a7d 100644 --- a/components/hooks/ProblemProvider/Verifier.js +++ b/components/hooks/ProblemProvider/Verifier.js @@ -1,12 +1,15 @@ +import React, { useEffect, useRef, useState } from "react"; +import { requestAllInfo, requestAllVerifiers } from "../../redux"; import { useGenericInfo } from "../ProblemProvider"; -import { requestAllVerifiers, requestAllInfo } from "../../redux"; -import React, { useEffect, useState, useRef } from "react"; export function useVerifier(url, problemName, problemNameMap, problemInfoMap) { const state = {}; [state.defaultVerifierMap] = useDefaultVerifierMap(url, problemInfoMap); [state.verifierOptions] = useVerifierOptions(url, problemName); - [state.chosenVerifier, state.setChosenVerifier] = useChosenVerifier(problemName, state.defaultVerifierMap); + [state.chosenVerifier, state.setChosenVerifier] = useChosenVerifier( + problemName, + state.defaultVerifierMap, + ); [state.verifierNameMap] = useVerifierNameMap(url, problemNameMap); return state; } @@ -79,7 +82,6 @@ function useChosenVerifier(problemName, defaultVerifierMap) { if (storedData) { const allData = JSON.parse(storedData); verifierVar = allData.verifier; - } isFirstRender.current = false; } diff --git a/components/hooks/ProblemProvider/index.js b/components/hooks/ProblemProvider/index.js index 9cceac42..68120a86 100644 --- a/components/hooks/ProblemProvider/index.js +++ b/components/hooks/ProblemProvider/index.js @@ -8,10 +8,10 @@ import React, { useEffect, useState } from "react"; import { requestInfo } from "../../redux"; import { useProblem } from "./Problem"; -import { useVerifier } from "./Verifier"; -import { useSolver } from "./Solver"; import { useReducer } from "./Reducer"; -import { useVisualization} from "./Visualization"; +import { useSolver } from "./Solver"; +import { useVerifier } from "./Verifier"; +import { useVisualization } from "./Visualization"; export function useProblemProvider(url) { const problem = useProblem(url); @@ -30,7 +30,7 @@ export function useGenericInfo(url, info) { useEffect(() => { (async () => { - setGenericInfo(!info ? {} : (await requestInfo(url, info)) ?? {}); + setGenericInfo(!info ? {} : ((await requestInfo(url, info)) ?? {})); })(); }, [info, url]); diff --git a/components/pageblocks/ProblemRowReact.js b/components/pageblocks/ProblemRowReact.js index 026489ef..9ba1d201 100644 --- a/components/pageblocks/ProblemRowReact.js +++ b/components/pageblocks/ProblemRowReact.js @@ -1,46 +1,63 @@ /** * ProblemRowReact.js - * + * * This component does the real grunt work of the ProblemRow component. It uses passed in props to style and provide default text for its objects, * uses and updates the global state for the problem and problem instance, and has a variety of listeners and API calls. - * + * * Essentialy, this is the brains of the ProblemRowReact.js component and deals with the GUI's Problem "Row" * @author Alex Diviney */ -import React, { useEffect, useState, useRef } from 'react' -import { useContext } from 'react'; -import 'bootstrap/dist/css/bootstrap.min.css' -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 React, { useContext, useEffect, useRef, useState } from "react"; +import "bootstrap/dist/css/bootstrap.min.css"; +import { + Download as DownloadIcon, + DragIndicator as DragIndicatorIcon, + Folder as FolderIcon, +} from "@mui/icons-material"; +import { Box, Button, IconButton, Stack, TextField } from "@mui/material"; +import ProblemInstanceParser from "../../Tools/ProblemInstanceParser"; +import { useProblemFilters } from "../hooks/ProblemFilters/useProblemFilters"; +import { useProblemIndex } from "../hooks/ProblemFilters/useProblemIndex"; +import { useProblemInfo } from "../hooks/ProblemProvider"; +import PopoverTooltipClick from "../widgets/PopoverTooltipClick"; +import ProblemFilterMenu from "../widgets/ProblemFilterMenu"; +import ProblemSection from "../widgets/ProblemSection"; +import SearchBarExtensible from "../widgets/SearchBarExtensible"; -import PopoverTooltipClick from '../widgets/PopoverTooltipClick'; -import ProblemFilterMenu from '../widgets/ProblemFilterMenu'; -import { useProblemInfo } from '../hooks/ProblemProvider' -import { useProblemIndex } from '../hooks/ProblemFilters/useProblemIndex'; -import { useProblemFilters } from '../hooks/ProblemFilters/useProblemFilters'; -import ProblemInstanceParser from '../../Tools/ProblemInstanceParser'; -import ProblemSection from '../widgets/ProblemSection'; -import SearchBarExtensible from '../widgets/SearchBarExtensible'; - -const ACCORDION_FORM_ONE = { placeHolder: "Select problem" } -const ACCORDION_FORM_TWO = { placeHolder: "default instance" } -var CARD = { cardBodyText: "Instance", cardHeaderText: "Problem", problemInstance: "" } -const TOOLTIP = { header: "Problem Information", formalDef: "Choose a problem to see information about it", info: "", credit: "" } +const ACCORDION_FORM_ONE = { placeHolder: "Select problem" }; +const ACCORDION_FORM_TWO = { placeHolder: "default instance" }; +var CARD = { cardBodyText: "Instance", cardHeaderText: "Problem", problemInstance: "" }; +const TOOLTIP = { + header: "Problem Information", + formalDef: "Choose a problem to see information about it", + info: "", + credit: "", +}; const THEME = { colors: { grey: "#424242", orange: "#d4441c" } }; // Display order for the dropdown's complexity-class sections. Unclassified // last -- it's the "not yet tagged" bucket, not a real complexity class. -const COMPLEXITY_CLASS_ORDER = ["P", "NPComplete", "NPHard", "NPIntermediate", "QuantumOracle", "Unclassified"]; +const COMPLEXITY_CLASS_ORDER = [ + "P", + "NPComplete", + "NPHard", + "NPIntermediate", + "QuantumOracle", + "Unclassified", +]; /** * 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, dragHandleProps }) { +export default function ProblemRowReact({ + url, + problemName, + setProblemName, + problemNameMap, + setProblemInstance, + dragHandleProps, +}) { const problemInfo = useProblemInfo(url, problemName); const { problemIndex, reductionGraph } = useProblemIndex(url); const { @@ -57,16 +74,14 @@ export default function ProblemRowReact({ url, problemName, setProblemName, prob // chain); intersect so a momentary population lag between the two can't put // an unlabeled option in the dropdown. const filteredProblemOptions = filteredProblems.filter((name) => problemNameMap.has(name)); - const [problemLocalInstance, setProblemLocalInstance] = useState("") + const [problemLocalInstance, setProblemLocalInstance] = useState(""); const defaultInstanceParsed = { test: true, input: "No Input, Default String", regex: "There is no regex string for this problem, parsing is likely not enabled", type: "No input, default string", - exampleStr: "" // No input, default string - - } - + exampleStr: "", // No input, default string + }; const [instanceParsed, setInstanceParsed] = useState(defaultInstanceParsed); const [seconds, setSeconds] = useState(1); @@ -74,9 +89,9 @@ export default function ProblemRowReact({ url, problemName, setProblemName, prob const isFirstRender = useRef(true); function openFileDialog() { - const input = document.createElement('input'); - input.type = 'file'; - input.accept = '.txt'; + const input = document.createElement("input"); + input.type = "file"; + input.accept = ".txt"; input.onchange = function (event) { const file = event.target.files[0]; @@ -94,9 +109,9 @@ export default function ProblemRowReact({ url, problemName, setProblemName, prob input.click(); } async function handleDownload() { - const blob = new Blob([problemLocalInstance], { type: 'text/plain' }); + const blob = new Blob([problemLocalInstance], { type: "text/plain" }); const url = URL.createObjectURL(blob); - const link = document.createElement('a'); + const link = document.createElement("a"); link.href = url; link.download = "query"; @@ -113,11 +128,12 @@ export default function ProblemRowReact({ url, problemName, setProblemName, prob timer = setInterval(() => { setSeconds(seconds + 1); if (seconds % 2 === 0) { - const cleanedInstance = problemLocalInstance.replaceAll(' ', '') - if (!cleanedInstance == '') { //Dont try to parse an empty string because it will fail and we dont want textbox to be red on empty input + const cleanedInstance = problemLocalInstance.replaceAll(" ", ""); + if (!cleanedInstance == "") { + //Dont try to parse an empty string because it will fail and we dont want textbox to be red on empty input const parser = new ProblemInstanceParser(); - const parsedOutput = parser.parse(problemName, cleanedInstance) - setInstanceParsed(parsedOutput) + const parsedOutput = parser.parse(problemName, cleanedInstance); + setInstanceParsed(parsedOutput); if (parsedOutput.test === true) { setProblemInstance(cleanedInstance); } @@ -126,9 +142,8 @@ export default function ProblemRowReact({ url, problemName, setProblemName, prob setSeconds(1); } }, 1000); - } - else { - clearInterval(timer) + } else { + clearInterval(timer); } // clearing interval return () => clearInterval(timer); @@ -153,23 +168,21 @@ export default function ProblemRowReact({ url, problemName, setProblemName, prob setProblemLocalInstance(problemVal); setProblemInstance(problemVal); - - }, [problemInfo, setProblemInstance]) + }, [problemInfo, setProblemInstance]); //Local state that handles problem instance change without triggering mass refreshing. const handleChangeInstance = (event) => { - setProblemLocalInstance(event.target.value) + setProblemLocalInstance(event.target.value); if (!instanceParsed.test) { defaultInstanceParsed.exampleStr = ""; } if (!timerIsActive) { setTimerActive(true); } - } + }; - const tip = - problemName - ? { + const tip = problemName + ? { header: problemInfo.problemName ?? "", formalDef: problemInfo.formalDefinition ?? "", // It makes description clean @@ -192,7 +205,7 @@ export default function ProblemRowReact({ url, problemName, setProblemName, prob sourceLink: problemInfo.sourceLink || "", isMathDef: true, // only this file adds the flag } - : TOOLTIP; + : TOOLTIP; return ( @@ -231,10 +244,10 @@ export default function ProblemRowReact({ url, problemName, setProblemName, prob size="small" title="Drag to reorder" sx={{ - cursor: 'grab', - color: '#424242', - backgroundColor: '#f5f5f5', - '&:hover': { backgroundColor: '#e0e0e0' }, + cursor: "grab", + color: "#424242", + backgroundColor: "#f5f5f5", + "&:hover": { backgroundColor: "#e0e0e0" }, mr: 1, }} > @@ -257,12 +270,14 @@ export default function ProblemRowReact({ url, problemName, setProblemName, prob sx={{ width: "100%" }} value={problemLocalInstance} onChange={handleChangeInstance} - helperText={!instanceParsed.test ? "Problem failed? Try: " + instanceParsed.exampleStr : ""} // Only displays the "Incorrect format" stuff when the input is activly wrong + helperText={ + !instanceParsed.test ? "Problem failed? Try: " + instanceParsed.exampleStr : "" + } // Only displays the "Incorrect format" stuff when the input is activly wrong className="hide-scrollbar" multiline maxRows={5} > -
+
- ) - } - )} - + return ( + + + + + {/**This is the REDUX LOGO Component. */} + + REDUX + + + + + {pages.map((page) => { + var currentHref = page.toLowerCase(); - - - - ); + if (currentHref === "home") { + currentHref = ""; + } else { + currentHref = currentHref.replace(" ", ""); + } + return ( + + ); + })} + + + + + ); }; -export default ResponsiveAppBar; \ No newline at end of file +export default ResponsiveAppBar; diff --git a/components/widgets/SearchBarExtensible.js b/components/widgets/SearchBarExtensible.js index d40f84db..f3b4cae0 100644 --- a/components/widgets/SearchBarExtensible.js +++ b/components/widgets/SearchBarExtensible.js @@ -1,5 +1,5 @@ +import { Autocomplete, Button, Divider, ListSubheader, Paper, TextField } from "@mui/material"; import React, { useState } from "react"; -import { Autocomplete, TextField, Paper, Divider, Button, ListSubheader } from "@mui/material"; export default function SearchBarExtensible({ selected, @@ -37,28 +37,34 @@ export default function SearchBarExtensible({ onInputChange={(event, value) => { setInput(value ?? ""); }} - value={disabled ? disabledMessage : optionsMap.get(selected) ?? ""} + value={disabled ? disabledMessage : (optionsMap.get(selected) ?? "")} onChange={(event, value) => { value = getKeyByValue(optionsMap, value) ?? ""; if (value === "" || options.includes(value)) { onSelect(value); } }} - options={Array.isArray(options) - ? [...options] - .sort((a, b) => sortOptions(a, b, { groupBy, groupOrder, optionsHighlight })) - .map((x) => optionsMap.get(x) ?? x) - : []} - groupBy={groupBy ? (option) => groupBy(getKeyByValue(optionsMap, option)) ?? "Unclassified" : undefined} + options={ + Array.isArray(options) + ? [...options] + .sort((a, b) => sortOptions(a, b, { groupBy, groupOrder, optionsHighlight })) + .map((x) => optionsMap.get(x) ?? x) + : [] + } + groupBy={ + groupBy + ? (option) => groupBy(getKeyByValue(optionsMap, option)) ?? "Unclassified" + : undefined + } // Bolds the group header so it reads as a section divider, not a selectable option. renderGroup={ groupBy ? (params) => ( -
  • - {params.group} - {params.children} -
  • - ) +
  • + {params.group} + {params.children} +
  • + ) : undefined } getOptionDisabled={ @@ -93,16 +99,16 @@ export default function SearchBarExtensible({ renderOption={ optionsHighlight || optionsDisabled ? (props, option) => { - const key = getKeyByValue(optionsMap, option); - const isDeemphasized = optionsHighlight ? !optionsHighlight.includes(key) : false; - const isDisabledOption = optionsDisabled ? optionsDisabled.includes(key) : false; - return ( -
  • - {option} - {isDisabledOption && disabledOptionHint ? ` (${disabledOptionHint})` : ""} -
  • - ); - } + const key = getKeyByValue(optionsMap, option); + const isDeemphasized = optionsHighlight ? !optionsHighlight.includes(key) : false; + const isDisabledOption = optionsDisabled ? optionsDisabled.includes(key) : false; + return ( +
  • + {option} + {isDisabledOption && disabledOptionHint ? ` (${disabledOptionHint})` : ""} +
  • + ); + } : null } /> diff --git a/components/widgets/ShareButton.js b/components/widgets/ShareButton.js index de4ba762..3a193328 100644 --- a/components/widgets/ShareButton.js +++ b/components/widgets/ShareButton.js @@ -1,63 +1,65 @@ -import React from 'react'; -import Button from 'react-bootstrap/Button'; -import { Share as ShareIcon } from '@mui/icons-material'; +import { Share as ShareIcon } from "@mui/icons-material"; +import React from "react"; +import Button from "react-bootstrap/Button"; const THEME = { colors: { grey: "#424242", orange: "#d4441c" } }; const createShareLink = (baseUrl, data) => { - const params = new URLSearchParams(data).toString(); - return `${baseUrl}?${params}`; + const params = new URLSearchParams(data).toString(); + return `${baseUrl}?${params}`; }; const handleShare = async (problem, solver, verifier, reducer) => { + const data = { + problem: problem.problemName ?? "", + instance: problem.problemInstance ?? "", + solver: solver.chosenSolver ?? "", + reduceTo: reducer.chosenReduceTo ?? "", + reductionType: reducer.chosenReductionType ?? "", + verifier: verifier.chosenVerifier ?? "", + }; - const data = { - problem: problem.problemName ?? "", - instance: problem.problemInstance ?? "", - solver: solver.chosenSolver ?? "", - reduceTo: reducer.chosenReduceTo ?? "", - reductionType: reducer.chosenReductionType ?? "", - verifier: verifier.chosenVerifier ?? "", - }; - - localStorage.setItem('problemData', JSON.stringify(data)); - // Create the share URL with the parameters - const shareUrl = createShareLink(window.location.origin + window.location.pathname, data); + localStorage.setItem("problemData", JSON.stringify(data)); + // Create the share URL with the parameters + const shareUrl = createShareLink(window.location.origin + window.location.pathname, data); - if (navigator.share) { - try { - await navigator.share({ - title: 'Check this out!', - text: 'Here is some interesting content.', - url: shareUrl - }); - console.log('Content shared successfully'); - } catch (error) { - console.error('Error sharing content:', error); - } - } else { - // Fallback: Copy the URL to the clipboard - try { - await navigator.clipboard.writeText(shareUrl); - alert('Web Share API is not supported in your browser. The share URL has been copied to your clipboard.'); - } catch (error) { - console.error('Error copying URL to clipboard:', error); - alert('Failed to copy the share URL to the clipboard.'); - } + if (navigator.share) { + try { + await navigator.share({ + title: "Check this out!", + text: "Here is some interesting content.", + url: shareUrl, + }); + console.log("Content shared successfully"); + } catch (error) { + console.error("Error sharing content:", error); } + } else { + // Fallback: Copy the URL to the clipboard + try { + await navigator.clipboard.writeText(shareUrl); + alert( + "Web Share API is not supported in your browser. The share URL has been copied to your clipboard.", + ); + } catch (error) { + console.error("Error copying URL to clipboard:", error); + alert("Failed to copy the share URL to the clipboard."); + } + } }; const ShareButton = ({ problem, solver, verifier, reducer }) => ( - + onClick={() => handleShare(problem, solver, verifier, reducer)} + > + + ); -export default ShareButton; \ No newline at end of file +export default ShareButton; diff --git a/components/widgets/TextBox.js b/components/widgets/TextBox.js index 152f5de6..e5da7211 100644 --- a/components/widgets/TextBox.js +++ b/components/widgets/TextBox.js @@ -1,14 +1,19 @@ -import React from 'react'; +import React from "react"; //This function exports the skeleton of a Textbox. The structure of the required prop object is very arbitary and subject to change function TextBox(props) { - return(
    - - - -
    ) + return ( +
    + + +
    + ); } -export default TextBox \ No newline at end of file +export default TextBox; diff --git a/components/widgets/VisualizationLogic.js b/components/widgets/VisualizationLogic.js index 4a11560b..289e903c 100644 --- a/components/widgets/VisualizationLogic.js +++ b/components/widgets/VisualizationLogic.js @@ -1,13 +1,12 @@ // This is a holder for visualizations that passes down urls based on switch data. - -import Split from 'react-split' -import { useEffect, useState } from 'react'; -import { Container } from '@mui/material'; -import { No_Renderable_Viz_Svg, Viz_Render_Error_Svg } from '../Visualization/svgs/No_Viz_SVG'; -import Visualizations from '../Visualization/svgs/Visualizations.js' -import { isRenderable } from '../Visualization/svgs/renderability'; -import { remapIdsDeep, makeIdsUnique, processReductions } from '../redux'; +import { Container } from "@mui/material"; +import { useEffect, useState } from "react"; +import Split from "react-split"; +import { makeIdsUnique, processReductions, remapIdsDeep } from "../redux"; +import { No_Renderable_Viz_Svg, Viz_Render_Error_Svg } from "../Visualization/svgs/No_Viz_SVG"; +import { isRenderable } from "../Visualization/svgs/renderability"; +import Visualizations from "../Visualization/svgs/Visualizations.js"; export default function VisualizationLogic({ url, @@ -28,133 +27,132 @@ export default function VisualizationLogic({ let visualization; let reducedVisualization; - const solve = visualizationState.solverOn + const solve = visualizationState.solverOn; - const handleBar = () => { } + const handleBar = () => {}; const [mappedProblemData, setMappedProblemData] = useState(null); const [mappedReductionData, setMappedReductionData] = useState(null); useEffect(() => { if (problemInstance && problemData) { - processReductions(url, chosenReductionType, problemInstance) - .then(rawGadgetMap => { - const { gadgets, fromIdMap } = makeIdsUnique(rawGadgetMap); - setGadgetMap(gadgets); - setMappedProblemData(remapIdsDeep(problemData, fromIdMap) || problemData); - }) + processReductions(url, chosenReductionType, problemInstance).then((rawGadgetMap) => { + const { gadgets, fromIdMap } = makeIdsUnique(rawGadgetMap); + setGadgetMap(gadgets); + setMappedProblemData(remapIdsDeep(problemData, fromIdMap) || problemData); + }); } }, [problemData, url, chosenReductionType, problemInstance]); -useEffect(() => { - if (visualizationState.reductionOn && reductionVisualization && url && problemInstance) { - processReductions(url, chosenReductionType, problemInstance) - .then(rawGadgetMap => { + useEffect(() => { + if (visualizationState.reductionOn && reductionVisualization && url && problemInstance) { + processReductions(url, chosenReductionType, problemInstance).then((rawGadgetMap) => { const { gadgets, toIdMap } = makeIdsUnique(rawGadgetMap); setGadgetMap(gadgets); setMappedReductionData(remapIdsDeep(reductionData, toIdMap) || reductionData); - }) - } -}, [ - visualizationState.reductionOn, - reductionVisualization, - chosenReductionType, - problemInstance, - reductionData, - url -]); - - -if (url && problemInstance && mappedProblemData && Object.keys(mappedProblemData).length > 0) { - if (!isRenderable(visualizationType)) { - visualization = ( - - ) - } else { - try { - visualization = Visualizations.get(visualizationType)(solve, url, mappedProblemData, gadgetMap, visualizationState.gadgetsOn) - } catch (err) { - console.error("visualization renderer threw", visualizationType, err) + }); + } + }, [ + visualizationState.reductionOn, + reductionVisualization, + chosenReductionType, + problemInstance, + reductionData, + url, + ]); + + if (url && problemInstance && mappedProblemData && Object.keys(mappedProblemData).length > 0) { + if (!isRenderable(visualizationType)) { visualization = ( - - ) - } - } - - if (visualizationState.reductionOn) { - if (!isRenderable(reductionVisualization)) { - reducedVisualization = ( - - ) + ); } else { try { - reducedVisualization = Visualizations.get(reductionVisualization)(solve, url, mappedReductionData, gadgetMap, visualizationState.gadgetsOn) - - //NOTE - Caleb, The following is a temporary fix until CLIQUE_SVG_REACT.js is fixed, currently it takes the 3sat instance, - // but should take the clique instance, once that is fixed the following code block should be able to be removed without issue - if (reductionName == "CLIQUE") { - //reducedVisualization = ReducedVisualizations.get(chosenReductionType)(solve, url, problemInstance, mappedSolution) - } - + visualization = Visualizations.get(visualizationType)( + solve, + url, + mappedProblemData, + gadgetMap, + visualizationState.gadgetsOn, + ); } catch (err) { - console.error("reduction visualization renderer threw", reductionVisualization, err) - reducedVisualization = ( + console.error("visualization renderer threw", visualizationType, err); + visualization = ( + ); + } + } + + if (visualizationState.reductionOn) { + if (!isRenderable(reductionVisualization)) { + reducedVisualization = ( + - ) + ); + } else { + try { + reducedVisualization = Visualizations.get(reductionVisualization)( + solve, + url, + mappedReductionData, + gadgetMap, + visualizationState.gadgetsOn, + ); + + //NOTE - Caleb, The following is a temporary fix until CLIQUE_SVG_REACT.js is fixed, currently it takes the 3sat instance, + // but should take the clique instance, once that is fixed the following code block should be able to be removed without issue + if (reductionName == "CLIQUE") { + //reducedVisualization = ReducedVisualizations.get(chosenReductionType)(solve, url, problemInstance, mappedSolution) + } + } catch (err) { + console.error("reduction visualization renderer threw", reductionVisualization, err); + reducedVisualization = ( + + ); + } } } } -} - - -if (!visualizationState.reductionOn && !loading) { - return ( - <> - - {visualization} - - - ) -} -else if (visualizationState.reductionOn && !loading) { - return ( - <> - - - {/* {"Container1"} */} - {visualization} - - - - {/* {"Container2"} */} - {reducedVisualization} - - - - - ) -} + if (!visualizationState.reductionOn && !loading) { + return ( + <> + {visualization} + + ); + } else if (visualizationState.reductionOn && !loading) { + return ( + <> + + + {/* {"Container1"} */} + {visualization} + + + + {/* {"Container2"} */} + {reducedVisualization} + + + + ); + } -return ( - <> - -) + return <>; } diff --git a/eslint.config.js b/eslint.config.js index d80fa83c..8c10714e 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -37,23 +37,14 @@ const importX = require("eslint-plugin-import-x"); const prettier = require("eslint-config-prettier"); // eslint-config-next exports an array of flat-config objects; spread it in. -const nextConfigs = Array.isArray(nextCoreWebVitals) - ? nextCoreWebVitals - : [nextCoreWebVitals]; +const nextConfigs = Array.isArray(nextCoreWebVitals) ? nextCoreWebVitals : [nextCoreWebVitals]; module.exports = [ // 1. Ignore what isn't ours. A config object with ONLY `ignores` is global. // public/ holds the vendored Q.js library + static assets — linting it produced 38 // spurious no-undef errors, so it's excluded here. { - ignores: [ - "node_modules/**", - ".next/**", - "out/**", - "build/**", - "public/**", - "next-env.d.ts", - ], + ignores: ["node_modules/**", ".next/**", "out/**", "build/**", "public/**", "next-env.d.ts"], }, // 2. Base JavaScript correctness — the previously-missing floor. diff --git a/next.config.js b/next.config.js index 195edde6..3e616343 100644 --- a/next.config.js +++ b/next.config.js @@ -1,6 +1,6 @@ /** @type {import('next').NextConfig} */ module.exports = { - output: 'standalone', + output: "standalone", reactStrictMode: true, - transpilePackages: ['@mui/material', '@mui/icons-material', '@mui/system'], -} + transpilePackages: ["@mui/material", "@mui/icons-material", "@mui/system"], +}; diff --git a/pages/Data.json b/pages/Data.json index acea7146..ca106a03 100644 --- a/pages/Data.json +++ b/pages/Data.json @@ -1,1002 +1,1002 @@ [ - { - "author": "Chinua Achebe", - "country": "Nigeria", - "imageLink": "images/things-fall-apart.jpg", - "language": "English", - "link": "https://en.wikipedia.org/wiki/Things_Fall_Apart\n", - "pages": 209, - "title": "Things Fall Apart", - "year": 1958 - }, - { - "author": "Hans Christian Andersen", - "country": "Denmark", - "imageLink": "images/fairy-tales.jpg", - "language": "Danish", - "link": "https://en.wikipedia.org/wiki/Fairy_Tales_Told_for_Children._First_Collection.\n", - "pages": 784, - "title": "Fairy tales", - "year": 1836 - }, - { - "author": "Dante Alighieri", - "country": "Italy", - "imageLink": "images/the-divine-comedy.jpg", - "language": "Italian", - "link": "https://en.wikipedia.org/wiki/Divine_Comedy\n", - "pages": 928, - "title": "The Divine Comedy", - "year": 1315 - }, - { - "author": "Unknown", - "country": "Sumer and Akkadian Empire", - "imageLink": "images/the-epic-of-gilgamesh.jpg", - "language": "Akkadian", - "link": "https://en.wikipedia.org/wiki/Epic_of_Gilgamesh\n", - "pages": 160, - "title": "The Epic Of Gilgamesh", - "year": -1700 - }, - { - "author": "Unknown", - "country": "Achaemenid Empire", - "imageLink": "images/the-book-of-job.jpg", - "language": "Hebrew", - "link": "https://en.wikipedia.org/wiki/Book_of_Job\n", - "pages": 176, - "title": "The Book Of Job", - "year": -600 - }, - { - "author": "Unknown", - "country": "India/Iran/Iraq/Egypt/Tajikistan", - "imageLink": "images/one-thousand-and-one-nights.jpg", - "language": "Arabic", - "link": "https://en.wikipedia.org/wiki/One_Thousand_and_One_Nights\n", - "pages": 288, - "title": "One Thousand and One Nights", - "year": 1200 - }, - { - "author": "Unknown", - "country": "Iceland", - "imageLink": "images/njals-saga.jpg", - "language": "Old Norse", - "link": "https://en.wikipedia.org/wiki/Nj%C3%A1ls_saga\n", - "pages": 384, - "title": "Nj\u00e1l's Saga", - "year": 1350 - }, - { - "author": "Jane Austen", - "country": "United Kingdom", - "imageLink": "images/pride-and-prejudice.jpg", - "language": "English", - "link": "https://en.wikipedia.org/wiki/Pride_and_Prejudice\n", - "pages": 226, - "title": "Pride and Prejudice", - "year": 1813 - }, - { - "author": "Honor\u00e9 de Balzac", - "country": "France", - "imageLink": "images/le-pere-goriot.jpg", - "language": "French", - "link": "https://en.wikipedia.org/wiki/Le_P%C3%A8re_Goriot\n", - "pages": 443, - "title": "Le P\u00e8re Goriot", - "year": 1835 - }, - { - "author": "Samuel Beckett", - "country": "Republic of Ireland", - "imageLink": "images/molloy-malone-dies-the-unnamable.jpg", - "language": "French, English", - "link": "https://en.wikipedia.org/wiki/Molloy_(novel)\n", - "pages": 256, - "title": "Molloy, Malone Dies, The Unnamable, the trilogy", - "year": 1952 - }, - { - "author": "Giovanni Boccaccio", - "country": "Italy", - "imageLink": "images/the-decameron.jpg", - "language": "Italian", - "link": "https://en.wikipedia.org/wiki/The_Decameron\n", - "pages": 1024, - "title": "The Decameron", - "year": 1351 - }, - { - "author": "Jorge Luis Borges", - "country": "Argentina", - "imageLink": "images/ficciones.jpg", - "language": "Spanish", - "link": "https://en.wikipedia.org/wiki/Ficciones\n", - "pages": 224, - "title": "Ficciones", - "year": 1965 - }, - { - "author": "Emily Bront\u00eb", - "country": "United Kingdom", - "imageLink": "images/wuthering-heights.jpg", - "language": "English", - "link": "https://en.wikipedia.org/wiki/Wuthering_Heights\n", - "pages": 342, - "title": "Wuthering Heights", - "year": 1847 - }, - { - "author": "Albert Camus", - "country": "Algeria, French Empire", - "imageLink": "images/l-etranger.jpg", - "language": "French", - "link": "https://en.wikipedia.org/wiki/The_Stranger_(novel)\n", - "pages": 185, - "title": "The Stranger", - "year": 1942 - }, - { - "author": "Paul Celan", - "country": "Romania, France", - "imageLink": "images/poems-paul-celan.jpg", - "language": "German", - "link": "\n", - "pages": 320, - "title": "Poems", - "year": 1952 - }, - { - "author": "Louis-Ferdinand C\u00e9line", - "country": "France", - "imageLink": "images/voyage-au-bout-de-la-nuit.jpg", - "language": "French", - "link": "https://en.wikipedia.org/wiki/Journey_to_the_End_of_the_Night\n", - "pages": 505, - "title": "Journey to the End of the Night", - "year": 1932 - }, - { - "author": "Miguel de Cervantes", - "country": "Spain", - "imageLink": "images/don-quijote-de-la-mancha.jpg", - "language": "Spanish", - "link": "https://en.wikipedia.org/wiki/Don_Quixote\n", - "pages": 1056, - "title": "Don Quijote De La Mancha", - "year": 1610 - }, - { - "author": "Geoffrey Chaucer", - "country": "England", - "imageLink": "images/the-canterbury-tales.jpg", - "language": "English", - "link": "https://en.wikipedia.org/wiki/The_Canterbury_Tales\n", - "pages": 544, - "title": "The Canterbury Tales", - "year": 1450 - }, - { - "author": "Anton Chekhov", - "country": "Russia", - "imageLink": "images/stories-of-anton-chekhov.jpg", - "language": "Russian", - "link": "https://en.wikipedia.org/wiki/List_of_short_stories_by_Anton_Chekhov\n", - "pages": 194, - "title": "Stories", - "year": 1886 - }, - { - "author": "Joseph Conrad", - "country": "United Kingdom", - "imageLink": "images/nostromo.jpg", - "language": "English", - "link": "https://en.wikipedia.org/wiki/Nostromo\n", - "pages": 320, - "title": "Nostromo", - "year": 1904 - }, - { - "author": "Charles Dickens", - "country": "United Kingdom", - "imageLink": "images/great-expectations.jpg", - "language": "English", - "link": "https://en.wikipedia.org/wiki/Great_Expectations\n", - "pages": 194, - "title": "Great Expectations", - "year": 1861 - }, - { - "author": "Denis Diderot", - "country": "France", - "imageLink": "images/jacques-the-fatalist.jpg", - "language": "French", - "link": "https://en.wikipedia.org/wiki/Jacques_the_Fatalist\n", - "pages": 596, - "title": "Jacques the Fatalist", - "year": 1796 - }, - { - "author": "Alfred D\u00f6blin", - "country": "Germany", - "imageLink": "images/berlin-alexanderplatz.jpg", - "language": "German", - "link": "https://en.wikipedia.org/wiki/Berlin_Alexanderplatz\n", - "pages": 600, - "title": "Berlin Alexanderplatz", - "year": 1929 - }, - { - "author": "Fyodor Dostoevsky", - "country": "Russia", - "imageLink": "images/crime-and-punishment.jpg", - "language": "Russian", - "link": "https://en.wikipedia.org/wiki/Crime_and_Punishment\n", - "pages": 551, - "title": "Crime and Punishment", - "year": 1866 - }, - { - "author": "Fyodor Dostoevsky", - "country": "Russia", - "imageLink": "images/the-idiot.jpg", - "language": "Russian", - "link": "https://en.wikipedia.org/wiki/The_Idiot\n", - "pages": 656, - "title": "The Idiot", - "year": 1869 - }, - { - "author": "Fyodor Dostoevsky", - "country": "Russia", - "imageLink": "images/the-possessed.jpg", - "language": "Russian", - "link": "https://en.wikipedia.org/wiki/Demons_(Dostoyevsky_novel)\n", - "pages": 768, - "title": "The Possessed", - "year": 1872 - }, - { - "author": "Fyodor Dostoevsky", - "country": "Russia", - "imageLink": "images/the-brothers-karamazov.jpg", - "language": "Russian", - "link": "https://en.wikipedia.org/wiki/The_Brothers_Karamazov\n", - "pages": 824, - "title": "The Brothers Karamazov", - "year": 1880 - }, - { - "author": "George Eliot", - "country": "United Kingdom", - "imageLink": "images/middlemarch.jpg", - "language": "English", - "link": "https://en.wikipedia.org/wiki/Middlemarch\n", - "pages": 800, - "title": "Middlemarch", - "year": 1871 - }, - { - "author": "Ralph Ellison", - "country": "United States", - "imageLink": "images/invisible-man.jpg", - "language": "English", - "link": "https://en.wikipedia.org/wiki/Invisible_Man\n", - "pages": 581, - "title": "Invisible Man", - "year": 1952 - }, - { - "author": "Euripides", - "country": "Greece", - "imageLink": "images/medea.jpg", - "language": "Greek", - "link": "https://en.wikipedia.org/wiki/Medea_(play)\n", - "pages": 104, - "title": "Medea", - "year": -431 - }, - { - "author": "William Faulkner", - "country": "United States", - "imageLink": "images/absalom-absalom.jpg", - "language": "English", - "link": "https://en.wikipedia.org/wiki/Absalom,_Absalom!\n", - "pages": 313, - "title": "Absalom, Absalom!", - "year": 1936 - }, - { - "author": "William Faulkner", - "country": "United States", - "imageLink": "images/the-sound-and-the-fury.jpg", - "language": "English", - "link": "https://en.wikipedia.org/wiki/The_Sound_and_the_Fury\n", - "pages": 326, - "title": "The Sound and the Fury", - "year": 1929 - }, - { - "author": "Gustave Flaubert", - "country": "France", - "imageLink": "images/madame-bovary.jpg", - "language": "French", - "link": "https://en.wikipedia.org/wiki/Madame_Bovary\n", - "pages": 528, - "title": "Madame Bovary", - "year": 1857 - }, - { - "author": "Gustave Flaubert", - "country": "France", - "imageLink": "images/l-education-sentimentale.jpg", - "language": "French", - "link": "https://en.wikipedia.org/wiki/Sentimental_Education\n", - "pages": 606, - "title": "Sentimental Education", - "year": 1869 - }, - { - "author": "Federico Garc\u00eda Lorca", - "country": "Spain", - "imageLink": "images/gypsy-ballads.jpg", - "language": "Spanish", - "link": "https://en.wikipedia.org/wiki/Gypsy_Ballads\n", - "pages": 218, - "title": "Gypsy Ballads", - "year": 1928 - }, - { - "author": "Gabriel Garc\u00eda M\u00e1rquez", - "country": "Colombia", - "imageLink": "images/one-hundred-years-of-solitude.jpg", - "language": "Spanish", - "link": "https://en.wikipedia.org/wiki/One_Hundred_Years_of_Solitude\n", - "pages": 417, - "title": "One Hundred Years of Solitude", - "year": 1967 - }, - { - "author": "Gabriel Garc\u00eda M\u00e1rquez", - "country": "Colombia", - "imageLink": "images/love-in-the-time-of-cholera.jpg", - "language": "Spanish", - "link": "https://en.wikipedia.org/wiki/Love_in_the_Time_of_Cholera\n", - "pages": 368, - "title": "Love in the Time of Cholera", - "year": 1985 - }, - { - "author": "Johann Wolfgang von Goethe", - "country": "Saxe-Weimar", - "imageLink": "images/faust.jpg", - "language": "German", - "link": "https://en.wikipedia.org/wiki/Goethe%27s_Faust\n", - "pages": 158, - "title": "Faust", - "year": 1832 - }, - { - "author": "Nikolai Gogol", - "country": "Russia", - "imageLink": "images/dead-souls.jpg", - "language": "Russian", - "link": "https://en.wikipedia.org/wiki/Dead_Souls\n", - "pages": 432, - "title": "Dead Souls", - "year": 1842 - }, - { - "author": "G\u00fcnter Grass", - "country": "Germany", - "imageLink": "images/the-tin-drum.jpg", - "language": "German", - "link": "https://en.wikipedia.org/wiki/The_Tin_Drum\n", - "pages": 600, - "title": "The Tin Drum", - "year": 1959 - }, - { - "author": "Jo\u00e3o Guimar\u00e3es Rosa", - "country": "Brazil", - "imageLink": "images/the-devil-to-pay-in-the-backlands.jpg", - "language": "Portuguese", - "link": "https://en.wikipedia.org/wiki/The_Devil_to_Pay_in_the_Backlands\n", - "pages": 494, - "title": "The Devil to Pay in the Backlands", - "year": 1956 - }, - { - "author": "Knut Hamsun", - "country": "Norway", - "imageLink": "images/hunger.jpg", - "language": "Norwegian", - "link": "https://en.wikipedia.org/wiki/Hunger_(Hamsun_novel)\n", - "pages": 176, - "title": "Hunger", - "year": 1890 - }, - { - "author": "Ernest Hemingway", - "country": "United States", - "imageLink": "images/the-old-man-and-the-sea.jpg", - "language": "English", - "link": "https://en.wikipedia.org/wiki/The_Old_Man_and_the_Sea\n", - "pages": 128, - "title": "The Old Man and the Sea", - "year": 1952 - }, - { - "author": "Homer", - "country": "Greece", - "imageLink": "images/the-iliad-of-homer.jpg", - "language": "Greek", - "link": "https://en.wikipedia.org/wiki/Iliad\n", - "pages": 608, - "title": "Iliad", - "year": -735 - }, - { - "author": "Homer", - "country": "Greece", - "imageLink": "images/the-odyssey-of-homer.jpg", - "language": "Greek", - "link": "https://en.wikipedia.org/wiki/Odyssey\n", - "pages": 374, - "title": "Odyssey", - "year": -800 - }, - { - "author": "Henrik Ibsen", - "country": "Norway", - "imageLink": "images/a-Dolls-house.jpg", - "language": "Norwegian", - "link": "https://en.wikipedia.org/wiki/A_Doll%27s_House\n", - "pages": 68, - "title": "A Doll's House", - "year": 1879 - }, - { - "author": "James Joyce", - "country": "Irish Free State", - "imageLink": "images/ulysses.jpg", - "language": "English", - "link": "https://en.wikipedia.org/wiki/Ulysses_(novel)\n", - "pages": 228, - "title": "Ulysses", - "year": 1922 - }, - { - "author": "Franz Kafka", - "country": "Czechoslovakia", - "imageLink": "images/stories-of-franz-kafka.jpg", - "language": "German", - "link": "https://en.wikipedia.org/wiki/Franz_Kafka_bibliography#Short_stories\n", - "pages": 488, - "title": "Stories", - "year": 1924 - }, - { - "author": "Franz Kafka", - "country": "Czechoslovakia", - "imageLink": "images/the-trial.jpg", - "language": "German", - "link": "https://en.wikipedia.org/wiki/The_Trial\n", - "pages": 160, - "title": "The Trial", - "year": 1925 - }, - { - "author": "Franz Kafka", - "country": "Czechoslovakia", - "imageLink": "images/the-castle.jpg", - "language": "German", - "link": "https://en.wikipedia.org/wiki/The_Castle_(novel)\n", - "pages": 352, - "title": "The Castle", - "year": 1926 - }, - { - "author": "K\u0101lid\u0101sa", - "country": "India", - "imageLink": "images/the-recognition-of-shakuntala.jpg", - "language": "Sanskrit", - "link": "https://en.wikipedia.org/wiki/Abhij%C3%B1%C4%81na%C5%9B%C4%81kuntalam\n", - "pages": 147, - "title": "The recognition of Shakuntala", - "year": 150 - }, - { - "author": "Yasunari Kawabata", - "country": "Japan", - "imageLink": "images/the-sound-of-the-mountain.jpg", - "language": "Japanese", - "link": "https://en.wikipedia.org/wiki/The_Sound_of_the_Mountain\n", - "pages": 288, - "title": "The Sound of the Mountain", - "year": 1954 - }, - { - "author": "Nikos Kazantzakis", - "country": "Greece", - "imageLink": "images/zorba-the-greek.jpg", - "language": "Greek", - "link": "https://en.wikipedia.org/wiki/Zorba_the_Greek\n", - "pages": 368, - "title": "Zorba the Greek", - "year": 1946 - }, - { - "author": "D. H. Lawrence", - "country": "United Kingdom", - "imageLink": "images/sons-and-lovers.jpg", - "language": "English", - "link": "https://en.wikipedia.org/wiki/Sons_and_Lovers\n", - "pages": 432, - "title": "Sons and Lovers", - "year": 1913 - }, - { - "author": "Halld\u00f3r Laxness", - "country": "Iceland", - "imageLink": "images/independent-people.jpg", - "language": "Icelandic", - "link": "https://en.wikipedia.org/wiki/Independent_People\n", - "pages": 470, - "title": "Independent People", - "year": 1934 - }, - { - "author": "Giacomo Leopardi", - "country": "Italy", - "imageLink": "images/poems-giacomo-leopardi.jpg", - "language": "Italian", - "link": "\n", - "pages": 184, - "title": "Poems", - "year": 1818 - }, - { - "author": "Doris Lessing", - "country": "United Kingdom", - "imageLink": "images/the-golden-notebook.jpg", - "language": "English", - "link": "https://en.wikipedia.org/wiki/The_Golden_Notebook\n", - "pages": 688, - "title": "The Golden Notebook", - "year": 1962 - }, - { - "author": "Astrid Lindgren", - "country": "Sweden", - "imageLink": "images/pippi-longstocking.jpg", - "language": "Swedish", - "link": "https://en.wikipedia.org/wiki/Pippi_Longstocking\n", - "pages": 160, - "title": "Pippi Longstocking", - "year": 1945 - }, - { - "author": "Lu Xun", - "country": "China", - "imageLink": "images/diary-of-a-madman.jpg", - "language": "Chinese", - "link": "https://en.wikipedia.org/wiki/A_Madman%27s_Diary\n", - "pages": 389, - "title": "Diary of a Madman", - "year": 1918 - }, - { - "author": "Naguib Mahfouz", - "country": "Egypt", - "imageLink": "images/children-of-gebelawi.jpg", - "language": "Arabic", - "link": "https://en.wikipedia.org/wiki/Children_of_Gebelawi\n", - "pages": 355, - "title": "Children of Gebelawi", - "year": 1959 - }, - { - "author": "Thomas Mann", - "country": "Germany", - "imageLink": "images/buddenbrooks.jpg", - "language": "German", - "link": "https://en.wikipedia.org/wiki/Buddenbrooks\n", - "pages": 736, - "title": "Buddenbrooks", - "year": 1901 - }, - { - "author": "Thomas Mann", - "country": "Germany", - "imageLink": "images/the-magic-mountain.jpg", - "language": "German", - "link": "https://en.wikipedia.org/wiki/The_Magic_Mountain\n", - "pages": 720, - "title": "The Magic Mountain", - "year": 1924 - }, - { - "author": "Herman Melville", - "country": "United States", - "imageLink": "images/moby-dick.jpg", - "language": "English", - "link": "https://en.wikipedia.org/wiki/Moby-Dick\n", - "pages": 378, - "title": "Moby Dick", - "year": 1851 - }, - { - "author": "Michel de Montaigne", - "country": "France", - "imageLink": "images/essais.jpg", - "language": "French", - "link": "https://en.wikipedia.org/wiki/Essays_(Montaigne)\n", - "pages": 404, - "title": "Essays", - "year": 1595 - }, - { - "author": "Elsa Morante", - "country": "Italy", - "imageLink": "images/history.jpg", - "language": "Italian", - "link": "https://en.wikipedia.org/wiki/History_(novel)\n", - "pages": 600, - "title": "History", - "year": 1974 - }, - { - "author": "Toni Morrison", - "country": "United States", - "imageLink": "images/beloved.jpg", - "language": "English", - "link": "https://en.wikipedia.org/wiki/Beloved_(novel)\n", - "pages": 321, - "title": "Beloved", - "year": 1987 - }, - { - "author": "Murasaki Shikibu", - "country": "Japan", - "imageLink": "images/the-tale-of-genji.jpg", - "language": "Japanese", - "link": "https://en.wikipedia.org/wiki/The_Tale_of_Genji\n", - "pages": 1360, - "title": "The Tale of Genji", - "year": 1006 - }, - { - "author": "Robert Musil", - "country": "Austria", - "imageLink": "images/the-man-without-qualities.jpg", - "language": "German", - "link": "https://en.wikipedia.org/wiki/The_Man_Without_Qualities\n", - "pages": 365, - "title": "The Man Without Qualities", - "year": 1931 - }, - { - "author": "Vladimir Nabokov", - "country": "Russia/United States", - "imageLink": "images/lolita.jpg", - "language": "English", - "link": "https://en.wikipedia.org/wiki/Lolita\n", - "pages": 317, - "title": "Lolita", - "year": 1955 - }, - { - "author": "George Orwell", - "country": "United Kingdom", - "imageLink": "images/nineteen-eighty-four.jpg", - "language": "English", - "link": "https://en.wikipedia.org/wiki/Nineteen_Eighty-Four\n", - "pages": 272, - "title": "Nineteen Eighty-Four", - "year": 1949 - }, - { - "author": "Ovid", - "country": "Roman Empire", - "imageLink": "images/the-metamorphoses-of-ovid.jpg", - "language": "Classical Latin", - "link": "https://en.wikipedia.org/wiki/Metamorphoses\n", - "pages": 576, - "title": "Metamorphoses", - "year": 100 - }, - { - "author": "Fernando Pessoa", - "country": "Portugal", - "imageLink": "images/the-book-of-disquiet.jpg", - "language": "Portuguese", - "link": "https://en.wikipedia.org/wiki/The_Book_of_Disquiet\n", - "pages": 272, - "title": "The Book of Disquiet", - "year": 1928 - }, - { - "author": "Edgar Allan Poe", - "country": "United States", - "imageLink": "images/tales-and-poems-of-edgar-allan-poe.jpg", - "language": "English", - "link": "https://en.wikipedia.org/wiki/Edgar_Allan_Poe_bibliography#Tales\n", - "pages": 842, - "title": "Tales", - "year": 1950 - }, - { - "author": "Marcel Proust", - "country": "France", - "imageLink": "images/a-la-recherche-du-temps-perdu.jpg", - "language": "French", - "link": "https://en.wikipedia.org/wiki/In_Search_of_Lost_Time\n", - "pages": 2408, - "title": "In Search of Lost Time", - "year": 1920 - }, - { - "author": "Fran\u00e7ois Rabelais", - "country": "France", - "imageLink": "images/gargantua-and-pantagruel.jpg", - "language": "French", - "link": "https://en.wikipedia.org/wiki/Gargantua_and_Pantagruel\n", - "pages": 623, - "title": "Gargantua and Pantagruel", - "year": 1533 - }, - { - "author": "Juan Rulfo", - "country": "Mexico", - "imageLink": "images/pedro-paramo.jpg", - "language": "Spanish", - "link": "https://en.wikipedia.org/wiki/Pedro_P%C3%A1ramo\n", - "pages": 124, - "title": "Pedro P\u00e1ramo", - "year": 1955 - }, - { - "author": "Rumi", - "country": "Sultanate of Rum", - "imageLink": "images/the-masnavi.jpg", - "language": "Persian", - "link": "https://en.wikipedia.org/wiki/Masnavi\n", - "pages": 438, - "title": "The Masnavi", - "year": 1236 - }, - { - "author": "Salman Rushdie", - "country": "United Kingdom, India", - "imageLink": "images/midnights-children.jpg", - "language": "English", - "link": "https://en.wikipedia.org/wiki/Midnight%27s_Children\n", - "pages": 536, - "title": "Midnight's Children", - "year": 1981 - }, - { - "author": "Saadi", - "country": "Persia, Persian Empire", - "imageLink": "images/bostan.jpg", - "language": "Persian", - "link": "https://en.wikipedia.org/wiki/Bustan_(book)\n", - "pages": 298, - "title": "Bostan", - "year": 1257 - }, - { - "author": "Tayeb Salih", - "country": "Sudan", - "imageLink": "images/season-of-migration-to-the-north.jpg", - "language": "Arabic", - "link": "https://en.wikipedia.org/wiki/Season_of_Migration_to_the_North\n", - "pages": 139, - "title": "Season of Migration to the North", - "year": 1966 - }, - { - "author": "Jos\u00e9 Saramago", - "country": "Portugal", - "imageLink": "images/blindness.jpg", - "language": "Portuguese", - "link": "https://en.wikipedia.org/wiki/Blindness_(novel)\n", - "pages": 352, - "title": "Blindness", - "year": 1995 - }, - { - "author": "William Shakespeare", - "country": "England", - "imageLink": "images/hamlet.jpg", - "language": "English", - "link": "https://en.wikipedia.org/wiki/Hamlet\n", - "pages": 432, - "title": "Hamlet", - "year": 1603 - }, - { - "author": "William Shakespeare", - "country": "England", - "imageLink": "images/king-lear.jpg", - "language": "English", - "link": "https://en.wikipedia.org/wiki/King_Lear\n", - "pages": 384, - "title": "King Lear", - "year": 1608 - }, - { - "author": "William Shakespeare", - "country": "England", - "imageLink": "images/othello.jpg", - "language": "English", - "link": "https://en.wikipedia.org/wiki/Othello\n", - "pages": 314, - "title": "Othello", - "year": 1609 - }, - { - "author": "Sophocles", - "country": "Greece", - "imageLink": "images/oedipus-the-king.jpg", - "language": "Greek", - "link": "https://en.wikipedia.org/wiki/Oedipus_the_King\n", - "pages": 88, - "title": "Oedipus the King", - "year": -430 - }, - { - "author": "Stendhal", - "country": "France", - "imageLink": "images/le-rouge-et-le-noir.jpg", - "language": "French", - "link": "https://en.wikipedia.org/wiki/The_Red_and_the_Black\n", - "pages": 576, - "title": "The Red and the Black", - "year": 1830 - }, - { - "author": "Laurence Sterne", - "country": "England", - "imageLink": "images/the-life-and-opinions-of-tristram-shandy.jpg", - "language": "English", - "link": "https://en.wikipedia.org/wiki/The_Life_and_Opinions_of_Tristram_Shandy,_Gentleman\n", - "pages": 640, - "title": "The Life And Opinions of Tristram Shandy", - "year": 1760 - }, - { - "author": "Italo Svevo", - "country": "Italy", - "imageLink": "images/confessions-of-zeno.jpg", - "language": "Italian", - "link": "https://en.wikipedia.org/wiki/Zeno%27s_Conscience\n", - "pages": 412, - "title": "Confessions of Zeno", - "year": 1923 - }, - { - "author": "Jonathan Swift", - "country": "Ireland", - "imageLink": "images/gullivers-travels.jpg", - "language": "English", - "link": "https://en.wikipedia.org/wiki/Gulliver%27s_Travels\n", - "pages": 178, - "title": "Gulliver's Travels", - "year": 1726 - }, - { - "author": "Leo Tolstoy", - "country": "Russia", - "imageLink": "images/war-and-peace.jpg", - "language": "Russian", - "link": "https://en.wikipedia.org/wiki/War_and_Peace\n", - "pages": 1296, - "title": "War and Peace", - "year": 1867 - }, - { - "author": "Leo Tolstoy", - "country": "Russia", - "imageLink": "images/anna-karenina.jpg", - "language": "Russian", - "link": "https://en.wikipedia.org/wiki/Anna_Karenina\n", - "pages": 864, - "title": "Anna Karenina", - "year": 1877 - }, - { - "author": "Leo Tolstoy", - "country": "Russia", - "imageLink": "images/the-death-of-ivan-ilyich.jpg", - "language": "Russian", - "link": "https://en.wikipedia.org/wiki/The_Death_of_Ivan_Ilyich\n", - "pages": 92, - "title": "The Death of Ivan Ilyich", - "year": 1886 - }, - { - "author": "Mark Twain", - "country": "United States", - "imageLink": "images/the-adventures-of-huckleberry-finn.jpg", - "language": "English", - "link": "https://en.wikipedia.org/wiki/Adventures_of_Huckleberry_Finn\n", - "pages": 224, - "title": "The Adventures of Huckleberry Finn", - "year": 1884 - }, - { - "author": "Valmiki", - "country": "India", - "imageLink": "images/ramayana.jpg", - "language": "Sanskrit", - "link": "https://en.wikipedia.org/wiki/Ramayana\n", - "pages": 152, - "title": "Ramayana", - "year": -450 - }, - { - "author": "Virgil", - "country": "Roman Empire", - "imageLink": "images/the-aeneid.jpg", - "language": "Classical Latin", - "link": "https://en.wikipedia.org/wiki/Aeneid\n", - "pages": 442, - "title": "The Aeneid", - "year": -23 - }, - { - "author": "Vyasa", - "country": "India", - "imageLink": "images/the-mahab-harata.jpg", - "language": "Sanskrit", - "link": "https://en.wikipedia.org/wiki/Mahabharata\n", - "pages": 276, - "title": "Mahabharata", - "year": -700 - }, - { - "author": "Walt Whitman", - "country": "United States", - "imageLink": "images/leaves-of-grass.jpg", - "language": "English", - "link": "https://en.wikipedia.org/wiki/Leaves_of_Grass\n", - "pages": 152, - "title": "Leaves of Grass", - "year": 1855 - }, - { - "author": "Virginia Woolf", - "country": "United Kingdom", - "imageLink": "images/mrs-dalloway.jpg", - "language": "English", - "link": "https://en.wikipedia.org/wiki/Mrs_Dalloway\n", - "pages": 216, - "title": "Mrs Dalloway", - "year": 1925 - }, - { - "author": "Virginia Woolf", - "country": "United Kingdom", - "imageLink": "images/to-the-lighthouse.jpg", - "language": "English", - "link": "https://en.wikipedia.org/wiki/To_the_Lighthouse\n", - "pages": 209, - "title": "To the Lighthouse", - "year": 1927 - }, - { - "author": "Marguerite Yourcenar", - "country": "France/Belgium", - "imageLink": "images/memoirs-of-hadrian.jpg", - "language": "French", - "link": "https://en.wikipedia.org/wiki/Memoirs_of_Hadrian\n", - "pages": 408, - "title": "Memoirs of Hadrian", - "year": 1951 - } - ] \ No newline at end of file + { + "author": "Chinua Achebe", + "country": "Nigeria", + "imageLink": "images/things-fall-apart.jpg", + "language": "English", + "link": "https://en.wikipedia.org/wiki/Things_Fall_Apart\n", + "pages": 209, + "title": "Things Fall Apart", + "year": 1958 + }, + { + "author": "Hans Christian Andersen", + "country": "Denmark", + "imageLink": "images/fairy-tales.jpg", + "language": "Danish", + "link": "https://en.wikipedia.org/wiki/Fairy_Tales_Told_for_Children._First_Collection.\n", + "pages": 784, + "title": "Fairy tales", + "year": 1836 + }, + { + "author": "Dante Alighieri", + "country": "Italy", + "imageLink": "images/the-divine-comedy.jpg", + "language": "Italian", + "link": "https://en.wikipedia.org/wiki/Divine_Comedy\n", + "pages": 928, + "title": "The Divine Comedy", + "year": 1315 + }, + { + "author": "Unknown", + "country": "Sumer and Akkadian Empire", + "imageLink": "images/the-epic-of-gilgamesh.jpg", + "language": "Akkadian", + "link": "https://en.wikipedia.org/wiki/Epic_of_Gilgamesh\n", + "pages": 160, + "title": "The Epic Of Gilgamesh", + "year": -1700 + }, + { + "author": "Unknown", + "country": "Achaemenid Empire", + "imageLink": "images/the-book-of-job.jpg", + "language": "Hebrew", + "link": "https://en.wikipedia.org/wiki/Book_of_Job\n", + "pages": 176, + "title": "The Book Of Job", + "year": -600 + }, + { + "author": "Unknown", + "country": "India/Iran/Iraq/Egypt/Tajikistan", + "imageLink": "images/one-thousand-and-one-nights.jpg", + "language": "Arabic", + "link": "https://en.wikipedia.org/wiki/One_Thousand_and_One_Nights\n", + "pages": 288, + "title": "One Thousand and One Nights", + "year": 1200 + }, + { + "author": "Unknown", + "country": "Iceland", + "imageLink": "images/njals-saga.jpg", + "language": "Old Norse", + "link": "https://en.wikipedia.org/wiki/Nj%C3%A1ls_saga\n", + "pages": 384, + "title": "Nj\u00e1l's Saga", + "year": 1350 + }, + { + "author": "Jane Austen", + "country": "United Kingdom", + "imageLink": "images/pride-and-prejudice.jpg", + "language": "English", + "link": "https://en.wikipedia.org/wiki/Pride_and_Prejudice\n", + "pages": 226, + "title": "Pride and Prejudice", + "year": 1813 + }, + { + "author": "Honor\u00e9 de Balzac", + "country": "France", + "imageLink": "images/le-pere-goriot.jpg", + "language": "French", + "link": "https://en.wikipedia.org/wiki/Le_P%C3%A8re_Goriot\n", + "pages": 443, + "title": "Le P\u00e8re Goriot", + "year": 1835 + }, + { + "author": "Samuel Beckett", + "country": "Republic of Ireland", + "imageLink": "images/molloy-malone-dies-the-unnamable.jpg", + "language": "French, English", + "link": "https://en.wikipedia.org/wiki/Molloy_(novel)\n", + "pages": 256, + "title": "Molloy, Malone Dies, The Unnamable, the trilogy", + "year": 1952 + }, + { + "author": "Giovanni Boccaccio", + "country": "Italy", + "imageLink": "images/the-decameron.jpg", + "language": "Italian", + "link": "https://en.wikipedia.org/wiki/The_Decameron\n", + "pages": 1024, + "title": "The Decameron", + "year": 1351 + }, + { + "author": "Jorge Luis Borges", + "country": "Argentina", + "imageLink": "images/ficciones.jpg", + "language": "Spanish", + "link": "https://en.wikipedia.org/wiki/Ficciones\n", + "pages": 224, + "title": "Ficciones", + "year": 1965 + }, + { + "author": "Emily Bront\u00eb", + "country": "United Kingdom", + "imageLink": "images/wuthering-heights.jpg", + "language": "English", + "link": "https://en.wikipedia.org/wiki/Wuthering_Heights\n", + "pages": 342, + "title": "Wuthering Heights", + "year": 1847 + }, + { + "author": "Albert Camus", + "country": "Algeria, French Empire", + "imageLink": "images/l-etranger.jpg", + "language": "French", + "link": "https://en.wikipedia.org/wiki/The_Stranger_(novel)\n", + "pages": 185, + "title": "The Stranger", + "year": 1942 + }, + { + "author": "Paul Celan", + "country": "Romania, France", + "imageLink": "images/poems-paul-celan.jpg", + "language": "German", + "link": "\n", + "pages": 320, + "title": "Poems", + "year": 1952 + }, + { + "author": "Louis-Ferdinand C\u00e9line", + "country": "France", + "imageLink": "images/voyage-au-bout-de-la-nuit.jpg", + "language": "French", + "link": "https://en.wikipedia.org/wiki/Journey_to_the_End_of_the_Night\n", + "pages": 505, + "title": "Journey to the End of the Night", + "year": 1932 + }, + { + "author": "Miguel de Cervantes", + "country": "Spain", + "imageLink": "images/don-quijote-de-la-mancha.jpg", + "language": "Spanish", + "link": "https://en.wikipedia.org/wiki/Don_Quixote\n", + "pages": 1056, + "title": "Don Quijote De La Mancha", + "year": 1610 + }, + { + "author": "Geoffrey Chaucer", + "country": "England", + "imageLink": "images/the-canterbury-tales.jpg", + "language": "English", + "link": "https://en.wikipedia.org/wiki/The_Canterbury_Tales\n", + "pages": 544, + "title": "The Canterbury Tales", + "year": 1450 + }, + { + "author": "Anton Chekhov", + "country": "Russia", + "imageLink": "images/stories-of-anton-chekhov.jpg", + "language": "Russian", + "link": "https://en.wikipedia.org/wiki/List_of_short_stories_by_Anton_Chekhov\n", + "pages": 194, + "title": "Stories", + "year": 1886 + }, + { + "author": "Joseph Conrad", + "country": "United Kingdom", + "imageLink": "images/nostromo.jpg", + "language": "English", + "link": "https://en.wikipedia.org/wiki/Nostromo\n", + "pages": 320, + "title": "Nostromo", + "year": 1904 + }, + { + "author": "Charles Dickens", + "country": "United Kingdom", + "imageLink": "images/great-expectations.jpg", + "language": "English", + "link": "https://en.wikipedia.org/wiki/Great_Expectations\n", + "pages": 194, + "title": "Great Expectations", + "year": 1861 + }, + { + "author": "Denis Diderot", + "country": "France", + "imageLink": "images/jacques-the-fatalist.jpg", + "language": "French", + "link": "https://en.wikipedia.org/wiki/Jacques_the_Fatalist\n", + "pages": 596, + "title": "Jacques the Fatalist", + "year": 1796 + }, + { + "author": "Alfred D\u00f6blin", + "country": "Germany", + "imageLink": "images/berlin-alexanderplatz.jpg", + "language": "German", + "link": "https://en.wikipedia.org/wiki/Berlin_Alexanderplatz\n", + "pages": 600, + "title": "Berlin Alexanderplatz", + "year": 1929 + }, + { + "author": "Fyodor Dostoevsky", + "country": "Russia", + "imageLink": "images/crime-and-punishment.jpg", + "language": "Russian", + "link": "https://en.wikipedia.org/wiki/Crime_and_Punishment\n", + "pages": 551, + "title": "Crime and Punishment", + "year": 1866 + }, + { + "author": "Fyodor Dostoevsky", + "country": "Russia", + "imageLink": "images/the-idiot.jpg", + "language": "Russian", + "link": "https://en.wikipedia.org/wiki/The_Idiot\n", + "pages": 656, + "title": "The Idiot", + "year": 1869 + }, + { + "author": "Fyodor Dostoevsky", + "country": "Russia", + "imageLink": "images/the-possessed.jpg", + "language": "Russian", + "link": "https://en.wikipedia.org/wiki/Demons_(Dostoyevsky_novel)\n", + "pages": 768, + "title": "The Possessed", + "year": 1872 + }, + { + "author": "Fyodor Dostoevsky", + "country": "Russia", + "imageLink": "images/the-brothers-karamazov.jpg", + "language": "Russian", + "link": "https://en.wikipedia.org/wiki/The_Brothers_Karamazov\n", + "pages": 824, + "title": "The Brothers Karamazov", + "year": 1880 + }, + { + "author": "George Eliot", + "country": "United Kingdom", + "imageLink": "images/middlemarch.jpg", + "language": "English", + "link": "https://en.wikipedia.org/wiki/Middlemarch\n", + "pages": 800, + "title": "Middlemarch", + "year": 1871 + }, + { + "author": "Ralph Ellison", + "country": "United States", + "imageLink": "images/invisible-man.jpg", + "language": "English", + "link": "https://en.wikipedia.org/wiki/Invisible_Man\n", + "pages": 581, + "title": "Invisible Man", + "year": 1952 + }, + { + "author": "Euripides", + "country": "Greece", + "imageLink": "images/medea.jpg", + "language": "Greek", + "link": "https://en.wikipedia.org/wiki/Medea_(play)\n", + "pages": 104, + "title": "Medea", + "year": -431 + }, + { + "author": "William Faulkner", + "country": "United States", + "imageLink": "images/absalom-absalom.jpg", + "language": "English", + "link": "https://en.wikipedia.org/wiki/Absalom,_Absalom!\n", + "pages": 313, + "title": "Absalom, Absalom!", + "year": 1936 + }, + { + "author": "William Faulkner", + "country": "United States", + "imageLink": "images/the-sound-and-the-fury.jpg", + "language": "English", + "link": "https://en.wikipedia.org/wiki/The_Sound_and_the_Fury\n", + "pages": 326, + "title": "The Sound and the Fury", + "year": 1929 + }, + { + "author": "Gustave Flaubert", + "country": "France", + "imageLink": "images/madame-bovary.jpg", + "language": "French", + "link": "https://en.wikipedia.org/wiki/Madame_Bovary\n", + "pages": 528, + "title": "Madame Bovary", + "year": 1857 + }, + { + "author": "Gustave Flaubert", + "country": "France", + "imageLink": "images/l-education-sentimentale.jpg", + "language": "French", + "link": "https://en.wikipedia.org/wiki/Sentimental_Education\n", + "pages": 606, + "title": "Sentimental Education", + "year": 1869 + }, + { + "author": "Federico Garc\u00eda Lorca", + "country": "Spain", + "imageLink": "images/gypsy-ballads.jpg", + "language": "Spanish", + "link": "https://en.wikipedia.org/wiki/Gypsy_Ballads\n", + "pages": 218, + "title": "Gypsy Ballads", + "year": 1928 + }, + { + "author": "Gabriel Garc\u00eda M\u00e1rquez", + "country": "Colombia", + "imageLink": "images/one-hundred-years-of-solitude.jpg", + "language": "Spanish", + "link": "https://en.wikipedia.org/wiki/One_Hundred_Years_of_Solitude\n", + "pages": 417, + "title": "One Hundred Years of Solitude", + "year": 1967 + }, + { + "author": "Gabriel Garc\u00eda M\u00e1rquez", + "country": "Colombia", + "imageLink": "images/love-in-the-time-of-cholera.jpg", + "language": "Spanish", + "link": "https://en.wikipedia.org/wiki/Love_in_the_Time_of_Cholera\n", + "pages": 368, + "title": "Love in the Time of Cholera", + "year": 1985 + }, + { + "author": "Johann Wolfgang von Goethe", + "country": "Saxe-Weimar", + "imageLink": "images/faust.jpg", + "language": "German", + "link": "https://en.wikipedia.org/wiki/Goethe%27s_Faust\n", + "pages": 158, + "title": "Faust", + "year": 1832 + }, + { + "author": "Nikolai Gogol", + "country": "Russia", + "imageLink": "images/dead-souls.jpg", + "language": "Russian", + "link": "https://en.wikipedia.org/wiki/Dead_Souls\n", + "pages": 432, + "title": "Dead Souls", + "year": 1842 + }, + { + "author": "G\u00fcnter Grass", + "country": "Germany", + "imageLink": "images/the-tin-drum.jpg", + "language": "German", + "link": "https://en.wikipedia.org/wiki/The_Tin_Drum\n", + "pages": 600, + "title": "The Tin Drum", + "year": 1959 + }, + { + "author": "Jo\u00e3o Guimar\u00e3es Rosa", + "country": "Brazil", + "imageLink": "images/the-devil-to-pay-in-the-backlands.jpg", + "language": "Portuguese", + "link": "https://en.wikipedia.org/wiki/The_Devil_to_Pay_in_the_Backlands\n", + "pages": 494, + "title": "The Devil to Pay in the Backlands", + "year": 1956 + }, + { + "author": "Knut Hamsun", + "country": "Norway", + "imageLink": "images/hunger.jpg", + "language": "Norwegian", + "link": "https://en.wikipedia.org/wiki/Hunger_(Hamsun_novel)\n", + "pages": 176, + "title": "Hunger", + "year": 1890 + }, + { + "author": "Ernest Hemingway", + "country": "United States", + "imageLink": "images/the-old-man-and-the-sea.jpg", + "language": "English", + "link": "https://en.wikipedia.org/wiki/The_Old_Man_and_the_Sea\n", + "pages": 128, + "title": "The Old Man and the Sea", + "year": 1952 + }, + { + "author": "Homer", + "country": "Greece", + "imageLink": "images/the-iliad-of-homer.jpg", + "language": "Greek", + "link": "https://en.wikipedia.org/wiki/Iliad\n", + "pages": 608, + "title": "Iliad", + "year": -735 + }, + { + "author": "Homer", + "country": "Greece", + "imageLink": "images/the-odyssey-of-homer.jpg", + "language": "Greek", + "link": "https://en.wikipedia.org/wiki/Odyssey\n", + "pages": 374, + "title": "Odyssey", + "year": -800 + }, + { + "author": "Henrik Ibsen", + "country": "Norway", + "imageLink": "images/a-Dolls-house.jpg", + "language": "Norwegian", + "link": "https://en.wikipedia.org/wiki/A_Doll%27s_House\n", + "pages": 68, + "title": "A Doll's House", + "year": 1879 + }, + { + "author": "James Joyce", + "country": "Irish Free State", + "imageLink": "images/ulysses.jpg", + "language": "English", + "link": "https://en.wikipedia.org/wiki/Ulysses_(novel)\n", + "pages": 228, + "title": "Ulysses", + "year": 1922 + }, + { + "author": "Franz Kafka", + "country": "Czechoslovakia", + "imageLink": "images/stories-of-franz-kafka.jpg", + "language": "German", + "link": "https://en.wikipedia.org/wiki/Franz_Kafka_bibliography#Short_stories\n", + "pages": 488, + "title": "Stories", + "year": 1924 + }, + { + "author": "Franz Kafka", + "country": "Czechoslovakia", + "imageLink": "images/the-trial.jpg", + "language": "German", + "link": "https://en.wikipedia.org/wiki/The_Trial\n", + "pages": 160, + "title": "The Trial", + "year": 1925 + }, + { + "author": "Franz Kafka", + "country": "Czechoslovakia", + "imageLink": "images/the-castle.jpg", + "language": "German", + "link": "https://en.wikipedia.org/wiki/The_Castle_(novel)\n", + "pages": 352, + "title": "The Castle", + "year": 1926 + }, + { + "author": "K\u0101lid\u0101sa", + "country": "India", + "imageLink": "images/the-recognition-of-shakuntala.jpg", + "language": "Sanskrit", + "link": "https://en.wikipedia.org/wiki/Abhij%C3%B1%C4%81na%C5%9B%C4%81kuntalam\n", + "pages": 147, + "title": "The recognition of Shakuntala", + "year": 150 + }, + { + "author": "Yasunari Kawabata", + "country": "Japan", + "imageLink": "images/the-sound-of-the-mountain.jpg", + "language": "Japanese", + "link": "https://en.wikipedia.org/wiki/The_Sound_of_the_Mountain\n", + "pages": 288, + "title": "The Sound of the Mountain", + "year": 1954 + }, + { + "author": "Nikos Kazantzakis", + "country": "Greece", + "imageLink": "images/zorba-the-greek.jpg", + "language": "Greek", + "link": "https://en.wikipedia.org/wiki/Zorba_the_Greek\n", + "pages": 368, + "title": "Zorba the Greek", + "year": 1946 + }, + { + "author": "D. H. Lawrence", + "country": "United Kingdom", + "imageLink": "images/sons-and-lovers.jpg", + "language": "English", + "link": "https://en.wikipedia.org/wiki/Sons_and_Lovers\n", + "pages": 432, + "title": "Sons and Lovers", + "year": 1913 + }, + { + "author": "Halld\u00f3r Laxness", + "country": "Iceland", + "imageLink": "images/independent-people.jpg", + "language": "Icelandic", + "link": "https://en.wikipedia.org/wiki/Independent_People\n", + "pages": 470, + "title": "Independent People", + "year": 1934 + }, + { + "author": "Giacomo Leopardi", + "country": "Italy", + "imageLink": "images/poems-giacomo-leopardi.jpg", + "language": "Italian", + "link": "\n", + "pages": 184, + "title": "Poems", + "year": 1818 + }, + { + "author": "Doris Lessing", + "country": "United Kingdom", + "imageLink": "images/the-golden-notebook.jpg", + "language": "English", + "link": "https://en.wikipedia.org/wiki/The_Golden_Notebook\n", + "pages": 688, + "title": "The Golden Notebook", + "year": 1962 + }, + { + "author": "Astrid Lindgren", + "country": "Sweden", + "imageLink": "images/pippi-longstocking.jpg", + "language": "Swedish", + "link": "https://en.wikipedia.org/wiki/Pippi_Longstocking\n", + "pages": 160, + "title": "Pippi Longstocking", + "year": 1945 + }, + { + "author": "Lu Xun", + "country": "China", + "imageLink": "images/diary-of-a-madman.jpg", + "language": "Chinese", + "link": "https://en.wikipedia.org/wiki/A_Madman%27s_Diary\n", + "pages": 389, + "title": "Diary of a Madman", + "year": 1918 + }, + { + "author": "Naguib Mahfouz", + "country": "Egypt", + "imageLink": "images/children-of-gebelawi.jpg", + "language": "Arabic", + "link": "https://en.wikipedia.org/wiki/Children_of_Gebelawi\n", + "pages": 355, + "title": "Children of Gebelawi", + "year": 1959 + }, + { + "author": "Thomas Mann", + "country": "Germany", + "imageLink": "images/buddenbrooks.jpg", + "language": "German", + "link": "https://en.wikipedia.org/wiki/Buddenbrooks\n", + "pages": 736, + "title": "Buddenbrooks", + "year": 1901 + }, + { + "author": "Thomas Mann", + "country": "Germany", + "imageLink": "images/the-magic-mountain.jpg", + "language": "German", + "link": "https://en.wikipedia.org/wiki/The_Magic_Mountain\n", + "pages": 720, + "title": "The Magic Mountain", + "year": 1924 + }, + { + "author": "Herman Melville", + "country": "United States", + "imageLink": "images/moby-dick.jpg", + "language": "English", + "link": "https://en.wikipedia.org/wiki/Moby-Dick\n", + "pages": 378, + "title": "Moby Dick", + "year": 1851 + }, + { + "author": "Michel de Montaigne", + "country": "France", + "imageLink": "images/essais.jpg", + "language": "French", + "link": "https://en.wikipedia.org/wiki/Essays_(Montaigne)\n", + "pages": 404, + "title": "Essays", + "year": 1595 + }, + { + "author": "Elsa Morante", + "country": "Italy", + "imageLink": "images/history.jpg", + "language": "Italian", + "link": "https://en.wikipedia.org/wiki/History_(novel)\n", + "pages": 600, + "title": "History", + "year": 1974 + }, + { + "author": "Toni Morrison", + "country": "United States", + "imageLink": "images/beloved.jpg", + "language": "English", + "link": "https://en.wikipedia.org/wiki/Beloved_(novel)\n", + "pages": 321, + "title": "Beloved", + "year": 1987 + }, + { + "author": "Murasaki Shikibu", + "country": "Japan", + "imageLink": "images/the-tale-of-genji.jpg", + "language": "Japanese", + "link": "https://en.wikipedia.org/wiki/The_Tale_of_Genji\n", + "pages": 1360, + "title": "The Tale of Genji", + "year": 1006 + }, + { + "author": "Robert Musil", + "country": "Austria", + "imageLink": "images/the-man-without-qualities.jpg", + "language": "German", + "link": "https://en.wikipedia.org/wiki/The_Man_Without_Qualities\n", + "pages": 365, + "title": "The Man Without Qualities", + "year": 1931 + }, + { + "author": "Vladimir Nabokov", + "country": "Russia/United States", + "imageLink": "images/lolita.jpg", + "language": "English", + "link": "https://en.wikipedia.org/wiki/Lolita\n", + "pages": 317, + "title": "Lolita", + "year": 1955 + }, + { + "author": "George Orwell", + "country": "United Kingdom", + "imageLink": "images/nineteen-eighty-four.jpg", + "language": "English", + "link": "https://en.wikipedia.org/wiki/Nineteen_Eighty-Four\n", + "pages": 272, + "title": "Nineteen Eighty-Four", + "year": 1949 + }, + { + "author": "Ovid", + "country": "Roman Empire", + "imageLink": "images/the-metamorphoses-of-ovid.jpg", + "language": "Classical Latin", + "link": "https://en.wikipedia.org/wiki/Metamorphoses\n", + "pages": 576, + "title": "Metamorphoses", + "year": 100 + }, + { + "author": "Fernando Pessoa", + "country": "Portugal", + "imageLink": "images/the-book-of-disquiet.jpg", + "language": "Portuguese", + "link": "https://en.wikipedia.org/wiki/The_Book_of_Disquiet\n", + "pages": 272, + "title": "The Book of Disquiet", + "year": 1928 + }, + { + "author": "Edgar Allan Poe", + "country": "United States", + "imageLink": "images/tales-and-poems-of-edgar-allan-poe.jpg", + "language": "English", + "link": "https://en.wikipedia.org/wiki/Edgar_Allan_Poe_bibliography#Tales\n", + "pages": 842, + "title": "Tales", + "year": 1950 + }, + { + "author": "Marcel Proust", + "country": "France", + "imageLink": "images/a-la-recherche-du-temps-perdu.jpg", + "language": "French", + "link": "https://en.wikipedia.org/wiki/In_Search_of_Lost_Time\n", + "pages": 2408, + "title": "In Search of Lost Time", + "year": 1920 + }, + { + "author": "Fran\u00e7ois Rabelais", + "country": "France", + "imageLink": "images/gargantua-and-pantagruel.jpg", + "language": "French", + "link": "https://en.wikipedia.org/wiki/Gargantua_and_Pantagruel\n", + "pages": 623, + "title": "Gargantua and Pantagruel", + "year": 1533 + }, + { + "author": "Juan Rulfo", + "country": "Mexico", + "imageLink": "images/pedro-paramo.jpg", + "language": "Spanish", + "link": "https://en.wikipedia.org/wiki/Pedro_P%C3%A1ramo\n", + "pages": 124, + "title": "Pedro P\u00e1ramo", + "year": 1955 + }, + { + "author": "Rumi", + "country": "Sultanate of Rum", + "imageLink": "images/the-masnavi.jpg", + "language": "Persian", + "link": "https://en.wikipedia.org/wiki/Masnavi\n", + "pages": 438, + "title": "The Masnavi", + "year": 1236 + }, + { + "author": "Salman Rushdie", + "country": "United Kingdom, India", + "imageLink": "images/midnights-children.jpg", + "language": "English", + "link": "https://en.wikipedia.org/wiki/Midnight%27s_Children\n", + "pages": 536, + "title": "Midnight's Children", + "year": 1981 + }, + { + "author": "Saadi", + "country": "Persia, Persian Empire", + "imageLink": "images/bostan.jpg", + "language": "Persian", + "link": "https://en.wikipedia.org/wiki/Bustan_(book)\n", + "pages": 298, + "title": "Bostan", + "year": 1257 + }, + { + "author": "Tayeb Salih", + "country": "Sudan", + "imageLink": "images/season-of-migration-to-the-north.jpg", + "language": "Arabic", + "link": "https://en.wikipedia.org/wiki/Season_of_Migration_to_the_North\n", + "pages": 139, + "title": "Season of Migration to the North", + "year": 1966 + }, + { + "author": "Jos\u00e9 Saramago", + "country": "Portugal", + "imageLink": "images/blindness.jpg", + "language": "Portuguese", + "link": "https://en.wikipedia.org/wiki/Blindness_(novel)\n", + "pages": 352, + "title": "Blindness", + "year": 1995 + }, + { + "author": "William Shakespeare", + "country": "England", + "imageLink": "images/hamlet.jpg", + "language": "English", + "link": "https://en.wikipedia.org/wiki/Hamlet\n", + "pages": 432, + "title": "Hamlet", + "year": 1603 + }, + { + "author": "William Shakespeare", + "country": "England", + "imageLink": "images/king-lear.jpg", + "language": "English", + "link": "https://en.wikipedia.org/wiki/King_Lear\n", + "pages": 384, + "title": "King Lear", + "year": 1608 + }, + { + "author": "William Shakespeare", + "country": "England", + "imageLink": "images/othello.jpg", + "language": "English", + "link": "https://en.wikipedia.org/wiki/Othello\n", + "pages": 314, + "title": "Othello", + "year": 1609 + }, + { + "author": "Sophocles", + "country": "Greece", + "imageLink": "images/oedipus-the-king.jpg", + "language": "Greek", + "link": "https://en.wikipedia.org/wiki/Oedipus_the_King\n", + "pages": 88, + "title": "Oedipus the King", + "year": -430 + }, + { + "author": "Stendhal", + "country": "France", + "imageLink": "images/le-rouge-et-le-noir.jpg", + "language": "French", + "link": "https://en.wikipedia.org/wiki/The_Red_and_the_Black\n", + "pages": 576, + "title": "The Red and the Black", + "year": 1830 + }, + { + "author": "Laurence Sterne", + "country": "England", + "imageLink": "images/the-life-and-opinions-of-tristram-shandy.jpg", + "language": "English", + "link": "https://en.wikipedia.org/wiki/The_Life_and_Opinions_of_Tristram_Shandy,_Gentleman\n", + "pages": 640, + "title": "The Life And Opinions of Tristram Shandy", + "year": 1760 + }, + { + "author": "Italo Svevo", + "country": "Italy", + "imageLink": "images/confessions-of-zeno.jpg", + "language": "Italian", + "link": "https://en.wikipedia.org/wiki/Zeno%27s_Conscience\n", + "pages": 412, + "title": "Confessions of Zeno", + "year": 1923 + }, + { + "author": "Jonathan Swift", + "country": "Ireland", + "imageLink": "images/gullivers-travels.jpg", + "language": "English", + "link": "https://en.wikipedia.org/wiki/Gulliver%27s_Travels\n", + "pages": 178, + "title": "Gulliver's Travels", + "year": 1726 + }, + { + "author": "Leo Tolstoy", + "country": "Russia", + "imageLink": "images/war-and-peace.jpg", + "language": "Russian", + "link": "https://en.wikipedia.org/wiki/War_and_Peace\n", + "pages": 1296, + "title": "War and Peace", + "year": 1867 + }, + { + "author": "Leo Tolstoy", + "country": "Russia", + "imageLink": "images/anna-karenina.jpg", + "language": "Russian", + "link": "https://en.wikipedia.org/wiki/Anna_Karenina\n", + "pages": 864, + "title": "Anna Karenina", + "year": 1877 + }, + { + "author": "Leo Tolstoy", + "country": "Russia", + "imageLink": "images/the-death-of-ivan-ilyich.jpg", + "language": "Russian", + "link": "https://en.wikipedia.org/wiki/The_Death_of_Ivan_Ilyich\n", + "pages": 92, + "title": "The Death of Ivan Ilyich", + "year": 1886 + }, + { + "author": "Mark Twain", + "country": "United States", + "imageLink": "images/the-adventures-of-huckleberry-finn.jpg", + "language": "English", + "link": "https://en.wikipedia.org/wiki/Adventures_of_Huckleberry_Finn\n", + "pages": 224, + "title": "The Adventures of Huckleberry Finn", + "year": 1884 + }, + { + "author": "Valmiki", + "country": "India", + "imageLink": "images/ramayana.jpg", + "language": "Sanskrit", + "link": "https://en.wikipedia.org/wiki/Ramayana\n", + "pages": 152, + "title": "Ramayana", + "year": -450 + }, + { + "author": "Virgil", + "country": "Roman Empire", + "imageLink": "images/the-aeneid.jpg", + "language": "Classical Latin", + "link": "https://en.wikipedia.org/wiki/Aeneid\n", + "pages": 442, + "title": "The Aeneid", + "year": -23 + }, + { + "author": "Vyasa", + "country": "India", + "imageLink": "images/the-mahab-harata.jpg", + "language": "Sanskrit", + "link": "https://en.wikipedia.org/wiki/Mahabharata\n", + "pages": 276, + "title": "Mahabharata", + "year": -700 + }, + { + "author": "Walt Whitman", + "country": "United States", + "imageLink": "images/leaves-of-grass.jpg", + "language": "English", + "link": "https://en.wikipedia.org/wiki/Leaves_of_Grass\n", + "pages": 152, + "title": "Leaves of Grass", + "year": 1855 + }, + { + "author": "Virginia Woolf", + "country": "United Kingdom", + "imageLink": "images/mrs-dalloway.jpg", + "language": "English", + "link": "https://en.wikipedia.org/wiki/Mrs_Dalloway\n", + "pages": 216, + "title": "Mrs Dalloway", + "year": 1925 + }, + { + "author": "Virginia Woolf", + "country": "United Kingdom", + "imageLink": "images/to-the-lighthouse.jpg", + "language": "English", + "link": "https://en.wikipedia.org/wiki/To_the_Lighthouse\n", + "pages": 209, + "title": "To the Lighthouse", + "year": 1927 + }, + { + "author": "Marguerite Yourcenar", + "country": "France/Belgium", + "imageLink": "images/memoirs-of-hadrian.jpg", + "language": "French", + "link": "https://en.wikipedia.org/wiki/Memoirs_of_Hadrian\n", + "pages": 408, + "title": "Memoirs of Hadrian", + "year": 1951 + } +] diff --git a/pages/_app.js b/pages/_app.js index 84564cd7..ae7825f7 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -1,18 +1,19 @@ -import Head from 'next/head' -import '../styles/globals.css' -import '../styles/q.css'; -import 'driver.js/dist/driver.css'; +import Head from "next/head"; +import "../styles/globals.css"; +import "../styles/q.css"; +import "driver.js/dist/driver.css"; + // import '../styles/reducedStyle.css' // Site-wide default metadata. Lives here (not _document) so it is emitted in the // server-rendered HTML that link crawlers (Discord, Slack, iMessage, Twitter, etc.) // read — they do not execute JS, so without these tags they show no preview. // Individual pages can override any of these with their own . -const SITE_URL = 'https://redux.isu.edu' -const SITE_NAME = 'Redux' +const SITE_URL = "https://redux.isu.edu"; +const SITE_NAME = "Redux"; const SITE_DESCRIPTION = - 'Redux is an educational platform for exploring computational complexity — problems, reductions, and NP-completeness.' -const OG_IMAGE = `${SITE_URL}/og-image.png` // TODO: add public/og-image.png (recommended 1200x630) + "Redux is an educational platform for exploring computational complexity — problems, reductions, and NP-completeness."; +const OG_IMAGE = `${SITE_URL}/og-image.png`; // TODO: add public/og-image.png (recommended 1200x630) function MyApp({ Component, pageProps }) { return ( @@ -38,7 +39,7 @@ function MyApp({ Component, pageProps }) { - ) + ); } -export default MyApp +export default MyApp; diff --git a/pages/aboutus/index.js b/pages/aboutus/index.js index cd2178dd..8108f84e 100644 --- a/pages/aboutus/index.js +++ b/pages/aboutus/index.js @@ -1,19 +1,17 @@ -import ResponsiveAppBar from "../../components/widgets/ResponsiveAppBar"; - import { - createTheme, - ThemeProvider, - Container, + Avatar, Box, - Typography, - Link, + Container, + CssBaseline, + createTheme, Grid, - Avatar, + Link, + ThemeProvider, Tooltip, - CssBaseline, + Typography, } from "@mui/material"; - import isulogo from "../../components/images/ISULogo.png"; +import ResponsiveAppBar from "../../components/widgets/ResponsiveAppBar"; // The only contributors whose GitHub profiles are known, along with avatar and link const contributorProfiles = { @@ -212,11 +210,7 @@ function ItemContributor({ name }) { title={ - + Redux - , a platform for NP-Complete problems. Input your challenges and - gain access to reductions, solutions, verifiers, and - visualizations. Join our community of problem solvers and unravel - computational complexities using the application library. The + , a platform for NP-Complete problems. Input your challenges and gain access to + reductions, solutions, verifiers, and visualizations. Join our community of problem + solvers and unravel computational complexities using the application library. The project was greatly inspired by Richard Karp's paper{" "} - Kaden Marchetti, Andrija Sevaljevic, Alex Diviney, Caleb - Eardley, Russell Phillips, Rajiv Khadka, Daniel Igbokwe, and - Paul Bodily. 2024. Redux: An Interactive, Dynamic Knowledge - Base for Teaching NP-completeness. In Proceedings of the 2024 - on Innovation and Technology in Computer Science Education V. 1 - (ITiCSE 2024). Association for Computing Machinery, New York, - NY, USA, 255–261.{" "} + Kaden Marchetti, Andrija Sevaljevic, Alex Diviney, Caleb Eardley, Russell + Phillips, Rajiv Khadka, Daniel Igbokwe, and Paul Bodily. 2024. Redux: An + Interactive, Dynamic Knowledge Base for Teaching NP-completeness. In Proceedings + of the 2024 on Innovation and Technology in Computer Science Education V. 1 + (ITiCSE 2024). Association for Computing Machinery, New York, NY, USA, 255–261.{" "} - Below are research publications and awards associated with the - Redux project and its contributors. + Below are research publications and awards associated with the Redux project and its + contributors. @@ -484,7 +475,6 @@ export default function AboutUsPage() { }} > {item.citation}{" "} - {item.doi && ( )} - {item.pdf && ( )} - {item.url && ( - Any opinions, findings, conclusions, or recommendations - expressed in this material are those of the author(s) and do not - necessarily reflect the views of the funding agencies who have - supported this work. + Any opinions, findings, conclusions, or recommendations expressed in this material + are those of the author(s) and do not necessarily reflect the views of the funding + agencies who have supported this work. @@ -611,7 +598,8 @@ export default function AboutUsPage() { sx={{ color: "#F47C20", fontWeight: 600 }} > BSD 3-Clause License - . + + . diff --git a/pages/api/hello.js b/pages/api/hello.js index df63de88..aee21e9a 100644 --- a/pages/api/hello.js +++ b/pages/api/hello.js @@ -1,5 +1,5 @@ // Next.js API route support: https://nextjs.org/docs/api-routes/introduction export default function handler(req, res) { - res.status(200).json({ name: 'John Doe' }) + res.status(200).json({ name: "John Doe" }); } diff --git a/pages/api/redux/[...path].js b/pages/api/redux/[...path].js index 1c7c40e7..bb635a22 100644 --- a/pages/api/redux/[...path].js +++ b/pages/api/redux/[...path].js @@ -5,12 +5,12 @@ export const config = { export default async function handler(req, res) { const baseUrl = process.env.REDUX_BASE_URL; if (!baseUrl) { - res.status(500).json({ error: 'REDUX_BASE_URL is not configured' }); + res.status(500).json({ error: "REDUX_BASE_URL is not configured" }); return; } - const suffix = req.url.replace(/^\/api\/redux\/?/, ''); - const targetUrl = `${baseUrl.replace(/\/$/, '')}/${suffix}`; + const suffix = req.url.replace(/^\/api\/redux\/?/, ""); + const targetUrl = `${baseUrl.replace(/\/$/, "")}/${suffix}`; // Guard against SSRF: the user-controlled suffix must not be able to steer the // request to a host/scheme other than the configured backend. Resolve the URL @@ -21,30 +21,30 @@ export default async function handler(req, res) { target = new URL(targetUrl); base = new URL(baseUrl); } catch { - res.status(400).json({ error: 'Invalid request path' }); + res.status(400).json({ error: "Invalid request path" }); return; } const isSameOrigin = target.origin === base.origin; - const isAllowedScheme = target.protocol === 'http:' || target.protocol === 'https:'; + const isAllowedScheme = target.protocol === "http:" || target.protocol === "https:"; if (!isSameOrigin || !isAllowedScheme) { - res.status(400).json({ error: 'Refusing to proxy request outside the configured backend' }); + res.status(400).json({ error: "Refusing to proxy request outside the configured backend" }); return; } const headers = {}; for (const [key, value] of Object.entries(req.headers)) { - if (!['host', 'connection', 'transfer-encoding'].includes(key.toLowerCase())) { - headers[key] = Array.isArray(value) ? value.join(', ') : value; + if (!["host", "connection", "transfer-encoding"].includes(key.toLowerCase())) { + headers[key] = Array.isArray(value) ? value.join(", ") : value; } } // Do not follow redirects server-side: a compromised or misbehaving backend // could 3xx us toward an internal address (another SSRF path). Forward the // redirect response to the client and let the browser decide what to do. - const fetchOptions = { method: req.method, headers, redirect: 'manual' }; + const fetchOptions = { method: req.method, headers, redirect: "manual" }; - if (!['GET', 'HEAD'].includes(req.method)) { + if (!["GET", "HEAD"].includes(req.method)) { const chunks = []; for await (const chunk of req) { chunks.push(chunk); @@ -62,7 +62,7 @@ export default async function handler(req, res) { res.status(upstream.status); for (const [key, value] of upstream.headers) { - if (!['transfer-encoding', 'connection'].includes(key.toLowerCase())) { + if (!["transfer-encoding", "connection"].includes(key.toLowerCase())) { res.setHeader(key, value); } } diff --git a/pages/api/render-tikz.js b/pages/api/render-tikz.js index fa75e079..9b25f22f 100644 --- a/pages/api/render-tikz.js +++ b/pages/api/render-tikz.js @@ -1,23 +1,23 @@ import tex2svg from "node-tikzjax"; export default async function handler(req, res) { - if (req.method !== "POST") return res.status(405).end(); - const { tikzBody } = req.body; + if (req.method !== "POST") return res.status(405).end(); + const { tikzBody } = req.body; - const tikzDocument = ` + const tikzDocument = ` \\begin{document} ${tikzBody} \\end{document} `; - try { - const svg = await tex2svg(tikzDocument, { - showConsole: false, - tikzLibraries: ["automata", "positioning", "arrows.meta"], - }); - res.status(200).json({ success: true, svg }); - } catch (err) { - console.error("TikZ Compilation Error:", err); - res.status(500).json({ success: false, error: err.message }); - } + try { + const svg = await tex2svg(tikzDocument, { + showConsole: false, + tikzLibraries: ["automata", "positioning", "arrows.meta"], + }); + res.status(200).json({ success: true, svg }); + } catch (err) { + console.error("TikZ Compilation Error:", err); + res.status(500).json({ success: false, error: err.message }); + } } diff --git a/pages/browse/index.js b/pages/browse/index.js index 5ce8a51e..2d750c9d 100644 --- a/pages/browse/index.js +++ b/pages/browse/index.js @@ -1,23 +1,23 @@ -import React, { useMemo } from "react"; -import ResponsiveAppBar from "../../components/widgets/ResponsiveAppBar"; -import FacetFilterGroup from "../../components/widgets/FacetFilterGroup"; -import ProblemCard from "../../components/widgets/ProblemCard"; -import SearchBarExtensible from "../../components/widgets/SearchBarExtensible"; -import { useProblemIndex } from "../../components/hooks/ProblemFilters/useProblemIndex"; -import { useProblemFilters } from "../../components/hooks/ProblemFilters/useProblemFilters"; -import { buildFacetOptions } from "../../components/hooks/ProblemFilters/facetOptions"; import { - createTheme, - ThemeProvider, - CssBaseline, - Container, Box, - Typography, - Grid, Button, Chip, CircularProgress, + Container, + CssBaseline, + createTheme, + Grid, + ThemeProvider, + Typography, } from "@mui/material"; +import React, { useMemo } from "react"; +import { buildFacetOptions } from "../../components/hooks/ProblemFilters/facetOptions"; +import { useProblemFilters } from "../../components/hooks/ProblemFilters/useProblemFilters"; +import { useProblemIndex } from "../../components/hooks/ProblemFilters/useProblemIndex"; +import FacetFilterGroup from "../../components/widgets/FacetFilterGroup"; +import ProblemCard from "../../components/widgets/ProblemCard"; +import ResponsiveAppBar from "../../components/widgets/ResponsiveAppBar"; +import SearchBarExtensible from "../../components/widgets/SearchBarExtensible"; // Same dark palette as pages/aboutus/index.js, for visual consistency across // the app's newer MUI-Grid-card pages. @@ -104,8 +104,8 @@ export default function BrowsePage() { Browse Problems - Filter the full problem list by complexity class, solver type, visualization type, - or reduction reachability. + Filter the full problem list by complexity class, solver type, visualization type, or + reduction reachability. {loading ? ( @@ -116,7 +116,15 @@ export default function BrowsePage() { ) : ( - + - Our goal from the beginning has not been to build a knowledge - base ourselves but to build a framework for crowd-sourced - contribution across the world, think Wikipedia. We hope to see - contributors add everything from new problems, algorithms, - reductions, visualizations, features, bug fixes, and beyond. Our - goal is to make the framework easy to understand and even easier - to extend. Below are tutorials and helpful information to get - you started. + Our goal from the beginning has not been to build a knowledge base ourselves but to + build a framework for crowd-sourced contribution across the world, think Wikipedia. + We hope to see contributors add everything from new problems, algorithms, + reductions, visualizations, features, bug fixes, and beyond. Our goal is to make the + framework easy to understand and even easier to extend. Below are tutorials and + helpful information to get you started. - Before submitting a pull request, make sure your changes run - locally, follow the existing project structure, include clear - descriptions of the work completed, and are tested carefully. - Additional checklist details will be added as the contribution + Before submitting a pull request, make sure your changes run locally, follow the + existing project structure, include clear descriptions of the work completed, and + are tested carefully. Additional checklist details will be added as the contribution documentation is expanded. @@ -257,10 +253,9 @@ export default function ContributePage() { textAlign: "justify", }} > - Interested in getting more involved? We love collaboration! - Whether you are an industry partner, a university research - group, or an individual passionate about getting involved, we - have lots of project ideas we could use your help with. If + Interested in getting more involved? We love collaboration! Whether you are an + industry partner, a university research group, or an individual passionate about + getting involved, we have lots of project ideas we could use your help with. If interested, please reach out to Dr. Paul Bodily at{" "} bodipaul@isu.edu - . + + . @@ -286,8 +282,8 @@ export default function ContributePage() { textAlign: "justify", }} > - Terms of Use content will be added here. This section is - intended to describe expectations and conditions for using Redux. + Terms of Use content will be added here. This section is intended to describe + expectations and conditions for using Redux. @@ -302,43 +298,42 @@ export default function ContributePage() { textAlign: "justify", }} > - Privacy Policy content will be added here. This section is - intended to explain what information is collected, how it is - used, and how user privacy is protected. + Privacy Policy content will be added here. This section is intended to explain what + information is collected, how it is used, and how user privacy is protected. - - - - + sx={{ + display: "flex", + justifyContent: "center", + alignItems: "center", + pt: 2, + pb: 3, + }} + > + + + + ); diff --git a/pages/help/index.js b/pages/help/index.js index de0156bf..3be669aa 100644 --- a/pages/help/index.js +++ b/pages/help/index.js @@ -1,15 +1,14 @@ -import ResponsiveAppBar from "../../components/widgets/ResponsiveAppBar"; -import isulogo from "../../components/images/ISULogo.png"; - import { - createTheme, - ThemeProvider, - Container, Box, - Typography, + Container, CssBaseline, + createTheme, Link, + ThemeProvider, + Typography, } from "@mui/material"; +import isulogo from "../../components/images/ISULogo.png"; +import ResponsiveAppBar from "../../components/widgets/ResponsiveAppBar"; const backgroundLinks = [ { @@ -179,39 +178,38 @@ export default function HelpPage() { mb: 2.2, }} > - Redux is a dynamic, interactive computer science knowledgebase - consisting of canonical computer science problems, solutions, - and reduction algorithms. The following pages provide helpful - background to the organization of problems, solutions, and - reductions in Redux based on the concept of complexity classes: + Redux is a dynamic, interactive computer science knowledgebase consisting of + canonical computer science problems, solutions, and reduction algorithms. The + following pages provide helpful background to the organization of problems, + solutions, and reductions in Redux based on the concept of complexity classes: - {backgroundLinks.map((link, index) => ( - - - {link.label} - - {index < backgroundLinks.length - 1 ? ", " : "."} - - ))} - + sx={{ + color: "#374151", + fontSize: "0.87rem", + lineHeight: 1.9, + textAlign: "justify", + }} + > + {backgroundLinks.map((link, index) => ( + + + {link.label} + + {index < backgroundLinks.length - 1 ? ", " : "."} + + ))} + @@ -226,8 +224,7 @@ export default function HelpPage() { mb: 2, }} > - All of the content of the Redux knowledge base can be accessed - directly via: + All of the content of the Redux knowledge base can be accessed directly via: @@ -308,35 +305,35 @@ export default function HelpPage() { - - - - + sx={{ + display: "flex", + justifyContent: "center", + alignItems: "center", + pt: 2, + pb: 3, + }} + > + + + + ); diff --git a/pages/index.js b/pages/index.js index da5190f9..252a0ce3 100644 --- a/pages/index.js +++ b/pages/index.js @@ -7,46 +7,40 @@ */ import React from "react"; //React is implicitly imported +import Button from "react-bootstrap/Button"; import ProblemRowReact from "../components/pageblocks/ProblemRowReact"; import ReduceToRowReact from "../components/pageblocks/ReduceToRowReact"; -import VisualizeRowReact from "../components/pageblocks/VisualizeRowReact"; import SolveRowReact from "../components/pageblocks/SolveRowReact"; import VerifyRowReact from "../components/pageblocks/VerifyRowReact"; -import Button from "react-bootstrap/Button"; +import VisualizeRowReact from "../components/pageblocks/VisualizeRowReact"; import "bootstrap/dist/css/bootstrap.min.css"; -import Image from "next/image"; -import isulogo from "../components/images/ISULogo.png"; -import ResponsiveAppBar from "../components/widgets/ResponsiveAppBar"; -import { - Box, - createTheme, - Grid, - ThemeProvider, - Typograph, -} from "@mui/material"; -import { Container } from "react-bootstrap"; -import { useProblemProvider } from "../components/hooks/ProblemProvider"; -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, + DndContext, PointerSensor, TouchSensor, useSensor, useSensors, } from "@dnd-kit/core"; import { + arrayMove, SortableContext, - verticalListSortingStrategy, useSortable, - arrayMove, + verticalListSortingStrategy, } from "@dnd-kit/sortable"; import { CSS } from "@dnd-kit/utilities"; +import { Box, createTheme, Grid, ThemeProvider, Typograph } from "@mui/material"; +import Image from "next/image"; +import { memo, useEffect, useState } from "react"; // CHANGED: added useState for row order +import { Container } from "react-bootstrap"; +import { useHandleParameters } from "../components/eventHandlers/handleParameters"; +import { useUnload } from "../components/eventHandlers/handleUnload"; +import { useProblemProvider } from "../components/hooks/ProblemProvider"; +import isulogo from "../components/images/ISULogo.png"; +import TourLauncher from "../components/tour/TourLauncher"; +import ResponsiveAppBar from "../components/widgets/ResponsiveAppBar"; +import ShareButton from "../components/widgets/ShareButton"; const SHOW_QUANTUM_VIS = false; //Flag to show a quantum circuit visualizer (sandbox feature) const ProblemRowMemo = memo(ProblemRowReact); @@ -55,17 +49,12 @@ const VisualizeRowMemo = memo(VisualizeRowReact); const SolveRowMemo = memo(SolveRowReact); const VerifyRowMemo = memo(VerifyRowReact); -const reduxBaseUrl = '/api/redux/'; +const reduxBaseUrl = "/api/redux/"; function SortableRow({ id, children }) { - const { - attributes, - listeners, - setNodeRef, - transform, - transition, - isDragging, - } = useSortable({ id }); + const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ + id, + }); const style = { transform: CSS.Transform.toString(transform), @@ -134,22 +123,12 @@ function MainPageContent() { //useHandleParameters(); - const { problem, solver, verifier, reducer, visualization } = - useProblemProvider(reduxBaseUrl); + const { problem, solver, verifier, reducer, visualization } = useProblemProvider(reduxBaseUrl); - const [rowOrder, setRowOrder] = useState([ - "problem", - "reduce", - "visualize", - "solve", - "verify", - ]); + const [rowOrder, setRowOrder] = useState(["problem", "reduce", "visualize", "solve", "verify"]); - // PointerSensor covers mouse; TouchSensor adds mobile/tablet support - const sensors = useSensors( - useSensor(PointerSensor), - useSensor(TouchSensor) - ); + // PointerSensor covers mouse; TouchSensor adds mobile/tablet support + const sensors = useSensors(useSensor(PointerSensor), useSensor(TouchSensor)); const rowMap = { problem: , @@ -209,10 +188,7 @@ function MainPageContent() { collisionDetection={closestCenter} onDragEnd={handleDragEnd} > - + {rowOrder.map((key) => ( {rowMap[key]} @@ -250,7 +226,7 @@ function MainPageContent() { export default function MainPage() { return ( <> - + ); -} \ No newline at end of file +} diff --git a/pages/navigationgraph/index.js b/pages/navigationgraph/index.js index f79e94ce..0537a11a 100644 --- a/pages/navigationgraph/index.js +++ b/pages/navigationgraph/index.js @@ -6,7 +6,6 @@ // import 'bootstrap/dist/css/bootstrap.min.css'; // import { Container, Box, createTheme, ThemeProvider } from '@mui/material'; - // //OverlayViewF // /** @@ -24,10 +23,8 @@ // } - // export default function Test(props) { - // const [tool, setToolTip] = useState({}); // const [nodeTarget, setTarget] = useState(); // const [show, setShow] = useState(false); @@ -64,15 +61,11 @@ // ellipseArray[0].setAttribute("fill", "none") // }) - // } // }, 2000); - // }, []) - - // const handleClick = event => { // // 👇️ refers to the div element // const element = event.target; @@ -90,8 +83,6 @@ // } // }).catch(console.log("Problem not defined")); - - // } else { // setTarget(null) // setShow(false); @@ -100,8 +91,6 @@ // } // }; - - // return ( // <> @@ -159,19 +148,12 @@ // // - - // ); - - - // } - - function HomePage() { - return
    BUILD ISSUE PATCH. TEMPORARY
    - } - - export default HomePage \ No newline at end of file + return
    BUILD ISSUE PATCH. TEMPORARY
    ; +} + +export default HomePage; diff --git a/styles/globals.css b/styles/globals.css index 440bc82d..2ae1171a 100644 --- a/styles/globals.css +++ b/styles/globals.css @@ -1,7 +1,17 @@ html, body { - font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, - Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; + font-family: + -apple-system, + BlinkMacSystemFont, + Segoe UI, + Roboto, + Oxygen, + Ubuntu, + Cantarell, + Fira Sans, + Droid Sans, + Helvetica Neue, + sans-serif; } a { @@ -9,8 +19,8 @@ a { text-decoration: none; } -.accordion{ - transform-origin: top; /* Ensure it scales from the top */ +.accordion { + transform-origin: top; /* Ensure it scales from the top */ border: 2px solid #ccc; /* Dark border */ } @@ -18,7 +28,6 @@ a { text-align: center; margin: 100px; background-color: aliceblue; - } .TextBoxInner { @@ -88,7 +97,6 @@ a { * { box-sizing: border-box; - } /* .accordion{ @@ -101,7 +109,6 @@ a { padding: 10px; } */ - /* Remove active colors from default accordion button */ .accordion-button:not(.collapsed) { color: rgba(255, 255, 255, 0); @@ -128,7 +135,6 @@ a { background-position: 50%; } - .gutter.gutter-horizontal { /* background-image: url('grips/vertical.png'); */ cursor: col-resize; @@ -162,4 +168,4 @@ svg { -webkit-user-select: none; /* Safari/Chrome */ -moz-user-select: none; /* Firefox */ -ms-user-select: none; /* IE/Edge */ -} \ No newline at end of file +} diff --git a/styles/q.css b/styles/q.css index 597d0b00..fc16ca1a 100644 --- a/styles/q.css +++ b/styles/q.css @@ -5,9 +5,6 @@ */ @charset "utf-8"; - - - /* This file is in the process of being separated @@ -26,297 +23,221 @@ */ - - - -svg, :root { - - - - /**************/ - /* */ - /* Colors */ - /* */ - /**************/ - - - /* Base color (blue) */ - - --Q-color-base-hue: 210; - --Q-color-base-saturation: 85%; - --Q-color-base-lightness: 40%; - - - /* Red */ - - --Q-color-red-hue: calc( var( --Q-color-base-hue ) + 180 - 30 ); - --Q-color-red-saturation: 85%; - --Q-color-red-lightness: 45%; - --Q-color-red: hsl( - - var( --Q-color-red-hue ), - var( --Q-color-red-saturation ), - var( --Q-color-red-lightness ) - ); - - - /* Orange */ - - --Q-color-orange-hue: calc( var( --Q-color-base-hue ) + 180 - 15 ); - --Q-color-orange-saturation: 85%; - --Q-color-orange-lightness: 50%; - --Q-color-orange: hsl( - - var( --Q-color-orange-hue ), - var( --Q-color-orange-saturation ), - var( --Q-color-orange-lightness ) - ); - - - /* Yellow */ - - --Q-color-yellow-hue: calc( var( --Q-color-base-hue ) + 180 + 15 ); - --Q-color-yellow-saturation: 90%; - --Q-color-yellow-lightness: 50%; - --Q-color-yellow: hsl( - - var( --Q-color-yellow-hue ), - var( --Q-color-yellow-saturation ), - var( --Q-color-yellow-lightness ) - ); - - - /* Green */ - - --Q-color-green-hue: calc( var( --Q-color-base-hue ) + 180 + 60 ); - --Q-color-green-saturation: 80%; - --Q-color-green-lightness: 35%; - --Q-color-green: hsl( - - var( --Q-color-green-hue ), - var( --Q-color-green-saturation ), - var( --Q-color-green-lightness ) - ); - - - /* Blue */ - - --Q-color-blue-hue: var( --Q-color-base-hue ); - --Q-color-blue-saturation: var( --Q-color-base-saturation ); - --Q-color-blue-lightness: var( --Q-color-base-lightness ); - --Q-color-blue: hsl( - - var( --Q-color-blue-hue ), - var( --Q-color-blue-saturation ), - var( --Q-color-blue-lightness ) - ); - - - /* Grayscale */ - - --Q-color-white: #FFFFFF; - --Q-color-chalk: #F9F9F9; - --Q-color-newsprint: #F3F3F3; - --Q-color-titanium: #CCCCCC; - --Q-color-slate: #777777; - --Q-color-charcoal: #333333; - --Q-color-black: #000000; - - - /* Background */ - - --Q-color-background-hue: var( --Q-color-base-hue ); - --Q-color-background-saturation: 15%; - --Q-color-background-lightness: 98%; - --Q-color-background: hsl( - - var( --Q-color-background-hue ), - var( --Q-color-background-saturation ), - var( --Q-color-background-lightness ) - ); - /*--Q-color-background: white;*/ - - - /* Misc */ - - --Q-text-color: hsl( - - var( --Q-color-base-hue ), - 5%, - 35% - ); - --Q-text-code-comment-color: rgba( 0, 0, 0, 0.4 ); - --Q-text-code-output-color: rgba( 0, 0, 0, 1 ); - - --Q-selection-color: var( --Q-color-black ); - --Q-selection-background-color: var( --Q-color-yellow ); - - --Q-hyperlink-internal-color: var( --Q-color-blue ); - --Q-hyperlink-external-color: var( --Q-text-color ); - - --Q-background-callout-color: var( --Q-color-white ); - - --Q-svg-fill-color: var( --Q-text-color ); - - - - - /* Fonts */ - - --Q-font-family-serif: 'Source Serif Pro', 'Roboto Slab', 'Georgia', serif; - --Q-font-family-sans: 'SF Pro Text', system-ui, -apple-system, 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif; - --Q-font-family-mono: 'Roboto Mono', 'Source Code Pro', 'Menlo', 'Courier New', monospace; - --Q-font-family-symbols: 'Georgia', serif; +svg, +:root { + /**************/ + /* */ + /* Colors */ + /* */ + /**************/ + + /* Base color (blue) */ + + --Q-color-base-hue: 210; + --Q-color-base-saturation: 85%; + --Q-color-base-lightness: 40%; + + /* Red */ + + --Q-color-red-hue: calc(var(--Q-color-base-hue) + 180 - 30); + --Q-color-red-saturation: 85%; + --Q-color-red-lightness: 45%; + --Q-color-red: hsl( + var(--Q-color-red-hue), + var(--Q-color-red-saturation), + var(--Q-color-red-lightness) + ); + + /* Orange */ + + --Q-color-orange-hue: calc(var(--Q-color-base-hue) + 180 - 15); + --Q-color-orange-saturation: 85%; + --Q-color-orange-lightness: 50%; + --Q-color-orange: hsl( + var(--Q-color-orange-hue), + var(--Q-color-orange-saturation), + var(--Q-color-orange-lightness) + ); + + /* Yellow */ + + --Q-color-yellow-hue: calc(var(--Q-color-base-hue) + 180 + 15); + --Q-color-yellow-saturation: 90%; + --Q-color-yellow-lightness: 50%; + --Q-color-yellow: hsl( + var(--Q-color-yellow-hue), + var(--Q-color-yellow-saturation), + var(--Q-color-yellow-lightness) + ); + + /* Green */ + + --Q-color-green-hue: calc(var(--Q-color-base-hue) + 180 + 60); + --Q-color-green-saturation: 80%; + --Q-color-green-lightness: 35%; + --Q-color-green: hsl( + var(--Q-color-green-hue), + var(--Q-color-green-saturation), + var(--Q-color-green-lightness) + ); + + /* Blue */ + + --Q-color-blue-hue: var(--Q-color-base-hue); + --Q-color-blue-saturation: var(--Q-color-base-saturation); + --Q-color-blue-lightness: var(--Q-color-base-lightness); + --Q-color-blue: hsl( + var(--Q-color-blue-hue), + var(--Q-color-blue-saturation), + var(--Q-color-blue-lightness) + ); + + /* Grayscale */ + + --Q-color-white: #ffffff; + --Q-color-chalk: #f9f9f9; + --Q-color-newsprint: #f3f3f3; + --Q-color-titanium: #cccccc; + --Q-color-slate: #777777; + --Q-color-charcoal: #333333; + --Q-color-black: #000000; + + /* Background */ + + --Q-color-background-hue: var(--Q-color-base-hue); + --Q-color-background-saturation: 15%; + --Q-color-background-lightness: 98%; + --Q-color-background: hsl( + var(--Q-color-background-hue), + var(--Q-color-background-saturation), + var(--Q-color-background-lightness) + ); + /*--Q-color-background: white;*/ + + /* Misc */ + + --Q-text-color: hsl(var(--Q-color-base-hue), 5%, 35%); + --Q-text-code-comment-color: rgba(0, 0, 0, 0.4); + --Q-text-code-output-color: rgba(0, 0, 0, 1); + + --Q-selection-color: var(--Q-color-black); + --Q-selection-background-color: var(--Q-color-yellow); + + --Q-hyperlink-internal-color: var(--Q-color-blue); + --Q-hyperlink-external-color: var(--Q-text-color); + + --Q-background-callout-color: var(--Q-color-white); + + --Q-svg-fill-color: var(--Q-text-color); + + /* Fonts */ + + --Q-font-family-serif: "Source Serif Pro", "Roboto Slab", "Georgia", serif; + --Q-font-family-sans: + "SF Pro Text", system-ui, -apple-system, "Helvetica Neue", "Helvetica", "Arial", sans-serif; + --Q-font-family-mono: "Roboto Mono", "Source Code Pro", "Menlo", "Courier New", monospace; + --Q-font-family-symbols: "Georgia", serif; } - - - - /*******************/ - /* */ - /* Interactive */ - /* */ +/*******************/ +/* */ +/* Interactive */ +/* */ /*******************/ - -.Q-input, +.Q-input, .Q-circuit-text-input { - - margin: 1.5rem 0 0 0 !important; - outline: none !important; - border: none !important; - border-radius: 1.2rem !important; - box-shadow: - 0.2rem 0.2rem 0.2rem rgba( 0, 0, 0, 0.15 ) inset, - -0.2rem -0.2rem 0.2rem rgba( 255, 255, 255, 1 ) inset; - - background: linear-gradient( - - 0.375turn, - rgba( 255, 255, 255, 1.0 ), - rgba( 255, 255, 255, 0.2 ) - ) !important; -} -.Q-input, + margin: 1.5rem 0 0 0 !important; + outline: none !important; + border: none !important; + border-radius: 1.2rem !important; + box-shadow: + 0.2rem 0.2rem 0.2rem rgba(0, 0, 0, 0.15) inset, + -0.2rem -0.2rem 0.2rem rgba(255, 255, 255, 1) inset; + + background: linear-gradient( + 0.375turn, + rgba(255, 255, 255, 1), + rgba(255, 255, 255, 0.2) + ) !important; +} +.Q-input, .Q-circuit-text-input { - - padding: 1.5rem !important; - color: #555 !important; - font-size: 0.9rem !important; - line-height: 1.2rem !important; + padding: 1.5rem !important; + color: #555 !important; + font-size: 0.9rem !important; + line-height: 1.2rem !important; } - - .Q-circuit-text-input { - - /*min-width: 18rem;*/ - width: 100%; - min-height: 8rem; - /*margin: 1rem 0 2rem 0;*/ - margin: 1rem 0 0 0; - border: 1px solid var( --Q-color-blue ); - border-radius: 0.5rem; - background-color: var( --Q-color-chalk ); - padding: 1rem 0 0 2rem; - color: var( --Q-color-blue ); - font-family: var( --Q-font-family-mono ); - font-size: 1.0rem; - line-height: 1.2rem; - white-space: pre; - word-wrap: normal;/* OMFG, iOS you make me sad. */ + /*min-width: 18rem;*/ + width: 100%; + min-height: 8rem; + /*margin: 1rem 0 2rem 0;*/ + margin: 1rem 0 0 0; + border: 1px solid var(--Q-color-blue); + border-radius: 0.5rem; + background-color: var(--Q-color-chalk); + padding: 1rem 0 0 2rem; + color: var(--Q-color-blue); + font-family: var(--Q-font-family-mono); + font-size: 1rem; + line-height: 1.2rem; + white-space: pre; + word-wrap: normal; /* OMFG, iOS you make me sad. */ } - - - - - .Q-button { - - position: relative; - text-align: right; - margin: 0.5rem 1rem 0 0; - border-radius: 3rem; - box-shadow: - -0.1rem -0.1rem 0 rgba( 255, 255, 255, 1 ), - 0.1rem 0.1rem 0.2rem rgba( 0, 0, 0, 0.3 ); - height: 3rem; - background: - var( --Q-color-blue ) - linear-gradient( - - 0.4turn, - rgba( 255, 255, 255, 0.2 ), - rgba( 0, 0, 0, 0.08 ) - ); - padding: 0.8rem 1.8rem; - color: var( --Q-color-white ); - font-family: var( --Q-font-family-sans ); - font-size: 1rem; - line-height: 1rem; - font-weight: 500; - letter-spacing: 0; - text-shadow: -1px -1px 0 rgba( 0, 0, 0, 0.1 ); - cursor: pointer; + position: relative; + text-align: right; + margin: 0.5rem 1rem 0 0; + border-radius: 3rem; + box-shadow: + -0.1rem -0.1rem 0 rgba(255, 255, 255, 1), + 0.1rem 0.1rem 0.2rem rgba(0, 0, 0, 0.3); + height: 3rem; + background: var(--Q-color-blue) + linear-gradient(0.4turn, rgba(255, 255, 255, 0.2), rgba(0, 0, 0, 0.08)); + padding: 0.8rem 1.8rem; + color: var(--Q-color-white); + font-family: var(--Q-font-family-sans); + font-size: 1rem; + line-height: 1rem; + font-weight: 500; + letter-spacing: 0; + text-shadow: -1px -1px 0 rgba(0, 0, 0, 0.1); + cursor: pointer; } .Q-button:hover { - - background: - hsl( - - var( --Q-color-blue-hue ), - var( --Q-color-blue-saturation ), - calc( var( --Q-color-blue-lightness ) * 1.2 ) - ) - linear-gradient( - - 0.4turn, - rgba( 255, 255, 255, 0.2 ), - rgba( 0, 0, 0, 0.08 ) - ); + background: hsl( + var(--Q-color-blue-hue), + var(--Q-color-blue-saturation), + calc(var(--Q-color-blue-lightness) * 1.2) + ) + linear-gradient(0.4turn, rgba(255, 255, 255, 0.2), rgba(0, 0, 0, 0.08)); } .Q-button:focus { - - margin-top: 0.7rem; - margin-bottom: -0.2rem; - margin-right: 0.9rem; - outline: none; - box-shadow: - -0.1rem -0.1rem 0 rgba( 255, 255, 255, 1 ) inset, - 0.1rem 0.1rem 0.2rem rgba( 0, 0, 0, 0.3 ) inset; - background: - var( --Q-color-blue ) - linear-gradient( - - 0.4turn, - rgba( 0, 0, 0, 0.08 ), - rgba( 255, 255, 255, 0.2 ) - ); + margin-top: 0.7rem; + margin-bottom: -0.2rem; + margin-right: 0.9rem; + outline: none; + box-shadow: + -0.1rem -0.1rem 0 rgba(255, 255, 255, 1) inset, + 0.1rem 0.1rem 0.2rem rgba(0, 0, 0, 0.3) inset; + background: var(--Q-color-blue) + linear-gradient(0.4turn, rgba(0, 0, 0, 0.08), rgba(255, 255, 255, 0.2)); } .Q-button[disabled] { - - box-shadow: - -0.1rem -0.1rem 0 rgba( 255, 255, 255, 1 ), - 0.1rem 0.1rem 0.2rem rgba( 0, 0, 0, 0.3 ); - background: - var( --Q-color-background ) - linear-gradient( - - 0.45turn, - rgba( 255, 255, 255, 0.1 ), - rgba( 0, 0, 0, 0.05 ) - ); - color: rgba( 0, 0, 0, 0.3 ); - text-shadow: 1px 1px 0 rgba( 255, 255, 255, 1 ); - cursor: default; + box-shadow: + -0.1rem -0.1rem 0 rgba(255, 255, 255, 1), + 0.1rem 0.1rem 0.2rem rgba(0, 0, 0, 0.3); + background: var(--Q-color-background) + linear-gradient(0.45turn, rgba(255, 255, 255, 0.1), rgba(0, 0, 0, 0.05)); + color: rgba(0, 0, 0, 0.3); + text-shadow: 1px 1px 0 rgba(255, 255, 255, 1); + cursor: default; } - - - - - /* The below still need to be prefaced with “Q-” @@ -324,167 +245,118 @@ svg, :root { */ - - - - - - /*************/ - /* */ - /* Maths */ - /* */ /*************/ - +/* */ +/* Maths */ +/* */ +/*************/ .maths { - - max-width: 100%; - overflow-x: auto; - font-family: var( --Q-font-family-sans ); + max-width: 100%; + overflow-x: auto; + font-family: var(--Q-font-family-sans); } dd .maths { - - margin-top: 0; - margin-left: 0; + margin-top: 0; + margin-left: 0; } - - - .symbol { - - font-size: 1.1em; - padding: 0 0.1em; - font-family: var( --Q-font-family-symbols ); - font-style: italic; - font-weight: 900; - letter-spacing: 0.05em; + font-size: 1.1em; + padding: 0 0.1em; + font-family: var(--Q-font-family-symbols); + font-style: italic; + font-weight: 900; + letter-spacing: 0.05em; } - - - .division { - - display: inline-block; - vertical-align: middle; - margin: 10px; + display: inline-block; + vertical-align: middle; + margin: 10px; } .division td { - - padding: 5px; + padding: 5px; } .dividend { - - border-bottom: 1px solid #CCC; - text-align: center; + border-bottom: 1px solid #ccc; + text-align: center; } .divisor { - - text-align: center; + text-align: center; } - - - .matrix { - - display: inline-block; - vertical-align: middle; - position: relative; - align: middle; - margin: 1em; - padding: 1em; - font-family: var( --Q-font-family-mono ); - font-weight: 300; - line-height: 1em; - text-align: right; + display: inline-block; + vertical-align: middle; + position: relative; + align: middle; + margin: 1em; + padding: 1em; + font-family: var(--Q-font-family-mono); + font-weight: 300; + line-height: 1em; + text-align: right; } .matrix td { - - padding: 5px 10px; + padding: 5px 10px; } -.matrix-bracket-left, .matrix-bracket-right { - - position: absolute; - top: 0; - width: 5px; - height: 100%; - border: 1px solid #CCC; +.matrix-bracket-left, +.matrix-bracket-right { + position: absolute; + top: 0; + width: 5px; + height: 100%; + border: 1px solid #ccc; } .matrix-bracket-left { - - left: 0; - border-right: none; + left: 0; + border-right: none; } .matrix-bracket-right { - - right: 0; - border-left: none; + right: 0; + border-left: none; } /*.matrix.qubit tr:first-child td { color: #BBB; }*/ - - .Q-state-vector, .complex-vector { - - font-family: var( --Q-font-family-mono ); + font-family: var(--Q-font-family-mono); } .Q-state-vector.bra::before, .complex-vector.bra::before { - - content: '⟨'; - color: #BBB; + content: "⟨"; + color: #bbb; } .Q-state-vector.bra::after, .complex-vector.bra::after { - - content: '|'; - color: #BBB; + content: "|"; + color: #bbb; } .Q-state-vector.ket::before, .complex-vector.ket::before { - - content: '|'; - color: #BBB; + content: "|"; + color: #bbb; } .Q-state-vector.ket::after, .complex-vector.ket::after { - - content: '⟩'; - color: #BBB; + content: "⟩"; + color: #bbb; } .Q-state-vector.bra + .Q-state-vector.ket::before, .complex-vector.bra + .complex-vector.ket::before { - - content: ''; + content: ""; } - - - - - - /* Copyright © 2019–2020, Stewart Smith. See LICENSE for details. */ - - - - - - - - - /* Z indices: @@ -562,110 +434,74 @@ dd .maths { */ - - - - - .Q-circuit, .Q-circuit-palette { - - position: relative; - width: 100%; + position: relative; + width: 100%; } .Q-circuit-palette { - - -moz-user-select: none; - -webkit-user-select: none; - -ms-user-select: none; - user-select: none; - line-height: 0; + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; + line-height: 0; } .Q-circuit-palette > div { - - display: inline-block; - position: relative; - width: 4rem; - height: 4rem; + display: inline-block; + position: relative; + width: 4rem; + height: 4rem; } - .Q-circuit { - - margin: 1rem 0 2rem 0; - /*border-top: 2px solid hsl( 0, 0%, 50% );*/ + margin: 1rem 0 2rem 0; + /*border-top: 2px solid hsl( 0, 0%, 50% );*/ } .Q-circuit-board-foreground { - - line-height: 3.85rem; - width: auto; + line-height: 3.85rem; + width: auto; } - - - - - - /***************/ - /* */ - /* Toolbar */ - /* */ /***************/ - +/* */ +/* Toolbar */ +/* */ +/***************/ .Q-circuit-toolbar { - - display: block; - -moz-user-select: none; - -webkit-user-select: none; - -ms-user-select: none; - user-select: none; - margin-bottom: 0.5rem; - - box-sizing: border-box; - display: grid; - grid-auto-columns: 3.6rem; - grid-auto-rows: 3.0rem; - grid-auto-flow: column; - + display: block; + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; + margin-bottom: 0.5rem; + + box-sizing: border-box; + display: grid; + grid-auto-columns: 3.6rem; + grid-auto-rows: 3rem; + grid-auto-flow: column; } .Q-circuit-button { - - position: relative; - display: inline-block; - /*margin: 0 0.5rem 0.5rem 0;*/ - width: 3.6rem; - height: 3rem; -/* box-shadow: + position: relative; + display: inline-block; + /*margin: 0 0.5rem 0.5rem 0;*/ + width: 3.6rem; + height: 3rem; + /* box-shadow: -0.1rem -0.1rem 0 rgba( 255, 255, 255, 0.8 ), 0.1rem 0.1rem 0.1rem rgba( 0, 0, 0, 0.35 );*/ - border-top: 1px solid hsl( - - var( --Q-color-background-hue ), - var( --Q-color-background-saturation ), - 100% - ); - border-right: 1px solid hsl( - - var( --Q-color-background-hue ), - var( --Q-color-background-saturation ), - 90% - ); - border-bottom: 1px solid hsl( - - var( --Q-color-background-hue ), - var( --Q-color-background-saturation ), - 85% - ); - border-left: 1px solid hsl( - - var( --Q-color-background-hue ), - var( --Q-color-background-saturation ), - 97% - ); - background: var( --Q-color-background ); -/* background: + border-top: 1px solid + hsl(var(--Q-color-background-hue), var(--Q-color-background-saturation), 100%); + border-right: 1px solid + hsl(var(--Q-color-background-hue), var(--Q-color-background-saturation), 90%); + border-bottom: 1px solid + hsl(var(--Q-color-background-hue), var(--Q-color-background-saturation), 85%); + border-left: 1px solid + hsl(var(--Q-color-background-hue), var(--Q-color-background-saturation), 97%); + background: var(--Q-color-background); + /* background: var( --Q-color-background ) linear-gradient( @@ -674,123 +510,96 @@ dd .maths { rgba( 0, 0, 0, 0.02 ), rgba( 255, 255, 255, 0.1 ) );*/ - color: hsl( - - var( --Q-color-background-hue ), - var( --Q-color-background-saturation ), - 30% - ); - text-shadow: 1px 1px 0 rgba( 255, 255, 255, 1 ); - /*border-radius: 0.5rem;*/ - /*border-radius: 100%;*/ - line-height: 2.9rem; - text-align: center; - cursor: pointer; - overflow: hidden; - font-weight: 900; + color: hsl(var(--Q-color-background-hue), var(--Q-color-background-saturation), 30%); + text-shadow: 1px 1px 0 rgba(255, 255, 255, 1); + /*border-radius: 0.5rem;*/ + /*border-radius: 100%;*/ + line-height: 2.9rem; + text-align: center; + cursor: pointer; + overflow: hidden; + font-weight: 900; } .Q-circuit-toolbar .Q-circuit-button:first-child { - - border-top-left-radius: 0.5rem; - border-bottom-left-radius: 0.5rem; + border-top-left-radius: 0.5rem; + border-bottom-left-radius: 0.5rem; } .Q-circuit-toolbar .Q-circuit-button:last-child { - - border-top-right-radius: 0.5rem; - border-bottom-right-radius: 0.5rem; + border-top-right-radius: 0.5rem; + border-bottom-right-radius: 0.5rem; } .Q-circuit-locked .Q-circuit-button, .Q-circuit-button[Q-disabled] { - - color: hsl( - - var( --Q-color-background-hue ), - var( --Q-color-background-saturation ), - 85% - ); - cursor: not-allowed; + color: hsl(var(--Q-color-background-hue), var(--Q-color-background-saturation), 85%); + cursor: not-allowed; } .Q-circuit-locked .Q-circuit-toggle-lock { - - color: inherit; - cursor: pointer; + color: inherit; + cursor: pointer; } - - - .Q-circuit-board-container { - - position: relative; - margin: 0 0 2rem 0; - margin: 0; - width: 100%; - max-height: 60vh; - overflow: scroll; + position: relative; + margin: 0 0 2rem 0; + margin: 0; + width: 100%; + max-height: 60vh; + overflow: scroll; } .Q-circuit-board { - - position: relative; - -moz-user-select: none; - -webkit-user-select: none; - -ms-user-select: none; - user-select: none; + position: relative; + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; } /*.Q-circuit-palette,*/ .Q-circuit-board-foreground, .Q-circuit-board-background, .Q-circuit-clipboard { - - box-sizing: border-box; - display: grid; - grid-auto-rows: 4rem; - grid-auto-columns: 4rem; - grid-auto-flow: column; + box-sizing: border-box; + display: grid; + grid-auto-rows: 4rem; + grid-auto-columns: 4rem; + grid-auto-flow: column; } /*.Q-circuit-palette,*/ .Q-circuit-board-foreground, .Q-circuit-board-background { - - position: relative; - top: 0; - left: 0; - width: 100%; - height: 100%; + position: relative; + top: 0; + left: 0; + width: 100%; + height: 100%; } .Q-circuit-clipboard { - - position: absolute; - z-index: 100; - min-width: 4rem; - min-height: 4rem; - transform: scale( 1.05 ); + position: absolute; + z-index: 100; + min-width: 4rem; + min-height: 4rem; + transform: scale(1.05); } -.Q-circuit-clipboard, .Q-circuit-clipboard > div { - - cursor: grabbing; +.Q-circuit-clipboard, +.Q-circuit-clipboard > div { + cursor: grabbing; } .Q-circuit-clipboard-danger .Q-circuit-operation { - - background-color: var( --Q-color-yellow ); + background-color: var(--Q-color-yellow); } .Q-circuit-clipboard-destroy { - - animation-name: Q-circuit-clipboard-poof; - animation-fill-mode: forwards; - animation-duration: 0.3s; - animation-iteration-count: 1; + animation-name: Q-circuit-clipboard-poof; + animation-fill-mode: forwards; + animation-duration: 0.3s; + animation-iteration-count: 1; } @keyframes Q-circuit-clipboard-poof { - - 100% { - - transform: scale( 1.5 ); - opacity: 0; - } + 100% { + transform: scale(1.5); + opacity: 0; + } } .Q-circuit-board-background { - - /* + /* Clipboard: 100 Operation: 0 @@ -798,350 +607,242 @@ dd .maths { Background: -20 */ - position: absolute; - z-index: -20; - color: rgba( 0, 0, 0, 0.2 ); + position: absolute; + z-index: -20; + color: rgba(0, 0, 0, 0.2); } .Q-circuit-board-background > div { - -/* transition: + /* transition: background-color 0.2s, color 0.2s;*/ } .Q-circuit-board-background .Q-circuit-cell-highlighted { - - background-color: hsl( - - var( --Q-color-background-hue ), - var( --Q-color-background-saturation ), - 95% - ); - /*transition: none;*/ + background-color: hsl(var(--Q-color-background-hue), var(--Q-color-background-saturation), 95%); + /*transition: none;*/ } - - - .Q-circuit-register-wire { - - position: absolute; - top: calc( 50% - 0.5px ); - width: 100%; - height: 1px; - background-color: hsl( - - var( --Q-color-background-hue ), - var( --Q-color-background-saturation ), - 50% - ); + position: absolute; + top: calc(50% - 0.5px); + width: 100%; + height: 1px; + background-color: hsl(var(--Q-color-background-hue), var(--Q-color-background-saturation), 50%); } - - .Q-circuit-palette > div, .Q-circuit-clipboard > div, .Q-circuit-board-foreground > div { - - text-align: center; + text-align: center; } - - - - - - /***************/ - /* */ - /* Headers */ - /* */ /***************/ - +/* */ +/* Headers */ +/* */ +/***************/ .Q-circuit-header { - - position: sticky; - z-index: 2; - margin: 0; - /*background-color: var( --Q-color-background );*/ - background-color: white; - color: hsl( - - var( --Q-color-background-hue ), - var( --Q-color-background-saturation ), - 75% - ); - font-family: var( --Q-font-family-mono ); + position: sticky; + z-index: 2; + margin: 0; + /*background-color: var( --Q-color-background );*/ + background-color: white; + color: hsl(var(--Q-color-background-hue), var(--Q-color-background-saturation), 75%); + font-family: var(--Q-font-family-mono); } .Q-circuit-input.Q-circuit-cell-highlighted, .Q-circuit-header.Q-circuit-cell-highlighted { - - background-color: hsl( - - var( --Q-color-background-hue ), - var( --Q-color-background-saturation ), - 95% - ); - color: black; + background-color: hsl(var(--Q-color-background-hue), var(--Q-color-background-saturation), 95%); + color: black; } .Q-circuit-selectall { - - z-index: 3; - margin: 0; - top: 0; - /*left: 4rem;*/ - /*grid-column: 2;*/ - left: 0; - grid-column-start: 1; - grid-column-end: 3; - grid-row: 1; - cursor: se-resize; + z-index: 3; + margin: 0; + top: 0; + /*left: 4rem;*/ + /*grid-column: 2;*/ + left: 0; + grid-column-start: 1; + grid-column-end: 3; + grid-row: 1; + cursor: se-resize; } .Q-circuit-moment-label, .Q-circuit-moment-add { - - grid-row: 1; - top: 0; - cursor: s-resize; + grid-row: 1; + top: 0; + cursor: s-resize; } .Q-circuit-register-label, .Q-circuit-register-add { - - grid-column: 2; - left: 4rem; - cursor: e-resize; + grid-column: 2; + left: 4rem; + cursor: e-resize; } .Q-circuit-moment-add, .Q-circuit-register-add { - - cursor: pointer; + cursor: pointer; } .Q-circuit-moment-add, .Q-circuit-register-add { - - display: none; + display: none; } .Q-circuit-selectall, .Q-circuit-moment-label, .Q-circuit-moment-add { - - border-bottom: 1px solid hsl( - - var( --Q-color-background-hue ), - var( --Q-color-background-saturation ), - 95% - ); + border-bottom: 1px solid + hsl(var(--Q-color-background-hue), var(--Q-color-background-saturation), 95%); } .Q-circuit-selectall, .Q-circuit-register-label, .Q-circuit-register-add { - - border-right: 1px solid hsl( - - var( --Q-color-background-hue ), - var( --Q-color-background-saturation ), - 95% - ); + border-right: 1px solid + hsl(var(--Q-color-background-hue), var(--Q-color-background-saturation), 95%); } .Q-circuit-input { - - position: sticky; - z-index: 2; - grid-column: 1; - left: 0; - /*background-color: var( --Q-color-background );*/ - background-color: white; - font-size: 1.5rem; - font-weight: 900; - font-family: var( --Q-font-family-mono ); + position: sticky; + z-index: 2; + grid-column: 1; + left: 0; + /*background-color: var( --Q-color-background );*/ + background-color: white; + font-size: 1.5rem; + font-weight: 900; + font-family: var(--Q-font-family-mono); } - - - - - .Q-circuit-operation-link-container { - - --Q-link-stroke: 3px; - --Q-link-radius: 100%; - - display: block; - position: relative; - left: calc( 50% - ( var( --Q-link-stroke ) / 2 )); - width: 50%; - height: 100%; - overflow: hidden; + --Q-link-stroke: 3px; + --Q-link-radius: 100%; + + display: block; + position: relative; + left: calc(50% - (var(--Q-link-stroke) / 2)); + width: 50%; + height: 100%; + overflow: hidden; } .Q-circuit-operation-link-container.Q-circuit-cell-highlighted { - - background-color: transparent; + background-color: transparent; } .Q-circuit-operation-link { + display: block; + position: absolute; + width: calc(var(--Q-link-stroke) * 2); + height: calc(100% - 4rem + var(--Q-link-stroke)); + /*border: var( --Q-link-stroke ) solid hsl( 0, 0%, 50% );*/ + border: var(--Q-link-stroke) solid hsl(var(--Q-color-background-hue), 10%, 30%); - display: block; - position: absolute; - width: calc( var( --Q-link-stroke ) * 2 ); - height: calc( 100% - 4rem + var( --Q-link-stroke )); - /*border: var( --Q-link-stroke ) solid hsl( 0, 0%, 50% );*/ - border: var( --Q-link-stroke ) solid hsl( + /*border: var( --Q-link-stroke ) solid var( --Q-color-orange );*/ - var( --Q-color-background-hue ), - 10%, - 30% - ); - - /*border: var( --Q-link-stroke ) solid var( --Q-color-orange );*/ - - transform: translate( -50%, calc( 2rem - ( var( --Q-link-stroke ) / 2 ))); - transform-origin: center; + transform: translate(-50%, calc(2rem - (var(--Q-link-stroke) / 2))); + transform-origin: center; } .Q-circuit-operation-link.Q-circuit-operation-link-curved { - - width: calc( var( --Q-link-radius ) - var( --Q-link-stroke )); - width: 200%; - border-radius: 100%; + width: calc(var(--Q-link-radius) - var(--Q-link-stroke)); + width: 200%; + border-radius: 100%; } - - - - - - /******************/ - /* */ - /* Operations */ - /* */ /******************/ - +/* */ +/* Operations */ +/* */ +/******************/ .Q-circuit-operation { - - position: relative; - /*--Q-operation-color-hue: var( --Q-color-green-hue ); + position: relative; + /*--Q-operation-color-hue: var( --Q-color-green-hue ); --Q-operation-color-main: var( --Q-color-green );*/ - - --Q-operation-color-hue: var( --Q-color-blue-hue ); - --Q-operation-color-main: hsl( - - var( --Q-operation-color-hue ), - 10%, - 35% - ); - - --Q-operation-color-light: hsl( - - var( --Q-operation-color-hue ), - 10%, - 50% - ); - --Q-operation-color-dark: hsl( - - var( --Q-operation-color-hue ), - 10%, - 25% - ); - color: white; - text-shadow: -0.05rem -0.05rem 0 rgba( 0, 0, 0, 0.1 ); - font-size: 1.5rem; - line-height: 2.9rem; - font-weight: 900; - cursor: grab; + + --Q-operation-color-hue: var(--Q-color-blue-hue); + --Q-operation-color-main: hsl(var(--Q-operation-color-hue), 10%, 35%); + + --Q-operation-color-light: hsl(var(--Q-operation-color-hue), 10%, 50%); + --Q-operation-color-dark: hsl(var(--Q-operation-color-hue), 10%, 25%); + color: white; + text-shadow: -0.05rem -0.05rem 0 rgba(0, 0, 0, 0.1); + font-size: 1.5rem; + line-height: 2.9rem; + font-weight: 900; + cursor: grab; } .Q-circuit-locked .Q-circuit-operation { - - cursor: not-allowed; + cursor: not-allowed; } .Q-circuit-operation-tile { - - position: absolute; - top: 0.5rem; - left: 0.5rem; - right: 0.5rem; - bottom: 0.5rem; - - /*margin: 0.5rem;*/ - /*padding: 0.5rem;*/ - - /*box-shadow: 0.1rem 0.1rem 0.2rem rgba( 0, 0, 0, 0.2 );*/ - border-radius: 0.2rem; - /* + position: absolute; + top: 0.5rem; + left: 0.5rem; + right: 0.5rem; + bottom: 0.5rem; + + /*margin: 0.5rem;*/ + /*padding: 0.5rem;*/ + + /*box-shadow: 0.1rem 0.1rem 0.2rem rgba( 0, 0, 0, 0.2 );*/ + border-radius: 0.2rem; + /* border-top: 0.1rem solid var( --Q-operation-color-light ); border-left: 0.1rem solid var( --Q-operation-color-light ); border-right: 0.1rem solid var( --Q-operation-color-dark ); border-bottom: 0.1rem solid var( --Q-operation-color-dark ); */ - background: - var( --Q-operation-color-main ) - /*linear-gradient( + background: var(--Q-operation-color-main); + /*linear-gradient( 0.45turn, rgba( 255, 255, 255, 0.1 ), rgba( 0, 0, 0, 0.05 ) - )*/; + )*/ } .Q-circuit-palette .Q-circuit-operation:hover { - - /*background-color: rgba( 255, 255, 255, 0.6 );*/ - background-color: white; + /*background-color: rgba( 255, 255, 255, 0.6 );*/ + background-color: white; } .Q-circuit-palette .Q-circuit-operation-tile { + --Q-before-rotation: 12deg; + --Q-before-x: 1px; + --Q-before-y: -2px; - --Q-before-rotation: 12deg; - --Q-before-x: 1px; - --Q-before-y: -2px; - - --Q-after-rotation: -7deg; - --Q-after-x: -2px; - --Q-after-y: 3px; - - box-shadow: 0.2rem 0.2rem 0.2rem rgba( 0, 0, 0, 0.2 ); + --Q-after-rotation: -7deg; + --Q-after-x: -2px; + --Q-after-y: 3px; + + box-shadow: 0.2rem 0.2rem 0.2rem rgba(0, 0, 0, 0.2); } .Q-circuit-palette .Q-circuit-operation-tile:before, .Q-circuit-palette .Q-circuit-operation-tile:after { - - content: ""; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - border-radius: 0.2rem; - /*background-color: hsl( 0, 0%, 60% );*/ - - background-color: var( --Q-operation-color-dark ); - transform: - translate( var( --Q-before-x ), var( --Q-before-y )) - rotate( var( --Q-before-rotation )); - z-index: -10; - /*z-index: 10;*/ - display: block; - box-shadow: 0.2rem 0.2rem 0.2rem rgba( 0, 0, 0, 0.2 ); + content: ""; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + border-radius: 0.2rem; + /*background-color: hsl( 0, 0%, 60% );*/ + + background-color: var(--Q-operation-color-dark); + transform: translate(var(--Q-before-x), var(--Q-before-y)) rotate(var(--Q-before-rotation)); + z-index: -10; + /*z-index: 10;*/ + display: block; + box-shadow: 0.2rem 0.2rem 0.2rem rgba(0, 0, 0, 0.2); } .Q-circuit-palette .Q-circuit-operation-tile:after { - - transform: - translate( var( --Q-after-x ), var( --Q-after-y )) - rotate( var( --Q-after-rotation )); - box-shadow: 0.2rem 0.2rem 0.2rem rgba( 0, 0, 0, 0.2 ); + transform: translate(var(--Q-after-x), var(--Q-after-y)) rotate(var(--Q-after-rotation)); + box-shadow: 0.2rem 0.2rem 0.2rem rgba(0, 0, 0, 0.2); } .Q-circuit-operation:hover .Q-circuit-operation-tile { - - color: white; + color: white; } - - - .Q-circuit-operation-hadamard .Q-circuit-operation-tile { - - /*--Q-operation-color-hue: var( --Q-color-red-hue );*/ - /*--Q-operation-color-main: var( --Q-color-red );*/ - - /*--Q-operation-color-hue: 0; + /*--Q-operation-color-hue: var( --Q-color-red-hue );*/ + /*--Q-operation-color-main: var( --Q-color-red );*/ + /*--Q-operation-color-hue: 0; --Q-operation-color-main: hsl( 0, 0%, 10% );*/ - - -/* background: + /* background: linear-gradient( -33deg, @@ -1153,179 +854,130 @@ dd .maths { .Q-circuit-operation-identity .Q-circuit-operation-tile, .Q-circuit-operation-control .Q-circuit-operation-tile, .Q-circuit-operation-target .Q-circuit-operation-tile { - - /*--Q-operation-color-hue: var( --Q-color-orange-hue );*/ - /*--Q-operation-color-main: var( --Q-color-orange );*/ - border-radius: 100%; + /*--Q-operation-color-hue: var( --Q-color-orange-hue );*/ + /*--Q-operation-color-main: var( --Q-color-orange );*/ + border-radius: 100%; } .Q-circuit-operation-identity .Q-circuit-operation-tile, .Q-circuit-operation-control .Q-circuit-operation-tile { - - top: calc( 50% - 0.7rem ); - left: calc( 50% - 0.7rem ); - width: 1.4rem; - height: 1.4rem; - overflow: hidden; -/* --Q-operation-color-hue: 0; + top: calc(50% - 0.7rem); + left: calc(50% - 0.7rem); + width: 1.4rem; + height: 1.4rem; + overflow: hidden; + /* --Q-operation-color-hue: 0; --Q-operation-color-main: hsl( 0, 0%, 10% );*/ } .Q-circuit-operation-pauli-x, .Q-circuit-operation-pauli-y, .Q-circuit-operation-pauli-z { - - /*--Q-operation-color-hue: var( --Q-color-red-hue );*/ - /*--Q-operation-color-main: var( --Q-color-red );*/ - -/* --Q-operation-color-hue: 0; + /*--Q-operation-color-hue: var( --Q-color-red-hue );*/ + /*--Q-operation-color-main: var( --Q-color-red );*/ + /* --Q-operation-color-hue: 0; --Q-operation-color-main: hsl( 0, 0%, 30% );*/ } .Q-circuit-operation-swap .Q-circuit-operation-tile { - - top: calc( 50% - 0.55rem ); - left: calc( 50% - 0.55rem ); - width: 1.2rem; - height: 1.2rem; - border-radius: 0; - transform-origin: center; - transform: rotate( 45deg ); - font-size: 0; + top: calc(50% - 0.55rem); + left: calc(50% - 0.55rem); + width: 1.2rem; + height: 1.2rem; + border-radius: 0; + transform-origin: center; + transform: rotate(45deg); + font-size: 0; } - - - - - - /********************/ - /* */ - /* Other states */ - /* */ /********************/ - +/* */ +/* Other states */ +/* */ +/********************/ .Q-circuit-palette > div:hover, .Q-circuit-board-foreground > div:hover { - - outline: 2px solid var( --Q-hyperlink-internal-color ); - outline-offset: -2px; + outline: 2px solid var(--Q-hyperlink-internal-color); + outline-offset: -2px; } .Q-circuit-palette > div:hover .Q-circuit-operation-tile { - - box-shadow: none; + box-shadow: none; } /*.Q-circuit-palette > div:hover,*/ .Q-circuit-board-foreground > div:hover { - - background-color: white; - color: black; + background-color: white; + color: black; } - - - - - .Q-circuit-clipboard > div, .Q-circuit-cell-selected { - - background-color: white; + background-color: white; } .Q-circuit-clipboard > div:before, .Q-circuit-cell-selected:before { - - content: ""; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - display: block; - z-index: -10; - box-shadow: - 0 0 1rem rgba( 0, 0, 0, 0.2 ), - 0.4rem 0.4rem 0.2rem rgba( 0, 0, 0, 0.2 ); - outline: 1px solid hsl( - - var( --Q-color-background-hue ), - var( --Q-color-background-saturation ), - 50% - ); - /*outline-offset: -1px;*/ + content: ""; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + display: block; + z-index: -10; + box-shadow: + 0 0 1rem rgba(0, 0, 0, 0.2), + 0.4rem 0.4rem 0.2rem rgba(0, 0, 0, 0.2); + outline: 1px solid hsl(var(--Q-color-background-hue), var(--Q-color-background-saturation), 50%); + /*outline-offset: -1px;*/ } - - - .Q-circuit-clipboard > div { - - background-color: white; + background-color: white; } .Q-circuit-clipboard > div:before { - - /* + /* This was very helpful! https://blog.dudak.me/2014/css-shadows-under-adjacent-elements/ */ - content: ""; - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - z-index: -10; - display: block; - box-shadow: 0.4rem 0.4rem 0.3rem rgba( 0, 0, 0, 0.2 ); + content: ""; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: -10; + display: block; + box-shadow: 0.4rem 0.4rem 0.3rem rgba(0, 0, 0, 0.2); } - - - - - /***************/ - /* */ - /* Buttons */ - /* */ /***************/ - +/* */ +/* Buttons */ +/* */ +/***************/ .Q-circuit-locked .Q-circuit-toggle-lock, .Q-circuit-locked .Q-circuit-toggle-lock:hover { - - background-color: var( --Q-color-red ); + background-color: var(--Q-color-red); } .Q-circuit-toggle-lock { - - z-index: 3; - left: 0; - top: 0; - grid-column: 1; - grid-row: 1; - cursor: pointer; - font-size: 1.1rem; - text-shadow: none; - font-weight: normal; + z-index: 3; + left: 0; + top: 0; + grid-column: 1; + grid-row: 1; + cursor: pointer; + font-size: 1.1rem; + text-shadow: none; + font-weight: normal; } .Q-circuit-button-undo, .Q-circuit-button-redo { - - font-size: 1.2rem; - line-height: 2.6rem; - font-weight: normal; + font-size: 1.2rem; + line-height: 2.6rem; + font-weight: normal; } - - .Q-circuit p { - - padding: 1rem; - color: hsl( - - var( --Q-color-background-hue ), - var( --Q-color-background-saturation ), - 66% - ); + padding: 1rem; + color: hsl(var(--Q-color-background-hue), var(--Q-color-background-saturation), 66%); } - -