Editing and persistence now works
This commit is contained in:
parent
188d53a768
commit
32f3cdabff
|
@ -1,64 +1,39 @@
|
||||||
[
|
{
|
||||||
{
|
"008000" : {
|
||||||
"address": 32768,
|
"label" : "ResetVector"
|
||||||
"label": "ResetVector",
|
|
||||||
"comment": null,
|
|
||||||
"flags": []
|
|
||||||
},
|
},
|
||||||
{
|
"008034" : {
|
||||||
"address": 32820,
|
"label" : "MainGameLoop"
|
||||||
"label": "MainGameLoop",
|
|
||||||
"comment": null,
|
|
||||||
"flags": []
|
|
||||||
},
|
},
|
||||||
{
|
"0080b5" : {
|
||||||
"address": 32949,
|
"label" : "JumpToGameMode"
|
||||||
"label": "JumpToGameMode",
|
|
||||||
"comment": null,
|
|
||||||
"flags": []
|
|
||||||
},
|
},
|
||||||
{
|
"0080c6" : {
|
||||||
"address": 165840,
|
"flags" : [ {
|
||||||
"label": null,
|
"flagType" : "JmpIndirectLongInterleavedTable",
|
||||||
"comment": null,
|
"start" : "008061",
|
||||||
"flags": [
|
"entries" : 28
|
||||||
{
|
} ]
|
||||||
"flagType": "JslTableRoutine",
|
|
||||||
"entries": 4
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
"00841e" : {
|
||||||
"address": 32966,
|
"label" : "ClearOam",
|
||||||
"label": null,
|
"comment" : "Test3"
|
||||||
"comment": null,
|
|
||||||
"flags": [
|
|
||||||
{
|
|
||||||
"flagType": "JmpIndirectLongInterleavedTable",
|
|
||||||
"start": 32865,
|
|
||||||
"entries": 28
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
"00879c" : {
|
||||||
"address": 835861,
|
"flags" : [ {
|
||||||
"label": null,
|
"flagType" : "NonReturningRoutine"
|
||||||
"comment": null,
|
} ]
|
||||||
"flags": [
|
|
||||||
{
|
|
||||||
"flagType": "JslTableRoutine",
|
|
||||||
"entries": 12
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
"0287d0" : {
|
||||||
"address": 34716,
|
"flags" : [ {
|
||||||
"label": null,
|
"flagType" : "JslTableRoutine",
|
||||||
"comment": null,
|
"entries" : 4
|
||||||
"flags": [
|
} ]
|
||||||
{
|
},
|
||||||
"flagType": "NonReturningRoutine"
|
"0cc115" : {
|
||||||
}
|
"flags" : [ {
|
||||||
]
|
"flagType" : "JslTableRoutine",
|
||||||
|
"entries" : 12
|
||||||
|
} ]
|
||||||
}
|
}
|
||||||
]
|
}
|
|
@ -78,11 +78,7 @@ class Grid {
|
||||||
add(y, ins.address,
|
add(y, ins.address,
|
||||||
text(actualAddress?.toFormattedString() ?: ""),
|
text(actualAddress?.toFormattedString() ?: ""),
|
||||||
text(ins.bytesToString()),
|
text(ins.bytesToString()),
|
||||||
input(value = insMetadata?.label ?: "")
|
editableField(presentedAddress, "label", insMetadata?.label),
|
||||||
.addClass("field-label")
|
|
||||||
.addClass("field-editable")
|
|
||||||
.attr("data-field", "label")
|
|
||||||
.attr("data-address", presentedAddress.value.toString()),
|
|
||||||
fragment {
|
fragment {
|
||||||
text(ins.printOpcodeAndSuffix())
|
text(ins.printOpcodeAndSuffix())
|
||||||
text(" ")
|
text(" ")
|
||||||
|
@ -107,11 +103,7 @@ class Grid {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
text(ins.postState?.toString() ?: ""),
|
text(ins.postState?.toString() ?: ""),
|
||||||
input(value = insMetadata?.comment ?: "")
|
editableField(presentedAddress, "comment", insMetadata?.comment)
|
||||||
.addClass("field-comment")
|
|
||||||
.addClass("field-editable")
|
|
||||||
.attr("data-field", "comment")
|
|
||||||
.attr("data-address", presentedAddress.value.toString())
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if (ins.opcode.continuation == Continuation.NO) {
|
if (ins.opcode.continuation == Continuation.NO) {
|
||||||
|
@ -119,6 +111,14 @@ class Grid {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun editableField(address: Address, type: String, value: String?): HtmlNode {
|
||||||
|
return input(value = value ?: "")
|
||||||
|
.addClass("field-$type")
|
||||||
|
.addClass("field-editable")
|
||||||
|
.attr("data-field", type)
|
||||||
|
.attr("data-address", address.toSimpleString())
|
||||||
|
}
|
||||||
|
|
||||||
private fun addDummy() {
|
private fun addDummy() {
|
||||||
val y = (height++)
|
val y = (height++)
|
||||||
add(y, null, null, null, null, text("..."), null, null)
|
add(y, null, null, null, null, text("..."), null, null)
|
||||||
|
|
|
@ -93,6 +93,8 @@ fun title(inner: InnerHtml = {}) = parent("title", inner)
|
||||||
fun HtmlArea.title(inner: InnerHtml = {}) = com.smallhacker.disbrowser.title(inner).appendTo(parent)
|
fun HtmlArea.title(inner: InnerHtml = {}) = com.smallhacker.disbrowser.title(inner).appendTo(parent)
|
||||||
fun link(inner: InnerHtml = {}) = leaf("link")
|
fun link(inner: InnerHtml = {}) = leaf("link")
|
||||||
fun HtmlArea.link(inner: InnerHtml = {}) = com.smallhacker.disbrowser.link(inner).appendTo(parent)
|
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 body(inner: InnerHtml = {}) = parent("body", inner)
|
||||||
fun HtmlArea.body(inner: InnerHtml = {}) = com.smallhacker.disbrowser.body(inner).appendTo(parent)
|
fun HtmlArea.body(inner: InnerHtml = {}) = com.smallhacker.disbrowser.body(inner).appendTo(parent)
|
||||||
fun div(inner: InnerHtml = {}) = parent("div", inner)
|
fun div(inner: InnerHtml = {}) = parent("div", inner)
|
||||||
|
|
|
@ -13,7 +13,7 @@ object Service {
|
||||||
private val romName = "Zelda no Densetsu - Kamigami no Triforce (Japan)"
|
private val romName = "Zelda no Densetsu - Kamigami no Triforce (Japan)"
|
||||||
private val romDir = Paths.get("""P:\Emulation\ROMs\SNES""")
|
private val romDir = Paths.get("""P:\Emulation\ROMs\SNES""")
|
||||||
private val metaDir = Paths.get("""P:\Programming\dis-browser""")
|
private val metaDir = Paths.get("""P:\Programming\dis-browser""")
|
||||||
private val metaFile = jsonFile<Metadata>(metaDir.resolve("$romName.json"))
|
private val metaFile = jsonFile<Metadata>(metaDir.resolve("$romName.json"), true)
|
||||||
private val metadata by lazy { metaFile.load() }
|
private val metadata by lazy { metaFile.load() }
|
||||||
|
|
||||||
private val romData = lazy {
|
private val romData = lazy {
|
||||||
|
@ -57,6 +57,7 @@ object Service {
|
||||||
head {
|
head {
|
||||||
title { text("Disassembly Browser") }
|
title { text("Disassembly Browser") }
|
||||||
link {}.attr("rel", "stylesheet").attr("href", "/resources/style.css")
|
link {}.attr("rel", "stylesheet").attr("href", "/resources/style.css")
|
||||||
|
meta {}.attr("charset", "UTF-8")
|
||||||
}
|
}
|
||||||
body {
|
body {
|
||||||
grid.output().appendTo(parent)
|
grid.output().appendTo(parent)
|
||||||
|
@ -67,12 +68,23 @@ object Service {
|
||||||
|
|
||||||
fun updateMetadata(address: Address, field: KMutableProperty1<MetadataLine, String?>, value: String) {
|
fun updateMetadata(address: Address, field: KMutableProperty1<MetadataLine, String?>, value: String) {
|
||||||
if (value.isEmpty()) {
|
if (value.isEmpty()) {
|
||||||
metadata[address]?.run {
|
if (address in metadata) {
|
||||||
field.set(this, null)
|
doUpdateMetadata(address, field, null)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
field.set(metadata.getOrAdd(address), value)
|
doUpdateMetadata(address, field, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun doUpdateMetadata(address: Address, field: KMutableProperty1<MetadataLine, String?>, value: String?) {
|
||||||
|
val line = metadata.getOrCreate(address)
|
||||||
|
field.set(line, value)
|
||||||
|
|
||||||
|
if (line.isEmpty()) {
|
||||||
|
metadata[address] = null
|
||||||
|
}
|
||||||
|
|
||||||
|
metaFile.save(metadata)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,15 @@
|
||||||
package com.smallhacker.disbrowser.asm
|
package com.smallhacker.disbrowser.asm
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonCreator
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import com.fasterxml.jackson.annotation.JsonValue
|
import com.fasterxml.jackson.annotation.JsonValue
|
||||||
import com.smallhacker.disbrowser.util.UInt24
|
import com.smallhacker.disbrowser.util.UInt24
|
||||||
import com.smallhacker.disbrowser.util.toUInt24
|
import com.smallhacker.disbrowser.util.toUInt24
|
||||||
|
import com.smallhacker.disbrowser.util.tryParseInt
|
||||||
|
|
||||||
data class Address(@JsonValue val value: UInt24): Comparable<Address> {
|
data class Address(val value: UInt24) : Comparable<Address> {
|
||||||
@JsonIgnore
|
|
||||||
val rom = (value and 0x8000u).toUInt() == 0u
|
val rom = (value and 0x8000u).toUInt() == 0u
|
||||||
@JsonIgnore
|
|
||||||
val pc = snesToPc(value)
|
val pc = snesToPc(value)
|
||||||
|
|
||||||
operator fun plus(offset: Int) = Address(value + offset)
|
operator fun plus(offset: Int) = Address(value + offset)
|
||||||
|
@ -18,13 +19,21 @@ data class Address(@JsonValue val value: UInt24): Comparable<Address> {
|
||||||
|
|
||||||
override fun toString(): String = toFormattedString()
|
override fun toString(): String = toFormattedString()
|
||||||
fun toFormattedString(): String = String.format("$%02x:%04x", (value shr 16).toInt(), (value and 0xFFFFu).toInt())
|
fun toFormattedString(): String = String.format("$%02x:%04x", (value shr 16).toInt(), (value and 0xFFFFu).toInt())
|
||||||
|
@JsonValue
|
||||||
fun toSimpleString(): String = String.format("%06x", value.toInt())
|
fun toSimpleString(): String = String.format("%06x", value.toInt())
|
||||||
|
|
||||||
fun withinBank(value: UShort): Address = Address((this.value and 0xFF_0000u) or value.toUInt24())
|
fun withinBank(value: UShort): Address = Address((this.value and 0xFF_0000u) or value.toUInt24())
|
||||||
|
|
||||||
override fun compareTo(other: Address) = value.toUInt().compareTo(other.value.toUInt())
|
override fun compareTo(other: Address) = value.toUInt().compareTo(other.value.toUInt())
|
||||||
|
|
||||||
infix fun distanceTo(other: Address)= Math.abs(value.toInt() - other.value.toInt()).toUInt()
|
infix fun distanceTo(other: Address) = Math.abs(value.toInt() - other.value.toInt()).toUInt()
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
@JsonCreator
|
||||||
|
fun parse(address: String): Address? = tryParseInt(address, 16)
|
||||||
|
?.let { Address(it.toUInt24()) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun address(snesAddress: Int) = Address(snesAddress.toUInt24())
|
fun address(snesAddress: Int) = Address(snesAddress.toUInt24())
|
||||||
|
|
|
@ -4,20 +4,29 @@ import com.fasterxml.jackson.annotation.*
|
||||||
import com.fasterxml.jackson.annotation.JsonSubTypes.Type
|
import com.fasterxml.jackson.annotation.JsonSubTypes.Type
|
||||||
import com.smallhacker.disbrowser.util.joinBytes
|
import com.smallhacker.disbrowser.util.joinBytes
|
||||||
import com.smallhacker.disbrowser.util.toUInt24
|
import com.smallhacker.disbrowser.util.toUInt24
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
class Metadata() {
|
class Metadata {
|
||||||
private val lines = HashMap<Address, MetadataLine>()
|
private val lines: MutableMap<Address, MetadataLine>
|
||||||
|
|
||||||
@JsonValue
|
constructor() {
|
||||||
private fun linesAsList() = lines.values.toList()
|
this.lines = HashMap()
|
||||||
|
|
||||||
@JsonCreator
|
|
||||||
private constructor(@JsonProperty lines: List<MetadataLine>) : this() {
|
|
||||||
lines.forEach { add(it) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun add(line: MetadataLine): Metadata {
|
@JsonCreator
|
||||||
lines[line.address] = line
|
private constructor(@JsonProperty lines: Map<Address, MetadataLine>) {
|
||||||
|
this.lines = HashMap(lines)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonValue
|
||||||
|
private fun serialize() = TreeMap(lines)
|
||||||
|
|
||||||
|
operator fun set(address: Address, line: MetadataLine?): Metadata {
|
||||||
|
if (line == null) {
|
||||||
|
lines.remove(address)
|
||||||
|
} else {
|
||||||
|
lines[address] = line
|
||||||
|
}
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,29 +34,19 @@ class Metadata() {
|
||||||
return lines[address]
|
return lines[address]
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getOrAdd(address: Address): MetadataLine {
|
operator fun contains(address: Address) = lines[address] != null
|
||||||
|
|
||||||
|
fun getOrCreate(address: Address): MetadataLine {
|
||||||
val line = this[address]
|
val line = this[address]
|
||||||
if (line != null) {
|
if (line != null) {
|
||||||
return line
|
return line
|
||||||
}
|
}
|
||||||
val newLine = MetadataLine(address)
|
val newLine = MetadataLine()
|
||||||
add(newLine)
|
this[address] = newLine
|
||||||
return newLine
|
return newLine
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Metadata.at(address: Int, runner: MetadataLine.() -> Unit) {
|
|
||||||
val line = MetadataLine(address(address))
|
|
||||||
this.add(line)
|
|
||||||
runner(line)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun metadata(runner: Metadata.() -> Unit): Metadata {
|
|
||||||
val metadata = Metadata()
|
|
||||||
runner(metadata)
|
|
||||||
return metadata
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "flagType")
|
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "flagType")
|
||||||
@JsonSubTypes(
|
@JsonSubTypes(
|
||||||
Type(value = NonReturningRoutine::class, name = "NonReturningRoutine"),
|
Type(value = NonReturningRoutine::class, name = "NonReturningRoutine"),
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
package com.smallhacker.disbrowser.asm
|
package com.smallhacker.disbrowser.asm
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude
|
||||||
|
|
||||||
|
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
|
||||||
data class MetadataLine(
|
data class MetadataLine(
|
||||||
val address: Address,
|
|
||||||
var label: String? = null,
|
var label: String? = null,
|
||||||
var comment: String? = null,
|
var comment: String? = null,
|
||||||
var preComment: String? = null,
|
var preComment: String? = null,
|
||||||
val flags: MutableList<InstructionFlag> = ArrayList()
|
val flags: MutableList<InstructionFlag> = ArrayList()
|
||||||
)
|
) {
|
||||||
|
@JsonIgnore
|
||||||
|
fun isEmpty() = (label == null) && (comment == null) && (preComment == null) && (flags.isEmpty())
|
||||||
|
}
|
||||||
|
|
|
@ -4,8 +4,7 @@ import com.smallhacker.disbrowser.HtmlNode
|
||||||
import com.smallhacker.disbrowser.Service
|
import com.smallhacker.disbrowser.Service
|
||||||
import com.smallhacker.disbrowser.asm.Address
|
import com.smallhacker.disbrowser.asm.Address
|
||||||
import com.smallhacker.disbrowser.asm.VagueNumber
|
import com.smallhacker.disbrowser.asm.VagueNumber
|
||||||
import com.smallhacker.disbrowser.util.toUInt24
|
import java.nio.charset.StandardCharsets
|
||||||
import com.smallhacker.disbrowser.util.tryParseInt
|
|
||||||
import javax.ws.rs.GET
|
import javax.ws.rs.GET
|
||||||
import javax.ws.rs.Path
|
import javax.ws.rs.Path
|
||||||
import javax.ws.rs.PathParam
|
import javax.ws.rs.PathParam
|
||||||
|
@ -31,18 +30,13 @@ class DisassemblyResource {
|
||||||
@Produces(MediaType.TEXT_HTML)
|
@Produces(MediaType.TEXT_HTML)
|
||||||
fun getIt(@PathParam("address") address: String, @PathParam("state") state: String): Response {
|
fun getIt(@PathParam("address") address: String, @PathParam("state") state: String): Response {
|
||||||
return handle {
|
return handle {
|
||||||
parseAddress(address)?.let {
|
Address.parse(address)?.let {
|
||||||
val flags = parseState(state)
|
val flags = parseState(state)
|
||||||
Service.showDisassembly(it, flags)
|
Service.showDisassembly(it, flags)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseAddress(address: String): Address? {
|
|
||||||
return tryParseInt(address, 16)
|
|
||||||
?.let { Address(it.toUInt24()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handle(runner: () -> HtmlNode?): Response {
|
private fun handle(runner: () -> HtmlNode?): Response {
|
||||||
try {
|
try {
|
||||||
val disassembly = runner()
|
val disassembly = runner()
|
||||||
|
@ -50,7 +44,9 @@ class DisassemblyResource {
|
||||||
return if (disassembly == null)
|
return if (disassembly == null)
|
||||||
Response.status(404).build()
|
Response.status(404).build()
|
||||||
else
|
else
|
||||||
Response.ok(disassembly.toString(), MediaType.TEXT_HTML).build()
|
Response.ok(disassembly.toString().toByteArray(StandardCharsets.UTF_8))
|
||||||
|
.encoding("UTF-8")
|
||||||
|
.build()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
throw e
|
throw e
|
||||||
|
|
|
@ -3,8 +3,6 @@ package com.smallhacker.disbrowser.resource
|
||||||
import com.smallhacker.disbrowser.Service
|
import com.smallhacker.disbrowser.Service
|
||||||
import com.smallhacker.disbrowser.asm.Address
|
import com.smallhacker.disbrowser.asm.Address
|
||||||
import com.smallhacker.disbrowser.asm.MetadataLine
|
import com.smallhacker.disbrowser.asm.MetadataLine
|
||||||
import com.smallhacker.disbrowser.util.toUInt24
|
|
||||||
import com.smallhacker.disbrowser.util.tryParseInt
|
|
||||||
import javax.ws.rs.Consumes
|
import javax.ws.rs.Consumes
|
||||||
import javax.ws.rs.POST
|
import javax.ws.rs.POST
|
||||||
import javax.ws.rs.Path
|
import javax.ws.rs.Path
|
||||||
|
@ -18,7 +16,7 @@ class RestResource {
|
||||||
@Path("{address}/{field}")
|
@Path("{address}/{field}")
|
||||||
@Consumes(MediaType.TEXT_PLAIN)
|
@Consumes(MediaType.TEXT_PLAIN)
|
||||||
fun getIt(@PathParam("address") address: String, @PathParam("field") fieldName: String, body: String): Response {
|
fun getIt(@PathParam("address") address: String, @PathParam("field") fieldName: String, body: String): Response {
|
||||||
val parsedAddress = parseAddress(address) ?: return Response.status(400).build()
|
val parsedAddress = Address.parse(address) ?: return Response.status(400).build()
|
||||||
val field = when (fieldName) {
|
val field = when (fieldName) {
|
||||||
"preComment" -> MetadataLine::preComment
|
"preComment" -> MetadataLine::preComment
|
||||||
"comment" -> MetadataLine::comment
|
"comment" -> MetadataLine::comment
|
||||||
|
@ -30,11 +28,4 @@ class RestResource {
|
||||||
|
|
||||||
return Response.noContent().build()
|
return Response.noContent().build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun parseAddress(address: String): Address? {
|
|
||||||
return tryParseInt(address, 16)
|
|
||||||
?.let { Address(it.toUInt24()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,9 +14,10 @@ interface JsonFile<T> {
|
||||||
fun save(value: T)
|
fun save(value: T)
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified T> jsonFile(path: Path): JsonFile<T> {
|
inline fun <reified T> jsonFile(path: Path, prettyPrint: Boolean = false): JsonFile<T> {
|
||||||
|
val writer = if (prettyPrint) jsonMapper.writerWithDefaultPrettyPrinter() else jsonMapper.writer()
|
||||||
return object : JsonFile<T> {
|
return object : JsonFile<T> {
|
||||||
override fun load() = jsonMapper.readValue<T>(path.toFile())
|
override fun load() = jsonMapper.readValue<T>(path.toFile())
|
||||||
override fun save(value: T) = jsonMapper.writeValue(path.toFile(), value)
|
override fun save(value: T) = writer.writeValue(path.toFile(), value)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
function center(el: HTMLElement) {
|
function center(el: HTMLElement) {
|
||||||
function documentOffsetTop (el: HTMLElement) {
|
function documentOffsetTop(el: HTMLElement) {
|
||||||
let top = el.offsetTop;
|
let top = el.offsetTop;
|
||||||
let parent = el.offsetParent;
|
let parent = el.offsetParent;
|
||||||
if (parent && parent instanceof HTMLElement) {
|
if (parent && parent instanceof HTMLElement) {
|
||||||
|
@ -9,7 +9,7 @@ function center(el: HTMLElement) {
|
||||||
}
|
}
|
||||||
|
|
||||||
let top = documentOffsetTop(el) - (window.innerHeight / 2);
|
let top = documentOffsetTop(el) - (window.innerHeight / 2);
|
||||||
window.scrollTo( 0, top );
|
window.scrollTo(0, top);
|
||||||
}
|
}
|
||||||
|
|
||||||
function highlight(el: HTMLElement | null) {
|
function highlight(el: HTMLElement | null) {
|
||||||
|
@ -45,8 +45,27 @@ function fromUrl() {
|
||||||
return fromHash() || fromPath();
|
return fromHash() || fromPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function xhr(url: string, method: string = "GET", body: (string | null) = null) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let xhr = new XMLHttpRequest();
|
||||||
|
xhr.onload = () => {
|
||||||
|
if (xhr.status < 400) {
|
||||||
|
resolve(xhr);
|
||||||
|
} else {
|
||||||
|
reject(xhr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhr.onerror = () => reject(xhr);
|
||||||
|
xhr.onabort = () => reject(xhr);
|
||||||
|
xhr.open(method, url);
|
||||||
|
xhr.send(body);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
highlight(fromUrl());
|
highlight(fromUrl());
|
||||||
window.addEventListener("hashchange", function () { highlight(fromHash()) }, false);
|
window.addEventListener("hashchange", function () {
|
||||||
|
highlight(fromHash())
|
||||||
|
}, false);
|
||||||
|
|
||||||
let comments = document.getElementsByClassName("field-editable");
|
let comments = document.getElementsByClassName("field-editable");
|
||||||
for (let i = 0; i < comments.length; i++) {
|
for (let i = 0; i < comments.length; i++) {
|
||||||
|
@ -54,9 +73,12 @@ for (let i = 0; i < comments.length; i++) {
|
||||||
comment.addEventListener("change", e => {
|
comment.addEventListener("change", e => {
|
||||||
let target = <HTMLInputElement>(e.target);
|
let target = <HTMLInputElement>(e.target);
|
||||||
let field = target.dataset.field || "";
|
let field = target.dataset.field || "";
|
||||||
let address = parseInt(target.dataset.address || "-1");
|
let address = target.dataset.address;
|
||||||
let value = (target).value;
|
let value = (target).value;
|
||||||
alert(field + "/" + address + "=" + value);
|
|
||||||
|
xhr(`/rest/${address}/${field}`, "POST", value)
|
||||||
|
.catch((xhr: XMLHttpRequest) => alert("Error: HTTP " + xhr.status));
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
}
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "amd",
|
"module": "amd",
|
||||||
|
"target": "es2016",
|
||||||
"strictNullChecks": true,
|
"strictNullChecks": true,
|
||||||
"noImplicitAny": true,
|
"noImplicitAny": true,
|
||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
|
|
Loading…
Reference in New Issue