mirror of
https://github.com/DaanVandenBosch/phantasmal-world.git
synced 2025-04-05 15:28:29 +08:00
ListProperties can now emit update events.
This commit is contained in:
parent
1039049fda
commit
62db02e278
@ -22,7 +22,7 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background-color: var(--control-bg-color);
|
background-color: var(--control-bg-color);
|
||||||
height: 24px;
|
height: 24px;
|
||||||
padding: 3px 8px;
|
padding: 3px 5px;
|
||||||
border: var(--control-inner-border);
|
border: var(--control-inner-border);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
@ -45,6 +45,11 @@
|
|||||||
color: hsl(0, 0%, 55%);
|
color: hsl(0, 0%, 55%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.core_Button_inner > * {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 3px;
|
||||||
|
}
|
||||||
|
|
||||||
.core_Button_center {
|
.core_Button_center {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
@ -59,11 +64,3 @@
|
|||||||
align-content: center;
|
align-content: center;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.core_Button_left {
|
|
||||||
padding: 0 6px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.core_Button_right {
|
|
||||||
padding: 0 0 0 6px;
|
|
||||||
}
|
|
||||||
|
@ -31,11 +31,12 @@ export class Button extends Control<HTMLButtonElement> {
|
|||||||
|
|
||||||
super(el.button({ class: "core_Button" }, inner_element), options);
|
super(el.button({ class: "core_Button" }, inner_element), options);
|
||||||
|
|
||||||
|
this.center_element = el.span({ class: "core_Button_center" });
|
||||||
|
|
||||||
if (options && options.icon_left != undefined) {
|
if (options && options.icon_left != undefined) {
|
||||||
inner_element.append(el.span({ class: "core_Button_left" }, icon(options.icon_left)));
|
inner_element.append(el.span({ class: "core_Button_left" }, icon(options.icon_left)));
|
||||||
}
|
}
|
||||||
|
|
||||||
this.center_element = el.span({ class: "core_Button_center" });
|
|
||||||
inner_element.append(this.center_element);
|
inner_element.append(this.center_element);
|
||||||
|
|
||||||
if (options && options.icon_right != undefined) {
|
if (options && options.icon_right != undefined) {
|
||||||
@ -71,5 +72,6 @@ export class Button extends Control<HTMLButtonElement> {
|
|||||||
|
|
||||||
protected set_text(text: string): void {
|
protected set_text(text: string): void {
|
||||||
this.center_element.textContent = text;
|
this.center_element.textContent = text;
|
||||||
|
this.center_element.hidden = text === "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,7 @@ export class FileButton extends Control<HTMLElement> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
inner_element.append(el.span({ text }));
|
inner_element.append(el.span({ class: "core_Button_center", text }));
|
||||||
|
|
||||||
this.element.append(inner_element, this.input);
|
this.element.append(inner_element, this.input);
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ import { Property } from "./property/Property";
|
|||||||
import { DependentProperty } from "./property/DependentProperty";
|
import { DependentProperty } from "./property/DependentProperty";
|
||||||
import { WritableListProperty } from "./property/list/WritableListProperty";
|
import { WritableListProperty } from "./property/list/WritableListProperty";
|
||||||
import { SimpleWritableListProperty } from "./property/list/SimpleWritableListProperty";
|
import { SimpleWritableListProperty } from "./property/list/SimpleWritableListProperty";
|
||||||
|
import { Observable } from "./Observable";
|
||||||
|
|
||||||
export function emitter<E>(): Emitter<E> {
|
export function emitter<E>(): Emitter<E> {
|
||||||
return new SimpleEmitter();
|
return new SimpleEmitter();
|
||||||
@ -15,8 +16,11 @@ export function property<T>(value: T): WritableProperty<T> {
|
|||||||
return new SimpleProperty(value);
|
return new SimpleProperty(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function list_property<T>(...values: T[]): WritableListProperty<T> {
|
export function list_property<T>(
|
||||||
return new SimpleWritableListProperty(...values);
|
extract_observables?: (element: T) => Observable<any>[],
|
||||||
|
...elements: T[]
|
||||||
|
): WritableListProperty<T> {
|
||||||
|
return new SimpleWritableListProperty(extract_observables, ...elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function add(left: Property<number>, right: number): Property<number> {
|
export function add(left: Property<number>, right: number): Property<number> {
|
||||||
|
@ -17,13 +17,13 @@ export abstract class AbstractMinimalProperty<T> implements Property<T> {
|
|||||||
|
|
||||||
observe(
|
observe(
|
||||||
observer: (change: PropertyChangeEvent<T>) => void,
|
observer: (change: PropertyChangeEvent<T>) => void,
|
||||||
options: { call_now?: boolean } = {},
|
options?: { call_now?: boolean },
|
||||||
): Disposable {
|
): Disposable {
|
||||||
if (!this.observers.includes(observer)) {
|
if (!this.observers.includes(observer)) {
|
||||||
this.observers.push(observer);
|
this.observers.push(observer);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.call_now) {
|
if (options && options.call_now) {
|
||||||
this.call_observer(observer, this.val);
|
this.call_observer(observer, this.val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ export type ListReplacement<T> = {
|
|||||||
|
|
||||||
export type ListUpdate<T> = {
|
export type ListUpdate<T> = {
|
||||||
readonly type: ListChangeType.Update;
|
readonly type: ListChangeType.Update;
|
||||||
readonly update: T[];
|
readonly updated: T[];
|
||||||
readonly index: number;
|
readonly index: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -48,5 +48,8 @@ export interface ListProperty<T> extends Property<T[]> {
|
|||||||
|
|
||||||
get(index: number): T;
|
get(index: number): T;
|
||||||
|
|
||||||
observe_list(observer: (change: ListPropertyChangeEvent<T>) => void): Disposable;
|
observe_list(
|
||||||
|
observer: (change: ListPropertyChangeEvent<T>) => void,
|
||||||
|
options?: { call_now?: boolean },
|
||||||
|
): Disposable;
|
||||||
}
|
}
|
||||||
|
@ -12,49 +12,80 @@ const logger = Logger.get("core/observable/property/list/SimpleWritableListPrope
|
|||||||
|
|
||||||
export class SimpleWritableListProperty<T> extends AbstractProperty<T[]>
|
export class SimpleWritableListProperty<T> extends AbstractProperty<T[]>
|
||||||
implements WritableListProperty<T> {
|
implements WritableListProperty<T> {
|
||||||
readonly length: Property<number>;
|
readonly length: Property<number>; // TODO: update length
|
||||||
|
|
||||||
get val(): T[] {
|
get val(): T[] {
|
||||||
return this.get_val();
|
return this.get_val();
|
||||||
}
|
}
|
||||||
|
|
||||||
set val(values: T[]) {
|
set val(elements: T[]) {
|
||||||
this.set_val(values);
|
this.set_val(elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
get_val(): T[] {
|
get_val(): T[] {
|
||||||
return this.values;
|
return this.elements;
|
||||||
}
|
}
|
||||||
|
|
||||||
set_val(values: T[]): T[] {
|
set_val(elements: T[]): T[] {
|
||||||
const removed = this.values.splice(0, this.values.length, ...values);
|
const removed = this.elements.splice(0, this.elements.length, ...elements);
|
||||||
this.emit_list({
|
this.emit_list({
|
||||||
type: ListChangeType.Replacement,
|
type: ListChangeType.Replacement,
|
||||||
removed,
|
removed,
|
||||||
inserted: values,
|
inserted: elements,
|
||||||
from: 0,
|
from: 0,
|
||||||
removed_to: removed.length,
|
removed_to: removed.length,
|
||||||
inserted_to: values.length,
|
inserted_to: elements.length,
|
||||||
});
|
});
|
||||||
return removed;
|
return removed;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly _length = property(0);
|
private readonly _length = property(0);
|
||||||
private readonly values: T[];
|
private readonly elements: T[];
|
||||||
|
private readonly extract_observables?: (element: T) => Observable<any>[];
|
||||||
|
/**
|
||||||
|
* Internal observers which observe observables related to this list's elements so that their
|
||||||
|
* changes can be propagated via update events.
|
||||||
|
*/
|
||||||
|
private readonly element_observers: { index: number; disposables: Disposable[] }[] = [];
|
||||||
|
/**
|
||||||
|
* External observers which are observing this list.
|
||||||
|
*/
|
||||||
private readonly list_observers: ((change: ListPropertyChangeEvent<T>) => void)[] = [];
|
private readonly list_observers: ((change: ListPropertyChangeEvent<T>) => void)[] = [];
|
||||||
|
|
||||||
constructor(...values: T[]) {
|
/**
|
||||||
|
* @param extract_observables - Extractor function called on each element in this list. Changes
|
||||||
|
* to the returned observables will be propagated via update events.
|
||||||
|
* @param elements - Initial elements of this list.
|
||||||
|
*/
|
||||||
|
constructor(extract_observables?: (element: T) => Observable<any>[], ...elements: T[]) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.length = this._length;
|
this.length = this._length;
|
||||||
this.values = values;
|
this.elements = elements;
|
||||||
|
this.extract_observables = extract_observables;
|
||||||
}
|
}
|
||||||
|
|
||||||
observe_list(observer: (change: ListPropertyChangeEvent<T>) => void): Disposable {
|
observe_list(
|
||||||
|
observer: (change: ListPropertyChangeEvent<T>) => 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.list_observers.includes(observer)) {
|
if (!this.list_observers.includes(observer)) {
|
||||||
this.list_observers.push(observer);
|
this.list_observers.push(observer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options && options.call_now) {
|
||||||
|
this.call_list_observer(observer, {
|
||||||
|
type: ListChangeType.Insertion,
|
||||||
|
inserted: this.elements,
|
||||||
|
from: 0,
|
||||||
|
to: this.elements.length,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dispose: () => {
|
dispose: () => {
|
||||||
const index = this.list_observers.indexOf(observer);
|
const index = this.list_observers.indexOf(observer);
|
||||||
@ -62,6 +93,16 @@ export class SimpleWritableListProperty<T> extends AbstractProperty<T[]>
|
|||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
this.list_observers.splice(index, 1);
|
this.list_observers.splice(index, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.list_observers.length === 0) {
|
||||||
|
for (const { disposables } of this.element_observers) {
|
||||||
|
for (const disposable of disposables) {
|
||||||
|
disposable.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.element_observers.splice(0, Infinity);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -74,21 +115,21 @@ export class SimpleWritableListProperty<T> extends AbstractProperty<T[]>
|
|||||||
/* TODO */ throw new Error("not implemented");
|
/* TODO */ throw new Error("not implemented");
|
||||||
}
|
}
|
||||||
|
|
||||||
update(f: (value: T[]) => T[]): void {
|
update(f: (element: T[]) => T[]): void {
|
||||||
this.splice(0, this.values.length, ...f(this.values));
|
this.splice(0, this.elements.length, ...f(this.elements));
|
||||||
}
|
}
|
||||||
|
|
||||||
get(index: number): T {
|
get(index: number): T {
|
||||||
return this.values[index];
|
return this.elements[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
set(index: number, value: T): void {
|
set(index: number, element: T): void {
|
||||||
const removed = [this.values[index]];
|
const removed = [this.elements[index]];
|
||||||
this.values[index] = value;
|
this.elements[index] = element;
|
||||||
this.emit_list({
|
this.emit_list({
|
||||||
type: ListChangeType.Replacement,
|
type: ListChangeType.Replacement,
|
||||||
removed,
|
removed,
|
||||||
inserted: [value],
|
inserted: [element],
|
||||||
from: index,
|
from: index,
|
||||||
removed_to: index + 1,
|
removed_to: index + 1,
|
||||||
inserted_to: index + 1,
|
inserted_to: index + 1,
|
||||||
@ -96,7 +137,7 @@ export class SimpleWritableListProperty<T> extends AbstractProperty<T[]>
|
|||||||
}
|
}
|
||||||
|
|
||||||
clear(): void {
|
clear(): void {
|
||||||
const removed = this.values.splice(0, this.values.length);
|
const removed = this.elements.splice(0, this.elements.length);
|
||||||
this.emit_list({
|
this.emit_list({
|
||||||
type: ListChangeType.Replacement,
|
type: ListChangeType.Replacement,
|
||||||
removed,
|
removed,
|
||||||
@ -111,9 +152,9 @@ export class SimpleWritableListProperty<T> extends AbstractProperty<T[]>
|
|||||||
let removed: T[];
|
let removed: T[];
|
||||||
|
|
||||||
if (delete_count == undefined) {
|
if (delete_count == undefined) {
|
||||||
removed = this.values.splice(index);
|
removed = this.elements.splice(index);
|
||||||
} else {
|
} else {
|
||||||
removed = this.values.splice(index, delete_count, ...items);
|
removed = this.elements.splice(index, delete_count, ...items);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.emit_list({
|
this.emit_list({
|
||||||
@ -129,14 +170,76 @@ export class SimpleWritableListProperty<T> extends AbstractProperty<T[]>
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected emit_list(change: ListPropertyChangeEvent<T>): void {
|
protected emit_list(change: ListPropertyChangeEvent<T>): void {
|
||||||
for (const observer of this.list_observers) {
|
if (this.list_observers.length && this.extract_observables) {
|
||||||
try {
|
switch (change.type) {
|
||||||
observer(change);
|
case ListChangeType.Insertion:
|
||||||
} catch (e) {
|
this.replace_element_observers(change.inserted, change.from, 0);
|
||||||
logger.error("Observer threw error.", e);
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.emit(this.values);
|
for (const observer of this.list_observers) {
|
||||||
|
this.call_list_observer(observer, change);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emit(this.elements);
|
||||||
|
}
|
||||||
|
|
||||||
|
private call_list_observer(
|
||||||
|
observer: (change: ListPropertyChangeEvent<T>) => void,
|
||||||
|
change: ListPropertyChangeEvent<T>,
|
||||||
|
): void {
|
||||||
|
try {
|
||||||
|
observer(change);
|
||||||
|
} catch (e) {
|
||||||
|
logger.error("Observer threw error.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private replace_element_observers(new_elements: T[], from: number, amount: number): void {
|
||||||
|
let index = from;
|
||||||
|
|
||||||
|
const removed = this.element_observers.splice(
|
||||||
|
from,
|
||||||
|
amount,
|
||||||
|
...new_elements.map(element => {
|
||||||
|
const obj = {
|
||||||
|
index,
|
||||||
|
disposables: this.extract_observables!(element).map(observable =>
|
||||||
|
observable.observe(() => {
|
||||||
|
this.emit_list({
|
||||||
|
type: ListChangeType.Update,
|
||||||
|
updated: [element],
|
||||||
|
index: obj.index,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
index++;
|
||||||
|
return obj;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const { disposables } of removed) {
|
||||||
|
for (const disposable of disposables) {
|
||||||
|
disposable.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (index < this.element_observers.length) {
|
||||||
|
this.element_observers[index].index += index;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
5
src/hunt_optimizer/gui/OptimizerView.css
Normal file
5
src/hunt_optimizer/gui/OptimizerView.css
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
.hunt_optimizer_OptimizerView {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import { ResizableWidget } from "../../core/gui/ResizableWidget";
|
import { ResizableWidget } from "../../core/gui/ResizableWidget";
|
||||||
import { el } from "../../core/gui/dom";
|
import { el } from "../../core/gui/dom";
|
||||||
import { WantedItemsView } from "./WantedItemsView";
|
import { WantedItemsView } from "./WantedItemsView";
|
||||||
|
import "./OptimizerView.css";
|
||||||
|
|
||||||
export class OptimizerView extends ResizableWidget {
|
export class OptimizerView extends ResizableWidget {
|
||||||
private readonly wanted_items_view: WantedItemsView;
|
private readonly wanted_items_view: WantedItemsView;
|
||||||
@ -11,12 +12,4 @@ export class OptimizerView extends ResizableWidget {
|
|||||||
this.wanted_items_view = this.disposable(new WantedItemsView());
|
this.wanted_items_view = this.disposable(new WantedItemsView());
|
||||||
this.element.append(this.wanted_items_view.element);
|
this.element.append(this.wanted_items_view.element);
|
||||||
}
|
}
|
||||||
|
|
||||||
resize(width: number, height: number): this {
|
|
||||||
super.resize(width, height);
|
|
||||||
|
|
||||||
this.wanted_items_view.resize(Math.min(200, width), height);
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,4 +2,16 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hunt_optimizer_WantedItemsView .hunt_optimizer_WantedItemsView_table_wrapper {
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.hunt_optimizer_WantedItemsView .hunt_optimizer_WantedItemsView_table_wrapper table {
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
@ -1,36 +1,103 @@
|
|||||||
import { ResizableWidget } from "../../core/gui/ResizableWidget";
|
|
||||||
import { el, Icon } from "../../core/gui/dom";
|
import { el, Icon } from "../../core/gui/dom";
|
||||||
import "./WantedItemsView.css";
|
import "./WantedItemsView.css";
|
||||||
import { hunt_optimizer_store } from "../stores/HuntOptimizerStore";
|
import { hunt_optimizer_store } from "../stores/HuntOptimizerStore";
|
||||||
import { Button } from "../../core/gui/Button";
|
import { Button } from "../../core/gui/Button";
|
||||||
import { Disposer } from "../../core/observable/Disposer";
|
import { Disposer } from "../../core/observable/Disposer";
|
||||||
|
import { Widget } from "../../core/gui/Widget";
|
||||||
|
import {
|
||||||
|
ListChangeType,
|
||||||
|
ListPropertyChangeEvent,
|
||||||
|
} from "../../core/observable/property/list/ListProperty";
|
||||||
|
import { WantedItemModel } from "../model";
|
||||||
|
import { NumberInput } from "../../core/gui/NumberInput";
|
||||||
|
|
||||||
export class WantedItemsView extends ResizableWidget {
|
export class WantedItemsView extends Widget {
|
||||||
private readonly tbody_element = el.tbody();
|
private readonly tbody_element = el.tbody();
|
||||||
private readonly table_disposer = this.disposable(new Disposer());
|
private readonly table_disposer = this.disposable(new Disposer());
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(el.div({ class: "hunt_optimizer_WantedItemsView" }));
|
super(el.div({ class: "hunt_optimizer_WantedItemsView" }));
|
||||||
|
|
||||||
this.element.append(el.h2({ text: "Wanted Items" }), el.table({}, this.tbody_element));
|
this.element.append(
|
||||||
|
el.h2({ text: "Wanted Items" }),
|
||||||
|
el.div(
|
||||||
|
{ class: "hunt_optimizer_WantedItemsView_table_wrapper" },
|
||||||
|
el.table({}, this.tbody_element),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
hunt_optimizer_store.wanted_items.observe_list(this.update_table);
|
hunt_optimizer_store.wanted_items.observe_list(this.update_table, { call_now: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
private update_table = (): void => {
|
private update_table = (change: ListPropertyChangeEvent<WantedItemModel>): void => {
|
||||||
this.tbody_element.append(
|
switch (change.type) {
|
||||||
...hunt_optimizer_store.wanted_items.val.map(wanted_item => {
|
case ListChangeType.Insertion:
|
||||||
const remove_button = this.table_disposer.add(
|
{
|
||||||
new Button("", { icon_left: Icon.Remove }),
|
const rows = change.inserted.map(this.create_row);
|
||||||
);
|
|
||||||
|
|
||||||
return el.tr(
|
if (change.from >= this.tbody_element.childElementCount) {
|
||||||
{},
|
this.tbody_element.append(...rows);
|
||||||
el.td({ text: wanted_item.amount.toString() }),
|
} else {
|
||||||
el.td({ text: wanted_item.item_type.name }),
|
for (let i = change.from; i < change.to; i++) {
|
||||||
el.td({}, remove_button.element),
|
this.tbody_element.children[i].insertAdjacentElement(
|
||||||
);
|
"afterend",
|
||||||
}),
|
rows[i - change.from],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
|
||||||
|
case ListChangeType.Update:
|
||||||
|
// TODO: update row
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private create_row = (wanted_item: WantedItemModel): HTMLTableRowElement => {
|
||||||
|
const amount_input = this.table_disposer.add(
|
||||||
|
new NumberInput(wanted_item.amount.val, { min: 1, step: 1 }),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.table_disposer.add_all(
|
||||||
|
amount_input.value.bind_to(wanted_item.amount),
|
||||||
|
amount_input.value.observe(({ value }) => wanted_item.set_amount(value)),
|
||||||
|
);
|
||||||
|
|
||||||
|
const remove_button = this.table_disposer.add(new Button("", { icon_left: Icon.Remove }));
|
||||||
|
|
||||||
|
return el.tr(
|
||||||
|
{},
|
||||||
|
el.td({}, amount_input.element),
|
||||||
|
el.td({ text: wanted_item.item_type.name }),
|
||||||
|
el.td({}, remove_button.element),
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,26 @@ import { ItemType } from "../../core/model/items";
|
|||||||
import { DifficultyModel, SectionIdModel } from "../../core/model";
|
import { DifficultyModel, SectionIdModel } from "../../core/model";
|
||||||
import { Episode } from "../../core/data_formats/parsing/quest/Episode";
|
import { Episode } from "../../core/data_formats/parsing/quest/Episode";
|
||||||
import { Duration } from "luxon";
|
import { Duration } from "luxon";
|
||||||
|
import { Property } from "../../core/observable/property/Property";
|
||||||
|
import { WritableProperty } from "../../core/observable/property/WritableProperty";
|
||||||
|
import { property } from "../../core/observable";
|
||||||
|
|
||||||
export class WantedItemModel {
|
export class WantedItemModel {
|
||||||
constructor(readonly item_type: ItemType, readonly amount: number) {}
|
readonly item_type: ItemType;
|
||||||
|
readonly amount: Property<number>;
|
||||||
|
|
||||||
|
private readonly _amount: WritableProperty<number>;
|
||||||
|
|
||||||
|
constructor(item_type: ItemType, amount: number) {
|
||||||
|
this.item_type = item_type;
|
||||||
|
this._amount = property(amount);
|
||||||
|
this.amount = this._amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_amount(amount: number): this {
|
||||||
|
this._amount.val = amount;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class OptimalResultModel {
|
export class OptimalResultModel {
|
||||||
|
@ -13,7 +13,7 @@ class HuntOptimizerPersister extends Persister {
|
|||||||
wanted_items.map(
|
wanted_items.map(
|
||||||
({ item_type, amount }): PersistedWantedItem => ({
|
({ item_type, amount }): PersistedWantedItem => ({
|
||||||
itemTypeId: item_type.id,
|
itemTypeId: item_type.id,
|
||||||
amount,
|
amount: amount.val,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -35,7 +35,9 @@ class HuntOptimizerStore {
|
|||||||
readonly wanted_items: ListProperty<WantedItemModel>;
|
readonly wanted_items: ListProperty<WantedItemModel>;
|
||||||
readonly result: Property<OptimalResultModel | undefined>;
|
readonly result: Property<OptimalResultModel | undefined>;
|
||||||
|
|
||||||
private readonly _wanted_items: WritableListProperty<WantedItemModel> = list_property();
|
private readonly _wanted_items: WritableListProperty<WantedItemModel> = list_property(
|
||||||
|
wanted_item => [wanted_item.amount],
|
||||||
|
);
|
||||||
private readonly _result: WritableProperty<OptimalResultModel | undefined> = property(
|
private readonly _result: WritableProperty<OptimalResultModel | undefined> = property(
|
||||||
undefined,
|
undefined,
|
||||||
);
|
);
|
||||||
@ -67,7 +69,7 @@ class HuntOptimizerStore {
|
|||||||
// Initialize this set before awaiting data, so user changes don't affect this optimization
|
// Initialize this set before awaiting data, so user changes don't affect this optimization
|
||||||
// run from this point on.
|
// run from this point on.
|
||||||
const wanted_items = new Set(
|
const wanted_items = new Set(
|
||||||
this.wanted_items.val.filter(w => w.amount > 0).map(w => w.item_type),
|
this.wanted_items.val.filter(w => w.amount.val > 0).map(w => w.item_type),
|
||||||
);
|
);
|
||||||
|
|
||||||
const methods = await hunt_method_stores.current.val.methods.promise;
|
const methods = await hunt_method_stores.current.val.methods.promise;
|
||||||
@ -77,7 +79,7 @@ class HuntOptimizerStore {
|
|||||||
const constraints: { [item_name: string]: { min: number } } = {};
|
const constraints: { [item_name: string]: { min: number } } = {};
|
||||||
|
|
||||||
for (const wanted of this.wanted_items.val) {
|
for (const wanted of this.wanted_items.val) {
|
||||||
constraints[wanted.item_type.name] = { min: wanted.amount };
|
constraints[wanted.item_type.name] = { min: wanted.amount.val };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a variable to the LP model per method per difficulty per section ID.
|
// Add a variable to the LP model per method per difficulty per section ID.
|
||||||
|
Loading…
Reference in New Issue
Block a user