Skip to content

Commit

Permalink
feat(plugin): migrate plugin runner to rxjs
Browse files Browse the repository at this point in the history
  • Loading branch information
Austaras committed Jul 27, 2022
1 parent 8280cf4 commit 21fbadb
Show file tree
Hide file tree
Showing 19 changed files with 320 additions and 428 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,30 +9,35 @@ import {
import { Clip } from './clip';
import assert from 'assert';
import ClipboardParse from './clipboard-parse';
import { Subscription } from 'rxjs';

class ClipboardPopulator {
private editor: Editor;
private hooks: PluginHooks;
private selection_manager: SelectionManager;
private clipboard: any;
private clipboard_parse: ClipboardParse;
private _editor: Editor;
private _hooks: PluginHooks;
private _selectionManager: SelectionManager;
private _clipboardParse: ClipboardParse;
private _sub = new Subscription();

constructor(
editor: Editor,
hooks: PluginHooks,
selectionManager: SelectionManager,
clipboard: any
selectionManager: SelectionManager
) {
this.editor = editor;
this.hooks = hooks;
this.selection_manager = selectionManager;
this.clipboard = clipboard;
this.clipboard_parse = new ClipboardParse(editor);
hooks.addHook(HookType.BEFORE_COPY, this.populate_app_clipboard, this);
hooks.addHook(HookType.BEFORE_CUT, this.populate_app_clipboard, this);
this._editor = editor;
this._hooks = hooks;
this._selectionManager = selectionManager;
this._clipboardParse = new ClipboardParse(editor);
this._sub.add(
hooks
.get(HookType.BEFORE_COPY)
.subscribe(this._populateAppClipboard)
);
this._sub.add(
hooks.get(HookType.BEFORE_CUT).subscribe(this._populateAppClipboard)
);
}

private async populate_app_clipboard(e: ClipboardEvent) {
private _populateAppClipboard = async (e: ClipboardEvent) => {
e.preventDefault();
e.stopPropagation();
const clips = await this.getClips();
Expand All @@ -58,7 +63,8 @@ class ClipboardPopulator {
}
}
}
}
};

private copy_to_cliboard_from_pc(clips: any[]) {
let success = false;
const tempElem = document.createElement('textarea');
Expand Down Expand Up @@ -91,8 +97,8 @@ class ClipboardPopulator {
}

private async get_clip_block_info(selBlock: SelectBlock) {
const block = await this.editor.getBlockById(selBlock.blockId);
const block_view = this.editor.getView(block.type);
const block = await this._editor.getBlockById(selBlock.blockId);
const block_view = this._editor.getView(block.type);
assert(block_view);
const block_info: ClipBlockInfo = {
type: block.type,
Expand All @@ -112,7 +118,8 @@ class ClipboardPopulator {

private async get_inner_clip(): Promise<InnerClipInfo> {
const clips: ClipBlockInfo[] = [];
const select_info: SelectInfo = await this.selection_manager.getSelectInfo();
const select_info: SelectInfo =
await this._selectionManager.getSelectInfo();
for (let i = 0; i < select_info.blocks.length; i++) {
const sel_block = select_info.blocks[i];
const clip_block_info = await this.get_clip_block_info(sel_block);
Expand All @@ -136,20 +143,16 @@ class ClipboardPopulator {
)
);

const html_clip = await this.clipboard_parse.generateHtml();
const html_clip = await this._clipboardParse.generateHtml();
html_clip &&
clips.push(new Clip(OFFICE_CLIPBOARD_MIMETYPE.HTML, html_clip));

return clips;
}

disposeInternal() {
this.hooks.removeHook(
HookType.BEFORE_COPY,
this.populate_app_clipboard
);
this.hooks.removeHook(HookType.BEFORE_CUT, this.populate_app_clipboard);
this.hooks = null;
this._sub.unsubscribe();
this._hooks = null;
}
}

Expand Down
3 changes: 1 addition & 2 deletions libs/components/editor-core/src/editor/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,7 @@ export class Editor implements Virgo {
this.clipboard_populator = new ClipboardPopulator(
this,
this.hooks,
this.selectionManager,
this.clipboard
this.selectionManager
);
}
}
Expand Down
94 changes: 20 additions & 74 deletions libs/components/editor-core/src/editor/plugin/hooks.ts
Original file line number Diff line number Diff line change
@@ -1,91 +1,37 @@
import {
HooksRunner,
PluginHooks,
HookType,
HookBaseArgs,
BlockDomInfo,
AnyFunction,
AnyThisType,
} from '../types';

interface PluginHookInfo {
thisObj?: AnyThisType;
callback: AnyFunction;
once: boolean;
}
import { Observable, Subject } from 'rxjs';
import { HooksRunner, HookType, BlockDomInfo, PluginHooks } from '../types';

export class Hooks implements HooksRunner, PluginHooks {
private _hooksMap: Map<string, PluginHookInfo[]> = new Map();
private _subject: Record<string, Subject<unknown>> = {};
private _observable: Record<string, Observable<unknown>> = {};

dispose() {
this._hooksMap.clear();
this._subject = {};
}

private _runHook(key: HookType, ...params: unknown[]): void {
const hookInfos: PluginHookInfo[] = this._hooksMap.get(key) || [];
hookInfos.forEach(hookInfo => {
if (hookInfo.once) {
this.removeHook(key, hookInfo.callback);
}
let isStoppedPropagation = false;
const hookOption: HookBaseArgs = {
stopImmediatePropagation: () => {
isStoppedPropagation = true;
},
};
hookInfo.callback.call(
hookInfo.thisObj || this,
...params,
hookOption
);
return isStoppedPropagation;
});
}

private _hasHook(key: HookType, callback: AnyFunction): boolean {
const hookInfos: PluginHookInfo[] = this._hooksMap.get(key) || [];
for (let i = hookInfos.length - 1; i >= 0; i--) {
if (hookInfos[i].callback === callback) {
return true;
}
if (this._subject[key] == null) {
this._subject[key] = new Subject();
this._observable[key] = this._subject[key].asObservable();
}
return false;
}
let payload: unknown = params;

// 执行多次
public addHook(
key: HookType,
callback: AnyFunction,
thisObj?: AnyThisType,
once?: boolean
): void {
if (this._hasHook(key, callback)) {
throw new Error('Duplicate registration of the same class');
if (params.length === 0) {
payload = undefined;
}
if (!this._hooksMap.has(key)) {
this._hooksMap.set(key, []);
if (params.length === 1) {
payload = params[0];
}
const hookInfos: PluginHookInfo[] = this._hooksMap.get(key);
hookInfos.push({ callback, thisObj, once });
this._subject[key].next(payload);
}

// 执行一次
public addOnceHook(
key: HookType,
callback: AnyFunction,
thisObj?: AnyThisType
): void {
this.addHook(key, callback, thisObj, true);
}

// 移除
public removeHook(key: HookType, callback: AnyFunction): void {
const hookInfos: PluginHookInfo[] = this._hooksMap.get(key) || [];
for (let i = hookInfos.length - 1; i >= 0; i--) {
if (hookInfos[i].callback === callback) {
hookInfos.splice(i, 1);
}
public get<K extends keyof HooksRunner>(key: K) {
if (this._subject[key] == null) {
this._subject[key] = new Subject();
this._observable[key] = this._subject[key].asObservable();
}

return this._observable[key] as any;
}

public init(): void {
Expand Down
7 changes: 3 additions & 4 deletions libs/components/editor-core/src/editor/scroll/scroll.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import EventEmitter from 'eventemitter3';

import { domToRect, Rect } from '@toeverything/utils';
import type { Editor as Block_editor } from '../editor';
import type { Editor as BlockEditor } from '../editor';

import { AsyncBlock } from '../block';

type VerticalTypes = 'up' | 'down' | null;
type HorizontalTypes = 'left' | 'right' | null;

export class ScrollManager {
private _editor: Block_editor;
private _editor: BlockEditor;
private _animationFrame: null | number = null;
private _eventName = 'scrolling';
private _currentMoveDirection: [HorizontalTypes, VerticalTypes] = [
Expand All @@ -20,9 +20,8 @@ export class ScrollManager {
private _scrollMoveOffset = 8;
private _scrollingEvent = new EventEmitter();

constructor(editor: Block_editor) {
constructor(editor: BlockEditor) {
this._editor = editor;
console.log('scrollmanager constructor', this._editor.ui_container);
}

private _updateScrollInfo(left: number, top: number) {
Expand Down
32 changes: 10 additions & 22 deletions libs/components/editor-core/src/editor/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import type { BlockHelper } from './block/block-helper';
import type { BlockCommands } from './commands/block-commands';
import type { DragDropManager } from './drag-drop';
import { MouseManager } from './mouse';
import { Observable } from 'rxjs';

// import { BrowserClipboard } from './clipboard/browser-clipboard';

Expand Down Expand Up @@ -176,10 +177,6 @@ export enum HookType {
BEFORE_CUT = 'beforeCut',
}

export interface HookBaseArgs {
stopImmediatePropagation: () => void;
}

export interface BlockDomInfo {
blockId: string;
dom: HTMLElement;
Expand Down Expand Up @@ -227,25 +224,16 @@ export interface HooksRunner {
beforeCut: (e: ClipboardEvent) => void;
}

export type AnyFunction = (...args: any[]) => any;
export type AnyThisType = ThisParameterType<any>;
export type PayloadType<T extends Array<any>> = T extends []
? void
: T extends [infer U]
? U
: T;

// hook管理,在editor、plugin中使用
export interface PluginHooks {
// 执行多次
addHook: (
key: HookType,
callback: AnyFunction,
thisObj?: AnyThisType,
once?: boolean
) => void;
// 执行一次
addOnceHook: (
key: HookType,
callback: AnyFunction,
thisObj?: AnyThisType
) => void;
// 移除
removeHook: (key: HookType, callback: AnyFunction) => void;
get<K extends keyof HooksRunner>(
key: K
): Observable<PayloadType<Parameters<HooksRunner[K]>>>;
}

export * from './drag-drop/types';
38 changes: 9 additions & 29 deletions libs/components/editor-plugins/src/base-plugin.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import {
HookType,
Virgo,
Plugin,
PluginHooks,
HookType,
} from '@toeverything/framework/virgo';
import { genErrorObj } from '@toeverything/utils';
import { Subscription } from 'rxjs';

export abstract class BasePlugin implements Plugin {
protected editor: Virgo;
protected hooks: PluginHooks;
private hook_queue: [type: HookType, fn: (...args: unknown[]) => void][] =
[];
private is_disposed = false;
protected sub: Subscription;
private _disposed = false;

// Unique identifier to distinguish between different Plugins
public static get pluginName(): string {
Expand All @@ -27,22 +27,8 @@ export abstract class BasePlugin implements Plugin {

constructor(editor: Virgo, hooks: PluginHooks) {
this.editor = editor;
// TODO perfect it
this.hooks = {
addHook: (...args) => {
this.hook_queue.push([args[0], args[1]]);
return hooks.addHook(...args);
},
addOnceHook(...args) {
return hooks.addHook(...args);
},
// TODO fix remove
removeHook(...args) {
return hooks.removeHook(...args);
},
};
this._onRender = this._onRender.bind(this);
hooks.addHook(HookType.RENDER, this._onRender, this);
this.hooks = hooks;
this.sub = hooks.get(HookType.RENDER).subscribe(() => this._onRender());
}

/**
Expand All @@ -62,18 +48,12 @@ export abstract class BasePlugin implements Plugin {
public dispose(): void {
// See https://stackoverflow.com/questions/33387318/access-to-static-properties-via-this-constructor-in-typescript
const pluginName = (this.constructor as typeof BasePlugin).pluginName;
if (this.is_disposed) {
if (this._disposed) {
console.warn(`Plugin '${pluginName}' already disposed`);
return;
}
this.is_disposed = true;
// FIX will remove hook multiple times
// if the hook has been removed manually
// or set once flag when add hook
this.hook_queue.forEach(([type, fn]) => {
this.hooks.removeHook(type, fn);
});
this.hook_queue = [];
this.sub.unsubscribe();
this._disposed = true;

const errorMsg = `You are trying to access an invalid editor or hooks.
The plugin '${pluginName}' has been disposed.
Expand Down
Loading

0 comments on commit 21fbadb

Please sign in to comment.