mirror of
https://github.com/penpot/penpot-plugins.git
synced 2025-01-06 14:50:21 -05:00
feat: rename layers plugin
This commit is contained in:
parent
b4e7415891
commit
2331e347b1
10 changed files with 457 additions and 54 deletions
|
@ -466,6 +466,7 @@
|
|||
<span class="icon icon-arrow-top"></span>
|
||||
<span class="icon icon-arrow-right"></span>
|
||||
<span class="icon icon-arrow-left"></span>
|
||||
<span class="icon icon-arrow-right-full"></span>
|
||||
<span class="icon icon-close"></span>
|
||||
<span class="icon icon-close-l"></span>
|
||||
<span class="icon icon-delete"></span>
|
||||
|
@ -488,6 +489,7 @@
|
|||
<span class="icon icon-arrow-top"></span>
|
||||
<span class="icon icon-arrow-right"></span>
|
||||
<span class="icon icon-arrow-left"></span>
|
||||
<span class="icon icon-arrow-right-full"></span>
|
||||
<span class="icon icon-close"></span>
|
||||
<span class="icon icon-close-l"></span>
|
||||
<span class="icon icon-delete"></span>
|
||||
|
|
|
@ -1,8 +1,29 @@
|
|||
.nav-tabs {
|
||||
background-color: var(--background-secondary);
|
||||
border-radius: var(--spacing-8);
|
||||
display: flex;
|
||||
margin-block-start: var(--spacing-24);
|
||||
|
||||
& .tab {
|
||||
background-color: var(--background-secondary);
|
||||
border: 2px solid transparent;
|
||||
color: var(--foreground-secondary);
|
||||
font-size: var(--font-size-s);
|
||||
padding-inline: var(--spacing-8);
|
||||
|
||||
&.active {
|
||||
background-color: var(--db-quaternary);
|
||||
border: 2px solid var(--background-secondary);
|
||||
color: var(--accent-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.explanation {
|
||||
margin-block-end: var(--spacing-8);
|
||||
}
|
||||
.form {
|
||||
padding-block: var(--spacing-12);
|
||||
padding-block-start: var(--spacing-8);
|
||||
}
|
||||
|
||||
.form-group {
|
||||
|
@ -13,3 +34,85 @@
|
|||
button {
|
||||
inline-size: 100%;
|
||||
}
|
||||
|
||||
[data-appearance='primary'].btn-feedback {
|
||||
background-color: var(--accent-tertiary);
|
||||
border: 2px solid var(--accent-tertiary);
|
||||
}
|
||||
|
||||
.icon-btn {
|
||||
margin: auto;
|
||||
.stroke {
|
||||
stroke: var(--db-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
::placeholder {
|
||||
color: var(--foreground-secondary);
|
||||
}
|
||||
|
||||
.no-match {
|
||||
border-bottom: 1px solid var(--background-quaternary);
|
||||
padding-block-end: var(--spacing-8);
|
||||
}
|
||||
|
||||
.preview-list {
|
||||
background-color: var(--background-tertiary);
|
||||
border-radius: var(--spacing-8);
|
||||
font-size: var(--font-size-xs);
|
||||
margin-block-start: var(--spacing-8);
|
||||
block-size: 215px;
|
||||
overflow: auto;
|
||||
padding: var(--spacing-8);
|
||||
|
||||
&.replace {
|
||||
block-size: 175px;
|
||||
}
|
||||
|
||||
& .preview-item {
|
||||
align-items: center;
|
||||
display: grid;
|
||||
grid-template-columns: 33% 7% 60%;
|
||||
}
|
||||
|
||||
::ng-deep .highlight {
|
||||
background-color: var(--accent-tertiary);
|
||||
color: var(--foreground-primary);
|
||||
opacity: 70%;
|
||||
}
|
||||
|
||||
& .original,
|
||||
& .result {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.result {
|
||||
margin-inline-start: var(--spacing-4);
|
||||
}
|
||||
}
|
||||
|
||||
.preview {
|
||||
margin-block-start: var(--spacing-24);
|
||||
}
|
||||
|
||||
:host[data-theme='light'] {
|
||||
.nav-tabs {
|
||||
background-color: var(--lb-tertiary);
|
||||
|
||||
& .tab {
|
||||
background-color: var(--lb-tertiary);
|
||||
|
||||
&.active {
|
||||
background-color: var(--lb-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.icon-btn {
|
||||
.stroke {
|
||||
stroke: var(--lb-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,122 @@
|
|||
<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 role="tablist" class="nav-tabs">
|
||||
<button
|
||||
(click)="selectTab('add')"
|
||||
[class.active]="tab === 'add'"
|
||||
type="button"
|
||||
role="tab"
|
||||
class="tab"
|
||||
>
|
||||
Add text
|
||||
</button>
|
||||
<button
|
||||
(click)="selectTab('replace')"
|
||||
[class.active]="tab === 'replace'"
|
||||
type="button"
|
||||
role="tab"
|
||||
class="tab"
|
||||
>
|
||||
Replace text
|
||||
</button>
|
||||
</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 *ngIf="tab === 'add'" class="form">
|
||||
<p class="explanation body-s">
|
||||
Select layers to rename (otherwise it will apply to all layers) and enter
|
||||
the text you want to add.
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<label class="input-label-hidden" for="search">Add text</label>
|
||||
<input
|
||||
#addElement
|
||||
[(ngModel)]="addText"
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder="[Original layer name]"
|
||||
id="addText"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
data-appearance="primary"
|
||||
[class.btn-feedback]="btnFeedback"
|
||||
(click)="updateText()"
|
||||
>
|
||||
<span *ngIf="!btnFeedback" class="text-btn">Add</span>
|
||||
<span *ngIf="btnFeedback" class="icon icon-btn icon-tick">
|
||||
<svg width="16" height="16" fill="none">
|
||||
<path d="M13.333 4 6 11.333 2.667 8" class="stroke" />
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
<p class="body-s preview">Previsualization:</p>
|
||||
<ul class="preview-list">
|
||||
<li class="preview-item" *ngFor="let preview of previewList()">
|
||||
<span class="original" [title]="preview.name">{{ preview.name }}</span>
|
||||
<span class="icon icon-arrow-right-full"></span>
|
||||
<span class="result" [title]="resultAddText(preview)">{{
|
||||
resultAddText(preview)
|
||||
}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div *ngIf="tab === 'replace'" class="form">
|
||||
<p class="explanation body-s">
|
||||
Select layers to rename (otherwise it will apply to all layers) and enter
|
||||
the replacement text.
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<label class="input-label-hidden" for="search">Search</label>
|
||||
<input
|
||||
[(ngModel)]="textToReplace.search"
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder="Search"
|
||||
id="search"
|
||||
#searchElement
|
||||
(keydown)="previewReplace()"
|
||||
/>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="input-label-hidden" for="replace">Replace</label>
|
||||
<input
|
||||
[(ngModel)]="textToReplace.replace"
|
||||
class="input"
|
||||
type="text"
|
||||
placeholder="Write the new text"
|
||||
id="replace"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
data-appearance="primary"
|
||||
(click)="updateText()"
|
||||
[class.btn-feedback]="btnFeedback"
|
||||
>
|
||||
<span *ngIf="!btnFeedback" class="text-btn">Replace</span>
|
||||
<span *ngIf="btnFeedback" class="icon icon-btn icon-tick">
|
||||
<svg width="16" height="16" fill="none">
|
||||
<path d="M13.333 4 6 11.333 2.667 8" class="stroke" />
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
<p class="body-s preview">Previsualization:</p>
|
||||
<ul class="preview-list replace">
|
||||
<li *ngIf="previewList().length === 0" class="no-match">
|
||||
No matches found
|
||||
</li>
|
||||
<li class="preview-item" *ngFor="let preview of previewList()">
|
||||
<span
|
||||
class="original"
|
||||
[innerHTML]="highlightMatch(preview.name)"
|
||||
[title]="preview.name"
|
||||
></span>
|
||||
<span class="icon icon-arrow-right-full"></span>
|
||||
<span class="result" [title]="resultReplaceText(preview.name)">{{
|
||||
resultReplaceText(preview.name)
|
||||
}}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<button type="button" data-appearance="primary" (click)="updateText()">
|
||||
Replace all
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
import { Component, inject } from '@angular/core';
|
||||
import { Component, ElementRef, ViewChild, 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 type {
|
||||
PluginMessageEvent,
|
||||
ReplaceText,
|
||||
ThemePluginEvent,
|
||||
} from '../app/model';
|
||||
import { filter, fromEvent, map, merge, take } from 'rxjs';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { PenpotShape } from '@penpot/plugin-types';
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
|
@ -17,12 +22,22 @@ import { FormsModule } from '@angular/forms';
|
|||
},
|
||||
})
|
||||
export class AppComponent {
|
||||
@ViewChild('searchElement') public searchElement!: ElementRef;
|
||||
@ViewChild('addElement') public addElement!: ElementRef;
|
||||
|
||||
route = inject(ActivatedRoute);
|
||||
messages$ = fromEvent<MessageEvent<PluginMessageEvent>>(window, 'message');
|
||||
public textToReplace: ReplaceText = {
|
||||
current: '',
|
||||
new: '',
|
||||
search: '',
|
||||
replace: '',
|
||||
};
|
||||
public addText = '[Original layer name]';
|
||||
public tab: 'add' | 'replace' = 'add';
|
||||
public btnFeedback = false;
|
||||
|
||||
constructor() {
|
||||
this.sendMessage({ type: 'ready' });
|
||||
}
|
||||
|
||||
initialTheme$ = this.route.queryParamMap.pipe(
|
||||
map((params) => params.get('theme')),
|
||||
|
@ -36,14 +51,95 @@ export class AppComponent {
|
|||
this.messages$.pipe(
|
||||
filter((event) => event.data.type === 'theme'),
|
||||
map((event) => {
|
||||
return event.data.content;
|
||||
return (event.data as ThemePluginEvent).content;
|
||||
})
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
previewList = toSignal(
|
||||
this.messages$.pipe(
|
||||
filter(
|
||||
(event) => event.data.type === 'init' || event.data.type === 'selection'
|
||||
),
|
||||
map((event) => {
|
||||
if (event.data.type === 'init') {
|
||||
return event.data.content.selection;
|
||||
} else if (event.data.type === 'selection') {
|
||||
return event.data.content.selection;
|
||||
}
|
||||
|
||||
return [];
|
||||
})
|
||||
),
|
||||
{
|
||||
initialValue: [],
|
||||
}
|
||||
);
|
||||
|
||||
public updateText() {
|
||||
this.sendMessage({ type: 'replace-text', content: this.textToReplace });
|
||||
if (this.tab === 'replace') {
|
||||
this.sendMessage({ type: 'replace-text', content: this.textToReplace });
|
||||
this.handleBtnFeedback();
|
||||
this.searchElement.nativeElement.focus();
|
||||
this.resetForm();
|
||||
} else {
|
||||
const elementsToUpdate = this.previewList().map((item) => {
|
||||
return {
|
||||
current: item.name,
|
||||
new: this.resultAddText(item),
|
||||
};
|
||||
});
|
||||
this.sendMessage({ type: 'add-text', content: elementsToUpdate });
|
||||
this.handleBtnFeedback();
|
||||
this.addElement.nativeElement.focus();
|
||||
this.resetForm();
|
||||
}
|
||||
}
|
||||
|
||||
public previewReplace() {
|
||||
this.sendMessage({
|
||||
type: 'preview-replace-text',
|
||||
content: this.textToReplace,
|
||||
});
|
||||
}
|
||||
|
||||
public resultReplaceText(text: string) {
|
||||
return text.replace(this.textToReplace.search, this.textToReplace.replace);
|
||||
}
|
||||
|
||||
public highlightMatch(text: string) {
|
||||
if (this.textToReplace.search) {
|
||||
return text.replace(
|
||||
this.textToReplace.search,
|
||||
`<span class="highlight">${this.textToReplace.search}</span>`
|
||||
);
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
public selectTab(tab: 'add' | 'replace') {
|
||||
this.tab = tab;
|
||||
this.resetForm();
|
||||
}
|
||||
|
||||
public resetForm() {
|
||||
this.textToReplace.search = '';
|
||||
this.textToReplace.replace = '';
|
||||
this.addText = '[Original layer name]';
|
||||
this.sendMessage({ type: 'ready' });
|
||||
}
|
||||
|
||||
public handleBtnFeedback() {
|
||||
this.btnFeedback = true;
|
||||
setTimeout(() => {
|
||||
this.btnFeedback = false;
|
||||
}, 750);
|
||||
}
|
||||
|
||||
public resultAddText(shape: PenpotShape) {
|
||||
return this.addText.replace('[Original layer name]', shape.name);
|
||||
}
|
||||
|
||||
private sendMessage(message: PluginMessageEvent): void {
|
||||
|
|
|
@ -1,7 +1,20 @@
|
|||
import { PenpotShape } from '@penpot/plugin-types';
|
||||
|
||||
export interface ReadyPluginEvent {
|
||||
type: 'ready';
|
||||
}
|
||||
export interface InitPluginEvent {
|
||||
type: 'init';
|
||||
content: {
|
||||
theme: string;
|
||||
selection: PenpotShape[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface SelectionPluginEvent {
|
||||
type: 'selection';
|
||||
content: {
|
||||
selection: PenpotShape[];
|
||||
};
|
||||
}
|
||||
export interface ThemePluginEvent {
|
||||
|
@ -14,12 +27,31 @@ export interface ReplaceTextPluginEvent {
|
|||
content: ReplaceText;
|
||||
}
|
||||
|
||||
export interface AddTextPluginEvent {
|
||||
type: 'add-text';
|
||||
content: AddText[];
|
||||
}
|
||||
|
||||
export interface PreviewReplaceTextPluginEvent {
|
||||
type: 'preview-replace-text';
|
||||
content: ReplaceText;
|
||||
}
|
||||
|
||||
export type PluginMessageEvent =
|
||||
| ReadyPluginEvent
|
||||
| InitPluginEvent
|
||||
| SelectionPluginEvent
|
||||
| ThemePluginEvent
|
||||
| ReplaceTextPluginEvent;
|
||||
| ReplaceTextPluginEvent
|
||||
| AddTextPluginEvent
|
||||
| PreviewReplaceTextPluginEvent;
|
||||
|
||||
export interface ReplaceText {
|
||||
search: string;
|
||||
replace: string;
|
||||
}
|
||||
|
||||
export interface AddText {
|
||||
current: string;
|
||||
new: string;
|
||||
}
|
||||
|
|
1
apps/rename-layers-plugin/src/assets/actions-tick.svg
Normal file
1
apps/rename-layers-plugin/src/assets/actions-tick.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg width="16" xmlns="http://www.w3.org/2000/svg" height="16" fill="none"><g data-testid="Icons / Actions / Tick M"><g class="fills"><rect rx="0" ry="0" width="16" height="16" class="frame-background"/></g><g data-testid="svg-path" class="frame-children"><path d="M13.333 4 6 11.333 2.667 8" class="fills"/><g class="strokes"><path d="M13.333 4 6 11.333 2.667 8" style="fill: none; stroke-width: 1; stroke: rgb(143, 157, 163); stroke-opacity: 1; stroke-linecap: round;" class="stroke-shape"/></g></g></g></svg>
|
After Width: | Height: | Size: 511 B |
|
@ -1,25 +1,78 @@
|
|||
import { PluginMessageEvent } from './app/model';
|
||||
|
||||
penpot.ui.open('Plugin rename layers', `?theme=${penpot.getTheme()}`, {
|
||||
width: 235,
|
||||
height: 245,
|
||||
penpot.ui.open('RENAME LAYER PLUGIN', `?theme=${penpot.getTheme()}`, {
|
||||
width: 290,
|
||||
height: 550,
|
||||
});
|
||||
|
||||
penpot.on('themechange', (theme) => {
|
||||
penpot.ui.sendMessage({ type: 'theme', content: theme });
|
||||
});
|
||||
|
||||
penpot.on('selectionchange', () => {
|
||||
resetSelection();
|
||||
});
|
||||
|
||||
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)
|
||||
);
|
||||
if (message.type === 'ready') {
|
||||
resetSelection();
|
||||
} else if (message.type === 'replace-text') {
|
||||
const shapes = getShapes();
|
||||
const shapesToUpdate = shapes?.filter((shape) => {
|
||||
return shape.name.includes(message.content.search);
|
||||
});
|
||||
shapesToUpdate?.forEach((shape) => {
|
||||
shape.name = shape.name.replace(
|
||||
message.content.current,
|
||||
message.content.new
|
||||
// eslint-disable-next-line
|
||||
message.content.search,
|
||||
message.content.replace
|
||||
);
|
||||
});
|
||||
updateReplaceTextPreview(message.content.search);
|
||||
} else if (message.type === 'preview-replace-text') {
|
||||
updateReplaceTextPreview(message.content.search);
|
||||
} else if (message.type === 'add-text') {
|
||||
const currentNames = message.content.map((shape) => shape.current);
|
||||
const shapes = getShapes();
|
||||
const shapesToUpdate = shapes?.filter((shape) =>
|
||||
currentNames.includes(shape.name)
|
||||
);
|
||||
shapesToUpdate?.forEach((shape) => {
|
||||
const newText = message.content.find((it) => it.current === shape.name);
|
||||
return (shape.name = newText?.new ?? shape.name);
|
||||
});
|
||||
resetSelection();
|
||||
}
|
||||
});
|
||||
|
||||
function getShapes() {
|
||||
return penpot.getSelectedShapes().length
|
||||
? penpot.getSelectedShapes()
|
||||
: penpot.getPage()?.findShapes();
|
||||
}
|
||||
|
||||
function resetSelection() {
|
||||
penpot.ui.sendMessage({
|
||||
type: 'selection',
|
||||
content: {
|
||||
selection: getShapes(),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function updateReplaceTextPreview(search: string) {
|
||||
if (search) {
|
||||
const shapes = getShapes();
|
||||
const shapesToUpdate = shapes?.filter((shape) => {
|
||||
return shape.name.includes(search);
|
||||
});
|
||||
penpot.ui.sendMessage({
|
||||
type: 'selection',
|
||||
content: {
|
||||
selection: shapesToUpdate,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
resetSelection();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
.icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
block-size: var(--spacing-16);
|
||||
cursor: default;
|
||||
font-family: var(--body-font);
|
||||
font-size: var(--font-size-medium);
|
||||
inline-size: var(--spacing-16);
|
||||
justify-content: center;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
block-size: var(--spacing-16);
|
||||
cursor: default;
|
||||
font-family: var(--body-font);
|
||||
font-size: var(--font-size-medium);
|
||||
inline-size: var(--spacing-16);
|
||||
justify-content: center;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.icon-arrow-bottom {
|
||||
background-image: url('../icons/arrow-bottom.svg');
|
||||
}
|
||||
|
||||
background-image: url('../icons/arrow-bottom.svg');
|
||||
}
|
||||
|
||||
.icon-arrow-left {
|
||||
background-image: url('../icons/arrow-left.svg');
|
||||
}
|
||||
|
@ -26,6 +26,10 @@
|
|||
background-image: url('../icons/arrow-top.svg');
|
||||
}
|
||||
|
||||
.icon-arrow-right-full {
|
||||
background-image: url('../icons/arrow-right-full.svg');
|
||||
}
|
||||
|
||||
.icon-close {
|
||||
background-image: url('../icons/actions-close.svg');
|
||||
}
|
||||
|
@ -88,4 +92,4 @@
|
|||
|
||||
.icon-download {
|
||||
background-image: url('../icons/app-download.svg');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,3 +72,18 @@ ul {
|
|||
background-color: var(--background-primary);
|
||||
color: var(--foreground-secondary);
|
||||
}
|
||||
|
||||
/* ===== Scrollbar CSS ===== */
|
||||
::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
-webkit-box-shadow: inset 0 0 6px transparent;
|
||||
border-radius: var(--spacing-8);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: var(--spacing-8);
|
||||
-webkit-box-shadow: inset 0 0 6px #aab5ba;
|
||||
}
|
||||
|
|
1
libs/plugins-styles/src/lib/icons/arrow-right-full.svg
Normal file
1
libs/plugins-styles/src/lib/icons/arrow-right-full.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg width="15" xmlns="http://www.w3.org/2000/svg" height="15" fill="none"><g data-testid="arrow-right"><defs><clipPath id="a" class="frame-clip frame-clip-def"><rect rx="0" ry="0" width="15" height="15"/></clipPath></defs><g clip-path="url(#a)"><g class="fills"><rect rx="0" ry="0" width="24" height="24" class="frame-background"/></g><g class="frame-children"><g data-testid="svg-path"><path d="M3.125 7.5h8.75" style="fill: none;" class="fills"/><g stroke-linecap="round" stroke-linejoin="round" class="strokes"><path d="M3.125 7.5h8.75" style="fill: none; stroke-width: 1; stroke: rgb(143, 157, 163); stroke-opacity: 1;" class="stroke-shape"/></g></g><g data-testid="svg-path"><path d="M7.5 3.125 11.875 7.5 7.5 11.875" style="fill: none;" class="fills"/><g stroke-linecap="round" stroke-linejoin="round" class="strokes"><path d="M7.5 3.125 11.875 7.5 7.5 11.875" style="fill: none; stroke-width: 1; stroke: rgb(143, 157, 163); stroke-opacity: 1;" class="stroke-shape"/></g></g></g></g></g></svg>
|
After Width: | Height: | Size: 1,000 B |
Loading…
Reference in a new issue