From 72617334e13920c9f3ae797681b2851a03caa1a5 Mon Sep 17 00:00:00 2001 From: Emanuel Pilz Date: Sat, 13 Sep 2025 03:33:21 +0200 Subject: [PATCH 1/3] Move submitter to its own page and prefill fields if possible --- .gitignore | 8 +- pointercrate-demonlist-api/src/lib.rs | 3 +- pointercrate-demonlist-api/src/pages.rs | 30 +++- .../src/account/demons.rs | 7 +- .../src/account/records.rs | 13 +- .../src/components/mod.rs | 23 ++- .../src/components/submitter.rs | 133 ----------------- .../src/demon_page.rs | 12 +- pointercrate-demonlist-pages/src/lib.rs | 1 + pointercrate-demonlist-pages/src/overview.rs | 11 +- .../src/submit_record.rs | 139 ++++++++++++++++++ .../static/css/demonlist.css | 7 - pointercrate-demonlist/src/player/get.rs | 14 ++ 13 files changed, 223 insertions(+), 178 deletions(-) delete mode 100644 pointercrate-demonlist-pages/src/components/submitter.rs create mode 100644 pointercrate-demonlist-pages/src/submit_record.rs diff --git a/.gitignore b/.gitignore index e2993f728..410107735 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,17 @@ +temp/ **/target/ .env .secret .idea .vscode +.DS_Store # random stuff I have because of enabling eslint in vscode node_modules eslint.config.mjs package-lock.json -package.json \ No newline at end of file +package.json + +# Local Postgres data and scripts +pg_data/ +pg_log diff --git a/pointercrate-demonlist-api/src/lib.rs b/pointercrate-demonlist-api/src/lib.rs index bd22c2b2e..3b31b1a8e 100644 --- a/pointercrate-demonlist-api/src/lib.rs +++ b/pointercrate-demonlist-api/src/lib.rs @@ -97,7 +97,8 @@ pub fn setup(rocket: Rocket) -> Rocket { pages::nation_stats_viewer, pages::demon_page, pages::demon_permalink, - pages::heatmap_css + pages::heatmap_css, + pages::submit_record, ], ) } diff --git a/pointercrate-demonlist-api/src/pages.rs b/pointercrate-demonlist-api/src/pages.rs index 5ce32a874..ae44bdae7 100644 --- a/pointercrate-demonlist-api/src/pages.rs +++ b/pointercrate-demonlist-api/src/pages.rs @@ -9,7 +9,7 @@ use pointercrate_core_api::{ error::Result, response::{Page, Response2}, }; -use pointercrate_demonlist::player::claim::PlayerClaim; +use pointercrate_demonlist::player::{claim::PlayerClaim, DatabasePlayer}; use pointercrate_demonlist::player::{FullPlayer, Player}; use pointercrate_demonlist::{ demon::{audit::audit_log_for_demon, current_list, list_at, FullDemon, MinimalDemon}, @@ -22,6 +22,7 @@ use pointercrate_demonlist_pages::{ demon_page::{DemonMovement, DemonPage}, overview::OverviewPage, statsviewer::individual::IndividualStatsViewer, + submit_record::SubmitRecordPage, }; use pointercrate_integrate::gd::GeometryDashConnector; use pointercrate_user::auth::NonMutating; @@ -32,10 +33,9 @@ use rocket::{futures::StreamExt, http::CookieJar}; use sqlx::PgConnection; #[localized] -#[rocket::get("/?&")] +#[rocket::get("/?")] pub async fn overview( - pool: &State, timemachine: Option, submitter: Option, cookies: &CookieJar<'_>, - auth: Option>, + pool: &State, timemachine: Option, cookies: &CookieJar<'_>, auth: Option>, ) -> Result { // A few months before pointercrate first went live - definitely the oldest data we have let beginning_of_time = NaiveDate::from_ymd_opt(2017, 1, 4).unwrap().and_hms_opt(0, 0, 0).unwrap(); @@ -44,6 +44,8 @@ pub async fn overview( let demonlist = current_list(&mut connection).await?; + dbg!(&demonlist); + let mut specified_when = cookies .get("when") .and_then(|cookie| DateTime::::parse_from_rfc3339(cookie.value()).ok()); @@ -84,7 +86,6 @@ pub async fn overview( }, demonlist, time_machine: tardis, - submitter_initially_visible: submitter.unwrap_or(false), claimed_player: match auth { Some(auth) => claimed_full_player(auth.user.user(), &mut connection).await, None => None, @@ -236,3 +237,22 @@ fn make_css_rule(code: &str, score: f64, highest_score: f64) -> String { 0xe0 as f64 + (0xc6 - 0xe0) as f64 * (score / highest_score), ) } + +#[localized] +#[rocket::get("/submit-record?")] +pub async fn submit_record(pool: &State, demon: Option, auth: Option>) -> Result { + let mut connection = pool.connection().await?; + + let demons = current_list(&mut connection).await?; + + let claimed_player = match auth { + Some(auth) => DatabasePlayer::by_user(auth.user.user().id, &mut connection).await.unwrap_or(None), + None => None, + }; + + Ok(Page::new(SubmitRecordPage { + demons, + initial_demon: demon, + initial_holder: claimed_player, + })) +} diff --git a/pointercrate-demonlist-pages/src/account/demons.rs b/pointercrate-demonlist-pages/src/account/demons.rs index dc928a45f..426accf0b 100644 --- a/pointercrate-demonlist-pages/src/account/demons.rs +++ b/pointercrate-demonlist-pages/src/account/demons.rs @@ -303,6 +303,7 @@ fn change_verifier_dialog() -> Markup { &tr("demon-verifier-dialog.info"), &tr("demon-verifier-dialog.submit"), "verifier", + &None, ) } @@ -314,6 +315,7 @@ fn change_publisher_dialog() -> Markup { &tr("demon-publisher-dialog.info"), &tr("demon-publisher-dialog.submit"), "publisher", + &None, ) } @@ -325,6 +327,7 @@ fn add_creator_dialog() -> Markup { &tr("demon-creator-dialog.info"), &tr("demon-creator-dialog.submit"), "creator", + &None, ) } @@ -370,13 +373,13 @@ fn demon_submitter() -> Markup { span.form-input.flex.col data-type = "dropdown" { label{(tr("demon-add-form.verifier-field")) } br; - (player_selection_dropdown("demon-add-verifier", "/api/v1/players/", "name", "verifier")) + (player_selection_dropdown("demon-add-verifier", "/api/v1/players/", "name", "verifier", &None)) p.error {} } span.form-input.flex.col data-type = "dropdown" { label {(tr("demon-add-form.publisher-field")) } br; - (player_selection_dropdown("demon-add-publisher", "/api/v1/players/", "name", "publisher")) + (player_selection_dropdown("demon-add-publisher", "/api/v1/players/", "name", "publisher", &None)) p.error {} } span.form-input.flex.col #demon-add-video { diff --git a/pointercrate-demonlist-pages/src/account/records.rs b/pointercrate-demonlist-pages/src/account/records.rs index 64b92ee2e..52a333e1e 100644 --- a/pointercrate-demonlist-pages/src/account/records.rs +++ b/pointercrate-demonlist-pages/src/account/records.rs @@ -1,6 +1,6 @@ -use crate::components::{ - demon_dropdown, player_selection_dialog, - submitter::{submit_panel, RecordSubmitter}, +use crate::{ + components::{demon_dropdown, player_selection_dialog}, + submit_record::submit_record_panel, }; use maud::{html, Markup, PreEscaped}; use pointercrate_core::{error::PointercrateError, localization::tr, permission::PermissionsManager, trp}; @@ -10,6 +10,7 @@ use pointercrate_core_pages::{ }; use pointercrate_demonlist::{ demon::{current_list, Demon}, + player::DatabasePlayer, LIST_HELPER, }; use pointercrate_user::auth::{AuthenticatedUser, NonMutating}; @@ -59,7 +60,6 @@ impl AccountPageTab for RecordsPage { html! { div.left { - (RecordSubmitter::new(false, &demons[..])) (record_manager(&demons[..])) (note_adder()) div.panel.fade #record-notes-container style = "display:none" { @@ -74,7 +74,7 @@ impl AccountPageTab for RecordsPage { (status_selector()) (record_selector()) (player_selector()) - (submit_panel()) + (submit_record_panel(None)) } (change_progress_dialog()) (change_video_dialog()) @@ -386,6 +386,7 @@ fn change_holder_dialog() -> Markup { &tr("record-holder-dialog.info"), &tr("record-holder-dialog.submit"), "player", + &None, ) } @@ -401,7 +402,7 @@ fn change_demon_dialog(demons: &[Demon]) -> Markup { p { (tr("record-videolink-dialog.info")) } - (demon_dropdown("edit-demon-record", demons.iter())) + (demon_dropdown("edit-demon-record", demons, None)) } } } diff --git a/pointercrate-demonlist-pages/src/components/mod.rs b/pointercrate-demonlist-pages/src/components/mod.rs index 3623bc10e..e03371d6d 100644 --- a/pointercrate-demonlist-pages/src/components/mod.rs +++ b/pointercrate-demonlist-pages/src/components/mod.rs @@ -5,15 +5,19 @@ use pointercrate_core::{localization::tr, trp}; use pointercrate_demonlist::demon::Demon; use pointercrate_demonlist::player::DatabasePlayer; -pub mod submitter; pub mod team; pub mod time_machine; -pub fn demon_dropdown<'a>(dropdown_id: &str, demons: impl Iterator) -> Markup { +pub fn demon_dropdown(dropdown_id: &str, demons: &[Demon], initial_demon: Option) -> Markup { + let initial_demon_name: &str = match initial_demon { + Some(position) => demons.get(position - 1).map_or("", |demon| &demon.base.name), + None => "", + }; + html! { div.dropdown-menu.js-search #(dropdown_id) { div { - input type = "text" name = "demon" required="" autocomplete="off"; + input type = "text" name = "demon" required="" autocomplete="off" value=(initial_demon_name); } div.menu { ul { @@ -26,11 +30,17 @@ pub fn demon_dropdown<'a>(dropdown_id: &str, demons: impl Iterator Markup { +pub fn player_selection_dropdown( + dropdown_id: &str, endpoint: &str, field: &str, form_field: &str, initial_player: &Option, +) -> Markup { + let initial_player_name: &str = match initial_player { + Some(player) => &player.name, + None => "", + }; html! { div.dropdown-menu #(dropdown_id) data-endpoint = (endpoint) data-field = (field) { div { - input type = "text" name = (form_field) required="" autocomplete="off" placeholder = (tr("record-submission.holder-input-placeholder")); + input type = "text" name = (form_field) required="" autocomplete="off" placeholder = (tr("record-submission.holder-input-placeholder")) value = (initial_player_name); } div.menu { // dynamically populated once the user starts typing @@ -42,6 +52,7 @@ pub fn player_selection_dropdown(dropdown_id: &str, endpoint: &str, field: &str, pub fn player_selection_dialog( dialog_id: &str, dropdown_id: &str, headline: &str, description: &str, button_text: &str, form_field: &str, + initial_player: &Option, ) -> Markup { html! { div.overlay.closable { @@ -55,7 +66,7 @@ pub fn player_selection_dialog( (description) } span.form-input.flex.col data-type = "dropdown" { - (player_selection_dropdown(dropdown_id, "/api/v1/players/", "name", form_field)) + (player_selection_dropdown(dropdown_id, "/api/v1/players/", "name", form_field, initial_player)) p.error {} } input.button.blue.hover type = "submit" style = "margin: 15px auto 0px;" value = (button_text); diff --git a/pointercrate-demonlist-pages/src/components/submitter.rs b/pointercrate-demonlist-pages/src/components/submitter.rs deleted file mode 100644 index e78525ccd..000000000 --- a/pointercrate-demonlist-pages/src/components/submitter.rs +++ /dev/null @@ -1,133 +0,0 @@ -use crate::components::{demon_dropdown, player_selection_dropdown}; -use maud::{html, Markup, Render}; -use pointercrate_core::{localization::tr, trp}; -use pointercrate_core_pages::trp_html; -use pointercrate_demonlist::{config, demon::Demon}; - -pub struct RecordSubmitter<'a> { - initially_visible: bool, - demons: &'a [Demon], -} - -impl<'a> RecordSubmitter<'a> { - pub fn new(visible: bool, demons: &'a [Demon]) -> RecordSubmitter<'a> { - RecordSubmitter { - initially_visible: visible, - demons, - } - } -} - -impl Render for RecordSubmitter<'_> { - fn render(&self) -> Markup { - html! { - section.panel.fade.closable #submitter style=(if !self.initially_visible {"display:none"} else {""}) { - span.plus.cross.hover {} - form #submission-form novalidate = "" { - div.underlined { - h2 { (tr("record-submission")) } - } - p.info-red.output {} - p.info-green.output {} - h3 { - (tr("record-submission.demon")) - } - p { - (trp!("record-submission.demon-info", "list-size" = config::extended_list_size())) - } - span.form-input data-type = "dropdown" { - (demon_dropdown("id_demon", self.demons.iter().filter(|demon| demon.base.position <= config::extended_list_size()))) - p.error {} - } - h3 { - (tr("record-submission.holder")) - } - p { - (tr("record-submission.holder-info")) - } - span.form-input.flex.col data-type = "dropdown" { - (player_selection_dropdown("id_player", "/api/v1/players/", "name", "player")) - p.error {} - } - h3 { - (tr("record-submission.progress")) - } - p { - (tr("record-submission.progress-info")) - } - span.form-input.flex.col #id_progress { - input type = "number" name = "progress" required="" placeholder = (tr("record-submission.progress-placeholder")) min="0" max="100"; - p.error {} - } - h3 { - (tr("record-submission.video")) - } - p { - (tr("record-submission.video-info")) - br {} - - i { (tr("record-submission.note")) ": " } - (tr("record-submission.video-note")) - } - span.form-input.flex.col #id_video { - input type = "url" name = "video" required = "" placeholder = (tr("record-submission.video-placeholder")) ; - p.error {} - } - h3 { - (tr("record-submission.raw-footage")) - } - p { - (tr("record-submission.raw-footage-info-a")) - } - p { - (tr("record-submission.raw-footage-info-b")) - } - p { - i { (tr("record-submission.note")) ": " } (tr("record-submission.raw-footage-note")) - } - span.form-input.flex.col #submit-raw-footage { - input type = "url" name = "raw_footage" required = "" placeholder = "https://drive.google.com/file/d/.../view?usp=sharing" {} - p.error {} - } - h3 { - (tr("record-submission.notes")) - } - p { - (tr("record-submission.notes-info")) - } - span.form-input.flex.col #submit-note { - textarea name = "note" placeholder = (tr("record-submission.notes-placeholder")) {} - p.error {} - } - p { - (trp_html!( - "record-submission.guidelines", - "guidelines-link" = html! { - a.link href = "/guidelines" { (tr("record-submission.guidelines-link")) } - } - )) - } - input.button.blue.hover type = "submit" style = "margin: 15px auto 0px;" value=(tr("record-submission.submit")); - } - } - } - } -} - -pub(crate) fn submit_panel() -> Markup { - html! { - section #submit.panel.fade.js-scroll-anim data-anim = "fade" { - div.underlined { - h2 { - (tr("record-submission-panel")) - } - } - p { - (tr("record-submission-panel.info")) - } - a.blue.hover.button.js-scroll data-destination = "submitter" data-reveal = "true" { - (tr("record-submission-panel.redirect")) - } - } - } -} diff --git a/pointercrate-demonlist-pages/src/demon_page.rs b/pointercrate-demonlist-pages/src/demon_page.rs index 78fc970b2..4610bcf4b 100644 --- a/pointercrate-demonlist-pages/src/demon_page.rs +++ b/pointercrate-demonlist-pages/src/demon_page.rs @@ -1,11 +1,6 @@ use crate::components::P; -use crate::{ - components::{ - submitter::{submit_panel, RecordSubmitter}, - team::Team, - }, - statsviewer::stats_viewer_panel, -}; +use crate::submit_record::submit_record_panel; +use crate::{components::team::Team, statsviewer::stats_viewer_panel}; use chrono::NaiveDateTime; use maud::{html, Markup, PreEscaped}; use pointercrate_core::{localization::tr, trp}; @@ -150,7 +145,6 @@ impl DemonPage { div.flex.m-center.container { main.left { - (RecordSubmitter::new(false, &self.demonlist)) (self.demon_panel()) div.panel.fade.js-scroll-anim.js-collapse data-anim = "fade" { h2.underlined.pad { @@ -193,7 +187,7 @@ impl DemonPage { aside.right { (self.team) (super::rules_panel()) - (submit_panel()) + (submit_record_panel(Some(self.data.position()))) (stats_viewer_panel()) (super::discord_panel()) } diff --git a/pointercrate-demonlist-pages/src/lib.rs b/pointercrate-demonlist-pages/src/lib.rs index e2a9cb82d..31ea92d07 100644 --- a/pointercrate-demonlist-pages/src/lib.rs +++ b/pointercrate-demonlist-pages/src/lib.rs @@ -8,6 +8,7 @@ pub mod components; pub mod demon_page; pub mod overview; pub mod statsviewer; +pub mod submit_record; struct ListSection { name: String, diff --git a/pointercrate-demonlist-pages/src/overview.rs b/pointercrate-demonlist-pages/src/overview.rs index 7e4259953..623195258 100644 --- a/pointercrate-demonlist-pages/src/overview.rs +++ b/pointercrate-demonlist-pages/src/overview.rs @@ -1,10 +1,7 @@ use crate::components::P; +use crate::submit_record::submit_record_panel; use crate::{ - components::{ - submitter::{submit_panel, RecordSubmitter}, - team::Team, - time_machine::Tardis, - }, + components::{team::Team, time_machine::Tardis}, statsviewer::stats_viewer_panel, }; use maud::{html, Markup, PreEscaped}; @@ -20,7 +17,6 @@ pub struct OverviewPage { pub team: Team, pub demonlist: Vec, pub time_machine: Tardis, - pub submitter_initially_visible: bool, pub claimed_player: Option, } @@ -97,7 +93,6 @@ impl OverviewPage { div.flex.m-center.container { main.left { (self.time_machine) - (RecordSubmitter::new(self.submitter_initially_visible, &self.demonlist)) @match &self.time_machine { Tardis::Activated { demons, ..} => { @@ -120,7 +115,7 @@ impl OverviewPage { aside.right { (self.team) (super::rules_panel()) - (submit_panel()) + (submit_record_panel(None)) (stats_viewer_panel()) (super::discord_panel()) } diff --git a/pointercrate-demonlist-pages/src/submit_record.rs b/pointercrate-demonlist-pages/src/submit_record.rs new file mode 100644 index 000000000..c768d1e99 --- /dev/null +++ b/pointercrate-demonlist-pages/src/submit_record.rs @@ -0,0 +1,139 @@ +use crate::components::{demon_dropdown, player_selection_dropdown}; +use maud::{html, Markup}; +use pointercrate_core::{localization::tr, trp}; +use pointercrate_core_pages::{head::HeadLike as _, trp_html, PageFragment}; +use pointercrate_demonlist::{config, demon::Demon, player::DatabasePlayer}; + +pub struct SubmitRecordPage { + pub initial_holder: Option, + /// Position of the demon initially selected + pub initial_demon: Option, + pub demons: Vec, +} + +impl From for PageFragment { + fn from(page: SubmitRecordPage) -> Self { + PageFragment::new("Submit Record - Geometry Dash Demonlist", "Submit a record to the Demonlist") + .module("/static/core/js/modules/form.js") + .module("/static/demonlist/js/modules/demonlist.js") + .module("/static/demonlist/js/demonlist.js") + .stylesheet("/static/demonlist/css/submit.css") + .body(page.body()) + } +} + +impl SubmitRecordPage { + fn body(&self) -> Markup { + html! { + form.panel.fade #submission-form novalidate = "" { + div.underlined { + h2 { (tr("record-submission")) } + } + p.info-red.output {} + p.info-green.output {} + h3 { + (tr("record-submission.demon")) + } + p { + (trp!("record-submission.demon-info", "list-size" = config::extended_list_size())) + } + span.form-input data-type = "dropdown" { + (demon_dropdown("id_demon", &self.demons, self.initial_demon)) + p.error {} + } + h3 { + (tr("record-submission.holder")) + } + p { + (tr("record-submission.holder-info")) + } + span.form-input.flex.col data-type = "dropdown" { + (player_selection_dropdown("id_player", "/api/v1/players/", "name", "player", &self.initial_holder)) + p.error {} + } + h3 { + (tr("record-submission.progress")) + } + p { + (tr("record-submission.progress-info")) + } + span.form-input.flex.col #id_progress { + input type = "number" name = "progress" required="" placeholder = (tr("record-submission.progress-placeholder")) min="0" max="100"; + p.error {} + } + h3 { + (tr("record-submission.video")) + } + p { + (tr("record-submission.video-info")) + br {} + + i { (tr("record-submission.note")) ": " } + (tr("record-submission.video-note")) + } + span.form-input.flex.col #id_video { + input type = "url" name = "video" required = "" placeholder = (tr("record-submission.video-placeholder")) ; + p.error {} + } + h3 { + (tr("record-submission.raw-footage")) + } + p { + (tr("record-submission.raw-footage-info-a")) + } + p { + (tr("record-submission.raw-footage-info-b")) + } + p { + i { (tr("record-submission.note")) ": " } (tr("record-submission.raw-footage-note")) + } + span.form-input.flex.col #submit-raw-footage { + input type = "url" name = "raw_footage" required = "" placeholder = "https://drive.google.com/file/d/.../view?usp=sharing" {} + p.error {} + } + h3 { + (tr("record-submission.notes")) + } + p { + (tr("record-submission.notes-info")) + } + span.form-input.flex.col #submit-note { + textarea name = "note" placeholder = (tr("record-submission.notes-placeholder")) {} + p.error {} + } + p { + (trp_html!( + "record-submission.guidelines", + "guidelines-link" = html! { + a.link href = "/guidelines" { (tr("record-submission.guidelines-link")) } + } + )) + } + input.button.blue.hover type = "submit" style = "margin: 15px auto 0px;" value=(tr("record-submission.submit")); + } + } + } +} + +pub(crate) fn submit_record_panel(demon_position: Option) -> Markup { + let search_params = match demon_position { + Some(position) => format!("?demon={position:?}"), + None => "".to_owned(), + }; + + html! { + section #submit.panel.fade.js-scroll-anim data-anim = "fade" { + div.underlined { + h2 { + (tr("record-submission-panel")) + } + } + p { + (tr("record-submission-panel.info")) + } + a.blue.hover.button.js-scroll href=(format!("/demonlist/submit-record{search_params}")) { + (tr("record-submission-panel.redirect")) + } + } + } +} diff --git a/pointercrate-demonlist-pages/static/css/demonlist.css b/pointercrate-demonlist-pages/static/css/demonlist.css index 386a3011c..b02637224 100644 --- a/pointercrate-demonlist-pages/static/css/demonlist.css +++ b/pointercrate-demonlist-pages/static/css/demonlist.css @@ -140,13 +140,6 @@ tr:nth-child(even) { margin: 20px 0px 10px 0px; } -#submission-form > p { - margin: 5px 0; - - font-size: 90%; - text-align: center; -} - .thumb { background: black; background-size: cover; diff --git a/pointercrate-demonlist/src/player/get.rs b/pointercrate-demonlist/src/player/get.rs index 14ad3677f..02736b6bd 100644 --- a/pointercrate-demonlist/src/player/get.rs +++ b/pointercrate-demonlist/src/player/get.rs @@ -114,6 +114,20 @@ impl DatabasePlayer { result => result, } } + + pub async fn by_user(user_id: i32, connection: &mut PgConnection) -> Result> { + sqlx::query_as!( + DatabasePlayer, + "SELECT players.id, players.name, players.banned FROM players + JOIN player_claims ON player_claims.player_id = players.id + JOIN members ON members.member_id = player_claims.member_id + WHERE members.member_id = $1", + user_id + ) + .fetch_optional(connection) + .await + .map_err(DemonlistError::from) + } } #[cfg(test)] From 3e3a7c8f0e919aac3f37efca6c8542a1d8f097a6 Mon Sep 17 00:00:00 2001 From: Emanuel Pilz Date: Sat, 4 Oct 2025 04:29:46 +0200 Subject: [PATCH 2/3] Revert "Move submitter to its own page and prefill fields if possible" This reverts commit 72617334e13920c9f3ae797681b2851a03caa1a5. --- .gitignore | 8 +- pointercrate-demonlist-api/src/lib.rs | 3 +- pointercrate-demonlist-api/src/pages.rs | 30 +--- .../src/account/demons.rs | 7 +- .../src/account/records.rs | 13 +- .../src/components/mod.rs | 23 +-- .../src/components/submitter.rs | 133 +++++++++++++++++ .../src/demon_page.rs | 12 +- pointercrate-demonlist-pages/src/lib.rs | 1 - pointercrate-demonlist-pages/src/overview.rs | 11 +- .../src/submit_record.rs | 139 ------------------ .../static/css/demonlist.css | 7 + pointercrate-demonlist/src/player/get.rs | 14 -- 13 files changed, 178 insertions(+), 223 deletions(-) create mode 100644 pointercrate-demonlist-pages/src/components/submitter.rs delete mode 100644 pointercrate-demonlist-pages/src/submit_record.rs diff --git a/.gitignore b/.gitignore index 410107735..e2993f728 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,11 @@ -temp/ **/target/ .env .secret .idea .vscode -.DS_Store # random stuff I have because of enabling eslint in vscode node_modules eslint.config.mjs package-lock.json -package.json - -# Local Postgres data and scripts -pg_data/ -pg_log +package.json \ No newline at end of file diff --git a/pointercrate-demonlist-api/src/lib.rs b/pointercrate-demonlist-api/src/lib.rs index 3b31b1a8e..bd22c2b2e 100644 --- a/pointercrate-demonlist-api/src/lib.rs +++ b/pointercrate-demonlist-api/src/lib.rs @@ -97,8 +97,7 @@ pub fn setup(rocket: Rocket) -> Rocket { pages::nation_stats_viewer, pages::demon_page, pages::demon_permalink, - pages::heatmap_css, - pages::submit_record, + pages::heatmap_css ], ) } diff --git a/pointercrate-demonlist-api/src/pages.rs b/pointercrate-demonlist-api/src/pages.rs index ae44bdae7..5ce32a874 100644 --- a/pointercrate-demonlist-api/src/pages.rs +++ b/pointercrate-demonlist-api/src/pages.rs @@ -9,7 +9,7 @@ use pointercrate_core_api::{ error::Result, response::{Page, Response2}, }; -use pointercrate_demonlist::player::{claim::PlayerClaim, DatabasePlayer}; +use pointercrate_demonlist::player::claim::PlayerClaim; use pointercrate_demonlist::player::{FullPlayer, Player}; use pointercrate_demonlist::{ demon::{audit::audit_log_for_demon, current_list, list_at, FullDemon, MinimalDemon}, @@ -22,7 +22,6 @@ use pointercrate_demonlist_pages::{ demon_page::{DemonMovement, DemonPage}, overview::OverviewPage, statsviewer::individual::IndividualStatsViewer, - submit_record::SubmitRecordPage, }; use pointercrate_integrate::gd::GeometryDashConnector; use pointercrate_user::auth::NonMutating; @@ -33,9 +32,10 @@ use rocket::{futures::StreamExt, http::CookieJar}; use sqlx::PgConnection; #[localized] -#[rocket::get("/?")] +#[rocket::get("/?&")] pub async fn overview( - pool: &State, timemachine: Option, cookies: &CookieJar<'_>, auth: Option>, + pool: &State, timemachine: Option, submitter: Option, cookies: &CookieJar<'_>, + auth: Option>, ) -> Result { // A few months before pointercrate first went live - definitely the oldest data we have let beginning_of_time = NaiveDate::from_ymd_opt(2017, 1, 4).unwrap().and_hms_opt(0, 0, 0).unwrap(); @@ -44,8 +44,6 @@ pub async fn overview( let demonlist = current_list(&mut connection).await?; - dbg!(&demonlist); - let mut specified_when = cookies .get("when") .and_then(|cookie| DateTime::::parse_from_rfc3339(cookie.value()).ok()); @@ -86,6 +84,7 @@ pub async fn overview( }, demonlist, time_machine: tardis, + submitter_initially_visible: submitter.unwrap_or(false), claimed_player: match auth { Some(auth) => claimed_full_player(auth.user.user(), &mut connection).await, None => None, @@ -237,22 +236,3 @@ fn make_css_rule(code: &str, score: f64, highest_score: f64) -> String { 0xe0 as f64 + (0xc6 - 0xe0) as f64 * (score / highest_score), ) } - -#[localized] -#[rocket::get("/submit-record?")] -pub async fn submit_record(pool: &State, demon: Option, auth: Option>) -> Result { - let mut connection = pool.connection().await?; - - let demons = current_list(&mut connection).await?; - - let claimed_player = match auth { - Some(auth) => DatabasePlayer::by_user(auth.user.user().id, &mut connection).await.unwrap_or(None), - None => None, - }; - - Ok(Page::new(SubmitRecordPage { - demons, - initial_demon: demon, - initial_holder: claimed_player, - })) -} diff --git a/pointercrate-demonlist-pages/src/account/demons.rs b/pointercrate-demonlist-pages/src/account/demons.rs index 426accf0b..dc928a45f 100644 --- a/pointercrate-demonlist-pages/src/account/demons.rs +++ b/pointercrate-demonlist-pages/src/account/demons.rs @@ -303,7 +303,6 @@ fn change_verifier_dialog() -> Markup { &tr("demon-verifier-dialog.info"), &tr("demon-verifier-dialog.submit"), "verifier", - &None, ) } @@ -315,7 +314,6 @@ fn change_publisher_dialog() -> Markup { &tr("demon-publisher-dialog.info"), &tr("demon-publisher-dialog.submit"), "publisher", - &None, ) } @@ -327,7 +325,6 @@ fn add_creator_dialog() -> Markup { &tr("demon-creator-dialog.info"), &tr("demon-creator-dialog.submit"), "creator", - &None, ) } @@ -373,13 +370,13 @@ fn demon_submitter() -> Markup { span.form-input.flex.col data-type = "dropdown" { label{(tr("demon-add-form.verifier-field")) } br; - (player_selection_dropdown("demon-add-verifier", "/api/v1/players/", "name", "verifier", &None)) + (player_selection_dropdown("demon-add-verifier", "/api/v1/players/", "name", "verifier")) p.error {} } span.form-input.flex.col data-type = "dropdown" { label {(tr("demon-add-form.publisher-field")) } br; - (player_selection_dropdown("demon-add-publisher", "/api/v1/players/", "name", "publisher", &None)) + (player_selection_dropdown("demon-add-publisher", "/api/v1/players/", "name", "publisher")) p.error {} } span.form-input.flex.col #demon-add-video { diff --git a/pointercrate-demonlist-pages/src/account/records.rs b/pointercrate-demonlist-pages/src/account/records.rs index 52a333e1e..64b92ee2e 100644 --- a/pointercrate-demonlist-pages/src/account/records.rs +++ b/pointercrate-demonlist-pages/src/account/records.rs @@ -1,6 +1,6 @@ -use crate::{ - components::{demon_dropdown, player_selection_dialog}, - submit_record::submit_record_panel, +use crate::components::{ + demon_dropdown, player_selection_dialog, + submitter::{submit_panel, RecordSubmitter}, }; use maud::{html, Markup, PreEscaped}; use pointercrate_core::{error::PointercrateError, localization::tr, permission::PermissionsManager, trp}; @@ -10,7 +10,6 @@ use pointercrate_core_pages::{ }; use pointercrate_demonlist::{ demon::{current_list, Demon}, - player::DatabasePlayer, LIST_HELPER, }; use pointercrate_user::auth::{AuthenticatedUser, NonMutating}; @@ -60,6 +59,7 @@ impl AccountPageTab for RecordsPage { html! { div.left { + (RecordSubmitter::new(false, &demons[..])) (record_manager(&demons[..])) (note_adder()) div.panel.fade #record-notes-container style = "display:none" { @@ -74,7 +74,7 @@ impl AccountPageTab for RecordsPage { (status_selector()) (record_selector()) (player_selector()) - (submit_record_panel(None)) + (submit_panel()) } (change_progress_dialog()) (change_video_dialog()) @@ -386,7 +386,6 @@ fn change_holder_dialog() -> Markup { &tr("record-holder-dialog.info"), &tr("record-holder-dialog.submit"), "player", - &None, ) } @@ -402,7 +401,7 @@ fn change_demon_dialog(demons: &[Demon]) -> Markup { p { (tr("record-videolink-dialog.info")) } - (demon_dropdown("edit-demon-record", demons, None)) + (demon_dropdown("edit-demon-record", demons.iter())) } } } diff --git a/pointercrate-demonlist-pages/src/components/mod.rs b/pointercrate-demonlist-pages/src/components/mod.rs index e03371d6d..3623bc10e 100644 --- a/pointercrate-demonlist-pages/src/components/mod.rs +++ b/pointercrate-demonlist-pages/src/components/mod.rs @@ -5,19 +5,15 @@ use pointercrate_core::{localization::tr, trp}; use pointercrate_demonlist::demon::Demon; use pointercrate_demonlist::player::DatabasePlayer; +pub mod submitter; pub mod team; pub mod time_machine; -pub fn demon_dropdown(dropdown_id: &str, demons: &[Demon], initial_demon: Option) -> Markup { - let initial_demon_name: &str = match initial_demon { - Some(position) => demons.get(position - 1).map_or("", |demon| &demon.base.name), - None => "", - }; - +pub fn demon_dropdown<'a>(dropdown_id: &str, demons: impl Iterator) -> Markup { html! { div.dropdown-menu.js-search #(dropdown_id) { div { - input type = "text" name = "demon" required="" autocomplete="off" value=(initial_demon_name); + input type = "text" name = "demon" required="" autocomplete="off"; } div.menu { ul { @@ -30,17 +26,11 @@ pub fn demon_dropdown(dropdown_id: &str, demons: &[Demon], initial_demon: Option } } -pub fn player_selection_dropdown( - dropdown_id: &str, endpoint: &str, field: &str, form_field: &str, initial_player: &Option, -) -> Markup { - let initial_player_name: &str = match initial_player { - Some(player) => &player.name, - None => "", - }; +pub fn player_selection_dropdown(dropdown_id: &str, endpoint: &str, field: &str, form_field: &str) -> Markup { html! { div.dropdown-menu #(dropdown_id) data-endpoint = (endpoint) data-field = (field) { div { - input type = "text" name = (form_field) required="" autocomplete="off" placeholder = (tr("record-submission.holder-input-placeholder")) value = (initial_player_name); + input type = "text" name = (form_field) required="" autocomplete="off" placeholder = (tr("record-submission.holder-input-placeholder")); } div.menu { // dynamically populated once the user starts typing @@ -52,7 +42,6 @@ pub fn player_selection_dropdown( pub fn player_selection_dialog( dialog_id: &str, dropdown_id: &str, headline: &str, description: &str, button_text: &str, form_field: &str, - initial_player: &Option, ) -> Markup { html! { div.overlay.closable { @@ -66,7 +55,7 @@ pub fn player_selection_dialog( (description) } span.form-input.flex.col data-type = "dropdown" { - (player_selection_dropdown(dropdown_id, "/api/v1/players/", "name", form_field, initial_player)) + (player_selection_dropdown(dropdown_id, "/api/v1/players/", "name", form_field)) p.error {} } input.button.blue.hover type = "submit" style = "margin: 15px auto 0px;" value = (button_text); diff --git a/pointercrate-demonlist-pages/src/components/submitter.rs b/pointercrate-demonlist-pages/src/components/submitter.rs new file mode 100644 index 000000000..e78525ccd --- /dev/null +++ b/pointercrate-demonlist-pages/src/components/submitter.rs @@ -0,0 +1,133 @@ +use crate::components::{demon_dropdown, player_selection_dropdown}; +use maud::{html, Markup, Render}; +use pointercrate_core::{localization::tr, trp}; +use pointercrate_core_pages::trp_html; +use pointercrate_demonlist::{config, demon::Demon}; + +pub struct RecordSubmitter<'a> { + initially_visible: bool, + demons: &'a [Demon], +} + +impl<'a> RecordSubmitter<'a> { + pub fn new(visible: bool, demons: &'a [Demon]) -> RecordSubmitter<'a> { + RecordSubmitter { + initially_visible: visible, + demons, + } + } +} + +impl Render for RecordSubmitter<'_> { + fn render(&self) -> Markup { + html! { + section.panel.fade.closable #submitter style=(if !self.initially_visible {"display:none"} else {""}) { + span.plus.cross.hover {} + form #submission-form novalidate = "" { + div.underlined { + h2 { (tr("record-submission")) } + } + p.info-red.output {} + p.info-green.output {} + h3 { + (tr("record-submission.demon")) + } + p { + (trp!("record-submission.demon-info", "list-size" = config::extended_list_size())) + } + span.form-input data-type = "dropdown" { + (demon_dropdown("id_demon", self.demons.iter().filter(|demon| demon.base.position <= config::extended_list_size()))) + p.error {} + } + h3 { + (tr("record-submission.holder")) + } + p { + (tr("record-submission.holder-info")) + } + span.form-input.flex.col data-type = "dropdown" { + (player_selection_dropdown("id_player", "/api/v1/players/", "name", "player")) + p.error {} + } + h3 { + (tr("record-submission.progress")) + } + p { + (tr("record-submission.progress-info")) + } + span.form-input.flex.col #id_progress { + input type = "number" name = "progress" required="" placeholder = (tr("record-submission.progress-placeholder")) min="0" max="100"; + p.error {} + } + h3 { + (tr("record-submission.video")) + } + p { + (tr("record-submission.video-info")) + br {} + + i { (tr("record-submission.note")) ": " } + (tr("record-submission.video-note")) + } + span.form-input.flex.col #id_video { + input type = "url" name = "video" required = "" placeholder = (tr("record-submission.video-placeholder")) ; + p.error {} + } + h3 { + (tr("record-submission.raw-footage")) + } + p { + (tr("record-submission.raw-footage-info-a")) + } + p { + (tr("record-submission.raw-footage-info-b")) + } + p { + i { (tr("record-submission.note")) ": " } (tr("record-submission.raw-footage-note")) + } + span.form-input.flex.col #submit-raw-footage { + input type = "url" name = "raw_footage" required = "" placeholder = "https://drive.google.com/file/d/.../view?usp=sharing" {} + p.error {} + } + h3 { + (tr("record-submission.notes")) + } + p { + (tr("record-submission.notes-info")) + } + span.form-input.flex.col #submit-note { + textarea name = "note" placeholder = (tr("record-submission.notes-placeholder")) {} + p.error {} + } + p { + (trp_html!( + "record-submission.guidelines", + "guidelines-link" = html! { + a.link href = "/guidelines" { (tr("record-submission.guidelines-link")) } + } + )) + } + input.button.blue.hover type = "submit" style = "margin: 15px auto 0px;" value=(tr("record-submission.submit")); + } + } + } + } +} + +pub(crate) fn submit_panel() -> Markup { + html! { + section #submit.panel.fade.js-scroll-anim data-anim = "fade" { + div.underlined { + h2 { + (tr("record-submission-panel")) + } + } + p { + (tr("record-submission-panel.info")) + } + a.blue.hover.button.js-scroll data-destination = "submitter" data-reveal = "true" { + (tr("record-submission-panel.redirect")) + } + } + } +} diff --git a/pointercrate-demonlist-pages/src/demon_page.rs b/pointercrate-demonlist-pages/src/demon_page.rs index 4610bcf4b..78fc970b2 100644 --- a/pointercrate-demonlist-pages/src/demon_page.rs +++ b/pointercrate-demonlist-pages/src/demon_page.rs @@ -1,6 +1,11 @@ use crate::components::P; -use crate::submit_record::submit_record_panel; -use crate::{components::team::Team, statsviewer::stats_viewer_panel}; +use crate::{ + components::{ + submitter::{submit_panel, RecordSubmitter}, + team::Team, + }, + statsviewer::stats_viewer_panel, +}; use chrono::NaiveDateTime; use maud::{html, Markup, PreEscaped}; use pointercrate_core::{localization::tr, trp}; @@ -145,6 +150,7 @@ impl DemonPage { div.flex.m-center.container { main.left { + (RecordSubmitter::new(false, &self.demonlist)) (self.demon_panel()) div.panel.fade.js-scroll-anim.js-collapse data-anim = "fade" { h2.underlined.pad { @@ -187,7 +193,7 @@ impl DemonPage { aside.right { (self.team) (super::rules_panel()) - (submit_record_panel(Some(self.data.position()))) + (submit_panel()) (stats_viewer_panel()) (super::discord_panel()) } diff --git a/pointercrate-demonlist-pages/src/lib.rs b/pointercrate-demonlist-pages/src/lib.rs index 31ea92d07..e2a9cb82d 100644 --- a/pointercrate-demonlist-pages/src/lib.rs +++ b/pointercrate-demonlist-pages/src/lib.rs @@ -8,7 +8,6 @@ pub mod components; pub mod demon_page; pub mod overview; pub mod statsviewer; -pub mod submit_record; struct ListSection { name: String, diff --git a/pointercrate-demonlist-pages/src/overview.rs b/pointercrate-demonlist-pages/src/overview.rs index 623195258..7e4259953 100644 --- a/pointercrate-demonlist-pages/src/overview.rs +++ b/pointercrate-demonlist-pages/src/overview.rs @@ -1,7 +1,10 @@ use crate::components::P; -use crate::submit_record::submit_record_panel; use crate::{ - components::{team::Team, time_machine::Tardis}, + components::{ + submitter::{submit_panel, RecordSubmitter}, + team::Team, + time_machine::Tardis, + }, statsviewer::stats_viewer_panel, }; use maud::{html, Markup, PreEscaped}; @@ -17,6 +20,7 @@ pub struct OverviewPage { pub team: Team, pub demonlist: Vec, pub time_machine: Tardis, + pub submitter_initially_visible: bool, pub claimed_player: Option, } @@ -93,6 +97,7 @@ impl OverviewPage { div.flex.m-center.container { main.left { (self.time_machine) + (RecordSubmitter::new(self.submitter_initially_visible, &self.demonlist)) @match &self.time_machine { Tardis::Activated { demons, ..} => { @@ -115,7 +120,7 @@ impl OverviewPage { aside.right { (self.team) (super::rules_panel()) - (submit_record_panel(None)) + (submit_panel()) (stats_viewer_panel()) (super::discord_panel()) } diff --git a/pointercrate-demonlist-pages/src/submit_record.rs b/pointercrate-demonlist-pages/src/submit_record.rs deleted file mode 100644 index c768d1e99..000000000 --- a/pointercrate-demonlist-pages/src/submit_record.rs +++ /dev/null @@ -1,139 +0,0 @@ -use crate::components::{demon_dropdown, player_selection_dropdown}; -use maud::{html, Markup}; -use pointercrate_core::{localization::tr, trp}; -use pointercrate_core_pages::{head::HeadLike as _, trp_html, PageFragment}; -use pointercrate_demonlist::{config, demon::Demon, player::DatabasePlayer}; - -pub struct SubmitRecordPage { - pub initial_holder: Option, - /// Position of the demon initially selected - pub initial_demon: Option, - pub demons: Vec, -} - -impl From for PageFragment { - fn from(page: SubmitRecordPage) -> Self { - PageFragment::new("Submit Record - Geometry Dash Demonlist", "Submit a record to the Demonlist") - .module("/static/core/js/modules/form.js") - .module("/static/demonlist/js/modules/demonlist.js") - .module("/static/demonlist/js/demonlist.js") - .stylesheet("/static/demonlist/css/submit.css") - .body(page.body()) - } -} - -impl SubmitRecordPage { - fn body(&self) -> Markup { - html! { - form.panel.fade #submission-form novalidate = "" { - div.underlined { - h2 { (tr("record-submission")) } - } - p.info-red.output {} - p.info-green.output {} - h3 { - (tr("record-submission.demon")) - } - p { - (trp!("record-submission.demon-info", "list-size" = config::extended_list_size())) - } - span.form-input data-type = "dropdown" { - (demon_dropdown("id_demon", &self.demons, self.initial_demon)) - p.error {} - } - h3 { - (tr("record-submission.holder")) - } - p { - (tr("record-submission.holder-info")) - } - span.form-input.flex.col data-type = "dropdown" { - (player_selection_dropdown("id_player", "/api/v1/players/", "name", "player", &self.initial_holder)) - p.error {} - } - h3 { - (tr("record-submission.progress")) - } - p { - (tr("record-submission.progress-info")) - } - span.form-input.flex.col #id_progress { - input type = "number" name = "progress" required="" placeholder = (tr("record-submission.progress-placeholder")) min="0" max="100"; - p.error {} - } - h3 { - (tr("record-submission.video")) - } - p { - (tr("record-submission.video-info")) - br {} - - i { (tr("record-submission.note")) ": " } - (tr("record-submission.video-note")) - } - span.form-input.flex.col #id_video { - input type = "url" name = "video" required = "" placeholder = (tr("record-submission.video-placeholder")) ; - p.error {} - } - h3 { - (tr("record-submission.raw-footage")) - } - p { - (tr("record-submission.raw-footage-info-a")) - } - p { - (tr("record-submission.raw-footage-info-b")) - } - p { - i { (tr("record-submission.note")) ": " } (tr("record-submission.raw-footage-note")) - } - span.form-input.flex.col #submit-raw-footage { - input type = "url" name = "raw_footage" required = "" placeholder = "https://drive.google.com/file/d/.../view?usp=sharing" {} - p.error {} - } - h3 { - (tr("record-submission.notes")) - } - p { - (tr("record-submission.notes-info")) - } - span.form-input.flex.col #submit-note { - textarea name = "note" placeholder = (tr("record-submission.notes-placeholder")) {} - p.error {} - } - p { - (trp_html!( - "record-submission.guidelines", - "guidelines-link" = html! { - a.link href = "/guidelines" { (tr("record-submission.guidelines-link")) } - } - )) - } - input.button.blue.hover type = "submit" style = "margin: 15px auto 0px;" value=(tr("record-submission.submit")); - } - } - } -} - -pub(crate) fn submit_record_panel(demon_position: Option) -> Markup { - let search_params = match demon_position { - Some(position) => format!("?demon={position:?}"), - None => "".to_owned(), - }; - - html! { - section #submit.panel.fade.js-scroll-anim data-anim = "fade" { - div.underlined { - h2 { - (tr("record-submission-panel")) - } - } - p { - (tr("record-submission-panel.info")) - } - a.blue.hover.button.js-scroll href=(format!("/demonlist/submit-record{search_params}")) { - (tr("record-submission-panel.redirect")) - } - } - } -} diff --git a/pointercrate-demonlist-pages/static/css/demonlist.css b/pointercrate-demonlist-pages/static/css/demonlist.css index b02637224..386a3011c 100644 --- a/pointercrate-demonlist-pages/static/css/demonlist.css +++ b/pointercrate-demonlist-pages/static/css/demonlist.css @@ -140,6 +140,13 @@ tr:nth-child(even) { margin: 20px 0px 10px 0px; } +#submission-form > p { + margin: 5px 0; + + font-size: 90%; + text-align: center; +} + .thumb { background: black; background-size: cover; diff --git a/pointercrate-demonlist/src/player/get.rs b/pointercrate-demonlist/src/player/get.rs index 02736b6bd..14ad3677f 100644 --- a/pointercrate-demonlist/src/player/get.rs +++ b/pointercrate-demonlist/src/player/get.rs @@ -114,20 +114,6 @@ impl DatabasePlayer { result => result, } } - - pub async fn by_user(user_id: i32, connection: &mut PgConnection) -> Result> { - sqlx::query_as!( - DatabasePlayer, - "SELECT players.id, players.name, players.banned FROM players - JOIN player_claims ON player_claims.player_id = players.id - JOIN members ON members.member_id = player_claims.member_id - WHERE members.member_id = $1", - user_id - ) - .fetch_optional(connection) - .await - .map_err(DemonlistError::from) - } } #[cfg(test)] From 36b92a38eb9450cc3f78aed4b0e4bad96c31059d Mon Sep 17 00:00:00 2001 From: Emanuel Pilz Date: Sat, 4 Oct 2025 04:30:19 +0200 Subject: [PATCH 3/3] Prefill fields in record submitter if possible --- .../static/js/modules/form.js | 3 +++ pointercrate-demonlist-api/src/pages.rs | 12 ++++++++-- .../src/account/demons.rs | 7 ++++-- .../src/account/records.rs | 10 +++++--- .../src/components/mod.rs | 13 ++++++---- .../src/components/submitter.rs | 24 ++++++++++++------- .../src/demon_page.rs | 10 +++++++- pointercrate-demonlist-pages/src/overview.rs | 2 +- pointercrate-demonlist/src/player/get.rs | 14 +++++++++++ 9 files changed, 73 insertions(+), 22 deletions(-) diff --git a/pointercrate-core-pages/static/js/modules/form.js b/pointercrate-core-pages/static/js/modules/form.js index 3504e7d3e..40189565a 100644 --- a/pointercrate-core-pages/static/js/modules/form.js +++ b/pointercrate-core-pages/static/js/modules/form.js @@ -133,6 +133,9 @@ export class DynamicSuggestionDropdown extends Dropdown { constructor(html) { super(html); + if (this.input.dataset.default !== undefined) { + this.input.value = this.input.dataset.default; + } this.endpoint = html.dataset.endpoint; this.field = html.dataset.field; diff --git a/pointercrate-demonlist-api/src/pages.rs b/pointercrate-demonlist-api/src/pages.rs index 5ce32a874..f12a2232d 100644 --- a/pointercrate-demonlist-api/src/pages.rs +++ b/pointercrate-demonlist-api/src/pages.rs @@ -9,7 +9,7 @@ use pointercrate_core_api::{ error::Result, response::{Page, Response2}, }; -use pointercrate_demonlist::player::claim::PlayerClaim; +use pointercrate_demonlist::player::{claim::PlayerClaim, DatabasePlayer}; use pointercrate_demonlist::player::{FullPlayer, Player}; use pointercrate_demonlist::{ demon::{audit::audit_log_for_demon, current_list, list_at, FullDemon, MinimalDemon}, @@ -110,11 +110,18 @@ pub async fn demon_permalink(demon_id: i32, pool: &State) -> R #[localized] #[rocket::get("//")] -pub async fn demon_page(position: i16, pool: &State, gd: &State) -> Result { +pub async fn demon_page( + position: i16, auth: Option>, pool: &State, gd: &State, +) -> Result { let mut connection = pool.connection().await?; let full_demon = FullDemon::by_position(position, &mut connection).await?; + let claimed_player = match auth { + Some(auth) => DatabasePlayer::by_user(auth.user.user().id, &mut connection).await?, + None => None, + }; + let audit_log = audit_log_for_demon(full_demon.demon.base.id, &mut connection).await?; let mut addition_time = None; @@ -161,6 +168,7 @@ pub async fn demon_page(position: i16, pool: &State, gd: &Stat movements: modifications, integration: gd.load_level_for_demon(&full_demon.demon).await, data: full_demon, + claimed_player, })) } diff --git a/pointercrate-demonlist-pages/src/account/demons.rs b/pointercrate-demonlist-pages/src/account/demons.rs index dc928a45f..b6c2bb2e3 100644 --- a/pointercrate-demonlist-pages/src/account/demons.rs +++ b/pointercrate-demonlist-pages/src/account/demons.rs @@ -303,6 +303,7 @@ fn change_verifier_dialog() -> Markup { &tr("demon-verifier-dialog.info"), &tr("demon-verifier-dialog.submit"), "verifier", + None, ) } @@ -314,6 +315,7 @@ fn change_publisher_dialog() -> Markup { &tr("demon-publisher-dialog.info"), &tr("demon-publisher-dialog.submit"), "publisher", + None, ) } @@ -325,6 +327,7 @@ fn add_creator_dialog() -> Markup { &tr("demon-creator-dialog.info"), &tr("demon-creator-dialog.submit"), "creator", + None, ) } @@ -370,13 +373,13 @@ fn demon_submitter() -> Markup { span.form-input.flex.col data-type = "dropdown" { label{(tr("demon-add-form.verifier-field")) } br; - (player_selection_dropdown("demon-add-verifier", "/api/v1/players/", "name", "verifier")) + (player_selection_dropdown("demon-add-verifier", "/api/v1/players/", "name", "verifier", None)) p.error {} } span.form-input.flex.col data-type = "dropdown" { label {(tr("demon-add-form.publisher-field")) } br; - (player_selection_dropdown("demon-add-publisher", "/api/v1/players/", "name", "publisher")) + (player_selection_dropdown("demon-add-publisher", "/api/v1/players/", "name", "publisher", None)) p.error {} } span.form-input.flex.col #demon-add-video { diff --git a/pointercrate-demonlist-pages/src/account/records.rs b/pointercrate-demonlist-pages/src/account/records.rs index 64b92ee2e..8f5ad4417 100644 --- a/pointercrate-demonlist-pages/src/account/records.rs +++ b/pointercrate-demonlist-pages/src/account/records.rs @@ -10,6 +10,7 @@ use pointercrate_core_pages::{ }; use pointercrate_demonlist::{ demon::{current_list, Demon}, + player::DatabasePlayer, LIST_HELPER, }; use pointercrate_user::auth::{AuthenticatedUser, NonMutating}; @@ -43,7 +44,7 @@ impl AccountPageTab for RecordsPage { } async fn content( - &self, _user: &AuthenticatedUser, _permissions: &PermissionsManager, connection: &mut PgConnection, + &self, user: &AuthenticatedUser, _permissions: &PermissionsManager, connection: &mut PgConnection, ) -> Markup { let demons = match current_list(connection).await { Ok(demons) => demons, @@ -57,9 +58,11 @@ impl AccountPageTab for RecordsPage { }, }; + let player = DatabasePlayer::by_user(user.user().id, connection).await.unwrap_or(None); + html! { div.left { - (RecordSubmitter::new(false, &demons[..])) + (RecordSubmitter::new(false, &demons[..], player.as_ref(), None)) (record_manager(&demons[..])) (note_adder()) div.panel.fade #record-notes-container style = "display:none" { @@ -386,6 +389,7 @@ fn change_holder_dialog() -> Markup { &tr("record-holder-dialog.info"), &tr("record-holder-dialog.submit"), "player", + None, ) } @@ -401,7 +405,7 @@ fn change_demon_dialog(demons: &[Demon]) -> Markup { p { (tr("record-videolink-dialog.info")) } - (demon_dropdown("edit-demon-record", demons.iter())) + (demon_dropdown("edit-demon-record", demons.iter(), None)) } } } diff --git a/pointercrate-demonlist-pages/src/components/mod.rs b/pointercrate-demonlist-pages/src/components/mod.rs index 3623bc10e..8a8a43cf6 100644 --- a/pointercrate-demonlist-pages/src/components/mod.rs +++ b/pointercrate-demonlist-pages/src/components/mod.rs @@ -9,11 +9,11 @@ pub mod submitter; pub mod team; pub mod time_machine; -pub fn demon_dropdown<'a>(dropdown_id: &str, demons: impl Iterator) -> Markup { +pub fn demon_dropdown<'a>(dropdown_id: &str, demons: impl Iterator, initial_demon: Option) -> Markup { html! { div.dropdown-menu.js-search #(dropdown_id) { div { - input type = "text" name = "demon" required="" autocomplete="off"; + input type = "text" name = "demon" required="" autocomplete="off" data-default=[initial_demon]; } div.menu { ul { @@ -26,11 +26,13 @@ pub fn demon_dropdown<'a>(dropdown_id: &str, demons: impl Iterator Markup { +pub fn player_selection_dropdown( + dropdown_id: &str, endpoint: &str, field: &str, form_field: &str, initial_player: Option<&DatabasePlayer>, +) -> Markup { html! { div.dropdown-menu #(dropdown_id) data-endpoint = (endpoint) data-field = (field) { div { - input type = "text" name = (form_field) required="" autocomplete="off" placeholder = (tr("record-submission.holder-input-placeholder")); + input type = "text" name = (form_field) required="" autocomplete="off" placeholder = (tr("record-submission.holder-input-placeholder")) data-default=[initial_player.map(|p| &p.name)]; } div.menu { // dynamically populated once the user starts typing @@ -42,6 +44,7 @@ pub fn player_selection_dropdown(dropdown_id: &str, endpoint: &str, field: &str, pub fn player_selection_dialog( dialog_id: &str, dropdown_id: &str, headline: &str, description: &str, button_text: &str, form_field: &str, + initial_player: Option<&DatabasePlayer>, ) -> Markup { html! { div.overlay.closable { @@ -55,7 +58,7 @@ pub fn player_selection_dialog( (description) } span.form-input.flex.col data-type = "dropdown" { - (player_selection_dropdown(dropdown_id, "/api/v1/players/", "name", form_field)) + (player_selection_dropdown(dropdown_id, "/api/v1/players/", "name", form_field, initial_player)) p.error {} } input.button.blue.hover type = "submit" style = "margin: 15px auto 0px;" value = (button_text); diff --git a/pointercrate-demonlist-pages/src/components/submitter.rs b/pointercrate-demonlist-pages/src/components/submitter.rs index e78525ccd..c5ffa97e1 100644 --- a/pointercrate-demonlist-pages/src/components/submitter.rs +++ b/pointercrate-demonlist-pages/src/components/submitter.rs @@ -2,23 +2,31 @@ use crate::components::{demon_dropdown, player_selection_dropdown}; use maud::{html, Markup, Render}; use pointercrate_core::{localization::tr, trp}; use pointercrate_core_pages::trp_html; -use pointercrate_demonlist::{config, demon::Demon}; +use pointercrate_demonlist::{config, demon::Demon, player::DatabasePlayer}; -pub struct RecordSubmitter<'a> { +pub struct RecordSubmitter<'d, 'p> { initially_visible: bool, - demons: &'a [Demon], + demons: &'d [Demon], + initial_demon: Option, + initial_holder: Option<&'p DatabasePlayer>, } -impl<'a> RecordSubmitter<'a> { - pub fn new(visible: bool, demons: &'a [Demon]) -> RecordSubmitter<'a> { +impl<'d, 'p> RecordSubmitter<'d, 'p> { + /// * `visible` - Show the record submitter. + /// * `demons` - The Demonlist. + /// * `holder` - Player to preselect as the record holder. `None` to not preselect a player. + /// * `demon` - Position of the demon in the demonlist to preselect. `None` to not preselect a demon. + pub fn new(visible: bool, demons: &'d [Demon], holder: Option<&'p DatabasePlayer>, demon: Option) -> RecordSubmitter<'d, 'p> { RecordSubmitter { initially_visible: visible, demons, + initial_demon: demon, + initial_holder: holder, } } } -impl Render for RecordSubmitter<'_> { +impl Render for RecordSubmitter<'_, '_> { fn render(&self) -> Markup { html! { section.panel.fade.closable #submitter style=(if !self.initially_visible {"display:none"} else {""}) { @@ -36,7 +44,7 @@ impl Render for RecordSubmitter<'_> { (trp!("record-submission.demon-info", "list-size" = config::extended_list_size())) } span.form-input data-type = "dropdown" { - (demon_dropdown("id_demon", self.demons.iter().filter(|demon| demon.base.position <= config::extended_list_size()))) + (demon_dropdown("id_demon", self.demons.iter().filter(|demon| demon.base.position <= config::extended_list_size()), self.initial_demon)) p.error {} } h3 { @@ -46,7 +54,7 @@ impl Render for RecordSubmitter<'_> { (tr("record-submission.holder-info")) } span.form-input.flex.col data-type = "dropdown" { - (player_selection_dropdown("id_player", "/api/v1/players/", "name", "player")) + (player_selection_dropdown("id_player", "/api/v1/players/", "name", "player", self.initial_holder)) p.error {} } h3 { diff --git a/pointercrate-demonlist-pages/src/demon_page.rs b/pointercrate-demonlist-pages/src/demon_page.rs index 78fc970b2..7ccab54f8 100644 --- a/pointercrate-demonlist-pages/src/demon_page.rs +++ b/pointercrate-demonlist-pages/src/demon_page.rs @@ -10,6 +10,7 @@ use chrono::NaiveDateTime; use maud::{html, Markup, PreEscaped}; use pointercrate_core::{localization::tr, trp}; use pointercrate_core_pages::{head::HeadLike, trp_html, PageFragment}; +use pointercrate_demonlist::player::DatabasePlayer; use pointercrate_demonlist::{ config::{self as list_config, extended_list_size}, demon::{Demon, FullDemon}, @@ -29,6 +30,7 @@ pub struct DemonPage { pub data: FullDemon, pub movements: Vec, pub integration: Option, + pub claimed_player: Option, } impl From for PageFragment { @@ -145,12 +147,18 @@ impl DemonPage { } } + let demon = self + .demonlist + .iter() + .position(|d| d.base.id == self.data.demon.base.id) + .map(|i| i as i16 + 1); + html! { (dropdowns) div.flex.m-center.container { main.left { - (RecordSubmitter::new(false, &self.demonlist)) + (RecordSubmitter::new(false, &self.demonlist, self.claimed_player.as_ref(), demon)) (self.demon_panel()) div.panel.fade.js-scroll-anim.js-collapse data-anim = "fade" { h2.underlined.pad { diff --git a/pointercrate-demonlist-pages/src/overview.rs b/pointercrate-demonlist-pages/src/overview.rs index 7e4259953..727dd711a 100644 --- a/pointercrate-demonlist-pages/src/overview.rs +++ b/pointercrate-demonlist-pages/src/overview.rs @@ -97,7 +97,7 @@ impl OverviewPage { div.flex.m-center.container { main.left { (self.time_machine) - (RecordSubmitter::new(self.submitter_initially_visible, &self.demonlist)) + (RecordSubmitter::new(self.submitter_initially_visible, &self.demonlist, self.claimed_player.as_ref().map(|p| &p.player.base), None)) @match &self.time_machine { Tardis::Activated { demons, ..} => { diff --git a/pointercrate-demonlist/src/player/get.rs b/pointercrate-demonlist/src/player/get.rs index 14ad3677f..02736b6bd 100644 --- a/pointercrate-demonlist/src/player/get.rs +++ b/pointercrate-demonlist/src/player/get.rs @@ -114,6 +114,20 @@ impl DatabasePlayer { result => result, } } + + pub async fn by_user(user_id: i32, connection: &mut PgConnection) -> Result> { + sqlx::query_as!( + DatabasePlayer, + "SELECT players.id, players.name, players.banned FROM players + JOIN player_claims ON player_claims.player_id = players.id + JOIN members ON members.member_id = player_claims.member_id + WHERE members.member_id = $1", + user_id + ) + .fetch_optional(connection) + .await + .map_err(DemonlistError::from) + } } #[cfg(test)]