Compare commits
2 Commits
0ab6aaac1c
...
cf9737fc76
Author | SHA1 | Date | |
---|---|---|---|
cf9737fc76
|
|||
5c91df206c
|
1
images/modal-default-critical-icon.svg
Normal file
1
images/modal-default-critical-icon.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 20 KiB |
1
images/modal-default-ok-icon.svg
Normal file
1
images/modal-default-ok-icon.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 17 KiB |
1
images/modal-default-warning-icon.svg
Normal file
1
images/modal-default-warning-icon.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 17 KiB |
123
src/_base.scss
123
src/_base.scss
@@ -1,3 +1,5 @@
|
||||
@use "sass:map";
|
||||
|
||||
$font-size: 100vh * 0.01;
|
||||
$icon-size: $font-size * 2;
|
||||
|
||||
@@ -51,11 +53,124 @@ $color-ternary-70: #8B0046;
|
||||
$color-ternary-60: #720033;
|
||||
$color-ternary-50: #5A0022;
|
||||
|
||||
$color-critical: #C91B13;
|
||||
$color-warning: #FFA316;
|
||||
$color-success: #4AAB79;
|
||||
$color-critical-130: #F29088;
|
||||
$color-critical-120: #E86F62;
|
||||
$color-critical-110: #DA4B3C;
|
||||
$color-critical-100: #C91B13;
|
||||
$color-critical-90: #A9191A;
|
||||
$color-critical-80: #88181C;
|
||||
$color-critical-70: #68171B;
|
||||
|
||||
$border-default-color: $greyscale-90;
|
||||
$color-success-130: #A6D3B1;
|
||||
$color-success-120: #8AC59D;
|
||||
$color-success-110: #6CB88A;
|
||||
$color-success-100: #4AAB79;
|
||||
$color-success-90: #418E63;
|
||||
$color-success-80: #38724E;
|
||||
$color-success-70: #2E573A;
|
||||
|
||||
$color-warning-130: #FFCCC0;
|
||||
$color-warning-120: #FFBC9E;
|
||||
$color-warning-110: #FFAC6A;
|
||||
$color-warning-100: #FFA114;
|
||||
$color-warning-90: #D18915;
|
||||
$color-warning-80: #A67016;
|
||||
$color-warning-70: #7D5816;
|
||||
|
||||
$colors: (
|
||||
"greyscale": (
|
||||
90: $greyscale-90,
|
||||
80: $greyscale-80,
|
||||
70: $greyscale-70,
|
||||
60: $greyscale-60,
|
||||
50: $greyscale-50,
|
||||
40: $greyscale-40,
|
||||
30: $greyscale-30,
|
||||
20: $greyscale-20,
|
||||
10: $greyscale-10,
|
||||
0: $greyscale-0,
|
||||
),
|
||||
|
||||
"primary": (
|
||||
150: $color-primary-150,
|
||||
140: $color-primary-140,
|
||||
130: $color-primary-130,
|
||||
120: $color-primary-120,
|
||||
110: $color-primary-110,
|
||||
100: $color-primary-100,
|
||||
90: $color-primary-90,
|
||||
80: $color-primary-80,
|
||||
70: $color-primary-70,
|
||||
60: $color-primary-60,
|
||||
50: $color-primary-50,
|
||||
),
|
||||
|
||||
"secondary": (
|
||||
150: $color-secondary-150,
|
||||
140: $color-secondary-140,
|
||||
130: $color-secondary-130,
|
||||
120: $color-secondary-120,
|
||||
110: $color-secondary-110,
|
||||
100: $color-secondary-100,
|
||||
90: $color-secondary-90,
|
||||
80: $color-secondary-80,
|
||||
70: $color-secondary-70,
|
||||
60: $color-secondary-60,
|
||||
50: $color-secondary-50,
|
||||
),
|
||||
|
||||
"ternary": (
|
||||
150: $color-ternary-150,
|
||||
140: $color-ternary-140,
|
||||
130: $color-ternary-130,
|
||||
120: $color-ternary-120,
|
||||
110: $color-ternary-110,
|
||||
100: $color-ternary-100,
|
||||
90: $color-ternary-90,
|
||||
80: $color-ternary-80,
|
||||
70: $color-ternary-70,
|
||||
60: $color-ternary-60,
|
||||
50: $color-ternary-50,
|
||||
),
|
||||
|
||||
"critical": (
|
||||
130: $color-critical-130,
|
||||
120: $color-critical-120,
|
||||
110: $color-critical-110,
|
||||
100: $color-critical-100,
|
||||
90: $color-critical-90,
|
||||
80: $color-critical-80,
|
||||
70: $color-critical-70,
|
||||
),
|
||||
|
||||
"success": (
|
||||
130: $color-success-130,
|
||||
120: $color-success-120,
|
||||
110: $color-success-110,
|
||||
100: $color-success-100,
|
||||
90: $color-success-90,
|
||||
80: $color-success-80,
|
||||
70: $color-success-70,
|
||||
),
|
||||
|
||||
"warning": (
|
||||
130: $color-warning-130,
|
||||
120: $color-warning-120,
|
||||
110: $color-warning-110,
|
||||
100: $color-warning-100,
|
||||
90: $color-warning-90,
|
||||
80: $color-warning-80,
|
||||
70: $color-warning-70,
|
||||
),
|
||||
);
|
||||
|
||||
@function get-color($color-name, $color-level) {
|
||||
$color: map.get($colors, $color-name);
|
||||
@return map.get($color, $color-level);
|
||||
|
||||
}
|
||||
|
||||
$border-default-color: get-color(greyscale, 90);
|
||||
$border-big-width: 4px;
|
||||
$border-big: solid $border-big-width $border-default-color;
|
||||
$border-normal-width: 2px;
|
||||
|
@@ -7,7 +7,7 @@
|
||||
border: $border-big;
|
||||
border-radius: $border-radius;
|
||||
|
||||
color: $greyscale-0;
|
||||
color: get-color(greyscale, 0);
|
||||
|
||||
font-family: "Geist";
|
||||
font-weight: bold;
|
||||
@@ -18,31 +18,36 @@
|
||||
}
|
||||
}
|
||||
|
||||
.register-button {
|
||||
@mixin button($color-name, $color-level) {
|
||||
@extend %button;
|
||||
|
||||
background-color: $color-ternary-90;
|
||||
background-color: get_color($color-name, $color-level);
|
||||
|
||||
&:hover {
|
||||
background-color: $color-ternary-80;
|
||||
background-color: get_color($color-name, calc($color-level - 10));
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: $color-ternary-70;
|
||||
background-color: get_color($color-name, calc($color-level - 20));
|
||||
}
|
||||
}
|
||||
|
||||
.register-button {
|
||||
@include button(ternary, 90);
|
||||
}
|
||||
|
||||
.login-button {
|
||||
@extend %button;
|
||||
|
||||
background-color: $color-secondary-90;
|
||||
|
||||
&:hover {
|
||||
background-color: $color-secondary-80;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: $color-secondary-70;
|
||||
}
|
||||
@include button(secondary, 90);
|
||||
}
|
||||
|
||||
.success-button {
|
||||
@include button(success, 100);
|
||||
}
|
||||
|
||||
.warning-button {
|
||||
@include button(warning, 100);
|
||||
}
|
||||
|
||||
.error-button {
|
||||
@include button(critical, 100);
|
||||
}
|
||||
|
@@ -75,6 +75,6 @@
|
||||
top: calc($logo-center-pos + ($background-height * 18));
|
||||
}
|
||||
|
||||
background-color: $greyscale-0;
|
||||
background-color: get-color(greyscale, 0);
|
||||
}
|
||||
}
|
||||
|
@@ -22,10 +22,10 @@
|
||||
aspect-ratio: $form-aspect-ratio;
|
||||
|
||||
border: $border-big;
|
||||
border-color: $color-primary-90;
|
||||
border-color: get-color(primary, 90);
|
||||
border-radius: $border-radius;
|
||||
|
||||
background-color: $greyscale-0;
|
||||
background-color: get-color(greyscale, 0);
|
||||
|
||||
display: grid;
|
||||
|
||||
|
@@ -7,6 +7,7 @@ pub mod icons;
|
||||
pub mod loading;
|
||||
pub mod login;
|
||||
pub mod main_window;
|
||||
pub mod modal;
|
||||
pub mod spinner;
|
||||
pub mod text_input;
|
||||
pub mod wallpaper;
|
||||
|
231
src/components/modal.rs
Normal file
231
src/components/modal.rs
Normal file
@@ -0,0 +1,231 @@
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use dioxus::prelude::*;
|
||||
use rand::distributions::{Alphanumeric, DistString};
|
||||
use tracing::{error, warn};
|
||||
|
||||
use super::button::{ErrorButton, SuccessButton, WarningButton};
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/style_vars.rs"));
|
||||
|
||||
use style::{COLOR_CRITICAL_100, COLOR_SUCCESS_100, COLOR_WARNING_100};
|
||||
|
||||
turf::style_sheet!("src/components/modal.scss");
|
||||
|
||||
#[derive(Clone, Eq, PartialEq, Hash)]
|
||||
pub enum Severity {
|
||||
Ok,
|
||||
Warning,
|
||||
Critical,
|
||||
}
|
||||
impl fmt::Display for Severity {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let repr = match self {
|
||||
Self::Ok => "Severity::Ok",
|
||||
Self::Warning => "Severity::Warning",
|
||||
Self::Critical => "Severity::Critical",
|
||||
};
|
||||
write!(f, "{repr}")
|
||||
}
|
||||
}
|
||||
|
||||
struct DicebearConfig<'a> {
|
||||
gesture: &'a str,
|
||||
color: &'a str,
|
||||
browns: Vec<u32>,
|
||||
eyes: Vec<u32>,
|
||||
lips: Vec<u32>,
|
||||
}
|
||||
|
||||
#[derive(Props)]
|
||||
pub struct ModalProps<'a> {
|
||||
pub severity: Severity,
|
||||
#[props(optional)]
|
||||
pub title: Option<&'a str>,
|
||||
pub children: Element<'a>,
|
||||
#[props(optional)]
|
||||
pub on_confirm: Option<EventHandler<'a, MouseEvent>>,
|
||||
}
|
||||
|
||||
fn dicebear_variants() -> &'static HashMap<Severity, DicebearConfig<'static>> {
|
||||
static HASHMAP: OnceLock<HashMap<Severity, DicebearConfig>> = OnceLock::new();
|
||||
HASHMAP.get_or_init(|| {
|
||||
let mut variants = HashMap::new();
|
||||
variants.insert(
|
||||
Severity::Critical,
|
||||
DicebearConfig {
|
||||
gesture: "wavePointLongArms",
|
||||
color: COLOR_CRITICAL_100,
|
||||
browns: vec![2, 6, 11, 13],
|
||||
eyes: vec![2, 4],
|
||||
lips: vec![1, 2, 7, 11, 19, 20, 24, 27],
|
||||
},
|
||||
);
|
||||
variants.insert(
|
||||
Severity::Warning,
|
||||
DicebearConfig {
|
||||
gesture: "pointLongArm",
|
||||
color: COLOR_WARNING_100,
|
||||
browns: vec![2, 5, 10, 13],
|
||||
eyes: vec![1, 3],
|
||||
lips: vec![1, 2, 4, 8, 10, 13, 18, 21, 29],
|
||||
},
|
||||
);
|
||||
variants.insert(
|
||||
Severity::Ok,
|
||||
DicebearConfig {
|
||||
gesture: "okLongArm",
|
||||
color: COLOR_SUCCESS_100,
|
||||
browns: vec![1, 3, 4, 7, 8, 9, 12],
|
||||
eyes: vec![5],
|
||||
lips: vec![3, 5, 9, 14, 17, 22, 23, 25, 30],
|
||||
},
|
||||
);
|
||||
|
||||
variants
|
||||
})
|
||||
}
|
||||
|
||||
fn render_dicebear_variants(values: &[u32]) -> String {
|
||||
values
|
||||
.iter()
|
||||
.map(|l| format!("variant{:02}", l))
|
||||
.collect::<Vec<String>>()
|
||||
.join(",")
|
||||
}
|
||||
|
||||
async fn generate_random_figure(url: &String, severity: Severity) -> Option<String> {
|
||||
let mut res: Option<String> = None;
|
||||
|
||||
let config = match dicebear_variants().get(&severity) {
|
||||
Some(config) => config,
|
||||
None => {
|
||||
error!("No dicebear configuration found for \"{severity}\"");
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
let seed = Alphanumeric.sample_string(&mut rand::thread_rng(), 16);
|
||||
|
||||
let color = config.color;
|
||||
let gesture = config.gesture;
|
||||
let rendered_browns = render_dicebear_variants(&config.browns);
|
||||
let rendered_eyes = render_dicebear_variants(&config.eyes);
|
||||
let rendered_lips = render_dicebear_variants(&config.lips);
|
||||
|
||||
let req = format!(
|
||||
"https://{url}/7.x/notionists/svg?\
|
||||
seed={seed}&\
|
||||
backgroundColor={color}&\
|
||||
gestureProbability=100&gesture={gesture}&\
|
||||
browsProbability=100&brows={rendered_browns}&\
|
||||
eyesProbability=100&eyes={rendered_eyes}&\
|
||||
lipsProbability=100&lips={rendered_lips}"
|
||||
);
|
||||
|
||||
match reqwest::get(req).await {
|
||||
Ok(result) => {
|
||||
match result.text().await {
|
||||
Ok(svg) => {
|
||||
res = Some(svg);
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Error during placeholder loading: {}", err);
|
||||
}
|
||||
};
|
||||
}
|
||||
Err(err) => {
|
||||
error!("Error during placeholder loading: {}", err);
|
||||
}
|
||||
};
|
||||
res
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Modal<'a>(cx: Scope<'a, ModalProps<'a>>) -> Element<'a> {
|
||||
// TODO: Use configuration file
|
||||
let url = "dicebear.tools.adrien.run".to_string();
|
||||
|
||||
let severity = cx.props.severity.clone();
|
||||
|
||||
let random_figure_future = use_future(cx, &url, |url| async move {
|
||||
generate_random_figure(&url, severity).await
|
||||
});
|
||||
|
||||
let figure = match random_figure_future.value() {
|
||||
Some(Some(svg)) => Some(rsx! {
|
||||
div {
|
||||
class: ClassName::MODAL_CONTENT_ICON_PLACEHOLDER,
|
||||
dangerous_inner_html: svg.as_str(),
|
||||
}
|
||||
}),
|
||||
Some(None) => {
|
||||
warn!("No profile image set or generated, display the placeholder");
|
||||
let path = match cx.props.severity {
|
||||
Severity::Ok => "./images/modal-default-ok-icon.svg",
|
||||
Severity::Warning => "./images/modal-default-warning-icon.svg",
|
||||
Severity::Critical => "./images/modal-default-critical-icon.svg",
|
||||
};
|
||||
Some(rsx! {
|
||||
div {
|
||||
class: ClassName::MODAL_CONTENT_ICON_PLACEHOLDER,
|
||||
img {
|
||||
src: path
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
|
||||
let button_class = match cx.props.severity {
|
||||
Severity::Ok => SuccessButton,
|
||||
Severity::Warning => WarningButton,
|
||||
Severity::Critical => ErrorButton,
|
||||
};
|
||||
|
||||
if figure.is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
cx.render(rsx! {
|
||||
style { STYLE_SHEET },
|
||||
|
||||
div {
|
||||
class: ClassName::MODAL,
|
||||
|
||||
div {
|
||||
class: ClassName::MODAL_CONTENT,
|
||||
|
||||
div {
|
||||
class: ClassName::MODAL_CONTENT_ICON,
|
||||
{figure}
|
||||
},
|
||||
|
||||
div {
|
||||
class: ClassName::MODAL_CONTENT_TITLE,
|
||||
cx.props.title,
|
||||
},
|
||||
|
||||
div {
|
||||
class: ClassName::MODAL_CONTENT_MSG,
|
||||
|
||||
&cx.props.children,
|
||||
},
|
||||
|
||||
div {
|
||||
class: ClassName::MODAL_CONTENT_BUTTONS,
|
||||
button_class {
|
||||
onclick: move |evt| {
|
||||
if let Some(cb) = &cx.props.on_confirm {
|
||||
cb.call(evt);
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
107
src/components/modal.scss
Normal file
107
src/components/modal.scss
Normal file
@@ -0,0 +1,107 @@
|
||||
@import "../_base.scss"
|
||||
|
||||
$modal-min-height: 15vh;
|
||||
$modal-max-height: 55vh;
|
||||
|
||||
.modal {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
position: relative;
|
||||
top: -100%;
|
||||
margin-bottom: -100%;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
|
||||
&__content {
|
||||
$content-width: 70vw;
|
||||
$title-height: 3vh;
|
||||
$space-height: 1vh;
|
||||
$buttons-height: 5vh;
|
||||
$icon-width: 10vw;
|
||||
$msg-max-width: 50vw;
|
||||
|
||||
$msg-min-height: calc($modal-min-height - $title-height - $space-height - $buttons-height);
|
||||
$msg-max-height: calc($modal-max-height - $title-height - $space-height - $buttons-height);
|
||||
|
||||
min-height: $modal-min-height;
|
||||
max-height: $modal-max-height;
|
||||
|
||||
width: min-content;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: $icon-width 1vw minmax(max-content, $msg-max-width);
|
||||
grid-template-rows: $title-height $space-height max-content $space-height $buttons-height;
|
||||
grid-template-areas:
|
||||
"icon . title"
|
||||
"icon . ."
|
||||
"icon . msg"
|
||||
". . ."
|
||||
"buttons buttons buttons"
|
||||
;
|
||||
|
||||
padding: 2.5%;
|
||||
|
||||
background: get-color(greyscale, 0);
|
||||
|
||||
border: $border-normal;
|
||||
border-radius: $border-radius;
|
||||
border-color: get-color(greyscale, 90);
|
||||
|
||||
&__icon {
|
||||
grid-area: icon;
|
||||
|
||||
aspect-ratio: 1;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
overflow: hidden;
|
||||
|
||||
border: $border-normal;
|
||||
border-radius: $border-radius;
|
||||
|
||||
&__placeholder {
|
||||
width: calc(100% + (2 * $border-normal-width));
|
||||
height: calc(100% - (2 * $border-normal-width));
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
grid-area: title;
|
||||
|
||||
width: max-content;
|
||||
|
||||
font-size: calc($title-height - 0.5vh);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&__msg {
|
||||
grid-area: msg;
|
||||
|
||||
height: fit-content;
|
||||
|
||||
min-height: $msg-min-height;
|
||||
max-height: $msg-max-height;
|
||||
max-width: $msg-max-width;
|
||||
|
||||
overflow: scroll;
|
||||
|
||||
font-size: 1.5vh;
|
||||
color: get-color(greyscale, 80);
|
||||
}
|
||||
|
||||
&__buttons {
|
||||
grid-area: buttons;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
@@ -18,21 +18,21 @@ $logo-aspect-ratio: calc($logo-width / $logo-height);
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
fill: $color-primary-100;
|
||||
stroke: $greyscale-90;
|
||||
fill: get-color(primary, 100);
|
||||
stroke: get-color(greyscale, 90);
|
||||
|
||||
animation: 3s multicolor linear infinite;
|
||||
animation-timing-function: steps($steps, end);
|
||||
|
||||
@keyframes multicolor {
|
||||
0% {
|
||||
fill: $color-primary-100;
|
||||
fill: get-color(primary, 100);
|
||||
}
|
||||
33% {
|
||||
fill: $color-secondary-100;
|
||||
fill: get-color(secondary, 100);
|
||||
}
|
||||
66% {
|
||||
fill: $color-ternary-100;
|
||||
fill: get-color(ternary, 100);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -7,7 +7,7 @@
|
||||
width: calc(100% - (2 * $horizontal-padding));
|
||||
|
||||
border: $border-normal;
|
||||
border-color: $color-primary-90;
|
||||
border-color: get-color(primary, 90);
|
||||
border-radius: $border-radius;
|
||||
|
||||
padding-left: $horizontal-padding;
|
||||
@@ -33,7 +33,7 @@
|
||||
}
|
||||
|
||||
&.invalid {
|
||||
border-color: $color-critical;
|
||||
border-color: get-color(critical, 100);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,13 +43,13 @@
|
||||
|
||||
font-size: 1.2vh;
|
||||
|
||||
color: $color-primary-90;
|
||||
color: get-color(primary, 90);
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
|
||||
&.invalid {
|
||||
color: $color-critical;
|
||||
color: get-color(critical, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,7 +118,7 @@
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
color: $color-secondary-100;
|
||||
color: get-color(secondary, 100),
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user