diff --git a/examples/fetch/Main.hs b/examples/fetch/Main.hs index 13b762c2..99675b0b 100644 --- a/examples/fetch/Main.hs +++ b/examples/fetch/Main.hs @@ -68,15 +68,15 @@ type GithubAPI = Get '[JSON] GitHub ---------------------------------------------------------------------------- -- | Uses servant to reify type-safe calls to the Fetch API getGithubAPI - :: (GitHub -> JSM ()) - -- ^ Successful callback - -> (MisoString -> JSM ()) + :: (MisoString -> JSM ()) -- ^ Errorful callback + -> (GitHub -> JSM ()) + -- ^ Successful callback -> JSM () getGithubAPI = fetch (Proxy @GithubAPI) "https://api.github.com" ---------------------------------------------------------------------------- updateModel :: Action -> Effect Model Action -updateModel FetchGitHub = withSink $ \snk -> getGithubAPI (snk . SetGitHub) (snk . ErrorHandler) +updateModel FetchGitHub = withSink $ \snk -> getGithubAPI (snk . ErrorHandler) (snk . SetGitHub) updateModel (SetGitHub apiInfo) = info ?= apiInfo updateModel (ErrorHandler msg) = diff --git a/js/miso.js b/js/miso.js index 75ad5e25..fbf90336 100644 --- a/js/miso.js +++ b/js/miso.js @@ -18,7 +18,7 @@ function callBlur(id, delay) { function setBodyComponent(componentId) { document.body.setAttribute("data-component-id", componentId); } -function fetchJSON(url, method, body, headers, successful, errorful) { +function fetchFFI(url, method, body, headers, successful, errorful) { var options = { method, headers }; if (body) { options["body"] = body; @@ -27,7 +27,7 @@ function fetchJSON(url, method, body, headers, successful, errorful) { if (!response.ok) { throw new Error(response.statusText); } - return response.json(); + return response.bytes(); }).then(successful).catch(errorful); } function shouldSync(node) { @@ -65,14 +65,10 @@ function mkVNode() { tag: "div", key: null, events: {}, - onDestroyed: () => { - }, - onBeforeDestroyed: () => { - }, - onCreated: () => { - }, - onBeforeCreated: () => { - }, + onDestroyed: () => {}, + onBeforeDestroyed: () => {}, + onCreated: () => {}, + onBeforeCreated: () => {}, shouldSync: false, type: "vnode" }; @@ -675,7 +671,7 @@ globalThis["miso"]["delegate"] = delegate; globalThis["miso"]["callBlur"] = callBlur; globalThis["miso"]["callFocus"] = callFocus; globalThis["miso"]["eventJSON"] = eventJSON; -globalThis["miso"]["fetchJSON"] = fetchJSON; +globalThis["miso"]["fetchFFI"] = fetchFFI; globalThis["miso"]["undelegate"] = undelegate; globalThis["miso"]["shouldSync"] = shouldSync; globalThis["miso"]["integrityCheck"] = integrityCheck; diff --git a/js/miso.prod.js b/js/miso.prod.js index cf2e5720..227f2aa8 100644 --- a/js/miso.prod.js +++ b/js/miso.prod.js @@ -1 +1 @@ -function Z(T,E){var V=function(){var N=document.getElementById(T);if(N&&N.focus)N.focus()};E>0?setTimeout(V,E):V()}function _(T,E){var V=function(){var N=document.getElementById(T);if(N&&N.blur)N.blur()};E>0?setTimeout(V,E):V()}function $(T){document.body.setAttribute("data-component-id",T)}function A(T,E,V,N,M,J){var S={method:E,headers:N};if(V)S.body=V;fetch(T,S).then((B)=>{if(!B.ok)throw new Error(B.statusText);return B.json()}).then(M).catch(J)}function q(T){if(T.children.length===0)return!1;var E=!0;for(let V of T.children)if(!V.key){E=!1;break}return E}var W="1.9.0.0";function D(T){var E=c(l(),T);if(!E.shouldSync)E.shouldSync=q(E);return E}function c(T,E){return Object.assign({},T,E)}function l(){return{props:{},css:{},children:[],ns:"html",domRef:null,tag:"div",key:null,events:{},onDestroyed:()=>{},onBeforeDestroyed:()=>{},onCreated:()=>{},onBeforeCreated:()=>{},shouldSync:!1,type:"vnode"}}function R(T,E,V){if(!T&&!E)return;else if(!T&&E)TT(E,V);else if(!E)a(T,V);else if(T.type===E.type)v(T,E,V);else I(T,E,V)}function I(T,E,V){if(O(T),E.type==="vtext")E.domRef=document.createTextNode(E.text),V.replaceChild(E.domRef,T.domRef);else g(E,(N)=>{V.replaceChild(N,T.domRef)});f(T)}function a(T,E){O(T),E.removeChild(T.domRef),f(T)}function v(T,E,V){if(T.type==="vtext"){if(T.text!==E.text)T.domRef.textContent=E.text;E.domRef=T.domRef;return}if(T.tag===E.tag&&E.key===T.key&&E["data-component-id"]===T["data-component-id"])E.domRef=T.domRef,Q(T,E);else I(T,E,V)}function f(T){j(T);for(let E in T.children)f(T.children[E])}function j(T){if(T.onDestroyed)T.onDestroyed();if(T.type==="vcomp")n(T)}function b(T){if(T.onBeforeDestroyed)T.onBeforeDestroyed()}function O(T){if(T.type==="vcomp"&&T.onBeforeUnmounted)T.onBeforeUnmounted();b(T);for(let E in T.children)O(T.children[E])}function h(T){if(T.onCreated)T.onCreated();if(T.type==="vcomp")e(T)}function d(T){if(T.onBeforeCreated)T.onBeforeCreated()}function Q(T,E){if(E.type!=="vtext"){if(!T)T=D({});if(s(T.props,E.props,E.domRef,E.ns==="svg"),r(T.css,E.css,E.domRef),E.type==="vnode")o(T,E,E.domRef)}}function s(T,E,V,N){var M;for(let J in T)if(M=E[J],M===void 0)if(N||!(J in V))V.removeAttribute(T[J]);else V[J]="";else{if(M===T[J]&&J!=="checked"&&J!=="value")continue;if(N)if(J==="href")V.setAttributeNS("http://www.w3.org/1999/xlink","href",M);else V.setAttribute(J,M);else if(J in V&&!(J==="list"||J==="form"))V[J]=M;else V.setAttribute(J,M)}for(let J in E){if(T&&T[J])continue;if(M=E[J],N)if(J==="href")V.setAttributeNS("http://www.w3.org/1999/xlink","href",M);else V.setAttribute(J,M);else if(J in V&&!(J==="list"||J==="form"))V[J]=E[J];else V.setAttribute(J,M)}}function r(T,E,V){var N;for(let M in T)if(N=E[M],!N)V.style[M]="";else if(N!==T[M])V.style[M]=N;for(let M in E){if(T&&T[M])continue;V.style[M]=E[M]}}function o(T,E,V){if(T.shouldSync&&E.shouldSync)ET(T.children,E.children,V);else{let N=E.children.length>T.children.length?E.children.length:T.children.length;for(let M=0;M0){console.error('AlreadyMountedException: Component "'+E+"' is already mounted");return}if(T.domRef.setAttribute("data-component-id",E),T.onBeforeMounted)T.onBeforeMounted();T.mount((N)=>{if(T.children.push(N),T.domRef.appendChild(N.domRef),T.onMounted)T.onMounted()})}function TT(T,E){if(T.type==="vtext")T.domRef=document.createTextNode(T.text),E.appendChild(T.domRef);else g(T,(V)=>{E.appendChild(V)})}function ET(T,E,V){var N=0,M=0,J=T.length-1,S=E.length-1,B,H,K,G,U,Y,w;for(;;){if(M>S&&N>J)break;if(H=E[M],K=E[S],U=T[N],G=T[J],N>J)R(null,H,V),V.insertBefore(H.domRef,U?U.domRef:null),T.splice(M,0,H),M++;else if(M>S){B=J;while(J>=N)V.removeChild(T[J--].domRef);T.splice(N,B-N+1);break}else if(U.key===H.key)R(T[N++],E[M++],V);else if(G.key===K.key)R(T[J--],E[S--],V);else if(U.key===K.key&&H.key===G.key)VT(G.domRef,U.domRef,V),NT(T,N,J),R(T[N++],E[M++],V),R(T[J--],E[S--],V);else if(U.key===K.key)V.insertBefore(U.domRef,G.domRef.nextSibling),T.splice(J,0,T.splice(N,1)[0]),R(T[J--],E[S--],V);else if(G.key===H.key)V.insertBefore(G.domRef,U.domRef),T.splice(N,0,T.splice(J,1)[0]),R(T[N++],H,V),M++;else{Y=!1,B=N;while(B<=J){if(T[B].key===H.key){Y=!0,w=T[B];break}B++}if(Y)T.splice(N,0,T.splice(B,1)[0]),R(T[N++],H,V),V.insertBefore(w.domRef,T[N].domRef),M++;else g(H,(i)=>{V.insertBefore(i,U.domRef)}),T.splice(N++,0,H),M++,J++}}}function VT(T,E,V){let N=T.nextSibling;V.insertBefore(T,E),V.insertBefore(E,N)}function NT(T,E,V){let N=T[E];T[E]=T[V],T[V]=N}function C(T,E,V,N){for(let M of E)T.addEventListener(M.name,function(J){y(J,T,V,N)},M.capture)}function P(T,E,V,N){for(let M of E)T.removeEventListener(M.name,function(J){y(J,T,V,N)},M.capture)}function y(T,E,V,N){V(function(M){if(T.target)k(T,M,MT(E,T.target),[],N)})}function MT(T,E){var V=[];while(T!==E)V.unshift(E),E=E.parentNode;return V}function k(T,E,V,N,M){if(!V.length){if(M)console.warn('Event "'+T.type+'" did not find an event handler to dispatch on',E,T);return}else if(V.length>1){N.unshift(E);for(let J of E.children){if(J.type==="vcomp")continue;if(J.domRef===V[1]){k(T,J,V.slice(1),N,M);break}}}else{let J=E.events[T.type];if(J){let S=J.options;if(S.preventDefault)T.preventDefault();if(J.runEvent(T),!S.stopPropagation)x(N,T)}else x(N,T)}}function x(T,E){for(let V of T)if(V.events[E.type]){let N=V.events[E.type],M=N.options;if(M.preventDefault)E.preventDefault();if(N.runEvent(E),M.stopPropagation){E.stopPropagation();break}}}function z(T,E){if(typeof T[0]==="object"){var V=[];for(var N=0;N0?[T[0]]:[];for(var N=1;N0)V=document.body.firstChild;else V=document.body.appendChild(document.createElement("div"));else if(T.childNodes.length===0)V=T.appendChild(document.createElement("div"));else{while(T.childNodes[E]&&(T.childNodes[E].nodeType===3||T.childNodes[E].localName==="script"))E++;if(!T.childNodes[E])V=document.body.appendChild(document.createElement("div"));else V=T.childNodes[E]}return V}function m(T,E,V){let N=BT(E);if(!F(T,V,N)){if(T)console.warn("Could not copy DOM into virtual DOM, falling back to diff");while(N.firstChild)N.removeChild(N.lastChild);return V.domRef=N,Q(null,V),!1}else if(T)if(!X(V))console.warn("Integrity check completed with errors");else console.info("Successfully prerendered page");return!0}function L(T,E,V){if(T)console.warn("VTree differed from node",E,V)}function u(T){if(T.substr(0,1)=="#"){let E=(T.length-1)/3,V=[17,1,0.062272][E-1];return[Math.round(parseInt(T.substr(1,E),16)*V),Math.round(parseInt(T.substr(1+E,E),16)*V),Math.round(parseInt(T.substr(1+2*E,E),16)*V)]}else return T.split("(")[1].split(")")[0].split(",").map((E)=>{return+E})}function X(T){return p(!0,T)}function p(T,E){if(E.type=="vtext"){if(E.domRef.nodeType!==3)console.warn("VText domRef not a TEXT_NODE",E),T=!1;else if(E.text!==E.domRef.textContent)console.warn("VText node content differs",E),T=!1}else{if(E.tag.toUpperCase()!==E.domRef.tagName)console.warn("Integrity check failed, tags differ",E.tag.toUpperCase(),E.domRef.tagName),T=!1;if("children"in E&&E.children.length!==E.domRef.childNodes.length)console.warn("Integrity check failed, children lengths differ",E,E.children,E.domRef.childNodes),T=!1;for(let V in E.props)if(V==="href"){let N=window.location.origin+"/"+E.props[V],M=E.domRef[V],J=E.props[V];if(N!==M&&J!==M&&J+"/"!==M&&N+"/"!==M)console.warn("Property "+V+" differs",E.props[V],E.domRef[V]),T=!1}else if(V==="height"||V==="width"){if(parseFloat(E.props[V])!==parseFloat(E.domRef[V]))console.warn("Property "+V+" differs",E.props[V],E.domRef[V]),T=!1}else if(V==="class"||V==="className"){if(E.props[V]!==E.domRef.className)console.warn("Property class differs",E.props[V],E.domRef.className),T=!1}else if(!E.domRef[V]){if(E.props[V]!==E.domRef.getAttribute(V))console.warn("Property "+V+" differs",E.props[V],E.domRef.getAttribute(V)),T=!1}else if(E.props[V]!==E.domRef[V])console.warn("Property "+V+" differs",E.props[V],E.domRef[V]),T=!1;for(let V in E.css)if(V==="color"){if(u(E.domRef.style[V]).toString()!==u(E.css[V]).toString())console.warn("Style "+V+" differs",E.css[V],E.domRef.style[V]),T=!1}else if(E.css[V]!==E.domRef.style[V])console.warn("Style "+V+" differs",E.css[V],E.domRef.style[V]),T=!1;for(let V of E.children){let N=p(T,V);T=T&&N}}return T}function F(T,E,V){switch(E.type){case"vtext":E.domRef=V;break;default:E.domRef=V,E.children=ST(E.children),h(E);for(var N=0;N{M.children.push(S),F(T,M,V.childNodes[N])});break;default:if(J.nodeType!==1)return!1;M.domRef=V.childNodes[N],F(T,M,M.domRef)}}}return!0}globalThis.miso={};globalThis.miso.diff=R;globalThis.miso.hydrate=m;globalThis.miso.version=W;globalThis.miso.delegate=C;globalThis.miso.callBlur=_;globalThis.miso.callFocus=Z;globalThis.miso.eventJSON=z;globalThis.miso.fetchJSON=A;globalThis.miso.undelegate=P;globalThis.miso.shouldSync=q;globalThis.miso.integrityCheck=X;globalThis.miso.setBodyComponent=$; +function Z(T,E){var V=function(){var M=document.getElementById(T);if(M&&M.focus)M.focus()};E>0?setTimeout(V,E):V()}function _(T,E){var V=function(){var M=document.getElementById(T);if(M&&M.blur)M.blur()};E>0?setTimeout(V,E):V()}function $(T){document.body.setAttribute("data-component-id",T)}function A(T,E,V,M,N,B){var H={method:E,headers:M};if(V)H.body=V;fetch(T,H).then((R)=>{if(!R.ok)throw new Error(R.statusText);return R.bytes()}).then(N).catch(B)}function q(T){if(T.children.length===0)return!1;var E=!0;for(let V of T.children)if(!V.key){E=!1;break}return E}var W="1.9.0.0";function D(T){var E=c(l(),T);if(!E.shouldSync)E.shouldSync=q(E);return E}function c(T,E){return Object.assign({},T,E)}function l(){return{props:{},css:{},children:[],ns:"html",domRef:null,tag:"div",key:null,events:{},onDestroyed:()=>{},onBeforeDestroyed:()=>{},onCreated:()=>{},onBeforeCreated:()=>{},shouldSync:!1,type:"vnode"}}function G(T,E,V){if(!T&&!E)return;else if(!T&&E)TT(E,V);else if(!E)a(T,V);else if(T.type===E.type)v(T,E,V);else w(T,E,V)}function w(T,E,V){if(h(T),E.type==="vtext")E.domRef=document.createTextNode(E.text),V.replaceChild(E.domRef,T.domRef);else g(E,(M)=>{V.replaceChild(M,T.domRef)});f(T)}function a(T,E){h(T),E.removeChild(T.domRef),f(T)}function v(T,E,V){if(T.type==="vtext"){if(T.text!==E.text)T.domRef.textContent=E.text;E.domRef=T.domRef;return}if(T.tag===E.tag&&E.key===T.key&&E["data-component-id"]===T["data-component-id"])E.domRef=T.domRef,Q(T,E);else w(T,E,V)}function f(T){j(T);for(let E in T.children)f(T.children[E])}function j(T){if(T.onDestroyed)T.onDestroyed();if(T.type==="vcomp")n(T)}function b(T){if(T.onBeforeDestroyed)T.onBeforeDestroyed()}function h(T){if(T.type==="vcomp"&&T.onBeforeUnmounted)T.onBeforeUnmounted();b(T);for(let E in T.children)h(T.children[E])}function O(T){if(T.onCreated)T.onCreated();if(T.type==="vcomp")e(T)}function d(T){if(T.onBeforeCreated)T.onBeforeCreated()}function Q(T,E){if(E.type!=="vtext"){if(!T)T=D({});if(s(T.props,E.props,E.domRef,E.ns==="svg"),r(T.css,E.css,E.domRef),E.type==="vnode")o(T,E,E.domRef)}}function s(T,E,V,M){var N;for(let B in T)if(N=E[B],N===void 0)if(M||!(B in V))V.removeAttribute(T[B]);else V[B]="";else{if(N===T[B]&&B!=="checked"&&B!=="value")continue;if(M)if(B==="href")V.setAttributeNS("http://www.w3.org/1999/xlink","href",N);else V.setAttribute(B,N);else if(B in V&&!(B==="list"||B==="form"))V[B]=N;else V.setAttribute(B,N)}for(let B in E){if(T&&T[B])continue;if(N=E[B],M)if(B==="href")V.setAttributeNS("http://www.w3.org/1999/xlink","href",N);else V.setAttribute(B,N);else if(B in V&&!(B==="list"||B==="form"))V[B]=E[B];else V.setAttribute(B,N)}}function r(T,E,V){var M;for(let N in T)if(M=E[N],!M)V.style[N]="";else if(M!==T[N])V.style[N]=M;for(let N in E){if(T&&T[N])continue;V.style[N]=E[N]}}function o(T,E,V){if(T.shouldSync&&E.shouldSync)ET(T.children,E.children,V);else{let M=E.children.length>T.children.length?E.children.length:T.children.length;for(let N=0;N0){console.error('AlreadyMountedException: Component "'+E+"' is already mounted");return}if(T.domRef.setAttribute("data-component-id",E),T.onBeforeMounted)T.onBeforeMounted();T.mount((M)=>{if(T.children.push(M),T.domRef.appendChild(M.domRef),T.onMounted)T.onMounted()})}function TT(T,E){if(T.type==="vtext")T.domRef=document.createTextNode(T.text),E.appendChild(T.domRef);else g(T,(V)=>{E.appendChild(V)})}function ET(T,E,V){var M=0,N=0,B=T.length-1,H=E.length-1,R,U,K,S,J,Y,I;for(;;){if(N>H&&M>B)break;if(U=E[N],K=E[H],J=T[M],S=T[B],M>B)G(null,U,V),V.insertBefore(U.domRef,J?J.domRef:null),T.splice(N,0,U),N++;else if(N>H){R=B;while(B>=M)V.removeChild(T[B--].domRef);T.splice(M,R-M+1);break}else if(J.key===U.key)G(T[M++],E[N++],V);else if(S.key===K.key)G(T[B--],E[H--],V);else if(J.key===K.key&&U.key===S.key)VT(S.domRef,J.domRef,V),MT(T,M,B),G(T[M++],E[N++],V),G(T[B--],E[H--],V);else if(J.key===K.key)V.insertBefore(J.domRef,S.domRef.nextSibling),T.splice(B,0,T.splice(M,1)[0]),G(T[B--],E[H--],V);else if(S.key===U.key)V.insertBefore(S.domRef,J.domRef),T.splice(M,0,T.splice(B,1)[0]),G(T[M++],U,V),N++;else{Y=!1,R=M;while(R<=B){if(T[R].key===U.key){Y=!0,I=T[R];break}R++}if(Y)T.splice(M,0,T.splice(R,1)[0]),G(T[M++],U,V),V.insertBefore(I.domRef,T[M].domRef),N++;else g(U,(i)=>{V.insertBefore(i,J.domRef)}),T.splice(M++,0,U),N++,B++}}}function VT(T,E,V){let M=T.nextSibling;V.insertBefore(T,E),V.insertBefore(E,M)}function MT(T,E,V){let M=T[E];T[E]=T[V],T[V]=M}function C(T,E,V,M){for(let N of E)T.addEventListener(N.name,function(B){y(B,T,V,M)},N.capture)}function F(T,E,V,M){for(let N of E)T.removeEventListener(N.name,function(B){y(B,T,V,M)},N.capture)}function y(T,E,V,M){V(function(N){if(T.target)k(T,N,NT(E,T.target),[],M)})}function NT(T,E){var V=[];while(T!==E)V.unshift(E),E=E.parentNode;return V}function k(T,E,V,M,N){if(!V.length){if(N)console.warn('Event "'+T.type+'" did not find an event handler to dispatch on',E,T);return}else if(V.length>1){M.unshift(E);for(let B of E.children){if(B.type==="vcomp")continue;if(B.domRef===V[1]){k(T,B,V.slice(1),M,N);break}}}else{let B=E.events[T.type];if(B){let H=B.options;if(H.preventDefault)T.preventDefault();if(B.runEvent(T),!H.stopPropagation)x(M,T)}else x(M,T)}}function x(T,E){for(let V of T)if(V.events[E.type]){let M=V.events[E.type],N=M.options;if(N.preventDefault)E.preventDefault();if(M.runEvent(E),N.stopPropagation){E.stopPropagation();break}}}function z(T,E){if(typeof T[0]==="object"){var V=[];for(var M=0;M0?[T[0]]:[];for(var M=1;M0)V=document.body.firstChild;else V=document.body.appendChild(document.createElement("div"));else if(T.childNodes.length===0)V=T.appendChild(document.createElement("div"));else{while(T.childNodes[E]&&(T.childNodes[E].nodeType===3||T.childNodes[E].localName==="script"))E++;if(!T.childNodes[E])V=document.body.appendChild(document.createElement("div"));else V=T.childNodes[E]}return V}function m(T,E,V){let M=RT(E);if(!L(T,V,M)){if(T)console.warn("Could not copy DOM into virtual DOM, falling back to diff");while(M.firstChild)M.removeChild(M.lastChild);return V.domRef=M,Q(null,V),!1}else if(T)if(!X(V))console.warn("Integrity check completed with errors");else console.info("Successfully prerendered page");return!0}function P(T,E,V){if(T)console.warn("VTree differed from node",E,V)}function u(T){if(T.substr(0,1)=="#"){let E=(T.length-1)/3,V=[17,1,0.062272][E-1];return[Math.round(parseInt(T.substr(1,E),16)*V),Math.round(parseInt(T.substr(1+E,E),16)*V),Math.round(parseInt(T.substr(1+2*E,E),16)*V)]}else return T.split("(")[1].split(")")[0].split(",").map((E)=>{return+E})}function X(T){return p(!0,T)}function p(T,E){if(E.type=="vtext"){if(E.domRef.nodeType!==3)console.warn("VText domRef not a TEXT_NODE",E),T=!1;else if(E.text!==E.domRef.textContent)console.warn("VText node content differs",E),T=!1}else{if(E.tag.toUpperCase()!==E.domRef.tagName)console.warn("Integrity check failed, tags differ",E.tag.toUpperCase(),E.domRef.tagName),T=!1;if("children"in E&&E.children.length!==E.domRef.childNodes.length)console.warn("Integrity check failed, children lengths differ",E,E.children,E.domRef.childNodes),T=!1;for(let V in E.props)if(V==="href"){let M=window.location.origin+"/"+E.props[V],N=E.domRef[V],B=E.props[V];if(M!==N&&B!==N&&B+"/"!==N&&M+"/"!==N)console.warn("Property "+V+" differs",E.props[V],E.domRef[V]),T=!1}else if(V==="height"||V==="width"){if(parseFloat(E.props[V])!==parseFloat(E.domRef[V]))console.warn("Property "+V+" differs",E.props[V],E.domRef[V]),T=!1}else if(V==="class"||V==="className"){if(E.props[V]!==E.domRef.className)console.warn("Property class differs",E.props[V],E.domRef.className),T=!1}else if(!E.domRef[V]){if(E.props[V]!==E.domRef.getAttribute(V))console.warn("Property "+V+" differs",E.props[V],E.domRef.getAttribute(V)),T=!1}else if(E.props[V]!==E.domRef[V])console.warn("Property "+V+" differs",E.props[V],E.domRef[V]),T=!1;for(let V in E.css)if(V==="color"){if(u(E.domRef.style[V]).toString()!==u(E.css[V]).toString())console.warn("Style "+V+" differs",E.css[V],E.domRef.style[V]),T=!1}else if(E.css[V]!==E.domRef.style[V])console.warn("Style "+V+" differs",E.css[V],E.domRef.style[V]),T=!1;for(let V of E.children){let M=p(T,V);T=T&&M}}return T}function L(T,E,V){switch(E.type){case"vtext":E.domRef=V;break;default:E.domRef=V,E.children=HT(E.children),O(E);for(var M=0;M{N.children.push(H),L(T,N,V.childNodes[M])});break;default:if(B.nodeType!==1)return!1;N.domRef=V.childNodes[M],L(T,N,N.domRef)}}}return!0}globalThis.miso={};globalThis.miso.diff=G;globalThis.miso.hydrate=m;globalThis.miso.version=W;globalThis.miso.delegate=C;globalThis.miso.callBlur=_;globalThis.miso.callFocus=Z;globalThis.miso.eventJSON=z;globalThis.miso.fetchFFI=A;globalThis.miso.undelegate=F;globalThis.miso.shouldSync=q;globalThis.miso.integrityCheck=X;globalThis.miso.setBodyComponent=$; diff --git a/miso.cabal b/miso.cabal index 5a639d55..010b82e2 100644 --- a/miso.cabal +++ b/miso.cabal @@ -207,6 +207,7 @@ library mtl < 2.4, network-uri < 2.7, servant < 0.21, + sop-core < 0.6, tagsoup < 0.15, text < 2.2, transformers < 0.7, diff --git a/src/Miso/FFI/Internal.hs b/src/Miso/FFI/Internal.hs index d2eeff72..91c1e061 100644 --- a/src/Miso/FFI/Internal.hs +++ b/src/Miso/FFI/Internal.hs @@ -2,6 +2,7 @@ {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE CPP #-} +{-# LANGUAGE TypeApplications #-} ----------------------------------------------------------------------------- -- | -- Module : Miso.FFI.Internal @@ -55,6 +56,7 @@ module Miso.FFI.Internal , addStyle , addStyleSheet , fetchJSON + , fetchFFI , shouldSync ) where ----------------------------------------------------------------------------- @@ -63,6 +65,7 @@ import Control.Monad (void, forM_) import Control.Monad.IO.Class (liftIO) import Data.Aeson hiding (Object) import qualified Data.Aeson as A +import qualified Data.ByteString.Lazy as BSL import Data.Maybe (isJust) import qualified Data.JSString as JSS #ifdef GHCJS_BOTH @@ -70,6 +73,13 @@ import Language.Javascript.JSaddle #else import Language.Javascript.JSaddle hiding (Success) #endif +#ifdef WASM +import qualified Data.ByteString.Internal as BS (create) +import qualified Data.ByteString as BS +import Foreign.Ptr (Ptr) +#else +import Data.Word (Word8) +#endif import Prelude hiding ((!!)) ----------------------------------------------------------------------------- import Miso.String @@ -424,18 +434,43 @@ fetchJSON -- ^ body -> [(MisoString,MisoString)] -- ^ headers + -> (MisoString -> JSM ()) + -- ^ errorful callback -> (action -> JSM ()) -- ^ successful callback + -> JSM () +fetchJSON url method maybeBody headers = + fetchFFI + eitherDecode + url + method + maybeBody + ( headers + <> [(ms "Content-Type", ms "application/json") | isJust maybeBody] + <> [(ms "Accept", ms $ "application/json")] + ) +fetchFFI + :: (BSL.ByteString -> Either String action) + -> MisoString + -- ^ url + -> MisoString + -- ^ method + -> Maybe MisoString + -- ^ body + -> [(MisoString,MisoString)] + -- ^ headers -> (MisoString -> JSM ()) -- ^ errorful callback + -> (action -> JSM ()) + -- ^ successful callback -> JSM () -fetchJSON url method maybeBody headers successful errorful = do +fetchFFI decoder url method maybeBody headers errorful successful = do successful_ <- toJSVal =<< do asyncCallback1 $ \jval -> - fromJSON <$> fromJSValUnchecked jval >>= \case - Error string -> - error ("fetchJSON: " <> string <> ": decode failure") - Success result -> do + decoder <$> toLazyByteString (JSUint8Array jval) >>= \case + Left string -> + error ("fetch: " <> string <> ": decode failure") + Right result -> successful result errorful_ <- toJSVal =<< do asyncCallback1 $ \jval -> @@ -444,16 +479,12 @@ fetchJSON url method maybeBody headers successful errorful = do url_ <- toJSVal url method_ <- toJSVal method body_ <- toJSVal maybeBody - let jsonHeaders = - [(ms "Content-Type", ms "application/json") | isJust maybeBody] - <> - [(ms "Accept", ms "application/json")] Object headers_ <- do o <- create - forM_ (headers <> jsonHeaders) $ \(k,v) -> do + forM_ headers $ \(k,v) -> do set k v o pure o - void $ moduleMiso # "fetchJSON" $ [url_, method_, body_, headers_, successful_, errorful_] + void $ moduleMiso # "fetchFFI" $ [url_, method_, body_, headers_, successful_, errorful_] ----------------------------------------------------------------------------- -- | shouldSync -- @@ -468,3 +499,30 @@ shouldSync vnode = do fromJSValUnchecked =<< do moduleMiso # "shouldSync" $ [vnode] ----------------------------------------------------------------------------- +newtype JSUint8Array = JSUint8Array JSVal + +#ifdef WASM + +foreign import javascript unsafe "$1.byteLength" + byteLength :: JSUint8Array -> Int + +foreign import javascript unsafe "(new Uint8Array(__exports.memory.buffer, $1, $2)).set($3)" + memorySetUint8Array :: Ptr a -> Int -> JSUint8Array -> IO () + +toStrictByteString :: JSUint8Array -> JSM BS.ByteString +toStrictByteString src_buf = liftIO $ do + let len = byteLength src_buf + case len of + 0 -> pure BS.empty + _ -> BS.create len $ \ptr -> memorySetUint8Array ptr len src_buf + +toLazyByteString :: JSUint8Array -> JSM BSL.ByteString +toLazyByteString = fmap BSL.fromStrict . toStrictByteString + +#else + +toLazyByteString :: JSUint8Array -> JSM BSL.ByteString +toLazyByteString = fmap BSL.pack . fromJSValUnchecked @[Word8] . \(JSUint8Array v) -> v + +#endif +----------------------------------------------------------------------------- diff --git a/src/Miso/Fetch.hs b/src/Miso/Fetch.hs index 85870c26..9beee51e 100644 --- a/src/Miso/Fetch.hs +++ b/src/Miso/Fetch.hs @@ -10,6 +10,8 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE LambdaCase #-} ----------------------------------------------------------------------------- -- | -- Module : Miso.Fetch @@ -59,15 +61,22 @@ module Miso.Fetch , fetchJSON ) where ----------------------------------------------------------------------------- -import Data.Aeson +import Data.Bifunctor (bimap) +import Data.Bifunctor (first) +import qualified Data.ByteString.Lazy as BSL +import Data.Either (partitionEithers) import Data.Kind (Type) -import Data.Proxy (Proxy(..)) +import Data.Proxy (Proxy (Proxy)) +import Data.SOP (All, (:.:) (Comp), NS (S), I (I)) +import Data.SOP.NP (NP (..), cpure_NP) import GHC.TypeLits import Language.Javascript.JSaddle (JSM) +import Network.HTTP.Media (renderHeader, MediaType) import Servant.API +import Servant.API.ContentTypes import Servant.API.Modifiers ----------------------------------------------------------------------------- -import Miso.FFI.Internal (fetchJSON) +import Miso.FFI.Internal (fetchJSON, fetchFFI) import Miso.Lens import Miso.String (MisoString, ms) import qualified Miso.String as MS @@ -112,6 +121,21 @@ defaultFetchOptions , _body = Nothing } ----------------------------------------------------------------------------- +optionsToUrl :: FetchOptions -> MisoString +optionsToUrl options = options ^. baseUrl <> options ^. currentPath <> params <> flags + where + params = MS.concat + [ mconcat + [ ms "?" + , MS.intercalate (ms "&") + [ k <> ms "=" <> v + | (k,v) <- options ^. queryParams + ] + ] + | not $ null (options ^. queryParams) + ] + flags = MS.mconcat [ ms "?" <> k | k <- options ^. queryFlags ] +----------------------------------------------------------------------------- class Fetch (api :: Type) where type ToFetch api :: Type fetch :: Proxy api -> MisoString -> ToFetch api @@ -132,8 +156,8 @@ instance (Fetch api, KnownSymbol path) => Fetch (path :> api) where options_ :: FetchOptions options_ = options & currentPath %~ (<> ms "/" <> path) ----------------------------------------------------------------------------- -instance (ToHttpApiData a, Fetch api, KnownSymbol path) => Fetch (Capture path a :> api) where - type ToFetch (Capture path a :> api) = a -> ToFetch api +instance (ToHttpApiData a, Fetch api, KnownSymbol path) => Fetch (Capture' mods path a :> api) where + type ToFetch (Capture' mods path a :> api) = a -> ToFetch api fetchWith Proxy options arg = fetchWith (Proxy @api) options_ where options_ :: FetchOptions @@ -143,12 +167,15 @@ instance (ToHttpApiData a, Fetch api, SBoolI (FoldRequired mods), KnownSymbol na type ToFetch (QueryParam' mods name a :> api) = RequiredArgument mods a -> ToFetch api fetchWith Proxy options arg = fetchWith (Proxy @api) options_ where - param (x :: a) = [(ms $ symbolVal (Proxy @name), ms (enc x))] + param :: a -> [(MisoString, MisoString)] + param x = [(ms $ symbolVal (Proxy @name), ms (enc x))] + #if MIN_VERSION_http_api_data(0,5,1) enc = toEncodedQueryParam #else enc = toEncodedUrlPiece #endif + options_ :: FetchOptions options_ = options & queryParams <>~ foldRequiredArgument (Proxy @mods) param (foldMap param) arg ----------------------------------------------------------------------------- @@ -157,41 +184,92 @@ instance (Fetch api, KnownSymbol name) => Fetch (QueryFlag name :> api) where fetchWith Proxy options flag = fetchWith (Proxy @api) options_ where options_ :: FetchOptions - options_ = options & queryFlags <>~ [ ms $ symbolVal (Proxy @name) | flag ] + options_ = options & queryFlags <>~ [ms $ symbolVal (Proxy @name) | flag] ----------------------------------------------------------------------------- -instance (ToJSON a, Fetch api) => Fetch (ReqBody '[JSON] a :> api) where - type ToFetch (ReqBody '[JSON] a :> api) = a -> ToFetch api - fetchWith Proxy options body_ = fetchWith (Proxy @api) (options_ (ms (encode body_))) +instance (MimeRender ct a, Fetch api) => Fetch (ReqBody' mods (ct ': cts) a :> api) where + type ToFetch (ReqBody' mods (ct ': cts) a :> api) = a -> ToFetch api + fetchWith Proxy options body_ = fetchWith (Proxy @api) options_ where - options_ :: MisoString -> FetchOptions - options_ b = options & body ?~ b + ctProxy :: Proxy ct + ctProxy = Proxy + + options_ :: FetchOptions + options_ = options + & body ?~ (ms (mimeRender ctProxy body_)) + & headers <>~ [(ms "Content-Type", ms $ renderHeader $ contentType ctProxy)] ----------------------------------------------------------------------------- -instance (KnownSymbol name, ToHttpApiData a, Fetch api) => Fetch (Header name a :> api) where - type ToFetch (Header name a :> api) = a -> ToFetch api - fetchWith Proxy options value = fetchWith (Proxy @api) o +instance (KnownSymbol name, ToHttpApiData a, Fetch api, SBoolI (FoldRequired mods)) => Fetch (Header' mods name a :> api) where + type ToFetch (Header' mods name a :> api) = RequiredArgument mods a -> ToFetch api + fetchWith Proxy options value = fetchWith (Proxy @api) options_ where headerName :: MisoString headerName = ms $ symbolVal (Proxy @name) - o :: FetchOptions - o = options & headers <>~ [ (headerName, ms (toHeader value)) ] + header :: a -> [(MisoString, MisoString)] + header x = [(headerName, ms (toHeader x))] + + options_ :: FetchOptions + options_ = options & headers <>~ foldRequiredArgument (Proxy @mods) header (foldMap header) value ----------------------------------------------------------------------------- -instance (ReflectMethod method, FromJSON a) => Fetch (Verb method code content a) where - type ToFetch (Verb method code content a) = (a -> JSM()) -> (MisoString -> JSM ()) -> JSM () - fetchWith Proxy options success_ error_ = - fetchJSON url method (options ^. body) (options ^. headers) success_ error_ +instance {-# OVERLAPPABLE #-} (ReflectMethod method, MimeUnrender ct a, cts' ~ (ct ': cts)) => Fetch (Verb method code cts' a) where + type ToFetch (Verb method code cts' a) = (MisoString -> JSM ()) -> (a -> JSM ()) -> JSM () + fetchWith Proxy options error_ success_ = + fetchFFI + (mimeUnrender ctProxy) + (optionsToUrl options) + (ms $ reflectMethod (Proxy @method)) + (options ^. body) + (options ^. headers <> [(ms "Accept", ms $ renderHeader $ contentType ctProxy)]) + error_ + success_ where - method = ms (reflectMethod (Proxy @method)) - params = MS.concat - [ mconcat - [ ms "?" - , MS.intercalate (ms "&") - [ k <> ms "=" <> v - | (k,v) <- options ^. queryParams - ] - ] - | not $ null (options ^. queryParams) - ] - flags = MS.mconcat [ ms "?" <> k | k <- options ^. queryFlags ] - url = options ^. baseUrl <> options ^. currentPath <> params <> flags + ctProxy = Proxy @ct +instance {-# OVERLAPPING #-} (ReflectMethod method) => Fetch (Verb method code cts NoContent) where + type ToFetch (Verb method code cts NoContent) = (MisoString -> JSM ()) -> (NoContent -> JSM ()) -> JSM () + fetchWith Proxy = fetchNoContent $ Proxy @method +#if MIN_VERSION_servant(0,17,0) +instance (ReflectMethod method) => Fetch (NoContentVerb method) where + type ToFetch (NoContentVerb method) = (MisoString -> JSM ()) -> (NoContent -> JSM ()) -> JSM () + fetchWith Proxy = fetchNoContent $ Proxy @method +#endif +fetchNoContent :: ReflectMethod method => Proxy method -> FetchOptions -> (MisoString -> JSM ()) -> (NoContent -> JSM ()) -> JSM () +fetchNoContent proxy_ options error_ success_ = + fetchFFI + (const $ pure NoContent) + (optionsToUrl options) + (ms $ reflectMethod proxy_) + (options ^. body) + (options ^. headers) + error_ + success_ +----------------------------------------------------------------------------- +#if MIN_VERSION_servant(0,18,1) +instance + ( AllMime contentTypes, + ReflectMethod method, + All (AllMimeUnrender contentTypes) as + ) => + Fetch (UVerb method contentTypes as) + where + type ToFetch (UVerb method contentTypes as) = (MisoString -> JSM ()) -> (Union as -> JSM ()) -> JSM () + fetchWith Proxy options error_ success_ = + fetchFFI + (first unlines . tryParsers . mimeUnrenders) + (optionsToUrl options) + (ms $ reflectMethod (Proxy @method)) + (options ^. body) + (options ^. headers <> map ((ms "Accept",) . ms . renderHeader) (allMime $ Proxy @contentTypes)) + error_ + success_ + where + mimeUnrenders :: BSL.ByteString -> NP ([] :.: Either (MediaType, String)) as + mimeUnrenders body_ = cpure_NP (Proxy @(AllMimeUnrender contentTypes)) + $ Comp $ allMimeUnrender (Proxy @contentTypes) <&> \(t, f) -> first (t,) $ f body_ + tryParsers :: NP ([] :.: Either (MediaType, String)) xs -> Either [String] (Union xs) + tryParsers = \case + Nil -> Left ["no parsers"] + Comp x :* xs -> case partitionEithers x of + (err', []) -> bimap (map (\(t, s) -> show t <> ": " <> s) err' <>) S $ tryParsers xs + (_, (res : _)) -> Right . inject . I $ res +#endif ----------------------------------------------------------------------------- diff --git a/ts/index.ts b/ts/index.ts index bfbca97e..f0a6b750 100644 --- a/ts/index.ts +++ b/ts/index.ts @@ -6,7 +6,7 @@ import { callBlur, callFocus, eventJSON, - fetchJSON, + fetchFFI, undelegate, shouldSync, integrityCheck, @@ -22,7 +22,7 @@ globalThis['miso']['delegate'] = delegate; globalThis['miso']['callBlur'] = callBlur; globalThis['miso']['callFocus'] = callFocus; globalThis['miso']['eventJSON'] = eventJSON; -globalThis['miso']['fetchJSON'] = fetchJSON; +globalThis['miso']['fetchFFI'] = fetchFFI; globalThis['miso']['undelegate'] = undelegate; globalThis['miso']['shouldSync'] = shouldSync; globalThis['miso']['integrityCheck'] = integrityCheck; diff --git a/ts/miso.ts b/ts/miso.ts index 902d171c..29930f60 100644 --- a/ts/miso.ts +++ b/ts/miso.ts @@ -1,7 +1,7 @@ import { diff } from './miso/dom'; import { delegate, undelegate, eventJSON } from './miso/event'; import { hydrate, integrityCheck } from './miso/hydrate'; -import { shouldSync, version, callFocus, callBlur, setBodyComponent, fetchJSON } from './miso/util'; +import { shouldSync, version, callFocus, callBlur, setBodyComponent, fetchFFI } from './miso/util'; import { VTree, VNode, VText, VComp, Props, CSS, Events, NS, DOMRef, EventCapture, EventObject, Options } from './miso/types'; import { vcomp, vnode, vtext } from './miso/smart'; @@ -15,7 +15,7 @@ export { callBlur, callFocus, eventJSON, - fetchJSON, + fetchFFI, undelegate, integrityCheck, setBodyComponent, diff --git a/ts/miso/util.ts b/ts/miso/util.ts index e92fb8cd..490cbe4e 100644 --- a/ts/miso/util.ts +++ b/ts/miso/util.ts @@ -21,7 +21,7 @@ export function setBodyComponent(componentId: string): void { document.body.setAttribute('data-component-id', componentId); } -export function fetchJSON ( +export function fetchFFI ( url : string, method : string, body : any, @@ -39,7 +39,7 @@ export function fetchJSON ( if (!response.ok) { throw new Error(response.statusText); } - return response.json(); + return response.bytes(); }) .then(successful) /* success callback */ .catch(errorful); /* error callback */