diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..2a6677a Binary files /dev/null and b/.DS_Store differ diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile deleted file mode 100644 index 2fe2d42..0000000 --- a/.devcontainer/Dockerfile +++ /dev/null @@ -1,9 +0,0 @@ -# Start from the universal image -FROM mcr.microsoft.com/devcontainers/universal:2 - -# Install openbox -RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ - && apt-get -y install --no-install-recommends openbox - -# Set openbox to start automatically -RUN echo 'openbox-session &' >> ~/.x()initrc \ No newline at end of file diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 401bffb..0000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,33 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/java -{ - "name": "Java", - // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile - "image": "mcr.microsoft.com/devcontainers/java:1-21-bullseye", - - "build": { - "dockerfile": "Dockerfile" - }, - - "features": { - "ghcr.io/devcontainers/features/java:1": {}, - "ghcr.io/devcontainers-extra/features/ant-sdkman:2": {}, - "ghcr.io/devcontainers/features/desktop-lite:1": { - "password": "vscode", - "webPort": "6080", - "vncPort": "5901" - } - } - - // Use 'forwardPorts' to make a list of ports inside the container available locally. - // "forwardPorts": [], - - // Use 'postCreateCommand' to run commands after the container is created. - // "postCreateCommand": "java -version", - - // Configure tool-specific properties. - // "customizations": {}, - - // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. - // "remoteUser": "root" -} diff --git a/nextjs-frontend/.env.example b/nextjs-frontend/.env.example new file mode 100644 index 0000000..261f85a --- /dev/null +++ b/nextjs-frontend/.env.example @@ -0,0 +1 @@ +NEXT_PUBLIC_WS_URI= \ No newline at end of file diff --git a/nextjs-frontend/.gitignore b/nextjs-frontend/.gitignore new file mode 100644 index 0000000..8dd70cc --- /dev/null +++ b/nextjs-frontend/.gitignore @@ -0,0 +1,44 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env +.env.local +.env.production +.env.development + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/nextjs-frontend/README.md b/nextjs-frontend/README.md new file mode 100644 index 0000000..e215bc4 --- /dev/null +++ b/nextjs-frontend/README.md @@ -0,0 +1,36 @@ +This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). + +## Getting Started + +First, run the development server: + +```bash +npm run dev +# or +yarn dev +# or +pnpm dev +# or +bun dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. + +This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. + +## Learn More + +To learn more about Next.js, take a look at the following resources: + +- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. +- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. + +You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! + +## Deploy on Vercel + +The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. + +Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/nextjs-frontend/bun.lockb b/nextjs-frontend/bun.lockb new file mode 100755 index 0000000..edd4df3 Binary files /dev/null and b/nextjs-frontend/bun.lockb differ diff --git a/nextjs-frontend/components.json b/nextjs-frontend/components.json new file mode 100644 index 0000000..d710b49 --- /dev/null +++ b/nextjs-frontend/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "src/app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/nextjs-frontend/eslint.config.mjs b/nextjs-frontend/eslint.config.mjs new file mode 100644 index 0000000..c85fb67 --- /dev/null +++ b/nextjs-frontend/eslint.config.mjs @@ -0,0 +1,16 @@ +import { dirname } from "path"; +import { fileURLToPath } from "url"; +import { FlatCompat } from "@eslint/eslintrc"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const compat = new FlatCompat({ + baseDirectory: __dirname, +}); + +const eslintConfig = [ + ...compat.extends("next/core-web-vitals", "next/typescript"), +]; + +export default eslintConfig; diff --git a/nextjs-frontend/next.config.ts b/nextjs-frontend/next.config.ts new file mode 100644 index 0000000..e9ffa30 --- /dev/null +++ b/nextjs-frontend/next.config.ts @@ -0,0 +1,7 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + /* config options here */ +}; + +export default nextConfig; diff --git a/nextjs-frontend/package.json b/nextjs-frontend/package.json new file mode 100644 index 0000000..c9ecf54 --- /dev/null +++ b/nextjs-frontend/package.json @@ -0,0 +1,36 @@ +{ + "name": "front", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev --turbopack", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@radix-ui/react-slot": "^1.2.3", + "@types/ws": "^8.18.1", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.476.0", + "next": "15.4.7", + "react": "^19.1.1", + "react-dom": "^19.1.1", + "react-fetch-streams": "^1.1.2", + "tailwind-merge": "^3.3.1", + "tailwindcss-animate": "^1.0.7", + "ws": "^8.18.3" + }, + "devDependencies": { + "typescript": "^5.9.2", + "@types/node": "^24.3.0", + "@types/react": "^19.1.10", + "@types/react-dom": "^19.1.7", + "postcss": "^8.5.6", + "tailwindcss": "^3.4.17", + "eslint": "^9.33.0", + "eslint-config-next": "15.4.7", + "@eslint/eslintrc": "^3.3.1" + } +} diff --git a/nextjs-frontend/postcss.config.mjs b/nextjs-frontend/postcss.config.mjs new file mode 100644 index 0000000..1a69fd2 --- /dev/null +++ b/nextjs-frontend/postcss.config.mjs @@ -0,0 +1,8 @@ +/** @type {import('postcss-load-config').Config} */ +const config = { + plugins: { + tailwindcss: {}, + }, +}; + +export default config; diff --git a/nextjs-frontend/public/file.svg b/nextjs-frontend/public/file.svg new file mode 100644 index 0000000..004145c --- /dev/null +++ b/nextjs-frontend/public/file.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nextjs-frontend/public/globe.svg b/nextjs-frontend/public/globe.svg new file mode 100644 index 0000000..567f17b --- /dev/null +++ b/nextjs-frontend/public/globe.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nextjs-frontend/public/next.svg b/nextjs-frontend/public/next.svg new file mode 100644 index 0000000..5174b28 --- /dev/null +++ b/nextjs-frontend/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nextjs-frontend/public/vercel.svg b/nextjs-frontend/public/vercel.svg new file mode 100644 index 0000000..7705396 --- /dev/null +++ b/nextjs-frontend/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nextjs-frontend/public/window.svg b/nextjs-frontend/public/window.svg new file mode 100644 index 0000000..b2b2a44 --- /dev/null +++ b/nextjs-frontend/public/window.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/nextjs-frontend/simulation_data.json b/nextjs-frontend/simulation_data.json new file mode 100644 index 0000000..93e0ad6 --- /dev/null +++ b/nextjs-frontend/simulation_data.json @@ -0,0 +1,124 @@ +{ + "foodValueForAnimals": 1, + "foodValueForPlants": 0.1, + "animalHungerDrain": 0.0045, + "animalBreedingCost": 0.2, + "mutationFactor": 0.2, + "entityAgeRate": 0.5, + "fieldScaleFactor": 0.55, + "doDayNightCycle": true, + "dayNightCycleSpeed": 0.1, + "doWeatherCycle": true, + "weatherChangeProbability": 1, + "windStrength": 0.1, + "stormMovementSpeedFactor": 0.5, + "showQuadTrees": true, + "animalHungerThreshold": 0.5, + "animalDyingOfHungerThreshold": 0.1, + "predatorsData": [ + { + "name": "Fox", + "multiplyingRate": [0.01, 0.15], + "maxLitterSize": [1, 3], + "maxAge": [120, 180], + "matureAge": [60, 80], + "mutationRate": [0.01, 0.05], + "maxSpeed": [4, 6.5], + "sight": [30, 45], + "numberOfEntitiesAtStart": 8, + "eats": ["Rabbit"], + "size": [3, 6], + "colour": [230, 20, 40], + "overcrowdingThreshold": [8, 25], + "overcrowdingRadius": [10, 15], + "maxOffspringSpawnDistance": [3, 5] + }, + { + "name": "Wolf", + "multiplyingRate": [0.01, 0.1], + "maxLitterSize": [1, 3], + "maxAge": [160, 220], + "matureAge": [70, 100], + "mutationRate": [0.01, 0.05], + "maxSpeed": [6, 7], + "sight": [25, 50], + "numberOfEntitiesAtStart": 8, + "eats": ["Rabbit", "Squirrel"], + "size": [4, 7], + "colour": [70, 10, 90], + "overcrowdingThreshold": [8, 25], + "overcrowdingRadius": [10, 15], + "maxOffspringSpawnDistance": [3, 4] + }, + { + "name": "Bear", + "multiplyingRate": [0.03, 0.05], + "maxLitterSize": [2, 3], + "maxAge": [160, 200], + "matureAge": [100, 120], + "mutationRate": [0.01, 0.05], + "maxSpeed": [3,4.5], + "sight": [55, 80], + "numberOfEntitiesAtStart": 4, + "eats": ["Wolf", "Fox"], + "size": [6, 9], + "colour": [74, 25, 25], + "overcrowdingThreshold": [8, 25], + "overcrowdingRadius": [10, 15], + "maxOffspringSpawnDistance": [3, 4] + } + ], + "preysData": [ + { + "name": "Rabbit", + "multiplyingRate": [0.3, 0.5], + "maxLitterSize": [4, 8], + "maxAge": [65, 100], + "matureAge": [20, 30], + "mutationRate": [0.01, 0.05], + "maxSpeed": [3.5, 4.5], + "sight": [25, 35], + "numberOfEntitiesAtStart": 25, + "eats": ["Grass"], + "size": [3, 5], + "colour": [40, 200, 240], + "overcrowdingThreshold": [5, 15], + "overcrowdingRadius": [10, 12], + "maxOffspringSpawnDistance": [3, 4] + }, + { + "name": "Squirrel", + "multiplyingRate": [0.1, 0.4], + "maxLitterSize": [2, 5], + "maxAge": [85, 105], + "matureAge": [50, 60], + "mutationRate": [0.01, 0.05], + "maxSpeed": [3.5, 5], + "sight": [20, 40], + "numberOfEntitiesAtStart": 16, + "eats": ["Grass"], + "size": [3, 6], + "colour": [40, 20, 200], + "overcrowdingThreshold": [10, 25], + "overcrowdingRadius": [10, 15], + "maxOffspringSpawnDistance": [3, 5] + } + ], + "plantsData": [ + { + "name": "Grass", + "mutationRate": [0.1, 0.12], + "multiplyingRate": [0.1, 0.2], + "numberOfSeeds": [2, 3], + "maxAge": [40, 60], + "matureAge": [30, 40], + "numberOfEntitiesAtStart": 200, + "size": [3, 7], + "colour": [40, 240, 30], + "maxOffspringSpawnDistance": [2, 35], + "overcrowdingThreshold": [2, 8], + "overcrowdingRadius": [4, 25], + "rainingGrowthFactor" : 4 + } + ] +} \ No newline at end of file diff --git a/nextjs-frontend/src/app/favicon.ico b/nextjs-frontend/src/app/favicon.ico new file mode 100644 index 0000000..718d6fe Binary files /dev/null and b/nextjs-frontend/src/app/favicon.ico differ diff --git a/nextjs-frontend/src/app/globals.css b/nextjs-frontend/src/app/globals.css new file mode 100644 index 0000000..87ce2cc --- /dev/null +++ b/nextjs-frontend/src/app/globals.css @@ -0,0 +1,121 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + + + +@layer base { + :root { + + --background: 0 0% 100%; + + --foreground: 0 0% 3.9%; + + --card: 0 0% 100%; + + --card-foreground: 0 0% 3.9%; + + --popover: 0 0% 100%; + + --popover-foreground: 0 0% 3.9%; + + --primary: 0 0% 9%; + + --primary-foreground: 0 0% 98%; + + --secondary: 0 0% 96.1%; + + --secondary-foreground: 0 0% 9%; + + --muted: 0 0% 96.1%; + + --muted-foreground: 0 0% 45.1%; + + --accent: 0 0% 96.1%; + + --accent-foreground: 0 0% 9%; + + --destructive: 0 84.2% 60.2%; + + --destructive-foreground: 0 0% 98%; + + --border: 0 0% 89.8%; + + --input: 0 0% 89.8%; + + --ring: 0 0% 3.9%; + + --chart-1: 12 76% 61%; + + --chart-2: 173 58% 39%; + + --chart-3: 197 37% 24%; + + --chart-4: 43 74% 66%; + + --chart-5: 27 87% 67%; + + --radius: 0.5rem + } + .dark { + + --background: 0 0% 3.9%; + + --foreground: 0 0% 98%; + + --card: 0 0% 3.9%; + + --card-foreground: 0 0% 98%; + + --popover: 0 0% 3.9%; + + --popover-foreground: 0 0% 98%; + + --primary: 0 0% 98%; + + --primary-foreground: 0 0% 9%; + + --secondary: 0 0% 14.9%; + + --secondary-foreground: 0 0% 98%; + + --muted: 0 0% 14.9%; + + --muted-foreground: 0 0% 63.9%; + + --accent: 0 0% 14.9%; + + --accent-foreground: 0 0% 98%; + + --destructive: 0 62.8% 30.6%; + + --destructive-foreground: 0 0% 98%; + + --border: 0 0% 14.9%; + + --input: 0 0% 14.9%; + + --ring: 0 0% 83.1%; + + --chart-1: 220 70% 50%; + + --chart-2: 160 60% 45%; + + --chart-3: 30 80% 55%; + + --chart-4: 280 65% 60%; + + --chart-5: 340 75% 55% + } +} + + + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/nextjs-frontend/src/app/layout.tsx b/nextjs-frontend/src/app/layout.tsx new file mode 100644 index 0000000..f0e0175 --- /dev/null +++ b/nextjs-frontend/src/app/layout.tsx @@ -0,0 +1,34 @@ +import type { Metadata } from 'next'; +import { Geist, Geist_Mono } from 'next/font/google'; +import '@/app/globals.css'; + +const geistSans = Geist({ + variable: "--font-geist-sans", + subsets: ["latin"], +}); + +const geistMono = Geist_Mono({ + variable: "--font-geist-mono", + subsets: ["latin"], +}); + +export const metadata: Metadata = { + title: "The Simulation", + description: "Woo this simulation and web application is awesome!", +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + {children} + + + ); +} diff --git a/nextjs-frontend/src/app/page.tsx b/nextjs-frontend/src/app/page.tsx new file mode 100644 index 0000000..a2f9b0b --- /dev/null +++ b/nextjs-frontend/src/app/page.tsx @@ -0,0 +1,19 @@ +'use client'; + +import { useState } from 'react'; + +import { CanvasComponent } from '@/components/canvasComponent'; +import { StartSimulationButton } from '@/components/startSimulationButton'; +import { StopSimulationButton } from '@/components/stopSimulationButton'; + +export default function Home() { + const [ws, setWs] = useState(null); + + return ( +
+ {ws && } + {!ws && } + {ws && } +
+ ); +} diff --git a/nextjs-frontend/src/components/canvasComponent.tsx b/nextjs-frontend/src/components/canvasComponent.tsx new file mode 100644 index 0000000..edf652e --- /dev/null +++ b/nextjs-frontend/src/components/canvasComponent.tsx @@ -0,0 +1,153 @@ +import { useRef, useEffect, useState } from 'react'; + +import { DisplayData, DrawCircleData, DrawEqualTriangleData, DrawLineData, DrawRectData, DrawTextData, DrawTransparentRectData, FillData } from '@/lib/schema'; + +export function CanvasComponent({ + ws, +}: { + ws: WebSocket; +}) { + const canvasRef = useRef(null); + const canvasCtxRef = useRef(null); + const [data, setData] = useState(null); + + ws.onmessage = (event) => { + try { + const apiData = JSON.parse(event.data); + setData(apiData); + } catch (e) { + console.error('Failed to parse ws message', e); + } + }; + + useEffect(() => { + if (data === null || !canvasRef.current || data == undefined || !data.d) return; + canvasCtxRef.current = canvasRef.current.getContext('2d'); + const context = canvasCtxRef.current; + if (!context) return; + + // Go through all the items in the data + while (true) { + let minId = 9999999; + let minKey = ""; + const keys = Object.keys(data.d) as (keyof typeof data.d)[]; + for (const key of keys) { + const len = data.d[key].d.length; + if (len === 0) continue; + const itemId = data.d[key].d[0] as number; + if (itemId < minId) { + minId = itemId; + minKey = key; + } + } + + if (minKey === "") break; + + if (minKey === "f") { + const fillData: FillData[] = data.d.f.d.splice(0, getLengthFromKey("f")); + fill(context, data.w, data.h, fillData); + } else if (minKey === "c") { + const circleData: DrawCircleData[] = data.d.c.d.splice(0, getLengthFromKey("c")); + drawCircle(context, circleData); + } else if (minKey === "e") { + const equalTriangleData: DrawEqualTriangleData[] = data.d.e.d.splice(0, getLengthFromKey("e")); + drawEqualTriangle(context, equalTriangleData); + } else if (minKey === "l") { + const lineData: DrawLineData[] = data.d.l.d.splice(0, getLengthFromKey("l")); + drawLine(context, lineData); + } else if (minKey === "r") { + const rectData: DrawRectData[] = data.d.r.d.splice(0, getLengthFromKey("r")); + drawRectangle(context, rectData); + } else if (minKey === "t") { + const textData: DrawTextData[] = data.d.t.d.splice(0, getLengthFromKey("t")); + drawText(context, textData); + } else if (minKey === "a") { + const transparentRectData: DrawTransparentRectData[] = data.d.a.d.splice(0, getLengthFromKey("a")); + drawTransparentRectangle(context, transparentRectData); + } else { + console.error("unknown key: ", minKey); + } + } + }, [data]); + + return ( + <> + {data !== null && } + + ) +} + +function getLengthFromKey(key: string) { + if (key === "f") return 4; + if (key === "c") return 7; + if (key === "e") return 7; + if (key === "l") return 8; + if (key === "r") return 9; + if (key === "t") return 8; + if (key === "a") return 9; + return 0; +} + +function fill(context: CanvasRenderingContext2D, width: number, height: number, d: FillData[]) { + context.fillStyle = `rgb(${d[1]}, ${d[2]}, ${d[3]})`; + context.fillRect(0, 0, width, height); +} + +function drawCircle(context: CanvasRenderingContext2D, d: DrawCircleData[]) { + context.fillStyle = `rgb(${d[1]}, ${d[2]}, ${d[3]})`; + context.beginPath(); + context.arc(d[4], d[5], d[6], 0, 2 * Math.PI); + context.closePath(); + context.fill(); +} + +function drawEqualTriangle(context: CanvasRenderingContext2D, d: DrawEqualTriangleData[]) { + context.fillStyle = `rgb(${d[1]}, ${d[2]}, ${d[3]})`; + + const xPoints: number[] = []; + const yPoints: number[] = []; + + for (let i = 0; i < 3; i++) { + const angle = Math.PI / 2 + i * 2 * Math.PI / 3; + xPoints[i] = d[4] + (d[6] * Math.cos(angle)); + yPoints[i] = d[5] - (d[6] * Math.sin(angle)); + } + + context.beginPath(); + context.moveTo(xPoints[0], yPoints[0]); + context.lineTo(xPoints[1], yPoints[1]); + context.lineTo(xPoints[2], yPoints[2]); + context.closePath(); + context.fill(); +} + +function drawRectangle(context: CanvasRenderingContext2D, d: DrawRectData[]) { + context.fillStyle = `rgb(${d[1]}, ${d[2]}, ${d[3]})`; + context.strokeStyle = `rgb(${d[1]}, ${d[2]}, ${d[3]})`; + + if (d[8]) { + context.fillRect(d[4] as number, d[5] as number, d[6] as number, d[7] as number); + } else { + context.strokeRect(d[4] as number, d[5] as number, d[6] as number, d[7] as number); + } +} + +function drawTransparentRectangle(context: CanvasRenderingContext2D, d: DrawTransparentRectData[]) { + context.fillStyle = `rgba(${d[1]}, ${d[2]}, ${d[3]}, ${d[8]})`; + context.fillRect(d[4], d[5], d[6], d[7]); +} + +function drawLine(context: CanvasRenderingContext2D, d: DrawLineData[]) { + context.strokeStyle = `rgb(${d[1]}, ${d[2]}, ${d[3]})`; + context.beginPath(); + context.moveTo(d[4], d[5]); + context.lineTo(d[6], d[7]); + context.closePath(); + context.stroke(); +} + +function drawText(context: CanvasRenderingContext2D, d: DrawTextData[]) { + context.fillStyle = `rgb(${d[1]}, ${d[2]}, ${d[3]})`; + context.font = `${d[5]}px sans-serif`; + context.fillText(d[4] as string, d[6] as number, d[7] as number); +} \ No newline at end of file diff --git a/nextjs-frontend/src/components/startSimulationButton.tsx b/nextjs-frontend/src/components/startSimulationButton.tsx new file mode 100644 index 0000000..5ce1e06 --- /dev/null +++ b/nextjs-frontend/src/components/startSimulationButton.tsx @@ -0,0 +1,24 @@ +import { Button } from '@/components/ui/button'; +import simulationData from '@/../simulation_data.json'; + +export function StartSimulationButton({ + setWs, +}: { + setWs: React.Dispatch>; +}) { + return ( + + ); +} \ No newline at end of file diff --git a/nextjs-frontend/src/components/stopSimulationButton.tsx b/nextjs-frontend/src/components/stopSimulationButton.tsx new file mode 100644 index 0000000..246636f --- /dev/null +++ b/nextjs-frontend/src/components/stopSimulationButton.tsx @@ -0,0 +1,18 @@ +import { Button } from '@/components/ui/button'; + +export function StopSimulationButton({ + setWs, + ws, +}: { + setWs: React.Dispatch>; + ws: WebSocket, +}) { + return ( + + ); +} \ No newline at end of file diff --git a/nextjs-frontend/src/components/ui/button.tsx b/nextjs-frontend/src/components/ui/button.tsx new file mode 100644 index 0000000..36496a2 --- /dev/null +++ b/nextjs-frontend/src/components/ui/button.tsx @@ -0,0 +1,56 @@ +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button" + return ( + + ) + } +) +Button.displayName = "Button" + +export { Button, buttonVariants } diff --git a/nextjs-frontend/src/lib/schema.ts b/nextjs-frontend/src/lib/schema.ts new file mode 100644 index 0000000..0d83e04 --- /dev/null +++ b/nextjs-frontend/src/lib/schema.ts @@ -0,0 +1,34 @@ +export type DisplayData = { + w: number, + h: number, + d: { + f: { d: FillData[] }, + c: { d: DrawCircleData[] }, + e: { d: DrawEqualTriangleData[] }, + l: { d: DrawLineData[] }, + r: { d: DrawRectData[] }, + t: { d: DrawTextData[] }, + a: { d: DrawTransparentRectData[] }, + }, +} | null; + +// Index, Red, Green, Blue +export type FillData = number; + +// Index, Red, Green, Blue, X, Y, Radius +export type DrawCircleData = number; + +// Index, Red, Green, Blue, X, Y, Radius +export type DrawEqualTriangleData = number; + +// Index, Red, Green, Blue, X1, Y1, X2, Y2 +export type DrawLineData = number; + +// Index, Red, Green, Blue, X, Y, Width, Height, Filled +export type DrawRectData = number | boolean; + +// Index, Red, Green, Blue, Text, Font Size, X, Y +export type DrawTextData = number | string; + +// Index, Red, Green, Blue, X, Y, Width, Height, Alpha +export type DrawTransparentRectData = number; \ No newline at end of file diff --git a/nextjs-frontend/src/lib/utils.ts b/nextjs-frontend/src/lib/utils.ts new file mode 100644 index 0000000..bd0c391 --- /dev/null +++ b/nextjs-frontend/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/nextjs-frontend/tailwind.config.ts b/nextjs-frontend/tailwind.config.ts new file mode 100644 index 0000000..266c06d --- /dev/null +++ b/nextjs-frontend/tailwind.config.ts @@ -0,0 +1,62 @@ +import type { Config } from "tailwindcss"; + +export default { + darkMode: ["class"], + content: [ + "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", + "./src/components/**/*.{js,ts,jsx,tsx,mdx}", + "./src/app/**/*.{js,ts,jsx,tsx,mdx}", + ], + theme: { + extend: { + colors: { + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + card: { + DEFAULT: 'hsl(var(--card))', + foreground: 'hsl(var(--card-foreground))' + }, + popover: { + DEFAULT: 'hsl(var(--popover))', + foreground: 'hsl(var(--popover-foreground))' + }, + primary: { + DEFAULT: 'hsl(var(--primary))', + foreground: 'hsl(var(--primary-foreground))' + }, + secondary: { + DEFAULT: 'hsl(var(--secondary))', + foreground: 'hsl(var(--secondary-foreground))' + }, + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))' + }, + accent: { + DEFAULT: 'hsl(var(--accent))', + foreground: 'hsl(var(--accent-foreground))' + }, + destructive: { + DEFAULT: 'hsl(var(--destructive))', + foreground: 'hsl(var(--destructive-foreground))' + }, + border: 'hsl(var(--border))', + input: 'hsl(var(--input))', + ring: 'hsl(var(--ring))', + chart: { + '1': 'hsl(var(--chart-1))', + '2': 'hsl(var(--chart-2))', + '3': 'hsl(var(--chart-3))', + '4': 'hsl(var(--chart-4))', + '5': 'hsl(var(--chart-5))' + } + }, + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)' + } + } + }, + plugins: [require("tailwindcss-animate")], +} satisfies Config; diff --git a/nextjs-frontend/tsconfig.json b/nextjs-frontend/tsconfig.json new file mode 100644 index 0000000..c133409 --- /dev/null +++ b/nextjs-frontend/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +}