diff --git a/src/core/gui/Table.ts b/src/core/gui/Table.ts index 0b0e3c10..8ed08031 100644 --- a/src/core/gui/Table.ts +++ b/src/core/gui/Table.ts @@ -11,7 +11,7 @@ import "./Table.css"; export type Column = { title: string; sticky?: boolean; - width?: number; + width: number; text_align?: string; create_cell(value: T, disposer: Disposer): HTMLTableCellElement; }; @@ -47,10 +47,10 @@ export class Table extends Widget { if (column.sticky) { th.style.position = "sticky"; th.style.left = `${left}px`; - left += column.width || 0; + left += column.width; } - if (column.width != undefined) th.style.width = `${column.width}px`; + th.style.width = `${column.width}px`; return th; }), @@ -84,7 +84,10 @@ export class Table extends Widget { this.tbody_element.append(...rows); } else { for (let i = 0; i < amount; i++) { - this.tbody_element.children[index + i].insertAdjacentElement("afterend", rows[i]); + this.tbody_element.children[index + i].insertAdjacentElement( + "beforebegin", + rows[i], + ); } } }; diff --git a/src/core/observable/property/list/SimpleListProperty.ts b/src/core/observable/property/list/SimpleListProperty.ts index 138a5ebe..fe39a1ef 100644 --- a/src/core/observable/property/list/SimpleListProperty.ts +++ b/src/core/observable/property/list/SimpleListProperty.ts @@ -63,10 +63,7 @@ export class SimpleListProperty extends AbstractProperty this.extract_observables = extract_observables; } - observe_list( - observer: (change: ListPropertyChangeEvent) => void, - options?: { call_now?: true }, - ): Disposable { + observe_list(observer: (change: ListPropertyChangeEvent) => void): Disposable { if (this.value_observers.length === 0 && this.extract_observables) { this.replace_element_observers(0, Infinity, this.values); } @@ -75,15 +72,6 @@ export class SimpleListProperty extends AbstractProperty this.list_observers.push(observer); } - if (options && options.call_now) { - this.call_list_observer(observer, { - type: ListChangeType.ListChange, - index: 0, - removed: [], - inserted: this.values, - }); - } - return { dispose: () => { const index = this.list_observers.indexOf(observer); diff --git a/src/core/observable/property/loadable/LoadableProperty.ts b/src/core/observable/property/loadable/LoadableProperty.ts deleted file mode 100644 index 199a7248..00000000 --- a/src/core/observable/property/loadable/LoadableProperty.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { Property } from "../Property"; -import { LoadableState } from "./LoadableState"; - -/** - * Represents a value that can be loaded asynchronously. - * [state]{@link LoadableProperty#state} represents the current state of this Loadable's value. - */ -export interface LoadableProperty extends Property { - readonly state: Property; - - /** - * True if the initial data load has happened. It may or may not have succeeded. - * Check [error]{@link LoadableProperty#error} to know whether an error occurred. - */ - readonly is_initialized: Property; - - /** - * True if a data load is underway. This may be the initializing load or a later reload. - */ - readonly is_loading: Property; - - /** - * This property returns valid data as soon as possible. - * If the Loadable is uninitialized a data load will be triggered, otherwise the current value will be returned. - */ - readonly promise: Promise; - - /** - * Contains the {@link Error} object if an error occurred during the most recent data load. - */ - readonly error: Property; - - /** - * Load the data. Initializes the Loadable if it is uninitialized. - */ - load(): Promise; -} diff --git a/src/core/observable/property/loadable/LoadableState.ts b/src/core/observable/property/loadable/LoadableState.ts deleted file mode 100644 index 1cd1a5fb..00000000 --- a/src/core/observable/property/loadable/LoadableState.ts +++ /dev/null @@ -1,26 +0,0 @@ -export enum LoadableState { - /** - * No attempt has been made to load data. - */ - Uninitialized, - - /** - * The first data load is underway. - */ - Initializing, - - /** - * Data was loaded at least once. The most recent load was successful. - */ - Nominal, - - /** - * Data was loaded at least once. The most recent load failed. - */ - Error, - - /** - * Data was loaded at least once. Another data load is underway. - */ - Reloading, -} diff --git a/src/core/observable/property/loadable/SimpleLoadableProperty.ts b/src/core/observable/property/loadable/SimpleLoadableProperty.ts deleted file mode 100644 index ef29eee3..00000000 --- a/src/core/observable/property/loadable/SimpleLoadableProperty.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { Property } from "../Property"; -import { WritableProperty } from "../WritableProperty"; -import { property } from "../../index"; -import { AbstractProperty } from "../AbstractProperty"; -import { LoadableState } from "./LoadableState"; -import { LoadableProperty } from "./LoadableProperty"; - -export class SimpleLoadableProperty extends AbstractProperty implements LoadableProperty { - get val(): T { - return this.get_val(); - } - - readonly state: Property; - readonly is_initialized: Property; - readonly is_loading: Property; - - get promise(): Promise { - // Load value on first use. - if (this._state.val === LoadableState.Uninitialized) { - return this.load_value(); - } else { - return this._promise; - } - } - - readonly error: Property; - - private _val: T; - private _promise: Promise; - private readonly _state: WritableProperty = property( - LoadableState.Uninitialized, - ); - private readonly _load?: () => Promise; - private readonly _error: WritableProperty = property(undefined); - - constructor(initial_value: T, load?: () => Promise) { - super(); - - this._val = initial_value; - this._promise = new Promise(resolve => resolve(this._val)); - this.state = this._state; - - this.is_initialized = this.state.map(state => state !== LoadableState.Uninitialized); - - this.is_loading = this.state.map( - state => state === LoadableState.Initializing || state === LoadableState.Reloading, - ); - - this._load = load; - this.error = this._error; - } - - get_val(): T { - return this._val; - } - - load(): Promise { - return this.load_value(); - } - - private async load_value(): Promise { - if (this.is_loading.val) return this._promise; - - this._state.val = LoadableState.Initializing; - const old_val = this._val; - - try { - if (this._load) { - this._promise = this._load(); - this._val = await this._promise; - } - - this._state.val = LoadableState.Nominal; - this._error.val = undefined; - return this._val; - } catch (e) { - this._state.val = LoadableState.Error; - this._error.val = e; - throw e; - } finally { - this.emit(old_val); - } - } -} diff --git a/src/core/observable/property/loadable/Store.ts b/src/core/observable/property/loadable/Store.ts deleted file mode 100644 index 648b4a27..00000000 --- a/src/core/observable/property/loadable/Store.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { LoadableState } from "./LoadableState"; -import { Property } from "../Property"; -import { WritableProperty } from "../WritableProperty"; -import { property } from "../../index"; - -export class Store { - readonly state: Property; - - /** - * True if the initial data load has happened. It may or may not have succeeded. - * Check [error]{@link LoadableProperty#error} to know whether an error occurred. - */ - readonly is_initialized: Property; - - /** - * True if a data load is underway. This may be the initializing load or a later reload. - */ - readonly is_loading: Property; - - /** - * Contains the {@link Error} object if an error occurred during the most recent data load. - */ - readonly error: Property; - - private readonly _state: WritableProperty = property( - LoadableState.Uninitialized, - ); - private readonly _error: WritableProperty = property(undefined); - - constructor() { - this.state = this._state; - - this.is_initialized = this.state.map(state => state !== LoadableState.Uninitialized); - - this.is_loading = this.state.map( - state => state === LoadableState.Initializing || state === LoadableState.Reloading, - ); - - this.error = this._error; - } -} diff --git a/src/core/sequential.test.ts b/src/core/sequential.test.ts new file mode 100644 index 00000000..6fc1a874 --- /dev/null +++ b/src/core/sequential.test.ts @@ -0,0 +1,17 @@ +import { sequential } from "./sequential"; + +test("sequential functions should run sequentially", () => { + let time = 10; + const f = sequential(() => new Promise(resolve => setTimeout(resolve, time--))); + + const resolved_values: number[] = []; + let last_promise!: Promise; + + for (let i = 0; i < 10; i++) { + last_promise = f().then(() => resolved_values.push(i)); + } + + expect(resolved_values).toEqual([]); + + return last_promise.then(() => expect(resolved_values).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])); +}); diff --git a/src/core/util.ts b/src/core/sequential.ts similarity index 100% rename from src/core/util.ts rename to src/core/sequential.ts diff --git a/src/core/stores/ServerMap.ts b/src/core/stores/ServerMap.ts index fcb20a47..2a14cfe9 100644 --- a/src/core/stores/ServerMap.ts +++ b/src/core/stores/ServerMap.ts @@ -2,7 +2,7 @@ import { Server } from "../model"; import { Property } from "../observable/property/Property"; import { gui_store } from "./GuiStore"; import { memoize } from "lodash"; -import { sequential } from "../util"; +import { sequential } from "../sequential"; import { Disposable } from "../observable/Disposable"; /** diff --git a/src/hunt_optimizer/gui/OptimizationResultView.ts b/src/hunt_optimizer/gui/OptimizationResultView.ts new file mode 100644 index 00000000..08f7fe89 --- /dev/null +++ b/src/hunt_optimizer/gui/OptimizationResultView.ts @@ -0,0 +1,87 @@ +import { Widget } from "../../core/gui/Widget"; +import { el } from "../../core/gui/dom"; +import { Table } from "../../core/gui/Table"; +import { hunt_optimizer_stores } from "../stores/HuntOptimizerStore"; +import { Disposable } from "../../core/observable/Disposable"; +import { list_property } from "../../core/observable"; +import { OptimalMethodModel } from "../model"; +import { Difficulty } from "../../core/model"; +import { Episode } from "../../core/data_formats/parsing/quest/Episode"; + +export class OptimizationResultView extends Widget { + private results_observer?: Disposable; + + constructor() { + super( + el.div( + { class: "hunt_optimizer_OptimizationResultView" }, + el.h2({ text: "Optimization Result" }), + ), + ); + + const optimal_methods = list_property(); + + this.element.append( + this.disposable( + new Table({ + values: optimal_methods, + columns: [ + { + title: "Difficulty", + width: 80, + create_cell(value: OptimalMethodModel) { + return el.td({ text: Difficulty[value.difficulty] }); + }, + }, + { + title: "Method", + width: 200, + create_cell(value: OptimalMethodModel) { + return el.td({ text: value.method_name }); + }, + }, + { + title: "Ep.", + width: 50, + create_cell(value: OptimalMethodModel) { + return el.td({ text: Episode[value.method_episode] }); + }, + }, + ], + }), + ).element, + ); + + this.disposable( + hunt_optimizer_stores.observe_current( + hunt_optimizer_store => { + if (this.results_observer) { + this.results_observer.dispose(); + } + + this.results_observer = hunt_optimizer_store.result.observe( + ({ value: result }) => { + if (result) { + optimal_methods.val = result.optimal_methods; + } else { + optimal_methods.val = []; + } + }, + { + call_now: true, + }, + ); + }, + { call_now: true }, + ), + ); + } + + dispose(): void { + super.dispose(); + + if (this.results_observer) { + this.results_observer.dispose(); + } + } +} diff --git a/src/hunt_optimizer/gui/OptimizerView.ts b/src/hunt_optimizer/gui/OptimizerView.ts index 9e8b862a..ab7acf7f 100644 --- a/src/hunt_optimizer/gui/OptimizerView.ts +++ b/src/hunt_optimizer/gui/OptimizerView.ts @@ -2,11 +2,15 @@ import { ResizableWidget } from "../../core/gui/ResizableWidget"; import { el } from "../../core/gui/dom"; import { WantedItemsView } from "./WantedItemsView"; import "./OptimizerView.css"; +import { OptimizationResultView } from "./OptimizationResultView"; export class OptimizerView extends ResizableWidget { constructor() { super(el.div({ class: "hunt_optimizer_OptimizerView" })); - this.element.append(this.disposable(new WantedItemsView()).element); + this.element.append( + this.disposable(new WantedItemsView()).element, + this.disposable(new OptimizationResultView()).element, + ); } } diff --git a/src/hunt_optimizer/gui/WantedItemsView.ts b/src/hunt_optimizer/gui/WantedItemsView.ts index 9012015c..03d61d48 100644 --- a/src/hunt_optimizer/gui/WantedItemsView.ts +++ b/src/hunt_optimizer/gui/WantedItemsView.ts @@ -9,7 +9,6 @@ import { } from "../../core/observable/property/list/ListProperty"; import { WantedItemModel } from "../model"; import { NumberInput } from "../../core/gui/NumberInput"; -import { ToolBar } from "../../core/gui/ToolBar"; import { hunt_optimizer_stores } from "../stores/HuntOptimizerStore"; import { Disposable } from "../../core/observable/Disposable"; @@ -23,7 +22,6 @@ export class WantedItemsView extends Widget { this.element.append( el.h2({ text: "Wanted Items" }), - this.disposable(new ToolBar({ children: [new Button("Optimize")] })).element, el.div( { class: "hunt_optimizer_WantedItemsView_table_wrapper" }, el.table({}, this.tbody_element), @@ -39,9 +37,6 @@ export class WantedItemsView extends Widget { this.wanted_items_observer = hunt_optimizer_store.wanted_items.observe_list( this.update_table, - { - call_now: true, - }, ); }, { call_now: true }, @@ -70,7 +65,7 @@ export class WantedItemsView extends Widget { } else { for (let i = 0; i < change.inserted.length; i++) { this.tbody_element.children[change.index + i].insertAdjacentElement( - "afterend", + "beforebegin", rows[i], ); }