|
@@ -1,390 +1,9 @@
|
|
|
-import { setAttributeInner } from "./common.js";
|
|
|
-
|
|
|
-class ListenerMap {
|
|
|
- constructor(root) {
|
|
|
- // bubbling events can listen at the root element
|
|
|
- this.global = {};
|
|
|
- // non bubbling events listen at the element the listener was created at
|
|
|
- this.local = {};
|
|
|
- this.root = root;
|
|
|
- }
|
|
|
-
|
|
|
- create(event_name, element, handler, bubbles) {
|
|
|
- if (bubbles) {
|
|
|
- if (this.global[event_name] === undefined) {
|
|
|
- this.global[event_name] = {};
|
|
|
- this.global[event_name].active = 1;
|
|
|
- this.global[event_name].callback = handler;
|
|
|
- this.root.addEventListener(event_name, handler);
|
|
|
- } else {
|
|
|
- this.global[event_name].active++;
|
|
|
- }
|
|
|
- } else {
|
|
|
- const id = element.getAttribute("data-dioxus-id");
|
|
|
- if (!this.local[id]) {
|
|
|
- this.local[id] = {};
|
|
|
- }
|
|
|
- this.local[id][event_name] = handler;
|
|
|
- element.addEventListener(event_name, handler);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- remove(element, event_name, bubbles) {
|
|
|
- if (bubbles) {
|
|
|
- this.global[event_name].active--;
|
|
|
- if (this.global[event_name].active === 0) {
|
|
|
- this.root.removeEventListener(
|
|
|
- event_name,
|
|
|
- this.global[event_name].callback
|
|
|
- );
|
|
|
- delete this.global[event_name];
|
|
|
- }
|
|
|
- } else {
|
|
|
- const id = element.getAttribute("data-dioxus-id");
|
|
|
- delete this.local[id][event_name];
|
|
|
- if (this.local[id].length === 0) {
|
|
|
- delete this.local[id];
|
|
|
- }
|
|
|
- element.removeEventListener(event_name, handler);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- removeAllNonBubbling(element) {
|
|
|
- const id = element.getAttribute("data-dioxus-id");
|
|
|
- delete this.local[id];
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
class InterpreterConfig {
|
|
|
constructor(intercept_link_redirects) {
|
|
|
this.intercept_link_redirects = intercept_link_redirects;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-class Interpreter {
|
|
|
- constructor(root, config) {
|
|
|
- this.config = config;
|
|
|
- this.root = root;
|
|
|
- this.listeners = new ListenerMap(root);
|
|
|
- this.nodes = [root];
|
|
|
- this.stack = [root];
|
|
|
- this.handlers = {};
|
|
|
- this.templates = {};
|
|
|
- this.lastNodeWasText = false;
|
|
|
- }
|
|
|
- top() {
|
|
|
- return this.stack[this.stack.length - 1];
|
|
|
- }
|
|
|
- pop() {
|
|
|
- return this.stack.pop();
|
|
|
- }
|
|
|
- MountToRoot() {
|
|
|
- this.AppendChildren(this.stack.length - 1);
|
|
|
- }
|
|
|
- SetNode(id, node) {
|
|
|
- this.nodes[id] = node;
|
|
|
- }
|
|
|
- PushRoot(root) {
|
|
|
- const node = this.nodes[root];
|
|
|
- this.stack.push(node);
|
|
|
- }
|
|
|
- PopRoot() {
|
|
|
- this.stack.pop();
|
|
|
- }
|
|
|
- AppendChildren(many) {
|
|
|
- // let root = this.nodes[id];
|
|
|
- let root = this.stack[this.stack.length - 1 - many];
|
|
|
- let to_add = this.stack.splice(this.stack.length - many);
|
|
|
- for (let i = 0; i < many; i++) {
|
|
|
- root.appendChild(to_add[i]);
|
|
|
- }
|
|
|
- }
|
|
|
- ReplaceWith(root_id, m) {
|
|
|
- let root = this.nodes[root_id];
|
|
|
- let els = this.stack.splice(this.stack.length - m);
|
|
|
- if (is_element_node(root.nodeType)) {
|
|
|
- this.listeners.removeAllNonBubbling(root);
|
|
|
- }
|
|
|
- root.replaceWith(...els);
|
|
|
- }
|
|
|
- InsertAfter(root, n) {
|
|
|
- let old = this.nodes[root];
|
|
|
- let new_nodes = this.stack.splice(this.stack.length - n);
|
|
|
- old.after(...new_nodes);
|
|
|
- }
|
|
|
- InsertBefore(root, n) {
|
|
|
- let old = this.nodes[root];
|
|
|
- let new_nodes = this.stack.splice(this.stack.length - n);
|
|
|
- old.before(...new_nodes);
|
|
|
- }
|
|
|
- Remove(root) {
|
|
|
- let node = this.nodes[root];
|
|
|
- if (node !== undefined) {
|
|
|
- if (is_element_node(node)) {
|
|
|
- this.listeners.removeAllNonBubbling(node);
|
|
|
- }
|
|
|
- node.remove();
|
|
|
- }
|
|
|
- }
|
|
|
- CreateTextNode(text, root) {
|
|
|
- const node = document.createTextNode(text);
|
|
|
- this.nodes[root] = node;
|
|
|
- this.stack.push(node);
|
|
|
- }
|
|
|
- CreatePlaceholder(root) {
|
|
|
- let el = document.createElement("pre");
|
|
|
- el.hidden = true;
|
|
|
- this.stack.push(el);
|
|
|
- this.nodes[root] = el;
|
|
|
- }
|
|
|
- NewEventListener(event_name, root, bubbles, handler) {
|
|
|
- const element = this.nodes[root];
|
|
|
- element.setAttribute("data-dioxus-id", `${root}`);
|
|
|
- this.listeners.create(event_name, element, handler, bubbles);
|
|
|
- }
|
|
|
- RemoveEventListener(root, event_name, bubbles) {
|
|
|
- const element = this.nodes[root];
|
|
|
- element.removeAttribute(`data-dioxus-id`);
|
|
|
- this.listeners.remove(element, event_name, bubbles);
|
|
|
- }
|
|
|
- SetText(root, text) {
|
|
|
- this.nodes[root].textContent = text;
|
|
|
- }
|
|
|
- SetAttribute(id, field, value, ns) {
|
|
|
- if (value === null) {
|
|
|
- this.RemoveAttribute(id, field, ns);
|
|
|
- } else {
|
|
|
- const node = this.nodes[id];
|
|
|
- setAttributeInner(node, field, value, ns);
|
|
|
- }
|
|
|
- }
|
|
|
- RemoveAttribute(root, field, ns) {
|
|
|
- const node = this.nodes[root];
|
|
|
- if (!ns) {
|
|
|
- switch (field) {
|
|
|
- case "value":
|
|
|
- node.value = "";
|
|
|
- break;
|
|
|
- case "checked":
|
|
|
- node.checked = false;
|
|
|
- break;
|
|
|
- case "selected":
|
|
|
- node.selected = false;
|
|
|
- break;
|
|
|
- case "dangerous_inner_html":
|
|
|
- node.innerHTML = "";
|
|
|
- break;
|
|
|
- default:
|
|
|
- node.removeAttribute(field);
|
|
|
- break;
|
|
|
- }
|
|
|
- } else if (ns == "style") {
|
|
|
- node.style.removeProperty(name);
|
|
|
- } else {
|
|
|
- node.removeAttributeNS(ns, field);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- GetClientRect(id) {
|
|
|
- const node = this.nodes[id];
|
|
|
- if (!node) {
|
|
|
- return;
|
|
|
- }
|
|
|
- const rect = node.getBoundingClientRect();
|
|
|
- return {
|
|
|
- type: "GetClientRect",
|
|
|
- origin: [rect.x, rect.y],
|
|
|
- size: [rect.width, rect.height],
|
|
|
- };
|
|
|
- }
|
|
|
-
|
|
|
- ScrollTo(id, behavior) {
|
|
|
- const node = this.nodes[id];
|
|
|
- if (!node) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- node.scrollIntoView({
|
|
|
- behavior: behavior,
|
|
|
- });
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- /// Set the focus on the element
|
|
|
- SetFocus(id, focus) {
|
|
|
- const node = this.nodes[id];
|
|
|
- if (!node) {
|
|
|
- return false;
|
|
|
- }
|
|
|
- if (focus) {
|
|
|
- node.focus();
|
|
|
- } else {
|
|
|
- node.blur();
|
|
|
- }
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- handleEdits(edits) {
|
|
|
- for (let template of edits.templates) {
|
|
|
- this.SaveTemplate(template);
|
|
|
- }
|
|
|
-
|
|
|
- for (let edit of edits.edits) {
|
|
|
- this.handleEdit(edit);
|
|
|
- }
|
|
|
-
|
|
|
- /*POST_HANDLE_EDITS*/
|
|
|
- }
|
|
|
-
|
|
|
- SaveTemplate(template) {
|
|
|
- let roots = [];
|
|
|
- for (let root of template.roots) {
|
|
|
- roots.push(this.MakeTemplateNode(root));
|
|
|
- }
|
|
|
- this.templates[template.name] = roots;
|
|
|
- }
|
|
|
-
|
|
|
- MakeTemplateNode(node) {
|
|
|
- switch (node.type) {
|
|
|
- case "Text":
|
|
|
- return document.createTextNode(node.text);
|
|
|
- case "Dynamic":
|
|
|
- let dyn = document.createElement("pre");
|
|
|
- dyn.hidden = true;
|
|
|
- return dyn;
|
|
|
- case "DynamicText":
|
|
|
- return document.createTextNode("placeholder");
|
|
|
- case "Element":
|
|
|
- let el;
|
|
|
-
|
|
|
- if (node.namespace != null) {
|
|
|
- el = document.createElementNS(node.namespace, node.tag);
|
|
|
- } else {
|
|
|
- el = document.createElement(node.tag);
|
|
|
- }
|
|
|
-
|
|
|
- for (let attr of node.attrs) {
|
|
|
- if (attr.type == "Static") {
|
|
|
- setAttributeInner(el, attr.name, attr.value, attr.namespace);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- for (let child of node.children) {
|
|
|
- el.appendChild(this.MakeTemplateNode(child));
|
|
|
- }
|
|
|
-
|
|
|
- return el;
|
|
|
- }
|
|
|
- }
|
|
|
- AssignId(path, id) {
|
|
|
- this.nodes[id] = this.LoadChild(path);
|
|
|
- }
|
|
|
- LoadChild(path) {
|
|
|
- // iterate through each number and get that child
|
|
|
- let node = this.stack[this.stack.length - 1];
|
|
|
-
|
|
|
- for (let i = 0; i < path.length; i++) {
|
|
|
- node = node.childNodes[path[i]];
|
|
|
- }
|
|
|
-
|
|
|
- return node;
|
|
|
- }
|
|
|
- HydrateText(path, value, id) {
|
|
|
- let node = this.LoadChild(path);
|
|
|
-
|
|
|
- if (node.nodeType == Node.TEXT_NODE) {
|
|
|
- node.textContent = value;
|
|
|
- } else {
|
|
|
- // replace with a textnode
|
|
|
- let text = document.createTextNode(value);
|
|
|
- node.replaceWith(text);
|
|
|
- node = text;
|
|
|
- }
|
|
|
-
|
|
|
- this.nodes[id] = node;
|
|
|
- }
|
|
|
- ReplacePlaceholder(path, m) {
|
|
|
- let els = this.stack.splice(this.stack.length - m);
|
|
|
- let node = this.LoadChild(path);
|
|
|
- node.replaceWith(...els);
|
|
|
- }
|
|
|
- LoadTemplate(name, index, id) {
|
|
|
- let node = this.templates[name][index].cloneNode(true);
|
|
|
- this.nodes[id] = node;
|
|
|
- this.stack.push(node);
|
|
|
- }
|
|
|
- handleEdit(edit) {
|
|
|
- switch (edit.type) {
|
|
|
- case "AppendChildren":
|
|
|
- this.AppendChildren(edit.m);
|
|
|
- break;
|
|
|
- case "AssignId":
|
|
|
- this.AssignId(edit.path, edit.id);
|
|
|
- break;
|
|
|
- case "CreatePlaceholder":
|
|
|
- this.CreatePlaceholder(edit.id);
|
|
|
- break;
|
|
|
- case "CreateTextNode":
|
|
|
- this.CreateTextNode(edit.value, edit.id);
|
|
|
- break;
|
|
|
- case "HydrateText":
|
|
|
- this.HydrateText(edit.path, edit.value, edit.id);
|
|
|
- break;
|
|
|
- case "LoadTemplate":
|
|
|
- this.LoadTemplate(edit.name, edit.index, edit.id);
|
|
|
- break;
|
|
|
- case "PushRoot":
|
|
|
- this.PushRoot(edit.id);
|
|
|
- break;
|
|
|
- case "ReplaceWith":
|
|
|
- this.ReplaceWith(edit.id, edit.m);
|
|
|
- break;
|
|
|
- case "ReplacePlaceholder":
|
|
|
- this.ReplacePlaceholder(edit.path, edit.m);
|
|
|
- break;
|
|
|
- case "InsertAfter":
|
|
|
- this.InsertAfter(edit.id, edit.m);
|
|
|
- break;
|
|
|
- case "InsertBefore":
|
|
|
- this.InsertBefore(edit.id, edit.m);
|
|
|
- break;
|
|
|
- case "Remove":
|
|
|
- this.Remove(edit.id);
|
|
|
- break;
|
|
|
- case "SetText":
|
|
|
- this.SetText(edit.id, edit.value);
|
|
|
- break;
|
|
|
- case "SetAttribute":
|
|
|
- this.SetAttribute(edit.id, edit.name, edit.value, edit.ns);
|
|
|
- break;
|
|
|
- case "RemoveAttribute":
|
|
|
- this.RemoveAttribute(edit.id, edit.name, edit.ns);
|
|
|
- break;
|
|
|
- case "RemoveEventListener":
|
|
|
- this.RemoveEventListener(edit.id, edit.name);
|
|
|
- break;
|
|
|
- case "NewEventListener":
|
|
|
- let bubbles = event_bubbles(edit.name);
|
|
|
-
|
|
|
- // if this is a mounted listener, we send the event immediately
|
|
|
- if (edit.name === "mounted") {
|
|
|
- window.ipc.postMessage(
|
|
|
- serializeIpcMessage("user_event", {
|
|
|
- name: edit.name,
|
|
|
- element: edit.id,
|
|
|
- data: null,
|
|
|
- bubbles,
|
|
|
- })
|
|
|
- );
|
|
|
- } else {
|
|
|
- this.NewEventListener(edit.name, edit.id, bubbles, (event) => {
|
|
|
- handler(event, edit.name, bubbles, this.config);
|
|
|
- });
|
|
|
- }
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
// this handler is only provided on the desktop and liveview implementations since this
|
|
|
// method is not used by the web implementation
|
|
|
function handler(event, name, bubbles, config) {
|
|
@@ -416,7 +35,7 @@ function handler(event, name, bubbles, config) {
|
|
|
const href = a_element.getAttribute("href");
|
|
|
if (href !== "" && href !== null && href !== undefined) {
|
|
|
window.ipc.postMessage(
|
|
|
- serializeIpcMessage("browser_open", { href })
|
|
|
+ window.interpreter.serializeIpcMessage("browser_open", { href })
|
|
|
);
|
|
|
}
|
|
|
}
|
|
@@ -444,7 +63,44 @@ function handler(event, name, bubbles, config) {
|
|
|
|
|
|
let contents = serialize_event(event);
|
|
|
|
|
|
- /*POST_EVENT_SERIALIZATION*/
|
|
|
+ // TODO: this should be liveview only
|
|
|
+ if (
|
|
|
+ target.tagName === "INPUT" &&
|
|
|
+ (event.type === "change" || event.type === "input")
|
|
|
+ ) {
|
|
|
+ const type = target.getAttribute("type");
|
|
|
+ if (type === "file") {
|
|
|
+ async function read_files() {
|
|
|
+ const files = target.files;
|
|
|
+ const file_contents = {};
|
|
|
+
|
|
|
+ for (let i = 0; i < files.length; i++) {
|
|
|
+ const file = files[i];
|
|
|
+
|
|
|
+ file_contents[file.name] = Array.from(
|
|
|
+ new Uint8Array(await file.arrayBuffer())
|
|
|
+ );
|
|
|
+ }
|
|
|
+ let file_engine = {
|
|
|
+ files: file_contents,
|
|
|
+ };
|
|
|
+ contents.files = file_engine;
|
|
|
+
|
|
|
+ if (realId === null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const message = window.interpreter.serializeIpcMessage("user_event", {
|
|
|
+ name: name,
|
|
|
+ element: parseInt(realId),
|
|
|
+ data: contents,
|
|
|
+ bubbles,
|
|
|
+ });
|
|
|
+ window.ipc.postMessage(message);
|
|
|
+ }
|
|
|
+ read_files();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
if (
|
|
|
target.tagName === "FORM" &&
|
|
@@ -476,7 +132,7 @@ function handler(event, name, bubbles, config) {
|
|
|
return;
|
|
|
}
|
|
|
window.ipc.postMessage(
|
|
|
- serializeIpcMessage("user_event", {
|
|
|
+ window.interpreter.serializeIpcMessage("user_event", {
|
|
|
name: name,
|
|
|
element: parseInt(realId),
|
|
|
data: contents,
|
|
@@ -506,6 +162,130 @@ function find_real_id(target) {
|
|
|
return realId;
|
|
|
}
|
|
|
|
|
|
+class ListenerMap {
|
|
|
+ constructor(root) {
|
|
|
+ // bubbling events can listen at the root element
|
|
|
+ this.global = {};
|
|
|
+ // non bubbling events listen at the element the listener was created at
|
|
|
+ this.local = {};
|
|
|
+ this.root = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ create(event_name, element, bubbles, handler) {
|
|
|
+ if (bubbles) {
|
|
|
+ if (this.global[event_name] === undefined) {
|
|
|
+ this.global[event_name] = {};
|
|
|
+ this.global[event_name].active = 1;
|
|
|
+ this.root.addEventListener(event_name, handler);
|
|
|
+ } else {
|
|
|
+ this.global[event_name].active++;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ const id = element.getAttribute("data-dioxus-id");
|
|
|
+ if (!this.local[id]) {
|
|
|
+ this.local[id] = {};
|
|
|
+ }
|
|
|
+ element.addEventListener(event_name, handler);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ remove(element, event_name, bubbles) {
|
|
|
+ if (bubbles) {
|
|
|
+ this.global[event_name].active--;
|
|
|
+ if (this.global[event_name].active === 0) {
|
|
|
+ this.root.removeEventListener(event_name, this.global[event_name].callback);
|
|
|
+ delete this.global[event_name];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ const id = element.getAttribute("data-dioxus-id");
|
|
|
+ delete this.local[id][event_name];
|
|
|
+ if (this.local[id].length === 0) {
|
|
|
+ delete this.local[id];
|
|
|
+ }
|
|
|
+ element.removeEventListener(event_name, this.global[event_name].callback);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ removeAllNonBubbling(element) {
|
|
|
+ const id = element.getAttribute("data-dioxus-id");
|
|
|
+ delete this.local[id];
|
|
|
+ }
|
|
|
+}
|
|
|
+function LoadChild(array) {
|
|
|
+ // iterate through each number and get that child
|
|
|
+ node = stack[stack.length - 1];
|
|
|
+
|
|
|
+ for (let i = 0; i < array.length; i++) {
|
|
|
+ end = array[i];
|
|
|
+ for (node = node.firstChild; end > 0; end--) {
|
|
|
+ node = node.nextSibling;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return node;
|
|
|
+}
|
|
|
+const listeners = new ListenerMap();
|
|
|
+let nodes = [];
|
|
|
+let stack = [];
|
|
|
+let root;
|
|
|
+const templates = {};
|
|
|
+let node, els, end, k;
|
|
|
+
|
|
|
+function AppendChildren(id, many) {
|
|
|
+ root = nodes[id];
|
|
|
+ els = stack.splice(stack.length - many);
|
|
|
+ for (k = 0; k < many; k++) {
|
|
|
+ root.appendChild(els[k]);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+window.interpreter = {}
|
|
|
+
|
|
|
+window.interpreter.initialize = function (root) {
|
|
|
+ nodes = [root];
|
|
|
+ stack = [root];
|
|
|
+ listeners.root = root;
|
|
|
+}
|
|
|
+
|
|
|
+window.interpreter.getClientRect = function (id) {
|
|
|
+ const node = nodes[id];
|
|
|
+ if (!node) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const rect = node.getBoundingClientRect();
|
|
|
+ return {
|
|
|
+ type: "GetClientRect",
|
|
|
+ origin: [rect.x, rect.y],
|
|
|
+ size: [rect.width, rect.height],
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+window.interpreter.scrollTo = function (id, behavior) {
|
|
|
+ const node = nodes[id];
|
|
|
+ if (!node) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ node.scrollIntoView({
|
|
|
+ behavior: behavior,
|
|
|
+ });
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+/// Set the focus on the element
|
|
|
+window.interpreter.setFocus = function (id, focus) {
|
|
|
+ const node = nodes[id];
|
|
|
+ if (!node) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ if (focus) {
|
|
|
+ node.focus();
|
|
|
+ } else {
|
|
|
+ node.blur();
|
|
|
+ }
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
function get_mouse_data(event) {
|
|
|
const {
|
|
|
altKey,
|
|
@@ -782,7 +562,7 @@ function serialize_event(event) {
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
-function serializeIpcMessage(method, params = {}) {
|
|
|
+window.interpreter.serializeIpcMessage = function (method, params = {}) {
|
|
|
return JSON.stringify({ method, params });
|
|
|
}
|
|
|
|