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.
This commit is contained in:
parent
3a6f137c9a
commit
023ae809ef
|
|
@ -0,0 +1,41 @@
|
||||||
|
<mat-card>
|
||||||
|
<mat-card-header>
|
||||||
|
<mat-card-title>Frames</mat-card-title>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<div class="frames-container">
|
||||||
|
<mat-tab-group [selectedIndex]="frames.indexOf(currentFrame)"
|
||||||
|
(selectedIndexChange)="switchFrame(frames[$event])">
|
||||||
|
<mat-tab *ngFor="let frameId of frames" [label]="frameId">
|
||||||
|
<div class="frame-content">
|
||||||
|
<h3>Particles in {{ frameId }}</h3>
|
||||||
|
<div class="particles-list">
|
||||||
|
<div *ngFor="let particle of particleData.frames[frameId]; let i = index" class="particle-item">
|
||||||
|
<span>Particle {{ i + 1 }}: ({{ particle.x.toFixed(2) }}, {{ particle.y.toFixed(2) }}
|
||||||
|
, {{ particle.z.toFixed(2) }})</span>
|
||||||
|
<button mat-icon-button color="warn" (click)="removeParticle(frameId, i)">
|
||||||
|
<mat-icon>delete</mat-icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="!particleData.frames[frameId] || particleData.frames[frameId].length === 0"
|
||||||
|
class="no-particles">
|
||||||
|
No particles in this frame. Click on the plane to add particles.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="frame-actions">
|
||||||
|
<button mat-raised-button color="warn" (click)="removeFrame(frameId)"
|
||||||
|
[disabled]="frames.length <= 1">
|
||||||
|
Remove Frame
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-tab>
|
||||||
|
</mat-tab-group>
|
||||||
|
<div class="add-frame">
|
||||||
|
<button mat-raised-button color="primary" (click)="addFrame()">
|
||||||
|
Add New Frame
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { FramesComponent } from './frames.component';
|
||||||
|
|
||||||
|
describe('FramesComponent', () => {
|
||||||
|
let component: FramesComponent;
|
||||||
|
let fixture: ComponentFixture<FramesComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [FramesComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(FramesComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
<mat-card class="color-picker-card">
|
||||||
|
<mat-card-header>
|
||||||
|
<mat-card-title>Particle Color</mat-card-title>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<div class="color-picker">
|
||||||
|
<input type="color" [(ngModel)]="selectedColor">
|
||||||
|
<span>Selected Color: {{ selectedColor }}</span>
|
||||||
|
</div>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||||
|
|
||||||
|
import {ParticleComponent} from './particle.component';
|
||||||
|
|
||||||
|
describe('ParticleComponent', () => {
|
||||||
|
let component: ParticleComponent;
|
||||||
|
let fixture: ComponentFixture<ParticleComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [ParticleComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(ParticleComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
<mat-card>
|
||||||
|
<mat-card-header>
|
||||||
|
<mat-card-title>Particle Properties</mat-card-title>
|
||||||
|
</mat-card-header>
|
||||||
|
<mat-card-content>
|
||||||
|
<div class="form-row">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Particle Name</mat-label>
|
||||||
|
<input matInput [(ngModel)]="particleData.particle_name" placeholder="Enter particle name">
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Display Name</mat-label>
|
||||||
|
<input matInput [(ngModel)]="particleData.display_name" placeholder="Enter display name">
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Particle Type</mat-label>
|
||||||
|
<mat-select [(ngModel)]="particleData.particle_type">
|
||||||
|
<mat-option *ngFor="let type of particleTypes" [value]="type">{{ type }}</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Lore</mat-label>
|
||||||
|
<textarea matInput [(ngModel)]="particleData.lore" placeholder="Enter lore"></textarea>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Display Item</mat-label>
|
||||||
|
<input matInput [(ngModel)]="particleData.display_item" placeholder="Enter display item">
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Permission</mat-label>
|
||||||
|
<input matInput [(ngModel)]="particleData.permission" placeholder="Enter permission">
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Package Permission</mat-label>
|
||||||
|
<input matInput [(ngModel)]="particleData.package_permission" placeholder="Enter package permission">
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Frame Delay</mat-label>
|
||||||
|
<input matInput type="number" [(ngModel)]="particleData.frame_delay" placeholder="Enter frame delay">
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Repeat</mat-label>
|
||||||
|
<input matInput type="number" [(ngModel)]="particleData.repeat" placeholder="Enter repeat count">
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Repeat Delay</mat-label>
|
||||||
|
<input matInput type="number" [(ngModel)]="particleData.repeat_delay"
|
||||||
|
placeholder="Enter repeat delay">
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Random Offset</mat-label>
|
||||||
|
<input matInput type="number" [(ngModel)]="particleData.random_offset"
|
||||||
|
placeholder="Enter random offset">
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<mat-checkbox [(ngModel)]="particleData.stationary">Stationary</mat-checkbox>
|
||||||
|
</div>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { PropertiesComponent } from './properties.component';
|
||||||
|
|
||||||
|
describe('PropertiesComponent', () => {
|
||||||
|
let component: PropertiesComponent;
|
||||||
|
let fixture: ComponentFixture<PropertiesComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [PropertiesComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(PropertiesComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,180 +8,32 @@
|
||||||
<main>
|
<main>
|
||||||
<section class="darkmodeSection">
|
<section class="darkmodeSection">
|
||||||
<section class="column">
|
<section class="column">
|
||||||
<div class="renderer-section row column">
|
<div class="renderer-section column">
|
||||||
<div #rendererContainer class="renderer-container column row"></div>
|
<div class="flex row">
|
||||||
<div class="plane-controls">
|
<div class="flex side-column">
|
||||||
<label>Plane Position (Z-axis):</label>
|
<app-particle-properties></app-particle-properties>
|
||||||
<mat-slider [min]="minOffset" [max]="maxOffset" step="1" #planeSlider>
|
</div>
|
||||||
<input matSliderThumb [(ngModel)]="planePosition" (input)="updatePlanePosition($event)">
|
<div class="flex middle-column">
|
||||||
</mat-slider>
|
<div #rendererContainer class="renderer-container">
|
||||||
<span>{{ planePosition }} offset from center</span>
|
</div>
|
||||||
</div>
|
<div class="plane-controls">
|
||||||
</div>
|
<label>Plane Position (Z-axis):</label>
|
||||||
<div class="row">
|
<mat-slider [min]="minOffset" [max]="maxOffset" step="1" #planeSlider>
|
||||||
<div class="column controls-section">
|
<input matSliderThumb [(ngModel)]="planePosition" (input)="updatePlanePosition($event)">
|
||||||
<mat-card>
|
</mat-slider>
|
||||||
<mat-card-header>
|
<span>{{ planePosition }} offset from center</span>
|
||||||
<mat-card-title>Particle Properties</mat-card-title>
|
</div>
|
||||||
</mat-card-header>
|
</div>
|
||||||
<mat-card-content>
|
<div class="flex side-column">
|
||||||
<div class="form-row">
|
<app-particle></app-particle>
|
||||||
<mat-form-field appearance="outline">
|
<app-frames></app-frames>
|
||||||
<mat-label>Particle Name</mat-label>
|
<div>
|
||||||
<input matInput [(ngModel)]="particleData.particle_name" placeholder="Enter particle name">
|
<button mat-fab extended (click)="copyJson()">
|
||||||
</mat-form-field>
|
<mat-icon>content_copy</mat-icon>
|
||||||
</div>
|
Copy JSON to clipboard
|
||||||
|
</button>
|
||||||
<div class="form-row">
|
</div>
|
||||||
<mat-form-field appearance="outline">
|
</div>
|
||||||
<mat-label>Display Name</mat-label>
|
|
||||||
<input matInput [(ngModel)]="particleData.display_name" placeholder="Enter display name">
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-row">
|
|
||||||
<mat-form-field appearance="outline">
|
|
||||||
<mat-label>Particle Type</mat-label>
|
|
||||||
<mat-select [(ngModel)]="particleData.particle_type">
|
|
||||||
<mat-option *ngFor="let type of particleTypes" [value]="type">{{ type }}</mat-option>
|
|
||||||
</mat-select>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-row">
|
|
||||||
<mat-form-field appearance="outline">
|
|
||||||
<mat-label>Lore</mat-label>
|
|
||||||
<textarea matInput [(ngModel)]="particleData.lore" placeholder="Enter lore"></textarea>
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-row">
|
|
||||||
<mat-form-field appearance="outline">
|
|
||||||
<mat-label>Display Item</mat-label>
|
|
||||||
<input matInput [(ngModel)]="particleData.display_item" placeholder="Enter display item">
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-row">
|
|
||||||
<mat-form-field appearance="outline">
|
|
||||||
<mat-label>Permission</mat-label>
|
|
||||||
<input matInput [(ngModel)]="particleData.permission" placeholder="Enter permission">
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-row">
|
|
||||||
<mat-form-field appearance="outline">
|
|
||||||
<mat-label>Package Permission</mat-label>
|
|
||||||
<input matInput [(ngModel)]="particleData.package_permission" placeholder="Enter package permission">
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-row">
|
|
||||||
<mat-form-field appearance="outline">
|
|
||||||
<mat-label>Frame Delay</mat-label>
|
|
||||||
<input matInput type="number" [(ngModel)]="particleData.frame_delay" placeholder="Enter frame delay">
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-row">
|
|
||||||
<mat-form-field appearance="outline">
|
|
||||||
<mat-label>Repeat</mat-label>
|
|
||||||
<input matInput type="number" [(ngModel)]="particleData.repeat" placeholder="Enter repeat count">
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-row">
|
|
||||||
<mat-form-field appearance="outline">
|
|
||||||
<mat-label>Repeat Delay</mat-label>
|
|
||||||
<input matInput type="number" [(ngModel)]="particleData.repeat_delay"
|
|
||||||
placeholder="Enter repeat delay">
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-row">
|
|
||||||
<mat-form-field appearance="outline">
|
|
||||||
<mat-label>Random Offset</mat-label>
|
|
||||||
<input matInput type="number" [(ngModel)]="particleData.random_offset"
|
|
||||||
placeholder="Enter random offset">
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-row">
|
|
||||||
<mat-checkbox [(ngModel)]="particleData.stationary">Stationary</mat-checkbox>
|
|
||||||
</div>
|
|
||||||
</mat-card-content>
|
|
||||||
</mat-card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="column">
|
|
||||||
<mat-card class="color-picker-card">
|
|
||||||
<mat-card-header>
|
|
||||||
<mat-card-title>Particle Color</mat-card-title>
|
|
||||||
</mat-card-header>
|
|
||||||
<mat-card-content>
|
|
||||||
<div class="color-picker">
|
|
||||||
<input type="color" [(ngModel)]="selectedColor">
|
|
||||||
<span>Selected Color: {{ selectedColor }}</span>
|
|
||||||
</div>
|
|
||||||
</mat-card-content>
|
|
||||||
</mat-card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="column frames-section">
|
|
||||||
<mat-card>
|
|
||||||
<mat-card-header>
|
|
||||||
<mat-card-title>Frames</mat-card-title>
|
|
||||||
</mat-card-header>
|
|
||||||
<mat-card-content>
|
|
||||||
<div class="frames-container">
|
|
||||||
<mat-tab-group [selectedIndex]="frames.indexOf(currentFrame)"
|
|
||||||
(selectedIndexChange)="switchFrame(frames[$event])">
|
|
||||||
<mat-tab *ngFor="let frameId of frames" [label]="frameId">
|
|
||||||
<div class="frame-content">
|
|
||||||
<h3>Particles in {{ frameId }}</h3>
|
|
||||||
<div class="particles-list">
|
|
||||||
<div *ngFor="let particle of particleData.frames[frameId]; let i = index" class="particle-item">
|
|
||||||
<span>Particle {{ i + 1 }}: ({{ particle.x.toFixed(2) }}, {{ particle.y.toFixed(2) }}
|
|
||||||
, {{ particle.z.toFixed(2) }})</span>
|
|
||||||
<button mat-icon-button color="warn" (click)="removeParticle(frameId, i)">
|
|
||||||
<mat-icon>delete</mat-icon>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div *ngIf="!particleData.frames[frameId] || particleData.frames[frameId].length === 0"
|
|
||||||
class="no-particles">
|
|
||||||
No particles in this frame. Click on the plane to add particles.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="frame-actions">
|
|
||||||
<button mat-raised-button color="warn" (click)="removeFrame(frameId)"
|
|
||||||
[disabled]="frames.length <= 1">
|
|
||||||
Remove Frame
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</mat-tab>
|
|
||||||
</mat-tab-group>
|
|
||||||
<div class="add-frame">
|
|
||||||
<button mat-raised-button color="primary" (click)="addFrame()">
|
|
||||||
Add New Frame
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</mat-card-content>
|
|
||||||
</mat-card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="column json-output">
|
|
||||||
|
|
||||||
|
|
||||||
<mat-card>
|
|
||||||
<mat-card-header>
|
|
||||||
<mat-card-title>JSON Output</mat-card-title>
|
|
||||||
</mat-card-header>
|
|
||||||
<mat-card-content>
|
|
||||||
<pre>{{ generateJson() }}</pre>
|
|
||||||
</mat-card-content>
|
|
||||||
</mat-card>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.renderer-container {
|
.renderer-container {
|
||||||
width: 100%;
|
height: 1000px;
|
||||||
height: 400px;
|
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
@ -16,6 +15,18 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.side-column {
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.middle-column {
|
||||||
|
flex: 2;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.plane-controls {
|
.plane-controls {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
|
|
@ -30,12 +41,6 @@
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.controls-section {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 300px;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-row {
|
.form-row {
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
@ -43,86 +48,3 @@
|
||||||
mat-form-field {
|
mat-form-field {
|
||||||
width: 100%;
|
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;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,12 @@ import {PlayerModelService} from './services/player-model.service';
|
||||||
import {IntersectionPlaneService} from './services/intersection-plane.service';
|
import {IntersectionPlaneService} from './services/intersection-plane.service';
|
||||||
import {ParticleManagerService} from './services/particle-manager.service';
|
import {ParticleManagerService} from './services/particle-manager.service';
|
||||||
import {InputHandlerService} from './services/input-handler.service';
|
import {InputHandlerService} from './services/input-handler.service';
|
||||||
import {FrameManagerService} from './services/frame-manager.service';
|
|
||||||
|
|
||||||
// Models
|
// 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({
|
@Component({
|
||||||
selector: 'app-particles',
|
selector: 'app-particles',
|
||||||
|
|
@ -39,7 +41,10 @@ import {ParticleData, ParticleType} from './models/particle.model';
|
||||||
MatTabsModule,
|
MatTabsModule,
|
||||||
MatCardModule,
|
MatCardModule,
|
||||||
MatIconModule,
|
MatIconModule,
|
||||||
HeaderComponent
|
HeaderComponent,
|
||||||
|
PropertiesComponent,
|
||||||
|
ParticleComponent,
|
||||||
|
FramesComponent,
|
||||||
],
|
],
|
||||||
templateUrl: './particles.component.html',
|
templateUrl: './particles.component.html',
|
||||||
styleUrl: './particles.component.scss'
|
styleUrl: './particles.component.scss'
|
||||||
|
|
@ -48,16 +53,13 @@ export class ParticlesComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
@ViewChild('rendererContainer') rendererContainer!: ElementRef;
|
@ViewChild('rendererContainer') rendererContainer!: ElementRef;
|
||||||
@ViewChild('planeSlider') planeSlider!: ElementRef;
|
@ViewChild('planeSlider') planeSlider!: ElementRef;
|
||||||
|
|
||||||
// UI state
|
|
||||||
public particleTypes = Object.values(ParticleType);
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private rendererService: RendererService,
|
private rendererService: RendererService,
|
||||||
private playerModelService: PlayerModelService,
|
private playerModelService: PlayerModelService,
|
||||||
private intersectionPlaneService: IntersectionPlaneService,
|
private intersectionPlaneService: IntersectionPlaneService,
|
||||||
private particleManagerService: ParticleManagerService,
|
private particleManagerService: ParticleManagerService,
|
||||||
private inputHandlerService: InputHandlerService,
|
private inputHandlerService: InputHandlerService,
|
||||||
private frameManagerService: FrameManagerService
|
private matSnackBar: MatSnackBar,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -131,41 +133,6 @@ export class ParticlesComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
return this.intersectionPlaneService.getMinOffset();
|
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
|
* Animation loop
|
||||||
*/
|
*/
|
||||||
|
|
@ -179,19 +146,6 @@ export class ParticlesComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
this.rendererService.render();
|
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
|
* Generate JSON output
|
||||||
|
|
@ -200,17 +154,9 @@ export class ParticlesComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||||
return this.particleManagerService.generateJson();
|
return this.particleManagerService.generateJson();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public copyJson() {
|
||||||
* Remove a particle
|
navigator.clipboard.writeText(this.generateJson()).then(() => {
|
||||||
*/
|
this.matSnackBar.open('Copied to clipboard', '', {duration: 2000})
|
||||||
public removeParticle(frameId: string, index: number): void {
|
});
|
||||||
this.particleManagerService.removeParticle(frameId, index);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a frame
|
|
||||||
*/
|
|
||||||
public removeFrame(frameId: string): void {
|
|
||||||
this.frameManagerService.removeFrame(frameId);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
@import '@angular/material/prebuilt-themes/azure-blue.css';
|
@import '@angular/material/prebuilt-themes/azure-blue.css';
|
||||||
|
@import url('https://fonts.googleapis.com/icon?family=Material+Icons');
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--white: #FFFFFF;
|
--white: #FFFFFF;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user