diff --git a/frontends/web/package-lock.json b/frontends/web/package-lock.json index f99b9f0a..101d0d78 100644 --- a/frontends/web/package-lock.json +++ b/frontends/web/package-lock.json @@ -13,6 +13,7 @@ "@microsoft/fetch-event-source": "^2.0.1", "@mui/icons-material": "^5.14.3", "@mui/material": "^5.14.3", + "@reduxjs/toolkit": "^2.2.5", "@tailwindcss/line-clamp": "^0.4.2", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.5.0", @@ -58,6 +59,7 @@ "react-multi-carousel": "^2.8.2", "react-player": "^2.6.2", "react-powerplug": "^1.0.0", + "react-redux": "^9.1.2", "react-router-dom": "^5.2.0", "react-router-hash-link": "^2.4.3", "react-scripts": "5.0.1", @@ -69,6 +71,7 @@ "react-table": "^7.7.0", "react-text-annotate": "^0.3.0", "recharts": "^1.8.5", + "redux-persist": "^6.0.0", "set-value": "^4.0.1", "style-loader": "^1.2.1", "sweetalert2": "^11.6.14", @@ -4695,6 +4698,38 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@reduxjs/toolkit": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.5.tgz", + "integrity": "sha512-aeFA/s5NCG7NoJe/MhmwREJxRkDs0ZaSqt0MxhWUrwCf1UQXpwR87RROJEql0uAkLI6U7snBOYOcKw83ew3FPg==", + "dependencies": { + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@reduxjs/toolkit/node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/@restart/context": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@restart/context/-/context-2.1.4.tgz", @@ -5961,6 +5996,11 @@ "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.8.tgz", "integrity": "sha512-d0XxK3YTObnWVp6rZuev3c49+j4Lo8g4L1ZRm9z5L0xpoZycUPshHgczK5gsUMaZOstjVYYi09p5gYvUtfChYw==" }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "node_modules/@types/warning": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.0.tgz", @@ -21472,6 +21512,28 @@ "resolved": "https://registry.npmjs.org/react-property/-/react-property-2.0.0.tgz", "integrity": "sha512-kzmNjIgU32mO4mmH5+iUyrqlpFQhF8K2k7eZ4fdLSOPFrD1XgEuSBv9LDEgxRXTMBqMd8ppT0x6TIzqE5pdGdw==" }, + "node_modules/react-redux": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.2.tgz", + "integrity": "sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==", + "dependencies": { + "@types/use-sync-external-store": "^0.0.3", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25", + "react": "^18.0", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -22904,6 +22966,27 @@ "balanced-match": "^1.0.0" } }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + }, + "node_modules/redux-persist": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz", + "integrity": "sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==", + "peerDependencies": { + "redux": ">4.0.0" + } + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "peerDependencies": { + "redux": "^5.0.0" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.4.tgz", @@ -24156,6 +24239,11 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, + "node_modules/reselect": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.0.tgz", + "integrity": "sha512-aw7jcGLDpSgNDyWBQLv2cedml85qd95/iszJjN988zX1t7AVRJi19d9kto5+W7oCfQ94gyo40dVbT6g2k4/kXg==" + }, "node_modules/resize-observer-polyfill": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", @@ -26922,6 +27010,14 @@ "react-dom": "^16.13.1 || ^17.0.0 || ^18.0.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", + "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/frontends/web/package.json b/frontends/web/package.json index de8a8c1b..6583f326 100644 --- a/frontends/web/package.json +++ b/frontends/web/package.json @@ -8,6 +8,7 @@ "@microsoft/fetch-event-source": "^2.0.1", "@mui/icons-material": "^5.14.3", "@mui/material": "^5.14.3", + "@reduxjs/toolkit": "^2.2.5", "@tailwindcss/line-clamp": "^0.4.2", "@testing-library/jest-dom": "^4.2.4", "@testing-library/react": "^9.5.0", @@ -53,6 +54,7 @@ "react-multi-carousel": "^2.8.2", "react-player": "^2.6.2", "react-powerplug": "^1.0.0", + "react-redux": "^9.1.2", "react-router-dom": "^5.2.0", "react-router-hash-link": "^2.4.3", "react-scripts": "5.0.1", @@ -64,6 +66,7 @@ "react-table": "^7.7.0", "react-text-annotate": "^0.3.0", "recharts": "^1.8.5", + "redux-persist": "^6.0.0", "set-value": "^4.0.1", "style-loader": "^1.2.1", "sweetalert2": "^11.6.14", diff --git a/frontends/web/src/containers/App.js b/frontends/web/src/containers/App.js index 804409c3..2da2eb33 100644 --- a/frontends/web/src/containers/App.js +++ b/frontends/web/src/containers/App.js @@ -66,6 +66,9 @@ import OthersTaskLanding from "new_front/pages/CommunitiesLandingPages/OthersTas import logoBlack from "new_front/assets/logo_black.png"; import logoWhite from "new_front/assets/logo_mlcommos_white.png"; import OverlayInstructionsProvider from "new_front/context/OverlayInstructions/Context"; +import { Provider } from "react-redux"; +import { PersistGate } from "redux-persist/integration/react"; +import { store, persistor } from "state/store.tsx"; const BASE_URL_2 = process.env.REACT_APP_API_HOST_2; @@ -155,456 +158,501 @@ class App extends React.Component { - - - - - - ( - - )} - /> - {!showContentOnly && ( - - + + + + + + + ( + + )} /> - - MLCommons Logo - - - Dynabench - - - - + + + + )} +
+ + + + + - )} - /> - ( - - )} - /> - ( - + + + + } + /> + ( + + )} + /> + ( + + )} + /> + ( + + )} + /> + + + + + + + + + + + - )} - /> - - - - - - - - - - - - - - - - - - - - - - - - - - -
- {!showContentOnly && ( -
- - -
- Copyright © 2023 MLCommons, Inc. -
-
- - Contact - -
-
- - Terms of Use - -
-
- - Data Policy - -
-
-
-
- )} -
-
-
-
+ + + + + + + + + + + + + + + + + {!showContentOnly && ( + + )} + + + + + +
diff --git a/frontends/web/src/new_front/components/CreateSamples/CreateSamples/AnnotationInterfaces/Contexts/DescribeImage.tsx b/frontends/web/src/new_front/components/CreateSamples/CreateSamples/AnnotationInterfaces/Contexts/DescribeImage.tsx index 5a4abf8f..8d0d2067 100644 --- a/frontends/web/src/new_front/components/CreateSamples/CreateSamples/AnnotationInterfaces/Contexts/DescribeImage.tsx +++ b/frontends/web/src/new_front/components/CreateSamples/CreateSamples/AnnotationInterfaces/Contexts/DescribeImage.tsx @@ -1,22 +1,32 @@ -import GeneralButton from "new_front/components/Buttons/GeneralButton"; -import BasicInput from "new_front/components/Inputs/BasicInput"; -import { ContextConfigType } from "new_front/types/createSamples/createSamples/annotationContext"; -import { ContextAnnotationFactoryType } from "new_front/types/createSamples/createSamples/annotationFactory"; import React, { FC, useContext, useEffect, useState } from "react"; -import DoubleDropDown from "new_front/components/Inputs/DoubleDropDown"; +import { useDispatch, useSelector } from "react-redux"; +import { PacmanLoader } from "react-spinners"; +import MDEditor from "@uiw/react-md-editor"; +import { Modal } from "react-bootstrap"; import Select from "react-select"; import useFetch from "use-http"; -import { PacmanLoader } from "react-spinners"; -import { CreateInterfaceContext } from "new_front/context/CreateInterface/Context"; import axios from "axios"; -import { Modal } from "react-bootstrap"; -import MDEditor from "@uiw/react-md-editor"; + +import { CreateInterfaceContext } from "new_front/context/CreateInterface/Context"; +import { + updateCountry, + updatelanguage, + resetCategoryAndConcept, + resetAllButCountry, +} from "state/wonders/wondersSlice"; +import { RootState } from "state/store"; + +import GeneralButton from "new_front/components/Buttons/GeneralButton"; +import DoubleDropDown from "new_front/components/Inputs/DoubleDropDown"; +import { ContextConfigType } from "new_front/types/createSamples/createSamples/annotationContext"; +import { ContextAnnotationFactoryType } from "new_front/types/createSamples/createSamples/annotationFactory"; type ImageUploadProps = { image: string; setFile: (file: File) => void; setImage: (image: string) => void; updateModelInputs: (input: object, metadata?: boolean) => void; + letMagnifier?: boolean; }; const ImageUpload = ({ @@ -24,8 +34,16 @@ const ImageUpload = ({ setFile, setImage, updateModelInputs, + letMagnifier, }: ImageUploadProps) => { const [dragging, setDragging] = useState(false); + const magnifierHeight = 200; + const magnifieWidth = 200; + const zoomLevel = 1.5; + const [imgWidth, setImgWidth] = useState(0); + const [imgHeight, setImgHeight] = useState(0); + const [showMagnifier, setShowMagnifier] = useState(false); + const [[x, y], setXY] = useState([0, 0]); const handleDragEnter = (e: React.DragEvent) => { e.preventDefault(); @@ -61,18 +79,37 @@ const ImageUpload = ({ } }; + const handleMouseEnter = (e: any) => { + const elem = e.currentTarget; + const { width, height } = elem.getBoundingClientRect(); + setImgWidth(width); + setImgHeight(height); + setShowMagnifier(true); + }; + + const handleMouseMove = (e: any) => { + const elem = e.currentTarget; + const { top, left } = elem.getBoundingClientRect(); + const x = e.pageX - left - window.scrollX; + const y = e.pageY - top - window.scrollY; + setXY([x, y]); + }; + return (
-
+
src setShowMagnifier(false)} onClick={() => { const input = document.getElementById("fileInput"); if (input) { @@ -80,6 +117,24 @@ const ImageUpload = ({ } }} /> + {letMagnifier && showMagnifier && ( +
+ )}
= ({ setIsGenerativeContext, generative_context, }) => { + const BASE_URL_2 = process.env.REACT_APP_API_HOST_2; + const { post, response, loading } = useFetch(); + const dispatch = useDispatch(); + const { updateModelInputs } = useContext(CreateInterfaceContext); + const country = useSelector((state: RootState) => state.wonders.country); + const language = useSelector((state: RootState) => state.wonders.language); + const selectedCategory = useSelector( + (state: RootState) => state.wonders.selectedCategory + ); + const selectedConcept = useSelector( + (state: RootState) => state.wonders.selectedConcept + ); + const [filterContext, setFilterContext] = useState(); const [image, setImage] = useState( - "https://w7.pngwing.com/pngs/527/625/png-transparent-scalable-graphics-computer-icons-upload-uploading-cdr-angle-text-thumbnail.png", + "https://w7.pngwing.com/pngs/527/625/png-transparent-scalable-graphics-computer-icons-upload-uploading-cdr-angle-text-thumbnail.png" ); const [description, setDescription] = useState(""); - const [selectedCategory, setSelectedCategory] = useState( - localStorage.getItem("selectedCategory") - ? JSON.parse(localStorage.getItem("selectedCategory") || "") - : null, - ); - const [selectedConcept, setSelectedConcept] = useState( - localStorage.getItem("selectedConcept") - ? JSON.parse(localStorage.getItem("selectedConcept") || "") - : null, - ); const [showExample, setShowExample] = useState(false); - const { post, response, loading } = useFetch(); const [countries, setCountries] = useState([]); - const [country, setCountry] = useState( - localStorage.getItem("country") || "", - ); const [languages, setLanguages] = useState([]); - const [language, setLanguage] = useState( - localStorage.getItem("language") || "", - ); const [file, setFile] = useState(); - const BASE_URL_2 = process.env.REACT_APP_API_HOST_2; - const { modelInputs, updateModelInputs, removeItem } = useContext(CreateInterfaceContext); - const handleGetLanguages = async (e: any) => { - if (!e.value) return; - setCountry(e.value); + const handleGetLanguages = async (country: string) => { // @ts-ignore const extractedLanguages = generative_context.artifacts.options.reduce( (acc: string[], item: any) => { - if (item.primary === e.value) acc.push(...item.secondary); + if (item.primary === country) acc.push(...item.secondary); return acc; }, - [] as string[], + [] as string[] ); - setLanguage(""); + dispatch(resetAllButCountry()); setLanguages(extractedLanguages); setFilterContext(null); updateModelInputs({ - origin_primary: e.value, + origin_primary: country, origin_secondary: extractedLanguages[0], }); }; + const handleCountryChange = (e: any) => { + if (!e.value) return; + dispatch(updateCountry(e.value)); + handleGetLanguages(e.value); + dispatch(resetCategoryAndConcept()); + }; + const handleLanguageChange = (e: any) => { - setLanguage(e.value); + dispatch(updatelanguage(e.value)); updateModelInputs({ origin_primary: country, origin_secondary: e.value }); }; @@ -190,14 +244,20 @@ const DescribeImage: FC = ({ }; const handleSaveData = async () => { - if (description.length < 20) { - alert("Please enter a description with at least 20 characters."); + if (!selectedCategory) { + alert("Please pick a category."); return; } - if (!file || !description || !selectedCategory || !selectedConcept) { - alert( - "Please choose an image, describe it, and pick a category and concept.", - ); + if (!selectedConcept) { + alert("Please pick a concept."); + return; + } + if (!file) { + alert("Please choose an image."); + return; + } + if (description.length < 20) { + alert("Please enter a description with at least 20 characters."); return; } const formData = new FormData(); @@ -228,50 +288,28 @@ const DescribeImage: FC = ({ // @ts-ignore const extractedCountries = generative_context.artifacts.options.map( // @ts-ignore - (item) => item.primary, + (item) => item.primary ); setCountries(extractedCountries); } }, [generative_context]); useEffect(() => { + if (country && !language) { + handleGetLanguages(country); + } if (country && language) { handleGetCategories(); } - }, [country, language]); - - useEffect(() => { - localStorage.setItem("language", language); - localStorage.setItem("country", country); - localStorage.removeItem("selectedCategory"); - localStorage.removeItem("selectedConcept"); - removeItem("category"); - removeItem("concept"); - setSelectedCategory(null); - setSelectedConcept(null); }, [language, country]); useEffect(() => { - if (localStorage.getItem("language") && localStorage.getItem("country")) { - updateModelInputs({ - origin_primary: localStorage.getItem("country"), - origin_secondary: localStorage.getItem("language"), - }); - } - if (localStorage.getItem("selectedCategory")) { - updateModelInputs({ category: selectedCategory.value }); - } - if (localStorage.getItem("selectedConcept")) { - updateModelInputs({ concept: selectedConcept.value }); - } - }, [localStorage]); + selectedCategory && updateModelInputs({ category: selectedCategory.value }); + }, [selectedCategory]); useEffect(() => { - // If language and country are already selected then fetch the context - if (localStorage.getItem("language") && localStorage.getItem("country")) { - handleGetCategories(); - } - }, [localStorage.getItem("language")]); + selectedConcept && updateModelInputs({ concept: selectedConcept.value }); + }, [selectedConcept]); return ( <> @@ -307,21 +345,22 @@ const DescribeImage: FC = ({
-
-
+
+

Country

({ value: language, label: language, @@ -336,9 +381,9 @@ const DescribeImage: FC = ({ onChange={handleLanguageChange} placeholder="Select a language" defaultValue={ - localStorage.getItem("language") && { - value: localStorage.getItem("language"), - label: localStorage.getItem("language"), + language && { + value: language, + label: language, } } /> @@ -351,10 +396,6 @@ const DescribeImage: FC = ({
@@ -371,7 +412,7 @@ const DescribeImage: FC = ({
@@ -386,6 +427,7 @@ const DescribeImage: FC = ({ setFile={setFile} setImage={setImage} updateModelInputs={updateModelInputs} + letMagnifier={file ? true : false} /> void; }; const DoubleDropDown = ({ filterContext, - selectedCategory, - setSelectedCategory, - selectedConcept, - setSelectedConcept, updateModelInputs, }: DoubleDropDownProps) => { + const dispatch = useDispatch(); + const selectedCategory = useSelector( + (state: RootState) => state.wonders.selectedCategory + ); + const selectedConcept = useSelector( + (state: RootState) => state.wonders.selectedConcept + ); + const handleCategoryChange = (selectedOption: any) => { - setSelectedCategory(selectedOption); updateModelInputs({ category: selectedOption.value }); - setSelectedConcept(null); + dispatch(updateCategory(selectedOption)); + dispatch(updateConcept(null)); }; const handleConceptChange = (selectedOption: any) => { updateModelInputs({ concept: selectedOption.value }); - setSelectedConcept(selectedOption); + dispatch(updateConcept(selectedOption)); }; - useEffect(() => { - if (selectedCategory) { - localStorage.setItem( - "selectedCategory", - selectedCategory ? JSON.stringify(selectedCategory) : "", - ); - } - if (selectedConcept) { - localStorage.setItem( - "selectedConcept", - selectedConcept ? JSON.stringify(selectedConcept) : "", - ); - } - }, [selectedCategory, selectedConcept]); - - const actualCategory = localStorage.getItem("selectedCategory") - ? JSON.parse(localStorage.getItem("selectedCategory") || "") - : null; - const actualConcept = localStorage.getItem("selectedConcept") - ? JSON.parse(localStorage.getItem("selectedConcept") || "") - : null; - return ( <> {filterContext && ( -
-
+
+

Category