mirror of
https://github.com/penpot/penpot-plugins.git
synced 2025-01-06 14:50:21 -05:00
Merge pull request #9 from penpot/contrast-plugin
feat: contrast plugin
This commit is contained in:
commit
06db3af490
28 changed files with 552 additions and 13 deletions
3
apps/contrast-plugin/.babelrc
Normal file
3
apps/contrast-plugin/.babelrc
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"presets": ["@nx/js/babel"]
|
||||
}
|
18
apps/contrast-plugin/.eslintrc.json
Normal file
18
apps/contrast-plugin/.eslintrc.json
Normal file
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"extends": ["../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
8
apps/contrast-plugin/.swcrc
Normal file
8
apps/contrast-plugin/.swcrc
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"jsc": {
|
||||
"parser": {
|
||||
"syntax": "typescript"
|
||||
},
|
||||
"target": "es2016"
|
||||
}
|
||||
}
|
16
apps/contrast-plugin/index.html
Normal file
16
apps/contrast-plugin/index.html
Normal file
|
@ -0,0 +1,16 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>ContrastPlugin</title>
|
||||
<base href="/" />
|
||||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
||||
<link rel="stylesheet" href="/src/styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
8
apps/contrast-plugin/project.json
Normal file
8
apps/contrast-plugin/project.json
Normal file
|
@ -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": {}
|
||||
}
|
BIN
apps/contrast-plugin/public/favicon.ico
Normal file
BIN
apps/contrast-plugin/public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
10
apps/contrast-plugin/public/manifest.json
Normal file
10
apps/contrast-plugin/public/manifest.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"name": "Contrast plugin",
|
||||
"code": "http://localhost:4201/plugin.js",
|
||||
"permissions": [
|
||||
"page:read",
|
||||
"file:read",
|
||||
"selection:read"
|
||||
]
|
||||
}
|
||||
|
95
apps/contrast-plugin/src/app/app.element.css
Normal file
95
apps/contrast-plugin/src/app/app.element.css
Normal file
|
@ -0,0 +1,95 @@
|
|||
.wrapper {
|
||||
color: var(--app-white);
|
||||
}
|
||||
|
||||
.color {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin-inline-end: var(--spacing-16);
|
||||
}
|
||||
|
||||
.color-preview {
|
||||
block-size: var(--spacing-36);
|
||||
border: 1px solid var(--df-secondary);
|
||||
border-radius: var(--spacing-4);
|
||||
display: block;
|
||||
inline-size: var(--spacing-36);
|
||||
margin-inline-end: var(--spacing-16);
|
||||
}
|
||||
|
||||
.fail {
|
||||
background-color: var(--error-500);
|
||||
}
|
||||
|
||||
.good {
|
||||
background-color: var(--success-500);
|
||||
}
|
||||
|
||||
.title {
|
||||
margin-block-end: var(--spacing-8);
|
||||
}
|
||||
|
||||
.list {
|
||||
display: flex;
|
||||
margin-block-end: var(--spacing-16);
|
||||
}
|
||||
|
||||
.tag {
|
||||
border-radius: var(--spacing-4);
|
||||
color: var(--db-primary);
|
||||
margin-inline-end: var(--spacing-16);
|
||||
padding: var(--spacing-4) var(--spacing-8);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.contrast-preview {
|
||||
align-items: center;
|
||||
border: 1px solid var(--df-secondary);
|
||||
border-radius: var(--spacing-4);
|
||||
box-sizing: content-box;
|
||||
block-size: calc(2 * var(--spacing-40));
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
inline-size: calc(100% - var(--spacing-16));
|
||||
margin-block-end: var(--spacing-16);
|
||||
padding-block: var(--spacing-24);
|
||||
}
|
||||
|
||||
.empty-preview {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.text {
|
||||
color: transparent;
|
||||
margin-block-end: var(--spacing-8);
|
||||
|
||||
&.small {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
&.large {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.icons-list {
|
||||
display: flex;
|
||||
gap: var(--spacing-8);
|
||||
margin-block-start: var(--spacing-8);
|
||||
}
|
||||
|
||||
.shape {
|
||||
block-size: var(--spacing-24);
|
||||
inline-size: var(--spacing-24);
|
||||
}
|
||||
|
||||
.circle {
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.triangle {
|
||||
border-left: var(--spacing-12) solid transparent;
|
||||
border-right: var(--spacing-12) solid transparent;
|
||||
border-bottom: var(--spacing-24) solid transparent;
|
||||
}
|
21
apps/contrast-plugin/src/app/app.element.spec.ts
Normal file
21
apps/contrast-plugin/src/app/app.element.spec.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { AppElement } from './app.element';
|
||||
|
||||
describe('AppElement', () => {
|
||||
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'
|
||||
);
|
||||
});
|
||||
});
|
184
apps/contrast-plugin/src/app/app.element.ts
Normal file
184
apps/contrast-plugin/src/app/app.element.ts
Normal file
|
@ -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 = `
|
||||
<div class="wrapper">
|
||||
<div id="contrast-preview" class="contrast-preview">
|
||||
<p id="empty-preview" class="empty-preview">Select two colors to calculate contrast</p>
|
||||
<p id="small-text" data-color="text" data-second class="text small">SMALL sample text</p>
|
||||
<p id="large-text" data-color="text" data-second class="text large">LARGE sample text</p>
|
||||
<ul class="icons-list">
|
||||
<span id="circle" class="shape circle"></span>
|
||||
<span id="square" class="shape square"></span>
|
||||
<span id="triangle" class="triangle"></span>
|
||||
</ul>
|
||||
</div>
|
||||
<p class="title body-l">Selected colors:</p>
|
||||
<ul class="list">
|
||||
<li class="color">
|
||||
<span id="first-color" data-first class="color-preview"></span>
|
||||
<code id="first-color-code"></code>
|
||||
</li>
|
||||
<li class="color">
|
||||
<span id="second-color" data-second class="color-preview"></span>
|
||||
<code id="second-color-code"></code>
|
||||
</li>
|
||||
</ul>
|
||||
<p class="title body-l">Contrast ratio: <span id="result">0 : 1</span></p>
|
||||
<p class="title body-l">Normal text:</p>
|
||||
<ul class="list">
|
||||
<li id="aa" class="tag fail">AA</li>
|
||||
<li id="aaa" class="tag fail">AAA</li>
|
||||
</ul>
|
||||
<p class="title body-l">Large text (24px or 19px + bold):</p>
|
||||
<ul class="list">
|
||||
<li id="aa-lg" class="tag fail">AA</li>
|
||||
<li id="aaa-lg" class="tag fail">AAA</li>
|
||||
</ul>
|
||||
<p class="title body-l">Graphics (such as form input borders):</p>
|
||||
<ul class="list">
|
||||
<li id="graphics" class="tag fail">AA</li>
|
||||
</ul>
|
||||
</div>
|
||||
`;
|
||||
|
||||
parent.postMessage({ content: 'ready' }, '*');
|
||||
}
|
||||
}
|
||||
customElements.define('app-root', AppElement);
|
0
apps/contrast-plugin/src/assets/.gitkeep
Normal file
0
apps/contrast-plugin/src/assets/.gitkeep
Normal file
1
apps/contrast-plugin/src/main.ts
Normal file
1
apps/contrast-plugin/src/main.ts
Normal file
|
@ -0,0 +1 @@
|
|||
import './app/app.element';
|
27
apps/contrast-plugin/src/plugin.ts
Normal file
27
apps/contrast-plugin/src/plugin.ts
Normal file
|
@ -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 });
|
||||
});
|
1
apps/contrast-plugin/src/styles.css
Normal file
1
apps/contrast-plugin/src/styles.css
Normal file
|
@ -0,0 +1 @@
|
|||
/* You can add global styles to this file, and also import other style files */
|
9
apps/contrast-plugin/tsconfig.app.json
Normal file
9
apps/contrast-plugin/tsconfig.app.json
Normal file
|
@ -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"]
|
||||
}
|
30
apps/contrast-plugin/tsconfig.json
Normal file
30
apps/contrast-plugin/tsconfig.json
Normal file
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
26
apps/contrast-plugin/tsconfig.spec.json
Normal file
26
apps/contrast-plugin/tsconfig.spec.json
Normal file
|
@ -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"
|
||||
]
|
||||
}
|
58
apps/contrast-plugin/vite.config.ts
Normal file
58
apps/contrast-plugin/vite.config.ts
Normal file
|
@ -0,0 +1,58 @@
|
|||
/// <reference types='vitest' />
|
||||
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',
|
||||
},
|
||||
},
|
||||
});
|
|
@ -1,6 +1,7 @@
|
|||
penpot.log('Hello from plugin');
|
||||
|
||||
penpot.ui.open('Plugin name', 'http://localhost:4201', {
|
||||
theme: 'light',
|
||||
width: 500,
|
||||
height: 600,
|
||||
});
|
||||
|
|
|
@ -2,6 +2,7 @@ Open UI:
|
|||
|
||||
```ts
|
||||
penpot.ui.open('Plugin name', 'http://localhost:4201', {
|
||||
theme: 'light',
|
||||
width: 500,
|
||||
height: 600,
|
||||
});
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -11,9 +11,12 @@ export let uiMessagesCallbacks: Callback<unknown>[] = [];
|
|||
|
||||
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<string, Callback<unknown>[]> = 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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
2
libs/plugins-runtime/src/lib/index.d.ts
vendored
2
libs/plugins-runtime/src/lib/index.d.ts
vendored
|
@ -14,7 +14,7 @@ interface File {
|
|||
interface EventsMap {
|
||||
pagechange: Page;
|
||||
filechange: File;
|
||||
selectionchange: string;
|
||||
selectionchange: string[];
|
||||
}
|
||||
|
||||
interface Penpot {
|
||||
|
|
|
@ -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(),
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue