/** * Essa classe representa uma interpretação do padrão de projetos Flux. * No padrão Flux os dados da aplicação são chamados de "estado" e existem * algumas regras para gerenciamento/manipulação desse estado: * * 1 - O estado é imutável. * 2 - Toda modificação de estado é representada por uma "ação". * 3 - Quando "ações" acontecem a "store" cria um novo estado e notifica a todos interessados. * * Nessa interpretação desse design pattern, o StateManager faz o papel da store, * notificando os manipuladores de estado (handlers), que são responsáveis por pedaços * do estado (slices). * * O StateManager mantém dois tipos de estados: "Histórico" e "Não Histórico". No estado * "Histórico", sempre que uma alteração de estado acontece, o estado anterior é guardado em * uma pilha, o que permite que possamos voltar no tempo, desfazendo algumas ações */ export default class StateManager { private _reducers: Array; private _past: Array = []; private _future: Array = []; private _present: any = {}; private _nonHist: any = {}; private _histClean: boolean = false; constructor(reducers: Array) { this._reducers = reducers; } public process(action: StateAction) { const oldPresent: any = this._present; let hasHistChange: boolean = false; this._histClean = false; this._reducers.forEach( reducer => { const sliceName = reducer.sliceName; const isHistoric = this.isHistoric(sliceName); const oldSlice = this.getSlice(sliceName, isHistoric); const newSlice = reducer.reduce(this, oldSlice, action); if (newSlice !== oldSlice) { this.updateSlice(sliceName, newSlice, isHistoric); hasHistChange ||= isHistoric; } } ); if (hasHistChange && !this._histClean) { this._past.push(oldPresent); this._future = []; document.dispatchEvent(new CustomEvent("undoableAction", { detail: this })) } } public select(sliceName: string, selector: (state: any) => any): any { const isHistoric = this.isHistoric(sliceName); return selector(this.getSlice(sliceName, isHistoric)); } private isHistoric(slice: string) { return slice.startsWith("hist::"); } private updateSlice(name: string, slice: any, isHistoric: boolean): void { if (isHistoric) { this._present = { ...this._present, [name]: slice }; if (slice === undefined) { delete this._present[name]; } } else { this._nonHist = { ...this._nonHist, [name]: slice }; if (slice === undefined) { delete this._nonHist[name]; } } } private getSlice(name: string, isHistoric: boolean): any { return isHistoric ? this._present[name] : this._nonHist[name]; } public canUndo(): boolean { return this._past.length > 0; } public canRedo(): boolean { return this._future.length > 0; } public undo() { if (this.canUndo()) { this._future.push(this._present); this._present = this._past.pop(); } } public redo() { if (this.canRedo()) { this._past.push(this._present); this._present = this._future.pop(); } } public clearUndo() { this._histClean = true; this._past = []; this._future = []; } public persist() { } } /** * Baseado no padrão Flux, qualquer alteração de estado parte de uma ação. * Essa ação pode conter um payload ou não. */ export interface StateAction { /** * O tipo da ação representa o que aconteceu. Com base nesse tipo * os manipuladores de estado agem criando um novo estado. */ type: string, /** * Muitas ações contém informações importantes para compor o novo estado. * Isso é chamado de payload. */ payload?: any } export interface ActionReducer { sliceName: string; reduce(stateManager: StateManager, state: any, action: StateAction): any; }