diff --git a/back-end/database/database.py b/back-end/database/database.py index 806c59b..7641d94 100644 --- a/back-end/database/database.py +++ b/back-end/database/database.py @@ -1,4 +1,13 @@ -from sqlalchemy import create_engine, Column, Integer, String,ForeignKey,DateTime,Enum,Boolean +from sqlalchemy import ( + create_engine, + Column, + Integer, + String, + ForeignKey, + DateTime, + Enum, + Boolean, +) from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker, relationship from models.models import UserRole @@ -6,17 +15,19 @@ import os # Database setup -DATABASE_URL = os.getenv('DATABASE', "sqlite:///./test.db") +DATABASE_URL = os.getenv("DATABASE", "sqlite:///./test.db") engine = create_engine(DATABASE_URL) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base() + # Function to create the database tables def getSessionLocal(): return SessionLocal + # Function to create the database tables def create_db_tables(): Base.metadata.create_all(bind=engine) @@ -35,27 +46,31 @@ class User(Base): image_url = Column(String) # Added field for user's profile image URL activated = Column(Boolean, default=False, nullable=False) # New activated column + class Projects(Base): __tablename__ = "projects" id = Column(Integer, primary_key=True, index=True) - name = Column(String, nullable=False) + name = Column(String, nullable=False) description = Column(String) project_type = Column(String, default="python") date_created = Column(DateTime, default=datetime.datetime.utcnow) code = Column(String) - user_id = Column(Integer, ForeignKey('users.id'), nullable=False) + user_id = Column(Integer, ForeignKey("users.id"), nullable=False) user = relationship("User") - + track = Column(String, nullable=True) + + class Curriculum(Base): __tablename__ = "curriculums" id = Column(Integer, primary_key=True, index=True) name = Column(String, nullable=False) description = Column(String) image_url = Column(String) - user_id = Column(Integer, ForeignKey('users.id'), nullable=False) + user_id = Column(Integer, ForeignKey("users.id"), nullable=False) user = relationship("User") lessons = relationship("Lesson", back_populates="curriculum") + class Lesson(Base): __tablename__ = "lessons" id = Column(Integer, primary_key=True, index=True) @@ -63,5 +78,5 @@ class Lesson(Base): description = Column(String) image_url = Column(String) video_url = Column(String) - curriculum_id = Column(Integer, ForeignKey('curriculums.id'), nullable=False) - curriculum = relationship("Curriculum", back_populates="lessons") \ No newline at end of file + curriculum_id = Column(Integer, ForeignKey("curriculums.id"), nullable=False) + curriculum = relationship("Curriculum", back_populates="lessons") diff --git a/back-end/main.py b/back-end/main.py index d281388..17d574f 100644 --- a/back-end/main.py +++ b/back-end/main.py @@ -1,8 +1,28 @@ -from models.models import UserRole, ProjectsCreate, LoginRequest, RegisterRequest, UpdateActiavtedRequest, UpdateUserRequest, UpdateUserPasswordRequest, UserResponse, SessionTokenRequest, UpdateUserRoleRequest, UpdateBetaTesterRequest, EmailVerificationRequest -from database.database import create_db_tables, User, Projects, Curriculum, Lesson, getSessionLocal +from models.models import ( + UserRole, + ProjectsCreate, + LoginRequest, + RegisterRequest, + UpdateActiavtedRequest, + UpdateUserRequest, + UpdateUserPasswordRequest, + UserResponse, + SessionTokenRequest, + UpdateUserRoleRequest, + UpdateBetaTesterRequest, + EmailVerificationRequest, +) +from database.database import ( + create_db_tables, + User, + Projects, + Curriculum, + Lesson, + getSessionLocal, +) from utils.utils_jwt import create_access_token, verify_access_token from fastapi import FastAPI, Depends, HTTPException, status -from utils.utils_hash import get_hashed, verify_hashed +from utils.utils_hash import get_hashed, verify_hashed from fastapi.middleware.cors import CORSMiddleware from fastapi.security import OAuth2PasswordBearer from jose import JWTError, jwt @@ -24,12 +44,13 @@ # Configure CORS origins = [ - "http://localhost:3000", "http://localhost:5000" # Add other origins as needed + "http://localhost:3000", + "http://localhost:5000", # Add other origins as needed ] app.add_middleware( CORSMiddleware, - allow_origins=["*"],#origins, + allow_origins=["*"], # origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], @@ -38,6 +59,7 @@ # Security oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") + def get_db(): db = SessionLocal() try: @@ -45,24 +67,25 @@ def get_db(): finally: db.close() + # Function to create admin user if not exists def create_admin_user(): db = SessionLocal() - admin_username = os.getenv('ADMIN_USERNAME', 'admin') - admin_password = os.getenv('ADMIN_PASSWORD', 'admin') - admin_email = os.getenv('ADMIN_EMAIL', 'admin@gmail.com') + admin_username = os.getenv("ADMIN_USERNAME", "admin") + admin_password = os.getenv("ADMIN_PASSWORD", "admin") + admin_email = os.getenv("ADMIN_EMAIL", "admin@gmail.com") admin_user = get_user(db, admin_username) if not admin_user: hashed_password = get_hashed(admin_password) new_admin = User( username=admin_username, hashed_password=hashed_password, - firstname= os.getenv('ADMIN_FIRSTNAME', 'Admin'), - lastname= os.getenv('ADMIN_LASTNAME', 'Admin'), + firstname=os.getenv("ADMIN_FIRSTNAME", "Admin"), + lastname=os.getenv("ADMIN_LASTNAME", "Admin"), email=admin_email, role=UserRole.ADMIN, beta_tester=False, - activated=True # Ensure admin is activated + activated=True, # Ensure admin is activated ) db.add(new_admin) db.commit() @@ -72,6 +95,7 @@ def create_admin_user(): logger.info("Admin user already exists") db.close() + @app.on_event("startup") def on_startup(): create_admin_user() @@ -80,20 +104,24 @@ def on_startup(): def get_user(db, username: str): return db.query(User).filter(User.username == username).first() + def authenticate_user(db, username: str, password: str): user = get_user(db, username) if not user or not verify_hashed(password, user.hashed_password): return False return user -async def get_current_user(token: str = Depends(oauth2_scheme), db: SessionLocal = Depends(get_db)): + +async def get_current_user( + token: str = Depends(oauth2_scheme), db: SessionLocal = Depends(get_db) +): credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", headers={"WWW-Authenticate": "Bearer"}, ) try: - payload = verify_access_token(token) + payload = verify_access_token(token) username: str = payload.get("sub") if username is None: raise credentials_exception @@ -106,11 +134,13 @@ async def get_current_user(token: str = Depends(oauth2_scheme), db: SessionLocal @app.post("/token") -async def login_for_access_token( login_request: LoginRequest, db: SessionLocal = Depends(get_db)): - +async def login_for_access_token( + login_request: LoginRequest, db: SessionLocal = Depends(get_db) +): + logger.info(f"Request body: {login_request}") user = authenticate_user(db, login_request.username, login_request.password) - + if not user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, @@ -118,18 +148,20 @@ async def login_for_access_token( login_request: LoginRequest, db: SessionLocal headers={"WWW-Authenticate": "Bearer"}, ) access_token = create_access_token(data={"sub": user.username}) - return {"user":user.username, "access_token": access_token, "token_type": "bearer"} + return {"user": user.username, "access_token": access_token, "token_type": "bearer"} @app.get("/users/me") -async def read_users_me(token: str = Depends(oauth2_scheme), db: SessionLocal = Depends(get_db)): +async def read_users_me( + token: str = Depends(oauth2_scheme), db: SessionLocal = Depends(get_db) +): credentials_exception = HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", headers={"WWW-Authenticate": "Bearer"}, ) try: - payload = verify_access_token(token) + payload = verify_access_token(token) username: str = payload.get("sub") if username is None: raise credentials_exception @@ -140,8 +172,13 @@ async def read_users_me(token: str = Depends(oauth2_scheme), db: SessionLocal = raise credentials_exception return user + @app.put("/users/me") -async def update_user_info(user_update: UpdateUserRequest, current_user: User = Depends(get_current_user), db: SessionLocal = Depends(get_db)): +async def update_user_info( + user_update: UpdateUserRequest, + current_user: User = Depends(get_current_user), + db: SessionLocal = Depends(get_db), +): # Fetch the current user from the database db_user = db.query(User).filter(User.id == current_user.id).first() if db_user is None: @@ -163,8 +200,13 @@ async def update_user_info(user_update: UpdateUserRequest, current_user: User = return db_user + @app.put("/users/me/password") -async def update_user_password(user_password_update: UpdateUserPasswordRequest, current_user: User = Depends(get_current_user), db: SessionLocal = Depends(get_db)): +async def update_user_password( + user_password_update: UpdateUserPasswordRequest, + current_user: User = Depends(get_current_user), + db: SessionLocal = Depends(get_db), +): # Fetch the current user from the database db_user = db.query(User).filter(User.id == current_user.id).first() if db_user is None: @@ -181,10 +223,19 @@ async def update_user_password(user_password_update: UpdateUserPasswordRequest, return db_user + @app.put("/users/{user_id}/beta_tester") -async def update_beta_tester_status(user_id: int, beta_tester_update: UpdateBetaTesterRequest, current_user: User = Depends(get_current_user), db: SessionLocal = Depends(get_db)): +async def update_beta_tester_status( + user_id: int, + beta_tester_update: UpdateBetaTesterRequest, + current_user: User = Depends(get_current_user), + db: SessionLocal = Depends(get_db), +): if current_user.role != UserRole.ADMIN: - raise HTTPException(status_code=403, detail="Not authorized for this action: UPDATE BETA TESTER STATUS") + raise HTTPException( + status_code=403, + detail="Not authorized for this action: UPDATE BETA TESTER STATUS", + ) db_user = db.query(User).filter(User.id == user_id).first() if db_user is None: @@ -197,10 +248,19 @@ async def update_beta_tester_status(user_id: int, beta_tester_update: UpdateBeta return db_user + @app.put("/users/{user_id}/activated") -async def update_beta_tester_status(user_id: int, activated_update: UpdateActiavtedRequest, current_user: User = Depends(get_current_user), db: SessionLocal = Depends(get_db)): +async def update_beta_tester_status( + user_id: int, + activated_update: UpdateActiavtedRequest, + current_user: User = Depends(get_current_user), + db: SessionLocal = Depends(get_db), +): if current_user.role != UserRole.ADMIN: - raise HTTPException(status_code=403, detail="Not authorized for this action: UPDATE BETA TESTER STATUS") + raise HTTPException( + status_code=403, + detail="Not authorized for this action: UPDATE BETA TESTER STATUS", + ) db_user = db.query(User).filter(User.id == user_id).first() if db_user is None: @@ -213,10 +273,18 @@ async def update_beta_tester_status(user_id: int, activated_update: UpdateActiav return db_user + @app.put("/users/{user_id}/role") -async def update_user_role(user_id: int, user_role_update: UpdateUserRoleRequest, current_user: User = Depends(get_current_user), db: SessionLocal = Depends(get_db)): +async def update_user_role( + user_id: int, + user_role_update: UpdateUserRoleRequest, + current_user: User = Depends(get_current_user), + db: SessionLocal = Depends(get_db), +): if current_user.role != UserRole.ADMIN: - raise HTTPException(status_code=403, detail="Not authorized for this action: UPDATE USER ROLE") + raise HTTPException( + status_code=403, detail="Not authorized for this action: UPDATE USER ROLE" + ) db_user = db.query(User).filter(User.id == user_id).first() if db_user is None: @@ -229,8 +297,11 @@ async def update_user_role(user_id: int, user_role_update: UpdateUserRoleRequest return db_user + @app.post("/register/") -async def register_user(register_request: RegisterRequest, db: SessionLocal = Depends(get_db)): +async def register_user( + register_request: RegisterRequest, db: SessionLocal = Depends(get_db) +): # Check if the user already exists print(register_request) db_user = get_user(db, register_request.username) @@ -239,33 +310,50 @@ async def register_user(register_request: RegisterRequest, db: SessionLocal = De # Create new user instance hashed_password = get_hashed(register_request.password) - new_user = User(username=register_request.username, - hashed_password=hashed_password, - firstname=register_request.firstname, - lastname=register_request.lastname, - email=register_request.email - ) + new_user = User( + username=register_request.username, + hashed_password=hashed_password, + firstname=register_request.firstname, + lastname=register_request.lastname, + email=register_request.email, + ) # Add new user to the database db.add(new_user) db.commit() db.refresh(new_user) - return {"username": new_user.username,"email":new_user.email, "id": new_user.id}#, "errorMessage": message} - + return { + "username": new_user.username, + "email": new_user.email, + "id": new_user.id, + } # , "errorMessage": message} + + @app.get("/users/", response_model=List[UserResponse]) -async def read_users(current_user: User = Depends(get_current_user), db: SessionLocal = Depends(get_db)): +async def read_users( + current_user: User = Depends(get_current_user), db: SessionLocal = Depends(get_db) +): if current_user.role != UserRole.ADMIN: - raise HTTPException(status_code=403, detail="Not authorized to access this resource: GET USERS") - + raise HTTPException( + status_code=403, detail="Not authorized to access this resource: GET USERS" + ) + users = db.query(User).all() return users + @app.delete("/users/{user_id}") -async def delete_user(user_id: int, current_user: User = Depends(get_current_user), db: SessionLocal = Depends(get_db)): +async def delete_user( + user_id: int, + current_user: User = Depends(get_current_user), + db: SessionLocal = Depends(get_db), +): if current_user.role != UserRole.ADMIN: - raise HTTPException(status_code=403, detail="Not authorized for this action: DELETE USER") - + raise HTTPException( + status_code=403, detail="Not authorized for this action: DELETE USER" + ) + db_user = db.query(User).filter(User.id == user_id).first() if db_user is None: raise HTTPException(status_code=404, detail="User not found in database") @@ -274,43 +362,87 @@ async def delete_user(user_id: int, current_user: User = Depends(get_current_use db.commit() return {"detail": "User deleted"} + @app.get("/projects/") -async def read_own_projects(current_user: User = Depends(get_current_user), db: SessionLocal = Depends(get_db)): +async def read_own_projects( + current_user: User = Depends(get_current_user), db: SessionLocal = Depends(get_db) +): return db.query(Projects).filter(Projects.user_id == current_user.id).all() + @app.post("/projects/") -async def create_project(project: ProjectsCreate, current_user: User = Depends(get_current_user), db: SessionLocal = Depends(get_db)): - db_project = Projects(name=project.name, description=project.description,project_type=project.project_type,code=project.code, user_id=current_user.id) +async def create_project( + project: ProjectsCreate, + current_user: User = Depends(get_current_user), + db: SessionLocal = Depends(get_db), +): + db_project = Projects( + name=project.name, + description=project.description, + project_type=project.project_type, + code=project.code, + track=project.track, + user_id=current_user.id, + ) db.add(db_project) db.commit() db.refresh(db_project) return db_project + @app.get("/projects/{project_id}") -async def read_project(project_id: int, current_user: User = Depends(get_current_user), db: SessionLocal = Depends(get_db)): - project = db.query(Projects).filter(Projects.id == project_id, Projects.user_id == current_user.id).first() +async def read_project( + project_id: int, + current_user: User = Depends(get_current_user), + db: SessionLocal = Depends(get_db), +): + project = ( + db.query(Projects) + .filter(Projects.id == project_id, Projects.user_id == current_user.id) + .first() + ) if project is None: raise HTTPException(status_code=404, detail="Project not found") return project + @app.delete("/projects/{project_id}") -async def delete_project(project_id: int, current_user: User = Depends(get_current_user), db: SessionLocal = Depends(get_db)): - db_project = db.query(Projects).filter(Projects.id == project_id, Projects.user_id == current_user.id).first() +async def delete_project( + project_id: int, + current_user: User = Depends(get_current_user), + db: SessionLocal = Depends(get_db), +): + db_project = ( + db.query(Projects) + .filter(Projects.id == project_id, Projects.user_id == current_user.id) + .first() + ) if db_project is None: raise HTTPException(status_code=404, detail="Project not found") db.delete(db_project) db.commit() return {"detail": "Project deleted"} + @app.put("/projects/{project_id}") -async def update_project(project_id: int, project_update: ProjectsCreate, current_user: User = Depends(get_current_user), db: SessionLocal = Depends(get_db)): - db_project = db.query(Projects).filter(Projects.id == project_id, Projects.user_id == current_user.id).first() +async def update_project( + project_id: int, + project_update: ProjectsCreate, + current_user: User = Depends(get_current_user), + db: SessionLocal = Depends(get_db), +): + db_project = ( + db.query(Projects) + .filter(Projects.id == project_id, Projects.user_id == current_user.id) + .first() + ) if db_project is None: raise HTTPException(status_code=404, detail="Project not found") db_project.name = project_update.name - db_project.description = project_update.description + db_project.description = project_update.description db_project.code = project_update.code + db_project.track = project_update.track db.commit() db.refresh(db_project) diff --git a/back-end/models/models.py b/back-end/models/models.py index 416c7b9..c4ca3d7 100644 --- a/back-end/models/models.py +++ b/back-end/models/models.py @@ -2,15 +2,18 @@ from pydantic import BaseModel from typing import Optional + class UserRole(PyEnum): - ADMIN = 'admin' - TUTOR = 'tutor' - USER = 'user' + ADMIN = "admin" + TUTOR = "tutor" + USER = "user" + class LoginRequest(BaseModel): username: str password: str + class RegisterRequest(BaseModel): username: str password: str @@ -18,24 +21,30 @@ class RegisterRequest(BaseModel): lastname: str email: str + class UpdateUserRequest(BaseModel): firstname: str lastname: str username: str email: str -class UpdateUserPasswordRequest(BaseModel): + +class UpdateUserPasswordRequest(BaseModel): password: str + class ProjectsCreate(BaseModel): name: str description: str - project_type: str + project_type: str code: str + track: str + class SessionTokenRequest(BaseModel): session_token: str + class UserResponse(BaseModel): id: int username: str @@ -46,19 +55,23 @@ class UserResponse(BaseModel): image_url: Optional[str] beta_tester: bool activated: bool - + class Config: orm_mode = True + class UpdateUserRoleRequest(BaseModel): role: UserRole + class UpdateBetaTesterRequest(BaseModel): beta_tester: bool + class EmailVerificationRequest(BaseModel): email: str username: str + class UpdateActiavtedRequest(BaseModel): - activated: bool \ No newline at end of file + activated: bool diff --git a/front-end/src/authentication/AuthInterfaces.ts b/front-end/src/authentication/AuthInterfaces.ts index 14c0894..d12b923 100644 --- a/front-end/src/authentication/AuthInterfaces.ts +++ b/front-end/src/authentication/AuthInterfaces.ts @@ -1,104 +1,108 @@ // Define the shape of your context data export interface AuthContextType { - user: User | null; - token: string; - loginAction: (data: LoginData) => Promise; - registerAction: (data: RegisterData) => Promise; + user: User | null; + token: string; + loginAction: (data: LoginData) => Promise; + registerAction: (data: RegisterData) => Promise; - logOutAction: () => void; - getUserDataAction: () => Promise; - updateUser: (data: UserData) => Promise; - updateUserPassword: (data: PassswordData) => Promise; - getAllUsers: () => Promise; - deleteUserByIdAction: (projectId: number) => Promise; - updateUserRole: (userId: number, data: RoleData) => Promise; - updateUserBetaTesterStatus: (userId: number, beta_tester: BetaTesterData) => Promise; - updateUserActivatedStatus: (userId: number, activated: ActivatedData) => Promise; + logOutAction: () => void; + getUserDataAction: () => Promise; + updateUser: (data: UserData) => Promise; + updateUserPassword: (data: PassswordData) => Promise; + getAllUsers: () => Promise; + deleteUserByIdAction: (projectId: number) => Promise; + updateUserRole: (userId: number, data: RoleData) => Promise; + updateUserBetaTesterStatus: (userId: number, beta_tester: BetaTesterData) => Promise; + updateUserActivatedStatus: (userId: number, activated: ActivatedData) => Promise; - createProjectAction: (data: NewProjectData) => Promise; - getProjectsAction: () => Promise; - deleteProjectByIdAction: (projectId: number) => Promise; - getProjectByIdAction: (projectId: number) => Promise; - updateProjectByIdAction: (projectId: number, data: NewProjectData) => Promise; - + createProjectAction: (data: NewProjectData) => Promise; + getProjectsAction: () => Promise; + deleteProjectByIdAction: (projectId: number) => Promise; + getProjectByIdAction: (projectId: number) => Promise; + updateProjectByIdAction: ( + projectId: number, + data: NewProjectData, + ) => Promise; } // Registration data export interface RegisterData { - username: string; - password: string; - email: string; - firstname: string; - lastname: string; + username: string; + password: string; + email: string; + firstname: string; + lastname: string; } export interface NewProjectData { - name: string; - description: string; - project_type: string; - code: string; + name: string; + description: string; + project_type: string; + code: string; + track: string; } export interface Project { - id: number; - name: string; - description: string; - project_type: string; - code: string; + track: string; + id: number; + name: string; + description: string; + project_type: string; + code: string; } export interface ProjectResponse { - projects: Project[]; - message?: string; + projects: Project[]; + message?: string; } export interface LoginData { - username: string; - password: string; + username: string; + password: string; } export interface User { - id: number; - username: string; - firstname: string; - lastname: string; - email: string; - role: string; - image_url?: string; - hashed_password?: string; - beta_tester: boolean; + id: number; + username: string; + firstname: string; + lastname: string; + email: string; + role: string; + image_url?: string; + hashed_password?: string; + beta_tester: boolean; } export interface UserData { - username: string; - firstname: string; - lastname: string; - email: string; + username: string; + firstname: string; + lastname: string; + email: string; } export interface PassswordData { - password: string; + password: string; } export enum UserRole { - ADMIN = 'admin', - TUTOR = 'tutor', - USER = 'user', + ADMIN = 'admin', + TUTOR = 'tutor', + USER = 'user', } export interface RoleData { - role: UserRole; + role: UserRole; } export interface BetaTesterData { - beta_tester: boolean; + beta_tester: boolean; } export interface ActivatedData { - activated: boolean; + activated: boolean; } export interface LoginResponse { - success: boolean; - detail: string; -} \ No newline at end of file + success: boolean; + detail: string; +} diff --git a/front-end/src/components/dashboard/NewProjectForm.tsx b/front-end/src/components/dashboard/NewProjectForm.tsx index 168b6eb..c9be213 100644 --- a/front-end/src/components/dashboard/NewProjectForm.tsx +++ b/front-end/src/components/dashboard/NewProjectForm.tsx @@ -73,6 +73,7 @@ const NewProjectForm = ({ isDescriptionDisabled, editorInitialValue, code }: New description: description, project_type: selectedOption, code: code, + track:'/js-simulator/stages/stage_white_rect.json' //Add a default on db when create a project }); const monacoPageUrl = `/monaco-page/${projectID}`; diff --git a/front-end/src/components/js-simulator/Simulator.tsx b/front-end/src/components/js-simulator/Simulator.tsx index 6bd7222..44e117f 100644 --- a/front-end/src/components/js-simulator/Simulator.tsx +++ b/front-end/src/components/js-simulator/Simulator.tsx @@ -2,35 +2,59 @@ import { Box, Button, Grid, Slider, useMediaQuery } from '@mui/material'; import React, { useRef, useEffect, useImperativeHandle, forwardRef, useState } from 'react'; import { scene, camera, renderer } from './scene.js'; import { ambientLight, directionalLight } from './environment_lights.js'; -import { plane, updateTexture} from './floor_loader.js'; +import { plane, updateTexture } from './floor_loader.js'; import { loadBaseObject } from './robot_loader.js'; -import { startAnimation, stopAnimation, stopMotion, moveStep, rotateStep, controls, rgb_set_color, changeCamera, just_rotate, just_move, drawLine } from './animate.js'; +import { + startAnimation, + stopAnimation, + stopMotion, + moveStep, + rotateStep, + controls, + rgb_set_color, + changeCamera, + just_rotate, + just_move, + drawLine, +} from './animate.js'; import { loadObjectsFromJSON, robot_position } from './stage_loader.js'; -import { faMap, faArrowUp, faArrowDown, faArrowLeft, faArrowRight, faBinoculars, faLightbulb, faRefresh } from '@fortawesome/free-solid-svg-icons'; +import { + faMap, + faArrowUp, + faArrowDown, + faArrowLeft, + faArrowRight, + faBinoculars, + faLightbulb, + faRefresh, +} from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { get_distance, get_acceleration, get_gyroscope, get_floor_sensor, get_light_sensor, traceLine } from './sensors.js'; +import { + get_distance, + get_acceleration, + get_gyroscope, + get_floor_sensor, + get_light_sensor, + traceLine, +} from './sensors.js'; import * as THREE from 'three'; import CardDialog from 'src/components/stage-select-popup/CardDialog'; // Import the CardDialog component type WebGLAppProps = { appsessionId: string; onMountChange: (isMounted: boolean) => void; + stageUrl: string; }; - - - - -const WebGLApp = forwardRef(({ appsessionId, onMountChange }: WebGLAppProps, ref) => { +const WebGLApp = forwardRef(({ appsessionId, onMountChange, stageUrl }: WebGLAppProps, ref) => { const mountRef = useRef(null); const [lightIntensity, setLightIntensity] = useState(100); - const [currentURL, setCurrentURL] = useState('/js-simulator/stages/stage_white_rect.json'); + const [currentURL, setCurrentURL] = useState(stageUrl); useImperativeHandle(ref, () => ({ - setDirectionalLightIntensity: (intensity: number) => { changeDirectionalLightIntensity(intensity); - } + }, })); const changeDirectionalLightIntensity = (intensity: number) => { @@ -50,8 +74,6 @@ const WebGLApp = forwardRef(({ appsessionId, onMountChange }: WebGLAppProps, ref resetScene(currentURL); - - const handleResize = () => { if (currentMountRef) { const { clientWidth, clientHeight } = currentMountRef; @@ -66,7 +88,6 @@ const WebGLApp = forwardRef(({ appsessionId, onMountChange }: WebGLAppProps, ref currentMountRef.appendChild(renderer.domElement); } - window.addEventListener('resize', handleResize); startAnimation(); @@ -104,11 +125,8 @@ const WebGLApp = forwardRef(({ appsessionId, onMountChange }: WebGLAppProps, ref changeCamera(); }; - - - - // New state for dialog - const [openDialog, setOpenDialog] = useState(false); + // New state for dialog + const [openDialog, setOpenDialog] = useState(false); // Dialog handler functions const handleOpenDialog = () => { @@ -120,7 +138,6 @@ const WebGLApp = forwardRef(({ appsessionId, onMountChange }: WebGLAppProps, ref }; const handleCardSelect = (url: string) => { - setCurrentURL(url); setOpenDialog(false); // Close the dialog after selecting a card }; @@ -131,23 +148,28 @@ const WebGLApp = forwardRef(({ appsessionId, onMountChange }: WebGLAppProps, ref rgb_set_color('off'); drawLine(false); } - + ambientLight.name = 'ambientLight'; scene.add(ambientLight); directionalLight.name = 'directionalLight'; scene.add(directionalLight); - + scene.add(traceLine); - + loadObjectsFromJSON(currentURL, scene); - + loadBaseObject(scene); }; - - return ( - +
@@ -191,7 +213,7 @@ const WebGLApp = forwardRef(({ appsessionId, onMountChange }: WebGLAppProps, ref - + - + ); }); -export { WebGLApp, moveStep, rotateStep, stopMotion, get_distance, rgb_set_color, get_acceleration, get_gyroscope, get_floor_sensor, just_move, just_rotate, get_light_sensor, drawLine}; +export { + WebGLApp, + moveStep, + rotateStep, + stopMotion, + get_distance, + rgb_set_color, + get_acceleration, + get_gyroscope, + get_floor_sensor, + just_move, + just_rotate, + get_light_sensor, + drawLine, +}; diff --git a/front-end/src/views/blockly-page/BlocklyPage.tsx b/front-end/src/views/blockly-page/BlocklyPage.tsx index 1fe4250..23bdbbb 100644 --- a/front-end/src/views/blockly-page/BlocklyPage.tsx +++ b/front-end/src/views/blockly-page/BlocklyPage.tsx @@ -51,6 +51,7 @@ const BlocklyPage = () => { const [isSimulatorLoading, setIsSimulatorLoading] = useState(true); // Loading state of Simulator const [showVideoPlayer, setShowVideoPlayer] = useState(false); const [showDrawer, setShowDrawer] = useState(false); + const [stageUrl, setStageUrl] = useState('/js-simulator/stages/stage_white_rect.json'); const runScriptRef = useRef<() => Promise>(); const auth = useAuth(); @@ -115,7 +116,11 @@ const BlocklyPage = () => { if (fetchedProject.code != '') { setEditorValue(fetchedProject.code); } + if(fetchedProject.track){ + setStageUrl(fetchedProject.track) + } setProjectTitle(fetchedProject.name); + } } else { //setEditorValue( ''); @@ -178,6 +183,7 @@ const BlocklyPage = () => { description: projectDescription, project_type: 'blockly', code: editorValue, + track: stageUrl, }); if (project) { handleShowSuccessAlert(t('alertMessages.projectUpdated')); @@ -337,7 +343,11 @@ const BlocklyPage = () => { )} - +
diff --git a/front-end/src/views/interactive-page/InteractivePage.tsx b/front-end/src/views/interactive-page/InteractivePage.tsx index e30f73d..09ec148 100644 --- a/front-end/src/views/interactive-page/InteractivePage.tsx +++ b/front-end/src/views/interactive-page/InteractivePage.tsx @@ -26,6 +26,8 @@ const InteractivePage = () => { const [isSimulatorLoading, setIsSimulatorLoading] = useState(true); const [videoBoxKey, setVideoBoxKey] = useState(uuidv4()); const location = useLocation(); + const [stageUrl, setStageUrl] = useState('/js-simulator/stages/stage_white_rect.json'); + const isResponsive = useMediaQuery('(max-width:1024px)'); @@ -64,7 +66,7 @@ const InteractivePage = () => { > - + diff --git a/front-end/src/views/monaco-page/MonacoPage.tsx b/front-end/src/views/monaco-page/MonacoPage.tsx index 4aadd31..d72edcb 100644 --- a/front-end/src/views/monaco-page/MonacoPage.tsx +++ b/front-end/src/views/monaco-page/MonacoPage.tsx @@ -71,6 +71,7 @@ const MonacoPage: React.FC = () => { const navigate = useNavigate(); const { projectId } = useParams<{ projectId: string }>(); const [isInPIP, setIsInPIP] = useState(false); + const [stageUrl, setStageUrl] = useState('/js-simulator/stages/stage_white_rect.json'); const isColumn = useMediaQuery('(max-width:1024px)'); @@ -133,6 +134,9 @@ const MonacoPage: React.FC = () => { setEditorValue(fetchedProject.code); setProjectTitle(fetchedProject.name); setProjectDescription(fetchedProject.description); + if (fetchedProject.track) { + setStageUrl(fetchedProject.track); + } } } else { setEditorValue(textart); @@ -178,6 +182,7 @@ const MonacoPage: React.FC = () => { description: projectDescription, project_type: 'blockly', code: editorValue, + track: stageUrl, }); if (project) { handleShowSuccessAlert(t('alertMessages.projectUpdated')); @@ -376,7 +381,11 @@ const MonacoPage: React.FC = () => { )} - +