From f50c29e0a653597f278f3aefafe764b9a232d625 Mon Sep 17 00:00:00 2001 From: Nodeful Date: Fri, 2 Apr 2021 02:45:39 +0300 Subject: [PATCH] added event destroyers to all components with event listening --- ui/src/app/app.module.ts | 4 - .../components/loading/loading.component.html | 2 +- .../components/loading/loading.component.ts | 4 +- .../components/tooltip/tooltip.component.html | 10 +-- .../components/tooltip/tooltip.component.ts | 5 ++ ui/src/app/sections/effects/effect.service.ts | 10 ++- .../advanced-equalizer.component.ts | 29 ++++-- .../advanced-equalizer.service.ts | 19 +++- .../basic-equalizer.service.ts | 19 +++- .../equalizers/equalizers.component.ts | 28 ++++-- .../effects/equalizers/equalizers.service.ts | 10 ++- .../effects/reverb/reverb.component.html | 22 ----- .../effects/reverb/reverb.component.scss | 7 -- .../effects/reverb/reverb.component.ts | 89 ------------------- .../sections/effects/reverb/reverb.service.ts | 39 -------- .../app/sections/outputs/outputs.component.ts | 9 +- .../app/sections/outputs/outputs.service.ts | 19 +++- .../sections/source/file/file.component.ts | 25 ++++-- .../app/sections/source/file/file.service.ts | 20 +++-- .../sections/source/input/input.component.ts | 20 +++-- .../sections/source/input/input.service.ts | 10 ++- .../app/sections/source/source.component.ts | 20 +++-- ui/src/app/sections/source/source.service.ts | 10 ++- .../balance/balance.component.ts | 26 ++++-- .../balance/balance.service.ts | 10 ++- .../booster/booster.component.ts | 29 ++++-- .../booster/booster.service.ts | 10 ++- ui/src/app/services/bridge.service.ts | 69 ++++++++++---- ui/src/app/services/data.service.ts | 20 ++++- 29 files changed, 332 insertions(+), 262 deletions(-) delete mode 100644 ui/src/app/sections/effects/reverb/reverb.component.html delete mode 100644 ui/src/app/sections/effects/reverb/reverb.component.scss delete mode 100644 ui/src/app/sections/effects/reverb/reverb.component.ts delete mode 100644 ui/src/app/sections/effects/reverb/reverb.service.ts diff --git a/ui/src/app/app.module.ts b/ui/src/app/app.module.ts index fdef7af..85dfee1 100644 --- a/ui/src/app/app.module.ts +++ b/ui/src/app/app.module.ts @@ -38,9 +38,6 @@ import { import { EqualizersComponent } from './sections/effects/equalizers/equalizers.component' -import { - ReverbComponent -} from './sections/effects/reverb/reverb.component' import { RecorderComponent } from './sections/recorder/recorder.component' @@ -101,7 +98,6 @@ import { MatSnackBarModule } from '@angular/material/snack-bar' BoosterComponent, BalanceComponent, EqualizersComponent, - ReverbComponent, RecorderComponent, OutputsComponent, InputComponent, diff --git a/ui/src/app/modules/eqmac-components/components/loading/loading.component.html b/ui/src/app/modules/eqmac-components/components/loading/loading.component.html index f34afb0..98243b6 100644 --- a/ui/src/app/modules/eqmac-components/components/loading/loading.component.html +++ b/ui/src/app/modules/eqmac-components/components/loading/loading.component.html @@ -10,4 +10,4 @@ -Loading \ No newline at end of file +{{text || 'Loading'}} \ No newline at end of file diff --git a/ui/src/app/modules/eqmac-components/components/loading/loading.component.ts b/ui/src/app/modules/eqmac-components/components/loading/loading.component.ts index 489d99b..5fbc885 100644 --- a/ui/src/app/modules/eqmac-components/components/loading/loading.component.ts +++ b/ui/src/app/modules/eqmac-components/components/loading/loading.component.ts @@ -1,4 +1,4 @@ -import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core' +import { Component, ViewChild, ElementRef, AfterViewInit, Input } from '@angular/core' @Component({ selector: 'eqm-loading', @@ -7,6 +7,8 @@ import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core' }) export class LoadingComponent implements AfterViewInit { @ViewChild('wave', { static: true }) wave: ElementRef + @Input() text?: string + @Input() showText = true ngAfterViewInit () { const path = this.wave.nativeElement diff --git a/ui/src/app/modules/eqmac-components/components/tooltip/tooltip.component.html b/ui/src/app/modules/eqmac-components/components/tooltip/tooltip.component.html index 9bfc410..6275a1f 100644 --- a/ui/src/app/modules/eqmac-components/components/tooltip/tooltip.component.html +++ b/ui/src/app/modules/eqmac-components/components/tooltip/tooltip.component.html @@ -1,10 +1,10 @@
-
+ [hidden]="!showArrow" + class="arrow-container" + [ngStyle]="arrowStyle" + > +
diff --git a/ui/src/app/modules/eqmac-components/components/tooltip/tooltip.component.ts b/ui/src/app/modules/eqmac-components/components/tooltip/tooltip.component.ts index 4936740..1b30461 100644 --- a/ui/src/app/modules/eqmac-components/components/tooltip/tooltip.component.ts +++ b/ui/src/app/modules/eqmac-components/components/tooltip/tooltip.component.ts @@ -42,6 +42,11 @@ export class TooltipComponent implements OnInit { } get style () { + if (!this.text?.length) { + return { + display: 'none' + } + } let x = -999 let y = -999 const body = document.body diff --git a/ui/src/app/sections/effects/effect.service.ts b/ui/src/app/sections/effects/effect.service.ts index 46585de..d564c09 100644 --- a/ui/src/app/sections/effects/effect.service.ts +++ b/ui/src/app/sections/effects/effect.service.ts @@ -16,7 +16,13 @@ export class EffectService extends DataService { return this.request({ method: 'POST', endpoint: '/enabled', data: { enabled } }) } - onEnabledChanged (callback: (enabled: boolean) => void) { - this.on('/enabled', ({ enabled }) => callback(enabled)) + onEnabledChanged (callback: EffectEnabledChangedEventCallback) { + this.on('/enabled', callback) + } + + offEnabledChanged (callback: EffectEnabledChangedEventCallback) { + this.off('/enabled', callback) } } + +export type EffectEnabledChangedEventCallback = (data: { enabled: boolean }) => void diff --git a/ui/src/app/sections/effects/equalizers/advanced-equalizer/advanced-equalizer.component.ts b/ui/src/app/sections/effects/equalizers/advanced-equalizer/advanced-equalizer.component.ts index da03137..74817b5 100644 --- a/ui/src/app/sections/effects/equalizers/advanced-equalizer/advanced-equalizer.component.ts +++ b/ui/src/app/sections/effects/equalizers/advanced-equalizer/advanced-equalizer.component.ts @@ -4,9 +4,10 @@ import { Input, EventEmitter, Output, - ChangeDetectorRef + ChangeDetectorRef, + OnDestroy } from '@angular/core' -import { AdvancedEqualizerService, AdvancedEqualizerPreset } from 'src/app/sections/effects/equalizers/advanced-equalizer/advanced-equalizer.service' +import { AdvancedEqualizerService, AdvancedEqualizerPreset, AdvancedEqualizerPresetsChangedEventCallback, AdvancedEqualizerSelectedPresetChangedEventCallback } from 'src/app/sections/effects/equalizers/advanced-equalizer/advanced-equalizer.service' import { EqualizerComponent } from '../equalizer.component' import { Options, CheckboxOption } from 'src/app/components/options/options.component' import { TransitionService } from '../../../../services/transitions.service' @@ -17,7 +18,7 @@ import { ApplicationService } from '../../../../services/app.service' templateUrl: './advanced-equalizer.component.html', styleUrls: [ './advanced-equalizer.component.scss' ] }) -export class AdvancedEqualizerComponent extends EqualizerComponent implements OnInit { +export class AdvancedEqualizerComponent extends EqualizerComponent implements OnInit, OnDestroy { @Input() enabled = true public ShowDefaultPresetsCheckbox: CheckboxOption = { @@ -159,15 +160,25 @@ export class AdvancedEqualizerComponent extends EqualizerComponent implements On this.ShowDefaultPresetsCheckbox.value = await this.service.getShowDefaultPresets() } + private onPresetsChangedEventCallback: AdvancedEqualizerPresetsChangedEventCallback + private onSelectedPresetChangedEventCallback: AdvancedEqualizerSelectedPresetChangedEventCallback protected setupEvents () { - this.service.onPresetsChanged(presets => { + this.onPresetsChangedEventCallback = presets => { if (!presets) return this.presets = presets - }) - this.service.onSelectedPresetChanged(preset => { + } + this.service.onPresetsChanged(this.onPresetsChangedEventCallback) + + this.onSelectedPresetChangedEventCallback = preset => { this.selectedPreset = preset this.setSelectedPresetsGains() - }) + } + this.service.onSelectedPresetChanged(this.onSelectedPresetChangedEventCallback) + } + + private destroyEvents () { + this.service.offPresetsChanged(this.onPresetsChangedEventCallback) + this.service.offSelectedPresetChanged(this.onSelectedPresetChangedEventCallback) } async selectPreset (preset: AdvancedEqualizerPreset) { @@ -248,4 +259,8 @@ export class AdvancedEqualizerComponent extends EqualizerComponent implements On get globalGainScreenValue () { return `${this.selectedPreset.gains.global > 0 ? '+' : ''}${(this.selectedPreset.gains.global.toFixed(1))}dB` } + + ngOnDestroy () { + this.destroyEvents() + } } diff --git a/ui/src/app/sections/effects/equalizers/advanced-equalizer/advanced-equalizer.service.ts b/ui/src/app/sections/effects/equalizers/advanced-equalizer/advanced-equalizer.service.ts index b457924..de29436 100644 --- a/ui/src/app/sections/effects/equalizers/advanced-equalizer/advanced-equalizer.service.ts +++ b/ui/src/app/sections/effects/equalizers/advanced-equalizer/advanced-equalizer.service.ts @@ -80,11 +80,22 @@ export class AdvancedEqualizerService extends EqualizersService { return this.request({ method: 'POST', endpoint: '/settings/show-default-presets', data: { show } }) } - onPresetsChanged (callback: (presets: AdvancedEqualizerPreset[]) => void) { - this.on('/presets', (presets) => callback(presets)) + onPresetsChanged (callback: AdvancedEqualizerPresetsChangedEventCallback) { + this.on('/presets', callback) } - onSelectedPresetChanged (callback: (preset: AdvancedEqualizerPreset) => void) { - this.on('/presets/selected', (preset) => callback(preset)) + offPresetsChanged (callback: AdvancedEqualizerPresetsChangedEventCallback) { + this.off('/presets', callback) + } + + onSelectedPresetChanged (callback: AdvancedEqualizerSelectedPresetChangedEventCallback) { + this.on('/presets/selected', callback) + } + + offSelectedPresetChanged (callback: AdvancedEqualizerSelectedPresetChangedEventCallback) { + this.off('/presets/selected', callback) } } + +export type AdvancedEqualizerPresetsChangedEventCallback = (presets: AdvancedEqualizerPreset[]) => void +export type AdvancedEqualizerSelectedPresetChangedEventCallback = (preset: AdvancedEqualizerPreset) => void diff --git a/ui/src/app/sections/effects/equalizers/basic-equalizer/basic-equalizer.service.ts b/ui/src/app/sections/effects/equalizers/basic-equalizer/basic-equalizer.service.ts index 04e442f..964758e 100644 --- a/ui/src/app/sections/effects/equalizers/basic-equalizer/basic-equalizer.service.ts +++ b/ui/src/app/sections/effects/equalizers/basic-equalizer/basic-equalizer.service.ts @@ -53,11 +53,22 @@ export class BasicEqualizerService extends EqualizersService { return this.request({ method: 'DELETE', endpoint: '/presets', data: { ...preset } as any }) } - onPresetsChanged (callback: (presets: BasicEqualizerPreset[]) => void) { - this.on('/presets', (presets) => callback(presets)) + onPresetsChanged (callback: BasicEqualizerPresetsChangedEventCallback) { + this.on('/presets', callback) } - onSelectedPresetChanged (callback: (preset: BasicEqualizerPreset) => void) { - this.on('/presets/selected', (preset) => callback(preset)) + offPresetsChanged (callback: BasicEqualizerPresetsChangedEventCallback) { + this.off('/presets', callback) + } + + onSelectedPresetChanged (callback: BasicEqualizerSelectedPresetChangedEventCallback) { + this.on('/presets/selected', callback) + } + + offSelectedPresetChanged (callback: BasicEqualizerSelectedPresetChangedEventCallback) { + this.off('/presets/selected', callback) } } + +export type BasicEqualizerPresetsChangedEventCallback = (presets: BasicEqualizerPreset[]) => void +export type BasicEqualizerSelectedPresetChangedEventCallback = (preset: BasicEqualizerPreset) => void diff --git a/ui/src/app/sections/effects/equalizers/equalizers.component.ts b/ui/src/app/sections/effects/equalizers/equalizers.component.ts index 338ea24..f73b083 100644 --- a/ui/src/app/sections/effects/equalizers/equalizers.component.ts +++ b/ui/src/app/sections/effects/equalizers/equalizers.component.ts @@ -1,5 +1,5 @@ -import { Component, OnInit, EventEmitter, Output, ViewChild, Input, ChangeDetectorRef } from '@angular/core' -import { EqualizersService, EqualizerType } from './equalizers.service' +import { Component, OnInit, EventEmitter, Output, ViewChild, Input, ChangeDetectorRef, OnDestroy } from '@angular/core' +import { EqualizersService, EqualizersTypeChangedEventCallback, EqualizerType } from './equalizers.service' import { BasicEqualizerComponent } from './basic-equalizer/basic-equalizer.component' import { AdvancedEqualizerComponent } from './advanced-equalizer/advanced-equalizer.component' import { EqualizerComponent } from './equalizer.component' @@ -8,6 +8,7 @@ import { MatDialog, MatDialogRef } from '@angular/material/dialog' import { OptionsDialogComponent } from '../../../components/options-dialog/options-dialog.component' import { EqualizerPreset } from './presets/equalizer-presets.component' import { UIService } from '../../../services/ui.service' +import { EffectEnabledChangedEventCallback } from '../effect.service' @Component({ selector: 'eqm-equalizers', @@ -15,7 +16,7 @@ import { UIService } from '../../../services/ui.service' styleUrls: [ './equalizers.component.scss' ], animations: [ FadeInOutAnimation ] }) -export class EqualizersComponent implements OnInit { +export class EqualizersComponent implements OnInit, OnDestroy { @Input() animationDuration = 500 @Input() animationFps = 30 @@ -75,14 +76,23 @@ export class EqualizersComponent implements OnInit { this.enabled = await this.equalizersService.getEnabled() } + private onEnabledChangedEventCallback: EffectEnabledChangedEventCallback + private onTypeChangedEventCallback: EqualizersTypeChangedEventCallback protected setupEvents () { - this.equalizersService.onEnabledChanged(enabled => { + this.onEnabledChangedEventCallback = ({ enabled }) => { this.enabled = enabled - }) + } + this.equalizersService.onEnabledChanged(this.onEnabledChangedEventCallback) - this.equalizersService.onTypeChanged(type => { + this.onTypeChangedEventCallback = ({ type }) => { this.type = type - }) + } + this.equalizersService.onTypeChanged(this.onTypeChangedEventCallback) + } + + private destroyEvents () { + this.equalizersService.offEnabledChanged(this.onEnabledChangedEventCallback) + this.equalizersService.offTypeChanged(this.onTypeChangedEventCallback) } setEnabled () { @@ -133,4 +143,8 @@ export class EqualizersComponent implements OnInit { selectPreset (preset: EqualizerPreset) { return this.activeEqualizer.selectPreset(preset) } + + ngOnDestroy () { + this.destroyEvents() + } } diff --git a/ui/src/app/sections/effects/equalizers/equalizers.service.ts b/ui/src/app/sections/effects/equalizers/equalizers.service.ts index 153488e..60ba6fb 100644 --- a/ui/src/app/sections/effects/equalizers/equalizers.service.ts +++ b/ui/src/app/sections/effects/equalizers/equalizers.service.ts @@ -22,7 +22,13 @@ export class EqualizersService extends EffectService { return this.request({ method: 'POST', endpoint: '/type', data: { type } }) } - onTypeChanged (callback: (type: EqualizerType) => void) { - this.on('/type', ({ type }) => callback(type)) + onTypeChanged (callback: EqualizersTypeChangedEventCallback) { + this.on('/type', callback) + } + + offTypeChanged (callback: EqualizersTypeChangedEventCallback) { + this.off('/type', callback) } } + +export type EqualizersTypeChangedEventCallback = (data: { type: EqualizerType }) => void diff --git a/ui/src/app/sections/effects/reverb/reverb.component.html b/ui/src/app/sections/effects/reverb/reverb.component.html deleted file mode 100644 index 0a19372..0000000 --- a/ui/src/app/sections/effects/reverb/reverb.component.html +++ /dev/null @@ -1,22 +0,0 @@ -
-
- - Reverb - -
-
-
- -
-
- Preset - -
-
- Dry/Wet Mix - {{formatDryWetMixValue()}}% -
-
-
-
-
\ No newline at end of file diff --git a/ui/src/app/sections/effects/reverb/reverb.component.scss b/ui/src/app/sections/effects/reverb/reverb.component.scss deleted file mode 100644 index 9023964..0000000 --- a/ui/src/app/sections/effects/reverb/reverb.component.scss +++ /dev/null @@ -1,7 +0,0 @@ -.reverb { - position: relative; - - .reverb-controls{ - // filter: grayscale(100%); - } -} \ No newline at end of file diff --git a/ui/src/app/sections/effects/reverb/reverb.component.ts b/ui/src/app/sections/effects/reverb/reverb.component.ts deleted file mode 100644 index 406c917..0000000 --- a/ui/src/app/sections/effects/reverb/reverb.component.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { - Component, - OnInit, - EventEmitter, - Output -} from '@angular/core' -import { - ReverbService -} from './reverb.service' - -@Component({ - selector: 'eqm-reverb', - templateUrl: './reverb.component.html', - styleUrls: [ './reverb.component.scss' ] -}) -export class ReverbComponent implements OnInit { - presets = [] - dryWetMix = 0.5 - enabled = true - selectedPreset = null - hide = false - - @Output() visibilityToggled = new EventEmitter() - - constructor (public reverbService: ReverbService) {} - - ngOnInit () { - this.getPresets() - this.sync() - this.setupEvents() - } - - protected setupEvents () { - this.reverbService.onEnabledChanged(enabled => { - this.enabled = enabled - }) - - this.reverbService.onMixChanged(mix => { - this.dryWetMix = mix - }) - - this.reverbService.onPresetChanged(preset => { - this.selectedPreset = preset - }) - } - - async sync () { - if (this.presets.length === 0) { - this.getPresets() - } - this.getSelectedPreset() - this.getMix() - this.getEnabled() - } - - async getPresets () { - this.presets = await this.reverbService.getPresets() - } - - async getSelectedPreset () { - const selectedPresetId = await this.reverbService.getSelectedPresetId() - this.selectedPreset = this.presets.find(preset => preset.id === selectedPresetId) - } - - async getMix () { - this.dryWetMix = await this.reverbService.getMix() - } - - setMix () { - this.reverbService.setMix(this.dryWetMix) - } - - async getEnabled () { - this.enabled = await this.reverbService.getEnabled() - } - - setEnabled () { - this.reverbService.setEnabled(this.enabled) - } - - formatDryWetMixValue () { - return Math.round(this.dryWetMix * 100) - } - - toggleVisibility () { - this.hide = !this.hide - this.visibilityToggled.emit() - } -} diff --git a/ui/src/app/sections/effects/reverb/reverb.service.ts b/ui/src/app/sections/effects/reverb/reverb.service.ts deleted file mode 100644 index adf7a54..0000000 --- a/ui/src/app/sections/effects/reverb/reverb.service.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Injectable } from '@angular/core' -import { EffectService } from '../effect.service' - -@Injectable({ - providedIn: 'root' -}) -export class ReverbService extends EffectService { - route = `${this.route}/reverb` - - async getMix () { - const resp = await this.request({ method: 'GET', endpoint: '/mix' }) - return resp.mix - } - - setMix (mix) { - return this.request({ method: 'POST', endpoint: '/mix', data: { mix } }) - } - - async getPresets () { - return this.request({ method: 'GET', endpoint: '/presets' }) - } - - async getSelectedPresetId () { - const resp = await this.request({ method: 'GET', endpoint: '/preset' }) - return resp.id - } - - setPreset (id) { - return this.request({ method: 'POST', endpoint: '/preset', data: { id } }) - } - - onMixChanged (callback: (mix: number) => void) { - this.on(`${this.route}/mix`, ({ mix }) => callback(mix)) - } - - onPresetChanged (callback: (id: number) => void) { - this.on(`${this.route}/preset`, ({ preset }) => callback(preset)) - } -} diff --git a/ui/src/app/sections/outputs/outputs.component.ts b/ui/src/app/sections/outputs/outputs.component.ts index 426e46b..d88871c 100644 --- a/ui/src/app/sections/outputs/outputs.component.ts +++ b/ui/src/app/sections/outputs/outputs.component.ts @@ -50,7 +50,12 @@ export class OutputsComponent implements OnInit { } setupEventListeners () { - this.service.onChanged(() => this.sync()) - this.service.onDevicesChanged(() => this.sync()) + this.service.onSelectedChanged(this.sync.bind(this)) + this.service.onDevicesChanged(this.sync.bind(this)) + } + + destroyEvents () { + this.service.offSelectedChanged(this.sync) + this.service.offDevicesChanged(this.sync) } } diff --git a/ui/src/app/sections/outputs/outputs.service.ts b/ui/src/app/sections/outputs/outputs.service.ts index 776b0d3..32a6667 100644 --- a/ui/src/app/sections/outputs/outputs.service.ts +++ b/ui/src/app/sections/outputs/outputs.service.ts @@ -40,11 +40,22 @@ export class OutputsService extends DataService { return this.request({ method: 'POST', endpoint: '/selected', data: { ...output } }) } - onChanged (callback: (id: number) => void) { - this.on('/selected', ({ id }) => callback(id)) + onSelectedChanged (callback: OutputsSelectedChangedEventCallback) { + this.on('/selected', callback) } - onDevicesChanged (callback: (devices: Output[]) => void) { - this.on('/devices', devices => callback(devices)) + offSelectedChanged (callback: OutputsSelectedChangedEventCallback) { + this.off('/selected', callback) + } + + onDevicesChanged (callback: OutputsDevicesChangedEventCallback) { + this.on('/devices', callback) + } + + offDevicesChanged (callback: OutputsDevicesChangedEventCallback) { + this.off('/devices', callback) } } + +export type OutputsSelectedChangedEventCallback = (data: { id: number }) => void +export type OutputsDevicesChangedEventCallback = (devices: Output[]) => void diff --git a/ui/src/app/sections/source/file/file.component.ts b/ui/src/app/sections/source/file/file.component.ts index 23529bb..9ec999a 100644 --- a/ui/src/app/sections/source/file/file.component.ts +++ b/ui/src/app/sections/source/file/file.component.ts @@ -7,6 +7,8 @@ import { UtilitiesService } from '../../../services/utilities.service' import { + FilePlayingChangedEventCallback, + FileProgressChangedEventCallback, FileService } from './file.service' import { ApplicationService } from '../../../services/app.service' @@ -49,6 +51,7 @@ export class FileComponent implements OnInit, OnDestroy { if (this.progressProjectionInterval) { clearInterval(this.progressProjectionInterval) } + this.destroyEvents() } public setDefaultMeta () { @@ -151,13 +154,23 @@ export class FileComponent implements OnInit, OnDestroy { }, 10) } - protected setupEvents () { - this.fileService.onPlayingChanged(playing => { - this.playing = playing - }) + private onPlayingChangedEventCallback: FilePlayingChangedEventCallback + private onProgressChangedEventCallback: FileProgressChangedEventCallback - this.fileService.onProgressChanged(progress => { + protected setupEvents () { + this.onPlayingChangedEventCallback = ({ playing }) => { + this.playing = playing + } + this.fileService.onPlayingChanged(this.onPlayingChangedEventCallback) + + this.onProgressChangedEventCallback = ({ progress }) => { this.progress = progress - }) + } + this.fileService.onProgressChanged(this.onProgressChangedEventCallback) + } + + private destroyEvents () { + this.fileService.offPlayingChanged(this.onPlayingChangedEventCallback) + this.fileService.offProgressChanged(this.onProgressChangedEventCallback) } } diff --git a/ui/src/app/sections/source/file/file.service.ts b/ui/src/app/sections/source/file/file.service.ts index d2b5d1d..52610e8 100644 --- a/ui/src/app/sections/source/file/file.service.ts +++ b/ui/src/app/sections/source/file/file.service.ts @@ -4,7 +4,6 @@ import { SourceService } from '../source.service' @Injectable({ providedIn: 'root' }) -@Injectable() export class FileService extends SourceService { route = `${this.route}/file` @@ -45,11 +44,22 @@ export class FileService extends SourceService { return resp.playing } - onPlayingChanged (callback: (playing: boolean) => void) { - this.on('/playing', ({ playing }) => callback(playing)) + onPlayingChanged (callback: FilePlayingChangedEventCallback) { + this.on('/playing', callback) } - onProgressChanged (callback: (progress: number) => void) { - this.on('/progress', ({ progress }) => callback(progress)) + offPlayingChanged (callback: FilePlayingChangedEventCallback) { + this.off('/playing', callback) + } + + onProgressChanged (callback: FileProgressChangedEventCallback) { + this.on('/progress', callback) + } + + offProgressChanged (callback: FileProgressChangedEventCallback) { + this.off('/progress', callback) } } + +export type FilePlayingChangedEventCallback = (data: { playing: boolean }) => void +export type FileProgressChangedEventCallback = (data: { progress: number }) => void diff --git a/ui/src/app/sections/source/input/input.component.ts b/ui/src/app/sections/source/input/input.component.ts index df0bec7..6601194 100644 --- a/ui/src/app/sections/source/input/input.component.ts +++ b/ui/src/app/sections/source/input/input.component.ts @@ -1,5 +1,5 @@ -import { Component, OnInit } from '@angular/core' -import { InputService } from './input.service' +import { Component, OnDestroy, OnInit } from '@angular/core' +import { InputDeviceChangedEventCallback, InputService } from './input.service' export interface InputDevice { deviceId: number @@ -12,7 +12,7 @@ export interface InputDevice { templateUrl: './input.component.html', styleUrls: [ './input.component.scss' ] }) -export class InputComponent implements OnInit { +export class InputComponent implements OnInit, OnDestroy { constructor (public inputService: InputService) { } devices: InputDevice[] = [] @@ -38,10 +38,16 @@ export class InputComponent implements OnInit { await this.inputService.getDevice() } + private onDeviceChangedEventCallback: InputDeviceChangedEventCallback protected setupEvents () { - this.inputService.onDeviceChanged((deviceId) => { + this.onDeviceChangedEventCallback = ({ deviceId }) => { this.setDeviceById(deviceId) - }) + } + this.inputService.onDeviceChanged(this.onDeviceChangedEventCallback) + } + + private destroyEvents () { + this.inputService.offDeviceChanged(this.onDeviceChangedEventCallback) } deviceSelected (device) { @@ -61,4 +67,8 @@ export class InputComponent implements OnInit { } this.selectedDevice = device } + + ngOnDestroy () { + this.destroyEvents() + } } diff --git a/ui/src/app/sections/source/input/input.service.ts b/ui/src/app/sections/source/input/input.service.ts index f691f45..79e7a58 100644 --- a/ui/src/app/sections/source/input/input.service.ts +++ b/ui/src/app/sections/source/input/input.service.ts @@ -20,7 +20,13 @@ export class InputService extends SourceService { return this.request({ method: 'POST', endpoint: '/device', data: { deviceId } }) } - onDeviceChanged (callback: (deviceId: number) => void) { - this.on(({ deviceId }) => callback(deviceId)) + onDeviceChanged (callback: InputDeviceChangedEventCallback) { + this.on(callback) + } + + offDeviceChanged (callback: InputDeviceChangedEventCallback) { + this.off(callback) } } + +export type InputDeviceChangedEventCallback = (data: { deviceId: number }) => void diff --git a/ui/src/app/sections/source/source.component.ts b/ui/src/app/sections/source/source.component.ts index fd0c973..1b6af80 100644 --- a/ui/src/app/sections/source/source.component.ts +++ b/ui/src/app/sections/source/source.component.ts @@ -1,12 +1,12 @@ -import { Component, OnInit } from '@angular/core' -import { SourceService, SourceType } from './source.service' +import { Component, OnDestroy, OnInit } from '@angular/core' +import { SourceChangedEventCallback, SourceService, SourceType } from './source.service' @Component({ selector: 'eqm-source', templateUrl: './source.component.html', styleUrls: [ './source.component.scss' ] }) -export class SourceComponent implements OnInit { +export class SourceComponent implements OnInit, OnDestroy { source: SourceType = 'File' constructor (public sourceService: SourceService) { } @@ -14,10 +14,16 @@ export class SourceComponent implements OnInit { this.setupEvents() } + private onSourceChangedEventCallback: SourceChangedEventCallback protected setupEvents () { - this.sourceService.onSourceChanged(source => { + this.onSourceChangedEventCallback = ({ source }) => { this.source = source - }) + } + this.sourceService.onSourceChanged(this.onSourceChangedEventCallback) + } + + private destroyEvents () { + this.sourceService.offSourceChanged(this.onSourceChangedEventCallback) } async setSource (source: SourceType) { @@ -26,4 +32,8 @@ export class SourceComponent implements OnInit { this.sourceService.setSource(this.source) } } + + ngOnDestroy () { + this.destroyEvents() + } } diff --git a/ui/src/app/sections/source/source.service.ts b/ui/src/app/sections/source/source.service.ts index 64dd272..8ae05c5 100644 --- a/ui/src/app/sections/source/source.service.ts +++ b/ui/src/app/sections/source/source.service.ts @@ -18,7 +18,13 @@ export class SourceService extends DataService { return this.request({ method: 'POST', endpoint: '', data: { source } }) } - onSourceChanged (callback: (source: SourceType) => void) { - this.on(({ source }) => callback(source)) + onSourceChanged (callback: SourceChangedEventCallback) { + this.on(callback) + } + + offSourceChanged (callback: SourceChangedEventCallback) { + this.off(callback) } } + +export type SourceChangedEventCallback = (data: { source: SourceType }) => void diff --git a/ui/src/app/sections/volume/booster-balance/balance/balance.component.ts b/ui/src/app/sections/volume/booster-balance/balance/balance.component.ts index b80a7c8..ec5e5c9 100644 --- a/ui/src/app/sections/volume/booster-balance/balance/balance.component.ts +++ b/ui/src/app/sections/volume/booster-balance/balance/balance.component.ts @@ -1,16 +1,17 @@ -import { Component, OnInit, Input } from '@angular/core' -import { BalanceService } from './balance.service' +import { Component, OnInit, Input, OnDestroy } from '@angular/core' +import { BalanceChangedEventCallback, BalanceService } from './balance.service' import { ApplicationService } from '../../../../services/app.service' import { KnobValueChangedEvent } from '../../../../modules/eqmac-components/components/knob/knob.component' import { FlatSliderValueChangedEvent } from '../../../../modules/eqmac-components/components/flat-slider/flat-slider.component' import { UIService } from '../../../../services/ui.service' +import { Subscription } from 'rxjs' @Component({ selector: 'eqm-balance', templateUrl: './balance.component.html', styleUrls: [ './balance.component.scss' ] }) -export class BalanceComponent implements OnInit { +export class BalanceComponent implements OnInit, OnDestroy { balance = 0 @Input() animationDuration = 500 @Input() animationFps = 30 @@ -40,15 +41,24 @@ export class BalanceComponent implements OnInit { this.replaceKnobsWithSliders = uiSettings.replaceKnobsWithSliders } + private onBalanceChangedEventCallback: BalanceChangedEventCallback + private onUISettingsChangedSubscription: Subscription protected setupEvents () { - this.balanceService.onBalanceChanged((balance) => { + this.onBalanceChangedEventCallback = ({ balance }) => { this.balance = balance - }) - this.ui.settingsChanged.subscribe(uiSettings => { + } + this.balanceService.onBalanceChanged(this.onBalanceChangedEventCallback) + + this.onUISettingsChangedSubscription = this.ui.settingsChanged.subscribe(uiSettings => { this.replaceKnobsWithSliders = uiSettings.replaceKnobsWithSliders }) } + protected destroyEvents () { + this.balanceService.offBalanceChanged(this.onBalanceChangedEventCallback) + this.onUISettingsChangedSubscription?.unsubscribe() + } + async getBalance () { this.balance = await this.balanceService.getBalance() } @@ -62,4 +72,8 @@ export class BalanceComponent implements OnInit { this.app.haptic() } } + + ngOnDestroy () { + this.destroyEvents() + } } diff --git a/ui/src/app/sections/volume/booster-balance/balance/balance.service.ts b/ui/src/app/sections/volume/booster-balance/balance/balance.service.ts index 51bcfc2..3fb1a45 100644 --- a/ui/src/app/sections/volume/booster-balance/balance/balance.service.ts +++ b/ui/src/app/sections/volume/booster-balance/balance/balance.service.ts @@ -14,7 +14,13 @@ export class BalanceService extends VolumeService { return this.request({ method: 'POST', endpoint: '/balance', data: { balance, transition } }) } - onBalanceChanged (callback: (balance: number) => void) { - this.on(({ balance }) => callback(balance)) + onBalanceChanged (callback: BalanceChangedEventCallback) { + this.on(callback) + } + + offBalanceChanged (callback: BalanceChangedEventCallback) { + this.off(callback) } } + +export type BalanceChangedEventCallback = (data: { balance: number }) => void diff --git a/ui/src/app/sections/volume/booster-balance/booster/booster.component.ts b/ui/src/app/sections/volume/booster-balance/booster/booster.component.ts index c64cc95..a397217 100644 --- a/ui/src/app/sections/volume/booster-balance/booster/booster.component.ts +++ b/ui/src/app/sections/volume/booster-balance/booster/booster.component.ts @@ -2,19 +2,21 @@ import { Component, OnInit, Input, - ChangeDetectorRef + ChangeDetectorRef, + OnDestroy } from '@angular/core' -import { BoosterService } from './booster.service' +import { BoosterGainChangedEventCallback, BoosterService } from './booster.service' import { ApplicationService } from '../../../../services/app.service' -import { UIService } from '../../../../services/ui.service' +import { UIService, UISettings } from '../../../../services/ui.service' +import { Subscription } from 'rxjs' @Component({ selector: 'eqm-booster', templateUrl: './booster.component.html', styleUrls: [ './booster.component.scss' ] }) -export class BoosterComponent implements OnInit { +export class BoosterComponent implements OnInit, OnDestroy { gain = 1 replaceKnobsWithSliders = false @Input() hide = false @@ -40,18 +42,27 @@ export class BoosterComponent implements OnInit { public ignoreUpdates = false public ignoreUpdatesDebouncer: NodeJS.Timer + private onGainChangedEventCallback: BoosterGainChangedEventCallback + private onUISettingsChangedEventSubscription: Subscription protected setupEvents () { - this.boosterService.onGainChanged(gain => { + this.onGainChangedEventCallback = ({ gain }) => { if (!this.ignoreUpdates) { this.gain = gain this.changeRef.detectChanges() } - }) - this.ui.settingsChanged.subscribe(uiSettings => { + } + this.boosterService.onGainChanged(this.onGainChangedEventCallback) + + this.onUISettingsChangedEventSubscription = this.ui.settingsChanged.subscribe(uiSettings => { this.replaceKnobsWithSliders = uiSettings.replaceKnobsWithSliders }) } + protected destroyEvents () { + this.boosterService.offGainChanged(this.onGainChangedEventCallback) + this.onUISettingsChangedEventSubscription.unsubscribe() + } + async syncUISettings () { const uiSettings = await this.ui.getSettings() this.replaceKnobsWithSliders = uiSettings.replaceKnobsWithSliders @@ -76,4 +87,8 @@ export class BoosterComponent implements OnInit { this.app.haptic() } } + + ngOnDestroy () { + this.destroyEvents() + } } diff --git a/ui/src/app/sections/volume/booster-balance/booster/booster.service.ts b/ui/src/app/sections/volume/booster-balance/booster/booster.service.ts index 99b708a..b38c9db 100644 --- a/ui/src/app/sections/volume/booster-balance/booster/booster.service.ts +++ b/ui/src/app/sections/volume/booster-balance/booster/booster.service.ts @@ -15,7 +15,13 @@ export class BoosterService extends VolumeService { this.request({ method: 'POST', data: { gain, transition } }) } - onGainChanged (callback: (gain: number) => void) { - this.on(({ gain }) => callback(gain)) + onGainChanged (callback: BoosterGainChangedEventCallback) { + this.on(callback) + } + + offGainChanged (callback: BoosterGainChangedEventCallback) { + this.off(callback) } } + +export type BoosterGainChangedEventCallback = (data: { gain: number }) => void diff --git a/ui/src/app/services/bridge.service.ts b/ui/src/app/services/bridge.service.ts index 740e879..d1b0831 100644 --- a/ui/src/app/services/bridge.service.ts +++ b/ui/src/app/services/bridge.service.ts @@ -17,9 +17,12 @@ type CallHandlerCallback = (data?: BridgeResponseData) => void type RegisterHandler = (handler: string, cb: RegisterHandlerCallback) => void type RegisterHandlerCallback = (data: JSONData, cb: (data?: BridgeResponseData) => void) => void +type EventHandler = (data: JSONData, res: BridgeResponse) => void | Promise + interface JSBridge { callHandler: CallHandler registerHandler: RegisterHandler + disableJavscriptAlertBoxSafetyTimeout: () => void } /** @@ -32,6 +35,10 @@ interface JSBridge { export class BridgeService { public static bridgeLoadTimeout = 10000 public static bridgeLoadPromise: Promise = null + private static didSpeedUp = false + private static readonly handlers: { + [event: string]: EventHandler[] + } = {} public get bridge () { if (BridgeService.bridgeLoadPromise) { @@ -66,6 +73,10 @@ export class BridgeService { async call (handler: string, data?: JSONData): Promise { return new Promise(async (resolve, reject) => { const bridge = await this.bridge + if (!BridgeService.didSpeedUp) { + BridgeService.didSpeedUp = true + bridge.disableJavscriptAlertBoxSafetyTimeout() + } bridge.callHandler(handler, data, res => { const err = res.error return err ? reject(new Error(err)) : resolve(res.data) @@ -73,22 +84,48 @@ export class BridgeService { }) } - async on (event: string, handler: (data: JSONData, res: BridgeResponse) => void | Promise) { + async on (event: string, handler: EventHandler) { const bridge = await this.bridge - bridge.registerHandler(event, async (data, cb) => { - const handleError = (err: string | Error) => { - console.error(err) - // eslint-disable-next-line @typescript-eslint/no-base-to-string - cb({ error: err.toString() }) - } - try { - await handler(data, { - send: (data) => cb({ data }), - error: (err) => handleError(err) - }) - } catch (err) { - handleError(err) - } - }) + let shouldRegister = false + if (!(event in BridgeService.handlers)) { + BridgeService.handlers[event] = [] + shouldRegister = true + } + + BridgeService.handlers[event].push(handler) + + if (shouldRegister) { + bridge.registerHandler(event, async (data, cb) => { + const handleError = (err: string | Error) => { + console.error(err) + // eslint-disable-next-line @typescript-eslint/no-base-to-string + cb({ error: err.toString() }) + } + + for (const handler of BridgeService.handlers[event]) { + try { + await handler(data, { + send: (data) => cb({ data }), + error: (err) => handleError(err) + }) + } catch (err) { + handleError(err) + } + } + }) + } + } + + async off (event: string, handler: EventHandler) { + if (!BridgeService.handlers[event]?.length) { + console.error(`Trying to unsubscribe from event: "${event}" when there are no handlers registered`) + return + } + const index = BridgeService.handlers[event]?.indexOf(handler) + if (index > -1) { + BridgeService.handlers[event].splice(index, 1) + } else { + console.error(`Trying to unsubscribe from event: "${event}" with a handler that is not registered`) + } } } diff --git a/ui/src/app/services/data.service.ts b/ui/src/app/services/data.service.ts index 8100be9..212335b 100644 --- a/ui/src/app/services/data.service.ts +++ b/ui/src/app/services/data.service.ts @@ -31,11 +31,23 @@ export class DataService { return resp } + private normalizeEventCallback (eventOrCallback: string | EventCallback, callback?: EventCallback) { + const event = typeof eventOrCallback === 'string' ? eventOrCallback : '' + callback = typeof eventOrCallback === 'function' ? eventOrCallback : callback + return { event, callback } + } + async on (callback: EventCallback) async on (event: string, callback: EventCallback) - async on (eventOrCallback: string | EventCallback, callback?: EventCallback) { - const eventName = typeof eventOrCallback === 'string' ? eventOrCallback : '' - callback = typeof eventOrCallback === 'function' ? eventOrCallback : callback - this.bridge.on(`${this.route}${eventName}`, callback) + async on (eventOrCallback: string | EventCallback, cb?: EventCallback) { + const { event, callback } = this.normalizeEventCallback(eventOrCallback, cb) + this.bridge.on(`${this.route}${event}`, callback) + } + + async off (callback: EventCallback) + async off (event: string, callback: EventCallback) + async off (eventOrCallback: string | EventCallback, cb?: EventCallback) { + const { event, callback } = this.normalizeEventCallback(eventOrCallback, cb) + this.bridge.off(`${this.route}${event}`, callback) } }