Skip to content

Commit 4ab1191

Browse files
authored
Merge pull request #151 from Akshat-Shu/image-parsing
Get your Time Table by uploading images
2 parents 58f3cc4 + a22efd4 commit 4ab1191

22 files changed

+3816
-60
lines changed

app.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@
1111
from flask_cors import CORS
1212
from timetable import generate_ics
1313
from gyft import get_courses
14+
import base64
15+
from PIL import Image
16+
from timetable.image_parser.table_parser import parse_table
17+
from timetable.image_parser.build_courses_from_image import build_courses_from_image
1418

1519

1620
app = Flask(__name__)
@@ -201,6 +205,49 @@ def download_ics():
201205
)
202206
except Exception as e:
203207
return jsonify({"status": "error", "message": str(e)}), 500
208+
209+
210+
@app.route("/parse_image", methods=["POST"])
211+
def image_parser():
212+
try:
213+
# print("Hello", request)
214+
data = request.form
215+
216+
all_fields = {
217+
"image": data.get("image"),
218+
}
219+
220+
221+
missing = check_missing_fields(all_fields)
222+
if len(missing) > 0:
223+
return ErpResponse(
224+
False, f"Missing Fields: {', '.join(missing)}", status_code=400
225+
).to_response()
226+
image = all_fields["image"]
227+
228+
if image.startswith("data:image"):
229+
image = image.split(",")[1]
230+
231+
image_data = io.BytesIO(base64.b64decode(image))
232+
data = parse_table(Image.open(image_data))
233+
234+
courses = build_courses_from_image(data)
235+
236+
ics_content = generate_ics(courses, "")
237+
238+
# Create an in-memory file-like object for the ics content
239+
ics_file = io.BytesIO()
240+
ics_file.write(ics_content.encode("utf-8"))
241+
ics_file.seek(0)
242+
243+
return send_file(
244+
ics_file,
245+
as_attachment=True,
246+
mimetype="text/calendar",
247+
download_name="timetable.ics",
248+
)
249+
except Exception as e:
250+
return ErpResponse(False, str(e), status_code=500).to_response()
204251

205252

206253
if __name__ == "__main__":

frontend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"@hookform/resolvers": "^3.6.0",
1414
"react": "^18.2.0",
1515
"react-dom": "^18.2.0",
16+
"react-dropzone": "^14.3.5",
1617
"react-hook-form": "^7.51.5",
1718
"react-hot-toast": "^2.4.1",
1819
"react-router-dom": "^6.23.1",

frontend/pnpm-lock.yaml

Lines changed: 55 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/src/App.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import Modal from "./components/Modal";
99

1010
const App: React.FC = () => {
1111
const [openModal, setOpenModal] = useState(false);
12+
const [modalContent, setModalContent] = useState<React.ReactNode | undefined>();
1213
return (
1314
<>
1415
<main>
@@ -18,7 +19,7 @@ const App: React.FC = () => {
1819
<div className="wrapper-item">
1920
<div id="wrapped">
2021
<Header />
21-
<MultiForm />
22+
<MultiForm openModal={setOpenModal} setModalContent={setModalContent} />
2223
</div>
2324
<Footer openModal={setOpenModal} />
2425
</div>
@@ -27,7 +28,7 @@ const App: React.FC = () => {
2728
</aside>
2829
</div>
2930
</main>
30-
{openModal && <Modal closeModal={setOpenModal} />}
31+
{openModal && <Modal closeModal={setOpenModal} modalContent={modalContent} setModalContent={setModalContent} />}
3132
</>
3233
);
3334
};

frontend/src/AppContext/AppContext.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ interface IUser {
1212
sessionToken: string | null;
1313
}
1414

15-
interface IAuth {
15+
export interface IAuth {
1616
user: IUser;
1717
currentStep: number;
1818
}

frontend/src/components/Modal.tsx

Lines changed: 30 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,20 @@ import React from "react";
22

33
interface ModalProps {
44
closeModal: React.Dispatch<React.SetStateAction<boolean>>;
5+
setModalContent?: React.Dispatch<React.SetStateAction<React.ReactNode | undefined>>;
6+
modalContent?: React.ReactNode;
57
}
68

7-
const Modal: React.FC<ModalProps> = ({ closeModal }) => {
9+
const Modal: React.FC<ModalProps> = ({ closeModal, setModalContent, modalContent }) => {
810
return (
911
<div className="modal-overlay">
1012
<div className="modal-content">
1113
<button
1214
className="close-button"
13-
onClick={() => closeModal(false)}
15+
onClick={() => {
16+
closeModal(false);
17+
if(setModalContent) setModalContent(undefined);
18+
}}
1419
>
1520
<svg
1621
fill="none"
@@ -27,25 +32,29 @@ const Modal: React.FC<ModalProps> = ({ closeModal }) => {
2732
/>
2833
</svg>
2934
</button>
30-
<h2>GYFT - MetaKGP</h2>
31-
<p>
32-
GYFT gives you an ICS file for your current semester
33-
timetable which you can add in any common calendar
34-
application. Now, you'll always know when your classes
35-
are—whether you decide to go or not!
36-
</p>
37-
<h4>How to get your timetable?</h4>
38-
<ol>
39-
<li>Enter your roll number and password for ERP login</li>
40-
<li>Answer the security question and enter the OTP.</li>
41-
<li>
42-
Download the timetable for the current semester in .ics
43-
format.
44-
</li>
45-
<li>
46-
Import the .ics file into your favorite calendar app!
47-
</li>
48-
</ol>
35+
{modalContent ? modalContent : (
36+
<>
37+
<h2>GYFT - MetaKGP</h2>
38+
<p>
39+
GYFT gives you an ICS file for your current semester
40+
timetable which you can add in any common calendar
41+
application. Now, you'll always know when your classes
42+
are—whether you decide to go or not!
43+
</p>
44+
<h4>How to get your timetable?</h4>
45+
<ol>
46+
<li>Enter your roll number and password for ERP login</li>
47+
<li>Answer the security question and enter the OTP.</li>
48+
<li>
49+
Download the timetable for the current semester in .ics
50+
format.
51+
</li>
52+
<li>
53+
Import the .ics file into your favorite calendar app!
54+
</li>
55+
</ol>
56+
</>
57+
)}
4958
</div>
5059
</div>
5160
);

frontend/src/components/MultiForm.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,24 @@
1-
import React from "react";
1+
import React, { useState } from "react";
22
import RollForm from "./RollForm";
33
import SecurityQueForm from "./SecurityQueForm";
44
import { useAppContext } from "../AppContext/AppContext";
55
import Timetable from "./Timetable";
66
import ErrorPage from "./ErrorPage";
77

8-
const MultiForm: React.FC = () => {
8+
type props = {
9+
openModal: React.Dispatch<React.SetStateAction<boolean>>;
10+
setModalContent: React.Dispatch<React.SetStateAction<React.ReactNode | undefined>>;
11+
};
12+
13+
const MultiForm: React.FC<props> = ({ openModal, setModalContent }) => {
914
const { currentStep, user } = useAppContext();
15+
const [ timeTableFile, setTimeTableFile ] = useState(undefined as Blob | undefined);
16+
if(currentStep == 3 && timeTableFile) return <Timetable file={timeTableFile} />;
1017
if (currentStep == 2 && user.sessionToken && user.ssoToken)
1118
return <Timetable />;
1219
if (currentStep == 1 && user.sessionToken && user.securityQuestion)
1320
return <SecurityQueForm />;
14-
if (currentStep == 0) return <RollForm />;
21+
if (currentStep == 0) return <RollForm openModal={openModal} setModalContent={setModalContent} setTimeTableFile={setTimeTableFile} />;
1522
return <ErrorPage />;
1623
};
1724

0 commit comments

Comments
 (0)