From a28a8ce624dbf3577781e020021fa54fef66355a Mon Sep 17 00:00:00 2001 From: Daan Vanden Bosch Date: Sat, 7 Sep 2019 17:49:21 +0200 Subject: [PATCH] Refactored table from MethodsForEpisodeView into a reusable Table widget. --- src/core/gui/Table.css | 72 +++++++++++ src/core/gui/Table.ts | 112 ++++++++++++++++++ src/core/gui/Widget.ts | 5 + src/core/observable/Disposer.ts | 35 ++++-- .../observable/property/list/ListProperty.ts | 48 ++------ .../property/list/SimpleListProperty.ts | 112 +++++++----------- .../gui/MethodsForEpisodeView.css | 89 +------------- .../gui/MethodsForEpisodeView.ts | 101 ++++++++-------- src/hunt_optimizer/gui/WantedItemsView.ts | 57 +++------ src/hunt_optimizer/stores/HuntMethodStore.ts | 2 +- 10 files changed, 344 insertions(+), 289 deletions(-) create mode 100644 src/core/gui/Table.css create mode 100644 src/core/gui/Table.ts diff --git a/src/core/gui/Table.css b/src/core/gui/Table.css new file mode 100644 index 00000000..f3076622 --- /dev/null +++ b/src/core/gui/Table.css @@ -0,0 +1,72 @@ +.core_Table { + display: block; + box-sizing: border-box; + overflow: auto; + background-color: var(--bg-color); + border-collapse: collapse; +} + +.core_Table tr { + display: flex; + align-items: stretch; +} + +.core_Table thead tr { + position: sticky; + top: 0; + z-index: 2; +} + +.core_Table th, +.core_Table td { + box-sizing: border-box; + overflow: hidden; + text-overflow: ellipsis; + padding: 3px 6px; + border-right: solid 1px var(--border-color); + border-bottom: solid 1px var(--border-color); + background-color: var(--bg-color); +} + +.core_Table tbody { + user-select: text; + cursor: text; +} + +.core_Table tbody th, +.core_Table tbody td { + white-space: nowrap; +} + +.core_Table tbody th { + text-align: left; +} + +.core_Table th.input { + padding: 0; + overflow: visible; +} + +.core_Table th.input .core_DurationInput { + z-index: 0; + height: 100%; + width: 100%; + border: none; +} + +.core_Table th.input .core_DurationInput:hover, +.core_Table th.input .core_DurationInput:focus-within { + margin: -1px; + height: calc(100% + 2px); + width: calc(100% + 2px); +} + +.core_Table th.input .core_DurationInput:hover { + z-index: 4; + border: var(--input-border-hover); +} + +.core_Table th.input .core_DurationInput:focus-within { + z-index: 6; + border: var(--input-border-focus); +} diff --git a/src/core/gui/Table.ts b/src/core/gui/Table.ts new file mode 100644 index 00000000..d927ddd1 --- /dev/null +++ b/src/core/gui/Table.ts @@ -0,0 +1,112 @@ +import { Widget, WidgetOptions } from "./Widget"; +import { el } from "./dom"; +import { + ListChangeType, + ListProperty, + ListPropertyChangeEvent, +} from "../observable/property/list/ListProperty"; +import { Disposer } from "../observable/Disposer"; +import "./Table.css"; + +export type Column = { + title: string; + sticky?: boolean; + width?: number; + create_cell(value: T, disposer: Disposer): HTMLTableCellElement; +}; + +export type TableOptions = WidgetOptions & { + values: ListProperty; + columns: Column[]; +}; + +export class Table extends Widget { + private readonly table_disposer = this.disposable(new Disposer()); + private readonly tbody_element = el.tbody(); + private readonly values: ListProperty; + private readonly columns: Column[]; + + constructor(options: TableOptions) { + super(el.table({ class: "core_Table" }), options); + + this.values = options.values; + this.columns = options.columns; + + const thead_element = el.thead(); + const header_tr_element = el.tr(); + + let left = 0; + + header_tr_element.append( + ...this.columns.map(column => { + const th = el.th({ + text: column.title, + }); + + if (column.width != undefined) th.style.width = `${column.width}px`; + + if (column.sticky) { + th.style.position = "sticky"; + th.style.left = `${left}px`; + left += column.width || 0; + } + + return th; + }), + ); + + thead_element.append(header_tr_element); + this.tbody_element = el.tbody(); + this.element.append(thead_element, this.tbody_element); + + this.disposables(this.values.observe_list(this.update_table)); + } + + private update_table = (change: ListPropertyChangeEvent): void => { + if (change.type === ListChangeType.ListChange) { + this.splice_rows(change.index, change.removed.length, change.inserted); + } else if (change.type === ListChangeType.ValueChange) { + // TODO: update rows + } + }; + + private splice_rows = (index: number, amount: number, inserted: T[]) => { + for (let i = 0; i < amount; i++) { + this.tbody_element.children[index].remove(); + } + + this.table_disposer.dispose_at(index, amount); + + const rows = inserted.map((value, i) => this.create_row(index + i, value)); + + if (index >= this.tbody_element.childElementCount) { + this.tbody_element.append(...rows); + } else { + for (let i = 0; i < amount; i++) { + this.tbody_element.children[index + i].insertAdjacentElement("afterend", rows[i]); + } + } + }; + + private create_row = (index: number, value: T): HTMLTableRowElement => { + const disposer = this.table_disposer.add(new Disposer()); + let left = 0; + + return el.tr( + {}, + ...this.columns.map(column => { + const cell = column.create_cell(value, disposer); + + if (column.width != undefined) cell.style.width = `${column.width}px`; + + if (column.sticky) { + cell.style.position = "sticky"; + cell.style.left = `${left}px`; + left += column.width || 0; + } + + return cell; + }), + ); + }; +} diff --git a/src/core/gui/Widget.ts b/src/core/gui/Widget.ts index b8ff40fa..6d14ad31 100644 --- a/src/core/gui/Widget.ts +++ b/src/core/gui/Widget.ts @@ -7,6 +7,7 @@ import { WidgetProperty } from "../observable/property/WidgetProperty"; import { Property } from "../observable/property/Property"; export type WidgetOptions = { + class?: string; enabled?: boolean | Property; tooltip?: string | Property; }; @@ -52,6 +53,10 @@ export abstract class Widget implements Dis this.tooltip = this._tooltip; if (options) { + if (options.class) { + this.element.classList.add(options.class); + } + if (typeof options.enabled === "boolean") { this.enabled.val = options.enabled; } else if (options.enabled) { diff --git a/src/core/observable/Disposer.ts b/src/core/observable/Disposer.ts index 984b8ff3..afac4f95 100644 --- a/src/core/observable/Disposer.ts +++ b/src/core/observable/Disposer.ts @@ -19,7 +19,11 @@ export class Disposer implements Disposable { } private _disposed = false; - private readonly disposables: Disposable[] = []; + private readonly disposables: Disposable[]; + + constructor(...disposables: Disposable[]) { + this.disposables = disposables; + } /** * Add a single disposable and return the given disposable. @@ -32,6 +36,17 @@ export class Disposer implements Disposable { return disposable; } + /** + * Insert a single disposable at the given index and return the given disposable. + */ + insert(index: number, disposable: T): T { + if (!this._disposed) { + this.disposables.splice(index, 0, disposable); + } + + return disposable; + } + /** * Add 0 or more disposables. */ @@ -47,13 +62,7 @@ export class Disposer implements Disposable { * Disposes all held disposables. */ dispose_all(): void { - for (const disposable of this.disposables.splice(0, this.disposables.length)) { - try { - disposable.dispose(); - } catch (e) { - logger.warn("Error while disposing.", e); - } - } + this.dispose_at(0, this.disposables.length); } /** @@ -63,4 +72,14 @@ export class Disposer implements Disposable { this.dispose_all(); this._disposed = true; } + + dispose_at(index: number, amount: number = 1): void { + for (const disposable of this.disposables.splice(index, amount)) { + try { + disposable.dispose(); + } catch (e) { + logger.warn("Error while disposing.", e); + } + } + } } diff --git a/src/core/observable/property/list/ListProperty.ts b/src/core/observable/property/list/ListProperty.ts index 05d46776..a1efd625 100644 --- a/src/core/observable/property/list/ListProperty.ts +++ b/src/core/observable/property/list/ListProperty.ts @@ -2,45 +2,23 @@ import { Property } from "../Property"; import { Disposable } from "../../Disposable"; export enum ListChangeType { - Insertion, - Removal, - Replacement, - Update, + ListChange, + ValueChange, } -export type ListPropertyChangeEvent = - | ListInsertion - | ListRemoval - | ListReplacement - | ListUpdate; +export type ListPropertyChangeEvent = ListChange | ListValueChange; -export type ListInsertion = { - readonly type: ListChangeType.Insertion; - readonly inserted: T[]; - readonly from: number; - readonly to: number; -}; - -export type ListRemoval = { - readonly type: ListChangeType.Removal; - readonly removed: T[]; - readonly from: number; - readonly to: number; -}; - -export type ListReplacement = { - readonly type: ListChangeType.Replacement; - readonly removed: T[]; - readonly inserted: T[]; - readonly from: number; - readonly removed_to: number; - readonly inserted_to: number; -}; - -export type ListUpdate = { - readonly type: ListChangeType.Update; - readonly updated: T[]; +export type ListChange = { + readonly type: ListChangeType.ListChange; readonly index: number; + readonly removed: T[]; + readonly inserted: T[]; +}; + +export type ListValueChange = { + readonly type: ListChangeType.ValueChange; + readonly index: number; + readonly updated: T[]; }; export interface ListProperty extends Property { diff --git a/src/core/observable/property/list/SimpleListProperty.ts b/src/core/observable/property/list/SimpleListProperty.ts index 5fbccc3e..138a5ebe 100644 --- a/src/core/observable/property/list/SimpleListProperty.ts +++ b/src/core/observable/property/list/SimpleListProperty.ts @@ -23,45 +23,43 @@ export class SimpleListProperty extends AbstractProperty } get_val(): T[] { - return this.elements; + return this.values; } set_val(elements: T[]): T[] { - const removed = this.elements.splice(0, this.elements.length, ...elements); + const removed = this.values.splice(0, this.values.length, ...elements); this.finalize_update({ - type: ListChangeType.Replacement, + type: ListChangeType.ListChange, + index: 0, removed, inserted: elements, - from: 0, - removed_to: removed.length, - inserted_to: elements.length, }); return removed; } private readonly _length = property(0); - private readonly elements: T[]; + private readonly values: T[]; private readonly extract_observables?: (element: T) => Observable[]; /** - * Internal observers which observe observables related to this list's elements so that their + * Internal observers which observe observables related to this list's values so that their * changes can be propagated via update events. */ - private readonly element_observers: { index: number; disposables: Disposable[] }[] = []; + private readonly value_observers: { index: number; disposables: Disposable[] }[] = []; /** * External observers which are observing this list. */ private readonly list_observers: ((change: ListPropertyChangeEvent) => void)[] = []; /** - * @param extract_observables - Extractor function called on each element in this list. Changes + * @param extract_observables - Extractor function called on each value in this list. Changes * to the returned observables will be propagated via update events. - * @param elements - Initial elements of this list. + * @param values - Initial values of this list. */ - constructor(extract_observables?: (element: T) => Observable[], ...elements: T[]) { + constructor(extract_observables?: (element: T) => Observable[], ...values: T[]) { super(); this.length = this._length; - this.elements = elements; + this.values = values; this.extract_observables = extract_observables; } @@ -69,8 +67,8 @@ export class SimpleListProperty extends AbstractProperty observer: (change: ListPropertyChangeEvent) => void, options?: { call_now?: true }, ): Disposable { - if (this.element_observers.length === 0 && this.extract_observables) { - this.replace_element_observers(this.elements, 0, Infinity); + if (this.value_observers.length === 0 && this.extract_observables) { + this.replace_element_observers(0, Infinity, this.values); } if (!this.list_observers.includes(observer)) { @@ -79,10 +77,10 @@ export class SimpleListProperty extends AbstractProperty if (options && options.call_now) { this.call_list_observer(observer, { - type: ListChangeType.Insertion, - inserted: this.elements, - from: 0, - to: this.elements.length, + type: ListChangeType.ListChange, + index: 0, + removed: [], + inserted: this.values, }); } @@ -95,13 +93,13 @@ export class SimpleListProperty extends AbstractProperty } if (this.list_observers.length === 0) { - for (const { disposables } of this.element_observers) { + for (const { disposables } of this.value_observers) { for (const disposable of disposables) { disposable.dispose(); } } - this.element_observers.splice(0, Infinity); + this.value_observers.splice(0, Infinity); } }, }; @@ -116,35 +114,31 @@ export class SimpleListProperty extends AbstractProperty } update(f: (element: T[]) => T[]): void { - this.splice(0, this.elements.length, ...f(this.elements)); + this.splice(0, this.values.length, ...f(this.values)); } get(index: number): T { - return this.elements[index]; + return this.values[index]; } set(index: number, element: T): void { - const removed = [this.elements[index]]; - this.elements[index] = element; + const removed = [this.values[index]]; + this.values[index] = element; this.finalize_update({ - type: ListChangeType.Replacement, + type: ListChangeType.ListChange, + index, removed, inserted: [element], - from: index, - removed_to: index + 1, - inserted_to: index + 1, }); } clear(): void { - const removed = this.elements.splice(0, this.elements.length); + const removed = this.values.splice(0, this.values.length); this.finalize_update({ - type: ListChangeType.Replacement, + type: ListChangeType.ListChange, + index: 0, removed, inserted: [], - from: 0, - removed_to: removed.length, - inserted_to: 0, }); } @@ -152,18 +146,16 @@ export class SimpleListProperty extends AbstractProperty let removed: T[]; if (delete_count == undefined) { - removed = this.elements.splice(index); + removed = this.values.splice(index); } else { - removed = this.elements.splice(index, delete_count, ...items); + removed = this.values.splice(index, delete_count, ...items); } this.finalize_update({ - type: ListChangeType.Replacement, + type: ListChangeType.ListChange, + index, removed, inserted: items, - from: index, - removed_to: index + removed.length, - inserted_to: index + items.length, }); return removed; @@ -171,39 +163,27 @@ export class SimpleListProperty extends AbstractProperty /** * Does the following in the given order: - * - Updates element observers + * - Updates value observers * - Emits ListPropertyChangeEvent * - Emits PropertyChangeEvent * - Sets length */ protected finalize_update(change: ListPropertyChangeEvent): void { - if (this.list_observers.length && this.extract_observables) { - switch (change.type) { - case ListChangeType.Insertion: - this.replace_element_observers(change.inserted, change.from, 0); - break; - - case ListChangeType.Removal: - this.replace_element_observers([], change.from, change.removed.length); - break; - - case ListChangeType.Replacement: - this.replace_element_observers( - change.inserted, - change.from, - change.removed.length, - ); - break; - } + if ( + this.list_observers.length && + this.extract_observables && + change.type === ListChangeType.ListChange + ) { + this.replace_element_observers(change.index, change.removed.length, change.inserted); } for (const observer of this.list_observers) { this.call_list_observer(observer, change); } - this.emit(this.elements); + this.emit(this.values); - this._length.val = this.elements.length; + this._length.val = this.values.length; } private call_list_observer( @@ -217,10 +197,10 @@ export class SimpleListProperty extends AbstractProperty } } - private replace_element_observers(new_elements: T[], from: number, amount: number): void { + private replace_element_observers(from: number, amount: number, new_elements: T[]): void { let index = from; - const removed = this.element_observers.splice( + const removed = this.value_observers.splice( from, amount, ...new_elements.map(element => { @@ -229,7 +209,7 @@ export class SimpleListProperty extends AbstractProperty disposables: this.extract_observables!(element).map(observable => observable.observe(() => { this.finalize_update({ - type: ListChangeType.Update, + type: ListChangeType.ValueChange, updated: [element], index: obj.index, }); @@ -247,8 +227,8 @@ export class SimpleListProperty extends AbstractProperty } } - while (index < this.element_observers.length) { - this.element_observers[index].index += index; + while (index < this.value_observers.length) { + this.value_observers[index].index += index; } } } diff --git a/src/hunt_optimizer/gui/MethodsForEpisodeView.css b/src/hunt_optimizer/gui/MethodsForEpisodeView.css index 1cd02713..d563e0d5 100644 --- a/src/hunt_optimizer/gui/MethodsForEpisodeView.css +++ b/src/hunt_optimizer/gui/MethodsForEpisodeView.css @@ -1,91 +1,4 @@ -.hunt_optimizer_MethodsForEpisodeView table { - display: block; - box-sizing: border-box; - overflow: auto; +.hunt_optimizer_MethodsForEpisodeView_table { width: 100%; height: 100%; - background-color: var(--bg-color); - border-collapse: collapse; -} - -.hunt_optimizer_MethodsForEpisodeView tr { - display: flex; - align-items: stretch; -} - -.hunt_optimizer_MethodsForEpisodeView thead tr { - position: sticky; - top: 0; - z-index: 2; -} - -.hunt_optimizer_MethodsForEpisodeView th, -.hunt_optimizer_MethodsForEpisodeView td { - box-sizing: border-box; - overflow: hidden; - text-overflow: ellipsis; - width: 80px; - padding: 3px 6px; - border-right: solid 1px var(--border-color); - border-bottom: solid 1px var(--border-color); - background-color: var(--bg-color); -} - -.hunt_optimizer_MethodsForEpisodeView th:first-child { - position: sticky; - left: 0; - width: 250px; -} - -.hunt_optimizer_MethodsForEpisodeView th:nth-child(2) { - position: sticky; - left: 250px; - width: 60px; -} - -.hunt_optimizer_MethodsForEpisodeView tbody { - user-select: text; - cursor: text; -} - -.hunt_optimizer_MethodsForEpisodeView tbody th, -.hunt_optimizer_MethodsForEpisodeView tbody td { - white-space: nowrap; -} - -.hunt_optimizer_MethodsForEpisodeView tbody th { - text-align: left; -} - -.hunt_optimizer_MethodsForEpisodeView tbody td { - text-align: right; -} - -.hunt_optimizer_MethodsForEpisodeView th.input { - padding: 0; - overflow: visible; -} - -.hunt_optimizer_MethodsForEpisodeView th.input .core_DurationInput { - z-index: 0; - height: 100%; - width: 100%; - border: none; -} - -.hunt_optimizer_MethodsForEpisodeView th.input .core_DurationInput:hover, -.hunt_optimizer_MethodsForEpisodeView th.input .core_DurationInput:focus-within { - margin: -1px; - height: calc(100% + 2px); - width: calc(100% + 2px); -} - -.hunt_optimizer_MethodsForEpisodeView th.input .core_DurationInput:hover { - z-index: 4; - border: var(--input-border-hover); -} - -.hunt_optimizer_MethodsForEpisodeView th.input .core_DurationInput:focus-within { - z-index: 6; - border: var(--input-border-focus); } diff --git a/src/hunt_optimizer/gui/MethodsForEpisodeView.ts b/src/hunt_optimizer/gui/MethodsForEpisodeView.ts index 36a6c0eb..8f69a3a0 100644 --- a/src/hunt_optimizer/gui/MethodsForEpisodeView.ts +++ b/src/hunt_optimizer/gui/MethodsForEpisodeView.ts @@ -12,12 +12,12 @@ import "./MethodsForEpisodeView.css"; import { Disposer } from "../../core/observable/Disposer"; import { DurationInput } from "../../core/gui/DurationInput"; import { Disposable } from "../../core/observable/Disposable"; +import { Table } from "../../core/gui/Table"; +import { list_property } from "../../core/observable"; export class MethodsForEpisodeView extends ResizableWidget { private readonly episode: Episode; private readonly enemy_types: NpcType[]; - private readonly tbody_element: HTMLTableSectionElement; - private readonly time_disposer = this.disposable(new Disposer()); private hunt_methods_observer?: Disposable; constructor(episode: Episode) { @@ -27,25 +27,55 @@ export class MethodsForEpisodeView extends ResizableWidget { this.enemy_types = ENEMY_NPC_TYPES.filter(type => npc_data(type).episode === this.episode); - const table_element = el.table(); - const thead_element = el.thead(); - const header_tr_element = el.tr(); + const hunt_methods = list_property(); - header_tr_element.append(el.th({ text: "Method" }), el.th({ text: "Time" })); + const table = this.disposable( + new Table({ + class: "hunt_optimizer_MethodsForEpisodeView_table", + values: hunt_methods, + columns: [ + { + title: "Method", + sticky: true, + width: 250, + create_cell(method: HuntMethodModel): HTMLTableDataCellElement { + return el.th({ text: method.name }); + }, + }, + { + title: "Time", + sticky: true, + width: 60, + create_cell( + method: HuntMethodModel, + disposer: Disposer, + ): HTMLTableDataCellElement { + const time_input = disposer.add(new DurationInput(method.time.val)); - for (const enemy_type of this.enemy_types) { - header_tr_element.append( - el.th({ - text: npc_data(enemy_type).simple_name, - }), - ); - } + disposer.add( + time_input.value.observe(({ value }) => + method.set_user_time(value), + ), + ); - this.tbody_element = el.tbody(); + return el.th({ class: "input" }, time_input.element); + }, + }, + ...this.enemy_types.map(enemy_type => { + return { + title: npc_data(enemy_type).simple_name, + width: 80, + create_cell(method: HuntMethodModel): HTMLTableDataCellElement { + const count = method.enemy_counts.get(enemy_type); + return el.td({ text: count == undefined ? "" : count.toString() }); + }, + }; + }), + ], + }), + ); - thead_element.append(header_tr_element); - table_element.append(thead_element, this.tbody_element); - this.element.append(table_element); + this.element.append(table.element); this.disposable( hunt_method_stores.observe_current( @@ -55,7 +85,11 @@ export class MethodsForEpisodeView extends ResizableWidget { } this.hunt_methods_observer = hunt_method_store.methods.observe( - this.update_table, + ({ value }) => { + hunt_methods.val = value.filter( + method => method.episode === this.episode, + ); + }, { call_now: true, }, @@ -73,35 +107,4 @@ export class MethodsForEpisodeView extends ResizableWidget { this.hunt_methods_observer.dispose(); } } - - private update_table = ({ value: methods }: { value: HuntMethodModel[] }) => { - this.time_disposer.dispose_all(); - const frag = document.createDocumentFragment(); - - for (const method of methods) { - if (method.episode === this.episode) { - const time_input = this.time_disposer.add(new DurationInput(method.time.val)); - - this.time_disposer.add( - time_input.value.observe(({ value }) => method.set_user_time(value)), - ); - - const cells: HTMLTableCellElement[] = [ - el.th({ text: method.name }), - el.th({ class: "input" }, time_input.element), - ]; - - // One cell per enemy type. - for (const enemy_type of this.enemy_types) { - const count = method.enemy_counts.get(enemy_type); - cells.push(el.td({ text: count == undefined ? "" : count.toString() })); - } - - frag.append(el.tr({}, ...cells)); - } - } - - this.tbody_element.innerHTML = ""; - this.tbody_element.append(frag); - }; } diff --git a/src/hunt_optimizer/gui/WantedItemsView.ts b/src/hunt_optimizer/gui/WantedItemsView.ts index 83c87117..9012015c 100644 --- a/src/hunt_optimizer/gui/WantedItemsView.ts +++ b/src/hunt_optimizer/gui/WantedItemsView.ts @@ -58,50 +58,23 @@ export class WantedItemsView extends Widget { } private update_table = (change: ListPropertyChangeEvent): void => { - switch (change.type) { - case ListChangeType.Insertion: - { - const rows = change.inserted.map(this.create_row); + if (change.type === ListChangeType.ListChange) { + for (let i = 0; i < change.removed.length; i++) { + this.tbody_element.children[change.index].remove(); + } - if (change.from >= this.tbody_element.childElementCount) { - this.tbody_element.append(...rows); - } else { - for (let i = change.from; i < change.to; i++) { - this.tbody_element.children[i].insertAdjacentElement( - "afterend", - rows[i - change.from], - ); - } - } + const rows = change.inserted.map(this.create_row); + + if (change.index >= this.tbody_element.childElementCount) { + this.tbody_element.append(...rows); + } else { + for (let i = 0; i < change.inserted.length; i++) { + this.tbody_element.children[change.index + i].insertAdjacentElement( + "afterend", + rows[i], + ); } - break; - - case ListChangeType.Removal: - for (let i = change.from; i < change.to; i++) { - this.tbody_element.children[change.from].remove(); - } - break; - - case ListChangeType.Replacement: - { - const rows = change.inserted.map(this.create_row); - - for (let i = change.from; i < change.removed_to; i++) { - this.tbody_element.children[change.from].remove(); - } - - if (change.from >= this.tbody_element.childElementCount) { - this.tbody_element.append(...rows); - } else { - for (let i = change.from; i < change.inserted_to; i++) { - this.tbody_element.children[i].insertAdjacentElement( - "afterend", - rows[i - change.from], - ); - } - } - } - break; + } } }; diff --git a/src/hunt_optimizer/stores/HuntMethodStore.ts b/src/hunt_optimizer/stores/HuntMethodStore.ts index 7617208f..55d57911 100644 --- a/src/hunt_optimizer/stores/HuntMethodStore.ts +++ b/src/hunt_optimizer/stores/HuntMethodStore.ts @@ -24,7 +24,7 @@ export class HuntMethodStore implements Disposable { private readonly disposer = new Disposer(); constructor(server: Server, methods: HuntMethodModel[]) { - this.methods = list_property(undefined, ...methods); + this.methods = list_property(method => [method.user_time], ...methods); this.disposer.add( this.methods.observe_list(() =>