From 41f8e53efc960b503491013bcee6a0099517a675 Mon Sep 17 00:00:00 2001 From: Daan Vanden Bosch Date: Tue, 8 Dec 2020 21:13:29 +0100 Subject: [PATCH] Added hunt method tables with dummy data. --- .../web/huntOptimizer/HuntOptimizer.kt | 10 +- .../controllers/MethodsController.kt | 37 +--- .../MethodsForEpisodeController.kt | 18 ++ .../widgets/MethodsForEpisodeWidget.kt | 45 +++-- .../huntOptimizer/widgets/MethodsWidget.kt | 13 +- .../web/questEditor/asm/AsmAnalyser.kt | 2 +- .../web/questEditor/stores/AsmStore.kt | 2 +- .../world/phantasmal/webui/dom/DomCreation.kt | 9 + .../world/phantasmal/webui/widgets/Table.kt | 168 ++++++++++++++++++ 9 files changed, 248 insertions(+), 56 deletions(-) create mode 100644 web/src/main/kotlin/world/phantasmal/web/huntOptimizer/controllers/MethodsForEpisodeController.kt create mode 100644 webui/src/main/kotlin/world/phantasmal/webui/widgets/Table.kt diff --git a/web/src/main/kotlin/world/phantasmal/web/huntOptimizer/HuntOptimizer.kt b/web/src/main/kotlin/world/phantasmal/web/huntOptimizer/HuntOptimizer.kt index eb78682c..b8c62ac5 100644 --- a/web/src/main/kotlin/world/phantasmal/web/huntOptimizer/HuntOptimizer.kt +++ b/web/src/main/kotlin/world/phantasmal/web/huntOptimizer/HuntOptimizer.kt @@ -6,8 +6,10 @@ import world.phantasmal.web.core.loading.AssetLoader import world.phantasmal.web.core.stores.UiStore import world.phantasmal.web.huntOptimizer.controllers.HuntOptimizerController import world.phantasmal.web.huntOptimizer.controllers.MethodsController +import world.phantasmal.web.huntOptimizer.controllers.MethodsForEpisodeController import world.phantasmal.web.huntOptimizer.stores.HuntMethodStore import world.phantasmal.web.huntOptimizer.widgets.HuntOptimizerWidget +import world.phantasmal.web.huntOptimizer.widgets.MethodsForEpisodeWidget import world.phantasmal.web.huntOptimizer.widgets.MethodsWidget import world.phantasmal.webui.DisposableContainer import world.phantasmal.webui.widgets.Widget @@ -24,12 +26,16 @@ class HuntOptimizer( // Controllers val huntOptimizerController = addDisposable(HuntOptimizerController(uiStore)) - val methodsController = addDisposable(MethodsController(uiStore, huntMethodStore)) + val methodsController = addDisposable(MethodsController(uiStore)) // Main Widget return HuntOptimizerWidget( ctrl = huntOptimizerController, - createMethodsWidget = { MethodsWidget(methodsController) } + createMethodsWidget = { + MethodsWidget(methodsController) { episode -> + MethodsForEpisodeWidget(MethodsForEpisodeController(huntMethodStore, episode)) + } + } ) } } diff --git a/web/src/main/kotlin/world/phantasmal/web/huntOptimizer/controllers/MethodsController.kt b/web/src/main/kotlin/world/phantasmal/web/huntOptimizer/controllers/MethodsController.kt index 17a829b6..645c51d7 100644 --- a/web/src/main/kotlin/world/phantasmal/web/huntOptimizer/controllers/MethodsController.kt +++ b/web/src/main/kotlin/world/phantasmal/web/huntOptimizer/controllers/MethodsController.kt @@ -1,23 +1,15 @@ package world.phantasmal.web.huntOptimizer.controllers import world.phantasmal.lib.fileFormats.quest.Episode -import world.phantasmal.observable.value.list.ListVal -import world.phantasmal.observable.value.list.MutableListVal -import world.phantasmal.observable.value.list.mutableListVal import world.phantasmal.web.core.PwToolType import world.phantasmal.web.core.controllers.PathAwareTab import world.phantasmal.web.core.controllers.PathAwareTabController import world.phantasmal.web.core.stores.UiStore import world.phantasmal.web.huntOptimizer.HuntOptimizerUrls -import world.phantasmal.web.huntOptimizer.models.HuntMethodModel -import world.phantasmal.web.huntOptimizer.stores.HuntMethodStore class MethodsTab(title: String, path: String, val episode: Episode) : PathAwareTab(title, path) -class MethodsController( - uiStore: UiStore, - huntMethodStore: HuntMethodStore, -) : PathAwareTabController( +class MethodsController(uiStore: UiStore) : PathAwareTabController( uiStore, PwToolType.HuntOptimizer, listOf( @@ -25,29 +17,4 @@ class MethodsController( MethodsTab("Episode II", HuntOptimizerUrls.methodsEpisodeII, Episode.II), MethodsTab("Episode IV", HuntOptimizerUrls.methodsEpisodeIV, Episode.IV), ) -) { - private val _episodeToMethods = mutableMapOf>() - - val episodeToMethods: Map> = _episodeToMethods - - init { - // TODO: Use filtered ListVals. - observe(huntMethodStore.methods) { methods -> - val ep1 = _episodeToMethods.getOrPut(Episode.I) { mutableListVal() } - val ep2 = _episodeToMethods.getOrPut(Episode.II) { mutableListVal() } - val ep4 = _episodeToMethods.getOrPut(Episode.IV) { mutableListVal() } - - ep1.clear() - ep2.clear() - ep4.clear() - - methods.forEach { method -> - when (method.episode) { - Episode.I -> ep1.add(method) - Episode.II -> ep2.add(method) - Episode.IV -> ep4.add(method) - } - } - } - } -} +) diff --git a/web/src/main/kotlin/world/phantasmal/web/huntOptimizer/controllers/MethodsForEpisodeController.kt b/web/src/main/kotlin/world/phantasmal/web/huntOptimizer/controllers/MethodsForEpisodeController.kt new file mode 100644 index 00000000..5bf39840 --- /dev/null +++ b/web/src/main/kotlin/world/phantasmal/web/huntOptimizer/controllers/MethodsForEpisodeController.kt @@ -0,0 +1,18 @@ +package world.phantasmal.web.huntOptimizer.controllers + +import world.phantasmal.lib.fileFormats.quest.Episode +import world.phantasmal.lib.fileFormats.quest.NpcType +import world.phantasmal.observable.value.list.ListVal +import world.phantasmal.web.huntOptimizer.models.HuntMethodModel +import world.phantasmal.web.huntOptimizer.stores.HuntMethodStore +import world.phantasmal.webui.controllers.Controller + +class MethodsForEpisodeController( + huntMethodStore: HuntMethodStore, + episode: Episode, +) : Controller() { + val enemies: List = NpcType.VALUES.filter { it.enemy && it.episode == episode } + + val methods: ListVal = + huntMethodStore.methods.filtered { it.episode == episode } +} diff --git a/web/src/main/kotlin/world/phantasmal/web/huntOptimizer/widgets/MethodsForEpisodeWidget.kt b/web/src/main/kotlin/world/phantasmal/web/huntOptimizer/widgets/MethodsForEpisodeWidget.kt index 7fa113c9..61599f44 100644 --- a/web/src/main/kotlin/world/phantasmal/web/huntOptimizer/widgets/MethodsForEpisodeWidget.kt +++ b/web/src/main/kotlin/world/phantasmal/web/huntOptimizer/widgets/MethodsForEpisodeWidget.kt @@ -1,22 +1,44 @@ package world.phantasmal.web.huntOptimizer.widgets import org.w3c.dom.Node -import world.phantasmal.lib.fileFormats.quest.Episode -import world.phantasmal.web.huntOptimizer.controllers.MethodsController +import world.phantasmal.web.huntOptimizer.controllers.MethodsForEpisodeController +import world.phantasmal.web.huntOptimizer.models.HuntMethodModel import world.phantasmal.webui.dom.div +import world.phantasmal.webui.widgets.Column +import world.phantasmal.webui.widgets.Table import world.phantasmal.webui.widgets.Widget -class MethodsForEpisodeWidget( - private val ctrl: MethodsController, - private val episode: Episode, -) : Widget() { +class MethodsForEpisodeWidget(private val ctrl: MethodsForEpisodeController) : Widget() { override fun Node.createElement() = div { className = "pw-hunt-optimizer-methods-for-episode" - bindChildrenTo(ctrl.episodeToMethods.getValue(episode)) { method, _ -> - div { textContent = method.name } - } + addChild( + Table( + values = ctrl.methods, + columns = listOf( + Column( + title = "Method", + fixed = true, + width = 250, + renderCell = { it.name }, + ), + Column( + title = "Time", + fixed = true, + width = 60, + renderCell = { it.time.value.toIsoString() }, + ), + *ctrl.enemies.map { enemy -> + Column( + title = enemy.simpleName, + width = 90, + renderCell = { 69 } + ) + }.toTypedArray() + ), + ) + ) } companion object { @@ -25,7 +47,10 @@ class MethodsForEpisodeWidget( // language=css style(""" .pw-hunt-optimizer-methods-for-episode { - overflow: auto; + display: grid; + grid-template-rows: 100%; + grid-template-columns: 100%; + overflow: hidden; } """.trimIndent()) } diff --git a/web/src/main/kotlin/world/phantasmal/web/huntOptimizer/widgets/MethodsWidget.kt b/web/src/main/kotlin/world/phantasmal/web/huntOptimizer/widgets/MethodsWidget.kt index 0acb0139..61063150 100644 --- a/web/src/main/kotlin/world/phantasmal/web/huntOptimizer/widgets/MethodsWidget.kt +++ b/web/src/main/kotlin/world/phantasmal/web/huntOptimizer/widgets/MethodsWidget.kt @@ -1,6 +1,7 @@ package world.phantasmal.web.huntOptimizer.widgets import org.w3c.dom.Node +import world.phantasmal.lib.fileFormats.quest.Episode import world.phantasmal.web.huntOptimizer.controllers.MethodsController import world.phantasmal.webui.dom.div import world.phantasmal.webui.widgets.TabContainer @@ -8,13 +9,14 @@ import world.phantasmal.webui.widgets.Widget class MethodsWidget( private val ctrl: MethodsController, + private val createMethodsForEpisodeWidget: (Episode) -> MethodsForEpisodeWidget, ) : Widget() { override fun Node.createElement() = div { className = "pw-hunt-optimizer-methods" addChild(TabContainer(ctrl = ctrl, createWidget = { tab -> - MethodsForEpisodeWidget(ctrl, tab.episode) + createMethodsForEpisodeWidget(tab.episode) })) } @@ -24,12 +26,9 @@ class MethodsWidget( // language=css style(""" .pw-hunt-optimizer-methods { - display: flex; - flex-direction: column; - } - - .pw-hunt-optimizer-methods > * { - flex-grow: 1; + display: grid; + grid-template-rows: 100%; + grid-template-columns: 100%; overflow: hidden; } """.trimIndent()) diff --git a/web/src/main/kotlin/world/phantasmal/web/questEditor/asm/AsmAnalyser.kt b/web/src/main/kotlin/world/phantasmal/web/questEditor/asm/AsmAnalyser.kt index 983e0ada..393934ba 100644 --- a/web/src/main/kotlin/world/phantasmal/web/questEditor/asm/AsmAnalyser.kt +++ b/web/src/main/kotlin/world/phantasmal/web/questEditor/asm/AsmAnalyser.kt @@ -62,7 +62,7 @@ object AsmAnalyser { processAsm() } - suspend fun updateAssembly(changes: List) { + suspend fun updateAsm(changes: List) { for (change in changes) { val (startLineNo, startCol, endLineNo, endCol) = change.range val linesChanged = endLineNo - startLineNo + 1 diff --git a/web/src/main/kotlin/world/phantasmal/web/questEditor/stores/AsmStore.kt b/web/src/main/kotlin/world/phantasmal/web/questEditor/stores/AsmStore.kt index dc5f3ce8..7e932bcc 100644 --- a/web/src/main/kotlin/world/phantasmal/web/questEditor/stores/AsmStore.kt +++ b/web/src/main/kotlin/world/phantasmal/web/questEditor/stores/AsmStore.kt @@ -66,7 +66,7 @@ class AsmStore( model.onDidChangeContent { e -> scope.launch { - AsmAnalyser.updateAssembly(e.changes.map { + AsmAnalyser.updateAsm(e.changes.map { AsmChange( AsmRange( it.range.startLineNumber, diff --git a/webui/src/main/kotlin/world/phantasmal/webui/dom/DomCreation.kt b/webui/src/main/kotlin/world/phantasmal/webui/dom/DomCreation.kt index 1e5e3858..3d42bd07 100644 --- a/webui/src/main/kotlin/world/phantasmal/webui/dom/DomCreation.kt +++ b/webui/src/main/kotlin/world/phantasmal/webui/dom/DomCreation.kt @@ -57,15 +57,24 @@ fun Node.span(block: HTMLSpanElement.() -> Unit = {}): HTMLSpanElement = fun Node.table(block: HTMLTableElement.() -> Unit = {}): HTMLTableElement = appendHtmlEl("TABLE", block) +fun Node.tbody(block: HTMLTableSectionElement.() -> Unit = {}): HTMLTableSectionElement = + appendHtmlEl("TBODY", block) + fun Node.td(block: HTMLTableCellElement.() -> Unit = {}): HTMLTableCellElement = appendHtmlEl("TD", block) fun Node.textarea(block: HTMLTextAreaElement.() -> Unit = {}): HTMLTextAreaElement = appendHtmlEl("TEXTAREA", block) +fun Node.tfoot(block: HTMLTableSectionElement.() -> Unit = {}): HTMLTableSectionElement = + appendHtmlEl("TFOOT", block) + fun Node.th(block: HTMLTableCellElement.() -> Unit = {}): HTMLTableCellElement = appendHtmlEl("TH", block) +fun Node.thead(block: HTMLTableSectionElement.() -> Unit = {}): HTMLTableSectionElement = + appendHtmlEl("THEAD", block) + fun Node.tr(block: HTMLTableRowElement.() -> Unit = {}): HTMLTableRowElement = appendHtmlEl("TR", block) diff --git a/webui/src/main/kotlin/world/phantasmal/webui/widgets/Table.kt b/webui/src/main/kotlin/world/phantasmal/webui/widgets/Table.kt new file mode 100644 index 00000000..eb3cab29 --- /dev/null +++ b/webui/src/main/kotlin/world/phantasmal/webui/widgets/Table.kt @@ -0,0 +1,168 @@ +package world.phantasmal.webui.widgets + +import org.w3c.dom.Node +import world.phantasmal.observable.value.Val +import world.phantasmal.observable.value.list.ListVal +import world.phantasmal.observable.value.trueVal +import world.phantasmal.webui.dom.* + +class Column( + val title: String, + val fixed: Boolean = false, + val width: Int, + val renderCell: (T) -> Any, +) + +class Table( + visible: Val = trueVal(), + enabled: Val = trueVal(), + private val values: ListVal, + private val columns: List>, +) : Widget(visible, enabled) { + override fun Node.createElement() = + table { + className = "pw-table" + + thead { + tr { + var runningWidth = 0 + + for ((index, column) in columns.withIndex()) { + th { + span { textContent = column.title } + + if (column.fixed) { + style.position = "sticky" + style.left = "${runningWidth}px" + runningWidth += column.width + } + + style.width = "${column.width}px" + } + } + } + } + tbody { + bindChildrenTo(values) { value, index -> + tr { + var runningWidth = 0 + + for ((index, column) in columns.withIndex()) { + (if (column.fixed) ::th else ::td) { + append(column.renderCell(value)) + + if (column.fixed) { + classList.add("pw-fixed") + style.left = "${runningWidth}px" + runningWidth += column.width + } + + style.width = "${column.width}px" + } + } + } + } + } + } + + companion object { + init { + @Suppress("CssUnresolvedCustomProperty", "CssUnusedSymbol") + // language=css + style(""" + .pw-table { + position: relative; + display: block; + box-sizing: border-box; + overflow: auto; + background-color: var(--pw-bg-color); + border-collapse: collapse; + } + + .pw-table tr { + display: flex; + align-items: stretch; + } + + .pw-table thead { + position: sticky; + display: inline-block; + top: 0; + z-index: 2; + } + + .pw-table thead tr { + position: sticky; + top: 0; + } + + .pw-table thead th { + display: flex; + flex-direction: column; + justify-content: center; + overflow: hidden; + } + + .pw-table th, + .pw-table td { + box-sizing: border-box; + overflow: hidden; + text-overflow: ellipsis; + padding: 3px 6px; + border-right: var(--pw-border); + border-bottom: var(--pw-border); + background-color: var(--pw-bg-color); + } + + .pw-table tbody { + user-select: text; + cursor: text; + } + + .pw-table tbody th, + .pw-table tbody td { + white-space: nowrap; + } + + .pw-table tbody th, + .pw-table tfoot th { + text-align: left; + } + + .pw-table th.pw-fixed { + position: sticky; + text-align: left; + } + + .pw-table th.input { + padding: 0; + overflow: visible; + } + + .pw-table th.input .pw-duration-input { + z-index: 0; + height: 100%; + width: 100%; + border: none; + } + + .pw-table th.input .pw-duration-input:hover, + .pw-table th.input .pw-duration-input:focus-within { + margin: -1px; + height: calc(100% + 2px); + width: calc(100% + 2px); + } + + .pw-table th.input .pw-duration-input:hover { + z-index: 4; + border: var(--pw-input-border-hover); + } + + .pw-table th.input .pw-duration-input:focus-within { + z-index: 6; + border: var(--pw-input-border-focus); + } + """.trimIndent()) + } + } +}