Skip to content

Commit

Permalink
add magic link functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
o-tho committed Nov 30, 2024
1 parent 3dbb54e commit 9e7dfa8
Show file tree
Hide file tree
Showing 7 changed files with 170 additions and 3 deletions.
4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ eframe = {version = "0.29.1", default-features = false, features = [
"persistence"
]}
wasm-bindgen-futures = "0.4.45"
poll-promise = {version="0.3.0", features=["web"]}
serde_cbor = "0.11.2"
snap = "1.1.1"
base64-url = "3.0.0"
rfd = "0.15.0"

[profile.release]
Expand Down
8 changes: 8 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ fn main() -> Result<(), ErrorWrapper> {
use std::path::Path;
let matches = Command::new("autograder")
.about("automatically grade MCQ exams using optical mark recognition")
.subcommand(Command::new("test"))
.subcommand(
Command::new("report")
.about("Generate a report")
Expand Down Expand Up @@ -134,6 +135,13 @@ fn main() -> Result<(), ErrorWrapper> {
_ => println!("Unsupported file type: {:?}", imagefile),
}
}
Some(("test", _sub_matches)) => {
println!("foo");
let t: Template =
serde_json::from_reader(std::fs::File::open("private/40qtemplate.json")?)?;
let outfile = std::fs::File::create("cbortest.cbor")?;
let _ = serde_cbor::to_writer(outfile, &t);
}
Some(("debug", sub_matches)) => {
let templatepath = sub_matches
.get_one::<String>("template")
Expand Down
99 changes: 99 additions & 0 deletions src/webapp/create_magic_link.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
use crate::template::{ExamKey, Template};
use crate::webapp::utils::{encode_key_template, upload_button, FileType};
use eframe::egui::{Context, ScrollArea};
use eframe::Frame;

use tokio::sync::mpsc::{channel, Receiver, Sender};
pub struct CreateMagicLink {
template: Option<Template>,
key: Option<ExamKey>,
data_channel: (Sender<(FileType, Vec<u8>)>, Receiver<(FileType, Vec<u8>)>),
}

impl Default for CreateMagicLink {
fn default() -> Self {
Self {
template: None,
key: None,
data_channel: channel(50),
}
}
}

impl CreateMagicLink {
fn to_link(&self) -> String {
let location = web_sys::window().expect("we need a window").location();

if let (Some(key), Some(template)) = (self.key.clone(), self.template.clone()) {
format!(
"{}{}#{}",
location.origin().unwrap_or("".into()),
location.pathname().unwrap_or("".into()),
encode_key_template(&key, &template)
)
} else {
"".into()
}
}
}

impl CreateMagicLink {
pub fn update(&mut self, ctx: &Context, _frame: &mut Frame) {
eframe::egui::CentralPanel::default().show(ctx, |ui| {
ui.label("A ✨magic link✨ allows you to share a template and an exam key together as a single link. This is great if you want to use autograder for specific exams and have to potentially re-grade many exams without having to upload the template and key file each time. Best to bookmark!");
ui.horizontal(|ui| {
upload_button(
ui,
&ctx,
"📂 Upload Template",
FileType::Template,
self.data_channel.0.clone(),
);
if self.template.is_some() {
ui.label("🎉");
}
});

ui.horizontal(|ui| {
upload_button(
ui,
&ctx,
"📂 Upload Exam Key",
FileType::Key,
self.data_channel.0.clone(),
);
if self.key.is_some() {
ui.label("👍");
}
});
if self.template.is_some() && self.key.is_some() {
let link = self.to_link();
ui.hyperlink_to("This is your magic link ✨", link.clone());
ScrollArea::vertical().show(ui, |ui| {
ui.add(egui::TextEdit::multiline(&mut link.as_str()));
});
}
});
while let Ok((file_type, data)) = self.data_channel.1.try_recv() {
match file_type {
FileType::Template => {
if let Ok(template) = serde_json::from_slice::<Template>(&data) {
self.template = Some(template);
log::info!("loaded template");
} else {
log::error!("could not parse template");
}
}
FileType::Key => {
if let Ok(key) = serde_json::from_slice::<ExamKey>(&data) {
self.key = Some(key);
log::info!("loaded key");
} else {
log::error!("could not parse template");
}
}
_ => {}
}
}
}
}
4 changes: 2 additions & 2 deletions src/webapp/generate_report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ use zip::ZipWriter;
use wasm_bindgen_futures::spawn_local;

pub struct GenerateReport {
template: Option<Template>,
key: Option<ExamKey>,
pub template: Option<Template>,
pub key: Option<ExamKey>,
raw_container_data: Option<Vec<u8>>,
zipped_results: Rc<RefCell<Option<Vec<u8>>>>,
data_channel: (Sender<(FileType, Vec<u8>)>, Receiver<(FileType, Vec<u8>)>),
Expand Down
1 change: 1 addition & 0 deletions src/webapp/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![cfg(target_arch = "wasm32")]
pub mod create_form;
pub mod create_key;
pub mod create_magic_link;
pub mod create_template;
pub mod generate_report;
pub mod help;
Expand Down
32 changes: 32 additions & 0 deletions src/webapp/utils.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::point::Point;
use crate::template::Box;
use crate::template::ExamKey;
use crate::template::Question;
use crate::template::Template;
use crate::webapp::create_template::{CircleSettings, LayoutSettings, PositionSettings};
Expand Down Expand Up @@ -153,3 +154,34 @@ pub fn question_builder(

result
}

pub fn encode_key_template(k: &ExamKey, t: &Template) -> String {
use base64_url::encode;
use snap::raw::Encoder;
if let Ok(serialized) = serde_cbor::to_vec(&(k, t)) {
let mut encoder = Encoder::new();
if let Ok(compressed) = encoder.compress_vec(&serialized) {
encode(&compressed)
} else {
"".into()
}
} else {
"".into()
}
}

pub fn decode_key_template(
encoded: &str,
) -> Result<(ExamKey, Template), std::boxed::Box<dyn std::error::Error>> {
use base64_url::decode;
use snap::raw::Decoder;
let decoded = decode(encoded)?;
log::info!("decoded");

let mut decoder = Decoder::new();
let decompressed = decoder.decompress_vec(&decoded)?;

let (k, t): (ExamKey, Template) = serde_cbor::from_slice(&decompressed)?;

Ok((k, t))
}
25 changes: 25 additions & 0 deletions src/webapp/webapp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ use eframe::egui::Context;

use crate::webapp::create_form::CreateForm;
use crate::webapp::create_key::CreateKey;
use crate::webapp::create_magic_link::CreateMagicLink;
use crate::webapp::create_template::CreateTemplate;

use crate::webapp::utils::decode_key_template;

use crate::webapp::generate_report::GenerateReport;
use crate::webapp::help::Help;

Expand All @@ -14,6 +17,7 @@ pub struct WebApp {
create_template: CreateTemplate,
create_form: CreateForm,
create_key: CreateKey,
create_magic_link: CreateMagicLink,
help: Help,
}

Expand All @@ -22,6 +26,7 @@ enum ViewType {
CreateTemplate,
CreateForm,
CreateKey,
CreateMagicLink,
Help,
}

Expand All @@ -31,6 +36,7 @@ impl Default for WebApp {
current_view: ViewType::Help,
generate_report: GenerateReport::default(),
create_form: CreateForm::default(),
create_magic_link: CreateMagicLink::default(),
create_template: CreateTemplate::default(),
create_key: CreateKey::default(),
help: Help::default(),
Expand All @@ -40,6 +46,16 @@ impl Default for WebApp {

impl eframe::App for WebApp {
fn update(&mut self, ctx: &Context, frame: &mut eframe::Frame) {
let location = web_sys::window().expect("huge websys error").location();

if !location.hash().unwrap().is_empty() {
let result = decode_key_template(&location.hash().unwrap().as_str()[1..]);
if let Ok((key, template)) = result {
self.current_view = ViewType::GenerateReport;
self.generate_report.template = Some(template);
self.generate_report.key = Some(key);
}
}
// Navigation bar
egui::TopBottomPanel::top("nav_bar").show(ctx, |ui| {
ui.horizontal(|ui| {
Expand All @@ -48,15 +64,23 @@ impl eframe::App for WebApp {
}
if ui.button("Create Form").clicked() {
self.current_view = ViewType::CreateForm;
let _ = location.set_hash("");
}
if ui.button("Create Template").clicked() {
self.current_view = ViewType::CreateTemplate;
let _ = location.set_hash("");
}
if ui.button("Create Key").clicked() {
self.current_view = ViewType::CreateKey;
let _ = location.set_hash("");
}
if ui.button("Create Magic Link").clicked() {
self.current_view = ViewType::CreateMagicLink;
let _ = location.set_hash("");
}
if ui.button("Help").clicked() {
self.current_view = ViewType::Help;
let _ = location.set_hash("");
}
});
});
Expand All @@ -66,6 +90,7 @@ impl eframe::App for WebApp {
ViewType::CreateForm => self.create_form.update(ctx, frame),
ViewType::CreateTemplate => self.create_template.update(ctx, frame),
ViewType::CreateKey => self.create_key.update(ctx, frame),
ViewType::CreateMagicLink => self.create_magic_link.update(ctx, frame),
ViewType::Help => self.help.update(ctx, frame),
}
}
Expand Down

0 comments on commit 9e7dfa8

Please sign in to comment.