= {
+ serialize(data: NodeSchema): string {
+ return JSON.stringify(data);
+ },
+ unserialize(data: string) {
+ return JSON.parse(data);
+ },
+};
+
+export function setSerialization(serializion: Serialization) {
+ currentSerializion = serializion;
+}
+
+export default class History {
+ private session: Session;
+ private records: Session[];
+ private point: number = 0;
+ private emitter = new EventEmitter();
+ private obx: Reaction;
+ private justWokeup: boolean = false;
+
+ constructor(
+ logger: () => any,
+ private redoer: (data: NodeSchema) => void,
+ private timeGap: number = 1000,
+ ) {
+ this.session = new Session(0, null, this.timeGap);
+ this.records = [this.session];
+
+ this.obx = autorun(() => {
+ const data = logger();
+ console.info('log');
+ if (this.justWokeup) {
+ this.justWokeup = false;
+ return;
+ }
+ untracked(() => {
+ const log = currentSerializion.serialize(data);
+ if (this.session.cursor === 0 && this.session.isActive()) {
+ this.session.log(log);
+ this.session.end();
+ } else if (this.session) {
+ if (this.session.isActive()) {
+ this.session.log(log);
+ } else {
+ this.session.end();
+ const cursor = this.session.cursor + 1;
+ const session = new Session(cursor, log, this.timeGap);
+ this.session = session;
+ this.records.splice(cursor, this.records.length - cursor, session);
+ }
+ }
+ });
+ }, true).$obx;
+ }
+
+ get hotData() {
+ return this.session.data;
+ }
+
+ isSavePoint(): boolean {
+ return this.point !== this.session.cursor;
+ }
+
+ go(cursor: number) {
+ this.session.end();
+
+ const currentCursor = this.session.cursor;
+ cursor = +cursor;
+ if (cursor < 0) {
+ cursor = 0;
+ } else if (cursor >= this.records.length) {
+ cursor = this.records.length - 1;
+ }
+ if (cursor === currentCursor) {
+ return;
+ }
+
+ const session = this.records[cursor];
+ const hotData = session.data;
+
+ this.obx.sleep();
+ try {
+ this.redoer(hotData);
+ this.emitter.emit('cursor', hotData);
+ } catch (e) {
+ //
+ }
+
+ this.justWokeup = true;
+ this.obx.wakeup();
+ this.session = session;
+
+ this.emitter.emit('statechange', this.getState());
+ }
+
+ back() {
+ if (!this.session) {
+ return;
+ }
+ const cursor = this.session.cursor - 1;
+ this.go(cursor);
+ }
+
+ forward() {
+ if (!this.session) {
+ return;
+ }
+ const cursor = this.session.cursor + 1;
+ this.go(cursor);
+ }
+
+ savePoint() {
+ if (!this.session) {
+ return;
+ }
+ this.session.end();
+ this.point = this.session.cursor;
+ this.emitter.emit('statechange', this.getState());
+ }
+
+ /**
+ * | 1 | 1 | 1 |
+ * | -------- | -------- | -------- |
+ * | modified | redoable | undoable |
+ */
+ getState(): number {
+ const cursor = this.session.cursor;
+ let state = 7;
+ // undoable ?
+ if (cursor <= 0) {
+ state -= 1;
+ }
+ // redoable ?
+ if (cursor >= this.records.length - 1) {
+ state -= 2;
+ }
+ // modified ?
+ if (this.point === cursor) {
+ state -= 4;
+ }
+ return state;
+ }
+
+ onStateChange(func: () => any) {
+ this.emitter.on('statechange', func);
+ return () => {
+ this.emitter.removeListener('statechange', func);
+ };
+ }
+
+ onCursor(func: () => any) {
+ this.emitter.on('cursor', func);
+ return () => {
+ this.emitter.removeListener('cursor', func);
+ };
+ }
+
+ destroy() {
+ this.emitter.removeAllListeners();
+ this.records = [];
+ }
+}
diff --git a/packages/designer/src/designer/helper/session.ts b/packages/designer/src/designer/helper/session.ts
new file mode 100644
index 000000000..8095d525d
--- /dev/null
+++ b/packages/designer/src/designer/helper/session.ts
@@ -0,0 +1,44 @@
+export default class Session {
+ private _data: any;
+ private activedTimer: any;
+
+ get data() {
+ return this._data;
+ }
+
+ constructor(readonly cursor: number, data: any, private timeGap: number = 1000) {
+ this.setTimer();
+ this.log(data);
+ }
+
+ log(data: any) {
+ if (!this.isActive()) {
+ return;
+ }
+ this._data = data;
+ this.setTimer();
+ }
+
+ isActive() {
+ return this.activedTimer != null;
+ }
+
+ end() {
+ if (this.isActive()) {
+ this.clearTimer();
+ console.info('session end');
+ }
+ }
+
+ private setTimer() {
+ this.clearTimer();
+ this.activedTimer = setTimeout(() => this.end(), this.timeGap);
+ }
+
+ private clearTimer() {
+ if (this.activedTimer) {
+ clearTimeout(this.activedTimer);
+ }
+ this.activedTimer = null;
+ }
+}
diff --git a/packages/designer/src/designer/project-view.tsx b/packages/designer/src/designer/project-view.tsx
index 1bb28b1f0..406563b8a 100644
--- a/packages/designer/src/designer/project-view.tsx
+++ b/packages/designer/src/designer/project-view.tsx
@@ -8,7 +8,6 @@ export default class ProjectView extends Component<{ designer: Designer }> {
render() {
const { designer } = this.props;
// TODO: support splitview
- console.info(designer.project.documents);
return (
{designer.project.documents.map(doc => {
diff --git a/packages/designer/src/designer/schema.ts b/packages/designer/src/designer/schema.ts
index cb2d4eb91..db7e75418 100644
--- a/packages/designer/src/designer/schema.ts
+++ b/packages/designer/src/designer/schema.ts
@@ -90,15 +90,14 @@ export type PropsList = Array<{
export type NodeData = NodeSchema | JSExpression | DOMText;
-export interface JSExpression {
- type: 'JSExpression';
- value: string;
-}
-
export function isJSExpression(data: any): data is JSExpression {
return data && data.type === 'JSExpression';
}
+export function isJSSlot(data: any): data is JSSlot {
+ return data && data.type === 'JSSlot';
+}
+
export function isDOMText(data: any): data is DOMText {
return typeof data === 'string';
}