set up a typescript pipeline for interpreter

This commit is contained in:
Jonathan Kelley 2024-02-23 18:52:21 -08:00
parent 53cafefdef
commit 7f60010c1e
No known key found for this signature in database
GPG key ID: 1FBB50F7EB0A08BE
11 changed files with 197 additions and 44 deletions

View file

@ -0,0 +1,65 @@
use std::{
fs::read_to_string,
hash::{DefaultHasher, Hash, Hasher},
process::Command,
};
fn main() {
// If any TS changes, re-run the build script
println!("cargo:rerun-if-changed=src/*.ts");
for entry in ["common", "form", "interpreter"].iter() {
gen_bindings(entry);
}
}
// okay...... so tsc might fail if the user doesn't have it installed
// we don't really want to fail if that's the case
// but if you started *editing* the .ts files, you're gonna have a bad time
// so.....
// we need to hash each of the .ts files and add that hash to the JS files
// if the hashes don't match, we need to fail the build
// that way we also don't need
fn gen_bindings(name: &str) {
let contents = read_to_string(&format!("src/{name}.ts")).unwrap();
let generated = read_to_string(&format!("src/gen/{name}.js")).unwrap_or_default();
let hashed = hash_file(&contents);
// If the file is generated, and the hash is the same, we're good, don't do anything
if generated
.lines()
.next()
.unwrap_or_default()
.starts_with(&format!("// DO NOT EDIT THIS FILE. HASH: {}", hashed))
{
return;
}
// If the file is generated, and the hash is different, we need to generate it
let status = Command::new("tsc")
.arg(format!("src/{name}.ts"))
.arg("--outDir")
.arg("gen")
.arg("--target")
.arg("es6")
.status()
.unwrap();
if !status.success() {
panic!(
"Failed to generate bindings for {}. Make sure you have tsc installed",
name
);
}
// The file should exist, and now we need write the TS hash to the file
let generated = read_to_string(&format!("gen/{name}.js")).unwrap();
let generated = format!("// DO NOT EDIT THIS FILE. HASH: {}\n{}", hashed, generated);
std::fs::write(&format!("src/gen/{name}.js"), generated).unwrap();
}
fn hash_file(obj: &str) -> u64 {
let mut hasher = DefaultHasher::new();
obj.hash(&mut hasher);
hasher.finish()
}

View file

@ -0,0 +1,3 @@
temporary generated code directory since tsc doesn't have a crossplatform way of generating typescript to stdout
https://github.com/microsoft/TypeScript/issues/1226#issuecomment-523544134

View file

@ -1,4 +1,5 @@
export function setAttributeInner(node, field, value, ns) {
const name = field;
if (ns === "style") {
// ????? why do we need to do this

View file

View file

@ -0,0 +1,81 @@
// DO NOT EDIT THIS FILE. HASH: 9578489549991746027
export function setAttributeInner(node, field, value, ns) {
const name = field;
if (ns === "style") {
// ????? why do we need to do this
if (node.style === undefined) {
node.style = {};
}
node.style[name] = value;
}
else if (!!ns) {
node.setAttributeNS(ns, name, value);
}
else {
switch (name) {
case "value":
if (value !== node.value) {
node.value = value;
}
break;
case "initial_value":
node.defaultValue = value;
break;
case "checked":
node.checked = truthy(value);
break;
case "initial_checked":
node.defaultChecked = truthy(value);
break;
case "selected":
node.selected = truthy(value);
break;
case "initial_selected":
node.defaultSelected = truthy(value);
break;
case "dangerous_inner_html":
node.innerHTML = value;
break;
default:
// https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364
if (!truthy(value) && bool_attrs.hasOwnProperty(name)) {
node.removeAttribute(name);
}
else {
node.setAttribute(name, value);
}
}
}
}
const bool_attrs = {
allowfullscreen: true,
allowpaymentrequest: true,
async: true,
autofocus: true,
autoplay: true,
checked: true,
controls: true,
default: true,
defer: true,
disabled: true,
formnovalidate: true,
hidden: true,
ismap: true,
itemscope: true,
loop: true,
multiple: true,
muted: true,
nomodule: true,
novalidate: true,
open: true,
playsinline: true,
readonly: true,
required: true,
reversed: true,
selected: true,
truespeed: true,
webkitdirectory: true,
};
function truthy(val) {
return val === "true" || val === true;
}

View file

@ -0,0 +1 @@
// DO NOT EDIT THIS FILE. HASH: 3476900567878811119

View file

@ -1,5 +1,7 @@
class InterpreterConfig {
constructor(intercept_link_redirects) {
intercept_link_redirects: boolean;
constructor(intercept_link_redirects: boolean) {
this.intercept_link_redirects = intercept_link_redirects;
}
}
@ -21,6 +23,42 @@ async function handler(event, name, bubbles, config) {
let contents = await serialize_event(event);
if (
target.tagName === "FORM" &&
(event.type === "submit" || event.type === "input")
) {
const formData = new FormData(target);
for (let name of formData.keys()) {
const fieldType = target.elements[name].type;
switch (fieldType) {
case "select-multiple":
contents.values[name] = formData.getAll(name);
break;
// add cases for fieldTypes that can hold multiple values here
default:
contents.values[name] = formData.get(name);
break;
}
}
}
if (
target.tagName === "SELECT" &&
event.type === "input"
) {
const selectData = target.options;
contents.values["options"] = [];
for (let i = 0; i < selectData.length; i++) {
let option = selectData[i];
if (option.selected) {
contents.values["options"].push(option.value.toString());
}
}
}
// TODO: this should be liveview only
if (
target.tagName === "INPUT" &&
@ -61,42 +99,6 @@ async function handler(event, name, bubbles, config) {
}
}
if (
target.tagName === "FORM" &&
(event.type === "submit" || event.type === "input")
) {
const formData = new FormData(target);
for (let name of formData.keys()) {
const fieldType = target.elements[name].type;
switch (fieldType) {
case "select-multiple":
contents.values[name] = formData.getAll(name);
break;
// add cases for fieldTypes that can hold multiple values here
default:
contents.values[name] = formData.get(name);
break;
}
}
}
if (
target.tagName === "SELECT" &&
event.type === "input"
) {
const selectData = target.options;
contents.values["options"] = [];
for (let i = 0; i < selectData.length; i++) {
let option = selectData[i];
if (option.selected) {
contents.values["options"].push(option.value.toString());
}
}
}
window.ipc.postMessage(
window.interpreter.serializeIpcMessage("user_event", {
name: name,
@ -324,7 +326,7 @@ window.interpreter.setFocus = function (id, focus) {
return true;
}
function get_mouse_data(event) {
function get_mouse_data(event: MouseEvent) {
const {
altKey,
button,
@ -447,7 +449,8 @@ async function serialize_event(event) {
case "dragover":
case "dragstart":
case "drop": {
let files = null;
let files = [];
if (event.dataTransfer && event.dataTransfer.files) {
files = ["a", "b", "c"];
// files = await serializeFileList(event.dataTransfer.files);

View file

@ -2,8 +2,8 @@
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/79236386")]
#![doc(html_favicon_url = "https://avatars.githubusercontent.com/u/79236386")]
pub static INTERPRETER_JS: &str = include_str!("./interpreter.js");
pub static COMMON_JS: &str = include_str!("./common.js");
pub static INTERPRETER_JS: &str = include_str!("./gen/interpreter.js");
pub static COMMON_JS: &str = include_str!("./gen/common.js");
#[cfg(feature = "sledgehammer")]
mod sledgehammer_bindings;
@ -20,7 +20,7 @@ pub use write_native_mutations::*;
#[cfg(all(feature = "minimal_bindings", feature = "webonly"))]
pub mod minimal_bindings {
use wasm_bindgen::{prelude::wasm_bindgen, JsValue};
#[wasm_bindgen(module = "/src/common.js")]
#[wasm_bindgen(module = "/src/gen/common.js")]
extern "C" {
pub fn setAttributeInner(node: JsValue, name: &str, value: JsValue, ns: Option<&str>);
}

View file

@ -36,8 +36,6 @@ pub struct UiEvent {
pub data: PlatformEventData,
}
//fn get_document(elem: &web_sys::Element) ->
impl WebsysDom {
pub fn new(cfg: Config, event_channel: mpsc::UnboundedSender<UiEvent>) -> Self {
let (document, root) = match cfg.root {

View file

@ -441,6 +441,7 @@ impl HasFileData for WebFormData {
fn files(&self) -> Option<std::sync::Arc<dyn FileEngine>> {
#[cfg(not(feature = "file_engine"))]
let files = None;
#[cfg(feature = "file_engine")]
let files = self
.element