From 023ae809efbfe04faad022881bbcc1630d8f6684 Mon Sep 17 00:00:00 2001 From: Teriuihi Date: Sun, 22 Jun 2025 19:06:52 +0200 Subject: [PATCH] Modularize `ParticlesComponent` by separating particle, frame, and property functionalities into dedicated components Refactored `ParticlesComponent` to use `ParticleComponent`, `FramesComponent`, and `PropertiesComponent` for better organization and reusability. Updated layout, improved UI structure, and centralized particle management logic. Enhanced clipboard functionality with a new JSON copy feature. --- .../components/frames/frames.component.html | 41 ++++ .../components/frames/frames.component.scss | 46 ++++ .../frames/frames.component.spec.ts | 23 ++ .../components/frames/frames.component.ts | 86 ++++++++ .../particle/particle.component.html | 11 + .../particle/particle.component.scss | 17 ++ .../particle/particle.component.spec.ts | 23 ++ .../components/particle/particle.component.ts | 40 ++++ .../properties/properties.component.html | 91 ++++++++ .../properties/properties.component.scss | 0 .../properties/properties.component.spec.ts | 23 ++ .../properties/properties.component.ts | 43 ++++ .../app/particles/particles.component.html | 200 +++--------------- .../app/particles/particles.component.scss | 104 ++------- .../src/app/particles/particles.component.ts | 80 ++----- frontend/src/styles.scss | 1 + 16 files changed, 497 insertions(+), 332 deletions(-) create mode 100644 frontend/src/app/particles/components/frames/frames.component.html create mode 100644 frontend/src/app/particles/components/frames/frames.component.scss create mode 100644 frontend/src/app/particles/components/frames/frames.component.spec.ts create mode 100644 frontend/src/app/particles/components/frames/frames.component.ts create mode 100644 frontend/src/app/particles/components/particle/particle.component.html create mode 100644 frontend/src/app/particles/components/particle/particle.component.scss create mode 100644 frontend/src/app/particles/components/particle/particle.component.spec.ts create mode 100644 frontend/src/app/particles/components/particle/particle.component.ts create mode 100644 frontend/src/app/particles/components/properties/properties.component.html create mode 100644 frontend/src/app/particles/components/properties/properties.component.scss create mode 100644 frontend/src/app/particles/components/properties/properties.component.spec.ts create mode 100644 frontend/src/app/particles/components/properties/properties.component.ts diff --git a/frontend/src/app/particles/components/frames/frames.component.html b/frontend/src/app/particles/components/frames/frames.component.html new file mode 100644 index 0000000..b988a6b --- /dev/null +++ b/frontend/src/app/particles/components/frames/frames.component.html @@ -0,0 +1,41 @@ + + + Frames + + +
+ + +
+

Particles in {{ frameId }}

+
+
+ Particle {{ i + 1 }}: ({{ particle.x.toFixed(2) }}, {{ particle.y.toFixed(2) }} + , {{ particle.z.toFixed(2) }}) + +
+
+ No particles in this frame. Click on the plane to add particles. +
+
+
+ +
+
+
+
+
+ +
+
+
+
diff --git a/frontend/src/app/particles/components/frames/frames.component.scss b/frontend/src/app/particles/components/frames/frames.component.scss new file mode 100644 index 0000000..3ae576d --- /dev/null +++ b/frontend/src/app/particles/components/frames/frames.component.scss @@ -0,0 +1,46 @@ +.frames-container { + margin-top: 10px; +} + +.frame-content { + padding: 15px; +} + +.particles-list { + max-height: 300px; + overflow-y: auto; + border: 1px solid #eee; + border-radius: 4px; + padding: 10px; + margin-bottom: 15px; +} + +.particle-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px; + border-bottom: 1px solid #eee; +} + +.particle-item:last-child { + border-bottom: none; +} + +.no-particles { + padding: 20px; + text-align: center; + color: #888; +} + +.frame-actions { + display: flex; + justify-content: flex-end; + margin-top: 10px; +} + +.add-frame { + margin-top: 15px; + display: flex; + justify-content: center; +} diff --git a/frontend/src/app/particles/components/frames/frames.component.spec.ts b/frontend/src/app/particles/components/frames/frames.component.spec.ts new file mode 100644 index 0000000..5db801e --- /dev/null +++ b/frontend/src/app/particles/components/frames/frames.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FramesComponent } from './frames.component'; + +describe('FramesComponent', () => { + let component: FramesComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [FramesComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(FramesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/particles/components/frames/frames.component.ts b/frontend/src/app/particles/components/frames/frames.component.ts new file mode 100644 index 0000000..484ede0 --- /dev/null +++ b/frontend/src/app/particles/components/frames/frames.component.ts @@ -0,0 +1,86 @@ +import {Component} from '@angular/core'; +import {MatButton, MatIconButton} from "@angular/material/button"; +import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from "@angular/material/card"; +import {MatTab, MatTabGroup} from "@angular/material/tabs"; +import {NgForOf, NgIf} from "@angular/common"; +import {ParticleData} from '../../models/particle.model'; +import {MatIcon} from '@angular/material/icon'; +import {ParticleManagerService} from '../../services/particle-manager.service'; +import {FrameManagerService} from '../../services/frame-manager.service'; + +@Component({ + selector: 'app-frames', + imports: [ + MatButton, + MatCard, + MatCardContent, + MatCardHeader, + MatCardTitle, + MatIcon, + MatIconButton, + MatTab, + MatTabGroup, + NgForOf, + NgIf + ], + templateUrl: './frames.component.html', + styleUrl: './frames.component.scss' +}) +export class FramesComponent { + + constructor( + private particleManagerService: ParticleManagerService, + private frameManagerService: FrameManagerService) { + } + + /** + * Get the particle data + */ + public get particleData(): ParticleData { + return this.particleManagerService.getParticleData(); + } + + /** + * Get the current frame + */ + public get currentFrame(): string { + return this.particleManagerService.getCurrentFrame(); + } + + /** + * Get all frames + */ + public get frames(): string[] { + return this.particleManagerService.getFrames(); + } + + /** + * Add a new frame + */ + public addFrame(): void { + this.frameManagerService.addFrame(); + } + + /** + * Switch to a different frame + */ + public switchFrame(frameId: string): void { + this.frameManagerService.switchFrame(frameId); + } + + /** + * Remove a particle + */ + public removeParticle(frameId: string, index: number): void { + this.particleManagerService.removeParticle(frameId, index); + } + + /** + * Remove a frame + */ + public removeFrame(frameId: string): void { + this.frameManagerService.removeFrame(frameId); + } + + +} diff --git a/frontend/src/app/particles/components/particle/particle.component.html b/frontend/src/app/particles/components/particle/particle.component.html new file mode 100644 index 0000000..84724b1 --- /dev/null +++ b/frontend/src/app/particles/components/particle/particle.component.html @@ -0,0 +1,11 @@ + + + Particle Color + + +
+ + Selected Color: {{ selectedColor }} +
+
+
diff --git a/frontend/src/app/particles/components/particle/particle.component.scss b/frontend/src/app/particles/components/particle/particle.component.scss new file mode 100644 index 0000000..f11c807 --- /dev/null +++ b/frontend/src/app/particles/components/particle/particle.component.scss @@ -0,0 +1,17 @@ +.color-picker-card { + margin-top: 20px; +} + +.color-picker { + display: flex; + align-items: center; + gap: 15px; +} + +.color-picker input[type="color"] { + width: 50px; + height: 50px; + border: none; + border-radius: 4px; + cursor: pointer; +} diff --git a/frontend/src/app/particles/components/particle/particle.component.spec.ts b/frontend/src/app/particles/components/particle/particle.component.spec.ts new file mode 100644 index 0000000..5b39607 --- /dev/null +++ b/frontend/src/app/particles/components/particle/particle.component.spec.ts @@ -0,0 +1,23 @@ +import {ComponentFixture, TestBed} from '@angular/core/testing'; + +import {ParticleComponent} from './particle.component'; + +describe('ParticleComponent', () => { + let component: ParticleComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ParticleComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ParticleComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/particles/components/particle/particle.component.ts b/frontend/src/app/particles/components/particle/particle.component.ts new file mode 100644 index 0000000..f2a4413 --- /dev/null +++ b/frontend/src/app/particles/components/particle/particle.component.ts @@ -0,0 +1,40 @@ +import {Component} from '@angular/core'; +import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card'; +import {FormsModule, ReactiveFormsModule} from '@angular/forms'; +import {ParticleManagerService} from '../../services/particle-manager.service'; + +@Component({ + selector: 'app-particle', + imports: [ + MatCard, + MatCardContent, + MatCardHeader, + MatCardTitle, + ReactiveFormsModule, + FormsModule + ], + templateUrl: './particle.component.html', + styleUrl: './particle.component.scss' +}) +export class ParticleComponent { + + constructor( + private particleManagerService: ParticleManagerService, + ) { + } + + /** + * Get the selected color + */ + public get selectedColor(): string { + return this.particleManagerService.getSelectedColor(); + } + + /** + * Set the selected color + */ + public set selectedColor(color: string) { + this.particleManagerService.setSelectedColor(color); + } + +} diff --git a/frontend/src/app/particles/components/properties/properties.component.html b/frontend/src/app/particles/components/properties/properties.component.html new file mode 100644 index 0000000..d702302 --- /dev/null +++ b/frontend/src/app/particles/components/properties/properties.component.html @@ -0,0 +1,91 @@ + + + Particle Properties + + +
+ + Particle Name + + +
+ +
+ + Display Name + + +
+ +
+ + Particle Type + + {{ type }} + + +
+ +
+ + Lore + + +
+ +
+ + Display Item + + +
+ +
+ + Permission + + +
+ +
+ + Package Permission + + +
+ +
+ + Frame Delay + + +
+ +
+ + Repeat + + +
+ +
+ + Repeat Delay + + +
+ +
+ + Random Offset + + +
+ +
+ Stationary +
+
+
diff --git a/frontend/src/app/particles/components/properties/properties.component.scss b/frontend/src/app/particles/components/properties/properties.component.scss new file mode 100644 index 0000000..e69de29 diff --git a/frontend/src/app/particles/components/properties/properties.component.spec.ts b/frontend/src/app/particles/components/properties/properties.component.spec.ts new file mode 100644 index 0000000..33213d7 --- /dev/null +++ b/frontend/src/app/particles/components/properties/properties.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { PropertiesComponent } from './properties.component'; + +describe('PropertiesComponent', () => { + let component: PropertiesComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [PropertiesComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(PropertiesComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/frontend/src/app/particles/components/properties/properties.component.ts b/frontend/src/app/particles/components/properties/properties.component.ts new file mode 100644 index 0000000..69ca7f9 --- /dev/null +++ b/frontend/src/app/particles/components/properties/properties.component.ts @@ -0,0 +1,43 @@ +import {Component} from '@angular/core'; +import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from "@angular/material/card"; +import {MatCheckbox} from "@angular/material/checkbox"; +import {MatFormField, MatInput, MatLabel} from "@angular/material/input"; +import {NgForOf} from "@angular/common"; +import {FormsModule, ReactiveFormsModule} from "@angular/forms"; +import {ParticleData, ParticleType} from '../../models/particle.model'; +import {MatSelect} from '@angular/material/select'; +import {MatOption} from '@angular/material/core'; +import {ParticleManagerService} from '../../services/particle-manager.service'; + +@Component({ + selector: 'app-particle-properties', + imports: [ + MatCard, + MatCardContent, + MatCardHeader, + MatCardTitle, + MatCheckbox, + MatFormField, + MatInput, + MatLabel, + MatOption, + MatSelect, + NgForOf, + ReactiveFormsModule, + FormsModule + ], + templateUrl: './properties.component.html', + styleUrl: './properties.component.scss' +}) +export class PropertiesComponent { + public particleTypes = Object.values(ParticleType); + + constructor( + private particleManagerService: ParticleManagerService, + ) { + } + + public get particleData(): ParticleData { + return this.particleManagerService.getParticleData(); + } +} diff --git a/frontend/src/app/particles/particles.component.html b/frontend/src/app/particles/particles.component.html index 55658c2..8daf127 100644 --- a/frontend/src/app/particles/particles.component.html +++ b/frontend/src/app/particles/particles.component.html @@ -8,180 +8,32 @@
-
-
-
- - - - - {{ planePosition }} offset from center -
-
-
-
- - - Particle Properties - - -
- - Particle Name - - -
- -
- - Display Name - - -
- -
- - Particle Type - - {{ type }} - - -
- -
- - Lore - - -
- -
- - Display Item - - -
- -
- - Permission - - -
- -
- - Package Permission - - -
- -
- - Frame Delay - - -
- -
- - Repeat - - -
- -
- - Repeat Delay - - -
- -
- - Random Offset - - -
- -
- Stationary -
-
-
-
- -
- - - Particle Color - - -
- - Selected Color: {{ selectedColor }} -
-
-
-
- -
- - - Frames - - -
- - -
-

Particles in {{ frameId }}

-
-
- Particle {{ i + 1 }}: ({{ particle.x.toFixed(2) }}, {{ particle.y.toFixed(2) }} - , {{ particle.z.toFixed(2) }}) - -
-
- No particles in this frame. Click on the plane to add particles. -
-
-
- -
-
-
-
-
- -
-
-
-
-
- -
- - - - - JSON Output - - -
{{ generateJson() }}
-
-
+
+
+
+ +
+
+
+
+
+ + + + + {{ planePosition }} offset from center +
+
+
+ + +
+ +
+
diff --git a/frontend/src/app/particles/particles.component.scss b/frontend/src/app/particles/particles.component.scss index ebb1f73..cf8954f 100644 --- a/frontend/src/app/particles/particles.component.scss +++ b/frontend/src/app/particles/particles.component.scss @@ -5,8 +5,7 @@ } .renderer-container { - width: 100%; - height: 400px; + height: 1000px; border: 1px solid #ccc; border-radius: 4px; overflow: hidden; @@ -16,6 +15,18 @@ align-items: center; } +.side-column { + flex: 1; + flex-direction: column; + gap: 20px; +} + +.middle-column { + flex: 2; + flex-direction: column; + gap: 20px; +} + .plane-controls { margin-top: 10px; padding: 10px; @@ -30,12 +41,6 @@ flex: 1; } -.controls-section { - flex: 1; - min-width: 300px; - gap: 20px; -} - .form-row { margin-bottom: 15px; } @@ -43,86 +48,3 @@ mat-form-field { width: 100%; } - -.color-picker-card { - margin-top: 20px; -} - -.color-picker { - display: flex; - align-items: center; - gap: 15px; -} - -.color-picker input[type="color"] { - width: 50px; - height: 50px; - border: none; - border-radius: 4px; - cursor: pointer; -} - -.frames-section { - margin-bottom: 20px; -} - -.frames-container { - margin-top: 10px; -} - -.frame-content { - padding: 15px; -} - -.particles-list { - max-height: 300px; - overflow-y: auto; - border: 1px solid #eee; - border-radius: 4px; - padding: 10px; - margin-bottom: 15px; -} - -.particle-item { - display: flex; - justify-content: space-between; - align-items: center; - padding: 8px; - border-bottom: 1px solid #eee; -} - -.particle-item:last-child { - border-bottom: none; -} - -.no-particles { - padding: 20px; - text-align: center; - color: #888; -} - -.frame-actions { - display: flex; - justify-content: flex-end; - margin-top: 10px; -} - -.add-frame { - margin-top: 15px; - display: flex; - justify-content: center; -} - -.json-output { - margin-top: 20px; -} - -.json-output pre { - background-color: #f5f5f5; - padding: 15px; - border-radius: 4px; - overflow-x: auto; - max-height: 300px; - font-family: monospace; - white-space: pre-wrap; -} diff --git a/frontend/src/app/particles/particles.component.ts b/frontend/src/app/particles/particles.component.ts index c769d1f..f78ea76 100644 --- a/frontend/src/app/particles/particles.component.ts +++ b/frontend/src/app/particles/particles.component.ts @@ -18,10 +18,12 @@ import {PlayerModelService} from './services/player-model.service'; import {IntersectionPlaneService} from './services/intersection-plane.service'; import {ParticleManagerService} from './services/particle-manager.service'; import {InputHandlerService} from './services/input-handler.service'; -import {FrameManagerService} from './services/frame-manager.service'; // Models -import {ParticleData, ParticleType} from './models/particle.model'; +import {PropertiesComponent} from './components/properties/properties.component'; +import {ParticleComponent} from './components/particle/particle.component'; +import {FramesComponent} from './components/frames/frames.component'; +import {MatSnackBar} from '@angular/material/snack-bar'; @Component({ selector: 'app-particles', @@ -39,7 +41,10 @@ import {ParticleData, ParticleType} from './models/particle.model'; MatTabsModule, MatCardModule, MatIconModule, - HeaderComponent + HeaderComponent, + PropertiesComponent, + ParticleComponent, + FramesComponent, ], templateUrl: './particles.component.html', styleUrl: './particles.component.scss' @@ -48,16 +53,13 @@ export class ParticlesComponent implements OnInit, AfterViewInit, OnDestroy { @ViewChild('rendererContainer') rendererContainer!: ElementRef; @ViewChild('planeSlider') planeSlider!: ElementRef; - // UI state - public particleTypes = Object.values(ParticleType); - constructor( private rendererService: RendererService, private playerModelService: PlayerModelService, private intersectionPlaneService: IntersectionPlaneService, private particleManagerService: ParticleManagerService, private inputHandlerService: InputHandlerService, - private frameManagerService: FrameManagerService + private matSnackBar: MatSnackBar, ) { } @@ -131,41 +133,6 @@ export class ParticlesComponent implements OnInit, AfterViewInit, OnDestroy { return this.intersectionPlaneService.getMinOffset(); } - /** - * Get the selected color - */ - public get selectedColor(): string { - return this.particleManagerService.getSelectedColor(); - } - - /** - * Set the selected color - */ - public set selectedColor(color: string) { - this.particleManagerService.setSelectedColor(color); - } - - /** - * Get the particle data - */ - public get particleData(): ParticleData { - return this.particleManagerService.getParticleData(); - } - - /** - * Get the current frame - */ - public get currentFrame(): string { - return this.particleManagerService.getCurrentFrame(); - } - - /** - * Get all frames - */ - public get frames(): string[] { - return this.particleManagerService.getFrames(); - } - /** * Animation loop */ @@ -179,19 +146,6 @@ export class ParticlesComponent implements OnInit, AfterViewInit, OnDestroy { this.rendererService.render(); } - /** - * Add a new frame - */ - public addFrame(): void { - this.frameManagerService.addFrame(); - } - - /** - * Switch to a different frame - */ - public switchFrame(frameId: string): void { - this.frameManagerService.switchFrame(frameId); - } /** * Generate JSON output @@ -200,17 +154,9 @@ export class ParticlesComponent implements OnInit, AfterViewInit, OnDestroy { return this.particleManagerService.generateJson(); } - /** - * Remove a particle - */ - public removeParticle(frameId: string, index: number): void { - this.particleManagerService.removeParticle(frameId, index); - } - - /** - * Remove a frame - */ - public removeFrame(frameId: string): void { - this.frameManagerService.removeFrame(frameId); + public copyJson() { + navigator.clipboard.writeText(this.generateJson()).then(() => { + this.matSnackBar.open('Copied to clipboard', '', {duration: 2000}) + }); } } diff --git a/frontend/src/styles.scss b/frontend/src/styles.scss index 032cac3..c7b4650 100644 --- a/frontend/src/styles.scss +++ b/frontend/src/styles.scss @@ -1,4 +1,5 @@ @import '@angular/material/prebuilt-themes/azure-blue.css'; +@import url('https://fonts.googleapis.com/icon?family=Material+Icons'); :root { --white: #FFFFFF;