0
Fork 0
mirror of https://github.com/penpot/penpot-plugins.git synced 2025-01-21 06:02:34 -05:00

feat: add poc-state-plugin app

This commit is contained in:
Juanfran 2024-04-23 12:33:38 +02:00
parent 2300ce23ac
commit e618cb1e9b
20 changed files with 9799 additions and 236 deletions

4
.gitignore vendored
View file

@ -40,3 +40,7 @@ Thumbs.db
.nx/cache
.env
.angular
**/assets/plugin.js

View file

@ -1,4 +1,5 @@
# Add files here to ignore them from prettier formatting
/dist
/coverage
/.nx/cache
/.nx/cache
.angular

View file

@ -0,0 +1,36 @@
{
"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,94 @@
{
"name": "poc-state-plugin",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"prefix": "app",
"sourceRoot": "apps/poc-state-plugin/src",
"tags": [],
"targets": {
"buildPlugin": {
"executor": "@nx/esbuild:esbuild",
"outputs": [
"{options.outputPath}"
],
"options": {
"minify": true,
"outputPath": "apps/poc-state-plugin/src/assets/",
"main": "apps/poc-state-plugin/src/plugin.ts",
"tsConfig": "apps/poc-state-plugin/tsconfig.plugin.json",
"generatePackageJson": false,
"format": [
"esm"
],
"deleteOutputPath": false
}
},
"build": {
"executor": "@angular-devkit/build-angular:application",
"outputs": ["{options.outputPath}"],
"options": {
"outputPath": "dist/apps/poc-state-plugin",
"index": "apps/poc-state-plugin/src/index.html",
"browser": "apps/poc-state-plugin/src/main.ts",
"polyfills": ["zone.js"],
"tsConfig": "apps/poc-state-plugin/tsconfig.app.json",
"assets": [
"apps/poc-state-plugin/src/favicon.ico",
"apps/poc-state-plugin/src/assets"
],
"styles": [
"libs/plugins-styles/src/lib/styles.css",
"apps/poc-state-plugin/src/styles.css"],
"scripts": [],
"optimization": {
"scripts": true,
"styles": true,
"fonts": false
}
},
"configurations": {
"production": {
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "1mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production",
"dependsOn": ["buildPlugin"]
},
"serve": {
"executor": "@angular-devkit/build-angular:dev-server",
"configurations": {
"production": {
"buildTarget": "poc-state-plugin:build:production"
},
"development": {
"buildTarget": "poc-state-plugin:build:development",
"port": 4202
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"executor": "@angular-devkit/build-angular:extract-i18n",
"options": {
"buildTarget": "poc-state-plugin:build"
}
}
}
}

View file

@ -0,0 +1,87 @@
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);
}
.name-wrap {
display: flex;
gap: 1rem;
margin-bottom: 1rem;
}
.name-wrap input {
flex: 1;
}
.actions-wrap {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 1rem;
margin-bottom: 1rem;
}

View file

@ -0,0 +1,140 @@
import { Component, signal } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule } from '@angular/forms';
import type { PenpotShape } from '@penpot/plugin-types';
@Component({
standalone: true,
selector: 'app-root',
imports: [ReactiveFormsModule],
template: `
<div class="wrapper" [attr.data-theme]="theme()">
<h1>Test area!</h1>
<p>
Current project name: <span>{{ projectName() }}</span>
</p>
<form [formGroup]="form" (ngSubmit)="updateName()">
<div class="name-wrap">
<label>Selected Shape: </label>
<input type="text" formControlName="name" />
<button type="submit">Update</button>
</div>
</form>
<div class="actions-wrap">
<button type="button" (click)="createRect()">+Rect</button>
<button type="button" (click)="moveX()">Move X</button>
<button type="button" (click)="moveY()">Move Y</button>
<button type="button" (click)="resizeW()">Resize W</button>
<button type="button" (click)="resizeH()">Resize H</button>
<button type="button" (click)="loremIpsum()">Lorem Ipsum</button>
</div>
<p>
<button
(click)="close()"
type="button"
data-appearance="primary"
data-variant="destructive"
class="act-close-plugin"
>
Close plugin
</button>
</p>
</div>
`,
styleUrl: './app.component.css',
})
export class AppComponent {
#pageId: null | string = null;
#fileId = null;
#revn = 0;
#selection = signal<PenpotShape[]>([]);
form = new FormGroup({
name: new FormControl(''),
});
theme = signal('');
projectName = signal('Unknown');
constructor() {
window.addEventListener('message', (event) => {
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.page.id,
event.data.content.page.name
);
} else if (event.data.type === 'selection') {
this.#refreshSelection(event.data.content.selection);
} 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.#refreshSelection(event.data.content.selection);
this.theme.set(event.data.content.theme);
} else if (event.data.type === 'theme') {
this.theme.set(event.data.content);
}
});
}
close() {
this.#sendMessage({ content: 'close' });
}
updateName() {
const id = this.#selection()[0].id;
const name = this.form.get('name')?.value;
this.#sendMessage({ content: 'change-name', data: { id, name } });
}
createRect() {
this.#sendMessage({ content: 'create-rect' });
}
moveX() {
const id = this.#selection()[0].id;
this.#sendMessage({ content: 'move-x', data: { id } });
}
moveY() {
const id = this.#selection()[0].id;
this.#sendMessage({ content: 'move-y', data: { id } });
}
resizeW() {
const id = this.#selection()[0].id;
this.#sendMessage({ content: 'resize-w', data: { id } });
}
resizeH() {
const id = this.#selection()[0].id;
this.#sendMessage({ content: 'resize-h', data: { id } });
}
loremIpsum() {
this.#sendMessage({ content: 'lorem-ipsum' });
}
#sendMessage(message: unknown) {
parent.postMessage(message, '*');
}
#refreshPage(pageId: string, name: string) {
this.#pageId = pageId;
this.projectName.set(name || 'Unknown');
}
#refreshSelection(selection: PenpotShape[]) {
this.#selection.set(selection);
if (selection && selection.length > 0) {
this.form.get('name')?.setValue(this.#selection()[0].name);
} else {
this.form.get('name')?.setValue('');
}
}
}

View file

@ -0,0 +1,9 @@
{
"name": "POC State Read",
"code": "http://localhost:4202/assets/plugin.js",
"permissions": [
"page:read",
"file:read",
"selection:read"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>poc-state-plugin</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
</head>
<body>
<app-root></app-root>
</body>
</html>

View file

@ -0,0 +1,4 @@
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent).catch((err) => console.error(err));

View file

@ -0,0 +1,113 @@
import { PenpotText } from '@penpot/plugin-types';
penpot.ui.open('Plugin name', 'http://localhost:4202', {
width: 500,
height: 600,
});
penpot.ui.onMessage<{ content: string; data: unknown }>((message) => {
if (message.content === 'close') {
penpot.closePlugin();
} else if (message.content === 'ready') {
const page = penpot.getPage();
const file = penpot.getFile();
if (!page || !file) {
return;
}
penpot.ui.sendMessage({
type: 'init',
content: {
name: page.name,
pageId: page.id,
fileId: file.id,
revn: file.revn,
theme: penpot.getTheme(),
selection: penpot.getSelectedShapes(),
},
});
} else if (message.content === 'change-name') {
const shape = penpot
.getPage()
?.getShapeById('' + (message.data as { id: string }).id);
if (shape) {
shape.name = (message.data as { name: string }).name;
}
} else if (message.content === 'create-rect') {
const shape = penpot.createRectangle();
penpot.log(shape);
} else if (message.content === 'move-x') {
const shape = penpot
.getPage()
?.getShapeById('' + (message.data as { id: string }).id);
if (shape) {
shape.x += 100;
}
} else if (message.content === 'move-y') {
const shape = penpot
.getPage()
?.getShapeById('' + (message.data as { id: string }).id);
if (shape) {
shape.y += 100;
}
} else if (message.content === 'resize-w') {
const shape = penpot
.getPage()
?.getShapeById('' + (message.data as { id: string }).id);
if (shape) {
shape.resize(shape.width * 2, shape.height);
}
} else if (message.content === 'resize-h') {
const shape = penpot
.getPage()
?.getShapeById('' + (message.data as { id: string }).id);
if (shape) {
shape.resize(shape.width, shape.height * 2);
}
} else if (message.content === 'lorem-ipsum') {
const selection = penpot.selection;
for (const shape of selection) {
(
shape as PenpotText
).characters = `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam id mauris ut felis finibus congue. Ut odio ipsum, condimentum id tellus sit amet, dapibus sagittis ligula. Pellentesque hendrerit, nulla sit amet aliquet scelerisque, orci nunc commodo tellus, quis hendrerit nisl massa non tellus.
Phasellus fringilla tortor elit, ac dictum tellus posuere sodales. Ut eget imperdiet ante. Nunc eros magna, tincidunt non finibus in, tempor elementum nunc. Sed commodo magna in arcu aliquam efficitur.`;
}
}
});
penpot.on('pagechange', () => {
const page = penpot.getPage();
const shapes = page?.findShapes();
penpot.ui.sendMessage({
type: 'page',
content: { page, shapes },
});
});
penpot.on('filechange', () => {
const file = penpot.getFile();
if (!file) {
return;
}
penpot.ui.sendMessage({
type: 'file',
content: {
id: file.id,
},
});
});
penpot.on('selectionchange', () => {
const selection = penpot.getSelectedShapes();
penpot.ui.sendMessage({ type: 'selection', content: { selection } });
});
penpot.on('themechange', (theme) => {
penpot.ui.sendMessage({ type: 'theme', content: theme });
});

View file

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

View file

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

View file

@ -0,0 +1,7 @@
{
"extends": "./tsconfig.json",
"include": ["src/**/*.ts"],
"compilerOptions": {
"types": ["node"]
}
}

View file

@ -0,0 +1,33 @@
{
"compilerOptions": {
"target": "es2022",
"useDefineForClassFields": false,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.app.json"
},
{
"path": "./tsconfig.editor.json"
},
{
"path": "./tsconfig.plugin.json"
}
],
"extends": "../../tsconfig.base.json",
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": true
}
}

View file

@ -0,0 +1,8 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"types": []
},
"files": ["src/plugin.ts"],
"include": ["../../libs/plugin-types/index.d.ts"],
}

View file

@ -0,0 +1,117 @@
# Creating a Plugin
This guide walks you through the steps to create an Angular plugin.
### Step 1: Initialize the Plugin
First, you need to create the scaffolding for your plugin. Use the following command, replacing `example-plugin` with the name of your plugin:
```sh
npx nx g @nx/angular:app example-plugin --directory=apps/example-plugin
```
### Step 2: Configure the Manifest
Next, create a `manifest.json` file inside the `/src/assets` directory. This file is crucial as it defines key properties of your plugin, including permissions and the entry point script.
```json
{
"name": "Example plugin",
"code": "http://localhost:4202/assets/plugin.js",
"permissions": ["page:read", "file:read", "selection:read"]
}
```
### Step 3: Update Project Configuration
Now, add the following configuration to your `project.json` to compile the `plugin.ts` file:
```typescript
"targets": {
"buildPlugin": {
"executor": "@nx/esbuild:esbuild",
"outputs": [
"{options.outputPath}"
],
"options": {
"minify": true,
"outputPath": "apps/example-plugin/src/assets/",
"main": "apps/example-plugin/src/plugin.ts",
"tsConfig": "apps/example-plugin/tsconfig.plugin.json",
"generatePackageJson": false,
"format": [
"esm"
],
"deleteOutputPath": false
}
},
}
```
Also, update `targets.build` with the following code to allow the use of Penpot styles.
```json
"styles": [
"libs/plugins-styles/src/lib/styles.css",
"apps/example-plugin/src/styles.css"
],
"optimization": {
"scripts": true,
"styles": true,
"fonts": false
}
```
### Step 4: Modify TypeScript Configuration
Create ``tsconfig.plugin.json` next to the `tsconfig.json`:
```json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"types": []
},
"files": ["src/plugin.ts"],
"include": ["../../libs/plugin-types/index.d.ts"]
}
```
Add the reference to the main tsconfig.json:
```json
"references": [
{
"path": "./tsconfig.plugin.json"
}
],
```
### Step 5: Run the plugin
Run this command:
```sh
npx nx run-many --targets=buildPlugin,serve --projects=poc-state-plugin --watch
```
This will run two tasks: `serve`, the usual Angular server, and `buildPlugin`, which will compile the `plugin.ts` file.
### Step 6: Load the Plugin in Penpot
Finally, to load your plugin into Penpot, execute the following command in the browser's console devtools:
```typescript
ɵloadPlugin({ manifest: 'http://localhost:4202/manifest.json' });
```
### Learn More About Plugin Development
For more detailed information on plugin development, check out our guides:
- [Plugin Usage Documentation](docs/plugin-usage.md)
- [Create API Documentation](docs/create-api.md)
### Using a Starter Template
If you prefer to kickstart your plugin development, consider using the [Penpot Plugin Starter Template](https://github.com/penpot/penpot-plugin-starter-template). It's a template designed to streamline the creation process for Penpot plugins.

11
nx.json
View file

@ -21,6 +21,11 @@
"cache": true,
"dependsOn": ["^build"],
"inputs": ["default", "^default"]
},
"@angular-devkit/build-angular:application": {
"cache": true,
"dependsOn": ["^build"],
"inputs": ["default", "^default"]
}
},
"plugins": [
@ -47,6 +52,12 @@
"linter": "eslint",
"unitTestRunner": "vitest",
"e2eTestRunner": "none"
},
"@nx/angular:application": {
"e2eTestRunner": "none",
"linter": "eslint",
"style": "css",
"unitTestRunner": "none"
}
}
}

9320
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -6,6 +6,7 @@
"start": "npx nx run plugins-runtime:build --watch --mode development & npx nx run plugins-runtime:preview",
"start:example": "npx nx run example-plugin:build --watch & npx nx run example-plugin:preview",
"start:read-plugin": "npx nx run poc-state-read-plugin:build --watch & npx nx run poc-state-read-plugin:preview",
"start:pc-plugin": "npx nx run-many --targets=buildPlugin,serve --projects=poc-state-plugin --watch",
"start:contrast-plugin": "npx nx run contrast-plugin:build --watch & npx nx run contrast-plugin:preview",
"start:rpc-api": "npx nx serve rpc-api",
"start:styles-example": "npx nx run example-styles:serve --port 4202",
@ -19,9 +20,19 @@
},
"private": true,
"devDependencies": {
"@angular-devkit/build-angular": "~17.1.0",
"@angular-devkit/core": "~17.1.0",
"@angular-devkit/schematics": "~17.1.0",
"@angular-eslint/eslint-plugin": "~17.0.0",
"@angular-eslint/eslint-plugin-template": "~17.0.0",
"@angular-eslint/template-parser": "~17.0.0",
"@angular/cli": "~17.1.0",
"@angular/compiler-cli": "~17.1.0",
"@angular/language-service": "~17.1.0",
"@commitlint/cli": "^18.6.0",
"@commitlint/config-conventional": "^18.6.0",
"@fastify/cors": "^9.0.1",
"@nx/angular": "^18.0.2",
"@nx/esbuild": "18.0.2",
"@nx/eslint": "18.0.2",
"@nx/eslint-plugin": "18.0.2",
@ -29,6 +40,7 @@
"@nx/node": "^18.0.2",
"@nx/vite": "18.0.2",
"@nx/web": "18.0.2",
"@schematics/angular": "~17.1.0",
"@swc-node/register": "~1.6.7",
"@swc/core": "~1.3.85",
"@swc/helpers": "~0.5.2",
@ -45,6 +57,7 @@
"happy-dom": "^13.6.2",
"husky": "^9.0.10",
"jsdom": "~22.1.0",
"jsonc-eslint-parser": "^2.1.0",
"nx": "18.0.2",
"prettier": "^2.6.2",
"swc-loader": "0.1.15",
@ -59,16 +72,26 @@
"packages/*"
],
"dependencies": {
"@angular/animations": "~17.1.0",
"@angular/common": "~17.1.0",
"@angular/compiler": "~17.1.0",
"@angular/core": "~17.1.0",
"@angular/forms": "~17.1.0",
"@angular/platform-browser": "~17.1.0",
"@angular/platform-browser-dynamic": "~17.1.0",
"@angular/router": "~17.1.0",
"@fastify/autoload": "~5.7.1",
"@fastify/sensible": "~5.2.0",
"@types/uuid": "^9.0.8",
"axios": "^1.6.0",
"fastify": "~4.13.0",
"fastify-plugin": "~4.5.0",
"rxjs": "~7.8.0",
"ses": "^1.1.0",
"tslib": "^2.3.0",
"uuid": "^9.0.1",
"zod": "^3.22.4"
"zod": "^3.22.4",
"zone.js": "~0.14.3"
},
"nx": {
"includedScripts": []