This commit is contained in:
Johannes Heuel
2022-06-25 17:16:13 +02:00
commit 9449d992c0
33 changed files with 5100 additions and 0 deletions

1
.env Normal file
View File

@@ -0,0 +1 @@
DATABASE_URL=postgres://user:password@localhost/diesel_demo

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
/target

2060
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

14
Cargo.toml Normal file
View File

@@ -0,0 +1,14 @@
[package]
name = "photos"
version = "0.1.0"
edition = "2021"
license = "MIT"
description = " "
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
[workspace]
members = ["frontend", "backend", "common"]
default-members = ["backend"]

21
Dockerfile Normal file
View File

@@ -0,0 +1,21 @@
FROM rust:latest as build
RUN rustup target add wasm32-unknown-unknown
RUN cargo install trunk wasm-bindgen-cli
RUN apt-get update && apt-get install -y npm binaryen && npm install -g sass
WORKDIR /app/src
COPY . .
RUN cd frontend && trunk build --release
RUN cargo build --release
FROM gcr.io/distroless/cc-debian10
RUN apt-get update && apt-get install -y libpq5
COPY --from=build /app/src/target/release/backend /app/backend
COPY --from=build /app/src/frontend/dist /app/dist
WORKDIR /app
CMD ["./backend"]

1396
backend/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

23
backend/Cargo.toml Normal file
View File

@@ -0,0 +1,23 @@
[package]
name = "backend"
version = "0.1.0"
edition = "2021"
license = "MIT"
description = " "
default-run = "backend"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
actix-web = "4"
actix-web-lab = "^0"
actix-files = "0.6"
env_logger = "0.9.0"
log = "0.4"
diesel = { version = "1.4.8", features = ["postgres", "r2d2"] }
diesel_migrations = "1.4"
dotenv = "0.15.0"
walkdir = "2"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
common = { path = "../common" }

5
backend/diesel.toml Normal file
View File

@@ -0,0 +1,5 @@
# For documentation on how to configure this file,
# see diesel.rs/guides/configuring-diesel-cli
[print_schema]
file = "src/schema.rs"

View File

@@ -0,0 +1,6 @@
-- This file was automatically created by Diesel to setup helper functions
-- and other internal bookkeeping. This file is safe to edit, any future
-- changes will be added to existing projects as new migrations.
DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass);
DROP FUNCTION IF EXISTS diesel_set_updated_at();

View File

@@ -0,0 +1,36 @@
-- This file was automatically created by Diesel to setup helper functions
-- and other internal bookkeeping. This file is safe to edit, any future
-- changes will be added to existing projects as new migrations.
-- Sets up a trigger for the given table to automatically set a column called
-- `updated_at` whenever the row is modified (unless `updated_at` was included
-- in the modified columns)
--
-- # Example
--
-- ```sql
-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW());
--
-- SELECT diesel_manage_updated_at('users');
-- ```
CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$
BEGIN
EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl);
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$
BEGIN
IF (
NEW IS DISTINCT FROM OLD AND
NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at
) THEN
NEW.updated_at := current_timestamp;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

View File

@@ -0,0 +1 @@
DROP TABLE pictures

View File

@@ -0,0 +1,18 @@
CREATE TABLE pictures (
id SERIAL PRIMARY KEY,
filepath VARCHAR NOT NULL,
created_at INTEGER,
focal_length VARCHAR,
shutter_speed VARCHAR,
width INTEGER NOT NULL,
height INTEGER NOT NULL,
make VARCHAR,
model VARCHAR,
lens VARCHAR,
orientation VARCHAR,
fnumber FLOAT,
iso INTEGER,
exposure_program VARCHAR,
exposure_compensation VARCHAR,
thumbnail VARCHAR
)

16
backend/src/actions.rs Normal file
View File

@@ -0,0 +1,16 @@
extern crate diesel;
use backend::models::*;
use diesel::prelude::*;
use backend::*;
type DbError = Box<dyn std::error::Error + Send + Sync>;
pub fn list_pictures(conn: &PgConnection) -> Result<Vec<Picture>, DbError> {
use self::schema::pictures::dsl::*;
Ok(pictures
.limit(50)
.order_by(created_at.desc())
.load::<Picture>(conn)?)
}

View File

@@ -0,0 +1,238 @@
extern crate diesel;
use backend::create_picture;
use backend::models::NewPicture;
use backend::establish_connection;
// use backend::*;
use std::ffi::OsStr;
use std::path::Path;
use std::time::UNIX_EPOCH;
use std::{fs, process::Command};
use walkdir::{DirEntry, WalkDir};
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize)]
struct PhotoExif {
#[serde(default, alias = "FocalLength")]
focal_length: Option<String>,
#[serde(default, alias = "ShutterSpeed")]
shutter_speed: Option<String>,
#[serde(alias = "ImageWidth")]
width: i32,
#[serde(alias = "ImageHeight")]
height: i32,
#[serde(default, alias = "Make")]
make: Option<String>,
#[serde(default, alias = "Model")]
model: Option<String>,
#[serde(default, alias = "LensID")]
lens: Option<String>,
#[serde(default, alias = "Orientation")]
orientation: Option<String>,
#[serde(default, alias = "FNumber")]
fnumber: Option<f64>,
#[serde(default, alias = "ExposureProgram")]
exposure_program: Option<String>,
#[serde(default, alias = "CreateDate")]
created_at: Option<i32>,
#[serde(default, alias = "ISO")]
iso: Option<i32>,
#[serde(default = "MaybeString::default", alias = "ExposureCompensation")]
exposure_compensation: MaybeString,
}
#[derive(Serialize, Deserialize)]
#[serde(untagged)]
enum MaybeString {
Number(i32),
Str(String),
None,
}
impl MaybeString {
fn default() -> MaybeString {
MaybeString::None
}
fn to_opt_string(&self) -> Option<String> {
if let MaybeString::Str(exp_comp) = &self {
Some(exp_comp.clone())
} else {
None
}
}
}
fn is_hidden(entry: &DirEntry) -> bool {
entry
.file_name()
.to_str()
.map(|s| s.starts_with('.'))
.unwrap_or(false)
}
fn is_image(entry: &DirEntry) -> bool {
let allowed_extensions = ["cr2", "cr3", "jpg", "jpeg"];
let extension = if let Some(ext) = entry.path().extension() {
ext
} else {
OsStr::new("")
};
if allowed_extensions
.iter()
.all(|&v| v != extension.to_ascii_lowercase())
{
return false;
}
true
}
static PICTURE_PATH: &str = "./pictures";
static LIBRARY_PATH: &str = "./examples";
fn main() {
let connection = establish_connection();
WalkDir::new(LIBRARY_PATH)
.into_iter()
.filter_map(Result::ok)
.filter(|e| !e.file_type().is_dir())
.filter(|e| !is_hidden(e))
.filter(is_image)
.into_iter()
.for_each(|path| {
let thumbnail = if let Ok(t) = extract_preview(path.path()) {
t
} else {
println!("Could not create thumbnail");
return;
};
let thumbnail = std::path::PathBuf::from(thumbnail.strip_prefix(PICTURE_PATH).unwrap());
let output = Command::new("exiftool")
.arg("-j")
.arg("-d")
.arg("%s")
.arg(path.path())
.output()
.expect("failed to execute exiftool");
let pel: Vec<PhotoExif> = serde_json::from_slice(&output.stdout).unwrap();
let pe = &pel[0];
println!("pe = {}", serde_json::to_string_pretty(pe).unwrap());
let created_at: Option<i32> = if let Some(c) = pe.created_at {
Some(c)
} else {
let metadata = fs::metadata(&path.path()).unwrap();
if let Ok(time) = metadata.created() {
Some(time.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
.try_into()
.unwrap())
} else {
println!("Not supported on this platform or filesystem");
None
}
};
let filepath = path.path().to_string_lossy().into_owned();
let new_picture = NewPicture {
filepath: filepath.clone(),
created_at: created_at.clone(),
focal_length: pe.focal_length.clone(),
shutter_speed: pe.shutter_speed.clone(),
width: pe.width,
height: pe.height,
make: pe.make.clone(),
model: pe.model.clone(),
lens: pe.lens.clone(),
orientation: pe.orientation.clone(),
fnumber: pe.fnumber,
iso: pe.iso,
exposure_program: pe.exposure_program.clone(),
exposure_compensation: pe.exposure_compensation.to_opt_string(),
thumbnail: Some(thumbnail.into_os_string().into_string().unwrap()),
};
let pic = create_picture(&connection, new_picture);
println!(
"Created picture with filepath={} and id={}",
filepath, pic
);
});
}
fn extract_preview(path: &Path) -> Result<std::path::PathBuf, Box<dyn std::error::Error>> {
let file_name = if let Some(p) = path.file_name() {
p
} else {
OsStr::new("")
};
let parent = if let Some(p) = path.parent() {
p
} else {
Path::new(LIBRARY_PATH)
};
let relative_parent = parent
.strip_prefix(LIBRARY_PATH)
.expect("Could not remove prefix");
let thumb_path = Path::new(PICTURE_PATH).join(relative_parent);
if !thumb_path.exists() {
fs::create_dir_all(&thumb_path).unwrap_or_else(|e| {
panic!("Could not create directory {}: {}", thumb_path.display(), e)
});
}
let mut thumbnail = thumb_path.join(file_name);
thumbnail.set_extension("jpg");
let extension = path.extension().unwrap();
let jpegs = ["jpg", "jpeg"];
if jpegs.iter().any(|&x| x == extension.to_ascii_lowercase()) {
match fs::copy(path, &thumbnail) {
Ok(_it) => return Ok(thumbnail),
Err(err) => return Err(err.into()),
};
}
let _output_thumb = Command::new("exiftool")
.arg("-if")
.arg("$jpgfromraw")
.arg("-b")
.arg("-jpgfromraw")
.arg("-w")
.arg(thumb_path.join("%f.jpg"))
.arg("-execute")
.arg("-if")
.arg("$previewimage")
.arg("-b")
.arg("-previewimage")
.arg("-w")
.arg(thumb_path.join("%f.jpg"))
.arg("-execute")
.arg("-tagsfromfile")
.arg("@")
.arg("-srcfile")
.arg(thumb_path.join("%f.jpg"))
.arg("-overwrite_original")
.arg("-common_args")
.arg("--ext")
.arg("jpg")
.arg(path)
.output()
.expect("failed to execute exiftool to extract thumbnail");
// println!("{:?}", _output_thumb);
if thumbnail.exists() {
Ok(thumbnail)
} else {
Err("Could not create thumbnail".into())
}
}

View File

@@ -0,0 +1,21 @@
extern crate diesel;
use self::models::*;
use diesel::prelude::*;
use backend::*;
fn main() {
use self::schema::pictures::dsl::*;
let connection = establish_connection();
let results = pictures
.limit(5)
.load::<Picture>(&connection)
.expect("Error loading pictures");
println!("Displaying {} pictures", results.len());
for picture in results {
println!("filepath: {}", picture.filepath);
println!("\tid: {}", picture.id);
}
}

30
backend/src/lib.rs Normal file
View File

@@ -0,0 +1,30 @@
#[macro_use]
extern crate diesel;
extern crate dotenv;
pub mod models;
pub mod schema;
use self::models::{NewPicture};
use diesel::prelude::*;
use diesel::pg::PgConnection;
use dotenv::dotenv;
use std::env;
pub fn establish_connection() -> PgConnection {
dotenv().ok();
let database_url = env::var("DATABASE_URL")
.expect("DATABASE_URL must be set");
PgConnection::establish(&database_url)
.unwrap_or_else(|_| panic!("Error connecting to {}", database_url))
}
pub fn create_picture<'a>(conn: &PgConnection, new_picture: NewPicture) -> usize {
use schema::pictures;
diesel::insert_into(pictures::table)
.values(&new_picture)
.execute(conn)
.expect("Error saving new picture")
}

132
backend/src/main.rs Normal file
View File

@@ -0,0 +1,132 @@
#[macro_use]
extern crate diesel;
#[macro_use]
extern crate diesel_migrations;
use actix_files as fs;
use actix_web::{Responder, Result};
use actix_web_lab::web::spa;
use backend::establish_connection;
use actix_web::{
// get, middleware, post, web, App, Error, HttpRequest, HttpResponse, HttpServer,
get,
middleware,
web,
App,
HttpServer,
};
use diesel::prelude::*;
use diesel::r2d2::{self, ConnectionManager};
// use uuid::Uuid;
mod actions;
mod models;
mod schema;
use common::OutputPicture;
type DbPool = r2d2::Pool<ConnectionManager<PgConnection>>;
// type DbError = Box<dyn std::error::Error + Send + Sync>;
#[get("/api/pictures/")]
async fn get_pictures(pool: web::Data<DbPool>) -> Result<impl Responder> {
let conn = pool.get().unwrap();
let pics = if let Ok(p) = actions::list_pictures(&conn) {
p
} else {
vec![]
};
let pics: Vec<OutputPicture> = pics
.iter()
.map(|x| OutputPicture {
thumbnail: x.thumbnail.clone(),
width: x.width.try_into().unwrap(),
height: x.height.try_into().unwrap(),
})
.collect();
Ok(web::Json(pics))
}
embed_migrations!("migrations");
#[actix_web::main]
async fn main() -> std::io::Result<()> {
dotenv::dotenv().ok();
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
let connection = establish_connection();
// // This will run the necessary migrations.
// embedded_migrations::run(&connection).expect("Could not migrate database.");
// By default the output is thrown out. If you want to redirect it to stdout, you
// should call embedded_migrations::run_with_output.
embedded_migrations::run_with_output(&connection, &mut std::io::stdout())
.expect("Could not migrate database.");
// set up database connection pool
let conn_spec = std::env::var("DATABASE_URL").expect("DATABASE_URL");
let manager = ConnectionManager::<PgConnection>::new(conn_spec);
let pool = r2d2::Pool::builder()
.build(manager)
.expect("Failed to create pool.");
let host = "0.0.0.0";
let port = 8081;
log::info!("starting HTTP server at http://{}:{}", host, port);
// Start HTTP server
HttpServer::new(move || {
App::new()
// set up DB pool to be used with web::Data<Pool> extractor
.app_data(web::Data::new(pool.clone()))
.wrap(middleware::Logger::default())
.service(get_pictures)
.service(fs::Files::new("/api/pictures/", "./pictures/"))
.service(
spa()
.index_file("./dist/index.html")
.static_resources_mount("/")
.static_resources_location("./dist")
.finish(),
)
// .service(fs::Files::new("/", STATIC_FILES_PATH).index_file("index.html"))
// .service(get_user)
// .service(add_user)
})
.bind((host, port))?
.run()
.await
}
#[cfg(test)]
mod tests {
use super::*;
use actix_web::test;
#[actix_web::test]
async fn user_routes() {
std::env::set_var("RUST_LOG", "actix_web=debug");
env_logger::init();
dotenv::dotenv().ok();
let connspec = std::env::var("DATABASE_URL").expect("DATABASE_URL");
let manager = ConnectionManager::<PgConnection>::new(connspec);
let pool = r2d2::Pool::builder()
.build(manager)
.expect("Failed to create pool.");
let _app = test::init_service(
App::new()
.app_data(web::Data::new(pool.clone()))
.wrap(middleware::Logger::default()), // .service(get_user)
// .service(add_user),
)
.await;
}
}

43
backend/src/models.rs Normal file
View File

@@ -0,0 +1,43 @@
use serde::Serialize;
use super::schema::pictures;
#[derive(Queryable, Serialize)]
pub struct Picture {
pub id: i32,
pub filepath: String,
pub created_at: Option<i32>,
pub focal_length: Option<String>,
pub shutter_speed: Option<String>,
pub width: i32,
pub height: i32,
pub make: Option<String>,
pub model: Option<String>,
pub lens: Option<String>,
pub orientation: Option<String>,
pub fnumber: Option<f64>,
pub iso: Option<i32>,
pub exposure_program: Option<String>,
pub exposure_compensation: Option<String>,
pub thumbnail: Option<String>,
}
#[derive(Insertable)]
#[table_name = "pictures"]
pub struct NewPicture {
pub filepath: String,
pub created_at: Option<i32>,
pub focal_length: Option<String>,
pub shutter_speed: Option<String>,
pub width: i32,
pub height: i32,
pub make: Option<String>,
pub model: Option<String>,
pub lens: Option<String>,
pub orientation: Option<String>,
pub fnumber: Option<f64>,
pub iso: Option<i32>,
pub exposure_program: Option<String>,
pub exposure_compensation: Option<String>,
pub thumbnail: Option<String>,
}

20
backend/src/schema.rs Normal file
View File

@@ -0,0 +1,20 @@
table! {
pictures (id) {
id -> Int4,
filepath -> Varchar,
created_at -> Nullable<Int4>,
focal_length -> Nullable<Varchar>,
shutter_speed -> Nullable<Varchar>,
width -> Int4,
height -> Int4,
make -> Nullable<Varchar>,
model -> Nullable<Varchar>,
lens -> Nullable<Varchar>,
orientation -> Nullable<Varchar>,
fnumber -> Nullable<Float8>,
iso -> Nullable<Int4>,
exposure_program -> Nullable<Varchar>,
exposure_compensation -> Nullable<Varchar>,
thumbnail -> Nullable<Varchar>,
}
}

10
common/Cargo.toml Normal file
View File

@@ -0,0 +1,10 @@
[package]
name = "common"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"

8
common/src/lib.rs Normal file
View File

@@ -0,0 +1,8 @@
use serde::{Serialize, Deserialize};
#[derive(Serialize,Deserialize)]
pub struct OutputPicture {
pub thumbnail: Option<String>,
pub width: u32,
pub height: u32,
}

26
docker-compose.yml Normal file
View File

@@ -0,0 +1,26 @@
version: '3.1'
services:
db:
image: postgres
#restart: always
ports:
- 5432:5432
environment:
POSTGRES_USER: user
POSTGRES_PASSWORD: password
adminer:
image: adminer
#restart: always
ports:
- 3000:8080
# photos:
# build: .
# environment:
# - DATABASE_URL="postgres://user:password@db/diesel_demo"
# ports:
# - 8081:8081

1
frontend/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
dist/

711
frontend/Cargo.lock generated Normal file
View File

@@ -0,0 +1,711 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "anyhow"
version = "1.0.42"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "595d3cfa7a60d4555cb5067b99f07142a08ea778de5cf993f7b75c7d8fabc486"
[[package]]
name = "anymap"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33954243bd79057c2de7338850b85983a44588021f8a5fee574a8888c6de4344"
[[package]]
name = "arrayvec"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "boolinator"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cfa8873f51c92e232f9bac4065cddef41b714152812bfc5f7672ba16d6ef8cd9"
[[package]]
name = "bumpalo"
version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c59e7af012c713f529e7a3ee57ce9b31ddd858d4b512923602f74608b009631"
[[package]]
name = "bytes"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cfg-match"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8100e46ff92eb85bf6dc2930c73f2a4f7176393c84a9446b3d501e1b354e7b34"
[[package]]
name = "console_error_panic_hook"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8d976903543e0c48546a91908f21588a680a8c8f984df9a5d69feccb2b2a211"
dependencies = [
"cfg-if 0.1.10",
"wasm-bindgen",
]
[[package]]
name = "convert_case"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
[[package]]
name = "derive_more"
version = "0.99.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40eebddd2156ce1bb37b20bbe5151340a31828b1f2d22ba4141f3531710e38df"
dependencies = [
"convert_case",
"proc-macro2",
"quote",
"rustc_version",
"syn",
]
[[package]]
name = "either"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
[[package]]
name = "fixedbitset"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e"
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "gloo"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ce6f2dfa9f57f15b848efa2aade5e1850dc72986b87a2b0752d44ca08f4967"
dependencies = [
"gloo-console-timer",
"gloo-events",
"gloo-file",
"gloo-timers",
]
[[package]]
name = "gloo-console-timer"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b48675544b29ac03402c6dffc31a912f716e38d19f7e74b78b7e900ec3c941ea"
dependencies = [
"web-sys",
]
[[package]]
name = "gloo-events"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "088514ec8ef284891c762c88a66b639b3a730134714692ee31829765c5bc814f"
dependencies = [
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "gloo-file"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f9fecfe46b5dc3cc46f58e98ba580cc714f2c93860796d002eb3527a465ef49"
dependencies = [
"gloo-events",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "gloo-timers"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f"
dependencies = [
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "hashbrown"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
[[package]]
name = "http"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "527e8c9ac747e28542699a951517aa9a6945af506cd1f2e1b53a576c17b6cc11"
dependencies = [
"bytes",
"fnv",
"itoa",
]
[[package]]
name = "indexmap"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]]
name = "integer-sqrt"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770"
dependencies = [
"num-traits",
]
[[package]]
name = "itertools"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
dependencies = [
"either",
]
[[package]]
name = "itoa"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
[[package]]
name = "js-sys"
version = "0.3.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "83bdfbace3a0e81a4253f73b49e960b053e396a11012cbd49b9b74d6a2b67062"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lexical-core"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe"
dependencies = [
"arrayvec",
"bitflags",
"cfg-if 1.0.0",
"ryu",
"static_assertions",
]
[[package]]
name = "libc"
version = "0.2.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790"
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "memchr"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
[[package]]
name = "memory_units"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3"
[[package]]
name = "nom"
version = "5.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
dependencies = [
"lexical-core",
"memchr",
"version_check",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "pathfinding"
version = "3.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84da3eab6c7638931f4876ebb03455be74db8eab1e344cd5a90daba8b3ad2f22"
dependencies = [
"fixedbitset",
"indexmap",
"integer-sqrt",
"itertools",
"num-traits",
"rustc-hash",
"thiserror",
]
[[package]]
name = "pest"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
dependencies = [
"ucd-trie",
]
[[package]]
name = "proc-macro2"
version = "1.0.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612"
dependencies = [
"unicode-xid",
]
[[package]]
name = "quote"
version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustc-hash"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc_version"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee"
dependencies = [
"semver",
]
[[package]]
name = "ryu"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "semver"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6"
dependencies = [
"semver-parser",
]
[[package]]
name = "semver-parser"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7"
dependencies = [
"pest",
]
[[package]]
name = "serde"
version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.126"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.64"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79"
dependencies = [
"itoa",
"ryu",
"serde",
]
[[package]]
name = "slab"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527"
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "syn"
version = "1.0.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c"
dependencies = [
"proc-macro2",
"quote",
"unicode-xid",
]
[[package]]
name = "thiserror"
version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "ucd-trie"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]]
name = "unicode-xid"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
[[package]]
name = "version_check"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]]
name = "wasm-bindgen"
version = "0.2.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994"
dependencies = [
"cfg-if 1.0.0",
"serde",
"serde_json",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a"
dependencies = [
"bumpalo",
"lazy_static",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fba7978c679d53ce2d0ac80c8c175840feb849a161664365d1287b41f2e67f1"
dependencies = [
"cfg-if 1.0.0",
"js-sys",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.81"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be"
[[package]]
name = "web-sys"
version = "0.3.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e828417b379f3df7111d3a2a9e5753706cae29c41f7c4029ee9fd77f3e09e582"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "wee_alloc"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e"
dependencies = [
"cfg-if 0.1.10",
"libc",
"memory_units",
"winapi",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "ybc"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0c409d21870c31cc3beb3b5ba8447306ecfac198876fa73bdce861b23299121"
dependencies = [
"derive_more",
"web-sys",
"yew",
"yew-router",
"yewtil",
]
[[package]]
name = "yew"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4d5154faef86dddd2eb333d4755ea5643787d20aca683e58759b0e53351409f"
dependencies = [
"anyhow",
"anymap",
"bincode",
"cfg-if 1.0.0",
"cfg-match",
"console_error_panic_hook",
"gloo",
"http",
"indexmap",
"js-sys",
"log",
"ryu",
"serde",
"serde_json",
"slab",
"thiserror",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"yew-macro",
]
[[package]]
name = "yew-example"
version = "0.1.0"
dependencies = [
"console_error_panic_hook",
"pathfinding",
"wasm-bindgen",
"wee_alloc",
"ybc",
"yew",
]
[[package]]
name = "yew-macro"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6e23bfe3dc3933fbe9592d149c9985f3047d08c637a884b9344c21e56e092ef"
dependencies = [
"boolinator",
"lazy_static",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "yew-router"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27666236d9597eac9be560e841e415e20ba67020bc8cd081076be178e159c8bc"
dependencies = [
"cfg-if 1.0.0",
"cfg-match",
"gloo",
"js-sys",
"log",
"nom",
"serde",
"serde_json",
"wasm-bindgen",
"web-sys",
"yew",
"yew-router-macro",
"yew-router-route-parser",
]
[[package]]
name = "yew-router-macro"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c0ace2924b7a175e2d1c0e62ee7022a5ad840040dcd52414ce5f410ab322dba"
dependencies = [
"proc-macro2",
"quote",
"syn",
"yew-router-route-parser",
]
[[package]]
name = "yew-router-route-parser"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de4a67208fb46b900af18a7397938b01f379dfc18da34799cfa8347eec715697"
dependencies = [
"nom",
]
[[package]]
name = "yewtil"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8543663ac49cd613df079282a1d8bdbdebdad6e02bac229f870fd4237b5d9aaa"
dependencies = [
"log",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
"yew",
]

22
frontend/Cargo.toml Normal file
View File

@@ -0,0 +1,22 @@
[package]
name = "yew-example"
version = "0.1.0"
authors = ["Anthony Dodd <dodd.anthonyjosiah@gmail.com>"]
edition = "2018"
[dependencies]
console_error_panic_hook = "0.1.6"
wasm-bindgen = "=0.2.81"
wee_alloc = "0.4.5"
ybc = "0.2"
yew = "0.18"
pathfinding = "3.0.13"
common = { path = "../common" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
anyhow = "1.0.58"
[features]
default = []
demo-abc = []
demo-xyz = []

7
frontend/Trunk.toml Normal file
View File

@@ -0,0 +1,7 @@
[build]
target = "index.html"
dist = "dist"
port = 3000
[[proxy]]
backend = "http://localhost:8081/api"

19
frontend/index.html Normal file
View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Trunk | Yew | YBC</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.3/css/bulma.min.css"/>
<link data-trunk rel="scss" href="src/index.scss"/>
<link data-trunk rel="scss" data-inline href="src/inline-scss.scss"/>
<link data-trunk rel="css" href="src/app.css"/>
<link data-trunk rel="icon" href="src/yew.svg"/>
<link data-trunk rel="copy-file" href="src/yew.svg"/>
<base data-trunk-public-url/>
</head>
<body>
<link data-trunk rel="rust" href="Cargo.toml" data-wasm-opt="z" data-bin="yew-example" data-cargo-features="demo-abc,demo-xyz"/>
</body>
</html>

1
frontend/src/app.css Normal file
View File

@@ -0,0 +1 @@
body {}

3
frontend/src/index.scss Normal file
View File

@@ -0,0 +1,3 @@
@charset "utf-8";
html {}

View File

@@ -0,0 +1,4 @@
.trunk_yew_example_fancy_green {
$nice_color: green;
background-color: $nice_color;
}

196
frontend/src/main.rs Normal file
View File

@@ -0,0 +1,196 @@
#![recursion_limit = "1024"]
#[global_allocator]
static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT;
use common::OutputPicture;
use console_error_panic_hook::set_once as set_panic_hook;
use wasm_bindgen::prelude::*;
use ybc::TileCtx::{Ancestor, Child, Parent};
use yew::prelude::*;
// use pathfinding::directed::dijkstra;
use yew::format::Json;
use yew::format::Nothing;
use yew::services::fetch::FetchService;
use yew::services::ConsoleService;
use yew::services::fetch::FetchTask;
use yew::services::fetch::Request;
use yew::services::fetch::Response;
pub enum Msg {
GetPictures,
ReceiveResponse(Result<Vec<OutputPicture>, anyhow::Error>),
}
struct App {
pictures: Option<Vec<OutputPicture>>,
fetch_task: Option<FetchTask>,
link: ComponentLink<Self>,
error: Option<String>,
}
impl App {
fn view_fetching(&self) -> Html {
if self.fetch_task.is_some() {
html! { <p>{ "Fetching data..." }</p> }
} else {
html! { <p></p> }
}
}
fn view_error(&self) -> Html {
if let Some(ref error) = self.error {
html! { <p>{ error.clone() }</p> }
} else {
html! {}
}
}
}
impl Component for App {
type Message = Msg;
type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
Self {
fetch_task: None,
link,
pictures: None,
error: None,
}
}
fn update(&mut self, msg: Self::Message) -> bool {
match msg {
Msg::GetPictures => {
let request = Request::get("http://localhost/api/pictures/")
.body(Nothing)
.expect("Could not build that request");
let callback =
self.link
.callback(|response: Response<Json<Result<Vec<OutputPicture>, anyhow::Error>>>| {
let Json(data) = response.into_body();
Msg::ReceiveResponse(data)
});
let task = FetchService::fetch(request, callback).expect("failed to start request");
self.fetch_task = Some(task);
true
}
Msg::ReceiveResponse(response) => {
match response {
Ok(pictures) => {
self.pictures = Some(pictures);
}
Err(error) => {
self.error = Some(error.to_string());
}
}
self.fetch_task = None;
true
}
}
}
fn change(&mut self, _: Self::Properties) -> bool {
false
}
fn view(&self) -> Html {
html! {
<>
<ybc::Navbar
classes=classes!("is-success")
padded=true
navbrand=html!{
<ybc::NavbarItem>
<ybc::Title classes=classes!("has-text-white") size=ybc::HeaderSize::Is4>{"Trunk | Yew | YBC"}</ybc::Title>
</ybc::NavbarItem>
}
navstart=html!{}
navend=html!{
<>
<ybc::NavbarItem>
<ybc::ButtonAnchor classes=classes!("is-black", "is-outlined") rel=String::from("noopener noreferrer") target=String::from("_blank") href="https://github.com/thedodd/trunk">
{"Trunk"}
</ybc::ButtonAnchor>
</ybc::NavbarItem>
<ybc::NavbarItem>
<ybc::ButtonAnchor classes=classes!("is-black", "is-outlined") rel=String::from("noopener noreferrer") target=String::from("_blank") href="https://yew.rs">
{"Yew"}
</ybc::ButtonAnchor>
</ybc::NavbarItem>
<ybc::NavbarItem>
<ybc::ButtonAnchor classes=classes!("is-black", "is-outlined") rel=String::from("noopener noreferrer") target=String::from("_blank") href="https://github.com/thedodd/ybc">
{"YBC"}
</ybc::ButtonAnchor>
</ybc::NavbarItem>
</>
}
/>
<ybc::Hero
classes=classes!("is-light")
size=ybc::HeroSize::FullheightWithNavbar
body=html!{
<ybc::Container classes=classes!("is-centered")>
<ybc::Tile ctx=Ancestor>
<>
<button onclick=self.link.callback(|_| Msg::GetPictures)>
{ "Load pictures" }
</button>
{ self.view_fetching() }
{ self.view_error() }
</>
<ybc::Tile ctx=Parent size=ybc::TileSize::Twelve>
<ybc::Tile ctx=Parent>
<ybc::Tile ctx=Child classes=classes!("notification", "is-success")>
<ybc::Subtitle size=ybc::HeaderSize::Is3 classes=classes!("has-text-white")>{"Trunk"}</ybc::Subtitle>
<p>{"Trunk is a WASM web application bundler for Rust."}</p>
</ybc::Tile>
</ybc::Tile>
<ybc::Tile ctx=Parent>
<ybc::Tile ctx=Child classes=classes!("notification", "is-success")>
<ybc::Icon size=ybc::Size::Large classes=classes!("is-pulled-right")><img src="yew.svg"/></ybc::Icon>
<ybc::Subtitle size=ybc::HeaderSize::Is3 classes=classes!("has-text-white")>
{"Yew"}
</ybc::Subtitle>
<p>{"Yew is a modern Rust framework for creating multi-threaded front-end web apps with WebAssembly."}</p>
</ybc::Tile>
</ybc::Tile>
<ybc::Tile ctx=Parent>
<ybc::Tile ctx=Child classes=classes!("notification", "is-success")>
<ybc::Subtitle size=ybc::HeaderSize::Is3 classes=classes!("has-text-white")>{"YBC"}</ybc::Subtitle>
<p>{"A Yew component library based on the Bulma CSS framework."}</p>
</ybc::Tile>
</ybc::Tile>
</ybc::Tile>
</ybc::Tile>
</ybc::Container>
}>
</ybc::Hero>
</>
}
}
}
#[wasm_bindgen(inline_js = "export function snippetTest() { console.log('Hello from JS FFI!'); }")]
extern "C" {
fn snippetTest();
}
fn main() {
set_panic_hook();
snippetTest();
// Show off some feature flag enabling patterns.
#[cfg(feature = "demo-abc")]
{
ConsoleService::log("feature `demo-abc` enabled");
}
#[cfg(feature = "demo-xyz")]
{
ConsoleService::log("feature `demo-xyz` enabled");
}
yew::start_app::<App>();
}

7
frontend/src/yew.svg Normal file
View File

@@ -0,0 +1,7 @@
<svg width="75" height="82" viewBox="0 0 75 82" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="38" cy="40" r="25" fill="#FFEFB8"/>
<path d="M38.2373 41.0339L14 14" stroke="#444444" stroke-width="6" stroke-linecap="round"/>
<path d="M38.2373 41.0339L62.4746 14" stroke="#444444" stroke-width="6" stroke-linecap="round"/>
<path d="M38.2373 41.0339L38.2373 69" stroke="#444444" stroke-width="6" stroke-linecap="round"/>
<circle cx="38" cy="41" r="7" fill="#FFD707" stroke="#444444" stroke-width="4"/>
</svg>

After

Width:  |  Height:  |  Size: 518 B

3
src/main.rs Normal file
View File

@@ -0,0 +1,3 @@
fn main() {
println!("Hello world!")
}