mirror of
https://github.com/penpot/penpot-plugins.git
synced 2025-01-21 06:02:34 -05:00
feat(plugins-runtime): add new events 'contentsave' and 'shapechange', changed on/off signatures
This commit is contained in:
parent
a1174c99a6
commit
2b8a76b2b0
5 changed files with 86 additions and 93 deletions
|
@ -121,6 +121,16 @@ function createRect() {
|
|||
const center = penpot.viewport.center;
|
||||
shape.x = center.x;
|
||||
shape.y = center.y;
|
||||
|
||||
penpot.on(
|
||||
'shapechange',
|
||||
(s) => {
|
||||
console.log('change', s.name, s.x, s.y);
|
||||
},
|
||||
{
|
||||
shapeId: shape.id,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function moveX(data: { id: string }) {
|
||||
|
|
41
libs/plugin-types/index.d.ts
vendored
41
libs/plugin-types/index.d.ts
vendored
|
@ -78,17 +78,19 @@ export interface Penpot
|
|||
* @param type The event type to listen for.
|
||||
* @param callback The callback function to execute when the event is triggered.
|
||||
* @param props The properties for the current event handler. Only makes sense for specific events.
|
||||
* @return the listener id that can be used to call `off` and cancel the listener
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* penpot.on('pagechange', () => {...do something}).
|
||||
* ```
|
||||
*/
|
||||
on: <T extends keyof EventsMap>(
|
||||
on<T extends keyof EventsMap>(
|
||||
type: T,
|
||||
callback: (event: EventsMap[T]) => void,
|
||||
props?: Map<string, unknown>
|
||||
) => void;
|
||||
props?: { [key: string]: unknown }
|
||||
): symbol;
|
||||
|
||||
/**
|
||||
* Removes an event listener for the specified event type.
|
||||
*
|
||||
|
@ -99,11 +101,25 @@ export interface Penpot
|
|||
* ```js
|
||||
* penpot.off('pagechange', () => {...do something}).
|
||||
* ```
|
||||
* @deprecated this method should not be used. Use instead off sending the `listenerId` (return value from `on` method)
|
||||
*/
|
||||
off: <T extends keyof EventsMap>(
|
||||
off<T extends keyof EventsMap>(
|
||||
type: T,
|
||||
callback: (event: EventsMap[T]) => void
|
||||
) => void;
|
||||
callback?: (event: EventsMap[T]) => void
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Removes an event listener for the specified event type.
|
||||
*
|
||||
* @param listenerId the id returned by the `on` method when the callback was set
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
* const listenerId = penpot.on('contentsave', () => console.log("Changed"));
|
||||
* penpot.off(listenerId);
|
||||
* ```
|
||||
*/
|
||||
off(listenerId: symbol): void;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1948,6 +1964,17 @@ export interface EventsMap {
|
|||
* The `finish` event is triggered when some operation is finished.
|
||||
*/
|
||||
finish: string;
|
||||
|
||||
/**
|
||||
* This event will triger whenever the shape in the props change. It's mandatory to send
|
||||
* with the props an object like `{ shapeId: '<id>' }`
|
||||
*/
|
||||
shapechange: PenpotShape;
|
||||
|
||||
/**
|
||||
* The `contentsave` event will trigger when the content file changes.
|
||||
*/
|
||||
contentsave: void;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2739,7 +2766,7 @@ export interface PenpotContext {
|
|||
addListener<T extends keyof EventsMap>(
|
||||
type: T,
|
||||
callback: (event: EventsMap[T]) => void,
|
||||
props?: Map<string, unknown>
|
||||
props?: { [key: string]: unknown }
|
||||
): symbol;
|
||||
|
||||
/**
|
||||
|
|
|
@ -39,13 +39,16 @@ export const validEvents = [
|
|||
'filechange',
|
||||
'selectionchange',
|
||||
'themechange',
|
||||
'shapechange',
|
||||
'contentsave',
|
||||
] as const;
|
||||
|
||||
export let uiMessagesCallbacks: Callback<unknown>[] = [];
|
||||
|
||||
let modals = new Set<PluginModalElement>([]);
|
||||
|
||||
const eventListeners: Map<string, Callback<unknown>[]> = new Map();
|
||||
// TODO: Remove when deprecating method `off`
|
||||
let listeners: { [key: string]: Map<object, symbol> } = {};
|
||||
|
||||
window.addEventListener('message', (event) => {
|
||||
try {
|
||||
|
@ -57,17 +60,10 @@ window.addEventListener('message', (event) => {
|
|||
}
|
||||
});
|
||||
|
||||
export function triggerEvent(
|
||||
type: keyof EventsMap,
|
||||
message: EventsMap[keyof EventsMap]
|
||||
) {
|
||||
if (type === 'themechange') {
|
||||
modals.forEach((modal) => {
|
||||
modal.setTheme(message as PenpotTheme);
|
||||
});
|
||||
}
|
||||
const listeners = eventListeners.get(type) || [];
|
||||
listeners.forEach((listener) => listener(message));
|
||||
export function themeChange(theme: PenpotTheme) {
|
||||
modals.forEach((modal) => {
|
||||
modal.setTheme(theme);
|
||||
});
|
||||
}
|
||||
|
||||
export function createApi(context: PenpotContext, manifest: Manifest): Penpot {
|
||||
|
@ -166,8 +162,9 @@ export function createApi(context: PenpotContext, manifest: Manifest): Penpot {
|
|||
|
||||
on<T extends keyof EventsMap>(
|
||||
type: T,
|
||||
callback: (event: EventsMap[T]) => void
|
||||
): void {
|
||||
callback: (event: EventsMap[T]) => void,
|
||||
props?: { [key: string]: unknown }
|
||||
): symbol {
|
||||
// z.function alter fn, so can't use it here
|
||||
z.enum(validEvents).parse(type);
|
||||
z.function().parse(callback);
|
||||
|
@ -175,24 +172,30 @@ export function createApi(context: PenpotContext, manifest: Manifest): Penpot {
|
|||
// To suscribe to events needs the read permission
|
||||
checkPermission('content:read');
|
||||
|
||||
const listeners = eventListeners.get(type) || [];
|
||||
listeners.push(callback as Callback<unknown>);
|
||||
eventListeners.set(type, listeners);
|
||||
const id = context.addListener(type, callback, props);
|
||||
|
||||
if (!listeners[type]) {
|
||||
listeners[type] = new Map<object, symbol>();
|
||||
}
|
||||
listeners[type].set(callback, id);
|
||||
return id;
|
||||
},
|
||||
|
||||
off<T extends keyof EventsMap>(
|
||||
type: T,
|
||||
callback: (event: EventsMap[T]) => void
|
||||
idtype: symbol | T,
|
||||
callback?: (event: EventsMap[T]) => void
|
||||
): void {
|
||||
z.enum(validEvents).parse(type);
|
||||
z.function().parse(callback);
|
||||
let listenerId: symbol | undefined;
|
||||
|
||||
const listeners = eventListeners.get(type) || [];
|
||||
if (typeof idtype === 'symbol') {
|
||||
listenerId = idtype;
|
||||
} else if (callback) {
|
||||
listenerId = listeners[idtype as T].get(callback);
|
||||
}
|
||||
|
||||
eventListeners.set(
|
||||
type,
|
||||
listeners.filter((listener) => listener !== callback)
|
||||
);
|
||||
if (listenerId) {
|
||||
context.removeListener(listenerId);
|
||||
}
|
||||
},
|
||||
|
||||
// Penpot State API
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { expect, describe, vi } from 'vitest';
|
||||
import { createApi, triggerEvent, uiMessagesCallbacks } from './index.js';
|
||||
import { createApi, themeChange, uiMessagesCallbacks } from './index.js';
|
||||
import openUIApi from './openUI.api.js';
|
||||
import type { PenpotFile } from '@penpot/plugin-types';
|
||||
|
||||
|
@ -28,6 +28,8 @@ describe('Plugin api', () => {
|
|||
getSelected: vi.fn(),
|
||||
getSelectedShapes: vi.fn(),
|
||||
getTheme: vi.fn(() => 'dark'),
|
||||
addListener: vi.fn(() => Symbol()),
|
||||
removeListener: vi.fn(),
|
||||
};
|
||||
|
||||
const api = createApi(mockContext as any, {
|
||||
|
@ -116,62 +118,15 @@ describe('Plugin api', () => {
|
|||
it('pagechange', () => {
|
||||
const callback = vi.fn();
|
||||
|
||||
api.on('pagechange', callback);
|
||||
const id = api.on('pagechange', callback);
|
||||
expect(mockContext.addListener).toHaveBeenCalled();
|
||||
expect(mockContext.addListener.mock.calls[0][0]).toBe('pagechange');
|
||||
expect(mockContext.addListener.mock.calls[0][1]).toBe(callback);
|
||||
|
||||
triggerEvent('pagechange', 'test' as any);
|
||||
|
||||
api.off('pagechange', callback);
|
||||
|
||||
triggerEvent('pagechange', 'test' as any);
|
||||
|
||||
expect(callback).toHaveBeenCalledWith('test');
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
api.off(id);
|
||||
expect(mockContext.removeListener).toHaveBeenCalled();
|
||||
expect(mockContext.removeListener.mock.calls[0][0]).toBe(id);
|
||||
});
|
||||
|
||||
it('filechange', () => {
|
||||
const callback = vi.fn();
|
||||
|
||||
api.on('filechange', callback);
|
||||
|
||||
triggerEvent('filechange', 'test' as any);
|
||||
|
||||
api.off('filechange', callback);
|
||||
|
||||
triggerEvent('filechange', 'test' as any);
|
||||
|
||||
expect(callback).toHaveBeenCalledWith('test');
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('selectionchange', () => {
|
||||
const callback = vi.fn();
|
||||
|
||||
api.on('selectionchange', callback);
|
||||
|
||||
triggerEvent('selectionchange', 'test' as any);
|
||||
|
||||
api.off('selectionchange', callback);
|
||||
|
||||
triggerEvent('selectionchange', 'test' as any);
|
||||
|
||||
expect(callback).toHaveBeenCalledWith('test');
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('themechange', () => {
|
||||
const callback = vi.fn();
|
||||
|
||||
api.on('themechange', callback);
|
||||
|
||||
triggerEvent('themechange', 'light');
|
||||
|
||||
api.off('themechange', callback);
|
||||
|
||||
triggerEvent('themechange', 'light');
|
||||
|
||||
expect(callback).toHaveBeenCalledWith('light');
|
||||
expect(callback).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
describe.concurrent('permissions', () => {
|
||||
|
@ -266,7 +221,7 @@ describe('Plugin api', () => {
|
|||
expect(modalMock.setTheme).toHaveBeenCalledWith('light');
|
||||
expect(api.getTheme()).toBe('light');
|
||||
|
||||
triggerEvent('themechange', 'dark' as any);
|
||||
themeChange('dark');
|
||||
expect(modalMock.setTheme).toHaveBeenCalledWith('dark');
|
||||
});
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { PenpotContext } from '@penpot/plugin-types';
|
||||
import type { PenpotContext, PenpotTheme } from '@penpot/plugin-types';
|
||||
|
||||
import { createApi } from './api/index.js';
|
||||
import { loadManifest, loadManifestCode } from './parse-manifest.js';
|
||||
|
@ -25,9 +25,7 @@ export const ɵloadPlugin = async function (manifest: Manifest) {
|
|||
return;
|
||||
}
|
||||
|
||||
for (const event of api.validEvents) {
|
||||
context.addListener(event, api.triggerEvent.bind(null, event));
|
||||
}
|
||||
context.addListener('themechange', (e: PenpotTheme) => api.themeChange(e));
|
||||
|
||||
const code = await loadManifestCode(manifest);
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue