Skip to content

Commit

Permalink
Add Core event bus (#89)
Browse files Browse the repository at this point in the history
* Add Core event bus

* Add try catch for getFragments call

* Fix build & rename uiContent to ui

* Fix lint
  • Loading branch information
gohabereg authored Aug 30, 2024
1 parent 83662ff commit 20f2ef7
Show file tree
Hide file tree
Showing 18 changed files with 339 additions and 66 deletions.
26 changes: 17 additions & 9 deletions packages/core/src/components/BlockManager.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { BlockAddedEvent, BlockRemovedEvent, EditorJSModel, EventType, ModelEvents } from '@editorjs/model';
import 'reflect-metadata';
import { Inject, Service } from 'typedi';
import { EditorUI } from '../ui/Editor/index.js';
import { BlockToolAdapter, CaretAdapter, FormattingAdapter } from '@editorjs/dom-adapters';
import ToolsManager from '../tools/ToolsManager.js';
import { BlockAPI, BlockToolData } from '@editorjs/editorjs';
import { CoreConfigValidated } from '../entities/Config.js';
import { BlockAddedCoreEvent, BlockRemovedCoreEvent, EventBus } from './EventBus/index.js';

/**
* Parameters for the BlocksManager.insert() method
Expand Down Expand Up @@ -45,9 +45,9 @@ export class BlocksManager {
#model: EditorJSModel;

/**
* Editor's UI class instance to add and remove blocks to the UI
* Editor's EventBus instance to exchange events between components
*/
#editorUI: EditorUI;
#eventBus: EventBus;

/**
* Caret Adapter instance
Expand All @@ -74,22 +74,22 @@ export class BlocksManager {
* BlocksManager constructor
* All parameters are injected thorugh the IoC container
* @param model - Editor's Document Model instance
* @param editorUI - Editor's UI class instance
* @param eventBus - Editor's EventBus instance
* @param caretAdapter - Caret Adapter instance
* @param toolsManager - Tools manager instance
* @param formattingAdapter - will be passed to BlockToolAdapter for rendering inputs` formatted text
* @param config - Editor validated configuration
*/
constructor(
model: EditorJSModel,
editorUI: EditorUI,
eventBus: EventBus,
caretAdapter: CaretAdapter,
toolsManager: ToolsManager,
formattingAdapter: FormattingAdapter,
@Inject('EditorConfig') config: CoreConfigValidated
) {
this.#model = model;
this.#editorUI = editorUI;
this.#eventBus = eventBus;
this.#caretAdapter = caretAdapter;
this.#toolsManager = toolsManager;
this.#formattingAdapter = formattingAdapter;
Expand Down Expand Up @@ -177,7 +177,12 @@ export class BlocksManager {
try {
const blockElement = await block.render();

this.#editorUI.addBlock(blockElement, index.blockIndex);
this.#eventBus.dispatchEvent(new BlockAddedCoreEvent({
tool: tool.name,
data: data.data,
ui: blockElement,
index: index.blockIndex,
}));
} catch (error) {
console.error(`[BlockManager] Block Tool ${data.name} failed to render`, error);
}
Expand All @@ -189,12 +194,15 @@ export class BlocksManager {
* @param event - BlockRemovedEvent
*/
#handleBlockRemovedEvent(event: BlockRemovedEvent): void {
const { index } = event.detail;
const { data, index } = event.detail;

if (index.blockIndex === undefined) {
throw new Error('Block index should be defined. Probably something wrong with the Editor Model. Please, report this issue');
}

this.#editorUI.removeBlock(index.blockIndex);
this.#eventBus.dispatchEvent(new BlockRemovedCoreEvent({
tool: data.name,
index: index.blockIndex,
}));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/* eslint-disable @typescript-eslint/naming-convention */
import type { BlockToolData } from '@editorjs/editorjs';
import { CoreEventBase } from './CoreEventBase.js';
import { CoreEventType } from './CoreEventType.js';

/**
* Payload of BlockAddedCoreEvent custom event
* Contains added block data: name, data, index and UI content to be rendered on tha page
* @template UI - type of the UI content
*/
export interface BlockAddedCoreEventPayload<UI = unknown> {
/**
* Name of the added Block Tool
*/
readonly tool: string;
/**
* Added Block data
*/
readonly data: BlockToolData;
/**
* UI content to be rendered on the page
*/
readonly ui: UI;
/**
* Index of the added Block
*/
readonly index: number;
}

/**
* Class for event that is being fired after the block is added
*/
export class BlockAddedCoreEvent<UI = unknown> extends CoreEventBase<BlockAddedCoreEventPayload<UI>> {
/**
* BlockAddedCoreEvent constructor function
* @param payload - BlockAdded event payload with tool name, block data, index and UI content
*/
constructor(payload: BlockAddedCoreEventPayload<UI>) {
super(CoreEventType.BlockAdded, payload);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { CoreEventBase } from './CoreEventBase.js';
import { CoreEventType } from './CoreEventType.js';

/**
* Payload of BlockRemovedCoreEvent custom event
*/
export interface BlockRemovedCoreEventPayload {
/**
* Block Tool name
*/
readonly tool: string;
/**
* Index of the removed block
*/
readonly index: number;
}

/**
* Class for event that is being fired after the block is removed
*/
export class BlockRemovedCoreEvent extends CoreEventBase<BlockRemovedCoreEventPayload> {
/**
* BlockRemovedCoreEvent constructor function
* @param payload - BlockRemoved event payload with toola name and block index
*/
constructor(payload: BlockRemovedCoreEventPayload) {
super(CoreEventType.BlockRemoved, payload);
}
}
17 changes: 17 additions & 0 deletions packages/core/src/components/EventBus/core-events/CoreEventBase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Represents a base class for core events.
* @template Payload - The type of the event payload.
*/
// eslint-disable-next-line n/no-unsupported-features/node-builtins
export class CoreEventBase<Payload = unknown> extends CustomEvent<Payload> {
/**
* CoreEventBase constructor function
* @param name - type of the core event
* @param payload - payload of the core event, can contain any data
*/
constructor(name: string, payload: Payload) {
super(`core:${name}`, {
detail: payload,
});
}
}
17 changes: 17 additions & 0 deletions packages/core/src/components/EventBus/core-events/CoreEventType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Enumeration of core events
*/
export enum CoreEventType {
/**
* Event is fired when a block is added to the Editor
*/
BlockAdded = 'block:added',
/**
* Event is fired when a block is removed from the Editor
*/
BlockRemoved = 'block:removed',
/**
* Event is fired when a tool is loaded
*/
ToolLoaded = 'tool:loaded'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { ToolFacadeClass } from '../../../tools/facades/index.js';
import { CoreEventBase } from './CoreEventBase.js';
import { CoreEventType } from './CoreEventType.js';

/**
* Payload of ToolLoadedCoreEvent custom event
* Contains laoded tool facade instance
* @todo replace facade object with API wrapper
*/
export interface ToolLoadedCoreEventPayload {
/**
* Loaded tool facade instance
*/
readonly tool: ToolFacadeClass;
}

/**
* Class for event that is being fired after the tool is loaded
*/
export class ToolLoadedCoreEvent extends CoreEventBase<ToolLoadedCoreEventPayload> {
/**
* ToolLoadedCoreEvent constructor function
* @param payload - ToolLoaded event payload with loaded tool facade instance
*/
constructor(payload: ToolLoadedCoreEventPayload) {
super(CoreEventType.ToolLoaded, payload);
}
}
4 changes: 4 additions & 0 deletions packages/core/src/components/EventBus/core-events/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './BlockAddedCoreEvent.js';
export * from './BlockRemovedCoreEvent.js';
export * from './ToolLoadedCoreEvent.js';
export * from './CoreEventType.js';
43 changes: 43 additions & 0 deletions packages/core/src/components/EventBus/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import 'reflect-metadata';
import { Service } from 'typedi';

export type Event<Channel extends string = string, Name extends string = string> = `${Channel}:${Name}`;

export type CoreEvent<Name extends string = string> = Event<'core', Name>;

/**
* Extension for the EventTarget interface to allow for custom events.
*/
declare global {
/**
* EventTarget interface extension
*/
interface EventTarget {
/**
* Adds an event listener for the specified event type
* @param type - a string representing the event type to listen for
* @param callback - the function to call when the event is triggered
* @param options - an options object that specifies characteristics about the event listener
*/
// eslint-disable-next-line n/no-unsupported-features/node-builtins
addEventListener(type: Event, callback: ((event: CustomEvent) => void) | null, options?: AddEventListenerOptions | boolean): void;
/**
* Removes an event listener for the specified event type
* @param type - a string representing the event type to stop listening for
* @param callback - the event callback to remove
* @param options - an options object that specifies characteristics about the event listener
*/
// eslint-disable-next-line n/no-unsupported-features/node-builtins
removeEventListener(type: Event, callback: ((event: CustomEvent) => void) | null, options?: EventListenerOptions | boolean): void;
}
}

/**
* EventBus class to handle events between components
* Extends native EventTarget class
*/
@Service()
export class EventBus extends EventTarget {}

export * from './core-events/index.js';
export * from './ui-events/index.js';
17 changes: 17 additions & 0 deletions packages/core/src/components/EventBus/ui-events/UIEventBase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Represents a base class for UI events.
* @template Payload - The type of the event payload.
*/
// eslint-disable-next-line n/no-unsupported-features/node-builtins
export class UIEventBase<Payload = unknown> extends CustomEvent<Payload> {
/**
* UIEventBase constructor function
* @param name - type of the core event
* @param payload - payload of the core event, can contain any data
*/
constructor(name: string, payload: Payload) {
super(`ui:${name}`, {
detail: payload,
});
}
}
1 change: 1 addition & 0 deletions packages/core/src/components/EventBus/ui-events/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './UIEventBase.js';
27 changes: 0 additions & 27 deletions packages/core/src/components/Toolbox.ts

This file was deleted.

13 changes: 7 additions & 6 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import type { CoreConfig } from '@editorjs/sdk';
import { BlocksManager } from './components/BlockManager.js';
import { EditorUI } from './ui/Editor/index.js';
import { ToolboxUI } from './ui/Toolbox/index.js';
import { Toolbox } from './components/Toolbox.js';

/**
* If no holder is provided via config, the editor will be appended to the element with this id
Expand Down Expand Up @@ -75,7 +74,6 @@ export default class Core {

this.validateConfig(config);

// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unnecessary-type-assertion
this.#config = config as CoreConfigValidated;

this.#iocContainer.set('EditorConfig', this.#config);
Expand All @@ -100,7 +98,11 @@ export default class Core {
this.#prepareUI();

this.#iocContainer.get(BlocksManager);
this.#iocContainer.get(Toolbox);

/**
* @todo avait when isReady API is implemented
*/
void this.#toolsManager.prepareTools();

this.#model.initializeDocument({ blocks });
}
Expand All @@ -110,11 +112,10 @@ export default class Core {
*/
#prepareUI(): void {
const editorUI = this.#iocContainer.get(EditorUI);
const toolboxUI = this.#iocContainer.get(ToolboxUI);

editorUI.render();
this.#iocContainer.get(ToolboxUI);

editorUI.addToolbox(toolboxUI.render());
editorUI.render();
}

/**
Expand Down
Loading

2 comments on commit 20f2ef7

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage report for ./packages/model

St.
Category Percentage Covered / Total
🟢 Statements 100% 769/769
🟢 Branches 99.51% 203/204
🟢 Functions 98.39% 183/186
🟢 Lines 100% 741/741

Test suite run success

389 tests passing in 24 suites.

Report generated by 🧪jest coverage report action from 20f2ef7

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage report for ./packages/collaboration-manager

St.
Category Percentage Covered / Total
🟢 Statements 86.11% 62/72
🟡 Branches 62.5% 15/24
🟢 Functions 100% 10/10
🟢 Lines 86.11% 62/72

Test suite run success

6 tests passing in 1 suite.

Report generated by 🧪jest coverage report action from 20f2ef7

Please sign in to comment.