mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-04 22:58:29 +08:00
Added hunt method tables with dummy data.
This commit is contained in:
parent
540e35ffc9
commit
41f8e53efc
@ -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))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -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 }
|
||||
}
|
@ -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())
|
||||
}
|
||||
|
@ -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())
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
||||
|
168
webui/src/main/kotlin/world/phantasmal/webui/widgets/Table.kt
Normal file
168
webui/src/main/kotlin/world/phantasmal/webui/widgets/Table.kt
Normal 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())
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user