Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11,695 changes: 6,868 additions & 4,827 deletions frontend/package-lock.json

Large diffs are not rendered by default.

31 changes: 15 additions & 16 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,33 +10,32 @@
},
"private": true,
"dependencies": {
"@angular/animations": "^17.3.0",
"@angular/common": "^17.3.0",
"@angular/compiler": "^17.3.0",
"@angular/core": "^17.3.0",
"@angular/forms": "^17.3.0",
"@angular/platform-browser": "^17.3.0",
"@angular/platform-browser-dynamic": "^17.3.0",
"@angular/router": "^17.3.0",
"@primeng/themes": "^21.0.4",
"primeflex": "^3.3.1",
"@angular/animations": "^21.0.0",
"@angular/common": "^21.0.0",
"@angular/compiler": "^21.0.0",
"@angular/core": "^21.0.0",
"@angular/forms": "^21.0.0",
"@angular/platform-browser": "^21.0.0",
"@angular/platform-browser-dynamic": "^21.0.0",
"@angular/router": "^21.0.0",
"@primeuix/themes": "^2.0.3",
"primeng": "^21.1.6",
"primeicons": "^7.0.0",
"primeng": "^17.18.15",
"rxjs": "~7.8.0",
"tslib": "^2.3.0",
"zone.js": "~0.14.3"
"zone.js": "~0.16.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "^17.3.17",
"@angular/cli": "^17.3.17",
"@angular/compiler-cli": "^17.3.0",
"@angular-devkit/build-angular": "^21.0.0",
"@angular/cli": "^21.0.0",
"@angular/compiler-cli": "^21.0.0",
"@types/jasmine": "~5.1.0",
"jasmine-core": "~5.1.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.2.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.1.0",
"typescript": "~5.4.2"
"typescript": "~5.9.0"
}
}
48 changes: 29 additions & 19 deletions frontend/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { RouterOutlet, RouterLink, RouterLinkActive } from '@angular/router';
import { CommonModule } from '@angular/common';
import {CommonModule, NgOptimizedImage} from '@angular/common';
import { Subject, takeUntil, interval } from 'rxjs';

import { MenubarModule } from 'primeng/menubar';
Expand All @@ -27,6 +27,7 @@ import { AppStore } from './store/app.store';
TagModule,
ToastModule,
ConfirmDialogModule,
NgOptimizedImage,
],
providers: [MessageService, ConfirmationService],
template: `
Expand All @@ -35,8 +36,7 @@ import { AppStore } from './store/app.store';
<!-- ── Top Nav Bar ─────────────────────────────────────────── -->
<header class="svs-topbar">
<div class="svs-topbar__brand">
<i class="pi pi-video svs-topbar__logo"></i>
<span class="svs-topbar__title">Subvision Studio</span>
<img class="svs-topbar__logo" ngSrc="../assets/img/logo.svg" width="140" height="32" alt="Logo">
<span class="svs-topbar__version">v0.1</span>
</div>

Expand Down Expand Up @@ -81,7 +81,8 @@ import { AppStore } from './store/app.store';

<!-- Backend health indicator -->
<div class="svs-status-indicator" [class.connected]="isBackendHealthy()">
<i class="pi" [class.pi-server]="isBackendHealthy()" [class.pi-exclamation-triangle]="!isBackendHealthy()"></i>
<i class="pi" [class.pi-server]="isBackendHealthy()"
[class.pi-exclamation-triangle]="!isBackendHealthy()"></i>
<span>{{ isBackendHealthy() ? 'Backend' : 'Backend ⚠' }}</span>
</div>

Expand All @@ -101,13 +102,13 @@ import { AppStore } from './store/app.store';

<!-- ── Main Content ────────────────────────────────────────── -->
<main class="svs-content">
<router-outlet />
<router-outlet/>
</main>

</div>

<p-toast position="bottom-right" [life]="4000" />
<p-confirmDialog />
<p-toast position="bottom-right" [life]="4000"/>
<p-confirmDialog/>
`,
styles: [`
.svs-shell {
Expand All @@ -121,12 +122,13 @@ import { AppStore } from './store/app.store';
.svs-topbar {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 1rem;
height: 44px;
min-height: 44px;
height: auto;
background: var(--svs-bg-surface);
border-bottom: 1px solid var(--svs-border);
padding: 0 1rem;
padding: 0.5rem 1rem;
z-index: 100;
}

Expand All @@ -136,10 +138,7 @@ import { AppStore } from './store/app.store';
gap: 0.5rem;
min-width: 180px;
}
.svs-topbar__logo {
color: var(--svs-accent);
font-size: 1.1rem;
}

.svs-topbar__title {
font-weight: 700;
font-size: 0.9rem;
Expand All @@ -158,6 +157,7 @@ import { AppStore } from './store/app.store';
.svs-topbar__nav {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 0.125rem;
flex: 1;
}
Expand All @@ -166,18 +166,28 @@ import { AppStore } from './store/app.store';
display: flex;
align-items: center;
gap: 0.35rem;
padding: 0.35rem 0.7rem;
padding: 0.35rem 0.85rem;
border-radius: var(--svs-radius-sm);
color: var(--svs-text-secondary);
text-decoration: none;
font-size: 0.78rem;
font-size: 0.8rem;
font-weight: 500;
transition: color 0.15s, background 0.15s;
transition: all 0.25s cubic-bezier(0.25, 0.8, 0.25, 1);

i { font-size: 0.8rem; }
i { font-size: 0.85rem; transition: transform 0.25s ease; }

&:hover { color: var(--svs-text-primary); background: var(--svs-bg-elevated); }
&.active { color: var(--svs-accent); background: rgba(59,130,246,0.12); }
&:hover {
color: var(--svs-text-primary);
background: rgba(255, 255, 255, 0.05);
transform: translateY(-1px);
text-shadow: 0 0 12px rgba(255, 255, 255, 0.2);
i { transform: scale(1.1); }
}
&.active {
color: var(--svs-accent);
background: rgba(99, 102, 241, 0.15);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
}
}

/* ── Status chips ───────────────────────────────────────────── */
Expand Down
52 changes: 50 additions & 2 deletions frontend/src/app/app.config.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,61 @@
import { ApplicationConfig } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideHttpClient, withFetch } from '@angular/common/http';
import { provideAnimations } from '@angular/platform-browser/animations';
import { routes } from './app.routes';
import { providePrimeNG } from 'primeng/config';
import Aura from '@primeuix/themes/aura';
import { definePreset } from '@primeuix/themes';
import { provideAnimationsAsync } from "@angular/platform-browser/animations/async";

const PremiumPreset = definePreset(Aura, {
semantic: {
primary: {
50: '#eef2ff',
100: '#e0e7ff',
200: '#c7d2fe',
300: '#a5b4fc',
400: '#818cf8',
500: '#6366f1',
600: '#4f46e5',
700: '#4338ca',
800: '#3730a3',
900: '#312e81',
950: '#1e1b4b'
},
colorScheme: {
dark: {
surface: {
0: '#ffffff',
50: '#f8fafc',
100: '#f1f5f9',
200: '#e2e8f0',
300: '#cbd5e1',
400: '#94a3b8',
500: '#64748b',
600: '#475569',
700: '#334155',
800: '#1e293b',
900: '#0f172a',
950: '#05060A'
}
}
}
}
});

export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideHttpClient(withFetch()),
provideAnimations(),
provideAnimationsAsync(),
providePrimeNG({
ripple: true,
theme: {
preset: PremiumPreset,
options: {
darkModeSelector: '.p-dark'
}
}
})
]
};
26 changes: 13 additions & 13 deletions frontend/src/app/features/cameras/cameras.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ import { TableModule } from 'primeng/table';
import { ButtonModule } from 'primeng/button';
import { DialogModule } from 'primeng/dialog';
import { InputTextModule } from 'primeng/inputtext';
import { DropdownModule } from 'primeng/dropdown';
import { InputSwitchModule } from 'primeng/inputswitch';
import { SelectModule } from 'primeng/select';
import { ToggleSwitchModule } from 'primeng/toggleswitch';
import { TagModule } from 'primeng/tag';
import { BadgeModule } from 'primeng/badge';
import { AccordionModule } from 'primeng/accordion';
import { SliderModule } from 'primeng/slider';
import { TooltipModule } from 'primeng/tooltip';
import { MessageService } from 'primeng/api';
import { ToastModule } from 'primeng/toast';

import { ApiService, Camera, CameraObsSceneOption, CameraSubscription } from '../../core/api.service';
import { AppStore } from '../../store/app.store';
import {TooltipModule} from "primeng/tooltip";

const REACTION_MODES = [
{ label: 'INFORM ONLY', value: 'INFORM_ONLY', severity: 'secondary' },
Expand All @@ -38,7 +38,7 @@ const EVENT_TYPES = [
imports: [
CommonModule, FormsModule, ReactiveFormsModule,
TableModule, ButtonModule, DialogModule, InputTextModule,
DropdownModule, InputSwitchModule, TagModule, BadgeModule,
SelectModule, ToggleSwitchModule, TagModule, BadgeModule,
AccordionModule, SliderModule, TooltipModule, ToastModule,
],
providers: [MessageService],
Expand Down Expand Up @@ -155,7 +155,7 @@ const EVENT_TYPES = [
<div class="form-row">
<div class="form-field">
<label>Source Type</label>
<p-dropdown formControlName="source_type" [options]="sourceTypes" optionLabel="label" optionValue="value" />
<p-select formControlName="source_type" [options]="sourceTypes" optionLabel="label" optionValue="value" />
</div>
<div class="form-field">
<label>Source URL / Scene Name</label>
Expand All @@ -165,7 +165,7 @@ const EVENT_TYPES = [

<div class="form-field">
<div class="switch-row">
<p-inputSwitch formControlName="enabled" />
<p-toggle-switch formControlName="enabled" />
<span>Enabled</span>
</div>
</div>
Expand All @@ -176,13 +176,13 @@ const EVENT_TYPES = [
<div formArrayName="subscriptions" class="subscriptions-list">
@for (sub of subscriptionsArray.controls; track i; let i = $index) {
<div [formGroupName]="i" class="subscription-row">
<p-dropdown
<p-select
formControlName="event_type"
[options]="eventTypeOptions"
placeholder="Event Type"
class="sub-event-select"
/>
<p-dropdown
<p-select
formControlName="mode"
[options]="modeOptions"
optionLabel="label"
Expand All @@ -193,7 +193,7 @@ const EVENT_TYPES = [
<label class="text-xs">Priority: {{ sub.get('priority')?.value }}</label>
<p-slider formControlName="priority" [min]="0" [max]="100" />
</div>
<p-inputSwitch formControlName="enabled" pTooltip="Enable subscription" />
<p-toggle-switch formControlName="enabled" pTooltip="Enable subscription" />
<p-button icon="pi pi-trash" severity="danger" [text]="true" size="small"
(onClick)="removeSubscription(i)" />
<div class="sub-scenes" formArrayName="obs_scene_options">
Expand All @@ -206,7 +206,7 @@ const EVENT_TYPES = [
<div class="sub-scenes-list">
@for (scene of getSubSceneArray(i).controls; track j; let j = $index) {
<div [formGroupName]="j" class="sub-scene-row">
<p-dropdown
<p-select
formControlName="scene_name"
[options]="obsSceneSelectOptionsFor(i)"
optionLabel="label"
Expand Down Expand Up @@ -331,9 +331,9 @@ const EVENT_TYPES = [
.text-center { text-align: center; }

:host ::ng-deep .svs-table { background: var(--svs-bg-card); }
:host ::ng-deep .sub-event-select .p-dropdown { width: 100%; }
:host ::ng-deep .sub-mode-select .p-dropdown { width: 100%; }
:host ::ng-deep .sub-scene-row .p-dropdown { width: 100%; }
:host ::ng-deep .sub-event-select .p-select { width: 100%; }
:host ::ng-deep .sub-mode-select .p-select { width: 100%; }
:host ::ng-deep .sub-scene-row .p-select { width: 100%; }
`],
})
export class CamerasComponent implements OnInit {
Expand Down
Loading
Loading