import { Step } from "./Step" ; import { EditCommand } from "./commands/EditCommand" ; import { FieldCommand } from "./commands/FieldCommand" ; import { MoveCommand } from "./commands/MoveCommand" ; import { shrewdStatic } from "bp/global" ; import { Disposable } from "bp/class" ; import type { IDesignLike , ITagObject } from "bp/content/interface" ; import type { JHistory , Memento } from "bp/content/json" ; import type { Command } from "./commands/Command" ; import type { TreeElement } from "bp/content/context" ; import type { IPoint } from "bp/math" ; import type { Control , Draggable } from "bp/design/class" ; /**
 *  HistoryService 負責管理使用者的操作歷史紀錄。
  @ shrewd export class HistoryService extends Disposable implements ISerializable < JHistory > { public static $create ( design , json ? ) { if ( ! HistoryService . _init ) return null ; return new HistoryService ( design , json ) ; } public static $initialize ( ) { HistoryService . _init = true ; } private static _init : boolean = false ; private static readonly _MAX_STEP = 30 ; private readonly _design : IDesignLike ; @ shrewd private readonly _steps : Step [ ] = [ ] ; @ shrewd private _index : number = 0 ; @ shrewd private _savedIndex : number = 0 ; private _queue : Command [ ] = [ ] ; private _construct : Memento [ ] = [ ] ; private _destruct : Memento [ ] = [ ] ; /** 最後已知的選取  Control 的 tag  private _selection : string [ ] = [ ] ; /** 是否正在移動歷史 */ private _moving : boolean = true ; private constructor ( design , json ? ) { super ( design instanceof Disposable ? design : undefined ) ; this . _design = design ; if ( json ) { try { this . _steps . push ( ... json . steps . map ( s => Step . restore ( design , s ) ) ) ; this . _index = json . index ; this . _savedIndex = json . savedIndex ; } catch ( e ) { } } } public toJSON ( ) { return { index : this . _index , savedIndex : this . _savedIndex , steps : this . _steps . map ( s => s . toJSON ( ) ) , } ; } private _enqueue ( command ) { if ( this . _moving ) return ; for ( let q of this . _queue ) { if ( command . $canAddTo ( q ) ) return command . $addTo ( q ) ; } this . _queue . push ( command ) ; } public $construct ( memento ) { if ( this . _moving ) return ; this . _construct . push ( memento ) ; } public $destruct ( memento ) { if ( this . _moving ) return ; this . _destruct . push ( memento ) ; } public $move ( target , loc , relative = true ) { let command = MoveCommand . $create ( target , loc , relative ) ; this . _enqueue ( command ) ; } public $add ( target ) { this . _enqueue ( EditCommand . $add ( target ) ) ; } public $remove ( target ) { this . _enqueue ( EditCommand . $remove ( target ) ) ; } /**
     * 處理累積的操作並且整理成  Step 物件。
     *
     *  param selection public $flush ( selection ) { let sel = selection . map ( c => c . $tag ) ; if ( this . _queue . length ) { let s = this . _lastStep ; if ( ! s || ! s . $tryAdd ( this . _queue , this . _construct , this . _destruct ) ) { let step = new Step ( this . _design , { commands : this . _queue , construct : this . _construct , destruct : this . _destruct , mode : this . _design . mode ?? "layout" , before : this . _selection , after : sel , } ) ; if ( ! step . $isVoid ) this . _addStep ( step ) ; } else if ( s . $isVoid ) { this . _steps . pop ( ) ; this . _index -- ; } this . _queue = [ ] ; this . _construct = [ ] ; this . _destruct = [ ] ; } this . _selection = sel ; this . _moving = false ; } /**
     * 自從上次存檔以來是否有經過修改。
     *
     * 這個必須是反應方法才能在 UI 上頭反應。
     */ @ shrewdStatic public get $modified ( ) : boolean { return this . _savedIndex != this . _index ; } public $notifySave ( ) { this . _savedIndex = this . _index ; } private _addStep ( step ) { if ( this . _steps . length > this . _index ) this . _steps . length = this . _index ; this . _steps [ this . _index ++ ] = step ; if ( this . _steps . length > HistoryService . _MAX_STEP ) { this . _steps . shift ( ) ; this . _index -- ; this . _savedIndex -- ; } } private get _lastStep ( ) : Step | undefined { if ( this . _index == 0 || this . _index < this . _steps . length ) return undefined ; return this . _steps [ this . _index - 1 ] ; } public $fieldChange ( target , prop , oldValue , newValue ) { if ( this . _moving ) return ; this . _enqueue ( FieldCommand . create ( target , prop , oldValue , newValue ) ) ; } @ shrewdStatic public get $canUndo ( ) : boolean { return this . _index > 0 ; } @ shrewdStatic public get $canRedo ( ) : boolean { return this . _index < this . _steps . length ; } public $undo ( ) { if ( this . $canUndo ) { this . _moving = true ; this . _steps [ -- this . _index ] . $undo ( ) ; } } public $redo ( ) { if ( this . $canRedo ) { this . _moving = true ; this . _steps [ this . _index ++ ] . $redo ( ) ; } } }