chore: initial template commit

This commit is contained in:
Gitea Bootstrap
2026-08-24 07:38:22 +00:00
commit 3b51b4a81a
16 changed files with 947 additions and 0 deletions
+29
View File
@@ -0,0 +1,29 @@
use crate::codegen::apis::PresseEintragApi;
use crate::codegen::types::presse_eintrag::{PressEintrag, PressEintragSentiments};
use crate::matterhorn::types::SortDirection;
use leptos::prelude::*;
use leptos::task::spawn_local;
use leptos_router::components::A;
use uuid::Uuid;
const ICON_BYTES: &[u8] = include_bytes!("../../assets/mridge-craft-icon.webp");
fn icon_data_url() -> String {
use base64::{engine::general_purpose::STANDARD, Engine};
format!("data:image/webp;base64,{}", STANDARD.encode(ICON_BYTES))
}
#[component]
pub fn HelloMRidge() -> impl IntoView {
view! {
<div class="flex flex-col w-full h-full p-6 space-y-6">
<div class="flex items-center gap-3">
<img src=icon_data_url() alt="MountainRidge icon" class="w-8 h-8 rounded-full object-cover" />
<h2 class="text-2xl font-bold">"Mont Fort Craft"</h2>
</div>
<div class="flex justify-center mt-4">
<h1>Hello MountainRidge!</h1>
</div>
</div>
}
}
+4
View File
@@ -0,0 +1,4 @@
mod hello_mridge;
pub mod views {
pub use super::hello_mridge::*;
}
+64
View File
@@ -0,0 +1,64 @@
mod components;
mod matterhorn;
use crate::components::views::{HelloMRidge};
use leptos::prelude::*;
use leptos::reactive::owner::Owner;
use leptos_router::components::{Route, Router, Routes};
use leptos_router::path;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
fn main() {
console_error_panic_hook::set_once();
mount_to_body(App);
}
/*
* App entrypoint; define new routes and add new components here.
* Hint: do not change the Router base and always ever use relative routes
*/
fn App() -> impl IntoView {
view! {
<Router base=get_base_path()>
<Routes fallback=|| view! { <p>"Not found"</p> }>
<Route path=path!("/") view=HelloMRidge/>
</Routes>
</Router>
}
}
/**
* DO NOT change this config retrieval method
*/
fn get_config() -> Option<(String, String)> {
let window = web_sys::window()?;
let config = js_sys::Reflect::get(&window, &"__appConfig".into()).ok()?;
let token = js_sys::Reflect::get(&config, &"token".into())
.ok()?
.as_string()?;
let user = js_sys::Reflect::get(&config, &"user".into())
.ok()?
.as_string()?;
Some((token, user))
}
/*
* Gets the base path for the web application
*/
pub fn get_base_path() -> String {
web_sys::window()
.and_then(|w| w.document())
.and_then(|d| d.query_selector("base").ok().flatten())
.and_then(|el| el.get_attribute("href"))
.unwrap_or_else(|| "/".to_string())
}
/*
* Gets the
*/
pub fn asset_path(file: &str) -> String {
format!("{}assets/{}", get_base_path(), file.trim_start_matches('/'))
}
+109
View File
@@ -0,0 +1,109 @@
use crate::codegen::types::PressEintrag;
use crate::get_config;
use crate::matterhorn::types;
use crate::matterhorn::types::{FetchInstanceRequest, PostInstancesRequest};
use leptos::prelude::GetValue;
use reqwest::{Client, StatusCode};
use serde_json::Value;
use thiserror::Error;
use uuid::Uuid;
#[derive(Debug, Error)]
pub enum ApiError {
#[error("network error: {0}")]
Network(#[from] reqwest::Error),
#[error("json error: {0}")]
Json(#[from] serde_json::Error),
#[error("http {0}")]
Http(u16, String),
#[error("missing 'records' field")]
MissingRecords,
#[error("records is not an array")]
RecordsNotArray,
#[error("other: {0}")]
Other(String),
}
#[derive(Clone)]
pub struct MatterhornApi {
client: Client,
base_url: String,
dataset_id: Option<Uuid>,
token: String,
}
impl MatterhornApi {
pub fn new() -> Self {
// todo base_url and dataset should be injected
let (token, _user) = get_config().expect("__embeddedAppConfig must be set");
Self {
client: Client::new(),
base_url: "http://127.0.0.1:8085/matterhorn/api/v0/ontology/2163f231-2e7c-44cf-bc14-fbde9edea884/data".into(),
dataset_id: None,
token,
}
}
pub async fn fetch_records(
&self,
matterhorn_entity_id: &u64,
projections: Vec<String>,
filters: Vec<types::DataGraphFilter>,
sorters: std::collections::HashMap<String, types::SortDirection>,
limit: u32,
) -> Result<Vec<Value>, ApiError> {
let url = format!("{:1}/{:2}", self.base_url, matterhorn_entity_id);
let request_body = FetchInstanceRequest {
projections,
filters,
sorters,
limit,
};
let resp = self
.client
.post(url)
.bearer_auth(self.token.as_str())
.json(&request_body)
.header("Accept", "application/json")
.send()
.await
.map_err(ApiError::Network)?;
// ── Step 1: Get generic map-like response ───────────────────────────────
let raw: Value = resp
.json::<Value>()
.await
.map_err(|e| ApiError::Other(format!("Deserialization failed: {}", e)))?;
let graph = raw.get("@graph").ok_or(ApiError::MissingRecords)?;
// Check it's an array and convert to Vec<Value>
let array = graph.as_array().ok_or_else(|| ApiError::RecordsNotArray)?;
// If you want owned values (most common when returning Vec<Value>)
Ok(array.to_vec())
}
pub async fn store_records(
&self,
matterhorn_entity_id: &u64,
records: Vec<Value>,
) -> Result<Vec<Value>, ApiError> {
let url = format!("{:1}/{:2}", self.base_url, matterhorn_entity_id);
let (token, _user) = get_config().expect("__embeddedAppConfig must be set");
let request_body = PostInstancesRequest { records };
let resp = self
.client
.post(url)
.bearer_auth(token)
.json(&request_body)
.header("Accept", "application/json")
.send()
.await
.map_err(ApiError::Network)?;
web_sys::console::info_1(&format!("{:#?}", "response send").into());
let status = resp.status();
if !status.is_success() {
let text = resp.text().await.unwrap_or_default();
return Err(ApiError::Http(status.as_u16(), text));
}
Ok(resp.json().await.unwrap_or_default())
}
}
+2
View File
@@ -0,0 +1,2 @@
pub mod api;
pub mod types;
+30
View File
@@ -0,0 +1,30 @@
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
#[derive(Serialize, Default)]
pub struct FetchInstanceRequest {
pub projections: Vec<String>,
pub filters: Vec<DataGraphFilter>,
pub sorters: HashMap<String, SortDirection>,
pub limit: u32,
}
#[derive(Serialize, Default)]
pub struct PostInstancesRequest {
pub records: Vec<Value>,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum SortDirection {
#[serde(rename = "ASC")]
Asc,
#[serde(rename = "DESC")]
Desc,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct DataGraphFilter {
pub field: String,
pub operator: String, // e.g. "EQ", "CONTAINS"
pub value: Value,
}
+254
View File
@@ -0,0 +1,254 @@
@import "tailwindcss";
@plugin "daisyui" {
themes: mountainridge --default;
darkTheme: mountainridge;
base: true;
styled: true;
utils: true;
logs: false;
}
@plugin "daisyui/theme" {
name: "mountainridge";
default: true;
prefersdark: true;
color-scheme: "dark";
font-family: "Geist", system-ui, sans-serif;
/* Backgrounds — charcoal scale */
--color-base-100: oklch(16% 0.02 214);
--color-base-200: oklch(13% 0.02 214);
--color-base-300: oklch(10% 0.02 214);
--color-base-content: oklch(92% 0.005 214);
/* Primary — sea-grass green rgb(88, 191, 177) */
--color-primary: oklch(72% 0.097 183);
--color-primary-content: oklch(13% 0.02 214);
/* Secondary — glacier light blue rgb(94, 150, 159) */
--color-secondary: oklch(60% 0.055 205);
--color-secondary-content: oklch(13% 0.02 214);
/* Accent — flamingo pink rgba(253, 117, 127) */
--color-accent: oklch(72% 0.155 14); /* flamingo pink */
--color-accent-content: oklch(13% 0.02 214);
/* Neutral — charcoal slate */
--color-neutral: oklch(28% 0.02 214);
--color-neutral-content: oklch(80% 0.01 214);
/* Semantic */
--color-info: oklch(60% 0.055 205);
--color-info-content: oklch(13% 0.02 214);
--color-success: oklch(72% 0.097 183);
--color-success-content: oklch(13% 0.02 214);
--color-warning: oklch(68% 0.16 42); /* alpenglow orange */
--color-warning-content: oklch(13% 0.02 214);
--color-error: oklch(72% 0.155 14); /* flamingo pink */
--color-error-content: oklch(13% 0.02 214);
/* Shape */
--radius-selector: 1.5rem;
--radius-field: 0.48rem;
--radius-box: 0.48rem;
--size-selector: 0.25rem;
--size-field: 0.25rem;
--border: 1px;
--depth: 1;
--noise: 0;
}
:root {
all: inherit;
color: var(--color-base-content); /* DaisyUI's base text color */
font-family: 'Geist', Sans, serif;
/* creating the perfect shadow according to twitter/X
- https://codepen.io/jh3y/pen/yLWgjpd
- https://x.com/jh3yy/status/1796208073830232574
*/
--tint: 214;
--alpha: 4;
--base: hsl(var(--tint, 214) 80% 27% / calc(var(--alpha, 4) * 1%));
--shade: hsl(from var(--base) calc(h + 8) 25 calc(l - 5));
--perfect-shadow: 0 0 0 1px var(--base),
0 1px 1px -0.5px var(--shade),
0 3px 3px -1.5px var(--shade),
0 6px 6px -3px var(--shade),
0 12px 12px -6px var(--base),
0 24px 24px -12px var(--base);
--shade-active: hsla(173, 41%, 53%, 0.66);
--base-active: hsl(var(--tint, 214) 80% 27% / calc(var(--alpha, 4) * 1%));
--perfect-shadow-active: 0 0 0 1px var(--base-active),
0 1px 1px -0.5px var(--shade-active),
0 3px 3px -1.5px var(--shade-active),
0 6px 6px -3px var(--shade-active),
0 12px 12px -6px var(--base-active),
0 24px 24px -12px var(--base-active);
--perfect-shadow-thin-active: 0 0 0 1px var(--base-active),
0 1px 2px -0.5px var(--shade-active),
0 2px 4px -1px var(--shade-active),
0 4px 8px -2px var(--shade-active);
--shade-active-warn: rgba(253, 117, 127, 0.66);
--perfect-shadow-warn: 0 0 0 1px var(--shade-active-warn),
0 1px 1px -0.5px var(--shade-active-warn),
0 3px 3px -1.5px var(--shade-active-warn),
0 6px 6px -3px var(--shade-active-warn),
0 12px 12px -6px var(--base-active),
0 24px 24px -12px var(--base-active);
--base-thin: hsl(var(--tint, 214) 80% 27% / calc(var(--alpha, 4) * 1%));
--shade-thin: hsl(from var(--base-thin) calc(h + 8) 25 calc(l - 5));
--perfect-shadow-thin: 0 0 0 1px var(--base-thin),
0 1px 2px -0.5px var(--shade-thin),
0 3px 6px -1px var(--shade-thin);
--perfect-shadow-light: 0px 6px 20px -4px rgba(0, 0, 0, 0.40), 0px 0px 30px -8px rgba(123, 94, 248, 0.10);
/* === DARK BACKGROUND SHADOWS === */
/* Tinted with glacier blue for atmosphere */
--tint-dark: 205;
--alpha-dark: 30;
--base-dark: hsl(var(--tint-dark) 60% 60% / calc(var(--alpha-dark) * 1%));
--shade-dark: hsl(from var(--base-dark) calc(h + 8) 70 calc(l + 10));
/* Full shadow — for cards, modals, elevated surfaces */
--perfect-shadow-dark: 0 0 0 1px hsl(205 40% 50% / 15%),
0 1px 1px -0.5px var(--base-dark),
0 3px 3px -1.5px var(--base-dark),
0 6px 6px -3px var(--shade-dark),
0 12px 12px -6px var(--shade-dark),
0 24px 24px -12px var(--base-dark);
/* Thin shadow — for buttons, inputs, small elements */
--perfect-shadow-thin-dark: 0 0 0 1px hsl(205 40% 50% / 12%),
0 1px 2px -0.5px var(--base-dark),
0 3px 6px -1px var(--shade-dark);
/* Active/focus state — sea-grass green glow */
--shade-active-dark: hsla(183, 35%, 55%, 0.45); /* sea-grass green */
--perfect-shadow-thin-active-dark: 0 0 0 1px hsla(183, 35%, 55%, 0.3),
0 1px 2px -0.5px var(--shade-active-dark),
0 2px 4px -1px var(--shade-active-dark),
0 4px 8px -2px var(--shade-active-dark);
/* Warning/error active state — flamingo pink glow */
--shade-active-warn-dark: rgba(253, 117, 127, 0.45);
--perfect-shadow-warn-dark: 0 0 0 1px rgba(253, 117, 127, 0.3),
0 1px 1px -0.5px var(--shade-active-warn-dark),
0 3px 3px -1.5px var(--shade-active-warn-dark),
0 6px 6px -3px var(--shade-active-warn-dark),
0 12px 12px -6px var(--shade-active-warn-dark);
--mountain-white-dark: rgb(245, 242, 242); /* ≈ 80% – popular "dark mode off-white" */
--mountain-white: rgb(255, 252, 252, 1);
--ghost-white: rgb(248, 248, 255, 1);
/* Very subtle darkening – still very bright */
--mountain-white-darker-1: rgb(245, 242, 242); /* ≈ 96% brightness */
--mountain-white-darker-2: rgb(235, 232, 232); /* ≈ 92% */
/* Noticeably darker but still clearly a very light / "off-white" */
--mountain-white-darker-3: rgb(230, 227, 227); /* ≈ 90% */
--mountain-white-darker-4: rgb(220, 217, 217); /* ≈ 86% */
--mountain-white-darker-5: rgb(204, 201, 201); /* ≈ 80% – popular "dark mode off-white" */
/* Deeper / moodier off-whites – start feeling more greyish */
--mountain-white-darker-6: rgb(189, 186, 186); /* ≈ 74% */
--mountain-white-darker-7: rgb(170, 167, 167); /* ≈ 67% – quite smoky now */
--mountain-white-darker-8: rgb(150, 147, 147); /* ≈ 59% – very muted/dark off-white */
--charcoal-deep: rgb(15, 20, 25);
--charcoal-dark: rgb(25, 30, 35); /* +10 */
--charcoal-medium: rgb(35, 40, 48); /* +20ish */
--charcoal-soft: rgb(45, 50, 58); /* noticeably lighter but still moody */
--charcoal-slate: rgb(55, 60, 68); /* good background / surface color */
--charcoal-lighter: rgb(70, 75, 85); /* readable dark gray, modern UI feel */
--deep-night-black: rgb(8, 18, 28);
--abyss-black: rgb(6, 15, 22);
--bs-dark: var(--charcoal-medium); /* changes the base dark color */
--bs-dark-rgb: 35, 40, 48;
--bs-blue: var(--glacier-blue);
/* custom colors */
--flamingo-pink: rgba(253, 117, 127, 1);
--sea-grass-green: rgba(88, 191, 177, 1);
--mountain-darkgray: rgb(12, 31, 49, 1);
--mountain-gray: rgb(102, 133, 139, 1);
--mountain-lightgray: rgb(121, 152, 155, 1);
--glacier-blue: rgb(28, 80, 101);
--glacier-darkblue: rgb(12, 39, 49);
--glacier-lightblue: rgb(94, 150, 159);
--glacier-blue-rgb: 94, 150, 159;
/* borders, corners, and shadows */
--perfect-border-radius: 0.48em;
--backdrop-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 1px 3px 0 rgba(0, 0, 0, 0.19);
--backdrop-transition: all 0.3s ease;
/* global overwrites of bootstrap vars */
--text-bg-primary-badge-color: var(--glacier-blue);
/* White to Darker Blue diagonal gradient */
--gradient-white-dark-blue: linear-gradient(
142deg,
var(--ghost-white) 0%,
var(--ghost-white) 55%,
var(--glacier-lightblue) 67%,
var(--glacier-lightblue) 75%,
var(--glacier-blue) 100%
);
--gradient-dark: linear-gradient(135deg, var(--mountain-white) 0%, var(--mountain-darkgray) 100%);
}
* {
scrollbar-width: none;
}
h1, h2, h3 {
font-weight: 300;
}
h4, h5, h6, p, body {
font-weight: 400;
}
.btn {
box-shadow: var(--perfect-shadow-thin-dark);
transition: transform 0.3s ease-out, box-shadow 0.3s ease-out;
}
.btn:hover {
transform: scale(1.1) translateY(-2px);
box-shadow: var(--perfect-shadow-thin-active-dark);
}
.btn:not(.btn-ghost):not(.btn-outline):hover {
transform: scale(1.1) translateY(-2px);
box-shadow: var(--perfect-shadow-thin-active-dark);
}