Skip to content

Commit

Permalink
Merge branch 'master' of github.com:Eason0729/mdoj
Browse files Browse the repository at this point in the history
  • Loading branch information
Eason0729 committed Sep 11, 2024
2 parents 51e491b + c9631d7 commit 3df904d
Show file tree
Hide file tree
Showing 18 changed files with 518 additions and 206 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 6 additions & 3 deletions frontend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,13 @@ leptos_router = { version = "0.6", features = ["nightly"] }
cfg-if = "1"
wasm-bindgen = "=0.2.92"
js-sys = "=0.3.69"
prost = { workspace = true }
prost.workspace = true
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
tracing-web = "0.1.3"
toml = { workspace = true }
toml.workspace = true
gloo = "0.11.0"
pulldown-cmark = "0.10.0"
tonic-web-wasm-client = "0.5.1"
serde_qs = "0.12.0"
serde_json = "1.0.120"
leptos_icons = "0.3.1"
Expand All @@ -38,6 +37,7 @@ lol_alloc = "0.4.1"
leptos_query = "0.5.3"
cookie = "0.18.1"
leptos_query_devtools = "0.1.3"
chrono.workspace = true

[dependencies.uuid]
version = "1.7.0"
Expand All @@ -60,6 +60,9 @@ features = ["serde"]
workspace = true
features = ["codegen", "prost", "channel"]

[dependencies.tonic-web-wasm-client]
version = "0.5.1"

[dependencies.serde]
workspace = true

Expand Down
20 changes: 19 additions & 1 deletion frontend/src/components/badge.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use leptos::*;
use tailwind_fuse::*;

use crate::utils::*;

#[component]
pub fn Badge(difficulty: u32) -> impl IntoView {
pub fn DifficultyBadge(difficulty: u32) -> impl IntoView {
let style = match difficulty {
0..500 => "border-green-500",
500..1000 => "border-green-700",
Expand All @@ -15,3 +17,19 @@ pub fn Badge(difficulty: u32) -> impl IntoView {

view! { <p class=tw_join!("p-1 m-1 w-min h-min border-2 rounded m-auto",style)>{difficulty}</p> }
}

#[component]
pub fn StateBadge(state: grpc::StateCode) -> impl IntoView {
let (style, display) = match state {
grpc::StateCode::Accepted => ("border-green-700", "AC"),
grpc::StateCode::Unknown => ("border-yellow-400", "UNK"),
grpc::StateCode::WrongAnswer => ("border-red-700", "WA"),
grpc::StateCode::CompileError => ("border-yellow-600", "CE"),
grpc::StateCode::RuntimeError => ("border-red-700", "RE"),
grpc::StateCode::RestrictedFunction => ("border-yellow-500", "RF"),
grpc::StateCode::TimeLimitExcess => ("border-red-500", "TLE"),
grpc::StateCode::MemoryLimitExcess => ("border-red-500", "MLE"),
grpc::StateCode::OutputLimitExcess => ("border-red-500", "OLE"),
};
view! { <p class=tw_join!("p-1 m-1 w-min h-min border-2 rounded m-auto",style)>{display}</p> }
}
4 changes: 2 additions & 2 deletions frontend/src/components/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub mod select;
pub mod toast;
pub mod toggle;

pub use badge::Badge;
pub use badge::{DifficultyBadge, StateBadge};
pub use button::{Button, ButtonVariant};
pub use editor::{create_editor_ref, Editor};
pub use errors::*;
Expand All @@ -31,7 +31,7 @@ pub use markdown::Markdown;
pub use modal::{Modal, ModalLevel};
pub use navbar::Navbar;
pub use paginate_navbar::PaginateNavbar;
pub use paginate_table::PaginateTable;
pub use paginate_table::{PaginateTable, PaginateTableWithoutSort};
pub use redirect_if::RedirectIf;
pub use search_bar::SearchBar;
pub use select::Select;
Expand Down
74 changes: 54 additions & 20 deletions frontend/src/components/paginate_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,61 @@ use tailwind_fuse::*;

use crate::utils::*;

#[component]
pub fn PaginateTableWithoutSort<const N: usize, H>(
#[prop(into)] headers: [(Option<()>, View); N],
#[prop(into)] rows: Result<Vec<(H, [View; N])>>,
#[prop(into, optional)] class: String,
#[prop(into)] order: ParamsMapKey<GrpcEnum<grpc::Order>>,
) -> impl IntoView
where
H: ToHref + Clone + 'static,
{
view! { <PaginateTable<N, DummyParamsMapValue, H> headers rows class order /> }
}

#[component]
pub fn PaginateTable<const N: usize, S, H>(
#[prop(into)] headers: [(Option<S::Output>, View); N],
#[prop(into)] rows: Vec<(H, [View; N])>,
#[prop(into)] rows: Result<Vec<(H, [View; N])>>,
#[prop(into, optional)] class: String,
#[prop(into)] sort: ParamsMapKey<S>,
#[prop(into, optional)] sort: Option<ParamsMapKey<S>>,
#[prop(into)] order: ParamsMapKey<GrpcEnum<grpc::Order>>,
) -> impl IntoView
where
S: ParamsMapValue + 'static,
H: ToHref + 'static,
H: ToHref + Clone + 'static,
{
let query_map = use_query_map();
let headers = headers.map(|(s, col)| {
let navigate = use_navigate();
let Some(s) = s.clone() else {
return view! { <th>{col}</th> };
};

let click = move |_| {
let Some(s) = s.clone() else {
return;
};
let mut query_map = query_map.get_untracked();
if s == query_map.get_key_with_default(sort) {
let toggle_order = match query_map.get_key_with_default(order) {
grpc::Order::Ascend => grpc::Order::Descend,
grpc::Order::Descend => grpc::Order::Ascend,
};
query_map.set_key(order, Some(toggle_order));
} else {
query_map.set_key(order, None);
query_map.set_key(sort, Some(s));
match sort {
Some(sort) if s == query_map.get_key_with_default(sort) => {
let toggle_order =
match query_map.get_key_with_default(order) {
grpc::Order::Ascend => grpc::Order::Descend,
grpc::Order::Descend => grpc::Order::Ascend,
};
query_map.set_key(order, Some(toggle_order));
}
Some(sort) => {
query_map.set_key(order, None);
query_map.set_key(sort, Some(s.clone()));
}
None => {
let toggle_order =
match query_map.get_key_with_default(order) {
grpc::Order::Ascend => grpc::Order::Descend,
grpc::Order::Descend => grpc::Order::Ascend,
};
query_map.set_key(order, Some(toggle_order));
}
}
navigate(
&query_map.to_url(),
Expand All @@ -48,12 +74,20 @@ where
</th>
}
});
let rows = rows
.into_iter()
.map(|(href, cols)| view! { <Row cols href /> })
.collect_view();
let rows = match rows {
Err(v) if v.kind == ErrorKind::OutOfRange => Ok(view! {
<tr class="grid col-span-full even:bg-black-900 text-sm text-center p-4">
<p>No result</p>
</tr>
}.into_view()),
Err(v)=>Err(v),
Ok(v) => Ok(v
.into_iter()
.map(|(href, cols)| view! { <Row cols href /> })
.collect_view()),
};
view! {
<table class=tw_join!("w-full grid gap-x-4", class)>
<table class=tw_join!("grid gap-x-4", class)>
<thead class="grid col-span-full grid-cols-subgrid font-bold text-base border-b-2 border-black-400 bg-black-900 p-4">
<tr class="contents">{headers}</tr>
</thead>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/search_bar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ pub fn SearchBar(
let search = create_rw_signal("".to_owned());
view! {
<form on:submit=move |e| submit(e, search.get_untracked()) class=tw_join!("relative",class)>
<Input value=search class="flex-grow"></Input>
<Input value=search class="grow"></Input>
<button type="submit" class="absolute right-4 top-0 h-full">
<Icon icon=icondata::BsSearch />
</button>
Expand Down
42 changes: 28 additions & 14 deletions frontend/src/components/select.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,46 @@
use std::usize;

use leptos::*;
use tailwind_fuse::tw_join;

#[derive(Debug, Clone, PartialEq, Eq)]
struct SelectedValue(ReadSignal<String>);
struct SelectedValue(ReadSignal<usize>);

#[component]
pub fn Select(
options: Vec<(String, View)>,
#[prop(into)] value: RwSignal<String>,
#[prop(into, optional)] placeholder: Option<String>,
pub fn Select<T>(
options: Vec<(T, View)>,
#[prop(into)] value: SignalSetter<T>,
#[prop(into, optional)] placeholder: Option<View>,
#[prop(into, optional)] id: Option<AttributeValue>,
#[prop(into, default = "".into())] class: String,
) -> impl IntoView {
let (get, set) = value.split();
) -> impl IntoView
where
T: Clone + 'static,
{
let (get, set) = create_signal(usize::MAX);
provide_context(SelectedValue(get));

let children = options
let (children, map): (Vec<_>, Vec<_>) = options
.into_iter()
.map(|(value, children)| {
view! { <SelectOption value>{children}</SelectOption> }
.enumerate()
.map(|(value, (t, children))| {
(view! { <SelectOption value>{children}</SelectOption> }, t)
})
.collect_view();
.unzip();

create_effect(move |_| {
let i = get();
if i == usize::MAX {
return;
}
value(map[i].clone());
});

view! {
<select
class=tw_join!(class, "text-text text-center bg-black-800 p-2")
id=id
on:change=move |e| set(event_target_value(&e))
on:change=move |e| set(event_target_value(&e).parse().unwrap())
>
<option selected disabled hidden>
{placeholder}
Expand All @@ -37,11 +51,11 @@ pub fn Select(
}

#[component]
fn SelectOption(children: Children, value: String) -> impl IntoView {
fn SelectOption(children: Children, value: usize) -> impl IntoView {
let selected_value = expect_context::<SelectedValue>().0;

view! {
<option value=value.clone() selected=move || selected_value() == value>
<option value=value selected=move || selected_value() == value>
{children()}
</option>
}
Expand Down
35 changes: 17 additions & 18 deletions frontend/src/pages/create/problem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ pub fn Problem() -> impl IntoView {
let time = create_rw_signal(0u64);
let memory = create_rw_signal(0u64);
let tags = create_rw_signal("".to_owned());
let match_rule = create_rw_signal("".to_owned());
let match_rule = create_rw_signal::<Option<grpc::MatchRule>>(None);
let editor_ref = create_editor_ref();

type TestcaseType = (Uuid, String, RwSignal<u32>, Promise, Promise);
Expand Down Expand Up @@ -117,20 +117,13 @@ pub fn Problem() -> impl IntoView {
let info = grpc::create_problem_request::Info {
title: title(),
difficulty: difficulty(),
time: time(),
memory: memory(),
time: time() * 1000,
memory: memory() << 20,
tags: tags().split_whitespace().map(|s| s.into()).collect(),
content: editor_ref.with(|e| {
e.as_ref().map(|e| e.get_value()).unwrap_or_default()
}),
match_rule: match_rule
.with(|rule| match rule.as_str() {
"EXACTLY" => grpc::MatchRule::MatchruleExactly,
"IGNORE_SNL" => grpc::MatchRule::MatchruleIgnoreSnl,
"SKIP_SNL" => grpc::MatchRule::MatchruleSkipSnl,
_ => unreachable!(),
})
.into(),
match_rule: match_rule().unwrap().into(),
// TODO: remove this when new API is complete
order: 0.0,
};
Expand All @@ -147,8 +140,8 @@ pub fn Problem() -> impl IntoView {

let disabled = Signal::derive(move || {
title.with(|v| v.is_empty())
|| match_rule.with(|v| v.is_empty())
|| create.pending()()
|| match_rule.with(|v| v.is_none())
});

let toast = use_toast();
Expand Down Expand Up @@ -228,12 +221,18 @@ pub fn Problem() -> impl IntoView {
};

let options = vec![
("EXACTLY".to_owned(), "Exactly".into_view()),
(
"IGNORE_SNL".to_owned(),
Some(grpc::MatchRule::MatchruleExactly),
"Exactly".into_view(),
),
(
Some(grpc::MatchRule::MatchruleIgnoreSnl),
"Ignore space and newline".into_view(),
),
("SKIP_SNL".to_owned(), "Skip space and newline".into_view()),
(
Some(grpc::MatchRule::MatchruleSkipSnl),
"Skip space and newline".into_view(),
),
];

view! {
Expand All @@ -256,16 +255,16 @@ pub fn Problem() -> impl IntoView {
<InputNumber value=difficulty />
</div>
<div class="flex flex-col min-w-fit grow">
<label class="text-text pb-2">Time (nanosecond)</label>
<label class="text-text pb-2">Time (MS)</label>
<InputNumber value=time />
</div>
<div class="flex flex-col min-w-fit grow">
<label class="text-text pb-2">Memory (byte)</label>
<label class="text-text pb-2">Memory (MB)</label>
<InputNumber value=memory />
</div>
<div class="flex flex-col min-w-fit grow">
<label class="text-text pb-2">Match Rule</label>
<Select value=match_rule placeholder="Match Rule" options />
<Select value=match_rule placeholder="Match Rule".into_view() options />
</div>
</div>

Expand Down
6 changes: 3 additions & 3 deletions frontend/src/pages/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ mod login;
mod problem;
mod problems;
mod rank;
mod submission;
mod submissions;

use about::About;
use contest::Contest;
Expand All @@ -20,7 +20,7 @@ use login::Login;
use problem::ProblemRouter;
use problems::Problems;
use rank::Rank;
use submission::Submission;
use submissions::Submissions;

use crate::{components::*, utils::*};

Expand Down Expand Up @@ -68,7 +68,7 @@ pub fn Pages() -> impl IntoView {
<Route path="" view=page_wrapper>
<Route path="" view=Home />
<Route path="/problems" view=Problems ssr=SsrMode::Async />
<Route path="/submissions" view=Submission />
<Route path="/submissions" view=Submissions />
<Route path="/contests" view=Contests />
<Route path="/contest" view=Contest />
<Route path="/about" view=About />
Expand Down
Loading

0 comments on commit 3df904d

Please sign in to comment.