mirror of
https://github.com/Smallhacker/disbrowser.git
synced 2025-04-09 07:39:20 +00:00
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,12 +133,15 @@ class Grid {
|
||||
}
|
||||
|
||||
private fun editableField(game: Game, address: SnesAddress, type: String, value: String?): HtmlNode {
|
||||
return input(value = value ?: "")
|
||||
.addClass("field-$type")
|
||||
.addClass("field-editable")
|
||||
.attr("data-field", type)
|
||||
.attr("data-game", game.id)
|
||||
.attr("data-address", address.toSimpleString())
|
||||
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?) {
|
||||
@ -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,30 +197,32 @@ class Grid {
|
||||
.max()
|
||||
?: -1
|
||||
|
||||
return table {
|
||||
for (y in 0 until height) {
|
||||
tr {
|
||||
for (x in 0..3) {
|
||||
val cssClass = cellClasses[x to y]
|
||||
td {
|
||||
content[x to y]?.appendTo(parent)
|
||||
}.addClass(cssClass)
|
||||
}
|
||||
for (x in 0 until arrowWidth) {
|
||||
val cssClass = arrowClasses[x to y]
|
||||
td {
|
||||
arrowCells[x to y]?.appendTo(parent)
|
||||
}.addClass(cssClass)
|
||||
}
|
||||
for (x in 4..contentMaxX) {
|
||||
val cssClass = cellClasses[x to y]
|
||||
td {
|
||||
content[x to y]?.appendTo(parent)
|
||||
}.addClass(cssClass)
|
||||
}
|
||||
}.addClass(rowClasses[y])
|
||||
.attr("id", rowId[y])
|
||||
.attr("row-certainty", rowCertainties[y])
|
||||
return htmlFragment {
|
||||
table {
|
||||
for (y in 0 until height) {
|
||||
tr {
|
||||
for (x in 0..3) {
|
||||
val cssClass = cellClasses[x to y]
|
||||
td {
|
||||
content[x to y]?.appendTo(parent)
|
||||
}.addClass(cssClass)
|
||||
}
|
||||
for (x in 0 until arrowWidth) {
|
||||
val cssClass = arrowClasses[x to y]
|
||||
td {
|
||||
arrowCells[x to y]?.appendTo(parent)
|
||||
}.addClass(cssClass)
|
||||
}
|
||||
for (x in 4..contentMaxX) {
|
||||
val cssClass = cellClasses[x to y]
|
||||
td {
|
||||
content[x to y]?.appendTo(parent)
|
||||
}.addClass(cssClass)
|
||||
}
|
||||
}.addClass(rowClasses[y])
|
||||
.attr("id", rowId[y])
|
||||
.attr("row-certainty", rowCertainties[y])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,20 +25,22 @@ class DisassemblyResource {
|
||||
@Produces(MediaType.TEXT_HTML)
|
||||
fun getIt(@PathParam("game") gameName: String) = handle {
|
||||
games.getGame(gameName)?.let { game ->
|
||||
table {
|
||||
Service.getVectors(game).forEach {
|
||||
tr {
|
||||
td { text(it.name) }
|
||||
td { text("(" + it.vectorLocation.toFormattedString() + ")") }
|
||||
td {
|
||||
a {
|
||||
text(it.label)
|
||||
}.attr("href", "/${game.id}/${it.codeLocation.toSimpleString()}/MX")
|
||||
htmlFragment {
|
||||
table {
|
||||
Service.getVectors(game).forEach {
|
||||
tr {
|
||||
td { text(it.name) }
|
||||
td { text("(" + it.vectorLocation.toFormattedString() + ")") }
|
||||
td {
|
||||
a {
|
||||
text(it.label)
|
||||
}.attr("href", "/${game.id}/${it.codeLocation.toSimpleString()}/MX")
|
||||
}
|
||||
td { text("(" + it.codeLocation.toFormattedString() + ")") }
|
||||
}
|
||||
td { text("(" + it.codeLocation.toFormattedString() + ")") }
|
||||
}
|
||||
}
|
||||
}.addClass("vector-table")
|
||||
}.addClass("vector-table")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -73,17 +75,29 @@ class DisassemblyResource {
|
||||
val output = runner()
|
||||
?: return Response.status(404).build()
|
||||
|
||||
val html = html {
|
||||
head {
|
||||
title { text("Disassembly Browser") }
|
||||
link {}.attr("rel", "stylesheet").attr("href", "/resources/style.css")
|
||||
meta {}.attr("charset", "UTF-8")
|
||||
}
|
||||
body {
|
||||
output.appendTo(parent)
|
||||
script().attr("src", "/resources/disbrowser.js")
|
||||
}
|
||||
}
|
||||
val html =
|
||||
htmlFragment {
|
||||
html {
|
||||
head {
|
||||
title { text("Disassembly Browser") }
|
||||
link.attr("rel", "stylesheet").attr("href", "/resources/style.css")
|
||||
meta.attr("charset", "UTF-8")
|
||||
}
|
||||
body {
|
||||
main {
|
||||
output.appendTo(parent)
|
||||
}
|
||||
|
||||
aside.addClass("sidebar") {
|
||||
button.attr("id", "btn-dark-mode") {
|
||||
text("Dark Mode")
|
||||
}
|
||||
}
|
||||
|
||||
script.attr("src", "/resources/disbrowser.js")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Response.ok(html.toString().toByteArray(StandardCharsets.UTF_8))
|
||||
.encoding("UTF-8")
|
||||
|
@ -105,7 +105,7 @@ tr.line-active {
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
.opcode-info{
|
||||
.opcode-info {
|
||||
text-decoration: green underline dotted;
|
||||
}
|
||||
|
||||
@ -125,56 +125,97 @@ tr.line-active {
|
||||
.field-editable-popup-icon:hover {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.field-editable-popup-icon::before {
|
||||
content: "[e]"
|
||||
}
|
||||
|
||||
[row-certainty="0"],[row-certainty="1"],[row-certainty="2"],[row-certainty="3"],[row-certainty="4"],
|
||||
[row-certainty="5"],[row-certainty="6"],[row-certainty="7"],[row-certainty="8"],[row-certainty="9"] {
|
||||
color: rgb(200,200,200);
|
||||
[row-certainty="0"], [row-certainty="1"], [row-certainty="2"], [row-certainty="3"], [row-certainty="4"],
|
||||
[row-certainty="5"], [row-certainty="6"], [row-certainty="7"], [row-certainty="8"], [row-certainty="9"] {
|
||||
color: rgb(200, 200, 200);
|
||||
}
|
||||
|
||||
[row-certainty="10"],[row-certainty="11"],[row-certainty="12"],[row-certainty="13"],[row-certainty="14"],
|
||||
[row-certainty="15"],[row-certainty="16"],[row-certainty="17"],[row-certainty="18"],[row-certainty="19"] {
|
||||
color: rgb(180,180,180);
|
||||
[row-certainty="10"], [row-certainty="11"], [row-certainty="12"], [row-certainty="13"], [row-certainty="14"],
|
||||
[row-certainty="15"], [row-certainty="16"], [row-certainty="17"], [row-certainty="18"], [row-certainty="19"] {
|
||||
color: rgb(180, 180, 180);
|
||||
}
|
||||
|
||||
[row-certainty="20"],[row-certainty="21"],[row-certainty="22"],[row-certainty="23"],[row-certainty="24"],
|
||||
[row-certainty="25"],[row-certainty="26"],[row-certainty="27"],[row-certainty="28"],[row-certainty="29"] {
|
||||
color: rgb(160,160,160);
|
||||
[row-certainty="20"], [row-certainty="21"], [row-certainty="22"], [row-certainty="23"], [row-certainty="24"],
|
||||
[row-certainty="25"], [row-certainty="26"], [row-certainty="27"], [row-certainty="28"], [row-certainty="29"] {
|
||||
color: rgb(160, 160, 160);
|
||||
}
|
||||
|
||||
[row-certainty="30"],[row-certainty="31"],[row-certainty="32"],[row-certainty="33"],[row-certainty="34"],
|
||||
[row-certainty="35"],[row-certainty="36"],[row-certainty="37"],[row-certainty="38"],[row-certainty="39"] {
|
||||
color: rgb(140,140,140);
|
||||
[row-certainty="30"], [row-certainty="31"], [row-certainty="32"], [row-certainty="33"], [row-certainty="34"],
|
||||
[row-certainty="35"], [row-certainty="36"], [row-certainty="37"], [row-certainty="38"], [row-certainty="39"] {
|
||||
color: rgb(140, 140, 140);
|
||||
}
|
||||
|
||||
[row-certainty="40"],[row-certainty="41"],[row-certainty="42"],[row-certainty="43"],[row-certainty="44"],
|
||||
[row-certainty="45"],[row-certainty="46"],[row-certainty="47"],[row-certainty="48"],[row-certainty="49"] {
|
||||
color: rgb(120,120,120);
|
||||
[row-certainty="40"], [row-certainty="41"], [row-certainty="42"], [row-certainty="43"], [row-certainty="44"],
|
||||
[row-certainty="45"], [row-certainty="46"], [row-certainty="47"], [row-certainty="48"], [row-certainty="49"] {
|
||||
color: rgb(120, 120, 120);
|
||||
}
|
||||
|
||||
[row-certainty="50"],[row-certainty="51"],[row-certainty="52"],[row-certainty="53"],[row-certainty="54"],
|
||||
[row-certainty="55"],[row-certainty="56"],[row-certainty="57"],[row-certainty="58"],[row-certainty="59"] {
|
||||
color: rgb(100,100,100);
|
||||
[row-certainty="50"], [row-certainty="51"], [row-certainty="52"], [row-certainty="53"], [row-certainty="54"],
|
||||
[row-certainty="55"], [row-certainty="56"], [row-certainty="57"], [row-certainty="58"], [row-certainty="59"] {
|
||||
color: rgb(100, 100, 100);
|
||||
}
|
||||
|
||||
[row-certainty="60"],[row-certainty="61"],[row-certainty="62"],[row-certainty="63"],[row-certainty="64"],
|
||||
[row-certainty="65"],[row-certainty="66"],[row-certainty="67"],[row-certainty="68"],[row-certainty="69"] {
|
||||
color: rgb(80,80,80);
|
||||
[row-certainty="60"], [row-certainty="61"], [row-certainty="62"], [row-certainty="63"], [row-certainty="64"],
|
||||
[row-certainty="65"], [row-certainty="66"], [row-certainty="67"], [row-certainty="68"], [row-certainty="69"] {
|
||||
color: rgb(80, 80, 80);
|
||||
}
|
||||
|
||||
[row-certainty="70"],[row-certainty="71"],[row-certainty="72"],[row-certainty="73"],[row-certainty="74"],
|
||||
[row-certainty="75"],[row-certainty="76"],[row-certainty="77"],[row-certainty="78"],[row-certainty="79"] {
|
||||
color: rgb(60,60,60);
|
||||
[row-certainty="70"], [row-certainty="71"], [row-certainty="72"], [row-certainty="73"], [row-certainty="74"],
|
||||
[row-certainty="75"], [row-certainty="76"], [row-certainty="77"], [row-certainty="78"], [row-certainty="79"] {
|
||||
color: rgb(60, 60, 60);
|
||||
}
|
||||
|
||||
[row-certainty="80"],[row-certainty="81"],[row-certainty="82"],[row-certainty="83"],[row-certainty="84"],
|
||||
[row-certainty="85"],[row-certainty="86"],[row-certainty="87"],[row-certainty="88"],[row-certainty="89"] {
|
||||
color: rgb(40,40,40);
|
||||
[row-certainty="80"], [row-certainty="81"], [row-certainty="82"], [row-certainty="83"], [row-certainty="84"],
|
||||
[row-certainty="85"], [row-certainty="86"], [row-certainty="87"], [row-certainty="88"], [row-certainty="89"] {
|
||||
color: rgb(40, 40, 40);
|
||||
}
|
||||
|
||||
[row-certainty="90"],[row-certainty="91"],[row-certainty="92"],[row-certainty="93"],[row-certainty="94"],
|
||||
[row-certainty="95"],[row-certainty="96"],[row-certainty="97"],[row-certainty="98"],[row-certainty="99"] {
|
||||
color: rgb(20,20,20);
|
||||
[row-certainty="90"], [row-certainty="91"], [row-certainty="92"], [row-certainty="93"], [row-certainty="94"],
|
||||
[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;
|
||||
}
|
@ -107,4 +107,57 @@ 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…
x
Reference in New Issue
Block a user