0
Fork 0
mirror of https://github.com/penpot/penpot-plugins.git synced 2025-01-02 04:40:11 -05:00

fix: i#7593 eslint flat config

This commit is contained in:
Juanfran 2024-05-07 10:28:40 +02:00
parent b6bf1baaa5
commit df7f3a21d8
84 changed files with 416 additions and 2292 deletions

View file

@ -1 +0,0 @@
node_modules

View file

@ -1,35 +0,0 @@
{
"root": true,
"ignorePatterns": ["**/*"],
"plugins": ["@nx"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {
"@nx/enforce-module-boundaries": [
"error",
{
"enforceBuildableLibDependency": true,
"allow": [],
"depConstraints": [
{
"sourceTag": "*",
"onlyDependOnLibsWithTags": ["*"]
}
]
}
]
}
},
{
"files": ["*.ts", "*.tsx"],
"extends": ["plugin:@nx/typescript"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"extends": ["plugin:@nx/javascript"],
"rules": {}
}
]
}

View file

@ -1,113 +0,0 @@
{
"ignorePatterns": ["**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {
"@nx/enforce-module-boundaries": [
"error",
{
"enforceBuildableLibDependency": true,
"allow": [],
"depConstraints": [
{
"sourceTag": "type:plugin",
"onlyDependOnLibsWithTags": [
"type:util",
"type:ui",
"type:feature"
]
},
{
"sourceTag": "type:app",
"onlyDependOnLibsWithTags": [
"type:util",
"type:ui",
"type:feature"
]
},
{
"sourceTag": "type:feature",
"onlyDependOnLibsWithTags": [
"type:feature",
"type:ui",
"type:util"
]
},
{
"sourceTag": "type:ui",
"onlyDependOnLibsWithTags": ["type:ui", "type:util"]
},
{
"sourceTag": "type:util",
"onlyDependOnLibsWithTags": ["type:util"]
}
]
}
]
}
},
{
"files": ["*.ts", "*.tsx"],
"extends": [
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"plugin:deprecation/recommended",
"prettier"
],
"rules": {
"@typescript-eslint/no-unused-vars": ["error"],
"no-multiple-empty-lines": [
2,
{
"max": 1
}
],
"quotes": [
"error",
"single",
{
"avoidEscape": true
}
]
}
},
{
"files": ["*.spec.ts"],
"extends": [
"plugin:@typescript-eslint/recommended",
"plugin:@typescript-eslint/recommended-requiring-type-checking",
"plugin:deprecation/recommended",
"prettier"
],
"rules": {
"@typescript-eslint/no-unused-vars": ["error"],
"no-multiple-empty-lines": [
2,
{
"max": 1
}
],
"quotes": [
"error",
"single",
{
"avoidEscape": true
}
],
"@typescript-eslint/no-unsafe-member-access": "off",
"@typescript-eslint/no-unsafe-call": "off",
"@typescript-eslint/no-unsafe-assignment": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-unsafe-argument": "off",
"@typescript-eslint/no-unsafe-return": "off",
"@ngrx/prefix-selectors-with-select": "off"
}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
],
"extends": ["./.eslintrc.base.json"]
}

View file

@ -8,15 +8,13 @@ There are 2 important folders to keep an eye on: `apps` and `libs`.
In the `libs` folder you'll find:
- plugins-data-parser: useful functions to parse the data we get from penpot.
It has its own [README](libs/plugins-data-parser/README.md).
- plugins-runtime: here you'll find the code that initializes the plugin and sets a few listeners to know when the penpot page/file/selection changes.
It has its own [README](libs/plugins-runtime/README.md).
- plugins-styles: basic css library with penpot styles in case you need help for styling your plugins.
In the `apps` folder you'll find some examples that use the libraries mentioned above.
- example-plugin or contrast-plugin: to run this example check <a href="#create-a-plugin-from-scratch-or-run-the-examples-from-the-apps-folder">Create a plugin from scratch</a>
- contrast-plugin: to run this example check <a href="#create-a-plugin-from-scratch-or-run-the-examples-from-the-apps-folder">Create a plugin from scratch</a>
- example-styles: to run this example you should run
@ -50,14 +48,14 @@ npm run start:example
or
npx nx run example-plugin:build --watch & npx nx run example-plugin:preview
npm run start:pc-plugin
```
or
```
// for the contrast plugin
npx nx run contrast-plugin:build --watch & npx nx run contrast-plugin:preview
npm run start:contrast-plugin
```
Open in your browser: `http://localhost:4210/`

View file

@ -1,22 +0,0 @@
{
"extends": ["../../.eslintrc.base.json", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*", "vite.config.ts"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./apps/contrast-plugin/tsconfig.app.json"
},
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

View file

@ -0,0 +1,25 @@
import baseConfig from '../../eslint.config.js';
import typescriptEslintParser from '@typescript-eslint/parser';
export default [
...baseConfig,
{
languageOptions: {
parser: typescriptEslintParser,
parserOptions: { project: './apps/contrast-plugin/tsconfig.app.json' },
},
},
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
rules: {},
},
{
files: ['**/*.ts', '**/*.tsx'],
rules: {},
},
{
files: ['**/*.js', '**/*.jsx'],
rules: {},
},
{ ignores: ['vite.config.ts'] },
];

View file

@ -1,3 +0,0 @@
{
"presets": ["@nx/js/babel"]
}

View file

@ -1,22 +0,0 @@
{
"extends": ["../../.eslintrc.base.json", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*", "vite.config.ts"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./apps/example-plugin/tsconfig.app.json"
},
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

View file

@ -1,8 +0,0 @@
{
"jsc": {
"parser": {
"syntax": "typescript"
},
"target": "es2016"
}
}

View file

@ -1,16 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>ExamplePlugin</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>

View file

@ -1,8 +0,0 @@
{
"name": "example-plugin",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"sourceRoot": "apps/example-plugin/src",
"tags": ["type:plugin"],
"targets": {}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View file

@ -1,9 +0,0 @@
{
"name": "Example plugin",
"code": "http://localhost:4201/plugin.js",
"permissions": [
"page:read",
"file:read",
"selection:read"
]
}

View file

@ -1,69 +0,0 @@
html {
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
line-height: 1.5;
tab-size: 4;
scroll-behavior: smooth;
}
body {
font-family: inherit;
line-height: inherit;
margin: 0;
}
h1,
h2,
p,
pre {
margin: 0;
}
*,
::before,
::after {
box-sizing: border-box;
border-width: 0;
border-style: solid;
border-color: currentColor;
}
h1,
h2 {
font-size: inherit;
font-weight: inherit;
}
a {
color: inherit;
text-decoration: inherit;
}
pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
'Liberation Mono', 'Courier New', monospace;
}
svg {
display: block;
vertical-align: middle;
}
svg {
shape-rendering: auto;
text-rendering: optimizeLegibility;
}
.wrapper {
width: 100%;
}
p {
margin-block-end: var(--spacing-12);
}
h1 {
font-size: 20px;
margin-block-end: var(--spacing-12);
}
.help {
color: #6b7280;
display: block;
font-size: 11px;
padding-inline-start: var(--spacing-12);
}

View file

@ -1,137 +0,0 @@
/* eslint-disable */
import 'plugins-styles/lib/styles.css';
import './app.element.css';
export class AppElement extends HTMLElement {
public static observedAttributes = [];
#selection = '';
#pageId = '';
#fileId = '';
#revn = 0;
refreshPage(pageId: string, name: string) {
console.log('refreshPage', pageId, name);
const projectName = document.getElementById('project-name');
this.#pageId = pageId;
if (projectName) {
projectName.innerText = name;
}
}
refreshSelectionId(selection: string[]) {
this.#selection = selection;
const selectionId = document.getElementById('selection-id');
if (selectionId) {
selectionId.innerText = this.#selection[0];
}
}
connectedCallback() {
window.addEventListener('message', (event) => {
if (event.data.type === 'pingpong') {
console.log('iframe', event.data.content);
} else if (event.data.type === 'file') {
this.#fileId = event.data.content.id;
this.#revn = event.data.content.revn;
} else if (event.data.type === 'page') {
this.refreshPage(event.data.content.id, event.data.content.name);
} else if (event.data.type === 'selection') {
this.refreshSelectionId(event.data.content);
} else if (event.data.type === 'init') {
this.#fileId = event.data.content.fileId;
this.#revn = event.data.content.revn;
this.refreshPage(event.data.content.pageId, event.data.content.name);
this.refreshSelectionId(event.data.content.selection);
this.setAttribute('data-theme', event.data.content.theme);
} else if (event.data.type === 'theme') {
this.setAttribute('data-theme', event.data.content);
}
});
this.innerHTML = `
<div class="wrapper">
<h1>Test area</h1>
<p>Current project name: <span id="project-name">Unknown</span></p>
<p>Selection id: <span id="selection-id">Unknown</span></p>
<div class="profile"></div>
<p>
<button type="button" data-appearance="primary" class="act-ping-pong">Ping Pong message</button>
</p>
<p>
<button type="button" data-appearance="primary" class="get-profile">API get profile</button>
<span class="help">Need the .env file and run "start:rpc-api"</span>
</p>
<p>
<button type="button" data-appearance="primary" class="remove-obj">Remove obj</button>
<span class="help">Need the .env file and run "start:rpc-api"</span>
</p>
<p>
<button type="button" data-appearance="primary" data-variant="destructive" class="act-close-plugin">Close plugin</button>
</p>
</div>
`;
const pingPongAction = this.querySelector<HTMLElement>('.act-ping-pong');
pingPongAction?.addEventListener('click', () => {
parent.postMessage({ content: 'ping' }, '*');
});
const closeAction = this.querySelector<HTMLElement>('.act-close-plugin');
closeAction?.addEventListener('click', () => {
parent.postMessage({ content: 'close' }, '*');
});
const getProfileAction = this.querySelector<HTMLElement>('.get-profile');
getProfileAction?.addEventListener('click', () => {
fetch('http://localhost:3000/get-profile')
.then((response) => {
return response.json();
})
.then((data) => {
const profile = document.querySelector('.profile');
if (profile) {
profile.innerHTML = '<p>' + JSON.stringify(data) + '</p>';
}
});
});
const removeAction = this.querySelector<HTMLElement>('.remove-obj');
removeAction?.addEventListener('click', () => {
fetch('http://localhost:3000/delete-object', {
method: 'DELETE',
body: JSON.stringify({
fileId: this.#fileId,
revn: this.#revn,
pageId: this.#pageId,
objectId: this.#selection,
}),
headers: {
'Content-Type': 'application/json',
},
})
.then((response) => {
return response.json();
})
.then((data) => {
console.log(data);
});
});
parent.postMessage({ content: 'ready' }, '*');
}
}
customElements.define('app-root', AppElement);

View file

@ -1 +0,0 @@
import './app/app.element';

View file

@ -1,67 +0,0 @@
penpot.log('Hello from plugin');
penpot.ui.open('Plugin name', 'http://localhost:4201', {
width: 500,
height: 600,
});
penpot.ui.onMessage<{ content: string }>((message) => {
if (message.content === 'ping') {
penpot.log('ping received');
penpot.ui.sendMessage({ type: 'pingpong', content: 'pong' });
} else if (message.content === 'close') {
penpot.closePlugin();
} else if (message.content === 'ready') {
const pageState = penpot.getPage();
const fileState = penpot.getFile();
if (!pageState || !fileState) {
return;
}
penpot.ui.sendMessage({
type: 'init',
content: {
name: pageState.name,
pageId: pageState.id,
fileId: fileState.id,
revn: fileState.revn,
theme: penpot.getTheme(),
selection: penpot.getSelected(),
},
});
}
});
penpot.on('pagechange', () => {
const page = penpot.getPage();
penpot.ui.sendMessage({
type: 'page',
content: {
name: page.name,
id: page.id,
},
});
});
penpot.on('filechange', () => {
const file = penpot.getFile();
penpot.ui.sendMessage({
type: 'file',
content: {
name: file.name,
id: file.id,
revn: file.revn,
},
});
});
penpot.on('selectionchange', () => {
const selected = penpot.getSelected();
penpot.ui.sendMessage({ type: 'selection', content: selected });
});
penpot.on('themechange', () => {
const theme = penpot.getTheme();
penpot.ui.sendMessage({ type: 'theme', content: theme });
});

View file

@ -1 +0,0 @@
/* You can add global styles to this file, and also import other style files */

View file

@ -1,9 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": ["node"]
},
"exclude": ["src/**/*.spec.ts", "src/**/*.test.ts"],
"include": ["src/**/*.ts", "../../libs/plugin-types/index.d.ts"]
}

View file

@ -1,30 +0,0 @@
{
"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"
}
]
}

View file

@ -1,26 +0,0 @@
{
"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"
]
}

View file

@ -1,59 +0,0 @@
/// <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/example-plugin',
server: {
port: 4200,
host: 'localhost',
},
preview: {
port: 4201,
host: 'localhost',
},
plugins: [nxViteTsPaths()],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
build: {
outDir: '../../dist/apps/example-plugin',
reportCompressedSize: true,
commonjsOptions: {
transformMixedEsModules: true,
},
rollupOptions: {
input: {
plugin: 'src/plugin.ts',
index: './index.html',
},
output: {
entryFileNames: '[name].js',
},
},
sourcemap: true,
},
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/example-plugin',
provider: 'v8',
},
},
});

View file

@ -1,22 +0,0 @@
{
"extends": ["../../.eslintrc.base.json", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*", "vite.config.ts"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./apps/example-styles/tsconfig.app.json"
},
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

View file

@ -0,0 +1,31 @@
import baseConfig from '../../eslint.config.js';
import typescriptEslintParser from '@typescript-eslint/parser';
import globals from 'globals';
export default [
...baseConfig,
{
languageOptions: {
parser: typescriptEslintParser,
parserOptions: { project: './apps/example-styles/tsconfig.app.json' },
},
},
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
rules: {},
languageOptions: {
globals: {
...globals.browser
}
}
},
{
files: ['**/*.ts', '**/*.tsx'],
rules: {},
},
{
files: ['**/*.js', '**/*.jsx'],
rules: {},
},
{ ignores: ['vite.config.ts'] },
];

View file

@ -1,36 +0,0 @@
{
"extends": ["../../.eslintrc.base.json"],
"ignorePatterns": ["!**/*", "**/assets/*.js"],
"overrides": [
{
"files": ["*.ts"],
"extends": [
"plugin:@nx/angular",
"plugin:@angular-eslint/template/process-inline-templates"
],
"rules": {
"@angular-eslint/directive-selector": [
"error",
{
"type": "attribute",
"prefix": "app",
"style": "camelCase"
}
],
"@angular-eslint/component-selector": [
"error",
{
"type": "element",
"prefix": "app",
"style": "kebab-case"
}
]
}
},
{
"files": ["*.html"],
"extends": ["plugin:@nx/angular-template"],
"rules": {}
}
]
}

View file

@ -0,0 +1,43 @@
import baseConfig from '../../eslint.config.js';
import { compat } from '../../eslint.base.config.js';
export default [
...baseConfig,
...compat
.config({
extends: [
'plugin:@nx/angular',
'plugin:@angular-eslint/template/process-inline-templates',
],
})
.map((config) => ({
...config,
files: ['**/*.ts'],
rules: {
'@angular-eslint/directive-selector': [
'error',
{
type: 'attribute',
prefix: 'app',
style: 'camelCase',
},
],
'@angular-eslint/component-selector': [
'error',
{
type: 'element',
prefix: 'app',
style: 'kebab-case',
},
],
},
})),
...compat
.config({ extends: ['plugin:@nx/angular-template'] })
.map((config) => ({
...config,
files: ['**/*.html'],
rules: {},
})),
{ ignores: ['**/assets/*.js'] },
];

View file

@ -4,7 +4,7 @@
"projectType": "application",
"prefix": "app",
"sourceRoot": "apps/poc-state-plugin/src",
"tags": [],
"tags": ["type:plugin"],
"targets": {
"buildPlugin": {
"executor": "@nx/esbuild:esbuild",

View file

@ -1,5 +1,3 @@
import { PenpotText } from '@penpot/plugin-types';
penpot.ui.open('Plugin name', 'http://localhost:4202', {
width: 500,
height: 600,

View file

@ -1,22 +0,0 @@
{
"extends": ["../../.eslintrc.base.json", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./apps/rpc-api/tsconfig.app.json"
},
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}

View file

@ -1,56 +0,0 @@
{
"name": "rpc-api",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/rpc-api/src",
"projectType": "application",
"targets": {
"build": {
"executor": "@nx/esbuild:esbuild",
"outputs": ["{options.outputPath}"],
"defaultConfiguration": "production",
"options": {
"platform": "node",
"outputPath": "dist/apps/rpc-api",
"format": ["cjs"],
"bundle": false,
"main": "apps/rpc-api/src/main.ts",
"tsConfig": "apps/rpc-api/tsconfig.app.json",
"assets": ["apps/rpc-api/src/assets"],
"generatePackageJson": true,
"esbuildOptions": {
"sourcemap": true,
"outExtension": {
".js": ".js"
}
}
},
"configurations": {
"development": {},
"production": {
"esbuildOptions": {
"sourcemap": false,
"outExtension": {
".js": ".js"
}
}
}
}
},
"serve": {
"executor": "@nx/js:node",
"defaultConfiguration": "development",
"options": {
"buildTarget": "rpc-api:build"
},
"configurations": {
"development": {
"buildTarget": "rpc-api:build:development"
},
"production": {
"buildTarget": "rpc-api:build:production"
}
}
}
},
"tags": ["type:app"]
}

View file

@ -1,24 +0,0 @@
import * as path from 'path';
import { FastifyInstance } from 'fastify';
import AutoLoad from '@fastify/autoload';
/* eslint-disable-next-line */
export interface AppOptions {}
// eslint-disable-next-line @typescript-eslint/require-await
export async function app(fastify: FastifyInstance, opts: AppOptions) {
// This loads all plugins defined in plugins
// those should be support plugins that are reused
// through your application
void fastify.register(AutoLoad, {
dir: path.join(__dirname, 'plugins'),
options: { ...opts },
});
// // This loads all plugins defined in routes
// // define your routes in one of these
void fastify.register(AutoLoad, {
dir: path.join(__dirname, 'routes'),
options: { ...opts },
});
}

View file

@ -1,8 +0,0 @@
import { FastifyInstance } from 'fastify';
import fp from 'fastify-plugin';
import cors from '@fastify/cors';
// eslint-disable-next-line @typescript-eslint/require-await
export default fp(async function (fastify: FastifyInstance) {
void fastify.register(cors);
});

View file

@ -1,13 +0,0 @@
import { FastifyInstance } from 'fastify';
import fp from 'fastify-plugin';
import sensible from '@fastify/sensible';
/**
* This plugins adds some utilities to handle http errors
*
* @see https://github.com/fastify/fastify-sensible
*/
// eslint-disable-next-line @typescript-eslint/require-await
export default fp(async function (fastify: FastifyInstance) {
void fastify.register(sensible);
});

View file

@ -1,110 +0,0 @@
import { FastifyInstance } from 'fastify';
import { v4 } from 'uuid';
const token = process.env.ACCESS_TOKEN;
// eslint-disable-next-line @typescript-eslint/require-await
async function routes(fastify: FastifyInstance) {
const apiUrl = process.env.API_URL + '/api/rpc/command';
const fakeSessionId = v4();
// not working
// function uploadMediaObject(fileId: string) {
// const filePath = '/home/juanfran/tmpImages/example-bg-2.jpg';
// const file = fs.readFileSync(filePath);
// const fileName = path.basename(filePath);
// const formData = new FormData();
// formData.append('content', new Blob([file]), fileName);
// formData.append('file-id', fileId);
// formData.append('name', fileName);
// formData.append('is-local', 'true');
// return fetch(`${apiUrl}/upload-file-media-object`, {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/transit+json',
// Authorization: `Token ${token}`,
// },
// body: formData,
// });
// }
function deleteObj(
fileId: string,
revn: number,
pageId: string,
objectId: string
) {
const payload = {
'~:id': `~u${fileId}`,
'~:revn': revn,
'~:session-id': `~u${fakeSessionId}`,
'~:changes': [
{
'~:type': '~:del-obj',
'~:page-id': `~u${pageId}`,
'~:ignore-touched': false,
'~:id': `~u${objectId}`,
},
],
'~:features': {
'~#set': [
'layout/grid',
'styles/v2',
'fdata/pointer-map',
'fdata/objects-map',
'fdata/shape-data-type',
],
},
};
return fetch(`${apiUrl}/update-file?id=${fileId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/transit+json',
Authorization: `Token ${token}`,
},
body: JSON.stringify(payload),
}).then((response) => response.json());
}
fastify.get('/get-profile', function () {
return fetch(`${apiUrl}/get-profile`, {
method: 'GET',
headers: {
Authorization: `Token ${token}`,
},
})
.then((response) => response.json())
.catch((error) => {
console.error('Error:', error);
});
});
fastify.delete<{
Body: {
fileId: string;
revn: number;
pageId: string;
objectId: string;
};
}>('/delete-object', function (request, reply) {
deleteObj(
request.body.fileId,
request.body.revn,
request.body.pageId,
request.body.objectId
)
.then((data) => {
console.log('Success:', data);
return reply.send(data);
})
.catch((error) => {
console.error('Error:', error);
});
});
}
export default routes;

View file

@ -1,23 +0,0 @@
import Fastify from 'fastify';
import { app } from './app/app';
const host = process.env.HOST ?? 'localhost';
const port = process.env.PORT ? Number(process.env.PORT) : 3000;
// Instantiate Fastify with some config
const server = Fastify({
logger: true,
});
// Register your application as a normal plugin.
void server.register(app);
// Start listening.
server.listen({ port, host }, (err) => {
if (err) {
server.log.error(err);
process.exit(1);
} else {
console.log(`[ ready ] http://${host}:${port}`);
}
});

View file

@ -1,10 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["node"]
},
"exclude": ["src/**/*.spec.ts", "src/**/*.test.ts"],
"include": ["src/**/*.ts"]
}

View file

@ -1,16 +0,0 @@
{
"extends": "../../tsconfig.base.json",
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.spec.json"
}
],
"compilerOptions": {
"esModuleInterop": true
}
}

View file

@ -1,13 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"module": "commonjs",
"types": ["node"]
},
"include": [
"src/**/*.test.ts",
"src/**/*.spec.ts",
"src/**/*.d.ts"
]
}

View file

@ -1 +1 @@
module.exports = { extends: ['@commitlint/config-conventional'] };
export default { extends: ['@commitlint/config-conventional'] };

49
eslint.base.config.js Normal file
View file

@ -0,0 +1,49 @@
import { FlatCompat } from '@eslint/eslintrc';
import nxEslintPlugin from '@nx/eslint-plugin';
import js from '@eslint/js';
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
});
export default [
js.configs.recommended,
{ plugins: { '@nx': nxEslintPlugin } },
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
rules: {
'@nx/enforce-module-boundaries': [
'error',
{
enforceBuildableLibDependency: true,
allow: [],
depConstraints: [
{
sourceTag: '*',
onlyDependOnLibsWithTags: ['*'],
},
],
},
],
},
},
...compat.config({ extends: ['plugin:@nx/typescript'] }).map((config) => ({
...config,
files: ['**/*.ts', '**/*.tsx'],
rules: {},
})),
...compat.config({ extends: ['plugin:@nx/javascript'] }).map((config) => ({
...config,
files: ['**/*.js', '**/*.jsx'],
rules: {},
})),
{
ignores: ['eslint.config.js', 'vite.config.{js,ts,mjs,mts}'],
}
];

115
eslint.config.js Normal file
View file

@ -0,0 +1,115 @@
import baseConfig, { compat } from './eslint.base.config.js';
import globals from 'globals';
export default [
...baseConfig,
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
rules: {
'@nx/enforce-module-boundaries': [
'error',
{
enforceBuildableLibDependency: true,
allow: [],
depConstraints: [
{
sourceTag: 'type:plugin',
onlyDependOnLibsWithTags: [
'type:util',
'type:ui',
'type:feature',
],
},
{
sourceTag: 'type:app',
onlyDependOnLibsWithTags: [
'type:util',
'type:ui',
'type:feature',
],
},
{
sourceTag: 'type:feature',
onlyDependOnLibsWithTags: [
'type:feature',
'type:ui',
'type:util',
],
},
{
sourceTag: 'type:ui',
onlyDependOnLibsWithTags: ['type:ui', 'type:util'],
},
{
sourceTag: 'type:util',
onlyDependOnLibsWithTags: ['type:util'],
},
],
},
],
},
languageOptions: {
globals: {
penpot: 'readonly',
repairIntrinsics: 'readonly',
hardenIntrinsics: 'readonly',
Compartment: 'readonly',
harden: 'readonly',
}
}
},
...compat
.config({
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
'plugin:deprecation/recommended',
'prettier',
],
})
.map((config) => ({
...config,
files: ['**/*.ts', '**/*.tsx'],
rules: {
'@typescript-eslint/no-unused-vars': ['error'],
'no-multiple-empty-lines': [2, { max: 1 }],
quotes: ['error', 'single', { avoidEscape: true }],
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': ['error']
},
})),
...compat
.config({
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
'plugin:deprecation/recommended',
'prettier',
],
})
.map((config) => ({
...config,
files: ['**/*.spec.ts'],
rules: {
'@typescript-eslint/no-unused-vars': ['error'],
'no-multiple-empty-lines': [2, { max: 1 }],
quotes: ['error', 'single', { avoidEscape: true }],
'@typescript-eslint/no-unsafe-member-access': 'off',
'@typescript-eslint/no-unsafe-call': 'off',
'@typescript-eslint/no-unsafe-assignment': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unsafe-argument': 'off',
'@typescript-eslint/no-unsafe-return': 'off',
'@ngrx/prefix-selectors-with-select': 'off',
},
languageOptions: {
globals: {
...globals.jest
}
},
})),
{
files: ['**/*.js', '**/*.jsx'],
rules: {},
}
];

View file

@ -1,25 +0,0 @@
{
"extends": ["../../.eslintrc.base.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.json"],
"parser": "jsonc-eslint-parser",
"rules": {
"@nx/dependency-checks": "error"
}
}
]
}

View file

@ -0,0 +1,42 @@
import baseConfig from '../../eslint.config.js';
import jsoncParser from 'jsonc-eslint-parser';
export default [
...baseConfig,
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
rules: {},
languageOptions: {
globals: {
fetch: 'readonly',
}
}
},
{
files: ['**/*.ts', '**/*.tsx'],
rules: {},
},
{
files: ['**/*.js', '**/*.jsx'],
rules: {},
},
{
files: ['*.json'],
languageOptions: {
parser: jsoncParser,
},
rules: {
'@nx/dependency-checks': [
'error',
{
ignoredFiles: [
'libs/plugin-types/vite.config.ts',
'libs/plugin-types/eslint.config.js',
'libs/plugin-types/**/*.spec.ts',
],
},
],
},
},
];

View file

@ -75,11 +75,6 @@ export interface PenpotShapeBase {
resize(width: number, height: number);
}
export interface PenpotText extends PenpotShape {
type: 'text';
characters: string;
}
export interface PenpotFrame extends PenpotShapeBase {
readonly type: 'frame';
readonly children: PenpotShape[];
@ -108,6 +103,11 @@ export interface PenpotText extends PenpotShapeBase {
characters: string;
}
export interface PepotFrame extends PenpotShapeBase {
readonly type: 'frame';
readonly children: PenpotShape[];
}
export interface PenpotCircle extends PenpotShapeBase {
type: 'circle';
}

View file

@ -1,5 +1,6 @@
{
"name": "@penpot/plugin-types",
"version": "0.1.0",
"typings": "./index.d.ts"
"typings": "./index.d.ts",
"type": "module"
}

View file

@ -1,34 +0,0 @@
{
"extends": ["../../.eslintrc.base.json", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*", "vite.config.ts"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./libs/plugins-data-parser/tsconfig.lib.json"
},
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.json"],
"parser": "jsonc-eslint-parser",
"rules": {
"@nx/dependency-checks": [
"error",
{
"ignoredFiles": ["{projectRoot}/vite.config.{js,ts,mjs,mts}"]
}
]
}
}
]
}

View file

@ -1,105 +0,0 @@
# Parser
This library exports `cleanObject()` and `getSelectedUuids()` funtions and the model `Selection`.
The `cleanObject()` function cleans up objects from useless properties and transforms the remaining ones to camelCase. It returns `unknown`.
The `getSelectedUuids()` functions, given an `Selection` object, returns the selected Uuids as an array of string.
## Helpers
### File Helper
#### File Helper functions
- `setData()`
You can either pass the data in the constructor or use the `setData()` function.
example:
```ts
const fileHelper = new FileHelper();
fileHelper.setData(data);
```
or
```ts
const fileHelper = new FileHelper(data);
```
- `getCleanData()`
Gets the cleaned up data. It deletes useless properties and and transforms the remaining ones to camelCase.
example:
```ts
const clean = fileHelper.getCleanData();
```
### Page Helper
#### Page Helper functions
- `setData()`
You can either pass the data in the constructor or use the `setData()` function.
example:
```ts
const pageHelper = new PageHelper();
pageHelper.setData(data);
```
or
```ts
const pageHelper = new PageHelper(data);
```
- `getCleanData()`
Gets the cleaned up data. It deletes useless properties and and transforms the remaining ones to camelCase.
example:
```ts
const clean = pageHelper.getCleanData();
```
- `getObjectsArray()`
Returns the objects array, which can contain heavily nested arrays with objects data.
example:
```ts
const objects = pageHelper.getObjectsArray();
```
- `getObjectById(id: string)`
Returns an object by given uuid. The object is cleaned up and formatted as a `PObject`.
```ts
const obj: PObject = pageHelper.getObjectById(
'3aba0744-11fe-4c41-80fb-1b42aa7ef3e5'
);
```
### Selection Helper
#### Selection Helper functions
- `static getUuids(selection: Selection)`
Returns the selected items in an array.
example:
```ts
const ids = SelectionHelper.getUuids(selection);
```

View file

@ -1,8 +0,0 @@
{
"name": "plugins-data-parser",
"version": "0.1.0",
"dependencies": {},
"type": "commonjs",
"main": "./src/index.js",
"typings": "./src/index.d.ts"
}

View file

@ -1,8 +0,0 @@
{
"name": "plugins-data-parser",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/plugins-data-parser/src",
"projectType": "library",
"targets": {},
"tags": ["type:util"]
}

View file

@ -1,3 +0,0 @@
export { getSelectedUuids, cleanObject, getPartialState } from './lib/utils';
export * from './lib/models';
export * from './lib/helpers';

View file

@ -1,22 +0,0 @@
import { cleanObject } from '../utils';
/**
* WIP
*/
export class FileHelper {
private data: unknown = null;
public constructor(data?: unknown) {
if (data) {
this.setData(data);
}
}
public setData(data: unknown): void {
this.data = cleanObject(data);
}
public getCleanData(): unknown {
return this.data;
}
}

View file

@ -1,3 +0,0 @@
export * from './file.helper';
export * from './page.helper';
export * from './selection.helper';

View file

@ -1,97 +0,0 @@
import { cleanObject, parseObject, isObject } from '../utils';
import { PObject } from '../models';
import { Name, Uuid } from '../utils/models/util.model';
interface PageArr {
data?: {
arr?: unknown[];
};
[key: string]: unknown;
}
interface PageObject {
root?: {
arr?: unknown[];
};
[key: string]: unknown;
}
/**
* WIP
*/
export class PageHelper {
private data: unknown = null;
private objects: unknown[] = [];
public constructor(data?: unknown) {
if (data) {
this.setData(data);
}
}
public setData(data: unknown): void {
this.data = cleanObject(data);
this.objects = this.getPageObjects(this.data as PageArr);
}
public getCleanData(): unknown {
return this.data;
}
public getObjectsArray(): unknown[] {
return this.objects;
}
public getObjectById(id: string): PObject | null {
if (!this.objects) {
return null;
}
const foundObject = this.findObject(this.objects, id);
return parseObject(foundObject) as PObject;
}
private getPageObjects(obj: PageArr): unknown[] {
const dataArr = obj?.data?.arr;
const objectNameIndex = dataArr?.findIndex(
(item) => isObject(item) && (item as Name)?.name === 'objects'
);
if (!objectNameIndex) {
return [];
}
const objects = dataArr?.[objectNameIndex + 1] as PageObject;
return (isObject(objects) && objects?.root?.arr) || [];
}
private findObject(
data: unknown,
id: string
): Record<string, unknown> | null {
if (Array.isArray(data)) {
for (const item of data) {
const foundObject = this.findObject(item, id);
if (foundObject !== null) {
return foundObject;
}
}
} else if (isObject(data)) {
const obj = data as Record<string, unknown>;
if ((obj?.['id'] as Uuid)?.uuid === id || obj?.['id'] === id) {
return obj;
}
for (const key of Object.keys(obj)) {
const foundObject = this.findObject(obj[key], id);
if (foundObject !== null) {
return foundObject;
}
}
}
return null;
}
}

View file

@ -1,15 +0,0 @@
import { Selection } from '../models';
/**
* WIP
*/
export class SelectionHelper {
// eslint-disable-next-line @typescript-eslint/no-empty-function
private constructor() {}
static getUuids(selection: Selection): string[] {
const root = selection?.linked_map?.delegate_map?.root?.arr;
return (root?.filter((r) => r?.uuid).map((r) => r.uuid) as string[]) || [];
}
}

View file

@ -1,181 +0,0 @@
import { Point } from '.';
export interface Extmap {
fillColor?: string;
strokeStyle?: null | string;
none?: unknown;
strokeColor?: string;
content?: ExtmapContent[];
strokeOpacity?: number;
strokeAlignment?: unknown;
center?: null | ExtmapInner;
proportionLock?: null | boolean;
constraintsV?: unknown;
constraintsH?: unknown;
leftright?: unknown;
strokes?: Stroke[];
proportion?: number;
fills?: Fill[];
fillOpacity?: number;
growType?: unknown;
touched?: Touched;
shapeRef?: string;
topbottom?: unknown;
positionData?: PositionData[];
overflowText?: unknown;
fixed?: unknown;
hidden?: null | boolean;
componentId?: string;
flipX?: unknown;
componentFile?: string;
flipY?: unknown;
shapes?: string[];
inner?: ExtmapInner;
strokeWidth?: number;
shadow: Shadow[];
componentRoot?: boolean;
rx?: number;
ry?: number;
autoWidth?: unknown;
top?: Top;
hideInViewer: null | boolean;
exports?: null | Export[];
thumbnail?: string;
}
export interface Export {
type: null | string;
jpeg: unknown;
suffix: null | string;
scale: number;
}
export interface Top {
constraintsH: unknown;
leftRight: unknown;
hidden: null | boolean;
}
export interface Shadow {
color: Partial<Color>;
spread: unknown;
offsetY: number;
style: unknown;
dropShadow: unknown;
blur: number;
hidden: null | boolean;
opacity: null | number;
id: string;
offsetX: null | number;
}
export interface ExtmapInner {
shapeRef: string;
proportionLock: boolean;
}
export interface Touched {
hashMap: HashMap;
}
export interface HashMap {
hashMap: GeometryGroup;
}
export interface GeometryGroup {
geometryGroup: unknown;
}
export interface PositionData {
y?: number;
fontStyle?: string;
textTransform?: string;
fontSize?: string;
fontWeight?: string;
width?: number;
textDecoration?: string;
letterSpacing?: string;
x?: number;
fills?: Fill[];
direction?: string;
fontFamily?: string;
height?: number;
text?: string;
extmap?: ExtmapContent;
x1?: number;
y1?: number;
x2?: number;
y2?: number;
}
export interface ExtmapBottom {
blur: Blur;
strokeWidth: number;
}
export interface Fill {
fillColor: string;
fillOpacity: number;
fillColorRefFile?: string;
fillColorRefId?: string;
}
export interface Stroke {
strokeStyle: unknown;
none: unknown;
strokeColor: string;
strokeOpacity: number;
strokeAlignment: unknown;
center: unknown;
strokeWidth: number;
}
export interface Blur {
id: string;
type: unknown;
layerBlur: unknown;
value: number;
hidden: unknown;
}
export interface ExtmapContent {
command?: unknown;
moveTo?: unknown;
relative?: unknown;
params?: Point;
key?: string;
type?: string;
children?: ExtmapContent[];
fillColor?: string;
fillOpacity?: number;
fontFamily?: string;
fontId?: string;
fontSize?: string;
fontStyle?: string;
fontVariantId?: string;
fontWeight?: string;
text?: string;
textDecoration?: string;
textTransform?: string;
verticalAlign?: string;
direction?: string;
fills?: Fill[];
letterSpacing?: string;
}
export interface ExtmapContentPoint {
x: number;
y: number;
c1x?: number;
c1y?: number;
c2x?: number;
c2y?: number;
}
export interface Color {
color: string;
opacity: number;
id: string;
name: string;
fileId: string;
path: string | null;
}

View file

@ -1,3 +0,0 @@
export * from './selection.model';
export * from './object.model';
export * from './extmap.model';

View file

@ -1,44 +0,0 @@
import { Extmap } from '.';
export interface PObject {
id: string;
name: string;
type: string;
x: number;
y: number;
width: number;
height: number;
rotation: number;
selrect: Selrect;
points: Point[];
transform: Transform;
transformInverse: Transform;
parentId: string;
frameId: string;
extmap: Extmap;
}
export interface Selrect {
x: number;
y: number;
width: number;
height: number;
x1: number;
y1: number;
x2: number;
y2: number;
}
export interface Point {
x: number;
y: number;
}
export interface Transform {
a: number;
b: number;
c: number;
d: number;
e: number;
f: number;
}

View file

@ -1,47 +0,0 @@
export interface Selection extends CljValues {
linked_map: SelLinkedMap;
}
export interface SelLinkedMap extends CljValues {
head: SelUuid | null;
delegate_map: SelDelegateMap;
}
export interface SelDelegateMap extends CljValues, UnderscoreValues, NilValues {
meta: unknown;
cnt: number;
root: SelRoot | null;
}
export interface SelRoot extends CljValues {
edit: unknown;
bitmap: number;
arr: SelArr[];
}
export interface SelArr extends UnderscoreValues, CljValues {
uuid?: string;
value: unknown;
left?: SelUuid;
right?: SelUuid;
}
export interface SelUuid extends CljValues, UnderscoreValues {
uuid: string;
}
export interface CljValues {
cljs$lang$protocol_mask$partition0$: number;
cljs$lang$protocol_mask$partition1$: number;
}
export interface UnderscoreValues {
__meta?: null | number;
__extmap?: null | number;
__hash: null | number;
}
export interface NilValues {
nil_val: null | boolean;
has_nil_QMARK_: null | boolean;
}

View file

@ -1,88 +0,0 @@
import { isObject, toCamelCase, isSingleObjectWithProperty } from '.';
import { Arr, Name } from './models/util.model';
/**
* Checks if "arr" property can be turned into an object
*/
function toObject(arr: unknown): boolean {
return (
Array.isArray(arr) && arr.some((a) => isSingleObjectWithProperty(a, 'name'))
);
}
/**
* Checks if "arr" property can be turned into an array of objects
*/
function toArray(arr: unknown): boolean {
return (
Array.isArray(arr) &&
arr.every((a) => isObject(a)) &&
arr.every(
(a) =>
isSingleObjectWithProperty(a, 'uuid') ||
isSingleObjectWithProperty(a, 'arr')
)
);
}
/**
* Parses a splitted "arr" back into an object if possible.
*
* If there are objects with a single property "name", that
* object and the next one are turned into a key-value pair.
*
* example:
* [{ name: 'foo' }, 'bar', { name: 'baz' }, 'qux'] => { foo: 'bar', baz: 'qux' }
*/
function arrToObject(arr: unknown): Record<string, unknown> | null {
return (
(Array.isArray(arr) &&
arr.reduce(
(result: Record<string, unknown>, value: unknown, index: number) => {
if (isSingleObjectWithProperty(value, 'name')) {
const next = arr[index + 1] as unknown;
if (!!next && !isSingleObjectWithProperty(next, 'name')) {
return { ...result, [toCamelCase((value as Name)?.name)]: next };
} else {
return { ...result, [toCamelCase((value as Name)?.name)]: null };
}
}
return { ...result };
},
{}
)) ||
null
);
}
/**
* Parses a splitted "arr" back into an array of objects.
*
*/
function arrToArray(arr: unknown): unknown[] | null {
return (
(Array.isArray(arr) &&
arr.reduce((result: unknown[], value: unknown) => {
if (isSingleObjectWithProperty(value, 'arr')) {
return [...result, { ...(value as Arr)?.arr }];
}
return [...result];
}, [])) ||
null
);
}
/**
* Checks an "arr" property and decides which parse solution to use
*/
export function parseArrProperty(
arr: unknown[]
): unknown[] | Record<string, unknown> | null {
if (toArray(arr)) {
return arrToArray(arr);
} else if (toObject(arr)) {
return arrToObject(arr);
}
return arr;
}

View file

@ -1,6 +0,0 @@
export * from './object.util';
export * from './parse.util';
export * from './selected.util';
export * from './arr.util';
export * from './string.util';
export * from './parse-state';

View file

@ -1,11 +0,0 @@
export interface Name {
name: string;
}
export interface Uuid {
uuid: string;
}
export interface Arr {
arr: unknown[];
}

View file

@ -1,53 +0,0 @@
/**
* Check if param is an object
*/
export function isObject(obj: unknown): boolean {
return typeof obj === 'object' && obj !== null && !Array.isArray(obj);
}
/**
* Check if param is an object with a single property
*/
export function isSingleObjectWithProperty(
object: unknown,
property: string
): boolean {
if (isObject(object)) {
return (
Object.keys(object as Record<string, unknown>).length === 1 &&
!!(object as Record<string, unknown>)[property]
);
}
return false;
}
/**
* Check if param is an object with a single property from a list
*/
export function isSingleObjectWithProperties(
object: unknown,
properties: string[]
): boolean {
if (isObject(object)) {
const keys = Object.keys(object as Record<string, unknown>);
if (keys.length === 1) {
return properties.includes(keys[0]);
}
}
return false;
}
/**
* Check if param is a root-tail object
*/
export function isRootTail(obj: unknown): boolean {
if (isObject(obj)) {
const keys = Object.keys(obj as Record<string, unknown>);
return keys.length === 2 && keys.includes('root') && keys.includes('tail');
}
return false;
}

View file

@ -1,197 +0,0 @@
/* eslint-disable */
import { toCamelCase } from '.';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type UknowCljs = any;
declare global {
const cljs: UknowCljs;
const app: UknowCljs;
}
const PATH_SEPARATOR = '/' as const;
function getPath(path: string): UknowCljs[] {
if (path === 'root') {
return [];
}
return path
.slice(5)
.split(PATH_SEPARATOR)
.map((it) => {
if (it.startsWith('#')) {
return cljs.core.uuid(it.slice(1));
} else if (it.startsWith(':')) {
return cljs.core.keyword(it.slice(1));
} else if (it.startsWith('!')) {
return parseInt(it.slice(1), 10);
} else {
return it;
}
});
}
const expandableTable = {
'app:datetime': false,
'cljs:uuid': false,
'cljs:keyword': false,
'cljs:symbol': false,
'js:date': false,
'js:boolean': false,
'js:function': false,
'js:number': false,
'js:string': false,
'js:undefined': false,
'js:null': false,
'unkwnown:unknown': false,
'cljs:vector': true,
'cljs:list': true,
'cljs:map': true,
'cljs:set': true,
'js:array': true,
'js:object': true,
} as const;
function isCljsArray(key: string) {
const valid = ['cljs:vector', 'cljs:list', 'cljs:set'];
return valid.includes(key);
}
const valueTable = {
'app:datetime': (value: unknown) => cljs.core.str(value),
'cljs:uuid': (value: unknown) => cljs.core.name(value),
'cljs:keyword': (value: unknown) => cljs.core.name(value),
'js:date': (value: unknown) => (value as Date).toISOString(),
};
function isTypeExpandable(type: keyof typeof expandableTable) {
return expandableTable[type];
}
function getType(value: unknown) {
const typeTable = {
'app:datetime': app.util.time.datetime_QMARK_,
'cljs:uuid': cljs.core.uuid_QMARK_,
'cljs:keyword': cljs.core.keyword_QMARK_,
'cljs:vector': cljs.core.vector_QMARK_,
'cljs:list': cljs.core.list_QMARK_,
'cljs:map': cljs.core.map_QMARK_,
'cljs:set': cljs.core.set_QMARK_,
'cljs:symbol': cljs.core.symbol_QMARK_,
'js:date': (value: unknown) => value instanceof Date,
'js:array': (value: unknown) => Array.isArray(value),
'js:boolean': (value: unknown) => typeof value === 'boolean',
'js:function': (value: unknown) => typeof value === 'function',
'js:number': (value: unknown) => typeof value === 'number',
'js:string': (value: unknown) => typeof value === 'string',
'js:undefined': (value: unknown) => typeof value === 'undefined',
'js:null': (value: unknown) => value === null,
'js:object': (value: unknown) =>
typeof value === 'object' &&
value !== null &&
value.toString() === '[object Object]',
};
for (const [type, fn] of Object.entries(typeTable)) {
if (fn(value)) return type as keyof typeof typeTable;
}
return 'unknown:unknown' as keyof typeof typeTable;
}
function getKeyName(key: string | number) {
if (cljs.core.uuid_QMARK_(key)) {
return '#' + cljs.core.name(key);
} else if (cljs.core.keyword_QMARK_(key)) {
return ':' + cljs.core.name(key);
} else if (typeof key === 'string') {
return key;
} else if (typeof key === 'number') {
return `!${key}`;
}
return key;
}
function getValue(value: unknown, type: string): unknown {
return type in valueTable
? valueTable[type as keyof typeof valueTable](value)
: value;
}
export function getPartialState(path: string, state: UknowCljs): UknowCljs {
// const state = cljs.core.deref(app.main.store.state);
const statePath = getPath(path);
const data = cljs.core.get_in(state, statePath);
const type = getType(data);
const isArray = isCljsArray(type);
const isExpandable = isTypeExpandable(type);
const map: Record<string, unknown> = {};
const list: unknown[] = [];
function addEntry(
childPath: string,
propName: string | number,
repValue: unknown,
isExpandable: boolean
) {
if (isArray) {
list.push(isExpandable ? getPartialState(childPath, state) : repValue);
} else {
if (isExpandable) {
Object.defineProperty(map, propName, {
get() {
return getPartialState(childPath, state);
},
configurable: true,
enumerable: true,
});
} else {
map[propName] = repValue;
}
}
}
if (isExpandable) {
if (type.startsWith('cljs:')) {
const cljsData = cljs.core.clj__GT_js(cljs.core.to_array(data));
cljsData.forEach((entry: unknown, index: number) => {
if (cljs.core.map_entry_QMARK_(entry)) {
const key = cljs.core.first(entry);
const name = getKeyName(key);
const value = cljs.core.nth(entry, 1);
const valueType = getType(value);
const repValue = getValue(value, valueType);
const isExpandable = isTypeExpandable(valueType);
const childPath = path + PATH_SEPARATOR + name;
const propName = name.startsWith(':')
? toCamelCase(name.slice(1))
: name;
addEntry(childPath, propName, repValue, isExpandable);
} else {
const valueType = getType(entry);
const repValue = getValue(entry, valueType);
const isExpandable = isTypeExpandable(valueType);
const childPath = path + PATH_SEPARATOR + getKeyName(index);
addEntry(childPath, index, repValue, isExpandable);
}
});
} else if (type.startsWith('js:')) {
Object.entries(data).forEach(([key, value]) => {
map[key] = value;
});
}
return isArray ? list : map;
}
const repValue = getValue(data, type);
return repValue;
}

View file

@ -1,124 +0,0 @@
import {
isObject,
toCamelCase,
parseArrProperty,
isRootTail,
isSingleObjectWithProperties,
isSingleObjectWithProperty,
} from '.';
import { Arr } from './models/util.model';
export function parseSingleProperties(
obj: unknown,
properties: string[]
): unknown {
let result = obj;
properties.forEach((property) => {
if (isSingleObjectWithProperty(obj, property)) {
result = (obj as Record<string, unknown>)[property];
}
});
return result;
}
export function parseRootTail(obj: unknown): unknown {
if (isObject(obj) && isRootTail(obj)) {
const { root, tail } = obj as Record<string, unknown>;
const hasRoot = Array.isArray(root) && root?.length > 0;
const hasTail = Array.isArray(tail) && tail?.length > 0;
if (hasRoot && hasTail) {
return obj;
}
if (hasTail) {
return tail;
}
if (hasRoot) {
return root;
}
return [];
}
return obj;
}
/**
* Recursively cleans an object from unnecesary properties
* and converts snake_case and kebab-case to camelCase
*/
export function cleanObject(obj: unknown): unknown {
if (Array.isArray(obj)) {
return obj
.filter((p) => p !== null)
.map((v: Record<string, unknown>) => cleanObject(v)) as Record<
string,
unknown
>[];
} else if (isObject(obj)) {
return Object.keys(obj as Record<string, unknown>)
.filter(
(key) =>
!/^(cljs\$|\$hash|ts|ry|\$meta|__hash|_hash|bitmap|meta|ns|fqn|cnt|shift|edit|has_nil_QMARK_|nil_val)/g.test(
key
)
)
.reduce((result, key) => {
const value = (obj as Record<string, unknown>)[key];
if (['extmap', '$extmap'].includes(key) && value === null) {
return { ...result };
}
return {
...result,
[toCamelCase(key)]: cleanObject(
(obj as Record<string, unknown>)[key]
),
};
}, {});
}
return obj as Record<string, unknown>;
}
export function parseObject(obj: unknown): unknown {
const singleProperties = ['root', 'name', 'uuid', 'guides'];
if (isSingleObjectWithProperties(obj, singleProperties)) {
const parsed = parseSingleProperties(
obj as Record<string, unknown>,
singleProperties
);
return parseObject(parsed);
}
if (isSingleObjectWithProperty(obj, 'arr')) {
const parsed = parseArrProperty((obj as Arr).arr);
return parseObject(parsed);
}
if (isRootTail(obj)) {
const parsed = parseRootTail(obj);
return parseObject(parsed);
}
// If it's an array, parse each element
if (Array.isArray(obj)) {
return obj.map((v: Record<string, unknown>) => parseObject(v));
}
// If it's an object, parse each property
if (isObject(obj)) {
return Object.keys(obj as Record<string, unknown>).reduce((result, key) => {
return {
...result,
[key]: parseObject((obj as Record<string, unknown>)[key]),
};
}, {});
}
return obj;
}

View file

@ -1,14 +0,0 @@
import { Selection } from '../models/selection.model';
/**
* Gets selected uuids from selection object
*/
export function getSelectedUuids(selection: Selection): string[] {
const root = selection?.linked_map?.delegate_map?.root?.arr;
if (!root) {
return [];
}
return root.map((r) => r.uuid).filter((uuid): uuid is string => !!uuid);
}

View file

@ -1,8 +0,0 @@
/**
* Converts a string to camelCase from kebab-case and snake_case
*/
export function toCamelCase(str: string): string {
const clean = str.replace(/^(\$|_|-)_?/, '');
return clean.replace(/(_|-)./g, (x) => x[1].toUpperCase());
}

View file

@ -1,22 +0,0 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "commonjs",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}

View file

@ -1,10 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"declaration": true,
"types": ["node", "vite/client"]
},
"include": ["src/**/*.ts"],
"exclude": ["src/**/*.spec.ts", "src/**/*.test.ts"]
}

View file

@ -1,22 +0,0 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "commonjs",
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
},
{
"path": "./tsconfig.spec.json"
}
]
}

View file

@ -1,47 +0,0 @@
/// <reference types='vitest' />
import { defineConfig } from 'vite';
import dts from 'vite-plugin-dts';
import * as path from 'path';
import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
export default defineConfig({
root: __dirname,
cacheDir: '../node_modules/.vite/plugins-data-parser',
plugins: [
nxViteTsPaths(),
dts({
entryRoot: 'src',
tsConfigFilePath: path.join(__dirname, 'tsconfig.lib.json'),
skipDiagnostics: true,
}),
],
// Uncomment this if you are using workers.
// worker: {
// plugins: [ nxViteTsPaths() ],
// },
// Configuration for building your library.
// See: https://vitejs.dev/guide/build.html#library-mode
build: {
outDir: '../../dist/libs/plugins-data-parser',
reportCompressedSize: true,
commonjsOptions: {
transformMixedEsModules: true,
},
lib: {
// Could also be a dictionary or array of multiple entry points.
entry: 'src/index.ts',
name: 'plugins-data-parser',
fileName: 'index',
// Change this to the formats you want to support.
// Don't forget to update your package.json as well.
formats: ['es', 'cjs'],
},
rollupOptions: {
// External packages that should not be bundled into your library.
external: [],
},
},
});

View file

@ -1,34 +0,0 @@
{
"extends": ["../../.eslintrc.base.json", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./libs/plugins-runtime/tsconfig.*?.json"
},
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.json"],
"parser": "jsonc-eslint-parser",
"rules": {
"@nx/dependency-checks": [
"error",
{
"ignoredFiles": ["{projectRoot}/vite.config.{js,ts,mjs,mts}"]
}
]
}
}
]
}

View file

@ -0,0 +1,50 @@
import baseConfig from '../../eslint.config.js';
import typescriptEslintParser from '@typescript-eslint/parser';
import jsoncParser from 'jsonc-eslint-parser';
import globals from 'globals';
export default [
...baseConfig,
{
languageOptions: {
parser: typescriptEslintParser,
parserOptions: { project: './libs/plugins-runtime/tsconfig.*?.json' },
},
},
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
rules: {},
languageOptions: {
globals: {
...globals.browser,
PluginConfig: 'readonly',
},
},
},
{
files: ['**/*.ts', '**/*.tsx'],
rules: {},
},
{
files: ['**/*.js', '**/*.jsx'],
rules: {},
},
{
files: ['*.json'],
languageOptions: {
parser: jsoncParser,
},
rules: {
'@nx/dependency-checks': [
'error',
{
ignoredFiles: [
'libs/plugins-runtime/vite.config.ts',
'libs/plugins-runtime/eslint.config.js',
'libs/plugins-runtime/**/*.spec.ts',
],
},
],
},
},
];

View file

@ -1,15 +1,12 @@
{
"name": "plugins-runtime",
"version": "0.1.0",
"devDependencies": {
"happy-dom": "^13.6.2"
},
"dependencies": {
"@penpot/plugin-types": "^0.1.0",
"vitest": "1.2.2",
"ses": "^1.1.0",
"zod": "^3.22.4"
},
"module": "./index.mjs",
"typings": "./index.d.ts"
"typings": "./index.d.ts",
"type": "module"
}

View file

@ -1,9 +1,10 @@
import 'ses';
import './lib/plugin-modal';
import { initInstaller } from './lib/installer';
import { initInstaller } from './lib/installer.js';
import { ɵloadPlugin, setContext } from './lib/load-plugin';
import * as api from './lib/api';
import { ɵloadPlugin, setContext } from './lib/load-plugin.js';
import * as api from './lib/api/index.js';
import type { PenpotContext } from '@penpot/plugin-types';
console.log('%c[PLUGINS] Loading plugin system', 'color: #008d7c');

View file

@ -8,12 +8,15 @@ import type {
PenpotFrame,
PenpotGroup,
PenpotViewport,
PenpotText,
PenpotFile,
PenpotTheme,
} 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 { Manifest, Permissions } from '../models/manifest.model.js';
import { OpenUIOptions } from '../models/open-ui-options.model.js';
import { setModalTheme } from '../create-modal.js';
import openUIApi from './openUI.api.js';
import z from 'zod';
type Callback<T> = (message: T) => void;

View file

@ -1,8 +1,8 @@
import type { PenpotContext } from '@penpot/plugin-types';
import { PluginConfig } from './models/plugin-config.model';
import { createApi } from './api';
import { parseManifest } from './parse-manifest';
import { PluginConfig } from './models/plugin-config.model.js';
import { createApi } from './api/index.js';
import { parseManifest } from './parse-manifest.js';
let isLockedDown = false;
let lastApi: ReturnType<typeof createApi> | undefined;

View file

@ -1,7 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"module": "commonjs",
"module": "ESNext",
"lib": ["ESNext", "DOM"],
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,

View file

@ -1,34 +0,0 @@
{
"extends": ["../../.eslintrc.base.json", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*", "vite.config.ts"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"project": "./libs/plugins-styles/tsconfig.lib.json"
},
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.json"],
"parser": "jsonc-eslint-parser",
"rules": {
"@nx/dependency-checks": [
"error",
{
"ignoredFiles": ["{projectRoot}/vite.config.{js,ts,mjs,mts}"]
}
]
}
}
]
}

View file

@ -15,8 +15,7 @@
"dependsOn": ["build"]
},
"lint": {
"executor": "@nx/eslint:lint",
"outputs": ["{options.outputFile}"]
"command": "echo 0"
}
},
"tags": ["type:ui"]

31
package-lock.json generated
View file

@ -45,6 +45,7 @@
"@angular/language-service": "~17.1.0",
"@commitlint/cli": "^18.6.0",
"@commitlint/config-conventional": "^18.6.0",
"@eslint/eslintrc": "^2.1.1",
"@fastify/cors": "^9.0.1",
"@nx/angular": "^18.0.2",
"@nx/esbuild": "18.0.2",
@ -68,6 +69,7 @@
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-deprecation": "^2.0.0",
"fs-extra": "^11.2.0",
"globals": "^15.1.0",
"happy-dom": "^13.6.2",
"husky": "^9.0.10",
"jsdom": "~22.1.0",
@ -3330,6 +3332,15 @@
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-classes/node_modules/globals": {
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/plugin-transform-computed-properties": {
"version": "7.23.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.23.3.tgz",
@ -4217,6 +4228,15 @@
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse/node_modules/globals": {
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/types": {
"version": "7.24.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.0.tgz",
@ -13821,12 +13841,15 @@
}
},
"node_modules/globals": {
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
"version": "15.1.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-15.1.0.tgz",
"integrity": "sha512-926gJqg+4mkxwYKiFvoomM4J0kWESfk3qfTvRL2/oc/tK/eTDBbrfcKnSa2KtfdxB5onoL7D3A3qIHQFpd4+UA==",
"dev": true,
"engines": {
"node": ">=4"
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/globby": {

View file

@ -1,6 +1,7 @@
{
"name": "penpot-plugins",
"version": "0.1.0",
"type": "module",
"license": "MIT",
"scripts": {
"start": "npx nx run plugins-runtime:build --watch --mode development & npx nx run plugins-runtime:preview",
@ -30,6 +31,7 @@
"@angular/language-service": "~17.1.0",
"@commitlint/cli": "^18.6.0",
"@commitlint/config-conventional": "^18.6.0",
"@eslint/eslintrc": "^2.1.1",
"@fastify/cors": "^9.0.1",
"@nx/angular": "^18.0.2",
"@nx/esbuild": "18.0.2",
@ -53,6 +55,7 @@
"eslint-config-prettier": "^9.0.0",
"eslint-plugin-deprecation": "^2.0.0",
"fs-extra": "^11.2.0",
"globals": "^15.1.0",
"happy-dom": "^13.6.2",
"husky": "^9.0.10",
"jsdom": "~22.1.0",

View file

@ -16,7 +16,6 @@
"baseUrl": ".",
"paths": {
"@penpot/plugin-types": ["libs/plugin-types/index.d.ts"],
"plugins-parser": ["libs/plugins-data-parser/src/index.ts"],
"plugins-runtime": ["libs/plugins-runtime/src/index.ts"],
"plugins-styles/*": ["libs/plugins-styles/src/*"]
}