From 50a752df70d5eccea501008363e106e3a45df20f Mon Sep 17 00:00:00 2001 From: "Adam D. Ruppe" Date: Sat, 11 May 2019 10:30:16 -0400 Subject: [PATCH] more recent updates --- cgi.d | 97 +++++++++++++++++++++++++++++++++++------------------- dom.d | 3 ++ email.d | 31 +++++++++++++++-- http2.d | 2 +- minigui.d | 7 ++++ nanovega.d | 5 +++ script.d | 2 ++ web.d | 13 ++++++-- 8 files changed, 120 insertions(+), 40 deletions(-) diff --git a/cgi.d b/cgi.d index dcf9992..2d3a872 100644 --- a/cgi.d +++ b/cgi.d @@ -1107,7 +1107,6 @@ class Cgi { } - /// void writeToFile(string filenameToSaveTo) const { import std.file; @@ -5272,7 +5271,7 @@ ScheduledJobHelper schedule(alias fn, T...)(T args) { /// interface ScheduledJobServer { - /// + /// Use the [schedule] function for a higher-level interface. int scheduleJob(int whenIs, int when, string executable, string func, string[] args); /// void cancelJob(int jobId); @@ -5282,6 +5281,16 @@ class ScheduledJobServerConnection : ScheduledJobServer { mixin ImplementRpcClientInterface!(ScheduledJobServer, "/tmp/arsd_scheduled_job_server"); } +final class ScheduledJobServerImplementation : ScheduledJobServer { + protected int scheduleJob(int whenIs, int when, string executable, string func, string[] args) { + return 0; + } + + protected void cancelJob(int jobId) { + + } +} + /// interface EventSourceServer { /++ @@ -6044,6 +6053,18 @@ static auto elementFor(T)(string displayName, string name) { else i.attrs.type = "number"; i.attrs.value = to!string(T.init); + } else static if(is(T == bool)) { + Element lbl; + if(displayName !is null) { + lbl = div.addChild("label"); + lbl.addChild("span", displayName, "label-text"); + lbl.appendText(" "); + } else { + lbl = div; + } + auto i = lbl.addChild("input", name); + i.attrs.type = "checkbox"; + i.attrs.name = name; } else static if(is(T == K[], K)) { auto templ = div.addChild("template"); templ.appendChild(elementFor!(K)(null, name)); @@ -6370,6 +6391,8 @@ auto formatReturnValueAsHtml(T)(T t) { } return dl; + } else static if(is(T == bool)) { + return Element.make("span", t ? "true" : "false", "automatic-data-display"); } else static if(is(T == E[], E)) { static if(is(E : RestObject!Proxy, Proxy)) { // treat RestObject similar to struct @@ -6439,18 +6462,6 @@ auto formatReturnValueAsHtml(T)(T t) { FIXME +/ class WebPresenter() { - -} - -/++ - The base class for the [dispatcher] function and object support. -+/ -class WebObject(Helper = void) { - Cgi cgi; - void initialize(Cgi cgi) { - this.cgi = cgi; - } - string script() { return ` `; @@ -6569,6 +6580,19 @@ class WebObject(Helper = void) { } } +/++ + The base class for the [dispatcher] function and object support. ++/ +class WebObject(Helper = void) { + Cgi cgi; + WebPresenter!() presenter; + + void initialize(Cgi cgi, WebPresenter!() presenter) { + this.cgi = cgi; + this.presenter = presenter; + } +} + /++ Serves a class' methods, as a kind of low-state RPC over the web. To be used with [dispatcher]. @@ -6582,10 +6606,10 @@ auto serveApi(T)(string urlPrefix) { import arsd.dom; import arsd.jsvar; - static bool handler(string urlPrefix, Cgi cgi, DispatcherDetails details) { + static bool handler(string urlPrefix, Cgi cgi, WebPresenter!() presenter, DispatcherDetails details) { auto obj = new T(); - obj.initialize(cgi); + obj.initialize(cgi, presenter); switch(cgi.pathInfo[urlPrefix.length .. $]) { static foreach(methodName; __traits(derivedMembers, T)){{ @@ -6594,7 +6618,7 @@ auto serveApi(T)(string urlPrefix) { case urlify(methodName): switch(cgi.request("format", "html")) { case "html": - auto container = obj.htmlContainer(); + auto container = obj.presenter.htmlContainer(); try { auto ret = callFromCgi!(__traits(getMember, obj, methodName))(&__traits(getMember, obj, methodName), cgi); container.appendChild(formatReturnValueAsHtml(ret)); @@ -6623,12 +6647,12 @@ auto serveApi(T)(string urlPrefix) { case "script.js": cgi.setResponseContentType("text/javascript"); cgi.gzipResponse = true; - cgi.write(obj.script(), true); + cgi.write(obj.presenter.script(), true); return true; case "style.css": cgi.setResponseContentType("text/css"); cgi.gzipResponse = true; - cgi.write(obj.style(), true); + cgi.write(obj.presenter.style(), true); return true; default: return false; @@ -6768,6 +6792,9 @@ mixin template Presenter() { } +// FIXME XSRF token, prolly can just put in a cookie and then it needs to be copied to header or form hidden value +// https://use-the-index-luke.com/sql/partial-results/fetch-next-page + /++ Base class for REST collections. +/ @@ -6934,7 +6961,7 @@ class CollectionOf(Obj, Helper = void) : RestObject!(Helper) { +/ auto serveRestObject(T)(string urlPrefix) { assert(urlPrefix[$ - 1] != '/', "Do NOT use a trailing slash on REST objects."); - static bool handler(string urlPrefix, Cgi cgi, DispatcherDetails details) { + static bool handler(string urlPrefix, Cgi cgi, WebPresenter!() presenter, DispatcherDetails details) { string url = cgi.pathInfo[urlPrefix.length .. $]; if(url.length && url[$ - 1] == '/') { @@ -6943,7 +6970,7 @@ auto serveRestObject(T)(string urlPrefix) { return true; } - return restObjectServeHandler!T(cgi, url); + return restObjectServeHandler!T(cgi, presenter, url); } return DispatcherDefinition!handler(urlPrefix, false); @@ -6957,7 +6984,7 @@ auto serveRestCollectionOf(T)(string urlPrefix) { return serveRestObject!(mixin(T.stringof ~ "s"))(urlPrefix); } -bool restObjectServeHandler(T)(Cgi cgi, string url) { +bool restObjectServeHandler(T)(Cgi cgi, WebPresenter!() presenter, string url) { string urlId = null; if(url.length && url[0] == '/') { // asking for a subobject @@ -6974,7 +7001,7 @@ bool restObjectServeHandler(T)(Cgi cgi, string url) { static if(is(T : CollectionOf!(C, P), C, P)) { if(urlId !is null) { - return restObjectServeHandler!C(cgi, url); // FIXME? urlId); + return restObjectServeHandler!C(cgi, presenter, url); // FIXME? urlId); } } @@ -7007,7 +7034,7 @@ bool restObjectServeHandler(T)(Cgi cgi, string url) { // FIXME return ValidationResult.valid; }; - obj.initialize(cgi); + obj.initialize(cgi, presenter); // FIXME: populate reflection info delegates @@ -7016,12 +7043,12 @@ bool restObjectServeHandler(T)(Cgi cgi, string url) { case "script.js": cgi.setResponseContentType("text/javascript"); cgi.gzipResponse = true; - cgi.write(obj.script(), true); + cgi.write(obj.presenter.script(), true); return true; case "style.css": cgi.setResponseContentType("text/css"); cgi.gzipResponse = true; - cgi.write(obj.style(), true); + cgi.write(obj.presenter.style(), true); return true; default: // intentionally blank @@ -7047,7 +7074,7 @@ bool restObjectServeHandler(T)(Cgi cgi, string url) { cgi.setResponseContentType("application/json"); cgi.write(obj.toJson().toString, true); } else { - auto container = obj.htmlContainer(); + auto container = obj.presenter.htmlContainer(); if(addFormLinks) { static if(is(T : CollectionOf!(C, P), C, P)) container.appendHtml(` @@ -7083,7 +7110,7 @@ bool restObjectServeHandler(T)(Cgi cgi, string url) { static if(is(T : CollectionOf!(C, P), C, P)) { auto results = obj.index(); if(cgi.request("format", "html") == "html") { - auto container = obj.htmlContainer(); + auto container = obj.presenter.htmlContainer(); auto html = formatReturnValueAsHtml(results.results); container.appendHtml(`
@@ -7119,7 +7146,7 @@ bool restObjectServeHandler(T)(Cgi cgi, string url) { case "PUT": case "POST": // an editing form for the object - auto container = obj.htmlContainer(); + auto container = obj.presenter.htmlContainer(); static if(__traits(compiles, () { auto o = new obj.PostProxy(); })) { auto form = (cgi.request("_method") == "POST") ? createAutomaticFormForObject(new obj.PostProxy()) : createAutomaticFormForObject(obj); } else { @@ -7132,7 +7159,7 @@ bool restObjectServeHandler(T)(Cgi cgi, string url) { break; case "DELETE": // FIXME: a delete form for the object (can be phrased "are you sure?") - auto container = obj.htmlContainer(); + auto container = obj.presenter.htmlContainer(); container.appendHtml(` Are you sure you want to delete this item? @@ -7237,7 +7264,7 @@ auto serveStaticFile(string urlPrefix, string filename = null, string contentTyp contentType = "application/javascript"; } - static bool handler(string urlPrefix, Cgi cgi, DispatcherDetails details) { + static bool handler(string urlPrefix, Cgi cgi, WebPresenter!() presenter, DispatcherDetails details) { if(details.contentType.indexOf("image/") == 0) cgi.setCache(true); cgi.setResponseContentType(details.contentType); @@ -7271,17 +7298,19 @@ auto serveStaticData(string urlPrefix, const(void)[] data, string contentType) { )) return; --- +/ -bool dispatcher(definitions...)(Cgi cgi) { +bool dispatcher(definitions...)(Cgi cgi, WebPresenter!() presenter = null) { + if(presenter is null) + presenter = new WebPresenter!(); // I can prolly make this more efficient later but meh. static foreach(definition; definitions) { if(definition.rejectFurther) { if(cgi.pathInfo == definition.urlPrefix) { - auto ret = definition.handler(definition.urlPrefix, cgi, definition.details); + auto ret = definition.handler(definition.urlPrefix, cgi, presenter, definition.details); if(ret) return true; } } else if(cgi.pathInfo.startsWith(definition.urlPrefix)) { - auto ret = definition.handler(definition.urlPrefix, cgi, definition.details); + auto ret = definition.handler(definition.urlPrefix, cgi, presenter, definition.details); if(ret) return true; } diff --git a/dom.d b/dom.d index fbad39c..b71b1bf 100644 --- a/dom.d +++ b/dom.d @@ -3,6 +3,9 @@ // FIXME: https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML // FIXME: appendChild should not fail if the thing already has a parent; it should just automatically remove it per standard. + +// xml entity references?! + /++ This is an html DOM implementation, started with cloning what the browser offers in Javascript, but going well beyond diff --git a/email.d b/email.d index dda80d0..25dff81 100644 --- a/email.d +++ b/email.d @@ -26,6 +26,13 @@ struct MimeAttachment { string id; /// } +/// +enum ToType { + to, + cc, + bcc +} + /++ For OUTGOING email @@ -65,12 +72,32 @@ class EmailMessage { private bool isMime = false; private bool isHtml = false; + /// + void addRecipient(string name, string email, ToType how = ToType.to) { + addRecipient(`"`~name~`" <`~email~`>`, how); + } + + /// + void addRecipient(string who, ToType how = ToType.to) { + final switch(how) { + case ToType.to: + to ~= who; + break; + case ToType.cc: + cc ~= who; + break; + case ToType.bcc: + bcc ~= who; + break; + } + } + /// void setTextBody(string text) { - textBody = text; + textBody = text.strip; } /// automatically sets a text fallback if you haven't already - void setHtmlBody(string html) { + void setHtmlBody()(string html) { isMime = true; isHtml = true; htmlBody = html; diff --git a/http2.d b/http2.d index 1f97919..cfcef30 100644 --- a/http2.d +++ b/http2.d @@ -1672,7 +1672,7 @@ class HttpApiClient() { /// var throwOnError(HttpResponse res) { if(res.code < 200 || res.code >= 300) - throw new Exception(res.codeText); + throw new Exception(res.codeText ~ " " ~ res.contentText); var response = var.fromJson(res.contentText); if(response.errors) { diff --git a/minigui.d b/minigui.d index a489c36..ce926e3 100644 --- a/minigui.d +++ b/minigui.d @@ -8,6 +8,13 @@ // https://docs.microsoft.com/en-us/windows/desktop/Controls/about-custom-draw +// FIXME: make the scroll thing go to bottom when the content changes. + +// add a knob slider view... you click and go up and down so basically same as a vertical slider, just presented as a round image + +// FIXME: the scroll area MUST be fixed to use the proper apis under the hood. + + // FIXME: add a command search thingy built in and implement tip. // FIXME: omg omg what if menu functions have arguments and it can pop up a gui or command line script them?! diff --git a/nanovega.d b/nanovega.d index d3f22e6..b069a17 100644 --- a/nanovega.d +++ b/nanovega.d @@ -142,6 +142,11 @@ $(SIDE_BY_SIDE ) ) +$(TIP + If you are going to use the library with a SDL OpenGL context, + try working with a backwards compatible context profile. +) + Creating drawing context ======================== diff --git a/script.d b/script.d index 48471b1..587db94 100644 --- a/script.d +++ b/script.d @@ -1976,6 +1976,8 @@ Expression parsePart(MyTokenStreamHere)(ref MyTokenStreamHere tokens) { } } +// FIXME: unary ! doesn't work right + funcLoop: while(!tokens.empty) { auto peek = tokens.front; if(peek.type == ScriptToken.Type.symbol) { diff --git a/web.d b/web.d index 8f26605..ee438e7 100644 --- a/web.d +++ b/web.d @@ -2780,6 +2780,11 @@ string formatAs(T, R)(T ret, string format, R api = null, JSONValue* returnValue } else { format = "html"; } + + static if(is(typeof(ret) : K[], K)) { + static if(is(K == struct)) + format = "table"; + } } string retstr; @@ -3999,9 +4004,11 @@ Table structToTable(T)(Document document, T arr, string[] fieldsToSkip = null) i { auto thead = t.addChild("thead"); auto tr = thead.addChild("tr"); - auto s = arr[0]; - foreach(idx, member; s.tupleof) - tr.addChild("th", s.tupleof[idx].stringof[2..$]); + if(arr.length) { + auto s = arr[0]; + foreach(idx, member; s.tupleof) + tr.addChild("th", s.tupleof[idx].stringof[2..$]); + } } bool odd = true;