From fd347e7a624e1a197a715a8d64823437df7d3baf Mon Sep 17 00:00:00 2001 From: hypercross Date: Mon, 20 Apr 2026 19:50:25 +0800 Subject: [PATCH] refactor: use InventoryItemState in InventoryItemContainer Replace local signal-based state management with the dedicated `InventoryItemState` class to encapsulate item properties and logic. --- .../src/gameobjects/InventoryItemContainer.ts | 58 +++++++------------ .../src/state/InventoryItemState.ts | 44 ++++++++++++++ 2 files changed, 65 insertions(+), 37 deletions(-) create mode 100644 packages/sts-like-viewer/src/state/InventoryItemState.ts diff --git a/packages/sts-like-viewer/src/gameobjects/InventoryItemContainer.ts b/packages/sts-like-viewer/src/gameobjects/InventoryItemContainer.ts index b800e72..23b099d 100644 --- a/packages/sts-like-viewer/src/gameobjects/InventoryItemContainer.ts +++ b/packages/sts-like-viewer/src/gameobjects/InventoryItemContainer.ts @@ -1,6 +1,6 @@ import Phaser from "phaser"; import { dragDropEventEffect, DragDropEventType } from "boardgame-phaser"; -import { GRID_CONFIG, ITEM_COLORS } from "@/config"; +import { GRID_CONFIG } from "@/config"; import { IDENTITY_TRANSFORM, ParsedShape, @@ -8,15 +8,15 @@ import { transformShape, type GameItem, } from "boardgame-core/samples/slay-the-spire-like"; -import { computed, signal, Signal } from "@preact/signals-core"; import { DisposableBag } from "../../../framework/dist"; +import { InventoryItemState } from "@/state/InventoryItemState"; export interface InventoryItemContainerCallbacks { onMoveItem: (itemId: string, newX: number, newY: number) => boolean; } export class InventoryItemContainer extends Phaser.GameObjects.Container { - private item: Signal; + private itemState: InventoryItemState; constructor( scene: Phaser.Scene, @@ -36,43 +36,34 @@ export class InventoryItemContainer extends Phaser.GameObjects.Container { ); this.add([graphics, label]); - this.item = signal(); + this.itemState = new InventoryItemState(); const disposables = new DisposableBag(this); - const itemName = computed(() => { - const item = this.item.value; - return item?.meta?.itemData.name ?? item?.id ?? ""; - }); disposables.addEffect(() => { - label.setText(itemName.value); + label.setText(this.itemState.name.value); }); - const shape = computed(() => { - return this.item.value?.shape; - }); - const color = computed(() => { - return this.getItemColor(this.item.value?.id ?? ""); - }); disposables.addEffect(() => { graphics.clear(); - if (!shape.value) return; - const cellRects = this.renderGraphics(graphics, shape.value, color.value); + if (!this.itemState.shape.value) return; + const cellRects = this.renderGraphics( + graphics, + this.itemState.shape.value, + this.itemState.color.value, + ); this.setupInteractive(cellRects); }); - const transform = computed(() => { - return this.item.value?.transform; - }); disposables.addEffect(() => { - if (!transform.value) return; - this.snapBack(transform.value); + if (!this.itemState.transform.value) return; + this.snapBack(this.itemState.transform.value); }); } - setItem(item: GameItem) { - this.item.value = item; + setItem(item: GameItem): void { + this.itemState.setItem(item); } renderGraphics( @@ -101,11 +92,6 @@ export class InventoryItemContainer extends Phaser.GameObjects.Container { return cellRects; } - private getItemColor(itemId: string): number { - const hash = itemId.split("").reduce((acc, c) => acc + c.charCodeAt(0), 0); - return ITEM_COLORS[hash % ITEM_COLORS.length]; - } - private setupInteractive(cellRects: { x: number; y: number }[]): void { this.setScrollFactor(0); const cellSize = GRID_CONFIG.VIEWER_CELL_SIZE; @@ -115,15 +101,14 @@ export class InventoryItemContainer extends Phaser.GameObjects.Container { hitArea: { x: number; y: number }[], x: number, y: number, - ) => { - return hitArea.some( + ) => + hitArea.some( (cell) => x >= cell.x && x < cell.x + cellSize && y >= cell.y && y < cell.y + cellSize, - ); - }, + ), useHandCursor: true, } as Phaser.Types.Input.InputConfiguration); @@ -140,7 +125,7 @@ export class InventoryItemContainer extends Phaser.GameObjects.Container { } else if (event.type === DragDropEventType.UP) { this.setAlpha(1); if (!this.handleDragEnd()) { - const t = this.item.peek()?.transform; + const t = this.itemState.transform.peek(); t && this.snapBack(t); } startX = startY = 0; @@ -149,9 +134,8 @@ export class InventoryItemContainer extends Phaser.GameObjects.Container { } private handleDragEnd(): boolean { - const item = this.item.value; + const item = this.itemState.item; if (!item) return false; - const itemId = item.id; const cellSize = GRID_CONFIG.VIEWER_CELL_SIZE; const shapeWidth = item.shape?.width ?? 1; @@ -169,7 +153,7 @@ export class InventoryItemContainer extends Phaser.GameObjects.Container { clampedX !== item.transform.offset.x || clampedY !== item.transform.offset.y ) { - return this.callbacks.onMoveItem(itemId, clampedX, clampedY); + return this.callbacks.onMoveItem(item.id, clampedX, clampedY); } return false; } diff --git a/packages/sts-like-viewer/src/state/InventoryItemState.ts b/packages/sts-like-viewer/src/state/InventoryItemState.ts new file mode 100644 index 0000000..65f6f35 --- /dev/null +++ b/packages/sts-like-viewer/src/state/InventoryItemState.ts @@ -0,0 +1,44 @@ +import { computed, signal, Signal } from "@preact/signals-core" +import { ITEM_COLORS } from "@/config" +import { + type GameItem, + type ParsedShape, + type Transform2D, +} from "boardgame-core/samples/slay-the-spire-like" + +export class InventoryItemState { + private readonly _item: Signal + + readonly name: Signal + readonly shape: Signal + readonly color: Signal + readonly transform: Signal + + constructor(initialItem?: GameItem) { + this._item = signal(initialItem) + + this.name = computed(() => { + const item = this._item.value + return item?.meta?.itemData.name ?? item?.id ?? "" + }) + + this.shape = computed(() => this._item.value?.shape) + + this.color = computed(() => this.computeColor(this._item.value?.id ?? "")) + + this.transform = computed(() => this._item.value?.transform) + } + + get item(): GameItem | undefined { + return this._item.value + } + + setItem(item: GameItem): void { + this._item.value = item + } + + private computeColor(itemId: string): number { + const hash = itemId.split("").reduce((acc, c) => acc + c.charCodeAt(0), 0) + return ITEM_COLORS[hash % ITEM_COLORS.length] + } +}