Spaces:
Running
Running
Upload 11 files
Browse files- Dockerfile +11 -0
- app.py +51 -0
- requirements.txt +3 -0
- static/css/style.css +171 -0
- static/js/history.js +46 -0
- static/js/main.js +96 -0
- static/js/pages.js +172 -0
- static/js/script.js +264 -0
- static/js/tools.js +44 -0
- static/js/utils.js +21 -0
- templates/index.html +71 -0
Dockerfile
ADDED
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.9
|
2 |
+
|
3 |
+
WORKDIR /code
|
4 |
+
|
5 |
+
COPY ./requirements.txt /code/requirements.txt
|
6 |
+
|
7 |
+
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
8 |
+
|
9 |
+
COPY . .
|
10 |
+
|
11 |
+
CMD ["gunicorn", "-b", "0.0.0.0:7860", "-w", "20", "main:app"]
|
app.py
ADDED
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import base64
|
2 |
+
from flask import Flask, request, jsonify, render_template
|
3 |
+
from io import BytesIO
|
4 |
+
from maincode import *
|
5 |
+
import re
|
6 |
+
|
7 |
+
app = Flask(__name__)
|
8 |
+
|
9 |
+
def calculate(expression):
|
10 |
+
try:
|
11 |
+
result = []
|
12 |
+
for eq in expression:
|
13 |
+
result.append(eval(eq.replace("=", "")))
|
14 |
+
print(result)
|
15 |
+
return result
|
16 |
+
except Exception as e:
|
17 |
+
print(f"Error : {e}")
|
18 |
+
return []
|
19 |
+
|
20 |
+
def advanced_calculator(text):
|
21 |
+
pattern = r'(\d+(?:\s*[+\-*/]\s*\d+)*)\s?='
|
22 |
+
matches = re.findall(pattern, text)
|
23 |
+
filtered_expressions = []
|
24 |
+
for match in matches:
|
25 |
+
equation = match + '='
|
26 |
+
remaining_text = text[text.find(equation)+len(equation):].strip()
|
27 |
+
if remaining_text and remaining_text[0].isdigit():
|
28 |
+
continue
|
29 |
+
filtered_expressions.append(equation.replace(" ", "").strip())
|
30 |
+
return calculate(filtered_expressions)
|
31 |
+
|
32 |
+
@app.route('/')
|
33 |
+
def home():
|
34 |
+
return render_template('index.html')
|
35 |
+
|
36 |
+
@app.route('/whitebai', methods=['POST'])
|
37 |
+
def whitebai():
|
38 |
+
data = request.get_json()
|
39 |
+
image_data = data.get('image')
|
40 |
+
if not image_data:
|
41 |
+
return jsonify({'message': 'No image data received'}), 400
|
42 |
+
image_data = image_data.split(',')[1]
|
43 |
+
image_bytes = base64.b64decode(image_data)
|
44 |
+
image_file = BytesIO(image_bytes)
|
45 |
+
elt = ExtractTextFromImage(image_file)
|
46 |
+
elt.process_file()
|
47 |
+
dat = elt.get_text()
|
48 |
+
return jsonify({'message': advanced_calculator(dat)})
|
49 |
+
|
50 |
+
if __name__ == '__main__':
|
51 |
+
app.run(debug=True)
|
requirements.txt
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
requests
|
2 |
+
flask
|
3 |
+
gunicorn
|
static/css/style.css
ADDED
@@ -0,0 +1,171 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
body {
|
2 |
+
margin: 0;
|
3 |
+
padding: 0;
|
4 |
+
overflow: hidden;
|
5 |
+
}
|
6 |
+
|
7 |
+
.whiteboard-container {
|
8 |
+
position: fixed;
|
9 |
+
top: 0;
|
10 |
+
left: 0;
|
11 |
+
width: 100vw;
|
12 |
+
height: 100vh;
|
13 |
+
}
|
14 |
+
|
15 |
+
#whiteboard {
|
16 |
+
width: 100%;
|
17 |
+
height: 100%;
|
18 |
+
cursor: crosshair;
|
19 |
+
touch-action: none;
|
20 |
+
}
|
21 |
+
|
22 |
+
.brush-preview {
|
23 |
+
position: fixed;
|
24 |
+
pointer-events: none;
|
25 |
+
border: 1px solid #000;
|
26 |
+
border-radius: 50%;
|
27 |
+
display: none;
|
28 |
+
}
|
29 |
+
|
30 |
+
.bottom-toolbar {
|
31 |
+
position: fixed;
|
32 |
+
bottom: 20px;
|
33 |
+
left: 50%;
|
34 |
+
transform: translateX(-50%);
|
35 |
+
background: white;
|
36 |
+
border-radius: 8px;
|
37 |
+
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
38 |
+
z-index: 1000;
|
39 |
+
transition: transform 0.3s ease;
|
40 |
+
}
|
41 |
+
|
42 |
+
.bottom-toolbar.hidden .toolbar-content {
|
43 |
+
display: none;
|
44 |
+
}
|
45 |
+
|
46 |
+
.bottom-toolbar.hidden .toggle-btn .material-icons {
|
47 |
+
transform: rotate(180deg);
|
48 |
+
}
|
49 |
+
|
50 |
+
.toggle-btn {
|
51 |
+
position: absolute;
|
52 |
+
left: 50%;
|
53 |
+
transform: translateX(-50%) translateY(-100%);
|
54 |
+
border-radius: 8px 8px 0 0;
|
55 |
+
padding: 4px 16px;
|
56 |
+
background: white;
|
57 |
+
border: none;
|
58 |
+
cursor: pointer;
|
59 |
+
box-shadow: 0 -2px 10px rgba(0,0,0,0.1);
|
60 |
+
}
|
61 |
+
|
62 |
+
.toggle-btn .material-icons {
|
63 |
+
transition: transform 0.3s ease;
|
64 |
+
}
|
65 |
+
|
66 |
+
.toolbar-content {
|
67 |
+
display: flex;
|
68 |
+
gap: 20px;
|
69 |
+
padding: 10px;
|
70 |
+
}
|
71 |
+
|
72 |
+
.tool-group {
|
73 |
+
display: flex;
|
74 |
+
align-items: center;
|
75 |
+
gap: 10px;
|
76 |
+
padding: 0 10px;
|
77 |
+
border-right: 1px solid #eee;
|
78 |
+
}
|
79 |
+
|
80 |
+
.tool-group:last-child {
|
81 |
+
border-right: none;
|
82 |
+
}
|
83 |
+
|
84 |
+
.tool-btn {
|
85 |
+
padding: 8px;
|
86 |
+
border: none;
|
87 |
+
border-radius: 4px;
|
88 |
+
cursor: pointer;
|
89 |
+
background: #f5f5f5;
|
90 |
+
display: flex;
|
91 |
+
align-items: center;
|
92 |
+
justify-content: center;
|
93 |
+
}
|
94 |
+
|
95 |
+
.tool-btn.active {
|
96 |
+
background: #e0e0e0;
|
97 |
+
color: #2196F3;
|
98 |
+
}
|
99 |
+
|
100 |
+
.tool-btn .material-icons {
|
101 |
+
font-size: 20px;
|
102 |
+
}
|
103 |
+
|
104 |
+
#colorPicker {
|
105 |
+
width: 40px;
|
106 |
+
height: 40px;
|
107 |
+
padding: 0;
|
108 |
+
border: none;
|
109 |
+
border-radius: 4px;
|
110 |
+
cursor: pointer;
|
111 |
+
}
|
112 |
+
|
113 |
+
#brushSize {
|
114 |
+
width: 100px;
|
115 |
+
}
|
116 |
+
|
117 |
+
#brushSizeLabel {
|
118 |
+
min-width: 40px;
|
119 |
+
text-align: center;
|
120 |
+
}
|
121 |
+
|
122 |
+
.page-controls {
|
123 |
+
display: flex;
|
124 |
+
align-items: center;
|
125 |
+
gap: 10px;
|
126 |
+
}
|
127 |
+
|
128 |
+
#pageInfo {
|
129 |
+
min-width: 80px;
|
130 |
+
text-align: center;
|
131 |
+
}
|
132 |
+
|
133 |
+
.action-buttons {
|
134 |
+
display: flex;
|
135 |
+
gap: 10px;
|
136 |
+
}
|
137 |
+
|
138 |
+
.action-btn {
|
139 |
+
display: flex;
|
140 |
+
align-items: center;
|
141 |
+
gap: 5px;
|
142 |
+
padding: 8px 16px;
|
143 |
+
border: none;
|
144 |
+
border-radius: 4px;
|
145 |
+
cursor: pointer;
|
146 |
+
font-size: 14px;
|
147 |
+
background: #f5f5f5;
|
148 |
+
color: #333;
|
149 |
+
}
|
150 |
+
|
151 |
+
.action-btn:hover {
|
152 |
+
background: #e0e0e0;
|
153 |
+
}
|
154 |
+
|
155 |
+
.ai-btn {
|
156 |
+
background: #2196F3;
|
157 |
+
color: white;
|
158 |
+
}
|
159 |
+
|
160 |
+
.ai-btn:hover {
|
161 |
+
background: #1976D2;
|
162 |
+
}
|
163 |
+
|
164 |
+
.submit-btn {
|
165 |
+
background: #4CAF50;
|
166 |
+
color: white;
|
167 |
+
}
|
168 |
+
|
169 |
+
.submit-btn:hover {
|
170 |
+
background: #388E3C;
|
171 |
+
}
|
static/js/history.js
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
class History {
|
2 |
+
constructor(maxStates = 50) {
|
3 |
+
this.states = [];
|
4 |
+
this.currentIndex = -1;
|
5 |
+
this.maxStates = maxStates;
|
6 |
+
}
|
7 |
+
|
8 |
+
push(state) {
|
9 |
+
// Remove any states after current index
|
10 |
+
this.states = this.states.slice(0, this.currentIndex + 1);
|
11 |
+
|
12 |
+
// Add new state
|
13 |
+
this.states.push(state);
|
14 |
+
|
15 |
+
// Remove oldest state if exceeding maxStates
|
16 |
+
if (this.states.length > this.maxStates) {
|
17 |
+
this.states.shift();
|
18 |
+
}
|
19 |
+
|
20 |
+
this.currentIndex = this.states.length - 1;
|
21 |
+
}
|
22 |
+
|
23 |
+
undo() {
|
24 |
+
if (this.currentIndex > 0) {
|
25 |
+
this.currentIndex--;
|
26 |
+
return this.states[this.currentIndex];
|
27 |
+
}
|
28 |
+
return null;
|
29 |
+
}
|
30 |
+
|
31 |
+
redo() {
|
32 |
+
if (this.currentIndex < this.states.length - 1) {
|
33 |
+
this.currentIndex++;
|
34 |
+
return this.states[this.currentIndex];
|
35 |
+
}
|
36 |
+
return null;
|
37 |
+
}
|
38 |
+
|
39 |
+
canUndo() {
|
40 |
+
return this.currentIndex > 0;
|
41 |
+
}
|
42 |
+
|
43 |
+
canRedo() {
|
44 |
+
return this.currentIndex < this.states.length - 1;
|
45 |
+
}
|
46 |
+
}
|
static/js/main.js
ADDED
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
class Whiteboard {
|
2 |
+
constructor() {
|
3 |
+
this.canvas = document.getElementById('whiteboard');
|
4 |
+
this.ctx = this.canvas.getContext('2d');
|
5 |
+
this.isDrawing = false;
|
6 |
+
this.setupCanvas();
|
7 |
+
this.setupEventListeners();
|
8 |
+
}
|
9 |
+
|
10 |
+
setupCanvas() {
|
11 |
+
const resize = () => {
|
12 |
+
this.canvas.width = window.innerWidth;
|
13 |
+
this.canvas.height = window.innerHeight;
|
14 |
+
|
15 |
+
this.ctx.strokeStyle = '#000';
|
16 |
+
this.ctx.lineWidth = 2;
|
17 |
+
this.ctx.lineCap = 'round';
|
18 |
+
this.ctx.lineJoin = 'round';
|
19 |
+
};
|
20 |
+
|
21 |
+
window.addEventListener('resize', resize);
|
22 |
+
resize();
|
23 |
+
}
|
24 |
+
|
25 |
+
setupEventListeners() {
|
26 |
+
this.canvas.addEventListener('mousedown', this.startDrawing.bind(this));
|
27 |
+
this.canvas.addEventListener('mousemove', this.draw.bind(this));
|
28 |
+
this.canvas.addEventListener('mouseup', this.stopDrawing.bind(this));
|
29 |
+
this.canvas.addEventListener('mouseout', this.stopDrawing.bind(this));
|
30 |
+
|
31 |
+
this.canvas.addEventListener('touchstart', (e) => {
|
32 |
+
e.preventDefault();
|
33 |
+
const touch = e.touches[0];
|
34 |
+
const mouseEvent = new MouseEvent('mousedown', {
|
35 |
+
clientX: touch.clientX,
|
36 |
+
clientY: touch.clientY
|
37 |
+
});
|
38 |
+
this.canvas.dispatchEvent(mouseEvent);
|
39 |
+
});
|
40 |
+
|
41 |
+
this.canvas.addEventListener('touchmove', (e) => {
|
42 |
+
e.preventDefault();
|
43 |
+
const touch = e.touches[0];
|
44 |
+
const mouseEvent = new MouseEvent('mousemove', {
|
45 |
+
clientX: touch.clientX,
|
46 |
+
clientY: touch.clientY
|
47 |
+
});
|
48 |
+
this.canvas.dispatchEvent(mouseEvent);
|
49 |
+
});
|
50 |
+
|
51 |
+
this.canvas.addEventListener('touchend', (e) => {
|
52 |
+
e.preventDefault();
|
53 |
+
const mouseEvent = new MouseEvent('mouseup', {});
|
54 |
+
this.canvas.dispatchEvent(mouseEvent);
|
55 |
+
});
|
56 |
+
|
57 |
+
document.getElementById('processBtn').addEventListener('click', this.processDrawing.bind(this));
|
58 |
+
document.getElementById('clearBtn').addEventListener('click', this.clearCanvas.bind(this));
|
59 |
+
}
|
60 |
+
|
61 |
+
startDrawing(e) {
|
62 |
+
this.isDrawing = true;
|
63 |
+
const rect = this.canvas.getBoundingClientRect();
|
64 |
+
const x = e.clientX - rect.left;
|
65 |
+
const y = e.clientY - rect.top;
|
66 |
+
this.ctx.beginPath();
|
67 |
+
this.ctx.moveTo(x, y);
|
68 |
+
}
|
69 |
+
|
70 |
+
draw(e) {
|
71 |
+
if (!this.isDrawing) return;
|
72 |
+
const rect = this.canvas.getBoundingClientRect();
|
73 |
+
const x = e.clientX - rect.left;
|
74 |
+
const y = e.clientY - rect.top;
|
75 |
+
this.ctx.lineTo(x, y);
|
76 |
+
this.ctx.stroke();
|
77 |
+
}
|
78 |
+
|
79 |
+
stopDrawing() {
|
80 |
+
this.isDrawing = false;
|
81 |
+
}
|
82 |
+
|
83 |
+
clearCanvas() {
|
84 |
+
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
85 |
+
}
|
86 |
+
|
87 |
+
processDrawing() {
|
88 |
+
const imageData = this.canvas.toDataURL('image/png');
|
89 |
+
console.log('Processing drawing...');
|
90 |
+
console.log(imageData.substring(0, 100) + '...');
|
91 |
+
}
|
92 |
+
}
|
93 |
+
|
94 |
+
document.addEventListener('DOMContentLoaded', () => {
|
95 |
+
new Whiteboard();
|
96 |
+
});
|
static/js/pages.js
ADDED
@@ -0,0 +1,172 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
class Pages {
|
2 |
+
constructor() {
|
3 |
+
this.pages = [{}]; // Initially one empty page
|
4 |
+
this.currentPage = 0;
|
5 |
+
this.drawing = false; // Whether the user is currently drawing
|
6 |
+
this.lastX = 0;
|
7 |
+
this.lastY = 0;
|
8 |
+
this.ctx = null; // Canvas context
|
9 |
+
this.setupControls();
|
10 |
+
this.setupCanvas();
|
11 |
+
this.setupColorPicker();
|
12 |
+
this.setupBrushSize();
|
13 |
+
}
|
14 |
+
|
15 |
+
getAllPages() {
|
16 |
+
return this.pages; // Simply return all pages in the array
|
17 |
+
}
|
18 |
+
|
19 |
+
setPageContent(states) {
|
20 |
+
const currentPageData = this.pages[this.currentPage];
|
21 |
+
if (states.length > 0) {
|
22 |
+
currentPageData.drawing = states[states.length - 1].imageData; // Store only the latest state
|
23 |
+
}
|
24 |
+
}
|
25 |
+
|
26 |
+
setupControls() {
|
27 |
+
const prevBtn = document.getElementById('prevBtn');
|
28 |
+
const nextBtn = document.getElementById('nextBtn');
|
29 |
+
const pageInfo = document.getElementById('pageInfo');
|
30 |
+
|
31 |
+
prevBtn.addEventListener('click', () => this.previousPage());
|
32 |
+
nextBtn.addEventListener('click', () => this.nextPage());
|
33 |
+
|
34 |
+
this.updatePageInfo();
|
35 |
+
}
|
36 |
+
|
37 |
+
setupCanvas() {
|
38 |
+
const canvas = document.getElementById('whiteboard');
|
39 |
+
this.ctx = canvas.getContext('2d');
|
40 |
+
canvas.width = window.innerWidth - 40;
|
41 |
+
canvas.height = window.innerHeight - 100;
|
42 |
+
this.clearCanvas();
|
43 |
+
|
44 |
+
// Event listeners for drawing
|
45 |
+
canvas.addEventListener('mousedown', (e) => this.startDrawing(e));
|
46 |
+
canvas.addEventListener('mousemove', (e) => this.draw(e));
|
47 |
+
canvas.addEventListener('mouseup', () => this.stopDrawing());
|
48 |
+
canvas.addEventListener('mouseout', () => this.stopDrawing());
|
49 |
+
}
|
50 |
+
|
51 |
+
getPageContent() {
|
52 |
+
return this.pages[this.currentPage].drawing; // Return the latest drawing state (imageData)
|
53 |
+
}
|
54 |
+
|
55 |
+
startDrawing(e) {
|
56 |
+
this.drawing = true;
|
57 |
+
const mousePos = getMousePos(this.ctx.canvas, e);
|
58 |
+
this.lastX = mousePos.x;
|
59 |
+
this.lastY = mousePos.y;
|
60 |
+
}
|
61 |
+
|
62 |
+
draw(e) {
|
63 |
+
if (!this.drawing) return;
|
64 |
+
const mousePos = getMousePos(this.ctx.canvas, e);
|
65 |
+
this.ctx.beginPath();
|
66 |
+
this.ctx.moveTo(this.lastX, this.lastY);
|
67 |
+
this.ctx.lineTo(mousePos.x, mousePos.y);
|
68 |
+
this.ctx.strokeStyle = this.pages[this.currentPage].color || '#000000'; // Default black if no color set
|
69 |
+
this.ctx.lineWidth = this.pages[this.currentPage].brushSize || 2; // Default brush size
|
70 |
+
this.ctx.stroke();
|
71 |
+
this.lastX = mousePos.x;
|
72 |
+
this.lastY = mousePos.y;
|
73 |
+
}
|
74 |
+
|
75 |
+
stopDrawing() {
|
76 |
+
this.drawing = false;
|
77 |
+
}
|
78 |
+
|
79 |
+
setupColorPicker() {
|
80 |
+
const colorPicker = document.getElementById('colorPicker');
|
81 |
+
colorPicker.addEventListener('input', (event) => {
|
82 |
+
this.pages[this.currentPage].color = event.target.value; // Store color for current page
|
83 |
+
});
|
84 |
+
}
|
85 |
+
|
86 |
+
setupBrushSize() {
|
87 |
+
const brushSizeInput = document.getElementById('brushSize');
|
88 |
+
brushSizeInput.addEventListener('input', (event) => {
|
89 |
+
this.pages[this.currentPage].brushSize = event.target.value; // Store brush size for current page
|
90 |
+
});
|
91 |
+
}
|
92 |
+
|
93 |
+
getCurrentPage() {
|
94 |
+
return this.currentPage;
|
95 |
+
}
|
96 |
+
|
97 |
+
getPageCount() {
|
98 |
+
return this.pages.length;
|
99 |
+
}
|
100 |
+
|
101 |
+
addPage() {
|
102 |
+
this.pages.push({}); // Add a new empty page
|
103 |
+
this.currentPage = this.pages.length - 1;
|
104 |
+
this.clearCanvas(); // Clear the canvas for the new page
|
105 |
+
this.updatePageInfo();
|
106 |
+
}
|
107 |
+
|
108 |
+
previousPage() {
|
109 |
+
if (this.currentPage > 0) {
|
110 |
+
this.pages[this.currentPage].drawing = this.ctx.getImageData(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); // Save current page's content
|
111 |
+
|
112 |
+
this.currentPage--;
|
113 |
+
this.loadPageData(); // Load the content of the previous page
|
114 |
+
this.updatePageInfo();
|
115 |
+
}
|
116 |
+
}
|
117 |
+
|
118 |
+
nextPage() {
|
119 |
+
if (this.pages[this.currentPage]) {
|
120 |
+
// Save content of current page to its storage
|
121 |
+
this.pages[this.currentPage].drawing = this.ctx.getImageData(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
|
122 |
+
}
|
123 |
+
|
124 |
+
// Add new page and clear canvas
|
125 |
+
if (this.pages[this.currentPage].drawing !== null) {
|
126 |
+
// Add new page only if current page is not empty
|
127 |
+
this.pages.push({ drawing: null });
|
128 |
+
this.currentPage++;
|
129 |
+
}
|
130 |
+
|
131 |
+
this.clearCanvas(); // Clear the canvas for the new page
|
132 |
+
this.loadPageData(); // Load the newly added page's data
|
133 |
+
this.updatePageInfo();
|
134 |
+
}
|
135 |
+
|
136 |
+
updatePageInfo() {
|
137 |
+
const pageInfo = document.getElementById('pageInfo');
|
138 |
+
pageInfo.textContent = `Page ${this.currentPage + 1}`;
|
139 |
+
}
|
140 |
+
|
141 |
+
clearCanvas() {
|
142 |
+
this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
|
143 |
+
}
|
144 |
+
|
145 |
+
loadPageData() {
|
146 |
+
const pageData = this.pages[this.currentPage];
|
147 |
+
|
148 |
+
// If there is saved content for this page, restore it
|
149 |
+
if (pageData.drawing) {
|
150 |
+
this.ctx.putImageData(pageData.drawing, 0, 0);
|
151 |
+
} else {
|
152 |
+
this.clearCanvas(); // If no content, clear canvas (empty page)
|
153 |
+
}
|
154 |
+
|
155 |
+
// You can add any additional settings here (color picker, brush size, etc.)
|
156 |
+
if (pageData.color) {
|
157 |
+
document.getElementById('colorPicker').value = pageData.color;
|
158 |
+
}
|
159 |
+
if (pageData.brushSize) {
|
160 |
+
document.getElementById('brushSize').value = pageData.brushSize;
|
161 |
+
}
|
162 |
+
}
|
163 |
+
}
|
164 |
+
|
165 |
+
function getMousePos(canvas, evt) {
|
166 |
+
const rect = canvas.getBoundingClientRect();
|
167 |
+
return {
|
168 |
+
x: evt.clientX - rect.left,
|
169 |
+
y: evt.clientY - rect.top,
|
170 |
+
};
|
171 |
+
|
172 |
+
}
|
static/js/script.js
ADDED
@@ -0,0 +1,264 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
class Whiteboard {
|
2 |
+
constructor() {
|
3 |
+
this.canvas = document.getElementById('whiteboard');
|
4 |
+
this.ctx = this.canvas.getContext('2d');
|
5 |
+
this.isDrawing = false;
|
6 |
+
|
7 |
+
this.history = new History();
|
8 |
+
this.tools = new DrawingTools(this.canvas, this.ctx);
|
9 |
+
this.pages = new Pages();
|
10 |
+
|
11 |
+
this.setupCanvas();
|
12 |
+
this.setupEventListeners();
|
13 |
+
this.setupBrushPreview();
|
14 |
+
this.setupToolbarToggle();
|
15 |
+
}
|
16 |
+
|
17 |
+
setupCanvas() {
|
18 |
+
const resize = () => {
|
19 |
+
this.canvas.width = window.innerWidth;
|
20 |
+
this.canvas.height = window.innerHeight;
|
21 |
+
this.setupDrawingStyle();
|
22 |
+
|
23 |
+
// Restore the current page
|
24 |
+
const pageContent = this.pages.getPageContent();
|
25 |
+
if (pageContent) {
|
26 |
+
this.ctx.putImageData(pageContent, 0, 0); // Use the drawing content from the current page
|
27 |
+
}
|
28 |
+
};
|
29 |
+
|
30 |
+
window.addEventListener('resize', resize);
|
31 |
+
resize();
|
32 |
+
}
|
33 |
+
|
34 |
+
setupToolbarToggle() {
|
35 |
+
const toolbar = document.querySelector('.bottom-toolbar');
|
36 |
+
const toggleBtn = document.getElementById('toggleToolbar');
|
37 |
+
|
38 |
+
toggleBtn.addEventListener('click', () => {
|
39 |
+
toolbar.classList.toggle('hidden');
|
40 |
+
});
|
41 |
+
}
|
42 |
+
|
43 |
+
setupBrushPreview() {
|
44 |
+
this.brushPreview = document.getElementById('brushPreview');
|
45 |
+
|
46 |
+
this.canvas.addEventListener('mousemove', (e) => {
|
47 |
+
const { x, y } = getMousePos(this.canvas, e);
|
48 |
+
this.updateBrushPreview(x, y);
|
49 |
+
});
|
50 |
+
|
51 |
+
this.canvas.addEventListener('mouseenter', () => {
|
52 |
+
this.brushPreview.style.display = 'block';
|
53 |
+
});
|
54 |
+
|
55 |
+
this.canvas.addEventListener('mouseleave', () => {
|
56 |
+
this.brushPreview.style.display = 'none';
|
57 |
+
});
|
58 |
+
}
|
59 |
+
|
60 |
+
updateBrushPreview(x, y) {
|
61 |
+
const size = this.tools.brushSize;
|
62 |
+
this.brushPreview.style.width = size + 'px';
|
63 |
+
this.brushPreview.style.height = size + 'px';
|
64 |
+
this.brushPreview.style.left = (x - size/2) + 'px';
|
65 |
+
this.brushPreview.style.top = (y - size/2) + 'px';
|
66 |
+
this.brushPreview.style.borderColor = this.tools.isEraser ? '#000' : this.tools.color;
|
67 |
+
}
|
68 |
+
|
69 |
+
setupDrawingStyle() {
|
70 |
+
this.ctx.lineCap = 'round';
|
71 |
+
this.ctx.lineJoin = 'round';
|
72 |
+
this.ctx.font = '24px Arial';
|
73 |
+
this.tools.applyToolSettings();
|
74 |
+
}
|
75 |
+
|
76 |
+
setupEventListeners() {
|
77 |
+
// Mouse events
|
78 |
+
this.canvas.addEventListener('mousedown', this.startDrawing.bind(this));
|
79 |
+
this.canvas.addEventListener('mousemove', this.draw.bind(this));
|
80 |
+
this.canvas.addEventListener('mouseup', this.stopDrawing.bind(this));
|
81 |
+
this.canvas.addEventListener('mouseout', this.stopDrawing.bind(this));
|
82 |
+
|
83 |
+
// Touch events
|
84 |
+
this.canvas.addEventListener('touchstart', this.handleTouch.bind(this));
|
85 |
+
this.canvas.addEventListener('touchmove', this.handleTouch.bind(this));
|
86 |
+
this.canvas.addEventListener('touchend', this.handleTouchEnd.bind(this));
|
87 |
+
|
88 |
+
// Button events
|
89 |
+
document.getElementById('aiBtn').addEventListener('click', this.processDrawing.bind(this));
|
90 |
+
document.getElementById('submitBtn').addEventListener('click', this.generatePDF.bind(this));
|
91 |
+
document.getElementById('clearBtn').addEventListener('click', this.clearCanvas.bind(this));
|
92 |
+
document.getElementById('undoBtn').addEventListener('click', this.undo.bind(this));
|
93 |
+
document.getElementById('redoBtn').addEventListener('click', this.redo.bind(this));
|
94 |
+
|
95 |
+
// Keyboard shortcuts
|
96 |
+
document.addEventListener('keydown', (e) => {
|
97 |
+
if (e.ctrlKey || e.metaKey) {
|
98 |
+
if (e.key === 'z') {
|
99 |
+
e.preventDefault();
|
100 |
+
this.undo();
|
101 |
+
} else if (e.key === 'y') {
|
102 |
+
e.preventDefault();
|
103 |
+
this.redo();
|
104 |
+
}
|
105 |
+
}
|
106 |
+
});
|
107 |
+
}
|
108 |
+
|
109 |
+
handleTouch(e) {
|
110 |
+
e.preventDefault();
|
111 |
+
const touch = e.touches[0];
|
112 |
+
const eventType = e.type === 'touchstart' ? 'mousedown' : 'mousemove';
|
113 |
+
const mouseEvent = new MouseEvent(eventType, {
|
114 |
+
clientX: touch.clientX,
|
115 |
+
clientY: touch.clientY
|
116 |
+
});
|
117 |
+
this.canvas.dispatchEvent(mouseEvent);
|
118 |
+
}
|
119 |
+
|
120 |
+
handleTouchEnd(e) {
|
121 |
+
e.preventDefault();
|
122 |
+
const mouseEvent = new MouseEvent('mouseup', {});
|
123 |
+
this.canvas.dispatchEvent(mouseEvent);
|
124 |
+
}
|
125 |
+
|
126 |
+
startDrawing(e) {
|
127 |
+
this.isDrawing = true;
|
128 |
+
const { x, y } = getMousePos(this.canvas, e);
|
129 |
+
this.tools.applyToolSettings();
|
130 |
+
this.ctx.beginPath();
|
131 |
+
this.ctx.moveTo(x, y);
|
132 |
+
}
|
133 |
+
|
134 |
+
draw(e) {
|
135 |
+
if (!this.isDrawing) return;
|
136 |
+
const { x, y } = getMousePos(this.canvas, e);
|
137 |
+
this.ctx.lineTo(x, y);
|
138 |
+
this.ctx.stroke();
|
139 |
+
}
|
140 |
+
|
141 |
+
stopDrawing() {
|
142 |
+
if (this.isDrawing) {
|
143 |
+
this.isDrawing = false;
|
144 |
+
this.saveState();
|
145 |
+
}
|
146 |
+
}
|
147 |
+
|
148 |
+
saveState() {
|
149 |
+
const imageData = this.ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
|
150 |
+
this.history.push(new DrawingState(imageData));
|
151 |
+
this.pages.setPageContent(this.history.states);
|
152 |
+
}
|
153 |
+
|
154 |
+
undo() {
|
155 |
+
const state = this.history.undo();
|
156 |
+
if (state) {
|
157 |
+
this.ctx.putImageData(state.imageData, 0, 0);
|
158 |
+
this.pages.setPageContent(this.history.states);
|
159 |
+
}
|
160 |
+
}
|
161 |
+
|
162 |
+
redo() {
|
163 |
+
const state = this.history.redo();
|
164 |
+
if (state) {
|
165 |
+
this.ctx.putImageData(state.imageData, 0, 0);
|
166 |
+
this.pages.setPageContent(this.history.states);
|
167 |
+
}
|
168 |
+
}
|
169 |
+
|
170 |
+
clearCanvas() {
|
171 |
+
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
172 |
+
this.saveState();
|
173 |
+
}
|
174 |
+
|
175 |
+
processDrawing() {
|
176 |
+
const tempCanvas = document.createElement('canvas');
|
177 |
+
const tempContext = tempCanvas.getContext('2d');
|
178 |
+
tempCanvas.width = this.canvas.width;
|
179 |
+
tempCanvas.height = this.canvas.height;
|
180 |
+
tempContext.fillStyle = 'white';
|
181 |
+
tempContext.fillRect(0, 0, tempCanvas.width, tempCanvas.height);
|
182 |
+
tempContext.drawImage(this.canvas, 0, 0);
|
183 |
+
const imageData = tempCanvas.toDataURL('image/png');
|
184 |
+
console.log('Processing drawing with AI...');
|
185 |
+
console.log(imageData.substring(0, 100) + '...');
|
186 |
+
fetch('/whitebai', {
|
187 |
+
method: 'POST',
|
188 |
+
headers: {
|
189 |
+
'Content-Type': 'application/json',
|
190 |
+
},
|
191 |
+
body: JSON.stringify({
|
192 |
+
image: imageData
|
193 |
+
})
|
194 |
+
})
|
195 |
+
.then(response => response.json())
|
196 |
+
.then(data => {
|
197 |
+
document.getElementById('result').innerText = data.message;
|
198 |
+
})
|
199 |
+
.catch(error => {
|
200 |
+
console.error('Error:', error);
|
201 |
+
});
|
202 |
+
}
|
203 |
+
|
204 |
+
async generatePDF() {
|
205 |
+
const pages = this.pages.getAllPages();
|
206 |
+
|
207 |
+
for (let i = 0; i < pages.length; i++) {
|
208 |
+
const pageContent = pages[i];
|
209 |
+
const tempCanvas = document.createElement('canvas');
|
210 |
+
tempCanvas.width = this.canvas.width;
|
211 |
+
tempCanvas.height = this.canvas.height;
|
212 |
+
const tempCtx = tempCanvas.getContext('2d');
|
213 |
+
tempCtx.fillStyle = 'white';
|
214 |
+
tempCtx.fillRect(0, 0, tempCanvas.width, tempCanvas.height);
|
215 |
+
|
216 |
+
if (pageContent && pageContent.drawing) {
|
217 |
+
const imageData = pageContent.drawing;
|
218 |
+
|
219 |
+
if (imageData) {
|
220 |
+
try {
|
221 |
+
tempCtx.putImageData(imageData, 0, 0);
|
222 |
+
console.log("Image applied to canvas for page " + i);
|
223 |
+
} catch (error) {
|
224 |
+
console.log("Error putting image data on page " + i, error);
|
225 |
+
}
|
226 |
+
} else {
|
227 |
+
console.log("No image data for page " + i);
|
228 |
+
}
|
229 |
+
}
|
230 |
+
|
231 |
+
const imgData = tempCanvas.toDataURL('image/png');
|
232 |
+
console.log(`Base64 data for page ${i}:`, imgData);
|
233 |
+
}
|
234 |
+
}
|
235 |
+
|
236 |
+
writeText(text, x = 20, y = 40) {
|
237 |
+
this.setupDrawingStyle();
|
238 |
+
this.ctx.fillStyle = this.tools.color;
|
239 |
+
this.ctx.fillText(text.toString(), x, y);
|
240 |
+
this.saveState();
|
241 |
+
}
|
242 |
+
}
|
243 |
+
|
244 |
+
const whiteboard = new Whiteboard();
|
245 |
+
const canvas = document.getElementById('whiteboard');
|
246 |
+
|
247 |
+
document.addEventListener('keydown', function(event) {
|
248 |
+
if (event.ctrlKey && event.key === 's') {
|
249 |
+
event.preventDefault();
|
250 |
+
const tempCanvas = document.createElement('canvas');
|
251 |
+
const tempContext = tempCanvas.getContext('2d');
|
252 |
+
tempCanvas.width = canvas.width;
|
253 |
+
tempCanvas.height = canvas.height;
|
254 |
+
tempContext.fillStyle = 'white';
|
255 |
+
tempContext.fillRect(0, 0, tempCanvas.width, tempCanvas.height);
|
256 |
+
tempContext.drawImage(canvas, 0, 0);
|
257 |
+
const imageData = tempCanvas.toDataURL("image/png");
|
258 |
+
const link = document.createElement('a');
|
259 |
+
link.href = imageData;
|
260 |
+
link.download = 'canvas-image.png';
|
261 |
+
link.click();
|
262 |
+
console.log("Canvas saved as image with white background!");
|
263 |
+
}
|
264 |
+
});
|
static/js/tools.js
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
class DrawingTools {
|
2 |
+
constructor(canvas, ctx) {
|
3 |
+
this.canvas = canvas;
|
4 |
+
this.ctx = ctx;
|
5 |
+
this.color = '#000000';
|
6 |
+
this.brushSize = 2;
|
7 |
+
this.isEraser = false;
|
8 |
+
this.setupTools();
|
9 |
+
}
|
10 |
+
|
11 |
+
setupTools() {
|
12 |
+
const colorPicker = document.getElementById('colorPicker');
|
13 |
+
const brushSize = document.getElementById('brushSize');
|
14 |
+
const brushSizeLabel = document.getElementById('brushSizeLabel');
|
15 |
+
const eraserBtn = document.getElementById('eraserBtn');
|
16 |
+
|
17 |
+
colorPicker.addEventListener('input', (e) => {
|
18 |
+
this.color = e.target.value;
|
19 |
+
this.isEraser = false;
|
20 |
+
eraserBtn.classList.remove('active');
|
21 |
+
});
|
22 |
+
|
23 |
+
brushSize.addEventListener('input', (e) => {
|
24 |
+
this.brushSize = e.target.value;
|
25 |
+
brushSizeLabel.textContent = `${this.brushSize}px`;
|
26 |
+
});
|
27 |
+
|
28 |
+
eraserBtn.addEventListener('click', () => {
|
29 |
+
this.isEraser = !this.isEraser;
|
30 |
+
eraserBtn.classList.toggle('active');
|
31 |
+
});
|
32 |
+
}
|
33 |
+
|
34 |
+
applyToolSettings() {
|
35 |
+
if (this.isEraser) {
|
36 |
+
this.ctx.globalCompositeOperation = 'destination-out';
|
37 |
+
this.ctx.strokeStyle = 'rgba(0,0,0,1)';
|
38 |
+
} else {
|
39 |
+
this.ctx.globalCompositeOperation = 'source-over';
|
40 |
+
this.ctx.strokeStyle = this.color;
|
41 |
+
}
|
42 |
+
this.ctx.lineWidth = this.brushSize;
|
43 |
+
}
|
44 |
+
}
|
static/js/utils.js
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
class DrawingState {
|
2 |
+
constructor(imageData) {
|
3 |
+
this.imageData = imageData;
|
4 |
+
}
|
5 |
+
}
|
6 |
+
|
7 |
+
function getMousePos(canvas, evt) {
|
8 |
+
const rect = canvas.getBoundingClientRect();
|
9 |
+
return {
|
10 |
+
x: evt.clientX - rect.left,
|
11 |
+
y: evt.clientY - rect.top
|
12 |
+
};
|
13 |
+
}
|
14 |
+
|
15 |
+
function getTouchPos(canvas, evt) {
|
16 |
+
const rect = canvas.getBoundingClientRect();
|
17 |
+
return {
|
18 |
+
x: evt.touches[0].clientX - rect.left,
|
19 |
+
y: evt.touches[0].clientY - rect.top
|
20 |
+
};
|
21 |
+
}
|
templates/index.html
ADDED
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8" />
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
6 |
+
<title>Whiteboard</title>
|
7 |
+
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}" />
|
8 |
+
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons" />
|
9 |
+
</head>
|
10 |
+
<body>
|
11 |
+
<div class="whiteboard-container">
|
12 |
+
<div style="display: flex; justify-content: center; align-items: center; height: 100vh;">
|
13 |
+
<canvas id="whiteboard"></canvas>
|
14 |
+
</div>
|
15 |
+
<div class="brush-preview" id="brushPreview"></div>
|
16 |
+
<div class="bottom-toolbar">
|
17 |
+
<button id="toggleToolbar" class="tool-btn toggle-btn" title="Toggle Toolbar">
|
18 |
+
<span class="material-icons">keyboard_arrow_up</span>
|
19 |
+
</button>
|
20 |
+
<div id="toolbarContent" class="toolbar-content">
|
21 |
+
<div class="tool-group">
|
22 |
+
<button id="undoBtn" class="tool-btn" title="Undo (Ctrl+Z)">
|
23 |
+
<span class="material-icons">undo</span>
|
24 |
+
</button>
|
25 |
+
<button id="redoBtn" class="tool-btn" title="Redo (Ctrl+Y)">
|
26 |
+
<span class="material-icons">redo</span>
|
27 |
+
</button>
|
28 |
+
</div>
|
29 |
+
<div class="tool-group">
|
30 |
+
<input type="color" id="colorPicker" value="#000000" title="Color">
|
31 |
+
<button id="eraserBtn" class="tool-btn" title="Eraser">
|
32 |
+
<span class="material-icons">auto_fix_high</span>
|
33 |
+
</button>
|
34 |
+
</div>
|
35 |
+
<div class="tool-group">
|
36 |
+
<input type="range" id="brushSize" min="1" max="50" value="2" title="Brush Size">
|
37 |
+
<span id="brushSizeLabel">2px</span>
|
38 |
+
</div>
|
39 |
+
<div class="page-controls">
|
40 |
+
<button id="prevBtn" class="tool-btn" title="Previous Page">
|
41 |
+
<span class="material-icons">navigate_before</span>
|
42 |
+
</button>
|
43 |
+
<span id="pageInfo">Page 1</span>
|
44 |
+
<button id="nextBtn" class="tool-btn" title="Next Page">
|
45 |
+
<span class="material-icons">navigate_next</span>
|
46 |
+
</button>
|
47 |
+
</div>
|
48 |
+
<div class="action-buttons">
|
49 |
+
<button id="clearBtn" class="tool-btn" title="Clear Page">
|
50 |
+
<span class="material-icons">delete_outline</span>
|
51 |
+
</button>
|
52 |
+
<button id="aiBtn" class="action-btn ai-btn">
|
53 |
+
<span class="material-icons">psychology</span>
|
54 |
+
AI
|
55 |
+
</button>
|
56 |
+
<button id="submitBtn" class="action-btn submit-btn">
|
57 |
+
<span class="material-icons">upload_file</span>
|
58 |
+
Submit
|
59 |
+
</button>
|
60 |
+
<p id="result">Testing</p>
|
61 |
+
</div>
|
62 |
+
</div>
|
63 |
+
</div>
|
64 |
+
</div>
|
65 |
+
<script src="{{ url_for('static', filename='js/utils.js') }}"></script>
|
66 |
+
<script src="{{ url_for('static', filename='js/history.js') }}"></script>
|
67 |
+
<script src="{{ url_for('static', filename='js/tools.js') }}"></script>
|
68 |
+
<script src="{{ url_for('static', filename='js/pages.js') }}"></script>
|
69 |
+
<script src="{{ url_for('static', filename='js/script.js') }}"></script>
|
70 |
+
</body>
|
71 |
+
</html>
|