Content-Length: 52136 | pFad | http://github.com/postgresml/postgresml/pull/966.diff
thub.com
diff --git a/pgml-apps/cargo-pgml-components/.gitignore b/pgml-apps/cargo-pgml-components/.gitignore
new file mode 100644
index 000000000..608af9905
--- /dev/null
+++ b/pgml-apps/cargo-pgml-components/.gitignore
@@ -0,0 +1 @@
+/static
diff --git a/pgml-apps/cargo-pgml-components/Cargo.lock b/pgml-apps/cargo-pgml-components/Cargo.lock
index 3c5ee69e9..fa54e9722 100644
--- a/pgml-apps/cargo-pgml-components/Cargo.lock
+++ b/pgml-apps/cargo-pgml-components/Cargo.lock
@@ -59,6 +59,18 @@ dependencies = [
"windows-sys",
]
+[[package]]
+name = "anyhow"
+version = "1.0.75"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
[[package]]
name = "bitflags"
version = "2.4.0"
@@ -67,14 +79,17 @@ checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
[[package]]
name = "cargo-pgml-components"
-version = "0.1.5"
+version = "0.1.7"
dependencies = [
+ "anyhow",
"clap",
"convert_case",
"env_logger",
"glob",
"log",
"md5",
+ "owo-colors",
+ "sailfish",
]
[[package]]
@@ -86,6 +101,12 @@ dependencies = [
"libc",
]
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
[[package]]
name = "clap"
version = "4.4.1"
@@ -155,6 +176,12 @@ dependencies = [
"termcolor",
]
+[[package]]
+name = "equivalent"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
+
[[package]]
name = "errno"
version = "0.3.3"
@@ -176,12 +203,30 @@ dependencies = [
"libc",
]
+[[package]]
+name = "filetime"
+version = "0.2.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4029edd3e734da6fe05b6cd7bd2960760a616bd2ddd0d59a0124746d6272af0"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "windows-sys",
+]
+
[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
+[[package]]
+name = "hashbrown"
+version = "0.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a"
+
[[package]]
name = "heck"
version = "0.4.1"
@@ -194,12 +239,31 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
+[[package]]
+name = "home"
+version = "0.5.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb"
+dependencies = [
+ "windows-sys",
+]
+
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
+[[package]]
+name = "indexmap"
+version = "2.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d"
+dependencies = [
+ "equivalent",
+ "hashbrown",
+]
+
[[package]]
name = "is-terminal"
version = "0.4.9"
@@ -211,6 +275,12 @@ dependencies = [
"windows-sys",
]
+[[package]]
+name = "itoap"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9028f49264629065d057f340a86acb84867925865f73bbf8d47b4d149a7e88b8"
+
[[package]]
name = "libc"
version = "0.2.147"
@@ -247,6 +317,12 @@ version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
+[[package]]
+name = "owo-colors"
+version = "3.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
+
[[package]]
name = "proc-macro2"
version = "1.0.66"
@@ -265,6 +341,15 @@ dependencies = [
"proc-macro2",
]
+[[package]]
+name = "redox_syscall"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
+dependencies = [
+ "bitflags 1.3.2",
+]
+
[[package]]
name = "regex"
version = "1.9.4"
@@ -300,13 +385,86 @@ version = "0.38.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed6248e1caa625eb708e266e06159f135e8c26f2bb7ceb72dc4b2766d0340964"
dependencies = [
- "bitflags",
+ "bitflags 2.4.0",
"errno",
"libc",
"linux-raw-sys",
"windows-sys",
]
+[[package]]
+name = "ryu"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
+
+[[package]]
+name = "sailfish"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7519b7521780097b0183bb4b0c7c2165b924f5f1d44c3ef765bde8c2f8008fd1"
+dependencies = [
+ "itoap",
+ "ryu",
+ "sailfish-macros",
+ "version_check",
+]
+
+[[package]]
+name = "sailfish-compiler"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "535500faca492ee8054fbffdfca6447ca97fa495e0ede9f28fa473e1a44f9d5c"
+dependencies = [
+ "filetime",
+ "home",
+ "memchr",
+ "proc-macro2",
+ "quote",
+ "serde",
+ "syn",
+ "toml",
+]
+
+[[package]]
+name = "sailfish-macros"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06a95a6b8a0f59bf66f430a4ed37ece23fcefcd26898399573043e56fb202be2"
+dependencies = [
+ "proc-macro2",
+ "sailfish-compiler",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.188"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.188"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_spanned"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186"
+dependencies = [
+ "serde",
+]
+
[[package]]
name = "strsim"
version = "0.10.0"
@@ -333,6 +491,40 @@ dependencies = [
"winapi-util",
]
+[[package]]
+name = "toml"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542"
+dependencies = [
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_edit",
+]
+
+[[package]]
+name = "toml_datetime"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "toml_edit"
+version = "0.19.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8123f27e969974a3dfba720fdb560be359f57b44302d280ba72e76a74480e8a"
+dependencies = [
+ "indexmap",
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "winnow",
+]
+
[[package]]
name = "unicode-ident"
version = "1.0.11"
@@ -351,6 +543,12 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
[[package]]
name = "winapi"
version = "0.3.9"
@@ -447,3 +645,12 @@ name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
+
+[[package]]
+name = "winnow"
+version = "0.5.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc"
+dependencies = [
+ "memchr",
+]
diff --git a/pgml-apps/cargo-pgml-components/Cargo.toml b/pgml-apps/cargo-pgml-components/Cargo.toml
index dcb4cdd23..84be9f5b5 100644
--- a/pgml-apps/cargo-pgml-components/Cargo.toml
+++ b/pgml-apps/cargo-pgml-components/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "cargo-pgml-components"
-version = "0.1.6"
+version = "0.1.7"
edition = "2021"
authors = ["PostgresML "]
license = "MIT"
@@ -15,3 +15,6 @@ clap = { version = "4", features = ["derive"] }
md5 = "0.7"
log = "0.4"
env_logger = "0.10"
+anyhow = "1"
+owo-colors = "3"
+sailfish = "0.8"
diff --git a/pgml-apps/cargo-pgml-components/sailfish.toml b/pgml-apps/cargo-pgml-components/sailfish.toml
new file mode 100644
index 000000000..2d804e09e
--- /dev/null
+++ b/pgml-apps/cargo-pgml-components/sailfish.toml
@@ -0,0 +1 @@
+template_dirs = ["src"]
diff --git a/pgml-apps/cargo-pgml-components/src/frontend/components.rs b/pgml-apps/cargo-pgml-components/src/frontend/components.rs
new file mode 100644
index 000000000..e4bf2b6b0
--- /dev/null
+++ b/pgml-apps/cargo-pgml-components/src/frontend/components.rs
@@ -0,0 +1,134 @@
+use convert_case::{Case, Casing};
+use sailfish::TemplateOnce;
+use std::fs::{create_dir_all, read_dir};
+use std::path::Path;
+use std::process::exit;
+
+use crate::frontend::templates;
+use crate::util::{error, info, unwrap_or_exit, write_to_file};
+
+static COMPONENT_DIRECTORY: &'static str = "src/components";
+static COMPONENT_MOD: &'static str = "src/components/mod.rs";
+
+pub struct Component {
+ name: String,
+}
+
+impl Component {
+ pub fn new(name: &str) -> Component {
+ Component {
+ name: name.to_string(),
+ }
+ }
+
+ pub fn path(&self) -> String {
+ self.name.to_case(Case::Snake).to_string()
+ }
+
+ pub fn name(&self) -> String {
+ self.name.to_case(Case::UpperCamel).to_string()
+ }
+
+ pub fn full_path(&self) -> String {
+ Path::new(COMPONENT_DIRECTORY)
+ .join(&self.path())
+ .display()
+ .to_string()
+ }
+
+ pub fn controller_name(&self) -> String {
+ self.path().replace("_", "-")
+ }
+
+ #[allow(dead_code)]
+ pub fn controller_path(&self) -> String {
+ format!("{}_controller.js", self.path())
+ }
+
+ pub fn rust_module(&self) -> String {
+ let full_path = self.full_path();
+ let path = Path::new(&full_path);
+ let components = path.components();
+
+ components
+ .skip(2) // skip src/components
+ .map(|c| c.as_os_str().to_str().unwrap())
+ .collect::>()
+ .join("::")
+ .to_string()
+ }
+}
+
+impl From<&Path> for Component {
+ fn from(path: &Path) -> Self {
+ assert!(path.is_dir());
+
+ let components = path.components();
+ let name = components
+ .clone()
+ .last()
+ .unwrap()
+ .as_os_str()
+ .to_str()
+ .unwrap();
+ Component::new(name)
+ }
+}
+
+//github.com/ Add a new component.
+pub fn add(name: &str, overwrite: bool) {
+ let component = Component::new(name);
+ let path = Path::new(COMPONENT_DIRECTORY).join(component.path());
+
+ if path.exists() && !overwrite {
+ error(&format!("component {} already exists", component.path()));
+ exit(1);
+ } else {
+ unwrap_or_exit!(create_dir_all(&path));
+ info(&format!("created directory {}", path.display()));
+ }
+
+ let rust = unwrap_or_exit!(templates::Component::new(&component).render_once());
+ let stimulus = unwrap_or_exit!(templates::Stimulus::new(&component).render_once());
+ let html = unwrap_or_exit!(templates::Html::new(&component).render_once());
+ let scss = String::new();
+
+ let html_path = path.join("template.html");
+ unwrap_or_exit!(write_to_file(&html_path, &html));
+ info(&format!("written {}", html_path.display()));
+
+ let stimulus_path = path.join(&format!("{}_controller.js", component.path()));
+ unwrap_or_exit!(write_to_file(&stimulus_path, &stimulus));
+ info(&format!("written {}", stimulus_path.display()));
+
+ let rust_path = path.join("mod.rs");
+ unwrap_or_exit!(write_to_file(&rust_path, &rust));
+ info(&format!("written {}", rust_path.display()));
+
+ let scss_path = path.join(&format!("{}.scss", component.path()));
+ unwrap_or_exit!(write_to_file(&scss_path, &scss));
+ info(&format!("written {}", scss_path.display()));
+
+ update_modules();
+}
+
+//github.com/ Update `mod.rs` with all the components in `src/components`.
+pub fn update_modules() {
+ let mut modules = Vec::new();
+
+ for path in unwrap_or_exit!(read_dir(COMPONENT_DIRECTORY)) {
+ let path = unwrap_or_exit!(path).path();
+
+ if path.is_file() {
+ continue;
+ }
+
+ let component = Component::from(Path::new(&path));
+ modules.push(component);
+ }
+
+ let modules = unwrap_or_exit!(templates::Mod { modules }.render_once());
+
+ unwrap_or_exit!(write_to_file(&Path::new(COMPONENT_MOD), &modules));
+ info(&format!("written {}", COMPONENT_MOD));
+}
diff --git a/pgml-apps/cargo-pgml-components/src/frontend/javascript.rs b/pgml-apps/cargo-pgml-components/src/frontend/javascript.rs
new file mode 100644
index 000000000..90b733122
--- /dev/null
+++ b/pgml-apps/cargo-pgml-components/src/frontend/javascript.rs
@@ -0,0 +1,137 @@
+//! Javascript bundling.
+
+use glob::glob;
+use std::fs::{copy, read_to_string, remove_file, File};
+use std::io::Write;
+use std::process::Command;
+
+use convert_case::{Case, Casing};
+
+use crate::util::{execute_command, info, unwrap_or_exit, warn};
+
+//github.com/ The name of the JS file that imports all other JS files
+//github.com/ created in the modules.
+static MODULES_FILE: &'static str = "static/js/modules.js";
+
+//github.com/ The JS bundle.
+static JS_FILE: &'static str = "static/js/bundle.js";
+static JS_FILE_HASHED: &'static str = "static/js/bundle.{}.js";
+static JS_HASH_FILE: &'static str = "static/js/.pgml-bundle";
+
+//github.com/ Finds all the JS files we have generated or the user has created.
+static MODULES_GLOB: &'static str = "src/components/**/*.js";
+static STATIC_JS_GLOB: &'static str = "static/js/*.js";
+
+//github.com/ Finds old JS bundles we created.
+static OLD_BUNLDES_GLOB: &'static str = "static/js/*.*.js";
+
+//github.com/ JS compiler
+static JS_COMPILER: &'static str = "rollup";
+
+//github.com/ Delete old bundles we may have created.
+fn cleanup_old_bundles() {
+ // Clean up old bundles
+ for file in unwrap_or_exit!(glob(OLD_BUNLDES_GLOB)) {
+ let file = unwrap_or_exit!(file);
+ debug!("removing {}", file.display());
+ unwrap_or_exit!(remove_file(file.clone()));
+ warn(&format!("deleted {}", file.display()));
+ }
+}
+
+fn assemble_modules() {
+ let js = unwrap_or_exit!(glob(MODULES_GLOB));
+ let js = js.chain(unwrap_or_exit!(glob(STATIC_JS_GLOB)));
+
+ // Don't bundle artifacts we produce.
+ let js = js.filter(|path| {
+ let path = path.as_ref().unwrap();
+ let path = path.display().to_string();
+
+ !path.contains("main.js") && !path.contains("bundle.js") && !path.contains("modules.js")
+ });
+
+ let mut modules = unwrap_or_exit!(File::create(MODULES_FILE));
+
+ unwrap_or_exit!(writeln!(&mut modules, "// Build with --bin components"));
+ unwrap_or_exit!(writeln!(
+ &mut modules,
+ "import {{ Application }} from '@hotwired/stimulus'"
+ ));
+ unwrap_or_exit!(writeln!(
+ &mut modules,
+ "const application = Application.start()"
+ ));
+
+ for source in js {
+ let source = unwrap_or_exit!(source);
+
+ let full_path = source.display();
+ let stem = source.file_stem().unwrap().to_str().unwrap();
+ let upper_camel = stem.to_case(Case::UpperCamel);
+
+ let mut controller_name = stem.split("_").collect::>();
+
+ if stem.contains("controller") {
+ let _ = controller_name.pop().unwrap();
+ }
+
+ let controller_name = controller_name.join("-");
+
+ unwrap_or_exit!(writeln!(
+ &mut modules,
+ "import {{ default as {} }} from '../../{}'",
+ upper_camel, full_path
+ ));
+
+ unwrap_or_exit!(writeln!(
+ &mut modules,
+ "application.register('{}', {})",
+ controller_name, upper_camel
+ ));
+ }
+
+ info(&format!("written {}", MODULES_FILE));
+}
+
+pub fn bundle() {
+ cleanup_old_bundles();
+ assemble_modules();
+
+ // Bundle JavaScript.
+ unwrap_or_exit!(execute_command(
+ Command::new(JS_COMPILER)
+ .arg(MODULES_FILE)
+ .arg("--file")
+ .arg(JS_FILE)
+ .arg("--format")
+ .arg("es"),
+ ));
+
+ info(&format!("written {}", JS_FILE));
+
+ // Hash the bundle.
+ let bundle = unwrap_or_exit!(read_to_string(JS_FILE));
+ let hash = format!("{:x}", md5::compute(bundle))
+ .chars()
+ .take(8)
+ .collect::();
+
+ unwrap_or_exit!(copy(JS_FILE, &JS_FILE_HASHED.replace("{}", &hash)));
+ info(&format!("written {}", JS_FILE_HASHED.replace("{}", &hash)));
+
+ // Legacy, remove code from main.js into respective modules.
+ unwrap_or_exit!(copy(
+ "static/js/main.js",
+ &format!("static/js/main.{}.js", &hash)
+ ));
+ info(&format!(
+ "written {}",
+ format!("static/js/main.{}.js", &hash)
+ ));
+
+ let mut hash_file = unwrap_or_exit!(File::create(JS_HASH_FILE));
+ unwrap_or_exit!(writeln!(&mut hash_file, "{}", hash));
+
+ info(&format!("written {}", JS_HASH_FILE));
+}
diff --git a/pgml-apps/cargo-pgml-components/src/frontend/mod.rs b/pgml-apps/cargo-pgml-components/src/frontend/mod.rs
new file mode 100644
index 000000000..55790107f
--- /dev/null
+++ b/pgml-apps/cargo-pgml-components/src/frontend/mod.rs
@@ -0,0 +1,5 @@
+pub mod components;
+pub mod javascript;
+pub mod sass;
+pub mod templates;
+pub mod tools;
diff --git a/pgml-apps/cargo-pgml-components/src/frontend/sass.rs b/pgml-apps/cargo-pgml-components/src/frontend/sass.rs
new file mode 100644
index 000000000..dc74cd5d2
--- /dev/null
+++ b/pgml-apps/cargo-pgml-components/src/frontend/sass.rs
@@ -0,0 +1,102 @@
+//! Collect and compile SASS files to produce CSS stylesheets.
+
+use glob::glob;
+use std::fs::{copy, read_to_string, remove_file, File};
+use std::io::Write;
+use std::process::Command;
+
+use crate::util::{execute_command, info, unwrap_or_exit, warn};
+
+//github.com/ The name of the SASS file that imports all other SASS files
+//github.com/ created in the modules.
+static MODULES_FILE: &'static str = "static/css/modules.scss";
+
+//github.com/ The SASS file assembling all other files.
+static SASS_FILE: &'static str = "static/css/bootstrap-theme.scss";
+
+//github.com/ The CSS bundle.
+static CSS_FILE: &'static str = "static/css/style.css";
+static CSS_FILE_HASHED: &'static str = "static/css/style.{}.css";
+static CSS_HASH_FILE: &'static str = "static/css/.pgml-bundle";
+
+//github.com/ Finds all the SASS files we have generated or the user has created.
+static MODULES_GLOB: &'static str = "src/components/**/*.scss";
+
+//github.com/ Finds old CSS bundles we created.
+static OLD_BUNLDES_GLOB: &'static str = "static/css/style.*.css";
+
+//github.com/ Sass compiler
+static SASS_COMPILER: &'static str = "sass";
+
+//github.com/ Find Sass files and register them with modules.scss.
+fn assemble_modules() {
+ // Assemble SCSS.
+ let scss = unwrap_or_exit!(glob(MODULES_GLOB));
+
+ let mut modules = unwrap_or_exit!(File::create(MODULES_FILE));
+
+ unwrap_or_exit!(writeln!(
+ &mut modules,
+ "// This file is automatically generated."
+ ));
+ unwrap_or_exit!(writeln!(
+ &mut modules,
+ "// There is no need to edit it manually."
+ ));
+ unwrap_or_exit!(writeln!(&mut modules, ""));
+
+ for stylesheet in scss {
+ let stylesheet = unwrap_or_exit!(stylesheet);
+
+ debug!("Adding '{}' to SCSS bundle", stylesheet.display());
+
+ let line = format!(r#"@import "../../{}";"#, stylesheet.display());
+
+ unwrap_or_exit!(writeln!(&mut modules, "{}", line));
+ }
+
+ info(&format!("written {}", MODULES_FILE));
+}
+
+//github.com/ Delete old bundles we may have created.
+fn cleanup_old_bundles() {
+ // Clean up old bundles
+ for file in unwrap_or_exit!(glob(OLD_BUNLDES_GLOB)) {
+ let file = unwrap_or_exit!(file);
+ debug!("removing {}", file.display());
+ unwrap_or_exit!(remove_file(file.clone()));
+ warn(&format!("deleted {}", file.display()));
+ }
+}
+
+//github.com/ Entrypoint.
+pub fn bundle() {
+ crate::frontend::tools::install();
+
+ assemble_modules();
+ cleanup_old_bundles();
+
+ // Build Sass.
+ unwrap_or_exit!(execute_command(
+ Command::new(SASS_COMPILER).arg(SASS_FILE).arg(CSS_FILE),
+ ));
+
+ info(&format!("written {}", CSS_FILE));
+
+ // Hash the bundle to bust all caches.
+ let bundle = read_to_string(CSS_FILE).expect("failed to read bundle.css");
+ let hash = format!("{:x}", md5::compute(bundle))
+ .chars()
+ .take(8)
+ .collect::();
+
+ let hash_file = CSS_FILE_HASHED.replace("{}", &hash);
+
+ unwrap_or_exit!(copy(CSS_FILE, &hash_file));
+ info(&format!("written {}", hash_file));
+
+ let mut hash_file = unwrap_or_exit!(File::create(CSS_HASH_FILE));
+ unwrap_or_exit!(writeln!(&mut hash_file, "{}", hash));
+
+ info(&format!("written {}", CSS_HASH_FILE));
+}
diff --git a/pgml-apps/cargo-pgml-components/src/frontend/templates/bundle.js.tpl b/pgml-apps/cargo-pgml-components/src/frontend/templates/bundle.js.tpl
new file mode 100644
index 000000000..37c5c346c
--- /dev/null
+++ b/pgml-apps/cargo-pgml-components/src/frontend/templates/bundle.js.tpl
@@ -0,0 +1,7 @@
+import { Application } from '@hotwired/stimulus'
+const application = Application.start()
+
+<% for component in components {
+import { default as <%= component.name() %> } from '../../<%= component.controller_path() %>'"
+application.register('<%= component.controller_name() %>')
+<% } %>
diff --git a/pgml-apps/cargo-pgml-components/src/frontend/templates/component.rs.tpl b/pgml-apps/cargo-pgml-components/src/frontend/templates/component.rs.tpl
new file mode 100644
index 000000000..483ccea1d
--- /dev/null
+++ b/pgml-apps/cargo-pgml-components/src/frontend/templates/component.rs.tpl
@@ -0,0 +1,16 @@
+use sailfish::TemplateOnce;
+use crate::components::component;
+
+#[derive(TemplateOnce, Default)]
+#[template(path = "<%= component_path %>/template.html")]
+pub struct <%= component_name %> {
+ value: String,
+}
+
+impl <%= component_name %> {
+ pub fn new() -> <%= component_name %> {
+ <%= component_name %>::default()
+ }
+}
+
+component!(<%= component_name %>);
diff --git a/pgml-apps/cargo-pgml-components/src/frontend/templates/mod.rs b/pgml-apps/cargo-pgml-components/src/frontend/templates/mod.rs
new file mode 100644
index 000000000..2b78f9f64
--- /dev/null
+++ b/pgml-apps/cargo-pgml-components/src/frontend/templates/mod.rs
@@ -0,0 +1,53 @@
+use sailfish::TemplateOnce;
+
+use crate::frontend::components::Component as ComponentModel;
+
+#[derive(TemplateOnce)]
+#[template(path = "frontend/templates/component.rs.tpl")]
+pub struct Component {
+ pub component_name: String,
+ pub component_path: String,
+}
+
+impl Component {
+ pub fn new(component: &ComponentModel) -> Self {
+ Self {
+ component_name: component.name(),
+ component_path: component.path(),
+ }
+ }
+}
+
+#[derive(TemplateOnce)]
+#[template(path = "frontend/templates/template.html.tpl")]
+pub struct Html {
+ pub controller_name: String,
+}
+
+impl Html {
+ pub fn new(component: &ComponentModel) -> Self {
+ Self {
+ controller_name: component.path().replace("_", "-"),
+ }
+ }
+}
+
+#[derive(TemplateOnce)]
+#[template(path = "frontend/templates/stimulus.js.tpl")]
+pub struct Stimulus {
+ pub controller_name: String,
+}
+
+impl Stimulus {
+ pub fn new(component: &ComponentModel) -> Self {
+ Self {
+ controller_name: component.controller_name(),
+ }
+ }
+}
+
+#[derive(TemplateOnce)]
+#[template(path = "frontend/templates/mod.rs.tpl")]
+pub struct Mod {
+ pub modules: Vec,
+}
diff --git a/pgml-apps/cargo-pgml-components/src/frontend/templates/mod.rs.tpl b/pgml-apps/cargo-pgml-components/src/frontend/templates/mod.rs.tpl
new file mode 100644
index 000000000..2458898bd
--- /dev/null
+++ b/pgml-apps/cargo-pgml-components/src/frontend/templates/mod.rs.tpl
@@ -0,0 +1,11 @@
+// This file is automatically generated.
+// You shouldn't modify it manually.
+
+mod component;
+pub(crate) use component::{component, Component};
+
+<% for component in modules.iter() { %>
+// <%= component.full_path() %>
+pub mod <%= component.path() %>;
+pub use <%= component.rust_module() %>::<%= component.name() %>;
+<% } %>
diff --git a/pgml-apps/cargo-pgml-components/src/frontend/templates/stimulus.js.tpl b/pgml-apps/cargo-pgml-components/src/frontend/templates/stimulus.js.tpl
new file mode 100644
index 000000000..ea0564b98
--- /dev/null
+++ b/pgml-apps/cargo-pgml-components/src/frontend/templates/stimulus.js.tpl
@@ -0,0 +1,14 @@
+import { Controller } from '@hotwired/stimulus'
+
+export default class extends Controller {
+ static targets = []
+ static outlets = []
+
+ initialize() {
+ console.log('Initialized <%= controller_name %>')
+ }
+
+ connect() {}
+
+ disconnect() {}
+}
diff --git a/pgml-apps/cargo-pgml-components/src/frontend/templates/template.html.tpl b/pgml-apps/cargo-pgml-components/src/frontend/templates/template.html.tpl
new file mode 100644
index 000000000..a5cf6b8ea
--- /dev/null
+++ b/pgml-apps/cargo-pgml-components/src/frontend/templates/template.html.tpl
@@ -0,0 +1,3 @@
+
+ <%%= value %>
+
diff --git a/pgml-apps/cargo-pgml-components/src/frontend/tools.rs b/pgml-apps/cargo-pgml-components/src/frontend/tools.rs
new file mode 100644
index 000000000..b6b2e785c
--- /dev/null
+++ b/pgml-apps/cargo-pgml-components/src/frontend/tools.rs
@@ -0,0 +1,29 @@
+//! Tools required by us to build stuff.
+
+use crate::util::{error, execute_command, unwrap_or_exit, warn};
+use std::process::{exit, Command};
+
+//github.com/ Required tools.
+static TOOLS: &[&str] = &["sass", "rollup"];
+
+//github.com/ Install any missing tools.
+pub fn install() {
+ if let Err(err) = execute_command(Command::new("node").arg("--version")) {
+ error("Node is not installed. Install it with nvm or your system package manager.");
+ debug!("{}", err);
+ exit(1);
+ }
+
+ for tool in TOOLS {
+ match execute_command(Command::new(tool).arg("--version")) {
+ Ok(_) => (),
+ Err(err) => {
+ debug!("{}", err);
+ warn(&format!("installing {}", tool));
+ unwrap_or_exit!(execute_command(
+ Command::new("npm").arg("install").arg("-g").arg(tool)
+ ));
+ }
+ }
+ }
+}
diff --git a/pgml-apps/cargo-pgml-components/src/main.rs b/pgml-apps/cargo-pgml-components/src/main.rs
index 4ed3305d8..6f2a57a6d 100644
--- a/pgml-apps/cargo-pgml-components/src/main.rs
+++ b/pgml-apps/cargo-pgml-components/src/main.rs
@@ -1,65 +1,20 @@
//! A tool to assemble and bundle our frontend components.
use clap::{Args, Parser, Subcommand};
-use convert_case::{Case, Casing};
-use glob::glob;
use std::env::{current_dir, set_current_dir};
-use std::fs::{create_dir_all, read_to_string, remove_file, File, read_dir};
-use std::io::Write;
+use std::fs::{create_dir_all};
use std::path::Path;
-use std::process::{exit, Command};
#[macro_use]
extern crate log;
+mod frontend;
+mod util;
+use util::{info, unwrap_or_exit};
+
//github.com/ These paths are exepcted to exist in the project directory.
static PROJECT_PATHS: &[&str] = &["src", "static/js", "static/css"];
-//github.com// These executables are required to be installed globally.
-static REQUIRED_EXECUTABLES: &[&str] = &["sass", "rollup"];
-
-static COMPONENT_TEMPLATE_RS: &'static str = r#"
-use sailfish::TemplateOnce;
-use crate::components::component;
-
-#[derive(TemplateOnce, Default)]
-#[template(path = "{component_path}/template.html")]
-pub struct {component_name} {
- value: String,
-}
-
-impl {component_name} {
- pub fn new() -> {component_name} {
- {component_name}::default()
- }
-}
-
-component!({component_name});
-"#;
-
-static COMPONENT_STIMULUS_JS: &'static str = r#"
-import { Controller } from '@hotwired/stimulus'
-
-export default class extends Controller {
- static targets = []
- static outlets = []
-
- initialize() {
- console.log('Initialized {controller_name}')
- }
-
- connect() {}
-
- disconnect() {}
-}
-"#;
-
-static COMPONENT_HTML: &'static str = r#"
-
- <%= value %>
-
-"#;
-
#[derive(Parser, Debug)]
#[command(author, version, about, long_about = None, propagate_version = true, bin_name = "cargo", name = "cargo")]
struct Cli {
@@ -77,8 +32,13 @@ struct PgmlCommands {
#[command(subcommand)]
command: Commands,
+ //github.com/ Specify project path (default: current directory)
#[arg(short, long)]
project_path: Option,
+
+ //github.com/ Overwrite existing files (default: false)
+ #[arg(short, long, default_value = "false")]
+ overwrite: bool,
}
#[derive(Subcommand, Debug)]
@@ -86,15 +46,15 @@ enum Commands {
//github.com/ Bundle SASS and JavaScript into neat bundle files.
Bundle {},
- //github.com/ Add a new component.
- AddComponent {
- name: String,
-
- #[arg(short, long, default_value = "false")]
- overwrite: bool,
- },
+ //github.com/ Add new elements to the project.
+ #[command(subcommand)]
+ Add(AddCommands),
+}
- UpdateComponents {},
+#[derive(Subcommand, Debug)]
+enum AddCommands {
+ //github.com/ Add a new component.
+ Component { name: String },
}
fn main() {
@@ -104,68 +64,15 @@ fn main() {
match cli.subcomand {
CargoSubcommands::PgmlComponents(pgml_commands) => match pgml_commands.command {
Commands::Bundle {} => bundle(pgml_commands.project_path),
- Commands::AddComponent { name, overwrite } => add_component(name, overwrite),
- Commands::UpdateComponents {} => update_components(),
-
+ Commands::Add(command) => match command {
+ AddCommands::Component { name } => crate::frontend::components::add(&name, pgml_commands.overwrite),
+ },
},
}
}
-fn execute_command(command: &mut Command) -> std::io::Result {
- let output = match command.output() {
- Ok(output) => output,
- Err(err) => {
- return Err(err);
- }
- };
-
- let stderr = String::from_utf8_lossy(&output.stderr).to_string();
- let stdout = String::from_utf8_lossy(&output.stderr).to_string();
-
- if !output.status.success() {
- error!(
- "{} failed: {}",
- command.get_program().to_str().unwrap(),
- String::from_utf8_lossy(&output.stderr).to_string(),
- );
- exit(1);
- }
-
- if !stderr.is_empty() {
- warn!("{}", stderr);
- }
-
- if !stdout.is_empty() {
- info!("{}", stdout);
- }
-
- Ok(stdout)
-}
-
-fn check_executables() {
- for executable in REQUIRED_EXECUTABLES {
- match execute_command(Command::new(executable).arg("--version")) {
- Ok(_) => (),
- Err(err) => {
- error!(
- "'{}' is not installed. Install it with 'npm install -g {}'",
- executable, executable
- );
- debug!(
- "Failed to execute '{} --version': {}",
- executable,
- err.to_string()
- );
- exit(1);
- }
- }
- }
-}
-
//github.com/ Bundle SASS and JavaScript into neat bundle files.
fn bundle(project_path: Option) {
- check_executables();
-
// Validate that the required project paths exist.
let cwd = if let Some(project_path) = project_path {
project_path
@@ -179,250 +86,15 @@ fn bundle(project_path: Option) {
let check = path.join(project_path);
if !check.exists() {
- error!(
- "Project path '{}/{}' does not exist but is required",
- path.display(),
- project_path
- );
- exit(1);
+ unwrap_or_exit!(create_dir_all(&check));
+ info(&format!("created {} directory", check.display()));
}
}
- set_current_dir(path).expect("failed to change paths");
-
- // Assemble SCSS.
- let scss = glob("src/components/**/*.scss").expect("failed to glob scss files");
-
- let mut modules =
- File::create("static/css/modules.scss").expect("failed to create modules.scss");
-
- for stylesheet in scss {
- let stylesheet = stylesheet.expect("failed to glob stylesheet");
-
- debug!("Adding '{}' to SCSS bundle", stylesheet.display());
-
- let line = format!(r#"@import "../../{}";"#, stylesheet.display());
-
- writeln!(&mut modules, "{}", line).expect("failed to write line to modules.scss");
- }
-
- drop(modules);
+ unwrap_or_exit!(set_current_dir(path));
+ frontend::sass::bundle();
+ frontend::javascript::bundle();
+ frontend::components::update_modules();
- // Clean up old bundles
- for file in glob("static/css/style.*.css").expect("failed to glob") {
- let file = file.expect("failed to glob file");
- debug!("Removing '{}'", file.display());
- let _ = remove_file(file);
- }
-
- // Bundle SCSS.
- // Build Bootstrap
- execute_command(
- Command::new("sass")
- .arg("static/css/bootstrap-theme.scss")
- .arg("static/css/style.css"),
- )
- .unwrap();
-
- // Hash the bundle.
- let bundle = read_to_string("static/css/style.css").expect("failed to read bundle.css");
- let hash = format!("{:x}", md5::compute(bundle))
- .chars()
- .take(8)
- .collect::();
-
- execute_command(
- Command::new("cp")
- .arg("static/css/style.css")
- .arg(format!("static/css/style.{}.css", hash)),
- )
- .unwrap();
-
- let mut hash_file =
- File::create("static/css/.pgml-bundle").expect("failed to create .pgml-bundle");
- writeln!(&mut hash_file, "{}", hash).expect("failed to write hash to .pgml-bundle");
- drop(hash_file);
-
- debug!("Created css .pgml-bundle with hash {}", hash);
-
- // Assemble JavaScript.
-
- // Remove prebuilt files.
- for file in glob::glob("static/js/*.*.js").expect("failed to glob") {
- let _ = remove_file(file.expect("failed to glob file"));
- }
-
- let js = glob("src/components/**/*.js").expect("failed to glob js files");
- let js = js.chain(glob("static/js/*.js").expect("failed to glob static/js/*.js"));
- let js = js.filter(|path| {
- let path = path.as_ref().unwrap();
- let path = path.display().to_string();
-
- !path.contains("main.js") && !path.contains("bundle.js") && !path.contains("modules.js")
- });
-
- let mut modules = File::create("static/js/modules.js").expect("failed to create modules.js");
-
- writeln!(&mut modules, "// Build with --bin components").unwrap();
- writeln!(
- &mut modules,
- "import {{ Application }} from '@hotwired/stimulus'"
- )
- .expect("failed to write to modules.js");
- writeln!(&mut modules, "const application = Application.start()")
- .expect("failed to write to modules.js");
-
- for source in js {
- let source = source.expect("failed to glob js file");
-
- let full_path = source.display();
- let stem = source.file_stem().unwrap().to_str().unwrap();
- let upper_camel = stem.to_case(Case::UpperCamel);
-
- let mut controller_name = stem.split("_").collect::>();
-
- if stem.contains("controller") {
- let _ = controller_name.pop().unwrap();
- }
-
- let controller_name = controller_name.join("-");
-
- writeln!(
- &mut modules,
- "import {{ default as {} }} from '../../{}'",
- upper_camel, full_path
- )
- .unwrap();
- writeln!(
- &mut modules,
- "application.register('{}', {})",
- controller_name, upper_camel
- )
- .unwrap();
- }
-
- drop(modules);
-
- // Bundle JavaScript.
- execute_command(
- Command::new("rollup")
- .arg("static/js/modules.js")
- .arg("--file")
- .arg("static/js/bundle.js")
- .arg("--format")
- .arg("es"),
- )
- .unwrap();
-
- // Hash the bundle.
- let bundle = read_to_string("static/js/bundle.js").expect("failed to read bundle.js");
- let hash = format!("{:x}", md5::compute(bundle))
- .chars()
- .take(8)
- .collect::();
-
- execute_command(
- Command::new("cp")
- .arg("static/js/bundle.js")
- .arg(format!("static/js/bundle.{}.js", hash)),
- )
- .unwrap();
-
- let mut hash_file =
- File::create("static/js/.pgml-bundle").expect("failed to create .pgml-bundle");
- writeln!(&mut hash_file, "{}", hash).expect("failed to write hash to .pgml-bundle");
- drop(hash_file);
-
- println!("Finished bundling CSS and JavaScript successfully");
-}
-
-fn add_component(name: String, overwrite: bool) {
- let component_name = name.as_str().to_case(Case::UpperCamel);
- let component_path = name.as_str().to_case(Case::Snake);
- let folder = Path::new("src/components").join(&component_path);
-
- if !folder.exists() {
- match create_dir_all(folder.clone()) {
- Ok(_) => (),
- Err(err) => {
- error!(
- "Failed to create path '{}' for component '{}': {}",
- folder.display(),
- name,
- err
- );
- exit(1);
- }
- }
- } else if !overwrite {
- error!("Component '{}' already exists", folder.display());
- exit(1);
- }
-
- // Create mod.rs
- let mod_file = format!(
- "{}",
- COMPONENT_TEMPLATE_RS
- .replace("{component_name}", &component_name)
- .replace("{component_path}", &component_path)
- );
-
- let mod_path = folder.join("mod.rs");
-
- let mut mod_file_fd = File::create(mod_path).expect("failed to create mod.rs");
- writeln!(&mut mod_file_fd, "{}", mod_file.trim()).expect("failed to write mod.rs");
- drop(mod_file_fd);
-
- // Create template.html
- let template_path = folder.join("template.html");
- let mut template_file = File::create(template_path).expect("failed to create template.html");
- let template_source =
- COMPONENT_HTML.replace("{controller_name}", &component_path.replace("_", "-"));
- writeln!(&mut template_file, "{}", template_source.trim(),)
- .expect("failed to write template.html");
- drop(template_file);
-
- // Create Stimulus controller
- let stimulus_path = folder.join(&format!("{}_controller.js", component_path));
- let mut template_file =
- File::create(stimulus_path).expect("failed to create stimulus controller");
- let controller_source =
- COMPONENT_STIMULUS_JS.replace("{controller_name}", &component_path.replace("_", "-"));
- writeln!(&mut template_file, "{}", controller_source.trim())
- .expect("failed to write stimulus controller");
- drop(template_file);
-
- // Create SASS file
- let sass_path = folder.join(&format!("{}.scss", component_path));
- let sass_file = File::create(sass_path).expect("failed to create sass file");
- drop(sass_file);
-
- println!("Component '{}' created successfully", folder.display());
- update_components();
-}
-
-fn update_components() {
- let mut file = File::create("src/components/mod.rs").expect("failed to create mod.rs");
-
- writeln!(&mut file, "// This file is automatically generated by cargo-pgml-components.").expect("failed to write to mod.rs");
- writeln!(&mut file, "// Do not modify it directly.").expect("failed to write to mod.rs");
- writeln!(&mut file, "mod component;").expect("failed to write to mod.rs");
- writeln!(&mut file, "pub(crate) use component::{{component, Component}};").expect("failed to write to mod.rs");
-
- for component in read_dir("src/components").expect("failed to read components directory") {
- let path = component.expect("dir entry").path();
-
- if path.is_file() {
- continue;
- }
-
- let components = path.components();
- let component_name = components.clone().last().expect("component_name").as_os_str().to_str().unwrap();
- let module = components.skip(2).map(|c| c.as_os_str().to_str().unwrap()).collect::>().join("::");
- // let module = format!("crate::{}", module);
- let component_name = component_name.to_case(Case::UpperCamel);
-
- writeln!(&mut file, "pub mod {};", module).expect("failed to write to mod.rs");
- writeln!(&mut file, "pub use {}::{};", module, component_name).expect("failed to write to mod.rs");
- }
+ info("Bundle complete");
}
diff --git a/pgml-apps/cargo-pgml-components/src/util.rs b/pgml-apps/cargo-pgml-components/src/util.rs
new file mode 100644
index 000000000..7205c4a9b
--- /dev/null
+++ b/pgml-apps/cargo-pgml-components/src/util.rs
@@ -0,0 +1,71 @@
+use owo_colors::OwoColorize;
+use std::fs::File;
+use std::io::Write;
+use std::path::Path;
+use std::process::{exit, Command};
+
+macro_rules! unwrap_or_exit {
+ ($i:expr) => {
+ match $i {
+ Ok(v) => v,
+ Err(e) => {
+ error!("{}:{}:{} {e}", file!(), line!(), column!());
+
+ std::process::exit(1);
+ }
+ }
+ };
+}
+
+pub(crate) use unwrap_or_exit;
+
+pub fn info(value: &str) {
+ println!("{}", value.green());
+}
+
+pub fn error(value: &str) {
+ println!("{}", value.red());
+}
+
+pub fn warn(value: &str) {
+ println!("{}", value.yellow());
+}
+
+pub fn execute_command(command: &mut Command) -> std::io::Result {
+ let output = match command.output() {
+ Ok(output) => output,
+ Err(err) => {
+ return Err(err);
+ }
+ };
+
+ let stderr = String::from_utf8_lossy(&output.stderr).to_string();
+ let stdout = String::from_utf8_lossy(&output.stderr).to_string();
+
+ if !output.status.success() {
+ error!(
+ "{} failed: {}",
+ command.get_program().to_str().unwrap(),
+ String::from_utf8_lossy(&output.stderr).to_string(),
+ );
+ exit(1);
+ }
+
+ if !stderr.is_empty() {
+ warn!("{}", stderr);
+ }
+
+ if !stdout.is_empty() {
+ info!("{}", stdout);
+ }
+
+ Ok(stdout)
+}
+
+pub fn write_to_file(path: &Path, content: &str) -> std::io::Result<()> {
+ let mut file = File::create(path)?;
+
+ file.write_all(content.as_bytes())?;
+
+ Ok(())
+}
diff --git a/pgml-dashboard/src/components/mod.rs b/pgml-dashboard/src/components/mod.rs
index 1c5737be0..64af15f8a 100644
--- a/pgml-dashboard/src/components/mod.rs
+++ b/pgml-dashboard/src/components/mod.rs
@@ -1,32 +1,62 @@
-// This file is automatically generated by cargo-pgml-components.
-// Do not modify it directly.
+// This file is automatically generated.
+// You shouldn't modify it manually.
+
mod component;
pub(crate) use component::{component, Component};
+
+
+// src/components/navbar_web_app
pub mod navbar_web_app;
pub use navbar_web_app::NavbarWebApp;
+
+// src/components/navbar
pub mod navbar;
pub use navbar::Navbar;
+
+// src/components/postgres_logo
pub mod postgres_logo;
pub use postgres_logo::PostgresLogo;
+
+// src/components/static_nav_link
pub mod static_nav_link;
pub use static_nav_link::StaticNavLink;
+
+// src/components/modal
pub mod modal;
pub use modal::Modal;
+
+// src/components/static_nav
pub mod static_nav;
pub use static_nav::StaticNav;
+
+// src/components/test_component
pub mod test_component;
pub use test_component::TestComponent;
+
+// src/components/nav
pub mod nav;
pub use nav::Nav;
+
+// src/components/left_nav_web_app
pub mod left_nav_web_app;
pub use left_nav_web_app::LeftNavWebApp;
+
+// src/components/github_icon
pub mod github_icon;
pub use github_icon::GithubIcon;
+
+// src/components/confirm_modal
pub mod confirm_modal;
pub use confirm_modal::ConfirmModal;
+
+// src/components/left_nav_menu
pub mod left_nav_menu;
pub use left_nav_menu::LeftNavMenu;
+
+// src/components/nav_link
pub mod nav_link;
pub use nav_link::NavLink;
+
+// src/components/breadcrumbs
pub mod breadcrumbs;
pub use breadcrumbs::Breadcrumbs;
diff --git a/pgml-dashboard/static/css/modules.scss b/pgml-dashboard/static/css/modules.scss
index e15d16010..c038c5029 100644
--- a/pgml-dashboard/static/css/modules.scss
+++ b/pgml-dashboard/static/css/modules.scss
@@ -1,3 +1,6 @@
+// This file is automatically generated.
+// There is no need to edit it manually.
+
@import "../../src/components/left_nav_menu/left_nav_menu.scss";
@import "../../src/components/left_nav_web_app/left_nav_web_app.scss";
@import "../../src/components/modal/modal.scss";
diff --git a/pgml-dashboard/templates/content/dashboard/panels/model.html b/pgml-dashboard/templates/content/dashboard/panels/model.html
index dc1b392d0..fbe188d2e 100644
--- a/pgml-dashboard/templates/content/dashboard/panels/model.html
+++ b/pgml-dashboard/templates/content/dashboard/panels/model.html
@@ -59,7 +59,7 @@ <%= param %>
+
@@ -46,7 +56,7 @@
-
+
@@ -60,17 +70,7 @@
-
-
-
+
<% if config::dev_mode() { %>
--- a PPN by Garber Painting Akron. With Image Size Reduction included!Fetched URL: http://github.com/postgresml/postgresml/pull/966.diff
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy