0
Fork 0
mirror of https://github.com/penpot/penpot-plugins.git synced 2025-03-06 04:45:44 -05:00

feat: rename layers plugin

This commit is contained in:
Marina López 2024-05-31 09:21:08 +02:00
parent 50d11177a6
commit b54eb57781
21 changed files with 384 additions and 8 deletions

View file

@ -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

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

@ -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
}
}
}
}

View file

@ -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%;
}

View file

@ -0,0 +1,26 @@
<div class="form">
<p class="explanation body-s">Introduce the text to replace</p>
<div class="form-group">
<label class="input-label-hidden" for="current">Search</label>
<input
[(ngModel)]="textToReplace.current"
class="input"
type="text"
placeholder="Search"
id="current"
/>
</div>
<div class="form-group">
<label class="input-label-hidden" for="replace">Replace</label>
<input
[(ngModel)]="textToReplace.new"
class="input"
type="text"
placeholder="Replace"
id="replace"
/>
</div>
<button type="button" data-appearance="primary" (click)="updateText()">
Replace all
</button>
</div>

View file

@ -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<MessageEvent<PluginMessageEvent>>(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, '*');
}
}

View file

@ -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)],
};

View file

@ -0,0 +1,3 @@
import { Route } from '@angular/router';
export const appRoutes: Route[] = [];

View file

@ -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;
}

View file

@ -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"]
}

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>rename-layers-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,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)
);

View file

@ -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<PluginMessageEvent>((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
);
});
}
});

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": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts"]
}

View file

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

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

@ -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",