Skip to content

Commit

Permalink
Merge pull request #12 from bitbrain-za/feature/backend-interaction
Browse files Browse the repository at this point in the history
Feature/backend interaction
  • Loading branch information
bitbrain-za authored Nov 14, 2023
2 parents dbc76e5 + 30ab472 commit 0d0ffbc
Show file tree
Hide file tree
Showing 18 changed files with 830 additions and 656 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "challenge_frontend"
version = "2.0.0"
version = "2.2.0"
edition = "2021"
rust-version = "1.71"
authors = ["Philip Barlow"]
Expand Down
22 changes: 18 additions & 4 deletions src/apps/app_windows.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use std::collections::BTreeSet;

use egui::{Context, ScrollArea, Ui};

use super::App;
use crate::helpers::AppState;
use egui::{Context, ScrollArea, Ui};
use std::collections::BTreeSet;
use std::sync::{Arc, Mutex};

// ----------------------------------------------------------------------------

Expand Down Expand Up @@ -74,13 +74,27 @@ fn set_open(open: &mut BTreeSet<String>, key: &'static str, is_open: bool) {
pub struct AppWindows {
about_is_open: bool,
apps: Apps,
#[serde(skip)]
pub app_state: Arc<Mutex<AppState>>,
}

impl Default for AppWindows {
fn default() -> Self {
Self {
about_is_open: true,
apps: Default::default(),
app_state: Arc::new(Mutex::new(AppState::default())),
}
}
}

impl AppWindows {
#[allow(dead_code)] //inhibit warnings when target =/= WASM
pub fn set_app_state_ref(&mut self, app_state: Arc<Mutex<AppState>>) {
self.app_state = app_state;

for app in &mut self.apps.apps {
app.set_app_state_ref(Arc::clone(&self.app_state));
}
}
}
Expand Down
97 changes: 25 additions & 72 deletions src/apps/binary_upload.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
use std::borrow::BorrowMut;
use std::sync::{Arc, Mutex};

use crate::helpers::{
refresh,
submission::{Submission, SubmissionPromise, SubmissionResult},
Challenges, Languages,
fetchers::Requestor,
submission::{Submission, SubmissionResult},
AppState, Challenges, Languages,
};
use gloo_net::http;
use poll_promise::Promise;
use std::future::Future;
use std::sync::mpsc::{channel, Receiver, Sender};
use web_sys::RequestCredentials;

struct Binary {
filename: String,
Expand All @@ -17,8 +17,6 @@ struct Binary {
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(default)]
pub struct BinaryUpload {
#[serde(skip)]
promise: SubmissionPromise,
#[serde(skip)]
last_result: SubmissionResult,
url: String,
Expand All @@ -27,15 +25,14 @@ pub struct BinaryUpload {
#[serde(skip)]
binary_channel: (Sender<Binary>, Receiver<Binary>),
#[serde(skip)]
submit: bool,
submitter: Option<Requestor>,
#[serde(skip)]
token_refresh_promise: refresh::RefreshPromise,
app_state: Arc<Mutex<AppState>>,
}

impl Default for BinaryUpload {
fn default() -> Self {
Self {
promise: Default::default(),
url: option_env!("BACKEND_URL")
.unwrap_or("http://123.4.5.6:3000/")
.to_string(),
Expand All @@ -44,59 +41,19 @@ impl Default for BinaryUpload {
..Default::default()
},
binary_channel: channel(),
submit: false,
token_refresh_promise: None,
submitter: None,
last_result: SubmissionResult::NotStarted,
app_state: Arc::new(Mutex::new(AppState::default())),
}
}
}

impl BinaryUpload {
fn submit(&mut self, ctx: &egui::Context) {
if !self.submit {
return;
}
self.submit = false;
fn submit(&mut self) {
let submission = self.run.clone();

let url = format!("{}api/game/binary", self.url);
log::debug!("Sending to {}", url);
let ctx = ctx.clone();

let promise = Promise::spawn_local(async move {
let formdata = submission.to_formdata();

let response = http::Request::post(&url)
.credentials(RequestCredentials::Include)
.body(formdata)
.unwrap()
.send()
.await
.unwrap();

let result: SubmissionResult = match response.status() {
200 => response.json().await.unwrap(),
401 => {
let text = response.text().await;
let text = text.map(|text| text.to_owned());
let text = match text {
Ok(text) => text,
Err(e) => e.to_string(),
};
log::warn!("Auth Error: {:?}", text);
SubmissionResult::NotAuthorized
}
_ => {
return Err(format!("Failed to submit code: {:?}", response));
}
};

ctx.request_repaint(); // wake up UI thread
log::info!("Result: {:?}", result);
Ok(result)
});

self.promise = Some(promise);
let url = format!("{}api/game/submit", self.url);
let app_state = Arc::clone(&self.app_state);
self.submitter = submission.sender(app_state, &url);
}
}

Expand All @@ -105,6 +62,10 @@ impl super::App for BinaryUpload {
"💻 File Upload"
}

fn set_app_state_ref(&mut self, app_state: Arc<Mutex<AppState>>) {
self.app_state = app_state;
}

fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
use super::View as _;
egui::Window::new(self.name())
Expand All @@ -116,27 +77,19 @@ impl super::App for BinaryUpload {
self.run.filename = f.filename;
self.run.binary = Some(f.bytes);
}
self.submit(ctx);
match refresh::check_refresh_promise(&mut self.token_refresh_promise) {
refresh::RefreshStatus::InProgress => {}
refresh::RefreshStatus::Success => {
self.submit = true;
}
refresh::RefreshStatus::Failed(_) => {}
_ => (),
}

let submission = Submission::check_submit_promise(&mut self.promise);
let submission = Submission::check_sender(&mut self.submitter);
match submission {
SubmissionResult::NotStarted => {}
SubmissionResult::NotAuthorized => {
self.token_refresh_promise = refresh::submit_refresh(&self.url);
self.last_result = submission;
}
_ => {
self.last_result = submission;
}
}
if let Some(fetcher) = self.submitter.borrow_mut() {
if fetcher.refresh_context() {
ctx.request_repaint();
}
}
}
}

Expand Down Expand Up @@ -191,7 +144,7 @@ impl super::View for BinaryUpload {
if ui.button("Submit").clicked() {
match self.run.validate() {
Ok(_) => {
self.submit = true;
self.submit();
}
Err(e) => {
self.last_result = SubmissionResult::Failure { message: e };
Expand Down
95 changes: 46 additions & 49 deletions src/apps/challenge_info.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::helpers::Challenges;
use crate::helpers::{fetchers::Requestor, AppState, Challenges};
use egui_commonmark::*;
use gloo_net::http;
use poll_promise::Promise;
use std::borrow::BorrowMut;
use std::sync::{Arc, Mutex};

#[derive(PartialEq, Clone, Copy, serde::Deserialize, serde::Serialize)]
enum FilterOption {
Expand All @@ -12,51 +12,44 @@ enum FilterOption {

#[derive(serde::Deserialize, serde::Serialize)]
pub struct ChallengeInfoApp {
challenge: Challenges,
#[serde(skip)]
promise: Option<Promise<Result<String, String>>>,
selected_challenge: Challenges,
#[serde(skip)]
active_challenge: Challenges,
#[serde(skip)]
refresh: bool,
info_fetcher: Option<Requestor>,
instructions: String,
#[serde(skip)]
app_state: Arc<Mutex<AppState>>,
}

impl Default for ChallengeInfoApp {
fn default() -> Self {
Self {
challenge: Challenges::default(),
promise: Default::default(),
refresh: true,

active_challenge: Challenges::default(),
selected_challenge: Challenges::default(),
info_fetcher: None,
active_challenge: Challenges::None,
instructions: "None".to_string(),
app_state: Arc::new(Mutex::new(AppState::default())),
}
}
}

impl ChallengeInfoApp {
fn fetch(&mut self, ctx: &egui::Context) {
if !self.refresh {
fn fetch(&mut self) {
if self.active_challenge == self.selected_challenge {
return;
}
self.refresh = false;

let url = self.challenge.get_info_url();
let ctx = ctx.clone();

let promise = poll_promise::Promise::spawn_local(async move {
let response = http::Request::get(&url);
let response = response.send().await.unwrap();
let text = response.text().await.map_err(|e| format!("{:?}", e));
ctx.request_repaint(); // wake up UI thread
text
});
self.promise = Some(promise);
log::debug!("Fetching challenge info");
self.active_challenge = self.selected_challenge;
let app_state = Arc::clone(&self.app_state);
self.info_fetcher = self.selected_challenge.fetcher(app_state);
}
fn check_info_promise(&mut self) {
let getter = &mut self.info_fetcher;

fn check_for_reload(&mut self) {
if self.active_challenge != self.challenge {
self.active_challenge = self.challenge;
self.refresh = true;
if let Some(getter) = getter {
let result = &getter.check_promise();
self.instructions = result.to_string();
}
}
}
Expand All @@ -66,8 +59,20 @@ impl super::App for ChallengeInfoApp {
"📖 Challenge Info"
}

fn set_app_state_ref(&mut self, app_state: Arc<Mutex<AppState>>) {
self.app_state = app_state;
}

fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
self.fetch(ctx);
self.fetch();

if let Some(fetcher) = self.info_fetcher.borrow_mut() {
if fetcher.refresh_context() {
log::debug!("Refreshing context");
ctx.request_repaint();
}
}

egui::Window::new(self.name())
.open(open)
.default_width(800.0)
Expand All @@ -86,38 +91,30 @@ impl super::App for ChallengeInfoApp {

impl super::View for ChallengeInfoApp {
fn ui(&mut self, ui: &mut egui::Ui) {
self.check_for_reload();

self.check_info_promise();
egui::SidePanel::right("ChallengeInfoSelection")
.resizable(false)
.show_inside(ui, |ui| {
ui.vertical(|ui| {
for challenge in Challenges::iter() {
ui.radio_value(&mut self.challenge, challenge, format!("{}", challenge));
ui.radio_value(
&mut self.selected_challenge,
challenge,
format!("{}", challenge),
);
}
ui.separator();
if ui.button("Refresh").clicked() {
self.refresh = true;
self.active_challenge = Challenges::None;
}
});
});
egui::CentralPanel::default().show_inside(ui, |ui| {
egui::ScrollArea::both()
.auto_shrink([false, false])
.show(ui, |ui| {
if let Some(promise) = &self.promise {
if let Some(result) = promise.ready() {
match result {
Ok(text) => {
let mut cache = CommonMarkCache::default();
CommonMarkViewer::new("viewer").show(ui, &mut cache, text);
}
Err(err) => {
ui.label(format!("Error fetching file: {}", err));
}
}
}
}
let mut cache = CommonMarkCache::default();
CommonMarkViewer::new("viewer").show(ui, &mut cache, &self.instructions);
});
});
}
Expand Down
Loading

0 comments on commit 0d0ffbc

Please sign in to comment.