diff --git a/packages/alea-frontend/components/DrawingInput.tsx b/packages/alea-frontend/components/DrawingInput.tsx new file mode 100644 index 000000000..3ba4f338f --- /dev/null +++ b/packages/alea-frontend/components/DrawingInput.tsx @@ -0,0 +1,235 @@ +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, + 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.canvas.style.touchAction = 'none'; + // 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 | React.TouchEvent + ) => { + 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 | 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: (clientX - rect.left) * scaleX, + y: (clientY - rect.top) * scaleY, + }; + } + // Function for ending the drawing + const endDrawing = ( + e: React.MouseEvent | React.TouchEvent + ) => { + 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 onResetClicked = () => { + setDrawingOperations([]); + const ctx = canvasRef.current.getContext('2d'); + ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height); + }; + const draw = (e: React.MouseEvent | React.TouchEvent) => { + if (!isDrawing) { + return; + } + e.preventDefault(); + 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, + }); + }; + const startDrawingTouch = (e: React.TouchEvent) => { + console.log(e); + }; + const drawingTouch = (e: React.TouchEvent) => { + e.preventDefault(); + + console.log(e); + }; + const endDrawingTouch = (e: React.TouchEvent) => { + console.log(e); + }; + return ( +
+ {children} + + +
+ ); +}; + +export default DrawingComponent; +function Menu({ setLineColor, setLineWidth, setLineOpacity, onReset }) { + return ( +
+ + { + setLineColor(e.target.value); + }} + /> + + { + setLineWidth(e.target.value); + }} + /> + + { + setLineOpacity(+e.target.value / 100); + }} + /> + +
+ ); +} 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; diff --git a/packages/alea-frontend/pages/taking-note/index.tsx b/packages/alea-frontend/pages/taking-note/index.tsx new file mode 100644 index 000000000..1bdfc81b8 --- /dev/null +++ b/packages/alea-frontend/pages/taking-note/index.tsx @@ -0,0 +1,7 @@ +import { NextPage } from 'next'; +import DrawingComponent from '../../components/DrawingInput'; + +const IndexPage: NextPage = () => { + return ; +}; +export default IndexPage;