bevy_capture_media 0.0.2

Event based screenshots & recording for Bevy Engine
Documentation
use std::path::{Path, PathBuf};
use std::rc::Rc;

use js_sys::{Array, Date, Promise, Uint8Array};
use wasm_bindgen::closure::Closure;
use wasm_bindgen::prelude::wasm_bindgen;
use wasm_bindgen::{JsCast, JsValue};
use wasm_bindgen_futures::JsFuture;
use web_sys::{Blob, BlobPropertyBag, Document, FileReader, Window};

macro_rules! null_return {
	($nullable: expr, $message: literal) => {
		match $nullable {
			Some(value) => value,
			None => {
				log::error!($message);
				return;
			}
		}
	};
	($nullable: expr, $message: literal, $on_error: expr) => {
		match $nullable {
			Some(value) => value,
			None => {
				log::error!($message);
				$on_error.call1(&JsValue::NULL, &JsValue::from_str($message));
				return;
			}
		}
	};
}
macro_rules! err_return {
	($nullable: expr, $message: literal) => {
		match $nullable {
			Ok(value) => value,
			Err(e) => {
				log::error!("{}; {:?}", $message, e);
				return;
			}
		}
	};
	($nullable: expr, $message: literal, $on_error: expr) => {
		match $nullable {
			Ok(value) => value,
			Err(e) => {
				log::error!("{}; {:?}", $message, e);
				$on_error.call1(&JsValue::NULL, &e);
				return;
			}
		}
	};
}

fn read_to_data(blob: &Blob) -> Promise {
	Promise::new(&mut |resolve, reject| {
		let file_reader: FileReader =
			err_return!(FileReader::new(), "Failed to create a file reader", reject);

		let reject = Rc::new(reject);
		let file_reader = Rc::new(file_reader);

		let closure_file_reader = file_reader.clone();
		let closure_reject = reject.clone();
		let mut closure = Closure::once(move || {
			let value = err_return!(
				closure_file_reader.result(),
				"Could not get file reader result",
				closure_reject
			);
			resolve.call1(&JsValue::NULL, &value);
		});

		file_reader.set_onload(Some(closure.as_ref().unchecked_ref()));
		closure.forget();
		err_return!(
			file_reader.read_as_data_url(blob),
			"Failed to read the image data as a data url",
			reject
		);
	})
}

pub fn focus_on_first_of_type(element_type: &str) {
	let window: Window = null_return!(
		web_sys::window(),
		"Didn't find a window to attach to while saving screenshot"
	);
	let document: Document = null_return!(
		window.document(),
		"Window did not contain a document to attach to while saving screenshot"
	);
	let element = null_return!(
		document.get_elements_by_tag_name(element_type).item(0),
		"Could not find element to focus on"
	);

	err_return!(
		element.dyn_into::<web_sys::HtmlElement>(),
		"Failed to attach blob url"
	)
	.focus();
}

async fn download_bytes_inner(file_name: PathBuf, bytes: Vec<u8>) {
	let bytes = bytes.as_slice();
	let js_byte_array = Uint8Array::from(bytes);
	let js_array = Array::new();
	js_array.push(&js_byte_array.buffer());

	let blob = err_return!(
		Blob::new_with_u8_array_sequence_and_options(
			&js_array,
			BlobPropertyBag::new().type_("image/png"),
		),
		"Failed to create screenshot blob data"
	);

	let obj_url = err_return!(
		JsFuture::from(read_to_data(&blob)).await,
		"Failed to create data url for screenshot"
	);
	let obj_url_string = null_return!(
		obj_url.as_string(),
		"Couldn't convert the data url from a JsValue to a String"
	);
	let window: Window = null_return!(
		web_sys::window(),
		"Didn't find a window to attach to while saving screenshot"
	);
	let document: Document = null_return!(
		window.document(),
		"Window did not contain a document to attach to while saving screenshot"
	);
	let link = err_return!(
		document.create_element("a"),
		"Could not create download link handler"
	);
	err_return!(
		link.set_attribute("href", obj_url_string.as_str()),
		"Failed to attach blob url"
	);
	err_return!(
		link.set_attribute("download", format!("{}", file_name.display()).as_str()),
		"Failed to attach file name"
	);
	let html_link = err_return!(
		link.dyn_into::<web_sys::HtmlElement>(),
		"Could not get interactable version of download link"
	);

	html_link.click();

	log::info!("Saving image to path {}", file_name.display());
}

pub fn download_bytes(file_name: PathBuf, bytes: Vec<u8>) {
	wasm_bindgen_futures::spawn_local(download_bytes_inner(file_name, bytes));
}

pub fn get_now() -> f64 {
	Date::now()
}