feat: add pdf gen capability
This commit is contained in:
1 parent
3cc67cb9e6
commit
8282ba4302
5 files changed
+370
-16
No files matched your search
@@ -35,6 +35,9 @@ RUN apt-get update -y \
|
|||||||
&& apt-get clean -y \
|
&& apt-get clean -y \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash
|
||||||
|
RUN cargo binstall typst-cli -y
|
||||||
|
|
||||||
# Remember to replace project_name with the actual project name
|
# Remember to replace project_name with the actual project name
|
||||||
COPY --from=builder /app/target/release/scholarships-rs /app/
|
COPY --from=builder /app/target/release/scholarships-rs /app/
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
#set page(margin: 40pt)
|
||||||
|
#set block(breakable: false)
|
||||||
|
|
||||||
|
#let map_block(header, val) = {
|
||||||
|
block(
|
||||||
|
width: 100%,
|
||||||
|
fill: rgb(200, 0, 0),
|
||||||
|
radius: 4pt,
|
||||||
|
below: 0pt,
|
||||||
|
above: 4pt,
|
||||||
|
inset: 2pt,
|
||||||
|
[
|
||||||
|
#show heading: set text(white)
|
||||||
|
#show heading: set text(size: 15pt)
|
||||||
|
#stack(dir: ttb,
|
||||||
|
block(
|
||||||
|
inset: 7pt,
|
||||||
|
align(horizon)[= #header]
|
||||||
|
),
|
||||||
|
align(center)[#block(
|
||||||
|
width: 100%,
|
||||||
|
fill: white,
|
||||||
|
inset: 8pt,
|
||||||
|
radius: 3pt,
|
||||||
|
align(left)[#val]
|
||||||
|
)],
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#let info_line(subject, key, dict: student) = [
|
||||||
|
|
||||||
|
*#subject* #dict.at(key, default: "N/A")
|
||||||
|
]
|
||||||
|
|
||||||
|
#let info_line_list(subject, key, dict: student) = [
|
||||||
|
|
||||||
|
*#subject* #dict.at(key, default: "N/A").join(", ")
|
||||||
|
]
|
||||||
|
|
||||||
|
#show heading: set align(center)
|
||||||
|
#show heading: set text(size: 20pt)
|
||||||
|
#v(10cm)
|
||||||
|
= #student.first_name #student.last_name
|
||||||
|
#show heading: set text(size: 17pt)
|
||||||
|
== Region 15 General Scholarship Application
|
||||||
|
#pagebreak()
|
||||||
|
|
||||||
|
#show heading: set block(below: 20pt, above: 30pt)
|
||||||
|
#show heading: set text(size: 20pt)
|
||||||
|
= Student Demographic Information
|
||||||
|
|
||||||
|
#info_line("First Name:", "first_name")
|
||||||
|
#info_line("Last Name:", "last_name")
|
||||||
|
#info_line("Date of Birth:", "dob")
|
||||||
|
#info_line("Gender:", "gender")
|
||||||
|
#info_line("Phone Number:", "phone_number")
|
||||||
|
#info_line("Email:","email")
|
||||||
|
#info_line("Street Address:", "street_address")
|
||||||
|
#info_line("Town:", "town")
|
||||||
|
|
||||||
|
= Academic Information
|
||||||
|
|
||||||
|
#info_line("Unweighted GPA:", "unweighted_gpa")
|
||||||
|
#info_line("Weighted GPA:", "weighted_gpa")
|
||||||
|
#info_line("Highest SAT Score:", "sat_score")
|
||||||
|
#info_line("Highest ACT Score:", "act_score")
|
||||||
|
#info_line("Academic Honors:", "academic_honors")
|
||||||
|
|
||||||
|
= Sports Participation
|
||||||
|
#if student.at("sports_participation", default: ()).len() == 0 [
|
||||||
|
Student did not enter any sports participation.
|
||||||
|
] else {
|
||||||
|
for pair in student.sports_participation {
|
||||||
|
map_block(pair.at("sport_name"), [
|
||||||
|
#info_line("Special Achievements:", "achievements", dict: pair)
|
||||||
|
#info_line_list("Grades Participated:", "grades", dict: pair)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
= Extracurricular Activities
|
||||||
|
#if student.at("extracurricular", default: ()).len() == 0 [
|
||||||
|
Student did not enter any extracurricular activities.
|
||||||
|
] else {
|
||||||
|
for pair in student.extracurricular {
|
||||||
|
map_block(pair.at("activity_name"), [
|
||||||
|
#info_line("Number of hours per week:", "num_hours", dict: pair)
|
||||||
|
#info_line("Number of weeks participated:", "num_weeks", dict: pair)
|
||||||
|
#info_line("Special Involvement:", "special_involvement", dict: pair)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
= Work Experience
|
||||||
|
#if student.at("work_experience", default: ()).len() == 0 [
|
||||||
|
Student did not enter any work experience.
|
||||||
|
] else {
|
||||||
|
for pair in student.work_experience {
|
||||||
|
map_block(pair.at("employer"), [
|
||||||
|
#info_line("Job Title:", "job_title", dict: pair)
|
||||||
|
#info_line("Start Date:", "start_date", dict: pair)
|
||||||
|
#info_line("End Date:", "end_date", dict: pair)
|
||||||
|
#info_line("Number of hours per week:", "num_hours", dict: pair)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
= University Information
|
||||||
|
|
||||||
|
#info_line("University Name:", "college_name")
|
||||||
|
#info_line("University Street Address:", "college_city")
|
||||||
|
#info_line("University State:", "college_state")
|
||||||
|
#info_line("University ZIP:", "college_zip")
|
||||||
|
#info_line("Has the student received an acceptance:", "college_acceptance")
|
||||||
|
#info_line("Student's chosen major:", "major")
|
||||||
|
#info_line("Student's intended career:", "intended_career")
|
||||||
|
|
||||||
|
= Family Information
|
||||||
|
|
||||||
|
#info_line("Total number of children in student's family:", "num_children")
|
||||||
|
#info_line("Total number of children currently attending college:", "num_children_college")
|
||||||
|
#info_line("Parent/Guardian 1 Name:", "parent_one_name")
|
||||||
|
#info_line("Parent/Guardian 1 Relationship:", "parent_one_relationship")
|
||||||
|
#info_line("Parent/Guardian 1 Occupation:", "parent_one_occupation")
|
||||||
|
#info_line("Parent/Guardian 1 Employer:", "parent_one_employer")
|
||||||
|
#info_line("Parent/Guardian 2 Name:", "parent_two_name")
|
||||||
|
#info_line("Parent/Guardian 2 Relationship:", "parent_two_relationship")
|
||||||
|
#info_line("Parent/Guardian 2 Occupation:", "parent_two_occupation")
|
||||||
|
#info_line("Parent/Guardian 2 Employer:", "parent_two_employer")
|
||||||
|
|
||||||
|
= Extra Eligibility Information
|
||||||
|
|
||||||
|
#info_line("Did the student attend BAS:", "attend_bas")
|
||||||
|
#info_line("Is the student a member of Midd-South Catholic Church:", "middsouth_church")
|
||||||
|
#info_line("Does the student have a family member who has served in the US Military:", "family_military_service")
|
||||||
|
#info_line("Has the student participated in Pomperaug Youth Baseball:", "youth_baseball")
|
||||||
|
#info_line("Has the student participated in the Panthers Aquatic Club:", "aquatic_club")
|
||||||
|
#info_line("Does the student have a family member in the Region 15 PEA:", "pea_member")
|
||||||
|
#info_line("Has the student participated in the PHS Music Program:", "music_program")
|
||||||
@@ -7,6 +7,7 @@ use leptos::prelude::*;
|
|||||||
use leptos::task::spawn_local;
|
use leptos::task::spawn_local;
|
||||||
use leptos::wasm_bindgen::JsCast;
|
use leptos::wasm_bindgen::JsCast;
|
||||||
use leptos::web_sys::{Blob, HtmlAnchorElement, Url, js_sys};
|
use leptos::web_sys::{Blob, HtmlAnchorElement, Url, js_sys};
|
||||||
|
use crate::pages::api::students::{get_student_info_json, get_student_pdf};
|
||||||
|
|
||||||
fn get_important_dates() -> Vec<DateInfo> {
|
fn get_important_dates() -> Vec<DateInfo> {
|
||||||
vec![
|
vec![
|
||||||
@@ -100,6 +101,36 @@ pub fn AdminUtilsPage() -> impl IntoView {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let on_click_json = move |_| {
|
||||||
|
spawn_local(async move {
|
||||||
|
let (file_name, file_bytes) = get_student_pdf("54d82438-c051-709d-30f7-c60e97f5112a".to_string())
|
||||||
|
.await
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let uint_arr = js_sys::Uint8Array::new_with_length(file_bytes.len() as u32);
|
||||||
|
uint_arr.copy_from(&file_bytes);
|
||||||
|
|
||||||
|
let blob_parts = js_sys::Array::new();
|
||||||
|
blob_parts.push(&uint_arr);
|
||||||
|
|
||||||
|
let blob = Blob::new_with_u8_array_sequence(&blob_parts).unwrap();
|
||||||
|
|
||||||
|
let blob_url = Url::create_object_url_with_blob(&blob).unwrap();
|
||||||
|
|
||||||
|
let window = window();
|
||||||
|
let document = window.document().unwrap();
|
||||||
|
let a: HtmlAnchorElement = document.create_element("a").unwrap().dyn_into().unwrap();
|
||||||
|
|
||||||
|
a.set_href(&blob_url);
|
||||||
|
a.set_download(&file_name);
|
||||||
|
document.body().unwrap().append_child(&a).unwrap();
|
||||||
|
a.click();
|
||||||
|
|
||||||
|
document.body().unwrap().remove_child(&a).unwrap();
|
||||||
|
Url::revoke_object_url(&blob_url).unwrap();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<div class="mx-auto px-6">
|
<div class="mx-auto px-6">
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
@@ -109,6 +140,7 @@ pub fn AdminUtilsPage() -> impl IntoView {
|
|||||||
<ActionButton on:click=on_click_comparisons>"Create Comparisons"</ActionButton>
|
<ActionButton on:click=on_click_comparisons>"Create Comparisons"</ActionButton>
|
||||||
<ActionButton on:click=on_click_dates>"Create Dates"</ActionButton>
|
<ActionButton on:click=on_click_dates>"Create Dates"</ActionButton>
|
||||||
<ActionButton on:click=on_click_export>"Scholarship Export"</ActionButton>
|
<ActionButton on:click=on_click_export>"Scholarship Export"</ActionButton>
|
||||||
|
<ActionButton on:click=on_click_json>"Get student JSON"</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}.into_any()
|
}.into_any()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use leptos::prelude::*;
|
|||||||
use leptos::server_fn::codec::Json;
|
use leptos::server_fn::codec::Json;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
use std::process::Stdio;
|
||||||
|
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
mod imports {
|
mod imports {
|
||||||
@@ -14,8 +15,12 @@ mod imports {
|
|||||||
pub use leptos::logging::{debug_log, error};
|
pub use leptos::logging::{debug_log, error};
|
||||||
pub use std::collections::HashMap;
|
pub use std::collections::HashMap;
|
||||||
pub use zip::write::SimpleFileOptions;
|
pub use zip::write::SimpleFileOptions;
|
||||||
|
pub use leptos::serde_json;
|
||||||
|
pub use std::process::Command;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static PDF_TEMPLATE: &str = include_str!("../../../pdf_template.typ");
|
||||||
|
|
||||||
#[server(input = Json)]
|
#[server(input = Json)]
|
||||||
pub async fn put_student_data(
|
pub async fn put_student_data(
|
||||||
subject: String,
|
subject: String,
|
||||||
@@ -512,3 +517,152 @@ pub async fn provider_get_all_input_files(
|
|||||||
|
|
||||||
get_all_input_files(form_name, input_name).await
|
get_all_input_files(form_name, input_name).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[server]
|
||||||
|
pub async fn get_student_pdf(
|
||||||
|
student_id: String,
|
||||||
|
) -> Result<(String, Vec<u8>), ServerFnError> {
|
||||||
|
use imports::*;
|
||||||
|
|
||||||
|
// NOTE: this API requires that the server has the typst-cli available ON PATH.
|
||||||
|
|
||||||
|
let student_info_str = get_student_info_json(student_id.clone()).await?;
|
||||||
|
let student_info = get_student_data(student_id, "DEMOGRAPHICS".to_string()).await?;
|
||||||
|
|
||||||
|
let first_name = student_info.get("first_name")
|
||||||
|
.map(|v| v.to_string())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let last_name = student_info.get("last_name")
|
||||||
|
.map(|v| v.to_string())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
let typst_string = student_info_str
|
||||||
|
.replace("{", "(")
|
||||||
|
.replace("}", ")")
|
||||||
|
.replace("[", "(")
|
||||||
|
.replace("]", ")")
|
||||||
|
// Ensures trailing commas for lists of dictionaries
|
||||||
|
.replace("))", "),)");
|
||||||
|
|
||||||
|
debug_log!("New string: {}", typst_string);
|
||||||
|
|
||||||
|
let pdf_string = format!("#let student = {typst_string}\n{PDF_TEMPLATE}");
|
||||||
|
debug_log!("PDF string: {}", pdf_string);
|
||||||
|
let mut child = Command::new("typst")
|
||||||
|
.arg("compile")
|
||||||
|
.arg("-")
|
||||||
|
.arg("-")
|
||||||
|
.stdin(Stdio::piped())
|
||||||
|
.stdout(Stdio::piped())
|
||||||
|
.spawn()?;
|
||||||
|
|
||||||
|
{
|
||||||
|
let stdin = child.stdin.as_mut().unwrap();
|
||||||
|
stdin.write_all(pdf_string.as_bytes())?;
|
||||||
|
stdin.flush()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = child.wait_with_output()?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
let msg = String::from_utf8_lossy(&output.stderr).to_string();
|
||||||
|
error!("Failed to generate PDF: {}", msg);
|
||||||
|
Err(ServerFnError::new(format!("Failed to generate PDF: {msg}")))
|
||||||
|
} else {
|
||||||
|
let bytes = output.stdout;
|
||||||
|
let file_name = format!("{first_name}{last_name}_Application.pdf");
|
||||||
|
Ok((file_name, bytes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[server]
|
||||||
|
pub async fn get_student_info_json(
|
||||||
|
student_id: String,
|
||||||
|
) -> Result<String, ServerFnError> {
|
||||||
|
use imports::*;
|
||||||
|
|
||||||
|
let client = create_dynamo_client().await;
|
||||||
|
|
||||||
|
let response = client
|
||||||
|
.query()
|
||||||
|
.table_name(MAIN_TABLE_NAME)
|
||||||
|
.expression_attribute_values(":hk", AttributeValue::S(format!("STUDENT#{student_id}")))
|
||||||
|
.key_condition_expression("HK = :hk")
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
let msg = e.message().unwrap_or("Unknown error occurred");
|
||||||
|
error!("{}", msg);
|
||||||
|
ServerFnError::new(msg)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let work_exp = client
|
||||||
|
.query()
|
||||||
|
.table_name(MAIN_TABLE_NAME)
|
||||||
|
.expression_attribute_values(":hk", AttributeValue::S(format!("STUDENT#{student_id}")))
|
||||||
|
.expression_attribute_values(":sk", AttributeValue::S("WORKEXP".to_string()))
|
||||||
|
.key_condition_expression("HK = :hk AND SK = :sk")
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
let msg = e.message().unwrap_or("Unknown error occurred");
|
||||||
|
error!("{}", msg);
|
||||||
|
ServerFnError::new(msg)
|
||||||
|
})
|
||||||
|
.map(|output| {
|
||||||
|
let items = output.items.unwrap_or_default();
|
||||||
|
debug_log!("Number of items: {}", items.len());
|
||||||
|
items.into_iter()
|
||||||
|
.next()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.get("extracurricular")
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or(AttributeValue::S("".to_string()))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let extracurricular = client
|
||||||
|
.query()
|
||||||
|
.table_name(MAIN_TABLE_NAME)
|
||||||
|
.expression_attribute_values(":hk", AttributeValue::S(format!("STUDENT#{student_id}")))
|
||||||
|
.expression_attribute_values(":sk", AttributeValue::S("DEMOGRAPHICS".to_string()))
|
||||||
|
.key_condition_expression("HK = :hk AND SK = :sk")
|
||||||
|
.send()
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
let msg = e.message().unwrap_or("Unknown error occurred");
|
||||||
|
error!("{}", msg);
|
||||||
|
ServerFnError::new(msg)
|
||||||
|
})
|
||||||
|
.map(|output| {
|
||||||
|
let items = output.items.unwrap_or_default();
|
||||||
|
debug_log!("Number of items: {}", items.len());
|
||||||
|
items.into_iter()
|
||||||
|
.next()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.get("extracurricular")
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or(AttributeValue::S("".to_string()))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let Some(items) = response.items else {
|
||||||
|
return Err(ServerFnError::new("Failed to find student information."));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Join items together into a single object.
|
||||||
|
let mut data = items.into_iter()
|
||||||
|
.fold(HashMap::new(), |mut map, item| {
|
||||||
|
item.into_iter()
|
||||||
|
.for_each(|(k, v)| { map.insert(k, v); });
|
||||||
|
map
|
||||||
|
});
|
||||||
|
|
||||||
|
data.insert("work_experience".to_string(), work_exp);
|
||||||
|
data.insert("extracurricular".to_string(), extracurricular);
|
||||||
|
|
||||||
|
let json_value: serde_json::Value = serde_dynamo::from_item(data)?;
|
||||||
|
let json_string = serde_json::to_string(&json_value)?;
|
||||||
|
println!("{}", json_string);
|
||||||
|
|
||||||
|
Ok(json_string)
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
use crate::components::{ActionButton, Banner, DataDisplay, Loading};
|
use crate::components::{ActionButton, Banner, DataDisplay, Loading};
|
||||||
use crate::pages::UnauthenticatedPage;
|
use crate::pages::UnauthenticatedPage;
|
||||||
use crate::pages::api::get_comparison_info;
|
use crate::pages::api::get_comparison_info;
|
||||||
use crate::pages::api::students::{GetStudentFiles, provider_get_all_input_files, get_student_data};
|
use crate::pages::api::students::{GetStudentFiles, provider_get_all_input_files, get_student_data, GetStudentPdf};
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use leptos::ev::{Event, MouseEvent};
|
use leptos::ev::{Event, MouseEvent};
|
||||||
use leptos::html::Dialog;
|
use leptos::html::Dialog;
|
||||||
@@ -1120,7 +1120,10 @@ pub fn StudentInformationDialog(
|
|||||||
// scholarship, meaning their essays and/or FAFSA by request.
|
// scholarship, meaning their essays and/or FAFSA by request.
|
||||||
|
|
||||||
let get_student_files = ServerAction::<GetStudentFiles>::new();
|
let get_student_files = ServerAction::<GetStudentFiles>::new();
|
||||||
let get_buttons_disabled = get_student_files.pending();
|
let get_student_pdf = ServerAction::<GetStudentPdf>::new();
|
||||||
|
let get_buttons_disabled = Memo::new(move |_| {
|
||||||
|
get_student_files.pending().get() && get_student_pdf.pending().get()
|
||||||
|
});
|
||||||
let on_click_fafsa = move |_| {
|
let on_click_fafsa = move |_| {
|
||||||
get_student_files.dispatch(GetStudentFiles {
|
get_student_files.dispatch(GetStudentFiles {
|
||||||
access_token: access_token.get().unwrap_or_default(),
|
access_token: access_token.get().unwrap_or_default(),
|
||||||
@@ -1142,23 +1145,41 @@ pub fn StudentInformationDialog(
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let on_click_pdf = move |_| {
|
||||||
|
debug_log!("Getting student PDF");
|
||||||
|
get_student_pdf.dispatch(GetStudentPdf {
|
||||||
|
student_id: student_id.get().unwrap_or_default(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
fn open_file(file_name: String, file: Vec<u8>) {
|
||||||
|
let base64 = base64::engine::general_purpose::STANDARD.encode(file);
|
||||||
|
let data_url = format!("data:application/zip;base64,{}", base64);
|
||||||
|
// Create a base64 link and open it.
|
||||||
|
let a: HtmlAnchorElement = document()
|
||||||
|
.create_element("a")
|
||||||
|
.expect("Failed to create anchor element")
|
||||||
|
.dyn_into()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
a.set_download(&*file_name);
|
||||||
|
a.set_href(&data_url);
|
||||||
|
document().body().unwrap().append_child(&a).unwrap();
|
||||||
|
a.click();
|
||||||
|
a.remove();
|
||||||
|
}
|
||||||
|
|
||||||
Effect::new(move || {
|
Effect::new(move || {
|
||||||
if let Some(Ok((file_name, file))) = get_student_files.value().get() {
|
if let Some(Ok((file_name, file))) = get_student_files.value().get() {
|
||||||
let base64 = base64::engine::general_purpose::STANDARD.encode(file);
|
open_file(file_name, file);
|
||||||
let data_url = format!("data:application/zip;base64,{}", base64);
|
get_student_files.clear();
|
||||||
// Create a base64 link and open it.
|
}
|
||||||
let a: HtmlAnchorElement = document()
|
});
|
||||||
.create_element("a")
|
|
||||||
.expect("Failed to create anchor element")
|
|
||||||
.dyn_into()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
a.set_download(&*file_name);
|
|
||||||
a.set_href(&data_url);
|
|
||||||
document().body().unwrap().append_child(&a).unwrap();
|
|
||||||
a.click();
|
|
||||||
a.remove();
|
|
||||||
|
|
||||||
|
// Copy and paste is bad, but it's a little short on time.
|
||||||
|
Effect::new(move || {
|
||||||
|
if let Some(Ok((file_name, file))) = get_student_pdf.value().get() {
|
||||||
|
open_file(file_name, file);
|
||||||
get_student_files.clear();
|
get_student_files.clear();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1192,6 +1213,9 @@ pub fn StudentInformationDialog(
|
|||||||
"Download Essay File(s)"
|
"Download Essay File(s)"
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</Show>
|
</Show>
|
||||||
|
<ActionButton on:click=on_click_pdf disabled=get_buttons_disabled>
|
||||||
|
"Download Student Application"
|
||||||
|
</ActionButton>
|
||||||
</div>
|
</div>
|
||||||
{move || Suspend::new(async move {
|
{move || Suspend::new(async move {
|
||||||
get_student_data(
|
get_student_data(
|
||||||
|
|||||||
Reference in new issue
Block a user