Skip to content

Commit

Permalink
refactoring, support pages
Browse files Browse the repository at this point in the history
  • Loading branch information
lexoyo committed Apr 22, 2024
1 parent c3eaf68 commit 4165ada
Show file tree
Hide file tree
Showing 10 changed files with 95 additions and 67 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ This GrapesJs plugin is designed to enhance the user experience within the edito
Features

* [x] Notification types with corresponding icons
* [x] Select component attached to the notification
* [x] Select component attached to the notification (supports components on different pages)
* [x] Customizable notification style
* [x] Internationalization
* [x] Local storage for persistent notifications
Expand All @@ -20,6 +20,7 @@ Features
* [x] Notification commands
* [x] editor.NotificationManager API
* [x] Group notifications
* [x] Support pages

### HTML
```html
Expand Down Expand Up @@ -59,7 +60,7 @@ API:
* `type`: `error`, `warning`, `success`, `info`
* `message`: `string`
* `timeout`: `number` (ms)
* `component`: `string` or GrapesJs `Component` (optional)
* `componentId`: `string`
* `editor.Notifications` methods:
* `add(notification)`
* `remove(notification)`
Expand All @@ -83,7 +84,7 @@ API:
| `storeKey` | Store notifications in local storage under this key | `string` | No storage |
| `icons` | Icons for the notification types | `object` | `{error: '\u2716', warning: '\u26A0', success: '\u2714', info: '\u2139'}` |
| `i18n` | Internationalization | `object` | Check the values in locale/en.js |
| `maxNotifications` | Maximum number of notifications to display | `number` | `5` |
| `maxNotifications` | Maximum number of notifications to display | `number` | No limit |
| `reverse` | Reverse the order of the notifications | `boolean` | `false` |

## Styling
Expand Down
2 changes: 1 addition & 1 deletion _index.html
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
message: `This is a ${type} notification${group ? `from group ${group}` : ''}! `.repeat(Math.floor(Math.random() * 10) + 1),
group,
type,
component: editor.getComponents().at(Math.floor(Math.random() * editor.getComponents().length)).getId(),
componentId: editor.getComponents().at(Math.floor(Math.random() * editor.getComponents().length)).getId(),
timeout: Math.floor(Math.random() * 5000) + 5000,
})
}
Expand Down
14 changes: 11 additions & 3 deletions dist/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import Backbone from 'backbone';
import { Component, Editor } from 'grapesjs';
import { Editor } from 'grapesjs';
import { StyleInfo } from 'lit/directives/style-map';

export interface NotificationOptions {
message: string;
group?: string;
timeout?: number;
component?: string | Component;
componentId?: string;
type: "info" | "warning" | "error" | "success";
icons: {
info: string;
Expand All @@ -20,7 +20,7 @@ export interface NotificationModel extends Backbone.Model<NotificationOptions> {
declare class Notification {
protected editor: NotificationEditor;
protected model: NotificationModel;
component: Component | null;
componentId: string | null;
group: string | null;
timeoutRef: NodeJS.Timeout | undefined;
message: string;
Expand All @@ -30,6 +30,14 @@ declare class Notification {
select(): void;
remove(): void;
getSvgIcon(type: string): string;
private getDefaultOptions;
/**
* Get all components in the editor
* This is a heavy operation
*/
private getAllComponents;
private getAllComponentInPage;
private getAllChildrenComponent;
}
export interface NotificationEditor extends Editor {
NotificationManager: NotificationManager;
Expand Down
4 changes: 2 additions & 2 deletions dist/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

110 changes: 64 additions & 46 deletions src/Notification.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Component, Page } from 'grapesjs'
import { NotificationEditor } from './NotificationManager'
import { NOTIFICATION_ADD } from './commands'

export interface NotificationOptions {
message: string
group?: string
timeout?: number
component?: string | Component
componentId?: string
type: 'info' | 'warning' | 'error' | 'success'
icons: {
info: string
Expand All @@ -15,69 +16,47 @@ export interface NotificationOptions {
}
}

export function getDefaultOptions(opts: Partial<NotificationOptions>): NotificationOptions {
return {
...{
icons: {
info: '\u{1F6A7}',
warning: '\u{26A0}',
error: '\u{1F6AB}',
success: '\u{2705}',
group: '\u{1F4CC}',
...opts.icons,
},
}, ...opts
} as NotificationOptions
}

function getAllComponents(editor: NotificationEditor): Component[] {
return editor.Pages.getAll().map(getAllComponentInPage).flat()
}

function getAllComponentInPage(page: Page): Component[] {
const body = page.getMainComponent() as Component
return [body].concat(getAllChildrenComponent(body))
}

function getAllChildrenComponent(component: Component): Component[] {
const children = Array.from(component.components().models)
return children.concat(children.map(getAllChildrenComponent).flat())
}

export interface NotificationModel extends Backbone.Model<NotificationOptions> {}

export class Notification {
component: Component | null = null
componentId: string | null = null
group: string | null = null
timeoutRef
message: string
type: 'info' | 'warning' | 'error' | 'success'
protected options: NotificationOptions

constructor(protected editor: NotificationEditor, protected model: NotificationModel) {
this.options = getDefaultOptions(model.attributes)
if(this.options.timeout) {
this.options = this.getDefaultOptions(model.attributes)
if (this.options.timeout) {
this.timeoutRef = setTimeout(() => this.remove(), this.options.timeout)
}
if(this.options.component) {
if(typeof this.options.component === 'string') {
this.component = getAllComponents(editor).find((c: Component) => c.getId() === this.options.component) || null
} else {
this.component = this.options.component
}
}
this.message = this.options.message!
this.type = this.options.type!
this.componentId = model.attributes.componentId || null
this.group = model.attributes.group || null
}
//get(key: keyof NotificationOptions): unknown {
// return this.options.attributes[key]
//}

select() {
if(this.component) {
this.editor.select(this.component)
this.editor.Canvas.scrollTo(this.component)
if (this.options.componentId) {
// This operation is heavy
const found = this.getAllComponents(this.editor)
.find(({component, page}) => component.getId() === this.options.componentId) || null
if (found) {
const {component, page} = found
this.editor.Pages.select(page)
this.editor.select(component)
this.editor.Canvas.scrollTo(component)
} else {
console.error(`Component with ID ${this.options.componentId} not found`)
this.editor.runCommand(NOTIFICATION_ADD, {
message: `Component with ID ${this.options.componentId} not found`,
type: 'error',
})
}
}
}

remove() {
this.editor.NotificationManager.remove(this.model)
this.timeoutRef && clearTimeout(this.timeoutRef)
Expand All @@ -86,4 +65,43 @@ export class Notification {
getSvgIcon(type: string): string {
return this.options.icons?.[type as keyof NotificationOptions['icons']]!
}

private getDefaultOptions(opts: Partial<NotificationOptions>): NotificationOptions {
return {
...{
icons: {
info: '\u{1F6A7}',
warning: '\u{26A0}',
error: '\u{1F6AB}',
success: '\u{2705}',
group: '\u{1F4CC}',
...opts.icons,
},
}, ...opts
} as NotificationOptions
}

/**
* Get all components in the editor
* This is a heavy operation
*/
private getAllComponents(editor: NotificationEditor) {
return editor.Pages.getAll().map(page => this.getAllComponentInPage(page)).flat()
}

private getAllComponentInPage(page: Page): {component: Component, page: Page}[] {
const body = page.getMainComponent() as Component
return [{
component: body,
page,
}].concat(
this.getAllChildrenComponent(body)
.map(component => ({component, page}))
)
}

private getAllChildrenComponent(component: Component): Component[] {
const children = Array.from(component.components().models)
return children.concat(children.map(child => this.getAllChildrenComponent(child)).flat())
}
}
13 changes: 7 additions & 6 deletions src/NotificationManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ export interface NotificationEditor extends Editor {
NotificationManager: NotificationManager
}

// Events
export const NOTIFICATION_CHANGED = 'notifications:changed'
export const NOTIFICATION_ADD = 'notifications:added'
export const NOTIFICATION_REMOVE = 'notifications:removed'
export const NOTIFICATION_CLEAR = 'notifications:cleared'
export const NOTIFICATION_ADDED = 'notifications:added'
export const NOTIFICATION_REMOVED = 'notifications:removed'
export const NOTIFICATION_CLEARED = 'notifications:cleared'

export interface NotificationManagerOptions {
style: Readonly<StyleInfo>
Expand Down Expand Up @@ -39,9 +40,9 @@ export class NotificationManager extends Backbone.Collection<NotificationModel>

// Relay events from model to the editor
this.on('all', () => this.modelChanged())
this.on('reset', () => this.editor.trigger(NOTIFICATION_CLEAR))
this.on('add', (model: Notification) => this.editor.trigger(NOTIFICATION_ADD, model))
this.on('remove', (model: Notification) => this.editor.trigger(NOTIFICATION_REMOVE, model))
this.on('reset', () => this.editor.trigger(NOTIFICATION_CLEARED))
this.on('add', (model: Notification) => this.editor.trigger(NOTIFICATION_ADDED, model))
this.on('remove', (model: Notification) => this.editor.trigger(NOTIFICATION_REMOVED, model))

// Init with the current models
this.modelInit()
Expand Down
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export default (editor: NotificationEditor, opts: Partial<NotificationManagerOpt
i18n: {},
container: defaultContainer(opts.container)!,
storeKey: undefined,
maxNotifications: 5,
maxNotifications: undefined,
reverse: false,
}, ...opts }

Expand Down
2 changes: 1 addition & 1 deletion src/locale/en.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export default {
'@silexlabs/grapesjs-notifications': {
'Close': '\u2716 Dismiss',
'CloseAll': '\u2716 Dismiss all',
'Select': '\u2713 Select component {componentName}',
'Select': '\u2713 Select component',
'Show': '\u2193 Show',
},
};
6 changes: 3 additions & 3 deletions src/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default function(editor: NotificationEditor, container: HTMLElement, noti
}

const organizedNotifications = organizeNotifications(notifications)
.slice(0, options.maxNotifications)
.slice(0, options.maxNotifications || notifications.length)

litRender(html`
<style>
Expand Down Expand Up @@ -111,8 +111,8 @@ function renderNotification(editor: NotificationEditor, notification: Notificati
</header>
<footer class="gjs-sm-footer">
<button @click=${() => notification.remove()} class="gjs-btn-prim">${editor.I18n.t('@silexlabs/grapesjs-notifications.Close')}</button>
${notification.component ? html`
<button @click=${() => notification.select()} class="gjs-btn-prim">${editor.I18n.t('@silexlabs/grapesjs-notifications.Select', { params: { componentName: notification.component.getName() }})}</button>
${notification.componentId ? html`
<button @click=${() => notification.select()} class="gjs-btn-prim">${editor.I18n.t('@silexlabs/grapesjs-notifications.Select')}</button>
` : ''}
</footer>
</li>
Expand Down

0 comments on commit 4165ada

Please sign in to comment.