Compare commits

...
Sign in to create a new pull request.

36 commits

Author SHA1 Message Date
1d207b56a6 Merge pull request 'Updated backend' (#15) from back_end into main
Reviewed-on: #15
2024-12-06 06:00:07 +00:00
iMax
75c29c719a Hot Fix: Debug for build 2024-12-06 06:57:50 +01:00
iMax
2b71492587 Hot Fix: Ajout page chaos + Fix mystère 2024-12-06 06:53:41 +01:00
SwimJéjé
0508ddaa7f Merge branch 'main' of ssh://git.leizour.fr:222/RedCrab/projet-nuitinfo-2024 into chaos_page 2024-12-06 06:47:59 +01:00
SwimJéjé
b2cb9ca5c8 Merge branch 'chaos_page' of ssh://git.leizour.fr:222/RedCrab/projet-nuitinfo-2024 into chaos_page 2024-12-06 06:47:19 +01:00
SwimJéjé
1e95438b2c Fin Chaos Page 2024-12-06 06:44:06 +01:00
iMax
0fa1a9b6f3 Hot Fix: Reactor routeur 2024-12-06 06:34:45 +01:00
iMax
6405c6db09 Merge branch 'main' of ssh://git.leizour.fr:222/RedCrab/projet-nuitinfo-2024 into chaos_page 2024-12-06 06:09:06 +01:00
iMax
f8ffe3e16f HARRRRRRD HOT FIX: Remove all error and warning 2024-12-06 06:08:00 +01:00
iMax
d685e00bc3 Merge branch 'main' of ssh://git.leizour.fr:222/RedCrab/projet-nuitinfo-2024 into main 2024-12-06 05:59:59 +01:00
iMax
3f9617549d Hot fix : NavBar ClassName 2024-12-06 05:59:30 +01:00
iMax
0e31e90af9 Création d'un formulaire basique 2024-12-06 05:58:13 +01:00
iMax
6e77233ac0 Merge branch 'chaos_page' of ssh://git.leizour.fr:222/RedCrab/projet-nuitinfo-2024 into chaos_page 2024-12-06 05:51:53 +01:00
1ec6b1b0da Merge pull request 'Updated docker-compose.yml' (#13) from back_end into main
Reviewed-on: #13
2024-12-06 04:47:59 +00:00
iMax
4682f8a61e Hot fix - Marker.tsx 2024-12-06 05:46:34 +01:00
SwimJéjé
53853dcce5 Merge branch 'main' of ssh://git.leizour.fr:222/RedCrab/projet-nuitinfo-2024 into chaos_page 2024-12-06 05:40:57 +01:00
SwimJéjé
ea2bb7ac3c Second commit 2024-12-06 05:38:08 +01:00
SwimJéjé
e1bcb4b022 Chaos Commit 2024-12-06 05:35:47 +01:00
iMax
cbae2746fd Merge branch 'main_page' into main 2024-12-06 05:07:10 +01:00
iMax
12fa0684a8 Changement de nom d'application. 2024-12-06 05:04:36 +01:00
iMax
3794ccd70d Merge branch 'main_page' of ssh://git.leizour.fr:222/RedCrab/projet-nuitinfo-2024 into main_page 2024-12-06 04:53:32 +01:00
b252122272 Merged nia 2024-12-06 04:44:34 +01:00
iMax
5bb2ac3f9b - Mise à jour de la DA de la main page.
- Ajout de data temporaire pour les articles.
2024-12-06 04:43:17 +01:00
=
71a829e726 Edit hero banner 2024-12-06 04:06:07 +01:00
d6f2d6cc09 VALIDER PAR OLIVIER : Merge pull request 'better api and yyyy/mm/dd format for dates in the db' (#8) from back_end_id into main
Reviewed-on: #8
2024-12-06 02:58:19 +00:00
e42b81539f Merge branch 'main' into back_end_id 2024-12-06 02:57:42 +00:00
ec2322f04c better api and yyyy/mm/dd format for dates in the db 2024-12-06 03:52:12 +01:00
iMax
1de47f3fb8 Hot fix 2024-12-06 03:51:26 +01:00
iMax
17affb3297 Merge branch 'main' of ssh://git.leizour.fr:222/RedCrab/projet-nuitinfo-2024 into main_page 2024-12-06 03:50:44 +01:00
1e956a54d2 Merge pull request 'Game feature complete' (#7) from game into main
Reviewed-on: #7
2024-12-06 02:41:27 +00:00
9ee9ae8cb7 Merge branch 'main' into game 2024-12-06 02:40:26 +00:00
iMax
f8c4481c88 - Feature Complete -> Gray Boxing a faire. 2024-12-06 03:37:26 +01:00
iMax
60a12d9cf9 - Ajout d'un océan.
- Ajout d'un personnage manequin.
--> *Modèle 3D du manequin inclus*
- Ajout d'une représentation des axes x,y,z.
- Ajout d'une entité Marker pour mettre des markers sur le manequin.
- Maj du CSS de GamePage.tsx
2024-12-06 01:51:30 +01:00
iMax
5d156416c9 Recollage au main 2024-12-05 22:15:23 +01:00
iMax
358681da38 Merge branch 'main' into game 2024-12-05 22:12:59 +01:00
iMax
706d491236 - implémentation de threejs.
- Création environnement 3D océan.
2024-12-05 21:42:37 +01:00
26 changed files with 710 additions and 55 deletions

View file

@ -12,7 +12,9 @@ pub fn init() -> sqlite::Result<()> {
"CREATE TABLE IF NOT EXISTS articles (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL,
subTitle TEXT,
auteur TEXT,
edited_at DATE_FORMAT('now', '%YYYY-%mm-%dd'),
published_at DATE_FORMAT('now', '%YYYY-%mm-%dd'),
content TEXT NOT NULL
)",
)?;

View file

@ -1,7 +1,7 @@
mod create_db;
use create_db::init;
use actix_web::{App, HttpServer, get, Responder, HttpResponse, http::header::ContentType};
use actix_web::{App, web, HttpServer, get, Responder, HttpResponse, http::header::ContentType};
use actix_files::Files;
use serde_json::json;
use sqlite::{Connection, State};
@ -11,9 +11,9 @@ use serde::{Serialize, Deserialize};
struct Article {
id: i64,
title: String,
auteur: Option<String>,
edited_at: Option<String>,
published_at: Option<String>,
auteur: String,
edited_at: String,
published_at: String,
content: String,
}
@ -31,13 +31,13 @@ async fn get_articles() -> impl Responder {
while let State::Row = stmt.next().unwrap() {
let id = stmt.read::<i64, _>(0).unwrap();
let title = stmt.read::<String, _>(1).unwrap();
let content = stmt.read::<String, _>(3).unwrap();
let content = stmt.read::<String, _>(5).unwrap();
articles.push(Article {
id,
title,
auteur: None,
edited_at: None,
published_at: None,
auteur: "".to_string(),
edited_at: "".to_string(),
published_at: "".to_string(),
content
});
}
@ -45,6 +45,54 @@ async fn get_articles() -> impl Responder {
HttpResponse::Ok().json(articles)
}
#[get("/api/articles/{id}")]
async fn get_article(path: web::Path<i64>) -> impl Responder {
let id = path.into_inner();
// Open the database connection
let conn = match Connection::open("./data/data.db") {
Ok(conn) => conn,
Err(err) => {
eprintln!("Failed to connect to database: {}", err);
return HttpResponse::InternalServerError().body("Failed to connect to database");
}
};
// Fetch the article from the database
match fetch_article_by_id(&conn, id) {
Ok(Some(article)) => HttpResponse::Ok().json(article),
Ok(None) => HttpResponse::NotFound().body(format!("Article with ID {} not found", id)),
Err(err) => {
eprintln!("Database query error: {}", err);
HttpResponse::InternalServerError().body("Database query failed")
}
}
}
/// Fetches an article by its ID from the database.
fn fetch_article_by_id(conn: &Connection, id: i64) -> Result<Option<Article>, sqlite::Error> {
let mut stmt = conn.prepare(
"SELECT id, title, auteur, edited_at, published_at, content
FROM articles WHERE id = ?1"
)?;
stmt.bind((1, id))?;
let mut article = None;
while let State::Row = stmt.next()? {
article = Some(Article {
id: stmt.read::<i64, _>(0)?,
title: stmt.read::<String, _>(1)?,
auteur: stmt.read::<String, _>(2)?,
edited_at: stmt.read::<String, _>(3)?,
published_at: stmt.read::<String, _>(4)?,
content: stmt.read::<String, _>(5)?,
});
}
Ok(article)
}
#[get("/api")]
async fn api() -> impl Responder {
let value = json!({
@ -72,6 +120,7 @@ async fn main() -> Result<(), std::io::Error> {
.service(hello)
.service(get_articles)
.service(api)
.service(get_article)
.service(Files::new("/", "public").index_file("index.html"))
.service(Files::new("/game", "public").index_file("index.html"))
.service(Files::new("/chaos", "public").index_file("index.html"))

View file

@ -4,7 +4,8 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<title>EcoMarin</title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0&icon_names=sailing" />
</head>
<body>
<div id="root"></div>

View file

@ -10,9 +10,14 @@
"preview": "vite preview"
},
"dependencies": {
"@react-three/drei": "^9.120.0",
"@react-three/fiber": "^8.17.10",
"@types/three": "^0.170.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router": "^7.0.2",
"three": "^0.171.0",
"three-stdlib": "^2.34.0",
"react-router-dom": "^6.2.1"
},
"devDependencies": {

BIN
front/public/models/man.glb Normal file

Binary file not shown.

View file

@ -0,0 +1,29 @@
import { LineBasicMaterial, Line, Group } from 'three'
import * as THREE from 'three'
export default function Axes() {
const axes = new Group()
const x = new Line(
new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(0, 0, 0),
new THREE.Vector3(100, 0, 0)]),
new LineBasicMaterial({ color: 0xff0000 }) // red
)
const y = new Line(
new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(0, 0, 0),
new THREE.Vector3(0, 100, 0)]),
new LineBasicMaterial({ color: 0x00ff00 }) // green
)
const z = new Line(
new THREE.BufferGeometry().setFromPoints([new THREE.Vector3(0, 0, 0),
new THREE.Vector3(0, 0, 100)]),
new LineBasicMaterial({ color: 0x0000ff }) // blue
)
axes.add(x)
axes.add(y)
axes.add(z)
return <primitive object={axes} />
}

View file

@ -0,0 +1,24 @@
import { useGLTF } from '@react-three/drei'
export default function Character() {
// import glb file
// load the glb file in "/models/BASEmodel.glb"
const { scene } = useGLTF('/models/man.glb')
// rotate the character
scene.rotation.x = -Math.PI / 2
scene.rotation.y = 0
// enfoncer le personnage dans le sol
scene.position.y = -1
return (
<group>
<primitive object={scene} />
<directionalLight position={[0, 10, 0]} intensity={5} />
</group>
)
}

View file

@ -0,0 +1,18 @@
import { Group } from 'three'
import * as THREE from 'three'
export default function Floor() {
const floor = new Group()
const floorGeometry = new THREE.PlaneGeometry(100, 100, 100, 100)
const floorMaterial = new THREE.MeshStandardMaterial({ color: 0x00ff00, side: THREE.DoubleSide })
const floorMesh = new THREE.Mesh(floorGeometry, floorMaterial)
floorMesh.rotation.x = -Math.PI / 2
floorMesh.position.y = -2
floor.add(floorMesh)
return <primitive object={floor} />
}

View file

@ -0,0 +1,22 @@
import React from 'react'
import * as THREE from 'three'
interface MarkerProps {
position: number[],
color: string,
onClick?: () => void
}
export default function Marker({ position, color, onClick }: MarkerProps) {
const [positionState, setPositionState] = React.useState(new THREE.Vector3(...position))
// Return the marker object
// return <primitive object={marker} />
return (
<mesh position={positionState} rotation={[Math.PI,0,0]} onClick={onClick} onPointerOver={() => setPositionState(positionState.clone().setZ(positionState.z + 0.1))} onPointerOut={() => setPositionState(new THREE.Vector3(...position))}>
<coneGeometry args={[0.15, 0.6, 6]} />
<meshStandardMaterial color={color} side={THREE.DoubleSide} />
</mesh>
)
}

View file

@ -0,0 +1,52 @@
// src/components/Ocean.tsx
import React, { useEffect, useRef } from 'react';
import * as THREE from 'three';
import { Water, WaterOptions } from 'three/examples/jsm/objects/Water.js';
const Ocean: React.FC = () => {
const waterGeometry = new THREE.PlaneGeometry(10000, 10000);
const waterOption:WaterOptions = {
textureWidth: 512,
textureHeight: 512,
waterNormals: new THREE.TextureLoader().load('https://threejs.org/examples/textures/waternormals.jpg', function (texture) {
texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
}),
alpha: 0.9,
sunDirection: new THREE.Vector3(),
sunColor: 0xffffff,
waterColor: '#001e0f',
distortionScale: 3.7,
fog: true,
};
const water = new Water(waterGeometry, waterOption);
water.rotation.x = -Math.PI / 2;
water.position.y = -1;
const waterRef = useRef<THREE.Mesh>(null);
useEffect(() => {
if (waterRef.current) {
waterRef.current.add(water);
}
}, [waterRef]);
useEffect(() => {
const animate = () => {
requestAnimationFrame(animate);
water.material.uniforms['time'].value += 0.1 / 60.0;
};
animate();
}, []);
return (
<mesh ref={waterRef}>
<meshStandardMaterial />
</mesh>
);
};
export default Ocean;

View file

@ -13,7 +13,7 @@ export default function ArticleCard({ articlePreview }: { articlePreview: Articl
margin: '20px',
padding: '1.25em',
paddingBottom: '1em',
flex: '27%',
flex: '0 1 30%',
textAlign: 'left',
}}>
<img src="https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fimages8.alphacoders.com%2F438%2F438396.jpg&f=1&nofb=1&ipt=14796576c139b58db62d2d2098392ef2fbaee77911040f7c0d98ab65a69c4644&ipo=images" alt="Article preview" style={{

View file

@ -8,16 +8,48 @@ export default function ArticlesSection() {
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<string | null>(null);
const articles: ArticlePreview[] = [
{
id: 1,
title: "The Great Barrier Reef",
preview: "The Great Barrier Reef is the largest coral reef system in the world, composed of over 2,900 individual reefs and 900 islands stretching for over 2,300 kilometers (1,400 mi) over an area of approximately 344,400 square kilometers (133,000 sq mi).",
},
{
id: 2,
title: "The Great Pacific Garbage Patch",
preview: "The Great Pacific Garbage Patch, also known as the Pacific trash vortex, is a gyre of marine debris particles in the north-central Pacific Ocean. It is located roughly from 135°W to 155°W and 35°N to 42°N.",
},
{
id: 3,
title: "The Amazon Rainforest",
preview: "The Amazon rainforest, alternatively, the Amazon Jungle, also known in English as Amazonia, is a moist broadleaf tropical rainforest in the Amazon biome that covers most of the Amazon basin of South America.",
},
{
id: 4,
title: "The Arctic",
preview: "The Arctic is a polar region located at the northernmost part of Earth. The Arctic consists of the Arctic Ocean, adjacent seas, and parts of Alaska (United States), Northern Canada (Canada), Finland, Greenland (Kingdom of Denmark), Iceland, Norway, Russia, and Sweden.",
},
{
id: 5,
title: "The Antarctic",
preview: "The Antarctic, is a polar region containing the geographic South Pole and is situated in the Antarctic region of the Southern Hemisphere, almost entirely south of the Antarctic Circle, and is surrounded by the Southern Ocean.",
}
];
useEffect(() => {
fetch('/api/articles')
.then(response => response.json())
// .then(response => response.json())
.then(_ => articles)
.then((data: ArticlePreview[]) => setArticlePreviews(data))
.catch(_ => setError('Failed to fetch articles'))
.finally(() => setLoading(false));
}, []);
return (
<div className="row" style={{
<div className="row" id="articles" style={{
display: 'flex',
flexWrap: 'wrap',
justifyContent: 'space-around',
@ -37,9 +69,12 @@ export default function ArticlesSection() {
textAlign: 'center',
width: '100%',
}}>Articles</h1>
{articlePreviews.map(articlePreview => (
<ArticleCard key={articlePreview.id} articlePreview={articlePreview} />
))}
<div style={{width: '100%', display: 'flex', justifyContent: 'space-around', flexWrap: 'wrap'}}>
{articlePreviews.map(articlePreview => (
<ArticleCard key={articlePreview.id} articlePreview={articlePreview} />
))}
</div>
{!loading && !error && articlePreviews.length === 0 && <p style={{color: "var(--color-lightblue)", fontSize: 22}}>No articles found</p>}
{loading && <p style={{color: "var(--color-lightblue)", fontSize: 22}}>Loading...</p>}
{error && <p style={{color: "var(--color-lightblue)", fontSize: 22}}>{error}</p>}

View file

@ -1,14 +1,14 @@
import { ReactNode, MouseEventHandler } from 'react';
import { ReactNode } from 'react';
interface ButtonProps {
onClick: MouseEventHandler<HTMLButtonElement>;
color: 'primary' | 'secondary';
children: ReactNode;
url?: string;
}
export default function Button({ onClick, color, children }: ButtonProps) {
export default function Button({ color, children, url }: ButtonProps) {
return (
<button style={{
<a style={{
backgroundColor: color === 'primary' ? 'var(--color-yellow)' : 'var(--color-darkblue)',
borderRadius: '8px',
borderWidth: '0',
@ -29,7 +29,8 @@ export default function Button({ onClick, color, children }: ButtonProps) {
userSelect: 'none',
WebkitUserSelect: 'none',
touchAction: 'manipulation',
}} onClick={onClick} className="ni-button" role="button">{children}</button>
textDecoration: 'none',
}} href={url} className="ni-button" role="button">{children}</a>
)
}

View file

@ -9,7 +9,7 @@ export default function ClickableLink({url, text}: ClickableLinkProps) {
backgroundColor: "transparent",
borderRadius: '8px',
borderWidth: '0',
color:"purple",
color:"white",
cursor: 'pointer',
display: 'inline-block',
fontFamily: '"Haas Grot Text R Web", "Helvetica Neue", Helvetica, Arial, sans-serif',

View file

@ -7,12 +7,14 @@ interface FooterProps {
export default function Footer ({bgcolor}:FooterProps) {
return (
<footer style={{display: "flex", justifyContent: "space-between", backgroundColor:bgcolor}}>
<div style={{ display : "flex", alignItems: "center", flexDirection: "row"}}>ENSIBS<br />RedCRAB</div>
<div style={{ display : "flex", alignItems: "center", flexDirection: "row"}}>
<div style={{ display : "flex", alignItems: "center", flexDirection: "column", margin:"5px", width:"10%"}}><p>ENSIBS</p><p>RedCRAB</p></div>
<p>Made with by RedCRAB</p>
<div style={{ display : "flex", alignItems: "center", flexDirection: "row", margin:"5px", width:"10%"}}>
<LogoButton
url="https://www-ensibs.univ-ubs.fr/fr/index.html"
logo = "https://www-ensibs.univ-ubs.fr/skins/ENSIBS/resources/img/logo.png"
style={{ width: "50%", height: "auto" }}/>
style={{ width: "100%", height: "auto",
padding: "10px"}}/>
</div>
</footer>
)

View file

@ -1,19 +1,13 @@
import Button from "./Button";
import NavBar from "./NavBar";
import { Link } from "react-router";
interface FstSectionProps {
centertxt: string;
txtbt1:string;
txtbt2:string;
image:string;
}
export default function FstSection ({centertxt, txtbt1, txtbt2, image}: FstSectionProps) {
export default function FstSection () {
return (
<div style={{
textAlign: "center",
color: "yellow",
backgroundImage: image,
backgroundImage: "url('/pictures/sea.gif')",
backgroundSize: "cover",
backgroundPosition: "center",
backgroundRepeat: "no-repeat",
@ -28,10 +22,65 @@ export default function FstSection ({centertxt, txtbt1, txtbt2, image}: FstSecti
}}
>
<div style={{height:"150px"}} />
<h1>{centertxt}</h1><br />
<h1
style={{
fontSize: "3em",
fontWeight: "bold",
textShadow: "2px 2px 4px #000000",
fontFamily: "Helvetica",
}}
>Help us to save our oceans</h1><br />
<div style={{display:"flex", justifyContent: "center", gap: "20px"}}>
<Button onClick={() => alert("One")} color="primary" children={txtbt1} />
<Button onClick={() => {alert("Two")}} color="secondary" children={txtbt2} />
{/* <Button color="primary" children={"Aller au jeu"} url="/game" />
<Button color="secondary" children={"Lire les articles"} url="/" /> */}
<Link to="/game" style={{
backgroundColor: 'var(--color-yellow)',
borderRadius: '8px',
borderWidth: '0',
color: 'var(--color-black)',
cursor: 'pointer',
display: 'inline-block',
fontFamily: '"Haas Grot Text R Web", "Helvetica Neue", Helvetica, Arial, sans-serif',
fontSize: '20px',
fontWeight: '500',
lineHeight: '20px',
listStyle: 'none',
margin: '0',
padding: '10px 12px',
textAlign: 'center',
transition: 'all 200ms',
verticalAlign: 'baseline',
whiteSpace: 'nowrap',
userSelect: 'none',
WebkitUserSelect: 'none',
touchAction: 'manipulation',
textDecoration: 'none',
}}>Aller au jeu</Link>
<Link to="/articles" style={{
backgroundColor: 'var(--color-darkblue)',
borderRadius: '8px',
borderWidth: '0',
color: 'var(--color-white)',
cursor: 'pointer',
display: 'inline-block',
fontFamily: '"Haas Grot Text R Web", "Helvetica Neue", Helvetica, Arial, sans-serif',
fontSize: '20px',
fontWeight: '500',
lineHeight: '20px',
listStyle: 'none',
margin: '0',
padding: '10px 12px',
textAlign: 'center',
transition: 'all 200ms',
verticalAlign: 'baseline',
whiteSpace: 'nowrap',
userSelect: 'none',
WebkitUserSelect: 'none',
touchAction: 'manipulation',
textDecoration: 'none',
}}>Lire les articles</Link>
</div>
</div></div>
)

View file

@ -0,0 +1,78 @@
import React, { useState } from "react";
interface ModalProps {
state: "none" | "flex",
setState: (state: "none" | "flex") => void
question : {
question: string,
reponse_idx: number,
choix: string[]
},
valides?: [boolean[], React.Dispatch<React.SetStateAction<boolean[]>>],
markerIndex?: number
}
export default function Modal({ state, setState, question, valides, markerIndex }: ModalProps) {
const [isSuccess, setIsSuccess] = useState(false);
const [isSubmitted, setIsSubmitted] = useState(false);
console.log(state, setState, question)
return (
<div style={{
position: "fixed",
top: 0,
left: 0,
width: "100%",
height: "100%",
backgroundColor: "rgba(0, 0, 0, 0.5)",
justifyContent: "center",
alignItems: "center",
display: state
}}>
<div style={{
width: "40%",
height: "40%",
backgroundColor: "white",
borderRadius: 10,
padding: 20
}}>
{
isSuccess ? <p>CONGRATULATIONS</p> : isSubmitted && !isSuccess && <p>WRONG : CORRECT ANSWER IS {question.choix[question.reponse_idx]}</p>
}
<h2>{question.question}</h2>
<div style={{ display: "flex", flexDirection: "column" }}>
{
question.choix.map((choix, index) => (
<button key={index} onClick={() => {
setIsSubmitted(true);
if (index === question.reponse_idx) {
console.log("Correct")
setIsSuccess(true);
const newValides = [...valides![0]];
newValides[markerIndex!] = true;
valides![1](newValides);
} else {
console.log("Wrong")
setIsSuccess(false);
}
}}
style={{
backgroundColor: isSubmitted && index === question.reponse_idx ? "green" : isSubmitted && index !== question.reponse_idx ? "red" : "white",
color: isSubmitted && index === question.reponse_idx ? "white" : "black"
}}
disabled={isSubmitted}
>{choix}</button>
))
}
<div/>
<button onClick={() => {
setIsSubmitted(false);
setIsSuccess(false);
setState("none");
}}>Close</button>
</div>
</div>
</div>
)}

View file

@ -1,17 +1,20 @@
import LogoButton from '../components/LogoButton.tsx'
import ClickableLink from './ClickableLink.tsx';
import { Link } from 'react-router';
import RoundButton from './RoundButton.tsx';
export default function NavBar(){
return (
<nav style={{ display: "flex", alignItems: "center", justifyContent: "space-between"}}>
<nav style={{ display: "flex", alignItems: "center", justifyContent: "space-between", padding: "0.5em 1em"}}>
<div style={{ display : "flex", alignItems: "center", flexDirection: "row"}}>
<LogoButton url="/" logo = "https://archlinux.org/static/hetzner_logo.41114a37d25f.png"/>
<ClickableLink url="/" text = "Accueil" />
<ClickableLink url="/game" text = "Jeu" />
{/* <LogoButton url="/" logo = "https://archlinux.org/static/hetzner_logo.41114a37d25f.png"/> */}
<span className="material-symbols-outlined"
style={{fontSize: "4em", color: "white", margin: "0.5em"}}>sailing</span>
<Link to="/" style={{ textDecoration: "none", color: "white", fontSize: "1.5em", margin: "0.5em", fontFamily: '"Haas Grot Text R Web", "Helvetica Neue", Helvetica, Arial, sans-serif'}}>Home</Link>
<Link to="/game" style={{ textDecoration: "none", color: "white", fontSize: "1.5em", margin: "0.5em", fontFamily: '"Haas Grot Text R Web", "Helvetica Neue", Helvetica, Arial, sans-serif'}}>Game</Link>
<Link to="/articles" style={{ textDecoration: "none", color: "white", fontSize: "1.5em", margin: "0.5em", fontFamily: '"Haas Grot Text R Web", "Helvetica Neue", Helvetica, Arial, sans-serif'}}>Articles</Link>
<Link to="/chaos" style={{ textDecoration: "none", color: "white", fontSize: "1.5em", margin: "0.5em", fontFamily: '"Haas Grot Text R Web", "Helvetica Neue", Helvetica, Arial, sans-serif'}}>Le Chaos</Link>
</div>
<div style={{ display : "flex", alignItems: "center", flexDirection: "row"}}>
<RoundButton url="https://archlinux.org" bgcolor="purple" text="?"/>
<RoundButton url="https://www.apple.com/macos/macos-sequoia/" bgcolor="var(--color-white)" text="?"/>
</div>
</nav>);
}

View file

@ -8,8 +8,11 @@ export default function RoundButton({url, bgcolor, text}: RoundButtonProps) {
return (<div>
<a href={url}><button style={{
backgroundColor: bgcolor,
borderColor:"blue",
borderStyle: 'solid',
borderColor:"var(--color-lightblue)",
borderRadius: '50%',
width: '45px',
height: '45px',
borderWidth: '1',
cursor: 'pointer',
display: 'inline-block',

View file

@ -0,0 +1,16 @@
//import { useState } from "react";
interface MonInputProps {
text: string;
new_focus: () => void;
police: string;
}
export default function MonInput({text, new_focus, police}: MonInputProps) {
return (
<div>
<input readOnly value={text} onFocus={new_focus} style={{fontFamily: police,width:1000}}></input>
</div>
)
}

View file

@ -0,0 +1,26 @@
interface MonButtonProps {
letter: string;
changetext: (arg0: string) => void;
sizeFrontw: number;
sizeFronth: number;
rdmFront: () => void;
color: string;
}
export default function MonButton({letter,changetext,sizeFrontw,sizeFronth,rdmFront,color}: MonButtonProps) {
function clicked() {rdmFront();
changetext(letter)}
return (
<div>
<button className="key" onClick={clicked} style={{width:sizeFrontw,height:sizeFronth,background:color}}>{letter}</button>
</div>
)
}

View file

@ -0,0 +1,12 @@
#keys {
display: grid;
grid-template-columns: auto auto 1fr;
}
.key {
font-size: 10px;
border: solid black;
}

View file

@ -2,6 +2,8 @@ import { BrowserRouter, Route, Routes } from "react-router";
import { createRoot } from 'react-dom/client'
import MainPage from "./pages/MainPage.tsx";
import GamePage from "./pages/GamePage.tsx";
import ChaosPage from "./pages/ChaosPage.tsx";
import ArticlePage from "./pages/ArticlePage.tsx";
import './index.css'
@ -12,6 +14,7 @@ createRoot(document.getElementById('root')!).render(
<Route path="/" element={<MainPage />} />
// Game page
<Route path="/game" element={<GamePage />} />
<Route path="/chaos" element={<ChaosPage />} />
// Article page (dynamic route)
<Route path="/article/:id" element={<ArticlePage />} />
// Not found

View file

@ -0,0 +1,86 @@
import MonButton from "../components/chaos/monButton";
import MonInput from "../components/chaos/MonInput";
import "../components/chaos/style.css"
import { useState } from "react";
export default function ChaosPage(){
const [array_letter,setArray_letter]=useState(["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"," ","!"]);
const [sizeFrontw,setSizeFrontw] = useState(16)
const [sizeFronth,setSizeFronth] = useState(20)
const [color,setColor] = useState("#ffffff")
function randomFront(){setSizeFronth(Math.floor(Math.random() * (1000)));
setSizeFrontw(Math.floor(Math.random() * (1000)));
setColor(`#${Math.floor(Math.random() * 16777215).toString(16)}`)}
function shuffleArray(arr: string[]) {
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1)); // Choisir un index aléatoire
[arr[i], arr[j]] = [arr[j], arr[i]]; // Échanger les éléments
}
return arr;
}
// console.log(shuffledArray); // Affiche un tableau mélangé
const [entry1,setEntry1] = useState("")
function E1(ent:string) {
setEntry1(entry1+ent);
}
const [connarddefocus,setFocus] = useState(()=>E1)
const [entry2,setEntry2] = useState("")
function E2(ent:string) {
setEntry2(entry2+ent);
}
function changeFocus(E: (ent:string)=>void) {
setArray_letter(shuffleArray(array_letter));
setFocus(()=>E);
}
const [entry3,setEntry3] = useState("")
function E3(ent:string) {
setEntry3(entry3+ent);
}
const [tel,setTel] = useState(0)
return(
<div>
<h1>Chaos Page</h1>
<p>Quel est votre nom ?</p>
<MonInput text={entry1} new_focus={()=>changeFocus(E1)} police={""}/>
<p>Quel adjectif désigne le mieux Xi Junpin ?</p>
<MonInput text={entry2} new_focus={()=>changeFocus(E2)} police={""}/>
<p>Combien font 1+1 ?</p>
<MonInput text={entry3} new_focus={()=>changeFocus(E3)} police={"Wingdings"}/>
<div id = "keys">
{array_letter.map((letter) => {return <MonButton
letter={letter}
changetext={connarddefocus}
sizeFrontw={sizeFrontw}
sizeFronth={sizeFronth}
rdmFront={randomFront}
color={color}/> })}
</div>
<fieldset>
<legend>Formulaire super mega bien</legend>
<input type="text" placeholder="nom"/>
<input type="text" placeholder="prenom"/>
<input type="text" placeholder="email"/>
<input type="text" placeholder="mdp"/>
<input type="text" placeholder="mdp2"/>
<label htmlFor="phone">phone : {tel}</label>
<input type="range" min="0" max="9999999999" step="1" name="phone" id="phone" value={tel} onChange={(e)=>setTel(parseInt(e.target.value))}/>
<input type="submit" value="envoyer"/>
</fieldset>
</div>
)
}

View file

@ -1,9 +1,152 @@
import { Canvas } from "@react-three/fiber";
import { OrbitControls, PerspectiveCamera, Sky } from "@react-three/drei";
import { useEffect, useState } from "react";
import Ocean from "../components/3d/Ocean";
import Character from "../components/3d/Character";
import Floor from "../components/3d/Floor";
import Marker from "../components/3d/Marker";
import Modal from "../components/Modal";
import { randInt } from "three/src/math/MathUtils.js";
export default function GamePage() {
const [stateModal, setStateModal] = useState("none" as "none" | "flex");
const [questionIndex, setQuestionIndex] = useState(0);
const QuestionList = [
// Ensemble de questions sur la polution des océans
// {question:string, reponse_idx:int, choix:[string, string, string, string]}
{
question: "Quel est le plus grand océan du monde ?",
reponse_idx: 1,
choix: ["Océan Atlantique", "Océan Pacifique", "Océan Indien", "Océan Arctique"]
},
{
question: "Comment s'appelle les continents de plastique dans les océans ?",
reponse_idx: 2,
choix: ["Plastico", "Plastiland", "Plastiques", "Plastiques"]
},
{
question: "Quel est le nom de la grande masse d'eau salée qui recouvre la majorité de la surface de la Terre ?",
reponse_idx: 3,
choix: ["Mer", "Lac", "Océan", "Rivière"]
},
{
question: "Quel est l'océan le plus froid ?",
reponse_idx: 0,
choix: ["Océan Arctique", "Océan Antarctique", "Océan Pacifique", "Océan Atlantique"]
},
{
question: "Quel est l'océan le plus chaud ?",
reponse_idx: 1,
choix: ["Océan Arctique", "Océan Antarctique", "Océan Pacifique", "Océan Atlantique"]
},
{
question: "Quel est l'océan le plus profond ?",
reponse_idx: 1,
choix: ["Océan Arctique", "Océan Antarctique", "Océan Pacifique", "Océan Atlantique"]
},
{
question: "Quel est l'océan le plus grand ?",
reponse_idx: 2,
choix: ["Océan Arctique", "Océan Antarctique", "Océan Pacifique", "Océan Atlantique"]
}
]
const [valides, setValides] = useState([false, false, false, false, false, false]);
const [markerIndex, setMarkerIndex] = useState(0);
const askQuestion = (markerIndex: number) => {
setQuestionIndex(randInt(0, QuestionList.length - 1));
setStateModal("flex");
setMarkerIndex(markerIndex);
}
const MakerList = [
{ position: [0, -0.1, 0], onClick: () => askQuestion(0) }, // body
{ position: [-0.5, -0.1, 2.5], onClick: () => askQuestion(1) }, // left foot
{ position: [0.5, -0.1, 2.5], onClick: () => askQuestion(2) }, // right foot
{ position: [-1, -0.1, 1], onClick: () => askQuestion(3) }, // left hand
{ position: [1, -0.1, 1], onClick: () => askQuestion(4) }, // right hand
{ position: [0, -0.1, -1], onClick: () => askQuestion(5) }, // head
]
// Win condition
useEffect(() => {
if (valides.every(valide => valide)) {
}
}, [valides])
console.log(stateModal, setStateModal);
return (
<div>
<h1>Game Page</h1>
<div id="canvas-container"
style={{
width: "100%",
height: "100%",
position: "fixed",
top: 0,
left: 0,
zIndex: -1
}}
>
{ !valides.every(valide => valide) === true ?
<p
style={{
position: "relative",
top: 0,
left: 0,
width: "100%",
height: "5%",
backgroundColor: "rgba(0, 0, 0, 1)",
color: "white",
display: "flex",
justifyContent: "center",
alignItems: "center",
margin: 0
}}
> <strong>Hint : </strong> Répondez correctement aux questions pour sauver le personnage</p>
: <p
style={{
position: "relative",
top: 0,
left: 0,
width: "100%",
height: "5%",
backgroundColor: "rgba(0, 0, 0, 1)",
color: "white",
display: "flex",
justifyContent: "center",
alignItems: "center",
margin: 0
}}
> <strong>Good Game : </strong> Bravo, vous avez sauvé le personnage</p>
}
<Canvas>
<Sky sunPosition={[100, 10, 100]} />
<OrbitControls />
<Ocean />
{/* <Axes /> */}
<Character />
<Floor />
{/* <Marker position={[0, 1, 0]} color="red" /> */}
{MakerList.map((marker, index) => (
<Marker key={index} position={marker.position} color={valides[index] ? "green" : "red"} onClick={marker.onClick} />
))}
<PerspectiveCamera makeDefault position={[0, 10, 3]} />
</Canvas>
<Modal state={stateModal} setState={setStateModal} question={QuestionList[questionIndex]} valides={[valides, setValides]} markerIndex={markerIndex} />
</div>
)
}

View file

@ -6,11 +6,7 @@ export default function MainPage() {
return (
<>
<div style={{ display: 'flex', flexDirection: 'column', minHeight: '100vh' }}>
<FstSection
centertxt="Choisissez votre numéro :"
txtbt1="Un"
txtbt2="Deux"
image="url('/pictures/sea.gif')"/>
<FstSection />
<ArticlesSection />
<Footer bgcolor="lightblue"/>
</div>