More fullstack fixes (#4737)
* allow `--server` only builds! * rollback example * capture `/:route` routes * fix: properly implement file uploads in forms * implement desktop form uploads * Performant serialization of form bytes * finish up file stuff * cleanups * add request headers to streaming context * remove some unwraps
This commit is contained in:
1 parent
fc01a002fd
commit
92792aded9
47 files changed
+1241
-703
No files matched your search
@@ -1 +1 @@
|
||||
function setAttributeInner(node,field,value,ns){if(ns==="style"){node.style.setProperty(field,value);return}if(ns){node.setAttributeNS(ns,field,value);return}switch(field){case"value":if(node.tagName==="OPTION")setAttributeDefault(node,field,value);else if(node.value!==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;case"style":let existingStyles={};for(let i=0;i<node.style.length;i++){let prop=node.style[i];existingStyles[prop]=node.style.getPropertyValue(prop)}node.setAttribute(field,value);for(let prop in existingStyles)if(!node.style.getPropertyValue(prop))node.style.setProperty(prop,existingStyles[prop]);break;case"multiple":if(setAttributeDefault(node,field,value),node.options!==null&&node.options!==void 0){let options=node.options;for(let option of options)option.selected=option.defaultSelected}break;default:setAttributeDefault(node,field,value)}}function setAttributeDefault(node,field,value){if(!truthy(value)&&isBoolAttr(field))node.removeAttribute(field);else node.setAttribute(field,value)}function truthy(val){return val==="true"||val===!0}function isBoolAttr(field){switch(field){case"allowfullscreen":case"allowpaymentrequest":case"async":case"autofocus":case"autoplay":case"checked":case"controls":case"default":case"defer":case"disabled":case"formnovalidate":case"hidden":case"ismap":case"itemscope":case"loop":case"multiple":case"muted":case"nomodule":case"novalidate":case"open":case"playsinline":case"readonly":case"required":case"reversed":case"selected":case"truespeed":case"webkitdirectory":return!0;default:return!1}}function retrieveFormValues(form){let formData=new FormData(form),contents={};return formData.forEach((value,key)=>{if(value instanceof File){contents[key]=[value.name];return}if(contents[key])contents[key].push(value);else contents[key]=[value]}),{valid:form.checkValidity(),values:contents}}export{setAttributeInner,retrieveFormValues};
|
||||
function setAttributeInner(node,field,value,ns){if(ns==="style"){node.style.setProperty(field,value);return}if(ns){node.setAttributeNS(ns,field,value);return}switch(field){case"value":if(node.tagName==="OPTION")setAttributeDefault(node,field,value);else if(node.value!==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;case"style":let existingStyles={};for(let i=0;i<node.style.length;i++){let prop=node.style[i];existingStyles[prop]=node.style.getPropertyValue(prop)}node.setAttribute(field,value);for(let prop in existingStyles)if(!node.style.getPropertyValue(prop))node.style.setProperty(prop,existingStyles[prop]);break;case"multiple":if(setAttributeDefault(node,field,value),node.options!==null&&node.options!==void 0){let options=node.options;for(let option of options)option.selected=option.defaultSelected}break;default:setAttributeDefault(node,field,value)}}function setAttributeDefault(node,field,value){if(!truthy(value)&&isBoolAttr(field))node.removeAttribute(field);else node.setAttribute(field,value)}function truthy(val){return val==="true"||val===!0}function isBoolAttr(field){switch(field){case"allowfullscreen":case"allowpaymentrequest":case"async":case"autofocus":case"autoplay":case"checked":case"controls":case"default":case"defer":case"disabled":case"formnovalidate":case"hidden":case"ismap":case"itemscope":case"loop":case"multiple":case"muted":case"nomodule":case"novalidate":case"open":case"playsinline":case"readonly":case"required":case"reversed":case"selected":case"truespeed":case"webkitdirectory":return!0;default:return!1}}function retrieveFormValues(form){let formData=new FormData(form),contents=[];return formData.forEach((value,key)=>{if(value instanceof File){let fileData={name:value.name,path:value.webkitRelativePath,size:value.size,last_modified:value.lastModified,content_type:value.type};contents.push([key,fileData])}else contents.push([key,value])}),{valid:form.checkValidity(),values:contents}}export{setAttributeInner,retrieveFormValues};
|
||||
@@ -1 +1 @@
|
||||
[6449103750905854967, 17669692872757955279, 4701826235737143966, 11420464406527728232, 3770103091118609057, 5444526391971481782, 14692042304579321130, 5052021921702764563, 7041565893588933645, 11339769846046015954]
|
||||
[17669692872757955279, 11420464406527728232, 3770103091118609057, 5444526391971481782, 8889858244860485542, 5052021921702764563, 5113938988622536969, 11339769846046015954]
|
||||
@@ -1 +1 @@
|
||||
function retrieveValues(event,target){let contents={values:{}},form=target.closest("form");if(form){if(event.type==="input"||event.type==="change"||event.type==="submit"||event.type==="reset"||event.type==="click")contents=retrieveFormValues(form)}return contents}function retrieveFormValues(form){let formData=new FormData(form),contents={};return formData.forEach((value,key)=>{if(value instanceof File){contents[key]=[value.name];return}if(contents[key])contents[key].push(value);else contents[key]=[value]}),{valid:form.checkValidity(),values:contents}}function retrieveSelectValue(target){let options=target.selectedOptions,values=[];for(let i=0;i<options.length;i++)values.push(options[i].value);return values}function serializeEvent(event,target){let contents={},extend=(obj)=>contents={...contents,...obj};if(event instanceof WheelEvent)extend(serializeWheelEvent(event));if(event instanceof MouseEvent)extend(serializeMouseEvent(event));if(event instanceof KeyboardEvent)extend(serializeKeyboardEvent(event));if(event instanceof InputEvent)extend(serializeInputEvent(event,target));if(event instanceof PointerEvent)extend(serializePointerEvent(event));if(event instanceof AnimationEvent)extend(serializeAnimationEvent(event));if(event instanceof TransitionEvent)extend({property_name:event.propertyName,elapsed_time:event.elapsedTime,pseudo_element:event.pseudoElement});if(event instanceof CompositionEvent)extend({data:event.data});if(event instanceof DragEvent)extend(serializeDragEvent(event));if(event instanceof FocusEvent)extend({});if(event instanceof ClipboardEvent)extend({});if(event instanceof CustomEvent){let detail=event.detail;if(detail instanceof ResizeObserverEntry)extend(serializeResizeEventDetail(detail));else if(detail instanceof IntersectionObserverEntry)extend(serializeIntersectionEventDetail(detail))}if(typeof TouchEvent!=="undefined"&&event instanceof TouchEvent)extend(serializeTouchEvent(event));if(event.type==="submit"||event.type==="reset"||event.type==="click"||event.type==="change"||event.type==="input")extend(serializeInputEvent(event,target));if(event instanceof DragEvent);if(event.type==="scroll")extend(serializeScrollEvent(event));return contents}function toSerializableResizeObserverSize(size,is_inline_width){return[is_inline_width?size.inlineSize:size.blockSize,is_inline_width?size.blockSize:size.inlineSize]}function serializeResizeEventDetail(detail){let is_inline_width=!0;if(detail.target instanceof HTMLElement){if(window.getComputedStyle(detail.target).getPropertyValue("writing-mode")!=="horizontal-tb")is_inline_width=!1}return{border_box_size:detail.borderBoxSize!==void 0?toSerializableResizeObserverSize(detail.borderBoxSize[0],is_inline_width):detail.contentRect,content_box_size:detail.contentBoxSize!==void 0?toSerializableResizeObserverSize(detail.contentBoxSize[0],is_inline_width):detail.contentRect,content_rect:detail.contentRect}}function serializeIntersectionEventDetail(detail){return{bounding_client_rect:detail.boundingClientRect,intersection_ratio:detail.intersectionRatio,intersection_rect:detail.intersectionRect,is_intersecting:detail.isIntersecting,root_bounds:detail.rootBounds,time_ms:Math.floor(Date.now()+detail.time)}}function serializeInputEvent(event,target){let contents={};if(target instanceof HTMLElement){let values=retrieveValues(event,target);contents.values=values.values,contents.valid=values.valid}if(event.target instanceof HTMLInputElement){let target2=event.target,value=target2.value??target2.textContent??"";if(target2.type==="checkbox")value=target2.checked?"true":"false";else if(target2.type==="radio")value=target2.value;contents.value=value}if(event.target instanceof HTMLTextAreaElement)contents.value=event.target.value;if(event.target instanceof HTMLSelectElement)contents.value=retrieveSelectValue(event.target).join(",");if(contents.value===void 0)contents.value="";return contents}function serializeWheelEvent(event){return{delta_x:event.deltaX,delta_y:event.deltaY,delta_z:event.deltaZ,delta_mode:event.deltaMode}}function serializeTouchEvent(event){return{alt_key:event.altKey,ctrl_key:event.ctrlKey,meta_key:event.metaKey,shift_key:event.shiftKey,changed_touches:serializeTouchList(event.changedTouches),target_touches:serializeTouchList(event.targetTouches),touches:serializeTouchList(event.touches)}}function serializePointerEvent(event){return{alt_key:event.altKey,button:event.button,buttons:event.buttons,client_x:event.clientX,client_y:event.clientY,ctrl_key:event.ctrlKey,meta_key:event.metaKey,page_x:event.pageX,page_y:event.pageY,screen_x:event.screenX,screen_y:event.screenY,shift_key:event.shiftKey,pointer_id:event.pointerId,width:event.width,height:event.height,pressure:event.pressure,tangential_pressure:event.tangentialPressure,tilt_x:event.tiltX,tilt_y:event.tiltY,twist:event.twist,pointer_type:event.pointerType,is_primary:event.isPrimary}}function serializeTouchList(touchList){let serializedTouches=[];for(let i=0;i<touchList.length;i++){let touch=touchLisLine truncated
|
||||
function serializeEvent(event,target){let contents={},extend=(obj)=>contents={...contents,...obj};if(event instanceof WheelEvent)extend(serializeWheelEvent(event));if(event instanceof MouseEvent)extend(serializeMouseEvent(event));if(event instanceof KeyboardEvent)extend(serializeKeyboardEvent(event));if(event instanceof InputEvent)extend(serializeInputEvent(event,target));if(event instanceof PointerEvent)extend(serializePointerEvent(event));if(event instanceof AnimationEvent)extend(serializeAnimationEvent(event));if(event instanceof TransitionEvent)extend({property_name:event.propertyName,elapsed_time:event.elapsedTime,pseudo_element:event.pseudoElement});if(event instanceof CompositionEvent)extend({data:event.data});if(event instanceof DragEvent)extend(serializeDragEvent(event));if(event instanceof FocusEvent)extend({});if(event instanceof ClipboardEvent)extend({});if(event instanceof CustomEvent){let detail=event.detail;if(detail instanceof ResizeObserverEntry)extend(serializeResizeEventDetail(detail));else if(detail instanceof IntersectionObserverEntry)extend(serializeIntersectionEventDetail(detail))}if(typeof TouchEvent<"u"&&event instanceof TouchEvent)extend(serializeTouchEvent(event));if(event.type==="submit"||event.type==="reset"||event.type==="click"||event.type==="change"||event.type==="input")extend(serializeInputEvent(event,target));if(event instanceof DragEvent){let files=[];if(event.dataTransfer&&event.dataTransfer.files)for(let i=0;i<event.dataTransfer.files.length;i++){let file=event.dataTransfer.files[i],data={path:file.name,size:file.size,last_modified:file.lastModified,content_type:file.type};files.push({key:file.name,file:data})}extend({files})}if(event.type==="scroll")extend(serializeScrollEvent(event));return contents}function toSerializableResizeObserverSize(size,is_inline_width){return[is_inline_width?size.inlineSize:size.blockSize,is_inline_width?size.blockSize:size.inlineSize]}function serializeResizeEventDetail(detail){let is_inline_width=!0;if(detail.target instanceof HTMLElement){if(window.getComputedStyle(detail.target).getPropertyValue("writing-mode")!=="horizontal-tb")is_inline_width=!1}return{border_box_size:detail.borderBoxSize!==void 0?toSerializableResizeObserverSize(detail.borderBoxSize[0],is_inline_width):detail.contentRect,content_box_size:detail.contentBoxSize!==void 0?toSerializableResizeObserverSize(detail.contentBoxSize[0],is_inline_width):detail.contentRect,content_rect:detail.contentRect}}function serializeIntersectionEventDetail(detail){return{bounding_client_rect:detail.boundingClientRect,intersection_ratio:detail.intersectionRatio,intersection_rect:detail.intersectionRect,is_intersecting:detail.isIntersecting,root_bounds:detail.rootBounds,time_ms:Math.floor(Date.now()+detail.time)}}function serializeInputEvent(event,target){let contents={};if(target instanceof HTMLElement){let values=extractSerializedFormValues(event,target);contents.values=values.values,contents.valid=values.valid}if(event.target instanceof HTMLInputElement){let target2=event.target,value=target2.value??target2.textContent??"";if(target2.type==="checkbox")value=target2.checked?"true":"false";else if(target2.type==="radio")value=target2.value;contents.value=value}if(event.target instanceof HTMLTextAreaElement)contents.value=event.target.value;if(event.target instanceof HTMLSelectElement)contents.value=retrieveSelectValue(event.target).join(",");if(contents.value===void 0)contents.value="";return contents}function serializeWheelEvent(event){return{delta_x:event.deltaX,delta_y:event.deltaY,delta_z:event.deltaZ,delta_mode:event.deltaMode}}function serializeTouchEvent(event){return{alt_key:event.altKey,ctrl_key:event.ctrlKey,meta_key:event.metaKey,shift_key:event.shiftKey,changed_touches:serializeTouchList(event.changedTouches),target_touches:serializeTouchList(event.targetTouches),touches:serializeTouchList(event.touches)}}function serializePointerEvent(event){return{alt_key:event.altKey,button:event.button,buttons:event.buttons,client_x:event.clientX,client_y:event.clientY,ctrl_key:event.ctrlKey,meta_key:event.metaKey,page_x:event.pageX,page_y:event.pageY,screen_x:event.screenX,screen_y:event.screenY,shift_key:event.shiftKey,pointer_id:event.pointerId,width:event.width,height:event.height,pressure:event.pressure,tangential_pressure:event.tangentialPressure,tilt_x:event.tiltX,tilt_y:event.tiltY,twist:event.twist,pointer_type:event.pointerType,is_primary:event.isPrimary}}function serializeTouchList(touchList){let serializedTouches=[];for(let i=0;i<touchList.length;i++){let touch=touchList[i];serializedTouches.push({identifier:touch.identifier,client_x:touch.clientX,client_y:touch.clientY,page_x:touch.pageX,page_y:touch.pageY,screen_x:touch.screenX,screen_y:touch.screenY,radius_x:touch.radiusX,radius_y:touch.radiusY,rotation_angle:touch.rotationAngle,force:touch.force})}return serializedTouches}function serializeMouseEvent(event){return{alt_key:event.altKey,button:event.button,buttons:event.buttonsLine truncated
|
||||
@@ -0,0 +1 @@
|
||||
function setAttributeInner(node,field,value,ns){if(ns==="style"){node.style.setProperty(field,value);return}if(ns){node.setAttributeNS(ns,field,value);return}switch(field){case"value":if(node.tagName==="OPTION")setAttributeDefault(node,field,value);else if(node.value!==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;case"style":let existingStyles={};for(let i=0;i<node.style.length;i++){let prop=node.style[i];existingStyles[prop]=node.style.getPropertyValue(prop)}node.setAttribute(field,value);for(let prop in existingStyles)if(!node.style.getPropertyValue(prop))node.style.setProperty(prop,existingStyles[prop]);break;case"multiple":if(setAttributeDefault(node,field,value),node.options!==null&&node.options!==void 0){let options=node.options;for(let option of options)option.selected=option.defaultSelected}break;default:setAttributeDefault(node,field,value)}}function setAttributeDefault(node,field,value){if(!truthy(value)&&isBoolAttr(field))node.removeAttribute(field);else node.setAttribute(field,value)}function truthy(val){return val==="true"||val===!0}function isBoolAttr(field){switch(field){case"allowfullscreen":case"allowpaymentrequest":case"async":case"autofocus":case"autoplay":case"checked":case"controls":case"default":case"defer":case"disabled":case"formnovalidate":case"hidden":case"ismap":case"itemscope":case"loop":case"multiple":case"muted":case"nomodule":case"novalidate":case"open":case"playsinline":case"readonly":case"required":case"reversed":case"selected":case"truespeed":case"webkitdirectory":return!0;default:return!1}}export{setAttributeInner};
|
||||
@@ -32,13 +32,10 @@ pub mod minimal_bindings {
|
||||
/// Some useful snippets that we use to share common functionality between the different platforms we support.
|
||||
///
|
||||
/// This maintains some sort of consistency between web, desktop, and liveview
|
||||
#[wasm_bindgen(module = "/src/js/common.js")]
|
||||
#[wasm_bindgen(module = "/src/js/set_attribute.js")]
|
||||
extern "C" {
|
||||
/// Set the attribute of the node
|
||||
pub fn setAttributeInner(node: JsValue, name: &str, value: JsValue, ns: Option<&str>);
|
||||
|
||||
/// Roll up all the values from the node into a JS object that we can deserialize
|
||||
pub fn retrieveFormValues(node: JsValue) -> JsValue;
|
||||
}
|
||||
|
||||
#[wasm_bindgen(module = "/src/js/hydrate.js")]
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
export { setAttributeInner } from "./set_attribute";
|
||||
export { retrieveFormValues } from "./form";
|
||||
@@ -1,63 +0,0 @@
|
||||
// Consistently deserialize forms and form elements for use across web/desktop/mobile
|
||||
|
||||
type FormValues = {
|
||||
valid?: boolean;
|
||||
values: { [key: string]: FormDataEntryValue[] };
|
||||
}
|
||||
|
||||
export function retrieveValues(event: Event, target: HTMLElement): FormValues {
|
||||
let contents: FormValues = {
|
||||
values: {}
|
||||
};
|
||||
|
||||
// If there's a form...
|
||||
let form = target.closest("form");
|
||||
|
||||
// If the target is an input, and the event is input or change, we want to get the value without going through the form
|
||||
if (form) {
|
||||
if (
|
||||
event.type === "input"
|
||||
|| event.type === "change"
|
||||
|| event.type === "submit"
|
||||
|| event.type === "reset"
|
||||
|| event.type === "click"
|
||||
) {
|
||||
contents = retrieveFormValues(form);
|
||||
}
|
||||
}
|
||||
|
||||
return contents;
|
||||
}
|
||||
|
||||
// todo: maybe encode spaces or something?
|
||||
// We encode select multiple as a comma separated list which breaks... when there's commas in the values
|
||||
export function retrieveFormValues(form: HTMLFormElement): FormValues {
|
||||
const formData = new FormData(form);
|
||||
const contents: { [key: string]: FormDataEntryValue[] } = {};
|
||||
formData.forEach((value, key) => {
|
||||
if (value instanceof File) {
|
||||
contents[key] = [value.name];
|
||||
return;
|
||||
}
|
||||
|
||||
if (contents[key]) {
|
||||
contents[key].push(value);
|
||||
} else {
|
||||
contents[key] = [value];
|
||||
}
|
||||
});
|
||||
return {
|
||||
valid: form.checkValidity(),
|
||||
values: contents
|
||||
};
|
||||
}
|
||||
|
||||
export function retrieveSelectValue(target: HTMLSelectElement): string[] {
|
||||
// there might be multiple...
|
||||
let options = target.selectedOptions;
|
||||
let values = [];
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
values.push(options[i].value);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
// provide since it doesn't have access to the dom.
|
||||
|
||||
import { BaseInterpreter, NodeId } from "./core";
|
||||
import { SerializedEvent, serializeEvent } from "./serialize";
|
||||
import { SerializedEvent, serializeEvent, SerializedFileData, extractSerializedFormValues, SerializedFormObject } from "./serialize";
|
||||
|
||||
// okay so, we've got this JSChannel thing from sledgehammer, implicitly imported into our scope
|
||||
// we want to extend it, and it technically extends base interpreter. To make typescript happy,
|
||||
@@ -21,6 +21,7 @@ export class NativeInterpreter extends JSChannel_ {
|
||||
intercept_link_redirects: boolean;
|
||||
ipc: any;
|
||||
edits: WebSocket;
|
||||
baseUri: string;
|
||||
eventsPath: string;
|
||||
headless: boolean;
|
||||
kickStylesheets: boolean;
|
||||
@@ -30,9 +31,10 @@ export class NativeInterpreter extends JSChannel_ {
|
||||
// however, for now we need to support it since WebSockets in fullstack doesn't exist yet
|
||||
liveview: boolean;
|
||||
|
||||
constructor(eventsPath: string, headless: boolean) {
|
||||
constructor(baseUri: string, headless: boolean) {
|
||||
super();
|
||||
this.eventsPath = eventsPath;
|
||||
this.baseUri = baseUri;
|
||||
this.eventsPath = `${baseUri}/__events`;
|
||||
this.kickStylesheets = false;
|
||||
this.headless = headless;
|
||||
}
|
||||
@@ -79,20 +81,76 @@ export class NativeInterpreter extends JSChannel_ {
|
||||
// Send a message to the host to open the file dialog if the target is a file input and has a dioxus id attached to it
|
||||
let target_id = getTargetId(target);
|
||||
if (target_id !== null) {
|
||||
const message = this.serializeIpcMessage("file_dialog", {
|
||||
event: "change&input",
|
||||
accept: target.getAttribute("accept"),
|
||||
directory: target.getAttribute("webkitdirectory") === "true",
|
||||
multiple: target.hasAttribute("multiple"),
|
||||
target: target_id,
|
||||
bubbles: event.bubbles,
|
||||
});
|
||||
this.ipc.postMessage(message);
|
||||
event.preventDefault();
|
||||
// Handle file inputs specifically, since we want to get the real file inputs from the native
|
||||
// dialog and then set the form inputs accordingly.
|
||||
if (
|
||||
target instanceof HTMLInputElement &&
|
||||
target.getAttribute("type") === "file"
|
||||
) {
|
||||
// Send a message to the host to open the file dialog if the target is a file input and has a dioxus id attached to it
|
||||
event.preventDefault();
|
||||
|
||||
const contents = serializeEvent(event, target);
|
||||
|
||||
const target_name = target.getAttribute("name") || "";
|
||||
|
||||
let requestData = {
|
||||
event: "change&input",
|
||||
accept: target.getAttribute("accept"),
|
||||
directory: target.getAttribute("webkitdirectory") === "true",
|
||||
multiple: target.hasAttribute("multiple"),
|
||||
target: target_id,
|
||||
bubbles: event.bubbles,
|
||||
target_name,
|
||||
values: contents.values,
|
||||
};
|
||||
|
||||
this.fetchAgainstHost("__file_dialog", requestData).then(response => response.json()).then(resp => {
|
||||
const formObjects: SerializedFormObject[] = resp.values;
|
||||
|
||||
// Create a new DataTransfer to hold the files
|
||||
const dataTransfer = new DataTransfer();
|
||||
|
||||
// We name the file the path, so we can just use the path as the name later on.
|
||||
for (let formObject of formObjects) {
|
||||
if (formObject.key == target_name && formObject.file != null) {
|
||||
const file = new File([], formObject.file.path, {
|
||||
type: formObject.file.content_type,
|
||||
lastModified: formObject.file.last_modified,
|
||||
});
|
||||
dataTransfer.items.add(file);
|
||||
}
|
||||
}
|
||||
|
||||
// Set the files on the input
|
||||
target.files = dataTransfer.files;
|
||||
|
||||
let body = {
|
||||
data: contents,
|
||||
element: target_id,
|
||||
bubbles: event.bubbles,
|
||||
};
|
||||
|
||||
// And then dispatch the actual event against the dom
|
||||
contents.values = formObjects;
|
||||
this.sendSerializedEvent({
|
||||
...body,
|
||||
name: "input",
|
||||
});
|
||||
this.sendSerializedEvent({
|
||||
...body,
|
||||
name: "change",
|
||||
});
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// @ts-ignore - wry gives us this
|
||||
this.ipc = window.ipc;
|
||||
|
||||
@@ -103,8 +161,23 @@ export class NativeInterpreter extends JSChannel_ {
|
||||
super.initialize(root, handler);
|
||||
}
|
||||
|
||||
serializeIpcMessage(method: string, params = {}) {
|
||||
return JSON.stringify({ method, params });
|
||||
fetchAgainstHost(path: string, data: { [key: string]: any }): Promise<Response> {
|
||||
let encoded_data = new TextEncoder().encode(JSON.stringify(data));
|
||||
const base64data = btoa(
|
||||
String.fromCharCode.apply(null, Array.from(encoded_data))
|
||||
);
|
||||
|
||||
return fetch(`${this.baseUri}/${path}`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
"x-dioxus-data": base64data
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
sendIpcMessage(method: string, params = {}) {
|
||||
const body = JSON.stringify({ method, params });
|
||||
this.ipc.postMessage(body);
|
||||
}
|
||||
|
||||
scrollTo(id: NodeId, options: ScrollIntoViewOptions): boolean {
|
||||
@@ -181,11 +254,14 @@ export class NativeInterpreter extends JSChannel_ {
|
||||
|
||||
// Windows drag-n-drop fix code. Called by wry drag-n-drop handler over the event loop.
|
||||
handleWindowsDragDrop() {
|
||||
// @ts-ignore
|
||||
if (window.dxDragLastElement) {
|
||||
const dragLeaveEvent = new DragEvent("dragleave", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
window.dxDragLastElement.dispatchEvent(dragLeaveEvent);
|
||||
|
||||
let data = new DataTransfer();
|
||||
@@ -199,23 +275,29 @@ export class NativeInterpreter extends JSChannel_ {
|
||||
cancelable: true,
|
||||
dataTransfer: data,
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
window.dxDragLastElement.dispatchEvent(dragDropEvent);
|
||||
// @ts-ignore
|
||||
window.dxDragLastElement = null;
|
||||
}
|
||||
}
|
||||
|
||||
handleWindowsDragOver(xPos, yPos) {
|
||||
handleWindowsDragOver(xPos: number, yPos: number) {
|
||||
const displayScaleFactor = window.devicePixelRatio || 1;
|
||||
xPos /= displayScaleFactor;
|
||||
yPos /= displayScaleFactor;
|
||||
const element = document.elementFromPoint(xPos, yPos);
|
||||
|
||||
// @ts-ignore
|
||||
if (element != window.dxDragLastElement) {
|
||||
// @ts-ignore
|
||||
if (window.dxDragLastElement) {
|
||||
const dragLeaveEvent = new DragEvent("dragleave", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
});
|
||||
// @ts-ignore
|
||||
window.dxDragLastElement.dispatchEvent(dragLeaveEvent);
|
||||
}
|
||||
|
||||
@@ -224,17 +306,21 @@ export class NativeInterpreter extends JSChannel_ {
|
||||
cancelable: true,
|
||||
});
|
||||
element.dispatchEvent(dragOverEvent);
|
||||
// @ts-ignore
|
||||
window.dxDragLastElement = element;
|
||||
}
|
||||
}
|
||||
|
||||
handleWindowsDragLeave() {
|
||||
// @ts-ignore
|
||||
if (window.dxDragLastElement) {
|
||||
const dragLeaveEvent = new DragEvent("dragleave", {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
});
|
||||
// @ts-ignore
|
||||
window.dxDragLastElement.dispatchEvent(dragLeaveEvent);
|
||||
// @ts-ignore
|
||||
window.dxDragLastElement = null;
|
||||
}
|
||||
}
|
||||
@@ -266,31 +352,30 @@ export class NativeInterpreter extends JSChannel_ {
|
||||
|
||||
handleEvent(event: Event, name: string, bubbles: boolean) {
|
||||
const target = event.target!;
|
||||
const realId = getTargetId(target)!;
|
||||
const element = getTargetId(target)!;
|
||||
const contents = serializeEvent(event, target);
|
||||
|
||||
// Handle the event on the virtualdom and then preventDefault if it also preventsDefault
|
||||
// Some listeners
|
||||
let body = {
|
||||
name: name,
|
||||
name,
|
||||
data: contents,
|
||||
element: realId,
|
||||
element,
|
||||
bubbles,
|
||||
};
|
||||
|
||||
// liveview does not have synchronous event handling, so we need to send the event to the host
|
||||
if (this.liveview) {
|
||||
// Okay, so the user might've requested some files to be read
|
||||
if (
|
||||
target instanceof HTMLInputElement &&
|
||||
(event.type === "change" || event.type === "input")
|
||||
) {
|
||||
if (target.getAttribute("type") === "file") {
|
||||
this.readFiles(target, contents, bubbles, realId, name);
|
||||
return;
|
||||
}
|
||||
if (
|
||||
this.liveview &&
|
||||
target instanceof HTMLInputElement &&
|
||||
(event.type === "change" || event.type === "input")
|
||||
) {
|
||||
if (target.getAttribute("type") === "file") {
|
||||
this.readFiles(target, contents, bubbles, element, name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const response = this.sendSerializedEvent(body);
|
||||
// capture/prevent default of the event if the virtualdom wants to
|
||||
if (response) {
|
||||
@@ -316,8 +401,7 @@ export class NativeInterpreter extends JSChannel_ {
|
||||
bubbles: boolean;
|
||||
}): EventSyncResult | void {
|
||||
if (this.liveview) {
|
||||
const message = this.serializeIpcMessage("user_event", body);
|
||||
this.ipc.postMessage(message);
|
||||
this.sendIpcMessage("user_event", body);
|
||||
} else {
|
||||
// Run the event handler on the virtualdom
|
||||
return handleVirtualdomEventSync(this.eventsPath, JSON.stringify(body));
|
||||
@@ -337,9 +421,7 @@ export class NativeInterpreter extends JSChannel_ {
|
||||
|
||||
const href = a_element.getAttribute("href");
|
||||
if (href !== "" && href !== null && href !== undefined) {
|
||||
this.ipc.postMessage(
|
||||
this.serializeIpcMessage("browser_open", { href })
|
||||
);
|
||||
this.sendIpcMessage("browser_open", { href })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
// Handle serialization of the event data across the IPC boundarytype SerializedEvent = {};
|
||||
|
||||
import { retrieveSelectValue, retrieveValues } from "./form";
|
||||
// Handle serialization of the event data across the IPC boundary
|
||||
//
|
||||
// Note that for files, we don't serialize their contents, expecting the renderer itself to handle
|
||||
// reading the file contents if needed. Reading files is an async operation, and should be done lazily
|
||||
// if the app needs it.
|
||||
|
||||
export type AppTouchEvent = TouchEvent;
|
||||
|
||||
export type SerializedEvent = {
|
||||
values?: { [key: string]: FormDataEntryValue[] };
|
||||
values?: SerializedFormObject[];
|
||||
value?: string;
|
||||
[key: string]: any;
|
||||
};
|
||||
@@ -17,7 +19,7 @@ export function serializeEvent(
|
||||
let contents = {};
|
||||
|
||||
// merge the object into the contents
|
||||
let extend = (obj: any) => (contents = { ...contents, ...obj });
|
||||
let extend = (obj: SerializedEvent) => (contents = { ...contents, ...obj });
|
||||
|
||||
if (event instanceof WheelEvent) {
|
||||
extend(serializeWheelEvent(event));
|
||||
@@ -84,22 +86,20 @@ export function serializeEvent(
|
||||
|
||||
// If there's any files, we need to serialize them
|
||||
if (event instanceof DragEvent) {
|
||||
// let files: { [key: string]: Uint8Array } = {};
|
||||
// if (event.dataTransfer && event.dataTransfer.files) {
|
||||
// files["a"] = new Uint8Array(0);
|
||||
// // files = {
|
||||
// // entries: Array.from(event.dataTransfer.files).map((file) => {
|
||||
// // return {
|
||||
// // name: file.name,
|
||||
// // type: file.type,
|
||||
// // size: file.size,
|
||||
// // last_modified: file.lastModified,
|
||||
// // };
|
||||
// // }
|
||||
// // };
|
||||
// // files = await serializeFileList(event.dataTransfer.files);
|
||||
// }
|
||||
// extend({ files: files });
|
||||
let files: SerializedFormObject[] = [];
|
||||
if (event.dataTransfer && event.dataTransfer.files) {
|
||||
for (let i = 0; i < event.dataTransfer.files.length; i++) {
|
||||
let file = event.dataTransfer.files[i];
|
||||
let data: SerializedFileData = {
|
||||
path: file.name,
|
||||
size: file.size,
|
||||
last_modified: file.lastModified,
|
||||
content_type: file.type,
|
||||
}
|
||||
files.push({ key: file.name, file: data });
|
||||
}
|
||||
}
|
||||
extend({ files });
|
||||
}
|
||||
|
||||
if (event.type === "scroll") {
|
||||
@@ -173,7 +173,7 @@ function serializeInputEvent(
|
||||
|
||||
// Attempt to retrieve the values from the form
|
||||
if (target instanceof HTMLElement) {
|
||||
let values = retrieveValues(event, target);
|
||||
let values = extractSerializedFormValues(event, target);
|
||||
contents.values = values.values;
|
||||
contents.valid = values.valid;
|
||||
}
|
||||
@@ -376,4 +376,85 @@ function serializeScrollEvent(event: Event): SerializedEvent {
|
||||
client_width: clientWidth,
|
||||
client_height: clientHeight,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export type SerializedFormData = {
|
||||
valid?: boolean;
|
||||
values: SerializedFormObject[];
|
||||
}
|
||||
|
||||
export type SerializedFormObject = {
|
||||
key: string;
|
||||
text?: string;
|
||||
file?: SerializedFileData;
|
||||
}
|
||||
export type SerializedFileData = {
|
||||
// we store the real path of the file as the name.
|
||||
// This makes it possible to simply read the file during event handling
|
||||
path: string;
|
||||
size?: number;
|
||||
last_modified?: number;
|
||||
content_type?: string;
|
||||
contents?: string; // base64 encoded, if present. not required to br present
|
||||
};
|
||||
|
||||
export function extractSerializedFormValues(event: Event, target: HTMLElement): SerializedFormData {
|
||||
let contents: SerializedFormData = {
|
||||
values: []
|
||||
};
|
||||
|
||||
// If there's a form...
|
||||
let form = target.closest("form");
|
||||
|
||||
// If the target is an input, and the event is input or change, we want to get the value without going through the form
|
||||
if (form) {
|
||||
if (
|
||||
event.type === "input"
|
||||
|| event.type === "change"
|
||||
|| event.type === "submit"
|
||||
|| event.type === "reset"
|
||||
|| event.type === "click"
|
||||
) {
|
||||
contents = retrieveFormValues(form);
|
||||
}
|
||||
}
|
||||
|
||||
return contents;
|
||||
}
|
||||
|
||||
// todo: maybe encode spaces or something?
|
||||
// We encode select multiple as a comma separated list which breaks... when there's commas in the values
|
||||
function retrieveFormValues(form: HTMLFormElement): SerializedFormData {
|
||||
const formData = new FormData(form);
|
||||
const contents: SerializedFormObject[] = [];
|
||||
|
||||
formData.forEach((value, key) => {
|
||||
if (value instanceof File) {
|
||||
let fileData: SerializedFileData = {
|
||||
path: value.name,
|
||||
size: value.size,
|
||||
last_modified: value.lastModified,
|
||||
content_type: value.type,
|
||||
};
|
||||
contents.push({ key, file: fileData });
|
||||
} else {
|
||||
contents.push({ key, text: value });
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
valid: form.checkValidity(),
|
||||
values: contents
|
||||
};
|
||||
}
|
||||
|
||||
export function retrieveSelectValue(target: HTMLSelectElement): string[] {
|
||||
// there might be multiple...
|
||||
let options = target.selectedOptions;
|
||||
let values = [];
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
values.push(options[i].value);
|
||||
}
|
||||
return values;
|
||||
}
|
||||
Reference in new issue
Block a user