chore: initial template commit
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
package-lock.json
|
||||
Cargo.lock
|
||||
/node_modules
|
||||
/target
|
||||
/dist
|
||||
@@ -0,0 +1,4 @@
|
||||
# Replace in specific config files
|
||||
agent.md
|
||||
Trunk.toml
|
||||
index.html
|
||||
+277
@@ -0,0 +1,277 @@
|
||||
/target
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# AWS User-specific
|
||||
.idea/**/aws.xml
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/jarRepositories.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# SonarLint plugin
|
||||
.idea/sonarlint/
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
### Rust template
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||
Cargo.lock
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
# RustRover
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
### Linux template
|
||||
*~
|
||||
|
||||
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||
.fuse_hidden*
|
||||
|
||||
# KDE directory preferences
|
||||
.directory
|
||||
|
||||
# Linux trash folder which might appear on any partition or disk
|
||||
.Trash-*
|
||||
|
||||
# .nfs files are created when an open file is removed but is still being accessed
|
||||
.nfs*
|
||||
|
||||
### Node template
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*
|
||||
|
||||
### macOS template
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
[package]
|
||||
name = "{{.Repository.Name}}"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
desktop = [] # enables tauri and keycloak
|
||||
|
||||
[dependencies]
|
||||
leptos = { version = "0.8.16", features = ["csr"] }
|
||||
leptos_router = "0.8.12"
|
||||
#leptos-keycloak-auth = "0.13.0"
|
||||
console_error_panic_hook = "0.1.7"
|
||||
wasm-bindgen = "0.2.108"
|
||||
uuid = { version = "1", features = ["serde"] }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
regress = "0.10.5"
|
||||
chrono = { version = "0.4.43", features = ["serde"] }
|
||||
anyhow = "1.0.101"
|
||||
serde_json = "1.0.149"
|
||||
reqwest = { version = "0.13.2", features = ["json"] }
|
||||
thiserror = "2.0.18"
|
||||
once_cell = "1.21.3"
|
||||
tokio = { version = "1.49.0", features = ["sync"] }
|
||||
gloo-timers = { version = "0.3", features = ["futures"] }
|
||||
base64 = "0.22.1"
|
||||
web-sys = "0.3.85"
|
||||
wasm-bindgen-futures = "0.4.58"
|
||||
js-sys = "0.3.85"
|
||||
@@ -0,0 +1,6 @@
|
||||
[build]
|
||||
filehash = false
|
||||
public_url = "/crafts/releases/mridge-mini-app/"
|
||||
|
||||
[serve]
|
||||
addresses = ["0.0.0.0"]
|
||||
@@ -0,0 +1,104 @@
|
||||
# Mountainridge Leptos + Matterhorn Template – AI Coding Agent Instructions
|
||||
|
||||
**Purpose**
|
||||
This is the **single source of truth**.
|
||||
Every AI coding agent **must** follow these rules exactly.
|
||||
Any deviation breaks the template contract, Matterhorn codegen pipeline, Keycloak integration, Tauri desktop build, or
|
||||
embedding mechanism.
|
||||
|
||||
## Core Overview
|
||||
|
||||
- Rust + **Leptos** (latest stable) **CSR-only** reactive web application
|
||||
- Data layer: **Matterhorn entities** (JSON-schema driven)
|
||||
- Styling: **Standalone TailwindCSS** + **DaisyUI** components (no `tailwind.config.js`)
|
||||
- Corporate theme: **"mountainridge"** (locked in `styles.css`)
|
||||
- Authentication: **Keycloak OAuth2**
|
||||
- Desktop variant: enabled with feature flag **`desktop`** (uses Tauri)
|
||||
|
||||
## Agent Tool Usage (Mandatory)
|
||||
|
||||
All agents have the following **injected tools** and **must** use them exclusively:
|
||||
|
||||
- `mridge_craft_add_matterhorn_entity` – add new Matterhorn data entities
|
||||
- `mridge_craft_build` – full build / serve / watch / desktop package (Cargo + Trunk + Tauri)
|
||||
- Git commit & push
|
||||
- Create issues for the craft
|
||||
|
||||
**Never** execute raw shell commands (`cargo`, `trunk`, `git`, `tauri`, etc.).
|
||||
All GitHub operations are fully wrapped — repo details are irrelevant.
|
||||
|
||||
## Project Structure & Immutable Rules
|
||||
|
||||
| Path | Purpose | Modification Rules |
|
||||
|------------------------|------------------------------------------------------------------------|---------------------------------------------------------------------|
|
||||
| `./assets/` | Extra CSS, fonts, images, static assets | Add only |
|
||||
| `./schemas/` | JSON schemas for Matterhorn entities | **Never edit manually** – use add-entity tool |
|
||||
| `./src/codegen/` | **Auto-generated only** (structs, API client, helpers) | **Never touch** |
|
||||
| `./src/codegen/api.rs` | Generated type-safe Matterhorn client (list/read/create/update/delete) | Auto-regenerated |
|
||||
| `./src/components/` | All custom Leptos components | Free (PascalCase.rs) |
|
||||
| `./src/main.rs` | Router + Keycloak token loader + BaseLayout | **Only edit the router section**. Never remove auth/base wrappers |
|
||||
| `./src/styles.css` | Global stylesheet + full "mountainridge" DaisyUI theme | Extend only – never overwrite theme block |
|
||||
| `./index.html` | HTML entry point (Trunk) | **Immutable section** (see below) – only meta/title changes allowed |
|
||||
|
||||
## Immutable / Protected Elements (Never Change)
|
||||
|
||||
**`./index.html`** – these exact lines **must remain untouched** (required for Trunk, favicon, public URL, and config
|
||||
injection):
|
||||
|
||||
```html
|
||||
|
||||
<link data-trunk rel="copy-dir" href="./assets" data-trunk-target-path="/assets"/>
|
||||
<link rel="icon" href="./assets/mridge-craft-icon.webp" type="image/webp"/>
|
||||
<base data-trunk-public-url/>
|
||||
<script>
|
||||
window.__embeddedAppConfig = {};
|
||||
window.addEventListener('message', function (e) {
|
||||
if (e.data.type === 'APP_CONFIG') {
|
||||
window.__embeddedAppConfig = e.data;
|
||||
}
|
||||
}, {once: true});
|
||||
</script>
|
||||
```
|
||||
|
||||
**`./src/styles.css`** – never delete or replace the `"daisyui/theme"` block.
|
||||
|
||||
**`./src/main.rs`** – never remove `<AuthProvider>`, Keycloak init, or `<BaseLayout>`.
|
||||
|
||||
**`./src/codegen/`** – generated; never manual edits.
|
||||
|
||||
## Authentication – Keycloak OAuth2
|
||||
|
||||
`main.rs` contains the protected Keycloak token loader:
|
||||
|
||||
- **Web/iframe mode** (default): token injected via `postMessage` (uses `window.__embeddedAppConfig`)
|
||||
- **Desktop mode** (`--features desktop`): full Keycloak login flow + Tauri
|
||||
|
||||
**Rule:** Never remove, refactor, or bypass any auth-related wrapper.
|
||||
|
||||
## Critical Rules for Coding Agents
|
||||
|
||||
1. **Data Dependencies**
|
||||
Always use `mridge_craft_add_matterhorn_entity` for any new or modified Matterhorn model.
|
||||
|
||||
2. **Routing**
|
||||
Add new routes **only** inside the existing `<Router>` block in `main.rs`.
|
||||
Preserve all auth/protected-route wrappers.
|
||||
|
||||
3. **Styling**
|
||||
Prefer DaisyUI classes everywhere.
|
||||
Tailwind is standalone — no config file.
|
||||
Never override the "mountainridge" theme.
|
||||
|
||||
4. **Data Fetching (CSR-only)**
|
||||
All operations **must** use the generated `codegen::api::*` client.
|
||||
No Leptos server functions.
|
||||
|
||||
5. **Building & Testing**
|
||||
Every compile, dev server, release, or Tauri desktop build → use `mridge_craft_build`.
|
||||
|
||||
## Standard Agent Workflow
|
||||
|
||||
1. Need new data entity? → use 'mrdige_add-entity' skill
|
||||
2. Create component(s) in `./src/components/`
|
||||
3. Add route in `main.rs` (inside router, respect auth)
|
||||
4. Commit/push via git tools or create issue if needed
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 117 KiB |
+21
@@ -0,0 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-theme="mountainridge">
|
||||
<head>
|
||||
<base data-trunk-public-url/>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>MountainRidge.xyz - Mont Fort App</title>
|
||||
<link data-trunk rel="copy-dir" href="assets"/>
|
||||
<link rel="icon" href="./assets/mridge-craft-icon.webp" type="image/webp"/>
|
||||
<link rel="stylesheet" href="./assets/styles.css"/>
|
||||
<script>
|
||||
window.__appConfig = {};
|
||||
window.addEventListener('message', function (e) {
|
||||
if (e.data.type === 'APP_CONFIG') {
|
||||
window.__appConfig = e.data;
|
||||
}
|
||||
}, {once: true});
|
||||
</script>
|
||||
</head>
|
||||
<body class="flex w-1 h-1"></body>
|
||||
</html>
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"daisyui": "^5.5.18"
|
||||
},
|
||||
"scripts": {
|
||||
"build-craft": "npm install && tailwindcss -i src/styles.css -o assets/styles.css --minify && trunk build --config Trunk.toml"
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
mod hello_mridge;
|
||||
pub mod views {
|
||||
pub use super::hello_mridge::*;
|
||||
}
|
||||
+64
@@ -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('/'))
|
||||
}
|
||||
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod api;
|
||||
pub mod types;
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user