add toast and start adding postgres
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@ -5,6 +5,8 @@
|
|||||||
|
|
||||||
# These are backup files generated by rustfmt
|
# These are backup files generated by rustfmt
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
|
migrations/
|
||||||
|
postgres/
|
||||||
serveurhttp
|
serveurhttp
|
||||||
test
|
test
|
||||||
.env
|
.env
|
||||||
|
|||||||
66
Cargo.lock
generated
66
Cargo.lock
generated
@ -776,6 +776,7 @@ dependencies = [
|
|||||||
"ident_case",
|
"ident_case",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
"strsim",
|
||||||
"syn 2.0.117",
|
"syn 2.0.117",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -855,6 +856,38 @@ dependencies = [
|
|||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "diesel"
|
||||||
|
version = "2.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f4ae09a41a4b89f94ec1e053623da8340d996bc32c6517d325a9daad9b239358"
|
||||||
|
dependencies = [
|
||||||
|
"diesel_derives",
|
||||||
|
"downcast-rs",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "diesel_derives"
|
||||||
|
version = "2.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "47618bf0fac06bb670c036e48404c26a865e6a71af4114dfd97dfe89936e404e"
|
||||||
|
dependencies = [
|
||||||
|
"diesel_table_macro_syntax",
|
||||||
|
"dsl_auto_type",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.117",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "diesel_table_macro_syntax"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fe2444076b48641147115697648dc743c2c00b61adade0f01ce67133c7babe8c"
|
||||||
|
dependencies = [
|
||||||
|
"syn 2.0.117",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.10.7"
|
version = "0.10.7"
|
||||||
@ -1630,12 +1663,32 @@ dependencies = [
|
|||||||
"litrs",
|
"litrs",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "downcast-rs"
|
||||||
|
version = "2.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dpi"
|
name = "dpi"
|
||||||
version = "0.1.2"
|
version = "0.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76"
|
checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dsl_auto_type"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dd122633e4bef06db27737f21d3738fb89c8f6d5360d6d9d7635dda142a7757e"
|
||||||
|
dependencies = [
|
||||||
|
"darling",
|
||||||
|
"either",
|
||||||
|
"heck 0.5.0",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.117",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dtoa"
|
name = "dtoa"
|
||||||
version = "1.0.11"
|
version = "1.0.11"
|
||||||
@ -1657,6 +1710,12 @@ version = "1.0.5"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
|
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.15.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.35"
|
version = "0.8.35"
|
||||||
@ -2475,6 +2534,7 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
|||||||
name = "httpserver"
|
name = "httpserver"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"diesel",
|
||||||
"dioxus",
|
"dioxus",
|
||||||
"dioxus-html",
|
"dioxus-html",
|
||||||
"dioxus-primitives",
|
"dioxus-primitives",
|
||||||
@ -4822,6 +4882,12 @@ dependencies = [
|
|||||||
"quote",
|
"quote",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "subsecond"
|
name = "subsecond"
|
||||||
version = "0.7.4"
|
version = "0.7.4"
|
||||||
|
|||||||
@ -7,6 +7,7 @@ edition = "2021"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
diesel = "2.3.7"
|
||||||
dioxus = { version = "0.7.1", features = ["router", "fullstack"] }
|
dioxus = { version = "0.7.1", features = ["router", "fullstack"] }
|
||||||
dioxus-html = "0.7.3"
|
dioxus-html = "0.7.3"
|
||||||
dioxus-primitives = { git = "https://github.com/DioxusLabs/components", version = "0.0.1", default-features = false }
|
dioxus-primitives = { git = "https://github.com/DioxusLabs/components", version = "0.0.1", default-features = false }
|
||||||
|
|||||||
@ -7,6 +7,9 @@
|
|||||||
'Noto Color Emoji';
|
'Noto Color Emoji';
|
||||||
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',
|
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',
|
||||||
monospace;
|
monospace;
|
||||||
|
--color-zinc-900: oklch(21% 0.006 285.885);
|
||||||
|
--color-black: #000;
|
||||||
|
--color-white: #fff;
|
||||||
--spacing: 0.25rem;
|
--spacing: 0.25rem;
|
||||||
--text-4xl: 2.25rem;
|
--text-4xl: 2.25rem;
|
||||||
--text-4xl--line-height: calc(2.5 / 2.25);
|
--text-4xl--line-height: calc(2.5 / 2.25);
|
||||||
@ -170,9 +173,34 @@
|
|||||||
.table {
|
.table {
|
||||||
display: table;
|
display: table;
|
||||||
}
|
}
|
||||||
|
.h-full {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.h-screen {
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
.w-full {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.w-screen {
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
.border-collapse {
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
.transform {
|
.transform {
|
||||||
transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
|
transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
|
||||||
}
|
}
|
||||||
|
.resize {
|
||||||
|
resize: both;
|
||||||
|
}
|
||||||
|
.border {
|
||||||
|
border-style: var(--tw-border-style);
|
||||||
|
border-width: 1px;
|
||||||
|
}
|
||||||
|
.bg-zinc-900 {
|
||||||
|
background-color: var(--color-zinc-900);
|
||||||
|
}
|
||||||
.p-1 {
|
.p-1 {
|
||||||
padding: calc(var(--spacing) * 1);
|
padding: calc(var(--spacing) * 1);
|
||||||
}
|
}
|
||||||
@ -190,6 +218,16 @@
|
|||||||
--tw-font-weight: var(--font-weight-bold);
|
--tw-font-weight: var(--font-weight-bold);
|
||||||
font-weight: var(--font-weight-bold);
|
font-weight: var(--font-weight-bold);
|
||||||
}
|
}
|
||||||
|
.text-white {
|
||||||
|
color: var(--color-white);
|
||||||
|
}
|
||||||
|
.underline {
|
||||||
|
text-decoration-line: underline;
|
||||||
|
}
|
||||||
|
.outline {
|
||||||
|
outline-style: var(--tw-outline-style);
|
||||||
|
outline-width: 1px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@property --tw-rotate-x {
|
@property --tw-rotate-x {
|
||||||
syntax: "*";
|
syntax: "*";
|
||||||
@ -211,10 +249,20 @@
|
|||||||
syntax: "*";
|
syntax: "*";
|
||||||
inherits: false;
|
inherits: false;
|
||||||
}
|
}
|
||||||
|
@property --tw-border-style {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
initial-value: solid;
|
||||||
|
}
|
||||||
@property --tw-font-weight {
|
@property --tw-font-weight {
|
||||||
syntax: "*";
|
syntax: "*";
|
||||||
inherits: false;
|
inherits: false;
|
||||||
}
|
}
|
||||||
|
@property --tw-outline-style {
|
||||||
|
syntax: "*";
|
||||||
|
inherits: false;
|
||||||
|
initial-value: solid;
|
||||||
|
}
|
||||||
@layer properties {
|
@layer properties {
|
||||||
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
|
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
|
||||||
*, ::before, ::after, ::backdrop {
|
*, ::before, ::after, ::backdrop {
|
||||||
@ -223,7 +271,9 @@
|
|||||||
--tw-rotate-z: initial;
|
--tw-rotate-z: initial;
|
||||||
--tw-skew-x: initial;
|
--tw-skew-x: initial;
|
||||||
--tw-skew-y: initial;
|
--tw-skew-y: initial;
|
||||||
|
--tw-border-style: solid;
|
||||||
--tw-font-weight: initial;
|
--tw-font-weight: initial;
|
||||||
|
--tw-outline-style: solid;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
9
diesel.toml
Normal file
9
diesel.toml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# For documentation on how to configure this file,
|
||||||
|
# see https://diesel.rs/guides/configuring-diesel-cli
|
||||||
|
|
||||||
|
[print_schema]
|
||||||
|
file = "src/schema.rs"
|
||||||
|
custom_type_derives = ["diesel::query_builder::QueryId", "Clone"]
|
||||||
|
|
||||||
|
[migrations_directory]
|
||||||
|
dir = "migrations"
|
||||||
10
shell.nix
10
shell.nix
@ -4,13 +4,15 @@ pkgs.mkShell {
|
|||||||
rustup
|
rustup
|
||||||
];
|
];
|
||||||
nativeBuildInputs = with pkgs;[
|
nativeBuildInputs = with pkgs;[
|
||||||
|
libpq
|
||||||
|
libmysqlclient
|
||||||
|
sqlite
|
||||||
|
|
||||||
];
|
];
|
||||||
PKG_CONFIG_PATH = "${pkgs.openssl.dev}/lib/pkgconfig";
|
PKG_CONFIG_PATH = "${pkgs.openssl.dev}/lib/pkgconfig";
|
||||||
|
|
||||||
|
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
source .env
|
[ ! -f .env ] || export $(grep -v '^#' .env | xargs)
|
||||||
|
|
||||||
'';
|
'';
|
||||||
|
|
||||||
UPLOAD_FOLDER="./test/";
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,3 +5,4 @@ pub use hero::Hero;
|
|||||||
mod echo;
|
mod echo;
|
||||||
pub use echo::Echo;
|
pub use echo::Echo;
|
||||||
pub mod progress;
|
pub mod progress;
|
||||||
|
pub mod toast;
|
||||||
|
|||||||
15
src/components/toast/component.rs
Normal file
15
src/components/toast/component.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
use dioxus::prelude::*;
|
||||||
|
use dioxus_primitives::toast::{self, ToastProviderProps};
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn ToastProvider(props: ToastProviderProps) -> Element {
|
||||||
|
rsx! {
|
||||||
|
document::Link { rel: "stylesheet", href: asset!("./style.css") }
|
||||||
|
toast::ToastProvider {
|
||||||
|
default_duration: props.default_duration,
|
||||||
|
max_toasts: props.max_toasts,
|
||||||
|
render_toast: props.render_toast,
|
||||||
|
{props.children}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
2
src/components/toast/mod.rs
Normal file
2
src/components/toast/mod.rs
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
mod component;
|
||||||
|
pub use component::*;
|
||||||
185
src/components/toast/style.css
Normal file
185
src/components/toast/style.css
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
.toast-container {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 9999;
|
||||||
|
right: 20px;
|
||||||
|
bottom: 20px;
|
||||||
|
max-width: 350px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-item {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast {
|
||||||
|
z-index: calc(var(--toast-count) - var(--toast-index));
|
||||||
|
display: flex;
|
||||||
|
overflow: hidden;
|
||||||
|
width: 18rem;
|
||||||
|
height: 4rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border: 1px solid var(--light, var(--primary-color-6))
|
||||||
|
var(--dark, var(--primary-color-7));
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
margin-top: -4rem;
|
||||||
|
box-shadow: 0 4px 12px rgb(0 0 0 / 15%);
|
||||||
|
filter: var(--light, none)
|
||||||
|
var(
|
||||||
|
--dark,
|
||||||
|
brightness(calc(0.5 + 0.5 * (1 - ((var(--toast-index) + 1) / 4))))
|
||||||
|
);
|
||||||
|
opacity: calc(1 - var(--toast-hidden));
|
||||||
|
transform: scale(
|
||||||
|
calc(100% - var(--toast-index) * 5%),
|
||||||
|
calc(100% - var(--toast-index) * 2%)
|
||||||
|
);
|
||||||
|
transition: transform 0.2s ease, margin-top 0.2s ease, opacity 0.2s ease;
|
||||||
|
|
||||||
|
--toast-hidden: calc(min(max(0, var(--toast-index) - 2), 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-container:not(:hover, :focus-within)
|
||||||
|
.toast[data-toast-even]:not([data-top]) {
|
||||||
|
animation: slide-up-even 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-container:not(:hover, :focus-within)
|
||||||
|
.toast[data-toast-odd]:not([data-top]) {
|
||||||
|
animation: slide-up-odd 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slide-up-even {
|
||||||
|
from {
|
||||||
|
transform: translateY(0.5rem)
|
||||||
|
scale(
|
||||||
|
calc(100% - var(--toast-index) * 5%),
|
||||||
|
calc(100% - var(--toast-index) * 2%)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translateY(0)
|
||||||
|
scale(
|
||||||
|
calc(100% - var(--toast-index) * 5%),
|
||||||
|
calc(100% - var(--toast-index) * 2%)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slide-up-odd {
|
||||||
|
from {
|
||||||
|
transform: translateY(0.5rem)
|
||||||
|
scale(
|
||||||
|
calc(100% - var(--toast-index) * 5%),
|
||||||
|
calc(100% - var(--toast-index) * 2%)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: translateY(0)
|
||||||
|
scale(
|
||||||
|
calc(100% - var(--toast-index) * 5%),
|
||||||
|
calc(100% - var(--toast-index) * 2%)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast[data-top] {
|
||||||
|
animation: slide-in 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-container:hover .toast[data-top],
|
||||||
|
.toast-container:focus-within .toast[data-top] {
|
||||||
|
animation: slide-in 0 ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slide-in {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(100%)
|
||||||
|
scale(
|
||||||
|
calc(110% - var(--toast-index) * 5%),
|
||||||
|
calc(110% - var(--toast-index) * 2%)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0)
|
||||||
|
scale(
|
||||||
|
calc(100% - var(--toast-index) * 5%),
|
||||||
|
calc(100% - var(--toast-index) * 2%)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-container:hover .toast,
|
||||||
|
.toast-container:focus-within .toast {
|
||||||
|
margin-top: var(--toast-padding);
|
||||||
|
filter: brightness(1);
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(calc(100%));
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast[data-type="success"] {
|
||||||
|
background-color: var(--primary-success-color);
|
||||||
|
color: var(--secondary-success-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast[data-type="error"] {
|
||||||
|
background-color: var(--primary-error-color);
|
||||||
|
color: var(--contrast-error-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast[data-type="warning"] {
|
||||||
|
background-color: var(--primary-warning-color);
|
||||||
|
color: var(--secondary-warning-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast[data-type="info"] {
|
||||||
|
background-color: var(--primary-info-color);
|
||||||
|
color: var(--secondary-info-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-content {
|
||||||
|
flex: 1;
|
||||||
|
margin-right: 8px;
|
||||||
|
transition: filter 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-title {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
color: var(--secondary-color-4);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-description {
|
||||||
|
color: var(--secondary-color-3);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-close {
|
||||||
|
align-self: flex-start;
|
||||||
|
padding: 0;
|
||||||
|
border: none;
|
||||||
|
margin: 0;
|
||||||
|
background: none;
|
||||||
|
color: var(--secondary-color-3);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toast-close:hover {
|
||||||
|
color: var(--secondary-color-1);
|
||||||
|
}
|
||||||
25
src/config.rs
Normal file
25
src/config.rs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
#[cfg(feature = "server")]
|
||||||
|
pub struct ServerConfig {
|
||||||
|
pub upload_folder: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
impl ServerConfig {
|
||||||
|
pub fn load() -> Self {
|
||||||
|
Self {
|
||||||
|
upload_folder: "./test/".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ClientConfig {
|
||||||
|
pub upload_max_size: usize
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ClientConfig {
|
||||||
|
pub fn load () -> Self {
|
||||||
|
Self {
|
||||||
|
upload_max_size: 1024 * 1024 * 1024 * 1 //1GB for testing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
13
src/main.rs
13
src/main.rs
@ -3,6 +3,10 @@ use dioxus::prelude::*;
|
|||||||
use views::{Upload};
|
use views::{Upload};
|
||||||
use tracing::Level;
|
use tracing::Level;
|
||||||
|
|
||||||
|
pub mod config;
|
||||||
|
|
||||||
|
use crate::components::toast::ToastProvider;
|
||||||
|
|
||||||
mod components;
|
mod components;
|
||||||
mod views;
|
mod views;
|
||||||
|
|
||||||
@ -25,9 +29,14 @@ fn main() {
|
|||||||
fn App() -> Element {
|
fn App() -> Element {
|
||||||
rsx! {
|
rsx! {
|
||||||
document::Link { rel: "icon", href: FAVICON }
|
document::Link { rel: "icon", href: FAVICON }
|
||||||
// document::Link { rel: "stylesheet", href: MAIN_CSS }
|
|
||||||
document::Link { rel: "stylesheet", href: TAILWIND_CSS }
|
document::Link { rel: "stylesheet", href: TAILWIND_CSS }
|
||||||
|
div {
|
||||||
|
class: "h-screen w-screen bg-zinc-900 text-white",
|
||||||
|
ToastProvider {
|
||||||
|
Upload {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Router::<Route> {}
|
// Router::<Route> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
0
src/models/file.rs
Normal file
0
src/models/file.rs
Normal file
@ -1,58 +1,62 @@
|
|||||||
use dioxus::{
|
use dioxus::{
|
||||||
fullstack::{ByteStream, FileStream},
|
fullstack::{FileStream},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
use dioxus_html::{FileData, HasFileData};
|
#[cfg(feature = "server")]
|
||||||
|
use crate::config::ServerConfig;
|
||||||
|
|
||||||
|
use crate::config::ClientConfig;
|
||||||
|
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use std::fs;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Write;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
|
|
||||||
use std::{ env, fs, io::Write };
|
use dioxus_primitives::toast::{
|
||||||
|
ToastOptions,
|
||||||
|
consume_toast
|
||||||
use random_string;
|
};
|
||||||
|
|
||||||
pub fn byte_to_human_size(size: u64) -> String {
|
pub fn byte_to_human_size(size: u64) -> String {
|
||||||
let sizes = vec!["B", "KB", "MB", "GB", "TB", "WTFAREYOUDOINGB"];
|
let sizes = vec!["B", "KB", "MB", "GB", "TB", "WTFAREYOUDOINGB"];
|
||||||
let mut current = 0;
|
let mut current = 0;
|
||||||
|
|
||||||
let mut res_size: f64 = size as f64;
|
let mut res_size: f64 = size as f64;
|
||||||
let mut res: String;
|
|
||||||
|
|
||||||
while (res_size >= 1000.0 && current < sizes.len()) {
|
while res_size >= 1000.0 && current < sizes.len() {
|
||||||
res_size /= 1000.0;
|
res_size /= 1000.0;
|
||||||
current += 1;
|
current += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
res = ((res_size * 100.0).round() / 100.0).to_string() + " " + sizes[current];
|
((res_size * 100.0).round() / 100.0).to_string() + " " + sizes[current]
|
||||||
res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[post("/api/upload")]
|
#[post("/api/upload")]
|
||||||
async fn upload_file(mut upload: FileStream) -> Result<String, HttpError> {
|
async fn upload_file(mut upload: FileStream) -> Result<String, HttpError> {
|
||||||
let UPLOAD_SIZE_LIMIT: u64 = 1024 * 1024 * 1024 * 1;
|
let s_config = ServerConfig::load();
|
||||||
let UPLOAD_FOLDER: String = match env::var("UPLOAD_FOLDER") {
|
let c_config = ClientConfig::load();
|
||||||
Ok(value) => { value },
|
|
||||||
Err(_) => { return HttpError::internal_server_error("UPLOAD_FOLDER not set"); }
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut total_len: u64 = 0;
|
let mut total_len: u64 = 0;
|
||||||
let mut error: Option<&str> = None;
|
let mut error: Option<&str> = None;
|
||||||
|
|
||||||
let filename: String = loop {
|
let filename: String = loop {
|
||||||
let cur = random_string::generate(20, "ABCDEFGHIJKLMNOPQRTSTUVWXYZ0123456789");
|
let cur = random_string::generate(20, "ABCDEFGHIJKLMNOPQRTSTUVWXYZ0123456789");
|
||||||
if (!fs::exists(UPLOAD_FOLDER.clone() + &cur).expect("can't check if file exists")) {
|
if (!fs::exists(s_config.upload_folder.clone() + &cur).expect("can't check if file exists")) {
|
||||||
break cur;
|
break cur;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(size) = upload.size() {
|
if let Some(size) = upload.size() {
|
||||||
if size > UPLOAD_SIZE_LIMIT {
|
if size > c_config.upload_max_size.try_into().unwrap() {
|
||||||
return HttpError::payload_too_large("this file is too large");
|
return HttpError::payload_too_large("this file is too large");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
let mut file = match fs::File::create(UPLOAD_FOLDER.clone() + &filename) {
|
let mut file = match fs::File::create(s_config.upload_folder.clone() + &filename) {
|
||||||
Ok(val) => { val },
|
Ok(val) => { val },
|
||||||
Err(_) => { return HttpError::internal_server_error("failed to open the output file") }
|
Err(_) => { return HttpError::internal_server_error("failed to open the output file") }
|
||||||
};
|
};
|
||||||
@ -61,7 +65,7 @@ async fn upload_file(mut upload: FileStream) -> Result<String, HttpError> {
|
|||||||
match chunk {
|
match chunk {
|
||||||
Ok(bytes) => {
|
Ok(bytes) => {
|
||||||
total_len += bytes.len() as u64;
|
total_len += bytes.len() as u64;
|
||||||
if total_len > UPLOAD_SIZE_LIMIT {
|
if total_len > c_config.upload_max_size.try_into().unwrap() {
|
||||||
error = Some("Uploaded file too large");
|
error = Some("Uploaded file too large");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -75,7 +79,7 @@ async fn upload_file(mut upload: FileStream) -> Result<String, HttpError> {
|
|||||||
match error {
|
match error {
|
||||||
Some(err)=> {
|
Some(err)=> {
|
||||||
file.sync_data();
|
file.sync_data();
|
||||||
fs::remove_file(UPLOAD_FOLDER.clone() + &filename);
|
fs::remove_file(s_config.upload_folder.clone() + &filename);
|
||||||
HttpError::internal_server_error(err)?
|
HttpError::internal_server_error(err)?
|
||||||
}
|
}
|
||||||
None => { Ok(filename) }
|
None => { Ok(filename) }
|
||||||
@ -101,7 +105,18 @@ pub fn build_table(files: Vec<(String, String, Option<Result<String, HttpError>>
|
|||||||
Some(res) => { match res {
|
Some(res) => { match res {
|
||||||
Ok(file_url) => {let url = file_url.clone(); rsx! { input {
|
Ok(file_url) => {let url = file_url.clone(); rsx! { input {
|
||||||
type:"button",
|
type:"button",
|
||||||
onclick: move |_| { web_sys::window().unwrap().navigator().clipboard().write_text(&url); },
|
onclick: move |_| {
|
||||||
|
let toast_api = consume_toast();
|
||||||
|
toast_api
|
||||||
|
.success(
|
||||||
|
"Success".to_string(),
|
||||||
|
ToastOptions::new()
|
||||||
|
.description("The url has been copied successfully")
|
||||||
|
.duration(Duration::from_secs(5))
|
||||||
|
.permanent(false),
|
||||||
|
);
|
||||||
|
let _ = web_sys::window().unwrap().navigator().clipboard().write_text(&url);
|
||||||
|
},
|
||||||
value: "{file_url}"
|
value: "{file_url}"
|
||||||
} } },
|
} } },
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@ -120,13 +135,12 @@ pub fn build_table(files: Vec<(String, String, Option<Result<String, HttpError>>
|
|||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn Upload() -> Element {
|
pub fn Upload() -> Element {
|
||||||
//TODO: global config struct
|
let config = ClientConfig::load();
|
||||||
let UPLOAD_SIZE_LIMIT: u64 = 1024 * 1024 * 1024 * 1;
|
|
||||||
|
|
||||||
let mut selected : Signal<Vec<(String, String, Option<Result<String, HttpError>>)>> = use_signal(|| vec![]);
|
let mut selected : Signal<Vec<(String, String, Option<Result<String, HttpError>>)>> = use_signal(|| vec![]);
|
||||||
|
|
||||||
rsx! {
|
rsx! {
|
||||||
div { class : "p-2",
|
div { class : "p-2 h-full w-full",
|
||||||
p { class: "text-4xl font-bold p-1", "Upload a file" }
|
p { class: "text-4xl font-bold p-1", "Upload a file" }
|
||||||
|
|
||||||
form {
|
form {
|
||||||
@ -144,7 +158,7 @@ pub fn Upload() -> Element {
|
|||||||
selected.with_mut(|files| {files.push((file.name(), byte_to_human_size(file.size()), None)); } );
|
selected.with_mut(|files| {files.push((file.name(), byte_to_human_size(file.size()), None)); } );
|
||||||
let idx = selected().len() - 1;
|
let idx = selected().len() - 1;
|
||||||
|
|
||||||
if (file.size() > UPLOAD_SIZE_LIMIT) {
|
if file.size() > config.upload_max_size as u64 {
|
||||||
//messy but firefox can't handle when server returns early
|
//messy but firefox can't handle when server returns early
|
||||||
selected.with_mut(|files| { files[idx].2 = Some(HttpError::payload_too_large("This file is too large")) });
|
selected.with_mut(|files| { files[idx].2 = Some(HttpError::payload_too_large("This file is too large")) });
|
||||||
return ;
|
return ;
|
||||||
@ -152,8 +166,10 @@ pub fn Upload() -> Element {
|
|||||||
|
|
||||||
let res = match upload_file(file.clone().into()).await {
|
let res = match upload_file(file.clone().into()).await {
|
||||||
Ok(file_id) => {
|
Ok(file_id) => {
|
||||||
let host = web_sys::window().unwrap().location().host().expect("unknown_host");
|
let location = web_sys::window().unwrap().location();
|
||||||
Ok(host + "/upload/" + &file_id)
|
let host = location.host().expect("unknown host");
|
||||||
|
let protocol = location.protocol().expect("unknown protocol");
|
||||||
|
Ok(protocol + "//" + &host + "/upload/" + &file_id)
|
||||||
},
|
},
|
||||||
Err(err) => { Err(err) }
|
Err(err) => { Err(err) }
|
||||||
};
|
};
|
||||||
|
|||||||
9
todo
9
todo
@ -1,11 +1,16 @@
|
|||||||
|
|
||||||
/upload
|
/upload
|
||||||
|
- can upload file DONE
|
||||||
|
- show current server space usage
|
||||||
|
- direct download on /upload/dl/<file id>
|
||||||
- can upload up to 100GB
|
- can upload up to 100GB
|
||||||
- if video, set mime
|
- if video, set mime
|
||||||
- deletion rules:
|
- deletion rules:
|
||||||
- older than 1 month
|
- older than 1 month
|
||||||
- more than 300GB used and more than 7 days old
|
- more than 300GB used, delete oldest until under 200GB used, unless it's less than 7 days old. hard limit on 500GB
|
||||||
- show file deletion info
|
- file info on /upload/<file id>
|
||||||
|
- file deletion queue position
|
||||||
|
- show file deletion info
|
||||||
|
|
||||||
/status (check status of dependent devices, maybe useless)
|
/status (check status of dependent devices, maybe useless)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user