JJ Colocate
This commit is contained in:
@@ -1,2 +1,2 @@
|
||||
[build]
|
||||
target = "wasm32-unknown-unknown"
|
||||
[build]
|
||||
target = "wasm32-unknown-unknown"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
**/target
|
||||
**/dist
|
||||
LICENSES
|
||||
LICENSE
|
||||
temp
|
||||
**/target
|
||||
**/dist
|
||||
LICENSES
|
||||
LICENSE
|
||||
temp
|
||||
README.md
|
||||
14
AobaClient/.gitignore
vendored
14
AobaClient/.gitignore
vendored
@@ -1,7 +1,7 @@
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target
|
||||
.DS_Store
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target
|
||||
.DS_Store
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
4916
AobaClient/Cargo.lock
generated
4916
AobaClient/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -1,37 +1,37 @@
|
||||
[package]
|
||||
name = "aoba-client"
|
||||
version = "0.1.0"
|
||||
authors = ["Amatsugu <khamraj@kaisei.app>"]
|
||||
edition = "2024"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dioxus = { version = "0.6.0", features = ["router"] }
|
||||
serde = "1.0.219"
|
||||
serde_repr = "0.1.20"
|
||||
tonic = { version = "*", default-features = false, features = [
|
||||
"codegen",
|
||||
"prost",
|
||||
] }
|
||||
prost = "0.13"
|
||||
tonic-web-wasm-client = "0.7"
|
||||
web-sys = { version = "0.3.77", features = ["Storage", "Window"] }
|
||||
|
||||
[build-dependencies]
|
||||
tonic-build = { version = "*", default-features = false, features = ["prost"] }
|
||||
|
||||
[features]
|
||||
default = ["web"]
|
||||
web = ["dioxus/web"]
|
||||
|
||||
[profile]
|
||||
|
||||
[profile.wasm-dev]
|
||||
inherits = "dev"
|
||||
opt-level = 1
|
||||
|
||||
[profile.server-dev]
|
||||
inherits = "dev"
|
||||
|
||||
[profile.android-dev]
|
||||
inherits = "dev"
|
||||
[package]
|
||||
name = "aoba-client"
|
||||
version = "0.1.0"
|
||||
authors = ["Amatsugu <khamraj@kaisei.app>"]
|
||||
edition = "2024"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dioxus = { version = "0.6.0", features = ["router"] }
|
||||
serde = "1.0.219"
|
||||
serde_repr = "0.1.20"
|
||||
tonic = { version = "*", default-features = false, features = [
|
||||
"codegen",
|
||||
"prost",
|
||||
] }
|
||||
prost = "0.13"
|
||||
tonic-web-wasm-client = "0.7"
|
||||
web-sys = { version = "0.3.77", features = ["Storage", "Window"] }
|
||||
|
||||
[build-dependencies]
|
||||
tonic-build = { version = "*", default-features = false, features = ["prost"] }
|
||||
|
||||
[features]
|
||||
default = ["web"]
|
||||
web = ["dioxus/web"]
|
||||
|
||||
[profile]
|
||||
|
||||
[profile.wasm-dev]
|
||||
inherits = "dev"
|
||||
opt-level = 1
|
||||
|
||||
[profile.server-dev]
|
||||
inherits = "dev"
|
||||
|
||||
[profile.android-dev]
|
||||
inherits = "dev"
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
[application]
|
||||
|
||||
[web.app]
|
||||
|
||||
# HTML title tag content
|
||||
title = "aoba-client"
|
||||
|
||||
# include `assets` in web platform
|
||||
[web.resource]
|
||||
|
||||
# Additional CSS style files
|
||||
style = []
|
||||
|
||||
# Additional JavaScript files
|
||||
script = []
|
||||
|
||||
[web.resource.dev]
|
||||
|
||||
# Javascript code file
|
||||
# serve: [dev-server] only
|
||||
script = []
|
||||
[application]
|
||||
|
||||
[web.app]
|
||||
|
||||
# HTML title tag content
|
||||
title = "aoba-client"
|
||||
|
||||
# include `assets` in web platform
|
||||
[web.resource]
|
||||
|
||||
# Additional CSS style files
|
||||
style = []
|
||||
|
||||
# Additional JavaScript files
|
||||
script = []
|
||||
|
||||
[web.resource.dev]
|
||||
|
||||
# Javascript code file
|
||||
# serve: [dev-server] only
|
||||
script = []
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
# Development
|
||||
|
||||
Your new bare-bones project includes minimal organization with a single `main.rs` file and a few assets.
|
||||
|
||||
```
|
||||
project/
|
||||
├─ assets/ # Any assets that are used by the app should be placed here
|
||||
├─ src/
|
||||
│ ├─ main.rs # main.rs is the entry point to your application and currently contains all components for the app
|
||||
├─ Cargo.toml # The Cargo.toml file defines the dependencies and feature flags for your project
|
||||
```
|
||||
|
||||
### Serving Your App
|
||||
|
||||
Run the following command in the root of your project to start developing with the default platform:
|
||||
|
||||
```bash
|
||||
dx serve
|
||||
```
|
||||
|
||||
To run for a different platform, use the `--platform platform` flag. E.g.
|
||||
```bash
|
||||
dx serve --platform desktop
|
||||
```
|
||||
|
||||
# Development
|
||||
|
||||
Your new bare-bones project includes minimal organization with a single `main.rs` file and a few assets.
|
||||
|
||||
```
|
||||
project/
|
||||
├─ assets/ # Any assets that are used by the app should be placed here
|
||||
├─ src/
|
||||
│ ├─ main.rs # main.rs is the entry point to your application and currently contains all components for the app
|
||||
├─ Cargo.toml # The Cargo.toml file defines the dependencies and feature flags for your project
|
||||
```
|
||||
|
||||
### Serving Your App
|
||||
|
||||
Run the following command in the root of your project to start developing with the default platform:
|
||||
|
||||
```bash
|
||||
dx serve
|
||||
```
|
||||
|
||||
To run for a different platform, use the `--platform platform` flag. E.g.
|
||||
```bash
|
||||
dx serve --platform desktop
|
||||
```
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB |
@@ -1,8 +1,8 @@
|
||||
$mainBGColor: #584577;
|
||||
$featureColor: #ce2d4f;
|
||||
$accentColor: #f0eaf8;
|
||||
|
||||
$mainTextColor: #eee;
|
||||
$brightTextColor: #fff;
|
||||
$invertTextColor: #222;
|
||||
$invertBrightTextColor: #000;
|
||||
$mainBGColor: #584577;
|
||||
$featureColor: #ce2d4f;
|
||||
$accentColor: #f0eaf8;
|
||||
|
||||
$mainTextColor: #eee;
|
||||
$brightTextColor: #fff;
|
||||
$invertTextColor: #222;
|
||||
$invertBrightTextColor: #000;
|
||||
|
||||
@@ -1,28 +1,28 @@
|
||||
label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
textarea,
|
||||
input[type="url"],
|
||||
input[type="email"],
|
||||
input[type="number"],
|
||||
input[type="tel"],
|
||||
input[type="text"] {
|
||||
}
|
||||
|
||||
.searchBar {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
|
||||
input {
|
||||
padding: 10px;
|
||||
font-size: 1.5rem;
|
||||
border-radius: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-height: 200px;
|
||||
min-width: 500px;
|
||||
}
|
||||
label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
textarea,
|
||||
input[type="url"],
|
||||
input[type="email"],
|
||||
input[type="number"],
|
||||
input[type="tel"],
|
||||
input[type="text"] {
|
||||
}
|
||||
|
||||
.searchBar {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
|
||||
input {
|
||||
padding: 10px;
|
||||
font-size: 1.5rem;
|
||||
border-radius: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-height: 200px;
|
||||
min-width: 500px;
|
||||
}
|
||||
|
||||
@@ -1,151 +1,151 @@
|
||||
@import "mixins";
|
||||
@import "colors";
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
background-color: $mainBGColor;
|
||||
color: $mainTextColor;
|
||||
box-sizing: border-box;
|
||||
font-family: "Noto Sans", sans-serif;
|
||||
font-optical-sizing: auto;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-variation-settings: "wdth" 100;
|
||||
}
|
||||
|
||||
.stickyTop {
|
||||
top: 0;
|
||||
position: sticky;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#main:has(#content) {
|
||||
display: grid;
|
||||
grid-template-columns: $navBarSize 1fr;
|
||||
grid-template-areas: "Nav Content";
|
||||
}
|
||||
|
||||
#content {
|
||||
grid-area: Content;
|
||||
overflow-x: hidden;
|
||||
padding: 10px;
|
||||
/* margin-left: $navBarSize; */
|
||||
}
|
||||
|
||||
$mediaItemSize: 300px;
|
||||
|
||||
.mediaGrid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
margin: 10px 0;
|
||||
|
||||
.mediaItem {
|
||||
width: $mediaItemSize;
|
||||
height: $mediaItemSize;
|
||||
overflow: hidden;
|
||||
display: grid;
|
||||
grid-template-columns: $mediaItemSize;
|
||||
grid-template-areas: "A";
|
||||
box-shadow: 0 0 2px #000;
|
||||
color: $mainTextColor;
|
||||
text-decoration: none;
|
||||
transition:
|
||||
transform 0.25s ease-out,
|
||||
box-shadow 0.25s ease-out;
|
||||
|
||||
> * {
|
||||
grid-area: A;
|
||||
}
|
||||
|
||||
img {
|
||||
aspect-ratio: 1;
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
object-position: center;
|
||||
background-color: $invertTextColor;
|
||||
border: 0;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.info {
|
||||
align-self: end;
|
||||
backdrop-filter: blur(20px) brightness(0.5);
|
||||
transition: transform 0.25s ease-out;
|
||||
transform: translateY(100%);
|
||||
padding: 2px;
|
||||
|
||||
.name {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
}
|
||||
.details {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: scale(110%) translateZ(2px);
|
||||
box-shadow: 0 0 8px #000;
|
||||
|
||||
.info {
|
||||
transform: translateY(0%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#main:has(#centralModal) {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
height: 100dvh;
|
||||
width: 100dvw;
|
||||
}
|
||||
|
||||
#centralModal {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.notif {
|
||||
background-color: red;
|
||||
display: grid;
|
||||
grid-template-columns: 50px 1fr;
|
||||
height: 50px;
|
||||
border-radius: 20px;
|
||||
border-bottom-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
|
||||
.icon {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.message {
|
||||
padding: 10px;
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
.codeSelect {
|
||||
line-break: anywhere;
|
||||
white-space: pre-wrap;
|
||||
background-color: $featureColor;
|
||||
padding: 5px;
|
||||
user-select: all;
|
||||
}
|
||||
@import "mixins";
|
||||
@import "colors";
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
background-color: $mainBGColor;
|
||||
color: $mainTextColor;
|
||||
box-sizing: border-box;
|
||||
font-family: "Noto Sans", sans-serif;
|
||||
font-optical-sizing: auto;
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-variation-settings: "wdth" 100;
|
||||
}
|
||||
|
||||
.stickyTop {
|
||||
top: 0;
|
||||
position: sticky;
|
||||
}
|
||||
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#main:has(#content) {
|
||||
display: grid;
|
||||
grid-template-columns: $navBarSize 1fr;
|
||||
grid-template-areas: "Nav Content";
|
||||
}
|
||||
|
||||
#content {
|
||||
grid-area: Content;
|
||||
overflow-x: hidden;
|
||||
padding: 10px;
|
||||
/* margin-left: $navBarSize; */
|
||||
}
|
||||
|
||||
$mediaItemSize: 300px;
|
||||
|
||||
.mediaGrid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
margin: 10px 0;
|
||||
|
||||
.mediaItem {
|
||||
width: $mediaItemSize;
|
||||
height: $mediaItemSize;
|
||||
overflow: hidden;
|
||||
display: grid;
|
||||
grid-template-columns: $mediaItemSize;
|
||||
grid-template-areas: "A";
|
||||
box-shadow: 0 0 2px #000;
|
||||
color: $mainTextColor;
|
||||
text-decoration: none;
|
||||
transition:
|
||||
transform 0.25s ease-out,
|
||||
box-shadow 0.25s ease-out;
|
||||
|
||||
> * {
|
||||
grid-area: A;
|
||||
}
|
||||
|
||||
img {
|
||||
aspect-ratio: 1;
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
object-position: center;
|
||||
background-color: $invertTextColor;
|
||||
border: 0;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.info {
|
||||
align-self: end;
|
||||
backdrop-filter: blur(20px) brightness(0.5);
|
||||
transition: transform 0.25s ease-out;
|
||||
transform: translateY(100%);
|
||||
padding: 2px;
|
||||
|
||||
.name {
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
}
|
||||
.details {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: scale(110%) translateZ(2px);
|
||||
box-shadow: 0 0 8px #000;
|
||||
|
||||
.info {
|
||||
transform: translateY(0%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#main:has(#centralModal) {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
height: 100dvh;
|
||||
width: 100dvw;
|
||||
}
|
||||
|
||||
#centralModal {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.notif {
|
||||
background-color: red;
|
||||
display: grid;
|
||||
grid-template-columns: 50px 1fr;
|
||||
height: 50px;
|
||||
border-radius: 20px;
|
||||
border-bottom-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
|
||||
.icon {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.message {
|
||||
padding: 10px;
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
.codeSelect {
|
||||
line-break: anywhere;
|
||||
white-space: pre-wrap;
|
||||
background-color: $featureColor;
|
||||
padding: 5px;
|
||||
user-select: all;
|
||||
}
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
$navBarSize: 64px;
|
||||
|
||||
@mixin mobile {
|
||||
@media (max-width: 700px) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin max-screen($size) {
|
||||
@media (max-width: #{$size}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin max-container($size) {
|
||||
@container (max-width: #{$size}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin small-container {
|
||||
@container (max-width: 500px) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin medium-container {
|
||||
@container (max-width: 800px) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin large-container {
|
||||
@container (max-width: 1000px) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin xlarge-container {
|
||||
@container (max-width: 1200px) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
$navBarSize: 64px;
|
||||
|
||||
@mixin mobile {
|
||||
@media (max-width: 700px) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin max-screen($size) {
|
||||
@media (max-width: #{$size}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin max-container($size) {
|
||||
@container (max-width: #{$size}) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin small-container {
|
||||
@container (max-width: 500px) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin medium-container {
|
||||
@container (max-width: 800px) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin large-container {
|
||||
@container (max-width: 1000px) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin xlarge-container {
|
||||
@container (max-width: 1200px) {
|
||||
@content;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
@import "mixins";
|
||||
@import "colors";
|
||||
|
||||
nav {
|
||||
display: grid;
|
||||
grid-template-areas: "Branding" "Nav" "Widgets" "Utils";
|
||||
grid-template-rows: auto 1fr auto auto;
|
||||
background-color: $featureColor;
|
||||
height: 100dvh;
|
||||
position: fixed;
|
||||
width: $navBarSize;
|
||||
box-shadow: 0 0 3px #000;
|
||||
gap: 20px;
|
||||
padding: 20px 0;
|
||||
|
||||
> * {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.branding {
|
||||
grid-area: Branding;
|
||||
|
||||
img {
|
||||
width: $navBarSize;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.mainNav {
|
||||
grid-area: Nav;
|
||||
}
|
||||
|
||||
.widgets {
|
||||
grid-area: Widgets;
|
||||
}
|
||||
|
||||
.utils {
|
||||
grid-area: Utils;
|
||||
}
|
||||
}
|
||||
@import "mixins";
|
||||
@import "colors";
|
||||
|
||||
nav {
|
||||
display: grid;
|
||||
grid-template-areas: "Branding" "Nav" "Widgets" "Utils";
|
||||
grid-template-rows: auto 1fr auto auto;
|
||||
background-color: $featureColor;
|
||||
height: 100dvh;
|
||||
position: fixed;
|
||||
width: $navBarSize;
|
||||
box-shadow: 0 0 3px #000;
|
||||
gap: 20px;
|
||||
padding: 20px 0;
|
||||
|
||||
> * {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.branding {
|
||||
grid-area: Branding;
|
||||
|
||||
img {
|
||||
width: $navBarSize;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.mainNav {
|
||||
grid-area: Nav;
|
||||
}
|
||||
|
||||
.widgets {
|
||||
grid-area: Widgets;
|
||||
}
|
||||
|
||||
.utils {
|
||||
grid-area: Utils;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tonic_build::configure()
|
||||
.build_server(false)
|
||||
.build_client(true)
|
||||
.compile_protos(
|
||||
&["../AobaServer/Proto/Aoba.proto", "../AobaServer/Proto/Auth.proto"],
|
||||
&["../AobaServer/Proto/"],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tonic_build::configure()
|
||||
.build_server(false)
|
||||
.build_client(true)
|
||||
.compile_protos(
|
||||
&["../AobaServer/Proto/Aoba.proto", "../AobaServer/Proto/Auth.proto"],
|
||||
&["../AobaServer/Proto/"],
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,30 +1,30 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[derive(PartialEq, Clone, Props)]
|
||||
pub struct ButtonProps {
|
||||
pub variant: Option<ButtonVariant>,
|
||||
pub text: String,
|
||||
pub onclick: Option<EventHandler<Event<MouseData>>>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone)]
|
||||
pub enum ButtonVariant {
|
||||
Base,
|
||||
Muted,
|
||||
Accented,
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Button(props: ButtonProps) -> Element {
|
||||
rsx! {
|
||||
button {
|
||||
onclick: move |event| {
|
||||
event.prevent_default();
|
||||
if let Some(h) = props.onclick {
|
||||
h.call(event);
|
||||
}
|
||||
},
|
||||
"{props.text}"
|
||||
}
|
||||
}
|
||||
}
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[derive(PartialEq, Clone, Props)]
|
||||
pub struct ButtonProps {
|
||||
pub variant: Option<ButtonVariant>,
|
||||
pub text: String,
|
||||
pub onclick: Option<EventHandler<Event<MouseData>>>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone)]
|
||||
pub enum ButtonVariant {
|
||||
Base,
|
||||
Muted,
|
||||
Accented,
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Button(props: ButtonProps) -> Element {
|
||||
rsx! {
|
||||
button {
|
||||
onclick: move |event| {
|
||||
event.prevent_default();
|
||||
if let Some(h) = props.onclick {
|
||||
h.call(event);
|
||||
}
|
||||
},
|
||||
"{props.text}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,35 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[derive(PartialEq, Clone, Props)]
|
||||
pub struct InputProps {
|
||||
pub r#type: Option<String>,
|
||||
pub value: Option<Signal<String>>,
|
||||
pub label: Option<String>,
|
||||
pub placeholder: Option<String>,
|
||||
pub name: String,
|
||||
pub oninput: Option<EventHandler<FormEvent>>,
|
||||
pub required: Option<bool>,
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Input(props: InputProps) -> Element {
|
||||
let label = props.label.unwrap_or("".into());
|
||||
let ph = props.placeholder.unwrap_or(label.clone());
|
||||
rsx! {
|
||||
label {
|
||||
"{label}"
|
||||
input {
|
||||
r#type: props.r#type.unwrap_or("text".into()),
|
||||
value: props.value,
|
||||
oninput: move |e| {
|
||||
if let Some(mut s) = props.value {
|
||||
s.set(e.value());
|
||||
}
|
||||
},
|
||||
name: props.name,
|
||||
placeholder: ph,
|
||||
required: props.required,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[derive(PartialEq, Clone, Props)]
|
||||
pub struct InputProps {
|
||||
pub r#type: Option<String>,
|
||||
pub value: Option<Signal<String>>,
|
||||
pub label: Option<String>,
|
||||
pub placeholder: Option<String>,
|
||||
pub name: String,
|
||||
pub oninput: Option<EventHandler<FormEvent>>,
|
||||
pub required: Option<bool>,
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Input(props: InputProps) -> Element {
|
||||
let label = props.label.unwrap_or("".into());
|
||||
let ph = props.placeholder.unwrap_or(label.clone());
|
||||
rsx! {
|
||||
label {
|
||||
"{label}"
|
||||
input {
|
||||
r#type: props.r#type.unwrap_or("text".into()),
|
||||
value: props.value,
|
||||
oninput: move |e| {
|
||||
if let Some(mut s) = props.value {
|
||||
s.set(e.value());
|
||||
}
|
||||
},
|
||||
name: props.name,
|
||||
placeholder: ph,
|
||||
required: props.required,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
mod button;
|
||||
mod input;
|
||||
pub use button::*;
|
||||
pub use input::*;
|
||||
mod button;
|
||||
mod input;
|
||||
pub use button::*;
|
||||
pub use input::*;
|
||||
|
||||
@@ -1,52 +1,52 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[component]
|
||||
pub fn Info() -> Element {
|
||||
rsx! {
|
||||
svg {
|
||||
class: "size-6",
|
||||
fill: "currentColor",
|
||||
view_box: "0 0 24 24",
|
||||
xmlns: "http://www.w3.org/2000/svg",
|
||||
path {
|
||||
clip_rule: "evenodd",
|
||||
d: "M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm8.706-1.442c1.146-.573 2.437.463 2.126 1.706l-.709 2.836.042-.02a.75.75 0 0 1 .67 1.34l-.04.022c-1.147.573-2.438-.463-2.127-1.706l.71-2.836-.042.02a.75.75 0 1 1-.671-1.34l.041-.022ZM12 9a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z",
|
||||
fill_rule: "evenodd",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Warn() -> Element {
|
||||
rsx! {
|
||||
svg {
|
||||
class: "size-6",
|
||||
fill: "currentColor",
|
||||
view_box: "0 0 24 24",
|
||||
xmlns: "http://www.w3.org/2000/svg",
|
||||
path {
|
||||
clip_rule: "evenodd",
|
||||
d: "M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003ZM12 8.25a.75.75 0 0 1 .75.75v3.75a.75.75 0 0 1-1.5 0V9a.75.75 0 0 1 .75-.75Zm0 8.25a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z",
|
||||
fill_rule: "evenodd",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Error() -> Element {
|
||||
rsx! {
|
||||
svg {
|
||||
class: "size-6",
|
||||
fill: "currentColor",
|
||||
view_box: "0 0 24 24",
|
||||
xmlns: "http://www.w3.org/2000/svg",
|
||||
path {
|
||||
clip_rule: "evenodd",
|
||||
d: "M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003ZM12 8.25a.75.75 0 0 1 .75.75v3.75a.75.75 0 0 1-1.5 0V9a.75.75 0 0 1 .75-.75Zm0 8.25a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z",
|
||||
fill_rule: "evenodd",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[component]
|
||||
pub fn Info() -> Element {
|
||||
rsx! {
|
||||
svg {
|
||||
class: "size-6",
|
||||
fill: "currentColor",
|
||||
view_box: "0 0 24 24",
|
||||
xmlns: "http://www.w3.org/2000/svg",
|
||||
path {
|
||||
clip_rule: "evenodd",
|
||||
d: "M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm8.706-1.442c1.146-.573 2.437.463 2.126 1.706l-.709 2.836.042-.02a.75.75 0 0 1 .67 1.34l-.04.022c-1.147.573-2.438-.463-2.127-1.706l.71-2.836-.042.02a.75.75 0 1 1-.671-1.34l.041-.022ZM12 9a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z",
|
||||
fill_rule: "evenodd",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Warn() -> Element {
|
||||
rsx! {
|
||||
svg {
|
||||
class: "size-6",
|
||||
fill: "currentColor",
|
||||
view_box: "0 0 24 24",
|
||||
xmlns: "http://www.w3.org/2000/svg",
|
||||
path {
|
||||
clip_rule: "evenodd",
|
||||
d: "M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003ZM12 8.25a.75.75 0 0 1 .75.75v3.75a.75.75 0 0 1-1.5 0V9a.75.75 0 0 1 .75-.75Zm0 8.25a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z",
|
||||
fill_rule: "evenodd",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Error() -> Element {
|
||||
rsx! {
|
||||
svg {
|
||||
class: "size-6",
|
||||
fill: "currentColor",
|
||||
view_box: "0 0 24 24",
|
||||
xmlns: "http://www.w3.org/2000/svg",
|
||||
path {
|
||||
clip_rule: "evenodd",
|
||||
d: "M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003ZM12 8.25a.75.75 0 0 1 .75.75v3.75a.75.75 0 0 1-1.5 0V9a.75.75 0 0 1 .75-.75Zm0 8.25a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Z",
|
||||
fill_rule: "evenodd",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,77 +1,77 @@
|
||||
use dioxus::prelude::*;
|
||||
use tonic::IntoRequest;
|
||||
|
||||
use crate::{
|
||||
components::MediaItem,
|
||||
rpc::{aoba::PageFilter, get_rpc_client},
|
||||
};
|
||||
|
||||
#[derive(PartialEq, Clone, Props)]
|
||||
pub struct MediaGridProps {
|
||||
pub query: Option<String>,
|
||||
#[props(default = Some(1))]
|
||||
pub page: Option<i32>,
|
||||
#[props(default = Some(100))]
|
||||
pub page_size: Option<i32>,
|
||||
}
|
||||
|
||||
impl IntoRequest<PageFilter> for MediaGridProps {
|
||||
fn into_request(self) -> tonic::Request<PageFilter> {
|
||||
let f: PageFilter = self.into();
|
||||
f.into_request()
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<PageFilter> for MediaGridProps {
|
||||
fn into(self) -> PageFilter {
|
||||
PageFilter {
|
||||
page: self.page,
|
||||
page_size: self.page_size,
|
||||
query: self.query,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn MediaGrid(props: MediaGridProps) -> Element {
|
||||
let media_result = use_resource(use_reactive!(|(props)| async move {
|
||||
let mut client = get_rpc_client();
|
||||
let result = client.list_media(props.into_request()).await;
|
||||
if let Ok(items) = result {
|
||||
return Ok(items.into_inner());
|
||||
} else {
|
||||
let err = result.err().unwrap();
|
||||
let message = err.message();
|
||||
return Err(format!("Failed to load results: {message}"));
|
||||
}
|
||||
}));
|
||||
|
||||
match media_result.cloned() {
|
||||
Some(value) => match value {
|
||||
Ok(result) => rsx! {
|
||||
div {
|
||||
class: "mediaGrid",
|
||||
{result.items.iter().map(|itm| rsx!{
|
||||
MediaItem { item: itm.clone() }
|
||||
})},
|
||||
}
|
||||
},
|
||||
Err(msg) => rsx! {
|
||||
div {
|
||||
class: "mediaGrid",
|
||||
div {
|
||||
"Failed to load results: {msg}"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
None => rsx! {
|
||||
div{
|
||||
class: "mediaGrid",
|
||||
div {
|
||||
"Loading..."
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
use dioxus::prelude::*;
|
||||
use tonic::IntoRequest;
|
||||
|
||||
use crate::{
|
||||
components::MediaItem,
|
||||
rpc::{aoba::PageFilter, get_rpc_client},
|
||||
};
|
||||
|
||||
#[derive(PartialEq, Clone, Props)]
|
||||
pub struct MediaGridProps {
|
||||
pub query: Option<String>,
|
||||
#[props(default = Some(1))]
|
||||
pub page: Option<i32>,
|
||||
#[props(default = Some(100))]
|
||||
pub page_size: Option<i32>,
|
||||
}
|
||||
|
||||
impl IntoRequest<PageFilter> for MediaGridProps {
|
||||
fn into_request(self) -> tonic::Request<PageFilter> {
|
||||
let f: PageFilter = self.into();
|
||||
f.into_request()
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<PageFilter> for MediaGridProps {
|
||||
fn into(self) -> PageFilter {
|
||||
PageFilter {
|
||||
page: self.page,
|
||||
page_size: self.page_size,
|
||||
query: self.query,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn MediaGrid(props: MediaGridProps) -> Element {
|
||||
let media_result = use_resource(use_reactive!(|(props)| async move {
|
||||
let mut client = get_rpc_client();
|
||||
let result = client.list_media(props.into_request()).await;
|
||||
if let Ok(items) = result {
|
||||
return Ok(items.into_inner());
|
||||
} else {
|
||||
let err = result.err().unwrap();
|
||||
let message = err.message();
|
||||
return Err(format!("Failed to load results: {message}"));
|
||||
}
|
||||
}));
|
||||
|
||||
match media_result.cloned() {
|
||||
Some(value) => match value {
|
||||
Ok(result) => rsx! {
|
||||
div {
|
||||
class: "mediaGrid",
|
||||
{result.items.iter().map(|itm| rsx!{
|
||||
MediaItem { item: itm.clone() }
|
||||
})},
|
||||
}
|
||||
},
|
||||
Err(msg) => rsx! {
|
||||
div {
|
||||
class: "mediaGrid",
|
||||
div {
|
||||
"Failed to load results: {msg}"
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
None => rsx! {
|
||||
div{
|
||||
class: "mediaGrid",
|
||||
div {
|
||||
"Loading..."
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::{HOST, rpc::aoba::MediaModel};
|
||||
|
||||
#[derive(PartialEq, Clone, Props)]
|
||||
pub struct MediaItemProps {
|
||||
pub item: MediaModel,
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn MediaItem(props: MediaItemProps) -> Element {
|
||||
let mtype = props.item.media_type().as_str_name();
|
||||
let filename = props.item.file_name;
|
||||
let id = props.item.media_id.unwrap().value;
|
||||
|
||||
let src = format!("{HOST}/m/thumb/{id}");
|
||||
rsx! {
|
||||
a { class: "mediaItem", href: "{HOST}/m/{id}", target: "_blank",
|
||||
img { src }
|
||||
span { class: "info",
|
||||
span { class: "name", "{filename}" }
|
||||
span { class: "details",
|
||||
span { "{mtype}" }
|
||||
span { "{props.item.view_count}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::{HOST, rpc::aoba::MediaModel};
|
||||
|
||||
#[derive(PartialEq, Clone, Props)]
|
||||
pub struct MediaItemProps {
|
||||
pub item: MediaModel,
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn MediaItem(props: MediaItemProps) -> Element {
|
||||
let mtype = props.item.media_type().as_str_name();
|
||||
let filename = props.item.file_name;
|
||||
let id = props.item.media_id.unwrap().value;
|
||||
|
||||
let src = format!("{HOST}/m/thumb/{id}");
|
||||
rsx! {
|
||||
a { class: "mediaItem", href: "{HOST}/m/{id}", target: "_blank",
|
||||
img { src }
|
||||
span { class: "info",
|
||||
span { class: "name", "{filename}" }
|
||||
span { class: "details",
|
||||
span { "{mtype}" }
|
||||
span { "{props.item.view_count}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
pub mod basic;
|
||||
mod media_grid;
|
||||
mod media_item;
|
||||
mod navbar;
|
||||
mod notif;
|
||||
mod search;
|
||||
pub use media_grid::*;
|
||||
pub use media_item::*;
|
||||
pub use navbar::*;
|
||||
pub use notif::*;
|
||||
pub use search::*;
|
||||
mod icons;
|
||||
pub mod basic;
|
||||
mod media_grid;
|
||||
mod media_item;
|
||||
mod navbar;
|
||||
mod notif;
|
||||
mod search;
|
||||
pub use media_grid::*;
|
||||
pub use media_item::*;
|
||||
pub use navbar::*;
|
||||
pub use notif::*;
|
||||
pub use search::*;
|
||||
mod icons;
|
||||
|
||||
@@ -1,56 +1,56 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::{Route, contexts::AuthContext};
|
||||
|
||||
const NAV_CSS: Asset = asset!("/assets/style/nav.scss");
|
||||
const NAV_ICON: Asset = asset!("/assets/favicon.ico");
|
||||
|
||||
#[component]
|
||||
pub fn Navbar() -> Element {
|
||||
rsx! {
|
||||
document::Link { rel: "stylesheet", href: NAV_CSS }
|
||||
nav {
|
||||
Branding {}
|
||||
MainNaviagation {}
|
||||
Widgets {}
|
||||
Utils {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn MainNaviagation() -> Element {
|
||||
rsx! {
|
||||
div { class: "mainNav",
|
||||
Link { class: "navItem", to: Route::Home {}, "Home" }
|
||||
Link { class: "navItem", to: Route::Settings {}, "Settings" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Branding() -> Element {
|
||||
rsx! {
|
||||
div { class: "branding",
|
||||
img { src: NAV_ICON, alt: "Aoba" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Widgets() -> Element {
|
||||
rsx! {
|
||||
div { class: "widgets" }
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Utils() -> Element {
|
||||
let mut auth_context = use_context::<AuthContext>();
|
||||
|
||||
rsx! {
|
||||
div { class: "utils",
|
||||
div { onclick: move |_| auth_context.logout(), "Logout" }
|
||||
}
|
||||
}
|
||||
}
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::{Route, contexts::AuthContext};
|
||||
|
||||
const NAV_CSS: Asset = asset!("/assets/style/nav.scss");
|
||||
const NAV_ICON: Asset = asset!("/assets/favicon.ico");
|
||||
|
||||
#[component]
|
||||
pub fn Navbar() -> Element {
|
||||
rsx! {
|
||||
document::Link { rel: "stylesheet", href: NAV_CSS }
|
||||
nav {
|
||||
Branding {}
|
||||
MainNaviagation {}
|
||||
Widgets {}
|
||||
Utils {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn MainNaviagation() -> Element {
|
||||
rsx! {
|
||||
div { class: "mainNav",
|
||||
Link { class: "navItem", to: Route::Home {}, "Home" }
|
||||
Link { class: "navItem", to: Route::Settings {}, "Settings" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Branding() -> Element {
|
||||
rsx! {
|
||||
div { class: "branding",
|
||||
img { src: NAV_ICON, alt: "Aoba" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Widgets() -> Element {
|
||||
rsx! {
|
||||
div { class: "widgets" }
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Utils() -> Element {
|
||||
let mut auth_context = use_context::<AuthContext>();
|
||||
|
||||
rsx! {
|
||||
div { class: "utils",
|
||||
div { onclick: move |_| auth_context.logout(), "Logout" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::components::icons;
|
||||
|
||||
#[derive(PartialEq, Clone, Props)]
|
||||
pub struct NotifProps {
|
||||
r#type: Option<NotifType>,
|
||||
message: String,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone)]
|
||||
pub enum NotifType {
|
||||
Notice,
|
||||
Error,
|
||||
Warning,
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Notif(props: NotifProps) -> Element {
|
||||
let t = props.r#type.unwrap_or(NotifType::Notice);
|
||||
let type_class = match t {
|
||||
NotifType::Notice => "notice",
|
||||
NotifType::Error => "error",
|
||||
NotifType::Warning => "warning",
|
||||
};
|
||||
let m = props.message;
|
||||
rsx! {
|
||||
div { class: "notif {type_class}",
|
||||
div { class: "icon",
|
||||
match t {
|
||||
NotifType::Notice => icons::Error(),
|
||||
NotifType::Error => icons::Error(),
|
||||
NotifType::Warning => icons::Warn(),
|
||||
}
|
||||
}
|
||||
div { class: "message", "{m}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::components::icons;
|
||||
|
||||
#[derive(PartialEq, Clone, Props)]
|
||||
pub struct NotifProps {
|
||||
r#type: Option<NotifType>,
|
||||
message: String,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Clone)]
|
||||
pub enum NotifType {
|
||||
Notice,
|
||||
Error,
|
||||
Warning,
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Notif(props: NotifProps) -> Element {
|
||||
let t = props.r#type.unwrap_or(NotifType::Notice);
|
||||
let type_class = match t {
|
||||
NotifType::Notice => "notice",
|
||||
NotifType::Error => "error",
|
||||
NotifType::Warning => "warning",
|
||||
};
|
||||
let m = props.message;
|
||||
rsx! {
|
||||
div { class: "notif {type_class}",
|
||||
div { class: "icon",
|
||||
match t {
|
||||
NotifType::Notice => icons::Error(),
|
||||
NotifType::Error => icons::Error(),
|
||||
NotifType::Warning => icons::Warn(),
|
||||
}
|
||||
}
|
||||
div { class: "message", "{m}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[component]
|
||||
pub fn Search(query: Signal<String>) -> Element {
|
||||
rsx! {
|
||||
div { class: "searchBar stickyTop",
|
||||
input {
|
||||
r#type: "search",
|
||||
placeholder: "Search Files",
|
||||
value: query,
|
||||
oninput: move |event| query.set(event.value()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[component]
|
||||
pub fn Search(query: Signal<String>) -> Element {
|
||||
rsx! {
|
||||
div { class: "searchBar stickyTop",
|
||||
input {
|
||||
r#type: "search",
|
||||
placeholder: "Search Files",
|
||||
value: query,
|
||||
oninput: move |event| query.set(event.value()),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
use dioxus::signals::{Signal, Writable};
|
||||
use web_sys::window;
|
||||
|
||||
use crate::rpc::{login, logout};
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct AuthContext {
|
||||
pub jwt: Signal<Option<String>>,
|
||||
}
|
||||
|
||||
impl AuthContext {
|
||||
pub fn login(&mut self, token: String) {
|
||||
self.jwt.set(Some(token.clone()));
|
||||
let local_storage = window().unwrap().local_storage().unwrap().unwrap();
|
||||
_ = local_storage.set_item("token", token.as_str());
|
||||
login(token.clone());
|
||||
}
|
||||
|
||||
pub fn logout(&mut self) {
|
||||
self.jwt.set(None);
|
||||
let local_storage = window().unwrap().local_storage().unwrap().unwrap();
|
||||
_ = local_storage.remove_item("token");
|
||||
logout();
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
println!("new");
|
||||
let local_storage = window().unwrap().local_storage().unwrap().unwrap();
|
||||
match local_storage.get_item("token") {
|
||||
Ok(value) => {
|
||||
if let Some(jwt) = value {
|
||||
login(jwt.clone());
|
||||
return AuthContext {
|
||||
jwt: Signal::new(Some(jwt)),
|
||||
};
|
||||
}
|
||||
return AuthContext::default();
|
||||
}
|
||||
Err(_) => AuthContext::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
use dioxus::signals::{Signal, Writable};
|
||||
use web_sys::window;
|
||||
|
||||
use crate::rpc::{login, logout};
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct AuthContext {
|
||||
pub jwt: Signal<Option<String>>,
|
||||
}
|
||||
|
||||
impl AuthContext {
|
||||
pub fn login(&mut self, token: String) {
|
||||
self.jwt.set(Some(token.clone()));
|
||||
let local_storage = window().unwrap().local_storage().unwrap().unwrap();
|
||||
_ = local_storage.set_item("token", token.as_str());
|
||||
login(token.clone());
|
||||
}
|
||||
|
||||
pub fn logout(&mut self) {
|
||||
self.jwt.set(None);
|
||||
let local_storage = window().unwrap().local_storage().unwrap().unwrap();
|
||||
_ = local_storage.remove_item("token");
|
||||
logout();
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
println!("new");
|
||||
let local_storage = window().unwrap().local_storage().unwrap().unwrap();
|
||||
match local_storage.get_item("token") {
|
||||
Ok(value) => {
|
||||
if let Some(jwt) = value {
|
||||
login(jwt.clone());
|
||||
return AuthContext {
|
||||
jwt: Signal::new(Some(jwt)),
|
||||
};
|
||||
}
|
||||
return AuthContext::default();
|
||||
}
|
||||
Err(_) => AuthContext::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
mod auth_context;
|
||||
pub use auth_context::*;
|
||||
mod auth_context;
|
||||
pub use auth_context::*;
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::{Route, components::Navbar, contexts::AuthContext, views::Login};
|
||||
|
||||
#[component]
|
||||
pub fn MainLayout() -> Element {
|
||||
let auth_context = use_context::<AuthContext>();
|
||||
|
||||
if auth_context.jwt.cloned().is_none() {
|
||||
return rsx! {
|
||||
Login {}
|
||||
};
|
||||
}
|
||||
|
||||
return rsx! {
|
||||
Navbar {}
|
||||
div { id: "content", Outlet::<Route> {} }
|
||||
};
|
||||
}
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::{Route, components::Navbar, contexts::AuthContext, views::Login};
|
||||
|
||||
#[component]
|
||||
pub fn MainLayout() -> Element {
|
||||
let auth_context = use_context::<AuthContext>();
|
||||
|
||||
if auth_context.jwt.cloned().is_none() {
|
||||
return rsx! {
|
||||
Login {}
|
||||
};
|
||||
}
|
||||
|
||||
return rsx! {
|
||||
Navbar {}
|
||||
div { id: "content", Outlet::<Route> {} }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
mod main_layout;
|
||||
pub use main_layout::*;
|
||||
mod main_layout;
|
||||
pub use main_layout::*;
|
||||
|
||||
@@ -1,45 +1,45 @@
|
||||
pub mod components;
|
||||
pub mod contexts;
|
||||
mod layouts;
|
||||
pub mod models;
|
||||
pub mod route;
|
||||
pub mod rpc;
|
||||
pub mod views;
|
||||
|
||||
use contexts::AuthContext;
|
||||
use dioxus::prelude::*;
|
||||
use route::Route;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
pub const HOST: &'static str = "http://localhost:8081";
|
||||
#[cfg(debug_assertions)]
|
||||
pub const RPC_HOST: &'static str = "http://localhost:8081";
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub const RPC_HOST: &'static str = "https://grpc.aoba.app:8443";
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub const HOST: &'static str = "https://aoba.app";
|
||||
|
||||
const FAVICON: Asset = asset!("/assets/favicon.ico");
|
||||
const MAIN_CSS: Asset = asset!("/assets/style/main.scss");
|
||||
const INPUT_CSS: Asset = asset!("/assets/style/inputs.scss");
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(App);
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn App() -> Element {
|
||||
let _auth_state = use_context_provider(|| AuthContext::new());
|
||||
rsx! {
|
||||
document::Link { rel: "icon", href: FAVICON }
|
||||
document::Link { rel: "preconnect", href: "https://fonts.googleapis.com" }
|
||||
document::Link { rel: "preconnect", href: "https://fonts.gstatic.com" }
|
||||
document::Link { rel: "stylesheet", href: MAIN_CSS }
|
||||
document::Link { rel: "stylesheet", href: INPUT_CSS }
|
||||
document::Link {
|
||||
rel: "stylesheet",
|
||||
href: "https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap",
|
||||
}
|
||||
Router::<Route> {}
|
||||
}
|
||||
}
|
||||
pub mod components;
|
||||
pub mod contexts;
|
||||
mod layouts;
|
||||
pub mod models;
|
||||
pub mod route;
|
||||
pub mod rpc;
|
||||
pub mod views;
|
||||
|
||||
use contexts::AuthContext;
|
||||
use dioxus::prelude::*;
|
||||
use route::Route;
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
pub const HOST: &'static str = "http://localhost:8081";
|
||||
#[cfg(debug_assertions)]
|
||||
pub const RPC_HOST: &'static str = "http://localhost:8081";
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub const RPC_HOST: &'static str = "https://grpc.aoba.app:8443";
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub const HOST: &'static str = "https://aoba.app";
|
||||
|
||||
const FAVICON: Asset = asset!("/assets/favicon.ico");
|
||||
const MAIN_CSS: Asset = asset!("/assets/style/main.scss");
|
||||
const INPUT_CSS: Asset = asset!("/assets/style/inputs.scss");
|
||||
|
||||
fn main() {
|
||||
dioxus::launch(App);
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn App() -> Element {
|
||||
let _auth_state = use_context_provider(|| AuthContext::new());
|
||||
rsx! {
|
||||
document::Link { rel: "icon", href: FAVICON }
|
||||
document::Link { rel: "preconnect", href: "https://fonts.googleapis.com" }
|
||||
document::Link { rel: "preconnect", href: "https://fonts.gstatic.com" }
|
||||
document::Link { rel: "stylesheet", href: MAIN_CSS }
|
||||
document::Link { rel: "stylesheet", href: INPUT_CSS }
|
||||
document::Link {
|
||||
rel: "stylesheet",
|
||||
href: "https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap",
|
||||
}
|
||||
Router::<Route> {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct Media {
|
||||
pub id: String,
|
||||
pub media_id: String,
|
||||
pub filename: String,
|
||||
pub media_type: MediaType,
|
||||
pub ext: String,
|
||||
pub view_count: i32,
|
||||
pub owner: String,
|
||||
}
|
||||
#[derive(Serialize_repr, Deserialize_repr, Clone, PartialEq)]
|
||||
#[repr(i32)]
|
||||
pub enum MediaType {
|
||||
Image,
|
||||
Audio,
|
||||
Video,
|
||||
Text,
|
||||
Code,
|
||||
Raw,
|
||||
}
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_repr::{Deserialize_repr, Serialize_repr};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq)]
|
||||
pub struct Media {
|
||||
pub id: String,
|
||||
pub media_id: String,
|
||||
pub filename: String,
|
||||
pub media_type: MediaType,
|
||||
pub ext: String,
|
||||
pub view_count: i32,
|
||||
pub owner: String,
|
||||
}
|
||||
#[derive(Serialize_repr, Deserialize_repr, Clone, PartialEq)]
|
||||
#[repr(i32)]
|
||||
pub enum MediaType {
|
||||
Image,
|
||||
Audio,
|
||||
Video,
|
||||
Text,
|
||||
Code,
|
||||
Raw,
|
||||
}
|
||||
|
||||
@@ -1 +1 @@
|
||||
pub mod media;
|
||||
pub mod media;
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
use crate::{
|
||||
layouts::MainLayout,
|
||||
views::{Home, Settings},
|
||||
};
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[derive(Debug, Clone, Routable, PartialEq)]
|
||||
#[rustfmt::skip]
|
||||
pub enum Route {
|
||||
#[layout(MainLayout)]
|
||||
#[route("/")]
|
||||
Home {},
|
||||
#[route("/settings")]
|
||||
Settings {},
|
||||
// #[end_layout]
|
||||
}
|
||||
use crate::{
|
||||
layouts::MainLayout,
|
||||
views::{Home, Settings},
|
||||
};
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[derive(Debug, Clone, Routable, PartialEq)]
|
||||
#[rustfmt::skip]
|
||||
pub enum Route {
|
||||
#[layout(MainLayout)]
|
||||
#[route("/")]
|
||||
Home {},
|
||||
#[route("/settings")]
|
||||
Settings {},
|
||||
// #[end_layout]
|
||||
}
|
||||
|
||||
@@ -1,75 +1,75 @@
|
||||
use std::sync::RwLock;
|
||||
|
||||
use aoba::{aoba_rpc_client::AobaRpcClient, auth_rpc_client::AuthRpcClient};
|
||||
use tonic::service::{Interceptor, interceptor::InterceptedService};
|
||||
use tonic_web_wasm_client::Client;
|
||||
|
||||
use crate::RPC_HOST;
|
||||
|
||||
pub mod aoba {
|
||||
tonic::include_proto!("aoba");
|
||||
tonic::include_proto!("aoba.auth");
|
||||
}
|
||||
|
||||
static RPC_CLIENT: RpcConnection = RpcConnection {
|
||||
aoba: RwLock::new(None),
|
||||
auth: RwLock::new(None),
|
||||
jwt: RwLock::new(None),
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RpcConnection {
|
||||
aoba: RwLock<Option<AobaRpcClient<InterceptedService<Client, AuthInterceptor>>>>,
|
||||
auth: RwLock<Option<AuthRpcClient<Client>>>,
|
||||
jwt: RwLock<Option<String>>,
|
||||
}
|
||||
|
||||
impl RpcConnection {
|
||||
pub fn get_client(&self) -> AobaRpcClient<InterceptedService<Client, AuthInterceptor>> {
|
||||
self.ensure_client();
|
||||
return self.aoba.read().unwrap().clone().unwrap();
|
||||
}
|
||||
|
||||
pub fn get_auth_client(&self) -> AuthRpcClient<Client> {
|
||||
self.ensure_client();
|
||||
return self.auth.read().unwrap().clone().unwrap();
|
||||
}
|
||||
|
||||
fn ensure_client(&self) {
|
||||
if self.aoba.read().unwrap().is_none() {
|
||||
let wasm_client = Client::new(RPC_HOST.into());
|
||||
let aoba_client = AobaRpcClient::with_interceptor(wasm_client.clone(), AuthInterceptor);
|
||||
*self.aoba.write().unwrap() = Some(aoba_client);
|
||||
*self.auth.write().unwrap() = Some(AuthRpcClient::new(wasm_client.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AuthInterceptor;
|
||||
impl Interceptor for AuthInterceptor {
|
||||
fn call(&mut self, mut request: tonic::Request<()>) -> Result<tonic::Request<()>, tonic::Status> {
|
||||
if let Some(jwt) = RPC_CLIENT.jwt.read().unwrap().clone() {
|
||||
request
|
||||
.metadata_mut()
|
||||
.insert("authorization", format!("Bearer {jwt}").parse().unwrap());
|
||||
}
|
||||
return Ok(request);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_rpc_client() -> AobaRpcClient<InterceptedService<Client, AuthInterceptor>> {
|
||||
return RPC_CLIENT.get_client();
|
||||
}
|
||||
|
||||
pub fn get_auth_rpc_client() -> AuthRpcClient<Client> {
|
||||
return RPC_CLIENT.get_auth_client();
|
||||
}
|
||||
|
||||
pub fn login(jwt: String) {
|
||||
*RPC_CLIENT.jwt.write().unwrap() = Some(jwt);
|
||||
}
|
||||
|
||||
pub fn logout() {
|
||||
*RPC_CLIENT.jwt.write().unwrap() = None;
|
||||
}
|
||||
use std::sync::RwLock;
|
||||
|
||||
use aoba::{aoba_rpc_client::AobaRpcClient, auth_rpc_client::AuthRpcClient};
|
||||
use tonic::service::{Interceptor, interceptor::InterceptedService};
|
||||
use tonic_web_wasm_client::Client;
|
||||
|
||||
use crate::RPC_HOST;
|
||||
|
||||
pub mod aoba {
|
||||
tonic::include_proto!("aoba");
|
||||
tonic::include_proto!("aoba.auth");
|
||||
}
|
||||
|
||||
static RPC_CLIENT: RpcConnection = RpcConnection {
|
||||
aoba: RwLock::new(None),
|
||||
auth: RwLock::new(None),
|
||||
jwt: RwLock::new(None),
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RpcConnection {
|
||||
aoba: RwLock<Option<AobaRpcClient<InterceptedService<Client, AuthInterceptor>>>>,
|
||||
auth: RwLock<Option<AuthRpcClient<Client>>>,
|
||||
jwt: RwLock<Option<String>>,
|
||||
}
|
||||
|
||||
impl RpcConnection {
|
||||
pub fn get_client(&self) -> AobaRpcClient<InterceptedService<Client, AuthInterceptor>> {
|
||||
self.ensure_client();
|
||||
return self.aoba.read().unwrap().clone().unwrap();
|
||||
}
|
||||
|
||||
pub fn get_auth_client(&self) -> AuthRpcClient<Client> {
|
||||
self.ensure_client();
|
||||
return self.auth.read().unwrap().clone().unwrap();
|
||||
}
|
||||
|
||||
fn ensure_client(&self) {
|
||||
if self.aoba.read().unwrap().is_none() {
|
||||
let wasm_client = Client::new(RPC_HOST.into());
|
||||
let aoba_client = AobaRpcClient::with_interceptor(wasm_client.clone(), AuthInterceptor);
|
||||
*self.aoba.write().unwrap() = Some(aoba_client);
|
||||
*self.auth.write().unwrap() = Some(AuthRpcClient::new(wasm_client.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AuthInterceptor;
|
||||
impl Interceptor for AuthInterceptor {
|
||||
fn call(&mut self, mut request: tonic::Request<()>) -> Result<tonic::Request<()>, tonic::Status> {
|
||||
if let Some(jwt) = RPC_CLIENT.jwt.read().unwrap().clone() {
|
||||
request
|
||||
.metadata_mut()
|
||||
.insert("authorization", format!("Bearer {jwt}").parse().unwrap());
|
||||
}
|
||||
return Ok(request);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_rpc_client() -> AobaRpcClient<InterceptedService<Client, AuthInterceptor>> {
|
||||
return RPC_CLIENT.get_client();
|
||||
}
|
||||
|
||||
pub fn get_auth_rpc_client() -> AuthRpcClient<Client> {
|
||||
return RPC_CLIENT.get_auth_client();
|
||||
}
|
||||
|
||||
pub fn login(jwt: String) {
|
||||
*RPC_CLIENT.jwt.write().unwrap() = Some(jwt);
|
||||
}
|
||||
|
||||
pub fn logout() {
|
||||
*RPC_CLIENT.jwt.write().unwrap() = None;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
use crate::components::{MediaGrid, Search};
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[component]
|
||||
pub fn Home() -> Element {
|
||||
let query = use_signal(|| "".to_string());
|
||||
|
||||
rsx! {
|
||||
Search { query }
|
||||
MediaGrid { query: query.cloned() }
|
||||
}
|
||||
}
|
||||
use crate::components::{MediaGrid, Search};
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[component]
|
||||
pub fn Home() -> Element {
|
||||
let query = use_signal(|| "".to_string());
|
||||
|
||||
rsx! {
|
||||
Search { query }
|
||||
MediaGrid { query: query.cloned() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,78 +1,78 @@
|
||||
use dioxus::prelude::*;
|
||||
use tonic::IntoRequest;
|
||||
|
||||
use crate::{
|
||||
components::{basic::Input, Notif, NotifType},
|
||||
contexts::AuthContext,
|
||||
rpc::{aoba::Credentials, get_auth_rpc_client},
|
||||
};
|
||||
|
||||
#[component]
|
||||
pub fn Login() -> Element {
|
||||
let username = use_signal(|| "".to_string());
|
||||
let password = use_signal(|| "".to_string());
|
||||
let mut error: Signal<Option<String>> = use_signal(|| None);
|
||||
let mut auth_context = use_context::<AuthContext>();
|
||||
|
||||
let login = move |e: Event<MouseData>| {
|
||||
e.prevent_default();
|
||||
if username.cloned().is_empty() || password.cloned().is_empty() {
|
||||
error.set(Some("Username and Password are required".into()));
|
||||
return;
|
||||
}
|
||||
|
||||
spawn(async move {
|
||||
let mut auth = get_auth_rpc_client();
|
||||
let result = auth
|
||||
.login(
|
||||
Credentials {
|
||||
user: username.cloned(),
|
||||
password: password.cloned(),
|
||||
}
|
||||
.into_request(),
|
||||
)
|
||||
.await;
|
||||
match result {
|
||||
Ok(res) => {
|
||||
match res.into_inner().result.unwrap() {
|
||||
crate::rpc::aoba::login_response::Result::Jwt(jwt) => {
|
||||
auth_context.login(jwt.token);
|
||||
}
|
||||
crate::rpc::aoba::login_response::Result::Error(login_error) => {
|
||||
auth_context.logout();
|
||||
error.set(Some(login_error.message));
|
||||
}
|
||||
};
|
||||
}
|
||||
Err(_err) => {
|
||||
auth_context.logout();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
rsx! {
|
||||
div { id: "centralModal",
|
||||
if let Some(err) = error.cloned() {
|
||||
Notif { r#type: NotifType::Error, message: err }
|
||||
}
|
||||
form {
|
||||
Input {
|
||||
r#type: "text",
|
||||
name: "username",
|
||||
label: "Username",
|
||||
value: username,
|
||||
required: true,
|
||||
}
|
||||
Input {
|
||||
r#type: "password",
|
||||
name: "password",
|
||||
label: "Password",
|
||||
value: password,
|
||||
required: true,
|
||||
}
|
||||
button { onclick: login, "Login!" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
use dioxus::prelude::*;
|
||||
use tonic::IntoRequest;
|
||||
|
||||
use crate::{
|
||||
components::{basic::Input, Notif, NotifType},
|
||||
contexts::AuthContext,
|
||||
rpc::{aoba::Credentials, get_auth_rpc_client},
|
||||
};
|
||||
|
||||
#[component]
|
||||
pub fn Login() -> Element {
|
||||
let username = use_signal(|| "".to_string());
|
||||
let password = use_signal(|| "".to_string());
|
||||
let mut error: Signal<Option<String>> = use_signal(|| None);
|
||||
let mut auth_context = use_context::<AuthContext>();
|
||||
|
||||
let login = move |e: Event<MouseData>| {
|
||||
e.prevent_default();
|
||||
if username.cloned().is_empty() || password.cloned().is_empty() {
|
||||
error.set(Some("Username and Password are required".into()));
|
||||
return;
|
||||
}
|
||||
|
||||
spawn(async move {
|
||||
let mut auth = get_auth_rpc_client();
|
||||
let result = auth
|
||||
.login(
|
||||
Credentials {
|
||||
user: username.cloned(),
|
||||
password: password.cloned(),
|
||||
}
|
||||
.into_request(),
|
||||
)
|
||||
.await;
|
||||
match result {
|
||||
Ok(res) => {
|
||||
match res.into_inner().result.unwrap() {
|
||||
crate::rpc::aoba::login_response::Result::Jwt(jwt) => {
|
||||
auth_context.login(jwt.token);
|
||||
}
|
||||
crate::rpc::aoba::login_response::Result::Error(login_error) => {
|
||||
auth_context.logout();
|
||||
error.set(Some(login_error.message));
|
||||
}
|
||||
};
|
||||
}
|
||||
Err(_err) => {
|
||||
auth_context.logout();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
rsx! {
|
||||
div { id: "centralModal",
|
||||
if let Some(err) = error.cloned() {
|
||||
Notif { r#type: NotifType::Error, message: err }
|
||||
}
|
||||
form {
|
||||
Input {
|
||||
r#type: "text",
|
||||
name: "username",
|
||||
label: "Username",
|
||||
value: username,
|
||||
required: true,
|
||||
}
|
||||
Input {
|
||||
r#type: "password",
|
||||
name: "password",
|
||||
label: "Password",
|
||||
value: password,
|
||||
required: true,
|
||||
}
|
||||
button { onclick: login, "Login!" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
mod home;
|
||||
mod login;
|
||||
pub use home::*;
|
||||
pub use login::*;
|
||||
|
||||
mod settings;
|
||||
pub use settings::Settings;
|
||||
mod home;
|
||||
mod login;
|
||||
pub use home::*;
|
||||
pub use login::*;
|
||||
|
||||
mod settings;
|
||||
pub use settings::Settings;
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::rpc::get_rpc_client;
|
||||
|
||||
#[component]
|
||||
pub fn Settings() -> Element {
|
||||
let dst = use_resource(async move || {
|
||||
let result = get_rpc_client().get_share_x_destination(()).await;
|
||||
if let Ok(d) = result {
|
||||
if let Some(r) = d.into_inner().dst_result {
|
||||
return match r {
|
||||
crate::rpc::aoba::share_x_response::DstResult::Destination(json) => json,
|
||||
crate::rpc::aoba::share_x_response::DstResult::Error(err) => err,
|
||||
};
|
||||
}
|
||||
return "No Result".to_string();
|
||||
}
|
||||
let err = result.err().unwrap();
|
||||
let status = err.message();
|
||||
return format!("Failed to load config: {status}").to_string();
|
||||
});
|
||||
|
||||
let d = dst.cloned().unwrap_or("".to_string());
|
||||
|
||||
rsx! {
|
||||
"this is settings"
|
||||
div {
|
||||
pre { class: "codeSelect", "{d}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::rpc::get_rpc_client;
|
||||
|
||||
#[component]
|
||||
pub fn Settings() -> Element {
|
||||
let dst = use_resource(async move || {
|
||||
let result = get_rpc_client().get_share_x_destination(()).await;
|
||||
if let Ok(d) = result {
|
||||
if let Some(r) = d.into_inner().dst_result {
|
||||
return match r {
|
||||
crate::rpc::aoba::share_x_response::DstResult::Destination(json) => json,
|
||||
crate::rpc::aoba::share_x_response::DstResult::Error(err) => err,
|
||||
};
|
||||
}
|
||||
return "No Result".to_string();
|
||||
}
|
||||
let err = result.err().unwrap();
|
||||
let status = err.message();
|
||||
return format!("Failed to load config: {status}").to_string();
|
||||
});
|
||||
|
||||
let d = dst.cloned().unwrap_or("".to_string());
|
||||
|
||||
rsx! {
|
||||
"this is settings"
|
||||
div {
|
||||
pre { class: "codeSelect", "{d}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user