Improved HTML DSL, experimental dark mode
This commit is contained in:
parent
1dd32a2de8
commit
3d7dcc08db
|
@ -47,7 +47,9 @@ class Grid {
|
|||
arrowClasses[x to y] = "arrow arrow-$dir-middle"
|
||||
}
|
||||
arrowClasses[x to y2] = "arrow arrow-$dir-end"
|
||||
arrowCells[x to yEnd] = div().addClass("arrow-head")
|
||||
arrowCells[x to yEnd] = htmlFragment {
|
||||
div.addClass("arrow-head")
|
||||
}
|
||||
}
|
||||
|
||||
private fun nextArrowX(y1: Int, y2: Int): Int {
|
||||
|
@ -79,10 +81,14 @@ class Grid {
|
|||
= ins.print(gameData)
|
||||
|
||||
add(y, ins.address,
|
||||
text(address ?: ""),
|
||||
text(bytes),
|
||||
htmlFragment {
|
||||
text(address ?: "")
|
||||
},
|
||||
htmlFragment {
|
||||
text(bytes)
|
||||
},
|
||||
editableField(game, indicativeAddress, "label", label),
|
||||
fragment {
|
||||
htmlFragment {
|
||||
if (secondaryMnemonic == null) {
|
||||
text(primaryMnemonic)
|
||||
} else {
|
||||
|
@ -113,7 +119,9 @@ class Grid {
|
|||
}.attr("href", url)
|
||||
}
|
||||
},
|
||||
text(state ?: ""),
|
||||
htmlFragment {
|
||||
text(state ?: "")
|
||||
},
|
||||
editableField(game, indicativeAddress, "comment", comment)
|
||||
)
|
||||
|
||||
|
@ -125,13 +133,16 @@ class Grid {
|
|||
}
|
||||
|
||||
private fun editableField(game: Game, address: SnesAddress, type: String, value: String?): HtmlNode {
|
||||
return input(value = value ?: "")
|
||||
return htmlFragment {
|
||||
input.attr("value", value ?: "")
|
||||
.attr("type", "text")
|
||||
.addClass("field-$type")
|
||||
.addClass("field-editable")
|
||||
.attr("data-field", type)
|
||||
.attr("data-game", game.id)
|
||||
.attr("data-address", address.toSimpleString())
|
||||
}
|
||||
}
|
||||
|
||||
private fun HtmlArea.editablePopupField(game: Game, address: SnesAddress, type: String, displayValue: String?, editValue: String?) {
|
||||
span {
|
||||
|
@ -148,7 +159,17 @@ class Grid {
|
|||
|
||||
private fun addDummy() {
|
||||
val y = (height++)
|
||||
add(y, null, null, null, null, text("..."), null, null)
|
||||
add(y,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
htmlFragment {
|
||||
text("...")
|
||||
},
|
||||
null,
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
private fun add(y: Int, address: SnesAddress?,
|
||||
|
@ -176,7 +197,8 @@ class Grid {
|
|||
.max()
|
||||
?: -1
|
||||
|
||||
return table {
|
||||
return htmlFragment {
|
||||
table {
|
||||
for (y in 0 until height) {
|
||||
tr {
|
||||
for (x in 0..3) {
|
||||
|
@ -204,3 +226,4 @@ class Grid {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
package com.smallhacker.disbrowser
|
||||
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
typealias InnerHtml = HtmlArea.() -> Unit
|
||||
|
||||
interface HtmlNode {
|
||||
fun print(): String {
|
||||
val out = StringBuilder()
|
||||
|
@ -40,13 +44,9 @@ open class HtmlElement(protected val tag: String) : HtmlNode {
|
|||
}
|
||||
}
|
||||
|
||||
private class ParentHtmlElement(tag: String, inner: InnerHtml) : HtmlElement(tag) {
|
||||
private class ParentHtmlElement(tag: String) : HtmlElement(tag) {
|
||||
private val children = ArrayList<HtmlNode>()
|
||||
|
||||
init {
|
||||
inner(HtmlArea(this))
|
||||
}
|
||||
|
||||
override fun printTo(out: StringBuilder): StringBuilder {
|
||||
super.printTo(out)
|
||||
children.forEach { it.printTo(out) }
|
||||
|
@ -57,22 +57,22 @@ private class ParentHtmlElement(tag: String, inner: InnerHtml) : HtmlElement(tag
|
|||
override fun append(node: HtmlNode) = apply { children.add(node) }
|
||||
}
|
||||
|
||||
private fun parent(tag: String, inner: InnerHtml): HtmlNode = ParentHtmlElement(tag, inner)
|
||||
private fun leaf(tag: String): HtmlNode = HtmlElement(tag)
|
||||
|
||||
typealias InnerHtml = HtmlArea.() -> Unit
|
||||
|
||||
class HtmlArea(val parent: HtmlNode)
|
||||
|
||||
fun text(text: String): HtmlNode = object : HtmlNode {
|
||||
private class HtmlTextNode(private val text: String): HtmlNode {
|
||||
override fun printTo(out: StringBuilder) = out.append(text)
|
||||
override fun attr(key: String, value: String?) = this
|
||||
override fun append(node: HtmlNode): HtmlNode = this
|
||||
}
|
||||
|
||||
fun HtmlArea.text(text: String) = com.smallhacker.disbrowser.text(text).appendTo(parent)
|
||||
private object ParentBuilder {
|
||||
operator fun getValue(a: HtmlArea, b: KProperty<*>) = ParentHtmlElement(b.name).appendTo(a.parent)
|
||||
}
|
||||
private object LeafBuilder {
|
||||
operator fun getValue(a: HtmlArea, b: KProperty<*>) = HtmlElement(b.name).appendTo(a.parent)
|
||||
}
|
||||
|
||||
fun fragment(inner: InnerHtml = {}) = object : HtmlNode {
|
||||
fun htmlFragment(inner: InnerHtml = {}) = object : HtmlNode {
|
||||
private val children = ArrayList<HtmlNode>()
|
||||
|
||||
init {
|
||||
|
@ -82,48 +82,45 @@ fun fragment(inner: InnerHtml = {}) = object : HtmlNode {
|
|||
override fun printTo(out: StringBuilder) = out.apply { children.forEach { it.printTo(out) } }
|
||||
|
||||
override fun append(node: HtmlNode) = apply { children.add(node) }
|
||||
}
|
||||
|
||||
fun HtmlArea.fragment(inner: InnerHtml = {}) = com.smallhacker.disbrowser.fragment(inner).appendTo(parent)
|
||||
fun html(inner: InnerHtml = {}) = parent("html", inner)
|
||||
fun HtmlArea.html(inner: InnerHtml = {}) = com.smallhacker.disbrowser.html(inner).appendTo(parent)
|
||||
fun head(inner: InnerHtml = {}) = parent("head", inner)
|
||||
fun HtmlArea.head(inner: InnerHtml = {}) = com.smallhacker.disbrowser.head(inner).appendTo(parent)
|
||||
fun title(inner: InnerHtml = {}) = parent("title", inner)
|
||||
fun HtmlArea.title(inner: InnerHtml = {}) = com.smallhacker.disbrowser.title(inner).appendTo(parent)
|
||||
fun link(inner: InnerHtml = {}) = leaf("link")
|
||||
fun HtmlArea.link(inner: InnerHtml = {}) = com.smallhacker.disbrowser.link(inner).appendTo(parent)
|
||||
fun meta(inner: InnerHtml = {}) = leaf("meta")
|
||||
fun HtmlArea.meta(inner: InnerHtml = {}) = com.smallhacker.disbrowser.meta(inner).appendTo(parent)
|
||||
fun body(inner: InnerHtml = {}) = parent("body", inner)
|
||||
fun HtmlArea.body(inner: InnerHtml = {}) = com.smallhacker.disbrowser.body(inner).appendTo(parent)
|
||||
fun div(inner: InnerHtml = {}) = parent("div", inner)
|
||||
fun HtmlArea.div(inner: InnerHtml = {}) = com.smallhacker.disbrowser.div(inner).appendTo(parent)
|
||||
fun span(inner: InnerHtml = {}) = parent("span", inner)
|
||||
fun HtmlArea.span(inner: InnerHtml = {}) = com.smallhacker.disbrowser.span(inner).appendTo(parent)
|
||||
fun table(inner: InnerHtml = {}) = parent("table", inner)
|
||||
fun HtmlArea.table(inner: InnerHtml = {}) = com.smallhacker.disbrowser.table(inner).appendTo(parent)
|
||||
fun tr(inner: InnerHtml = {}) = parent("tr", inner)
|
||||
fun HtmlArea.tr(inner: InnerHtml = {}) = com.smallhacker.disbrowser.tr(inner).appendTo(parent)
|
||||
fun td(inner: InnerHtml = {}) = parent("td", inner)
|
||||
fun HtmlArea.td(inner: InnerHtml = {}) = com.smallhacker.disbrowser.td(inner).appendTo(parent)
|
||||
fun a(inner: InnerHtml = {}) = parent("a", inner)
|
||||
fun HtmlArea.a(inner: InnerHtml = {}) = com.smallhacker.disbrowser.a(inner).appendTo(parent)
|
||||
fun script(inner: InnerHtml = {}) = parent("script", inner)
|
||||
fun HtmlArea.script(inner: InnerHtml = {}) = com.smallhacker.disbrowser.script(inner).appendTo(parent)
|
||||
fun input(type: String = "text", value: String = "") = leaf("input").attr("type", type).attr("value", value)
|
||||
fun HtmlArea.input(type: String = "text", value: String = "") = com.smallhacker.disbrowser.input(type, value).appendTo(parent)
|
||||
override fun toString(): String = print()
|
||||
}
|
||||
fun HtmlArea.text(text: String) = HtmlTextNode(text).appendTo(parent)
|
||||
val HtmlArea.fragment get() = htmlFragment().appendTo(parent)
|
||||
val HtmlArea.html by ParentBuilder
|
||||
val HtmlArea.head by ParentBuilder
|
||||
val HtmlArea.title by ParentBuilder
|
||||
val HtmlArea.link by LeafBuilder
|
||||
val HtmlArea.meta by LeafBuilder
|
||||
val HtmlArea.body by ParentBuilder
|
||||
val HtmlArea.main by ParentBuilder
|
||||
val HtmlArea.aside by ParentBuilder
|
||||
val HtmlArea.div by ParentBuilder
|
||||
val HtmlArea.span by ParentBuilder
|
||||
val HtmlArea.table by ParentBuilder
|
||||
val HtmlArea.tr by ParentBuilder
|
||||
val HtmlArea.td by ParentBuilder
|
||||
val HtmlArea.a by ParentBuilder
|
||||
val HtmlArea.script by ParentBuilder
|
||||
val HtmlArea.input by LeafBuilder
|
||||
val HtmlArea.button by ParentBuilder
|
||||
|
||||
fun HtmlNode.appendTo(node: HtmlNode) = apply { node.append(this) }
|
||||
fun HtmlNode.addClass(c: String?) = attrAdd("class", c)
|
||||
val HtmlNode.inner get() = this
|
||||
operator fun HtmlNode.invoke(inner: InnerHtml): HtmlNode = append(htmlFragment(inner))
|
||||
|
||||
fun HtmlNode.attrSet(name: String): MutableSet<String> = (attr(name) ?: "")
|
||||
fun HtmlNode.attr(key: String, value: String?, inner: InnerHtml) = attr(key, value).inner(inner)
|
||||
fun HtmlNode.addClass(c: String?, inner: InnerHtml) = addClass(c).inner(inner)
|
||||
fun HtmlNode.append(node: HtmlNode, inner: InnerHtml) = append(node).inner(inner)
|
||||
|
||||
private fun HtmlNode.attrSet(name: String): MutableSet<String> = (attr(name) ?: "")
|
||||
.split(" ")
|
||||
.asSequence()
|
||||
.filterNot { it.isEmpty() }
|
||||
.toMutableSet()
|
||||
|
||||
fun HtmlNode.attrAdd(name: String, value: String?) = apply {
|
||||
private fun HtmlNode.attrAdd(name: String, value: String?) = apply {
|
||||
if (value != null) {
|
||||
val set = attrSet(name)
|
||||
set.add(value)
|
||||
|
|
|
@ -25,6 +25,7 @@ class DisassemblyResource {
|
|||
@Produces(MediaType.TEXT_HTML)
|
||||
fun getIt(@PathParam("game") gameName: String) = handle {
|
||||
games.getGame(gameName)?.let { game ->
|
||||
htmlFragment {
|
||||
table {
|
||||
Service.getVectors(game).forEach {
|
||||
tr {
|
||||
|
@ -41,6 +42,7 @@ class DisassemblyResource {
|
|||
}.addClass("vector-table")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("{address}")
|
||||
|
@ -73,15 +75,27 @@ class DisassemblyResource {
|
|||
val output = runner()
|
||||
?: return Response.status(404).build()
|
||||
|
||||
val html = html {
|
||||
val html =
|
||||
htmlFragment {
|
||||
html {
|
||||
head {
|
||||
title { text("Disassembly Browser") }
|
||||
link {}.attr("rel", "stylesheet").attr("href", "/resources/style.css")
|
||||
meta {}.attr("charset", "UTF-8")
|
||||
link.attr("rel", "stylesheet").attr("href", "/resources/style.css")
|
||||
meta.attr("charset", "UTF-8")
|
||||
}
|
||||
body {
|
||||
main {
|
||||
output.appendTo(parent)
|
||||
script().attr("src", "/resources/disbrowser.js")
|
||||
}
|
||||
|
||||
aside.addClass("sidebar") {
|
||||
button.attr("id", "btn-dark-mode") {
|
||||
text("Dark Mode")
|
||||
}
|
||||
}
|
||||
|
||||
script.attr("src", "/resources/disbrowser.js")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -125,6 +125,7 @@ tr.line-active {
|
|||
.field-editable-popup-icon:hover {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.field-editable-popup-icon::before {
|
||||
content: "[e]"
|
||||
}
|
||||
|
@ -178,3 +179,43 @@ tr.line-active {
|
|||
[row-certainty="95"], [row-certainty="96"], [row-certainty="97"], [row-certainty="98"], [row-certainty="99"] {
|
||||
color: rgb(20, 20, 20);
|
||||
}
|
||||
|
||||
body.dark-mode {
|
||||
filter: invert(0.9) hue-rotate(180deg);
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
main {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 50px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
padding: 8px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
position: absolute;
|
||||
right: -200px;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 250px;
|
||||
z-index: 100;
|
||||
background-color: lightgray;
|
||||
border-left: 3px double black;
|
||||
padding: 10px 10px 10px 50px;
|
||||
box-sizing: border-box;
|
||||
transition: right 0.1s;
|
||||
box-shadow: -1px 0 13px rgba(0,0,0,0.4);
|
||||
}
|
||||
|
||||
.sidebar:hover {
|
||||
right: 0;
|
||||
}
|
|
@ -108,3 +108,56 @@ for (let i = 0; i < popupEditables.length; i++) {
|
|||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
class PersistentProperty<T> {
|
||||
private _value: T;
|
||||
private readonly key: string;
|
||||
private readonly onChange: (value: T) => void;
|
||||
|
||||
constructor(key: string, defaultValue: T, onChange: (value: T) => void) {
|
||||
this.key = key;
|
||||
this.onChange = onChange;
|
||||
this.value = this.parse(key, defaultValue);
|
||||
}
|
||||
|
||||
get value(): T {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
set value(value: T) {
|
||||
this._value = value;
|
||||
localStorage.setItem(this.key, JSON.stringify(value));
|
||||
this.onChange(value);
|
||||
}
|
||||
|
||||
private parse(key: string, defaultValue: T): T {
|
||||
let value = localStorage.getItem(key);
|
||||
if (value === null) {
|
||||
return defaultValue;
|
||||
}
|
||||
let parsedValue = JSON.parse(value);
|
||||
if (parsedValue === null) {
|
||||
return defaultValue;
|
||||
}
|
||||
return parsedValue;
|
||||
}
|
||||
}
|
||||
|
||||
let darkMode = new PersistentProperty<boolean>(
|
||||
"ui.presentation.darkMode",
|
||||
false,
|
||||
(enable) => {
|
||||
if (enable) {
|
||||
document.body.classList.add("dark-mode")
|
||||
} else {
|
||||
document.body.classList.remove("dark-mode")
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
let btnDarkMode = document.getElementById("btn-dark-mode");
|
||||
if (btnDarkMode) {
|
||||
btnDarkMode.addEventListener("click", () => {
|
||||
darkMode.value = !darkMode.value;
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue