refactor: add react frontend

This commit is contained in:
Alex Wellnitz 2024-09-19 21:19:11 +02:00
parent fd17e4fe6b
commit 7ee3ead05b
46 changed files with 7119 additions and 0 deletions

7
backend/.dockerignore Normal file
View File

@ -0,0 +1,7 @@
profiles/
results/
__pycache__/
.venv/
tests/
simc
simcrunner_export.simc

7
backend/.gitignore vendored Normal file
View File

@ -0,0 +1,7 @@
.venv/
__pycache__/
results/
profiles/
simc/
simc
simcrunner_export.simc

21
backend/Dockerfile Normal file
View File

@ -0,0 +1,21 @@
FROM simulationcraftorg/simc:latest
ENV LANG=de_DE.UTF-8
RUN apk add --no-cache python3 py3-pip git
ENV VIRTUAL_ENV=/opt/venv
RUN python3 -m venv $VIRTUAL_ENV
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
# Install dependencies:
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 8000
ENTRYPOINT []
CMD ["fastapi", "run", "main.py", "--port", "8000"]

18
backend/README.md Normal file
View File

@ -0,0 +1,18 @@
# SIM Free
## Description
### Installation
```bash
git clone https://github.com/alexohneander/sim_free
cd sim_free
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
### Run
```bash
fastapi run main.py
```

104
backend/main.py Normal file
View File

@ -0,0 +1,104 @@
import os
import logging
import uuid
import time
import functools
from pathlib import Path
from simcrunner import Simc, JsonExport, Arguments, Profile
from typing import Union, Annotated
from fastapi import FastAPI, Form
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
from starlette.responses import FileResponse, HTMLResponse
from simcrunner.simc import HtmlExport
# SIMC Settings
logging.basicConfig(level=logging.INFO)
# simc_path = os.path.join('tests', 'simc')
simc_path = "./"
app = FastAPI()
origins = [
"http://localhost",
"http://localhost:3000",
"https://sim-free.dev-null.rocks",
"*"
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.mount("/_next", StaticFiles(directory="templates/_next"), name="static")
app.mount("/_next/image", StaticFiles(directory="templates/_next"), name="static")
app.mount("/img", StaticFiles(directory="templates/img"), name="static")
# ROUTES
@app.get("/")
def read_root():
index_path = os.path.join('templates', 'index.html')
return FileResponse(index_path)
@app.post("/sim/current_gear")
def simulate_current_gear(simcprofile: Annotated[str, Form()]):
profile_path = create_sim_arguments(simcprofile)
export_path = create_html_export()
html_export = HtmlExport(export_path)
profile = Profile(profile_path)
args = Arguments(profile, iterations=1000)
runner = Simc(simc_path=simc_path)
(runner
.add_args(args)
.add_args('target_error=0.05', threads=4)
.add_args(html_export)
.run())
# response = FileResponse(export_path)
# remove_temp_files(profile_path, export_path)
return FileResponse(export_path)
# HELPER Functions
def create_profile(profile_path: str, profile_data: str):
with open(profile_path, 'w') as file:
# Write content to the file
file.write(profile_data)
def create_html_export():
rand_uuid = uuid.uuid4()
export_path = os.path.join('results', str(rand_uuid)+'.html')
return export_path
def create_sim_arguments(profile_data: str):
rand_uuid = uuid.uuid4()
profile_path = os.path.join('profiles', str(rand_uuid)+'.simc')
create_profile(profile_path, profile_data)
return profile_path
def remove_temp_files(profile_path: str, export_path: str):
time.sleep(1)
os.remove(export_path)
os.remove(profile_path)
@functools.lru_cache(maxsize=2)
def read_file_with_lru_cache(file_path):
# Read the file content
with open(file_path, 'r', encoding="utf-8") as file:
file_content = file.read()
print(f"Reading from file: {file_path}")
return file_content

2
backend/requirements.txt Normal file
View File

@ -0,0 +1,2 @@
fastapi[standard]==0.115.0
git+https://github.com/simcminmax/simcrunner.git

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
self.__BUILD_MANIFEST={__rewrites:{afterFiles:[],beforeFiles:[],fallback:[]},"/_error":["static/chunks/pages/_error-1be831200e60c5c0.js"],sortedPages:["/_app","/_error"]},self.__BUILD_MANIFEST_CB&&self.__BUILD_MANIFEST_CB();

View File

@ -0,0 +1 @@
self.__SSG_MANIFEST=new Set([]);self.__SSG_MANIFEST_CB&&self.__SSG_MANIFEST_CB()

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[409],{7589:function(e,t,n){(window.__NEXT_P=window.__NEXT_P||[]).push(["/_not-found/page",function(){return n(5457)}])},5457:function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),Object.defineProperty(t,"default",{enumerable:!0,get:function(){return s}}),n(9920);let i=n(7437);n(2265);let o={fontFamily:'system-ui,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji"',height:"100vh",textAlign:"center",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center"},l={display:"inline-block"},r={display:"inline-block",margin:"0 20px 0 0",padding:"0 23px 0 0",fontSize:24,fontWeight:500,verticalAlign:"top",lineHeight:"49px"},d={fontSize:14,fontWeight:400,lineHeight:"49px",margin:0};function s(){return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)("title",{children:"404: This page could not be found."}),(0,i.jsx)("div",{style:o,children:(0,i.jsxs)("div",{children:[(0,i.jsx)("style",{dangerouslySetInnerHTML:{__html:"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}),(0,i.jsx)("h1",{className:"next-error-h1",style:r,children:"404"}),(0,i.jsx)("div",{style:l,children:(0,i.jsx)("h2",{style:d,children:"This page could not be found."})})]})})]})}("function"==typeof t.default||"object"==typeof t.default&&null!==t.default)&&void 0===t.default.__esModule&&(Object.defineProperty(t.default,"__esModule",{value:!0}),Object.assign(t.default,t),e.exports=t.default)}},function(e){e.O(0,[971,23,744],function(){return e(e.s=7589)}),_N_E=e.O()}]);

View File

@ -0,0 +1 @@
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[185],{5825:function(e,a,_){Promise.resolve().then(_.t.bind(_,7800,23)),Promise.resolve().then(_.t.bind(_,2544,23)),Promise.resolve().then(_.t.bind(_,3054,23))},3054:function(){},2544:function(e){e.exports={style:{fontFamily:"'__geistMono_c3aa02', '__geistMono_Fallback_c3aa02'"},className:"__className_c3aa02",variable:"__variable_c3aa02"}},7800:function(e){e.exports={style:{fontFamily:"'__geistSans_1e4310', '__geistSans_Fallback_1e4310'"},className:"__className_1e4310",variable:"__variable_1e4310"}}},function(e){e.O(0,[571,971,23,744],function(){return e(e.s=5825)}),_N_E=e.O()}]);

View File

@ -0,0 +1 @@
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[931],{1603:function(e,t,r){Promise.resolve().then(r.t.bind(r,8173,23)),Promise.resolve().then(r.t.bind(r,9671,23)),Promise.resolve().then(r.bind(r,436))},436:function(e,t,r){"use strict";r.d(t,{SimCurrentGear:function(){return c}});var a=r(7437),s=r(2265),n=r(9671),o=r.n(n);function c(){let[e,t]=(0,s.useState)(!1),[r,n]=(0,s.useState)("");async function c(e){try{let r=await fetch("http://127.0.0.1:8000/sim/current_gear",{method:"POST",headers:{"Access-Control-Allow-Origin":"*"},body:e});if(r.ok)t(!0),n(await r.text());else throw Error("Error! status: ".concat(r.status))}catch(e){e instanceof Error?console.log(e.message):console.log("Unexpected error",e)}}return(0,a.jsxs)("div",{children:[(0,a.jsx)("iframe",{id:"renderframe",style:{display:"".concat(e?"block":"none"),width:"100%",height:"100vh",marginBottom:"50px"},srcDoc:"".concat(r)}),(0,a.jsxs)("form",{action:c,children:[(0,a.jsx)("div",{className:o().ctas,children:(0,a.jsx)("textarea",{className:o().textarea,rows:10,id:"simcprofile",name:"simcprofile",style:{display:"".concat(e?"none":"block")}})}),(0,a.jsxs)("div",{className:o().ctas,children:[(0,a.jsx)("button",{className:o().primary,type:"submit",children:"Run Sim"}),(0,a.jsx)("a",{href:"https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app",target:"_blank",rel:"noopener noreferrer",className:o().secondary,children:"Read our docs"})]})]})]})}},9671:function(e){e.exports={page:"page_page__ZU32B",main:"page_main__GlU4n",ctas:"page_ctas__g5wGe",textarea:"page_textarea__8C2wx",primary:"page_primary__V8M9Y",secondary:"page_secondary__lm_PT",footer:"page_footer__sHKi3",logo:"page_logo__7fc9l"}}},function(e){e.O(0,[967,173,971,23,744],function(){return e(e.s=1603)}),_N_E=e.O()}]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[744],{400:function(e,n,t){Promise.resolve().then(t.t.bind(t,5751,23)),Promise.resolve().then(t.t.bind(t,6513,23)),Promise.resolve().then(t.t.bind(t,6130,23)),Promise.resolve().then(t.t.bind(t,9275,23)),Promise.resolve().then(t.t.bind(t,5324,23)),Promise.resolve().then(t.t.bind(t,1343,23))}},function(e){var n=function(n){return e(e.s=n)};e.O(0,[971,23],function(){return n(1028),n(400)}),_N_E=e.O()}]);

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[888],{1597:function(n,_,u){(window.__NEXT_P=window.__NEXT_P||[]).push(["/_app",function(){return u(2239)}])}},function(n){var _=function(_){return n(n.s=_)};n.O(0,[774,179],function(){return _(1597),_(6036)}),_N_E=n.O()}]);

View File

@ -0,0 +1 @@
(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[820],{1981:function(n,_,u){(window.__NEXT_P=window.__NEXT_P||[]).push(["/_error",function(){return u(3387)}])}},function(n){n.O(0,[888,774,179],function(){return n(n.s=1981)}),_N_E=n.O()}]);

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
!function(){"use strict";var e,t,n,r,o,u,i,c,f,a={},l={};function d(e){var t=l[e];if(void 0!==t)return t.exports;var n=l[e]={exports:{}},r=!0;try{a[e](n,n.exports,d),r=!1}finally{r&&delete l[e]}return n.exports}d.m=a,e=[],d.O=function(t,n,r,o){if(n){o=o||0;for(var u=e.length;u>0&&e[u-1][2]>o;u--)e[u]=e[u-1];e[u]=[n,r,o];return}for(var i=1/0,u=0;u<e.length;u++){for(var n=e[u][0],r=e[u][1],o=e[u][2],c=!0,f=0;f<n.length;f++)i>=o&&Object.keys(d.O).every(function(e){return d.O[e](n[f])})?n.splice(f--,1):(c=!1,o<i&&(i=o));if(c){e.splice(u--,1);var a=r();void 0!==a&&(t=a)}}return t},d.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return d.d(t,{a:t}),t},n=Object.getPrototypeOf?function(e){return Object.getPrototypeOf(e)}:function(e){return e.__proto__},d.t=function(e,r){if(1&r&&(e=this(e)),8&r||"object"==typeof e&&e&&(4&r&&e.__esModule||16&r&&"function"==typeof e.then))return e;var o=Object.create(null);d.r(o);var u={};t=t||[null,n({}),n([]),n(n)];for(var i=2&r&&e;"object"==typeof i&&!~t.indexOf(i);i=n(i))Object.getOwnPropertyNames(i).forEach(function(t){u[t]=function(){return e[t]}});return u.default=function(){return e},d.d(o,u),o},d.d=function(e,t){for(var n in t)d.o(t,n)&&!d.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},d.f={},d.e=function(e){return Promise.all(Object.keys(d.f).reduce(function(t,n){return d.f[n](e,t),t},[]))},d.u=function(e){},d.miniCssF=function(e){},d.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r={},o="_N_E:",d.l=function(e,t,n,u){if(r[e]){r[e].push(t);return}if(void 0!==n)for(var i,c,f=document.getElementsByTagName("script"),a=0;a<f.length;a++){var l=f[a];if(l.getAttribute("src")==e||l.getAttribute("data-webpack")==o+n){i=l;break}}i||(c=!0,(i=document.createElement("script")).charset="utf-8",i.timeout=120,d.nc&&i.setAttribute("nonce",d.nc),i.setAttribute("data-webpack",o+n),i.src=d.tu(e)),r[e]=[t];var s=function(t,n){i.onerror=i.onload=null,clearTimeout(p);var o=r[e];if(delete r[e],i.parentNode&&i.parentNode.removeChild(i),o&&o.forEach(function(e){return e(n)}),t)return t(n)},p=setTimeout(s.bind(null,void 0,{type:"timeout",target:i}),12e4);i.onerror=s.bind(null,i.onerror),i.onload=s.bind(null,i.onload),c&&document.head.appendChild(i)},d.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},d.tt=function(){return void 0===u&&(u={createScriptURL:function(e){return e}},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(u=trustedTypes.createPolicy("nextjs#bundler",u))),u},d.tu=function(e){return d.tt().createScriptURL(e)},d.p="/_next/",i={272:0,571:0,967:0},d.f.j=function(e,t){var n=d.o(i,e)?i[e]:void 0;if(0!==n){if(n)t.push(n[2]);else if(/^(272|571|967)$/.test(e))i[e]=0;else{var r=new Promise(function(t,r){n=i[e]=[t,r]});t.push(n[2]=r);var o=d.p+d.u(e),u=Error();d.l(o,function(t){if(d.o(i,e)&&(0!==(n=i[e])&&(i[e]=void 0),n)){var r=t&&("load"===t.type?"missing":t.type),o=t&&t.target&&t.target.src;u.message="Loading chunk "+e+" failed.\n("+r+": "+o+")",u.name="ChunkLoadError",u.type=r,u.request=o,n[1](u)}},"chunk-"+e,e)}}},d.O.j=function(e){return 0===i[e]},c=function(e,t){var n,r,o=t[0],u=t[1],c=t[2],f=0;if(o.some(function(e){return 0!==i[e]})){for(n in u)d.o(u,n)&&(d.m[n]=u[n]);if(c)var a=c(d)}for(e&&e(t);f<o.length;f++)r=o[f],d.o(i,r)&&i[r]&&i[r][0](),i[r]=0;return d.O(a)},(f=self.webpackChunk_N_E=self.webpackChunk_N_E||[]).forEach(c.bind(null,0)),f.push=c.bind(null,f.push.bind(f))}();

View File

@ -0,0 +1 @@
@font-face{font-family:__geistSans_1e4310;src:url(/_next/static/media/4473ecc91f70f139-s.p.woff) format("woff");font-display:swap;font-weight:100 900}@font-face{font-family:__geistSans_Fallback_1e4310;src:local("Arial");ascent-override:85.83%;descent-override:20.52%;line-gap-override:9.33%;size-adjust:107.19%}.__className_1e4310{font-family:__geistSans_1e4310,__geistSans_Fallback_1e4310}.__variable_1e4310{--font-geist-sans:"__geistSans_1e4310","__geistSans_Fallback_1e4310"}@font-face{font-family:__geistMono_c3aa02;src:url(/_next/static/media/463dafcda517f24f-s.p.woff) format("woff");font-display:swap;font-weight:100 900}@font-face{font-family:__geistMono_Fallback_c3aa02;src:local("Arial");ascent-override:69.97%;descent-override:16.73%;line-gap-override:7.61%;size-adjust:131.49%}.__className_c3aa02{font-family:__geistMono_c3aa02,__geistMono_Fallback_c3aa02}.__variable_c3aa02{--font-geist-mono:"__geistMono_c3aa02","__geistMono_Fallback_c3aa02"}:root{--background:#fff;--foreground:#171717}@media (prefers-color-scheme:dark){:root{--background:#0a0a0a;--foreground:#ededed}}body,html{max-width:100vw;overflow-x:hidden}body{color:var(--foreground);background:var(--background);font-family:Arial,Helvetica,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}*{box-sizing:border-box;padding:0;margin:0}a{color:inherit;text-decoration:none}@media (prefers-color-scheme:dark){html{color-scheme:dark}}

View File

@ -0,0 +1 @@
.page_page__ZU32B{--gray-rgb:0,0,0;--gray-alpha-200:rgba(var(--gray-rgb),0.08);--gray-alpha-100:rgba(var(--gray-rgb),0.05);--button-primary-hover:#383838;--button-secondary-hover:#f2f2f2;display:grid;grid-template-rows:20px 1fr 20px;align-items:center;justify-items:center;min-height:100svh;padding:80px;grid-gap:64px;gap:64px;font-family:var(--font-geist-sans)}@media (prefers-color-scheme:dark){.page_page__ZU32B{--gray-rgb:255,255,255;--gray-alpha-200:rgba(var(--gray-rgb),0.145);--gray-alpha-100:rgba(var(--gray-rgb),0.06);--button-primary-hover:#ccc;--button-secondary-hover:#1a1a1a}}.page_main__GlU4n{display:flex;flex-direction:column;gap:32px;grid-row-start:2}.page_main__GlU4n ol{font-family:var(--font-geist-mono);padding-left:0;margin:0;font-size:14px;line-height:24px;letter-spacing:-.01em;list-style-position:inside}.page_main__GlU4n li:not(:last-of-type){margin-bottom:8px}.page_main__GlU4n code{font-family:inherit;background:var(--gray-alpha-100);padding:2px 4px;border-radius:4px;font-weight:600}.page_ctas__g5wGe{display:flex;gap:16px}.page_ctas__g5wGe a,.page_ctas__g5wGe button{-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:128px;height:48px;padding:0 20px;border:1px solid transparent;transition:background .2s,color .2s,border-color .2s;cursor:pointer;display:flex;align-items:center;justify-content:center;font-size:16px;line-height:20px;font-weight:500}.page_ctas__g5wGe .page_textarea__8C2wx{width:100%;margin-bottom:40px}a.page_primary__V8M9Y,button.page_primary__V8M9Y{background:var(--foreground);color:var(--background);gap:8px}a.page_secondary__lm_PT{border-color:var(--gray-alpha-200);min-width:180px}.page_footer__sHKi3{grid-row-start:3;display:flex;gap:24px}.page_footer__sHKi3 a{display:flex;align-items:center;gap:8px}.page_footer__sHKi3 img{flex-shrink:0}@media (hover:hover) and (pointer:fine){a.page_primary__V8M9Y:hover{background:var(--button-primary-hover);border-color:transparent}a.page_secondary__lm_PT:hover{background:var(--button-secondary-hover);border-color:transparent}.page_footer__sHKi3 a:hover{text-decoration:underline;text-underline-offset:4px}}@media (max-width:600px){.page_page__ZU32B{padding:32px 32px 80px}.page_main__GlU4n{align-items:center}.page_main__GlU4n ol{text-align:center}.page_ctas__g5wGe{flex-direction:column}.page_ctas__g5wGe a{font-size:14px;height:40px;padding:0 16px}a.page_secondary__lm_PT{min-width:auto}.page_footer__sHKi3{flex-wrap:wrap;align-items:center;justify-content:center}}.page_logo__7fc9l{margin:auto}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,7 @@
2:I[436,["173","static/chunks/173-4f078887a7bfcdfc.js","931","static/chunks/app/page-7811c68757d4c4c5.js"],"SimCurrentGear"]
3:I[8173,["173","static/chunks/173-4f078887a7bfcdfc.js","931","static/chunks/app/page-7811c68757d4c4c5.js"],"Image"]
4:I[9275,[],""]
5:I[1343,[],""]
0:["6eAreke72TFTOl_oq5LbI",[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",{"children":["__PAGE__",{},[["$L1",["$","div",null,{"className":"page_page__ZU32B","children":[["$","main",null,{"className":"page_main__GlU4n","children":[["$","img",null,{"className":"page_logo__7fc9l","src":"/img/warcraft-icon-22.png","alt":"Sim-Free Logo","width":150,"height":150}],["$","ol",null,{"children":[["$","li",null,{"children":["Copy/paste the text from the SimulationCraft addon. ",["$","a",null,{"className":"page_primary__V8M9Y","target":"_blank","href":"https://github.com/simulationcraft/simc-addon","children":"How to install and use the SimC addon"}]]}],["$","li",null,{"children":"Select pieces of gear and Sim-Free will sim them"}]]}],["$","$L2",null,{}]]}],["$","footer",null,{"className":"page_footer__sHKi3","children":[["$","a",null,{"href":"https://github.com/alexohneander/sim_free/issues/new","target":"_blank","rel":"noopener noreferrer","children":[["$","$L3",null,{"aria-hidden":true,"src":"https://nextjs.org/icons/file.svg","alt":"File icon","width":16,"height":16}],"Issues"]}],["$","a",null,{"href":"https://github.com/alexohneander/sim_free/","target":"_blank","rel":"noopener noreferrer","children":[["$","$L3",null,{"aria-hidden":true,"src":"https://nextjs.org/icons/window.svg","alt":"Window icon","width":16,"height":16}],"Project"]}],["$","a",null,{"href":"https://alexohneander.de","target":"_blank","rel":"noopener noreferrer","children":[["$","$L3",null,{"aria-hidden":true,"src":"https://nextjs.org/icons/globe.svg","alt":"Globe icon","width":16,"height":16}],"Go to alexohneander.de →"]}]]}]]}],[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/b5daad47fe07b32d.css","precedence":"next","crossOrigin":"$undefined"}]]],null],null]},[[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/9de320161955c026.css","precedence":"next","crossOrigin":"$undefined"}]],["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__variable_1e4310 __variable_c3aa02","children":["$","$L4",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L5",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[]}]}]}]],null],null],["$L6",null]]]]
6:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"SimC-Free"}],["$","meta","3",{"name":"description","content":"Generated by create next app"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}],["$","meta","5",{"name":"next-size-adjust"}]]
1:null

3
frontend/.eslintrc.json Normal file
View File

@ -0,0 +1,3 @@
{
"extends": ["next/core-web-vitals", "next/typescript"]
}

36
frontend/.gitignore vendored Normal file
View File

@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

36
frontend/README.md Normal file
View File

@ -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.

6
frontend/next.config.mjs Normal file
View File

@ -0,0 +1,6 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
output: "export",
};
export default nextConfig;

6384
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

29
frontend/package.json Normal file
View File

@ -0,0 +1,29 @@
{
"name": "frontend",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@chakra-ui/next-js": "^2.2.0",
"@chakra-ui/react": "^2.8.2",
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"framer-motion": "^11.5.5",
"next": "14.2.12",
"react": "^18",
"react-dom": "^18"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "14.2.12",
"typescript": "^5"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,42 @@
:root {
--background: #ffffff;
--foreground: #171717;
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}
html,
body {
max-width: 100vw;
overflow-x: hidden;
}
body {
color: var(--foreground);
background: var(--background);
font-family: Arial, Helvetica, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
a {
color: inherit;
text-decoration: none;
}
@media (prefers-color-scheme: dark) {
html {
color-scheme: dark;
}
}

View File

@ -0,0 +1,33 @@
import type { Metadata } from "next";
import localFont from "next/font/local";
import "./globals.css";
const geistSans = localFont({
src: "./fonts/GeistVF.woff",
variable: "--font-geist-sans",
weight: "100 900",
});
const geistMono = localFont({
src: "./fonts/GeistMonoVF.woff",
variable: "--font-geist-mono",
weight: "100 900",
});
export const metadata: Metadata = {
title: "SimC-Free",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body className={`${geistSans.variable} ${geistMono.variable}`}>
{children}
</body>
</html>
);
}

View File

@ -0,0 +1,184 @@
.page {
--gray-rgb: 0, 0, 0;
--gray-alpha-200: rgba(var(--gray-rgb), 0.08);
--gray-alpha-100: rgba(var(--gray-rgb), 0.05);
--button-primary-hover: #383838;
--button-secondary-hover: #f2f2f2;
display: grid;
grid-template-rows: 20px 1fr 20px;
align-items: center;
justify-items: center;
min-height: 100svh;
padding: 80px;
gap: 64px;
font-family: var(--font-geist-sans);
}
@media (prefers-color-scheme: dark) {
.page {
--gray-rgb: 255, 255, 255;
--gray-alpha-200: rgba(var(--gray-rgb), 0.145);
--gray-alpha-100: rgba(var(--gray-rgb), 0.06);
--button-primary-hover: #ccc;
--button-secondary-hover: #1a1a1a;
}
}
.main {
display: flex;
flex-direction: column;
gap: 32px;
grid-row-start: 2;
}
.main ol {
font-family: var(--font-geist-mono);
padding-left: 0;
margin: 0;
font-size: 14px;
line-height: 24px;
letter-spacing: -0.01em;
list-style-position: inside;
}
.main li:not(:last-of-type) {
margin-bottom: 8px;
}
.main code {
font-family: inherit;
background: var(--gray-alpha-100);
padding: 2px 4px;
border-radius: 4px;
font-weight: 600;
}
.ctas {
display: flex;
gap: 16px;
}
.ctas a,
.ctas button {
appearance: none;
border-radius: 128px;
height: 48px;
padding: 0 20px;
border: none;
border: 1px solid transparent;
transition:
background 0.2s,
color 0.2s,
border-color 0.2s;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
line-height: 20px;
font-weight: 500;
}
.ctas .textarea {
width: 100%;
margin-bottom: 40px;
}
a.primary {
background: var(--foreground);
color: var(--background);
gap: 8px;
}
button.primary {
background: var(--foreground);
color: var(--background);
gap: 8px;
}
a.secondary {
border-color: var(--gray-alpha-200);
min-width: 180px;
}
.footer {
grid-row-start: 3;
display: flex;
gap: 24px;
}
.footer a {
display: flex;
align-items: center;
gap: 8px;
}
.footer img {
flex-shrink: 0;
}
/* Enable hover only on non-touch devices */
@media (hover: hover) and (pointer: fine) {
a.primary:hover {
background: var(--button-primary-hover);
border-color: transparent;
}
a.secondary:hover {
background: var(--button-secondary-hover);
border-color: transparent;
}
.footer a:hover {
text-decoration: underline;
text-underline-offset: 4px;
}
}
@media (max-width: 600px) {
.page {
padding: 32px;
padding-bottom: 80px;
}
.main {
align-items: center;
}
.main ol {
text-align: center;
}
.ctas {
flex-direction: column;
}
.ctas a {
font-size: 14px;
height: 40px;
padding: 0 16px;
}
a.secondary {
min-width: auto;
}
.footer {
flex-wrap: wrap;
align-items: center;
justify-content: center;
}
}
.logo {
margin: auto;
}
/* @media (prefers-color-scheme: dark) {
.logo {
filter: invert();
}
} */

78
frontend/src/app/page.tsx Normal file
View File

@ -0,0 +1,78 @@
import Image from "next/image";
import styles from "./page.module.css";
import { SimCurrentGear } from "./ui/forms/simCurrentGear";
export default function Home() {
return (
<div className={styles.page}>
<main className={styles.main}>
<img
className={styles.logo}
src={`/img/warcraft-icon-22.png`}
alt="Sim-Free Logo"
width={150}
height={150}
// priority
/>
<ol>
<li>
Copy/paste the text from the SimulationCraft addon. {}
<a
className={styles.primary}
target="_blank"
href="https://github.com/simulationcraft/simc-addon"
>
How to install and use the SimC addon
</a>
</li>
<li>Select pieces of gear and Sim-Free will sim them</li>
</ol>
<SimCurrentGear />
</main>
<footer className={styles.footer}>
<a
href="https://github.com/alexohneander/sim_free/issues/new"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="https://nextjs.org/icons/file.svg"
alt="File icon"
width={16}
height={16}
/>
Issues
</a>
<a
href="https://github.com/alexohneander/sim_free/"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="https://nextjs.org/icons/window.svg"
alt="Window icon"
width={16}
height={16}
/>
Project
</a>
<a
href="https://alexohneander.de"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="https://nextjs.org/icons/globe.svg"
alt="Globe icon"
width={16}
height={16}
/>
Go to alexohneander.de
</a>
</footer>
</div>
);
}

View File

@ -0,0 +1,76 @@
"use client";
import { useState } from "react";
import styles from "../../page.module.css";
export function SimCurrentGear() {
const [isFetched, setIsFetched] = useState(false);
const [fetchedData, setFetchedData] = useState("");
async function fetchSimResult(formData: FormData) {
try {
const response = await fetch("http://127.0.0.1:8000/sim/current_gear", {
method: "POST",
headers: {
"Access-Control-Allow-Origin": "*",
},
body: formData,
});
if (!response.ok) {
throw new Error(`Error! status: ${response.status}`);
} else {
setIsFetched(true);
setFetchedData(await response.text());
}
} catch (err) {
if (err instanceof Error) {
// ✅ TypeScript knows err is Error
console.log(err.message);
} else {
console.log("Unexpected error", err);
}
}
}
return (
<div>
<iframe
id="renderframe"
style={{
display: `${isFetched ? "block" : "none"}`,
width: "100%",
height: "100vh",
marginBottom: "50px",
}}
srcDoc={`${fetchedData}`}
/>
<form action={fetchSimResult}>
<div className={styles.ctas}>
<textarea
className={styles.textarea}
rows={10}
id="simcprofile"
name="simcprofile"
style={{
display: `${isFetched ? "none" : "block"}`,
}}
/>
</div>
<div className={styles.ctas}>
<button className={styles.primary} type="submit">
Run Sim
</button>
<a
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
className={styles.secondary}
>
Read our docs
</a>
</div>
</form>
</div>
);
}

26
frontend/tsconfig.json Normal file
View File

@ -0,0 +1,26 @@
{
"compilerOptions": {
"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"]
}