Skip to content

Commit 327857b

Browse files
committed
Merge branch 'develop' of https://github.com/makeopensource/devU into #87-Attendance-page
2 parents 0372ccc + a13750e commit 327857b

File tree

2 files changed

+227
-0
lines changed

2 files changed

+227
-0
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
@import 'variables';
2+
3+
.viewPdfWrapper {
4+
display: flex;
5+
gap: 20px;
6+
}
7+
8+
.annotationOptions {
9+
position: sticky;
10+
height: fit-content;
11+
top: 40px;
12+
}
13+
14+
.highlightColors {
15+
min-width: 200px;
16+
display: flex;
17+
justify-content: flex-start;
18+
gap: 10px;
19+
margin-top: 20px;
20+
// background-color: #555;
21+
}
22+
23+
.colorBtn {
24+
width: min-content;
25+
padding: 5px 10px;
26+
font-weight: 700;
27+
color: $text-color;
28+
border-radius: 5px;
29+
}
30+
31+
.blue {
32+
border: 2px solid rgba(0, 225, 255, 0.75);
33+
background-color:rgba(0, 225, 255, 0.25);
34+
}
35+
36+
.red {
37+
border: 2px solid rgba(255, 60, 0, 0.75);
38+
background-color: rgba(255, 60, 0, 0.25);
39+
}
40+
41+
.green {
42+
border: 2px solid rgba(80, 255, 0, 0.75);
43+
background-color:rgba(80, 255, 0, 0.25);
44+
}
45+
46+
.yellow {
47+
border: 2px solid rgba(255, 247, 0, 0.75);
48+
background-color:rgba(255, 247, 0, 0.25);
49+
}
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import React, { useState, useRef } from 'react';
2+
import { Document, Page } from 'react-pdf';
3+
import "react-pdf/dist/esm/Page/TextLayer.css";
4+
import "react-pdf/dist/esm/Page/AnnotationLayer.css";
5+
import { useAppSelector } from 'redux/hooks'
6+
import styles from "./pdfViewer.scss";
7+
8+
interface PDFWithHighlightProps {
9+
file: Blob | File;
10+
}
11+
12+
interface Highlight {
13+
color: string,
14+
text: string,
15+
rect: {
16+
top: number;
17+
left: number;
18+
width: number;
19+
height: number;
20+
}
21+
}
22+
23+
const PDFViewer: React.FC<PDFWithHighlightProps> = ({ file }) => {
24+
const [highlights, setHighlights] = useState<Highlight[]>([]);
25+
const [numPages, setNumPages] = useState(0);
26+
const [annotate, setAnnotate] = useState(false);
27+
const [annotationColor, setAnnotationColor] = useState('rgba(0, 225, 255, 0.25)');
28+
const [annotationBorderColor, setAnnotationBorderColor] = useState('rgba(0, 225, 255, 0.75)');
29+
const containerRef = useRef<HTMLDivElement | null>(null);
30+
const role = useAppSelector((store) => store.roleMode)
31+
32+
const [startPoint, setStartPoint] = useState<{ x: number; y: number } | null>(null);
33+
const [currentRect, setCurrentRect] = useState<Highlight['rect'] | null>(null);
34+
35+
const handleMouseDown = (e: React.MouseEvent) => {
36+
if (!containerRef.current) return;
37+
const containerRect = containerRef.current.getBoundingClientRect();
38+
setStartPoint({
39+
x: e.clientX - containerRect.left,
40+
y: e.clientY - containerRect.top,
41+
});
42+
};
43+
44+
const handleMouseMove = (e: React.MouseEvent) => {
45+
if (!startPoint || !containerRef.current) return;
46+
const containerRect = containerRef.current.getBoundingClientRect();
47+
const x = e.clientX - containerRect.left;
48+
const y = e.clientY - containerRect.top;
49+
50+
const width = Math.abs(x - startPoint.x);
51+
const height = Math.abs(y - startPoint.y);
52+
53+
setCurrentRect({
54+
left: Math.min(x, startPoint.x),
55+
top: Math.min(y, startPoint.y),
56+
width,
57+
height,
58+
});
59+
};
60+
61+
const handleMouseUp = () => {
62+
if (currentRect) {
63+
const text = prompt("Enter annotation text:");
64+
setHighlights((prev) => [
65+
...prev,
66+
{
67+
color: annotationColor,
68+
rect: currentRect,
69+
text: text || "", // Add text to the highlight object
70+
},
71+
]);
72+
}
73+
setStartPoint(null);
74+
setCurrentRect(null);
75+
};
76+
77+
const onDocumentLoadSuccess = ({ numPages }: { numPages: number }) => {
78+
setNumPages(numPages)
79+
}
80+
81+
const toggleAnnotate = () => {
82+
setAnnotate(!annotate);
83+
}
84+
85+
const setColors = (e: React.MouseEvent<HTMLButtonElement>) => {
86+
const computedStyle = window.getComputedStyle(e.currentTarget);
87+
const color = computedStyle.backgroundColor;
88+
const borderColor = color.replace("0.25", "0.75");
89+
console.log(borderColor);
90+
91+
setAnnotationColor(color);
92+
93+
// color is in rgba. change the opacity to 0.75
94+
setAnnotationBorderColor(borderColor);
95+
}
96+
97+
return (
98+
<div className={styles.viewPdfWrapper}>
99+
<div
100+
ref={containerRef}
101+
style={{ position: 'relative' }}
102+
onMouseDown={annotate ? handleMouseDown : undefined}
103+
onMouseMove={annotate ? handleMouseMove : undefined}
104+
onMouseUp={annotate ? handleMouseUp : undefined}
105+
>
106+
{/* react-pdf document */}
107+
<Document
108+
file={file}
109+
onLoadSuccess={onDocumentLoadSuccess}
110+
>
111+
{[...Array(numPages)].map((_, index) => {
112+
const pageNumber = index + 1;
113+
return (
114+
<Page
115+
key={pageNumber}
116+
pageNumber={pageNumber}
117+
renderTextLayer={false}
118+
renderAnnotationLayer={false}
119+
scale={1.0}
120+
/>
121+
);
122+
})}
123+
</Document>
124+
{/* Render current rectangle while dragging */}
125+
{annotate && currentRect && (
126+
<div
127+
style={{
128+
position: 'absolute',
129+
left: `${currentRect.left}px`,
130+
top: `${currentRect.top}px`,
131+
width: `${currentRect.width}px`,
132+
height: `${currentRect.height}px`,
133+
backgroundColor: annotationColor,
134+
border: `2px dashed ${annotationBorderColor}`,
135+
pointerEvents: 'none',
136+
}}
137+
/>
138+
)}
139+
{/* Show all highlights */}
140+
{highlights.map((highlight, index) => (
141+
<div
142+
key={index}
143+
style={{
144+
position: 'absolute',
145+
left: `${highlight.rect.left}px`,
146+
top: `${highlight.rect.top}px`,
147+
width: `${highlight.rect.width}px`,
148+
height: `${highlight.rect.height}px`,
149+
backgroundColor: highlight.color,
150+
pointerEvents: 'none',
151+
textAlign: 'center',
152+
padding: '2px',
153+
}}
154+
><span style={{ color: '#000', margin: 'auto', fontWeight: '700' }}>{highlight.text}</span></div>
155+
))}
156+
</div>
157+
158+
{/* annotation options */}
159+
{role.isInstructor() && (<div className={styles.annotationOptions}>
160+
<button
161+
className="btnSecondary"
162+
onClick={toggleAnnotate}
163+
style={{ whiteSpace: 'nowrap' }}
164+
>
165+
{annotate ? 'Set View Mode' : 'Set Annotate Mode'}
166+
</button>
167+
{annotate && (<div className={styles.highlightColors}>
168+
<button className={`${styles.colorBtn} ${styles.blue}`} onClick={setColors}>Blue</button>
169+
<button className={`${styles.colorBtn} ${styles.green}`} onClick={setColors}>Green</button>
170+
<button className={`${styles.colorBtn} ${styles.red}`} onClick={setColors}>Red</button>
171+
<button className={`${styles.colorBtn} ${styles.yellow}`} onClick={setColors}>Yellow</button>
172+
</div>)}
173+
</div>)}
174+
</div>
175+
);
176+
};
177+
178+
export default PDFViewer;

0 commit comments

Comments
 (0)