Refactor pages, Add Space + HClampLayout

This commit is contained in:
MrLetsplay 2025-01-18 22:50:11 +01:00
parent c6eec9364f
commit a68cc635a8
Signed by: mr
SSH Key Fingerprint: SHA256:92jBH80vpXyaZHjaIl47pjRq+Yt7XGTArqQg1V7hSqg
15 changed files with 266 additions and 178 deletions

View File

@ -7,15 +7,6 @@ import org.slf4j.helpers.NOPLogger;
import me.mrletsplay.nojs._test.DynPage; import me.mrletsplay.nojs._test.DynPage;
import me.mrletsplay.nojs._test.LinksPage; import me.mrletsplay.nojs._test.LinksPage;
import me.mrletsplay.nojs._test.TestPage;
import me.mrletsplay.nojs.action.Action;
import me.mrletsplay.nojs.action.ActionData;
import me.mrletsplay.nojs.component.impl.Button;
import me.mrletsplay.nojs.component.impl.Button.Style;
import me.mrletsplay.nojs.component.impl.Form;
import me.mrletsplay.nojs.component.impl.Link;
import me.mrletsplay.nojs.component.impl.TextInput;
import me.mrletsplay.nojs.page.StaticPage;
import me.mrletsplay.simplehttpserver.dom.html.HtmlDocument; import me.mrletsplay.simplehttpserver.dom.html.HtmlDocument;
import me.mrletsplay.simplehttpserver.dom.html.HtmlElement; import me.mrletsplay.simplehttpserver.dom.html.HtmlElement;
import me.mrletsplay.simplehttpserver.dom.html.element.HtmlButton; import me.mrletsplay.simplehttpserver.dom.html.element.HtmlButton;
@ -67,19 +58,6 @@ public class NoJS {
// new TestEndpoint().register(httpServer.getDocumentProvider()); // new TestEndpoint().register(httpServer.getDocumentProvider());
StaticPage test = new StaticPage()
.add(new Button().text("Click me!").action(new Action("poopy").extraData(new ActionData().put("test", "abc"))).style(Style.LINK))
.add(new Link().text("Click me!").href("#"))
.add(new Form()
.add(new TextInput("username").placeholder("Username"))
.add(new TextInput("password").placeholder("Password"))
.add(new Button().text("Login"))
.add(new Button().text("Can't click here").enabled(false))
.action(new Action("beans"))
);
test.register(httpServer.getDocumentProvider(), "/test");
new TestPage().register(httpServer.getDocumentProvider(), "/test2");
new DynPage().register(httpServer.getDocumentProvider(), "/dyn"); new DynPage().register(httpServer.getDocumentProvider(), "/dyn");
new LinksPage().register(httpServer.getDocumentProvider(), "/links"); new LinksPage().register(httpServer.getDocumentProvider(), "/links");
@ -94,6 +72,7 @@ public class NoJS {
HttpRequestContext ctx = HttpRequestContext.getCurrentContext(); HttpRequestContext ctx = HttpRequestContext.getCurrentContext();
ctx.getServerHeader().setContent(MimeType.CSS, in.readAllBytes()); ctx.getServerHeader().setContent(MimeType.CSS, in.readAllBytes());
} catch(IOException ex) { } catch(IOException ex) {
// TODO: error handling
throw new RuntimeException(ex); throw new RuntimeException(ex);
} }
}); });

View File

@ -1,7 +1,6 @@
package me.mrletsplay.nojs._test; package me.mrletsplay.nojs._test;
import java.util.Date; import java.util.Date;
import java.util.List;
import me.mrletsplay.nojs.action.Action; import me.mrletsplay.nojs.action.Action;
import me.mrletsplay.nojs.component.Component; import me.mrletsplay.nojs.component.Component;
@ -12,26 +11,20 @@ import me.mrletsplay.nojs.component.impl.Text;
import me.mrletsplay.nojs.page.Page; import me.mrletsplay.nojs.page.Page;
import me.mrletsplay.nojs.page.layout.GridLayout; import me.mrletsplay.nojs.page.layout.GridLayout;
import me.mrletsplay.nojs.page.layout.InsetLayout; import me.mrletsplay.nojs.page.layout.InsetLayout;
import me.mrletsplay.nojs.page.layout.Layout;
public class DynPage implements Page { public class DynPage implements Page {
@Override @Override
public List<Component> getComponents() { public Component getContent() {
return List.of( return new Group().add(
new Text().text(new Date().toString()), new Text().text(new Date().toString()),
new Form() new Form()
.action(Action.empty()) .action(Action.empty())
.layout(new InsetLayout(GridLayout.ofColumns(1), "100px")) .layout(new InsetLayout(GridLayout.ofColumns(1), "10px"))
.add(new Button().text("Refresh")), .add(new Button().text("Refresh")),
new Group() new Group()
.add(new Text().text("Your name is")) .add(new Text().text("Your name is"))
); ).layout(GridLayout.ofColumns(1));
}
@Override
public Layout getLayout() {
return GridLayout.ofColumns(1);
} }
} }

View File

@ -4,6 +4,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import me.mrletsplay.nojs.action.Action; import me.mrletsplay.nojs.action.Action;
import me.mrletsplay.nojs.action.ActionData;
import me.mrletsplay.nojs.action.ActionEvent; import me.mrletsplay.nojs.action.ActionEvent;
import me.mrletsplay.nojs.action.ActionHandler; import me.mrletsplay.nojs.action.ActionHandler;
import me.mrletsplay.nojs.component.Component; import me.mrletsplay.nojs.component.Component;
@ -16,7 +17,7 @@ import me.mrletsplay.nojs.component.impl.Text;
import me.mrletsplay.nojs.component.impl.TextInput; import me.mrletsplay.nojs.component.impl.TextInput;
import me.mrletsplay.nojs.page.Page; import me.mrletsplay.nojs.page.Page;
import me.mrletsplay.nojs.page.layout.GridLayout; import me.mrletsplay.nojs.page.layout.GridLayout;
import me.mrletsplay.nojs.page.layout.Layout; import me.mrletsplay.nojs.page.layout.HClampLayout;
public class LinksPage implements Page { public class LinksPage implements Page {
@ -24,13 +25,28 @@ public class LinksPage implements Page {
private String error; private String error;
@Override @Override
public List<Component> getComponents() { public Component getContent() {
Group linksGroup = new Group() Group linksGroup = new Group()
.layout(GridLayout.ofColumns(1) .layout(GridLayout.ofColumns(1)
.gap("2px")); .gap("4px"));
for(String s : links) { for(String s : links) {
linksGroup.add(new Link().href(s).text(s)); linksGroup.add(
new Form().add(
new Link()
.href(s)
.text(s)
.target(Link.Target.BLANK),
new Button()
.text("Delete")
)
.action(new Action("removeLink")
.extraData(new ActionData().put("link", s)))
.layout(new GridLayout()
.columns("1fr", "auto")
.gap("4px")
.vAlignment(GridLayout.VAlignment.START))
);
} }
if(links.isEmpty()) { if(links.isEmpty()) {
@ -38,34 +54,39 @@ public class LinksPage implements Page {
} }
if(error != null) { if(error != null) {
linksGroup.add(new Message().text(error)); linksGroup.add(new Message()
.text(error)
.type(Message.Type.ERROR));
error = null; error = null;
} }
return List.of( return new Group()
linksGroup, .add(
new Form() new Group().add(
.add(new Text().text("Add a new link:")) linksGroup,
.add(new Group() new Form().add(
.add(new TextInput("link").placeholder("Insert link here")) new Text().text("Add a new link:"),
.add(new Button().text("Add Link")) new Group().add(
.layout(new GridLayout().columns("1fr", "auto").gap("4px")) new TextInput("linkName").placeholder("Link Name"),
new TextInput("link").placeholder("Insert link here"),
new Button().text("Add Link")
)
.layout(GridLayout.ofColumns(1)
.gap("4px"))
)
.layout(GridLayout.ofColumns(1).gap("4px"))
.action(new Action("addLink"))
) )
.layout(GridLayout.ofColumns(1).gap("4px")) .layout(GridLayout.ofColumns(1).gap("10px"))
.action(new Action("addLink")) )
); .layout(new HClampLayout("200px", "100%", "800px")
} .hAlignment(HClampLayout.HAlignment.CENTER));
@Override
public Layout getLayout() {
return GridLayout.ofColumns(1)
.gap("10px");
} }
@ActionHandler("addLink") @ActionHandler("addLink")
public void addLink(ActionEvent event) { public void addLink(ActionEvent event) {
String link = event.getData("link"); String link = event.getData("link");
if(link == null || link.isBlank()) { if(link == null || link.isBlank() || (!link.startsWith("http://") && !link.startsWith("https://"))) {
error = "Invalid link"; error = "Invalid link";
return; return;
} }
@ -73,4 +94,10 @@ public class LinksPage implements Page {
links.add(event.getData("link")); links.add(event.getData("link"));
} }
@ActionHandler("removeLink")
public void removeLink(ActionEvent event) {
String link = event.getData("link");
links.remove(link);
}
} }

View File

@ -1,30 +0,0 @@
package me.mrletsplay.nojs._test;
import me.mrletsplay.nojs.action.Action;
import me.mrletsplay.nojs.action.ActionEvent;
import me.mrletsplay.nojs.action.ActionHandler;
import me.mrletsplay.nojs.component.impl.Button;
import me.mrletsplay.nojs.component.impl.Form;
import me.mrletsplay.nojs.component.impl.Link;
import me.mrletsplay.nojs.component.impl.TextInput;
import me.mrletsplay.nojs.page.StaticPage;
import me.mrletsplay.nojs.page.layout.GridLayout;
public class TestPage extends StaticPage {
public TestPage() {
add(new Form()
.add(new TextInput("beans"))
.add(new Button())
.add(new Link().href("/test").text("Link to other page"))
.action(new Action("submit"))
.layout(GridLayout.ofSize(2, 2))
);
}
@ActionHandler("submit")
public void onSubmit(ActionEvent event) {
System.out.println("Got event " + event.getAction());
}
}

View File

@ -4,7 +4,7 @@ import me.mrletsplay.nojs.page.layout.Layout;
public interface ContainerComponent extends Component { public interface ContainerComponent extends Component {
public ContainerComponent add(Component component); public ContainerComponent add(Component... components);
public ContainerComponent layout(Layout layout); public ContainerComponent layout(Layout layout);

View File

@ -21,8 +21,12 @@ public class Form implements ActionableComponent, ContainerComponent {
} }
@Override @Override
public Form add(Component component) { public Form add(Component... components) {
components.add(component); for(Component c : components) {
if(c == null) continue;
this.components.add(c);
}
return this; return this;
} }

View File

@ -18,8 +18,12 @@ public class Group implements ContainerComponent {
} }
@Override @Override
public Group add(Component component) { public Group add(Component... components) {
components.add(component); for(Component c : components) {
if(c == null) continue;
this.components.add(c);
}
return this; return this;
} }

View File

@ -7,10 +7,12 @@ public class Link implements Component {
private String text; private String text;
private String href; private String href;
private Target target;
public Link() { public Link() {
this.text = "Link"; this.text = "Link";
this.href = "#"; this.href = "#";
this.target = Target.SELF;
} }
public Link text(String text) { public Link text(String text) {
@ -23,13 +25,31 @@ public class Link implements Component {
return this; return this;
} }
public Link target(Target target) {
this.target = target != null ? target : Target.SELF;
return this;
}
@Override @Override
public HtmlElement toHtml() { public HtmlElement toHtml() {
HtmlElement link = HtmlElement.of("a"); HtmlElement link = HtmlElement.of("a");
link.addClass("link"); link.addClass("link");
if(target != Target.SELF) {
link.setAttribute("target", "_" + target.name().toLowerCase());
}
link.setText(text); link.setText(text);
link.setAttribute("href", href); link.setAttribute("href", href);
return link; return link;
} }
public static enum Target {
SELF,
BLANK,
;
}
} }

View File

@ -12,9 +12,11 @@ import me.mrletsplay.simplehttpserver.dom.html.element.HtmlRaw;
public class Message implements Component { public class Message implements Component {
private String text; private String text;
private Type type;
public Message() { public Message() {
this.text = "Message"; this.text = "Message";
this.type = Type.INFO;
} }
public Message text(String text) { public Message text(String text) {
@ -22,11 +24,18 @@ public class Message implements Component {
return this; return this;
} }
public Message type(Type type) {
this.type = type != null ? type : Type.INFO;
return this;
}
@Override @Override
public HtmlElement toHtml() { public HtmlElement toHtml() {
HtmlElement message = HtmlElement.of("div"); HtmlElement message = HtmlElement.of("div");
message.addClass("message"); message.addClass("message");
message.setAttribute("data-type", type.name().toLowerCase());
HtmlElement textSpan = HtmlElement.of("span"); HtmlElement textSpan = HtmlElement.of("span");
textSpan.setText(text); textSpan.setText(text);
message.appendChild(textSpan); message.appendChild(textSpan);
@ -46,4 +55,14 @@ public class Message implements Component {
return message; return message;
} }
public static enum Type {
INFO,
SUCCESS,
WARNING,
ERROR,
;
}
} }

View File

@ -0,0 +1,38 @@
package me.mrletsplay.nojs.component.impl;
import me.mrletsplay.nojs.component.Component;
import me.mrletsplay.simplehttpserver.dom.html.HtmlElement;
public class Space implements Component {
private String width;
private String height;
public Space width(String width) {
this.width = width;
return this;
}
public Space height(String height) {
this.height = height;
return this;
}
@Override
public HtmlElement toHtml() {
HtmlElement space = HtmlElement.of("span");
space.addClass("space");
if(width != null) {
space.appendAttribute("style", "min-width:" + width + ";");
}
if(height != null) {
space.appendAttribute("style", "min-height:" + height + ";");
}
return space;
}
}

View File

@ -1,11 +1,8 @@
package me.mrletsplay.nojs.page; package me.mrletsplay.nojs.page;
import java.util.List;
import me.mrletsplay.nojs.action.ActionEvent; import me.mrletsplay.nojs.action.ActionEvent;
import me.mrletsplay.nojs.action.ActionHandlers; import me.mrletsplay.nojs.action.ActionHandlers;
import me.mrletsplay.nojs.component.Component; import me.mrletsplay.nojs.component.Component;
import me.mrletsplay.nojs.page.layout.Layout;
import me.mrletsplay.simplehttpserver.dom.css.CssElement; import me.mrletsplay.simplehttpserver.dom.css.CssElement;
import me.mrletsplay.simplehttpserver.dom.css.CssSelector; import me.mrletsplay.simplehttpserver.dom.css.CssSelector;
import me.mrletsplay.simplehttpserver.dom.css.StyleSheet; import me.mrletsplay.simplehttpserver.dom.css.StyleSheet;
@ -22,11 +19,7 @@ import me.mrletsplay.simplehttpserver.http.response.HtmlResponse;
public interface Page extends HttpDocument, ActionHandlers { public interface Page extends HttpDocument, ActionHandlers {
public List<Component> getComponents(); public Component getContent();
public default Layout getLayout() {
return null;
}
public default HtmlDocument toHtml() { public default HtmlDocument toHtml() {
HtmlDocument doc = new HtmlDocument(); HtmlDocument doc = new HtmlDocument();
@ -40,14 +33,7 @@ public interface Page extends HttpDocument, ActionHandlers {
doc.getHeadNode().appendChild(HtmlElement.style(sheet)); doc.getHeadNode().appendChild(HtmlElement.style(sheet));
Layout layout = getLayout(); doc.getBodyNode().appendChild(getContent().toHtml());
if(layout != null) {
layout.apply(doc.getBodyNode());
}
for(Component c : getComponents()) {
doc.getBodyNode().appendChild(c.toHtml());
}
return doc; return doc;
} }
@ -59,7 +45,6 @@ public interface Page extends HttpDocument, ActionHandlers {
if(ctx.getClientHeader().getMethod() == HttpRequestMethod.POST) { if(ctx.getClientHeader().getMethod() == HttpRequestMethod.POST) {
// TODO: error handling // TODO: error handling
UrlEncoded data = ctx.getClientHeader().getPostData().getParsedAs(DefaultClientContentTypes.URLENCODED); UrlEncoded data = ctx.getClientHeader().getPostData().getParsedAs(DefaultClientContentTypes.URLENCODED);
System.out.println(data);
ActionEvent e = new ActionEvent(data); ActionEvent e = new ActionEvent(data);
onAction(e); onAction(e);

View File

@ -1,56 +0,0 @@
package me.mrletsplay.nojs.page;
import java.util.ArrayList;
import java.util.List;
import me.mrletsplay.nojs.action.ActionEvent;
import me.mrletsplay.nojs.action.ActionHandlers;
import me.mrletsplay.nojs.component.Component;
import me.mrletsplay.nojs.page.layout.Layout;
public class StaticPage implements Page {
private List<Component> components;
private Layout layout;
private List<ActionHandlers> actionHandlers;
public StaticPage() {
this.components = new ArrayList<>();
this.actionHandlers = new ArrayList<>();
}
public StaticPage add(Component component) {
components.add(component);
return this;
}
public StaticPage layout(Layout layout) {
this.layout = layout;
return this;
}
public StaticPage addActionHandler(ActionHandlers handler) {
actionHandlers.add(handler);
return this;
}
@Override
public List<Component> getComponents() {
return components;
}
@Override
public Layout getLayout() {
return layout;
}
@Override
public void onAction(ActionEvent event) {
Page.super.onAction(event);
for(ActionHandlers handler : actionHandlers) {
handler.onAction(event);
}
}
}

View File

@ -14,11 +14,15 @@ public class GridLayout implements Layout {
private List<String> rows; private List<String> rows;
private String gap; private String gap;
private Flow flow; private Flow flow;
private HAlignment hAlignment;
private VAlignment vAlignment;
public GridLayout() { public GridLayout() {
this.columns = new ArrayList<>(); this.columns = new ArrayList<>();
this.rows = new ArrayList<>(); this.rows = new ArrayList<>();
this.flow = Flow.ROW; this.flow = Flow.ROW;
this.hAlignment = HAlignment.NORMAL;
this.vAlignment = VAlignment.NORMAL;
} }
public GridLayout columns(Collection<String> columns) { public GridLayout columns(Collection<String> columns) {
@ -49,6 +53,16 @@ public class GridLayout implements Layout {
return this; return this;
} }
public GridLayout hAlignment(HAlignment hAlignment) {
this.hAlignment = hAlignment != null ? hAlignment : HAlignment.NORMAL;
return this;
}
public GridLayout vAlignment(VAlignment vAlignment) {
this.vAlignment = vAlignment != null ? vAlignment : VAlignment.NORMAL;
return this;
}
@Override @Override
public void apply(HtmlElement html) { public void apply(HtmlElement html) {
String css = "display:grid;"; String css = "display:grid;";
@ -69,7 +83,15 @@ public class GridLayout implements Layout {
css += "grid-auto-flow:" + flow.name().toLowerCase() + ";"; css += "grid-auto-flow:" + flow.name().toLowerCase() + ";";
} }
html.setAttribute("style", css); if(hAlignment != HAlignment.NORMAL) {
css += "justify-content:" + hAlignment.name().toLowerCase() + ";";
}
if(vAlignment != VAlignment.NORMAL) {
css += "align-items:" + vAlignment.name().toLowerCase() + ";";
}
html.appendAttribute("style", css);
} }
public static GridLayout ofColumns(int columns) { public static GridLayout ofColumns(int columns) {
@ -112,4 +134,27 @@ public class GridLayout implements Layout {
} }
public static enum HAlignment {
NORMAL,
START,
CENTER,
END,
STRETCH,
;
}
public static enum VAlignment {
NORMAL,
START,
CENTER,
END,
STRETCH,
;
}
} }

View File

@ -0,0 +1,50 @@
package me.mrletsplay.nojs.page.layout;
import me.mrletsplay.simplehttpserver.dom.html.HtmlElement;
public class HClampLayout implements Layout {
private String
min,
preferred,
max;
private HAlignment hAlignment;
public HClampLayout(String min, String preferred, String max) {
this.min = min == null ? "0" : min;
this.preferred = preferred == null ? "100%" : preferred;
this.max = max == null ? "100%" : max;
this.hAlignment = HAlignment.NORMAL;
}
public HClampLayout hAlignment(HAlignment hAlignment) {
this.hAlignment = hAlignment != null ? hAlignment : HAlignment.NORMAL;
return this;
}
@Override
public void apply(HtmlElement html) {
String css = "display:grid;";
css += "grid-template-columns:clamp(" + min + "," + preferred + "," + max + ");";
if(hAlignment != HAlignment.NORMAL) {
css += "justify-content:" + hAlignment.name().toLowerCase() + ";";
}
html.appendAttribute("style", css);
}
public static enum HAlignment {
NORMAL,
START,
CENTER,
END,
STRETCH,
;
}
}

View File

@ -4,6 +4,11 @@ html {
--accent: #3584e4; --accent: #3584e4;
--text: #fff; --text: #fff;
--link: var(--accent); --link: var(--accent);
--info: var(--accent);
--success: green;
--warning: darkorange;
--error: orangered;
} }
* { * {
@ -66,13 +71,28 @@ body {
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
position: fixed; position: fixed;
top: 0; bottom: 0;
left: 0; left: 0;
min-height: 30px; min-height: 30px;
background-color: orangered;
width: 100vw; width: 100vw;
} }
.message[data-type="info"] {
background-color: var(--info);
}
.message[data-type="success"] {
background-color: var(--success);
}
.message[data-type="warning"] {
background-color: var(--warning);
}
.message[data-type="error"] {
background-color: var(--error);
}
.message>label>input { .message>label>input {
display: none; display: none;
} }
@ -92,28 +112,18 @@ body {
.message>label { .message>label {
position: relative; position: relative;
/*background: var(--accent);*/
cursor: pointer; cursor: pointer;
font-weight: bold;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.message>label>svg { .message>label>svg {
fill: white; fill: var(--text);
width: 20px; width: 20px;
height: 20px; height: 20px;
} }
/*.message>label:after {
display: block;
text-align: center;
content: "x";
width: 100%;
height: 100%;
}*/
.message:has(:checked) { .message:has(:checked) {
display: none; display: none;
} }