RED-6774 - added redact dialog

This commit is contained in:
Valentin Mihai 2023-06-16 22:06:52 +03:00
parent 532ee9fa06
commit 0350eb4124
20 changed files with 552 additions and 19 deletions

View File

@ -2,6 +2,8 @@ import { IManualRedactionEntry } from '@red/domain';
export const ManualRedactionEntryTypes = {
DICTIONARY: 'DICTIONARY',
REDACT: 'REDACT',
HINT: 'HINT',
REDACTION: 'REDACTION',
FALSE_POSITIVE: 'FALSE_POSITIVE',
} as const;

View File

@ -0,0 +1,87 @@
<section class="dialog">
<form (submit)="save()" [formGroup]="form">
<div [translate]="'redact-text.dialog.title'" class="dialog-header heading-l"></div>
<div class="dialog-content">
<div class="iqser-input-group w-450">
<label [translate]="'redact-text.dialog.content.selected-text'"></label>
{{ form.get('selectedText').value }}
</div>
<iqser-details-radio [options]="options" formControlName="option"></iqser-details-radio>
<ng-container *deny="roles.getRss; if: !dictionaryRequest">
<div class="iqser-input-group required w-450">
<label [translate]="'redact-text.dialog.content.reason'"></label>
<mat-form-field>
<mat-select
[placeholder]="'redact-text.dialog.content.reason-placeholder' | translate"
class="full-width"
formControlName="reason"
>
<mat-option
*ngFor="let option of legalOptions"
[matTooltip]="option.description"
[value]="option"
matTooltipPosition="after"
>
{{ option.label }}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div class="iqser-input-group w-450">
<label [translate]="'redact-text.dialog.content.legal-basis'"></label>
<input [value]="form.get('reason').value?.legalBasis" disabled type="text" />
</div>
</ng-container>
<ng-container *deny="roles.getRss; if: dictionaryRequest">
<div class="iqser-input-group required w-450">
<label [translate]="'redact-text.dialog.content.type'"></label>
<mat-form-field>
<mat-select formControlName="dictionary" [placeholder]="'redact-text.dialog.content.type-placeholder' | translate">
<mat-select-trigger>{{ displayedDictionaryLabel }}</mat-select-trigger>
<mat-option
*ngFor="let dictionary of dictionaries"
[matTooltip]="dictionary.description"
[value]="dictionary.type"
matTooltipPosition="after"
>
<span> {{ dictionary.label }} </span>
</mat-option>
</mat-select>
</mat-form-field>
</div>
</ng-container>
<div class="iqser-input-group w-450">
<label [translate]="'redact-text.dialog.content.comment'"></label>
<textarea
formControlName="comment"
iqserHasScrollbar
name="comment"
rows="4"
type="text"
[placeholder]="'redact-text.dialog.content.comment-placeholder' | translate"
></textarea>
</div>
</div>
<div class="dialog-actions">
<iqser-icon-button
[disabled]="disabled"
[label]="'redact-text.dialog.actions.save' | translate"
[submit]="true"
[type]="iconButtonTypes.primary"
>
</iqser-icon-button>
<div class="all-caps-label cancel" mat-dialog-close [translate]="'redact-text.dialog.actions.cancel'"></div>
</div>
</form>
<iqser-circle-button (action)="close()" class="dialog-close" icon="iqser:close"></iqser-circle-button>
</section>

View File

@ -0,0 +1,4 @@
label {
font-weight: bold;
padding-bottom: 8px;
}

View File

@ -0,0 +1,163 @@
import { Component, Inject, OnInit } from '@angular/core';
import { BaseDialogComponent, DetailsRadioOption, IqserPermissionsService } from '@iqser/common-ui';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Dictionary, Dossier, File, IAddRedactionRequest } from '@red/domain';
import { UntypedFormGroup, Validators } from '@angular/forms';
import { Roles } from '@users/roles';
import { firstValueFrom } from 'rxjs';
import { JustificationsService } from '@services/entity-services/justifications.service';
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
import { LegalBasisOption } from '../manual-redaction-dialog/manual-annotation-dialog.component';
import { DictionaryService } from '@services/entity-services/dictionary.service';
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
import { ManualRedactionEntryType, ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry.wrapper';
import { redactTextTranslations } from '@translations/redact-text-translations';
import { RedactTextOption, RedactTextOptions } from './redact-text-options';
const PIN_ICON = 'red:push-pin';
const FOLDER_ICON = 'red:folder';
@Component({
templateUrl: './redact-text-dialog.component.html',
styleUrls: ['./redact-text-dialog.component.scss'],
})
export class RedactTextDialogComponent extends BaseDialogComponent implements OnInit {
readonly roles = Roles;
readonly options: DetailsRadioOption<RedactTextOption>[];
dictionaryRequest = false;
legalOptions: LegalBasisOption[] = [];
dictionaries: Dictionary[] = [];
readonly #translations = redactTextTranslations;
readonly #dossier: Dossier;
readonly #type: ManualRedactionEntryType;
constructor(
private _justificationsService: JustificationsService,
private _activeDossiersService: ActiveDossiersService,
private _dictionaryService: DictionaryService,
private _iqserPermissionsService: IqserPermissionsService,
protected readonly _dialogRef: MatDialogRef<RedactTextDialogComponent>,
@Inject(MAT_DIALOG_DATA) readonly data: { manualRedactionEntryWrapper: ManualRedactionEntryWrapper; dossierId: string; file: File },
) {
super(_dialogRef);
this.#dossier = _activeDossiersService.find(this.data.dossierId);
this.#type = this.data.manualRedactionEntryWrapper.type;
this.options = this.#options();
this.form = this.#getForm();
this.initialFormValue = this.form.getRawValue();
}
async ngOnInit(): Promise<void> {
this.dictionaries = await this._dictionaryService.getDictionariesOptions(this.#dossier.dossierTemplateId, this.#dossier.id);
const data = await firstValueFrom(this._justificationsService.getForDossierTemplate(this.#dossier.dossierTemplateId));
this.legalOptions = data.map(lbm => ({
legalBasis: lbm.reason,
description: lbm.description,
label: lbm.name,
}));
this.#selectReason();
this.#formatSelectedTextValue();
}
get displayedDictionaryLabel() {
const dictType = this.form.get('dictionary').value;
if (dictType) {
return this.dictionaries.find(d => d.type === dictType).label;
}
return null;
}
save(): void {
this.#enhanceManualRedaction(this.data.manualRedactionEntryWrapper.manualRedactionEntry);
try {
const annotation = this.data.manualRedactionEntryWrapper.manualRedactionEntry;
this._dialogRef.close({
annotation,
dictionary: this.dictionaries.find(d => d.type === this.form.get('dictionary').value),
});
} catch (e) {
this._toaster.error(_('manual-annotation.dialog.error'));
}
}
// toggleType() {
// console.log('test');
// this.dictionaryRequest = this.form.get('option').value === RedactTextOptions.IN_DOSSIER;
// }
#getForm(): UntypedFormGroup {
return this._formBuilder.group({
selectedText: this.data?.manualRedactionEntryWrapper?.manualRedactionEntry?.value,
reason: [null],
comment: [null],
dictionary: [null, Validators.required],
classification: '',
multiplePages: '',
option: [this.options[0], Validators.required],
});
}
#formatSelectedTextValue(): void {
this.data.manualRedactionEntryWrapper.manualRedactionEntry.value =
this.data.manualRedactionEntryWrapper.manualRedactionEntry.value.replace(
// eslint-disable-next-line no-control-regex,max-len
/([^\s\d-]{2,})[-\u00AD]\u000D\u000A|[\u000A\u000B\u000C\u000D\u0085\u2028\u2029]/gi,
'$1',
);
}
#selectReason() {
if (this.legalOptions.length === 1) {
this.form.get('reason').setValue(this.legalOptions[0]);
}
}
#enhanceManualRedaction(addRedactionRequest: IAddRedactionRequest) {
const legalOption: LegalBasisOption = this.form.get('reason').value;
addRedactionRequest.type = this.form.get('dictionary').value;
if (legalOption) {
addRedactionRequest.reason = legalOption.description;
addRedactionRequest.legalBasis = legalOption.legalBasis;
}
if (this._iqserPermissionsService.has(Roles.getRss)) {
const selectedType = this.dictionaries.find(d => d.type === addRedactionRequest.type);
addRedactionRequest.addToDictionary = selectedType.hasDictionary;
} else {
addRedactionRequest.addToDictionary = this.dictionaryRequest && addRedactionRequest.type !== 'dossier_redaction';
addRedactionRequest.addToDossierDictionary = this.dictionaryRequest && addRedactionRequest.type === 'dossier_redaction';
}
if (!addRedactionRequest.reason) {
addRedactionRequest.reason = 'Dictionary Request';
}
const commentValue = this.form.get('comment').value;
addRedactionRequest.comment = commentValue ? { text: commentValue } : null;
addRedactionRequest.section = this.form.get('section').value;
addRedactionRequest.value = addRedactionRequest.rectangle
? this.form.get('classification').value
: this.form.get('selectedText').value;
}
#options() {
return [
{
label: this.#translations[this.#type].onlyHere.label,
description: this.#translations[this.#type].onlyHere.description,
icon: PIN_ICON,
value: RedactTextOptions.ONLY_HERE,
},
{
label: this.#translations[this.#type].inDossier.label,
description: this.#translations[this.#type].inDossier.description,
icon: FOLDER_ICON,
value: RedactTextOptions.IN_DOSSIER,
},
];
}
}

View File

@ -0,0 +1,6 @@
export const RedactTextOptions = {
ONLY_HERE: 'ONLY_HERE',
IN_DOSSIER: 'IN_DOSSIER',
} as const;
export type RedactTextOption = keyof typeof RedactTextOptions;

View File

@ -40,7 +40,7 @@ import { ManualRedactionEntryWrapper } from '@models/file/manual-redaction-entry
import { AnnotationWrapper } from '@models/file/annotation.wrapper';
import { AnnotationDrawService } from '../pdf-viewer/services/annotation-draw.service';
import { AnnotationProcessingService } from './services/annotation-processing.service';
import { Dictionary, File, ViewModes } from '@red/domain';
import { Dictionary, File, IManualRedactionEntry, ViewModes } from '@red/domain';
import { PermissionsService } from '@services/permissions.service';
import { combineLatest, firstValueFrom, of, pairwise } from 'rxjs';
import { PreferencesKeys, UserPreferenceService } from '@users/user-preference.service';
@ -165,15 +165,15 @@ export class FilePreviewScreenComponent
});
effect(() => {
const selectedText = this._documentViewer.selectedText();
const canPerformActions = this.pdfProxyService.canPerformActions();
const isCurrentPageExcluded = this.state.file().isPageExcluded(this.pdf.currentPage());
if ((selectedText.length > 2 || this._isJapaneseString(selectedText)) && canPerformActions && !isCurrentPageExcluded) {
this.pdf.enable(textActions);
} else {
this.pdf.disable(textActions);
}
// const selectedText = this._documentViewer.selectedText();
// const canPerformActions = this.pdfProxyService.canPerformActions();
// const isCurrentPageExcluded = this.state.file().isPageExcluded(this.pdf.currentPage());
//
// if ((selectedText.length > 2 || this._isJapaneseString(selectedText)) && canPerformActions && !isCurrentPageExcluded) {
// this.pdf.enable(textActions);
// } else {
// this.pdf.disable(textActions);
// }
});
}
@ -364,6 +364,34 @@ export class FilePreviewScreenComponent
});
}
openRedactTextDialog(manualRedactionEntryWrapper: ManualRedactionEntryWrapper) {
return this._ngZone.run(() => {
const file = this.state.file();
manualRedactionEntryWrapper.manualRedactionEntry.value = 'This is selected text';
this._dialogService.openDialog(
'redactText',
{ manualRedactionEntryWrapper, dossierId: this.dossierId, file },
(result: { annotation: IManualRedactionEntry; dictionary?: Dictionary }) => {
const selectedAnnotations = this._annotationManager.selected;
if (selectedAnnotations.length > 0) {
this._annotationManager.delete([selectedAnnotations[0].Id]);
}
const add$ = this._manualRedactionService.addAnnotation(
[result.annotation],
this.dossierId,
this.fileId,
result.dictionary?.label,
);
const addAndReload$ = add$.pipe(switchMap(() => this._filesService.reload(this.dossierId, file)));
return firstValueFrom(addAndReload$.pipe(catchError(() => of(undefined))));
},
);
});
}
toggleFullScreen() {
this.fullScreen = !this.fullScreen;
if (this.fullScreen) {
@ -623,6 +651,10 @@ export class FilePreviewScreenComponent
this.openManualAnnotationDialog($event);
});
this.addActiveScreenSubscription = this.pdfProxyService.redactTextRequested$.subscribe($event => {
this.openRedactTextDialog($event);
});
this.addActiveScreenSubscription = this.pdfProxyService.pageChanged$.subscribe(page =>
this._ngZone.run(() => this.#updateQueryParamsPage(page)),
);

View File

@ -65,6 +65,7 @@ import { PagesComponent } from './components/pages/pages.component';
import { SharedModule } from '@shared/shared.module';
import { SharedDossiersModule } from '../shared-dossiers/shared-dossiers.module';
import { FalsePositiveDialogComponent } from './dialogs/false-positive-dialog/false-positive-dialog.component';
import { RedactTextDialogComponent } from './dialogs/redact-text-dialog/redact-text-dialog.component';
const routes: IqserRoutes = [
{
@ -89,6 +90,7 @@ const dialogs = [
ImportRedactionsDialogComponent,
RssDialogComponent,
FalsePositiveDialogComponent,
RedactTextDialogComponent,
];
const components = [

View File

@ -11,6 +11,7 @@ import { ResizeAnnotationDialogComponent } from '../dialogs/resize-annotation-di
import { HighlightActionDialogComponent } from '../dialogs/highlight-action-dialog/highlight-action-dialog.component';
import { RssDialogComponent } from '../dialogs/rss-dialog/rss-dialog.component';
import { FalsePositiveDialogComponent } from '../dialogs/false-positive-dialog/false-positive-dialog.component';
import { RedactTextDialogComponent } from '../dialogs/redact-text-dialog/redact-text-dialog.component';
type DialogType =
| 'confirm'
@ -23,7 +24,8 @@ type DialogType =
| 'forceAnnotation'
| 'manualAnnotation'
| 'highlightAction'
| 'falsePositive';
| 'falsePositive'
| 'redactText';
@Injectable()
export class FilePreviewDialogService extends DialogService<DialogType> {
@ -65,6 +67,9 @@ export class FilePreviewDialogService extends DialogService<DialogType> {
falsePositive: {
component: FalsePositiveDialogComponent,
},
redactText: {
component: RedactTextDialogComponent,
},
};
constructor(protected readonly _dialog: MatDialog) {

View File

@ -42,6 +42,7 @@ import Quad = Core.Math.Quad;
export class PdfProxyService {
readonly annotationSelected$ = this.#annotationSelected$;
readonly manualAnnotationRequested$ = new Subject<ManualRedactionEntryWrapper>();
readonly redactTextRequested$ = new Subject<ManualRedactionEntryWrapper>();
readonly pageChanged$ = this._pdf.pageChanged$.pipe(
tap(() => this._handleCustomActions()),
tap(() => this._pdf.resetAnnotationActions()),
@ -60,6 +61,7 @@ export class PdfProxyService {
? this._convertPath('/assets/icons/general/pdftron-action-add-component.svg')
: this._convertPath('/assets/icons/general/pdftron-action-add-redaction.svg');
readonly #addDictIcon = this._convertPath('/assets/icons/general/pdftron-action-add-dict.svg');
readonly #addHintIcon = this._convertPath('/assets/icons/general/pdftron-action-add-hint.svg');
constructor(
private readonly _translateService: TranslateService,
@ -157,6 +159,19 @@ export class PdfProxyService {
}
if (this._iqserPermissionsService.has(Roles.redactions.write) || this._iqserPermissionsService.has(Roles.redactions.request)) {
popups.push({
type: 'actionButton',
dataElement: TextPopups.REDACT_TEXT,
img: this.#addRedactionIcon,
onClick: () => this._ngZone.run(() => this._redactText(ManualRedactionEntryTypes.REDACT)),
});
popups.push({
type: 'actionButton',
dataElement: TextPopups.ADD_HINT,
img: this.#addHintIcon,
onClick: () => this._ngZone.run(() => this._redactText(ManualRedactionEntryTypes.HINT)),
});
popups.push({
type: 'actionButton',
dataElement: TextPopups.ADD_REDACTION,
@ -189,6 +204,13 @@ export class PdfProxyService {
this.manualAnnotationRequested$.next({ manualRedactionEntry, type });
}
private _redactText(type: ManualRedactionEntryType) {
const selectedQuads: Record<string, Quad[]> = this._pdf.documentViewer.getSelectedTextQuads();
const text = this._documentViewer.selectedText();
const manualRedactionEntry = this._getManualRedaction(selectedQuads, text, true);
this.redactTextRequested$.next({ manualRedactionEntry, type });
}
private _handleCustomActions() {
const isCurrentPageExcluded = this._state.file().isPageExcluded(this._pdf.currentPage());
@ -208,7 +230,7 @@ export class PdfProxyService {
this._pdf.enable(TEXT_POPUPS_TO_TOGGLE);
this._viewerHeaderService.enable(HEADER_ITEMS_TO_TOGGLE);
if (this._documentViewer.selectedText().length > 2) {
if (this._documentViewer.selectedText()?.length > 2) {
this._pdf.enable([TextPopups.ADD_DICTIONARY, TextPopups.ADD_FALSE_POSITIVE]);
}
} else {

View File

@ -29,6 +29,8 @@ export const HeaderElements = {
export type HeaderElementType = ValuesOf<typeof HeaderElements>;
export const TextPopups = {
REDACT_TEXT: 'redact-text',
ADD_HINT: 'add-hint',
ADD_REDACTION: 'add-redaction',
ADD_DICTIONARY: 'add-dictionary',
ADD_RECTANGLE: 'add-rectangle',

View File

@ -63,6 +63,7 @@ export class IconsModule {
'padding-top-bottom',
'page',
'preview',
'push-pin',
'put-back',
'read-only',
'ready-for-approval',

View File

@ -144,12 +144,12 @@ export class REDDocumentViewer {
}
#listenForDocEvents() {
this.#document.addEventListener('textSelected', ([, selectedText, pageNumber]: [Quad, string, number]) => {
this.#ngZone.run(() => {
this.#disableTextPopupIfCompareMode(pageNumber);
this.#selectedText.set(selectedText);
});
});
// this.#document.addEventListener('textSelected', ([, selectedText, pageNumber]: [Quad, string, number]) => {
// this.#ngZone.run(() => {
// this.#disableTextPopupIfCompareMode(pageNumber);
// this.#selectedText.set(selectedText);
// });
// });
this.#document.addEventListener('pageComplete', event => {
this.#ngZone.run(() => {

View File

@ -0,0 +1,29 @@
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
interface Option {
label: string;
description: string;
}
export const redactTextTranslations: Record<'REDACT' | 'HINT', Record<'onlyHere' | 'inDossier', Option>> = {
REDACT: {
onlyHere: {
label: _('redact-text.dialog.content.options.redact.only-here.label'),
description: _('redact-text.dialog.content.options.redact.only-here.description'),
},
inDossier: {
label: _('redact-text.dialog.content.options.redact.in-dossier.label'),
description: _('redact-text.dialog.content.options.redact.in-dossier.description'),
},
},
HINT: {
onlyHere: {
label: _('redact-text.dialog.content.options.hint.only-here.label'),
description: _('redact-text.dialog.content.options.hint.only-here.description'),
},
inDossier: {
label: _('redact-text.dialog.content.options.hint.in-dossier.label'),
description: _('redact-text.dialog.content.options.hint.in-dossier.description'),
},
},
} as const;

View File

@ -1900,6 +1900,47 @@
},
"header": "Bildtypen bearbeiten"
},
"redact-text": {
"dialog": {
"actions": {
"cancel": "",
"save": ""
},
"content": {
"comment": "",
"comment-placeholder": "",
"legal-basis": "",
"options": {
"hint": {
"in-dossier": {
"description": "",
"label": ""
},
"only-here": {
"description": "",
"label": ""
}
},
"redact": {
"in-dossier": {
"description": "",
"label": ""
},
"only-here": {
"description": "",
"label": ""
}
}
},
"reason": "",
"reason-placeholder": "",
"selected-text": "",
"type": "",
"type-placeholder": ""
},
"title": ""
}
},
"redaction-abbreviation": "R",
"references": "",
"remove-annotations-dialog": {

View File

@ -1900,6 +1900,47 @@
},
"header": "Edit Image Type"
},
"redact-text": {
"dialog": {
"actions": {
"cancel": "Cancel",
"save": "Save"
},
"content": {
"comment": "Comment",
"comment-placeholder": "Add remarks or mentions ...",
"legal-basis": "Legal Basis",
"options": {
"hint": {
"in-dossier": {
"description": "Add hint in every document in Dossier Alpha.",
"label": "Add hint in dossier"
},
"only-here": {
"description": "Add hint only at this position in this document.",
"label": "Add hint only here"
}
},
"redact": {
"in-dossier": {
"description": "Add redaction in every document in Dossier Alpha.",
"label": "Redact in dossier"
},
"only-here": {
"description": "Add redaction only at this position in this document.",
"label": "Redact only here"
}
}
},
"reason": "Reason",
"reason-placeholder": "Select a reason ...",
"selected-text": "Selected text:",
"type": "Type",
"type-placeholder": "Select type ..."
},
"title": "Redact text"
}
},
"redaction-abbreviation": "R",
"references": "{count} {count, plural, one{reference} other{references}}",
"remove-annotations-dialog": {

View File

@ -1900,6 +1900,47 @@
},
"header": "Bildtypen bearbeiten"
},
"redact-text": {
"dialog": {
"actions": {
"cancel": "",
"save": ""
},
"content": {
"comment": "",
"comment-placeholder": "",
"legal-basis": "",
"options": {
"hint": {
"in-dossier": {
"description": "",
"label": ""
},
"only-here": {
"description": "",
"label": ""
}
},
"redact": {
"in-dossier": {
"description": "",
"label": ""
},
"only-here": {
"description": "",
"label": ""
}
}
},
"reason": "",
"reason-placeholder": "",
"selected-text": "",
"type": "",
"type-placeholder": ""
},
"title": ""
}
},
"redaction-abbreviation": "C",
"references": "",
"remove-annotations-dialog": {

View File

@ -1900,6 +1900,47 @@
},
"header": "Edit Image Type"
},
"redact-text": {
"dialog": {
"actions": {
"cancel": "Cancel",
"save": "Save"
},
"content": {
"comment": "Comment",
"comment-placeholder": "Add remarks or mentions ...",
"legal-basis": "Legal Basis",
"options": {
"hint": {
"in-dossier": {
"description": "Add hint in every document in Dossier Alpha.",
"label": "Add hint in dossier"
},
"only-here": {
"description": "Add hint only at this position in this document.",
"label": "Add hint only here"
}
},
"redact": {
"in-dossier": {
"description": "Add redaction in every document in Dossier Alpha.",
"label": "Redact in dossier"
},
"only-here": {
"description": "Add redaction only at this position in this document.",
"label": "Redact only here"
}
}
},
"reason": "Reason",
"reason-placeholder": "Select a reason ...",
"selected-text": "Selected text:",
"type": "Type",
"type-placeholder": "Select type ..."
},
"title": "Redact text"
}
},
"redaction-abbreviation": "C",
"references": "{count} {count, plural, one{reference} other{references}}",
"remove-annotations-dialog": {

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg height="16px" version="1.1" viewBox="0 0 16 16" width="16px"
xmlns="http://www.w3.org/2000/svg">
<g fill="none" fill-rule="evenodd" id="Redacted" stroke="none" stroke-width="1">
<circle fill="#94989f" cx="8" cy="8" r="8" />
<text fill="#FFFFFF" font-family="Arial, Helvetica, sans-serif" font-size="11" font-weight="500"
id="H">
<tspan x="4" y="12">H</tspan>
</text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 486 B

View File

@ -3,7 +3,7 @@
xmlns="http://www.w3.org/2000/svg">
<g fill="none" fill-rule="evenodd" id="Redacted" stroke="none" stroke-width="1">
<rect fill="#283241" height="16" id="Rectangle" width="16" x="0" y="0"></rect>
<text fill="#FFFFFF" font-family="Inter-SemiBold, Inter" font-size="11" font-weight="500"
<text fill="#FFFFFF" font-family="Arial, Helvetica, sans-serif" font-size="11" font-weight="500"
id="H">
<tspan x="4.421875" y="12">R</tspan>
</text>

Before

Width:  |  Height:  |  Size: 519 B

After

Width:  |  Height:  |  Size: 526 B

View File

@ -0,0 +1,3 @@
<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M75.5002 10L79.4998 55H85V65H55V100H45V65H15V55H20.5002L24.4998 10H20V0H80V10H75.5002ZM34.4995 10L30.4998 55H69.4991L65.4995 10H34.4995Z" fill="#283241"/>
</svg>

After

Width:  |  Height:  |  Size: 311 B