mirror of
https://github.com/booklore-app/booklore.git
synced 2026-04-17 10:06:56 -04:00
This commit is contained in:
@@ -244,6 +244,17 @@ export class LibraryShelfMenuService {
|
||||
this.dialogLauncherService.openMagicShelfEditDialog((entity?.id as number));
|
||||
}
|
||||
},
|
||||
{
|
||||
label: this.t.translate('book.shelfMenuService.magicShelf.exportJson'),
|
||||
icon: 'pi pi-copy',
|
||||
command: () => {
|
||||
if (entity?.filterJson) {
|
||||
navigator.clipboard.writeText(entity.filterJson).then(() => {
|
||||
this.messageService.add({severity: 'success', summary: this.t.translate('common.success'), detail: this.t.translate('book.shelfMenuService.toast.magicShelfJsonCopiedDetail')});
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
separator: true
|
||||
},
|
||||
|
||||
@@ -72,6 +72,16 @@
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (showImportPanel) {
|
||||
<div class="import-panel">
|
||||
<textarea pTextarea [(ngModel)]="importJson" [ngModelOptions]="{standalone: true}" rows="10" [placeholder]="t('importJson.placeholder')" class="import-textarea"></textarea>
|
||||
<div class="import-actions">
|
||||
<p-button [label]="t('actions.cancel')" severity="secondary" [outlined]="true" (onClick)="toggleImportPanel()" size="small"/>
|
||||
<p-button [label]="t('importJson.apply')" icon="pi pi-check" severity="success" (onClick)="applyImportedJson()" size="small"/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="rules-container">
|
||||
<ng-container *ngTemplateOutlet="groupTemplate; context: { group: form.get('group') }"></ng-container>
|
||||
</div>
|
||||
@@ -79,6 +89,16 @@
|
||||
</div>
|
||||
|
||||
<div class="dialog-footer">
|
||||
@if (!editMode) {
|
||||
<p-button
|
||||
icon="pi pi-file-import"
|
||||
[label]="t('importJson.buttonLabel')"
|
||||
[outlined]="true"
|
||||
severity="info"
|
||||
size="small"
|
||||
(onClick)="toggleImportPanel()"
|
||||
/>
|
||||
}
|
||||
<div class="footer-actions">
|
||||
<p-button
|
||||
[label]="t('actions.cancel')"
|
||||
|
||||
@@ -15,6 +15,28 @@
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.import-panel {
|
||||
margin-bottom: 0.75rem;
|
||||
padding: 1rem;
|
||||
background: var(--overlay-background);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
|
||||
.import-textarea {
|
||||
width: 100%;
|
||||
font-family: monospace;
|
||||
font-size: 0.85rem;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.import-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
}
|
||||
|
||||
.magic-shelf-content {
|
||||
padding: 2rem;
|
||||
background: var(--card-background);
|
||||
@@ -190,6 +212,7 @@
|
||||
|
||||
.dialog-footer {
|
||||
@include panel.dialog-footer-end;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.save-button-section {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {Component, inject, OnInit} from '@angular/core';
|
||||
import {AbstractControl, FormArray, FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
|
||||
import {AbstractControl, FormArray, FormControl, FormGroup, FormsModule, ReactiveFormsModule, Validators} from '@angular/forms';
|
||||
import {Button} from 'primeng/button';
|
||||
import {NgTemplateOutlet} from '@angular/common';
|
||||
import {InputText} from 'primeng/inputtext';
|
||||
@@ -24,6 +24,7 @@ import {BookService} from '../../book/service/book.service';
|
||||
import {ShelfService} from '../../book/service/shelf.service';
|
||||
import {Shelf} from '../../book/model/shelf.model';
|
||||
import {TranslocoDirective, TranslocoService} from '@jsverse/transloco';
|
||||
import {TextareaModule} from 'primeng/textarea';
|
||||
|
||||
export type RuleOperator =
|
||||
| 'equals'
|
||||
@@ -230,6 +231,7 @@ const READ_STATUS_KEYS: Record<string, string> = {
|
||||
standalone: true,
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
FormsModule,
|
||||
NgTemplateOutlet,
|
||||
InputText,
|
||||
Select,
|
||||
@@ -241,7 +243,8 @@ const READ_STATUS_KEYS: Record<string, string> = {
|
||||
CheckboxModule,
|
||||
IconDisplayComponent,
|
||||
Tooltip,
|
||||
TranslocoDirective
|
||||
TranslocoDirective,
|
||||
TextareaModule
|
||||
]
|
||||
})
|
||||
export class MagicShelfComponent implements OnInit {
|
||||
@@ -450,6 +453,8 @@ export class MagicShelfComponent implements OnInit {
|
||||
shelfId: number | null = null;
|
||||
isAdmin: boolean = false;
|
||||
editMode!: boolean;
|
||||
showImportPanel = false;
|
||||
importJson = '';
|
||||
|
||||
libraryService = inject(LibraryService);
|
||||
shelfService = inject(ShelfService);
|
||||
@@ -794,6 +799,40 @@ export class MagicShelfComponent implements OnInit {
|
||||
this.form.get('isPublic')?.setValue(checked);
|
||||
}
|
||||
|
||||
toggleImportPanel() {
|
||||
this.showImportPanel = !this.showImportPanel;
|
||||
if (this.showImportPanel) {
|
||||
this.importJson = '';
|
||||
}
|
||||
}
|
||||
|
||||
applyImportedJson() {
|
||||
const trimmed = this.importJson.trim();
|
||||
if (!trimmed) {
|
||||
this.messageService.add({severity: 'warn', summary: this.t.translate('magicShelf.toast.validationErrorSummary'), detail: this.t.translate('magicShelf.importJson.emptyError')});
|
||||
return;
|
||||
}
|
||||
|
||||
let parsed: GroupRule;
|
||||
try {
|
||||
parsed = JSON.parse(trimmed);
|
||||
} catch {
|
||||
this.messageService.add({severity: 'error', summary: this.t.translate('magicShelf.toast.errorSummary'), detail: this.t.translate('magicShelf.importJson.parseError')});
|
||||
return;
|
||||
}
|
||||
|
||||
if (parsed.type !== 'group' || !Array.isArray(parsed.rules)) {
|
||||
this.messageService.add({severity: 'error', summary: this.t.translate('magicShelf.toast.errorSummary'), detail: this.t.translate('magicShelf.importJson.structureError')});
|
||||
return;
|
||||
}
|
||||
|
||||
const builtGroup = this.buildGroupFromData(parsed);
|
||||
this.form.setControl('group', builtGroup);
|
||||
this.showImportPanel = false;
|
||||
this.importJson = '';
|
||||
this.messageService.add({severity: 'success', summary: this.t.translate('magicShelf.toast.successSummary'), detail: this.t.translate('magicShelf.importJson.successDetail')});
|
||||
}
|
||||
|
||||
submit() {
|
||||
if (!this.hasAtLeastOneValidRule(this.group)) {
|
||||
this.messageService.add({severity: 'warn', summary: this.t.translate('magicShelf.toast.validationErrorSummary'), detail: this.t.translate('magicShelf.toast.validationErrorDetail')});
|
||||
|
||||
@@ -279,6 +279,7 @@
|
||||
"magicShelf": {
|
||||
"optionsLabel": "Options",
|
||||
"editMagicShelf": "Edit Magic Shelf",
|
||||
"exportJson": "Copy JSON",
|
||||
"deleteMagicShelf": "Delete Magic Shelf"
|
||||
},
|
||||
"confirm": {
|
||||
@@ -298,6 +299,7 @@
|
||||
"shelfDeleteFailedDetail": "Failed to delete shelf",
|
||||
"magicShelfDeletedDetail": "Magic shelf was deleted",
|
||||
"magicShelfDeleteFailedDetail": "Failed to delete shelf",
|
||||
"magicShelfJsonCopiedDetail": "Magic shelf JSON copied to clipboard",
|
||||
"failedSummary": "Failed"
|
||||
},
|
||||
"loading": {
|
||||
|
||||
@@ -265,6 +265,15 @@
|
||||
"comicCoverArtists": "Cover Artists",
|
||||
"comicEditors": "Editors"
|
||||
},
|
||||
"importJson": {
|
||||
"buttonLabel": "Import",
|
||||
"placeholder": "{\n \"type\": \"group\",\n \"join\": \"and\",\n \"rules\": [\n { \"field\": \"readStatus\", \"operator\": \"equals\", \"value\": \"READING\" }\n ]\n}",
|
||||
"apply": "Apply",
|
||||
"emptyError": "Please paste a JSON configuration first.",
|
||||
"parseError": "Invalid JSON. Please check the syntax and try again.",
|
||||
"structureError": "Invalid structure. The JSON must have \"type\": \"group\" and a \"rules\" array.",
|
||||
"successDetail": "Rules imported successfully. Review and save when ready."
|
||||
},
|
||||
"toast": {
|
||||
"validationErrorSummary": "Validation Error",
|
||||
"validationErrorDetail": "You must add at least one valid rule before saving.",
|
||||
|
||||
@@ -279,6 +279,7 @@
|
||||
"magicShelf": {
|
||||
"optionsLabel": "Opciones",
|
||||
"editMagicShelf": "Editar estante mágico",
|
||||
"exportJson": "Copiar JSON",
|
||||
"deleteMagicShelf": "Eliminar estante mágico"
|
||||
},
|
||||
"confirm": {
|
||||
@@ -298,6 +299,7 @@
|
||||
"shelfDeleteFailedDetail": "Error al eliminar el estante",
|
||||
"magicShelfDeletedDetail": "Estante mágico eliminado",
|
||||
"magicShelfDeleteFailedDetail": "Error al eliminar el estante",
|
||||
"magicShelfJsonCopiedDetail": "JSON del estante mágico copiado al portapapeles",
|
||||
"failedSummary": "Error"
|
||||
},
|
||||
"loading": {
|
||||
|
||||
@@ -265,6 +265,15 @@
|
||||
"comicCoverArtists": "Artistas de Portada",
|
||||
"comicEditors": "Editores"
|
||||
},
|
||||
"importJson": {
|
||||
"buttonLabel": "Importar",
|
||||
"placeholder": "{\n \"type\": \"group\",\n \"join\": \"and\",\n \"rules\": [\n { \"field\": \"readStatus\", \"operator\": \"equals\", \"value\": \"READING\" }\n ]\n}",
|
||||
"apply": "Aplicar",
|
||||
"emptyError": "Pegue una configuración JSON primero.",
|
||||
"parseError": "JSON inválido. Verifique la sintaxis e intente de nuevo.",
|
||||
"structureError": "Estructura inválida. El JSON debe tener \"type\": \"group\" y un arreglo \"rules\".",
|
||||
"successDetail": "Reglas importadas exitosamente. Revise y guarde cuando esté listo."
|
||||
},
|
||||
"toast": {
|
||||
"validationErrorSummary": "Error de Validación",
|
||||
"validationErrorDetail": "Debe agregar al menos una regla válida antes de guardar.",
|
||||
|
||||
Reference in New Issue
Block a user