diff --git a/apps/contrast-plugin/src/app/app.element.ts b/apps/contrast-plugin/src/app/app.element.ts index d3bccd1..1528db1 100644 --- a/apps/contrast-plugin/src/app/app.element.ts +++ b/apps/contrast-plugin/src/app/app.element.ts @@ -4,7 +4,7 @@ import './app.element.css'; export class AppElement extends HTMLElement { public static observedAttributes = []; - public page: any; + public shapes: any; calculateContrast(firstColor: string, secondColor: string) { const luminosityFirstColor = this.getLuminosity(firstColor); @@ -136,24 +136,9 @@ export class AppElement extends HTMLElement { } } - initCalculate(selection: string[]) { - const shapes = - this.page.objects['#00000000-0000-0000-0000-000000000000'].shapes; - - const index1 = shapes.findIndex((shape: any) => { - return shape === selection[0]; - }); - - const index2 = shapes.findIndex((shape: any) => { - return shape === selection[1]; - }); - - if (index1 < index2) { - selection = [selection[1], selection[0]]; - } - - const obj0 = this.page.objects['#' + selection[0]]?.fills?.[0]?.fillColor; - const obj1 = this.page.objects['#' + selection[1]]?.fills?.[0]?.fillColor; + initCalculate(shapes: any) { + const obj0 = shapes[0]?.fills?.[0]?.fillColor; + const obj1 = shapes[1]?.fills?.[0]?.fillColor; if (obj0 && obj1) { this.calculateContrast(obj0, obj1); @@ -162,25 +147,21 @@ export class AppElement extends HTMLElement { connectedCallback() { window.addEventListener('message', (event) => { - if (event.data.type === 'selection') { - if (event.data.content.length === 2) { - this.initCalculate(event.data.content); + if (event.data.type === 'init') { + this.setAttribute('data-theme', event.data.content.theme); + if (event.data.content.selection.length >= 2) { + this.initCalculate(event.data.content.shapes); + } + } else if (event.data.type === 'selection') { + if (event.data.content.shapes.length >= 2) { + this.initCalculate(event.data.content.shapes); } else { this.setColors(null, null); this.setResult('0'); this.setA11yTags(0); } - } else if (event.data.type === 'page') { - this.page = event.data.content; - } else if (event.data.type === 'init') { - this.setAttribute('data-theme', event.data.content.theme); - this.page = event.data.content.page; - - if (event.data.content.selection.length === 2) { - this.initCalculate(event.data.content.selection); - } } else if (event.data.type === 'theme') { - this.setAttribute('data-theme', event.data.content); + this.setAttribute('data-theme', event.data.content.theme); } }); diff --git a/apps/contrast-plugin/src/plugin.ts b/apps/contrast-plugin/src/plugin.ts index abefc2e..6af5992 100644 --- a/apps/contrast-plugin/src/plugin.ts +++ b/apps/contrast-plugin/src/plugin.ts @@ -5,36 +5,22 @@ penpot.ui.open('Contrast plugin', 'http://localhost:4210', { penpot.ui.onMessage<{ content: string }>((message) => { if (message.content === 'ready') { - const pageState = penpot.getPageState(); - const fileState = penpot.getFileState(); - - if (!pageState || !fileState) { - return; - } - penpot.ui.sendMessage({ type: 'init', content: { - name: pageState.name, - pageId: pageState.id, - page: pageState, - fileId: fileState.id, - revn: fileState.revn, theme: penpot.getTheme(), - selection: penpot.getSelection(), + shapes: penpot.getSelectedShapes(), }, }); } }); -penpot.on('selectionchange', (id) => { - penpot.ui.sendMessage({ type: 'selection', content: id }); +penpot.on('selectionchange', () => { + const shapes = penpot.getSelectedShapes(); + penpot.ui.sendMessage({ type: 'selection', content: { shapes } }); }); -penpot.on('themechange', (theme) => { - penpot.ui.sendMessage({ type: 'theme', content: theme }); -}); - -penpot.on('pagechange', (page) => { - penpot.ui.sendMessage({ type: 'page', content: page }); +penpot.on('themechange', () => { + const theme = penpot.getTheme(); + penpot.ui.sendMessage({ type: 'theme', content: { theme } }); }); diff --git a/apps/example-plugin/src/plugin.ts b/apps/example-plugin/src/plugin.ts index 4456a19..f974b6c 100644 --- a/apps/example-plugin/src/plugin.ts +++ b/apps/example-plugin/src/plugin.ts @@ -27,13 +27,14 @@ penpot.ui.onMessage<{ content: string }>((message) => { fileId: fileState.id, revn: fileState.revn, theme: penpot.getTheme(), - selection: penpot.getSelection(), + selection: penpot.getSelected(), }, }); } }); -penpot.on('pagechange', (page) => { +penpot.on('pagechange', () => { + const page = penpot.getPage(); penpot.ui.sendMessage({ type: 'page', content: { @@ -43,7 +44,8 @@ penpot.on('pagechange', (page) => { }); }); -penpot.on('filechange', (file) => { +penpot.on('filechange', () => { + const file = penpot.getFile(); penpot.ui.sendMessage({ type: 'file', content: { @@ -54,10 +56,12 @@ penpot.on('filechange', (file) => { }); }); -penpot.on('selectionchange', (selected) => { +penpot.on('selectionchange', () => { + const selected = penpot.getSelected(); penpot.ui.sendMessage({ type: 'selection', content: selected }); }); -penpot.on('themechange', (theme) => { +penpot.on('themechange', () => { + const theme = penpot.getTheme(); penpot.ui.sendMessage({ type: 'theme', content: theme }); }); diff --git a/apps/poc-state-read-plugin/src/plugin.ts b/apps/poc-state-read-plugin/src/plugin.ts index 1f87364..9ddd5ec 100644 --- a/apps/poc-state-read-plugin/src/plugin.ts +++ b/apps/poc-state-read-plugin/src/plugin.ts @@ -29,8 +29,8 @@ penpot.ui.onMessage<{ content: string }>((message) => { }); penpot.on('pagechange', () => { - const page = penpot.getPage(); - const shapes = page.findShapes(); + const page = penpot.getPage(); + const shapes = page?.findShapes(); penpot.ui.sendMessage({ type: 'page', @@ -49,7 +49,6 @@ penpot.on('filechange', () => { }); penpot.on('selectionchange', () => { - // const selected = await penpot.queryObject({id: selection[0]}); const selected: string[] = penpot.getSelected(); penpot.ui.sendMessage({ type: 'selection', content: selected }); }); diff --git a/libs/plugin-types/index.d.ts b/libs/plugin-types/index.d.ts index e18b73a..9e18291 100644 --- a/libs/plugin-types/index.d.ts +++ b/libs/plugin-types/index.d.ts @@ -1,14 +1,24 @@ -export interface PenpotPage { - name: string; +export interface PenpotFile { id: string; + name: string; + revn: number; +} +export interface PenpotPage { + id: string; + name: string; findShapes(): PenpotShape[]; } -export interface PenpotFile { - name: string; +export interface PenpotFill { + fillColor: string; + fillOpacity: number; +} + +export interface PenpotShape { id: string; - revn: number; + name: string; + fills: PenpotFill[] } export interface EventsMap { @@ -45,6 +55,7 @@ export interface Penpot { getPage: () => PenpotPage | null; getCurrentPage: () => PenpotPage | null; getSelected: () => string[]; + getSelectedShapes(): PenpotShape[]; getTheme: () => PenpotTheme; fetch: typeof fetch; } diff --git a/libs/plugins-runtime/src/lib/api/index.ts b/libs/plugins-runtime/src/lib/api/index.ts index c2654e5..b5e0f2a 100644 --- a/libs/plugins-runtime/src/lib/api/index.ts +++ b/libs/plugins-runtime/src/lib/api/index.ts @@ -2,6 +2,7 @@ import type { Penpot, EventsMap } from '@penpot/plugin-types'; import { Manifest, Permissions } from '../models/manifest.model'; import { OpenUIOptions } from '../models/open-ui-options.model'; +import { setModalTheme } from '../create-modal'; import openUIApi from './openUI.api'; import z from 'zod'; @@ -30,6 +31,9 @@ export function triggerEvent( type: keyof EventsMap, message: EventsMap[keyof EventsMap] ) { + if (type === 'themechange' && modal) { + setModalTheme(modal, message); + } const listeners = eventListeners.get(type) || []; listeners.forEach((listener) => listener(message)); } @@ -53,8 +57,9 @@ export function createApi(context: PenpotContext, manifest: Manifest) { const penpot: Penpot = { ui: { open: (name: string, url: string, options: OpenUIOptions) => { - const theme = context.getTheme() as 'dark' | 'light'; + const theme = context.getTheme() as 'light' | 'dark'; modal = openUIApi(name, url, theme, options); + setModalTheme(modal, theme); modal.addEventListener('close', closePlugin, { once: true, @@ -71,7 +76,6 @@ export function createApi(context: PenpotContext, manifest: Manifest) { onMessage: (callback: (message: T) => void) => { z.function().parse(callback); - uiMessagesCallbacks.push(callback as Callback); }, }, @@ -104,7 +108,6 @@ export function createApi(context: PenpotContext, manifest: Manifest) { } const listeners = eventListeners.get(type) || []; - listeners.push(callback as Callback); eventListeners.set(type, listeners); }, @@ -145,6 +148,11 @@ export function createApi(context: PenpotContext, manifest: Manifest) { return context.getSelected(); }, + getSelectedShapes(): PenpotShape[] { + checkPermission('selection:read'); + return context.getSelectedShapes(); + }, + getTheme(): PenpotTheme { return context.getTheme(); }, diff --git a/libs/plugins-runtime/src/lib/api/plugin-api.spec.ts b/libs/plugins-runtime/src/lib/api/plugin-api.spec.ts index c1bcbbb..b01a3e9 100644 --- a/libs/plugins-runtime/src/lib/api/plugin-api.spec.ts +++ b/libs/plugins-runtime/src/lib/api/plugin-api.spec.ts @@ -1,10 +1,6 @@ import { expect, describe, vi } from 'vitest'; import { createApi, - setFileState, - setPageState, - setSelection, - setTheme, triggerEvent, uiMessagesCallbacks, } from './index.js'; @@ -29,7 +25,16 @@ vi.hoisted(() => { }); describe('Plugin api', () => { - const api = createApi({ + const mockContext = { + addListener: vi.fn(), + getFile: vi.fn(), + getPage: vi.fn(), + getSelected: vi.fn(), + getSelectedShapes: vi.fn(), + getTheme: vi.fn(() => 'dark'), + }; + + const api = createApi(mockContext, { name: 'test', code: '', permissions: ['page:read', 'file:read', 'selection:read'], @@ -209,9 +214,9 @@ describe('Plugin api', () => { id: '123', }; - setPageState(examplePage); + mockContext.getPage.mockImplementation(() => examplePage); - const pageState = api.getPageState(); + const pageState = api.getPage(); expect(pageState).toEqual(examplePage); }); @@ -223,9 +228,9 @@ describe('Plugin api', () => { revn: 0, } as FileState; - setFileState(exampleFile); + mockContext.getFile.mockImplementation(() => exampleFile); - const fileState = api.getFileState(); + const fileState = api.getFile(); expect(fileState).toEqual(exampleFile); }); @@ -233,9 +238,9 @@ describe('Plugin api', () => { it('get selection', () => { const selection = ['123']; - setSelection(selection); + mockContext.getSelected.mockImplementation(() => selection); - const currentSelection = api.getSelection(); + const currentSelection = api.getSelected(); expect(currentSelection).toEqual(selection); }); @@ -246,12 +251,11 @@ describe('Plugin api', () => { const options = { width: 100, height: 100 }; const openUIApiMock = vi.mocked(openUIApi); + mockContext.getTheme.mockImplementation(() => 'light'); + api.ui.open(name, url, options); - setTheme('light'); - const modalMock = openUIApiMock.mock.results[0].value; - expect(modalMock.setAttribute).toHaveBeenCalledWith('data-theme', 'light'); expect(modalMock.setAttribute).toHaveBeenCalledTimes(1); expect(api.getTheme()).toBe('light'); diff --git a/libs/plugins-runtime/src/lib/global.d.ts b/libs/plugins-runtime/src/lib/global.d.ts index 5a5777a..b6970d2 100644 --- a/libs/plugins-runtime/src/lib/global.d.ts +++ b/libs/plugins-runtime/src/lib/global.d.ts @@ -8,6 +8,7 @@ export declare global { getCurrentPage(): PenpotPage; getPage(): PenpotPage; getSelected(): string[]; + getSelectedShapes(): PenpotShape[]; getTheme(): PenpotTheme; } } diff --git a/package.json b/package.json index 437dd87..8f18591 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "start": "npx nx run plugins-runtime:build --watch & npx nx run plugins-runtime:preview", "start:example": "npx nx run example-plugin:build --watch & npx nx run example-plugin:preview", "start:read-plugin": "npx nx run poc-state-read-plugin:build --watch & npx nx run poc-state-read-plugin:preview", + "start:contrast-plugin": "npx nx run contrast-plugin:build --watch & npx nx run contrast-plugin:preview", "start:rpc-api": "npx nx serve rpc-api", "start:styles-example": "npx nx run example-styles:serve --port 4202", "build": "npx nx build plugins-runtime --emptyOutDir=true",