Table can now have a changing amount of columns.

This commit is contained in:
Daan Vanden Bosch 2021-03-13 15:39:56 +01:00
parent aeded71dde
commit e21bce7695
11 changed files with 4476 additions and 4404 deletions

View File

@ -5,7 +5,7 @@ import world.phantasmal.lib.cursor.Cursor
class ItemPmt(
val statBoosts: List<PmtStatBoost>,
val frames: List<PmtFrame>,
val barriers: List<PmtFrame>,
val barriers: List<PmtBarrier>,
val units: List<PmtUnit>,
val tools: List<List<PmtTool>>,
val weapons: List<List<PmtWeapon>>,
@ -42,6 +42,8 @@ class PmtFrame(
val unknown1: Int,
)
typealias PmtBarrier = PmtFrame
class PmtUnit(
val id: Int,
val type: Int,

View File

@ -12,5 +12,8 @@ private val EP_AND_NAME_TO_NPC_TYPE: Map<Pair<String, Episode>, NpcType> =
}
}
/**
* Uniquely identifies an NPC. Tries to match on [NpcType.simpleName] and [NpcType.ultimateName].
*/
fun NpcType.Companion.fromNameAndEpisode(name: String, episode: Episode): NpcType? =
EP_AND_NAME_TO_NPC_TYPE[Pair(name, episode)]

View File

@ -136,7 +136,7 @@ private fun download(
sectionId,
enemy = npcType,
itemTypeId = itemType.id,
dropRate = dropRateNum.toDouble() / dropRateDenom.toDouble(),
anythingRate = dropRateNum.toDouble() / dropRateDenom.toDouble(),
rareRate = rareRateNum.toDouble() / rareRateDenom.toDouble(),
))
} catch (e: Exception) {

View File

@ -1,26 +0,0 @@
package world.phantasmal.web.shared.dto
import kotlinx.serialization.Serializable
import world.phantasmal.lib.Episode
import world.phantasmal.lib.fileFormats.quest.NpcType
@Serializable
class EnemyDrop(
val difficulty: Difficulty,
val episode: Episode,
val sectionId: SectionId,
val enemy: NpcType,
val itemTypeId: Int,
val dropRate: Double,
val rareRate: Double,
)
@Serializable
class BoxDrop(
val difficulty: Difficulty,
val episode: Episode,
val sectionId: SectionId,
val areaId: Int,
val itemTypeId: Int,
val dropRate: Double,
)

View File

@ -0,0 +1,43 @@
package world.phantasmal.web.shared.dto
import kotlinx.serialization.Serializable
import world.phantasmal.lib.Episode
import world.phantasmal.lib.fileFormats.quest.NpcType
@Serializable
sealed class ItemDrop {
abstract val difficulty: Difficulty
abstract val episode: Episode
abstract val sectionId: SectionId
abstract val itemTypeId: Int
abstract val dropRate: Double
}
@Serializable
class EnemyDrop(
override val difficulty: Difficulty,
override val episode: Episode,
override val sectionId: SectionId,
val enemy: NpcType,
override val itemTypeId: Int,
/**
* Chance that this enemy drops anything at all.
*/
val anythingRate: Double,
/**
* Chance that an enemy drops this rare item, if it drops anything at all.
*/
val rareRate: Double,
) : ItemDrop() {
override val dropRate: Double = anythingRate * rareRate
}
@Serializable
class BoxDrop(
override val difficulty: Difficulty,
override val episode: Episode,
override val sectionId: SectionId,
val areaId: Int,
override val itemTypeId: Int,
override val dropRate: Double,
) : ItemDrop()

View File

@ -37,8 +37,14 @@ class ItemDropStore(
class EnemyDropTable(
private val table: Map<Triple<Difficulty, SectionId, NpcType>, EnemyDrop>,
/**
* Mapping of [ItemType] ids to [EnemyDrop]s.
*/
private val itemTypeToDrops: Map<Int, List<EnemyDrop>>,
) {
fun getDrop(difficulty: Difficulty, sectionId: SectionId, npcType: NpcType): EnemyDrop? =
table[Triple(difficulty, sectionId, npcType)]
fun getDropsForItemType(itemType: ItemType): List<EnemyDrop> =
itemTypeToDrops[itemType.id] ?: emptyList()
}

View File

@ -3,6 +3,7 @@ package world.phantasmal.web.huntOptimizer.controllers
import world.phantasmal.lib.Episode
import world.phantasmal.lib.fileFormats.quest.NpcType
import world.phantasmal.observable.value.list.ListVal
import world.phantasmal.observable.value.list.listVal
import world.phantasmal.observable.value.list.mutableListVal
import world.phantasmal.web.huntOptimizer.models.HuntMethodModel
import world.phantasmal.web.huntOptimizer.stores.HuntMethodStore
@ -19,27 +20,27 @@ class MethodsForEpisodeController(
private val methods = mutableListVal<HuntMethodModel>()
private val enemies: List<NpcType> = NpcType.VALUES.filter { it.enemy && it.episode == episode }
override val fixedColumns = 2
override val values: ListVal<HuntMethodModel> = methods
override val columns: List<Column<HuntMethodModel>> = listOf(
override val columns: ListVal<Column<HuntMethodModel>> = listVal(
Column(
key = METHOD_COL_KEY,
title = "Method",
fixed = true,
width = 250,
sortable = true,
),
Column(
key = TIME_COL_KEY,
title = "Time",
fixed = true,
width = 50,
input = true,
sortable = true,
),
*enemies.map { enemy ->
// Word-wrap long names.
val title = when(enemy) {
val title = when (enemy) {
NpcType.Gigobooma -> "Gigo-\nbooma"
NpcType.Shambertin -> "Shamber-\ntin"
else -> enemy.simpleName
@ -51,6 +52,7 @@ class MethodsForEpisodeController(
headerClassName = "pw-hunt-optimizer-methods-for-episode-header-cell",
className = "pw-hunt-optimizer-methods-for-episode-cell",
sortable = true,
textAlign = "right",
)
}.toTypedArray()
)

File diff suppressed because it is too large Load Diff

View File

@ -5,10 +5,6 @@ import world.phantasmal.observable.value.list.ListVal
class Column<T>(
val key: String,
val title: String,
/**
* Whether the column stays in place while scrolling.
*/
val fixed: Boolean = false,
val width: Int,
/**
* Whether cells in this column contain an input widget.
@ -18,6 +14,7 @@ class Column<T>(
val sortable: Boolean = false,
val headerClassName: String? = null,
val className: String? = null,
val textAlign: String? = null,
)
enum class SortDirection {
@ -33,8 +30,13 @@ interface SortColumn<T> {
abstract class TableController<T> : Controller() {
private val sortColumns: MutableList<SortColumnImpl> = mutableListOf()
/**
* How many columns stay in place on the left side while scrolling.
*/
open val fixedColumns: Int = 0
abstract val values: ListVal<T>
abstract val columns: List<Column<T>>
abstract val columns: ListVal<Column<T>>
open fun sort(sortColumns: List<SortColumn<T>>) {
error("Not sortable.")

View File

@ -176,7 +176,7 @@ fun <T> bindDisposableChildrenTo(
},
childrenRemoved = {
disposer.disposeAll()
}
},
)
disposable {
@ -189,6 +189,7 @@ fun <T> bindChildrenTo(
parent: Element,
list: ListVal<T>,
createChild: Node.(T, index: Int) -> Node,
after: (ListValChangeEvent<T>) -> Unit = {},
): Disposable =
bindChildrenTo(
parent,
@ -196,13 +197,15 @@ fun <T> bindChildrenTo(
createChild,
childrenRemoved = { _, _ ->
// Do nothing.
}
},
after,
)
fun <T> bindDisposableChildrenTo(
parent: Element,
list: ListVal<T>,
createChild: Node.(T, index: Int) -> Pair<Node, Disposable>,
after: (ListValChangeEvent<T>) -> Unit = {},
): Disposable {
val disposer = Disposer()
@ -216,7 +219,8 @@ fun <T> bindDisposableChildrenTo(
},
childrenRemoved = { index, count ->
disposer.removeAt(index, count)
}
},
after,
)
return disposable {
@ -249,6 +253,7 @@ private fun <T> bindChildrenTo(
list: ListVal<T>,
createChild: Node.(T, index: Int) -> Node,
childrenRemoved: (index: Int, count: Int) -> Unit,
after: (ListValChangeEvent<T>) -> Unit,
): Disposable =
list.observeList(callNow = true) { change: ListValChangeEvent<T> ->
if (change is ListValChangeEvent.Change) {
@ -270,4 +275,6 @@ private fun <T> bindChildrenTo(
parent.insertBefore(frag, parent.childNodes[change.index])
}
}
after(change)
}

View File

@ -1,6 +1,6 @@
package world.phantasmal.webui.widgets
import org.w3c.dom.Node
import org.w3c.dom.*
import world.phantasmal.core.disposable.Disposer
import world.phantasmal.observable.value.Val
import world.phantasmal.observable.value.trueVal
@ -25,33 +25,16 @@ class Table<T>(
tr {
className = "pw-table-row pw-table-header-row"
var runningWidth = 0
for (column in ctrl.columns) {
th {
className = "pw-table-cell"
column.headerClassName?.let { classList.add(it) }
textContent = column.title
if (column.fixed) {
style.position = "sticky"
style.left = "${runningWidth}px"
runningWidth += column.width
}
style.width = "${column.width}px"
if (column.sortable) {
onmousedown = { e ->
if (e.buttons.toInt() == 1) {
ctrl.sortByColumn(column)
}
}
}
}
}
addDisposable(bindChildrenTo(
this,
ctrl.columns,
createChild = { column, _ ->
createHeaderRowCell(column)
},
after = {
positionFixedColumns(row = this, headerRow = true)
},
))
}
}
tbody {
@ -61,38 +44,16 @@ class Table<T>(
val row = tr {
className = "pw-table-row"
var runningWidth = 0
for (column in ctrl.columns) {
(if (column.fixed) ::th else ::td) {
className = "pw-table-cell pw-table-body-cell"
column.className?.let { classList.add(it) }
val child = renderCell(value, column)
if (child is Widget) {
rowDisposer.add(child)
append(child.element)
} else {
append(child)
}
if (column.input) {
classList.add("pw-table-cell-input")
}
if (column.fixed) {
classList.add("pw-table-cell-fixed")
style.left = "${runningWidth}px"
runningWidth += column.width
}
style.width = "${column.width}px"
column.tooltip?.let { title = it(value) }
}
}
addDisposable(bindChildrenTo(
this,
ctrl.columns,
createChild = { column, _ ->
createRowCell(column, value, rowDisposer)
},
after = {
positionFixedColumns(row = this, headerRow = false)
},
))
}
Pair(row, rowDisposer)
@ -100,6 +61,77 @@ class Table<T>(
}
}
private fun Node.createHeaderRowCell(column: Column<T>): HTMLTableCellElement =
th {
className = "pw-table-cell"
column.headerClassName?.let { classList.add(it) }
textContent = column.title
style.width = "${column.width}px"
if (column.sortable) {
onmousedown = { e ->
if (e.buttons.toInt() == 1) {
ctrl.sortByColumn(column)
}
}
}
}
private fun Node.createRowCell(
column: Column<T>,
value: T,
rowDisposer: Disposer,
): HTMLTableCellElement =
td {
className = "pw-table-cell pw-table-body-cell"
column.className?.let { classList.add(it) }
val child = renderCell(value, column)
if (child is Widget) {
rowDisposer.add(child)
append(child.element)
} else {
append(child)
}
if (column.input) {
classList.add("pw-table-cell-input")
}
style.width = "${column.width}px"
column.tooltip?.let { title = it(value) }
column.textAlign?.let { style.textAlign = it }
}
private fun positionFixedColumns(row: HTMLTableRowElement, headerRow: Boolean) {
val columns = ctrl.columns.value
var left = 0
for (index in columns.indices) {
val el = row.children[index].unsafeCast<HTMLElement>()
if (index < ctrl.fixedColumns) {
el.style.position = "sticky"
el.style.left = "${left}px"
if (!headerRow) {
el.classList.add("pw-table-cell-fixed")
}
} else {
el.style.position = ""
}
left += columns[index].width
}
}
companion object {
init {
@Suppress("CssUnresolvedCustomProperty", "CssUnusedSymbol")
@ -164,6 +196,7 @@ class Table<T>(
}
.pw-table-cell-fixed {
font-weight: bold;
position: sticky;
text-align: left;
}