@sankhyalabs/core 1.0.20 → 1.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dataunit/DataUnit.d.ts +60 -0
- package/dist/dataunit/DataUnit.js +162 -0
- package/dist/dataunit/DataUnit.js.map +1 -0
- package/dist/dataunit/metadata/DataType.d.ts +9 -0
- package/dist/dataunit/metadata/DataType.js +37 -0
- package/dist/dataunit/metadata/DataType.js.map +1 -0
- package/dist/dataunit/metadata/UnitMetadata.d.ts +49 -0
- package/dist/dataunit/metadata/UnitMetadata.js +26 -0
- package/dist/dataunit/metadata/UnitMetadata.js.map +1 -0
- package/dist/dataunit/state/HistReducer.d.ts +10 -0
- package/dist/dataunit/state/HistReducer.js +28 -0
- package/dist/dataunit/state/HistReducer.js.map +1 -0
- package/dist/dataunit/state/StateManager.d.ts +57 -0
- package/dist/dataunit/state/StateManager.js +109 -0
- package/dist/dataunit/state/StateManager.js.map +1 -0
- package/dist/dataunit/state/action/DataUnitAction.d.ts +26 -0
- package/dist/dataunit/state/action/DataUnitAction.js +32 -0
- package/dist/dataunit/state/action/DataUnitAction.js.map +1 -0
- package/dist/dataunit/state/slice/AddedRecordsSlice.d.ts +10 -0
- package/dist/dataunit/state/slice/AddedRecordsSlice.js +25 -0
- package/dist/dataunit/state/slice/AddedRecordsSlice.js.map +1 -0
- package/dist/dataunit/state/slice/ChangesSlice.d.ts +12 -0
- package/dist/dataunit/state/slice/ChangesSlice.js +73 -0
- package/dist/dataunit/state/slice/ChangesSlice.js.map +1 -0
- package/dist/dataunit/state/slice/CurrentRecordsSlice.d.ts +11 -0
- package/dist/dataunit/state/slice/CurrentRecordsSlice.js +46 -0
- package/dist/dataunit/state/slice/CurrentRecordsSlice.js.map +1 -0
- package/dist/dataunit/state/slice/RecordsSlice.d.ts +10 -0
- package/dist/dataunit/state/slice/RecordsSlice.js +38 -0
- package/dist/dataunit/state/slice/RecordsSlice.js.map +1 -0
- package/dist/dataunit/state/slice/RemovedRecordsSlice.d.ts +9 -0
- package/dist/dataunit/state/slice/RemovedRecordsSlice.js +21 -0
- package/dist/dataunit/state/slice/RemovedRecordsSlice.js.map +1 -0
- package/dist/dataunit/state/slice/SelectionSlice.d.ts +11 -0
- package/dist/dataunit/state/slice/SelectionSlice.js +109 -0
- package/dist/dataunit/state/slice/SelectionSlice.js.map +1 -0
- package/dist/dataunit/state/slice/UnitMetadataSlice.d.ts +11 -0
- package/dist/dataunit/state/slice/UnitMetadataSlice.js +21 -0
- package/dist/dataunit/state/slice/UnitMetadataSlice.js.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/ui/FloatingManager.js +23 -5
- package/dist/ui/FloatingManager.js.map +1 -1
- package/package.json +1 -1
- package/src/dataunit/DataUnit.ts +237 -0
- package/src/dataunit/metadata/DataType.ts +38 -0
- package/src/dataunit/metadata/UnitMetadata.ts +55 -0
- package/src/dataunit/state/HistReducer.ts +34 -0
- package/src/dataunit/state/StateManager.ts +153 -0
- package/src/dataunit/state/action/DataUnitAction.ts +48 -0
- package/src/dataunit/state/slice/AddedRecordsSlice.ts +31 -0
- package/src/dataunit/state/slice/ChangesSlice.ts +91 -0
- package/src/dataunit/state/slice/CurrentRecordsSlice.ts +54 -0
- package/src/dataunit/state/slice/RecordsSlice.ts +50 -0
- package/src/dataunit/state/slice/RemovedRecordsSlice.ts +26 -0
- package/src/dataunit/state/slice/SelectionSlice.ts +128 -0
- package/src/dataunit/state/slice/UnitMetadataSlice.ts +30 -0
- package/src/index.ts +3 -1
- package/src/ui/FloatingManager.ts +28 -7
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import { UnitMetadata, FieldMetadata } from "./metadata/UnitMetadata";
|
|
2
|
+
import { convertType } from "./metadata/DataType";
|
|
3
|
+
|
|
4
|
+
import { DataUnitAction, Action } from "./state/action/DataUnitAction";
|
|
5
|
+
import StateManager from "./state/StateManager";
|
|
6
|
+
|
|
7
|
+
import { HistReducer, canRedo, canUndo } from "./state/HistReducer";
|
|
8
|
+
import { UnitMetadataReducer, getFieldMetadata, getMetadata } from "./state/slice/UnitMetadataSlice";
|
|
9
|
+
import { RecordsReducer } from "./state/slice/RecordsSlice";
|
|
10
|
+
import { SelectionReducer, getSelection, hasNext, hasPrevious } from "./state/slice/SelectionSlice";
|
|
11
|
+
import { ChangesReducer, isDirty, getChangesToSave } from "./state/slice/ChangesSlice";
|
|
12
|
+
import { RemovedRecordsReducer } from "./state/slice/RemovedRecordsSlice";
|
|
13
|
+
import { AddedRecordsReducer } from "./state/slice/AddedRecordsSlice";
|
|
14
|
+
import { CurrentRecordsReducer, getCurrentRecords, getFieldValue } from "./state/slice/CurrentRecordsSlice";
|
|
15
|
+
|
|
16
|
+
export default class DataUnit {
|
|
17
|
+
|
|
18
|
+
private _name: string;
|
|
19
|
+
private _observers: Array<(action: DataUnitAction) => void>;
|
|
20
|
+
private _stateManager: StateManager;
|
|
21
|
+
|
|
22
|
+
public metadataLoader?: (dataUnit: string) => Promise<UnitMetadata>;
|
|
23
|
+
public dataLoader?: (dataUnit: string) => Promise<Array<Record>>;
|
|
24
|
+
public saveLoader?: (dataUnit: string, changes: Array<Change>) => Promise<Array<SavedRecord>>;
|
|
25
|
+
|
|
26
|
+
constructor(name: string) {
|
|
27
|
+
this._name = name;
|
|
28
|
+
this._stateManager = new StateManager(
|
|
29
|
+
[
|
|
30
|
+
HistReducer,
|
|
31
|
+
UnitMetadataReducer,
|
|
32
|
+
RecordsReducer,
|
|
33
|
+
RemovedRecordsReducer,
|
|
34
|
+
AddedRecordsReducer,
|
|
35
|
+
SelectionReducer,
|
|
36
|
+
ChangesReducer,
|
|
37
|
+
CurrentRecordsReducer
|
|
38
|
+
]
|
|
39
|
+
);
|
|
40
|
+
this._observers = [];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
public get name(): string {
|
|
44
|
+
return this._name;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Métodos privados
|
|
48
|
+
private validateAndTypeValue(fieldName: string, newValue: any): any {
|
|
49
|
+
const fmd: FieldMetadata|undefined = getFieldMetadata(this._stateManager, fieldName);
|
|
50
|
+
if (!fmd) {
|
|
51
|
+
throw new Error(`Campo ${fieldName} não encontrado.`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return convertType(fmd.dataType, newValue);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Loaders
|
|
58
|
+
public loadMetadata(): void {
|
|
59
|
+
this.dispatchAction(Action.LOADING_METADATA);
|
|
60
|
+
if (this.metadataLoader) {
|
|
61
|
+
this.metadataLoader(this._name).then(
|
|
62
|
+
metadata => this.dispatchAction(Action.METADATA_LOADED, metadata)
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
public loadData(): void {
|
|
68
|
+
this.dispatchAction(Action.LOADING_DATA);
|
|
69
|
+
if (this.dataLoader) {
|
|
70
|
+
this.dataLoader(this._name).then(
|
|
71
|
+
records => this.dispatchAction(Action.DATA_LOADED, records)
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
public saveData(): void {
|
|
77
|
+
|
|
78
|
+
const changes: Array<Change> = getChangesToSave(this._stateManager);
|
|
79
|
+
|
|
80
|
+
if(changes.length > 0){
|
|
81
|
+
this.dispatchAction(Action.SAVING_DATA);
|
|
82
|
+
if(this.saveLoader){
|
|
83
|
+
this.saveLoader(this._name, changes).then(
|
|
84
|
+
records => this.dispatchAction(Action.DATA_SAVED, {changes, records})
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// API
|
|
91
|
+
public getMetadata(): UnitMetadata {
|
|
92
|
+
return getMetadata(this._stateManager);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
public addRecord(): void {
|
|
96
|
+
this.dispatchAction(Action.RECORDS_ADDED, [{}]);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
public removeSelectedRecords(): void {
|
|
100
|
+
const selection = getSelection(this._stateManager);
|
|
101
|
+
if (selection) {
|
|
102
|
+
this.dispatchAction(Action.RECORDS_REMOVED, selection);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
public getRecords(): Array<Record>|undefined {
|
|
107
|
+
const records = getCurrentRecords(this._stateManager);
|
|
108
|
+
return records ? Array.from(records.values()) : undefined;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
public getFieldValue(fieldName: string): any {
|
|
112
|
+
return getFieldValue(this._stateManager, fieldName);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
public getSelection(): Array<string> {
|
|
116
|
+
return getSelection(this._stateManager);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
public setSelection(selection: Array<string>): void {
|
|
120
|
+
this.dispatchAction(Action.SELECTION_CHANGED, { type: "id", selection });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
public setSelectionByIndex(selection: Array<number>): void {
|
|
124
|
+
this.dispatchAction(Action.SELECTION_CHANGED, { type: "index", selection });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
public nextRecord(): void {
|
|
128
|
+
this.dispatchAction(Action.NEXT_SELECTED);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
public previousRecord(): void {
|
|
132
|
+
this.dispatchAction(Action.PREVIOUS_SELECTED);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
public setFieldValue(fieldName: string, newValue: any): void {
|
|
136
|
+
|
|
137
|
+
const typedValue = this.validateAndTypeValue(fieldName, newValue);
|
|
138
|
+
const currentValue = this.getFieldValue(fieldName);
|
|
139
|
+
|
|
140
|
+
if (currentValue !== typedValue) {
|
|
141
|
+
this.dispatchAction(Action.DATA_CHANGED, { [fieldName]: typedValue });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
public cancelEdition(): void {
|
|
146
|
+
this.dispatchAction(Action.EDITION_CANCELED);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
public isDirty(): boolean {
|
|
150
|
+
return isDirty(this._stateManager);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
public hasNext(): boolean {
|
|
154
|
+
return hasNext(this._stateManager);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
public hasPrevious(): boolean {
|
|
158
|
+
return hasPrevious(this._stateManager);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
public canUndo(): boolean {
|
|
162
|
+
return canUndo(this._stateManager);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
public canRedo(): boolean {
|
|
166
|
+
return canRedo(this._stateManager);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
public undo() {
|
|
170
|
+
this.dispatchAction(Action.CHANGE_UNDONE);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
public redo() {
|
|
174
|
+
this.dispatchAction(Action.CHANGE_REDONE);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Actions / State manager
|
|
178
|
+
private dispatchAction(actionType: Action, payload?: any): void {
|
|
179
|
+
const action = new DataUnitAction(actionType, payload);
|
|
180
|
+
|
|
181
|
+
//interceptor
|
|
182
|
+
this._stateManager.process(action);
|
|
183
|
+
this._observers.forEach(f => f(action));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
public subscribe(observer: (action: DataUnitAction) => void) {
|
|
187
|
+
this._observers.push(observer);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
public unsubscribe(observer: Function) {
|
|
191
|
+
this._observers = this._observers.filter(f => f !== observer);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export interface Record {
|
|
197
|
+
__record__id__: string;
|
|
198
|
+
__parent__record__id__?: string;
|
|
199
|
+
[key: string]: any;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export interface SavedRecord extends Record{
|
|
203
|
+
__old__id__?: string;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export enum ChangeOperation {
|
|
207
|
+
INSERT,
|
|
208
|
+
UPDATE,
|
|
209
|
+
DELETE
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export class Change {
|
|
213
|
+
|
|
214
|
+
public record: Record;
|
|
215
|
+
public updatingFields: any;
|
|
216
|
+
|
|
217
|
+
private _operation: ChangeOperation;
|
|
218
|
+
|
|
219
|
+
constructor(record: Record, updates: any, operation: ChangeOperation) {
|
|
220
|
+
this.record = record;
|
|
221
|
+
this.updatingFields = updates;
|
|
222
|
+
this._operation = operation;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
public isInsert(): boolean {
|
|
226
|
+
return this._operation === ChangeOperation.INSERT;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
public isDelete(): boolean {
|
|
230
|
+
return this._operation === ChangeOperation.DELETE;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
public isUpdate(): boolean {
|
|
234
|
+
return this._operation === ChangeOperation.UPDATE;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export enum DataType{
|
|
2
|
+
NUMBER = "NUMBER",
|
|
3
|
+
DATE = "DATE",
|
|
4
|
+
TEXT = "TEXT",
|
|
5
|
+
BOOLEAN = "BOOLEAN",
|
|
6
|
+
JSON = "JSON"
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export const convertType = (dataType:DataType, value: any): any => {
|
|
10
|
+
if(value === undefined || value === null){
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
switch (dataType){
|
|
14
|
+
case DataType.NUMBER:
|
|
15
|
+
return value === "" || isNaN(value) ? null : Number(value);
|
|
16
|
+
case DataType.JSON:
|
|
17
|
+
return JSON.parse(value);
|
|
18
|
+
case DataType.BOOLEAN:
|
|
19
|
+
return Boolean(value);
|
|
20
|
+
case DataType.DATE:
|
|
21
|
+
return new Date(value.toString())
|
|
22
|
+
default:
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const toString = (dataType:DataType, value: any): string => {
|
|
28
|
+
|
|
29
|
+
if(value === undefined || value === null){
|
|
30
|
+
return value;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if(dataType === DataType.JSON){
|
|
34
|
+
return JSON.stringify(value);
|
|
35
|
+
} else {
|
|
36
|
+
return value.toString();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { DataType } from "./DataType";
|
|
2
|
+
|
|
3
|
+
export interface UnitMetadata{
|
|
4
|
+
name: string;
|
|
5
|
+
label: string;
|
|
6
|
+
fields: Array<FieldMetadata>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface FieldMetadata {
|
|
10
|
+
name: string;
|
|
11
|
+
label: string;
|
|
12
|
+
dataType: DataType;
|
|
13
|
+
userInterface?: UserInterface;
|
|
14
|
+
defaultValue?: string;
|
|
15
|
+
readOnly?: boolean;
|
|
16
|
+
required?: boolean;
|
|
17
|
+
properties?: Array<UIProperty>;
|
|
18
|
+
dependencies?: Array<FieldDependency>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface UIProperty {
|
|
22
|
+
name: string;
|
|
23
|
+
value: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface FieldDependency {
|
|
27
|
+
masterFields: Array<string>;
|
|
28
|
+
type: DependencyType;
|
|
29
|
+
expression: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export enum DependencyType {
|
|
33
|
+
SEARCHING = "SEARCHING",
|
|
34
|
+
REQUIREMENT = "REQUIREMENT",
|
|
35
|
+
VISIBILITY = "REQUIREMENT"
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export enum UserInterface {
|
|
39
|
+
FILE = "FILE",
|
|
40
|
+
IMAGE = "IMAGE",
|
|
41
|
+
DATE = "DATE",
|
|
42
|
+
DATETIME = "DATETIME",
|
|
43
|
+
ELAPSEDTIME = "ELAPSEDTIME",
|
|
44
|
+
CHECKBOX = "CHECKBOX",
|
|
45
|
+
SWITCH = "SWITCH",
|
|
46
|
+
OPTIONSELECTOR = "OPTIONSELECTOR",
|
|
47
|
+
DECIMALNUMBER = "DECIMALNUMBER",
|
|
48
|
+
INTEGERNUMBER = "INTEGERNUMBER",
|
|
49
|
+
SEARCH = "SEARCH",
|
|
50
|
+
SHORTTEXT = "SHORTTEXT",
|
|
51
|
+
PASSWORD = "PASSWORD",
|
|
52
|
+
MASKEDTEXT = "MASKEDTEXT",
|
|
53
|
+
LONGTEXT = "LONGTEXT",
|
|
54
|
+
HTML = "HTML"
|
|
55
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { ActionReducer } from "./StateManager";
|
|
2
|
+
import { Action } from "./action/DataUnitAction";
|
|
3
|
+
import StateManager, {StateAction} from "./StateManager";
|
|
4
|
+
|
|
5
|
+
class HistReducerImpl implements ActionReducer {
|
|
6
|
+
|
|
7
|
+
public sliceName: string = "";
|
|
8
|
+
|
|
9
|
+
public reduce(stateManager: StateManager, _currentState: void, action: StateAction): void {
|
|
10
|
+
|
|
11
|
+
switch (action.type) {
|
|
12
|
+
case Action.DATA_SAVED:
|
|
13
|
+
case Action.EDITION_CANCELED:
|
|
14
|
+
stateManager.clearUndo();
|
|
15
|
+
break;
|
|
16
|
+
case Action.CHANGE_UNDONE:
|
|
17
|
+
stateManager.undo();
|
|
18
|
+
break;
|
|
19
|
+
case Action.CHANGE_REDONE:
|
|
20
|
+
stateManager.redo();
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const HistReducer = new HistReducerImpl();
|
|
27
|
+
|
|
28
|
+
export const canUndo = (stateManager: StateManager): boolean => {
|
|
29
|
+
return stateManager.canUndo();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const canRedo = (stateManager: StateManager): boolean =>{
|
|
33
|
+
return stateManager.canRedo();
|
|
34
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Essa classe representa uma interpretação do padrão de projetos Flux.
|
|
3
|
+
* No padrão Flux os dados da aplicação são chamados de "estado" e existem
|
|
4
|
+
* algumas regras para gerenciamento/manipulação desse estado:
|
|
5
|
+
*
|
|
6
|
+
* 1 - O estado é imutável.
|
|
7
|
+
* 2 - Toda modificação de estado é representada por uma "ação".
|
|
8
|
+
* 3 - Quando "ações" acontecem a "store" cria um novo estado e notifica a todos interessados.
|
|
9
|
+
*
|
|
10
|
+
* Nessa interpretação desse design pattern, o StateManager faz o papel da store,
|
|
11
|
+
* notificando os manipuladores de estado (handlers), que são responsáveis por pedaços
|
|
12
|
+
* do estado (slices).
|
|
13
|
+
*
|
|
14
|
+
* O StateManager mantém dois tipos de estados: "Histórico" e "Não Histórico". No estado
|
|
15
|
+
* "Histórico", sempre que uma alteração de estado acontece, o estado anterior é guardado em
|
|
16
|
+
* uma pilha, o que permite que possamos voltar no tempo, desfazendo algumas ações
|
|
17
|
+
*/
|
|
18
|
+
export default class StateManager {
|
|
19
|
+
|
|
20
|
+
private _reducers: Array<ActionReducer>;
|
|
21
|
+
|
|
22
|
+
private _past: Array<any> = [];
|
|
23
|
+
private _future: Array<any> = [];
|
|
24
|
+
private _present: any = {};
|
|
25
|
+
|
|
26
|
+
private _nonHist: any = {};
|
|
27
|
+
private _histClean: boolean = false;
|
|
28
|
+
|
|
29
|
+
constructor(reducers: Array<ActionReducer>) {
|
|
30
|
+
this._reducers = reducers;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
public process(action: StateAction) {
|
|
34
|
+
|
|
35
|
+
const oldPresent: any = this._present;
|
|
36
|
+
let hasChange: boolean = false;
|
|
37
|
+
let hasHistChange: boolean = false;
|
|
38
|
+
this._histClean = false;
|
|
39
|
+
|
|
40
|
+
this._reducers.forEach(
|
|
41
|
+
reducer => {
|
|
42
|
+
const sliceName = reducer.sliceName;
|
|
43
|
+
const isHistoric = this.isHistoric(sliceName);
|
|
44
|
+
const oldSlice = this.getSlice(sliceName, isHistoric);
|
|
45
|
+
const newSlice = reducer.reduce(this, oldSlice, action);
|
|
46
|
+
if (newSlice !== oldSlice) {
|
|
47
|
+
this.updateSlice(sliceName, newSlice, isHistoric);
|
|
48
|
+
hasChange = true;
|
|
49
|
+
hasHistChange ||= isHistoric;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
if (hasHistChange && !this._histClean) {
|
|
55
|
+
this._past.push(oldPresent);
|
|
56
|
+
this._future = [];
|
|
57
|
+
document.dispatchEvent(new CustomEvent("undoableAction", { detail: this }))
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (hasChange) {
|
|
61
|
+
console.log(JSON.stringify(this, (_key, value) => {
|
|
62
|
+
if (value instanceof Map) {
|
|
63
|
+
return { dataType: "Map", value: Array.from(value.entries()) };
|
|
64
|
+
} else {
|
|
65
|
+
return value;
|
|
66
|
+
}
|
|
67
|
+
}, 3));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
public select(sliceName: string, selector: (state: any) => any): any {
|
|
72
|
+
const isHistoric = this.isHistoric(sliceName);
|
|
73
|
+
return selector(this.getSlice(sliceName, isHistoric));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private isHistoric(slice: string) {
|
|
77
|
+
return slice.startsWith("hist::");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private updateSlice(name: string, slice: any, isHistoric: boolean): void {
|
|
81
|
+
if (isHistoric) {
|
|
82
|
+
this._present = { ...this._present, [name]: slice };
|
|
83
|
+
if (slice === undefined) {
|
|
84
|
+
delete this._present[name];
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
this._nonHist = { ...this._nonHist, [name]: slice };
|
|
88
|
+
if (slice === undefined) {
|
|
89
|
+
delete this._nonHist[name];
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private getSlice(name: string, isHistoric: boolean): any {
|
|
95
|
+
return isHistoric ? this._present[name] : this._nonHist[name];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
public canUndo(): boolean {
|
|
99
|
+
return this._past.length > 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public canRedo(): boolean {
|
|
103
|
+
return this._future.length > 0;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
public undo() {
|
|
107
|
+
if (this.canUndo()) {
|
|
108
|
+
this._future.push(this._present);
|
|
109
|
+
this._present = this._past.pop();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
public redo() {
|
|
114
|
+
if (this.canRedo()) {
|
|
115
|
+
this._past.push(this._present);
|
|
116
|
+
this._present = this._future.pop();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
public clearUndo() {
|
|
121
|
+
this._histClean = true;
|
|
122
|
+
this._past = [];
|
|
123
|
+
this._future = [];
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
public persist() {
|
|
127
|
+
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Baseado no padrão Flux, qualquer alteração de estado parte de uma ação.
|
|
134
|
+
* Essa ação pode conter um payload ou não.
|
|
135
|
+
*/
|
|
136
|
+
export interface StateAction {
|
|
137
|
+
/**
|
|
138
|
+
* O tipo da ação representa o que aconteceu. Com base nesse tipo
|
|
139
|
+
* os manipuladores de estado agem criando um novo estado.
|
|
140
|
+
*/
|
|
141
|
+
type: string,
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Muitas ações contém informações importantes para compor o novo estado.
|
|
145
|
+
* Isso é chamado de payload.
|
|
146
|
+
*/
|
|
147
|
+
payload?: any
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export interface ActionReducer {
|
|
151
|
+
sliceName: string;
|
|
152
|
+
reduce(stateManager: StateManager, state: any, action: StateAction): any;
|
|
153
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
|
|
2
|
+
import { StateAction } from "../StateManager";
|
|
3
|
+
|
|
4
|
+
export class DataUnitAction implements StateAction{
|
|
5
|
+
|
|
6
|
+
private _type: Action;
|
|
7
|
+
private _payload: any;
|
|
8
|
+
|
|
9
|
+
constructor(type: Action, payload?:any){
|
|
10
|
+
this._type = type;
|
|
11
|
+
this._payload = payload;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
public get type(): Action{
|
|
15
|
+
return this._type;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public get payload(): any{
|
|
19
|
+
return this._payload;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export enum Action{
|
|
24
|
+
|
|
25
|
+
LOADING_METADATA = "loadingMetadata",
|
|
26
|
+
METADATA_LOADED = "metadataLoaded",
|
|
27
|
+
|
|
28
|
+
LOADING_DATA = "loadingData",
|
|
29
|
+
DATA_LOADED = "dataLoaded",
|
|
30
|
+
|
|
31
|
+
SAVING_DATA = "savingData",
|
|
32
|
+
DATA_SAVED = "dataSaved",
|
|
33
|
+
|
|
34
|
+
RECORDS_REMOVED = "recordsRemoved",
|
|
35
|
+
RECORDS_ADDED = "recordsAdded",
|
|
36
|
+
DATA_CHANGED = "dataChanged",
|
|
37
|
+
|
|
38
|
+
EDITION_CANCELED = "editionCanceled",
|
|
39
|
+
CHANGE_UNDONE = "changeUndone",
|
|
40
|
+
CHANGE_REDONE = "changeRedone",
|
|
41
|
+
|
|
42
|
+
SELECTION_CHANGED = "selectionChanged",
|
|
43
|
+
NEXT_SELECTED = "nextSelected",
|
|
44
|
+
PREVIOUS_SELECTED = "previousSelected",
|
|
45
|
+
|
|
46
|
+
STATE_CHANGED = "stateChanged"
|
|
47
|
+
|
|
48
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
|
|
2
|
+
import { ActionReducer, StateAction } from "../StateManager";
|
|
3
|
+
import { Action } from "../action/DataUnitAction";
|
|
4
|
+
import StateManager from "../StateManager";
|
|
5
|
+
import { Record } from "../../DataUnit";
|
|
6
|
+
|
|
7
|
+
class AddedRecordsReducerImpl implements ActionReducer{
|
|
8
|
+
|
|
9
|
+
public sliceName: string = "hist::addedRecords";
|
|
10
|
+
|
|
11
|
+
public reduce(_stateManager:StateManager, currentState: Array<Record>, action: StateAction): Array<Record>|undefined {
|
|
12
|
+
switch(action.type){
|
|
13
|
+
case Action.RECORDS_ADDED:
|
|
14
|
+
const newState:Array <Record> = currentState ? currentState.slice() : [];
|
|
15
|
+
action.payload.forEach((r: any) => {
|
|
16
|
+
newState.push({__record__id__: "NEW_" + (newState.length + 1), ...r});
|
|
17
|
+
});
|
|
18
|
+
return newState;
|
|
19
|
+
case Action.DATA_SAVED:
|
|
20
|
+
case Action.EDITION_CANCELED:
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
return currentState;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const AddedRecordsReducer = new AddedRecordsReducerImpl();
|
|
28
|
+
|
|
29
|
+
export const getAddedRecords = (stateManager: StateManager): Array<Record> => {
|
|
30
|
+
return stateManager.select(AddedRecordsReducer.sliceName, (state: Array<Record>) => state);
|
|
31
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
|
|
2
|
+
import { ActionReducer, StateAction } from "../StateManager";
|
|
3
|
+
import { Action } from "../action/DataUnitAction";
|
|
4
|
+
import StateManager from "../StateManager";
|
|
5
|
+
import { getSelection } from "./SelectionSlice";
|
|
6
|
+
import { getRecords } from "./RecordsSlice";
|
|
7
|
+
import { getRemovedRecords } from "./RemovedRecordsSlice";
|
|
8
|
+
import { getAddedRecords } from "./AddedRecordsSlice";
|
|
9
|
+
import { Change, ChangeOperation } from "../../DataUnit";
|
|
10
|
+
|
|
11
|
+
class ChangesReducerImpl implements ActionReducer{
|
|
12
|
+
|
|
13
|
+
public sliceName: string = "hist::changes";
|
|
14
|
+
|
|
15
|
+
public reduce(stateManager:StateManager, currentState: Map<string, any>, action: StateAction): Map<string, any>|undefined {
|
|
16
|
+
switch(action.type){
|
|
17
|
+
|
|
18
|
+
case Action.DATA_CHANGED:
|
|
19
|
+
const selection = getSelection(stateManager);
|
|
20
|
+
if(selection){
|
|
21
|
+
const newState = new Map(currentState);
|
|
22
|
+
selection.forEach(recordId => {
|
|
23
|
+
newState.set(recordId, {...newState.get(recordId), ...action.payload});
|
|
24
|
+
});
|
|
25
|
+
return newState;
|
|
26
|
+
}
|
|
27
|
+
return currentState;
|
|
28
|
+
|
|
29
|
+
case Action.DATA_SAVED:
|
|
30
|
+
case Action.EDITION_CANCELED:
|
|
31
|
+
return undefined;
|
|
32
|
+
}
|
|
33
|
+
return currentState;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const ChangesReducer = new ChangesReducerImpl();
|
|
39
|
+
|
|
40
|
+
export const getChanges = (stateManager: StateManager): Map<string, any> => {
|
|
41
|
+
return stateManager.select(ChangesReducer.sliceName, (state: Map<string, any>) => state);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const isDirty = (stateManager: StateManager): boolean => {
|
|
45
|
+
|
|
46
|
+
if (getAddedRecords(stateManager) !== undefined) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (getRemovedRecords(stateManager) !== undefined) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return getChanges(stateManager) !== undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export const getChangesToSave = (stateManager: StateManager): Array<Change> => {
|
|
58
|
+
const result: Array<Change> = [];
|
|
59
|
+
|
|
60
|
+
const changes = getChanges(stateManager);
|
|
61
|
+
const records = getRecords(stateManager);
|
|
62
|
+
|
|
63
|
+
if (records) {
|
|
64
|
+
records.forEach(r => {
|
|
65
|
+
if(changes){
|
|
66
|
+
const c = changes.get(r.__record__id__);
|
|
67
|
+
if (c) {
|
|
68
|
+
result.push(new Change(r, c, ChangeOperation.UPDATE));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const addedRecords = getAddedRecords(stateManager);
|
|
75
|
+
if (addedRecords) {
|
|
76
|
+
addedRecords.forEach(r => {
|
|
77
|
+
result.push(new Change(r, changes?.get(r.__record__id__), ChangeOperation.INSERT));
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const removedRecords = getRemovedRecords(stateManager);
|
|
82
|
+
const recordsById: any = {};
|
|
83
|
+
records.forEach(r=>recordsById[r.__record__id__] = r);
|
|
84
|
+
if (removedRecords) {
|
|
85
|
+
removedRecords.forEach(id => {
|
|
86
|
+
result.push(new Change(recordsById[id], undefined, ChangeOperation.DELETE));
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return result;
|
|
91
|
+
}
|