13 Commits

12 changed files with 3500 additions and 17 deletions

80
.github/workflows/deploy-pages.yml vendored Normal file
View File

@@ -0,0 +1,80 @@
name: Deploy WebAssembly to GitHub Pages
on:
push:
branches:
- rust
workflow_dispatch: {}
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: true
jobs:
build:
name: Build WebAssembly
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-unknown-unknown
- name: Cache cargo registry and git
uses: actions/cache@v4
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
key: ${{ runner.os }}-cargo-${{ hashFiles('schafkopf-logic/Cargo.toml') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Cache trunk build artifacts
uses: actions/cache@v4
with:
path: |
~/.cache/trunk/
schafkopf-logic/target/wasm-bindgen/
schafkopf-logic/target/wasm-opt/
schafkopf-logic/target/wasm32-unknown-unknown/
~/.cache/.wasm-pack/
~/.cache/wasm-pack/
key: ${{ runner.os }}-trunk-${{ hashFiles('schafkopf-logic/Cargo.toml') }}
restore-keys: |
${{ runner.os }}-trunk-
- name: Install trunk
run: cargo install trunk --locked --force
- name: Build with trunk
working-directory: schafkopf-logic
run: |
trunk build --release --public-url "/${{ github.event.repository.name }}/"
- name: Upload Pages artifact
uses: actions/upload-pages-artifact@v3
with:
path: schafkopf-logic/dist
deploy:
name: Deploy to GitHub Pages
runs-on: ubuntu-latest
needs: build
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy
id: deployment
uses: actions/deploy-pages@v4

4
schafkopf-logic/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
target
Cargo.lock
shell.nix
dist

View File

@@ -0,0 +1,11 @@
[package]
name = "schafkopf-game"
version = "0.1.0"
edition = "2024"
[dependencies]
schafkopf-logic = "0.1.0"
bevy = { version = "0.17", features = ["png", "default_font"] }
[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.3", features = ["wasm_js"] }

View File

@@ -0,0 +1,7 @@
[build]
dist = "dist"
release = true
[serve]
open = false
port = 8080

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Schafkopf Logic</title>
<link data-trunk rel="rust" href="Cargo.toml" />
<link data-trunk rel="copy-dir" href="assets" />
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body {
width: 100%;
height: 100%;
overflow: hidden;
}
canvas {
display: block;
width: 100vw;
height: 100vh;
background-color: #000;
}
</style>
</head>
<body></body>
</html>

1146
schafkopf-logic/src/main.rs Normal file

File diff suppressed because it is too large Load Diff

2075
schafkopf-os/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,3 +2,12 @@
name = "schafkopf-os" name = "schafkopf-os"
version = "0.1.0" version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies]
tokio = { version = "1", features = ["full"] }
octocrab = { version = "0.47"}
axum = "0.8.6"
askama = "0.14.0"
tower-http = { version = "0.6", features = ["fs", "compression-full", "set-header"] }
tower = "0.5.2"
http = "1.3.1"

View File

@@ -1,3 +1,134 @@
fn main() { use octocrab::{Octocrab, Result};
println!("Hello, world!");
use http::{header, HeaderValue};
use axum::{
routing::{get},
http::StatusCode,
Json, Router,
};
use axum::extract::Path;
use axum::response::IntoResponse;
use axum::response::Response;
use http::HeaderMap;
use tower_http::{
services::{ServeDir},
};
use tower_http::set_header::SetResponseHeaderLayer;
use tower::ServiceBuilder;
use askama::Template;
#[tokio::main]
async fn main() -> Result<()> {
let static_service = ServiceBuilder::new()
.layer(SetResponseHeaderLayer::if_not_present(
header::CACHE_CONTROL,
HeaderValue::from_static("no-cache, no-store, must-revalidate"),
))
.layer(SetResponseHeaderLayer::if_not_present(
header::PRAGMA,
HeaderValue::from_static("no-cache"),
))
.layer(SetResponseHeaderLayer::if_not_present(
header::EXPIRES,
HeaderValue::from_static("0"),
))
.service(ServeDir::new("static"));
// build our application with a route
let app: Router = Router::new()
.route("/api", get(root))
.route("/", get(index))
.route("/api/firmware/{tag}", get(download_firmware))
.nest_service("/static", static_service);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
axum::serve(listener, app).await.unwrap();
Ok(())
}
async fn root() -> Result<Json<Vec<String>>, StatusCode> {
let gh = Octocrab::builder().build().map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let owner = "Vale54321";
let repo = "schafkop-neu";
let mut tags: Vec<String> = vec![];
let page = gh.repos(owner, repo).releases().list().per_page(100).send().await.map_err(|_| StatusCode::BAD_GATEWAY)?;
for release in &page.items {
tags.push(release.tag_name.clone());
}
Ok(Json(tags))
}
async fn index() -> impl axum::response::IntoResponse {
let index = Index {
title: "Schafkopf OS",
message: "Welcome to Schafkopf OS!",
version: app_version(),
};
axum::response::Html(index.render().unwrap())
}
pub fn app_version() -> &'static str {
env!("CARGO_PKG_VERSION")
}
#[derive(Template)]
#[template(path = "index.html")]
struct Index<'a> {
title: &'a str,
message: &'a str,
version: &'a str,
}
async fn download_firmware(
Path(tag): Path<String>,
) -> std::result::Result<Response, StatusCode> {
let gh = Octocrab::builder()
.build()
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
let owner = "Vale54321";
let repo = "schafkop-neu";
// Get the release for the provided tag
let release = gh
.repos(owner, repo)
.releases()
.get_by_tag(&tag)
.await
.map_err(|_| StatusCode::BAD_GATEWAY)?;
// List assets for the release
let assets = gh
.repos(owner, repo)
.releases()
.assets(release.id.0)
.per_page(100)
.send()
.await
.map_err(|_| StatusCode::BAD_GATEWAY)?;
// Find the "firmware.uf2" asset
let Some(asset) = assets.items.into_iter().find(|a| a.name == "firmware.uf2") else {
return Err(StatusCode::NOT_FOUND);
};
let location = asset.browser_download_url.to_string();
let mut headers = HeaderMap::new();
headers.insert(
header::LOCATION,
HeaderValue::from_str(&location).map_err(|_| StatusCode::BAD_GATEWAY)?,
);
// 302 Found with Location header
let resp = (StatusCode::FOUND, headers).into_response();
Ok(resp)
} }

View File

@@ -1,22 +1,10 @@
const btn = document.getElementById('pingBtn'); const btn = document.getElementById('pingBtn');
const versionBtn = document.getElementById('versionBtn');
const out = document.getElementById('result'); const out = document.getElementById('result');
btn?.addEventListener('click', async () => { btn?.addEventListener('click', async () => {
out.textContent = 'Requesting /api/ping…'; out.textContent = 'Requesting /api/ping…';
try { try {
const res = await fetch('/api/ping'); const res = await fetch('/api');
const json = await res.json();
out.textContent = JSON.stringify(json, null, 2);
} catch (err) {
out.textContent = 'Error: ' + (err?.message || String(err));
}
});
versionBtn?.addEventListener('click', async () => {
out.textContent = 'Requesting /api/version…';
try {
const res = await fetch('/api/version');
const json = await res.json(); const json = await res.json();
out.textContent = JSON.stringify(json, null, 2); out.textContent = JSON.stringify(json, null, 2);
} catch (err) { } catch (err) {

View File

@@ -10,12 +10,11 @@
<main class="container"> <main class="container">
<h1>{{ title }}</h1> <h1>{{ title }}</h1>
<p class="lead">{{ message }}</p> <p class="lead">{{ message }}</p>
<p class="version">Version: {{ version }}</p> <p class="version">Schafkopf OS Version: {{ version }}</p>
<section class="card"> <section class="card">
<h2>API Test</h2> <h2>API Test</h2>
<button id="pingBtn">Ping API</button> <button id="pingBtn">Ping API</button>
<button id="versionBtn">Get Version</button>
<pre id="result">Waiting…</pre> <pre id="result">Waiting…</pre>
</section> </section>
</main> </main>