diff --git a/README.md b/README.md index fcb56be..332ca37 100644 --- a/README.md +++ b/README.md @@ -64,14 +64,15 @@ Open in your browser: `http://localhost:4210/` ## Sample plugins -| Plugin | Description | PORT | Start command | Manifest URL | -| --------------------- | ----------------------------------------------------------- | ---- | ------------------------------- | ------------------------------------------ | -| poc-state-plugin | Sandbox plugin to test new plugins api functionality | 4301 | npm run start:plugin:poc-state | http://localhost:4301/assets/manifest.json | -| contrast-plugin | Sample plugin that gives you color contrast information | 4302 | npm run start:plugin:contrast | http://localhost:4302/manifest.json | -| icons-plugin | Tool to add icons from [Feather](https://feathericons.com/) | 4303 | npm run start:plugin:icons | http://localhost:4303/assets/manifest.json | -| lorem-ipsum-plugin | Generate Lorem ipsum text | 4304 | npm run start:plugin:loremipsum | http://localhost:4304/assets/manifest.json | -| create-palette-plugin | Creates a board with all the palette colors | 4305 | npm run start:plugin:palette | http://localhost:4305/assets/manifest.json | -| table-plugin | Create or import table | 4306 | npm run start:table-plugin | http://localhost:4306/assets/manifest.json | | -- | +| Plugin | Description | PORT | Start command | Manifest URL | +| --------------------- | ----------------------------------------------------------- | ---- | --------------------------------- | ------------------------------------------ | +| poc-state-plugin | Sandbox plugin to test new plugins api functionality | 4301 | npm run start:plugin:poc-state | http://localhost:4301/assets/manifest.json | +| contrast-plugin | Sample plugin that gives you color contrast information | 4302 | npm run start:plugin:contrast | http://localhost:4302/manifest.json | +| icons-plugin | Tool to add icons from [Feather](https://feathericons.com/) | 4303 | npm run start:plugin:icons | http://localhost:4303/assets/manifest.json | +| lorem-ipsum-plugin | Generate Lorem ipsum text | 4304 | npm run start:plugin:loremipsum | http://localhost:4304/assets/manifest.json | +| create-palette-plugin | Creates a board with all the palette colors | 4305 | npm run start:plugin:palette | http://localhost:4305/assets/manifest.json | +| table-plugin | Create or import table | 4306 | npm run start:table-plugin | http://localhost:4306/assets/manifest.json | +| rename-layers-plugin | Rename layers in bulk | 4307 | npm run start:plugin:renamelayers | http://localhost:4307/assets/manifest.json | ## Web Apps diff --git a/apps/rename-layers-plugin/eslint.config.js b/apps/rename-layers-plugin/eslint.config.js new file mode 100644 index 0000000..29c2176 --- /dev/null +++ b/apps/rename-layers-plugin/eslint.config.js @@ -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'] }, +]; diff --git a/apps/rename-layers-plugin/project.json b/apps/rename-layers-plugin/project.json new file mode 100644 index 0000000..3bb5af9 --- /dev/null +++ b/apps/rename-layers-plugin/project.json @@ -0,0 +1,92 @@ +{ + "name": "rename-layers-plugin", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "projectType": "application", + "prefix": "app", + "sourceRoot": "apps/rename-layers-plugin/src", + "tags": [], + "targets": { + "build": { + "executor": "@angular-devkit/build-angular:application", + "outputs": ["{options.outputPath}"], + "options": { + "outputPath": "dist/apps/rename-layers-plugin", + "index": "apps/rename-layers-plugin/src/index.html", + "browser": "apps/rename-layers-plugin/src/main.ts", + "polyfills": ["zone.js"], + "tsConfig": "apps/rename-layers-plugin/tsconfig.app.json", + "assets": [ + "apps/rename-layers-plugin/src/favicon.ico", + "apps/rename-layers-plugin/src/assets" + ], + "styles": [ + "libs/plugins-styles/src/lib/styles.css", + "apps/rename-layers-plugin/src/styles.css" + ], + "optimization": { + "scripts": true, + "styles": true, + "fonts": false + }, + "scripts": [] + }, + "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": "rename-layers-plugin:build:production" + }, + "development": { + "buildTarget": "rename-layers-plugin:build:development", + "host": "0.0.0.0", + "port": 4307 + } + }, + "defaultConfiguration": "development" + }, + "extract-i18n": { + "executor": "@angular-devkit/build-angular:extract-i18n", + "options": { + "buildTarget": "rename-layers-plugin:build" + } + }, + "buildPlugin": { + "executor": "@nx/esbuild:esbuild", + "outputs": ["{options.outputPath}"], + "options": { + "minify": true, + "outputPath": "apps/rename-layers-plugin/src/assets/", + "main": "apps/rename-layers-plugin/src/plugin.ts", + "tsConfig": "apps/rename-layers-plugin/tsconfig.plugin.json", + "generatePackageJson": false, + "format": ["esm"], + "deleteOutputPath": false + } + } + } +} diff --git a/apps/rename-layers-plugin/src/app/app.component.css b/apps/rename-layers-plugin/src/app/app.component.css new file mode 100644 index 0000000..81a0697 --- /dev/null +++ b/apps/rename-layers-plugin/src/app/app.component.css @@ -0,0 +1,15 @@ +.explanation { + margin-block-end: var(--spacing-8); +} +.form { + padding-block: var(--spacing-12); +} + +.form-group { + margin-block-end: var(--spacing-8); +} + +.input, +button { + inline-size: 100%; +} diff --git a/apps/rename-layers-plugin/src/app/app.component.html b/apps/rename-layers-plugin/src/app/app.component.html new file mode 100644 index 0000000..c563c0a --- /dev/null +++ b/apps/rename-layers-plugin/src/app/app.component.html @@ -0,0 +1,26 @@ +
+

Introduce the text to replace

+
+ + +
+
+ + +
+ +
diff --git a/apps/rename-layers-plugin/src/app/app.component.ts b/apps/rename-layers-plugin/src/app/app.component.ts new file mode 100644 index 0000000..e2ea095 --- /dev/null +++ b/apps/rename-layers-plugin/src/app/app.component.ts @@ -0,0 +1,52 @@ +import { Component, inject } from '@angular/core'; +import { ActivatedRoute, RouterModule } from '@angular/router'; +import { toSignal } from '@angular/core/rxjs-interop'; +import { CommonModule } from '@angular/common'; +import type { PluginMessageEvent, ReplaceText } from '../app/model'; +import { filter, fromEvent, map, merge, take } from 'rxjs'; +import { FormsModule } from '@angular/forms'; + +@Component({ + standalone: true, + imports: [RouterModule, CommonModule, FormsModule], + selector: 'app-root', + templateUrl: './app.component.html', + styleUrl: './app.component.css', + host: { + '[attr.data-theme]': 'theme()', + }, +}) +export class AppComponent { + route = inject(ActivatedRoute); + messages$ = fromEvent>(window, 'message'); + public textToReplace: ReplaceText = { + current: '', + new: '', + }; + + initialTheme$ = this.route.queryParamMap.pipe( + map((params) => params.get('theme')), + filter((theme) => !!theme), + take(1) + ); + + theme = toSignal( + merge( + this.initialTheme$, + this.messages$.pipe( + filter((event) => event.data.type === 'theme'), + map((event) => { + return event.data.content; + }) + ) + ) + ); + + public updateText() { + this.sendMessage({ type: 'replace-text', content: this.textToReplace }); + } + + private sendMessage(message: PluginMessageEvent): void { + parent.postMessage(message, '*'); + } +} diff --git a/apps/rename-layers-plugin/src/app/app.config.ts b/apps/rename-layers-plugin/src/app/app.config.ts new file mode 100644 index 0000000..ed40494 --- /dev/null +++ b/apps/rename-layers-plugin/src/app/app.config.ts @@ -0,0 +1,7 @@ +import { ApplicationConfig } from '@angular/core'; +import { provideRouter } from '@angular/router'; +import { appRoutes } from './app.routes'; + +export const appConfig: ApplicationConfig = { + providers: [provideRouter(appRoutes)], +}; diff --git a/apps/rename-layers-plugin/src/app/app.routes.ts b/apps/rename-layers-plugin/src/app/app.routes.ts new file mode 100644 index 0000000..8762dfe --- /dev/null +++ b/apps/rename-layers-plugin/src/app/app.routes.ts @@ -0,0 +1,3 @@ +import { Route } from '@angular/router'; + +export const appRoutes: Route[] = []; diff --git a/apps/rename-layers-plugin/src/app/model.ts b/apps/rename-layers-plugin/src/app/model.ts new file mode 100644 index 0000000..7947fa8 --- /dev/null +++ b/apps/rename-layers-plugin/src/app/model.ts @@ -0,0 +1,25 @@ +export interface InitPluginEvent { + type: 'init'; + content: { + theme: string; + }; +} +export interface ThemePluginEvent { + type: 'theme'; + content: string; +} + +export interface ReplaceTextPluginEvent { + type: 'replace-text'; + content: ReplaceText; +} + +export type PluginMessageEvent = + | InitPluginEvent + | ThemePluginEvent + | ReplaceTextPluginEvent; + +export interface ReplaceText { + current: string; + new: string; +} diff --git a/apps/rename-layers-plugin/src/assets/.gitkeep b/apps/rename-layers-plugin/src/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/apps/rename-layers-plugin/src/assets/manifest.json b/apps/rename-layers-plugin/src/assets/manifest.json new file mode 100644 index 0000000..a5c7f80 --- /dev/null +++ b/apps/rename-layers-plugin/src/assets/manifest.json @@ -0,0 +1,7 @@ +{ + "name": "Rename layers plugin", + "host": "http://localhost:4307", + "description": "Change the name of one or several layers", + "code": "/assets/plugin.js", + "permissions": ["page:read", "file:read", "selection:read"] +} diff --git a/apps/rename-layers-plugin/src/favicon.ico b/apps/rename-layers-plugin/src/favicon.ico new file mode 100644 index 0000000..317ebcb Binary files /dev/null and b/apps/rename-layers-plugin/src/favicon.ico differ diff --git a/apps/rename-layers-plugin/src/index.html b/apps/rename-layers-plugin/src/index.html new file mode 100644 index 0000000..c5ca795 --- /dev/null +++ b/apps/rename-layers-plugin/src/index.html @@ -0,0 +1,13 @@ + + + + + rename-layers-plugin + + + + + + + + diff --git a/apps/rename-layers-plugin/src/main.ts b/apps/rename-layers-plugin/src/main.ts new file mode 100644 index 0000000..514c89a --- /dev/null +++ b/apps/rename-layers-plugin/src/main.ts @@ -0,0 +1,7 @@ +import { bootstrapApplication } from '@angular/platform-browser'; +import { appConfig } from './app/app.config'; +import { AppComponent } from './app/app.component'; + +bootstrapApplication(AppComponent, appConfig).catch((err) => + console.error(err) +); diff --git a/apps/rename-layers-plugin/src/plugin.ts b/apps/rename-layers-plugin/src/plugin.ts new file mode 100644 index 0000000..cdfcad1 --- /dev/null +++ b/apps/rename-layers-plugin/src/plugin.ts @@ -0,0 +1,25 @@ +import { PluginMessageEvent } from './app/model'; + +penpot.ui.open('Plugin rename layers', `?theme=${penpot.getTheme()}`, { + width: 235, + height: 200, +}); + +penpot.on('themechange', (theme) => { + penpot.ui.sendMessage({ type: 'theme', content: theme }); +}); + +penpot.ui.onMessage((message) => { + if (message.type === 'replace-text') { + const shapes = penpot.getPage()?.findShapes(); + const shapesToUpdate = shapes?.filter((shape) => + shape.name.includes(message.content.current) + ); + shapesToUpdate?.forEach((shape) => { + shape.name = shape.name.replace( + message.content.current, + message.content.new + ); + }); + } +}); diff --git a/apps/rename-layers-plugin/src/styles.css b/apps/rename-layers-plugin/src/styles.css new file mode 100644 index 0000000..90d4ee0 --- /dev/null +++ b/apps/rename-layers-plugin/src/styles.css @@ -0,0 +1 @@ +/* You can add global styles to this file, and also import other style files */ diff --git a/apps/rename-layers-plugin/tsconfig.app.json b/apps/rename-layers-plugin/tsconfig.app.json new file mode 100644 index 0000000..fff4a41 --- /dev/null +++ b/apps/rename-layers-plugin/tsconfig.app.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": [] + }, + "files": ["src/main.ts"], + "include": ["src/**/*.d.ts"], + "exclude": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts"] +} diff --git a/apps/rename-layers-plugin/tsconfig.editor.json b/apps/rename-layers-plugin/tsconfig.editor.json new file mode 100644 index 0000000..4ee6393 --- /dev/null +++ b/apps/rename-layers-plugin/tsconfig.editor.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/**/*.ts"], + "compilerOptions": { + "types": [] + } +} diff --git a/apps/rename-layers-plugin/tsconfig.json b/apps/rename-layers-plugin/tsconfig.json new file mode 100644 index 0000000..4c48587 --- /dev/null +++ b/apps/rename-layers-plugin/tsconfig.json @@ -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 + } +} diff --git a/apps/rename-layers-plugin/tsconfig.plugin.json b/apps/rename-layers-plugin/tsconfig.plugin.json new file mode 100644 index 0000000..961987f --- /dev/null +++ b/apps/rename-layers-plugin/tsconfig.plugin.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "types": [] + }, + "files": ["src/plugin.ts"], + "include": ["../../libs/plugin-types/index.d.ts"] +} diff --git a/package.json b/package.json index c761a50..79563a3 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "start:plugin:loremipsum": "npx nx run lorem-ipsum-plugin:init", "start:plugin:palette": "npx nx run create-palette-plugin:build --watch & npx nx run create-palette-plugin:preview", "start:plugin:table": "npx nx run table-plugin:init", + "start:plugin:renamelayers": "npx nx run rename-layers-plugin:init", "build": "npx nx build plugins-runtime --emptyOutDir=true", "lint": "nx run-many --all --target=lint --parallel", "lint:affected": "npx nx affected --target=lint",