From 27627f40ef3d710a2f81d86355b1bbb6b68c28c4 Mon Sep 17 00:00:00 2001 From: behrooz bozorg chami Date: Sat, 16 Aug 2025 12:24:51 +0200 Subject: [PATCH 1/5] an input which can user draw on it. --- .../alea-frontend/components/DrawingInput.tsx | 185 ++++++++++++++++++ 1 file changed, 185 insertions(+) create mode 100644 packages/alea-frontend/components/DrawingInput.tsx diff --git a/packages/alea-frontend/components/DrawingInput.tsx b/packages/alea-frontend/components/DrawingInput.tsx new file mode 100644 index 000000000..e5b1d0f4d --- /dev/null +++ b/packages/alea-frontend/components/DrawingInput.tsx @@ -0,0 +1,185 @@ +import React, { useRef, useEffect, useState } from 'react'; + +const DrawingComponent = ({ + initialDrawing = [], + children, + onExport, + width = '100%', + height = '400px', + id = '', +}: { + initialDrawing?: []; + children?; + onExport?; + width?: string; + height?: string; + id?: string; +}) => { + const canvasRef = useRef(null); + const ctxRef = useRef(null); + const [isDrawing, setIsDrawing] = useState(false); + const [lineWidth, setLineWidth] = useState(2); + const [lineColor, setLineColor] = useState('black'); + const [lineOpacity, setLineOpacity] = useState(1); + const [drawingOperations, setDrawingOperations] = useState( + JSON.parse(localStorage.getItem(`${id}-draw`) ?? '[]') + ); + // Initialization when the component + // mounts for the first time + useEffect(() => { + const canvas = canvasRef.current; + const ctx = canvas.getContext('2d'); + // ctx.lineCap = 'round'; + // ctx.lineJoin = 'round'; + ctx.globalAlpha = lineOpacity; + ctx.strokeStyle = lineColor; + ctx.lineWidth = lineWidth; + ctxRef.current = ctx; + }, [lineColor, lineOpacity, lineWidth]); + useEffect(() => { + drawingOperations.forEach((c) => { + ctxRef.current.globalAlpha = c.opacity; + ctxRef.current.strokeStyle = c.color; + ctxRef.current.lineWidth = c.width; + switch (c.status) { + case DrawingStatus.start: + ctxRef.current.beginPath(); + ctxRef.current.moveTo(c.x, c.y); + break; + case DrawingStatus.continue: + ctxRef.current.lineTo(c.x, c.y); + ctxRef.current.stroke(); + ctxRef.current.beginPath(); + ctxRef.current.moveTo(c.x, c.y); + break; + case DrawingStatus.end: + ctxRef.current.closePath(); + break; + default: + break; + } + }); + }, []); + // Function for starting the drawing + const startDrawing = (e: React.MouseEvent) => { + const { x, y } = getMousePos(e); + ctxRef.current.beginPath(); + + ctxRef.current.moveTo(x, y); + drawingOperations.push({ + status: DrawingStatus.start, + x, + y, + color: lineColor, + opacity: lineOpacity, + width: lineWidth, + }); + setIsDrawing(true); + }; + function getMousePos(evt: React.MouseEvent) { + const rect = canvasRef.current.getBoundingClientRect(), + scaleX = canvasRef.current.width / rect.width, + scaleY = canvasRef.current.height / rect.height; + return { + x: (evt.clientX - rect.left) * scaleX, + y: (evt.clientY - rect.top) * scaleY, + }; + } + // Function for ending the drawing + const endDrawing = (e: React.MouseEvent) => { + ctxRef.current.closePath(); + const { x, y } = getMousePos(e); + drawingOperations.push({ + status: DrawingStatus.end, + x, + y, + color: lineColor, + opacity: lineOpacity, + width: lineWidth, + }); + setIsDrawing(false); + }; + setTimeout(() => localStorage.setItem(`${id}-draw`, JSON.stringify(drawingOperations)), 300); + const draw = (e: React.MouseEvent) => { + if (!isDrawing) { + return; + } + const { x, y } = getMousePos(e); + ctxRef.current.lineTo(x, y); + + ctxRef.current.stroke(); + // for smoother drawing + ctxRef.current.beginPath(); + ctxRef.current.moveTo(x, y); + drawingOperations.push({ + status: DrawingStatus.continue, + x, + y, + color: lineColor, + opacity: lineOpacity, + width: lineWidth, + }); + }; + return ( +
+ {children} + + +
+ ); +}; + +export default DrawingComponent; +function Menu({ setLineColor, setLineWidth, setLineOpacity }) { + return ( +
+ + { + setLineColor(e.target.value); + }} + /> + + { + setLineWidth(e.target.value); + }} + /> + + { + setLineOpacity(+e.target.value / 100); + }} + /> +
+ ); +} +enum DrawingStatus { + start, + continue, + end, +} From 995171467fcf20eee38f907d1f2e4b89af337d02 Mon Sep 17 00:00:00 2001 From: behrooz bozorg chami Date: Sat, 16 Aug 2025 12:25:01 +0200 Subject: [PATCH 2/5] small demo of it --- .../pages/taking-note/[courseId].tsx | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 packages/alea-frontend/pages/taking-note/[courseId].tsx diff --git a/packages/alea-frontend/pages/taking-note/[courseId].tsx b/packages/alea-frontend/pages/taking-note/[courseId].tsx new file mode 100644 index 000000000..5e727ed97 --- /dev/null +++ b/packages/alea-frontend/pages/taking-note/[courseId].tsx @@ -0,0 +1,56 @@ +import { FTML, FTMLDocument, FTMLSetup, getFlamsServer } from '@kwarc/ftml-react'; +import { Box, CircularProgress } from '@mui/material'; +import { getCourseInfo } from '@stex-react/api'; +import { CourseInfo } from '@stex-react/utils'; +import { NextPage } from 'next'; +import { useRouter } from 'next/router'; +import DrawingComponent from '../../components/DrawingInput'; +import { useEffect, useState } from 'react'; + +const CourseTakingNote: NextPage = () => { + const router = useRouter(); + const courseId = router.query.courseId as string; + const [courses, setCourses] = useState<{ [id: string]: CourseInfo } | undefined>(undefined); + const [toc, setToc] = useState(undefined); + useEffect(() => { + getCourseInfo().then(setCourses); + }, []); + useEffect(() => { + const notes = courses?.[courseId]?.notes; + if (!notes) return; + setToc(undefined); + getFlamsServer() + .contentToc({ uri: notes }) + .then(([css, toc] = [[], []]) => { + setToc(toc); + }); + }, [router.isReady, courses, courseId]); + if (!router.isReady || !courses || !toc) { + return ; + } + const courseInfo = courses[courseId]; + const { notes } = courseInfo; + + return ( + <> + + { + if (kind.type === 'Slide') + return (ch) => ( + + + {ch} + + + ); + return; + }} + > + + + ); +}; +export default CourseTakingNote; From 787747f6f59081916c5eff05014ab8ce62d2cb38 Mon Sep 17 00:00:00 2001 From: Behrooz Date: Mon, 18 Aug 2025 18:38:05 +0200 Subject: [PATCH 3/5] change --- packages/alea-frontend/components/DrawingInput.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/alea-frontend/components/DrawingInput.tsx b/packages/alea-frontend/components/DrawingInput.tsx index e5b1d0f4d..df767a02e 100644 --- a/packages/alea-frontend/components/DrawingInput.tsx +++ b/packages/alea-frontend/components/DrawingInput.tsx @@ -120,6 +120,15 @@ const DrawingComponent = ({ width: lineWidth, }); }; + const startDrawingTouch = (e: React.TouchEvent) => { + console.log(e); + }; + const drawingTouch = (e: React.TouchEvent) => { + console.log(e); + }; + const endDrawingTouch = (e: React.TouchEvent) => { + console.log(e); + }; return (
{children} @@ -128,6 +137,9 @@ const DrawingComponent = ({ onMouseDown={startDrawing} onMouseUp={endDrawing} onMouseMove={draw} + onTouchStart={startDrawingTouch} + onTouchMove={drawingTouch} + onTouchEnd={endDrawingTouch} style={{ position: 'absolute', top: 0, From 206c6e1b2aace0e90a79098e0ad7ec91d9f0030f Mon Sep 17 00:00:00 2001 From: behrooz bozorg chami Date: Mon, 18 Aug 2025 18:40:44 +0200 Subject: [PATCH 4/5] add --- .../alea-frontend/components/DrawingInput.tsx | 33 ++++++++++++++----- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/packages/alea-frontend/components/DrawingInput.tsx b/packages/alea-frontend/components/DrawingInput.tsx index e5b1d0f4d..c9bcc2c03 100644 --- a/packages/alea-frontend/components/DrawingInput.tsx +++ b/packages/alea-frontend/components/DrawingInput.tsx @@ -1,5 +1,18 @@ import React, { useRef, useEffect, useState } from 'react'; +enum DrawingStatus { + start, + continue, + end, +} +type DrawingOperation = { + status: DrawingStatus; + x: number; + y: number; + color: string; + opacity: number; + width: number; +}; const DrawingComponent = ({ initialDrawing = [], children, @@ -21,7 +34,8 @@ const DrawingComponent = ({ const [lineWidth, setLineWidth] = useState(2); const [lineColor, setLineColor] = useState('black'); const [lineOpacity, setLineOpacity] = useState(1); - const [drawingOperations, setDrawingOperations] = useState( + + const [drawingOperations, setDrawingOperations] = useState( JSON.parse(localStorage.getItem(`${id}-draw`) ?? '[]') ); // Initialization when the component @@ -29,6 +43,7 @@ const DrawingComponent = ({ useEffect(() => { const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); + ctx.canvas.style.touchAction = 'none'; // ctx.lineCap = 'round'; // ctx.lineJoin = 'round'; ctx.globalAlpha = lineOpacity; @@ -100,6 +115,11 @@ const DrawingComponent = ({ setIsDrawing(false); }; setTimeout(() => localStorage.setItem(`${id}-draw`, JSON.stringify(drawingOperations)), 300); + const onResetClicked = () => { + setDrawingOperations([]); + const ctx = canvasRef.current.getContext('2d'); + ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height); + }; const draw = (e: React.MouseEvent) => { if (!isDrawing) { return; @@ -121,7 +141,7 @@ const DrawingComponent = ({ }); }; return ( -
+
{children} @@ -175,11 +196,7 @@ function Menu({ setLineColor, setLineWidth, setLineOpacity }) { setLineOpacity(+e.target.value / 100); }} /> +
); } -enum DrawingStatus { - start, - continue, - end, -} From 2770224b6bd9cbdb0b16d2f79804857f7fe646c5 Mon Sep 17 00:00:00 2001 From: behrooz bozorg chami Date: Wed, 20 Aug 2025 10:36:15 +0200 Subject: [PATCH 5/5] fix touch bug --- .../alea-frontend/components/DrawingInput.tsx | 41 ++++++++++++++----- .../alea-frontend/pages/taking-note/index.tsx | 7 ++++ 2 files changed, 38 insertions(+), 10 deletions(-) create mode 100644 packages/alea-frontend/pages/taking-note/index.tsx diff --git a/packages/alea-frontend/components/DrawingInput.tsx b/packages/alea-frontend/components/DrawingInput.tsx index 5cbba7ac0..3ba4f338f 100644 --- a/packages/alea-frontend/components/DrawingInput.tsx +++ b/packages/alea-frontend/components/DrawingInput.tsx @@ -76,7 +76,9 @@ const DrawingComponent = ({ }); }, []); // Function for starting the drawing - const startDrawing = (e: React.MouseEvent) => { + const startDrawing = ( + e: React.MouseEvent | React.TouchEvent + ) => { const { x, y } = getMousePos(e); ctxRef.current.beginPath(); @@ -91,17 +93,30 @@ const DrawingComponent = ({ }); setIsDrawing(true); }; - function getMousePos(evt: React.MouseEvent) { + function getMousePos( + evt: React.MouseEvent | React.TouchEvent + ): { x: number; y: number } { + let clientX: number, clientY: number; + if (!evt) return; + if ('clientX' in evt) { + clientX = evt.clientX; + clientY = evt.clientY; + } else { + clientX = evt.touches[0]?.clientX ?? 0; + clientY = evt.touches[0]?.clientY ?? 0; + } const rect = canvasRef.current.getBoundingClientRect(), scaleX = canvasRef.current.width / rect.width, scaleY = canvasRef.current.height / rect.height; return { - x: (evt.clientX - rect.left) * scaleX, - y: (evt.clientY - rect.top) * scaleY, + x: (clientX - rect.left) * scaleX, + y: (clientY - rect.top) * scaleY, }; } // Function for ending the drawing - const endDrawing = (e: React.MouseEvent) => { + const endDrawing = ( + e: React.MouseEvent | React.TouchEvent + ) => { ctxRef.current.closePath(); const { x, y } = getMousePos(e); drawingOperations.push({ @@ -120,10 +135,11 @@ const DrawingComponent = ({ const ctx = canvasRef.current.getContext('2d'); ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height); }; - const draw = (e: React.MouseEvent) => { + const draw = (e: React.MouseEvent | React.TouchEvent) => { if (!isDrawing) { return; } + e.preventDefault(); const { x, y } = getMousePos(e); ctxRef.current.lineTo(x, y); @@ -144,22 +160,27 @@ const DrawingComponent = ({ console.log(e); }; const drawingTouch = (e: React.TouchEvent) => { + e.preventDefault(); + console.log(e); }; const endDrawingTouch = (e: React.TouchEvent) => { console.log(e); }; return ( -
+
{children} { + return ; +}; +export default IndexPage;