Skip to content

Commit

Permalink
Replace redux by rematch and simplify stores
Browse files Browse the repository at this point in the history
  • Loading branch information
Sascha Goldhofer committed Aug 19, 2020
1 parent 470752e commit 26b47b1
Show file tree
Hide file tree
Showing 35 changed files with 390 additions and 491 deletions.
2 changes: 1 addition & 1 deletion app/array/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ const data = {
const $editor = document.querySelector(".editor") as HTMLElement;
const editron = new Controller(schema, data, {
plugins: [
new SortablePlugin({})
new SortablePlugin()
]
});
editron.createEditor("#", $editor);
Expand Down
4 changes: 2 additions & 2 deletions app/object/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@ const editron = new Controller(schema, data, {
],
},
plugins: [
new RemoteDataPlugin({}),
new SyncPlugin({})
new RemoteDataPlugin(),
new SyncPlugin()
]
});
editron.createEditor("#", $editor);
Expand Down
2 changes: 1 addition & 1 deletion app/sortablearray/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ const data = {
const $editor = document.querySelector(".editor") as HTMLElement;
const editron = new Controller(schema, data, {
plugins: [
new SortablePlugin({})
new SortablePlugin()
]
});
editron.createEditor("#", $editor);
Expand Down
7 changes: 6 additions & 1 deletion editron.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ import Controller from "./src/Controller";

export default Controller;
export * as components from "./src/components";
export * as services from "./src/services";

export { default as DataService } from "./src/services/DataService";
export { default as LocationService } from "./src/services/LocationService";
export { default as SchemaService } from "./src/services/SchemaService";
export { default as ValidationService } from "./src/services/ValidationService";

export * as utils from "./src/utils";
export { default as AbstractEditor } from "./src/editors/AbstractEditor";
export { default as AbstractValueEditor } from "./src/editors/AbstractValueEditor";
Expand Down
2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,6 @@
"mithril": "2.x",
"mithril-material-forms": "^3.2.2",
"nanoevents": "5.1.8",
"redux": "^3.6.0",
"redux-undo": "^0.6.1",
"sortablejs": "1.10.2",
"with-observable": "^1.2.0"
}
Expand Down
10 changes: 5 additions & 5 deletions src/Controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import LocationService from "./services/LocationService";
import plugin, { Plugin } from "./plugin";
import SchemaService from "./services/SchemaService";
import selectEditor from "./utils/selectEditor";
import State from "./services/State";
import Store from "./store";
import UISchema from "./utils/UISchema";
import ValidationService from "./services/ValidationService";
import { Editor, EditorPlugin, SetEnabledEvent } from "./editors/Editor";
Expand Down Expand Up @@ -95,7 +95,7 @@ export default class Controller {
/** list instantiated services */
services: Services;
/** current state of errors, ui and data */
state: State;
store: Store;
/** editron proxy instance */
#proxy: Foxy;

Expand Down Expand Up @@ -126,7 +126,7 @@ export default class Controller {
};

this.editors = this.options.editors;
this.state = new State();
this.store = new Store();
this.core = new Core();
this.#proxy = createProxy(this.options.proxy);

Expand Down Expand Up @@ -155,9 +155,9 @@ export default class Controller {
this.services = {
instances: new InstanceService(this),
location: new LocationService(),
data: new DataService(this.state, data),
data: new DataService(this.store, data),
schema: schemaService,
validation: new ValidationService(this.state, schema, this.core)
validation: new ValidationService(this.store, schema, this.core)
};

this.service("data").watch(event => {
Expand Down
1 change: 1 addition & 0 deletions src/editors/arrayeditor/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ export default class ArrayEditor extends AbstractEditor {
}

applyPatches(patch: Patch): void {
console.log("array apply patch", this.pointer, patch);
const { pointer, controller, viewModel, children, $items } = this;

// fetch a copy of the original list
Expand Down
60 changes: 32 additions & 28 deletions src/services/DataService.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import copy from "./utils/copy";
import dataReducer from "./reducers/dataReducer";
import diffpatch from "./utils/diffpatch";
import getParentPointer from "./utils/getParentPointer";
import createDiff, { Patch, PatchResult } from "./utils/createDiff";
import getTypeOf from "json-schema-library/lib/getTypeOf";
import gp from "gson-pointer";
import isRootPointer from "./utils/isRootPointer";
import State from "./State";
import { ActionTypes, ActionCreators } from "./reducers/actions";
import Store from "../store";
import { JSONData, JSONPointer } from "../types";
import { isPointer } from "../utils/UISchema";
import { UpdateDataEvent } from "../editors/Editor";

const DEBUG = false;
const ID = "data";


export type AddChange = {
Expand Down Expand Up @@ -129,32 +128,31 @@ function getObjectChangeList(patchResult: PatchResult): Array<Change> {
return changeList;
}


export default class DataService {
/** state store-id of service */
id = "data";
/** current state */
state: State;
store: Store;
/** current observers on json-pointer changes */
observers: { [pointer: string]: Array<Observer> } = {};
/** internal value to track previous data */
lastUpdate = {};
/** list of active watchers on update-lifecycle events */
watcher = [];


/**
* Read and modify form data and notify observers
* @param state - current state/store of application
* @param data - current application data (form)
*/
constructor(state: State, data?: JSONData) {
if (!(state instanceof State)) {
constructor(store: Store, data?: JSONData) {
if (!(store instanceof Store)) {
throw new Error("Given state in DataService must be of instance 'State'");
}

this.state = state;
this.state.register(this.id, dataReducer);
this.store = store;
this.onStateChanged = this.onStateChanged.bind(this);
this.state.subscribe(this.id, this.onStateChanged);
this.store.subscribe(ID, this.onStateChanged);

if (data !== undefined) {
this.set("#", data);
Expand All @@ -164,8 +162,8 @@ export default class DataService {

// improved version - supporting multiple patches
onStateChanged() {
const current = this.state.get(this.id);
const patches = createDiff(this.lastUpdate, current.data.present);
const { present: data } = this.store.get(ID);
const patches = createDiff(this.lastUpdate, data);
if (patches.length === 0) {
return;
}
Expand All @@ -179,6 +177,7 @@ export default class DataService {
// this is a major performance improvement for array-item movements
if (parentDataType === "array") {
const changes = getArrayChangeList(patches[i], this.lastUpdate);
console.log("update array container", changes);
this.notifyWatcher({ type: "data:update:container", value: { pointer: eventLocation, changes }});

} else if (parentDataType === "object") {
Expand All @@ -192,12 +191,13 @@ export default class DataService {
}

this.notifyWatcher({ type: "data:update:done", value: patches });
this.lastUpdate = current.data.present;
this.lastUpdate = data;
}

/** clear undo/redo stack */
resetUndoRedo() {
this.state.get(this.id).data.past.length = 0;
this.store.dispatch.data.clearHistory();
this.store.previousState?.errors.pop();
}

/**
Expand All @@ -215,8 +215,8 @@ export default class DataService {
* @returns data, associated with `pointer`
*/
getDataByReference(pointer: JSONPointer = "#") {
// eslint-disable-next-line no-invalid-this
return gp.get(this.state.get(this.id)?.data.present, pointer);
const state = this.store.store.getState();
return gp.get(state.data.present, pointer);
}

/**
Expand All @@ -236,12 +236,15 @@ export default class DataService {
return;
}

this.notifyWatcher({ type: "data:update:before", value: { pointer, action: ActionTypes.DATA_SET }});
this.state.dispatch(ActionCreators.setData(pointer, value, currentValue, isSynched));
this.notifyWatcher({ type: "data:update:before", value: { pointer, action: "data" }});
// this.store.dispatch(ActionCreators.setData(pointer, value, currentValue, isSynched));
this.store.dispatch.data.set({ pointer, value });

if (pointer === "#" && isSynched === false) {
const store = this.store.store;
store.dispatch.data.removeLastUndo();
// do not add root changes to undo
this.state.get(this.id).data.past.pop();
// this.store.get(this.id).data.past.pop();
}
}

Expand All @@ -264,24 +267,26 @@ export default class DataService {

/** get valid undo count */
undoCount(): number {
return this.state.get(this.id).data.past.length;
return this.store.get(ID).past.length;
}

/** get valid redo count */
redoCount(): number {
return this.state.get(this.id).data.future.length;
return this.store.get(ID).future.length;
}

/** undo last change */
undo() {
this.notifyWatcher({ type: "data:update:before", value: { pointer: "#", action: ActionTypes.UNDO }});
this.state.dispatch(ActionCreators.undo());
this.notifyWatcher({ type: "data:update:before", value: { pointer: "#", action: "undo" }});
this.store.dispatch.data.undo();
// this.store.dispatch(ActionCreators.undo());
}

/** redo last undo */
redo() {
this.notifyWatcher({ type: "data:update:before", value: { pointer: "#", action: ActionTypes.REDO }});
this.state.dispatch(ActionCreators.redo());
this.notifyWatcher({ type: "data:update:before", value: { pointer: "#", action: "redo" }});
// this.store.dispatch(ActionCreators.redo());
this.store.dispatch.data.redo();
}

notifyWatcher(event: Event) {
Expand Down Expand Up @@ -351,8 +356,7 @@ export default class DataService {

/** destroy service */
destroy() {
this.state.unsubscribe(this.id, this.onStateChanged);
this.state.unregister(this.id);
this.store.unsubscribe(ID, this.onStateChanged);
}
}

35 changes: 21 additions & 14 deletions src/services/LocationService.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import gp from "gson-pointer";
import UIState, { EventType as UIEvent } from "./uistate";
import { JSONPointer } from "../types";
const DELAY = 25;
import { getState, dispatch, watch as watchState } from "../store/global";


function getViewportHeight() {
return Math.max(document.documentElement.clientHeight, window.innerHeight || 0);
Expand Down Expand Up @@ -82,11 +83,17 @@ export default class LocationService {
this.options = { ...defaultOptions, ...options };
this.setCurrent("");

UIState.on(UIEvent.CURRENT_PAGE, pointer =>
this.notifyWatcher(<PageEvent>{ type: "location:page", value: pointer }));

UIState.on(UIEvent.CURRENT_POINTER, pointer =>
this.notifyWatcher(<TargetEvent>{ type: "location:target", value: pointer }));
watchState(event => {
if (event.type === "global" && event.value.modelId === "ui") {
const { changes } = event.value;
if (changes.currentPage) {
this.notifyWatcher(<PageEvent>{ type: "location:page", value: changes.currentPage });
}
if (changes.currentPointer) {
this.notifyWatcher(<TargetEvent>{ type: "location:target", value: changes.currentPointer });
}
}
});
}

// update page and target pointer
Expand All @@ -97,31 +104,31 @@ export default class LocationService {
}

const nextPage = matches.pop();
const currentPage = UIState.getCurrentPage();
const { currentPage } = getState().ui;
if (currentPage !== nextPage) {
UIState.setCurrentPage(gp.join(nextPage, true));
dispatch.ui.setCurrentPage(gp.join(nextPage, true));
}
UIState.setCurrentPointer(gp.join(targetPointer, true));
dispatch.ui.setCurrentPointer(gp.join(targetPointer, true));
this.focus(rootElement);
}

/** set target pointer */
setCurrent(pointer: JSONPointer) {
if (pointer !== this.getCurrent()) {
UIState.setCurrentPointer(pointer);
dispatch.ui.setCurrentPointer(pointer);
this.notifyWatcher(<FocusEvent>{ type: "focus", value: pointer });
}
}

getCurrent() {
return UIState.getCurrentPointer();
return getState().ui.currentPointer;
}

/** focus target pointer */
focus(rootElement = this.options.rootElement) {
clearTimeout(this.timeout);

const pointer = UIState.getCurrentPointer();
const pointer = getState().ui.currentPointer;
const targetElement = <HTMLElement>rootElement.querySelector(`[data-point="${pointer}"]`);
if (targetElement == null) {
console.log(`Location:focus - target ${pointer} not found`);
Expand Down Expand Up @@ -151,10 +158,10 @@ export default class LocationService {
}

blur(pointer: JSONPointer) {
if (UIState.getCurrentPointer() !== pointer) {
if (getState().ui.currentPointer !== pointer) {
return;
}
UIState.setCurrentPointer("");
dispatch.ui.setCurrentPage("");
this.notifyWatcher(<BlurEvent>{ type: "blur", value: pointer });
}

Expand Down
20 changes: 17 additions & 3 deletions src/services/OverlayService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,18 @@
import m from "mithril";
import Overlay from "../components/overlay";
import createElement from "../utils/createElement";
import UIState, { EventType as UIEvent } from "./uistate";
import { watch, getState } from "../store/global";


UIState.on(UIEvent.OVERLAY, value => OverlayService.onChange(value?.element, value?.options));
watch(event => {
if (event.type === "global" && event.value.modelId === "ui") {
if (event.value.changes.overlay) {
const overlay = event.value.changes.overlay;
console.log("overlay event", overlay);
OverlayService.onChange(overlay?.element, overlay?.options);
}
}
});


export const defaultOptions = {
Expand Down Expand Up @@ -46,7 +54,13 @@ const OverlayService = {
},

close() {
UIState.setOverlay();
// const currentContent = getState().ui.overlay;
// if (currentContent !== content) {
// this.state.dispatch(ActionCreators.setOverlay(content));
// this.emitter.emit(EventType.OVERLAY, this.get("overlay"));
// }


// must destroy component for reuse
m.render(this.getElement(), m("i"));
},
Expand Down
Loading

0 comments on commit 26b47b1

Please sign in to comment.