Skip to content

Commit

Permalink
Reload on panic
Browse files Browse the repository at this point in the history
  • Loading branch information
Kijewski committed Aug 21, 2024
1 parent 2c31a9d commit 6869cf6
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 48 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ license = "MIT OR Apache-2.0"
[dependencies]
rinja_derive_standalone = { version = "*", path = "rinja/rinja_derive_standalone", features = ["humansize", "num-traits", "serde_json", "urlencode"] }

console_error_panic_hook = "0.1.7"
gloo-utils = "0.2.0"
once_cell = "1.19.0"
prettyplease = "0.2.20"
Expand All @@ -31,6 +32,9 @@ features = [
"Storage",
]

[lints.clippy]
type_complexity = "allow"

[profile.release]
opt-level = "z"
lto = "fat"
Expand Down
33 changes: 28 additions & 5 deletions script.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -85,23 +85,46 @@ window.save_clipboard = function (text) {
});
};

window.toggle_element = function(event, elementId) {
window.toggle_element = function (event, elementId) {
if (event.target && event.target.id === elementId) {
document.getElementById(elementId).classList.toggle("display");
}
};

window.handle_blur = function(event, elementId) {
window.handle_blur = function (event, elementId) {
const parent = document.getElementById(elementId);
if (!parent.contains(document.activeElement) &&
if (
!parent.contains(document.activeElement) &&
!parent.contains(event.relatedTarget)
) {
parent.classList.remove("display");
}
};

window.reset_code = function(event, text) {
const input = event.target.parentElement.parentElement.querySelector("textarea");
window.reset_code = function (event, text) {
const input =
event.target.parentElement.parentElement.querySelector("textarea");
input.value = text;
input.dispatchEvent(new Event("input"));
};

const state = history.state || {};
const reload_counter = +state.reload_counter || 0;
if (reload_counter > 0) {
console.warn("reload_counter: ", reload_counter);
}
state.reload_counter = 0;
history.replaceState(state, "");

window.panic_reload = function () {
state.reload_counter = reload_counter + 1;
history.replaceState(state, "");
window.setTimeout(function () {
if (reload_counter > 2) {
console.warn("Hit a panic. Three times.");
} else {
console.warn("Hit a panic, reloading.");
window.location.reload();
}
}, 0);
};
121 changes: 79 additions & 42 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ use crate::editor::Editor;
use crate::{ThrowAt, ASSETS};

#[derive(Properties, PartialEq, Clone)]
pub struct Props {
struct Props {
theme: Rc<str>,
rust: Rc<str>,
tmpl: Rc<str>,
Expand All @@ -29,50 +29,13 @@ pub struct Props {
timeout: Option<i32>,
}

fn local_storage() -> Option<Storage> {
let window = window()?;
match window.local_storage() {
Ok(storage) => storage,
_ => None,
}
}

const THEME_SOURCE_KEY: &str = "play-rinja-theme";
const STRUCT_SOURCE_KEY: &str = "play-rinja-struct";
const TMPL_SOURCE_KEY: &str = "play-rinja-template";

fn get_data_from_local_storage(storage: &Storage, key: &str) -> Option<String> {
let text = storage.get_item(key).ok()??;
JSON::parse(&text).ok()?.as_string()
}

fn save_to_local_storage(storage: &Storage, key: &str, data: &str) {
if let Ok(data) = JSON::stringify(&JsValue::from_str(data)) {
if let Some(data) = data.as_string() {
// Doesn't matter whether or not it succeeded.
let _ = storage.set_item(key, &data);
}
}
}

#[function_component]
pub fn App() -> Html {
let state = use_state(|| {
let local_storage = local_storage();
let local_storage_or = |default: &str, key: &str| -> Rc<str> {
let value = local_storage
.as_ref()
.and_then(|ls| get_data_from_local_storage(ls, key));
match value.as_deref() {
Some(value) => value.into(),
None => default.into(),
}
};

let theme = local_storage_or(DEFAULT_THEME, THEME_SOURCE_KEY);
let rust = local_storage_or(STRUCT_SOURCE, STRUCT_SOURCE_KEY);
let tmpl = local_storage_or(TMPL_SOURCE, TMPL_SOURCE_KEY);

let (theme, rust, tmpl) = get_last_editor_state().unwrap_or_default();
let theme = theme.unwrap_or_else(|| Rc::from(DEFAULT_THEME));
let rust = rust.unwrap_or_else(|| Rc::from(STRUCT_SOURCE));
let tmpl = tmpl.unwrap_or_else(|| Rc::from(TMPL_SOURCE));
let (code, duration) = convert_source(&rust, &tmpl);
Props {
theme,
Expand Down Expand Up @@ -368,6 +331,80 @@ pub fn App() -> Html {
}
}

const THEME_SOURCE_KEY: &str = "play-rinja-theme";
const STRUCT_SOURCE_KEY: &str = "play-rinja-struct";
const TMPL_SOURCE_KEY: &str = "play-rinja-template";

fn local_storage() -> Option<Storage> {
let window = window()?;
match window.local_storage() {
Ok(storage) => storage,
_ => None,
}
}

fn save_to_local_storage(storage: &Storage, key: &str, data: &str) {
if let Ok(data) = JSON::stringify(&JsValue::from_str(data)) {
if let Some(data) = data.as_string() {
// Doesn't matter whether or not it succeeded.
let _ = storage.set_item(key, &data);
}
}
}

// Read last editor state from local storage.
// Then delete the known editor state.
// Then, if the app did not crash while processing the retrieved state, save it again.
fn get_last_editor_state() -> Option<(Option<Rc<str>>, Option<Rc<str>>, Option<Rc<str>>)> {
let window = window()?;
let storage = window.local_storage().ok().flatten()?;

let mut theme = None;
let mut rust = None;
let mut tmpl = None;
let mut raw_theme = None;
let mut raw_rust = None;
let mut raw_tmpl = None;

for (key, raw_dest, dest) in [
(THEME_SOURCE_KEY, &mut raw_theme, &mut theme),
(STRUCT_SOURCE_KEY, &mut raw_rust, &mut rust),
(TMPL_SOURCE_KEY, &mut raw_tmpl, &mut tmpl),
] {
let Some(raw) = storage.get_item(key).ok().flatten() else {
continue;
};
let _ = storage.remove_item(key);
let Some(parsed) = JSON::parse(&raw).ok().and_then(|s| s.as_string()) else {
continue;
};
*raw_dest = Some(raw);
*dest = Some(Rc::from(parsed));
}
if theme.is_none() && rust.is_none() && theme.is_none() {
return None;
}

let callback = Closure::once(move || {
if crate::PANICKED.load(std::sync::atomic::Ordering::Acquire) {
return;
}

for (key, value) in [
(THEME_SOURCE_KEY, raw_theme.take()),
(STRUCT_SOURCE_KEY, raw_rust.take()),
(TMPL_SOURCE_KEY, raw_tmpl.take()),
] {
if let Some(value) = value {
let _ = storage.set_item(key, &value);
}
}
});
let _ = window.set_timeout_with_callback(callback.into_js_value().unchecked_ref());

Some((theme, rust, tmpl))
}

fn replace_timeout(new_state: &mut Props, state: UseStateHandle<Props>) {
let handler = Closure::<dyn Fn()>::new({
let theme = Rc::clone(&new_state.theme);
Expand Down
24 changes: 23 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,38 @@
mod app;
mod editor;

use std::panic::Location;
use std::panic::{Location, PanicInfo};
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering::{Relaxed, SeqCst};

use once_cell::sync::Lazy;
use syntect::highlighting::Theme;
use syntect::parsing::SyntaxSet;
use syntect_assets::assets::HighlightingAssets;
use wasm_bindgen::prelude::wasm_bindgen;
use web_sys::js_sys::Error;
use web_sys::wasm_bindgen::throw_val;

use crate::app::App;

fn main() {
yew::set_custom_panic_hook({
Box::new(move |info: &PanicInfo| {
if PANICKED
.compare_exchange(false, true, SeqCst, Relaxed)
.is_ok()
{
panic_reload();
}
console_error_panic_hook::hook(info);
})
});

yew::Renderer::<App>::new().render();
}

static PANICKED: AtomicBool = AtomicBool::new(false);

trait ThrowAt<T> {
fn unwrap_at(self) -> T;
}
Expand Down Expand Up @@ -72,3 +89,8 @@ static ASSETS: Lazy<(&SyntaxSet, &[(&str, &Theme)])> = Lazy::new(|| {
let syntax_set = assets.get_syntax_set().unwrap_at();
(syntax_set, themes)
});

#[wasm_bindgen]
extern "C" {
fn panic_reload();
}

0 comments on commit 6869cf6

Please sign in to comment.