diff --git a/package-lock.json b/package-lock.json index a9882157..398b0116 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ecole-directe-plus", - "version": "0.2.5", + "version": "0.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "ecole-directe-plus", - "version": "0.2.5", + "version": "0.3.0", "dependencies": { "@floating-ui/react": "^0.25.4", "@vitejs/plugin-basic-ssl": "^1.0.1", @@ -14,6 +14,7 @@ "body-scroll-lock": "^4.0.0-beta.0", "crypto-js": "^4.1.1", "dompurify": "^3.1.0", + "js-sha256": "^0.11.0", "react-content-loader": "^6.2.1", "react-router-dom": "^6.16.0", "uuid": "^9.0.1", @@ -1047,6 +1048,11 @@ "node": ">=4" } }, + "node_modules/js-sha256": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.11.0.tgz", + "integrity": "sha512-6xNlKayMZvds9h1Y1VWc0fQHQ82BxTXizWPEtEeGvmOUYpBRy4gbWroHLpzowe6xiQhHpelCQiE7HEdznyBL9Q==" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -2023,6 +2029,11 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" }, + "js-sha256": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.11.0.tgz", + "integrity": "sha512-6xNlKayMZvds9h1Y1VWc0fQHQ82BxTXizWPEtEeGvmOUYpBRy4gbWroHLpzowe6xiQhHpelCQiE7HEdznyBL9Q==" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", diff --git a/package.json b/package.json index 5adb7928..4900b676 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ecole-directe-plus", - "version": "0.2.5", + "version": "0.3.0", "type": "module", "description": "Ecole Directe plus is a React web app that is designed to replace and improve EcoleDirecte", "dependencies": { @@ -10,6 +10,7 @@ "body-scroll-lock": "^4.0.0-beta.0", "crypto-js": "^4.1.1", "dompurify": "^3.1.0", + "js-sha256": "^0.11.0", "react-content-loader": "^6.2.1", "react-router-dom": "^6.16.0", "uuid": "^9.0.1", diff --git a/public/images/new/about-arrow.svg b/public/images/about-arrow.svg similarity index 100% rename from public/images/new/about-arrow.svg rename to public/images/about-arrow.svg diff --git a/public/images/canardman-sleeping.svg b/public/images/canardman-sleeping.svg new file mode 100644 index 00000000..9a24714d --- /dev/null +++ b/public/images/canardman-sleeping.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/App.jsx b/src/App.jsx index f43398e3..0a133add 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -69,7 +69,7 @@ function consoleLogEDPLogo() { consoleLogEDPLogo(); -const currentEDPVersion = "0.2.5"; +const currentEDPVersion = "0.3.0"; const apiVersion = "4.53.4"; // secret webhooks @@ -936,6 +936,7 @@ export default function App() { enabledFeatures.moyenneMin = settings.moyenneMin; enabledFeatures.moyenneMax = settings.moyenneMax; + enabledFeatures.coefficient = settings.coefficientNote; // add the average of all subjects a special type of chart for (const period in periods) { @@ -964,20 +965,44 @@ export default function App() { setDefaultPeriod(periods) } - function sortNextHomeworks(homeworks) { // This function will sort (I would rather call it translate) the EcoleDirecte response to a better js object + function sortNextHomeworks(homeworks) { // This function will sort (I would rather call it translate) the EcoleDirecte response to a better js object + const upcomingAssignments = [] const sortedHomeworks = Object.fromEntries(Object.entries(homeworks).map((day) => { - return [day[0], day[1].map((homework) => { + return [day[0], day[1].map((homework, i) => { const { codeMatiere, donneLe, effectue, idDevoir, interrogation, matiere, /* rendreEnLigne, documentsAFaire // I don't know what to do with that for now */ } = homework; - return ({ + const task = { id: idDevoir, subjectCode: codeMatiere, subject: matiere, addDate: donneLe, isInterrogation: interrogation, isDone: effectue, - }) + } + + if (interrogation && upcomingAssignments.length < 3) { + upcomingAssignments.push({ + date: day[0], + id: idDevoir, + index: i, + subject: matiere, + subjectCode: codeMatiere, + }); + } + + return task; })] })) + + if (upcomingAssignments.length > 0) { + let i = 0; + while (upcomingAssignments.length < 3) { + upcomingAssignments.push({ + id: "dummy" + i, + }); + i++; + } + } + changeUserData("upcomingAssignments", upcomingAssignments) return sortedHomeworks } @@ -988,9 +1013,9 @@ export default function App() { if (!aFaire) { return null; } - + const { donneLe, effectue, contenu, contenuDeSeance, document } = aFaire; - return ({ + return { id: id, subjectCode: codeMatiere, subject: matiere, @@ -1002,10 +1027,9 @@ export default function App() { files: document, sessionContent: contenuDeSeance.contenu, sessionContentFiles: contenuDeSeance.documents, - }) + } }).filter((item) => item)] })) - console.log("sortedHomeworks:", sortedHomeworks) return sortedHomeworks } @@ -1401,56 +1425,57 @@ export default function App() { } else { endpoint = "cahierdetexte/" + getISODate(date); } - - fetch( - getProxiedURL(`https://api.ecoledirecte.com/v3/Eleves/${accountsListState[userId].id}/${endpoint}.awp?verbe=get&v=${apiVersion}`, true), - { - method: "POST", - headers: { - "x-token": tokenState + if (accountsListState[activeAccount].firstName === "Guest") { + if (date === "incoming") { + import("./data/homeworks.json").then((module) => { + changeUserData("sortedHomeworks", sortNextHomeworks(module.data)) + }) + } else { + import("./data/detailed_homeworks.json").then((module) => { + changeUserData("sortedHomeworks", { ...getUserData("sortedHomeworks"), ...sortDayHomeworks({ [module.data.date]: module.data.matieres }) }) + }) + } + abortControllers.current.splice(abortControllers.current.indexOf(controller), 1); + } else { + fetch( + getProxiedURL(`https://api.ecoledirecte.com/v3/Eleves/${accountsListState[userId].id}/${endpoint}.awp?verbe=get&v=${apiVersion}`, true), + { + method: "POST", + headers: { + "x-token": tokenState + }, + body: "data={}", + signal: controller.signal }, - body: "data={}", - signal: controller.signal - }, - ) - .then((response) => response.json()) - .then((response) => { - let code; - if (accountsListState[activeAccount].firstName === "Guest") { - code = 49969; - } else { - code = response.code; - } - if (code === 200) { - if (date === "incoming") { - changeUserData("sortedHomeworks", { ...sortNextHomeworks(response.data), ...getUserData("sortedHomeworks") }) - } else { - changeUserData("sortedHomeworks", { ...getUserData("sortedHomeworks"), ...sortDayHomeworks({ [response.data.date]: response.data.matieres }) }) + ) + .then((response) => response.json()) + .then((response) => { + const code = response.code; + if (code === 200) { + if (date === "incoming") { + changeUserData("sortedHomeworks", { ...sortNextHomeworks(response.data), ...getUserData("sortedHomeworks") }) + } else { + changeUserData("sortedHomeworks", { ...getUserData("sortedHomeworks"), ...sortDayHomeworks({ [response.data.date]: response.data.matieres }) }) + } + } else if (code === 520 || code === 525) { + // token invalide + console.log("INVALID TOKEN: LOGIN REQUIRED"); + requireLogin(); } - } else if (code === 520 || code === 525) { - // token invalide - console.log("INVALID TOKEN: LOGIN REQUIRED"); - requireLogin(); - } else if (code === 49969) { - let userHomeworks = structuredClone(homeworks); - import("./data/homeworks.json").then((module) => { - userHomeworks[userId] = module.data; - setHomeworks(userHomeworks); - }) - } - setTokenState((old) => (response?.token || old)); - }) - .catch((error) => { - if (error.message === "Unexpected token 'P', \"Proxy error\" is not valid JSON") { - setProxyError(true); - } - }) - .finally(() => { - abortControllers.current.splice(abortControllers.current.indexOf(controller), 1); - }) + setTokenState((old) => (response?.token || old)); + }) + .catch((error) => { + if (error.message === "Unexpected token 'P', \"Proxy error\" is not valid JSON") { + setProxyError(true); + } + }) + .finally(() => { + abortControllers.current.splice(abortControllers.current.indexOf(controller), 1); + }) + } } - async function fetchHomeworksDone({ tasksDone=[], tasksNotDone=[]}, controller = (new AbortController())) { + async function fetchHomeworksDone({ tasksDone = [], tasksNotDone = [] }, controller = (new AbortController())) { /** * Change the state of selected homeworks * @param tasksDone Tasks switched to true @@ -1468,7 +1493,7 @@ export default function App() { headers: { "x-token": tokenState }, - body: "data=" + JSON.stringify({idDevoirsEffectues: tasksDone, idDevoirsNonEffectues: tasksNotDone}), + body: "data=" + JSON.stringify({ idDevoirsEffectues: tasksDone, idDevoirsNonEffectues: tasksNotDone }), signal: controller.signal }, ) diff --git a/src/components/EdpUnblock/EdpUnblock.css b/src/components/EdpUnblock/EdpUnblock.css index c9dca02a..f57dd26b 100644 --- a/src/components/EdpUnblock/EdpUnblock.css +++ b/src/components/EdpUnblock/EdpUnblock.css @@ -85,7 +85,7 @@ font-size: var(--font-size-36); } -.edpu-page main > div > p { +.edpu-page main > div > p, .edpu-page main > div > p span { max-width: 650px; font-size: var(--font-size-20); color: #b4b4f0; diff --git a/src/components/EdpUnblock/EdpUnblock.jsx b/src/components/EdpUnblock/EdpUnblock.jsx index 4b86690a..f9746aa3 100644 --- a/src/components/EdpUnblock/EdpUnblock.jsx +++ b/src/components/EdpUnblock/EdpUnblock.jsx @@ -106,7 +106,7 @@ export default function EdpUnblock() { - Besoin d’aide ? + Besoin d’aide ?
@@ -119,7 +119,7 @@ export default function EdpUnblock() {

Ecole Directe Plus Unblock

-

Ecole Directe Plus a besoin de cette extension de navigateur pour accéder au contenu fourni par l’API d’EcoleDirecte.

+

Ecole Directe Plus a besoin de cette extension de navigateur pour fonctionner correctement et accéder à l’API d’EcoleDirecte.

{compatibilityCondition ? <>

Malheureusement, l'extension Ecole Directe Plus Unblock n'est pas disponible sur votre navigateur. 😥

S'il vous plaît considérez l'usage d'un navigateur compatible comme le {userOS === "iOS" ? "navigateur Orion" : "navigateur Firefox"}.

: null} {browserLogosInfos[userBrowser].logo} @@ -136,9 +136,9 @@ export default function EdpUnblock() {

Qu'est-ce qu'Ecole Directe Plus Unblock ?

EDP Unblock est une extension de navigateur qui offre un accès ininterrompu à Ecole Directe Plus en donnant l'accès en continu aux données fournies par l'API d'EcoleDirecte. Cette extension est nécessaire au bon fonctionnement d'Ecole Directe Plus.

Où et comment installer EDP Unblock ?

-

EDP Unblock étant une extension de navigateur, la source d'installation diffère en fonction de votre navigateur et votre OS. Cliquez sur le bouton "Ajouter l'extension" ci-dessus et vous devriez être redirigé automatiquement vers la boutique d'extensions compatible avec votre navigateur. Mise en garde : EDP Unblock n'est pas disponible sur tous les navigateurs suivant les plateformes. Sur iOS et iPadOS, Apple restreint fortement la distribution d'extensions, EDP Unblock sera donc uniquement disponible sur le navigateur Orion. Si vous êtes sur un appareil Android, considérez l'usage du navigateur Firefox ou KiwiBrowser. Si vous êtes sur MacOS, tous les navigateurs hormis Safari devrait être compatibles avec EDP Unblock. Enfin, si vous utilisez un ordinateur sous Windows ou Linux, la grande majorité des navigateurs devraient être compatibles avec l'extension (basé sur Chromium : Chrome, Edge, Brave, Opera, ... ; basé sur Gecko : Firefox)

+

EDP Unblock étant une extension de navigateur, la source d'installation diffère en fonction de votre navigateur et votre OS. Cliquez sur le bouton "Ajouter l'extension" ci-dessus et vous devriez être redirigé automatiquement vers la boutique d'extensions compatible avec votre navigateur. Mise en garde : EDP Unblock n'est pas disponible sur tous les navigateurs suivant les plateformes. Sur iOS et iPadOS, Apple restreint fortement la distribution d'extensions, EDP Unblock sera donc uniquement disponible sur le navigateur Orion. Si vous êtes sur un appareil Android, considérez l'usage du navigateur Firefox ou KiwiBrowser. Si vous êtes sur MacOS, tous les navigateurs hormis Safari devraient être compatibles avec EDP Unblock. Enfin, si vous utilisez un ordinateur sous Windows ou Linux, la grande majorité des navigateurs devraient être compatibles avec l'extension (basé sur Chromium : Chrome, Edge, Brave, Opera, ... ; basé sur Gecko : Firefox)

Vie privée et confidentialité

-

EDP Unblock est exclusivement active sur les domaines `ecole-directe.plus` ainsi que `ecoledirecte.com`. L'extension ne peut pas accéder aux informations provenant de n'importe quel autre site web. De plus, EDP Unblock ne lit aucune donnée : l'extension sert simplement de passerelle aux requêtes pour "arriver correctement à destination", mais n'a pas accès à leur contenu. Ainsi, EDP Unblock ne collecte aucune donnée et effectue toutes ces opérations en local sur l'appareil du client.

+

EDP Unblock est exclusivement active sur les domaines `ecole-directe.plus` ainsi que `ecoledirecte.com`. L'extension ne peut pas accéder aux informations provenant de n'importe quel autre site web. De plus, EDP Unblock ne lit aucune donnée : l'extension joue simplement le rôle de passerelle aux requêtes pour "les amener correctement à destination", mais n'a pas accès à leur contenu. Ainsi, EDP Unblock ne collecte aucune donnée et effectue toutes ces opérations en local sur l'appareil client.

Divers

L'extension Ecole Directe Plus Unblock, tout comme le site Ecole Directe Plus, est un projet open-source sous license MIT, le code source est donc disponible en ligne : dépôt Github.

diff --git a/src/components/Feedback/FeedbackForm.jsx b/src/components/Feedback/FeedbackForm.jsx index 88f12a2e..d9a9d9b9 100644 --- a/src/components/Feedback/FeedbackForm.jsx +++ b/src/components/Feedback/FeedbackForm.jsx @@ -16,6 +16,7 @@ import AtWhite from "../graphics/AtWhite" import { getProxiedURL } from "../../utils/requests"; import "./FeedbackForm.css"; +import { getBrowser, getOS } from "../../utils/utils"; export default function FeedbackForm({ activeUser, carpeConviviale, onSubmit=() => {} }) { @@ -28,6 +29,7 @@ export default function FeedbackForm({ activeUser, carpeConviviale, onSubmit=() ### Étapes pour reproduire : ### Navigateur/OS/Appareil : +${getBrowser()} ; ${getOS()} ; (Complété automatiquement. Modifiable) `, `### Description de la fonctionnalité : diff --git a/src/components/Lab/Lab.jsx b/src/components/Lab/Lab.jsx index ca511d95..c5d86238 100644 --- a/src/components/Lab/Lab.jsx +++ b/src/components/Lab/Lab.jsx @@ -38,7 +38,7 @@ import { getBrowser, getOS } from "../../utils/utils"; import TextInput from "../generic/UserInputs/TextInput"; import { clearHTML } from "../../utils/html"; import EncodedHTMLDiv from "../generic/CustomDivs/EncodedHTMLDiv"; - +import { textToHSL } from "../../utils/utils" export default function Lab({ fetchGrades }) { const addNotification = useCreateNotification() // States @@ -57,6 +57,7 @@ export default function Lab({ fetchGrades }) { const [displayProxyErrorNotification, setDisplayProxyErrorNotification] = useState(false); const [toggleSwitchState, setToggleSwitchState] = useState(false) const [targetURL, setTargetURL] = useState(""); + const [testHash, setTestHash] = useState(""); const navigate = useNavigate(); @@ -76,6 +77,7 @@ export default function Lab({ fetchGrades }) { navigate(url); } + const testHSLValues = textToHSL(testHash) // JSX return (
@@ -302,10 +304,11 @@ export default function Lab({ fetchGrades }) {

-
+

Contrasts improved:

{`PHA+PHNwYW4gc3R5bGU9ImZvbnQtc2l6ZToxOHB4Ij48c3BhbiBzdHlsZT0iY29sb3I6IzI5ODBiOSI+Qm9uam91ciAmYWdyYXZlOyB0b3VzLDwvc3Bhbj48L3NwYW4+PC9wPgoKPHA+PHNwYW4gc3R5bGU9ImZvbnQtc2l6ZToxOHB4Ij48c3BhbiBzdHlsZT0iY29sb3I6IzI5ODBiOSI+TWVyY3JlZGkgMTQgZiZlYWN1dGU7dnJpZXIgYXVyYSBsaWV1ICZhZ3JhdmU7IDEwaDEwIGxhIG1lc3NlIGRlcyBDZW5kcmVzLiBOb3VzIG5vdXMgcmV0cm91dmVyb25zICZhZ3JhdmU7IGxhIGNoYXBlbGxlLiA8L3NwYW4+PC9zcGFuPjwvcD4KCjxwPjxzcGFuIHN0eWxlPSJmb250LXNpemU6MThweCI+PHNwYW4gc3R5bGU9ImNvbG9yOiMyOTgwYjkiPkxlIG1lcmNyZWRpIGRlcyBDZW5kcmVzIGVzdCBsZSBqb3VyIG8mdWdyYXZlOyBkZXMgY2hyJmVhY3V0ZTt0aWVucyBlbnRyZW50IGRhbnMgbGUgY2FyJmVjaXJjO21lLiA8L3NwYW4+PC9zcGFuPjwvcD4KCjxwPjxzcGFuIHN0eWxlPSJmb250LXNpemU6MThweCI+PHNwYW4gc3R5bGU9ImNvbG9yOiMyOTgwYjkiPlNpIHZvdXMgdm91cyBzb3VoYWl0ZXogeSBwYXJ0aWNpcGVyLCA8c3Ryb25nPm1lcmNpIGRlIHZvdXMgaW5zY3JpcmUgYXZhbnQgbGUgbWFyZGkgMTMgJmFncmF2ZTsgMThoPC9zdHJvbmc+LCBhdXByJmVncmF2ZTtzIGRlIE1tZSBYWFhYWCBvdSBhdXByJmVncmF2ZTtzIGRlIG1vaS1tJmVjaXJjO21lKiAoZGlyZWN0ZW1lbnQgb3UgZW4gciZlYWN1dGU7cG9uZGFudCAmYWdyYXZlOyBjZSBtYWlsKS48L3NwYW4+PC9zcGFuPjwvcD4KCjxwPjxzcGFuIHN0eWxlPSJmb250LXNpemU6MThweCI+PHNwYW4gc3R5bGU9ImNvbG9yOiMyOTgwYjkiPkJvbm5lIGpvdXJuJmVhY3V0ZTtlICZhZ3JhdmU7IHRvdXMsPC9zcGFuPjwvc3Bhbj48L3A+Cgo8cD4qSmUgc3VpcyBhYnNlbnQgcG91ciBmb3JtYXRpb24gbHVuZGkgZXQgbWFyZGkgMTIgZXQgMTMgZiZlYWN1dGU7dnJpZXIuPC9wPg==`} + setTestHash(e.target.value)} value={testHash} style={{backgroundColor: `hsl(${testHSLValues[0]}, ${testHSLValues[1]}%, ${testHSLValues[2]}%)`}} /> {/* FOOTER */}
diff --git a/src/components/Login/A2FLogin.css b/src/components/Login/A2FLogin.css index d514b027..18dc2df6 100644 --- a/src/components/Login/A2FLogin.css +++ b/src/components/Login/A2FLogin.css @@ -30,14 +30,28 @@ } .A2F-login .A2F-answers-container { + width: 100%; + height: 100%; + overflow: auto; +} + +.A2F-login .A2F-answers { display: flex; - flex-flow: column wrap; + flex-flow: column nowrap; gap: 5px 20px; width: max-content; align-content: center; margin-inline: auto; margin-bottom: 30px; max-height: 250px; + text-align: left; +} + +.A2F-login .A2F-answers > .radio-button:first-child { + padding-top: 10px; +} +.A2F-login .A2F-answers > .radio-button:last-child { + padding-bottom: 10px; } .A2F-login .A2F-content-loader-container { diff --git a/src/components/Login/A2FLogin.jsx b/src/components/Login/A2FLogin.jsx index 5c7a2a8e..cae28428 100644 --- a/src/components/Login/A2FLogin.jsx +++ b/src/components/Login/A2FLogin.jsx @@ -7,6 +7,7 @@ import PopUp from "../generic/PopUps/PopUp"; import RadioButton from "../generic/UserInputs/RadioButton"; import { decodeBase64 } from "../../utils/utils"; import Button from "../generic/UserInputs/Button"; +import ScrollShadedDiv from "../generic/CustomDivs/ScrollShadedDiv"; export default function A2FLogin({ fetchA2F, ...props }) { const [A2FForm, setA2FForm] = useState({}); @@ -60,40 +61,42 @@ export default function A2FLogin({ fetchA2F, ...props }) { > } - {Object.keys(A2FForm).length > 0 - ?
- {A2FForm.propositions.map((answer, index) => setChoice(event.target.dataset.value)}>{decodeBase64(answer)})} -
- :
- {Array.from({ length: 11 }, (_, index) =>
- - - - - - -
)} -
- } -

{errorMessage}

+ + {Object.keys(A2FForm).length > 0 + ?
+ {A2FForm.propositions.map((answer, index) => setChoice(event.target.dataset.value)}>{decodeBase64(answer)})} +
+ :
+ {Array.from({ length: 11 }, (_, index) =>
+ + + + + + +
)} +
+ } +
+ {errorMessage &&

{errorMessage}

}
diff --git a/src/components/Root.jsx b/src/components/Root.jsx index 651ad16c..6feb7443 100644 --- a/src/components/Root.jsx +++ b/src/components/Root.jsx @@ -29,8 +29,7 @@ export default function Root({ currentEDPVersion, token, accountsList, fakeLogin } function redirectToApp() { - // navigate(`/app/${activeAccount}/dashboard`, { replace: true }); - navigate(`/app/${activeAccount}/grades`, { replace: true }); + navigate(`/app/${activeAccount}/dashboard`, { replace: true }); } function redirectToLab() { @@ -87,6 +86,8 @@ export default function Root({ currentEDPVersion, token, accountsList, fakeLogin return 0; } else if (parsedOldVersion <= 24) { // v0.2.4 return 0; + } else if (parsedOldVersion <= 25) { // v0.2.5 + return 0; } else { localStorage.clear(); } diff --git a/src/components/app/Dashboard/Dashboard.jsx b/src/components/app/Dashboard/Dashboard.jsx index 517a3921..88e59835 100644 --- a/src/components/app/Dashboard/Dashboard.jsx +++ b/src/components/app/Dashboard/Dashboard.jsx @@ -1,6 +1,6 @@ import { useState, useEffect } from "react"; -import { useParams, useNavigate } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; import { WindowsContainer, @@ -15,6 +15,7 @@ import BottomSheet from "../../generic/PopUps/BottomSheet"; import EncodedHTMLDiv from "../../generic/CustomDivs/EncodedHTMLDiv"; import "./Dashboard.css"; +import UpcomingAssignments from "../Homeworks/UpcomingAssignments"; export default function Dashboard({ fetchUserGrades, grades, fetchHomeworks, activeAccount, isLoggedIn, useUserData, sortGrades }) { const navigate = useNavigate(); @@ -84,12 +85,12 @@ export default function Dashboard({ fetchUserGrades, grades, fetchHomeworks, act */} - + navigate("../homeworks")}>

Prochains contrôles

- - + +
@@ -98,13 +99,13 @@ export default function Dashboard({ fetchUserGrades, grades, fetchHomeworks, act navigate("../homeworks")}>

Cahier de texte

- +
- + navigate("../timetable")}>

Emploi du temps

diff --git a/src/components/app/Dashboard/LastGrades.jsx b/src/components/app/Dashboard/LastGrades.jsx index e09bded3..06e98a81 100644 --- a/src/components/app/Dashboard/LastGrades.jsx +++ b/src/components/app/Dashboard/LastGrades.jsx @@ -1,4 +1,4 @@ -import { useContext } from "react" +import { useRef, useContext } from "react" import { AppContext } from "../../../App" import Grade from "../Grades/Grade" import { Link, useNavigate } from "react-router-dom" @@ -14,12 +14,15 @@ import { import "./LastGrades.css" import { formatDateRelative } from "../../../utils/date"; +import ContentLoader from "react-content-loader" export default function LastGrades({ activeAccount, className = "", ...props }) { const navigate = useNavigate(); + const contentLoadersRandomValues = useRef({ subjectNameWidth: Array.from({ length: 3 }, (_, i) => Math.floor(Math.random() * 75) + 75), badgesNumber: Array.from({ length: 3 }, (_, i) => Math.floor(Math.random() * 3) + 1), datesWidth: Array.from({ length: 3 }, (_, i) => Math.floor(Math.random() * 25) + 85) }) - const { useUserData } = useContext(AppContext) + const { actualDisplayTheme, useUserData, useUserSettings } = useContext(AppContext) const lastGrades = useUserData().get("lastGrades"); + const settings = useUserSettings(); return ( navigate("../grades")}> @@ -43,7 +46,56 @@ export default function LastGrades({ activeAccount, className = "", ...props }) {formatDateRelative(el.date, window.matchMedia("(max-width: 1850px)").matches)} ) - :

Chargement en cours...

+ : Array.from({ length: 3 }, (_, i) =>
  • +
    + + + + + + + + {Array.from({ length: contentLoadersRandomValues.current.badgesNumber[i] }, (_, i) => + + )} + + + + + + +
    +
  • ) } diff --git a/src/components/app/Grades/Grade.jsx b/src/components/app/Grades/Grade.jsx index 0de361ee..7c712c54 100644 --- a/src/components/app/Grades/Grade.jsx +++ b/src/components/app/Grades/Grade.jsx @@ -9,8 +9,9 @@ import { AppContext } from "../../../App"; import "./Grade.css"; export default function Grade({ grade, className = "", ...props }) { - const { useUserSettings, deleteFakeGrade } = useContext(AppContext); // de même pour ça + const { useUserSettings, useUserData, deleteFakeGrade } = useContext(AppContext); // de même pour ça + const coefficientEnabled = useUserData().get("gradesEnabledFeatures")?.coefficient; const isGradeScaleEnabled = useUserSettings("isGradeScaleEnabled"); const gradeScale = useUserSettings("gradeScale"); const [classList, setClassList] = useState([]); @@ -147,7 +148,7 @@ export default function Grade({ grade, className = "", ...props }) { {["Abs", "Disp", "NE", "EA", "Comp"].includes(grade.value) ? grade.value : {(isGradeScaleEnabled.get() && !isNaN(grade.value) ? Math.round((grade.value * gradeScale.get() / (grade.scale ?? 20)) * 100) / 100 : grade.value)?.toString().replace(".", ",")} {isGradeScaleEnabled.get() || ((grade.scale ?? 20) != 20 && /{grade.scale})} - {(grade.coef ?? 1) !== 1 && ({grade.coef ?? 1})} + {coefficientEnabled && (grade.coef ?? 1) !== 1 && ({grade.coef ?? 1})} } {(grade.isReal ?? true) === false && {deleteFakeGrade(grade.id, grade.subjectKey, grade.periodKey)}}/>} diff --git a/src/components/app/Grades/Information.css b/src/components/app/Grades/Information.css index 7644b48e..081a1783 100644 --- a/src/components/app/Grades/Information.css +++ b/src/components/app/Grades/Information.css @@ -43,7 +43,10 @@ text-align: center; font-size: var(--font-size-14); color: rgb(var(--text-color-alt)); - margin-bottom: 5px; +} + +.information .information-hr { + margin-top: 5px; } .information .grade-zone { diff --git a/src/components/app/Grades/Information.jsx b/src/components/app/Grades/Information.jsx index 56c1b848..0b87920c 100644 --- a/src/components/app/Grades/Information.jsx +++ b/src/components/app/Grades/Information.jsx @@ -183,8 +183,8 @@ export default function Information({ sortedGrades, activeAccount, selectedPerio
    {selectedElement.classMax.toString().replace(".", ",")}{isNaN(selectedElement.classMax) ? null : /{selectedElement.scale}}
    } -

    coefficient : {selectedElement.coef}{selectedElement.isSignificant ? "" : (selectedElement.isReal ? " (non significatif)" : " (note simulée)")}

    -
    + {true &&

    coefficient : {selectedElement.coef}{selectedElement.isSignificant ? "" : (selectedElement.isReal ? " (non significatif)" : " (note simulée)")}

    } +

    {capitalizeFirstLetter(selectedElement.name)}

    diff --git a/src/components/app/Grades/Results.jsx b/src/components/app/Grades/Results.jsx index c38b3051..321db01f 100644 --- a/src/components/app/Grades/Results.jsx +++ b/src/components/app/Grades/Results.jsx @@ -32,6 +32,8 @@ export default function Results({ activeAccount, sortedGrades, selectedPeriod, s if (element !== null) { if (element.scrollIntoViewIfNeeded !== undefined) { element.scrollIntoViewIfNeeded(); + } else { + element.scrollIntoView(); } } } diff --git a/src/components/app/Header/Header.jsx b/src/components/app/Header/Header.jsx index cd7e0786..c62026e2 100644 --- a/src/components/app/Header/Header.jsx +++ b/src/components/app/Header/Header.jsx @@ -148,7 +148,8 @@ export default function Header({ currentEDPVersion, token, accountsList, setActi title: "Accueil", link: `/app/${activeAccount}/dashboard`, icon: , - notifications: notifications?.dashboard || 0 + notifications: notifications?.dashboard || 0, + isNew: false }, { enabled: accountsList[activeAccount]?.modules?.filter((item) => item.code === "NOTES").map((item) => item.enable).includes(true) ?? true, @@ -157,7 +158,8 @@ export default function Header({ currentEDPVersion, token, accountsList, setActi title: "Notes", link: `/app/${activeAccount}/grades`, icon: , - notifications: notifications?.grades || 0 + notifications: notifications?.grades || 0, + isNew: false }, { enabled: accountsList[activeAccount]?.modules?.filter((item) => item.code === "CAHIER_DE_TEXTES").map((item) => item.enable).includes(true) ?? true, @@ -166,7 +168,8 @@ export default function Header({ currentEDPVersion, token, accountsList, setActi title: "Cahier de texte", link: `/app/${activeAccount}/homeworks`, icon: , - notifications: notifications?.homeworks || 0 + notifications: notifications?.homeworks || 0, + isNew: true }, { enabled: accountsList[activeAccount]?.modules?.filter((item) => item.code === "EDT").map((item) => item.enable).includes(true) ?? true, @@ -175,7 +178,8 @@ export default function Header({ currentEDPVersion, token, accountsList, setActi title: "Emploi du temps", link: `/app/${activeAccount}/timetable`, icon: , - notifications: notifications?.timetable || 0 + notifications: notifications?.timetable || 0, + isNew: false }, { enabled: accountsList[activeAccount]?.modules?.filter((item) => item.code === "MESSAGERIE").map((item) => item.enable).includes(true) ?? true, @@ -184,7 +188,8 @@ export default function Header({ currentEDPVersion, token, accountsList, setActi title: "Messagerie", link: `/app/${activeAccount}/messaging`, icon: , - notifications: notifications?.messaging || 0 + notifications: notifications?.messaging || 0, + isNew: false } ] // Behavior @@ -210,7 +215,7 @@ export default function Header({ currentEDPVersion, token, accountsList, setActi
      {headerNavigationButtons.map((headerButton) => ( headerButton.enabled &&
    • - +
    • ) )} diff --git a/src/components/app/Header/HeaderNavigationButton.css b/src/components/app/Header/HeaderNavigationButton.css index 48c3bdb6..cdd2448d 100644 --- a/src/components/app/Header/HeaderNavigationButton.css +++ b/src/components/app/Header/HeaderNavigationButton.css @@ -68,6 +68,28 @@ animation: notification-pop-in .2s ease forwards; } +.header-button .notifications.new { + background-color: rgb(var(--background-color-1)); + width: 52px; + color: rgb(var(--text-color-main)); + animation: new-feature-notification-animation .8s ease alternate forwards; + animation-iteration-count: 20; + left: 40px; +} + +.light .header-button .notifications.new { + box-shadow: 0 0 20px rgba(0, 0, 0, .2); +} + +@keyframes new-feature-notification-animation { + from { + scale: .9; + } + to { + scale: 1; + } +} + @keyframes notification-pop-in { from { opacity: 0; diff --git a/src/components/app/Header/HeaderNavigationButton.jsx b/src/components/app/Header/HeaderNavigationButton.jsx index c85cd1ab..b759612d 100644 --- a/src/components/app/Header/HeaderNavigationButton.jsx +++ b/src/components/app/Header/HeaderNavigationButton.jsx @@ -2,13 +2,13 @@ import { Link } from "react-router-dom"; import "./HeaderNavigationButton.css"; -export default function HeaderNavigationButton({ link, title, icon, notifications, className="", id="", ...props}) { +export default function HeaderNavigationButton({ link, title, icon, notifications, isNew=false, className="", id="", ...props}) { return (
      {icon} - {(notifications !== 0) &&
      {notifications}
      } + {(notifications !== 0 || isNew) &&
      {isNew ? "NEW" : notifications}
      }
      {title} diff --git a/src/components/app/Homeworks/DetailedTask.css b/src/components/app/Homeworks/DetailedTask.css index 7fc54e6a..e5d52caa 100644 --- a/src/components/app/Homeworks/DetailedTask.css +++ b/src/components/app/Homeworks/DetailedTask.css @@ -3,6 +3,10 @@ flex-direction: column; gap: 5px; flex-shrink: 0; + transition: .1s; +} +.detailed-task.done { + opacity: .4; } .task-header { @@ -72,7 +76,7 @@ /* change task content items appearance */ .detailed-task .task-content a { - word-break: break-word; + word-break: break-all; } .detailed-task .task-content a:is(:hover, :focus-visible) { opacity: .8; @@ -106,6 +110,10 @@ pointer-events: none; filter: brightness(0.6); } +.light .task-footer .task-footer-button.disabled { + pointer-events: none; + filter: opacity(0.4); +} .task-footer .task-footer-button:is(:hover, :focus-visible) { background-color: rgb(var(--background-color-3)); } diff --git a/src/components/app/Homeworks/DetailedTask.jsx b/src/components/app/Homeworks/DetailedTask.jsx index 1ca07f98..1c3e97e5 100644 --- a/src/components/app/Homeworks/DetailedTask.jsx +++ b/src/components/app/Homeworks/DetailedTask.jsx @@ -4,7 +4,7 @@ import EncodedHTMLDiv from "../../generic/CustomDivs/EncodedHTMLDiv" import CheckBox from "../../generic/UserInputs/CheckBox" import { AppContext } from "../../../App" import { applyZoom } from "../../../utils/zoom"; -import { Link } from "react-router-dom" +import { Link, useLocation } from "react-router-dom" import "./DetailedTask.css" import PatchNotesIcon from "../../graphics/PatchNotesIcon" @@ -13,8 +13,9 @@ import CopyButton from "../../generic/CopyButton" import { clearHTML } from "../../../utils/html" export default function DetailedTask({ task, userHomeworks, day, taskIndex, setBottomSheetSession, ...props }) { const isMouseInCheckBoxRef = useRef(false); + const detailedTaskRef = useRef(null); const taskCheckboxRef = useRef(null); - const { actualDisplayTheme, fetchHomeworks, fetchHomeworksDone, useUserSettings } = useContext(AppContext) + const { actualDisplayTheme, fetchHomeworksDone, useUserSettings } = useContext(AppContext) const settings = useUserSettings(); const homeworks = userHomeworks.get() @@ -25,6 +26,8 @@ export default function DetailedTask({ task, userHomeworks, day, taskIndex, setB "", ] + const location = useLocation(); + const oldLocationHash = useRef(null); const hashParameters = location.hash.split(";") useEffect(() => { @@ -35,18 +38,45 @@ export default function DetailedTask({ task, userHomeworks, day, taskIndex, setB content: task.sessionContent, }) } - }, []) + }, [hashParameters]); + + function scrollIntoViewNearestParent(element) { + const parent = element.parentElement; + const parentBounds = parent.getBoundingClientRect(); + const bounds = element.getBoundingClientRect(); + + console.log("scrollIntoViewNearestParent ~ bounds.y - parentBounds.y + parentBounds.scrollTop:", bounds.y - parentBounds.y + parent.scrollTop) + parent.scrollTo(0, bounds.y - parentBounds.y + parent.scrollTop - 20) + } useEffect(() => { - const controller = new AbortController(); - if (!task.content) { - fetchHomeworks(controller, day) + if (oldLocationHash.current === location.hash) { + return; + } + + oldLocationHash.current = location.hash; + + if (["#patch-notes", "#policy", "#feedback"].includes(location.hash)) { + return; } - return () => { - controller.abort(); + const anchors = location.hash.split(";"); + + if (anchors.length < 2) { + return; } - }, [day, task]); + + const taskId = parseInt(anchors[1]); + + if (isNaN(taskId)) { + return; + } + + if (taskId === task.id && detailedTaskRef.current) { + setTimeout(() => scrollIntoViewNearestParent(detailedTaskRef.current), 200); + } + + }, [location, detailedTaskRef.current, homeworks]) function completedTaskAnimation() { const bounds = taskCheckboxRef.current.getBoundingClientRect(); @@ -79,7 +109,7 @@ export default function DetailedTask({ task, userHomeworks, day, taskIndex, setB } return <>{(task?.content - ?
      + ?
      { checkTask(day, task, taskIndex) }} checked={task.isDone} onMouseEnter={() => isMouseInCheckBoxRef.current = true} onMouseLeave={() => isMouseInCheckBoxRef.current = false} />

      @@ -90,7 +120,7 @@ export default function DetailedTask({ task, userHomeworks, day, taskIndex, setB {task.addDate && Donné le {(new Date(task.addDate)).toLocaleDateString()} par {task.teacher}} {task.isInterrogation && évaluation}

      - } >{task.content} + } backgroundColor={actualDisplayTheme === "dark" ? "#40405b" : "#e4e4ff"} >{task.content}
      { e.stopPropagation(); setBottomSheetSession({ @@ -129,7 +159,7 @@ export default function DetailedTask({ task, userHomeworks, day, taskIndex, setB
      -
      +
      Contenu de séance
      Fichiers
      diff --git a/src/components/app/Homeworks/Homeworks.jsx b/src/components/app/Homeworks/Homeworks.jsx index f43c9d92..7ee1b753 100644 --- a/src/components/app/Homeworks/Homeworks.jsx +++ b/src/components/app/Homeworks/Homeworks.jsx @@ -14,10 +14,9 @@ import { AppContext } from "../../../App"; import Notebook from "./Notebook"; import BottomSheet from "../../generic/PopUps/BottomSheet"; import EncodedHTMLDiv from "../../generic/CustomDivs/EncodedHTMLDiv"; +import UpcomingAssignments from "./UpcomingAssignments"; import "./Homeworks.css"; - - export default function Homeworks({ isLoggedIn, activeAccount, fetchHomeworks }) { // States @@ -65,11 +64,11 @@ export default function Homeworks({ isLoggedIn, activeAccount, fetchHomeworks })

      Prochains devoirs surveillés

      - - + + - +

      Calendrier

      @@ -78,7 +77,7 @@ export default function Homeworks({ isLoggedIn, activeAccount, fetchHomeworks })
      - +

      Cahier de texte

      diff --git a/src/components/app/Homeworks/Interrogation.jsx b/src/components/app/Homeworks/Interrogation.jsx new file mode 100644 index 00000000..12fe293d --- /dev/null +++ b/src/components/app/Homeworks/Interrogation.jsx @@ -0,0 +1,76 @@ +import { useContext, useRef } from "react"; +import { textToHSL } from "../../../utils/utils" +import { formatDateRelative } from "../../../utils/date" +import CheckBox from "../../generic/UserInputs/CheckBox" +import { AppContext } from "../../../App"; +import { useNavigate } from "react-router-dom"; +import { applyZoom } from "../../../utils/zoom"; + +export default function Interrogation({ task }) { // This component only exists to give a ref for each interrogation + const { useUserData, fetchHomeworksDone } = useContext(AppContext) + const sortedHomeworks = useUserData("sortedHomeworks"); + const currentSortedHomeworks = sortedHomeworks.get(); + const isMouseInCheckBoxRef = useRef(false) + const taskCheckboxRef = useRef(null); + + const navigate = useNavigate() + + function completedTaskAnimation() { + const bounds = taskCheckboxRef.current.getBoundingClientRect(); + const origin = { + x: bounds.left + bounds.width / 2, + y: bounds.top + bounds.height / 2 + } + confetti({ + particleCount: 40, + spread: 70, + origin: { + x: origin.x / applyZoom(window.innerWidth), + y: origin.y / applyZoom(window.innerHeight) + }, + }); + } + + function checkTask(date, taskIndex) { + const task = currentSortedHomeworks[date][taskIndex] + const tasksToUpdate = (task.isDone ? { + tasksNotDone: [task.id], + } : { + tasksDone: [task.id], + }) + fetchHomeworksDone(tasksToUpdate); + if (tasksToUpdate.tasksDone !== undefined) { + completedTaskAnimation(); + } + currentSortedHomeworks[date][taskIndex].isDone = !task.isDone; + sortedHomeworks.set(currentSortedHomeworks); + } + + function handleClick(date, id) { + if (!isMouseInCheckBoxRef.current) { + navigate(`#${date};${id}`) + } + } + + function handleKeyDown(e, date, id) { + console.log(e) + if (["Enter", " "].includes(e.key)) { + navigate(`#${date};${id}`) + } + } + + const taskColor = typeof task.id === "number" ? textToHSL(task.subjectCode) : undefined; + + return typeof task.id === "number" + ?
      handleKeyDown(e, task.date, task.id)} onClick={() => handleClick(task.date, task.id)} className={`upcoming-assignments ${currentSortedHomeworks[task.date][task.index].isDone ? "done" : ""}`} + style={{ + backgroundColor: `hsl(${taskColor[0]}, ${taskColor[1]}%, ${taskColor[2]}%)`, + "--text-color-task": `hsl(${taskColor[0]}, ${taskColor[1] - 20}%, ${taskColor[2] - 20}%)`, + "--background-color-task": `hsl(${taskColor[0]}, ${taskColor[1] - 5}%, ${taskColor[2] - 5}%)`, + }}> + { checkTask(task.date, task.index) }} ref={taskCheckboxRef} checked={currentSortedHomeworks[task.date][task.index].isDone} id={`${task.id}-upcoming-assignments"`} onMouseEnter={() => isMouseInCheckBoxRef.current = true} onMouseLeave={() => isMouseInCheckBoxRef.current = false} style={{ "backgroundImage": currentSortedHomeworks[task.date][task.index].isDone ? `url("data:image/svg+xml,%3Csvg width='126' height='90' viewBox='0 0 126 90' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M3.00999 29.4982L4.7539 27.7442C8.77118 23.7036 15.3475 23.8312 19.205 28.0246L47.306 58.5723C48.849 60.2496 51.4795 60.3007 53.0864 58.6844L108.318 3.13256C112.228 -0.799963 118.591 -0.799963 122.501 3.13256L122.99 3.62419C126.868 7.5247 126.868 13.8249 122.99 17.7254L52.9741 88.147C51.4102 89.72 48.8649 89.72 47.301 88.147L3.00999 43.5994C-0.868052 39.6989 -0.868055 33.3987 3.00999 29.4982Z' fill='hsl(${taskColor[0]}, ${taskColor[1] - 20}%, ${taskColor[2] - 20}%)'/%3E%3C/svg%3E")` : "" }} /> + {task.subject} + {formatDateRelative(new Date(task.date))} +
      + :
      +} \ No newline at end of file diff --git a/src/components/app/Homeworks/Notebook.css b/src/components/app/Homeworks/Notebook.css index 788e4206..21ba8d02 100644 --- a/src/components/app/Homeworks/Notebook.css +++ b/src/components/app/Homeworks/Notebook.css @@ -6,7 +6,6 @@ .notebook-container { display: flex; flex-flow: nowrap row; - justify-content: space-between; padding: 20px; gap: 40px; overflow-y: visible; @@ -103,6 +102,9 @@ display: flex; flex-flow: column nowrap; } +.performance .notebook-day { + border: 2px solid rgb(var(--background-color-3));; +} .notebook-day:active { cursor: grabbing; @@ -114,6 +116,13 @@ opacity: 1; } +.notebook-window:fullscreen .notebook-day { + width: min(400px, 100%); +} +.notebook-window:fullscreen .notebook-day.selected { + width: min(800px, 100%); +} + .notebook-day.selected .tasks-container { gap: 30px; } @@ -181,4 +190,5 @@ flex-flow: nowrap column; gap: 15px; padding: 15px; + scroll-behavior: smooth; } \ No newline at end of file diff --git a/src/components/app/Homeworks/Notebook.jsx b/src/components/app/Homeworks/Notebook.jsx index eec87079..00c0efef 100644 --- a/src/components/app/Homeworks/Notebook.jsx +++ b/src/components/app/Homeworks/Notebook.jsx @@ -13,7 +13,7 @@ import DetailedTask from "./DetailedTask"; import { canScroll } from "../../../utils/DOM"; export default function Notebook({ setBottomSheetSession, hideDateController = false }) { - const { actualDisplayTheme, useUserData, useUserSettings } = useContext(AppContext); + const { isLoggedIn, actualDisplayTheme, useUserData, useUserSettings, fetchHomeworks } = useContext(AppContext); const settings = useUserSettings(); const userHomeworks = useUserData("sortedHomeworks"); const location = useLocation(); @@ -97,7 +97,7 @@ export default function Notebook({ setBottomSheetSession, hideDateController = f for (let element of elements) { const elementBounds = element.getBoundingClientRect(); - if (elementBounds.width > 300) { + if (elementBounds.width > (document.fullscreenElement?.classList.contains("notebook-window") ? 400 : 300)) { oldSelectedElementBounds = elementBounds; break; } @@ -108,7 +108,8 @@ export default function Notebook({ setBottomSheetSession, hideDateController = f const bounds = element.getBoundingClientRect(); const containerBounds = notebookContainerRef.current.getBoundingClientRect(); - notebookContainerRef.current.scrollTo(bounds.x - containerBounds.x + Math.min(600, containerBounds.width) / 2 * (oldSelectedElementBounds.x >= bounds.x) + notebookContainerRef.current.scrollLeft - containerBounds.width / 2, 0) + const TASK_MAX_WIDTH = Math.min(document.fullscreenElement?.classList.contains("notebook-window") ? 800 : 600, containerBounds.width); + notebookContainerRef.current.scrollTo(bounds.x - containerBounds.x + TASK_MAX_WIDTH / 2 * (oldSelectedElementBounds.x >= bounds.x) + notebookContainerRef.current.scrollLeft - containerBounds.width / 2, 0) } @@ -279,12 +280,25 @@ export default function Notebook({ setBottomSheetSession, hideDateController = f }, [isMouseOverTasksContainer, isMouseIntoScrollableContainer, tasksContainersRefs.current]) + useEffect(() => { + const controller = new AbortController(); + if ((homeworks && homeworks[selectedDate] && !homeworks[selectedDate][0].content) && isLoggedIn) { + fetchHomeworks(controller, selectedDate) + } + + return () => { + controller.abort(); + } + }, [selectedDate, homeworks, isLoggedIn]); + return <> {!hideDateController ?
      - navigateToDate(nearestHomeworkDate(-1, selectedDate))} tabIndex={0} > + navigateToDate(nearestHomeworkDate(-1, selectedDate))} tabIndex={0} onKeyDown={(event) => { if (event.key === "Enter" || event.key === " ") { navigateToDate(nearestHomeworkDate(-1, selectedDate)) } } } > + + - navigateToDate(nearestHomeworkDate(1, selectedDate))} tabIndex={0} > + navigateToDate(nearestHomeworkDate(1, selectedDate))} tabIndex={0} onKeyDown={(event) => { if (event.key === "Enter" || event.key === " ") { navigateToDate(nearestHomeworkDate(1, selectedDate)) } } } >
      : null } diff --git a/src/components/app/Homeworks/Task.css b/src/components/app/Homeworks/Task.css index c656a9bf..2410a163 100644 --- a/src/components/app/Homeworks/Task.css +++ b/src/components/app/Homeworks/Task.css @@ -12,6 +12,9 @@ outline: none; overflow: hidden; } +.task.done { + opacity: .4; +} .task .task-title { padding: 0.75em; @@ -64,7 +67,7 @@ transition: .1s !important; } -.task .check-box:is(:hover, :focus-within) input { +.task .check-box:is(:hover, :has(:focus-visible)) input { opacity: .25; } diff --git a/src/components/app/Homeworks/UpcomingAssignments.css b/src/components/app/Homeworks/UpcomingAssignments.css new file mode 100644 index 00000000..a928577c --- /dev/null +++ b/src/components/app/Homeworks/UpcomingAssignments.css @@ -0,0 +1,124 @@ +.upcoming-assignments-container { + display: flex; + flex-direction: column; + padding: 15px; + gap: 10px; +} + +.upcoming-assignments { + display: flex; + align-items: center; + border-radius: 15px; + padding-inline: 15px; + justify-content: space-between; + gap: 10px; + flex: 1; + cursor: pointer; + transition: .2s; + overflow: hidden; +} + +.upcoming-assignments.done { + opacity: .7; +} + +.upcoming-assignments:is(:hover, :focus-visible) { + filter: brightness(1.1); + outline: none; +} +.upcoming-assignments:active { + filter: brightness(0.9); +} + +.upcoming-assignments label { + flex-shrink: 0; + width: 35px; + aspect-ratio: 1/1; +} + +.upcoming-assignments input[type="checkbox"] { + height: 100%; + width: 100%; + border-color: var(--text-color-task); + background-color: var(--background-color-task); + border-width: 4px; +} + +.upcoming-assignments input[type="checkbox"]:is(:hover, :focus-visible):not(:checked) { + border-color: rgba(0, 0, 0, 0.3); +} + +.upcoming-assignments input[type="checkbox"]:checked { + border-color: var(--text-color-task); + background-color: var(--background-color-task); +} + +.upcoming-assignments span { + font-weight: var(--font-weight-extra-bold); + color: var(--text-color-task); + flex: 1; + text-align: center; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.upcoming-assignments span:last-child { + text-align: end; + flex: unset; +} + +.upcoming-assignments .interrogation-label { + position: relative; +} + +.upcoming-assignments .interrogation-label::after { + content: ""; + width: 0; + background-color: var(--text-color-task); + height: 2.2px; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + transition: .5s; +} + +.upcoming-assignments.done .interrogation-label::after { + width: 107%; +} + +.dummy-interrogation { + flex: 1; +} + +.upcoming-assignments-container .loading { + display: flex; + flex-direction: column; + justify-content: space-evenly; + align-items: center; + height: 100%; +} + +.upcoming-assignments-container .loading span { + text-align: center; + max-width: 80%; + color: #8787B4; + font-weight: var(--font-weight-extra-bold); +} + +.upcoming-assignments-container .loading svg { + height: 60%; + min-width: 70px; + max-width: 120px; +} + +@media only screen and (max-width: 869px) { + .dummy-interrogation { + display: none; + } + + .upcoming-assignments { + padding: 15px; + } +} \ No newline at end of file diff --git a/src/components/app/Homeworks/UpcomingAssignments.jsx b/src/components/app/Homeworks/UpcomingAssignments.jsx new file mode 100644 index 00000000..c509db0a --- /dev/null +++ b/src/components/app/Homeworks/UpcomingAssignments.jsx @@ -0,0 +1,28 @@ +import { useState, useContext } from "react"; +import { AppContext } from "../../../App"; +import Interrogation from "./Interrogation"; +import CanardmanSleeping from "../../graphics/CanardmanSleeping"; + +import "./UpcomingAssignments.css"; +const placeholder = [ + Vous n'avez n'as pas de contrôles prochainement, profitez-en pour regarder Canardman dormir., + Aucun contrôle de prévu.
      Tiens, voilà Canardman qui dort.
      , + C'est calme...
      Canardman en profite pour faire dodo.
      , + C'est trop calme...
      Canardman n'aime pas trop beaucoup ça.
      , + Les contrôles se font discrets, Canardman peut dormir tranquille., + Aucun contrôle à venir, Canardman l'a bien compris., +] + +export default function UpcomingAssignments() { + const { useUserData } = useContext(AppContext) + const upcomingAssignments = useUserData("upcomingAssignments"); + const currentUpcomingAssignments = upcomingAssignments.get(); + const [choosenPlaceholder, _] = useState(placeholder[parseInt(Math.random() * placeholder.length)]) + + return currentUpcomingAssignments?.length + ? currentUpcomingAssignments.map((e) => ) + :
      + {choosenPlaceholder} + +
      +} \ No newline at end of file diff --git a/src/components/generic/CustomDivs/EncodedHTMLDiv.jsx b/src/components/generic/CustomDivs/EncodedHTMLDiv.jsx index a4f6a109..2eea2a86 100644 --- a/src/components/generic/CustomDivs/EncodedHTMLDiv.jsx +++ b/src/components/generic/CustomDivs/EncodedHTMLDiv.jsx @@ -3,6 +3,7 @@ import { useState, useRef, useEffect } from "react"; import { clearHTML } from "../../../utils/html"; export default function EncodedHTMLDiv({ children, nonEncodedChildren=null, backgroundColor, ...props }) { + console.log("EncodedHTMLDiv ~ backgroundColor:", backgroundColor) const [backgroundColorState, setBackgroundColorState] = useState(null); const divRef = useRef(null); @@ -25,7 +26,7 @@ export default function EncodedHTMLDiv({ children, nonEncodedChildren=null, back }, []); return ( -
      +
      {nonEncodedChildren &&
      {nonEncodedChildren}
      }
      diff --git a/src/components/generic/PatchNotes.jsx b/src/components/generic/PatchNotes.jsx index 97e8ef54..cbe7a06f 100644 --- a/src/components/generic/PatchNotes.jsx +++ b/src/components/generic/PatchNotes.jsx @@ -5,50 +5,74 @@ export default function PatchNotes({ currentEDPVersion, onClose }) { return (
      - +

      - La version 0.2.5 est arrivée ! Cette mise à jour n'apporte certainement pas toutes les nouvelles fonctionnalités que vous auriez pu espérer. Toutefois, nous avons quelques nouveautés pour vous... + Canardman va enfin pouvoir se mettre au travail ! Après une longue attente, nous sommes impatients de vous faire découvrir des fonctionnalités attendues :

      Nouveautés

      -

      -

      Rencontrez la communauté et les développeurs d'Ecole Directe Plus en rejoignant le serveur Discord !

      - -
      - Plus d'informations -

      - Nous avons récemment créé un serveur Discord communautaire pour Ecole Directe Plus. Vous pourrez y retrouver les autres adeptes d'EDP, discuter avec les développeurs, nous aider à corriger les bugs que vous rencontrez, etc. - Vous serez également aux premières loges en cas d'annonce importante. De plus, vous pourrez consulter les retours des utilisateurs et découvrir ce qu'ils pensent d'EDP. - Rejoignez le Canardman-Gang en cliquant ici

      -
      -

      -
      -
    • Ajout de boutons dans la catégorie "Informations" pour télécharger le sujet et la correction d'une évaluation s'ils sont disponibles.
    • -

      Améliorations

      -
        -
      • Un bouton a été ajouté pour vous permettre de montrer/cacher votre mot de passe dans le menu de connexion.
      • -
      • Le scrolling a été amélioré sur mobile de sorte qu'il ne se bloque plus lors d'un clic sur l'en-tête d'une fenêtre.
      • -
      +
    • Le cahier de texte est enfin là ! Canardman n'aura plus d'excuse pour ne pas faire ses devoirs
    • +
    • Page de retour : votre navigateur et système d'exploitation seront automatiquement détectés, vous n'aurez plus à vous en soucier
    • +
    • L'accueil, vous vous en souvenez ? Ça sera désormais la page principale. Retrouvez-y un résumé clair et concis de ce qui compte vraiment
    • +
    • Faire ses devoirs ne sera plus jamais ennuyeux grâce à l'incroyable explosion de confetti qui accompagne la complétion de chaque tâche (pas sûr de celle ça)
    • +
    • Vous vous faîtes surprendre par chacun de vos devoirs surveillés ? Cette situation gênante n'arrivera PLUS JAMAIS car vous bénéficierez d'un aperçu rapide des prochains contrôles
    • Correction de bugs

        -
      • Vous avez été nombreux à nous signaler ce problème assez embarrassant : désormais, les comptes dont les matières avaient toutes un coefficient de 0 verront leur moyenne générale et de groupe de matière calculées correctement.
      • -
      • Les notes notées "absent", "non-évalué", "dispensé", … n'affichent plus N/A.
      • -
      • Les notes simulées ne sont plus considérées comme de nouvelles notes.
      • -
      • Les graphiques s'adaptent mieux à la taille de l'écran de votre appareil.
      • -
      • La période sélectionnée ne se réinitialise plus quand l'utilisateur change de page.
      • -
      • Correction d'un bug causant une animation de chargement infinie.
      • -
      • Correction d'un bug provoquant une infinité de ré-rendus de la page.
      • -
      • Bug de la bottom sheet causant quelques glitch.
      • -
      • Amélioration générale des performances et de la stabilité.
      • +
      • Correction d'un bug bloquant dû à l'absence de coefficient
      • +
      • Gestion des barêmes à virgule
      • +
      • Intégration de l'authentification à deux facteurs mise en place par ED pour assurer la sécurité des comptes
      • +
      • Mise à jour des mentions légales pour plus de transparence
      • +
      • Amélioration de la gestion des Checkbox
      • +
      • Correction d'un bug d'affichage sur les volets "Évaluations" et "Graphique" sur Firefox
      • +
      • Amélioration de la navigation au clavier
      • +
      • Ajout d'une animation de chargement du contenu sur les "Dernières Notes"

      Divers

      -
    • Veuillez noter qu'Ecole Directe Plus est encore en cours de développement. Nous travaillons d'arrache-pied pour vous fournir la meilleure version possible du service.
    • +
    • Veuillez noter qu'Ecole Directe Plus est un service non-affilié à Aplim ou EcoleDirecte et est encore en cours de développement. Bénévolement, nous travaillons d'arrache-pied pour vous fournir la meilleure version possible du service.
    • Vous avez un problème ou avez rencontré un bug ? Vous pouvez nous partager votre expérience dans la nouvelle page de feedback
    • +
    • Ecole Directe Plus a son propre serveur Discord ! Rejoignez le maintenant pour discuter avec les développeurs et tout le Canardman-Gang !
    • Découvrez le trailer d'annonce d'Ecole Directe Plus qui expose en quelques images les ambitions que nous avons pour ce projet en constante évolution :
    • {/*
        + + v0.2.5 +

        + La version 0.2.5 est arrivée ! Cette mise à jour n'apporte certainement pas toutes les nouvelles fonctionnalités que vous auriez pu espérer. Toutefois, nous avons quelques nouveautés pour vous... +

        +

        Nouveautés

        +

        +

        Rencontrez la communauté et les développeurs d'Ecole Directe Plus en rejoignant le serveur Discord !

        + +
        + Plus d'informations +

        + Nous avons récemment créé un serveur Discord communautaire pour Ecole Directe Plus. Vous pourrez y retrouver les autres adeptes d'EDP, discuter avec les développeurs, nous aider à corriger les bugs que vous rencontrez, etc. + Vous serez également aux premières loges en cas d'annonce importante. De plus, vous pourrez consulter les retours des utilisateurs et découvrir ce qu'ils pensent d'EDP. + Rejoignez le Canardman-Gang en cliquant ici

        +
        +

        +
        +
      • Ajout de boutons dans la catégorie "Informations" pour télécharger le sujet et la correction d'une évaluation s'ils sont disponibles.
      • +

        Améliorations

        +
          +
        • Un bouton a été ajouté pour vous permettre de montrer/cacher votre mot de passe dans le menu de connexion.
        • +
        • Le scrolling a été amélioré sur mobile de sorte qu'il ne se bloque plus lors d'un clic sur l'en-tête d'une fenêtre.
        • +
        +

        Correction de bugs

        +
          +
        • Vous avez été nombreux à nous signaler ce problème assez embarrassant : désormais, les comptes dont les matières avaient toutes un coefficient de 0 verront leur moyenne générale et de groupe de matière calculées correctement.
        • +
        • Les notes notées "absent", "non-évalué", "dispensé", … n'affichent plus N/A.
        • +
        • Les notes simulées ne sont plus considérées comme de nouvelles notes.
        • +
        • Les graphiques s'adaptent mieux à la taille de l'écran de votre appareil.
        • +
        • La période sélectionnée ne se réinitialise plus quand l'utilisateur change de page.
        • +
        • Correction d'un bug causant une animation de chargement infinie.
        • +
        • Correction d'un bug provoquant une infinité de ré-rendus de la page.
        • +
        • Bug de la bottom sheet causant quelques glitch.
        • +
        • Amélioration générale des performances et de la stabilité.
        • +
        + v0.2.3

        diff --git a/src/components/generic/Policy.jsx b/src/components/generic/Policy.jsx index 57ea32bd..3979d5af 100644 --- a/src/components/generic/Policy.jsx +++ b/src/components/generic/Policy.jsx @@ -24,11 +24,11 @@ export default function Policy({ onCloseNavigateURL }) {

        ✅ Ecole Directe Plus ne collecte aucune information sur les utilisateurs du service.

        ✅ Ecole Directe Plus ne crée pas de compte lors de la connexion, la connexion a lieu sur les serveurs d'Aplim. Autrement dit, nous ne STOCKONS PAS les identifiants des utilisateurs se connectant.

        ✅ Ecole Directe Plus ne permet, ni ne prétend donner accès à des données auxquelles l'élève n'a pas accès, incluant, mais ne se limitant pas aux : points aux examens* et au rang de l'élève*.

        -

        ℹ️ Les seules données collectées le sont par Aplim (EcoleDirecte) conformément à leur politique de confidentialité décrite dans les Mentions Légales.

        +

        ℹ️ Les seules données collectées le sont par Aplim (EcoleDirecte) conformément à leur politique de confidentialité décrite dans leurs Mentions Légales.

        *Si l'accès à ces données est possible par l'utilisateur sur la plateforme officielle d'EcoleDirecte, ces données peuvent être affichées sur Ecole Directe Plus. Par ailleurs, si les moyennes de l'utilisateur ne sont pas disponibles, elles seront calculées, mais ce de façon locale sur l'appareil du client, les informations ne sont PAS transmises à nos serveurs.


      -

      Ecole Directe Plus est un service non-affilié utilisant des données fournis par EcoleDirecte, héritant ainsi de leurs mentions légales qui sont présentées ci-dessous :

      +

      Ecole Directe Plus est un service non-affilié utilisant des données fournies par EcoleDirecte, héritant ainsi de leurs mentions légales qui sont présentées ci-dessous :

    • Les données personnelles sécurisées figurant sur ce site Internet concernent des élèves et les familles, et sont fournies par le logiciel Charlemagne des établissements scolaires au sein desquels ceux-ci sont scolarisés.
    • EcoleDirecte ne collecte aucune donnée personnelle directement sur le site Internet, ni cookie, à l’exception des email et téléphone mobile, utilisés EXCLUSIVEMENT pour la récupération des identifiants.
    • Ces établissements scolaires se sont engagés à apporter tous leurs soins dans la qualité des informations diffusées. Il s’agit toutefois d’indications qui, en aucun cas, ne pourraient faire foi en lieu et place des documents usuels (bulletins de notes, relevés de notes, relevés d’absences et de sanctions).
    • diff --git a/src/components/graphics/CanardmanSleeping.jsx b/src/components/graphics/CanardmanSleeping.jsx new file mode 100644 index 00000000..0f1f19fd --- /dev/null +++ b/src/components/graphics/CanardmanSleeping.jsx @@ -0,0 +1,17 @@ + +import "./graphics.css" +import "./CanarmanSleeping.css" +export default function CanardmanSleeping({ className = "", id = "", alt, ...props }) { + return ( + + + + + + + + + + + ) +} \ No newline at end of file diff --git a/src/components/graphics/CanarmanSleeping.css b/src/components/graphics/CanarmanSleeping.css new file mode 100644 index 00000000..e03d778c --- /dev/null +++ b/src/components/graphics/CanarmanSleeping.css @@ -0,0 +1,50 @@ +.sleeping-canardman { + overflow: visible; +} + +#Z-0 { + opacity: 0; + animation: Z-0-growing 1.5s infinite; +} + +#Z-1 { + animation: Z-1-growing 1.5s infinite; +} + +#Z-2 { + animation: Z-2-growing 1.5s infinite; +} + +#Z-3 { + opacity: 1; + animation: Z-3-growing 1.5s infinite; +} + +@keyframes Z-0-growing { + from { + opacity: 0; + } + to { + opacity: 1; + transform: scale(1.25) translate(-8px, -13.5px); + } +} + +@keyframes Z-1-growing { + to { + transform: scale(1.3) translate(-7.5px, -13.2px); + } +} + +@keyframes Z-2-growing { + to { + transform: scale(1.36) translate(-10.3px, -9.4px); + } +} + +@keyframes Z-3-growing { + to { + transform: scale(1.38) translate(-11px, -5px); + opacity: 0; + } +} \ No newline at end of file diff --git a/src/components/graphics/ExtensionIcon.jsx b/src/components/graphics/ExtensionIcon.jsx index e9e70de1..c20ff7e3 100644 --- a/src/components/graphics/ExtensionIcon.jsx +++ b/src/components/graphics/ExtensionIcon.jsx @@ -3,7 +3,7 @@ import "./graphics.css" export default function ExtensionIcon({ className = "", id = "", alt, ...props }) { return ( - + ) } \ No newline at end of file diff --git a/src/components/graphics/graphics.css b/src/components/graphics/graphics.css index d3c49865..6a1638b4 100644 --- a/src/components/graphics/graphics.css +++ b/src/components/graphics/graphics.css @@ -224,3 +224,14 @@ fill: url(#paint5_radial_2388_27); } +.fill-8787B4 { + fill: #8787B4; +} + +.fill-canardman-main { + fill: rgb(var(--background-color-canarman-main)); +} + +.fill-181829 { + fill: #181829; +} \ No newline at end of file diff --git a/src/data/detailed_homeworks.json b/src/data/detailed_homeworks.json new file mode 100644 index 00000000..141c053c --- /dev/null +++ b/src/data/detailed_homeworks.json @@ -0,0 +1,81 @@ +{ + "code": 200, + "token": "a781c855-727e-48a6-9d20-894b3179e5ca", + "host": "HTTP94", + "data": { + "date": "2024-05-21", + "matieres": [ + { + "entityCode": "T-MATH2", + "entityLibelle": "T-MATH2", + "entityType": "G", + "matiere": "MATHEMATIQUES", + "codeMatiere": "MATHS", + "nomProf": "Mme BELLINI V.", + "id": 19881, + "interrogation": false, + "blogActif": false, + "nbJourMaxRenduDevoir": 0, + "aFaire": { + "idDevoir": 19881, + "contenu": "PHA+RmluaXImbmJzcDtleCAxMSBwIDI0NzwvcD4K", + "rendreEnLigne": false, + "donneLe": "2024-05-14", + "effectue": true, + "ressource": "", + "documentsRendusDeposes": false, + "ressourceDocuments": [], + "documents": [], + "elementsProg": [], + "liensManuel": [], + "documentsRendus": [], + "contenuDeSeance": { + "contenu": "PHA+QXV0b21hdGlzbWVzPC9wPgoKPHA+Q291cnMgOiAyKSBDYWxjdWwgZCYjMzk7dW5lIGludCZlYWN1dGU7Z3JhbGUgJmFncmF2ZTsgbCYjMzk7YWlkZSBkJiMzOTt1bmUgcHJpbWl0aXZlPC9wPgoKPHA+RXhlcmNpY2VzIDogZXggNTAsIDQ1IChhLCBjKSwgNDYgcCAyNTU8L3A+Cgo8cD5Db3VycyA6IElJSSBQcm9wcmkmZWFjdXRlO3QmZWFjdXRlO3MgZGUgbCYjMzk7aW50JmVhY3V0ZTtncmFsZSA7IDEpIFByZW1pJmVncmF2ZTtyZXMgcHJvcHJpJmVhY3V0ZTt0JmVhY3V0ZTtzIDsgMikgTGluJmVhY3V0ZTthcml0JmVhY3V0ZTs8L3A+Cgo8cD5FeGVyY2ljZXMgOiBleCA2MSBwIDI1NjwvcD4KCjxwPkNvdXJzIDogMykgUmVsYXRpb24gZGUgQ2hhc2xlczwvcD4KCjxwPkV4ZXJjaWNlcyA6IGV4IDY1IHAgMjU2PC9wPgoKPHA+Q291cnMgOiA0KSBQb3NpdGl2aXQmZWFjdXRlOyA7IDUpIENvbXBhcmFpc29uPC9wPgoKPHA+RXhlcmNpY2VzIDogZXggNjYgKGQpIHAgMjU2IDsgZXggMTEgcCAyNDc8L3A+Cg==", + "documents": [] + } + } + }, + { + "entityCode": "T-MATEX", + "entityLibelle": "T-MATEX", + "entityType": "G", + "matiere": "MATHS EXPERTES", + "codeMatiere": "MATEX", + "nomProf": "Mme GAUTHERON V.", + "id": 19814, + "interrogation": true, + "blogActif": false, + "nbJourMaxRenduDevoir": 0, + "aFaire": { + "idDevoir": 19814, + "contenu": "PGRpdj5GaW5pciBsZXMgZXhlcmNpY2VzIDogNDRiY2QuNDguMTRhLjYxYS42My42NC43My43NC43NSBwMTAxIMOgIDEwNyBldCByZWdhcmRlciBsYSBjb3JyZWN0aW9uIGVuIHBpw6hjZSBqb2ludGUuPC9kaXY+PGRpdj48YnI+PC9kaXY+RFMgOiBHcmFwaGVzIGV0IMOJcXVhdGlvbnMgcG9seW5vbWlhbGVzJm5ic3A7", + "rendreEnLigne": false, + "donneLe": "2024-05-15", + "effectue": false, + "ressource": "", + "documentsRendusDeposes": false, + "ressourceDocuments": [], + "documents": [ + { + "id": 2014, + "libelle": "Exs Tle Exp.pdf", + "date": "2024-05-15", + "taille": 698351, + "type": "FICHIER_CDT", + "signatureDemandee": false, + "etatSignatures": [], + "signature": {} + } + ], + "elementsProg": [], + "liensManuel": [], + "documentsRendus": [], + "contenuDeSeance": { + "contenu": "", + "documents": [] + } + } + } + ] + } +} \ No newline at end of file diff --git a/src/utils/date.js b/src/utils/date.js index 555570dd..d0075973 100644 --- a/src/utils/date.js +++ b/src/utils/date.js @@ -1,20 +1,29 @@ - export function formatDateRelative(date, short=true) { + const dayMs = 24*3600*1000 const now = new Date(); const today = new Date(now.getFullYear(), now.getMonth(), now.getDate()); - const yesterday = new Date(today); - yesterday.setDate(yesterday.getDate() - 1); + const tomorrow = new Date(today.getTime() + dayMs); + const yesterday = new Date(today.getTime() - dayMs); + + const months = ["Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"] + const weekDays = ["Dimanche", "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi"] const comparedDate = new Date(date.getFullYear(), date.getMonth(), date.getDate()); - if (comparedDate.getTime() === today.getTime()) { + if (comparedDate.getTime() - now.getTime() >= 7 * dayMs) { + return `${comparedDate.getDate()} ${months[comparedDate.getMonth()]}`; + } else if (comparedDate.getTime() - now.getTime() > 1 * dayMs ) { + return `${weekDays[comparedDate.getDay()]}`; + } else if (comparedDate.getTime() === tomorrow.getTime()) { + return "Demain"; + } else if (comparedDate.getTime() === today.getTime()) { return "Aujourd'hui"; } else if (comparedDate.getTime() === yesterday.getTime()) { return "Hier"; - } else if (now.getTime() - comparedDate.getTime() < 7*(24*3600*1000) ) { - return (short ? "" : "Il y a ") + `${Math.floor((now.getTime() - comparedDate.getTime()) / (24*3600*1000))} jours`; - } else if (now.getTime() - comparedDate.getTime() < 14*(24*3600*1000) ) { - return (short ? "" : "Il y a ") + `1 semaine`; + } else if (comparedDate.getTime() - now.getTime() > -7 * dayMs ) { + return `${short ? "" : "Il y a "} ${Math.floor((now.getTime() - comparedDate.getTime()) / dayMs)} jours`; + } else if (comparedDate.getTime() - now.getTime() > -14 * dayMs ) { + return `${short ? "" : "Il y a "} 1 semaine`; } else { return date.toLocaleDateString(); } diff --git a/src/utils/gradesTools.jsx b/src/utils/gradesTools.js similarity index 95% rename from src/utils/gradesTools.jsx rename to src/utils/gradesTools.js index 775433e3..69073be4 100644 --- a/src/utils/gradesTools.jsx +++ b/src/utils/gradesTools.js @@ -17,7 +17,11 @@ export function getGradeValue(gradeValue) { } export function safeParseFloat(value) { - return isNaN(parseFloat(value?.replace(",", "."))) ? "N/A" : parseFloat(value?.replace(",", ".")) + if (typeof value === "number") { + return value + } + const parsedValue = parseFloat(value?.replace(",", ".")) + return isNaN(parsedValue) ? "N/A" : parsedValue } export function calcAverage(list) { diff --git a/src/utils/html.js b/src/utils/html.js index ccad806b..e011b3ec 100644 --- a/src/utils/html.js +++ b/src/utils/html.js @@ -97,8 +97,7 @@ export function clearHTML(html, backgroundColor, asString=true) { * Decodes, makes readable, sanitizes and improve contrasts of html content with optional inline style * @param html HTML content to clear * @param backgroundColor The color of the background on which the text will be displayed. Allow the function to improve constrasts. (RGB format: list) - */ - + */ let decodedHTML = decodeBase64(html); let readableOutput = decodeHtmlEscaped(decodedHTML); @@ -113,6 +112,19 @@ export function clearHTML(html, backgroundColor, asString=true) { // get all elements that contain inline style and define a text color const allElements = parsedHTML.querySelectorAll("*"); + allElements.forEach(el => { + const style = el.getAttribute("style"); + const WHITES = ["white", "#FFFFFF", "#FFF", "#ffffff", "#fff", "rgb(255, 255, 255)"]; + if (el?.style && (WHITES.includes(el.style.backgroundColor) || WHITES.includes(el.style.background))) { + el.style.background = ""; + el.style.backgroundColor = ""; + } + if (hasParentWithInlineBackground(el)) { + if (!style || !style.includes("color:")) { + el.style.color = "black"; + } + } + }); const elementsWithColor = Array.from(allElements).filter(el => { const style = el.getAttribute("style"); return style && style.includes("color:") && !hasParentWithInlineBackground(el); // selects color and unselects those with parents that has background-color (or highlighting) diff --git a/src/utils/utils.js b/src/utils/utils.js index 8e7e4153..4ba4c4ac 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -1,5 +1,6 @@ import CryptoJS from 'crypto-js'; import { v5 as uuidv5 } from "uuid"; +import sha256 from 'js-sha256'; const key = "THIS_IS_A_PLACEHOLDER_FOR_YOUR_OWN_SECURITY" // Replace this key with a string of your choice const UUID_NAMESPACE = "7bbc8dba-be5b-4ff2-b516-713692d5f601"; @@ -51,10 +52,10 @@ export function getISODate(date) { date = new Date(date) } return date.getUTCFullYear() + - "-" + - (date.getUTCMonth() + 1 + "").padStart(2, "0") + - "-" + - date.getUTCDate().toString().padStart(2, "0") + "-" + + (date.getUTCMonth() + 1 + "").padStart(2, "0") + + "-" + + date.getUTCDate().toString().padStart(2, "0") } export function capitalizeFirstLetter(string) { @@ -135,12 +136,12 @@ export function getBrowser() { // I didn't check all browsers, see : https://dev const UA = navigator.userAgent return ( (UA.includes("OPR/") || UA.includes("Opera/")) ? "Opera" : // verified on my computer - (UA.includes("Chromium/")) ? "Chromium" : // not verified - (UA.includes("SeaMonkey/")) ? "Seamonkey" : // I honestly hope people don't use this anymore - (UA.includes("Firefox/")) ? "Firefox" : // verified on my computer - (UA.includes("Edg/")) ? "Edge" : // verified on my computer - (UA.includes("Chrome/")) ? "Chrome" : // verified on my computer - "Safari" // not verified + (UA.includes("Chromium/")) ? "Chromium" : // not verified + (UA.includes("SeaMonkey/")) ? "Seamonkey" : // I honestly hope people don't use this anymore + (UA.includes("Firefox/")) ? "Firefox" : // verified on my computer + (UA.includes("Edg/")) ? "Edge" : // verified on my computer + (UA.includes("Chrome/")) ? "Chrome" : // verified on my computer + "Safari" // not verified ) } @@ -166,3 +167,11 @@ export function getOS() { return os; } + +export function textToHSL(str, initialS = 42, initialL = 73, variationS = 10, variationL = 10) { + const int = parseInt(sha256(str), 16); + const l = int % 10000; + const h = Math.round((int % (10 ** 8)) / (10 ** 4)); + const s = Math.round((int % (10 ** 12)) / (10 ** 8)); + return [360 * (h / 9999), initialS + variationS * (s / 9999), initialL + variationL * (l / 9999)]; // [{0-360}, {70-100}, {40-70}] +} \ No newline at end of file