Added hunt method tables with dummy data.

This commit is contained in:
Daan Vanden Bosch 2020-12-08 21:13:29 +01:00
parent 540e35ffc9
commit 41f8e53efc
9 changed files with 248 additions and 56 deletions

View File

@ -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))
}
}
)
}
}

View File

@ -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<MethodsTab>(
class MethodsController(uiStore: UiStore) : PathAwareTabController<MethodsTab>(
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<Episode, MutableListVal<HuntMethodModel>>()
val episodeToMethods: Map<Episode, ListVal<HuntMethodModel>> = _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)
}
}
}
}
}
)

View File

@ -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> = NpcType.VALUES.filter { it.enemy && it.episode == episode }
val methods: ListVal<HuntMethodModel> =
huntMethodStore.methods.filtered { it.episode == episode }
}

View File

@ -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<HuntMethodModel>(
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())
}

View File

@ -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())

View File

@ -62,7 +62,7 @@ object AsmAnalyser {
processAsm()
}
suspend fun updateAssembly(changes: List<AsmChange>) {
suspend fun updateAsm(changes: List<AsmChange>) {
for (change in changes) {
val (startLineNo, startCol, endLineNo, endCol) = change.range
val linesChanged = endLineNo - startLineNo + 1

View File

@ -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,

View File

@ -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)

View File

@ -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<T>(
val title: String,
val fixed: Boolean = false,
val width: Int,
val renderCell: (T) -> Any,
)
class Table<T>(
visible: Val<Boolean> = trueVal(),
enabled: Val<Boolean> = trueVal(),
private val values: ListVal<T>,
private val columns: List<Column<T>>,
) : 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())
}
}
}