fix(plugins): ensure reactive data binding and loading state on page reload

main
Juan Manuel Bardallo 2026-10-22 11:17:48 +02:00
parent c4aa997516
commit 49ccdfc4ec
4 changed files with 90 additions and 7 deletions

View File

@ -1,18 +1,20 @@
import { Component, Input, OnInit, OnChanges, SimpleChanges } from '@angular/core';
import { Component, Input, OnInit, OnChanges, OnDestroy, SimpleChanges } from '@angular/core';
import { PluginManifest, PluginService, SchemaField } from '@services/plugin.service';
import { ToastrService } from 'ngx-toastr';
import { Subscription } from 'rxjs';
@Component({
selector: 'app-plugin-settings',
templateUrl: './plugin-settings.component.html',
styleUrls: ['./plugin-settings.component.css']
})
export class PluginSettingsComponent implements OnInit, OnChanges {
export class PluginSettingsComponent implements OnInit, OnChanges, OnDestroy {
@Input() pluginName: string = '';
plugin?: PluginManifest;
schema: SchemaField[] = [];
configData: Record<string, any> = {};
private pluginSub?: Subscription;
constructor(
private pluginService: PluginService,
@ -20,9 +22,16 @@ export class PluginSettingsComponent implements OnInit, OnChanges {
) {}
ngOnInit(): void {
this.pluginSub = this.pluginService.plugins$.subscribe(() => {
this.loadPluginData();
});
this.loadPluginData();
}
ngOnDestroy(): void {
this.pluginSub?.unsubscribe();
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['pluginName']) {
this.loadPluginData();

View File

@ -115,3 +115,36 @@
font-size: 0.95rem;
line-height: 1.5;
}
.loading-plugins-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: #ffffff;
border: 1px solid #e2e8f0;
border-radius: 16px;
padding: 60px 24px;
gap: 16px;
}
.loading-spinner {
width: 38px;
height: 38px;
border: 3px solid #e2e8f0;
border-top-color: #1976d2;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.loading-text {
color: #64748b;
font-size: 0.95rem;
margin: 0;
}

View File

@ -4,7 +4,9 @@
<div class="header-main-row">
<mat-icon class="header-title-icon">extension</mat-icon>
<h1 class="header-main-title">Plugins y Extensiones</h1>
<span class="plugins-count-badge">{{ plugins.length }} Activo(s)</span>
@if (!isLoading) {
<span class="plugins-count-badge">{{ plugins.length }} Activo(s)</span>
}
</div>
<p class="header-subtitle">
Gestiona y personaliza los módulos y extensiones instalados en el servidor OpenGnsys. Cada plugin opera de manera desacoplada sin interferir con el núcleo del sistema.
@ -12,7 +14,12 @@
</div>
</div>
@if (plugins.length > 0) {
@if (isLoading) {
<div class="loading-plugins-box">
<div class="loading-spinner"></div>
<p class="loading-text">Cargando extensiones y plugins instalados...</p>
</div>
} @else if (plugins.length > 0) {
<div class="plugins-content-card">
<mat-tab-group [(selectedIndex)]="selectedTabIndex" class="custom-plugin-tabs" animationDuration="200ms">
@for (plugin of plugins; track plugin.name) {
@ -35,7 +42,7 @@
<h3 class="empty-title">No hay plugins instalados</h3>
<p class="empty-desc">
Actualmente no hay ninguna extensión o plugin registrado en este servidor OpenGnsys.
Cuando instales un paquete compatible (como <code>og-plugin-freeze</code>), aparecerá automáticamente aquí.
Cuando instales un paquete compatible (como <code>ogfreeze</code>), aparecerá automáticamente aquí.
</p>
</div>
}

View File

@ -1,18 +1,52 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, OnDestroy, ChangeDetectorRef, inject } from '@angular/core';
import { PluginManifest, PluginService } from '@services/plugin.service';
import { Subscription } from 'rxjs';
@Component({
selector: 'app-plugins',
templateUrl: './plugins.component.html',
styleUrls: ['./plugins.component.css']
})
export class PluginsComponent implements OnInit {
export class PluginsComponent implements OnInit, OnDestroy {
plugins: PluginManifest[] = [];
selectedTabIndex: number = 0;
isLoading: boolean = true;
private pluginSub?: Subscription;
private cdr = inject(ChangeDetectorRef);
constructor(public pluginService: PluginService) {}
ngOnInit(): void {
// 1. Asignar estado actual en memoria si ya existe
this.plugins = this.pluginService.getPlugins();
if (this.plugins.length > 0) {
this.isLoading = false;
}
// 2. Suscribirse reactivamente al flujo de plugins
this.pluginSub = this.pluginService.plugins$.subscribe(plugins => {
this.plugins = plugins || [];
if (this.plugins.length > 0) {
this.isLoading = false;
}
this.cdr.markForCheck();
});
// 3. Forzar refresco desde el backend para garantizar datos actualizados
this.pluginService.refreshPlugins().subscribe({
next: (plugins) => {
this.plugins = plugins || [];
this.isLoading = false;
this.cdr.markForCheck();
},
error: () => {
this.isLoading = false;
this.cdr.markForCheck();
}
});
}
ngOnDestroy(): void {
this.pluginSub?.unsubscribe();
}
}