/** * The picture list of a tile (#118): which blocks it is drawn from, and in which order – the * order is the animation, so moving a picture is an edit of its own rather than a cosmetic sort. * The kind, the rate and the playback are ordinary properties and go through `SetAssetPropertyCommand`. */ import type { Project } from '../model/Project'; import { resolvePicture } from '../model/texture'; import { moveItem } from '../util/list'; import { type Command, CommandError, MoveCommand } from './Command'; import { requireTile } from './require'; /** Appends a picture; `at` puts it before the picture with that index instead. */ export class AddTilePictureCommand implements Command { readonly label = 'Add tile picture'; private index = -1; constructor( private readonly assetId: string, private readonly pictureId: string, private readonly at?: number, ) {} execute(project: Project): void { const asset = requireTile(project, this.assetId); // A texture or one of its frames – anything else names no block, and a list of dead ids is // the one thing the tab could not explain to whoever built it. if (!resolvePicture(project, this.pictureId)) throw new CommandError(`${this.pictureId} is not a texture or a frame`); this.index = this.at === undefined ? asset.pictures.length : clampIndex(this.at, asset.pictures.length); asset.pictures.splice(this.index, 0, this.pictureId); } undo(project: Project): void { requireTile(project, this.assetId).pictures.splice(this.index, 1); } } export class RemoveTilePictureCommand implements Command { readonly label = 'Remove tile picture'; private removed = ''; constructor( private readonly assetId: string, private readonly index: number, ) {} execute(project: Project): void { const asset = requireTile(project, this.assetId); const picture = asset.pictures[this.index]; if (picture === undefined) throw new CommandError(`${asset.name} has no picture ${this.index + 1}`); this.removed = picture; asset.pictures.splice(this.index, 1); } undo(project: Project): void { requireTile(project, this.assetId).pictures.splice(this.index, 0, this.removed); } } /** Moves one picture to another place in the list; the step in between is what changes the animation. */ export class MoveTilePictureCommand extends MoveCommand { constructor( private readonly assetId: string, from: number, to: number, ) { super('Reorder tile pictures', from, to); } protected move(project: Project, from: number, to: number): void { const asset = requireTile(project, this.assetId); if (asset.pictures[from] === undefined) throw new CommandError(`${asset.name} has no picture ${from + 1}`); if (!moveItem(asset.pictures, from, to)) throw new CommandError(`${asset.name} has no place ${to + 1} to move it to`); } } function clampIndex(index: number, length: number): number { if (!Number.isFinite(index)) return length; return Math.max(0, Math.min(length, Math.floor(index))); }