From 9be5235bc3e74faec3d785baa2d32bafda682460 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marina=20L=C3=B3pez?= Date: Thu, 7 Mar 2024 10:34:06 +0100 Subject: [PATCH] feat: contrast plugin --- apps/contrast-plugin/.babelrc | 3 + apps/contrast-plugin/.eslintrc.json | 18 ++ apps/contrast-plugin/.swcrc | 8 + apps/contrast-plugin/index.html | 16 ++ apps/contrast-plugin/project.json | 8 + apps/contrast-plugin/public/favicon.ico | Bin 0 -> 15086 bytes apps/contrast-plugin/public/manifest.json | 10 + apps/contrast-plugin/src/app/app.element.css | 95 +++++++++ .../src/app/app.element.spec.ts | 21 ++ apps/contrast-plugin/src/app/app.element.ts | 184 ++++++++++++++++++ apps/contrast-plugin/src/assets/.gitkeep | 0 apps/contrast-plugin/src/main.ts | 1 + apps/contrast-plugin/src/plugin.ts | 27 +++ apps/contrast-plugin/src/styles.css | 1 + apps/contrast-plugin/tsconfig.app.json | 9 + apps/contrast-plugin/tsconfig.json | 30 +++ apps/contrast-plugin/tsconfig.spec.json | 26 +++ apps/contrast-plugin/vite.config.ts | 58 ++++++ apps/example-plugin/src/plugin.ts | 1 + docs/plugin-usage.md | 1 + libs/plugins-runtime/package.json | 1 + libs/plugins-runtime/src/index.ts | 6 +- libs/plugins-runtime/src/lib/api/index.ts | 13 +- libs/plugins-runtime/src/lib/create-modal.ts | 1 + libs/plugins-runtime/src/lib/index.d.ts | 2 +- .../src/lib/models/open-ui-options.schema.ts | 1 + libs/plugins-runtime/src/lib/plugin-modal.ts | 18 +- libs/plugins-styles/src/lib/core/fonts.css | 6 +- 28 files changed, 552 insertions(+), 13 deletions(-) create mode 100644 apps/contrast-plugin/.babelrc create mode 100644 apps/contrast-plugin/.eslintrc.json create mode 100644 apps/contrast-plugin/.swcrc create mode 100644 apps/contrast-plugin/index.html create mode 100644 apps/contrast-plugin/project.json create mode 100644 apps/contrast-plugin/public/favicon.ico create mode 100644 apps/contrast-plugin/public/manifest.json create mode 100644 apps/contrast-plugin/src/app/app.element.css create mode 100644 apps/contrast-plugin/src/app/app.element.spec.ts create mode 100644 apps/contrast-plugin/src/app/app.element.ts create mode 100644 apps/contrast-plugin/src/assets/.gitkeep create mode 100644 apps/contrast-plugin/src/main.ts create mode 100644 apps/contrast-plugin/src/plugin.ts create mode 100644 apps/contrast-plugin/src/styles.css create mode 100644 apps/contrast-plugin/tsconfig.app.json create mode 100644 apps/contrast-plugin/tsconfig.json create mode 100644 apps/contrast-plugin/tsconfig.spec.json create mode 100644 apps/contrast-plugin/vite.config.ts diff --git a/apps/contrast-plugin/.babelrc b/apps/contrast-plugin/.babelrc new file mode 100644 index 0000000..f2f3806 --- /dev/null +++ b/apps/contrast-plugin/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["@nx/js/babel"] +} diff --git a/apps/contrast-plugin/.eslintrc.json b/apps/contrast-plugin/.eslintrc.json new file mode 100644 index 0000000..9d9c0db --- /dev/null +++ b/apps/contrast-plugin/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/apps/contrast-plugin/.swcrc b/apps/contrast-plugin/.swcrc new file mode 100644 index 0000000..a2d5b04 --- /dev/null +++ b/apps/contrast-plugin/.swcrc @@ -0,0 +1,8 @@ +{ + "jsc": { + "parser": { + "syntax": "typescript" + }, + "target": "es2016" + } +} diff --git a/apps/contrast-plugin/index.html b/apps/contrast-plugin/index.html new file mode 100644 index 0000000..88aef45 --- /dev/null +++ b/apps/contrast-plugin/index.html @@ -0,0 +1,16 @@ + + + + + ContrastPlugin + + + + + + + + + + + diff --git a/apps/contrast-plugin/project.json b/apps/contrast-plugin/project.json new file mode 100644 index 0000000..497af03 --- /dev/null +++ b/apps/contrast-plugin/project.json @@ -0,0 +1,8 @@ +{ + "name": "contrast-plugin", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "projectType": "application", + "sourceRoot": "apps/contrast-plugin/src", + "tags": ["type:plugin"], + "targets": {} +} diff --git a/apps/contrast-plugin/public/favicon.ico b/apps/contrast-plugin/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..317ebcb2336e0833a22dddf0ab287849f26fda57 GIT binary patch literal 15086 zcmeI332;U^%p|z7g|#(P)qFEA@4f!_@qOK2 z_lJl}!lhL!VT_U|uN7%8B2iKH??xhDa;*`g{yjTFWHvXn;2s{4R7kH|pKGdy(7z!K zgftM+Ku7~24TLlh(!g)gz|foI94G^t2^IO$uvX$3(OR0<_5L2sB)lMAMy|+`xodJ{ z_Uh_1m)~h?a;2W{dmhM;u!YGo=)OdmId_B<%^V^{ovI@y`7^g1_V9G}*f# zNzAtvou}I!W1#{M^@ROc(BZ! z+F!!_aR&Px3_reO(EW+TwlW~tv*2zr?iP7(d~a~yA|@*a89IUke+c472NXM0wiX{- zl`UrZC^1XYyf%1u)-Y)jj9;MZ!SLfd2Hl?o|80Su%Z?To_=^g_Jt0oa#CT*tjx>BI z16wec&AOWNK<#i0Qd=1O$fymLRoUR*%;h@*@v7}wApDl^w*h}!sYq%kw+DKDY)@&A z@9$ULEB3qkR#85`lb8#WZw=@})#kQig9oqy^I$dj&k4jU&^2(M3q{n1AKeGUKPFbr z1^<)aH;VsG@J|B&l>UtU#Ejv3GIqERzYgL@UOAWtW<{p#zy`WyJgpCy8$c_e%wYJL zyGHRRx38)HyjU3y{-4z6)pzb>&Q1pR)B&u01F-|&Gx4EZWK$nkUkOI|(D4UHOXg_- zw{OBf!oWQUn)Pe(=f=nt=zkmdjpO^o8ZZ9o_|4tW1ni+Un9iCW47*-ut$KQOww!;u z`0q)$s6IZO!~9$e_P9X!hqLxu`fpcL|2f^I5d4*a@Dq28;@2271v_N+5HqYZ>x;&O z05*7JT)mUe&%S0@UD)@&8SmQrMtsDfZT;fkdA!r(S=}Oz>iP)w=W508=Rc#nNn7ym z1;42c|8($ALY8#a({%1#IXbWn9-Y|0eDY$_L&j{63?{?AH{);EzcqfydD$@-B`Y3<%IIj7S7rK_N}je^=dEk%JQ4c z!tBdTPE3Tse;oYF>cnrapWq*o)m47X1`~6@(!Y29#>-#8zm&LXrXa(3=7Z)ElaQqj z-#0JJy3Fi(C#Rx(`=VXtJ63E2_bZGCz+QRa{W0e2(m3sI?LOcUBx)~^YCqZ{XEPX)C>G>U4tfqeH8L(3|pQR*zbL1 zT9e~4Tb5p9_G}$y4t`i*4t_Mr9QYvL9C&Ah*}t`q*}S+VYh0M6GxTTSXI)hMpMpIq zD1ImYqJLzbj0}~EpE-aH#VCH_udYEW#`P2zYmi&xSPs_{n6tBj=MY|-XrA;SGA_>y zGtU$?HXm$gYj*!N)_nQ59%lQdXtQZS3*#PC-{iB_sm+ytD*7j`D*k(P&IH2GHT}Eh z5697eQECVIGQAUe#eU2I!yI&%0CP#>%6MWV z@zS!p@+Y1i1b^QuuEF*13CuB zu69dve5k7&Wgb+^s|UB08Dr3u`h@yM0NTj4h7MnHo-4@xmyr7(*4$rpPwsCDZ@2be zRz9V^GnV;;?^Lk%ynzq&K(Aix`mWmW`^152Hoy$CTYVehpD-S1-W^#k#{0^L`V6CN+E z!w+xte;2vu4AmVNEFUOBmrBL>6MK@!O2*N|2=d|Y;oN&A&qv=qKn73lDD zI(+oJAdgv>Yr}8(&@ZuAZE%XUXmX(U!N+Z_sjL<1vjy1R+1IeHt`79fnYdOL{$ci7 z%3f0A*;Zt@ED&Gjm|OFTYBDe%bbo*xXAQsFz+Q`fVBH!N2)kaxN8P$c>sp~QXnv>b zwq=W3&Mtmih7xkR$YA)1Yi?avHNR6C99!u6fh=cL|KQ&PwF!n@ud^n(HNIImHD!h87!i*t?G|p0o+eelJ?B@A64_9%SBhNaJ64EvKgD&%LjLCYnNfc; znj?%*p@*?dq#NqcQFmmX($wms@CSAr9#>hUR^=I+=0B)vvGX%T&#h$kmX*s=^M2E!@N9#m?LhMvz}YB+kd zG~mbP|D(;{s_#;hsKK9lbVK&Lo734x7SIFJ9V_}2$@q?zm^7?*XH94w5Qae{7zOMUF z^?%F%)c1Y)Q?Iy?I>knw*8gYW#ok|2gdS=YYZLiD=CW|Nj;n^x!=S#iJ#`~Ld79+xXpVmUK^B(xO_vO!btA9y7w3L3-0j-y4 z?M-V{%z;JI`bk7yFDcP}OcCd*{Q9S5$iGA7*E1@tfkyjAi!;wP^O71cZ^Ep)qrQ)N z#wqw0_HS;T7x3y|`P==i3hEwK%|>fZ)c&@kgKO1~5<5xBSk?iZV?KI6&i72H6S9A* z=U(*e)EqEs?Oc04)V-~K5AUmh|62H4*`UAtItO$O(q5?6jj+K^oD!04r=6#dsxp?~}{`?&sXn#q2 zGuY~7>O2=!u@@Kfu7q=W*4egu@qPMRM>(eyYyaIE<|j%d=iWNdGsx%c!902v#ngNg z@#U-O_4xN$s_9?(`{>{>7~-6FgWpBpqXb`Ydc3OFL#&I}Irse9F_8R@4zSS*Y*o*B zXL?6*Aw!AfkNCgcr#*yj&p3ZDe2y>v$>FUdKIy_2N~}6AbHc7gA3`6$g@1o|dE>vz z4pl(j9;kyMsjaw}lO?(?Xg%4k!5%^t#@5n=WVc&JRa+XT$~#@rldvN3S1rEpU$;XgxVny7mki3 z-Hh|jUCHrUXuLr!)`w>wgO0N%KTB-1di>cj(x3Bav`7v z3G7EIbU$z>`Nad7Rk_&OT-W{;qg)-GXV-aJT#(ozdmnA~Rq3GQ_3mby(>q6Ocb-RgTUhTN)))x>m&eD;$J5Bg zo&DhY36Yg=J=$Z>t}RJ>o|@hAcwWzN#r(WJ52^g$lh^!63@hh+dR$&_dEGu&^CR*< z!oFqSqO@>xZ*nC2oiOd0eS*F^IL~W-rsrO`J`ej{=ou_q^_(<$&-3f^J z&L^MSYWIe{&pYq&9eGaArA~*kA { + let app: AppElement; + + beforeEach(() => { + app = new AppElement(); + }); + + it('should create successfully', () => { + expect(app).toBeTruthy(); + }); + + it('should have a greeting', () => { + app.connectedCallback(); + + expect(app.querySelector('h1').innerHTML).toContain( + 'Welcome contrast-plugin' + ); + }); +}); diff --git a/apps/contrast-plugin/src/app/app.element.ts b/apps/contrast-plugin/src/app/app.element.ts new file mode 100644 index 0000000..7ca5dff --- /dev/null +++ b/apps/contrast-plugin/src/app/app.element.ts @@ -0,0 +1,184 @@ +import 'plugins-styles/lib/styles.css'; +import './app.element.css'; + +export class AppElement extends HTMLElement { + public static observedAttributes = []; + + calculateContrast(firstColor: string, secondColor: string) { + const luminosityFirstColor = this.getLuminosity(firstColor); + const luminositySecondColor = this.getLuminosity(secondColor); + + const result = (luminosityFirstColor + 0.05) / (luminositySecondColor + 0.05); + this.setColors(firstColor, secondColor); + this.setResult(result.toFixed(2).toString()); + this.setA11yTags(result); + } + + getLuminosity(color: string) { + const rgb = this.hexToRgb(color); + return 0.2126 * (rgb[0]/255) + 0.7152 * (rgb[1]/255) + 0.0722 * (rgb[2]/255); + } + + hexToRgb(hex: string) { + const r = parseInt(hex.slice(1, 3), 16) + const g = parseInt(hex.slice(3, 5), 16) + const b = parseInt(hex.slice(5, 7), 16) + return [ r, g, b ]; + } + + setResult(text: string) { + const selector = document.getElementById('result'); + + if (selector) { + selector.innerText = `${text} : 1`; + } + } + + setColors(firstColor: string | null, secondColor: string | null) { + const color1 = document.getElementById('first-color'); + const color2 = document.getElementById('second-color'); + const code1 = document.getElementById('first-color-code'); + const code2 = document.getElementById('second-color-code'); + const contrastPreview = document.getElementById('contrast-preview'); + const smallText = document.getElementById('small-text'); + const largeText = document.getElementById('large-text'); + const circle = document.getElementById('circle'); + const square = document.getElementById('square'); + const triangle = document.getElementById('triangle'); + + if (color1 && code1) { + color1.style.background = firstColor ? firstColor : 'transparent'; + code1.innerText = firstColor ? firstColor : ''; + } + + if (color2 && code2) { + color2.style.background = secondColor ? secondColor : 'transparent'; + code2.innerText = secondColor ? secondColor : ''; + } + + if (contrastPreview && smallText && largeText && circle && square && triangle) { + contrastPreview.style.background = secondColor ? secondColor : 'transparent'; + smallText.style.color = firstColor ? firstColor : 'transparent'; + largeText.style.color = firstColor ? firstColor : 'transparent'; + circle.style.background = firstColor ? firstColor : 'transparent'; + square.style.background = firstColor ? firstColor : 'transparent'; + triangle.style.borderBottom = firstColor ? `var(--spacing-24) solid ${firstColor}` : 'var(--spacing-24) solid transparent'; + } + + const emptyPreview = document.getElementById('empty-preview'); + if (!firstColor && !secondColor && emptyPreview) { + emptyPreview.style.display = 'block'; + } else if (emptyPreview) { + emptyPreview.style.display = 'none'; + } + } + + setA11yTags(result: number) { + const selectors = { + aa: document.getElementById('aa'), + aaa: document.getElementById('aaa'), + aaLg: document.getElementById('aa-lg'), + aaaLg: document.getElementById('aaa-lg'), + graphics: document.getElementById('graphics') + }; + const fail = 'tag fail'; + const good = 'tag good'; + + function setClass(selector: HTMLElement | null, className: string) { + if (selector) { + selector.className = className; + } + } + + if (result > 7) { + setClass(selectors.aa, good); + setClass(selectors.aaa, good); + setClass(selectors.aaLg, good); + setClass(selectors.aaaLg, good); + setClass(selectors.graphics, good); + } else if (result > 4.5) { + setClass(selectors.aa, good); + setClass(selectors.aaa, fail); + setClass(selectors.aaLg, good); + setClass(selectors.aaaLg, good); + setClass(selectors.graphics, good); + } else if (result > 3) { + setClass(selectors.aa, fail); + setClass(selectors.aaa, fail); + setClass(selectors.aaLg, good); + setClass(selectors.aaaLg, fail); + setClass(selectors.graphics, good); + } else { + setClass(selectors.aa, fail); + setClass(selectors.aaa, fail); + setClass(selectors.aaLg, fail); + setClass(selectors.aaaLg, fail); + setClass(selectors.graphics, fail); + } + } + + connectedCallback() { + window.addEventListener('message', (event) => { + if (event.data.type === 'selection') { + if (event.data.content.length === 2) { + this.calculateContrast('#d5d1d1', '#000410'); + } else { + this.setColors(null, null); + this.setResult('0'); + this.setA11yTags(0); + } + } else if (event.data.type === 'page') { + console.log('refrespage', event.data); + } else if (event.data.type === 'init') { + if (event.data.content.selection.length === 2) { + //TODO get real colors from selection + this.calculateContrast('#d5d1d1', '#000410'); + } + } + }); + + this.innerHTML = ` +
+
+

Select two colors to calculate contrast

+

SMALL sample text

+

LARGE sample text

+
    + + + +
+
+

Selected colors:

+
    +
  • + + +
  • +
  • + + +
  • +
+

Contrast ratio: 0 : 1

+

Normal text:

+
    +
  • AA
  • +
  • AAA
  • +
+

Large text (24px or 19px + bold):

+
    +
  • AA
  • +
  • AAA
  • +
+

Graphics (such as form input borders):

+
    +
  • AA
  • +
+
+ `; + + parent.postMessage({ content: 'ready' }, '*'); + } +} +customElements.define('app-root', AppElement); diff --git a/apps/contrast-plugin/src/assets/.gitkeep b/apps/contrast-plugin/src/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/apps/contrast-plugin/src/main.ts b/apps/contrast-plugin/src/main.ts new file mode 100644 index 0000000..fdb879d --- /dev/null +++ b/apps/contrast-plugin/src/main.ts @@ -0,0 +1 @@ +import './app/app.element'; diff --git a/apps/contrast-plugin/src/plugin.ts b/apps/contrast-plugin/src/plugin.ts new file mode 100644 index 0000000..acd637c --- /dev/null +++ b/apps/contrast-plugin/src/plugin.ts @@ -0,0 +1,27 @@ +penpot.ui.open('Contrast plugin', 'http://localhost:4201', { + theme: 'dark', + width: 450, + height: 625, +}); + +penpot.ui.onMessage<{ content: string }>((message) => { + if (message.content === 'ready') { + const pageState = penpot.getPageState(); + const fileState = penpot.getFileState(); + + penpot.ui.sendMessage({ + type: 'init', + content: { + name: pageState.name, + pageId: pageState.id, + fileId: fileState.id, + revn: fileState.revn, + selection: penpot.getSelection(), + }, + }); + } +}); + +penpot.on('selectionchange', (id) => { + penpot.ui.sendMessage({ type: 'selection', content: id }); +}); \ No newline at end of file diff --git a/apps/contrast-plugin/src/styles.css b/apps/contrast-plugin/src/styles.css new file mode 100644 index 0000000..90d4ee0 --- /dev/null +++ b/apps/contrast-plugin/src/styles.css @@ -0,0 +1 @@ +/* You can add global styles to this file, and also import other style files */ diff --git a/apps/contrast-plugin/tsconfig.app.json b/apps/contrast-plugin/tsconfig.app.json new file mode 100644 index 0000000..14d797a --- /dev/null +++ b/apps/contrast-plugin/tsconfig.app.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": ["node"] + }, + "exclude": ["src/**/*.spec.ts", "src/**/*.test.ts"], + "include": ["src/**/*.ts", "../../libs/plugins-runtime/src/lib/index.d.ts"] +} diff --git a/apps/contrast-plugin/tsconfig.json b/apps/contrast-plugin/tsconfig.json new file mode 100644 index 0000000..cee4bba --- /dev/null +++ b/apps/contrast-plugin/tsconfig.json @@ -0,0 +1,30 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ESNext", "DOM"], + "moduleResolution": "Node", + "strict": true, + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "noEmit": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "skipLibCheck": true, + "types": ["vite/client"] + }, + "include": ["src"], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/apps/contrast-plugin/tsconfig.spec.json b/apps/contrast-plugin/tsconfig.spec.json new file mode 100644 index 0000000..3c002c2 --- /dev/null +++ b/apps/contrast-plugin/tsconfig.spec.json @@ -0,0 +1,26 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": [ + "vitest/globals", + "vitest/importMeta", + "vite/client", + "node", + "vitest" + ] + }, + "include": [ + "vite.config.ts", + "vitest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.test.tsx", + "src/**/*.spec.tsx", + "src/**/*.test.js", + "src/**/*.spec.js", + "src/**/*.test.jsx", + "src/**/*.spec.jsx", + "src/**/*.d.ts" + ] +} diff --git a/apps/contrast-plugin/vite.config.ts b/apps/contrast-plugin/vite.config.ts new file mode 100644 index 0000000..4cc4784 --- /dev/null +++ b/apps/contrast-plugin/vite.config.ts @@ -0,0 +1,58 @@ +/// +import { defineConfig } from 'vite'; + +import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin'; + +export default defineConfig({ + root: __dirname, + cacheDir: '../../node_modules/.vite/apps/contrast-plugin', + + server: { + port: 4200, + host: 'localhost', + }, + + preview: { + port: 4300, + host: 'localhost', + }, + + plugins: [nxViteTsPaths()], + + // Uncomment this if you are using workers. + // worker: { + // plugins: [ nxViteTsPaths() ], + // }, + + build: { + outDir: '../../dist/apps/contrast-plugin', + reportCompressedSize: true, + commonjsOptions: { + transformMixedEsModules: true, + }, + rollupOptions: { + input: { + plugin: 'src/plugin.ts', + index: './index.html', + }, + output: { + entryFileNames: '[name].js', + }, + }, + }, + + test: { + globals: true, + cache: { + dir: '../../node_modules/.vitest', + }, + environment: 'jsdom', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + + reporters: ['default'], + coverage: { + reportsDirectory: '../../coverage/apps/contrast-plugin', + provider: 'v8', + }, + }, +}); diff --git a/apps/example-plugin/src/plugin.ts b/apps/example-plugin/src/plugin.ts index 36869d2..c3b98bd 100644 --- a/apps/example-plugin/src/plugin.ts +++ b/apps/example-plugin/src/plugin.ts @@ -1,6 +1,7 @@ penpot.log('Hello from plugin'); penpot.ui.open('Plugin name', 'http://localhost:4201', { + theme: 'light', width: 500, height: 600, }); diff --git a/docs/plugin-usage.md b/docs/plugin-usage.md index dc9711c..b728a50 100644 --- a/docs/plugin-usage.md +++ b/docs/plugin-usage.md @@ -2,6 +2,7 @@ Open UI: ```ts penpot.ui.open('Plugin name', 'http://localhost:4201', { + theme: 'light', width: 500, height: 600, }); diff --git a/libs/plugins-runtime/package.json b/libs/plugins-runtime/package.json index a63950a..a8900d2 100644 --- a/libs/plugins-runtime/package.json +++ b/libs/plugins-runtime/package.json @@ -5,6 +5,7 @@ "happy-dom": "^13.6.2" }, "dependencies": { + "plugins-data-parser": "^0.0.1", "vitest": "1.2.2", "ses": "^1.1.0", "zod": "^3.22.4" diff --git a/libs/plugins-runtime/src/index.ts b/libs/plugins-runtime/src/index.ts index 68929e1..7d882e5 100644 --- a/libs/plugins-runtime/src/index.ts +++ b/libs/plugins-runtime/src/index.ts @@ -3,6 +3,7 @@ import './lib/plugin-modal'; import { ɵloadPlugin } from './lib/load-plugin'; import { setFileState, setPageState, setSelection } from './lib/api'; +import { getSelectedUuids } from 'plugins-parser'; repairIntrinsics({ evalTaming: 'unsafeEval', @@ -32,8 +33,9 @@ export function initialize(api: any) { // eslint-disable-next-line @typescript-eslint/no-explicit-any api.addListener('plugin-selection', 'selection', (selection: any) => { - console.log('Selection Changed:', selection); + const selectionData = getSelectedUuids(selection); + console.log('Selection Changed:', selectionData); - setSelection(selection?.linked_map?.head?.uuid); + setSelection(selectionData); }); } diff --git a/libs/plugins-runtime/src/lib/api/index.ts b/libs/plugins-runtime/src/lib/api/index.ts index fc4b2df..d5dcf96 100644 --- a/libs/plugins-runtime/src/lib/api/index.ts +++ b/libs/plugins-runtime/src/lib/api/index.ts @@ -11,9 +11,12 @@ export let uiMessagesCallbacks: Callback[] = []; let modal: HTMLElement | null = null; -let pageState: Page | null = null; -let fileState: File | null = null; -let selection: null | string = null; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +let pageState = {} as any; +// eslint-disable-next-line @typescript-eslint/no-explicit-any +let fileState = {} as any; + +let selection: null | string[] = null; const eventListeners: Map[]> = new Map(); @@ -43,8 +46,8 @@ export function setFileState(file: File) { triggerEvent('filechange', file); } -export function setSelection(selectionId: string) { - if (selectionId === selection) { +export function setSelection(selectionId: string[]) { + if (JSON.stringify(selectionId) === JSON.stringify(selection)) { return; } diff --git a/libs/plugins-runtime/src/lib/create-modal.ts b/libs/plugins-runtime/src/lib/create-modal.ts index 6cf4300..a6468ad 100644 --- a/libs/plugins-runtime/src/lib/create-modal.ts +++ b/libs/plugins-runtime/src/lib/create-modal.ts @@ -3,6 +3,7 @@ import { OpenUIOptions } from './models/open-ui-options.model'; export function createModal(name: string, url: string, options: OpenUIOptions) { const modal = document.createElement('plugin-modal'); + modal.setAttribute('data-theme', options.theme); modal.setAttribute('title', name); modal.setAttribute('iframe-src', url); modal.setAttribute('width', String(options.width || 300)); diff --git a/libs/plugins-runtime/src/lib/index.d.ts b/libs/plugins-runtime/src/lib/index.d.ts index 6f88630..da7de9f 100644 --- a/libs/plugins-runtime/src/lib/index.d.ts +++ b/libs/plugins-runtime/src/lib/index.d.ts @@ -14,7 +14,7 @@ interface File { interface EventsMap { pagechange: Page; filechange: File; - selectionchange: string; + selectionchange: string[]; } interface Penpot { diff --git a/libs/plugins-runtime/src/lib/models/open-ui-options.schema.ts b/libs/plugins-runtime/src/lib/models/open-ui-options.schema.ts index f008033..2cf9ba1 100644 --- a/libs/plugins-runtime/src/lib/models/open-ui-options.schema.ts +++ b/libs/plugins-runtime/src/lib/models/open-ui-options.schema.ts @@ -1,6 +1,7 @@ import { z } from 'zod'; export const openUISchema = z.object({ + theme: z.literal('dark') || z.literal('light'), width: z.number().positive(), height: z.number().positive(), }); diff --git a/libs/plugins-runtime/src/lib/plugin-modal.ts b/libs/plugins-runtime/src/lib/plugin-modal.ts index bd8f9b3..2d8b048 100644 --- a/libs/plugins-runtime/src/lib/plugin-modal.ts +++ b/libs/plugins-runtime/src/lib/plugin-modal.ts @@ -76,17 +76,28 @@ export class PluginModalElement extends HTMLElement { display: flex; flex-direction: column; position: fixed; - inset-block-end: 10px; + inset-block-start: 10px; inset-inline-start: 10px; z-index: 1000; - background: white; padding: 20px; - border-radius: 5px; + border-radius: 20px; box-shadow: 0 4px 8px rgba(0,0,0,0.1); inline-size: ${width}px; block-size: ${height}px; } + :host([data-theme="dark"]) { + background: #2e3434; + border: 1px solid #2e3434; + color: #ffffff; + } + + :host([data-theme="light"]) { + background: #ffffff; + border: 1px solid #eef0f2; + color: #18181a; + } + .header { display: flex; justify-content: space-between; @@ -99,7 +110,6 @@ export class PluginModalElement extends HTMLElement { } h1 { - color: #000; font-family: Arial, sans-serif; margin: 0; margin-block-end: 10px; diff --git a/libs/plugins-styles/src/lib/core/fonts.css b/libs/plugins-styles/src/lib/core/fonts.css index 7bae92f..0d12885 100644 --- a/libs/plugins-styles/src/lib/core/fonts.css +++ b/libs/plugins-styles/src/lib/core/fonts.css @@ -18,6 +18,10 @@ html, body { font-style: normal; } +code { + font-family: 'Work Sans', sans-serif; +} + .display { font-weight: var(--font-weight-regular); font-size: 36px; @@ -87,7 +91,7 @@ html, body { line-height: var(--font-line-height-s); } -.code-font { +code, .code-font { font-weight: var(--font-weight-regular); font-size: var(--font-size-s); line-height: var(--font-line-height-l);