From b7b1a3025c0a325a7c5c1eb4fbab1ca0d246556f Mon Sep 17 00:00:00 2001 From: Sean Hess Date: Wed, 5 Nov 2025 11:01:17 -0700 Subject: [PATCH] ViewState fix state updates js cleanup --- client/dist/action.d.ts | 5 +- client/dist/hyperbole.js | 2 +- client/dist/hyperbole.js.map | 2 +- client/dist/message.d.ts | 1 + client/src/action.ts | 14 +- client/src/index.ts | 34 ++-- client/src/message.ts | 1 + examples/Example/App.hs | 4 +- examples/Example/AppRoute.hs | 2 + examples/Example/Page/Advanced.hs | 2 +- examples/Example/Page/CSS.hs | 2 +- examples/Example/Page/Contacts.hs | 6 +- examples/Example/Page/FormSimple.hs | 10 +- examples/Example/Page/Intro.hs | 4 +- examples/Example/Page/State/Actions.hs | 2 +- examples/Example/Page/State/Effects.hs | 14 +- examples/Example/Page/State/View.hs | 47 ++++++ examples/Example/Page/Todos/Todo.hs | 4 +- examples/Example/Page/Todos/TodoCSS.hs | 11 +- examples/Example/Style/Cyber.hs | 8 +- examples/Example/View/Layout.hs | 1 + examples/examples.cabal | 1 + hyperbole.cabal | 4 +- src/Web/Hyperbole.hs | 3 +- src/Web/Hyperbole/Data/Encoded.hs | 28 +++- src/Web/Hyperbole/Data/Param.hs | 14 +- src/Web/Hyperbole/Document.hs | 4 +- src/Web/Hyperbole/Effect/Client.hs | 3 +- src/Web/Hyperbole/Effect/Hyperbole.hs | 4 +- src/Web/Hyperbole/Effect/Response.hs | 36 ++-- src/Web/Hyperbole/HyperView.hs | 4 - src/Web/Hyperbole/HyperView/Event.hs | 33 ++-- src/Web/Hyperbole/HyperView/Forms.hs | 158 +++++------------- src/Web/Hyperbole/HyperView/Handled.hs | 15 +- src/Web/Hyperbole/HyperView/Hyper.hs | 31 +++- src/Web/Hyperbole/HyperView/Input.hs | 30 ++-- src/Web/Hyperbole/HyperView/Types.hs | 22 ++- src/Web/Hyperbole/HyperView/ViewId.hs | 56 ------- src/Web/Hyperbole/Page.hs | 2 +- src/Web/Hyperbole/Server/Handler.hs | 28 ++-- src/Web/Hyperbole/Server/Message.hs | 40 ++++- src/Web/Hyperbole/Server/Options.hs | 13 +- src/Web/Hyperbole/Server/Socket.hs | 29 ++-- src/Web/Hyperbole/Server/Wai.hs | 20 ++- src/Web/Hyperbole/Types/Event.hs | 7 +- src/Web/Hyperbole/Types/Request.hs | 2 +- src/Web/Hyperbole/Types/Response.hs | 13 +- src/Web/Hyperbole/View.hs | 6 +- src/Web/Hyperbole/View/Render.hs | 12 +- src/Web/Hyperbole/View/Tag.hs | 27 +++ src/Web/Hyperbole/View/Types.hs | 105 +++++++----- .../{HyperView => View}/ViewAction.hs | 2 +- src/Web/Hyperbole/View/ViewId.hs | 33 ++++ 53 files changed, 536 insertions(+), 425 deletions(-) create mode 100644 examples/Example/Page/State/View.hs delete mode 100644 src/Web/Hyperbole/HyperView/ViewId.hs rename src/Web/Hyperbole/{HyperView => View}/ViewAction.hs (94%) create mode 100644 src/Web/Hyperbole/View/ViewId.hs diff --git a/client/dist/action.d.ts b/client/dist/action.d.ts index 990b4239..e6db324a 100644 --- a/client/dist/action.d.ts +++ b/client/dist/action.d.ts @@ -1,12 +1,13 @@ -import { Meta, ViewId, RequestId, EncodedAction } from "./message"; +import { Meta, ViewId, RequestId, EncodedAction, ViewState } from "./message"; export type ActionMessage = { viewId: ViewId; action: EncodedAction; requestId: RequestId; + state?: ViewState; meta: Meta[]; form: URLSearchParams | undefined; }; -export declare function actionMessage(id: ViewId, action: EncodedAction, reqId: RequestId, form?: FormData): ActionMessage; +export declare function actionMessage(id: ViewId, action: EncodedAction, state: ViewState | undefined, reqId: RequestId, form?: FormData): ActionMessage; export declare function toSearch(form?: FormData): URLSearchParams | undefined; export declare function renderActionMessage(msg: ActionMessage): string; export declare function renderForm(form: URLSearchParams | undefined): string; diff --git a/client/dist/hyperbole.js b/client/dist/hyperbole.js index e42b9b74..999a9b6b 100644 --- a/client/dist/hyperbole.js +++ b/client/dist/hyperbole.js @@ -1,3 +1,3 @@ /*! For license information please see hyperbole.js.LICENSE.txt */ -(()=>{var e={296:e=>{function t(e,t=100,n={}){if("function"!=typeof e)throw new TypeError(`Expected the first parameter to be a function, got \`${typeof e}\`.`);if(t<0)throw new RangeError("`wait` must not be negative.");const{immediate:o}="boolean"==typeof n?{immediate:n}:n;let r,i,a,c,s;function u(){const t=r,n=i;return r=void 0,i=void 0,s=e.apply(t,n),s}function l(){const e=Date.now()-c;e=0?a=setTimeout(l,t-e):(a=void 0,o||(s=u()))}const d=function(...e){if(r&&this!==r&&Object.getPrototypeOf(this)===Object.getPrototypeOf(r))throw new Error("Debounced method called with different contexts of the same prototype.");r=this,i=e,c=Date.now();const n=o&&!a;return a||(a=setTimeout(l,t)),n&&(s=u()),s};return Object.defineProperty(d,"isPending",{get:()=>void 0!==a}),d.clear=()=>{a&&(clearTimeout(a),a=void 0)},d.flush=()=>{a&&d.trigger()},d.trigger=()=>{s=u(),d.clear()},d}e.exports.debounce=t,e.exports=t},147:e=>{"use strict";e.exports=JSON.parse('{"name":"web-ui","version":"0.6.0","description":"Development -----------","main":"index.js","directories":{"client":"client"},"scripts":{"build":"npx webpack"},"author":"","license":"ISC","devDependencies":{"ts-loader":"^9.4.1","typescript":"^4.8.3","uglify":"^0.1.5","webpack":"^5.88.2","webpack-cli":"^4.10.0"},"dependencies":{"omdomdom":"^0.3.2","debounce":"^2.2.0"}}')}},t={};function n(o){var r=t[o];if(void 0!==r)return r.exports;var i=t[o]={exports:{}};return e[o](i,i.exports,n),i.exports}(()=>{"use strict";var e=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t=function(e,t){var n=e.length,o=-1;if(n)for(;++oe.length)&&(t=e.length);for(var n=0,o=new Array(t);n0)for(;d>0;)r.node.removeChild(r.node.childNodes[l-1]),l--,d--}}(r,i,n)}},y=function n(o){var r,i=arguments.length>1&&void 0!==arguments[1]&&arguments[1];"string"==typeof o&&(r=o.trim().replace(/\s+\s+/g,">"),o=(new DOMParser).parseFromString(r,"text/html").body);var a="BODY"===o.tagName,c=o.childNodes,s=c?c.length:0;if(a){if(s>1)throw new Error("[OmDomDom]: Your element should not have more than one root node.");if(0===s)throw new Error("[OmDomDom]: Your element should have at least one root node.");return n(c[0])}var l=3===o.nodeType?"text":8===o.nodeType?"comment":o.tagName.toLowerCase(),d=i||"svg"===l,f=1===o.nodeType?function(t){var n=function(t){return Array.prototype.reduce.call(t.attributes,(function(t,n){return e(u,n.name)||(t[n.name]=n.value),t}),{})}(t);return function(e,t){for(var n in u){var o=u[n].propName,r=e.getAttribute(n);n===u.style.attrName?t[n]=e.style[o]:"string"==typeof r&&(t[n]=r)}}(t,n),n}(o):{},v=s>0?null:o.textContent,p=Array(s);return t(c,(function(e,t){p[t]=n(e,d)})),{type:l,attributes:f,children:p,content:v,node:o,isSVGContext:d}};function w(e,t){var n=[];for(var o of t){let t=e(o);if(!t)break;n.push(t)}return n}function g(e){return{cookies:e.filter((e=>"Cookie"==e.key)).map((e=>e.value)),error:E("Error",e),query:E("Query",e),pageTitle:E("PageTitle",e),events:q("Event",e).map(k),actions:q("Trigger",e).map(I)}}function b(e){return g(w(C,e.trim().split("\n")))}function E(e,t){return t.find((t=>t.key==e))?.value}function q(e,t){return t.filter((t=>t.key==e)).map((e=>e.value))}function C(e){let t=e.match(/^(\w+)\: (.*)$/);if(t)return{key:t[1],value:t[2]}}function k(e){let[t,n]=A(e);return{name:t,detail:JSON.parse(n)}}function I(e){let[t,n]=A(e);return[t,n]}function A(e){let t=e.indexOf("|");if(-1===t){let t=new Error("Bad Encoding, Expected Segment");throw t.message=e,t}return[e.slice(0,t),e.slice(t+1)]}function x(e){if(!e)return;const t=new URLSearchParams;return e.forEach(((e,n)=>{t.append(n,e)})),t}let S=0;function T(e,t){return e+" "+function(e){return""==e?"|":e.replace(/_/g,"\\_").replace(/\s+/g,"_")}(t)}const L=`${"https:"===window.location.protocol?"wss:":"ws:"}//${window.location.host}${window.location.pathname}`;class D extends EventTarget{constructor(){super(...arguments),this.hasEverConnected=!1,this.isConnected=!1,this.reconnectDelay=0,this.queue=[]}connect(e=L){const t=new WebSocket(e);function n(e){console.error("Connect Error",e)}function o(e){console.error("Socket Error",e)}this.socket=t,t.addEventListener("error",n),t.addEventListener("open",(e=>{console.log("Websocket Connected"),this.hasEverConnected&&document.dispatchEvent(new Event("hyp-socket-reconnect")),this.isConnected=!0,this.hasEverConnected=!0,this.reconnectDelay=1e3,t.removeEventListener("error",n),t.addEventListener("error",o),document.dispatchEvent(new Event("hyp-socket-connect")),this.runQueue()})),t.addEventListener("close",(n=>{this.isConnected&&document.dispatchEvent(new Event("hyp-socket-disconnect")),this.isConnected=!1,t.removeEventListener("error",o),this.hasEverConnected&&(console.log("Reconnecting in "+this.reconnectDelay/1e3+"s"),setTimeout((()=>this.connect(e)),this.reconnectDelay)),t.removeEventListener("error",o)})),t.addEventListener("message",(e=>this.onMessage(e)))}async sendAction(e){if(this.isConnected){let t=function(e){return[["|ACTION|","ViewId: "+e.viewId,"Action: "+e.action,"RequestId: "+e.requestId].join("\n"),(n=e.meta,n.map((e=>e.key+": "+e.value)).join("\n"))].join("\n")+((t=e.form)?"\n\n"+t:"");var t,n}(e);this.socket.send(t)}else this.queue.push(e)}runQueue(){let e=this.queue.pop();e&&(console.log("runQueue: ",e),this.sendAction(e),this.runQueue())}onMessage(e){let{command:t,metas:n,rest:o}=function(e){let t=e.split("\n"),n=t[0],o=w(C,t.slice(1));return{command:n,metas:o,rest:function(e,t){let n=0;for(;n{let t=parseInt(e.dataset.delay)||0,n=e.dataset.onload;setTimeout((()=>{let t=B(e);if(e.dataset.onload!=n)return;const o=new CustomEvent("hyp-load",{bubbles:!0,detail:{target:t,onLoad:n}});e.dispatchEvent(o)}),t)}))}function P(e){e.querySelectorAll("[data-onmouseenter]").forEach((e=>{let t=e.dataset.onmouseenter,n=B(e);e.onmouseenter=()=>{const o=new CustomEvent("hyp-mouseenter",{bubbles:!0,detail:{target:n,onMouseEnter:t}});e.dispatchEvent(o)}}))}function V(e){e.querySelectorAll("[data-onmouseleave]").forEach((e=>{let t=e.dataset.onmouseleave,n=B(e);e.onmouseleave=()=>{const o=new CustomEvent("hyp-mouseleave",{bubbles:!0,detail:{target:n,onMouseLeave:t}});e.dispatchEvent(o)}}))}function B(e){let t=function(e){let t=e.closest("[data-target]");return t?.dataset.target||e.closest("[id]")?.id}(e),n=document.getElementById(t);if(n)return n;console.error("Cannot find target: ",t,e)}let U,Q=n(147);console.log("Hyperbole "+Q.version);let $=new Set;async function H(e,t,n){if(void 0===e)return void console.error("Undefined HyperView!",t);if(void 0===t)return void console.error("Undefined Action!",e.id);if(e.activeRequest&&!e.activeRequest?.isCancelled&&"Drop"==e.concurrency)return void console.warn("Drop action overlapping with active request ("+e.activeRequest+")",t);e._timeout=setTimeout((()=>{e.classList.add("hyp-loading")}),100);let o={requestId:++S,isCancelled:!1},r=function(e,t,n,o){return{viewId:e,action:t,requestId:n,meta:[{key:"Cookie",value:decodeURI(document.cookie)},{key:"Query",value:window.location.search}],form:x(o)}}(e.id,t,o.requestId,n);e.activeRequest=o,Y.sendAction(r)}function W(e){let t=e.targetViewId||e.viewId,n=document.getElementById(t);if(!n)return console.error("Missing Update Target: ",t,e),n;if(e.requestId{let t=e.getAttribute("value");void 0!==t&&(e.value=t)})),e.querySelectorAll("input[type=checkbox]").forEach((e=>{let t="True"==e.dataset.checked;e.checked=t}))}(a),F(a)):console.warn("Target Missing: ",n.id),n}function _(e,t){for(var n of(null!=e.query&&function(e){if(e!=function(){const e=window.location.search;return e.startsWith("?")?e.substring(1):e}()){""!=e&&(e="?"+e);let t=location.pathname+e;window.history.replaceState({},"",t)}}(e.query),null!=e.pageTitle&&(document.title=e.pageTitle),e.events))setTimeout((()=>{let e=new CustomEvent(n.name,{bubbles:!0,detail:n.detail});(t||document).dispatchEvent(e)}),10);e.actions.forEach((([e,t])=>{setTimeout((()=>{let n=window.Hyperbole.hyperView(e);n&&H(n,t)}),10)}))}function F(e){e.querySelectorAll("[id]").forEach((t=>{t.runAction=function(e){H(this,e)}.bind(t),t.concurrency=t.dataset.concurrency||"Drop",t.cancelActiveRequest=function(){t.activeRequest&&!t.activeRequest?.isCancelled&&(t.activeRequest.isCancelled=!0)},J(e)}))}function J(e){let t=new Event("hyp-content",{bubbles:!0});e.dispatchEvent(t)}document.addEventListener("DOMContentLoaded",(function(){var e;_(b(document.getElementById("hyp.metadata").innerText)),U=document.querySelector("style"),e=async function(e,t){H(e,t)},document.addEventListener("hyp-load",(function(t){let n=t.detail.onLoad,o=t.detail.target;e(o,n)})),document.addEventListener("hyp-mouseenter",(function(t){let n=t.detail.onMouseEnter,o=t.detail.target;e(o,n)})),document.addEventListener("hyp-mouseleave",(function(t){let n=t.detail.onMouseLeave,o=t.detail.target;e(o,n)})),j(document.body),P(document.body),V(document.body),F(document.body),M("click",(async function(e,t){H(e,t)})),M("dblclick",(async function(e,t){H(e,t)})),N("keydown",(async function(e,t){H(e,t)})),N("keyup",(async function(e,t){H(e,t)})),document.addEventListener("submit",(function(e){let t=e.target;if(!t?.dataset.onsubmit)return void console.error("Missing onSubmit: ",t);e.preventDefault();let n=B(t);const o=new FormData(t);!async function(e,t,n){H(e,t,n)}(n,t.dataset.onsubmit,o)})),document.addEventListener("change",(function(e){let t=e.target.closest("[data-onchange]");t&&(e.preventDefault(),null!=t.value?async function(e,t){H(e,t)}(B(t),T(t.dataset.onchange,t.value)):console.error("Missing input value:",t))})),document.addEventListener("input",(function(e){let t=e.target.closest("[data-oninput]");if(!t)return;let n=parseInt(t.dataset.delay)||250;if(n<250&&console.warn("Input delay < 250 can result in poor performance."),!t?.dataset.oninput)return void console.error("Missing onInput: ",t);e.preventDefault();let o=B(t);(function(e){"Replace"==e.concurrency&&e.cancelActiveRequest()})(o),t.debouncedCallback||(t.debouncedCallback=O((()=>{let e=T(t.dataset.oninput,t.value);!async function(e,t){H(e,t)}(o,e)}),n)),t.debouncedCallback()}))}));const Y=new D;Y.connect(),Y.addEventListener("update",(e=>W(e.detail))),Y.addEventListener("response",(e=>function(e){let t=W(e);delete t.activeRequest,clearTimeout(t._timeout),t.classList.remove("hyp-loading")}(e.detail))),Y.addEventListener("redirect",(e=>{return t=e.detail,console.log("REDIRECT",t),void(window.location.href=t.url);var t})),window.Hyperbole={runAction:H,parseMetadata:b,action:function(e,...t){return e+t.reduce(((e,t)=>e+" "+JSON.stringify(t)),"")},hyperView:function(e){let t=document.getElementById(e);if(t?.runAction)return t;console.error("Element id="+e+" was not a HyperView")},socket:Y}})()})(); +(()=>{var e={296:e=>{function t(e,t=100,n={}){if("function"!=typeof e)throw new TypeError(`Expected the first parameter to be a function, got \`${typeof e}\`.`);if(t<0)throw new RangeError("`wait` must not be negative.");const{immediate:o}="boolean"==typeof n?{immediate:n}:n;let r,i,a,s,c;function u(){const t=r,n=i;return r=void 0,i=void 0,c=e.apply(t,n),c}function l(){const e=Date.now()-s;e=0?a=setTimeout(l,t-e):(a=void 0,o||(c=u()))}const d=function(...e){if(r&&this!==r&&Object.getPrototypeOf(this)===Object.getPrototypeOf(r))throw new Error("Debounced method called with different contexts of the same prototype.");r=this,i=e,s=Date.now();const n=o&&!a;return a||(a=setTimeout(l,t)),n&&(c=u()),c};return Object.defineProperty(d,"isPending",{get:()=>void 0!==a}),d.clear=()=>{a&&(clearTimeout(a),a=void 0)},d.flush=()=>{a&&d.trigger()},d.trigger=()=>{c=u(),d.clear()},d}e.exports.debounce=t,e.exports=t},147:e=>{"use strict";e.exports=JSON.parse('{"name":"web-ui","version":"0.6.0","description":"Development -----------","main":"index.js","directories":{"client":"client"},"scripts":{"build":"npx webpack"},"author":"","license":"ISC","devDependencies":{"ts-loader":"^9.4.1","typescript":"^4.8.3","uglify":"^0.1.5","webpack":"^5.88.2","webpack-cli":"^4.10.0"},"dependencies":{"omdomdom":"^0.3.2","debounce":"^2.2.0"}}')}},t={};function n(o){var r=t[o];if(void 0!==r)return r.exports;var i=t[o]={exports:{}};return e[o](i,i.exports,n),i.exports}(()=>{"use strict";var e=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t=function(e,t){var n=e.length,o=-1;if(n)for(;++oe.length)&&(t=e.length);for(var n=0,o=new Array(t);n0)for(;d>0;)r.node.removeChild(r.node.childNodes[l-1]),l--,d--}}(r,i,n)}},y=function n(o){var r,i=arguments.length>1&&void 0!==arguments[1]&&arguments[1];"string"==typeof o&&(r=o.trim().replace(/\s+\s+/g,">"),o=(new DOMParser).parseFromString(r,"text/html").body);var a="BODY"===o.tagName,s=o.childNodes,c=s?s.length:0;if(a){if(c>1)throw new Error("[OmDomDom]: Your element should not have more than one root node.");if(0===c)throw new Error("[OmDomDom]: Your element should have at least one root node.");return n(s[0])}var l=3===o.nodeType?"text":8===o.nodeType?"comment":o.tagName.toLowerCase(),d=i||"svg"===l,f=1===o.nodeType?function(t){var n=function(t){return Array.prototype.reduce.call(t.attributes,(function(t,n){return e(u,n.name)||(t[n.name]=n.value),t}),{})}(t);return function(e,t){for(var n in u){var o=u[n].propName,r=e.getAttribute(n);n===u.style.attrName?t[n]=e.style[o]:"string"==typeof r&&(t[n]=r)}}(t,n),n}(o):{},v=c>0?null:o.textContent,p=Array(c);return t(s,(function(e,t){p[t]=n(e,d)})),{type:l,attributes:f,children:p,content:v,node:o,isSVGContext:d}};function w(e,t){var n=[];for(var o of t){let t=e(o);if(!t)break;n.push(t)}return n}function g(e){return{cookies:e.filter((e=>"Cookie"==e.key)).map((e=>e.value)),error:E("Error",e),query:E("Query",e),pageTitle:E("PageTitle",e),events:q("Event",e).map(k),actions:q("Trigger",e).map(I)}}function b(e){return g(w(C,e.trim().split("\n")))}function E(e,t){return t.find((t=>t.key==e))?.value}function q(e,t){return t.filter((t=>t.key==e)).map((e=>e.value))}function C(e){let t=e.match(/^(\w+)\: (.*)$/);if(t)return{key:t[1],value:t[2]}}function k(e){let[t,n]=A(e);return{name:t,detail:JSON.parse(n)}}function I(e){let[t,n]=A(e);return[t,n]}function A(e){let t=e.indexOf("|");if(-1===t){let t=new Error("Bad Encoding, Expected Segment");throw t.message=e,t}return[e.slice(0,t),e.slice(t+1)]}function x(e){if(!e)return;const t=new URLSearchParams;return e.forEach(((e,n)=>{t.append(n,e)})),t}let S=0;function T(e,t){return e+" "+function(e){return""==e?"|":e.replace(/_/g,"\\_").replace(/\s+/g,"_")}(t)}const L=`${"https:"===window.location.protocol?"wss:":"ws:"}//${window.location.host}${window.location.pathname}`;class D extends EventTarget{constructor(){super(...arguments),this.hasEverConnected=!1,this.isConnected=!1,this.reconnectDelay=0,this.queue=[]}connect(e=L){const t=new WebSocket(e);function n(e){console.error("Connect Error",e)}function o(e){console.error("Socket Error",e)}this.socket=t,t.addEventListener("error",n),t.addEventListener("open",(e=>{console.log("Websocket Connected"),this.hasEverConnected&&document.dispatchEvent(new Event("hyp-socket-reconnect")),this.isConnected=!0,this.hasEverConnected=!0,this.reconnectDelay=1e3,t.removeEventListener("error",n),t.addEventListener("error",o),document.dispatchEvent(new Event("hyp-socket-connect")),this.runQueue()})),t.addEventListener("close",(n=>{this.isConnected&&document.dispatchEvent(new Event("hyp-socket-disconnect")),this.isConnected=!1,t.removeEventListener("error",o),this.hasEverConnected&&(console.log("Reconnecting in "+this.reconnectDelay/1e3+"s"),setTimeout((()=>this.connect(e)),this.reconnectDelay)),t.removeEventListener("error",o)})),t.addEventListener("message",(e=>this.onMessage(e)))}async sendAction(e){if(this.isConnected){let t=function(e){let t=["|ACTION|","ViewId: "+e.viewId,"Action: "+e.action];return e.state&&t.push("State: "+e.state),t.push("RequestId: "+e.requestId),[t.join("\n"),(o=e.meta,o.map((e=>e.key+": "+e.value)).join("\n"))].join("\n")+((n=e.form)?"\n\n"+n:"");var n,o}(e);this.socket.send(t)}else this.queue.push(e)}runQueue(){let e=this.queue.pop();e&&(console.log("runQueue: ",e),this.sendAction(e),this.runQueue())}onMessage(e){let{command:t,metas:n,rest:o}=function(e){let t=e.split("\n"),n=t[0],o=w(C,t.slice(1));return{command:n,metas:o,rest:function(e,t){let n=0;for(;n{let t=parseInt(e.dataset.delay)||0,n=e.dataset.onload;setTimeout((()=>{let t=B(e);if(e.dataset.onload!=n)return;const o=new CustomEvent("hyp-load",{bubbles:!0,detail:{target:t,onLoad:n}});e.dispatchEvent(o)}),t)}))}function P(e){e.querySelectorAll("[data-onmouseenter]").forEach((e=>{let t=e.dataset.onmouseenter,n=B(e);e.onmouseenter=()=>{const o=new CustomEvent("hyp-mouseenter",{bubbles:!0,detail:{target:n,onMouseEnter:t}});e.dispatchEvent(o)}}))}function V(e){e.querySelectorAll("[data-onmouseleave]").forEach((e=>{let t=e.dataset.onmouseleave,n=B(e);e.onmouseleave=()=>{const o=new CustomEvent("hyp-mouseleave",{bubbles:!0,detail:{target:n,onMouseLeave:t}});e.dispatchEvent(o)}}))}function B(e){let t=function(e){let t=e.closest("[data-target]");return t?.dataset.target||e.closest("[id]")?.id}(e),n=document.getElementById(t);if(n)return n;console.error("Cannot find target: ",t,e)}let U,Q=n(147);console.log("Hyperbole "+Q.version);let $=new Set;async function H(e,t,n){if(void 0===e)return void console.error("Undefined HyperView!",t);if(void 0===t)return void console.error("Undefined Action!",e.id);if(e.activeRequest&&!e.activeRequest?.isCancelled&&"Drop"==e.concurrency)return void console.warn("Drop action overlapping with active request ("+e.activeRequest+")",t);e._timeout=setTimeout((()=>{e.classList.add("hyp-loading")}),100);let o=e.dataset.state,r={requestId:++S,isCancelled:!1},i=function(e,t,n,o,r){return{viewId:e,action:t,state:n,requestId:o,meta:[{key:"Cookie",value:decodeURI(document.cookie)},{key:"Query",value:window.location.search}],form:x(r)}}(e.id,t,o,r.requestId,n);e.activeRequest=r,Y.sendAction(i)}function W(e){let t=e.targetViewId||e.viewId,n=document.getElementById(t);if(!n)return console.error("Missing Update Target: ",t,e),n;if(e.requestId{let t=e.getAttribute("value");void 0!==t&&(e.value=t)})),e.querySelectorAll("input[type=checkbox]").forEach((e=>{let t="True"==e.dataset.checked;e.checked=t}))}(s),F(s),n):(console.warn("Target Missing: ",n.id),n)}function _(e,t){for(var n of(null!=e.query&&function(e){if(e!=function(){const e=window.location.search;return e.startsWith("?")?e.substring(1):e}()){""!=e&&(e="?"+e);let t=location.pathname+e;window.history.replaceState({},"",t)}}(e.query),null!=e.pageTitle&&(document.title=e.pageTitle),e.events))setTimeout((()=>{let e=new CustomEvent(n.name,{bubbles:!0,detail:n.detail});(t||document).dispatchEvent(e)}),10);e.actions.forEach((([e,t])=>{setTimeout((()=>{let n=window.Hyperbole.hyperView(e);n&&H(n,t)}),10)}))}function F(e){e.querySelectorAll("[id]").forEach((t=>{t.runAction=function(e){H(this,e)}.bind(t),t.concurrency=t.dataset.concurrency||"Drop",t.cancelActiveRequest=function(){t.activeRequest&&!t.activeRequest?.isCancelled&&(t.activeRequest.isCancelled=!0)},J(e)}))}function J(e){let t=new Event("hyp-content",{bubbles:!0});e.dispatchEvent(t)}document.addEventListener("DOMContentLoaded",(function(){var e;_(b(document.getElementById("hyp.metadata").innerText)),U=document.querySelector("style"),e=async function(e,t){H(e,t)},document.addEventListener("hyp-load",(function(t){let n=t.detail.onLoad,o=t.detail.target;e(o,n)})),document.addEventListener("hyp-mouseenter",(function(t){let n=t.detail.onMouseEnter,o=t.detail.target;e(o,n)})),document.addEventListener("hyp-mouseleave",(function(t){let n=t.detail.onMouseLeave,o=t.detail.target;e(o,n)})),j(document.body),P(document.body),V(document.body),F(document.body),M("click",(async function(e,t){H(e,t)})),M("dblclick",(async function(e,t){H(e,t)})),N("keydown",(async function(e,t){H(e,t)})),N("keyup",(async function(e,t){H(e,t)})),document.addEventListener("submit",(function(e){let t=e.target;if(!t?.dataset.onsubmit)return void console.error("Missing onSubmit: ",t);e.preventDefault();let n=B(t);const o=new FormData(t);!async function(e,t,n){H(e,t,n)}(n,t.dataset.onsubmit,o)})),document.addEventListener("change",(function(e){let t=e.target.closest("[data-onchange]");t&&(e.preventDefault(),null!=t.value?async function(e,t){H(e,t)}(B(t),T(t.dataset.onchange,t.value)):console.error("Missing input value:",t))})),document.addEventListener("input",(function(e){let t=e.target.closest("[data-oninput]");if(!t)return;let n=parseInt(t.dataset.delay)||250;if(n<250&&console.warn("Input delay < 250 can result in poor performance."),!t?.dataset.oninput)return void console.error("Missing onInput: ",t);e.preventDefault();let o=B(t);(function(e){"Replace"==e.concurrency&&e.cancelActiveRequest()})(o),t.debouncedCallback||(t.debouncedCallback=O((()=>{let e=T(t.dataset.oninput,t.value);!async function(e,t){H(e,t)}(o,e)}),n)),t.debouncedCallback()}))}));const Y=new D;Y.connect(),Y.addEventListener("update",(e=>W(e.detail))),Y.addEventListener("response",(e=>function(e){let t=W(e);delete t.activeRequest,clearTimeout(t._timeout),t.classList.remove("hyp-loading")}(e.detail))),Y.addEventListener("redirect",(e=>{return t=e.detail,console.log("REDIRECT",t),void(window.location.href=t.url);var t})),window.Hyperbole={runAction:H,parseMetadata:b,action:function(e,...t){return e+t.reduce(((e,t)=>e+" "+JSON.stringify(t)),"")},hyperView:function(e){let t=document.getElementById(e);if(t?.runAction)return t;console.error("Element id="+e+" was not a HyperView")},socket:Y}})()})(); //# sourceMappingURL=hyperbole.js.map \ No newline at end of file diff --git a/client/dist/hyperbole.js.map b/client/dist/hyperbole.js.map index f2968100..0f7ae471 100644 --- a/client/dist/hyperbole.js.map +++ b/client/dist/hyperbole.js.map @@ -1 +1 @@ -{"version":3,"file":"hyperbole.js","mappings":"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","sources":["webpack://web-ui/./node_modules/debounce/index.js","webpack://web-ui/webpack/bootstrap","webpack://web-ui/./node_modules/omdomdom/lib/omdomdom.es.js","webpack://web-ui/./src/lib.ts","webpack://web-ui/./src/message.ts","webpack://web-ui/./src/action.ts","webpack://web-ui/./src/sockets.ts","webpack://web-ui/./src/events.ts","webpack://web-ui/./src/index.ts","webpack://web-ui/./src/response.ts","webpack://web-ui/./src/browser.ts"],"sourcesContent":["function debounce(function_, wait = 100, options = {}) {\n\tif (typeof function_ !== 'function') {\n\t\tthrow new TypeError(`Expected the first parameter to be a function, got \\`${typeof function_}\\`.`);\n\t}\n\n\tif (wait < 0) {\n\t\tthrow new RangeError('`wait` must not be negative.');\n\t}\n\n\t// TODO: Deprecate the boolean parameter at some point.\n\tconst {immediate} = typeof options === 'boolean' ? {immediate: options} : options;\n\n\tlet storedContext;\n\tlet storedArguments;\n\tlet timeoutId;\n\tlet timestamp;\n\tlet result;\n\n\tfunction run() {\n\t\tconst callContext = storedContext;\n\t\tconst callArguments = storedArguments;\n\t\tstoredContext = undefined;\n\t\tstoredArguments = undefined;\n\t\tresult = function_.apply(callContext, callArguments);\n\t\treturn result;\n\t}\n\n\tfunction later() {\n\t\tconst last = Date.now() - timestamp;\n\n\t\tif (last < wait && last >= 0) {\n\t\t\ttimeoutId = setTimeout(later, wait - last);\n\t\t} else {\n\t\t\ttimeoutId = undefined;\n\n\t\t\tif (!immediate) {\n\t\t\t\tresult = run();\n\t\t\t}\n\t\t}\n\t}\n\n\tconst debounced = function (...arguments_) {\n\t\tif (\n\t\t\tstoredContext\n\t\t\t&& this !== storedContext\n\t\t\t&& Object.getPrototypeOf(this) === Object.getPrototypeOf(storedContext)\n\t\t) {\n\t\t\tthrow new Error('Debounced method called with different contexts of the same prototype.');\n\t\t}\n\n\t\tstoredContext = this; // eslint-disable-line unicorn/no-this-assignment\n\t\tstoredArguments = arguments_;\n\t\ttimestamp = Date.now();\n\n\t\tconst callNow = immediate && !timeoutId;\n\n\t\tif (!timeoutId) {\n\t\t\ttimeoutId = setTimeout(later, wait);\n\t\t}\n\n\t\tif (callNow) {\n\t\t\tresult = run();\n\t\t}\n\n\t\treturn result;\n\t};\n\n\tObject.defineProperty(debounced, 'isPending', {\n\t\tget() {\n\t\t\treturn timeoutId !== undefined;\n\t\t},\n\t});\n\n\tdebounced.clear = () => {\n\t\tif (!timeoutId) {\n\t\t\treturn;\n\t\t}\n\n\t\tclearTimeout(timeoutId);\n\t\ttimeoutId = undefined;\n\t};\n\n\tdebounced.flush = () => {\n\t\tif (!timeoutId) {\n\t\t\treturn;\n\t\t}\n\n\t\tdebounced.trigger();\n\t};\n\n\tdebounced.trigger = () => {\n\t\tresult = run();\n\n\t\tdebounced.clear();\n\t};\n\n\treturn debounced;\n}\n\n// Adds compatibility for ES modules\nmodule.exports.debounce = debounce;\n\nmodule.exports = debounce;\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","/*!\n * @license MIT (https://github.com/geotrev/omdomdom/blob/master/LICENSE)\n * Omdomdom v0.3.2 (https://github.com/geotrev/omdomdom)\n * Copyright 2023 George Treviranus \n */\nvar DATA_KEY_ATTRIBUTE$1 = \"data-key\";\nvar hasProperty = function hasProperty(obj, prop) {\n return Object.prototype.hasOwnProperty.call(obj, prop);\n};\nvar keyIsValid = function keyIsValid(map, key) {\n if (!key) return false;\n if (hasProperty(map, key)) {\n console.warn(\"[OmDomDom]: Children with duplicate keys detected. Children with duplicate keys will be skipped, resulting in dropped node references. Keys must be unique and non-indexed.\");\n return false;\n }\n return true;\n};\nvar forEach = function forEach(items, fn) {\n var length = items.length;\n var idx = -1;\n if (!length) return;\n while (++idx < length) {\n if (fn(items[idx], idx) === false) break;\n }\n};\nvar createKeyMap = function createKeyMap(children) {\n var map = {};\n forEach(children, function (child) {\n var key = child.attributes[DATA_KEY_ATTRIBUTE$1];\n if (keyIsValid(map, key)) {\n map[key] = child;\n }\n });\n for (var _ in map) {\n return map;\n }\n};\nvar insertBefore = function insertBefore(parent, child, refNode) {\n return parent.node.insertBefore(child.node, refNode);\n};\nvar assignVNode = function assignVNode(template, vNode) {\n for (var property in template) {\n vNode[property] = template[property];\n }\n};\n\nvar toHTML = function toHTML(htmlString) {\n var processedDOMString = htmlString.trim().replace(/\\s+\\s+/g, \">\");\n var parser = new DOMParser();\n var context = parser.parseFromString(processedDOMString, \"text/html\");\n return context.body;\n};\n\nfunction _iterableToArrayLimit(arr, i) {\n var _i = null == arr ? null : \"undefined\" != typeof Symbol && arr[Symbol.iterator] || arr[\"@@iterator\"];\n if (null != _i) {\n var _s,\n _e,\n _x,\n _r,\n _arr = [],\n _n = !0,\n _d = !1;\n try {\n if (_x = (_i = _i.call(arr)).next, 0 === i) {\n if (Object(_i) !== _i) return;\n _n = !1;\n } else for (; !(_n = (_s = _x.call(_i)).done) && (_arr.push(_s.value), _arr.length !== i); _n = !0);\n } catch (err) {\n _d = !0, _e = err;\n } finally {\n try {\n if (!_n && null != _i.return && (_r = _i.return(), Object(_r) !== _r)) return;\n } finally {\n if (_d) throw _e;\n }\n }\n return _arr;\n }\n}\nfunction _slicedToArray(arr, i) {\n return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();\n}\nfunction _arrayWithHoles(arr) {\n if (Array.isArray(arr)) return arr;\n}\nfunction _unsupportedIterableToArray(o, minLen) {\n if (!o) return;\n if (typeof o === \"string\") return _arrayLikeToArray(o, minLen);\n var n = Object.prototype.toString.call(o).slice(8, -1);\n if (n === \"Object\" && o.constructor) n = o.constructor.name;\n if (n === \"Map\" || n === \"Set\") return Array.from(o);\n if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);\n}\nfunction _arrayLikeToArray(arr, len) {\n if (len == null || len > arr.length) len = arr.length;\n for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];\n return arr2;\n}\nfunction _nonIterableRest() {\n throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\");\n}\n\nvar Types = {\n STRING: \"string\",\n INT: \"number\",\n BOOL: \"boolean\"\n};\nvar DomProperties = {};\nvar createRecord = function createRecord(attrName, propName, type) {\n return {\n attrName: attrName,\n propName: propName,\n type: type\n };\n};\nvar stringProps = [[\"style\", \"cssText\"], [\"class\", \"className\"]];\nforEach(stringProps, function (_ref) {\n var _ref2 = _slicedToArray(_ref, 2),\n attr = _ref2[0],\n property = _ref2[1];\n DomProperties[attr] = createRecord(attr, property || attr, Types.STRING);\n});\nvar booleanProps = [\"autofocus\", \"draggable\", \"hidden\", \"checked\", \"multiple\", \"muted\", \"selected\"];\nforEach(booleanProps, function (attr) {\n DomProperties[attr] = createRecord(attr, attr, Types.BOOL);\n});\nvar integerProps = [[\"tabindex\", \"tabIndex\"]];\nforEach(integerProps, function (_ref3) {\n var _ref4 = _slicedToArray(_ref3, 2),\n attr = _ref4[0],\n property = _ref4[1];\n DomProperties[attr] = createRecord(attr, property, Types.INT);\n});\nvar Namespace = {\n xlink: {\n prefix: \"xlink:\",\n resource: \"http://www.w3.org/1999/xlink\"\n },\n xml: {\n prefix: \"xml:\",\n resource: \"http://www.w3.org/XML/1998/namespace\"\n }\n};\n\nvar setProperty = function setProperty(element, type, prop, value) {\n switch (type) {\n case Types.STRING:\n {\n if (prop === DomProperties.style.propName) {\n if (value === null) {\n element.style[prop] = \"\";\n } else {\n element.style[prop] = value;\n }\n } else if (value === null) {\n element[prop] = \"\";\n } else {\n element[prop] = value;\n }\n break;\n }\n case Types.INT:\n {\n if (value === null) {\n var attr = prop.toLowerCase();\n element.removeAttribute(attr);\n } else if (value === \"0\") {\n element[prop] = 0;\n } else if (value === \"-1\") {\n element[prop] = -1;\n } else {\n var parsed = parseInt(value, 10);\n if (!isNaN(parsed)) {\n element[prop] = parsed;\n }\n }\n break;\n }\n case Types.BOOL:\n {\n if ([\"\", \"true\"].indexOf(value) < 0) {\n element[prop] = false;\n } else {\n element[prop] = true;\n }\n break;\n }\n }\n};\n\nvar removeAttributes = function removeAttributes(vNode, attributes) {\n forEach(attributes, function (attrName) {\n if (hasProperty(DomProperties, attrName)) {\n var propertyRecord = DomProperties[attrName];\n setProperty(vNode.node, propertyRecord.type, propertyRecord.propName, null);\n } else {\n if (attrName in vNode.node) {\n setProperty(vNode.node, Types.STRING, attrName, null);\n }\n vNode.node.removeAttribute(attrName);\n }\n delete vNode.attributes[attrName];\n });\n};\nvar setAttributes = function setAttributes(vNode, attributes) {\n for (var attrName in attributes) {\n var value = attributes[attrName];\n vNode.attributes[attrName] = value;\n if (hasProperty(DomProperties, attrName)) {\n var propertyRecord = DomProperties[attrName];\n setProperty(vNode.node, propertyRecord.type, propertyRecord.propName, value);\n continue;\n }\n if (attrName.startsWith(Namespace.xlink.prefix)) {\n vNode.node.setAttributeNS(Namespace.xlink.resource, attrName, value);\n continue;\n }\n if (attrName.startsWith(Namespace.xml.prefix)) {\n vNode.node.setAttributeNS(Namespace.xml.resource, attrName, value);\n continue;\n }\n if (attrName in vNode.node) {\n setProperty(vNode.node, Types.STRING, attrName, value);\n }\n vNode.node.setAttribute(attrName, value || \"\");\n }\n};\nvar getPropertyValues = function getPropertyValues(element, attributes) {\n for (var attrName in DomProperties) {\n var propertyRecord = DomProperties[attrName];\n var propName = propertyRecord.propName;\n var attrValue = element.getAttribute(attrName);\n if (attrName === DomProperties.style.attrName) {\n attributes[attrName] = element.style[propName];\n } else if (typeof attrValue === \"string\") {\n attributes[attrName] = attrValue;\n }\n }\n};\nvar getBaseAttributes = function getBaseAttributes(element) {\n return Array.prototype.reduce.call(element.attributes, function (attributes, attrName) {\n if (!hasProperty(DomProperties, attrName.name)) {\n attributes[attrName.name] = attrName.value;\n }\n return attributes;\n }, {});\n};\nvar getAttributes = function getAttributes(element) {\n var attributes = getBaseAttributes(element);\n getPropertyValues(element, attributes);\n return attributes;\n};\nvar updateAttributes = function updateAttributes(template, vNode) {\n var removedAttributes = [];\n var changedAttributes = {};\n for (var attrName in vNode.attributes) {\n var oldValue = vNode.attributes[attrName];\n var nextValue = template.attributes[attrName];\n if (oldValue === nextValue) continue;\n if (typeof nextValue === \"undefined\") {\n removedAttributes.push(attrName);\n }\n }\n for (var _attrName in template.attributes) {\n var _oldValue = vNode.attributes[_attrName];\n var _nextValue = template.attributes[_attrName];\n if (_oldValue === _nextValue) continue;\n if (typeof _nextValue !== \"undefined\") {\n changedAttributes[_attrName] = _nextValue;\n }\n }\n removeAttributes(vNode, removedAttributes);\n setAttributes(vNode, changedAttributes);\n};\n\nvar DATA_KEY_ATTRIBUTE = \"data-key\";\nfunction patchChildren(template, vNode, patch) {\n var templateChildrenLength = template.children.length;\n var vNodeChildrenLength = vNode.children.length;\n if (!templateChildrenLength && !vNodeChildrenLength) return;\n var vNodeKeyMap = createKeyMap(vNode.children);\n var nextChildren = Array(templateChildrenLength);\n if (vNodeKeyMap !== undefined) {\n forEach(template.children, function (templateChild, idx) {\n var childNodes = vNode.node.childNodes;\n var key = templateChild.attributes[DATA_KEY_ATTRIBUTE];\n if (Object.prototype.hasOwnProperty.call(vNodeKeyMap, key)) {\n var keyedChild = vNodeKeyMap[key];\n if (Array.prototype.indexOf.call(childNodes, keyedChild.node) !== idx) {\n insertBefore(vNode, keyedChild, childNodes[idx]);\n }\n nextChildren[idx] = keyedChild;\n delete vNodeKeyMap[key];\n patch(templateChild, nextChildren[idx]);\n } else {\n insertBefore(vNode, templateChild, childNodes[idx]);\n nextChildren[idx] = templateChild;\n }\n });\n } else {\n forEach(template.children, function (templateChild, idx) {\n var vNodeChild = vNode.children[idx];\n if (typeof vNodeChild !== \"undefined\") {\n patch(templateChild, vNodeChild);\n nextChildren[idx] = vNodeChild;\n } else {\n vNode.node.appendChild(templateChild.node);\n nextChildren[idx] = templateChild;\n }\n });\n }\n vNode.children = nextChildren;\n var childNodesLength = vNode.node.childNodes.length;\n var delta = childNodesLength - templateChildrenLength;\n if (delta > 0) {\n while (delta > 0) {\n vNode.node.removeChild(vNode.node.childNodes[childNodesLength - 1]);\n childNodesLength--;\n delta--;\n }\n }\n}\n\nvar patch = function patch(template, vNode, rootNode) {\n if (!template || !vNode) return;\n rootNode = rootNode || vNode.node.parentNode;\n var contentChanged = template.content && template.content !== vNode.content;\n if (template.type !== vNode.type || contentChanged) {\n rootNode.replaceChild(template.node, vNode.node);\n return assignVNode(template, vNode);\n }\n updateAttributes(template, vNode);\n patchChildren(template, vNode, patch);\n};\nvar render = function render(vNode, root) {\n root.appendChild(vNode.node);\n};\nvar create = function create(node) {\n var isSVGContext = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;\n if (typeof node === \"string\") {\n node = toHTML(node);\n }\n var isRoot = node.tagName === \"BODY\";\n var childNodes = node.childNodes;\n var numChildNodes = childNodes ? childNodes.length : 0;\n if (isRoot) {\n if (numChildNodes > 1) {\n throw new Error(\"[OmDomDom]: Your element should not have more than one root node.\");\n } else if (numChildNodes === 0) {\n throw new Error(\"[OmDomDom]: Your element should have at least one root node.\");\n } else {\n return create(childNodes[0]);\n }\n }\n var type = node.nodeType === 3 ? \"text\" : node.nodeType === 8 ? \"comment\" : node.tagName.toLowerCase();\n var isSVG = isSVGContext || type === \"svg\";\n var attributes = node.nodeType === 1 ? getAttributes(node) : {};\n var content = numChildNodes > 0 ? null : node.textContent;\n var children = Array(numChildNodes);\n forEach(childNodes, function (child, idx) {\n children[idx] = create(child, isSVG);\n });\n return {\n type: type,\n attributes: attributes,\n children: children,\n content: content,\n node: node,\n isSVGContext: isSVG\n };\n};\n\nexport { create, patch, render };\n//# sourceMappingURL=omdomdom.es.js.map\n","\n\nexport function takeWhileMap(pred: (val: T) => A | undefined, lines: T[]): A[] {\n var output = []\n for (var line of lines) {\n let a = pred(line)\n if (a)\n output.push(a)\n else\n break;\n }\n\n return output\n}\n\nexport function dropWhile(pred: (val: T) => A | undefined, lines: T[]): T[] {\n let index = 0;\n while (index < lines.length && pred(lines[index])) {\n index++;\n }\n return lines.slice(index);\n}\n\n","\nimport { takeWhileMap, dropWhile } from \"./lib\"\n\n\n\nexport type Meta = { key: string, value: string }\nexport type ViewId = string\nexport type RequestId = number\nexport type EncodedAction = string\n\ntype RemoteEvent = { name: string, detail: any }\n\n\nexport function renderMetas(meta: Meta[]): string {\n return meta.map(m => m.key + \": \" + m.value).join('\\n')\n}\n\nexport type Metadata = {\n cookies?: string[]\n // redirect?: string\n error?: string\n query?: string\n events?: RemoteEvent[]\n actions?: [ViewId, string][],\n pageTitle?: string\n}\n\n\nexport function toMetadata(meta: Meta[]): Metadata {\n\n return {\n cookies: meta.filter(m => m.key == \"Cookie\").map(m => m.value),\n // redirect: metaValue(\"Redirect\", meta),\n error: metaValue(\"Error\", meta),\n query: metaValue(\"Query\", meta),\n pageTitle: metaValue(\"PageTitle\", meta),\n events: metaValuesAll(\"Event\", meta).map(parseRemoteEvent),\n actions: metaValuesAll(\"Trigger\", meta).map(parseAction),\n }\n}\n\n// viewId: meta.find(m => m.key == \"VIEW-ID\")?.value,\n\nexport function parseMetadata(input: string): Metadata {\n let metas = takeWhileMap(parseMeta, input.trim().split(\"\\n\"))\n return toMetadata(metas)\n}\n\n\nexport function metaValue(key: string, metas: Meta[]): string | undefined {\n return metas.find(m => m.key == key)?.value\n}\n\nexport function metaValuesAll(key: string, metas: Meta[]): string[] {\n return metas.filter(m => m.key == key).map(m => m.value)\n}\n\nexport type SplitMessage = {\n command: string,\n metas: Meta[],\n rest: string[]\n}\n\n\nexport function splitMessage(message: string): SplitMessage {\n let lines = message.split(\"\\n\")\n let command: string = lines[0]\n let metas: Meta[] = takeWhileMap(parseMeta, lines.slice(1))\n // console.log(\"Split Metadata\", lines.length)\n // console.log(\" [0]\", lines[0])\n // console.log(\" [1]\", lines[1])\n let rest = dropWhile(l => l == \"\", lines.slice(metas.length + 1))\n\n return { command, metas, rest }\n}\n\nexport function parseMeta(line: string): Meta | undefined {\n let match = line.match(/^(\\w+)\\: (.*)$/)\n if (match) {\n return {\n key: match[1],\n value: match[2]\n }\n }\n}\n\n\nexport function parseRemoteEvent(input: string): RemoteEvent {\n let [name, data] = breakNextSegment(input)\n return {\n name,\n detail: JSON.parse(data)\n }\n}\n\nexport function parseAction(input: string): [ViewId, string] {\n let [viewId, action] = breakNextSegment(input)\n return [viewId, action]\n}\n\nfunction breakNextSegment(input: string): [string, string] | undefined {\n let ix = input.indexOf('|')\n if (ix === -1) {\n let err = new Error(\"Bad Encoding, Expected Segment\")\n err.message = input\n throw err\n }\n return [input.slice(0, ix), input.slice(ix + 1)]\n}\n\n","\nimport { takeWhileMap } from \"./lib\"\nimport { Meta, ViewId, RequestId, EncodedAction } from \"./message\"\nimport * as message from \"./message\"\n\n\n\nexport type ActionMessage = {\n viewId: ViewId\n action: EncodedAction\n requestId: RequestId\n meta: Meta[]\n form: URLSearchParams | undefined\n}\n\n\n\n\nexport function actionMessage(id: ViewId, action: EncodedAction, reqId: RequestId, form?: FormData): ActionMessage {\n let meta: Meta[] = [\n { key: \"Cookie\", value: decodeURI(document.cookie) },\n { key: \"Query\", value: window.location.search }\n ]\n\n return { viewId: id, action, requestId: reqId, meta, form: toSearch(form) }\n}\n\nexport function toSearch(form?: FormData): URLSearchParams | undefined {\n if (!form) return undefined\n\n const params = new URLSearchParams()\n\n form.forEach((value, key) => {\n params.append(key, value as string)\n })\n\n return params\n}\n\nexport function renderActionMessage(msg: ActionMessage): string {\n let header = [\n \"|ACTION|\",\n \"ViewId: \" + msg.viewId,\n \"Action: \" + msg.action,\n \"RequestId: \" + msg.requestId\n ]\n\n\n return [\n header.join('\\n'),\n message.renderMetas(msg.meta),\n ].join('\\n') + renderForm(msg.form)\n}\n\n\nexport function renderForm(form: URLSearchParams | undefined): string {\n if (!form) return \"\"\n return \"\\n\\n\" + form\n}\n\nlet globalRequestId: RequestId = 0\n\nexport type Request = {\n requestId: RequestId\n isCancelled: boolean\n}\n\nexport function newRequest(): Request {\n let requestId = ++globalRequestId\n return { requestId, isCancelled: false }\n}\n\n\n\n// Sanitized Encoding ------------------------------------\n\nexport function encodedParam(action: string, param: string): string {\n return action + ' ' + sanitizeParam(param)\n}\n\nfunction sanitizeParam(param: string): string {\n if (param == \"\") {\n return \"|\"\n }\n\n return param.replace(/_/g, \"\\\\_\").replace(/\\s+/g, \"_\")\n}\n","import { ActionMessage, renderActionMessage } from './action'\nimport { ResponseBody } from \"./response\"\nimport * as message from \"./message\"\nimport { ViewId, RequestId, EncodedAction, metaValue, Metadata } from \"./message\"\n\nconst protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';\nconst defaultAddress = `${protocol}//${window.location.host}${window.location.pathname}`\n\n\n\nexport class SocketConnection extends EventTarget {\n socket: WebSocket\n\n hasEverConnected: Boolean = false\n isConnected: Boolean = false\n reconnectDelay: number = 0\n queue: ActionMessage[] = []\n\n // constructor() { super() }\n\n connect(addr = defaultAddress) {\n const sock = new WebSocket(addr)\n this.socket = sock\n\n function onConnectError(ev: Event) {\n console.error(\"Connect Error\", ev)\n }\n\n function onSocketError(ev: Event) {\n console.error(\"Socket Error\", ev)\n }\n\n\n // initial connection errors\n sock.addEventListener('error', onConnectError)\n\n sock.addEventListener('open', (_event) => {\n console.log(\"Websocket Connected\")\n\n if (this.hasEverConnected) {\n document.dispatchEvent(new Event(\"hyp-socket-reconnect\"))\n }\n\n this.isConnected = true\n this.hasEverConnected = true\n this.reconnectDelay = 1000\n sock.removeEventListener('error', onConnectError)\n sock.addEventListener('error', onSocketError)\n\n document.dispatchEvent(new Event(\"hyp-socket-connect\"))\n\n this.runQueue()\n })\n\n sock.addEventListener('close', _ => {\n if (this.isConnected) {\n document.dispatchEvent(new Event(\"hyp-socket-disconnect\"))\n }\n\n this.isConnected = false\n sock.removeEventListener('error', onSocketError)\n\n // attempt to reconnect in 1s\n if (this.hasEverConnected) {\n console.log(\"Reconnecting in \" + (this.reconnectDelay / 1000) + \"s\")\n setTimeout(() => this.connect(addr), this.reconnectDelay)\n }\n\n sock.removeEventListener('error', onSocketError)\n })\n\n sock.addEventListener('message', ev => this.onMessage(ev))\n }\n\n async sendAction(action: ActionMessage) {\n if (this.isConnected) {\n let msg = renderActionMessage(action)\n this.socket.send(msg)\n }\n else {\n this.queue.push(action)\n }\n }\n\n private runQueue() {\n // send all messages queued while disconnected \n let next: ActionMessage | null = this.queue.pop()\n if (next) {\n console.log(\"runQueue: \", next)\n this.sendAction(next)\n this.runQueue()\n }\n }\n\n\n // full responses will never be sent over!\n private onMessage(event: MessageEvent) {\n let { command, metas, rest } = message.splitMessage(event.data)\n // console.log(\"MESSAGE\", command, metas, rest)\n\n let requestId = parseInt(requireMeta(\"RequestId\"), 0)\n\n function requireMeta(key: string): string {\n let val = metaValue(key, metas)\n if (!val) throw new ProtocolError(\"Missing Required Metadata: \" + key, event.data)\n return val\n }\n\n function parseResponse(rest: string[]): Update {\n let viewId = requireMeta(\"ViewId\")\n let action = requireMeta(\"Action\")\n return {\n requestId,\n targetViewId: undefined,\n viewId,\n action,\n meta: message.toMetadata(metas),\n body: rest.join(\"\\n\"),\n }\n }\n\n function parseUpdate(rest: string[]): Update {\n let up = parseResponse(rest)\n // add the TargetViewId\n up.targetViewId = metaValue(\"TargetViewId\", metas)\n return up\n }\n\n function parseRedirect(rest: string[]): Redirect {\n let url = rest[0]\n return {\n requestId,\n url\n }\n }\n\n switch (command) {\n\n case \"|UPDATE|\":\n return this.dispatchEvent(new CustomEvent(\"update\", { detail: parseUpdate(rest) }))\n\n case \"|RESPONSE|\":\n return this.dispatchEvent(new CustomEvent(\"response\", { detail: parseResponse(rest) }))\n\n case \"|REDIRECT|\":\n return this.dispatchEvent(new CustomEvent(\"redirect\", { detail: parseRedirect(rest) }))\n }\n }\n\n\n // so what if they send remote events in the page? trigger, redirect, page title, etc...\n // we aren't connected yet on a page thing\n\n // private async waitMessage(reqId: RequestId, id: ViewId): Promise {\n // return new Promise((resolve, reject) => {\n // const onMessage = (event: MessageEvent) => {\n // let data: string = event.data\n // let lines = data.split(\"\\n\").slice(1) // drop the command line\n //\n // let parsed = splitMetadata(lines)\n // let metadata: Metadata = parsed.metadata\n //\n // if (!metadata.requestId) {\n // console.error(\"Missing RequestId!\", metadata, event.data)\n // return\n // }\n //\n // if (metadata.requestId != reqId) {\n // // skip, it's not us!\n // return\n // }\n //\n //\n // // We have found our message. Remove the listener\n // this.socket.removeEventListener('message', onMessage)\n //\n // // set the cookies. These happen automatically in http\n // metadata.cookies.forEach((cookie: string) => {\n // document.cookie = cookie\n // })\n //\n // if (metadata.error) {\n // reject(new FetchError(id, metadata.error, parsed.rest.join('\\n')))\n // return\n // }\n //\n // resolve(parsed)\n // }\n //\n // this.socket.addEventListener('message', onMessage)\n // this.socket.addEventListener('error', reject)\n // })\n // }\n\n disconnect() {\n this.isConnected = false\n this.hasEverConnected = false\n this.socket.close()\n }\n}\n\n\nexport type Update = {\n requestId: RequestId\n meta: Metadata\n viewId: ViewId\n targetViewId?: ViewId\n action: EncodedAction\n body: ResponseBody\n}\n\nexport type Redirect = {\n requestId: RequestId\n url: string\n}\n\nexport type MessageType = string\n\n\n// PARSING MESSAGE ---------------------------------------\n\nexport class ProtocolError extends Error {\n constructor(description: string, body: string) {\n super(description + \"\\n\" + body)\n this.name = \"ProtocolError\"\n }\n}\n","\nimport * as debounce from 'debounce'\nimport { encodedParam } from './action'\n\nexport type UrlFragment = string\n\nexport function listenKeydown(cb: (target: HTMLElement, action: string) => void): void {\n listenKeyEvent(\"keydown\", cb)\n}\n\nexport function listenKeyup(cb: (target: HTMLElement, action: string) => void): void {\n listenKeyEvent(\"keyup\", cb)\n}\n\nexport function listenKeyEvent(event: string, cb: (target: HTMLElement, action: string) => void): void {\n document.addEventListener(event.toLowerCase(), function(e: KeyboardEvent) {\n let source = e.target as HTMLInputElement\n\n let datasetKey = \"on\" + event + e.key\n let action = source.dataset[datasetKey]\n if (!action) return\n\n e.preventDefault()\n cb(nearestTarget(source), action)\n })\n}\n\nexport function listenBubblingEvent(event: string, cb: (target: HTMLElement, action: string) => void): void {\n document.addEventListener(event, function(e) {\n let el = e.target as HTMLInputElement\n\n // clicks can fire on internal elements. Find the parent with a click handler\n let source = el.closest(\"[data-on\" + event + \"]\") as HTMLElement\n if (!source) return\n\n e.preventDefault()\n let target = nearestTarget(source)\n cb(target, source.dataset[\"on\" + event])\n })\n}\n\nexport function listenClick(cb: (target: HTMLElement, action: string) => void): void {\n listenBubblingEvent(\"click\", cb)\n}\n\nexport function listenDblClick(cb: (target: HTMLElement, action: string) => void): void {\n listenBubblingEvent(\"dblclick\", cb)\n}\n\n\nexport function listenTopLevel(cb: (target: HTMLElement, action: string) => void): void {\n document.addEventListener(\"hyp-load\", function(e: CustomEvent) {\n let action = e.detail.onLoad\n let target = e.detail.target\n cb(target, action)\n })\n\n document.addEventListener(\"hyp-mouseenter\", function(e: CustomEvent) {\n let action = e.detail.onMouseEnter\n let target = e.detail.target\n cb(target, action)\n })\n\n document.addEventListener(\"hyp-mouseleave\", function(e: CustomEvent) {\n let action = e.detail.onMouseLeave\n let target = e.detail.target\n cb(target, action)\n })\n}\n\n\nexport function listenLoad(node: HTMLElement): void {\n\n // it doesn't really matter WHO runs this except that it should have target\n node.querySelectorAll(\"[data-onload]\").forEach((load: HTMLElement) => {\n let delay = parseInt(load.dataset.delay) || 0\n let onLoad = load.dataset.onload\n // console.log(\"load start\", load.dataset.onLoad)\n\n // load no longer exists!\n // we should clear the timeout or back out if the dom is replaced in the interem\n setTimeout(() => {\n let target = nearestTarget(load)\n // console.log(\"load go\", load.dataset.onLoad)\n\n if (load.dataset.onload != onLoad) {\n // the onLoad no longer exists\n return\n }\n\n const event = new CustomEvent(\"hyp-load\", { bubbles: true, detail: { target, onLoad } })\n load.dispatchEvent(event)\n }, delay)\n })\n}\n\nexport function listenMouseEnter(node: HTMLElement): void {\n node.querySelectorAll(\"[data-onmouseenter]\").forEach((node: HTMLElement) => {\n let onMouseEnter = node.dataset.onmouseenter\n\n let target = nearestTarget(node)\n\n node.onmouseenter = () => {\n const event = new CustomEvent(\"hyp-mouseenter\", { bubbles: true, detail: { target, onMouseEnter } })\n node.dispatchEvent(event)\n }\n })\n}\n\nexport function listenMouseLeave(node: HTMLElement): void {\n node.querySelectorAll(\"[data-onmouseleave]\").forEach((node: HTMLElement) => {\n let onMouseLeave = node.dataset.onmouseleave\n\n let target = nearestTarget(node)\n\n node.onmouseleave = () => {\n const event = new CustomEvent(\"hyp-mouseleave\", { bubbles: true, detail: { target, onMouseLeave } })\n node.dispatchEvent(event)\n }\n })\n}\n\n\nexport function listenChange(cb: (target: HTMLElement, action: string) => void): void {\n document.addEventListener(\"change\", function(e) {\n let el = e.target as HTMLElement\n\n let source = el.closest(\"[data-onchange]\") as HTMLInputElement\n\n if (!source) return\n e.preventDefault()\n\n if (source.value == null) {\n console.error(\"Missing input value:\", source)\n return\n }\n\n let target = nearestTarget(source)\n let action = encodedParam(source.dataset.onchange, source.value)\n cb(target, action)\n })\n}\n\ninterface LiveInputElement extends HTMLInputElement {\n debouncedCallback?: Function;\n}\n\nexport function listenInput(startedTyping: (target: HTMLElement) => void, cb: (target: HTMLElement, action: string) => void): void {\n document.addEventListener(\"input\", function(e) {\n let el = e.target as HTMLElement\n let source = el.closest(\"[data-oninput]\") as LiveInputElement\n\n if (!source) return\n\n let delay = parseInt(source.dataset.delay) || 250\n if (delay < 250) {\n console.warn(\"Input delay < 250 can result in poor performance.\")\n }\n\n if (!source?.dataset.oninput) {\n console.error(\"Missing onInput: \", source)\n return\n }\n\n e.preventDefault()\n\n let target = nearestTarget(source)\n\n // I want to CANCEL the active request as soon as we start typing\n startedTyping(target)\n\n if (!source.debouncedCallback) {\n source.debouncedCallback = debounce(() => {\n let action = encodedParam(source.dataset.oninput, source.value)\n cb(target, action)\n }, delay)\n }\n\n source.debouncedCallback()\n })\n}\n\n\n\nexport function listenFormSubmit(cb: (target: HTMLElement, action: string, form: FormData) => void): void {\n document.addEventListener(\"submit\", function(e) {\n let form = e.target as HTMLFormElement\n\n if (!form?.dataset.onsubmit) {\n console.error(\"Missing onSubmit: \", form)\n return\n }\n\n e.preventDefault()\n\n let target = nearestTarget(form)\n const formData = new FormData(form)\n cb(target, form.dataset.onsubmit, formData)\n })\n}\n\nfunction nearestTargetId(node: HTMLElement): string | undefined {\n let targetData = node.closest(\"[data-target]\") as HTMLElement | undefined\n return targetData?.dataset.target || node.closest(\"[id]\")?.id\n}\n\nfunction nearestTarget(node: HTMLElement): HTMLElement {\n let targetId = nearestTargetId(node)\n let target = document.getElementById(targetId)\n\n if (!target) {\n console.error(\"Cannot find target: \", targetId, node)\n return\n }\n\n return target\n}\n","import { patch, create } from \"omdomdom/lib/omdomdom.es.js\"\nimport { SocketConnection, Update, Redirect } from './sockets'\nimport { listenChange, listenClick, listenDblClick, listenFormSubmit, listenLoad, listenTopLevel, listenInput, listenKeydown, listenKeyup, listenMouseEnter, listenMouseLeave } from './events'\nimport { actionMessage, ActionMessage, Request, newRequest } from './action'\nimport { ViewId, Metadata, parseMetadata } from './message'\nimport { setQuery } from \"./browser\"\nimport { parseResponse, Response, LiveUpdate } from './response'\n\nlet PACKAGE = require('../package.json');\n\n\n// console.log(\"VERSION 2\", INIT_PAGE, INIT_STATE)\nconsole.log(\"Hyperbole \" + PACKAGE.version)\n\n\nlet rootStyles: HTMLStyleElement;\nlet addedRulesIndex = new Set();\n\n\n\n\n\n// Run an action in a given HyperView\nasync function runAction(target: HyperView, action: string, form?: FormData) {\n if (target === undefined) {\n console.error(\"Undefined HyperView!\", action)\n return\n }\n\n if (action === undefined) {\n console.error(\"Undefined Action!\", target.id)\n return\n }\n\n if (target.activeRequest && !target.activeRequest?.isCancelled) {\n // Active Request!\n if (target.concurrency == \"Drop\") {\n console.warn(\"Drop action overlapping with active request (\" + target.activeRequest + \")\", action)\n return\n }\n }\n\n target._timeout = setTimeout(() => {\n // add loading after 100ms, not right away\n // if it runs shorter than that we probably don't want to show the user any loading feedback\n target.classList.add(\"hyp-loading\")\n }, 100)\n\n let req = newRequest()\n let msg = actionMessage(target.id, action, req.requestId, form)\n\n // Set the requestId\n target.activeRequest = req\n\n sock.sendAction(msg)\n}\n\n\nfunction handleRedirect(red: Redirect) {\n console.log(\"REDIRECT\", red)\n window.location.href = red.url\n}\n\n// in-process update\nfunction handleResponse(res: Update) {\n // console.log(\"Handle Response\", res)\n let target = handleUpdate(res)\n\n // clean up the request\n delete target.activeRequest\n clearTimeout(target._timeout)\n target.classList.remove(\"hyp-loading\")\n}\n\nfunction handleUpdate(res: Update): HyperView {\n // console.log(\"|UPDATE|\", res)\n\n let targetViewId = res.targetViewId || res.viewId\n let target = document.getElementById(targetViewId) as HyperView\n\n\n if (!target) {\n console.error(\"Missing Update Target: \", targetViewId, res)\n return target\n }\n\n if (res.requestId < target.activeRequest?.requestId) {\n // this should only happen on Replace, since other requests should be dropped\n // but it's safe to assume we never want to apply an old requestId\n console.warn(\"Ignore Stale Action (\" + res.requestId + \") vs (\" + target.activeRequest.requestId + \"): \" + res.action)\n return target\n }\n else if (target.activeRequest?.isCancelled) {\n console.warn(\"Cancelled request\", target.activeRequest?.requestId)\n delete target.activeRequest\n return target\n }\n\n let update: LiveUpdate = parseResponse(res.body)\n\n if (!update.content) {\n console.error(\"Empty Response!\", res.body)\n return target\n }\n\n // First, update the stylesheet\n addCSS(update.css)\n\n\n // Patch the node\n const old: VNode = create(target)\n let next: VNode = create(update.content)\n next.attributes = old.attributes\n patch(next, old)\n\n\n // Emit relevant events\n let newTarget = document.getElementById(target.id)\n dispatchContent(newTarget)\n\n if (newTarget) {\n // execute the metadata, anything that doesn't interrupt the dom update\n runMetadata(res.meta, newTarget)\n\n // now way for these to bubble)\n listenLoad(newTarget)\n listenMouseEnter(newTarget)\n listenMouseLeave(newTarget)\n fixInputs(newTarget)\n enrichHyperViews(newTarget)\n }\n else {\n console.warn(\"Target Missing: \", target.id)\n }\n\n return target\n}\n// catch (err) {\n// console.error(\"Caught Error in HyperView (\" + target.id + \"):\\n\", err)\n//\n// // Hyperbole catches handler errors, and the server controls what to display to the user on an error\n// // but if you manage to crash your parent server process somehow, the response may be empty\n// target.innerHTML = err.body || \"
Hyperbole Internal Error
\"\n// }\n\n\n// Remove loading and clear add timeout\n\n// function runMetadataImmediate(meta: Metadata): boolean {\n// if (meta.redirect) {\n// // perform a redirect immediately\n// window.location.href = meta.redirect\n// return true\n// }\n// }\n\nfunction runMetadata(meta: Metadata, target?: HTMLElement) {\n if (meta.query != null) {\n setQuery(meta.query)\n }\n\n if (meta.pageTitle != null) {\n document.title = meta.pageTitle\n }\n\n for (var remoteEvent of meta.events) {\n setTimeout(() => {\n let event = new CustomEvent(remoteEvent.name, { bubbles: true, detail: remoteEvent.detail })\n let eventTarget = target || document\n eventTarget.dispatchEvent(event)\n }, 10)\n }\n\n meta.actions.forEach(([viewId, action]) => {\n setTimeout(() => {\n let view = window.Hyperbole.hyperView(viewId)\n if (view) {\n runAction(view, action)\n }\n }, 10)\n })\n}\n\n\nfunction fixInputs(target: HTMLElement) {\n let focused = target.querySelector(\"[autofocus]\") as HTMLInputElement\n if (focused?.focus) {\n focused.focus()\n }\n\n target.querySelectorAll(\"input[value]\").forEach((input: HTMLInputElement) => {\n let val = input.getAttribute(\"value\")\n if (val !== undefined) {\n input.value = val\n }\n })\n\n target.querySelectorAll(\"input[type=checkbox]\").forEach((checkbox: HTMLInputElement) => {\n let checked = checkbox.dataset.checked == \"True\"\n checkbox.checked = checked\n })\n}\n\nfunction addCSS(src: HTMLStyleElement | null) {\n if (!src) return;\n const rules: any = src.sheet.cssRules\n for (const rule of rules) {\n if (addedRulesIndex.has(rule.cssText) == false) {\n rootStyles.sheet.insertRule(rule.cssText);\n addedRulesIndex.add(rule.cssText);\n }\n }\n}\n\n\n\n\nfunction init() {\n // metadata attached to initial page loads need to be executed\n let meta = parseMetadata(document.getElementById(\"hyp.metadata\").innerText)\n // runMetadataImmediate(meta)\n runMetadata(meta)\n\n rootStyles = document.querySelector('style')\n\n listenTopLevel(async function(target: HyperView, action: string) {\n runAction(target, action)\n })\n\n listenLoad(document.body)\n listenMouseEnter(document.body)\n listenMouseLeave(document.body)\n enrichHyperViews(document.body)\n\n\n listenClick(async function(target: HyperView, action: string) {\n // console.log(\"CLICK\", target.id, action)\n runAction(target, action)\n })\n\n listenDblClick(async function(target: HyperView, action: string) {\n // console.log(\"DBLCLICK\", target.id, action)\n runAction(target, action)\n })\n\n listenKeydown(async function(target: HyperView, action: string) {\n // console.log(\"KEYDOWN\", target.id, action)\n runAction(target, action)\n })\n\n listenKeyup(async function(target: HyperView, action: string) {\n // console.log(\"KEYUP\", target.id, action)\n runAction(target, action)\n })\n\n listenFormSubmit(async function(target: HyperView, action: string, form: FormData) {\n // console.log(\"FORM\", target.id, action, form)\n runAction(target, action, form)\n })\n\n listenChange(async function(target: HyperView, action: string) {\n runAction(target, action)\n })\n\n function onStartedTyping(target: HyperView) {\n if (target.concurrency == \"Replace\") {\n target.cancelActiveRequest()\n }\n }\n\n listenInput(onStartedTyping, async function(target: HyperView, action: string) {\n runAction(target, action)\n })\n}\n\n\n\nfunction enrichHyperViews(node: HTMLElement): void {\n // enrich all the hyperviews\n node.querySelectorAll(\"[id]\").forEach((element: HyperView) => {\n element.runAction = function(action: string) {\n runAction(this, action)\n }.bind(element)\n\n element.concurrency = element.dataset.concurrency || \"Drop\"\n\n element.cancelActiveRequest = function() {\n if (element.activeRequest && !element.activeRequest?.isCancelled) {\n element.activeRequest.isCancelled = true\n }\n }\n\n dispatchContent(node)\n })\n}\n\nfunction dispatchContent(node: HTMLElement): void {\n let event = new Event(\"hyp-content\", { bubbles: true })\n node.dispatchEvent(event)\n}\n\ndocument.addEventListener(\"DOMContentLoaded\", init)\n\n\n\n\n// Should we connect to the socket or not?\nconst sock = new SocketConnection()\nsock.connect()\nsock.addEventListener(\"update\", (ev: CustomEvent) => handleUpdate(ev.detail))\nsock.addEventListener(\"response\", (ev: CustomEvent) => handleResponse(ev.detail))\nsock.addEventListener(\"redirect\", (ev: CustomEvent) => handleRedirect(ev.detail))\n\n\n\n\n\ntype VNode = {\n // One of three value types are used:\n // - The tag name of the element\n // - \"text\" if text node\n // - \"comment\" if comment node\n type: string\n\n // An object whose key/value pairs are the attribute\n // name and value, respectively\n attributes: [string: string]\n\n // Is set to `true` if a node is an `svg`, which tells\n // Omdomdom to treat it, and its children, as such\n isSVGContext: Boolean\n\n // The content of a \"text\" or \"comment\" node\n content: string\n\n // An array of virtual node children\n children: Array\n\n // The real DOM node\n node: Node\n}\n\n\n\n\n\ndeclare global {\n interface Window {\n Hyperbole?: HyperboleAPI;\n }\n}\n\nexport interface HyperboleAPI {\n runAction(target: HTMLElement, action: string, form?: FormData): Promise\n action(con: string, ...params: any[]): string\n hyperView(viewId: ViewId): HyperView | undefined\n parseMetadata(input: string): Metadata\n socket: SocketConnection\n}\n\n\n\n\nexport interface HyperView extends HTMLElement {\n runAction(target: HTMLElement, action: string, form?: FormData): Promise\n activeRequest?: Request\n cancelActiveRequest(): void\n concurrency: ConcurrencyMode\n _timeout?: any\n}\n\ntype ConcurrencyMode = string\n\n\nwindow.Hyperbole =\n{\n runAction: runAction,\n parseMetadata: parseMetadata,\n action: function(con, ...params: any[]) {\n let ps = params.reduce((str, param) => str + \" \" + JSON.stringify(param), \"\")\n return con + ps\n },\n hyperView: function(viewId) {\n let element = document.getElementById(viewId) as any\n if (!element?.runAction) {\n console.error(\"Element id=\" + viewId + \" was not a HyperView\")\n return undefined\n }\n return element\n },\n socket: sock\n}\n","\nimport { ViewId, Metadata } from './message'\n\n\n\nexport type Response = {\n meta: Metadata\n body: ResponseBody\n}\n\nexport type ResponseBody = string\n\nexport function parseResponse(res: ResponseBody): LiveUpdate {\n const parser = new DOMParser()\n const doc = parser.parseFromString(res, 'text/html')\n const css = doc.querySelector(\"style\") as HTMLStyleElement\n const content = doc.querySelector(\"div\") as HTMLElement\n\n return {\n content: content,\n css: css\n }\n}\n\nexport type LiveUpdate = {\n content: HTMLElement\n css: HTMLStyleElement | null\n}\n\n\nexport class FetchError extends Error {\n viewId: ViewId\n body: string\n constructor(viewId: ViewId, msg: string, body: string) {\n super(msg)\n this.viewId = viewId\n this.name = \"Fetch Error\"\n this.body = body\n }\n}\n","\nexport function setQuery(query: string) {\n if (query != currentQuery()) {\n if (query != \"\") query = \"?\" + query\n let url = location.pathname + query\n // console.log(\"history.replaceState(\", url, \")\")\n window.history.replaceState({}, \"\", url)\n }\n}\n\nfunction currentQuery(): string {\n const query = window.location.search;\n return query.startsWith('?') ? query.substring(1) : query;\n}\n"],"names":["debounce","function_","wait","options","TypeError","RangeError","immediate","storedContext","storedArguments","timeoutId","timestamp","result","run","callContext","callArguments","undefined","apply","later","last","Date","now","setTimeout","debounced","arguments_","this","Object","getPrototypeOf","Error","callNow","defineProperty","get","clear","clearTimeout","flush","trigger","module","exports","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","__webpack_modules__","hasProperty","obj","prop","prototype","hasOwnProperty","call","forEach","items","fn","length","idx","insertBefore","parent","child","refNode","node","_slicedToArray","arr","i","Array","isArray","_arrayWithHoles","_i","Symbol","iterator","_s","_e","_x","_r","_arr","_n","_d","next","done","push","value","err","return","_iterableToArrayLimit","o","minLen","_arrayLikeToArray","n","toString","slice","constructor","name","from","test","_unsupportedIterableToArray","_nonIterableRest","len","arr2","Types","DomProperties","createRecord","attrName","propName","type","_ref","_ref2","attr","property","_ref3","_ref4","Namespace","setProperty","element","style","toLowerCase","removeAttribute","parsed","parseInt","isNaN","indexOf","patch","template","vNode","rootNode","parentNode","contentChanged","content","replaceChild","assignVNode","removedAttributes","changedAttributes","attributes","oldValue","nextValue","_attrName","_oldValue","_nextValue","propertyRecord","removeAttributes","startsWith","setAttributeNS","setAttribute","setAttributes","updateAttributes","templateChildrenLength","children","vNodeChildrenLength","vNodeKeyMap","map","_","key","console","warn","keyIsValid","createKeyMap","nextChildren","templateChild","childNodes","keyedChild","vNodeChild","appendChild","childNodesLength","delta","removeChild","patchChildren","create","processedDOMString","isSVGContext","arguments","trim","replace","DOMParser","parseFromString","body","isRoot","tagName","numChildNodes","nodeType","isSVG","reduce","getBaseAttributes","attrValue","getAttribute","getPropertyValues","getAttributes","textContent","takeWhileMap","pred","lines","output","line","a","toMetadata","meta","cookies","filter","m","error","metaValue","query","pageTitle","events","metaValuesAll","parseRemoteEvent","actions","parseAction","parseMetadata","input","parseMeta","split","metas","find","match","data","breakNextSegment","detail","JSON","parse","viewId","action","ix","message","toSearch","form","params","URLSearchParams","append","globalRequestId","encodedParam","param","sanitizeParam","defaultAddress","window","location","protocol","host","pathname","SocketConnection","EventTarget","hasEverConnected","isConnected","reconnectDelay","queue","connect","addr","sock","WebSocket","onConnectError","ev","onSocketError","socket","addEventListener","_event","log","document","dispatchEvent","Event","removeEventListener","runQueue","onMessage","sendAction","msg","requestId","join","renderActionMessage","send","pop","event","command","rest","index","dropWhile","l","requireMeta","val","ProtocolError","parseResponse","targetViewId","CustomEvent","up","parseUpdate","url","parseRedirect","disconnect","close","description","super","listenKeyEvent","cb","e","source","target","datasetKey","dataset","preventDefault","nearestTarget","listenBubblingEvent","closest","listenLoad","querySelectorAll","load","delay","onLoad","onload","bubbles","listenMouseEnter","onMouseEnter","onmouseenter","listenMouseLeave","onMouseLeave","onmouseleave","targetId","targetData","id","nearestTargetId","getElementById","rootStyles","PACKAGE","version","addedRulesIndex","Set","async","runAction","activeRequest","isCancelled","concurrency","_timeout","classList","add","req","reqId","decodeURI","cookie","search","actionMessage","handleUpdate","res","update","doc","css","querySelector","src","rules","sheet","cssRules","rule","has","cssText","insertRule","addCSS","old","newTarget","dispatchContent","runMetadata","focused","focus","checkbox","checked","fixInputs","enrichHyperViews","remoteEvent","substring","currentQuery","history","replaceState","setQuery","title","view","Hyperbole","hyperView","bind","cancelActiveRequest","innerText","onsubmit","formData","FormData","onchange","oninput","startedTyping","debouncedCallback","remove","handleResponse","handleRedirect","red","href","con","str","stringify"],"sourceRoot":""} \ No newline at end of file +{"version":3,"file":"hyperbole.js","mappings":"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","sources":["webpack://web-ui/./node_modules/debounce/index.js","webpack://web-ui/webpack/bootstrap","webpack://web-ui/./node_modules/omdomdom/lib/omdomdom.es.js","webpack://web-ui/./src/lib.ts","webpack://web-ui/./src/message.ts","webpack://web-ui/./src/action.ts","webpack://web-ui/./src/sockets.ts","webpack://web-ui/./src/events.ts","webpack://web-ui/./src/index.ts","webpack://web-ui/./src/response.ts","webpack://web-ui/./src/browser.ts"],"sourcesContent":["function debounce(function_, wait = 100, options = {}) {\n\tif (typeof function_ !== 'function') {\n\t\tthrow new TypeError(`Expected the first parameter to be a function, got \\`${typeof function_}\\`.`);\n\t}\n\n\tif (wait < 0) {\n\t\tthrow new RangeError('`wait` must not be negative.');\n\t}\n\n\t// TODO: Deprecate the boolean parameter at some point.\n\tconst {immediate} = typeof options === 'boolean' ? {immediate: options} : options;\n\n\tlet storedContext;\n\tlet storedArguments;\n\tlet timeoutId;\n\tlet timestamp;\n\tlet result;\n\n\tfunction run() {\n\t\tconst callContext = storedContext;\n\t\tconst callArguments = storedArguments;\n\t\tstoredContext = undefined;\n\t\tstoredArguments = undefined;\n\t\tresult = function_.apply(callContext, callArguments);\n\t\treturn result;\n\t}\n\n\tfunction later() {\n\t\tconst last = Date.now() - timestamp;\n\n\t\tif (last < wait && last >= 0) {\n\t\t\ttimeoutId = setTimeout(later, wait - last);\n\t\t} else {\n\t\t\ttimeoutId = undefined;\n\n\t\t\tif (!immediate) {\n\t\t\t\tresult = run();\n\t\t\t}\n\t\t}\n\t}\n\n\tconst debounced = function (...arguments_) {\n\t\tif (\n\t\t\tstoredContext\n\t\t\t&& this !== storedContext\n\t\t\t&& Object.getPrototypeOf(this) === Object.getPrototypeOf(storedContext)\n\t\t) {\n\t\t\tthrow new Error('Debounced method called with different contexts of the same prototype.');\n\t\t}\n\n\t\tstoredContext = this; // eslint-disable-line unicorn/no-this-assignment\n\t\tstoredArguments = arguments_;\n\t\ttimestamp = Date.now();\n\n\t\tconst callNow = immediate && !timeoutId;\n\n\t\tif (!timeoutId) {\n\t\t\ttimeoutId = setTimeout(later, wait);\n\t\t}\n\n\t\tif (callNow) {\n\t\t\tresult = run();\n\t\t}\n\n\t\treturn result;\n\t};\n\n\tObject.defineProperty(debounced, 'isPending', {\n\t\tget() {\n\t\t\treturn timeoutId !== undefined;\n\t\t},\n\t});\n\n\tdebounced.clear = () => {\n\t\tif (!timeoutId) {\n\t\t\treturn;\n\t\t}\n\n\t\tclearTimeout(timeoutId);\n\t\ttimeoutId = undefined;\n\t};\n\n\tdebounced.flush = () => {\n\t\tif (!timeoutId) {\n\t\t\treturn;\n\t\t}\n\n\t\tdebounced.trigger();\n\t};\n\n\tdebounced.trigger = () => {\n\t\tresult = run();\n\n\t\tdebounced.clear();\n\t};\n\n\treturn debounced;\n}\n\n// Adds compatibility for ES modules\nmodule.exports.debounce = debounce;\n\nmodule.exports = debounce;\n","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tvar cachedModule = __webpack_module_cache__[moduleId];\n\tif (cachedModule !== undefined) {\n\t\treturn cachedModule.exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","/*!\n * @license MIT (https://github.com/geotrev/omdomdom/blob/master/LICENSE)\n * Omdomdom v0.3.2 (https://github.com/geotrev/omdomdom)\n * Copyright 2023 George Treviranus \n */\nvar DATA_KEY_ATTRIBUTE$1 = \"data-key\";\nvar hasProperty = function hasProperty(obj, prop) {\n return Object.prototype.hasOwnProperty.call(obj, prop);\n};\nvar keyIsValid = function keyIsValid(map, key) {\n if (!key) return false;\n if (hasProperty(map, key)) {\n console.warn(\"[OmDomDom]: Children with duplicate keys detected. Children with duplicate keys will be skipped, resulting in dropped node references. Keys must be unique and non-indexed.\");\n return false;\n }\n return true;\n};\nvar forEach = function forEach(items, fn) {\n var length = items.length;\n var idx = -1;\n if (!length) return;\n while (++idx < length) {\n if (fn(items[idx], idx) === false) break;\n }\n};\nvar createKeyMap = function createKeyMap(children) {\n var map = {};\n forEach(children, function (child) {\n var key = child.attributes[DATA_KEY_ATTRIBUTE$1];\n if (keyIsValid(map, key)) {\n map[key] = child;\n }\n });\n for (var _ in map) {\n return map;\n }\n};\nvar insertBefore = function insertBefore(parent, child, refNode) {\n return parent.node.insertBefore(child.node, refNode);\n};\nvar assignVNode = function assignVNode(template, vNode) {\n for (var property in template) {\n vNode[property] = template[property];\n }\n};\n\nvar toHTML = function toHTML(htmlString) {\n var processedDOMString = htmlString.trim().replace(/\\s+\\s+/g, \">\");\n var parser = new DOMParser();\n var context = parser.parseFromString(processedDOMString, \"text/html\");\n return context.body;\n};\n\nfunction _iterableToArrayLimit(arr, i) {\n var _i = null == arr ? null : \"undefined\" != typeof Symbol && arr[Symbol.iterator] || arr[\"@@iterator\"];\n if (null != _i) {\n var _s,\n _e,\n _x,\n _r,\n _arr = [],\n _n = !0,\n _d = !1;\n try {\n if (_x = (_i = _i.call(arr)).next, 0 === i) {\n if (Object(_i) !== _i) return;\n _n = !1;\n } else for (; !(_n = (_s = _x.call(_i)).done) && (_arr.push(_s.value), _arr.length !== i); _n = !0);\n } catch (err) {\n _d = !0, _e = err;\n } finally {\n try {\n if (!_n && null != _i.return && (_r = _i.return(), Object(_r) !== _r)) return;\n } finally {\n if (_d) throw _e;\n }\n }\n return _arr;\n }\n}\nfunction _slicedToArray(arr, i) {\n return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest();\n}\nfunction _arrayWithHoles(arr) {\n if (Array.isArray(arr)) return arr;\n}\nfunction _unsupportedIterableToArray(o, minLen) {\n if (!o) return;\n if (typeof o === \"string\") return _arrayLikeToArray(o, minLen);\n var n = Object.prototype.toString.call(o).slice(8, -1);\n if (n === \"Object\" && o.constructor) n = o.constructor.name;\n if (n === \"Map\" || n === \"Set\") return Array.from(o);\n if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);\n}\nfunction _arrayLikeToArray(arr, len) {\n if (len == null || len > arr.length) len = arr.length;\n for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];\n return arr2;\n}\nfunction _nonIterableRest() {\n throw new TypeError(\"Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\");\n}\n\nvar Types = {\n STRING: \"string\",\n INT: \"number\",\n BOOL: \"boolean\"\n};\nvar DomProperties = {};\nvar createRecord = function createRecord(attrName, propName, type) {\n return {\n attrName: attrName,\n propName: propName,\n type: type\n };\n};\nvar stringProps = [[\"style\", \"cssText\"], [\"class\", \"className\"]];\nforEach(stringProps, function (_ref) {\n var _ref2 = _slicedToArray(_ref, 2),\n attr = _ref2[0],\n property = _ref2[1];\n DomProperties[attr] = createRecord(attr, property || attr, Types.STRING);\n});\nvar booleanProps = [\"autofocus\", \"draggable\", \"hidden\", \"checked\", \"multiple\", \"muted\", \"selected\"];\nforEach(booleanProps, function (attr) {\n DomProperties[attr] = createRecord(attr, attr, Types.BOOL);\n});\nvar integerProps = [[\"tabindex\", \"tabIndex\"]];\nforEach(integerProps, function (_ref3) {\n var _ref4 = _slicedToArray(_ref3, 2),\n attr = _ref4[0],\n property = _ref4[1];\n DomProperties[attr] = createRecord(attr, property, Types.INT);\n});\nvar Namespace = {\n xlink: {\n prefix: \"xlink:\",\n resource: \"http://www.w3.org/1999/xlink\"\n },\n xml: {\n prefix: \"xml:\",\n resource: \"http://www.w3.org/XML/1998/namespace\"\n }\n};\n\nvar setProperty = function setProperty(element, type, prop, value) {\n switch (type) {\n case Types.STRING:\n {\n if (prop === DomProperties.style.propName) {\n if (value === null) {\n element.style[prop] = \"\";\n } else {\n element.style[prop] = value;\n }\n } else if (value === null) {\n element[prop] = \"\";\n } else {\n element[prop] = value;\n }\n break;\n }\n case Types.INT:\n {\n if (value === null) {\n var attr = prop.toLowerCase();\n element.removeAttribute(attr);\n } else if (value === \"0\") {\n element[prop] = 0;\n } else if (value === \"-1\") {\n element[prop] = -1;\n } else {\n var parsed = parseInt(value, 10);\n if (!isNaN(parsed)) {\n element[prop] = parsed;\n }\n }\n break;\n }\n case Types.BOOL:\n {\n if ([\"\", \"true\"].indexOf(value) < 0) {\n element[prop] = false;\n } else {\n element[prop] = true;\n }\n break;\n }\n }\n};\n\nvar removeAttributes = function removeAttributes(vNode, attributes) {\n forEach(attributes, function (attrName) {\n if (hasProperty(DomProperties, attrName)) {\n var propertyRecord = DomProperties[attrName];\n setProperty(vNode.node, propertyRecord.type, propertyRecord.propName, null);\n } else {\n if (attrName in vNode.node) {\n setProperty(vNode.node, Types.STRING, attrName, null);\n }\n vNode.node.removeAttribute(attrName);\n }\n delete vNode.attributes[attrName];\n });\n};\nvar setAttributes = function setAttributes(vNode, attributes) {\n for (var attrName in attributes) {\n var value = attributes[attrName];\n vNode.attributes[attrName] = value;\n if (hasProperty(DomProperties, attrName)) {\n var propertyRecord = DomProperties[attrName];\n setProperty(vNode.node, propertyRecord.type, propertyRecord.propName, value);\n continue;\n }\n if (attrName.startsWith(Namespace.xlink.prefix)) {\n vNode.node.setAttributeNS(Namespace.xlink.resource, attrName, value);\n continue;\n }\n if (attrName.startsWith(Namespace.xml.prefix)) {\n vNode.node.setAttributeNS(Namespace.xml.resource, attrName, value);\n continue;\n }\n if (attrName in vNode.node) {\n setProperty(vNode.node, Types.STRING, attrName, value);\n }\n vNode.node.setAttribute(attrName, value || \"\");\n }\n};\nvar getPropertyValues = function getPropertyValues(element, attributes) {\n for (var attrName in DomProperties) {\n var propertyRecord = DomProperties[attrName];\n var propName = propertyRecord.propName;\n var attrValue = element.getAttribute(attrName);\n if (attrName === DomProperties.style.attrName) {\n attributes[attrName] = element.style[propName];\n } else if (typeof attrValue === \"string\") {\n attributes[attrName] = attrValue;\n }\n }\n};\nvar getBaseAttributes = function getBaseAttributes(element) {\n return Array.prototype.reduce.call(element.attributes, function (attributes, attrName) {\n if (!hasProperty(DomProperties, attrName.name)) {\n attributes[attrName.name] = attrName.value;\n }\n return attributes;\n }, {});\n};\nvar getAttributes = function getAttributes(element) {\n var attributes = getBaseAttributes(element);\n getPropertyValues(element, attributes);\n return attributes;\n};\nvar updateAttributes = function updateAttributes(template, vNode) {\n var removedAttributes = [];\n var changedAttributes = {};\n for (var attrName in vNode.attributes) {\n var oldValue = vNode.attributes[attrName];\n var nextValue = template.attributes[attrName];\n if (oldValue === nextValue) continue;\n if (typeof nextValue === \"undefined\") {\n removedAttributes.push(attrName);\n }\n }\n for (var _attrName in template.attributes) {\n var _oldValue = vNode.attributes[_attrName];\n var _nextValue = template.attributes[_attrName];\n if (_oldValue === _nextValue) continue;\n if (typeof _nextValue !== \"undefined\") {\n changedAttributes[_attrName] = _nextValue;\n }\n }\n removeAttributes(vNode, removedAttributes);\n setAttributes(vNode, changedAttributes);\n};\n\nvar DATA_KEY_ATTRIBUTE = \"data-key\";\nfunction patchChildren(template, vNode, patch) {\n var templateChildrenLength = template.children.length;\n var vNodeChildrenLength = vNode.children.length;\n if (!templateChildrenLength && !vNodeChildrenLength) return;\n var vNodeKeyMap = createKeyMap(vNode.children);\n var nextChildren = Array(templateChildrenLength);\n if (vNodeKeyMap !== undefined) {\n forEach(template.children, function (templateChild, idx) {\n var childNodes = vNode.node.childNodes;\n var key = templateChild.attributes[DATA_KEY_ATTRIBUTE];\n if (Object.prototype.hasOwnProperty.call(vNodeKeyMap, key)) {\n var keyedChild = vNodeKeyMap[key];\n if (Array.prototype.indexOf.call(childNodes, keyedChild.node) !== idx) {\n insertBefore(vNode, keyedChild, childNodes[idx]);\n }\n nextChildren[idx] = keyedChild;\n delete vNodeKeyMap[key];\n patch(templateChild, nextChildren[idx]);\n } else {\n insertBefore(vNode, templateChild, childNodes[idx]);\n nextChildren[idx] = templateChild;\n }\n });\n } else {\n forEach(template.children, function (templateChild, idx) {\n var vNodeChild = vNode.children[idx];\n if (typeof vNodeChild !== \"undefined\") {\n patch(templateChild, vNodeChild);\n nextChildren[idx] = vNodeChild;\n } else {\n vNode.node.appendChild(templateChild.node);\n nextChildren[idx] = templateChild;\n }\n });\n }\n vNode.children = nextChildren;\n var childNodesLength = vNode.node.childNodes.length;\n var delta = childNodesLength - templateChildrenLength;\n if (delta > 0) {\n while (delta > 0) {\n vNode.node.removeChild(vNode.node.childNodes[childNodesLength - 1]);\n childNodesLength--;\n delta--;\n }\n }\n}\n\nvar patch = function patch(template, vNode, rootNode) {\n if (!template || !vNode) return;\n rootNode = rootNode || vNode.node.parentNode;\n var contentChanged = template.content && template.content !== vNode.content;\n if (template.type !== vNode.type || contentChanged) {\n rootNode.replaceChild(template.node, vNode.node);\n return assignVNode(template, vNode);\n }\n updateAttributes(template, vNode);\n patchChildren(template, vNode, patch);\n};\nvar render = function render(vNode, root) {\n root.appendChild(vNode.node);\n};\nvar create = function create(node) {\n var isSVGContext = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;\n if (typeof node === \"string\") {\n node = toHTML(node);\n }\n var isRoot = node.tagName === \"BODY\";\n var childNodes = node.childNodes;\n var numChildNodes = childNodes ? childNodes.length : 0;\n if (isRoot) {\n if (numChildNodes > 1) {\n throw new Error(\"[OmDomDom]: Your element should not have more than one root node.\");\n } else if (numChildNodes === 0) {\n throw new Error(\"[OmDomDom]: Your element should have at least one root node.\");\n } else {\n return create(childNodes[0]);\n }\n }\n var type = node.nodeType === 3 ? \"text\" : node.nodeType === 8 ? \"comment\" : node.tagName.toLowerCase();\n var isSVG = isSVGContext || type === \"svg\";\n var attributes = node.nodeType === 1 ? getAttributes(node) : {};\n var content = numChildNodes > 0 ? null : node.textContent;\n var children = Array(numChildNodes);\n forEach(childNodes, function (child, idx) {\n children[idx] = create(child, isSVG);\n });\n return {\n type: type,\n attributes: attributes,\n children: children,\n content: content,\n node: node,\n isSVGContext: isSVG\n };\n};\n\nexport { create, patch, render };\n//# sourceMappingURL=omdomdom.es.js.map\n","\n\nexport function takeWhileMap(pred: (val: T) => A | undefined, lines: T[]): A[] {\n var output = []\n for (var line of lines) {\n let a = pred(line)\n if (a)\n output.push(a)\n else\n break;\n }\n\n return output\n}\n\nexport function dropWhile(pred: (val: T) => A | undefined, lines: T[]): T[] {\n let index = 0;\n while (index < lines.length && pred(lines[index])) {\n index++;\n }\n return lines.slice(index);\n}\n\n","\nimport { takeWhileMap, dropWhile } from \"./lib\"\n\n\n\nexport type Meta = { key: string, value: string }\nexport type ViewId = string\nexport type RequestId = number\nexport type EncodedAction = string\nexport type ViewState = string\n\ntype RemoteEvent = { name: string, detail: any }\n\n\nexport function renderMetas(meta: Meta[]): string {\n return meta.map(m => m.key + \": \" + m.value).join('\\n')\n}\n\nexport type Metadata = {\n cookies?: string[]\n // redirect?: string\n error?: string\n query?: string\n events?: RemoteEvent[]\n actions?: [ViewId, string][],\n pageTitle?: string\n}\n\n\nexport function toMetadata(meta: Meta[]): Metadata {\n\n return {\n cookies: meta.filter(m => m.key == \"Cookie\").map(m => m.value),\n // redirect: metaValue(\"Redirect\", meta),\n error: metaValue(\"Error\", meta),\n query: metaValue(\"Query\", meta),\n pageTitle: metaValue(\"PageTitle\", meta),\n events: metaValuesAll(\"Event\", meta).map(parseRemoteEvent),\n actions: metaValuesAll(\"Trigger\", meta).map(parseAction),\n }\n}\n\n// viewId: meta.find(m => m.key == \"VIEW-ID\")?.value,\n\nexport function parseMetadata(input: string): Metadata {\n let metas = takeWhileMap(parseMeta, input.trim().split(\"\\n\"))\n return toMetadata(metas)\n}\n\n\nexport function metaValue(key: string, metas: Meta[]): string | undefined {\n return metas.find(m => m.key == key)?.value\n}\n\nexport function metaValuesAll(key: string, metas: Meta[]): string[] {\n return metas.filter(m => m.key == key).map(m => m.value)\n}\n\nexport type SplitMessage = {\n command: string,\n metas: Meta[],\n rest: string[]\n}\n\n\nexport function splitMessage(message: string): SplitMessage {\n let lines = message.split(\"\\n\")\n let command: string = lines[0]\n let metas: Meta[] = takeWhileMap(parseMeta, lines.slice(1))\n // console.log(\"Split Metadata\", lines.length)\n // console.log(\" [0]\", lines[0])\n // console.log(\" [1]\", lines[1])\n let rest = dropWhile(l => l == \"\", lines.slice(metas.length + 1))\n\n return { command, metas, rest }\n}\n\nexport function parseMeta(line: string): Meta | undefined {\n let match = line.match(/^(\\w+)\\: (.*)$/)\n if (match) {\n return {\n key: match[1],\n value: match[2]\n }\n }\n}\n\n\nexport function parseRemoteEvent(input: string): RemoteEvent {\n let [name, data] = breakNextSegment(input)\n return {\n name,\n detail: JSON.parse(data)\n }\n}\n\nexport function parseAction(input: string): [ViewId, string] {\n let [viewId, action] = breakNextSegment(input)\n return [viewId, action]\n}\n\nfunction breakNextSegment(input: string): [string, string] | undefined {\n let ix = input.indexOf('|')\n if (ix === -1) {\n let err = new Error(\"Bad Encoding, Expected Segment\")\n err.message = input\n throw err\n }\n return [input.slice(0, ix), input.slice(ix + 1)]\n}\n\n","\nimport { takeWhileMap } from \"./lib\"\nimport { Meta, ViewId, RequestId, EncodedAction, ViewState } from \"./message\"\nimport * as message from \"./message\"\n\n\n\nexport type ActionMessage = {\n viewId: ViewId\n action: EncodedAction\n requestId: RequestId\n state?: ViewState\n meta: Meta[]\n form: URLSearchParams | undefined\n}\n\n\n\n\nexport function actionMessage(id: ViewId, action: EncodedAction, state: ViewState | undefined, reqId: RequestId, form?: FormData): ActionMessage {\n let meta: Meta[] = [\n { key: \"Cookie\", value: decodeURI(document.cookie) },\n { key: \"Query\", value: window.location.search }\n ]\n\n return { viewId: id, action, state, requestId: reqId, meta, form: toSearch(form) }\n}\n\nexport function toSearch(form?: FormData): URLSearchParams | undefined {\n if (!form) return undefined\n\n const params = new URLSearchParams()\n\n form.forEach((value, key) => {\n params.append(key, value as string)\n })\n\n return params\n}\n\nexport function renderActionMessage(msg: ActionMessage): string {\n let header = [\n \"|ACTION|\",\n \"ViewId: \" + msg.viewId,\n \"Action: \" + msg.action,\n ]\n\n\n if (msg.state) {\n header.push(\"State: \" + msg.state)\n }\n\n header.push(\"RequestId: \" + msg.requestId)\n\n return [\n header.join('\\n'),\n message.renderMetas(msg.meta),\n ].join('\\n') + renderForm(msg.form)\n}\n\n\nexport function renderForm(form: URLSearchParams | undefined): string {\n if (!form) return \"\"\n return \"\\n\\n\" + form\n}\n\nlet globalRequestId: RequestId = 0\n\nexport type Request = {\n requestId: RequestId\n isCancelled: boolean\n}\n\nexport function newRequest(): Request {\n let requestId = ++globalRequestId\n return { requestId, isCancelled: false }\n}\n\n\n\n// Sanitized Encoding ------------------------------------\n\nexport function encodedParam(action: string, param: string): string {\n return action + ' ' + sanitizeParam(param)\n}\n\nfunction sanitizeParam(param: string): string {\n if (param == \"\") {\n return \"|\"\n }\n\n return param.replace(/_/g, \"\\\\_\").replace(/\\s+/g, \"_\")\n}\n","import { ActionMessage, renderActionMessage } from './action'\nimport { ResponseBody } from \"./response\"\nimport * as message from \"./message\"\nimport { ViewId, RequestId, EncodedAction, metaValue, Metadata } from \"./message\"\n\nconst protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';\nconst defaultAddress = `${protocol}//${window.location.host}${window.location.pathname}`\n\n\n\nexport class SocketConnection extends EventTarget {\n socket: WebSocket\n\n hasEverConnected: Boolean = false\n isConnected: Boolean = false\n reconnectDelay: number = 0\n queue: ActionMessage[] = []\n\n // constructor() { super() }\n\n connect(addr = defaultAddress) {\n const sock = new WebSocket(addr)\n this.socket = sock\n\n function onConnectError(ev: Event) {\n console.error(\"Connect Error\", ev)\n }\n\n function onSocketError(ev: Event) {\n console.error(\"Socket Error\", ev)\n }\n\n\n // initial connection errors\n sock.addEventListener('error', onConnectError)\n\n sock.addEventListener('open', (_event) => {\n console.log(\"Websocket Connected\")\n\n if (this.hasEverConnected) {\n document.dispatchEvent(new Event(\"hyp-socket-reconnect\"))\n }\n\n this.isConnected = true\n this.hasEverConnected = true\n this.reconnectDelay = 1000\n sock.removeEventListener('error', onConnectError)\n sock.addEventListener('error', onSocketError)\n\n document.dispatchEvent(new Event(\"hyp-socket-connect\"))\n\n this.runQueue()\n })\n\n sock.addEventListener('close', _ => {\n if (this.isConnected) {\n document.dispatchEvent(new Event(\"hyp-socket-disconnect\"))\n }\n\n this.isConnected = false\n sock.removeEventListener('error', onSocketError)\n\n // attempt to reconnect in 1s\n if (this.hasEverConnected) {\n console.log(\"Reconnecting in \" + (this.reconnectDelay / 1000) + \"s\")\n setTimeout(() => this.connect(addr), this.reconnectDelay)\n }\n\n sock.removeEventListener('error', onSocketError)\n })\n\n sock.addEventListener('message', ev => this.onMessage(ev))\n }\n\n async sendAction(action: ActionMessage) {\n if (this.isConnected) {\n let msg = renderActionMessage(action)\n this.socket.send(msg)\n }\n else {\n this.queue.push(action)\n }\n }\n\n private runQueue() {\n // send all messages queued while disconnected \n let next: ActionMessage | null = this.queue.pop()\n if (next) {\n console.log(\"runQueue: \", next)\n this.sendAction(next)\n this.runQueue()\n }\n }\n\n\n // full responses will never be sent over!\n private onMessage(event: MessageEvent) {\n let { command, metas, rest } = message.splitMessage(event.data)\n // console.log(\"MESSAGE\", command, metas, rest)\n\n let requestId = parseInt(requireMeta(\"RequestId\"), 0)\n\n function requireMeta(key: string): string {\n let val = metaValue(key, metas)\n if (!val) throw new ProtocolError(\"Missing Required Metadata: \" + key, event.data)\n return val\n }\n\n function parseResponse(rest: string[]): Update {\n let viewId = requireMeta(\"ViewId\")\n let action = requireMeta(\"Action\")\n return {\n requestId,\n targetViewId: undefined,\n viewId,\n action,\n meta: message.toMetadata(metas),\n body: rest.join(\"\\n\"),\n }\n }\n\n function parseUpdate(rest: string[]): Update {\n let up = parseResponse(rest)\n // add the TargetViewId\n up.targetViewId = metaValue(\"TargetViewId\", metas)\n return up\n }\n\n function parseRedirect(rest: string[]): Redirect {\n let url = rest[0]\n return {\n requestId,\n url\n }\n }\n\n switch (command) {\n\n case \"|UPDATE|\":\n return this.dispatchEvent(new CustomEvent(\"update\", { detail: parseUpdate(rest) }))\n\n case \"|RESPONSE|\":\n return this.dispatchEvent(new CustomEvent(\"response\", { detail: parseResponse(rest) }))\n\n case \"|REDIRECT|\":\n return this.dispatchEvent(new CustomEvent(\"redirect\", { detail: parseRedirect(rest) }))\n }\n }\n\n\n // so what if they send remote events in the page? trigger, redirect, page title, etc...\n // we aren't connected yet on a page thing\n\n // private async waitMessage(reqId: RequestId, id: ViewId): Promise {\n // return new Promise((resolve, reject) => {\n // const onMessage = (event: MessageEvent) => {\n // let data: string = event.data\n // let lines = data.split(\"\\n\").slice(1) // drop the command line\n //\n // let parsed = splitMetadata(lines)\n // let metadata: Metadata = parsed.metadata\n //\n // if (!metadata.requestId) {\n // console.error(\"Missing RequestId!\", metadata, event.data)\n // return\n // }\n //\n // if (metadata.requestId != reqId) {\n // // skip, it's not us!\n // return\n // }\n //\n //\n // // We have found our message. Remove the listener\n // this.socket.removeEventListener('message', onMessage)\n //\n // // set the cookies. These happen automatically in http\n // metadata.cookies.forEach((cookie: string) => {\n // document.cookie = cookie\n // })\n //\n // if (metadata.error) {\n // reject(new FetchError(id, metadata.error, parsed.rest.join('\\n')))\n // return\n // }\n //\n // resolve(parsed)\n // }\n //\n // this.socket.addEventListener('message', onMessage)\n // this.socket.addEventListener('error', reject)\n // })\n // }\n\n disconnect() {\n this.isConnected = false\n this.hasEverConnected = false\n this.socket.close()\n }\n}\n\n\nexport type Update = {\n requestId: RequestId\n meta: Metadata\n viewId: ViewId\n targetViewId?: ViewId\n action: EncodedAction\n body: ResponseBody\n}\n\nexport type Redirect = {\n requestId: RequestId\n url: string\n}\n\nexport type MessageType = string\n\n\n// PARSING MESSAGE ---------------------------------------\n\nexport class ProtocolError extends Error {\n constructor(description: string, body: string) {\n super(description + \"\\n\" + body)\n this.name = \"ProtocolError\"\n }\n}\n","\nimport * as debounce from 'debounce'\nimport { encodedParam } from './action'\n\nexport type UrlFragment = string\n\nexport function listenKeydown(cb: (target: HTMLElement, action: string) => void): void {\n listenKeyEvent(\"keydown\", cb)\n}\n\nexport function listenKeyup(cb: (target: HTMLElement, action: string) => void): void {\n listenKeyEvent(\"keyup\", cb)\n}\n\nexport function listenKeyEvent(event: string, cb: (target: HTMLElement, action: string) => void): void {\n document.addEventListener(event.toLowerCase(), function(e: KeyboardEvent) {\n let source = e.target as HTMLInputElement\n\n let datasetKey = \"on\" + event + e.key\n let action = source.dataset[datasetKey]\n if (!action) return\n\n e.preventDefault()\n cb(nearestTarget(source), action)\n })\n}\n\nexport function listenBubblingEvent(event: string, cb: (target: HTMLElement, action: string) => void): void {\n document.addEventListener(event, function(e) {\n let el = e.target as HTMLInputElement\n\n // clicks can fire on internal elements. Find the parent with a click handler\n let source = el.closest(\"[data-on\" + event + \"]\") as HTMLElement\n if (!source) return\n\n e.preventDefault()\n let target = nearestTarget(source)\n cb(target, source.dataset[\"on\" + event])\n })\n}\n\nexport function listenClick(cb: (target: HTMLElement, action: string) => void): void {\n listenBubblingEvent(\"click\", cb)\n}\n\nexport function listenDblClick(cb: (target: HTMLElement, action: string) => void): void {\n listenBubblingEvent(\"dblclick\", cb)\n}\n\n\nexport function listenTopLevel(cb: (target: HTMLElement, action: string) => void): void {\n document.addEventListener(\"hyp-load\", function(e: CustomEvent) {\n let action = e.detail.onLoad\n let target = e.detail.target\n cb(target, action)\n })\n\n document.addEventListener(\"hyp-mouseenter\", function(e: CustomEvent) {\n let action = e.detail.onMouseEnter\n let target = e.detail.target\n cb(target, action)\n })\n\n document.addEventListener(\"hyp-mouseleave\", function(e: CustomEvent) {\n let action = e.detail.onMouseLeave\n let target = e.detail.target\n cb(target, action)\n })\n}\n\n\nexport function listenLoad(node: HTMLElement): void {\n\n // it doesn't really matter WHO runs this except that it should have target\n node.querySelectorAll(\"[data-onload]\").forEach((load: HTMLElement) => {\n let delay = parseInt(load.dataset.delay) || 0\n let onLoad = load.dataset.onload\n // console.log(\"load start\", load.dataset.onLoad)\n\n // load no longer exists!\n // we should clear the timeout or back out if the dom is replaced in the interem\n setTimeout(() => {\n let target = nearestTarget(load)\n // console.log(\"load go\", load.dataset.onLoad)\n\n if (load.dataset.onload != onLoad) {\n // the onLoad no longer exists\n return\n }\n\n const event = new CustomEvent(\"hyp-load\", { bubbles: true, detail: { target, onLoad } })\n load.dispatchEvent(event)\n }, delay)\n })\n}\n\nexport function listenMouseEnter(node: HTMLElement): void {\n node.querySelectorAll(\"[data-onmouseenter]\").forEach((node: HTMLElement) => {\n let onMouseEnter = node.dataset.onmouseenter\n\n let target = nearestTarget(node)\n\n node.onmouseenter = () => {\n const event = new CustomEvent(\"hyp-mouseenter\", { bubbles: true, detail: { target, onMouseEnter } })\n node.dispatchEvent(event)\n }\n })\n}\n\nexport function listenMouseLeave(node: HTMLElement): void {\n node.querySelectorAll(\"[data-onmouseleave]\").forEach((node: HTMLElement) => {\n let onMouseLeave = node.dataset.onmouseleave\n\n let target = nearestTarget(node)\n\n node.onmouseleave = () => {\n const event = new CustomEvent(\"hyp-mouseleave\", { bubbles: true, detail: { target, onMouseLeave } })\n node.dispatchEvent(event)\n }\n })\n}\n\n\nexport function listenChange(cb: (target: HTMLElement, action: string) => void): void {\n document.addEventListener(\"change\", function(e) {\n let el = e.target as HTMLElement\n\n let source = el.closest(\"[data-onchange]\") as HTMLInputElement\n\n if (!source) return\n e.preventDefault()\n\n if (source.value == null) {\n console.error(\"Missing input value:\", source)\n return\n }\n\n let target = nearestTarget(source)\n let action = encodedParam(source.dataset.onchange, source.value)\n cb(target, action)\n })\n}\n\ninterface LiveInputElement extends HTMLInputElement {\n debouncedCallback?: Function;\n}\n\nexport function listenInput(startedTyping: (target: HTMLElement) => void, cb: (target: HTMLElement, action: string) => void): void {\n document.addEventListener(\"input\", function(e) {\n let el = e.target as HTMLElement\n let source = el.closest(\"[data-oninput]\") as LiveInputElement\n\n if (!source) return\n\n let delay = parseInt(source.dataset.delay) || 250\n if (delay < 250) {\n console.warn(\"Input delay < 250 can result in poor performance.\")\n }\n\n if (!source?.dataset.oninput) {\n console.error(\"Missing onInput: \", source)\n return\n }\n\n e.preventDefault()\n\n let target = nearestTarget(source)\n\n // I want to CANCEL the active request as soon as we start typing\n startedTyping(target)\n\n if (!source.debouncedCallback) {\n source.debouncedCallback = debounce(() => {\n let action = encodedParam(source.dataset.oninput, source.value)\n cb(target, action)\n }, delay)\n }\n\n source.debouncedCallback()\n })\n}\n\n\n\nexport function listenFormSubmit(cb: (target: HTMLElement, action: string, form: FormData) => void): void {\n document.addEventListener(\"submit\", function(e) {\n let form = e.target as HTMLFormElement\n\n if (!form?.dataset.onsubmit) {\n console.error(\"Missing onSubmit: \", form)\n return\n }\n\n e.preventDefault()\n\n let target = nearestTarget(form)\n const formData = new FormData(form)\n cb(target, form.dataset.onsubmit, formData)\n })\n}\n\nfunction nearestTargetId(node: HTMLElement): string | undefined {\n let targetData = node.closest(\"[data-target]\") as HTMLElement | undefined\n return targetData?.dataset.target || node.closest(\"[id]\")?.id\n}\n\nfunction nearestTarget(node: HTMLElement): HTMLElement {\n let targetId = nearestTargetId(node)\n let target = document.getElementById(targetId)\n\n if (!target) {\n console.error(\"Cannot find target: \", targetId, node)\n return\n }\n\n return target\n}\n","import { patch, create } from \"omdomdom/lib/omdomdom.es.js\"\nimport { SocketConnection, Update, Redirect } from './sockets'\nimport { listenChange, listenClick, listenDblClick, listenFormSubmit, listenLoad, listenTopLevel, listenInput, listenKeydown, listenKeyup, listenMouseEnter, listenMouseLeave } from './events'\nimport { actionMessage, ActionMessage, Request, newRequest } from './action'\nimport { ViewId, Metadata, parseMetadata, ViewState } from './message'\nimport { setQuery } from \"./browser\"\nimport { parseResponse, Response, LiveUpdate } from './response'\n\nlet PACKAGE = require('../package.json');\n\n\n// console.log(\"VERSION 2\", INIT_PAGE, INIT_STATE)\nconsole.log(\"Hyperbole \" + PACKAGE.version)\n\n\nlet rootStyles: HTMLStyleElement;\nlet addedRulesIndex = new Set();\n\n\n\n\n\n// Run an action in a given HyperView\nasync function runAction(target: HyperView, action: string, form?: FormData) {\n if (target === undefined) {\n console.error(\"Undefined HyperView!\", action)\n return\n }\n\n if (action === undefined) {\n console.error(\"Undefined Action!\", target.id)\n return\n }\n\n if (target.activeRequest && !target.activeRequest?.isCancelled) {\n // Active Request!\n if (target.concurrency == \"Drop\") {\n console.warn(\"Drop action overlapping with active request (\" + target.activeRequest + \")\", action)\n return\n }\n }\n\n target._timeout = setTimeout(() => {\n // add loading after 100ms, not right away\n // if it runs shorter than that we probably don't want to show the user any loading feedback\n target.classList.add(\"hyp-loading\")\n }, 100)\n\n let state = target.dataset.state\n\n let req = newRequest()\n let msg = actionMessage(target.id, action, state, req.requestId, form)\n\n // Set the requestId\n target.activeRequest = req\n\n sock.sendAction(msg)\n}\n\n\nfunction handleRedirect(red: Redirect) {\n console.log(\"REDIRECT\", red)\n window.location.href = red.url\n}\n\n// in-process update\nfunction handleResponse(res: Update) {\n // console.log(\"Handle Response\", res)\n let target = handleUpdate(res)\n\n // clean up the request\n delete target.activeRequest\n clearTimeout(target._timeout)\n target.classList.remove(\"hyp-loading\")\n}\n\nfunction handleUpdate(res: Update): HyperView {\n // console.log(\"|UPDATE|\", res)\n\n let targetViewId = res.targetViewId || res.viewId\n let target = document.getElementById(targetViewId) as HyperView\n\n\n if (!target) {\n console.error(\"Missing Update Target: \", targetViewId, res)\n return target\n }\n\n if (res.requestId < target.activeRequest?.requestId) {\n // this should only happen on Replace, since other requests should be dropped\n // but it's safe to assume we never want to apply an old requestId\n console.warn(\"Ignore Stale Action (\" + res.requestId + \") vs (\" + target.activeRequest.requestId + \"): \" + res.action)\n return target\n }\n else if (target.activeRequest?.isCancelled) {\n console.warn(\"Cancelled request\", target.activeRequest?.requestId)\n delete target.activeRequest\n return target\n }\n\n let update: LiveUpdate = parseResponse(res.body)\n\n if (!update.content) {\n console.error(\"Empty Response!\", res.body)\n return target\n }\n\n // First, update the stylesheet\n addCSS(update.css)\n\n\n // Patch the node\n const old: VNode = create(target)\n let next: VNode = create(update.content)\n let state = (next.attributes as any)[\"data-state\"]\n next.attributes = old.attributes\n patch(next, old)\n\n\n // Emit relevant events\n let newTarget = document.getElementById(target.id)\n dispatchContent(newTarget)\n\n if (!newTarget) {\n console.warn(\"Target Missing: \", target.id)\n return target\n }\n\n // re-add state attribute \n newTarget.dataset.state = state\n\n // execute the metadata, anything that doesn't interrupt the dom update\n runMetadata(res.meta, newTarget)\n\n // now way for these to bubble)\n listenLoad(newTarget)\n listenMouseEnter(newTarget)\n listenMouseLeave(newTarget)\n fixInputs(newTarget)\n enrichHyperViews(newTarget)\n\n return target\n}\n// catch (err) {\n// console.error(\"Caught Error in HyperView (\" + target.id + \"):\\n\", err)\n//\n// // Hyperbole catches handler errors, and the server controls what to display to the user on an error\n// // but if you manage to crash your parent server process somehow, the response may be empty\n// target.innerHTML = err.body || \"
Hyperbole Internal Error
\"\n// }\n\n\n// Remove loading and clear add timeout\n\n// function runMetadataImmediate(meta: Metadata): boolean {\n// if (meta.redirect) {\n// // perform a redirect immediately\n// window.location.href = meta.redirect\n// return true\n// }\n// }\n\nfunction runMetadata(meta: Metadata, target?: HTMLElement) {\n if (meta.query != null) {\n setQuery(meta.query)\n }\n\n if (meta.pageTitle != null) {\n document.title = meta.pageTitle\n }\n\n for (var remoteEvent of meta.events) {\n setTimeout(() => {\n let event = new CustomEvent(remoteEvent.name, { bubbles: true, detail: remoteEvent.detail })\n let eventTarget = target || document\n eventTarget.dispatchEvent(event)\n }, 10)\n }\n\n meta.actions.forEach(([viewId, action]) => {\n setTimeout(() => {\n let view = window.Hyperbole.hyperView(viewId)\n if (view) {\n runAction(view, action)\n }\n }, 10)\n })\n}\n\n\nfunction fixInputs(target: HTMLElement) {\n let focused = target.querySelector(\"[autofocus]\") as HTMLInputElement\n if (focused?.focus) {\n focused.focus()\n }\n\n target.querySelectorAll(\"input[value]\").forEach((input: HTMLInputElement) => {\n let val = input.getAttribute(\"value\")\n if (val !== undefined) {\n input.value = val\n }\n })\n\n target.querySelectorAll(\"input[type=checkbox]\").forEach((checkbox: HTMLInputElement) => {\n let checked = checkbox.dataset.checked == \"True\"\n checkbox.checked = checked\n })\n}\n\nfunction addCSS(src: HTMLStyleElement | null) {\n if (!src) return;\n const rules: any = src.sheet.cssRules\n for (const rule of rules) {\n if (addedRulesIndex.has(rule.cssText) == false) {\n rootStyles.sheet.insertRule(rule.cssText);\n addedRulesIndex.add(rule.cssText);\n }\n }\n}\n\n\n\n\nfunction init() {\n // metadata attached to initial page loads need to be executed\n let meta = parseMetadata(document.getElementById(\"hyp.metadata\").innerText)\n // runMetadataImmediate(meta)\n runMetadata(meta)\n\n rootStyles = document.querySelector('style')\n\n listenTopLevel(async function(target: HyperView, action: string) {\n runAction(target, action)\n })\n\n listenLoad(document.body)\n listenMouseEnter(document.body)\n listenMouseLeave(document.body)\n enrichHyperViews(document.body)\n\n\n listenClick(async function(target: HyperView, action: string) {\n // console.log(\"CLICK\", target.id, action)\n runAction(target, action)\n })\n\n listenDblClick(async function(target: HyperView, action: string) {\n // console.log(\"DBLCLICK\", target.id, action)\n runAction(target, action)\n })\n\n listenKeydown(async function(target: HyperView, action: string) {\n // console.log(\"KEYDOWN\", target.id, action)\n runAction(target, action)\n })\n\n listenKeyup(async function(target: HyperView, action: string) {\n // console.log(\"KEYUP\", target.id, action)\n runAction(target, action)\n })\n\n listenFormSubmit(async function(target: HyperView, action: string, form: FormData) {\n // console.log(\"FORM\", target.id, action, form)\n runAction(target, action, form)\n })\n\n listenChange(async function(target: HyperView, action: string) {\n runAction(target, action)\n })\n\n function onStartedTyping(target: HyperView) {\n if (target.concurrency == \"Replace\") {\n target.cancelActiveRequest()\n }\n }\n\n listenInput(onStartedTyping, async function(target: HyperView, action: string) {\n runAction(target, action)\n })\n}\n\n\n\nfunction enrichHyperViews(node: HTMLElement): void {\n // enrich all the hyperviews\n node.querySelectorAll(\"[id]\").forEach((element: HyperView) => {\n element.runAction = function(action: string) {\n runAction(this, action)\n }.bind(element)\n\n element.concurrency = element.dataset.concurrency || \"Drop\"\n\n element.cancelActiveRequest = function() {\n if (element.activeRequest && !element.activeRequest?.isCancelled) {\n element.activeRequest.isCancelled = true\n }\n }\n\n dispatchContent(node)\n })\n}\n\nfunction dispatchContent(node: HTMLElement): void {\n let event = new Event(\"hyp-content\", { bubbles: true })\n node.dispatchEvent(event)\n}\n\ndocument.addEventListener(\"DOMContentLoaded\", init)\n\n\n\n\n// Should we connect to the socket or not?\nconst sock = new SocketConnection()\nsock.connect()\nsock.addEventListener(\"update\", (ev: CustomEvent) => handleUpdate(ev.detail))\nsock.addEventListener(\"response\", (ev: CustomEvent) => handleResponse(ev.detail))\nsock.addEventListener(\"redirect\", (ev: CustomEvent) => handleRedirect(ev.detail))\n\n\n\n\n\ntype VNode = {\n // One of three value types are used:\n // - The tag name of the element\n // - \"text\" if text node\n // - \"comment\" if comment node\n type: string\n\n // An object whose key/value pairs are the attribute\n // name and value, respectively\n attributes: [string: string]\n\n // Is set to `true` if a node is an `svg`, which tells\n // Omdomdom to treat it, and its children, as such\n isSVGContext: Boolean\n\n // The content of a \"text\" or \"comment\" node\n content: string\n\n // An array of virtual node children\n children: Array\n\n // The real DOM node\n node: Node\n}\n\n\n\n\n\ndeclare global {\n interface Window {\n Hyperbole?: HyperboleAPI;\n }\n}\n\nexport interface HyperboleAPI {\n runAction(target: HTMLElement, action: string, form?: FormData): Promise\n action(con: string, ...params: any[]): string\n hyperView(viewId: ViewId): HyperView | undefined\n parseMetadata(input: string): Metadata\n socket: SocketConnection\n}\n\n\n\n\nexport interface HyperView extends HTMLElement {\n runAction(target: HTMLElement, action: string, form?: FormData): Promise\n activeRequest?: Request\n cancelActiveRequest(): void\n concurrency: ConcurrencyMode\n _timeout?: any\n}\n\ntype ConcurrencyMode = string\n\n\nwindow.Hyperbole =\n{\n runAction: runAction,\n parseMetadata: parseMetadata,\n action: function(con, ...params: any[]) {\n let ps = params.reduce((str, param) => str + \" \" + JSON.stringify(param), \"\")\n return con + ps\n },\n hyperView: function(viewId) {\n let element = document.getElementById(viewId) as any\n if (!element?.runAction) {\n console.error(\"Element id=\" + viewId + \" was not a HyperView\")\n return undefined\n }\n return element\n },\n socket: sock\n}\n","\nimport { ViewId, Metadata } from './message'\n\n\n\nexport type Response = {\n meta: Metadata\n body: ResponseBody\n}\n\nexport type ResponseBody = string\n\nexport function parseResponse(res: ResponseBody): LiveUpdate {\n const parser = new DOMParser()\n const doc = parser.parseFromString(res, 'text/html')\n const css = doc.querySelector(\"style\") as HTMLStyleElement\n const content = doc.querySelector(\"div\") as HTMLElement\n\n return {\n content: content,\n css: css\n }\n}\n\nexport type LiveUpdate = {\n content: HTMLElement\n css: HTMLStyleElement | null\n}\n\n\nexport class FetchError extends Error {\n viewId: ViewId\n body: string\n constructor(viewId: ViewId, msg: string, body: string) {\n super(msg)\n this.viewId = viewId\n this.name = \"Fetch Error\"\n this.body = body\n }\n}\n","\nexport function setQuery(query: string) {\n if (query != currentQuery()) {\n if (query != \"\") query = \"?\" + query\n let url = location.pathname + query\n // console.log(\"history.replaceState(\", url, \")\")\n window.history.replaceState({}, \"\", url)\n }\n}\n\nfunction currentQuery(): string {\n const query = window.location.search;\n return query.startsWith('?') ? query.substring(1) : query;\n}\n"],"names":["debounce","function_","wait","options","TypeError","RangeError","immediate","storedContext","storedArguments","timeoutId","timestamp","result","run","callContext","callArguments","undefined","apply","later","last","Date","now","setTimeout","debounced","arguments_","this","Object","getPrototypeOf","Error","callNow","defineProperty","get","clear","clearTimeout","flush","trigger","module","exports","__webpack_module_cache__","__webpack_require__","moduleId","cachedModule","__webpack_modules__","hasProperty","obj","prop","prototype","hasOwnProperty","call","forEach","items","fn","length","idx","insertBefore","parent","child","refNode","node","_slicedToArray","arr","i","Array","isArray","_arrayWithHoles","_i","Symbol","iterator","_s","_e","_x","_r","_arr","_n","_d","next","done","push","value","err","return","_iterableToArrayLimit","o","minLen","_arrayLikeToArray","n","toString","slice","constructor","name","from","test","_unsupportedIterableToArray","_nonIterableRest","len","arr2","Types","DomProperties","createRecord","attrName","propName","type","_ref","_ref2","attr","property","_ref3","_ref4","Namespace","setProperty","element","style","toLowerCase","removeAttribute","parsed","parseInt","isNaN","indexOf","patch","template","vNode","rootNode","parentNode","contentChanged","content","replaceChild","assignVNode","removedAttributes","changedAttributes","attributes","oldValue","nextValue","_attrName","_oldValue","_nextValue","propertyRecord","removeAttributes","startsWith","setAttributeNS","setAttribute","setAttributes","updateAttributes","templateChildrenLength","children","vNodeChildrenLength","vNodeKeyMap","map","_","key","console","warn","keyIsValid","createKeyMap","nextChildren","templateChild","childNodes","keyedChild","vNodeChild","appendChild","childNodesLength","delta","removeChild","patchChildren","create","processedDOMString","isSVGContext","arguments","trim","replace","DOMParser","parseFromString","body","isRoot","tagName","numChildNodes","nodeType","isSVG","reduce","getBaseAttributes","attrValue","getAttribute","getPropertyValues","getAttributes","textContent","takeWhileMap","pred","lines","output","line","a","toMetadata","meta","cookies","filter","m","error","metaValue","query","pageTitle","events","metaValuesAll","parseRemoteEvent","actions","parseAction","parseMetadata","input","parseMeta","split","metas","find","match","data","breakNextSegment","detail","JSON","parse","viewId","action","ix","message","toSearch","form","params","URLSearchParams","append","globalRequestId","encodedParam","param","sanitizeParam","defaultAddress","window","location","protocol","host","pathname","SocketConnection","EventTarget","hasEverConnected","isConnected","reconnectDelay","queue","connect","addr","sock","WebSocket","onConnectError","ev","onSocketError","socket","addEventListener","_event","log","document","dispatchEvent","Event","removeEventListener","runQueue","onMessage","sendAction","msg","header","state","requestId","join","renderActionMessage","send","pop","event","command","rest","index","dropWhile","l","requireMeta","val","ProtocolError","parseResponse","targetViewId","CustomEvent","up","parseUpdate","url","parseRedirect","disconnect","close","description","super","listenKeyEvent","cb","e","source","target","datasetKey","dataset","preventDefault","nearestTarget","listenBubblingEvent","closest","listenLoad","querySelectorAll","load","delay","onLoad","onload","bubbles","listenMouseEnter","onMouseEnter","onmouseenter","listenMouseLeave","onMouseLeave","onmouseleave","targetId","targetData","id","nearestTargetId","getElementById","rootStyles","PACKAGE","version","addedRulesIndex","Set","async","runAction","activeRequest","isCancelled","concurrency","_timeout","classList","add","req","reqId","decodeURI","cookie","search","actionMessage","handleUpdate","res","update","doc","css","querySelector","src","rules","sheet","cssRules","rule","has","cssText","insertRule","addCSS","old","newTarget","dispatchContent","runMetadata","focused","focus","checkbox","checked","fixInputs","enrichHyperViews","remoteEvent","substring","currentQuery","history","replaceState","setQuery","title","view","Hyperbole","hyperView","bind","cancelActiveRequest","innerText","onsubmit","formData","FormData","onchange","oninput","startedTyping","debouncedCallback","remove","handleResponse","handleRedirect","red","href","con","str","stringify"],"sourceRoot":""} \ No newline at end of file diff --git a/client/dist/message.d.ts b/client/dist/message.d.ts index ee8db9e2..5f7b9b20 100644 --- a/client/dist/message.d.ts +++ b/client/dist/message.d.ts @@ -5,6 +5,7 @@ export type Meta = { export type ViewId = string; export type RequestId = number; export type EncodedAction = string; +export type ViewState = string; type RemoteEvent = { name: string; detail: any; diff --git a/client/src/action.ts b/client/src/action.ts index 846ee29b..7a309548 100644 --- a/client/src/action.ts +++ b/client/src/action.ts @@ -1,6 +1,6 @@ import { takeWhileMap } from "./lib" -import { Meta, ViewId, RequestId, EncodedAction } from "./message" +import { Meta, ViewId, RequestId, EncodedAction, ViewState } from "./message" import * as message from "./message" @@ -9,6 +9,7 @@ export type ActionMessage = { viewId: ViewId action: EncodedAction requestId: RequestId + state?: ViewState meta: Meta[] form: URLSearchParams | undefined } @@ -16,13 +17,13 @@ export type ActionMessage = { -export function actionMessage(id: ViewId, action: EncodedAction, reqId: RequestId, form?: FormData): ActionMessage { +export function actionMessage(id: ViewId, action: EncodedAction, state: ViewState | undefined, reqId: RequestId, form?: FormData): ActionMessage { let meta: Meta[] = [ { key: "Cookie", value: decodeURI(document.cookie) }, { key: "Query", value: window.location.search } ] - return { viewId: id, action, requestId: reqId, meta, form: toSearch(form) } + return { viewId: id, action, state, requestId: reqId, meta, form: toSearch(form) } } export function toSearch(form?: FormData): URLSearchParams | undefined { @@ -42,10 +43,15 @@ export function renderActionMessage(msg: ActionMessage): string { "|ACTION|", "ViewId: " + msg.viewId, "Action: " + msg.action, - "RequestId: " + msg.requestId ] + if (msg.state) { + header.push("State: " + msg.state) + } + + header.push("RequestId: " + msg.requestId) + return [ header.join('\n'), message.renderMetas(msg.meta), diff --git a/client/src/index.ts b/client/src/index.ts index cee5a6b8..50b5b576 100644 --- a/client/src/index.ts +++ b/client/src/index.ts @@ -2,7 +2,7 @@ import { patch, create } from "omdomdom/lib/omdomdom.es.js" import { SocketConnection, Update, Redirect } from './sockets' import { listenChange, listenClick, listenDblClick, listenFormSubmit, listenLoad, listenTopLevel, listenInput, listenKeydown, listenKeyup, listenMouseEnter, listenMouseLeave } from './events' import { actionMessage, ActionMessage, Request, newRequest } from './action' -import { ViewId, Metadata, parseMetadata } from './message' +import { ViewId, Metadata, parseMetadata, ViewState } from './message' import { setQuery } from "./browser" import { parseResponse, Response, LiveUpdate } from './response' @@ -46,8 +46,10 @@ async function runAction(target: HyperView, action: string, form?: FormData) { target.classList.add("hyp-loading") }, 100) + let state = target.dataset.state + let req = newRequest() - let msg = actionMessage(target.id, action, req.requestId, form) + let msg = actionMessage(target.id, action, state, req.requestId, form) // Set the requestId target.activeRequest = req @@ -110,6 +112,7 @@ function handleUpdate(res: Update): HyperView { // Patch the node const old: VNode = create(target) let next: VNode = create(update.content) + let state = (next.attributes as any)["data-state"] next.attributes = old.attributes patch(next, old) @@ -118,21 +121,24 @@ function handleUpdate(res: Update): HyperView { let newTarget = document.getElementById(target.id) dispatchContent(newTarget) - if (newTarget) { - // execute the metadata, anything that doesn't interrupt the dom update - runMetadata(res.meta, newTarget) - - // now way for these to bubble) - listenLoad(newTarget) - listenMouseEnter(newTarget) - listenMouseLeave(newTarget) - fixInputs(newTarget) - enrichHyperViews(newTarget) - } - else { + if (!newTarget) { console.warn("Target Missing: ", target.id) + return target } + // re-add state attribute + newTarget.dataset.state = state + + // execute the metadata, anything that doesn't interrupt the dom update + runMetadata(res.meta, newTarget) + + // now way for these to bubble) + listenLoad(newTarget) + listenMouseEnter(newTarget) + listenMouseLeave(newTarget) + fixInputs(newTarget) + enrichHyperViews(newTarget) + return target } // catch (err) { diff --git a/client/src/message.ts b/client/src/message.ts index 2c505cc3..e1a2633a 100644 --- a/client/src/message.ts +++ b/client/src/message.ts @@ -7,6 +7,7 @@ export type Meta = { key: string, value: string } export type ViewId = string export type RequestId = number export type EncodedAction = string +export type ViewState = string type RemoteEvent = { name: string, detail: any } diff --git a/examples/Example/App.hs b/examples/Example/App.hs index cc1160b3..5ec4b1f6 100644 --- a/examples/Example/App.hs +++ b/examples/Example/App.hs @@ -58,6 +58,7 @@ import Example.Page.State.Actions qualified as Actions import Example.Page.State.Effects qualified as Effects import Example.Page.State.Query qualified as Query import Example.Page.State.Sessions qualified as Sessions +import Example.Page.State.View qualified as SView import Example.Page.Test qualified as Test import Example.Page.Todos.Todo qualified as Todo import Example.Page.Todos.TodoCSS qualified as TodoCSS @@ -121,7 +122,7 @@ exampleApp config users count chats = do runApp = runReader config . runTodosSession . runUsersIO users . runDebugIO . runConcurrent . runRandom . runOAuth2 config.oauth config.manager router :: forall es. (Hyperbole :> es, OAuth2 :> es, Todos :> es, Users :> es, Debug :> es, Concurrent :> es, IOE :> es, GenRandom :> es, Reader AppConfig :> es) => AppRoute -> Eff es Response - router Chat = runReader chats $ runPage $ Chat.page + router Chat = runReader chats $ runPage Chat.page router (Hello h) = runPage $ hello h router (Contacts (Contact uid)) = Contact.response uid router (Contacts ContactsAll) = runPage Contacts.page @@ -141,6 +142,7 @@ exampleApp config users count chats = do case r of StateRoot -> redirect $ routeUri (State Actions) Actions -> runPage Actions.page + StateView -> runPage SView.page Effects -> runReader count $ runPage Effects.page Sessions -> runPage Sessions.page Query -> runPage Query.page diff --git a/examples/Example/AppRoute.hs b/examples/Example/AppRoute.hs index be8f7897..abcf336f 100644 --- a/examples/Example/AppRoute.hs +++ b/examples/Example/AppRoute.hs @@ -68,6 +68,7 @@ instance Route DataRoute where data StateRoute = StateRoot | Actions + | StateView | Effects | Query | Sessions @@ -110,6 +111,7 @@ routeTitle (Hello _) = "Hello World" routeTitle (Contacts ContactsAll) = "Contacts" routeTitle (State Effects) = "Effects" routeTitle (State StateRoot) = "State" +routeTitle (State StateView) = "Built-in State" routeTitle (State Actions) = "Managing State" routeTitle (State Query) = "Query" routeTitle (State Sessions) = "Sessions" diff --git a/examples/Example/Page/Advanced.hs b/examples/Example/Page/Advanced.hs index 0fc61433..c0c45b78 100644 --- a/examples/Example/Page/Advanced.hs +++ b/examples/Example/Page/Advanced.hs @@ -58,5 +58,5 @@ controlView = do targetView :: View Controls () targetView = do - target Message $ do + target Message () $ do button (SetMessage "Targeted!") ~ btn $ "Target SetMessage" diff --git a/examples/Example/Page/CSS.hs b/examples/Example/Page/CSS.hs index 6381ccab..00525b09 100644 --- a/examples/Example/Page/CSS.hs +++ b/examples/Example/Page/CSS.hs @@ -64,7 +64,7 @@ example = do el $ do text "You can opt-out of Atomic CSS and use external classes with " code "class_" - addContext Root ext + runViewContext Root () ext where header = bold h3 = header . fontSize 18 diff --git a/examples/Example/Page/Contacts.hs b/examples/Example/Page/Contacts.hs index 2dbf4013..6acb3d36 100644 --- a/examples/Example/Page/Contacts.hs +++ b/examples/Example/Page/Contacts.hs @@ -87,7 +87,7 @@ allContactsView fil us = col ~ gap 20 $ do row ~ gap 10 $ do button (Reload Nothing) ~ Style.btnLight $ "Reload" - target (InlineContact 2) $ button Edit ~ Style.btnLight $ "Edit Sara" + target (InlineContact 2) () $ button Edit ~ Style.btnLight $ "Edit Sara" hyper NewContact newContactButton where @@ -124,7 +124,7 @@ newContactButton = do newContactForm :: View NewContact () newContactForm = do row ~ pad 10 . gap 10 . border 1 $ do - target Contacts $ do + target Contacts () $ do contactForm AddUser (genFields :: ContactForm Maybe) col $ do space @@ -171,4 +171,4 @@ contactEdit u = do el ~ (display None . whenLoading flexCol) $ contactLoading col ~ (whenLoading (display None) . gap 10) $ do Contact.contactEdit ViewContact Save u - target Contacts $ button (DeleteUser u.id) ~ btn' Danger . pad (XY 10 0) $ text "Delete" + target Contacts () $ button (DeleteUser u.id) ~ btn' Danger . pad (XY 10 0) $ text "Delete" diff --git a/examples/Example/Page/FormSimple.hs b/examples/Example/Page/FormSimple.hs index 0aed4c59..20a3f214 100644 --- a/examples/Example/Page/FormSimple.hs +++ b/examples/Example/Page/FormSimple.hs @@ -69,10 +69,10 @@ formView = do el $ text "Planet" field "planet" $ do radioGroup Earth $ do - radioOption Mercury - radioOption Venus - radioOption Earth - radioOption Mars + planet Mercury + planet Venus + planet Earth + planet Mars field "moon" $ do label $ do @@ -85,7 +85,7 @@ formView = do submit "Submit" ~ btn where - radioOption val = + planet val = label ~ flexRow . gap 10 $ do radio val ~ width 32 text (pack (show val)) diff --git a/examples/Example/Page/Intro.hs b/examples/Example/Page/Intro.hs index f5ca61da..1e4b1f0a 100644 --- a/examples/Example/Page/Intro.hs +++ b/examples/Example/Page/Intro.hs @@ -25,7 +25,7 @@ page = do el "HyperViews update independently. In this example, two Message HyperViews are embedded into the same page with different ids." el "Try inspecting the page in the Chrome dev tools and watching both the DOM and messages" col ~ embed . Cyber.font $ do - addContext Root simple + runViewContext Root () simple example Counter $ do el "Actions can have parameters for reusability, or to keep track of simple state" @@ -34,4 +34,4 @@ page = do code "viewCount :: Int -> View Counter ()." text "Notice how it expects the current count as a parameter." col ~ embed $ do - addContext Root counter + runViewContext Root () counter diff --git a/examples/Example/Page/State/Actions.hs b/examples/Example/Page/State/Actions.hs index ac81dee9..f03ccc9f 100644 --- a/examples/Example/Page/State/Actions.hs +++ b/examples/Example/Page/State/Actions.hs @@ -24,7 +24,7 @@ page = do col ~ embed $ do pre countExample ~ font col ~ embed $ do - addContext Root counter + runViewContext Root () counter countExample :: Text countExample = diff --git a/examples/Example/Page/State/Effects.hs b/examples/Example/Page/State/Effects.hs index eb962657..d81a89dc 100644 --- a/examples/Example/Page/State/Effects.hs +++ b/examples/Example/Page/State/Effects.hs @@ -7,7 +7,7 @@ import Effectful import Effectful.Concurrent.STM import Effectful.Reader.Dynamic import Example.AppRoute hiding (Counter) -import Example.Style.Cyber as Cyber (btn, font) +import Example.Style.Cyber as Cyber (btn, dataFeature, font) import Example.View.Layout import Web.Atomic.CSS import Web.Hyperbole as Hyperbole @@ -44,12 +44,12 @@ instance (Reader (TVar Int) :> es, Concurrent :> es) => HyperView Counter es whe pure $ viewCount n viewCount :: Int -> View Counter () -viewCount n = col ~ gap 10 $ do - row ~ Cyber.font $ do - el ~ bold . fontSize 48 . border 1 . pad (XY 20 0) $ text $ pack $ show n - row ~ gap 10 $ do - button Decrement "Decrement" ~ btn - button Increment "Increment" ~ btn +viewCount n = row $ do + col ~ gap 10 $ do + el ~ dataFeature $ text $ pack $ show n + row ~ gap 10 $ do + button Decrement "Decrement" ~ btn + button Increment "Increment" ~ btn modify :: (Concurrent :> es, Reader (TVar Int) :> es) => (Int -> Int) -> Eff es Int modify f = do diff --git a/examples/Example/Page/State/View.hs b/examples/Example/Page/State/View.hs new file mode 100644 index 00000000..7b61d553 --- /dev/null +++ b/examples/Example/Page/State/View.hs @@ -0,0 +1,47 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE TypeFamilies #-} + +module Example.Page.State.View where + +import Data.Text (pack) +import Effectful.State.Static.Local +import Example.AppRoute qualified as Route +import Example.Style.Cyber as Style +import Example.View.Layout +import Web.Atomic.CSS +import Web.Hyperbole + +page :: (Hyperbole :> es) => Page es '[Counter] +page = do + let rt = Route.State Route.StateView + pure $ exampleLayout rt $ do + example rt $ do + col ~ embed $ do + hyper' Counter 1 viewCount + +data Counter = Counter + deriving (Generic) +instance ViewId Counter where + type ViewState Counter = Int + +instance HyperView Counter es where + data Action Counter + = Increment + | Decrement + deriving (Generic, ViewAction) + + update Increment = do + modify @Int (+ 1) + pure viewCount + update Decrement = do + modify @Int (subtract 1) + pure viewCount + +viewCount :: View Counter () +viewCount = row $ do + n <- viewState + col ~ gap 10 $ do + el ~ dataFeature $ text $ pack $ show n + row ~ gap 10 $ do + button Decrement "Decrement" ~ Style.btn + button Increment "Increment" ~ Style.btn diff --git a/examples/Example/Page/Todos/Todo.hs b/examples/Example/Page/Todos/Todo.hs index eabf1ee6..105e0530 100644 --- a/examples/Example/Page/Todos/Todo.hs +++ b/examples/Example/Page/Todos/Todo.hs @@ -147,10 +147,10 @@ instance (Todos :> es) => HyperView TodoView es where todoView :: FilterTodo -> Todo -> View TodoView () todoView filt todo = do row ~ border (TRBL 0 0 1 0) . pad 10 . showDestroyOnHover $ do - target AllTodos $ do + target AllTodos () $ do toggleCheckbox (SetCompleted filt todo) todo.completed el (text todo.task) @ onDblClick (Edit filt todo) ~ completed . pad (XY 18 4) . grow - target AllTodos $ do + target AllTodos () $ do button (Destroy filt todo) "✕" ~ cls "destroy-btn" . opacity 0 . hover (color Primary) . pad 4 where completed = if todo.completed then Style.strikethrough else id diff --git a/examples/Example/Page/Todos/TodoCSS.hs b/examples/Example/Page/Todos/TodoCSS.hs index 9f056937..2278dbbc 100644 --- a/examples/Example/Page/Todos/TodoCSS.hs +++ b/examples/Example/Page/Todos/TodoCSS.hs @@ -9,7 +9,6 @@ import Example.Effects.Todos (FilterTodo (..), Todo, TodoId, Todos) import Example.Effects.Todos qualified as Todos import Example.Page.Todos.Todo (Action (..), AllTodos (..), TodoForm (..), TodoView (..), pluralize) import Web.Hyperbole as Hyperbole -import Web.Hyperbole.HyperView.Forms (Input (Input)) {- @@ -119,8 +118,7 @@ todoForm = do let f :: TodoForm FieldName = fieldNames form (MkTodosAction SubmitTodo) $ do field f.task $ do - Input (FieldName nm) <- context - input' -- we use a custom input field, because the Hyperbole one overrides autocomplete + input TextInput -- we use a custom input field, because the Hyperbole one overrides autocomplete @ class_ "new-todo" {- -- . autofocus @@ -129,10 +127,7 @@ todoForm = do FIXME: but since this example is meant to match as close as possible to the original CSS version FIXME: and not diverge too much from the other todo example, I'm leaving as-is. -} - . att "autocomplete" "off" . placeholder "What needs to be done?" - . value "" - . name nm -- because we use a custom field, we must provide this param for the library statusBar :: FilterTodo -> [Todo] -> View CSSTodos () statusBar filt todos = do @@ -191,7 +186,7 @@ todoView filt todo = do @ bool id (class_ "completed") todo.completed $ do div' @ class_ "view" $ do - target CSSTodos $ do + target CSSTodos () $ do input' @ class_ "toggle" . att "type" "checkbox" @@ -201,7 +196,7 @@ todoView filt todo = do label' @ class_ "label" . onDblClick (MkTodoAction $ Edit filt todo) $ do text todo.task - target CSSTodos $ do + target CSSTodos () $ do button (MkTodosAction $ Destroy filt todo) @ class_ "destroy" $ "" todoEditView :: FilterTodo -> Todo -> View CSSTodo () diff --git a/examples/Example/Style/Cyber.hs b/examples/Example/Style/Cyber.hs index 041090e4..e1a2dc50 100644 --- a/examples/Example/Style/Cyber.hs +++ b/examples/Example/Style/Cyber.hs @@ -74,10 +74,10 @@ hovClr Primary = PrimaryLight hovClr c = c font :: (Styleable h) => CSS h -> CSS h -font = utility ("share-tech") ["font-family" :. "'Share Tech Mono'"] +font = utility "share-tech" ["font-family" :. "'Share Tech Mono'"] -cyberError :: View Body () -> View Body () -cyberError inner = +cyberError :: View () () -> Body +cyberError inner = renderBody $ el ~ wipeIn . border (T 4) . borderColor lightRed $ do el ~ bg midRed . clip 10 . pad 10 . color White $ inner @@ -86,6 +86,6 @@ cyberError inner = wipeIn :: (Styleable h) => CSS h -> CSS h wipeIn = utility "wipe-in" ["animation" :. "wipeIn 0.5s steps(20, end) forwards"] -glitch :: Text -> View Body () +glitch :: Text -> View c () glitch msg = el ~ cls "glitch" @ att "data-text" msg $ text msg diff --git a/examples/Example/View/Layout.hs b/examples/Example/View/Layout.hs index d7db0dc0..edbb0f6c 100644 --- a/examples/Example/View/Layout.hs +++ b/examples/Example/View/Layout.hs @@ -64,6 +64,7 @@ exampleMenu current = do case current of State _ -> do exampleLink (State Actions) ~ sub + exampleLink (State StateView) ~ sub exampleLink (State Effects) ~ sub exampleLink (State Query) ~ sub exampleLink (State Sessions) ~ sub diff --git a/examples/examples.cabal b/examples/examples.cabal index 3dc52eb4..2529d44d 100644 --- a/examples/examples.cabal +++ b/examples/examples.cabal @@ -78,6 +78,7 @@ executable examples Example.Page.State.Effects Example.Page.State.Query Example.Page.State.Sessions + Example.Page.State.View Example.Page.Test Example.Page.Todos.Todo Example.Page.Todos.TodoCSS diff --git a/hyperbole.cabal b/hyperbole.cabal index d5151a36..5c53ab81 100644 --- a/hyperbole.cabal +++ b/hyperbole.cabal @@ -57,8 +57,6 @@ library Web.Hyperbole.HyperView.Hyper Web.Hyperbole.HyperView.Input Web.Hyperbole.HyperView.Types - Web.Hyperbole.HyperView.ViewAction - Web.Hyperbole.HyperView.ViewId Web.Hyperbole.Page Web.Hyperbole.Route Web.Hyperbole.Server.Handler @@ -77,6 +75,8 @@ library Web.Hyperbole.View.Render Web.Hyperbole.View.Tag Web.Hyperbole.View.Types + Web.Hyperbole.View.ViewAction + Web.Hyperbole.View.ViewId other-modules: Paths_hyperbole autogen-modules: diff --git a/src/Web/Hyperbole.hs b/src/Web/Hyperbole.hs index 0560c78f..999e00a9 100644 --- a/src/Web/Hyperbole.hs +++ b/src/Web/Hyperbole.hs @@ -118,9 +118,8 @@ module Web.Hyperbole -- * HyperView #hyperview# , HyperView (..) - , ViewId - , ViewAction , hyper + , hyper' , HasViewId (..) -- * Interactive Elements #interactive# diff --git a/src/Web/Hyperbole/Data/Encoded.hs b/src/Web/Hyperbole/Data/Encoded.hs index db6722ca..b04f199d 100644 --- a/src/Web/Hyperbole/Data/Encoded.hs +++ b/src/Web/Hyperbole/Data/Encoded.hs @@ -69,10 +69,7 @@ decodeEither t = do -- | Basic Encoding encodedToText :: Encoded -> Text encodedToText (Encoded con values) = - let params = T.intercalate " " $ fmap encodeParam values - in case params of - "" -> con.text - _ -> con.text <> " " <> params + T.intercalate " " (con.text : fmap encodeParam values) encodedParseText :: Text -> Either String Encoded @@ -83,8 +80,8 @@ encodedParseText inp = encodedParser = do con <- AC.takeTill AC.isSpace AC.skipSpace - params <- paramParser `sepBy` AC.char ' ' - pure $ Encoded (ConName (cs con)) params + ps <- paramParser `sepBy` AC.char ' ' + pure $ Encoded (ConName (cs con)) ps genericToEncoded :: (Generic a, GToEncoded (Rep a)) => a -> Encoded @@ -112,6 +109,14 @@ class ToEncoded a where instance ToEncoded Encoded where toEncoded = id +instance ToEncoded () where + toEncoded _ = mempty +instance ToEncoded ParamValue where + toEncoded p = Encoded mempty [toParam p] +instance ToEncoded Int where + toEncoded = toEncoded . toParam +instance ToEncoded Text where + toEncoded = toEncoded . toParam -- | Custom Encoding for embedding into web documents. Noteably used for 'ViewId' and 'ViewAction' @@ -123,6 +128,17 @@ class FromEncoded a where instance FromEncoded Encoded where parseEncoded = pure +instance FromEncoded () where + parseEncoded _ = pure () +instance FromEncoded ParamValue where + parseEncoded (Encoded _ ps) = do + case ps of + [p] -> parseParam p + _ -> Left $ "Expected single param value [param] but got: " <> show ps +instance FromEncoded Int where + parseEncoded enc = parseEncoded enc >>= parseParam +instance FromEncoded Text where + parseEncoded enc = parseEncoded enc >>= parseParam fromResult :: A.Result a -> Either String a diff --git a/src/Web/Hyperbole/Data/Param.hs b/src/Web/Hyperbole/Data/Param.hs index 869bc3f4..8881e512 100644 --- a/src/Web/Hyperbole/Data/Param.hs +++ b/src/Web/Hyperbole/Data/Param.hs @@ -26,7 +26,7 @@ newtype Param = Param {text :: Text} -- | Encode arbitrarily complex data into url form encoded data newtype ParamValue = ParamValue {value :: Text} deriving newtype (Ord, Eq) - deriving (Show) + deriving (Show, Generic) instance IsString ParamValue where @@ -47,6 +47,8 @@ class ToParam a where toParam = genericToParam +instance ToParam ParamValue where + toParam = id instance ToParam Int where toParam = jsonParam instance ToParam Integer where @@ -79,6 +81,8 @@ instance ToParam URI where toParam = toParam . uriToText instance ToParam Value where toParam = jsonParam +instance (ToParam a, ToParam b) => ToParam (a, b) where + toParam (a, b) = toParam [toParam a, toParam b] {- | Decode data from a 'query', 'session', or 'form' parameter value @@ -105,6 +109,8 @@ class FromParam a where -- decodeParamValue = parseParam . decodeParam -- Permissive instances. Some of these come directly from forms! +instance FromParam ParamValue where + parseParam = pure instance FromParam Int where parseParam "" = pure 0 parseParam p = jsonParse p @@ -119,6 +125,12 @@ instance FromParam Double where parseParam p = jsonParse p instance FromParam Text where parseParam = parseQueryParam +instance (FromParam a, FromParam b) => FromParam (a, b) where + parseParam p = do + ps <- parseParam @[ParamValue] p + case ps of + [pa, pb] -> (,) <$> parseParam pa <*> parseParam pb + _ -> Left $ "Expected [a,b] but got: " <> cs p.value -- -- we don't need to desanitize the text diff --git a/src/Web/Hyperbole/Document.hs b/src/Web/Hyperbole/Document.hs index 37a0bb02..338bfc92 100644 --- a/src/Web/Hyperbole/Document.hs +++ b/src/Web/Hyperbole/Document.hs @@ -4,6 +4,7 @@ module Web.Hyperbole.Document where import Data.ByteString.Lazy qualified as BL import Data.String.Interpolate (i) +import GHC.Generics (Generic) import Web.Hyperbole.View @@ -18,7 +19,7 @@ document :: View DocumentHead () -> BL.ByteString -> BL.ByteString document docHead cnt = [i| - #{renderLazyByteString $ addContext DocumentHead docHead} + #{renderLazyByteString $ runViewContext DocumentHead () docHead} #{cnt} @@ -35,6 +36,7 @@ document docHead cnt = > #EMBED Example/Docs/App.hs app -} data DocumentHead = DocumentHead + deriving (Generic, ViewId) {- | A simple mobile-friendly document with all required embeds and live reload diff --git a/src/Web/Hyperbole/Effect/Client.hs b/src/Web/Hyperbole/Effect/Client.hs index 1079030b..42c8747a 100644 --- a/src/Web/Hyperbole/Effect/Client.hs +++ b/src/Web/Hyperbole/Effect/Client.hs @@ -4,11 +4,12 @@ import Data.Aeson import Data.Text (Text) import Effectful import Effectful.Dispatch.Dynamic -import Effectful.Reader.Dynamic +import Effectful.Reader.Static import Web.Hyperbole.Effect.Hyperbole import Web.Hyperbole.HyperView import Web.Hyperbole.Types.Client (clientSetPageTitle) import Web.Hyperbole.Types.Event +import Web.Hyperbole.View (toAction, toViewId) {- | Trigger an action for an arbitrary 'HyperView' diff --git a/src/Web/Hyperbole/Effect/Hyperbole.hs b/src/Web/Hyperbole/Effect/Hyperbole.hs index f805b7f1..656e0445 100644 --- a/src/Web/Hyperbole/Effect/Hyperbole.hs +++ b/src/Web/Hyperbole/Effect/Hyperbole.hs @@ -1,4 +1,3 @@ -{-# LANGUAGE LambdaCase #-} {-# LANGUAGE UndecidableInstances #-} module Web.Hyperbole.Effect.Hyperbole where @@ -14,14 +13,13 @@ import Web.Hyperbole.Types.Client import Web.Hyperbole.Types.Event import Web.Hyperbole.Types.Request import Web.Hyperbole.Types.Response -import Web.Hyperbole.View (View) -- | The 'Hyperbole' 'Effect' allows you to access information in the 'Request', manually respond, and manipulate the Client 'session' and 'query'. data Hyperbole :: Effect where GetRequest :: Hyperbole m Request RespondNow :: Response -> Hyperbole m a - PushUpdate :: TargetViewId -> View Body () -> Hyperbole m () + PushUpdate :: ViewUpdate -> Hyperbole m () ModClient :: (Client -> Client) -> Hyperbole m () GetClient :: Hyperbole m Client -- TODO: this should actually execute the other view, and send the response to the client diff --git a/src/Web/Hyperbole/Effect/Response.hs b/src/Web/Hyperbole/Effect/Response.hs index 1b87e557..958e955a 100644 --- a/src/Web/Hyperbole/Effect/Response.hs +++ b/src/Web/Hyperbole/Effect/Response.hs @@ -3,32 +3,34 @@ module Web.Hyperbole.Effect.Response where import Data.Text (Text) import Effectful import Effectful.Dispatch.Dynamic -import Effectful.Reader.Dynamic +import Effectful.Reader.Static +import Effectful.State.Static.Local +import Web.Hyperbole.Data.Encoded import Web.Hyperbole.Data.URI import Web.Hyperbole.Effect.Hyperbole (Hyperbole (..)) -import Web.Hyperbole.HyperView (ConcurrencyValue (..), HyperView (..), ViewId (..), hyperUnsafe) -import Web.Hyperbole.HyperView.ViewId (viewId) +import Web.Hyperbole.HyperView (ConcurrencyValue (..), HyperView (..), hyperUnsafe) import Web.Hyperbole.Types.Event import Web.Hyperbole.Types.Response -import Web.Hyperbole.View.Types +import Web.Hyperbole.View -- | Respond with the given hyperview -hyperView :: (HyperView id es) => id -> View id () -> Eff es Response -hyperView i vw = do +hyperView :: (HyperView id es, ToEncoded (ViewState id)) => id -> ViewState id -> View id () -> Eff es Response +hyperView i st vw = do let vid = TargetViewId (toViewId i) - pure $ Response vid $ hyperUnsafe i vw + pure $ Response $ ViewUpdate vid $ renderBody $ hyperUnsafe i st vw -pushUpdate :: (Hyperbole :> es, ViewId id, ConcurrencyValue (Concurrency id)) => View id () -> Eff (Reader id : es) () +pushUpdate :: (Hyperbole :> es, ViewId id, ToEncoded (ViewState id), ConcurrencyValue (Concurrency id)) => View id () -> Eff (Reader id : State (ViewState id) : es) () pushUpdate vw = do i <- viewId - pushUpdateTo i vw + st <- get + pushUpdateTo i st vw -pushUpdateTo :: (Hyperbole :> es, ViewId id, ConcurrencyValue (Concurrency id)) => id -> View id () -> Eff es () -pushUpdateTo i vw = do - send $ PushUpdate (TargetViewId $ toViewId i) $ hyperUnsafe i vw +pushUpdateTo :: (Hyperbole :> es, ViewId id, ToEncoded (ViewState id), ConcurrencyValue (Concurrency id)) => id -> ViewState id -> View id () -> Eff es () +pushUpdateTo i st vw = do + send $ PushUpdate $ ViewUpdate (TargetViewId $ toViewId i) $ renderBody $ hyperUnsafe i st vw -- | Abort execution and respond with an error @@ -38,9 +40,9 @@ respondError err = do -- | Abort execution and respond with an error view -respondErrorView :: (Hyperbole :> es) => Text -> View Body () -> Eff es a +respondErrorView :: (Hyperbole :> es) => Text -> View () () -> Eff es a respondErrorView msg vw = do - send $ RespondNow $ Err $ ErrCustom $ ServerError msg vw + send $ RespondNow $ Err $ ErrCustom $ ServerError msg $ renderBody vw {- | Abort execution and respond with 404 Not Found @@ -66,6 +68,6 @@ redirect = send . RespondNow . Redirect -- | Respond with a generic view. Normally you will return a view from the page or handler instead of using this function -view :: View Body () -> Response -view = - Response (TargetViewId mempty) +view :: View () () -> Response +view v = + Response $ ViewUpdate (TargetViewId mempty) (renderBody v) diff --git a/src/Web/Hyperbole/HyperView.hs b/src/Web/Hyperbole/HyperView.hs index 4100cb61..29318d7f 100644 --- a/src/Web/Hyperbole/HyperView.hs +++ b/src/Web/Hyperbole/HyperView.hs @@ -2,8 +2,6 @@ module Web.Hyperbole.HyperView ( module Web.Hyperbole.HyperView.Types , module Web.Hyperbole.HyperView.Input , module Web.Hyperbole.HyperView.Event - , module Web.Hyperbole.HyperView.ViewId - , module Web.Hyperbole.HyperView.ViewAction , module Web.Hyperbole.HyperView.Handled , module Web.Hyperbole.HyperView.Hyper ) where @@ -13,6 +11,4 @@ import Web.Hyperbole.HyperView.Handled import Web.Hyperbole.HyperView.Hyper import Web.Hyperbole.HyperView.Input import Web.Hyperbole.HyperView.Types -import Web.Hyperbole.HyperView.ViewAction -import Web.Hyperbole.HyperView.ViewId diff --git a/src/Web/Hyperbole/HyperView/Event.hs b/src/Web/Hyperbole/HyperView/Event.hs index 70c59a6d..5306a71c 100644 --- a/src/Web/Hyperbole/HyperView/Event.hs +++ b/src/Web/Hyperbole/HyperView/Event.hs @@ -7,16 +7,13 @@ import Web.Atomic.Types import Web.Hyperbole.Data.Encoded import Web.Hyperbole.HyperView.Handled import Web.Hyperbole.HyperView.Types -import Web.Hyperbole.HyperView.ViewAction -import Web.Hyperbole.HyperView.ViewId import Web.Hyperbole.View -import Web.Hyperbole.View.Types (ViewContext) type DelayMs = Int -event :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Name -> Action id -> Attributes a -> Attributes a +event :: (ViewAction (Action id), Attributable a) => Name -> Action id -> Attributes a -> Attributes a event nm a = att (eventName nm) (encodedToText $ toAction a) @@ -30,24 +27,24 @@ eventName t = "data-on" <> t #EMBED Example/Page/Concurrency.hs viewTaskLoad @ -} -onLoad :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Action id -> DelayMs -> Attributes a -> Attributes a +onLoad :: (ViewAction (Action id), Attributable a) => Action id -> DelayMs -> Attributes a -> Attributes a onLoad a delay = do event "load" a . att "data-delay" (cs $ show delay) -onClick :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Action id -> Attributes a -> Attributes a +onClick :: (ViewAction (Action id), Attributable a) => Action id -> Attributes a -> Attributes a onClick = event "click" -onDblClick :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Action id -> Attributes a -> Attributes a +onDblClick :: (ViewAction (Action id), Attributable a) => Action id -> Attributes a -> Attributes a onDblClick = event "dblclick" -onMouseEnter :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Action id -> Attributes a -> Attributes a +onMouseEnter :: (ViewAction (Action id), Attributable a) => Action id -> Attributes a -> Attributes a onMouseEnter = event "mouseenter" -onMouseLeave :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Action id -> Attributes a -> Attributes a +onMouseLeave :: (ViewAction (Action id), Attributable a) => Action id -> Attributes a -> Attributes a onMouseLeave = event "mouseleave" @@ -57,7 +54,7 @@ WARNING: a short delay can result in poor performance. It is not recommended to > input (onInput OnSearch) 250 id -} -onInput :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => (Text -> Action id) -> DelayMs -> Attributes a -> Attributes a +onInput :: (ViewAction (Action id), Attributable a) => (Text -> Action id) -> DelayMs -> Attributes a -> Attributes a onInput a delay = do att (eventName "input") (encodedToText $ toActionInput a) . att "data-delay" (cs $ show delay) @@ -65,21 +62,21 @@ onInput a delay = do -- WARNING: no way to do this generically right now, because toActionInput is specialized to Text -- the change event DOES assume that the target has a string value -- but, that doesn't let us implement dropdown -onChange :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => (value -> Action id) -> Attributes a -> Attributes a +onChange :: (ViewAction (Action id), Attributable a) => (value -> Action id) -> Attributes a -> Attributes a onChange a = do att (eventName "change") (encodedToText $ toActionInput a) -onSubmit :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Action id -> Attributes a -> Attributes a +onSubmit :: (ViewAction (Action id), Attributable a) => Action id -> Attributes a -> Attributes a onSubmit = event "submit" -onKeyDown :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Key -> Action id -> Attributes a -> Attributes a +onKeyDown :: (ViewAction (Action id), Attributable a) => Key -> Action id -> Attributes a -> Attributes a onKeyDown key = do event ("keydown-" <> keyDataAttribute key) -onKeyUp :: (ViewAction (Action id), ViewContext a ~ id, Attributable a) => Key -> Action id -> Attributes a -> Attributes a +onKeyUp :: (ViewAction (Action id), Attributable a) => Key -> Action id -> Attributes a -> Attributes a onKeyUp key = do event ("keyup-" <> keyDataAttribute key) @@ -122,7 +119,7 @@ toActionInput act = -- | Internal -dataTarget :: (ViewId id, ViewContext a ~ id, Attributable a) => id -> Attributes a -> Attributes a +dataTarget :: (ViewId id, Attributable a) => id -> Attributes a -> Attributes a dataTarget = att "data-target" . encodedToText . toViewId @@ -132,7 +129,7 @@ dataTarget = att "data-target" . encodedToText . toViewId #EMBED Example/Page/Advanced.hs targetView @ -} -target :: forall id ctx. (HyperViewHandled id ctx, ViewId id) => id -> View id () -> View ctx () -target newId view = do - addContext newId $ do +target :: forall id ctx. (HyperViewHandled id ctx, ViewId id) => id -> ViewState id -> View id () -> View ctx () +target newId st view = do + runViewContext newId st $ do view @ dataTarget newId diff --git a/src/Web/Hyperbole/HyperView/Forms.hs b/src/Web/Hyperbole/HyperView/Forms.hs index 69cf0840..a5f5fa69 100644 --- a/src/Web/Hyperbole/HyperView/Forms.hs +++ b/src/Web/Hyperbole/HyperView/Forms.hs @@ -16,7 +16,6 @@ module Web.Hyperbole.HyperView.Forms , label , input , checkbox - , Radio (..) , radioGroup , radio , select @@ -60,7 +59,6 @@ import Web.Hyperbole.Effect.Response (parseError) import Web.Hyperbole.HyperView.Event (onSubmit) import Web.Hyperbole.HyperView.Input (Option (..), checked) import Web.Hyperbole.HyperView.Types -import Web.Hyperbole.HyperView.ViewAction import Web.Hyperbole.View @@ -160,7 +158,9 @@ instance GenField (Maybe a) where ------------------------------------------------------------------------------ -- | Context that allows form fields -data FormFields id = FormFields id +newtype FormFields id = FormFields id + deriving (Generic) + deriving newtype (ViewId) {- | Type-safe \. Calls (Action id) on submit @@ -171,9 +171,8 @@ data FormFields id = FormFields id -} form :: (ViewAction (Action id)) => Action id -> View (FormFields id) () -> View id () form a cnt = do - vid <- context tag "form" @ onSubmit a $ do - addContext (FormFields vid) cnt + runChildView FormFields cnt -- | Button that submits the 'form' @@ -182,8 +181,8 @@ submit = tag "button" @ att "type" "submit" -- | Form FieldName. This is embeded as the name attribute, and refers to the key need to parse the form when submitted. See 'fieldNames' -newtype FieldName a = FieldName Text - deriving newtype (Show, IsString) +newtype FieldName a = FieldName {value :: Text} + deriving newtype (Show, IsString, FromParam, ToParam) -- | Display a 'FormField'. See 'form' and 'Form' @@ -193,7 +192,7 @@ field -> View (Input id a) () -> View (FormFields id) () field fn = - addContext (Input fn) + runChildView (\(FormFields i) -> Input i fn) -- | Choose one for 'input's to give the browser autocomplete hints @@ -217,8 +216,12 @@ data InputType data Input (id :: Type) (a :: Type) = Input - { inputName :: FieldName a + { id :: id + , inputName :: FieldName a } + deriving (Generic) +instance (ViewId id, FromParam id, ToParam id) => ViewId (Input id a) where + type ViewState (Input id a) = ViewState id {- | label for a 'field' @@ -229,10 +232,10 @@ label = tag "label" -- | input for a 'field' -input :: InputType -> View (Input id a) () +input :: forall id a. InputType -> View (Input id a) () input ft = do - Input (FieldName nm) <- context - tag "input" @ att "type" (inpType ft) . name nm . att "autocomplete" (auto ft) $ none + inp :: Input id a <- viewId + tag "input" @ att "type" (inpType ft) . name inp.inputName.value . att "autocomplete" (auto ft) $ none where inpType NewPassword = "password" inpType CurrentPassword = "password" @@ -242,51 +245,56 @@ input ft = do inpType _ = "text" auto :: InputType -> Text - auto = pack . kebab . show + auto TextInput = "off" + auto inp = pack . kebab . show $ inp -checkbox :: Bool -> View (Input id a) () +checkbox :: forall id a. Bool -> View (Input id a) () checkbox isChecked = do - Input (FieldName nm) <- context - tag "input" @ att "type" "checkbox" . name nm $ none @ checked isChecked + inp :: Input id a <- viewId + tag "input" @ att "type" "checkbox" . name inp.inputName.value $ none @ checked isChecked -- NOTE: Radio is a special type of selection different from list type or -- select. select or list input can be thought of one wrapper and multiple -- options whereas radio is multiple wrappers with options. The context required -- for radio is more than that required for select. -data Radio (id :: Type) (a :: Type) (b :: Type) = Selection - { inputCtx :: Input id a - , defaultOption :: b +data Radio (id :: Type) (a :: Type) (opt :: Type) = Radio + { id :: id + , inputName :: FieldName a + , defaultOption :: opt } + deriving (Generic) +instance (FromParam id, ToParam id, FromParam a, ToParam a, ToParam opt, FromParam opt) => ViewId (Radio id a opt) where + type ViewState (Radio id a opt) = ViewState id -radioGroup :: b -> View (Radio id a b) () -> View (Input id a) () -radioGroup defOpt = modifyContext $ \inp -> Selection inp defOpt +radioGroup :: opt -> View (Radio id a opt) () -> View (Input id a) () +radioGroup defOpt = runChildView (\(inp :: Input id a) -> Radio inp.id inp.inputName defOpt) -radio :: (Eq b, ToParam b) => b -> View (Radio id a b) () +radio :: forall id a opt. (Eq opt, ToParam opt) => opt -> View (Radio id a opt) () radio val = do - Selection (Input (FieldName nm)) defOpt <- context + rd :: Radio id a opt <- viewId tag "input" @ att "type" "radio" - . name nm + . name rd.inputName.value . value (toParam val).value - . checked (defOpt == val) + . checked (rd.defaultOption == val) $ none -select :: (Eq opt) => opt -> View (Option opt id) () -> View (Input id a) () +select :: forall opt id a. (Eq opt) => opt -> View (Option id opt) () -> View (Input id a) () select defOpt options = do - Input (FieldName nm) <- context - tag "select" @ name nm $ addContext (Option defOpt) options + inp :: Input id a <- viewId + tag "select" @ name inp.inputName.value $ runChildView (\_ -> Option inp.id defOpt) options -- | textarea for a 'field' -textarea :: Maybe Text -> View (Input id a) () +textarea :: forall id a. Maybe Text -> View (Input id a) () textarea mDefaultText = do - Input (FieldName nm) <- context - tag "textarea" @ name nm $ text $ fromMaybe "" mDefaultText + inp :: Input id a <- viewId + tag "textarea" @ name inp.inputName.value $ text $ fromMaybe "" mDefaultText ------------------------------------------------------------------------------ @@ -554,91 +562,3 @@ instance (GFieldsGen f) => GFieldsGen (M1 C c f) where -- -- instance (GCollect f v) => GCollect (M1 C c f) v where -- gCollect (M1 f) = gCollect f - ------------------------------------------------------------------------------- - --- newtype User = User Text --- deriving newtype (FromParam) --- --- --- data TestForm f = TestForm --- { name :: Field f Text --- , age :: Field f Int --- , user :: Field f User --- } --- deriving (Generic, FromFormF, GenFields Maybe, GenFields Validated) - --- test :: (Hyperbole :> es) => Eff es (TestForm Identity) --- test = do --- tf <- formData --- pure tf - --- formView :: (ViewAction (Action id)) => View id () --- formView = do --- -- generate a ContactForm' FieldName --- let f = fieldNames @ContactForm --- form undefined (gap 10 . pad 10) $ do --- -- f.name :: FieldName Text --- -- f.name = FieldName "name" --- field f.name id $ do --- label "Contact Name" --- input Username (placeholder "contact name") --- --- -- f.age :: FieldName Int --- -- f.age = FieldName "age" --- field f.age id $ do --- label "Age" --- input Number (placeholder "age" . value "0") --- --- submit id "Submit" --- --- --- formView' :: (ViewAction (Action id)) => ContactForm Validated -> View id () --- formView' contact = do --- -- generate a ContactForm' FieldName --- let f = formFields @ContactForm contact --- form undefined (gap 10 . pad 10) $ do --- -- f.name :: FieldName Text --- -- f.name = FieldName "name" --- field f.name id $ do --- label "Contact Name" --- input Username (placeholder "contact name") --- --- -- f.age :: FieldName Int --- -- f.age = FieldName "age" --- field f.age id $ do --- label "Age" --- input Number (placeholder "age" . value "0") --- --- field f.age id $ do --- label "Username" --- input Username (placeholder "username") --- --- case f.age.value of --- Invalid t -> el_ (text t) --- Valid -> el_ "Username is available" --- _ -> none --- --- submit id "Submit" --- where --- valStyle (Invalid _) = id --- valStyle Valid = id --- valStyle _ = id --- --- --- data ContactForm' = ContactForm' --- { name :: Text --- , age :: Int --- } --- deriving (Generic) --- instance FormParse ContactForm' --- --- --- formView'' :: (ViewAction (Action id)) => View id () --- formView'' = do --- form undefined (gap 10 . pad 10) $ do --- -- f.name :: FieldName Text --- -- f.name = FieldName "name" --- field (FieldName "name") id $ do --- label "Contact Name" --- input Username (placeholder "contact name") diff --git a/src/Web/Hyperbole/HyperView/Handled.hs b/src/Web/Hyperbole/HyperView/Handled.hs index f3e4f375..d8ac6557 100644 --- a/src/Web/Hyperbole/HyperView/Handled.hs +++ b/src/Web/Hyperbole/HyperView/Handled.hs @@ -6,6 +6,7 @@ import Data.Kind (Constraint, Type) import GHC.TypeLits hiding (Mod) import Web.Hyperbole.HyperView.Types import Web.Hyperbole.TypeList +import Web.Hyperbole.View (View) type family ValidDescendents x :: [Type] where @@ -75,12 +76,14 @@ type NotInPage x total = ) -type HyperViewHandled id ctx = - ( -- the id must be found in the children of the context - ElemOr id (ctx : Require ctx) (NotHandled id ctx (Require ctx)) - , -- Make sure the descendents of id are in the context for the root page - CheckDescendents id ctx - ) +type family HyperViewHandled id ctx :: Constraint where + HyperViewHandled id (View view ()) = TypeError ('Text "View c () is not a valid ViewState, did you forget to pass ViewState into target or runViewContext?") + HyperViewHandled id ctx = + ( -- the id must be found in the children of the context + ElemOr id (ctx : Require ctx) (NotHandled id ctx (Require ctx)) + , -- Make sure the descendents of id are in the context for the root page + CheckDescendents id ctx + ) -- TODO: Report which view requires the missing one diff --git a/src/Web/Hyperbole/HyperView/Hyper.hs b/src/Web/Hyperbole/HyperView/Hyper.hs index 16f73fa2..7e2117f6 100644 --- a/src/Web/Hyperbole/HyperView/Hyper.hs +++ b/src/Web/Hyperbole/HyperView/Hyper.hs @@ -6,8 +6,8 @@ import Web.Atomic.Types import Web.Hyperbole.Data.Encoded as Encoded import Web.Hyperbole.HyperView.Handled (HyperViewHandled) import Web.Hyperbole.HyperView.Types -import Web.Hyperbole.HyperView.ViewId -import Web.Hyperbole.View (View, addContext, tag) +import Web.Hyperbole.View (View, runViewContext, tag) +import Web.Hyperbole.View.ViewId {- | Embed a 'HyperView' into a page or another 'View' @@ -18,19 +18,34 @@ import Web.Hyperbole.View (View, addContext, tag) -} hyper :: forall id ctx - . (HyperViewHandled id ctx, ViewId id, ConcurrencyValue (Concurrency id)) + . (HyperViewHandled id ctx, ViewId id, ViewState id ~ (), ViewState id ~ (), ConcurrencyValue (Concurrency id)) => id -> View id () -> View ctx () -hyper = hyperUnsafe +hyper vid = hyperUnsafe vid () -hyperUnsafe :: forall id ctx. (ViewId id, ConcurrencyValue (Concurrency id)) => id -> View id () -> View ctx () -hyperUnsafe vid vw = do - tag "div" @ att "id" (encodedToText $ toViewId vid) . concurrency $ - addContext vid vw +hyper' + :: forall id ctx + . (HyperViewHandled id ctx, ViewId id, ViewState id ~ ViewState id, ToEncoded (ViewState id), ConcurrencyValue (Concurrency id)) + => id + -> ViewState id + -> View id () + -> View ctx () +hyper' = hyperUnsafe + + +hyperUnsafe :: forall id ctx. (ViewId id, ViewState id ~ ViewState id, ToEncoded (ViewState id), ConcurrencyValue (Concurrency id)) => id -> ViewState id -> View id () -> View ctx () +hyperUnsafe vid st vw = do + tag "div" @ att "id" (encodedToText $ toViewId vid) . state . concurrency $ + runViewContext vid st vw where concurrency = case concurrencyMode @(Concurrency id) of Drop -> id Replace -> att "data-concurrency" (encode Replace) + + state = + if encode st == mempty + then id + else att "data-state" (encode st) diff --git a/src/Web/Hyperbole/HyperView/Input.hs b/src/Web/Hyperbole/HyperView/Input.hs index b487a420..8a2d19f2 100644 --- a/src/Web/Hyperbole/HyperView/Input.hs +++ b/src/Web/Hyperbole/HyperView/Input.hs @@ -2,11 +2,11 @@ module Web.Hyperbole.HyperView.Input where import Data.String.Conversions (cs) import Data.Text (Text) +import GHC.Generics (Generic) import Web.Atomic.Types -import Web.Hyperbole.Data.Param (ParamValue (..), ToParam (..)) +import Web.Hyperbole.Data.Param (FromParam, ParamValue (..), ToParam (..)) import Web.Hyperbole.HyperView.Event (DelayMs, onChange, onClick, onInput) import Web.Hyperbole.HyperView.Types (HyperView (..)) -import Web.Hyperbole.HyperView.ViewAction (ViewAction (..)) import Web.Hyperbole.Route (Route (..), routeUri) import Web.Hyperbole.View @@ -41,24 +41,28 @@ button action cnt = do @ -} dropdown - :: (ViewAction (Action id)) + :: forall opt id + . (ViewAction (Action id)) => (opt -> Action id) -> opt -- default option - -> View (Option opt id) () + -> View (Option id opt) () -> View id () dropdown act defOpt options = do + st :: ViewState id <- viewState + i :: id <- viewId tag "select" @ onChange act $ do - addContext (Option defOpt) options + runViewContext (Option i defOpt) st options -- | An option for a 'dropdown' or 'select' option - :: (ViewAction (Action id), Eq opt, ToParam opt) + :: forall opt id + . (ViewAction (Action id), Eq opt, ToParam opt) => opt -> Text - -> View (Option opt id) () + -> View (Option id opt) () option opt cnt = do - os <- context + os :: Option id opt <- viewId tag "option" @ att "value" (toParam opt).value @ selected (os.defaultOption == opt) $ text cnt @@ -68,9 +72,15 @@ selected b = if b then att "selected" "true" else id -- | The view context for an 'option' -data Option opt id = Option - { defaultOption :: opt +data Option id opt = Option + { id :: id + , defaultOption :: opt } + deriving (Generic) + + +instance (ToParam id, ToParam opt, FromParam id, FromParam opt) => ViewId (Option id opt) where + type ViewState (Option id opt) = ViewState id {- | A live search field. Set a DelayMs to avoid hitting the server on every keystroke diff --git a/src/Web/Hyperbole/HyperView/Types.hs b/src/Web/Hyperbole/HyperView/Types.hs index 32f3785e..edd18dd0 100644 --- a/src/Web/Hyperbole/HyperView/Types.hs +++ b/src/Web/Hyperbole/HyperView/Types.hs @@ -6,15 +6,16 @@ module Web.Hyperbole.HyperView.Types where import Data.Kind (Type) import Effectful -import Effectful.Reader.Dynamic +import Effectful.Reader.Static +import Effectful.State.Static.Local import GHC.Generics -import Web.Hyperbole.Data.Encoded (FromEncoded, ToEncoded (..)) +import Web.Hyperbole.Data.Encoded as Encoded import Web.Hyperbole.Effect.Hyperbole (Hyperbole) -import Web.Hyperbole.HyperView.ViewAction -import Web.Hyperbole.HyperView.ViewId -import Web.Hyperbole.View (View, none) +import Web.Hyperbole.View (View (..), ViewAction, ViewId (..), none) +-- HyperView -------------------------------------------- + {- | HyperViews are interactive subsections of a 'Page' Create an instance with a unique view id type and a sum type describing the actions the HyperView supports. The View Id can contain context (a database id, for example) @@ -42,6 +43,9 @@ class (ViewId id, ViewAction (Action id), ConcurrencyValue (Concurrency id)) => type Require id = '[] + -- type ViewState id :: Type + -- type ViewState id = () + -- | Control how overlapping actions are handled. 'Drop' by default -- -- > type Concurrency Autocomplete = Replace @@ -55,7 +59,13 @@ class (ViewId id, ViewAction (Action id), ConcurrencyValue (Concurrency id)) => -- -- > update (SetMessage msg) = pure $ messageView msg -- > update ClearMessage = pure $ messageView "" - update :: (Hyperbole :> es) => Action id -> Eff (Reader id : es) (View id ()) + update :: (Hyperbole :> es) => Action id -> Eff (Reader id : State (ViewState id) : es) (View id ()) + + +instance HyperView () es where + data Action () = TupleNone + deriving (Generic, ViewAction) + update _ = pure none -- convert the type to a value diff --git a/src/Web/Hyperbole/HyperView/ViewId.hs b/src/Web/Hyperbole/HyperView/ViewId.hs deleted file mode 100644 index 79a3b720..00000000 --- a/src/Web/Hyperbole/HyperView/ViewId.hs +++ /dev/null @@ -1,56 +0,0 @@ -{-# LANGUAGE DefaultSignatures #-} - -module Web.Hyperbole.HyperView.ViewId where - -import Data.Text (Text) -import Effectful -import Effectful.Reader.Dynamic -import GHC.Generics -import Web.Hyperbole.Data.Encoded as Encoded -import Web.Hyperbole.View (View, context) - - -{- | A unique identifier for a 'HyperView' - -@ -#EMBED Example/Page/Simple.hs data Message -@ --} -class ViewId a where - toViewId :: a -> Encoded - default toViewId :: (Generic a, GToEncoded (Rep a)) => a -> Encoded - toViewId = genericToEncoded - - - parseViewId :: Encoded -> Either String a - default parseViewId :: (Generic a, GFromEncoded (Rep a)) => Encoded -> Either String a - parseViewId = genericParseEncoded - - -{- | Access the 'viewId' in a 'View' or 'update' - -@ -#EMBED Example/Page/Concurrency.hs data LazyData - -#EMBED Example/Page/Concurrency.hs instance (Debug :> es, GenRandom :> es) => HyperView LazyData es where -@ --} -class HasViewId m view where - viewId :: m view - - -instance HasViewId (View ctx) ctx where - viewId = context -instance HasViewId (Eff (Reader view : es)) view where - viewId = ask - - -encodeViewId :: (ViewId id) => id -> Text -encodeViewId = encodedToText . toViewId - - -decodeViewId :: (ViewId id) => Text -> Maybe id -decodeViewId t = do - case parseViewId =<< decodeEither t of - Left _ -> Nothing - Right a -> pure a diff --git a/src/Web/Hyperbole/Page.hs b/src/Web/Hyperbole/Page.hs index fec2e25f..0926b9b2 100644 --- a/src/Web/Hyperbole/Page.hs +++ b/src/Web/Hyperbole/Page.hs @@ -2,7 +2,7 @@ module Web.Hyperbole.Page where import Data.Kind (Type) import Effectful -import Effectful.Reader.Dynamic +import Effectful.Reader.Static import Web.Hyperbole.Effect.Hyperbole import Web.Hyperbole.HyperView (Root (..)) import Web.Hyperbole.Server.Handler (RunHandlers, runLoad) diff --git a/src/Web/Hyperbole/Server/Handler.hs b/src/Web/Hyperbole/Server/Handler.hs index 05bc1c8f..3587bd5f 100644 --- a/src/Web/Hyperbole/Server/Handler.hs +++ b/src/Web/Hyperbole/Server/Handler.hs @@ -6,7 +6,8 @@ module Web.Hyperbole.Server.Handler where import Data.Kind (Type) import Effectful import Effectful.Dispatch.Dynamic -import Effectful.Reader.Dynamic +import Effectful.Reader.Static +import Effectful.State.Static.Local import Web.Hyperbole.Data.Encoded import Web.Hyperbole.Effect.Hyperbole import Web.Hyperbole.Effect.Response (hyperView, respondError) @@ -18,14 +19,14 @@ import Web.Hyperbole.View class RunHandlers (views :: [Type]) es where - runHandlers :: (Hyperbole :> es) => Event TargetViewId Encoded -> Eff es (Maybe Response) + runHandlers :: (Hyperbole :> es) => Event TargetViewId Encoded Encoded -> Eff es (Maybe Response) instance RunHandlers '[] es where runHandlers _ = pure Nothing -instance (HyperView view es, RunHandlers views es) => RunHandlers (view : views) es where +instance (HyperView view es, ToEncoded (ViewState view), FromEncoded (ViewState view), RunHandlers views es) => RunHandlers (view : views) es where runHandlers rawEvent = do mr <- runHandler @view rawEvent (update @view) case mr of @@ -35,17 +36,17 @@ instance (HyperView view es, RunHandlers views es) => RunHandlers (view : views) runHandler :: forall id es - . (HyperView id es, Hyperbole :> es) - => Event TargetViewId Encoded - -> (Action id -> Eff (Reader id : es) (View id ())) + . (HyperView id es, ToEncoded (ViewState id), FromEncoded (ViewState id), Hyperbole :> es) + => Event TargetViewId Encoded Encoded + -> (Action id -> Eff (Reader id : State (ViewState id) : es) (View id ())) -> Eff es (Maybe Response) runHandler rawEvent run = do -- Get an event matching our type. If it doesn't match, skip to the next handler - mev <- decodeEvent @id rawEvent :: Eff es (Maybe (Event id (Action id))) + mev <- decodeEvent @id rawEvent :: Eff es (Maybe (Event id (Action id) (ViewState id))) case mev of Just evt -> do - vw <- runReader evt.viewId $ run evt.action - res <- hyperView evt.viewId vw + (vw, st) <- runState evt.state $ runReader evt.viewId $ run evt.action + res <- hyperView evt.viewId st vw pure $ Just res _ -> do pure Nothing @@ -73,14 +74,15 @@ loadPageResponse :: Eff es (View (Root total) ()) -> Eff es Response loadPageResponse run = do vw <- run let vid = TargetViewId $ toViewId Root - let res = Response vid $ addContext Root vw + let res = Response $ ViewUpdate vid $ renderBody $ runViewContext Root () vw pure res -- despite not needing any effects, this must be in Eff es to get `es` on the RHS -decodeEvent :: forall id es. (HyperView id es) => Event TargetViewId Encoded -> Eff es (Maybe (Event id (Action id))) -decodeEvent (Event (TargetViewId ti) eact) = +decodeEvent :: forall id es. (HyperView id es, FromEncoded (ViewState id)) => Event TargetViewId Encoded Encoded -> Eff es (Maybe (Event id (Action id) (ViewState id))) +decodeEvent (Event (TargetViewId ti) eact est) = pure $ either (const Nothing) Just $ do vid <- parseViewId ti act <- parseAction eact - pure $ Event vid act + st <- parseEncoded est + pure $ Event vid act st diff --git a/src/Web/Hyperbole/Server/Message.hs b/src/Web/Hyperbole/Server/Message.hs index 5f6da697..7e9ace36 100644 --- a/src/Web/Hyperbole/Server/Message.hs +++ b/src/Web/Hyperbole/Server/Message.hs @@ -2,6 +2,7 @@ module Web.Hyperbole.Server.Message where +import Control.Applicative ((<|>)) import Control.Exception (Exception) import Data.Aeson qualified as Aeson import Data.Attoparsec.Text (Parser, char, endOfLine, isEndOfLine, parseOnly, sepBy, string, takeText, takeTill, takeWhile1) @@ -11,6 +12,7 @@ import Data.List qualified as L import Data.String.Conversions (cs) import Data.Text (Text) import Data.Text qualified as T +import GHC.Generics (Generic) import Web.Hyperbole.Data.Cookie (Cookie, Cookies) import Web.Hyperbole.Data.Cookie qualified as Cookie import Web.Hyperbole.Data.Encoded @@ -21,6 +23,7 @@ import Web.Hyperbole.Effect.Hyperbole (Remote (..)) import Web.Hyperbole.Types.Client (Client (..)) import Web.Hyperbole.Types.Event import Web.Hyperbole.Types.Request +import Web.Hyperbole.View (ViewId) {- @@ -36,7 +39,7 @@ import Web.Hyperbole.Types.Request data Message = Message { messageType :: Text - , event :: Event TargetViewId Encoded + , event :: Event TargetViewId Encoded Encoded , requestId :: RequestId , metadata :: Metadata , body :: MessageBody @@ -85,29 +88,42 @@ parseActionMessage = parseOnly parser body = do MessageBody . cs . T.strip <$> takeText - event :: Parser (Event TargetViewId Encoded) + event :: Parser (Event TargetViewId Encoded Encoded) event = do vid <- targetViewId - endOfLine act <- encodedAction - endOfLine - pure $ Event vid act + st <- encodedState <|> pure mempty + pure $ Event vid act st where targetViewId :: Parser TargetViewId targetViewId = do _ <- string "ViewId: " line <- takeLine - case encodedParseText line of + v <- case encodedParseText line of Left e -> fail $ "Parse Encoded ViewId failed: " <> cs e <> " from " <> cs line Right a -> pure $ TargetViewId a + endOfLine + pure v encodedAction :: Parser Encoded encodedAction = do _ <- string "Action: " inp <- takeLine - case encodedParseText inp of + v <- case encodedParseText inp of Left e -> fail $ "Parse Encoded ViewAction failed: " <> cs e <> " from " <> cs inp Right a -> pure a + endOfLine + pure v + + encodedState :: Parser Encoded + encodedState = do + _ <- string "State: " + inp <- takeLine + v <- case encodedParseText inp of + Left e -> fail $ "Parse Encoded ViewState failed: " <> cs e <> " from " <> cs inp + Right a -> pure a + endOfLine + pure v requestId :: Parser RequestId requestId = do @@ -150,8 +166,14 @@ type MetaKey = Text newtype Metadata = Metadata [(Text, Text)] deriving newtype (Semigroup, Monoid) - deriving (Show) + deriving (Show, Generic) + deriving anyclass (ViewId) + +-- instance HyperView Metadata es where +-- data Action Metadata = MetaNone +-- deriving (Generic, ViewAction) +-- update _ = pure none metadata :: MetaKey -> Text -> Metadata metadata key value = Metadata [(key, value)] @@ -170,7 +192,7 @@ requestMetadata req = metaRequestId (RequestId reqId) = metadata "RequestId" (cs reqId) - eventMetadata :: Event TargetViewId Encoded -> Metadata + eventMetadata :: Event TargetViewId Encoded Encoded -> Metadata eventMetadata event = Metadata [ ("ViewId", encodedToText event.viewId.encoded) diff --git a/src/Web/Hyperbole/Server/Options.hs b/src/Web/Hyperbole/Server/Options.hs index c0acc220..6a271205 100644 --- a/src/Web/Hyperbole/Server/Options.hs +++ b/src/Web/Hyperbole/Server/Options.hs @@ -29,10 +29,11 @@ defaultErrorMessage = \case e -> cs $ drop 3 $ show e -defaultErrorBody :: Text -> View Body () -defaultErrorBody msg = - el ~ bg (HexColor "#F00") . color (HexColor "#FFF") $ do - text msg +defaultErrorBody :: Text -> Body +defaultErrorBody msg = Body $ + renderLazyByteString $ do + el ~ bg (HexColor "#F00") . color (HexColor "#FFF") $ do + text msg defaultError :: ResponseError -> ServerError @@ -43,9 +44,9 @@ defaultError = \case let msg = defaultErrorMessage err in ServerError msg (defaultErrorBody msg) where - errNotHandled :: Event TargetViewId Encoded -> ServerError + errNotHandled :: Event TargetViewId Encoded Encoded -> ServerError errNotHandled ev = - ServerError "Action Not Handled" $ do + ServerError "Action Not Handled" $ Body $ renderLazyByteString $ do el $ do text "No Handler for Event viewId: " text $ encodedToText ev.viewId.encoded diff --git a/src/Web/Hyperbole/Server/Socket.hs b/src/Web/Hyperbole/Server/Socket.hs index 8318eaf8..466144ca 100644 --- a/src/Web/Hyperbole/Server/Socket.hs +++ b/src/Web/Hyperbole/Server/Socket.hs @@ -34,7 +34,6 @@ import Web.Hyperbole.Types.Client import Web.Hyperbole.Types.Event (Event (..), TargetViewId (..)) import Web.Hyperbole.Types.Request import Web.Hyperbole.Types.Response -import Web.Hyperbole.View (View, addContext, renderLazyByteString) data SocketRequest = SocketRequest @@ -57,7 +56,7 @@ runHyperboleSocket _opts conn req = reinterpret (runHyperboleLocal req) $ \_ -> pure req RespondNow r -> do throwError_ r - PushUpdate vid vw -> do + PushUpdate (ViewUpdate vid vw) -> do sendUpdate conn (targetViewMetadata vid <> requestMetadata req) vw GetClient -> do Local.get @Client @@ -98,7 +97,7 @@ handleRequestSocket opts actions wreq conn eff = do Right (resp, clnt, rmts) -> do let meta = requestMetadata req <> responseMetadata req.path clnt rmts case resp of - (Response _ vw) -> do + (Response (ViewUpdate _ vw)) -> do sendResponse conn meta vw (Err err) -> sendError conn meta (opts.serverError err) (Redirect url) -> sendRedirect conn meta url @@ -109,10 +108,10 @@ handleRequestSocket opts actions wreq conn eff = do _ <- waitCatch a clearRunningAction req.requestId req.event where - addRunningAction :: (IOE :> es, Concurrent :> es) => Async () -> RequestId -> Maybe (Event TargetViewId Encoded) -> Eff es () + addRunningAction :: (IOE :> es, Concurrent :> es) => Async () -> RequestId -> Maybe (Event TargetViewId Encoded Encoded) -> Eff es () addRunningAction a (RequestId reqId) = \case Nothing -> pure () - Just (Event vid act) -> do + Just (Event vid act _) -> do -- liftIO $ putStrLn $ " [add] (" <> cs reqId <> ") " <> cs clientId.value <> "|" <> show vid maold <- atomically $ do m <- readTVar @RunningActions actions @@ -124,10 +123,10 @@ handleRequestSocket opts actions wreq conn eff = do liftIO $ putStrLn $ "CANCEL (" <> cs reqId <> ") " <> cs (encodedToText vid.encoded) <> ": " <> cs (encodedToText actold) cancel aold - clearRunningAction :: (IOE :> es, Concurrent :> es) => RequestId -> Maybe (Event TargetViewId Encoded) -> Eff es () + clearRunningAction :: (IOE :> es, Concurrent :> es) => RequestId -> Maybe (Event TargetViewId Encoded Encoded) -> Eff es () clearRunningAction (RequestId _) = \case Nothing -> pure () - Just (Event vid _) -> do + Just (Event vid _ _) -> do _ <- atomically $ modifyTVar actions $ M.delete vid pure () @@ -186,14 +185,14 @@ handleRequestSocket opts actions wreq conn eff = do maybe (Left $ MissingMeta (cs key)) pure $ lookupMetadata key m -sendResponse :: (IOE :> es) => Connection -> Metadata -> View Body () -> Eff es () -sendResponse conn meta vw = do - sendMessage "RESPONSE" conn meta (MessageHtml $ renderLazyByteString $ addContext Body vw) +sendResponse :: (IOE :> es) => Connection -> Metadata -> Body -> Eff es () +sendResponse conn meta (Body b) = do + sendMessage "RESPONSE" conn meta (MessageHtml b) -sendUpdate :: (IOE :> es) => Connection -> Metadata -> View Body () -> Eff es () -sendUpdate conn meta vw = do - sendMessage "UPDATE" conn meta (MessageHtml $ renderLazyByteString $ addContext Body vw) +sendUpdate :: (IOE :> es) => Connection -> Metadata -> Body -> Eff es () +sendUpdate conn meta (Body b) = do + sendMessage "UPDATE" conn meta (MessageHtml b) sendRedirect :: (IOE :> es) => Connection -> Metadata -> URI -> Eff es () @@ -203,8 +202,8 @@ sendRedirect conn meta u = do -- TODO: this isn't an UPDATE? sendError :: (IOE :> es) => Connection -> Metadata -> ServerError -> Eff es () -sendError conn meta (ServerError err body) = do - sendMessage "UPDATE" conn (metadata "Error" err <> meta) (MessageHtml $ renderLazyByteString $ addContext Body body) +sendError conn meta (ServerError err (Body body)) = do + sendMessage "UPDATE" conn (metadata "Error" err <> meta) (MessageHtml body) newtype Command = Command Text diff --git a/src/Web/Hyperbole/Server/Wai.hs b/src/Web/Hyperbole/Server/Wai.hs index 18d7005a..6ffcd10d 100644 --- a/src/Web/Hyperbole/Server/Wai.hs +++ b/src/Web/Hyperbole/Server/Wai.hs @@ -32,7 +32,7 @@ import Web.Hyperbole.Types.Client import Web.Hyperbole.Types.Event (Event (..), TargetViewId (..)) import Web.Hyperbole.Types.Request import Web.Hyperbole.Types.Response -import Web.Hyperbole.View (View, addContext, renderLazyByteString, script', type_) +import Web.Hyperbole.View (View, renderLazyByteString, runViewContext, script', type_) handleRequestWai @@ -61,7 +61,7 @@ runHyperboleWai req = reinterpret (runHyperboleLocal req) $ \_ -> \case pure req RespondNow r -> do throwError_ r - PushUpdate _ _ -> do + PushUpdate _ -> do -- ignore! you can't push updates using WAI pure () GetClient -> do @@ -83,12 +83,12 @@ sendResponse options req client res remotes respond = do response metas = \case (Err err) -> respondError (errStatus err) [] $ options.serverError err - (Response _ vw) -> do + (Response (ViewUpdate _ vw)) -> do respondHtml status200 (clientHeaders client) $ renderViewResponse metas vw (Redirect u) -> do let url = uriToText u let hs = ("Location", cs url) : clientHeaders client - respondHtml status200 hs $ renderViewResponse metas $ do + respondHtml status200 hs $ renderViewResponse metas $ Body $ renderLazyByteString $ do script' [i|window.location = '#{uriToText u}'|] @@ -107,9 +107,9 @@ sendResponse options req client res remotes respond = do Nothing -> options.toDocument body _ -> body - renderViewResponse :: Metadata -> View Body () -> BL.ByteString - renderViewResponse metas vw = - addDocument $ renderLazyByteString (addContext metas $ scriptMeta metas) <> "\n\n" <> renderLazyByteString (addContext Body vw) + renderViewResponse :: Metadata -> Body -> BL.ByteString + renderViewResponse metas (Body body) = + addDocument $ renderLazyByteString (runViewContext metas () $ scriptMeta metas) <> "\n\n" <> body respondError s hs serr = respondHtml s hs $ renderViewResponse (metaError serr.message) serr.body respondHtml s hs = Wai.responseLBS s (contentType ContentHtml : hs) @@ -163,13 +163,15 @@ fromWaiRequest wr body = do , requestId } where - lookupEvent :: [Header] -> Maybe (Event TargetViewId Encoded) + lookupEvent :: [Header] -> Maybe (Event TargetViewId Encoded Encoded) lookupEvent headers = do viewIdText <- cs <$> L.lookup "Hyp-ViewId" headers actText <- cs <$> L.lookup "Hyp-Action" headers + stText <- cs <$> L.lookup "Hyp-State" headers act <- decode actText viewId <- TargetViewId <$> decode viewIdText - pure $ Event viewId act + st <- decode stText + pure $ Event viewId act st -- Client only returns ONE Cookie header, with everything concatenated diff --git a/src/Web/Hyperbole/Types/Event.hs b/src/Web/Hyperbole/Types/Event.hs index 71019189..5418be0c 100644 --- a/src/Web/Hyperbole/Types/Event.hs +++ b/src/Web/Hyperbole/Types/Event.hs @@ -15,11 +15,12 @@ instance Show TargetViewId where -- | An action, with its corresponding id -data Event id act = Event +data Event id act st = Event { viewId :: id , action :: act + , state :: st } -instance (Show act, Show id) => Show (Event id act) where - show e = "Event " <> show e.viewId <> " " <> show e.action +instance (Show act, Show id, Show st) => Show (Event id act st) where + show e = "Event " <> show e.viewId <> " " <> show e.action <> " " <> show e.state diff --git a/src/Web/Hyperbole/Types/Request.hs b/src/Web/Hyperbole/Types/Request.hs index 06514fa8..41f939fe 100644 --- a/src/Web/Hyperbole/Types/Request.hs +++ b/src/Web/Hyperbole/Types/Request.hs @@ -21,7 +21,7 @@ data Request = Request , body :: BL.ByteString , method :: Method , cookies :: Cookies - , event :: Maybe (Event TargetViewId Encoded) + , event :: Maybe (Event TargetViewId Encoded Encoded) , requestId :: RequestId } deriving (Show) diff --git a/src/Web/Hyperbole/Types/Response.hs b/src/Web/Hyperbole/Types/Response.hs index 897e6a40..fd08372b 100644 --- a/src/Web/Hyperbole/Types/Response.hs +++ b/src/Web/Hyperbole/Types/Response.hs @@ -2,21 +2,24 @@ module Web.Hyperbole.Types.Response where +import Data.ByteString.Lazy qualified as BL import Data.String (IsString (..)) import Data.String.Conversions (cs) import Data.Text (Text) import Web.Hyperbole.Data.Encoded (Encoded) import Web.Hyperbole.Data.URI (URI) import Web.Hyperbole.Types.Event -import Web.Hyperbole.View -data Body = Body +newtype Body = Body BL.ByteString + + +data ViewUpdate = ViewUpdate {viewId :: TargetViewId, body :: Body} -- | A processed response for the client, which might be a 'ResponseError' data Response - = Response TargetViewId (View Body ()) + = Response ViewUpdate | Redirect URI | Err ResponseError @@ -29,7 +32,7 @@ data ResponseError | ErrServer Text | ErrCustom ServerError | ErrInternal - | ErrNotHandled (Event TargetViewId Encoded) + | ErrNotHandled (Event TargetViewId Encoded Encoded) | ErrAuth Text instance Show ResponseError where show = \case @@ -49,5 +52,5 @@ instance IsString ResponseError where -- Serialized server error data ServerError = ServerError { message :: Text - , body :: View Body () + , body :: Body } diff --git a/src/Web/Hyperbole/View.hs b/src/Web/Hyperbole/View.hs index c631ca53..4700ed71 100644 --- a/src/Web/Hyperbole/View.hs +++ b/src/Web/Hyperbole/View.hs @@ -1,5 +1,7 @@ module Web.Hyperbole.View ( module Web.Hyperbole.View.Types + , module Web.Hyperbole.View.ViewId + , module Web.Hyperbole.View.ViewAction , module Web.Hyperbole.View.Embed , module Web.Hyperbole.View.Render , module Web.Hyperbole.View.Tag @@ -12,5 +14,7 @@ import Web.Hyperbole.View.CSS import Web.Hyperbole.View.Embed import Web.Hyperbole.View.Render import Web.Hyperbole.View.Tag hiding (form, input, label) -import Web.Hyperbole.View.Types (View (..), addContext, context, modifyContext, none, raw, tag, text) +import Web.Hyperbole.View.Types +import Web.Hyperbole.View.ViewAction +import Web.Hyperbole.View.ViewId diff --git a/src/Web/Hyperbole/View/Render.hs b/src/Web/Hyperbole/View/Render.hs index 749c99de..a8683761 100644 --- a/src/Web/Hyperbole/View/Render.hs +++ b/src/Web/Hyperbole/View/Render.hs @@ -1,17 +1,23 @@ module Web.Hyperbole.View.Render ( renderText , renderLazyByteString + , renderBody ) where import Data.ByteString.Lazy qualified as BL import Data.Text (Text) import Web.Atomic.Render qualified as Atomic -import Web.Hyperbole.View.Types (View, runView) +import Web.Hyperbole.Types.Response (Body (..)) +import Web.Hyperbole.View.Types (View, execView) renderText :: View () () -> Text -renderText = Atomic.renderText . runView () +renderText = Atomic.renderText . execView () () renderLazyByteString :: View () () -> BL.ByteString -renderLazyByteString = Atomic.renderLazyByteString . runView () +renderLazyByteString = Atomic.renderLazyByteString . execView () () + + +renderBody :: View () () -> Body +renderBody v = Body $ renderLazyByteString v diff --git a/src/Web/Hyperbole/View/Tag.hs b/src/Web/Hyperbole/View/Tag.hs index 62fb7284..5a2969b3 100644 --- a/src/Web/Hyperbole/View/Tag.hs +++ b/src/Web/Hyperbole/View/Tag.hs @@ -10,11 +10,38 @@ import Data.Text qualified as T import Effectful import Effectful.State.Static.Local import Web.Atomic.CSS +import Web.Atomic.Html qualified as Atomic import Web.Atomic.Types import Web.Hyperbole.Data.URI import Web.Hyperbole.View.Types +-- Html --------------------------------------------- + +tag :: Text -> View c () -> View c () +tag = tag' False + + +tag' :: Bool -> Text -> View c () -> View c () +tag' inline n (View eff) = View $ do + inner <- eff + pure $ Atomic.tag' inline n inner + + +text :: Text -> View c () +text t = View $ pure $ Atomic.text t + + +none :: View c () +none = View $ pure Atomic.none + + +raw :: Text -> View c () +raw t = View $ pure $ Atomic.raw t + + +--- + el :: View c () -> View c () el = tag "div" diff --git a/src/Web/Hyperbole/View/Types.hs b/src/Web/Hyperbole/View/Types.hs index 445969c1..1eb39ec4 100644 --- a/src/Web/Hyperbole/View/Types.hs +++ b/src/Web/Hyperbole/View/Types.hs @@ -2,14 +2,18 @@ module Web.Hyperbole.View.Types where -import Data.Kind (Type) import Data.String (IsString (..)) import Data.Text (Text, pack) import Effectful import Effectful.Reader.Static +import Effectful.State.Static.Local +import GHC.Generics import Web.Atomic.Html (Html (..)) import Web.Atomic.Html qualified as Atomic import Web.Atomic.Types +import Web.Hyperbole.Data.Encoded (decodeEither, encodedToText) +import Web.Hyperbole.Data.Param (FromParam, ToParam (..)) +import Web.Hyperbole.View.ViewId -- View ------------------------------------------------------------ @@ -20,16 +24,16 @@ import Web.Atomic.Types #EMBED Example/Docs/BasicPage.hs helloWorld @ -} -newtype View c a = View {html :: Eff '[Reader c] (Html a)} +newtype View c a = View {html :: Eff '[Reader (c, ViewState c)] (Html a)} instance IsString (View c ()) where fromString s = View $ pure $ Atomic.text (pack s) -runView :: forall c a. c -> View c a -> Html a -runView c (View eff) = do - runPureEff $ runReader c eff +execView :: forall c a. c -> ViewState c -> View c a -> Html a +execView c st (View eff) = do + runPureEff $ runReader (c, st) eff instance Functor (View c) where @@ -54,59 +58,49 @@ instance Monad (View ctx) where View ea >>= famb = View $ do a :: a <- (.value) <$> ea let View eb :: View ctx b = famb a - hb <- eb - pure $ hb + eb -- Context ----------------------------------------- -type family ViewContext (v :: Type) where - ViewContext (View c x) = c - ViewContext (View c x -> View c x) = c +-- type family ViewContext (v :: Type) where +-- ViewContext (View c x) = c +-- ViewContext (View c x -> View c x) = c + +newtype ChildView a = ChildView a + deriving (Generic) +instance (ViewId a, FromParam a, ToParam a) => ViewId (ChildView a) where + type ViewState (ChildView a) = ViewState a -- TEST: appending Empty -context :: forall c. View c c +context :: forall c. View c (c, ViewState c) context = View $ do - c <- ask @c + c <- ask @(c, ViewState c) pure $ pure c -addContext :: ctx -> View ctx () -> View c () -addContext c (View eff) = View $ do - pure $ runPureEff $ runReader c eff - - -modifyContext - :: forall ctx0 ctx1. (ctx0 -> ctx1) -> View ctx1 () -> View ctx0 () -modifyContext f (View eff) = View $ do - ctx0 <- ask @ctx0 - pure $ runPureEff $ runReader (f ctx0) eff - - --- Html --------------------------------------------- - -tag :: Text -> View c () -> View c () -tag = tag' False +viewState :: View c (ViewState c) +viewState = snd <$> context -tag' :: Bool -> Text -> View c () -> View c () -tag' inline n (View eff) = View $ do - content <- eff - pure $ Atomic.tag' inline n content +runViewContext :: ctx -> ViewState ctx -> View ctx () -> View c () +runViewContext c st (View eff) = View $ do + pure $ runPureEff $ runReader (c, st) eff -text :: Text -> View c () -text t = View $ pure $ Atomic.text t +runChildView :: (ViewState ctx ~ ViewState c) => (c -> ctx) -> View ctx () -> View c () +runChildView f v = do + st <- viewState + c <- viewId + runViewContext (f c) st v -none :: View c () -none = View $ pure Atomic.none - - -raw :: Text -> View c () -raw t = View $ pure $ Atomic.raw t - +-- modifyContext +-- :: forall ctx0 ctx1. (ctx0 -> ctx1) -> View ctx1 () -> View ctx0 () +-- modifyContext f (View eff) = View $ do +-- ctx0 <- ask @ctx0 +-- pure $ runPureEff $ runReader (f ctx0) eff -- Attributes ----------------------------------------- @@ -120,3 +114,32 @@ instance Styleable (View c a) where modCSS f (View eff) = View $ do h <- eff pure $ modCSS f h + + +{- | Access the 'viewId' in a 'View' or 'update' + +@ +#EMBED Example/Page/Concurrency.hs data LazyData + +#EMBED Example/Page/Concurrency.hs instance (Debug :> es, GenRandom :> es) => HyperView LazyData es where +@ +-} +class HasViewId m view where + viewId :: m view + + +instance HasViewId (View ctx) ctx where + viewId = fst <$> context +instance (ViewState view ~ st) => HasViewId (Eff (Reader view : State st : es)) view where + viewId = ask + + +encodeViewId :: (ViewId id) => id -> Text +encodeViewId = encodedToText . toViewId + + +decodeViewId :: (ViewId id) => Text -> Maybe id +decodeViewId t = do + case parseViewId =<< decodeEither t of + Left _ -> Nothing + Right a -> pure a diff --git a/src/Web/Hyperbole/HyperView/ViewAction.hs b/src/Web/Hyperbole/View/ViewAction.hs similarity index 94% rename from src/Web/Hyperbole/HyperView/ViewAction.hs rename to src/Web/Hyperbole/View/ViewAction.hs index 42394eed..8cb63399 100644 --- a/src/Web/Hyperbole/HyperView/ViewAction.hs +++ b/src/Web/Hyperbole/View/ViewAction.hs @@ -1,6 +1,6 @@ {-# LANGUAGE DefaultSignatures #-} -module Web.Hyperbole.HyperView.ViewAction where +module Web.Hyperbole.View.ViewAction where import Data.Text (Text) import GHC.Generics diff --git a/src/Web/Hyperbole/View/ViewId.hs b/src/Web/Hyperbole/View/ViewId.hs new file mode 100644 index 00000000..b97748b6 --- /dev/null +++ b/src/Web/Hyperbole/View/ViewId.hs @@ -0,0 +1,33 @@ +{-# LANGUAGE DefaultSignatures #-} + +module Web.Hyperbole.View.ViewId where + +import Data.Kind (Type) +import GHC.Generics +import Web.Hyperbole.Data.Encoded as Encoded + + +{- | A unique identifier for a 'HyperView' + +@ +#EMBED Example/Page/Simple.hs data Message +@ +-} +class ViewId a where + type ViewState a :: Type + type ViewState a = () + + + toViewId :: a -> Encoded + default toViewId :: (Generic a, GToEncoded (Rep a)) => a -> Encoded + toViewId = genericToEncoded + + + parseViewId :: Encoded -> Either String a + default parseViewId :: (Generic a, GFromEncoded (Rep a)) => Encoded -> Either String a + parseViewId = genericParseEncoded + + +instance ViewId () where + toViewId _ = mempty + parseViewId _ = pure ()