more recent updates

This commit is contained in:
Adam D. Ruppe 2019-05-11 10:30:16 -04:00
parent 6f908d3e68
commit 50a752df70
8 changed files with 120 additions and 40 deletions

97
cgi.d
View File

@ -1107,7 +1107,6 @@ class Cgi {
} }
/// ///
void writeToFile(string filenameToSaveTo) const { void writeToFile(string filenameToSaveTo) const {
import std.file; import std.file;
@ -5272,7 +5271,7 @@ ScheduledJobHelper schedule(alias fn, T...)(T args) {
/// ///
interface ScheduledJobServer { interface ScheduledJobServer {
/// /// Use the [schedule] function for a higher-level interface.
int scheduleJob(int whenIs, int when, string executable, string func, string[] args); int scheduleJob(int whenIs, int when, string executable, string func, string[] args);
/// ///
void cancelJob(int jobId); void cancelJob(int jobId);
@ -5282,6 +5281,16 @@ class ScheduledJobServerConnection : ScheduledJobServer {
mixin ImplementRpcClientInterface!(ScheduledJobServer, "/tmp/arsd_scheduled_job_server"); 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 { interface EventSourceServer {
/++ /++
@ -6044,6 +6053,18 @@ static auto elementFor(T)(string displayName, string name) {
else else
i.attrs.type = "number"; i.attrs.type = "number";
i.attrs.value = to!string(T.init); 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)) { } else static if(is(T == K[], K)) {
auto templ = div.addChild("template"); auto templ = div.addChild("template");
templ.appendChild(elementFor!(K)(null, name)); templ.appendChild(elementFor!(K)(null, name));
@ -6370,6 +6391,8 @@ auto formatReturnValueAsHtml(T)(T t) {
} }
return dl; 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)) { } else static if(is(T == E[], E)) {
static if(is(E : RestObject!Proxy, Proxy)) { static if(is(E : RestObject!Proxy, Proxy)) {
// treat RestObject similar to struct // treat RestObject similar to struct
@ -6439,18 +6462,6 @@ auto formatReturnValueAsHtml(T)(T t) {
FIXME FIXME
+/ +/
class WebPresenter() { 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() { string script() {
return ` 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]. 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.dom;
import arsd.jsvar; 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(); auto obj = new T();
obj.initialize(cgi); obj.initialize(cgi, presenter);
switch(cgi.pathInfo[urlPrefix.length .. $]) { switch(cgi.pathInfo[urlPrefix.length .. $]) {
static foreach(methodName; __traits(derivedMembers, T)){{ static foreach(methodName; __traits(derivedMembers, T)){{
@ -6594,7 +6618,7 @@ auto serveApi(T)(string urlPrefix) {
case urlify(methodName): case urlify(methodName):
switch(cgi.request("format", "html")) { switch(cgi.request("format", "html")) {
case "html": case "html":
auto container = obj.htmlContainer(); auto container = obj.presenter.htmlContainer();
try { try {
auto ret = callFromCgi!(__traits(getMember, obj, methodName))(&__traits(getMember, obj, methodName), cgi); auto ret = callFromCgi!(__traits(getMember, obj, methodName))(&__traits(getMember, obj, methodName), cgi);
container.appendChild(formatReturnValueAsHtml(ret)); container.appendChild(formatReturnValueAsHtml(ret));
@ -6623,12 +6647,12 @@ auto serveApi(T)(string urlPrefix) {
case "script.js": case "script.js":
cgi.setResponseContentType("text/javascript"); cgi.setResponseContentType("text/javascript");
cgi.gzipResponse = true; cgi.gzipResponse = true;
cgi.write(obj.script(), true); cgi.write(obj.presenter.script(), true);
return true; return true;
case "style.css": case "style.css":
cgi.setResponseContentType("text/css"); cgi.setResponseContentType("text/css");
cgi.gzipResponse = true; cgi.gzipResponse = true;
cgi.write(obj.style(), true); cgi.write(obj.presenter.style(), true);
return true; return true;
default: default:
return false; 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. Base class for REST collections.
+/ +/
@ -6934,7 +6961,7 @@ class CollectionOf(Obj, Helper = void) : RestObject!(Helper) {
+/ +/
auto serveRestObject(T)(string urlPrefix) { auto serveRestObject(T)(string urlPrefix) {
assert(urlPrefix[$ - 1] != '/', "Do NOT use a trailing slash on REST objects."); 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 .. $]; string url = cgi.pathInfo[urlPrefix.length .. $];
if(url.length && url[$ - 1] == '/') { if(url.length && url[$ - 1] == '/') {
@ -6943,7 +6970,7 @@ auto serveRestObject(T)(string urlPrefix) {
return true; return true;
} }
return restObjectServeHandler!T(cgi, url); return restObjectServeHandler!T(cgi, presenter, url);
} }
return DispatcherDefinition!handler(urlPrefix, false); return DispatcherDefinition!handler(urlPrefix, false);
@ -6957,7 +6984,7 @@ auto serveRestCollectionOf(T)(string urlPrefix) {
return serveRestObject!(mixin(T.stringof ~ "s"))(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; string urlId = null;
if(url.length && url[0] == '/') { if(url.length && url[0] == '/') {
// asking for a subobject // asking for a subobject
@ -6974,7 +7001,7 @@ bool restObjectServeHandler(T)(Cgi cgi, string url) {
static if(is(T : CollectionOf!(C, P), C, P)) { static if(is(T : CollectionOf!(C, P), C, P)) {
if(urlId !is null) { 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 // FIXME
return ValidationResult.valid; return ValidationResult.valid;
}; };
obj.initialize(cgi); obj.initialize(cgi, presenter);
// FIXME: populate reflection info delegates // FIXME: populate reflection info delegates
@ -7016,12 +7043,12 @@ bool restObjectServeHandler(T)(Cgi cgi, string url) {
case "script.js": case "script.js":
cgi.setResponseContentType("text/javascript"); cgi.setResponseContentType("text/javascript");
cgi.gzipResponse = true; cgi.gzipResponse = true;
cgi.write(obj.script(), true); cgi.write(obj.presenter.script(), true);
return true; return true;
case "style.css": case "style.css":
cgi.setResponseContentType("text/css"); cgi.setResponseContentType("text/css");
cgi.gzipResponse = true; cgi.gzipResponse = true;
cgi.write(obj.style(), true); cgi.write(obj.presenter.style(), true);
return true; return true;
default: default:
// intentionally blank // intentionally blank
@ -7047,7 +7074,7 @@ bool restObjectServeHandler(T)(Cgi cgi, string url) {
cgi.setResponseContentType("application/json"); cgi.setResponseContentType("application/json");
cgi.write(obj.toJson().toString, true); cgi.write(obj.toJson().toString, true);
} else { } else {
auto container = obj.htmlContainer(); auto container = obj.presenter.htmlContainer();
if(addFormLinks) { if(addFormLinks) {
static if(is(T : CollectionOf!(C, P), C, P)) static if(is(T : CollectionOf!(C, P), C, P))
container.appendHtml(` container.appendHtml(`
@ -7083,7 +7110,7 @@ bool restObjectServeHandler(T)(Cgi cgi, string url) {
static if(is(T : CollectionOf!(C, P), C, P)) { static if(is(T : CollectionOf!(C, P), C, P)) {
auto results = obj.index(); auto results = obj.index();
if(cgi.request("format", "html") == "html") { if(cgi.request("format", "html") == "html") {
auto container = obj.htmlContainer(); auto container = obj.presenter.htmlContainer();
auto html = formatReturnValueAsHtml(results.results); auto html = formatReturnValueAsHtml(results.results);
container.appendHtml(` container.appendHtml(`
<form> <form>
@ -7119,7 +7146,7 @@ bool restObjectServeHandler(T)(Cgi cgi, string url) {
case "PUT": case "PUT":
case "POST": case "POST":
// an editing form for the object // an editing form for the object
auto container = obj.htmlContainer(); auto container = obj.presenter.htmlContainer();
static if(__traits(compiles, () { auto o = new obj.PostProxy(); })) { static if(__traits(compiles, () { auto o = new obj.PostProxy(); })) {
auto form = (cgi.request("_method") == "POST") ? createAutomaticFormForObject(new obj.PostProxy()) : createAutomaticFormForObject(obj); auto form = (cgi.request("_method") == "POST") ? createAutomaticFormForObject(new obj.PostProxy()) : createAutomaticFormForObject(obj);
} else { } else {
@ -7132,7 +7159,7 @@ bool restObjectServeHandler(T)(Cgi cgi, string url) {
break; break;
case "DELETE": case "DELETE":
// FIXME: a delete form for the object (can be phrased "are you sure?") // FIXME: a delete form for the object (can be phrased "are you sure?")
auto container = obj.htmlContainer(); auto container = obj.presenter.htmlContainer();
container.appendHtml(` container.appendHtml(`
<form method="POST"> <form method="POST">
Are you sure you want to delete this item? 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"; 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) if(details.contentType.indexOf("image/") == 0)
cgi.setCache(true); cgi.setCache(true);
cgi.setResponseContentType(details.contentType); cgi.setResponseContentType(details.contentType);
@ -7271,17 +7298,19 @@ auto serveStaticData(string urlPrefix, const(void)[] data, string contentType) {
)) return; )) 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. // I can prolly make this more efficient later but meh.
static foreach(definition; definitions) { static foreach(definition; definitions) {
if(definition.rejectFurther) { if(definition.rejectFurther) {
if(cgi.pathInfo == definition.urlPrefix) { 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) if(ret)
return true; return true;
} }
} else if(cgi.pathInfo.startsWith(definition.urlPrefix)) { } 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) if(ret)
return true; return true;
} }

3
dom.d
View File

@ -3,6 +3,9 @@
// FIXME: https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML // 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. // 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 This is an html DOM implementation, started with cloning
what the browser offers in Javascript, but going well beyond what the browser offers in Javascript, but going well beyond

31
email.d
View File

@ -26,6 +26,13 @@ struct MimeAttachment {
string id; /// string id; ///
} }
///
enum ToType {
to,
cc,
bcc
}
/++ /++
For OUTGOING email For OUTGOING email
@ -65,12 +72,32 @@ class EmailMessage {
private bool isMime = false; private bool isMime = false;
private bool isHtml = 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) { void setTextBody(string text) {
textBody = text; textBody = text.strip;
} }
/// automatically sets a text fallback if you haven't already /// automatically sets a text fallback if you haven't already
void setHtmlBody(string html) { void setHtmlBody()(string html) {
isMime = true; isMime = true;
isHtml = true; isHtml = true;
htmlBody = html; htmlBody = html;

View File

@ -1672,7 +1672,7 @@ class HttpApiClient() {
/// ///
var throwOnError(HttpResponse res) { var throwOnError(HttpResponse res) {
if(res.code < 200 || res.code >= 300) 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); var response = var.fromJson(res.contentText);
if(response.errors) { if(response.errors) {

View File

@ -8,6 +8,13 @@
// https://docs.microsoft.com/en-us/windows/desktop/Controls/about-custom-draw // 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: 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?! // FIXME: omg omg what if menu functions have arguments and it can pop up a gui or command line script them?!

View File

@ -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 Creating drawing context
======================== ========================

View File

@ -1976,6 +1976,8 @@ Expression parsePart(MyTokenStreamHere)(ref MyTokenStreamHere tokens) {
} }
} }
// FIXME: unary ! doesn't work right
funcLoop: while(!tokens.empty) { funcLoop: while(!tokens.empty) {
auto peek = tokens.front; auto peek = tokens.front;
if(peek.type == ScriptToken.Type.symbol) { if(peek.type == ScriptToken.Type.symbol) {

7
web.d
View File

@ -2780,6 +2780,11 @@ string formatAs(T, R)(T ret, string format, R api = null, JSONValue* returnValue
} else { } else {
format = "html"; format = "html";
} }
static if(is(typeof(ret) : K[], K)) {
static if(is(K == struct))
format = "table";
}
} }
string retstr; string retstr;
@ -3999,10 +4004,12 @@ Table structToTable(T)(Document document, T arr, string[] fieldsToSkip = null) i
{ {
auto thead = t.addChild("thead"); auto thead = t.addChild("thead");
auto tr = thead.addChild("tr"); auto tr = thead.addChild("tr");
if(arr.length) {
auto s = arr[0]; auto s = arr[0];
foreach(idx, member; s.tupleof) foreach(idx, member; s.tupleof)
tr.addChild("th", s.tupleof[idx].stringof[2..$]); tr.addChild("th", s.tupleof[idx].stringof[2..$]);
} }
}
bool odd = true; bool odd = true;
auto tbody = t.addChild("tbody"); auto tbody = t.addChild("tbody");