diff --git a/frontend/public/docker_langauges/bash.svg b/frontend/public/docker_langauges/bash.svg new file mode 100644 index 00000000..890b5d92 --- /dev/null +++ b/frontend/public/docker_langauges/bash.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/docker_langauges/custom.svg b/frontend/public/docker_langauges/custom.svg new file mode 100644 index 00000000..c3416414 --- /dev/null +++ b/frontend/public/docker_langauges/custom.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/docker_langauges/haskell.svg b/frontend/public/docker_langauges/haskell.svg new file mode 100644 index 00000000..0c627ebd --- /dev/null +++ b/frontend/public/docker_langauges/haskell.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/docker_langauges/node-js.svg b/frontend/public/docker_langauges/node-js.svg new file mode 100644 index 00000000..3e77c253 --- /dev/null +++ b/frontend/public/docker_langauges/node-js.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/public/docker_langauges/python.svg b/frontend/public/docker_langauges/python.svg new file mode 100644 index 00000000..bf2a1601 --- /dev/null +++ b/frontend/public/docker_langauges/python.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/components/forms/projectFormTabs/DockerFormTab.tsx b/frontend/src/components/forms/projectFormTabs/DockerFormTab.tsx index edd80af6..f09d02a6 100644 --- a/frontend/src/components/forms/projectFormTabs/DockerFormTab.tsx +++ b/frontend/src/components/forms/projectFormTabs/DockerFormTab.tsx @@ -1,18 +1,62 @@ -import { UploadOutlined } from "@ant-design/icons" -import {Button, Form, Input, Switch, Upload} from "antd" +import { CodepenCircleFilled, InboxOutlined, UploadOutlined } from "@ant-design/icons" +import { Button, Dropdown, Form, Input, Menu, Select, SelectProps, Switch, Upload } from "antd" +import { TextAreaProps } from "antd/es/input" import { FormInstance } from "antd/lib" -import {FC, useEffect} from "react" +import React, { FC, useEffect, useLayoutEffect, useMemo, useState } from "react" import { useTranslation } from "react-i18next" import useAppApi from "../../../hooks/useAppApi" import MarkdownTooltip from "../../common/MarkdownTooltip" import MarkdownTextfield from "../../input/MarkdownTextfield" +import TextArea from "antd/es/input/TextArea" + +import BashIcon from "../../../../public/docker_langauges/bash.svg" +import PythonIcon from "../../../../public/docker_langauges/python.svg" +import NodeIcon from "../../../../public/docker_langauges/node-js.svg" +import HaskellIcon from "../../../../public/docker_langauges/haskell.svg" +import Custom from "../../../../public/docker_langauges/custom.svg" + + +type DockerLanguage = "bash" | "python" | "node" | "haskell" | "custom" +const languageOptions: Record = { + bash: "fedora", + python: "python", + node: "node", + haskell: "haskell", + custom: "" +} + +const imageToLanguage: Record = { + fedora: "bash", + python: "python", + node: "node", + haskell: "haskell", +} + + +const languagesSelectorItems:SelectProps["options"] = [ + { + label: <>Bash, + value: "bash", + },{ + label: <>Python, + value: "python", + }, { + label: <>NodeJS, + value: "node", + }, { + label: <>Haskell, + value: "haskell", + }, { + label: <>Custom, + value: "custom", + } +] const DockerFormTab: FC<{ form: FormInstance }> = ({ form }) => { const { t } = useTranslation() - const {message} = useAppApi() - + const { message } = useAppApi() const dockerImage = Form.useWatch("dockerImage", form) const dockerTemplate = Form.useWatch("dockerTemplate", form) const dockerMode = Form.useWatch("dockerMode", form) @@ -28,6 +72,8 @@ const DockerFormTab: FC<{ form: FormInstance }> = ({ form }) => { form.validateFields(["dockerScript", "dockerTemplate"]) }, [dockerDisabled]) + + const dockerImageSelect= useMemo(()=> imageToLanguage[dockerImage] || "custom",[dockerImage]) function isValidTemplate(template: string): string { if (template.length === 0) { @@ -56,7 +102,7 @@ const DockerFormTab: FC<{ form: FormInstance }> = ({ form }) => { const isDescription = line.length >= 13 && line.substring(0, 13).toLowerCase() === ">description=" // option lines if (line.toLowerCase() !== ">required" && line.toLowerCase() !== ">optional" && !isDescription) { - return t("project.tests.dockerTemplateValidation.inValidOptions", { line:lineNumber.toString() }) + return t("project.tests.dockerTemplateValidation.inValidOptions", { line: lineNumber.toString() }) } } else { isConfigurationLine = false @@ -69,31 +115,22 @@ const DockerFormTab: FC<{ form: FormInstance }> = ({ form }) => { return "" } - - const normFile = (e: any) => { if (Array.isArray(e)) { - return e; + return e } - return e?.fileList; - }; + return e?.fileList + } - let switchClassName = 'template-switch' + let switchClassName = "template-switch" let scriptPlaceholder - + if (withTemplate) { - switchClassName += ' template-switch-active' - scriptPlaceholder = "bash /shared/input/helloworld.sh > \"/shared/output/helloWorldTest\"\n"+ - "bash /shared/input/helloug.sh > \"/shared/output/helloUGent\"\n" + switchClassName += " template-switch-active" + scriptPlaceholder = 'bash /shared/input/helloworld.sh > "/shared/output/helloWorldTest"\n' + 'bash /shared/input/helloug.sh > "/shared/output/helloUGent"\n' } else { - switchClassName += ' template-switch-inactive' - scriptPlaceholder = "output=$(bash /shared/input/helloworld.sh)\n"+ - "if [[ \"$output\" == \"Hello World\" ]]; then \n"+ - " echo 'Test one is successful\n"+ - " echo 'PUSH ALLOWED' > /shared/output/testOutput\n"+ - "else\n"+ - " echo 'Test one failed: script failed to print \"Hello World\"'\n"+ - "fi" + switchClassName += " template-switch-inactive" + scriptPlaceholder = "output=$(bash /shared/input/helloworld.sh)\n" + 'if [[ "$output" == "Hello World" ]]; then \n' + " echo 'Test one is successful\n" + " echo 'PUSH ALLOWED' > /shared/output/testOutput\n" + "else\n" + " echo 'Test one failed: script failed to print \"Hello World\"'\n" + "fi" } @@ -110,11 +147,16 @@ const DockerFormTab: FC<{ form: FormInstance }> = ({ form }) => { name="dockerImage" > form.setFieldValue("dockerImage", languageOptions[val])} + options={languagesSelectorItems} + />} placeholder={t("project.tests.dockerImagePlaceholder")} /> - <> = ({ form }) => { } name="dockerScript" > - @@ -224,4 +266,4 @@ const DockerFormTab: FC<{ form: FormInstance }> = ({ form }) => { ) } -export default DockerFormTab +export default DockerFormTab \ No newline at end of file diff --git a/frontend/src/components/forms/projectFormTabs/GeneralFormTab.tsx b/frontend/src/components/forms/projectFormTabs/GeneralFormTab.tsx index 37d1357d..df5ca7c5 100644 --- a/frontend/src/components/forms/projectFormTabs/GeneralFormTab.tsx +++ b/frontend/src/components/forms/projectFormTabs/GeneralFormTab.tsx @@ -2,106 +2,88 @@ import { DatePicker, Form, FormInstance, Input, Switch, Typography } from "antd" import { useTranslation } from "react-i18next" import { FC } from "react" import MarkdownEditor from "../../input/MarkdownEditor" -import dayjs from 'dayjs'; +import dayjs from "dayjs" const GeneralFormTab: FC<{ form: FormInstance }> = ({ form }) => { - const { t } = useTranslation() - const description = Form.useWatch("description", form) - const visible = Form.useWatch("visible", form) + const { t } = useTranslation() + const description = Form.useWatch("description", form) + const visible = Form.useWatch("visible", form) - return ( - <> - - - + return ( + <> + + + - - {t("project.change.description")} - - + {t("project.change.description")} + - - - + + + - {!visible && ( - - current && current.isBefore(dayjs().startOf('day'))} - /> - - )} + {!visible && ( + + current && current.isBefore(dayjs().startOf("day"))} + /> + + )} - - - + + + - - { - const hours = []; - for (let i = 0; i < dayjs().hour(); i++) { - hours.push(i); - } - return hours; - }, - disabledMinutes: (selectedHour) => { - const minutes = []; - if (selectedHour === dayjs().hour()) { - for (let i = 0; i < dayjs().minute(); i++) { - minutes.push(i); - } - } - return minutes; - }, - disabledSeconds: (selectedHour, selectedMinute) => { - const seconds = []; - if (selectedHour === dayjs().hour() && selectedMinute === dayjs().minute()) { - for (let i = 0; i < dayjs().second(); i++) { - seconds.push(i); - } - } - return seconds; - }, - }} - format="YYYY-MM-DD HH:mm:ss" - disabledDate={(current) => current && current.isBefore(dayjs().startOf('day'))} - - /> - - - ) + + { + const hours = [] + for (let i = 0; i < dayjs().hour(); i++) { + hours.push(i) + } + return hours + }, + }} + format="YYYY-MM-DD HH:mm:ss" + disabledDate={(current) => current && current.isBefore(dayjs().startOf("day"))} + /> + + + ) } export default GeneralFormTab diff --git a/frontend/src/i18n/en/translation.json b/frontend/src/i18n/en/translation.json index 54faddf6..292d63ff 100644 --- a/frontend/src/i18n/en/translation.json +++ b/frontend/src/i18n/en/translation.json @@ -213,7 +213,7 @@ "simpleMode": "Without template", "templateMode": "With template", "fileStructureTooltip": "This templates specifies the file structure a submission has to follow.\nIt uses the following syntax:\n* Folders end on `'/'`\n* Use indents to specify files inside a folder\n* Regex can be used\n\t* `'.'` is still a normal `'.'`\n\t* `'\\.'` can be used as regex `'.'`\n* `'-'` at the start of a line specifies a file/folder that is not allowed", - "dockerImageTooltip": "Specify a valid Docker-container from [Docker Hub](https://hub.docker.com/) on which the test script will be run.", + "dockerImageTooltip": "Specify a valid Docker container from [Docker Hub](https://hub.docker.com/) on which the test script will be run. You can also choose a language with a preselected container.", "dockerScriptTooltip": "Bash-script that is executed.\n* The files of the student's submission can be found in `'/shared/input'`\n* Extra files uploaded below can be found in `'/shared/extra'`\n\n More information about the required output depends on the mode and can be found below.", "dockerTemplateTooltip": "To specify specific tests, you need to provide a template. First, enter the test name with '@{test}'. Below this, you can use '>' to provide options such as ('>required', '>optional', '>description'). Everything under these options until the next test or the end of the file is the expected output.", "dockerTestDirTooltip": "Upload additional files needed for the Docker test. These files are available in the folder `'/shared/extra'`.\n\nOnce uploaded u can click the filename to download them again. Uploading a new file will replace the old one.", diff --git a/frontend/src/i18n/nl/translation.json b/frontend/src/i18n/nl/translation.json index baadff8a..b5c963aa 100644 --- a/frontend/src/i18n/nl/translation.json +++ b/frontend/src/i18n/nl/translation.json @@ -215,7 +215,7 @@ "templateMode": "Met sjabloon", "fileStructurePreview": "Voorbeeld van bestandsstructuur", "fileStructureTooltip": "Dit sjabloon specificeert de bestandsstructuur die een indiening moet volgen.\nHet gebruikt de volgende syntax:\n* Mappen eindigen op `'/'`\n* Gebruik inspringing om bestanden binnen een map aan te geven\n* Regex kan worden gebruikt\n\t* `'.'` is nog steeds een normale `'.'`\n\t* `'\\.'` kan worden gebruikt als regex `'.'`\n* `'-'` aan het begin van een regel geeft aan dat een bestand/map niet is toegestaan", - "dockerImageTooltip": "Specificeer een geldige Docker-container van [Docker Hub](https://hub.docker.com/) waarop het testscript zal worden uitgevoerd.", + "dockerImageTooltip": "Specificeer een geldige Docker-container van [Docker Hub](https://hub.docker.com/) waarop het testscript zal worden uitgevoerd. Je kan ook kiezen voor een voorgeconfigureerde programmeertaal met bijhorende container.", "dockerScriptTooltip": "Bash-script dat wordt uitgevoerd.\n* De bestanden van de student zijn indieningen zijn te vinden in `'/shared/input'`\n* Extra bestanden die hieronder zijn geüpload, zijn te vinden in `'/shared/extra'`\n\nMeer informatie over de vereiste uitvoer is afhankelijk van de modus en is hieronder te vinden.", "dockerTemplateTooltip": "Om specifieke tests te definiëren, moet je een sjabloon invoeren. Geef eerst de naam van de test in met '@{test}'. Hieronder kun je met een '>' opties geven zoals ('>required', '>optional', '>description'). Alles onder de opties tot de volgende test of het einde van het bestand is de verwachte output.", "dockerTestDirTooltip": "Upload extra bestanden die nodig zijn voor de dockertest. Deze bestanden zijn beschikbaar in de map `'/shared/extra'`.\n\nAls je een file geüpload hebt kan je deze downloaden door op de bestandsnaam te klikken. Als je een nieuw bestand uploadt zal het oude bestand overschreven worden.", @@ -250,7 +250,7 @@ "getStarted": "Aan de slag", "docs": "Documentatie" }, - + "submission": { "submission": "Indiening", "submittedFiles": "Ingediende bestanden:", diff --git a/frontend/src/pages/editProject/EditProject.tsx b/frontend/src/pages/editProject/EditProject.tsx index cfc75422..7098c253 100644 --- a/frontend/src/pages/editProject/EditProject.tsx +++ b/frontend/src/pages/editProject/EditProject.tsx @@ -43,10 +43,9 @@ const EditProject: React.FC = () => { dockerImage: null, dockerMode: false } + if (response.success) { const tests = response.response.data - console.log(tests) - if (tests.extraFilesName) { const downloadLink = AppRoutes.DOWNLOAD_PROJECT_TESTS.replace(":projectId", projectId).replace(":courseId", courseId!) @@ -75,7 +74,6 @@ const EditProject: React.FC = () => { } form.setFieldsValue(formVals) - setInitialDockerValues(formVals) } @@ -88,6 +86,9 @@ const EditProject: React.FC = () => { const handleCreation = async () => { const values: ProjectFormData & DockerFormData = form.getFieldsValue() + + console.log(values) + if (values.visible) { values.visibleAfter = null } diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 8d61f9c2..9c426dcb 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -181,6 +181,13 @@ html { } +.select-icon { + width: 1em; + margin-right: 0.5rem; + position: relative; + top: 2px; +} + /* *************************** Landing page *************************** */ .landing-page * {