Merge gmpl main2 (#37)
* addPage for gmpl * implement gmpl full_functional * final * del unused variable * change type of variable * change test * code fix * code fix * fixing var error (var -> let) --------- Co-authored-by: SinusFox <61253950+SinusFox@users.noreply.github.com>
This commit is contained in:
@@ -1,22 +1,31 @@
|
|||||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import '@testing-library/jest-dom';
|
import '@testing-library/jest-dom';
|
||||||
import Home from "../src/app/page";
|
import Home from '../src/app/page';
|
||||||
import { customLog, customLogClear } from '../src/app/scripts';
|
import { customLog, customLogClear } from '../src/app/scripts';
|
||||||
|
|
||||||
|
// Mock customLog and customLogClear
|
||||||
jest.mock('../src/app/scripts', () => ({
|
jest.mock('../src/app/scripts', () => ({
|
||||||
customLog: jest.fn(),
|
customLog: jest.fn(),
|
||||||
customLogClear: jest.fn(),
|
customLogClear: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// Mock next/navigation instead of next/router
|
||||||
|
jest.mock('next/navigation', () => ({
|
||||||
|
useRouter: jest.fn().mockReturnValue({
|
||||||
|
push: jest.fn(),
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Mock GLPKAPI to avoid issues with undefined LPF_ECOND
|
||||||
jest.mock('../src/solver/glpk.min.js', () => ({
|
jest.mock('../src/solver/glpk.min.js', () => ({
|
||||||
LPF_ECOND: 2,
|
LPF_ECOND: 2,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
test('render home page', () => {
|
test('render home page', () => {
|
||||||
// render website
|
// Render Home component
|
||||||
render(<Home />);
|
render(<Home />);
|
||||||
|
|
||||||
// check if text is in document
|
// Check if the heading text "OR-Tool" is present in the document
|
||||||
const headingElement = screen.getByText(/OR-Tool/i); // text search in document
|
const headingElement = screen.getByText(/OR-Tool/i); // Match text that contains "OR-Tool"
|
||||||
expect(headingElement).toBeInTheDocument();
|
expect(headingElement).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,6 +20,13 @@ jest.mock('../src/solver/glpk.min.js', () => ({
|
|||||||
// Mocking console.log
|
// Mocking console.log
|
||||||
const consoleLogMock = jest.spyOn(console, 'log').mockImplementation(() => {});
|
const consoleLogMock = jest.spyOn(console, 'log').mockImplementation(() => {});
|
||||||
|
|
||||||
|
// Mock useRouter to avoid invariant error
|
||||||
|
jest.mock('next/navigation', () => ({
|
||||||
|
useRouter: jest.fn(() => ({
|
||||||
|
push: jest.fn(), // Mock the 'push' function
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
document.body.innerHTML = `
|
document.body.innerHTML = `
|
||||||
<div>
|
<div>
|
||||||
@@ -90,7 +97,7 @@ test('calculate_click should display "Calculating" in the output box', () => {
|
|||||||
document.getElementById('bounds').value = 'x <= 5';
|
document.getElementById('bounds').value = 'x <= 5';
|
||||||
document.getElementById('vars').value = 'x\ny';
|
document.getElementById('vars').value = 'x\ny';
|
||||||
|
|
||||||
// Simuliere den Button-Klick, der die Berechnung startet
|
// Simulate the button click to trigger calculation
|
||||||
fireEvent.click(screen.getByText('Calculate'));
|
fireEvent.click(screen.getByText('Calculate'));
|
||||||
|
|
||||||
// Check the contents of out box
|
// Check the contents of out box
|
||||||
@@ -101,4 +108,3 @@ test('calculate_click should display "Calculating" in the output box', () => {
|
|||||||
mockClear.mockRestore();
|
mockClear.mockRestore();
|
||||||
mockLog.mockRestore();
|
mockLog.mockRestore();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import React, { createContext, useState, ReactNode } from 'react';
|
||||||
|
|
||||||
|
export const LanguageContext = createContext<{
|
||||||
|
language: string;
|
||||||
|
setLanguage: (lang: string) => void;
|
||||||
|
}>({
|
||||||
|
language: 'eng',
|
||||||
|
setLanguage: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const LanguageProvider = ({ children }: { children: ReactNode }) => {
|
||||||
|
const [language, setLanguage] = useState('eng');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LanguageContext.Provider value={{ language, setLanguage }}>
|
||||||
|
{children}
|
||||||
|
</LanguageContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -61,6 +61,14 @@ body {
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button_spec {
|
||||||
|
border: 2px solid #5353535c;
|
||||||
|
background-color: #1010105c;
|
||||||
|
border-radius: 20px;
|
||||||
|
margin: 10px;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.button:hover {
|
.button:hover {
|
||||||
border: 2px solid #5353535c;
|
border: 2px solid #5353535c;
|
||||||
background-color: #5353535c;
|
background-color: #5353535c;
|
||||||
@@ -186,3 +194,163 @@ body {
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.containerGmpl {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: 20px;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #2a2a2a;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid #8d8d8d;
|
||||||
|
max-width: 90%;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headerGmpl {
|
||||||
|
font-size: 24px;
|
||||||
|
color: #ffffff;
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fileInput {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttoncontainerGmpl {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonGmpl {
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-size: 16px;
|
||||||
|
background-color: #28a745;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.togglebuttonGmpl {
|
||||||
|
margin: 10px 0;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #007bff;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fileContentBox {
|
||||||
|
background-color: #3c3c3c;
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 15px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
border-radius: 5px;
|
||||||
|
font-family: 'Courier New', Courier, monospace;
|
||||||
|
white-space: pre-line;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textarea {
|
||||||
|
width: 100%;
|
||||||
|
background-color: #202020;
|
||||||
|
color: #ffffff;
|
||||||
|
border: 1px solid #8d8d8d;
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 10px;
|
||||||
|
font-family: 'Courier New', Courier, monospace;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
overflow: scroll;
|
||||||
|
max-height: 800px;
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.downloadbuttonGmpl {
|
||||||
|
padding: 10px 20px;
|
||||||
|
font-size: 16px;
|
||||||
|
background-color: #28a745;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loadingSpinner {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 16px;
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pre {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subheaderGmpl {
|
||||||
|
color: #ffffff;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.msgZone {
|
||||||
|
margin-top: 20px;
|
||||||
|
color: #ffcc00;
|
||||||
|
background-color: #333;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.syntaxErrorBox {
|
||||||
|
margin-top: 20px;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #ffcc00;
|
||||||
|
border-radius: 5px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.errorPopup {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 20px;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: #ffcc00;
|
||||||
|
color: #333;
|
||||||
|
border-radius: 5px;
|
||||||
|
box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.7);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popupContent {
|
||||||
|
background-color: #333;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 10px;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
width: 80%;
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popupResult {
|
||||||
|
max-height: 300px;
|
||||||
|
overflow-y: auto;
|
||||||
|
text-align: left;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #8d8d8d;
|
||||||
|
background-color: #202020;
|
||||||
|
color: #ffffff;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,359 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import React, { useState, useContext, useRef, useEffect } from 'react';
|
||||||
|
import text from "../lang";
|
||||||
|
import { LanguageContext } from '../context/LanguageContext';
|
||||||
|
import * as GLPKAPI from "../../solver/glpk.min.js"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const GlpPage = () => {
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
const { language, setLanguage } = useContext(LanguageContext);
|
||||||
|
const [model, setModel] = useState('gen');
|
||||||
|
|
||||||
|
const tr_hTitle = text(language, 'header_title');
|
||||||
|
const tr_hSubtitle = text(language, 'header_subtitle');
|
||||||
|
const tr_calcButton = text(language, "buttonCalc");
|
||||||
|
const tr_GmplTitle = text(language, 'GmplHeader');
|
||||||
|
const tr_GenProblems = text(language, 'GenProblem');
|
||||||
|
const tr_SpecProblems = text(language, 'SpecProblem');
|
||||||
|
const tr_fileUpload = text(language, 'FileUpload');
|
||||||
|
const tr_fileName = text(language, 'FileName');
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const [fileContent, setFileContent] = useState<string>('');
|
||||||
|
const [isFileUploaded, setIsFileUploaded] = useState(false);
|
||||||
|
const [showFileContent, setShowFileContent] = useState(true);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [solverTime, setSolverTime] = useState<string>('');
|
||||||
|
const [showPopup, setShowPopup] = useState(false);
|
||||||
|
const [resultContent, setResultContent] = useState<string>('');
|
||||||
|
//const [syntaxErrors, setSyntaxErrors] = useState<string[]>([]);
|
||||||
|
const [showErrorPopup, setShowErrorPopup] = useState(false);
|
||||||
|
const solverTimeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
const solverAbortController = useRef<AbortController | null>(null);
|
||||||
|
//const [highlightedContent, setHighlightedContent] = useState<string>('');
|
||||||
|
const [fileName, setFileName] = useState<string>("");
|
||||||
|
|
||||||
|
|
||||||
|
const handleLanguageChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
|
setLanguage(event.target.value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeModel = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
|
const selectedModel = event.target.value;
|
||||||
|
setModel(selectedModel);
|
||||||
|
|
||||||
|
if (selectedModel === 'spec') {
|
||||||
|
router.push('/');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//useEffect(() => {
|
||||||
|
// if (syntaxErrors.length > 0) {
|
||||||
|
// setShowErrorPopup(true);
|
||||||
|
// }
|
||||||
|
//}, [syntaxErrors]);
|
||||||
|
|
||||||
|
const addMessage = (message: string) => {
|
||||||
|
const msgZone = document.getElementById("msgZone");
|
||||||
|
if (msgZone) {
|
||||||
|
msgZone.innerHTML += `<div>${message}</div>`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// const updateHighlightedContent = (content: string) => {
|
||||||
|
// const highlighted = highlightErrors(content);
|
||||||
|
// setHighlightedContent(highlighted);
|
||||||
|
// };
|
||||||
|
|
||||||
|
|
||||||
|
const handleFileUpload = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const file = event.target.files?.[0];
|
||||||
|
if (file) {
|
||||||
|
setFileName(file.name);
|
||||||
|
}
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
const content = e.target?.result as string;
|
||||||
|
setFileContent(content);
|
||||||
|
// updateHighlightedContent(content);
|
||||||
|
addMessage("File successfully uploaded and read.");
|
||||||
|
setIsFileUploaded(true);
|
||||||
|
|
||||||
|
// Perform syntax check
|
||||||
|
//const errors = checkSyntax(content);
|
||||||
|
//setSyntaxErrors(errors);
|
||||||
|
// updateHighlightedContent(content);
|
||||||
|
};
|
||||||
|
reader.readAsText(file);
|
||||||
|
};
|
||||||
|
|
||||||
|
//const checkSyntax = (content: string): string[] => {
|
||||||
|
// const errors: string[] = [];
|
||||||
|
// const lines = content.split("\n");
|
||||||
|
//
|
||||||
|
// const ignorePattern = /^(#|\/\/|printf|\/\*|\*).*$/;
|
||||||
|
//
|
||||||
|
// lines.forEach((line, index) => {
|
||||||
|
// if (ignorePattern.test(line) || line.trim() === "") {
|
||||||
|
// return; // Ignore this line
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// const validGmplLinePattern = /^(var|param|set|maximize|minimize|s\.t\.|subject to|for|in|if|then|else|end|:=|<=|>=|=|\+|\-|\*|\/|[a-zA-Z_][a-zA-Z0-9_]*\s*[=<>+\-*/]*\s*[0-9a-zA-Z_]+.*;)/;
|
||||||
|
//
|
||||||
|
// if (!validGmplLinePattern.test(line)) {
|
||||||
|
// errors.push(`Syntax error on line ${index + 1}: ${line}`);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// return errors;
|
||||||
|
//};
|
||||||
|
|
||||||
|
// Highlight syntax errors in the file content
|
||||||
|
//const highlightErrors = (content: string) => {
|
||||||
|
// const lines = content.split("\n");
|
||||||
|
// return lines.map((line, index) => {
|
||||||
|
// const error = syntaxErrors.find((error) => error.includes(`line ${index + 1}`));
|
||||||
|
// if (error) {
|
||||||
|
// return `<span style="background-color: yellow; color: black;" title="${error}">${line}</span>`;
|
||||||
|
// }
|
||||||
|
// return line;
|
||||||
|
// }).join("\n");
|
||||||
|
//};
|
||||||
|
|
||||||
|
// Function to dynamically set the height of the textarea
|
||||||
|
const getTextAreaHeight = (value: any) => {
|
||||||
|
const stringValue = String(value);
|
||||||
|
const lineCount = stringValue.split("\n").length;
|
||||||
|
if (lineCount <= 3) return lineCount;
|
||||||
|
return 3;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to abort solving after a certain time
|
||||||
|
const abortSolving = () => {
|
||||||
|
if (solverAbortController.current) {
|
||||||
|
solverAbortController.current.abort();
|
||||||
|
setIsLoading(false);
|
||||||
|
addMessage("Solving was aborted after the timeout.");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const solve = () => {
|
||||||
|
addMessage("Starting the solver...");
|
||||||
|
setIsLoading(true);
|
||||||
|
const startTime = performance.now();
|
||||||
|
|
||||||
|
// Set a timeout to abort the solver after 5 seconds
|
||||||
|
solverTimeoutRef.current = setTimeout(abortSolving, 5000);
|
||||||
|
|
||||||
|
solverAbortController.current = new AbortController();
|
||||||
|
const { signal } = solverAbortController.current;
|
||||||
|
|
||||||
|
try {
|
||||||
|
let model = fileContent;
|
||||||
|
let lp = GLPKAPI.glp_create_prob();
|
||||||
|
let tran = GLPKAPI.glp_mpl_alloc_wksp();
|
||||||
|
GLPKAPI._glp_mpl_init_rand(tran, 1);
|
||||||
|
|
||||||
|
GLPKAPI.glp_mpl_read_model_from_string(tran, "model", model, 0);
|
||||||
|
GLPKAPI.glp_mpl_generate(tran, null, function () { });
|
||||||
|
addMessage("Model successfully converted to solver format.");
|
||||||
|
|
||||||
|
GLPKAPI.glp_mpl_build_prob(tran, lp);
|
||||||
|
let smcp = new GLPKAPI.SMCP({ presolve: GLPKAPI.GLP_ON });
|
||||||
|
GLPKAPI.glp_simplex(lp, smcp);
|
||||||
|
|
||||||
|
let iocp = new GLPKAPI.IOCP({ presolve: GLPKAPI.GLP_ON });
|
||||||
|
GLPKAPI.glp_intopt(lp, iocp);
|
||||||
|
GLPKAPI.glp_mpl_postsolve(tran, lp, GLPKAPI.GLP_MIP);
|
||||||
|
|
||||||
|
addMessage("Model solved successfully.");
|
||||||
|
setIsLoading(false);
|
||||||
|
|
||||||
|
const endTime = performance.now();
|
||||||
|
const solverDuration = ((endTime - startTime) / 1000).toFixed(2);
|
||||||
|
setSolverTime(`Solver time: ${solverDuration} seconds`);
|
||||||
|
addMessage(`Solver time: ${solverDuration} seconds`);
|
||||||
|
|
||||||
|
let status;
|
||||||
|
switch (GLPKAPI.glp_mip_status(lp)) {
|
||||||
|
case GLPKAPI.GLP_OPT:
|
||||||
|
status = "OPTIMAL";
|
||||||
|
break;
|
||||||
|
case GLPKAPI.GLP_UNDEF:
|
||||||
|
status = "UNDEFINED SOLUTION";
|
||||||
|
break;
|
||||||
|
case GLPKAPI.GLP_INFEAS:
|
||||||
|
status = "INFEASIBLE SOLUTION";
|
||||||
|
break;
|
||||||
|
case GLPKAPI.GLP_NOFEAS:
|
||||||
|
status = "NO FEASIBLE SOLUTION";
|
||||||
|
break;
|
||||||
|
case GLPKAPI.GLP_FEAS:
|
||||||
|
status = "FEASIBLE SOLUTION";
|
||||||
|
break;
|
||||||
|
case GLPKAPI.GLP_UNBND:
|
||||||
|
status = "UNBOUNDED SOLUTION";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = `Solution status: ${status}`;
|
||||||
|
let variables = "Variable results:\n";
|
||||||
|
for (let i = 1; i <= GLPKAPI.glp_get_num_cols(lp); i++) {
|
||||||
|
variables += `${GLPKAPI.glp_get_col_name(lp, i)} = ${GLPKAPI.glp_mip_col_val(lp, i)}\n`;
|
||||||
|
}
|
||||||
|
if (solverTimeoutRef.current) clearTimeout(solverTimeoutRef.current);
|
||||||
|
setResultContent(`${result}\n\n${variables}\n\nSolver time: ${solverDuration} seconds`);
|
||||||
|
setShowPopup(true);
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
setIsLoading(false);
|
||||||
|
addMessage("<div class='alert alert-danger'>" + (err as Error).toString() + "</div>");
|
||||||
|
console.log(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const downloadFile = () => {
|
||||||
|
const element = document.createElement("a");
|
||||||
|
const file = new Blob([fileContent], { type: 'text/plain' });
|
||||||
|
element.href = URL.createObjectURL(file);
|
||||||
|
element.download = "problem.gmpl";
|
||||||
|
document.body.appendChild(element);
|
||||||
|
element.click();
|
||||||
|
document.body.removeChild(element);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<header className="header">
|
||||||
|
<div className="title">
|
||||||
|
<main className="header_box">
|
||||||
|
{tr_hTitle}
|
||||||
|
<br />
|
||||||
|
<span className="header_copyright">
|
||||||
|
<i>{tr_hSubtitle}</i>
|
||||||
|
</span>
|
||||||
|
<br />
|
||||||
|
<select id="language_current" value={language} onChange={handleLanguageChange} className="dropdown-custom">
|
||||||
|
<option value="ger">Deutsch</option>
|
||||||
|
<option value="eng">English</option>
|
||||||
|
</select>
|
||||||
|
<select id="language_current" value={model} onChange={changeModel} className="dropdown-custom">
|
||||||
|
<option value="gen">{tr_GenProblems}</option>
|
||||||
|
<option value="spec">{tr_SpecProblems}</option>
|
||||||
|
</select>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div className="containerGmpl">
|
||||||
|
<h1 className="headerGmpl">{tr_GmplTitle}</h1>
|
||||||
|
|
||||||
|
{/* File Upload */}
|
||||||
|
<div className="button_spec">
|
||||||
|
<label htmlFor="fileUpload" className="button_green">
|
||||||
|
{tr_fileUpload}
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id="fileUpload"
|
||||||
|
type="file"
|
||||||
|
accept=".mod,.dat,.txt,.gmpl"
|
||||||
|
onChange={handleFileUpload}
|
||||||
|
style={{ display: "none" }}
|
||||||
|
/>
|
||||||
|
<span className="fileName">
|
||||||
|
{fileName ? fileName : tr_fileName}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{isFileUploaded && (
|
||||||
|
<>
|
||||||
|
<div className="buttoncontainerGmpl">
|
||||||
|
<button className="button_green" onClick={solve} disabled={isLoading}>
|
||||||
|
{tr_calcButton}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{fileContent && (
|
||||||
|
<div>
|
||||||
|
{showFileContent && (
|
||||||
|
<div className="fileContentBox">
|
||||||
|
<h2 className="subheaderGmpl">File Content</h2>
|
||||||
|
{/* Use a textarea for editable text */}
|
||||||
|
<textarea
|
||||||
|
className="textarea"
|
||||||
|
value={fileContent}
|
||||||
|
onChange={(e) => {
|
||||||
|
setFileContent(e.target.value);
|
||||||
|
//updateHighlightedContent(e.target.value);
|
||||||
|
}}
|
||||||
|
rows={getTextAreaHeight(fileContent)}
|
||||||
|
/>
|
||||||
|
<button className="button" onClick={downloadFile}>
|
||||||
|
Download GMPL File
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Loading Spinner */}
|
||||||
|
{isLoading && (
|
||||||
|
<div className="loadingSpinner">Loading...</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div id="msgZone" className="msgZone"></div>
|
||||||
|
<div>{solverTime}</div> {/* Display Solver Time */}
|
||||||
|
|
||||||
|
{/* Syntax Errors
|
||||||
|
{syntaxErrors.length > 0 && (
|
||||||
|
<div className="syntaxErrorBox">
|
||||||
|
<h3>Syntax Errors</h3>
|
||||||
|
<ul>
|
||||||
|
{syntaxErrors.map((error, index) => (
|
||||||
|
<li key={index}>{error}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
*/}
|
||||||
|
|
||||||
|
{/* Syntax Error Popup
|
||||||
|
{showErrorPopup && (
|
||||||
|
<div className="errorPopup">
|
||||||
|
<p>There are syntax errors in the file.</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
*/}
|
||||||
|
|
||||||
|
{/* Result Popup */}
|
||||||
|
{showPopup && (
|
||||||
|
<div className="popup">
|
||||||
|
<div className="popupContent">
|
||||||
|
<h2>Solver Results</h2>
|
||||||
|
<div className="popupResult">
|
||||||
|
<pre className="pre">{resultContent}</pre>
|
||||||
|
</div>
|
||||||
|
<button className="button_green" onClick={() => setShowPopup(false)}>Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
export default GlpPage;
|
||||||
@@ -8,6 +8,16 @@ export default function text(lang: string, input: string): string {
|
|||||||
return "von Spaceholder Programming";
|
return "von Spaceholder Programming";
|
||||||
case "boxObjTitle":
|
case "boxObjTitle":
|
||||||
return "Ziel";
|
return "Ziel";
|
||||||
|
case "GmplHeader":
|
||||||
|
return "Allgemeine Lineare Probleme";
|
||||||
|
case "FileUpload":
|
||||||
|
return "Datei hochladen";
|
||||||
|
case "FileName":
|
||||||
|
return "Kein File ausgewählt";
|
||||||
|
case "SpecProblem":
|
||||||
|
return "Spezifisches Problem";
|
||||||
|
case "GenProblem":
|
||||||
|
return "Allgemeines Lineares Problem";
|
||||||
case "boxObjDesc":
|
case "boxObjDesc":
|
||||||
return "Geben Sie Ihr Ziel hier ein. Es ist nur ein Ziel erlaubt. Verwenden Sie eine Zeile dafür (kein 'Enter'!). Erlaubte Symbole sind 0-9, a-z, A-Z und <>=.\nBeispiel:\nx + y\n-786433 x1 + 655361 x2";
|
return "Geben Sie Ihr Ziel hier ein. Es ist nur ein Ziel erlaubt. Verwenden Sie eine Zeile dafür (kein 'Enter'!). Erlaubte Symbole sind 0-9, a-z, A-Z und <>=.\nBeispiel:\nx + y\n-786433 x1 + 655361 x2";
|
||||||
case "boxSubjTitle":
|
case "boxSubjTitle":
|
||||||
@@ -109,6 +119,16 @@ export default function text(lang: string, input: string): string {
|
|||||||
// English translation
|
// English translation
|
||||||
if (lang === "eng") {
|
if (lang === "eng") {
|
||||||
switch (input) {
|
switch (input) {
|
||||||
|
case "GmplHeader":
|
||||||
|
return "General Linear Problems";
|
||||||
|
case "SpecProblem":
|
||||||
|
return "Specific Problem";
|
||||||
|
case "FileUpload":
|
||||||
|
return "Upload File";
|
||||||
|
case "FileName":
|
||||||
|
return "No File selected";
|
||||||
|
case "GenProblem":
|
||||||
|
return "General Linear Problems";
|
||||||
case "header_title":
|
case "header_title":
|
||||||
return "OR-Tool";
|
return "OR-Tool";
|
||||||
case "header_subtitle":
|
case "header_subtitle":
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import localFont from "next/font/local";
|
import localFont from "next/font/local";
|
||||||
|
import { LanguageProvider } from './context/LanguageContext';
|
||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
|
|
||||||
const geistSans = localFont({
|
const geistSans = localFont({
|
||||||
@@ -19,16 +20,19 @@ export const metadata: Metadata = {
|
|||||||
description: "OR-Tool by Spaceholder Programming",
|
description: "OR-Tool by Spaceholder Programming",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
children,
|
children,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
|
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<body
|
<body
|
||||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||||
>
|
>
|
||||||
|
<LanguageProvider>
|
||||||
{children}
|
{children}
|
||||||
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
|
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
|
||||||
<footer className=" flex gap-6 flex-wrap items-center justify-center">
|
<footer className=" flex gap-6 flex-wrap items-center justify-center">
|
||||||
@@ -79,6 +83,8 @@ export default function RootLayout({
|
|||||||
</a>
|
</a>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
|
</LanguageProvider>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
+22
-2
@@ -1,13 +1,19 @@
|
|||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState, useContext } from 'react';
|
||||||
import { Box, Button, Output } from "./modules";
|
import { Box, Button, Output } from "./modules";
|
||||||
import { calculate_click, downloadLP, downloadMPS } from "./scripts";
|
import { calculate_click, downloadLP, downloadMPS } from "./scripts";
|
||||||
import text from "./lang";
|
import text from "./lang";
|
||||||
|
import { spec } from 'node:test/reporters';
|
||||||
|
import { useRouter } from 'next/navigation';
|
||||||
|
import { LanguageContext } from './context/LanguageContext';
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const [language, setLanguage] = useState('eng');
|
const { language, setLanguage } = useContext(LanguageContext);
|
||||||
const [maxminOption, setMaxminOption] = useState('maximize');
|
const [maxminOption, setMaxminOption] = useState('maximize');
|
||||||
|
const [model, selectedModel] = useState('spec');
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
|
|
||||||
const tr_hTitle = text(language, 'header_title');
|
const tr_hTitle = text(language, 'header_title');
|
||||||
const tr_hSubtitle = text(language, 'header_subtitle');
|
const tr_hSubtitle = text(language, 'header_subtitle');
|
||||||
@@ -25,11 +31,21 @@ export default function Home() {
|
|||||||
const tr_calc_min = text(language, "minimize");
|
const tr_calc_min = text(language, "minimize");
|
||||||
const tr_calcButton = text(language, "buttonCalc");
|
const tr_calcButton = text(language, "buttonCalc");
|
||||||
const tr_boxExportMPS = text(language, "boxExportMPS");
|
const tr_boxExportMPS = text(language, "boxExportMPS");
|
||||||
|
const tr_GenProblems = text(language, 'GenProblem');
|
||||||
|
const tr_SpecProblems = text(language, 'SpecProblem');
|
||||||
|
|
||||||
const handleLanguageChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
const handleLanguageChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
setLanguage(event.target.value);
|
setLanguage(event.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const changeModel = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
|
selectedModel(event.target.value);
|
||||||
|
|
||||||
|
if (event.target.value === 'gen') {
|
||||||
|
router.push('./glp');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleMaxMinChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
const handleMaxMinChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
setMaxminOption(event.target.value);
|
setMaxminOption(event.target.value);
|
||||||
};
|
};
|
||||||
@@ -49,6 +65,10 @@ export default function Home() {
|
|||||||
<option value="ger">Deutsch</option>
|
<option value="ger">Deutsch</option>
|
||||||
<option value="eng">English</option>
|
<option value="eng">English</option>
|
||||||
</select>
|
</select>
|
||||||
|
<select id="language_current" value={model} onChange={changeModel} className="dropdown-custom">
|
||||||
|
<option value="gen">{tr_GenProblems}</option>
|
||||||
|
<option value="spec">{tr_SpecProblems}</option>
|
||||||
|
</select>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|||||||
Reference in New Issue
Block a user