@rocketshow/designer 1.63.0 → 1.65.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -158,7 +158,9 @@
158
158
  "create-fixture-from-profile-file": "Von Profil-Datei erstellen",
159
159
  "create-from-local-profile": "Von Profil-Datei erstellen",
160
160
  "create-fixture-from-profile-file-dialog-title": "OFL Profil-Datei auswählen",
161
- "invalid-fixture-profile-file": "Das ist keine gültige OFL Profil-Datei. Bitte wähle eine gültige OFL Profil-Datei."
161
+ "invalid-fixture-profile-file": "Das ist keine gültige OFL Profil-Datei. Bitte wähle eine gültige OFL Profil-Datei.",
162
+ "edit-universes": "Universen bearbeiten",
163
+ "edit-universes-title": "Universen bearbeiten"
162
164
  },
163
165
  "effect": {
164
166
  "add-new": "Neuen Effekt hinzufügen",
@@ -157,7 +157,9 @@
157
157
  "profiles-update-error-title": "Update error",
158
158
  "create-fixture-from-profile-file": "Create from profile file",
159
159
  "create-fixture-from-profile-file-dialog-title": "Select an OFL profile file",
160
- "invalid-fixture-profile-file": "This is not a valid OFL fixture profile file. Please select a valid profile file."
160
+ "invalid-fixture-profile-file": "This is not a valid OFL fixture profile file. Please select a valid profile file.",
161
+ "edit-universes": "Edit universes",
162
+ "edit-universes-title": "Edit universes"
161
163
  },
162
164
  "effect": {
163
165
  "add-new": "Add new Effect",
@@ -24,9 +24,9 @@ import * as i5 from 'ngx-dropzone-wrapper';
24
24
  import { DropzoneModule } from 'ngx-dropzone-wrapper';
25
25
  import * as i3$2 from '@angular/forms';
26
26
  import { FormsModule } from '@angular/forms';
27
+ import Sortable from 'sortablejs';
27
28
  import * as i12 from 'ngx-bootstrap/popover';
28
29
  import { PopoverModule } from 'ngx-bootstrap/popover';
29
- import Sortable from 'sortablejs';
30
30
  import * as i10 from 'ngx-bootstrap/typeahead';
31
31
  import { TypeaheadModule } from 'ngx-bootstrap/typeahead';
32
32
  import * as i4 from 'ngx-bootstrap-slider';
@@ -1900,7 +1900,7 @@ class FixtureService {
1900
1900
  // no free space left for the needed channels
1901
1901
  return -1;
1902
1902
  }
1903
- addFixture(profile, fixturePool) {
1903
+ addFixture(profile, fixturePool, channelPool) {
1904
1904
  const fixture = new Fixture();
1905
1905
  fixture.uuid = this.uuidService.getUuid();
1906
1906
  fixture.profileUuid = profile.uuid;
@@ -1925,7 +1925,7 @@ class FixtureService {
1925
1925
  }
1926
1926
  const mode = this.getModeByFixture(profile, fixture);
1927
1927
  const channelCount = this.getModeChannelCount(profile, mode);
1928
- const firstChannel = this.getNextFreeDmxChannel(fixturePool, channelCount);
1928
+ const firstChannel = this.getNextFreeDmxChannel(channelPool ?? fixturePool, channelCount);
1929
1929
  if (firstChannel >= 0) {
1930
1930
  fixture.dmxFirstChannel = firstChannel;
1931
1931
  fixturePool.push(fixture);
@@ -2207,6 +2207,7 @@ class PresetService {
2207
2207
  for (let i = this.selectedPreset.fixtures.length - 1; i >= 0; i--) {
2208
2208
  const projectFixture = this.fixtureService.getFixtureByUuid(this.selectedPreset.fixtures[i].fixtureUuid);
2209
2209
  if (projectFixture.dmxFirstChannel === fixture.dmxFirstChannel &&
2210
+ projectFixture.dmxUniverseUuid === fixture.dmxUniverseUuid &&
2210
2211
  ((!this.selectedPreset.fixtures[i].pixelKey && !pixelKey) || this.selectedPreset.fixtures[i].pixelKey === pixelKey)) {
2211
2212
  this.selectedPreset.fixtures.splice(i, 1);
2212
2213
  }
@@ -2216,7 +2217,7 @@ class PresetService {
2216
2217
  for (const projectPresetFixture of this.projectService.project.presetFixtures) {
2217
2218
  if ((!projectPresetFixture.pixelKey && !pixelKey) || projectPresetFixture.pixelKey === pixelKey) {
2218
2219
  const projectFixture = this.fixtureService.getFixtureByUuid(projectPresetFixture.fixtureUuid);
2219
- if (projectFixture.dmxFirstChannel === fixture.dmxFirstChannel) {
2220
+ if (projectFixture.dmxFirstChannel === fixture.dmxFirstChannel && projectFixture.dmxUniverseUuid === fixture.dmxUniverseUuid) {
2220
2221
  const presetFixture = new PresetFixture();
2221
2222
  presetFixture.fixtureUuid = projectPresetFixture.fixtureUuid;
2222
2223
  presetFixture.pixelKey = pixelKey;
@@ -2286,6 +2287,7 @@ class PresetService {
2286
2287
  const fixture = this.fixtureService.getFixtureByUuid(presetFixture.fixtureUuid);
2287
2288
  for (const projectFixture of this.projectService.project.fixtures) {
2288
2289
  if (projectFixture.dmxFirstChannel === fixture.dmxFirstChannel &&
2290
+ projectFixture.dmxUniverseUuid === fixture.dmxUniverseUuid &&
2289
2291
  !this.fixtureIsSelected(projectFixture, presetFixture.pixelKey, preset)) {
2290
2292
  const newPresetFixture = new PresetFixture();
2291
2293
  newPresetFixture.fixtureUuid = projectFixture.uuid;
@@ -5386,6 +5388,43 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
5386
5388
  type: Output
5387
5389
  }] } });
5388
5390
 
5391
+ class FixturePoolEditUniversesComponent {
5392
+ constructor(bsModalRef, uuidService) {
5393
+ this.bsModalRef = bsModalRef;
5394
+ this.uuidService = uuidService;
5395
+ this.universes = [];
5396
+ this.newUniverseName = '';
5397
+ this.onClose = new Subject();
5398
+ }
5399
+ addUniverse() {
5400
+ const name = (this.newUniverseName || '').trim();
5401
+ if (!name)
5402
+ return;
5403
+ this.universes.push({ uuid: this.uuidService.getUuid(), name });
5404
+ this.newUniverseName = '';
5405
+ }
5406
+ removeUniverse(universe) {
5407
+ const index = this.universes.findIndex((u) => u.uuid === universe.uuid);
5408
+ if (index >= 0) {
5409
+ this.universes.splice(index, 1);
5410
+ }
5411
+ }
5412
+ ok() {
5413
+ this.onClose.next(this.universes);
5414
+ this.bsModalRef.hide();
5415
+ }
5416
+ cancel() {
5417
+ this.onClose.next(undefined);
5418
+ this.bsModalRef.hide();
5419
+ }
5420
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: FixturePoolEditUniversesComponent, deps: [{ token: i1$1.BsModalRef }, { token: UuidService }], target: i0.ɵɵFactoryTarget.Component }); }
5421
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: FixturePoolEditUniversesComponent, isStandalone: false, selector: "lib-fixture-pool-edit-universes", ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title pull-left\">{{ 'designer.fixture-pool.edit-universes-title' | translate }}</h4>\n <button type=\"button\" class=\"close pull-right\" aria-label=\"Close\" (click)=\"cancel()\">\n <span aria-hidden=\"true\">&times;</span>\n </button>\n</div>\n<div class=\"modal-body\">\n <!-- Add universe -->\n <div class=\"input-group mb-3\">\n <input\n type=\"text\"\n class=\"form-control\"\n maxlength=\"200\"\n [(ngModel)]=\"newUniverseName\"\n (keydown.enter)=\"addUniverse(); $event.stopPropagation(); $event.preventDefault()\"\n placeholder=\"{{ 'designer.fixture-pool.universe-name' | translate }}\"\n />\n <div class=\"input-group-append\">\n <button type=\"button\" class=\"btn btn-secondary\" [disabled]=\"!newUniverseName || !newUniverseName.trim()\" (click)=\"addUniverse()\">\n <i class=\"fa fa-plus\" aria-hidden=\"true\"></i>\n </button>\n </div>\n </div>\n\n <!-- Sortable universe list -->\n <ul class=\"list-group\" [sortablejs]=\"universes\">\n @for (universe of universes; track universe.uuid) {\n <li class=\"list-group-item d-flex align-items-center py-1\">\n <div class=\"list-sort-handle mr-2\" style=\"cursor: move; cursor: -webkit-grabbing\">\n <i class=\"fa fa-bars\" aria-hidden=\"true\"></i>\n </div>\n <span class=\"flex-grow-1\">{{ universe.name }}</span>\n <button class=\"btn btn-primary btn-sm\" type=\"button\" (click)=\"removeUniverse(universe)\">\n <i class=\"fa fa-minus\" aria-hidden=\"true\"></i>\n </button>\n </li>\n }\n </ul>\n</div>\n<div class=\"modal-footer\">\n <a class=\"mr-3 my-auto\" href=\"#\" role=\"button\" (click)=\"cancel(); (false)\">\n {{ 'designer.misc.cancel' | translate }}\n </a>\n <button type=\"button\" class=\"btn btn-primary\" (click)=\"ok()\">{{ 'designer.misc.ok' | translate }}</button>\n</div>\n", styles: [""], dependencies: [{ kind: "directive", type: i3$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$2.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i3$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: SortablejsDirective, selector: "[sortablejs]", inputs: ["sortablejs", "sortablejsContainer", "sortablejsOptions"], outputs: ["sortablejsInit"] }, { kind: "pipe", type: i2.TranslatePipe, name: "translate" }] }); }
5422
+ }
5423
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: FixturePoolEditUniversesComponent, decorators: [{
5424
+ type: Component,
5425
+ args: [{ selector: 'lib-fixture-pool-edit-universes', standalone: false, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title pull-left\">{{ 'designer.fixture-pool.edit-universes-title' | translate }}</h4>\n <button type=\"button\" class=\"close pull-right\" aria-label=\"Close\" (click)=\"cancel()\">\n <span aria-hidden=\"true\">&times;</span>\n </button>\n</div>\n<div class=\"modal-body\">\n <!-- Add universe -->\n <div class=\"input-group mb-3\">\n <input\n type=\"text\"\n class=\"form-control\"\n maxlength=\"200\"\n [(ngModel)]=\"newUniverseName\"\n (keydown.enter)=\"addUniverse(); $event.stopPropagation(); $event.preventDefault()\"\n placeholder=\"{{ 'designer.fixture-pool.universe-name' | translate }}\"\n />\n <div class=\"input-group-append\">\n <button type=\"button\" class=\"btn btn-secondary\" [disabled]=\"!newUniverseName || !newUniverseName.trim()\" (click)=\"addUniverse()\">\n <i class=\"fa fa-plus\" aria-hidden=\"true\"></i>\n </button>\n </div>\n </div>\n\n <!-- Sortable universe list -->\n <ul class=\"list-group\" [sortablejs]=\"universes\">\n @for (universe of universes; track universe.uuid) {\n <li class=\"list-group-item d-flex align-items-center py-1\">\n <div class=\"list-sort-handle mr-2\" style=\"cursor: move; cursor: -webkit-grabbing\">\n <i class=\"fa fa-bars\" aria-hidden=\"true\"></i>\n </div>\n <span class=\"flex-grow-1\">{{ universe.name }}</span>\n <button class=\"btn btn-primary btn-sm\" type=\"button\" (click)=\"removeUniverse(universe)\">\n <i class=\"fa fa-minus\" aria-hidden=\"true\"></i>\n </button>\n </li>\n }\n </ul>\n</div>\n<div class=\"modal-footer\">\n <a class=\"mr-3 my-auto\" href=\"#\" role=\"button\" (click)=\"cancel(); (false)\">\n {{ 'designer.misc.cancel' | translate }}\n </a>\n <button type=\"button\" class=\"btn btn-primary\" (click)=\"ok()\">{{ 'designer.misc.ok' | translate }}</button>\n</div>\n" }]
5426
+ }], ctorParameters: () => [{ type: i1$1.BsModalRef }, { type: UuidService }] });
5427
+
5389
5428
  class FixturePoolComponent {
5390
5429
  constructor(bsModalRef, fixtureService, uuidService, projectService, previewService, translateService, toastrService, presetService, configService, modalService, livePreviewService) {
5391
5430
  this.bsModalRef = bsModalRef;
@@ -5407,48 +5446,41 @@ class FixturePoolComponent {
5407
5446
  // extend this list at runtime; otherwise it is provided by the host
5408
5447
  // application through the ConfigService.
5409
5448
  this.universes = [];
5410
- // Name buffer for the "add universe" input shown when freeUniverseEdit is true.
5411
- this.newUniverseName = '';
5412
5449
  this.fixturePool = structuredClone(this.projectService.project.fixtures);
5413
5450
  // Initialise the universe list from the config. We keep a local copy so
5414
5451
  // edits done in free-edit mode don't bleed into the config until the user
5415
5452
  // confirms with OK (see `ok()` below).
5416
5453
  this.universes = structuredClone(this.configService.universes || []);
5417
- if (this.fixturePool.length > 0) {
5418
- this.selectFixture(this.fixturePool[0]);
5454
+ if (this.universes.length === 0 && this.configService.freeUniverseEdit) {
5455
+ this.universes.push({ uuid: this.uuidService.getUuid(), name: 'Universe 1' });
5456
+ }
5457
+ if (this.universes.length > 0) {
5458
+ this.selectedUniverse = this.universes[0];
5459
+ }
5460
+ const firstVisible = this.currentUniverseFixtures[0] ?? this.fixturePool[0];
5461
+ if (firstVisible) {
5462
+ this.selectFixture(firstVisible);
5419
5463
  }
5420
5464
  }
5421
5465
  get freeUniverseEdit() {
5422
5466
  return this.configService.freeUniverseEdit;
5423
5467
  }
5424
- addUniverse(name) {
5425
- const trimmed = (name || '').trim();
5426
- if (!trimmed) {
5427
- return;
5428
- }
5429
- const universe = {
5430
- uuid: this.uuidService.getUuid(),
5431
- name: trimmed,
5432
- };
5433
- this.universes.push(universe);
5434
- this.newUniverseName = '';
5435
- // If the currently selected fixture has no universe yet, auto-assign
5436
- // the freshly created one for convenience.
5437
- if (this.selectedFixture && !this.selectedFixture.dmxUniverseUuid) {
5438
- this.selectedFixture.dmxUniverseUuid = universe.uuid;
5468
+ /** Fixtures belonging to the currently selected universe. */
5469
+ get currentUniverseFixtures() {
5470
+ if (!this.selectedUniverse) {
5471
+ return this.fixturePool;
5439
5472
  }
5473
+ return this.fixturePool.filter((f) => f.dmxUniverseUuid === this.selectedUniverse.uuid);
5440
5474
  }
5441
- removeUniverse(universe) {
5442
- const index = this.universes.findIndex((u) => u.uuid === universe.uuid);
5443
- if (index < 0) {
5444
- return;
5445
- }
5446
- this.universes.splice(index, 1);
5447
- // Clear the universe assignment from any fixture that referenced it.
5448
- for (const fixture of this.fixturePool) {
5449
- if (fixture.dmxUniverseUuid === universe.uuid) {
5450
- fixture.dmxUniverseUuid = undefined;
5451
- }
5475
+ /** Fixtures used for channel-map calculations (same as currentUniverseFixtures). */
5476
+ get channelFixtures() {
5477
+ return this.currentUniverseFixtures;
5478
+ }
5479
+ selectUniverse(universe) {
5480
+ this.selectedUniverse = universe;
5481
+ // Deselect the current fixture if it doesn't belong to the new universe.
5482
+ if (this.selectedFixture && this.selectedFixture.dmxUniverseUuid !== universe?.uuid) {
5483
+ this.selectFixture(this.currentUniverseFixtures[0] ?? undefined);
5452
5484
  }
5453
5485
  }
5454
5486
  ngOnInit() {
@@ -5498,8 +5530,9 @@ class FixturePoolComponent {
5498
5530
  // minimal profile passed from the search.
5499
5531
  this.fixtureService.loadProfileByUuid(searchProfile.uuid).subscribe(() => {
5500
5532
  const profile = this.fixtureService.getProfileByUuid(searchProfile.uuid);
5501
- const newFixture = this.fixtureService.addFixture(profile, this.fixturePool);
5533
+ const newFixture = this.fixtureService.addFixture(profile, this.fixturePool, this.currentUniverseFixtures);
5502
5534
  if (newFixture) {
5535
+ newFixture.dmxUniverseUuid = this.selectedUniverse?.uuid;
5503
5536
  this.selectFixture(newFixture);
5504
5537
  }
5505
5538
  });
@@ -5511,7 +5544,7 @@ class FixturePoolComponent {
5511
5544
  fixture.name = originalFixture.name;
5512
5545
  fixture.modeShortName = originalFixture.modeShortName;
5513
5546
  fixture.dmxFirstChannel = originalFixture.dmxFirstChannel;
5514
- fixture.dmxUniverseUuid = originalFixture.dmxUniverseUuid;
5547
+ fixture.dmxUniverseUuid = this.selectedUniverse?.uuid;
5515
5548
  this.fixturePool.push(fixture);
5516
5549
  this.selectFixture(fixture);
5517
5550
  }
@@ -5525,8 +5558,8 @@ class FixturePoolComponent {
5525
5558
  break;
5526
5559
  }
5527
5560
  }
5528
- if (!this.selectedFixture && this.fixturePool && this.fixturePool.length > 0) {
5529
- this.selectFixture((this.selectedFixture = this.fixturePool[0]));
5561
+ if (!this.selectedFixture) {
5562
+ this.selectFixture(this.currentUniverseFixtures[0] ?? undefined);
5530
5563
  }
5531
5564
  // remove unused profiles
5532
5565
  for (let i = 0; i < this.projectService.project.fixtureProfiles.length; i++) {
@@ -5543,7 +5576,7 @@ class FixturePoolComponent {
5543
5576
  }
5544
5577
  }
5545
5578
  channelOccupied(index) {
5546
- for (const fixture of this.fixturePool) {
5579
+ for (const fixture of this.channelFixtures) {
5547
5580
  const profile = this.fixtureService.getProfileByUuid(fixture.profileUuid);
5548
5581
  const mode = this.fixtureService.getModeByFixture(profile, fixture);
5549
5582
  const channelCount = this.fixtureService.getModeChannelCount(profile, mode);
@@ -5554,7 +5587,7 @@ class FixturePoolComponent {
5554
5587
  return false;
5555
5588
  }
5556
5589
  channelOccupiedStart(index) {
5557
- for (const fixture of this.fixturePool) {
5590
+ for (const fixture of this.channelFixtures) {
5558
5591
  if (index === fixture.dmxFirstChannel) {
5559
5592
  return true;
5560
5593
  }
@@ -5562,7 +5595,7 @@ class FixturePoolComponent {
5562
5595
  return false;
5563
5596
  }
5564
5597
  channelOccupiedEnd(index) {
5565
- for (const fixture of this.fixturePool) {
5598
+ for (const fixture of this.channelFixtures) {
5566
5599
  const profile = this.fixtureService.getProfileByUuid(fixture.profileUuid);
5567
5600
  const mode = this.fixtureService.getModeByFixture(profile, fixture);
5568
5601
  const channelCount = this.fixtureService.getModeChannelCount(profile, mode);
@@ -5573,16 +5606,21 @@ class FixturePoolComponent {
5573
5606
  return false;
5574
5607
  }
5575
5608
  channelOverlapped(index) {
5609
+ return this.channelOverlappedInFixtures(index, this.channelFixtures);
5610
+ }
5611
+ channelOverlappedInFixtures(index, fixtures) {
5576
5612
  let occupiedFixture;
5577
- for (const fixture of this.fixturePool) {
5613
+ for (const fixture of fixtures) {
5578
5614
  const profile = this.fixtureService.getProfileByUuid(fixture.profileUuid);
5579
5615
  const mode = this.fixtureService.getModeByFixture(profile, fixture);
5580
5616
  const channelCount = this.fixtureService.getModeChannelCount(profile, mode);
5581
5617
  if (index >= fixture.dmxFirstChannel && index < fixture.dmxFirstChannel + channelCount) {
5582
5618
  if (occupiedFixture) {
5583
- if (occupiedFixture.dmxFirstChannel !== fixture.dmxFirstChannel || occupiedFixture.modeShortName !== fixture.modeShortName) {
5584
- // fixtures are allowed to overlap, if they start at the same channel and
5585
- // use the same mode. otherwise it's not allowed.
5619
+ if (occupiedFixture.dmxFirstChannel !== fixture.dmxFirstChannel ||
5620
+ occupiedFixture.modeShortName !== fixture.modeShortName ||
5621
+ occupiedFixture.dmxUniverseUuid !== fixture.dmxUniverseUuid) {
5622
+ // fixtures are allowed to overlap, if they start at the same channel,
5623
+ // use the same mode and belong to the same universe. otherwise it's not allowed.
5586
5624
  return true;
5587
5625
  }
5588
5626
  }
@@ -5599,7 +5637,7 @@ class FixturePoolComponent {
5599
5637
  let newSelectedFixture;
5600
5638
  // find a dragging fixture and select it, but don't change the selection, if the
5601
5639
  // currently selected fixture might also be selected (on overlapped fixtures)
5602
- for (const fixture of this.fixturePool) {
5640
+ for (const fixture of this.channelFixtures) {
5603
5641
  const profile = this.fixtureService.getProfileByUuid(fixture.profileUuid);
5604
5642
  const mode = this.fixtureService.getModeByFixture(profile, fixture);
5605
5643
  const channelCount = this.fixtureService.getModeChannelCount(profile, mode);
@@ -5655,15 +5693,18 @@ class FixturePoolComponent {
5655
5693
  }
5656
5694
  }
5657
5695
  ok() {
5658
- // don't allow OK if overlapping fixtures exist
5659
- for (let i = 0; i < this.dmxChannels.length; i++) {
5660
- if (this.channelOverlapped(i)) {
5661
- const msg = 'designer.fixture-pool.channel-occupied';
5662
- const title = 'designer.fixture-pool.channel-occupied-title';
5663
- this.translateService.get([msg, title]).subscribe((result) => {
5664
- this.toastrService.error(result[msg], result[title]);
5665
- });
5666
- return;
5696
+ // don't allow OK if overlapping fixtures exist within any universe
5697
+ for (const universe of this.universes) {
5698
+ const universeFixtures = this.fixturePool.filter((f) => f.dmxUniverseUuid === universe.uuid);
5699
+ for (let i = 0; i < this.dmxChannels.length; i++) {
5700
+ if (this.channelOverlappedInFixtures(i, universeFixtures)) {
5701
+ const msg = 'designer.fixture-pool.channel-occupied';
5702
+ const title = 'designer.fixture-pool.channel-occupied-title';
5703
+ this.translateService.get([msg, title]).subscribe((result) => {
5704
+ this.toastrService.error(result[msg], result[title]);
5705
+ });
5706
+ return;
5707
+ }
5667
5708
  }
5668
5709
  }
5669
5710
  // remove deleted fixtures/pixel keys from preset fixtures
@@ -5762,10 +5803,45 @@ class FixturePoolComponent {
5762
5803
  });
5763
5804
  });
5764
5805
  }
5806
+ openEditUniverses() {
5807
+ const ref = this.modalService.show(FixturePoolEditUniversesComponent, {
5808
+ keyboard: true,
5809
+ ignoreBackdropClick: false,
5810
+ initialState: { universes: structuredClone(this.universes) },
5811
+ });
5812
+ ref.content.onClose.subscribe((result) => {
5813
+ if (!result)
5814
+ return;
5815
+ // Clear fixture universe assignments for any removed universe
5816
+ for (const universe of this.universes) {
5817
+ if (!result.find((u) => u.uuid === universe.uuid)) {
5818
+ for (const fixture of this.fixturePool) {
5819
+ if (fixture.dmxUniverseUuid === universe.uuid) {
5820
+ fixture.dmxUniverseUuid = undefined;
5821
+ }
5822
+ }
5823
+ }
5824
+ }
5825
+ this.universes = result;
5826
+ // Re-anchor selectedUniverse to the new object reference from result.
5827
+ // The modal works on a structuredClone, so all objects have new references.
5828
+ const stillSelected = this.universes.find((u) => u.uuid === this.selectedUniverse?.uuid);
5829
+ if (stillSelected) {
5830
+ this.selectedUniverse = stillSelected;
5831
+ }
5832
+ else {
5833
+ this.selectedUniverse = this.universes[0] ?? undefined;
5834
+ this.selectFixture(this.currentUniverseFixtures[0] ?? undefined);
5835
+ }
5836
+ });
5837
+ }
5765
5838
  createFixtureFromProfileFile() {
5766
5839
  const bsModalRef = this.modalService.show(FixturePoolCreateFromFileComponent, { keyboard: true, ignoreBackdropClick: false });
5767
5840
  bsModalRef.content.onClose.subscribe((profile) => {
5768
- const newFixture = this.fixtureService.addFixture(profile, this.fixturePool);
5841
+ const newFixture = this.fixtureService.addFixture(profile, this.fixturePool, this.currentUniverseFixtures);
5842
+ if (newFixture) {
5843
+ newFixture.dmxUniverseUuid = this.selectedUniverse?.uuid;
5844
+ }
5769
5845
  this.selectFixture(newFixture);
5770
5846
  });
5771
5847
  }
@@ -5773,11 +5849,11 @@ class FixturePoolComponent {
5773
5849
  this.ok();
5774
5850
  }
5775
5851
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: FixturePoolComponent, deps: [{ token: i1$1.BsModalRef }, { token: FixtureService }, { token: UuidService }, { token: ProjectService }, { token: PreviewService }, { token: i2.TranslateService }, { token: i3$1.ToastrService }, { token: PresetService }, { token: ConfigService }, { token: i1$1.BsModalService }, { token: LivePreviewService }], target: i0.ɵɵFactoryTarget.Component }); }
5776
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: FixturePoolComponent, isStandalone: false, selector: "lib-app-fixture-pool", host: { listeners: { "window:mouseup": "mouseUp($event)", "document:keydown.enter": "handleKeyboardEvent($event)" } }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title pull-left\">{{ 'designer.fixture-pool.title' | translate }}</h4>\n <button type=\"button\" class=\"close pull-right\" aria-label=\"Close\" (click)=\"cancel(); (false)\">\n <span aria-hidden=\"true\">&times;</span>\n </button>\n</div>\n<div class=\"modal-body\">\n <div class=\"row\">\n <div class=\"col-6\">\n <!-- Search for profiles -->\n <input\n type=\"text\"\n class=\"form-control input-block mb-3\"\n id=\"searchSet\"\n placeholder=\"{{ 'designer.fixture-pool.search-profiles' | translate }}\"\n (input)=\"searchExpression = $event.target.value; filterProfiles()\"\n />\n\n <!-- Profiles -->\n <div class=\"card border-secondary w-100\" style=\"height: 200px\">\n <div class=\"card-body p-0 w-100\">\n <div class=\"list-group list-group-flush w-100\">\n @if (loadingProfiles) {\n <a class=\"list-group-item flex-column align-items-start d-flex\">\n <p class=\"mb-0 mx-auto\">\n <i class=\"fa fa-spinner fa-pulse fa-fw\"></i>\n </p>\n </a>\n } @for (profile of filteredProfiles | sort : 'uuid'; track profile) {\n <div class=\"list-group-item\">\n <div class=\"row\">\n <div class=\"col-10 col-auto my-auto pl-2\">\n <p class=\"mb-0\">\n <span class=\"icon-{{ fixtureService.getFixtureIconClass(profile) }} mr-1\"></span> {{ profile.manufacturerName }} -\n {{ profile.name }}\n </p>\n </div>\n <div class=\"col-2 my-auto\">\n <a class=\"btn btn-primary btn-sm float-right\" href=\"#\" role=\"button\" (click)=\"addFixture(profile); (false)\">\n <i class=\"fa fa-plus\" aria-hidden=\"true\"></i>\n </a>\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n <div>\n <p class=\"d-inline-block mb-0 mt-2\">\n Powered by the <a href=\"https://open-fixture-library.org/\" target=\"_blank\">Open Fixture Library</a>\n </p>\n <a\n class=\"mt-2 btn btn-secondary btn-sm float-right\"\n href=\"https://open-fixture-library.org/fixture-editor\"\n target=\"_blank\"\n role=\"button\"\n >\n <i class=\"fa fa-file-o\" aria-hidden=\"true\"></i> {{ 'designer.fixture-pool.add-profile' | translate }}\n </a>\n @if (configService.localProfiles) {\n <button\n type=\"button\"\n class=\"mr-2 mt-2 btn btn-secondary btn-sm float-right\"\n (click)=\"updateProfiles(); (false)\"\n [disabled]=\"updatingProfiles\"\n role=\"button\"\n >\n @if (updatingProfiles) {\n <i class=\"fa fa-spinner fa-pulse fa-fw\" aria-hidden=\"true\"></i>\n } @if (!updatingProfiles) {\n <i class=\"fa fa-download fa-fw\" aria-hidden=\"true\"></i>\n }\n {{ 'designer.fixture-pool.update-profiles' | translate }}\n </button>\n }\n </div>\n </div>\n <div class=\"col-6\">\n <div class=\"row\">\n <!-- Add fixture from local profile -->\n <div class=\"col-6\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"createFixtureFromProfileFile()\" role=\"button\">\n <i class=\"fa fa-plus\" aria-hidden=\"true\"></i> {{ 'designer.fixture-pool.create-fixture-from-profile-file' | translate }}\n </button>\n </div>\n\n <!-- Universe -->\n <div class=\"col-6\">\n <div class=\"form-group row\">\n <label class=\"col-lg-4 col-form-label\">{{ 'designer.fixture-pool.universe' | translate }}</label>\n <div class=\"col-lg-8\">\n <select\n class=\"custom-select\"\n [disabled]=\"!selectedFixture || universes.length === 0\"\n [ngModel]=\"selectedFixture?.dmxUniverseUuid\"\n (ngModelChange)=\"selectedFixture && (selectedFixture.dmxUniverseUuid = $event)\"\n >\n @for (universe of universes; track universe.uuid) {\n <option [ngValue]=\"universe.uuid\">{{ universe.name }}</option>\n }\n </select>\n </div>\n </div>\n\n <!-- Free universe edit: let the user add/remove their own universes -->\n @if (freeUniverseEdit) {\n <div class=\"form-group row\">\n <label class=\"col-lg-4 col-form-label\">{{ 'designer.fixture-pool.add-universe' | translate }}</label>\n <div class=\"col-lg-8\">\n <div class=\"input-group\">\n <input\n type=\"text\"\n class=\"form-control\"\n maxlength=\"200\"\n [(ngModel)]=\"newUniverseName\"\n (keydown.enter)=\"addUniverse(newUniverseName); $event.stopPropagation(); $event.preventDefault()\"\n placeholder=\"{{ 'designer.fixture-pool.universe-name' | translate }}\"\n />\n <div class=\"input-group-append\">\n <button\n type=\"button\"\n class=\"btn btn-secondary\"\n [disabled]=\"!newUniverseName || !newUniverseName.trim()\"\n (click)=\"addUniverse(newUniverseName)\"\n >\n <i class=\"fa fa-plus\" aria-hidden=\"true\"></i>\n </button>\n </div>\n </div>\n </div>\n </div>\n @if (universes.length > 0) {\n <div class=\"form-group row\">\n <div class=\"col-lg-12\">\n <ul class=\"list-group\">\n @for (universe of universes; track universe.uuid) {\n <li class=\"list-group-item d-flex justify-content-between align-items-center py-1\">\n <span>{{ universe.name }}</span>\n <a class=\"btn btn-primary btn-sm\" href=\"#\" role=\"button\" (click)=\"removeUniverse(universe); (false)\">\n <i class=\"fa fa-minus\" aria-hidden=\"true\"></i>\n </a>\n </li>\n }\n </ul>\n </div>\n </div>\n } }\n </div>\n </div>\n\n <!-- Fixtures -->\n <div class=\"card border-secondary\" style=\"height: 235px\">\n <div class=\"card-body p-0\">\n <div class=\"list-group list-group-flush\" [sortablejs]=\"fixturePool\">\n @for (fixture of fixturePool; track fixture) {\n <div\n class=\"list-group-item\"\n [class.active]=\"fixture == selectedFixture\"\n (click)=\"selectFixture(fixture)\"\n style=\"cursor: pointer\"\n >\n <div class=\"row\">\n <div class=\"col-auto list-sort-handle my-auto\" style=\"cursor: move; cursor: -webkit-grabbing\">\n <i class=\"fa fa-bars\" aria-hidden=\"true\"></i>\n </div>\n <div class=\"col-auto flex-grow pl-0 my-auto\">\n <p class=\"mb-0\">\n <span class=\"icon-{{ fixtureService.getFixtureIconClass(fixtureService.getProfileByUuid(fixture.profileUuid)) }} mr-1\">\n </span\n >{{ fixture.name }}\n </p>\n </div>\n <div class=\"col-auto my-auto\">\n <a class=\"btn btn-primary btn-sm float-right\" href=\"#\" role=\"button\" (click)=\"removeFixture(fixture); (false)\">\n <i class=\"fa fa-minus\" aria-hidden=\"true\"></i>\n </a>\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n <div class=\"row mt-3\">\n <div class=\"col\">\n <!-- Tab navigation -->\n <ul class=\"nav nav-tabs mb-4\" id=\"myTab\" role=\"tablist\">\n <li class=\"nav-item\">\n <a class=\"nav-link active\" id=\"sets-tab\" data-toggle=\"tab\" href=\"#dmx\" role=\"tab\">\n <i class=\"icon-ola\" aria-hidden=\"true\"></i> {{ 'designer.fixture-pool.dmx' | translate }}\n </a>\n </li>\n <li class=\"nav-item\">\n <a class=\"nav-link\" id=\"compositions-tab\" data-toggle=\"tab\" href=\"#settings\" role=\"tab\">\n <i class=\"fa fa-cog\" aria-hidden=\"true\"></i> {{ 'designer.fixture-pool.settings' | translate }}\n </a>\n </li>\n </ul>\n\n <!-- Tab content -->\n <div class=\"tab-content\" id=\"myTabContent\">\n <!-- DMX universe overview-->\n <div class=\"tab-pane show active\" id=\"dmx\" role=\"tabpanel\">\n <!-- Show all channel numbers -->\n <!-- <div class=\"form-group row\">\n <div class=\"col-auto flex-grow\"></div>\n <div class=\"col-lg-3 d-flex\">\n <div class=\"form-check my-auto ml-auto\">\n <input type=\"checkbox\" class=\"form-check-input\" id=\"showChannelNumbers\" [ngModel]=\"showChannelNumbers\"\n (ngModelChange)=\"showChannelNumbers = $event\">\n <label class=\"form-check-label\" for=\"showChannelNumbers\">Show channels</label>\n </div>\n </div>\n </div> -->\n\n <!-- List of DMX channels -->\n <div>\n @for (channel of dmxChannels; track $index; let i = $index) {\n <div\n class=\"dmx-channel\"\n [class.dmx-channel-occupied]=\"channelOccupied(i)\"\n [class.dmx-channel-occupied-start]=\"channelOccupiedStart(i)\"\n [class.dmx-channel-occupied-end]=\"channelOccupiedEnd(i)\"\n [class.dmx-channel-selected]=\"channelSelected(i)\"\n [class.dmx-channel-overlapped]=\"channelOverlapped(i)\"\n (mousedown)=\"channelMouseDown($event)\"\n (mouseover)=\"channelMouseOver($event)\"\n [attr.data-index]=\"i\"\n popover=\"{{ i + 1 }}\"\n placement=\"top\"\n triggers=\"mouseenter:mouseleave\"\n >\n <div class=\"d-flex w-100 dmx-channel-text\" [attr.data-index]=\"i\" [class.dmx-channel-text-visible]=\"showChannelNumbers\">\n <div class=\"m-auto\" style=\"font-size: 11px\" [attr.data-index]=\"i\"><small [attr.data-index]=\"i\">&nbsp;</small></div>\n </div>\n </div>\n }\n </div>\n </div>\n\n <!-- Fixture settings -->\n <div class=\"tab-pane\" id=\"settings\" role=\"tabpanel\">\n @if (selectedFixture && selectedFixtureProfile) {\n <div>\n <div class=\"form-group row\">\n <label class=\"col-lg-3 col-form-label\" for=\"fixtureName\">{{ 'designer.fixture-pool.fixture-name' | translate }}</label>\n <div class=\"col-lg-9\">\n <input type=\"text\" class=\"form-control\" [(ngModel)]=\"selectedFixture.name\" maxlength=\"200\" id=\"fixtureName\" />\n </div>\n </div>\n <div class=\"form-group row\">\n <label class=\"col-lg-3 col-form-label\">{{ 'designer.fixture-pool.mode' | translate }}</label>\n <div class=\"col-lg-9\">\n <select\n class=\"custom-select\"\n [ngModel]=\"selectedFixture.modeShortName\"\n (ngModelChange)=\"selectedFixture.modeShortName = $event\"\n >\n @for (mode of selectedFixtureProfile.modes; track mode) {\n <option [ngValue]=\"mode.shortName || mode.name\">\n {{ mode.name }}\n - {{ fixtureService.getModeChannelCount(selectedFixtureProfile, mode) }}\n {{ 'designer.fixture-pool.channels' | translate }}\n </option>\n }\n </select>\n </div>\n </div>\n <div class=\"form-group row\">\n <label class=\"col-lg-3 col-form-label\">{{ 'designer.fixture-pool.first-channel' | translate }}</label>\n <div class=\"col-lg-9\">\n <select\n class=\"custom-select\"\n [ngModel]=\"selectedFixture.dmxFirstChannel\"\n (ngModelChange)=\"selectedFixture.dmxFirstChannel = $event\"\n >\n @for (channel of dmxChannels; track $index; let i = $index) {\n <option [ngValue]=\"i\">\n {{ i + 1 }}\n </option>\n }\n </select>\n </div>\n </div>\n <div class=\"form-group row\">\n <label class=\"col-lg-3 col-form-label\"></label>\n <div class=\"col-lg-9\">\n <button type=\"button\" class=\"btn btn-primary\" (click)=\"addCopy(selectedFixture)\">\n {{ 'designer.fixture.add-copy' | translate }}\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n</div>\n<div class=\"modal-footer\">\n <a class=\"mr-3 my-auto\" href=\"#\" role=\"button\" (click)=\"cancel(); (false)\">\n {{ 'designer.misc.cancel' | translate }}\n </a>\n <button type=\"button\" class=\"btn btn-primary\" (click)=\"ok()\">{{ 'designer.misc.ok' | translate }}</button>\n</div>\n", styles: [".list-group-item{padding:.3rem 1.25rem}.dmx-channel{display:flex;float:left;width:28px;border:1px solid white;margin-left:-1px;margin-bottom:-1px;padding-bottom:2px}.dmx-channel-occupied{background-color:#63462e;border-left:none;border-right:none;cursor:move}.dmx-channel-occupied-start{border-left:1px solid white;border-top-left-radius:8px;border-bottom-left-radius:8px}.dmx-channel-occupied-end{border-right:1px solid white;border-top-right-radius:8px;border-bottom-right-radius:8px}.dmx-channel-selected{background-color:#fd7e14}.dmx-channel-overlapped{background-color:red;border:1px solid red;cursor:move}.card{overflow:auto}\n"], dependencies: [{ kind: "directive", type: i3$2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3$2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3$2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i3$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$2.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i3$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i12.PopoverDirective, selector: "[popover]", inputs: ["adaptivePosition", "boundariesElement", "popover", "popoverContext", "popoverTitle", "placement", "outsideClick", "triggers", "container", "containerClass", "isOpen", "delay"], outputs: ["onShown", "onHidden"], exportAs: ["bs-popover"] }, { kind: "directive", type: SortablejsDirective, selector: "[sortablejs]", inputs: ["sortablejs", "sortablejsContainer", "sortablejsOptions"], outputs: ["sortablejsInit"] }, { kind: "pipe", type: i2.TranslatePipe, name: "translate" }, { kind: "pipe", type: ArraySortPipe, name: "sort" }] }); }
5852
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.16", type: FixturePoolComponent, isStandalone: false, selector: "lib-app-fixture-pool", host: { listeners: { "window:mouseup": "mouseUp($event)", "document:keydown.enter": "handleKeyboardEvent($event)" } }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title pull-left\">{{ 'designer.fixture-pool.title' | translate }}</h4>\n <button type=\"button\" class=\"close pull-right\" aria-label=\"Close\" (click)=\"cancel(); (false)\">\n <span aria-hidden=\"true\">&times;</span>\n </button>\n</div>\n<div class=\"modal-body\">\n <div class=\"row\">\n <div class=\"col-6\">\n <!-- Search for profiles -->\n <input\n type=\"text\"\n class=\"form-control input-block mb-3\"\n id=\"searchSet\"\n placeholder=\"{{ 'designer.fixture-pool.search-profiles' | translate }}\"\n (input)=\"searchExpression = $event.target.value; filterProfiles()\"\n />\n\n <!-- Profiles -->\n <div class=\"card border-secondary w-100\" style=\"height: 200px\">\n <div class=\"card-body p-0 w-100\">\n <div class=\"list-group list-group-flush w-100\">\n @if (loadingProfiles) {\n <a class=\"list-group-item flex-column align-items-start d-flex\">\n <p class=\"mb-0 mx-auto\">\n <i class=\"fa fa-spinner fa-pulse fa-fw\"></i>\n </p>\n </a>\n } @for (profile of filteredProfiles | sort : 'uuid'; track profile) {\n <div class=\"list-group-item\">\n <div class=\"row\">\n <div class=\"col-10 col-auto my-auto pl-2\">\n <p class=\"mb-0\">\n <span class=\"icon-{{ fixtureService.getFixtureIconClass(profile) }} mr-1\"></span> {{ profile.manufacturerName }} -\n {{ profile.name }}\n </p>\n </div>\n <div class=\"col-2 my-auto\">\n <a class=\"btn btn-primary btn-sm float-right\" href=\"#\" role=\"button\" (click)=\"addFixture(profile); (false)\">\n <i class=\"fa fa-plus\" aria-hidden=\"true\"></i>\n </a>\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n <div>\n <p class=\"d-inline-block mb-0 mt-2\">\n Powered by the <a href=\"https://open-fixture-library.org/\" target=\"_blank\">Open Fixture Library</a>\n </p>\n <a\n class=\"mt-2 btn btn-secondary btn-sm float-right\"\n href=\"https://open-fixture-library.org/fixture-editor\"\n target=\"_blank\"\n role=\"button\"\n >\n <i class=\"fa fa-file-o\" aria-hidden=\"true\"></i> {{ 'designer.fixture-pool.add-profile' | translate }}\n </a>\n @if (configService.localProfiles) {\n <button\n type=\"button\"\n class=\"mr-2 mt-2 btn btn-secondary btn-sm float-right\"\n (click)=\"updateProfiles(); (false)\"\n [disabled]=\"updatingProfiles\"\n role=\"button\"\n >\n @if (updatingProfiles) {\n <i class=\"fa fa-spinner fa-pulse fa-fw\" aria-hidden=\"true\"></i>\n } @if (!updatingProfiles) {\n <i class=\"fa fa-download fa-fw\" aria-hidden=\"true\"></i>\n }\n {{ 'designer.fixture-pool.update-profiles' | translate }}\n </button>\n }\n </div>\n </div>\n <div class=\"col-6\">\n <div class=\"row\">\n <!-- Add fixture from local profile -->\n <div class=\"col-6\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"createFixtureFromProfileFile()\" role=\"button\">\n <i class=\"fa fa-plus\" aria-hidden=\"true\"></i> {{ 'designer.fixture-pool.create-fixture-from-profile-file' | translate }}\n </button>\n </div>\n\n <!-- Universe -->\n <div class=\"col-6\">\n <div class=\"form-group row\">\n <label class=\"col-lg-4 col-form-label\">{{ 'designer.fixture-pool.universe' | translate }}</label>\n <div class=\"col-lg-8\">\n <div class=\"input-group\">\n <select\n class=\"custom-select\"\n [disabled]=\"universes.length === 0\"\n [ngModel]=\"selectedUniverse\"\n (ngModelChange)=\"selectUniverse($event)\"\n >\n @for (universe of universes; track universe.uuid) {\n <option [ngValue]=\"universe\">{{ universe.name }}</option>\n }\n </select>\n @if (freeUniverseEdit) {\n <div class=\"input-group-append\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"openEditUniverses()\">\n <i class=\"fa fa-pencil\" aria-hidden=\"true\"></i>\n </button>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Fixtures -->\n <div class=\"card border-secondary\" style=\"height: 235px\">\n <div class=\"card-body p-0\">\n <div class=\"list-group list-group-flush\" [sortablejs]=\"fixturePool\">\n @for (fixture of currentUniverseFixtures; track fixture) {\n <div\n class=\"list-group-item\"\n [class.active]=\"fixture == selectedFixture\"\n (click)=\"selectFixture(fixture)\"\n style=\"cursor: pointer\"\n >\n <div class=\"row\">\n <div class=\"col-auto list-sort-handle my-auto\" style=\"cursor: move; cursor: -webkit-grabbing\">\n <i class=\"fa fa-bars\" aria-hidden=\"true\"></i>\n </div>\n <div class=\"col-auto flex-grow pl-0 my-auto\">\n <p class=\"mb-0\">\n <span class=\"icon-{{ fixtureService.getFixtureIconClass(fixtureService.getProfileByUuid(fixture.profileUuid)) }} mr-1\">\n </span\n >{{ fixture.name }}\n </p>\n </div>\n <div class=\"col-auto my-auto\">\n <a class=\"btn btn-primary btn-sm float-right\" href=\"#\" role=\"button\" (click)=\"removeFixture(fixture); (false)\">\n <i class=\"fa fa-minus\" aria-hidden=\"true\"></i>\n </a>\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n <div class=\"row mt-3\">\n <div class=\"col\">\n <!-- Tab navigation -->\n <ul class=\"nav nav-tabs mb-4\" id=\"myTab\" role=\"tablist\">\n <li class=\"nav-item\">\n <a class=\"nav-link active\" id=\"sets-tab\" data-toggle=\"tab\" href=\"#dmx\" role=\"tab\">\n <i class=\"icon-ola\" aria-hidden=\"true\"></i> {{ 'designer.fixture-pool.dmx' | translate }}\n </a>\n </li>\n <li class=\"nav-item\">\n <a class=\"nav-link\" id=\"compositions-tab\" data-toggle=\"tab\" href=\"#settings\" role=\"tab\">\n <i class=\"fa fa-cog\" aria-hidden=\"true\"></i> {{ 'designer.fixture-pool.settings' | translate }}\n </a>\n </li>\n </ul>\n\n <!-- Tab content -->\n <div class=\"tab-content\" id=\"myTabContent\">\n <!-- DMX universe overview-->\n <div class=\"tab-pane show active\" id=\"dmx\" role=\"tabpanel\">\n <!-- Show all channel numbers -->\n <!-- <div class=\"form-group row\">\n <div class=\"col-auto flex-grow\"></div>\n <div class=\"col-lg-3 d-flex\">\n <div class=\"form-check my-auto ml-auto\">\n <input type=\"checkbox\" class=\"form-check-input\" id=\"showChannelNumbers\" [ngModel]=\"showChannelNumbers\"\n (ngModelChange)=\"showChannelNumbers = $event\">\n <label class=\"form-check-label\" for=\"showChannelNumbers\">Show channels</label>\n </div>\n </div>\n </div> -->\n\n <!-- List of DMX channels -->\n <div>\n @for (channel of dmxChannels; track $index; let i = $index) {\n <div\n class=\"dmx-channel\"\n [class.dmx-channel-occupied]=\"channelOccupied(i)\"\n [class.dmx-channel-occupied-start]=\"channelOccupiedStart(i)\"\n [class.dmx-channel-occupied-end]=\"channelOccupiedEnd(i)\"\n [class.dmx-channel-selected]=\"channelSelected(i)\"\n [class.dmx-channel-overlapped]=\"channelOverlapped(i)\"\n (mousedown)=\"channelMouseDown($event)\"\n (mouseover)=\"channelMouseOver($event)\"\n [attr.data-index]=\"i\"\n popover=\"{{ i + 1 }}\"\n placement=\"top\"\n triggers=\"mouseenter:mouseleave\"\n >\n <div class=\"d-flex w-100 dmx-channel-text\" [attr.data-index]=\"i\" [class.dmx-channel-text-visible]=\"showChannelNumbers\">\n <div class=\"m-auto\" style=\"font-size: 11px\" [attr.data-index]=\"i\"><small [attr.data-index]=\"i\">&nbsp;</small></div>\n </div>\n </div>\n }\n </div>\n </div>\n\n <!-- Fixture settings -->\n <div class=\"tab-pane\" id=\"settings\" role=\"tabpanel\">\n @if (selectedFixture && selectedFixtureProfile) {\n <div>\n <div class=\"form-group row\">\n <label class=\"col-lg-3 col-form-label\" for=\"fixtureName\">{{ 'designer.fixture-pool.fixture-name' | translate }}</label>\n <div class=\"col-lg-9\">\n <input type=\"text\" class=\"form-control\" [(ngModel)]=\"selectedFixture.name\" maxlength=\"200\" id=\"fixtureName\" />\n </div>\n </div>\n <div class=\"form-group row\">\n <label class=\"col-lg-3 col-form-label\">{{ 'designer.fixture-pool.mode' | translate }}</label>\n <div class=\"col-lg-9\">\n <select\n class=\"custom-select\"\n [ngModel]=\"selectedFixture.modeShortName\"\n (ngModelChange)=\"selectedFixture.modeShortName = $event\"\n >\n @for (mode of selectedFixtureProfile.modes; track mode) {\n <option [ngValue]=\"mode.shortName || mode.name\">\n {{ mode.name }}\n - {{ fixtureService.getModeChannelCount(selectedFixtureProfile, mode) }}\n {{ 'designer.fixture-pool.channels' | translate }}\n </option>\n }\n </select>\n </div>\n </div>\n <div class=\"form-group row\">\n <label class=\"col-lg-3 col-form-label\">{{ 'designer.fixture-pool.universe' | translate }}</label>\n <div class=\"col-lg-9\">\n <select\n class=\"custom-select\"\n [disabled]=\"universes.length === 0\"\n [ngModel]=\"selectedFixture.dmxUniverseUuid\"\n (ngModelChange)=\"selectedFixture.dmxUniverseUuid = $event\"\n >\n @for (universe of universes; track universe.uuid) {\n <option [ngValue]=\"universe.uuid\">{{ universe.name }}</option>\n }\n </select>\n </div>\n </div>\n <div class=\"form-group row\">\n <label class=\"col-lg-3 col-form-label\">{{ 'designer.fixture-pool.first-channel' | translate }}</label>\n <div class=\"col-lg-9\">\n <select\n class=\"custom-select\"\n [ngModel]=\"selectedFixture.dmxFirstChannel\"\n (ngModelChange)=\"selectedFixture.dmxFirstChannel = $event\"\n >\n @for (channel of dmxChannels; track $index; let i = $index) {\n <option [ngValue]=\"i\">\n {{ i + 1 }}\n </option>\n }\n </select>\n </div>\n </div>\n <div class=\"form-group row\">\n <label class=\"col-lg-3 col-form-label\"></label>\n <div class=\"col-lg-9\">\n <button type=\"button\" class=\"btn btn-primary\" (click)=\"addCopy(selectedFixture)\">\n {{ 'designer.fixture.add-copy' | translate }}\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n</div>\n<div class=\"modal-footer\">\n <a class=\"mr-3 my-auto\" href=\"#\" role=\"button\" (click)=\"cancel(); (false)\">\n {{ 'designer.misc.cancel' | translate }}\n </a>\n <button type=\"button\" class=\"btn btn-primary\" (click)=\"ok()\">{{ 'designer.misc.ok' | translate }}</button>\n</div>\n", styles: [".list-group-item{padding:.3rem 1.25rem}.dmx-channel{display:flex;float:left;width:28px;border:1px solid white;margin-left:-1px;margin-bottom:-1px;padding-bottom:2px;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.dmx-channel-occupied{background-color:#63462e;border-left:none;border-right:none;cursor:move}.dmx-channel-occupied-start{border-left:1px solid white;border-top-left-radius:8px;border-bottom-left-radius:8px}.dmx-channel-occupied-end{border-right:1px solid white;border-top-right-radius:8px;border-bottom-right-radius:8px}.dmx-channel-selected{background-color:#fd7e14}.dmx-channel-overlapped{background-color:red;border:1px solid red;cursor:move}.card{overflow:auto}\n"], dependencies: [{ kind: "directive", type: i3$2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3$2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i3$2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i3$2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i3$2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$2.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i3$2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i12.PopoverDirective, selector: "[popover]", inputs: ["adaptivePosition", "boundariesElement", "popover", "popoverContext", "popoverTitle", "placement", "outsideClick", "triggers", "container", "containerClass", "isOpen", "delay"], outputs: ["onShown", "onHidden"], exportAs: ["bs-popover"] }, { kind: "directive", type: SortablejsDirective, selector: "[sortablejs]", inputs: ["sortablejs", "sortablejsContainer", "sortablejsOptions"], outputs: ["sortablejsInit"] }, { kind: "pipe", type: i2.TranslatePipe, name: "translate" }, { kind: "pipe", type: ArraySortPipe, name: "sort" }] }); }
5777
5853
  }
5778
5854
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: FixturePoolComponent, decorators: [{
5779
5855
  type: Component,
5780
- args: [{ selector: 'lib-app-fixture-pool', standalone: false, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title pull-left\">{{ 'designer.fixture-pool.title' | translate }}</h4>\n <button type=\"button\" class=\"close pull-right\" aria-label=\"Close\" (click)=\"cancel(); (false)\">\n <span aria-hidden=\"true\">&times;</span>\n </button>\n</div>\n<div class=\"modal-body\">\n <div class=\"row\">\n <div class=\"col-6\">\n <!-- Search for profiles -->\n <input\n type=\"text\"\n class=\"form-control input-block mb-3\"\n id=\"searchSet\"\n placeholder=\"{{ 'designer.fixture-pool.search-profiles' | translate }}\"\n (input)=\"searchExpression = $event.target.value; filterProfiles()\"\n />\n\n <!-- Profiles -->\n <div class=\"card border-secondary w-100\" style=\"height: 200px\">\n <div class=\"card-body p-0 w-100\">\n <div class=\"list-group list-group-flush w-100\">\n @if (loadingProfiles) {\n <a class=\"list-group-item flex-column align-items-start d-flex\">\n <p class=\"mb-0 mx-auto\">\n <i class=\"fa fa-spinner fa-pulse fa-fw\"></i>\n </p>\n </a>\n } @for (profile of filteredProfiles | sort : 'uuid'; track profile) {\n <div class=\"list-group-item\">\n <div class=\"row\">\n <div class=\"col-10 col-auto my-auto pl-2\">\n <p class=\"mb-0\">\n <span class=\"icon-{{ fixtureService.getFixtureIconClass(profile) }} mr-1\"></span> {{ profile.manufacturerName }} -\n {{ profile.name }}\n </p>\n </div>\n <div class=\"col-2 my-auto\">\n <a class=\"btn btn-primary btn-sm float-right\" href=\"#\" role=\"button\" (click)=\"addFixture(profile); (false)\">\n <i class=\"fa fa-plus\" aria-hidden=\"true\"></i>\n </a>\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n <div>\n <p class=\"d-inline-block mb-0 mt-2\">\n Powered by the <a href=\"https://open-fixture-library.org/\" target=\"_blank\">Open Fixture Library</a>\n </p>\n <a\n class=\"mt-2 btn btn-secondary btn-sm float-right\"\n href=\"https://open-fixture-library.org/fixture-editor\"\n target=\"_blank\"\n role=\"button\"\n >\n <i class=\"fa fa-file-o\" aria-hidden=\"true\"></i> {{ 'designer.fixture-pool.add-profile' | translate }}\n </a>\n @if (configService.localProfiles) {\n <button\n type=\"button\"\n class=\"mr-2 mt-2 btn btn-secondary btn-sm float-right\"\n (click)=\"updateProfiles(); (false)\"\n [disabled]=\"updatingProfiles\"\n role=\"button\"\n >\n @if (updatingProfiles) {\n <i class=\"fa fa-spinner fa-pulse fa-fw\" aria-hidden=\"true\"></i>\n } @if (!updatingProfiles) {\n <i class=\"fa fa-download fa-fw\" aria-hidden=\"true\"></i>\n }\n {{ 'designer.fixture-pool.update-profiles' | translate }}\n </button>\n }\n </div>\n </div>\n <div class=\"col-6\">\n <div class=\"row\">\n <!-- Add fixture from local profile -->\n <div class=\"col-6\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"createFixtureFromProfileFile()\" role=\"button\">\n <i class=\"fa fa-plus\" aria-hidden=\"true\"></i> {{ 'designer.fixture-pool.create-fixture-from-profile-file' | translate }}\n </button>\n </div>\n\n <!-- Universe -->\n <div class=\"col-6\">\n <div class=\"form-group row\">\n <label class=\"col-lg-4 col-form-label\">{{ 'designer.fixture-pool.universe' | translate }}</label>\n <div class=\"col-lg-8\">\n <select\n class=\"custom-select\"\n [disabled]=\"!selectedFixture || universes.length === 0\"\n [ngModel]=\"selectedFixture?.dmxUniverseUuid\"\n (ngModelChange)=\"selectedFixture && (selectedFixture.dmxUniverseUuid = $event)\"\n >\n @for (universe of universes; track universe.uuid) {\n <option [ngValue]=\"universe.uuid\">{{ universe.name }}</option>\n }\n </select>\n </div>\n </div>\n\n <!-- Free universe edit: let the user add/remove their own universes -->\n @if (freeUniverseEdit) {\n <div class=\"form-group row\">\n <label class=\"col-lg-4 col-form-label\">{{ 'designer.fixture-pool.add-universe' | translate }}</label>\n <div class=\"col-lg-8\">\n <div class=\"input-group\">\n <input\n type=\"text\"\n class=\"form-control\"\n maxlength=\"200\"\n [(ngModel)]=\"newUniverseName\"\n (keydown.enter)=\"addUniverse(newUniverseName); $event.stopPropagation(); $event.preventDefault()\"\n placeholder=\"{{ 'designer.fixture-pool.universe-name' | translate }}\"\n />\n <div class=\"input-group-append\">\n <button\n type=\"button\"\n class=\"btn btn-secondary\"\n [disabled]=\"!newUniverseName || !newUniverseName.trim()\"\n (click)=\"addUniverse(newUniverseName)\"\n >\n <i class=\"fa fa-plus\" aria-hidden=\"true\"></i>\n </button>\n </div>\n </div>\n </div>\n </div>\n @if (universes.length > 0) {\n <div class=\"form-group row\">\n <div class=\"col-lg-12\">\n <ul class=\"list-group\">\n @for (universe of universes; track universe.uuid) {\n <li class=\"list-group-item d-flex justify-content-between align-items-center py-1\">\n <span>{{ universe.name }}</span>\n <a class=\"btn btn-primary btn-sm\" href=\"#\" role=\"button\" (click)=\"removeUniverse(universe); (false)\">\n <i class=\"fa fa-minus\" aria-hidden=\"true\"></i>\n </a>\n </li>\n }\n </ul>\n </div>\n </div>\n } }\n </div>\n </div>\n\n <!-- Fixtures -->\n <div class=\"card border-secondary\" style=\"height: 235px\">\n <div class=\"card-body p-0\">\n <div class=\"list-group list-group-flush\" [sortablejs]=\"fixturePool\">\n @for (fixture of fixturePool; track fixture) {\n <div\n class=\"list-group-item\"\n [class.active]=\"fixture == selectedFixture\"\n (click)=\"selectFixture(fixture)\"\n style=\"cursor: pointer\"\n >\n <div class=\"row\">\n <div class=\"col-auto list-sort-handle my-auto\" style=\"cursor: move; cursor: -webkit-grabbing\">\n <i class=\"fa fa-bars\" aria-hidden=\"true\"></i>\n </div>\n <div class=\"col-auto flex-grow pl-0 my-auto\">\n <p class=\"mb-0\">\n <span class=\"icon-{{ fixtureService.getFixtureIconClass(fixtureService.getProfileByUuid(fixture.profileUuid)) }} mr-1\">\n </span\n >{{ fixture.name }}\n </p>\n </div>\n <div class=\"col-auto my-auto\">\n <a class=\"btn btn-primary btn-sm float-right\" href=\"#\" role=\"button\" (click)=\"removeFixture(fixture); (false)\">\n <i class=\"fa fa-minus\" aria-hidden=\"true\"></i>\n </a>\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n <div class=\"row mt-3\">\n <div class=\"col\">\n <!-- Tab navigation -->\n <ul class=\"nav nav-tabs mb-4\" id=\"myTab\" role=\"tablist\">\n <li class=\"nav-item\">\n <a class=\"nav-link active\" id=\"sets-tab\" data-toggle=\"tab\" href=\"#dmx\" role=\"tab\">\n <i class=\"icon-ola\" aria-hidden=\"true\"></i> {{ 'designer.fixture-pool.dmx' | translate }}\n </a>\n </li>\n <li class=\"nav-item\">\n <a class=\"nav-link\" id=\"compositions-tab\" data-toggle=\"tab\" href=\"#settings\" role=\"tab\">\n <i class=\"fa fa-cog\" aria-hidden=\"true\"></i> {{ 'designer.fixture-pool.settings' | translate }}\n </a>\n </li>\n </ul>\n\n <!-- Tab content -->\n <div class=\"tab-content\" id=\"myTabContent\">\n <!-- DMX universe overview-->\n <div class=\"tab-pane show active\" id=\"dmx\" role=\"tabpanel\">\n <!-- Show all channel numbers -->\n <!-- <div class=\"form-group row\">\n <div class=\"col-auto flex-grow\"></div>\n <div class=\"col-lg-3 d-flex\">\n <div class=\"form-check my-auto ml-auto\">\n <input type=\"checkbox\" class=\"form-check-input\" id=\"showChannelNumbers\" [ngModel]=\"showChannelNumbers\"\n (ngModelChange)=\"showChannelNumbers = $event\">\n <label class=\"form-check-label\" for=\"showChannelNumbers\">Show channels</label>\n </div>\n </div>\n </div> -->\n\n <!-- List of DMX channels -->\n <div>\n @for (channel of dmxChannels; track $index; let i = $index) {\n <div\n class=\"dmx-channel\"\n [class.dmx-channel-occupied]=\"channelOccupied(i)\"\n [class.dmx-channel-occupied-start]=\"channelOccupiedStart(i)\"\n [class.dmx-channel-occupied-end]=\"channelOccupiedEnd(i)\"\n [class.dmx-channel-selected]=\"channelSelected(i)\"\n [class.dmx-channel-overlapped]=\"channelOverlapped(i)\"\n (mousedown)=\"channelMouseDown($event)\"\n (mouseover)=\"channelMouseOver($event)\"\n [attr.data-index]=\"i\"\n popover=\"{{ i + 1 }}\"\n placement=\"top\"\n triggers=\"mouseenter:mouseleave\"\n >\n <div class=\"d-flex w-100 dmx-channel-text\" [attr.data-index]=\"i\" [class.dmx-channel-text-visible]=\"showChannelNumbers\">\n <div class=\"m-auto\" style=\"font-size: 11px\" [attr.data-index]=\"i\"><small [attr.data-index]=\"i\">&nbsp;</small></div>\n </div>\n </div>\n }\n </div>\n </div>\n\n <!-- Fixture settings -->\n <div class=\"tab-pane\" id=\"settings\" role=\"tabpanel\">\n @if (selectedFixture && selectedFixtureProfile) {\n <div>\n <div class=\"form-group row\">\n <label class=\"col-lg-3 col-form-label\" for=\"fixtureName\">{{ 'designer.fixture-pool.fixture-name' | translate }}</label>\n <div class=\"col-lg-9\">\n <input type=\"text\" class=\"form-control\" [(ngModel)]=\"selectedFixture.name\" maxlength=\"200\" id=\"fixtureName\" />\n </div>\n </div>\n <div class=\"form-group row\">\n <label class=\"col-lg-3 col-form-label\">{{ 'designer.fixture-pool.mode' | translate }}</label>\n <div class=\"col-lg-9\">\n <select\n class=\"custom-select\"\n [ngModel]=\"selectedFixture.modeShortName\"\n (ngModelChange)=\"selectedFixture.modeShortName = $event\"\n >\n @for (mode of selectedFixtureProfile.modes; track mode) {\n <option [ngValue]=\"mode.shortName || mode.name\">\n {{ mode.name }}\n - {{ fixtureService.getModeChannelCount(selectedFixtureProfile, mode) }}\n {{ 'designer.fixture-pool.channels' | translate }}\n </option>\n }\n </select>\n </div>\n </div>\n <div class=\"form-group row\">\n <label class=\"col-lg-3 col-form-label\">{{ 'designer.fixture-pool.first-channel' | translate }}</label>\n <div class=\"col-lg-9\">\n <select\n class=\"custom-select\"\n [ngModel]=\"selectedFixture.dmxFirstChannel\"\n (ngModelChange)=\"selectedFixture.dmxFirstChannel = $event\"\n >\n @for (channel of dmxChannels; track $index; let i = $index) {\n <option [ngValue]=\"i\">\n {{ i + 1 }}\n </option>\n }\n </select>\n </div>\n </div>\n <div class=\"form-group row\">\n <label class=\"col-lg-3 col-form-label\"></label>\n <div class=\"col-lg-9\">\n <button type=\"button\" class=\"btn btn-primary\" (click)=\"addCopy(selectedFixture)\">\n {{ 'designer.fixture.add-copy' | translate }}\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n</div>\n<div class=\"modal-footer\">\n <a class=\"mr-3 my-auto\" href=\"#\" role=\"button\" (click)=\"cancel(); (false)\">\n {{ 'designer.misc.cancel' | translate }}\n </a>\n <button type=\"button\" class=\"btn btn-primary\" (click)=\"ok()\">{{ 'designer.misc.ok' | translate }}</button>\n</div>\n", styles: [".list-group-item{padding:.3rem 1.25rem}.dmx-channel{display:flex;float:left;width:28px;border:1px solid white;margin-left:-1px;margin-bottom:-1px;padding-bottom:2px}.dmx-channel-occupied{background-color:#63462e;border-left:none;border-right:none;cursor:move}.dmx-channel-occupied-start{border-left:1px solid white;border-top-left-radius:8px;border-bottom-left-radius:8px}.dmx-channel-occupied-end{border-right:1px solid white;border-top-right-radius:8px;border-bottom-right-radius:8px}.dmx-channel-selected{background-color:#fd7e14}.dmx-channel-overlapped{background-color:red;border:1px solid red;cursor:move}.card{overflow:auto}\n"] }]
5856
+ args: [{ selector: 'lib-app-fixture-pool', standalone: false, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title pull-left\">{{ 'designer.fixture-pool.title' | translate }}</h4>\n <button type=\"button\" class=\"close pull-right\" aria-label=\"Close\" (click)=\"cancel(); (false)\">\n <span aria-hidden=\"true\">&times;</span>\n </button>\n</div>\n<div class=\"modal-body\">\n <div class=\"row\">\n <div class=\"col-6\">\n <!-- Search for profiles -->\n <input\n type=\"text\"\n class=\"form-control input-block mb-3\"\n id=\"searchSet\"\n placeholder=\"{{ 'designer.fixture-pool.search-profiles' | translate }}\"\n (input)=\"searchExpression = $event.target.value; filterProfiles()\"\n />\n\n <!-- Profiles -->\n <div class=\"card border-secondary w-100\" style=\"height: 200px\">\n <div class=\"card-body p-0 w-100\">\n <div class=\"list-group list-group-flush w-100\">\n @if (loadingProfiles) {\n <a class=\"list-group-item flex-column align-items-start d-flex\">\n <p class=\"mb-0 mx-auto\">\n <i class=\"fa fa-spinner fa-pulse fa-fw\"></i>\n </p>\n </a>\n } @for (profile of filteredProfiles | sort : 'uuid'; track profile) {\n <div class=\"list-group-item\">\n <div class=\"row\">\n <div class=\"col-10 col-auto my-auto pl-2\">\n <p class=\"mb-0\">\n <span class=\"icon-{{ fixtureService.getFixtureIconClass(profile) }} mr-1\"></span> {{ profile.manufacturerName }} -\n {{ profile.name }}\n </p>\n </div>\n <div class=\"col-2 my-auto\">\n <a class=\"btn btn-primary btn-sm float-right\" href=\"#\" role=\"button\" (click)=\"addFixture(profile); (false)\">\n <i class=\"fa fa-plus\" aria-hidden=\"true\"></i>\n </a>\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n <div>\n <p class=\"d-inline-block mb-0 mt-2\">\n Powered by the <a href=\"https://open-fixture-library.org/\" target=\"_blank\">Open Fixture Library</a>\n </p>\n <a\n class=\"mt-2 btn btn-secondary btn-sm float-right\"\n href=\"https://open-fixture-library.org/fixture-editor\"\n target=\"_blank\"\n role=\"button\"\n >\n <i class=\"fa fa-file-o\" aria-hidden=\"true\"></i> {{ 'designer.fixture-pool.add-profile' | translate }}\n </a>\n @if (configService.localProfiles) {\n <button\n type=\"button\"\n class=\"mr-2 mt-2 btn btn-secondary btn-sm float-right\"\n (click)=\"updateProfiles(); (false)\"\n [disabled]=\"updatingProfiles\"\n role=\"button\"\n >\n @if (updatingProfiles) {\n <i class=\"fa fa-spinner fa-pulse fa-fw\" aria-hidden=\"true\"></i>\n } @if (!updatingProfiles) {\n <i class=\"fa fa-download fa-fw\" aria-hidden=\"true\"></i>\n }\n {{ 'designer.fixture-pool.update-profiles' | translate }}\n </button>\n }\n </div>\n </div>\n <div class=\"col-6\">\n <div class=\"row\">\n <!-- Add fixture from local profile -->\n <div class=\"col-6\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"createFixtureFromProfileFile()\" role=\"button\">\n <i class=\"fa fa-plus\" aria-hidden=\"true\"></i> {{ 'designer.fixture-pool.create-fixture-from-profile-file' | translate }}\n </button>\n </div>\n\n <!-- Universe -->\n <div class=\"col-6\">\n <div class=\"form-group row\">\n <label class=\"col-lg-4 col-form-label\">{{ 'designer.fixture-pool.universe' | translate }}</label>\n <div class=\"col-lg-8\">\n <div class=\"input-group\">\n <select\n class=\"custom-select\"\n [disabled]=\"universes.length === 0\"\n [ngModel]=\"selectedUniverse\"\n (ngModelChange)=\"selectUniverse($event)\"\n >\n @for (universe of universes; track universe.uuid) {\n <option [ngValue]=\"universe\">{{ universe.name }}</option>\n }\n </select>\n @if (freeUniverseEdit) {\n <div class=\"input-group-append\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"openEditUniverses()\">\n <i class=\"fa fa-pencil\" aria-hidden=\"true\"></i>\n </button>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Fixtures -->\n <div class=\"card border-secondary\" style=\"height: 235px\">\n <div class=\"card-body p-0\">\n <div class=\"list-group list-group-flush\" [sortablejs]=\"fixturePool\">\n @for (fixture of currentUniverseFixtures; track fixture) {\n <div\n class=\"list-group-item\"\n [class.active]=\"fixture == selectedFixture\"\n (click)=\"selectFixture(fixture)\"\n style=\"cursor: pointer\"\n >\n <div class=\"row\">\n <div class=\"col-auto list-sort-handle my-auto\" style=\"cursor: move; cursor: -webkit-grabbing\">\n <i class=\"fa fa-bars\" aria-hidden=\"true\"></i>\n </div>\n <div class=\"col-auto flex-grow pl-0 my-auto\">\n <p class=\"mb-0\">\n <span class=\"icon-{{ fixtureService.getFixtureIconClass(fixtureService.getProfileByUuid(fixture.profileUuid)) }} mr-1\">\n </span\n >{{ fixture.name }}\n </p>\n </div>\n <div class=\"col-auto my-auto\">\n <a class=\"btn btn-primary btn-sm float-right\" href=\"#\" role=\"button\" (click)=\"removeFixture(fixture); (false)\">\n <i class=\"fa fa-minus\" aria-hidden=\"true\"></i>\n </a>\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n <div class=\"row mt-3\">\n <div class=\"col\">\n <!-- Tab navigation -->\n <ul class=\"nav nav-tabs mb-4\" id=\"myTab\" role=\"tablist\">\n <li class=\"nav-item\">\n <a class=\"nav-link active\" id=\"sets-tab\" data-toggle=\"tab\" href=\"#dmx\" role=\"tab\">\n <i class=\"icon-ola\" aria-hidden=\"true\"></i> {{ 'designer.fixture-pool.dmx' | translate }}\n </a>\n </li>\n <li class=\"nav-item\">\n <a class=\"nav-link\" id=\"compositions-tab\" data-toggle=\"tab\" href=\"#settings\" role=\"tab\">\n <i class=\"fa fa-cog\" aria-hidden=\"true\"></i> {{ 'designer.fixture-pool.settings' | translate }}\n </a>\n </li>\n </ul>\n\n <!-- Tab content -->\n <div class=\"tab-content\" id=\"myTabContent\">\n <!-- DMX universe overview-->\n <div class=\"tab-pane show active\" id=\"dmx\" role=\"tabpanel\">\n <!-- Show all channel numbers -->\n <!-- <div class=\"form-group row\">\n <div class=\"col-auto flex-grow\"></div>\n <div class=\"col-lg-3 d-flex\">\n <div class=\"form-check my-auto ml-auto\">\n <input type=\"checkbox\" class=\"form-check-input\" id=\"showChannelNumbers\" [ngModel]=\"showChannelNumbers\"\n (ngModelChange)=\"showChannelNumbers = $event\">\n <label class=\"form-check-label\" for=\"showChannelNumbers\">Show channels</label>\n </div>\n </div>\n </div> -->\n\n <!-- List of DMX channels -->\n <div>\n @for (channel of dmxChannels; track $index; let i = $index) {\n <div\n class=\"dmx-channel\"\n [class.dmx-channel-occupied]=\"channelOccupied(i)\"\n [class.dmx-channel-occupied-start]=\"channelOccupiedStart(i)\"\n [class.dmx-channel-occupied-end]=\"channelOccupiedEnd(i)\"\n [class.dmx-channel-selected]=\"channelSelected(i)\"\n [class.dmx-channel-overlapped]=\"channelOverlapped(i)\"\n (mousedown)=\"channelMouseDown($event)\"\n (mouseover)=\"channelMouseOver($event)\"\n [attr.data-index]=\"i\"\n popover=\"{{ i + 1 }}\"\n placement=\"top\"\n triggers=\"mouseenter:mouseleave\"\n >\n <div class=\"d-flex w-100 dmx-channel-text\" [attr.data-index]=\"i\" [class.dmx-channel-text-visible]=\"showChannelNumbers\">\n <div class=\"m-auto\" style=\"font-size: 11px\" [attr.data-index]=\"i\"><small [attr.data-index]=\"i\">&nbsp;</small></div>\n </div>\n </div>\n }\n </div>\n </div>\n\n <!-- Fixture settings -->\n <div class=\"tab-pane\" id=\"settings\" role=\"tabpanel\">\n @if (selectedFixture && selectedFixtureProfile) {\n <div>\n <div class=\"form-group row\">\n <label class=\"col-lg-3 col-form-label\" for=\"fixtureName\">{{ 'designer.fixture-pool.fixture-name' | translate }}</label>\n <div class=\"col-lg-9\">\n <input type=\"text\" class=\"form-control\" [(ngModel)]=\"selectedFixture.name\" maxlength=\"200\" id=\"fixtureName\" />\n </div>\n </div>\n <div class=\"form-group row\">\n <label class=\"col-lg-3 col-form-label\">{{ 'designer.fixture-pool.mode' | translate }}</label>\n <div class=\"col-lg-9\">\n <select\n class=\"custom-select\"\n [ngModel]=\"selectedFixture.modeShortName\"\n (ngModelChange)=\"selectedFixture.modeShortName = $event\"\n >\n @for (mode of selectedFixtureProfile.modes; track mode) {\n <option [ngValue]=\"mode.shortName || mode.name\">\n {{ mode.name }}\n - {{ fixtureService.getModeChannelCount(selectedFixtureProfile, mode) }}\n {{ 'designer.fixture-pool.channels' | translate }}\n </option>\n }\n </select>\n </div>\n </div>\n <div class=\"form-group row\">\n <label class=\"col-lg-3 col-form-label\">{{ 'designer.fixture-pool.universe' | translate }}</label>\n <div class=\"col-lg-9\">\n <select\n class=\"custom-select\"\n [disabled]=\"universes.length === 0\"\n [ngModel]=\"selectedFixture.dmxUniverseUuid\"\n (ngModelChange)=\"selectedFixture.dmxUniverseUuid = $event\"\n >\n @for (universe of universes; track universe.uuid) {\n <option [ngValue]=\"universe.uuid\">{{ universe.name }}</option>\n }\n </select>\n </div>\n </div>\n <div class=\"form-group row\">\n <label class=\"col-lg-3 col-form-label\">{{ 'designer.fixture-pool.first-channel' | translate }}</label>\n <div class=\"col-lg-9\">\n <select\n class=\"custom-select\"\n [ngModel]=\"selectedFixture.dmxFirstChannel\"\n (ngModelChange)=\"selectedFixture.dmxFirstChannel = $event\"\n >\n @for (channel of dmxChannels; track $index; let i = $index) {\n <option [ngValue]=\"i\">\n {{ i + 1 }}\n </option>\n }\n </select>\n </div>\n </div>\n <div class=\"form-group row\">\n <label class=\"col-lg-3 col-form-label\"></label>\n <div class=\"col-lg-9\">\n <button type=\"button\" class=\"btn btn-primary\" (click)=\"addCopy(selectedFixture)\">\n {{ 'designer.fixture.add-copy' | translate }}\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n </div>\n</div>\n<div class=\"modal-footer\">\n <a class=\"mr-3 my-auto\" href=\"#\" role=\"button\" (click)=\"cancel(); (false)\">\n {{ 'designer.misc.cancel' | translate }}\n </a>\n <button type=\"button\" class=\"btn btn-primary\" (click)=\"ok()\">{{ 'designer.misc.ok' | translate }}</button>\n</div>\n", styles: [".list-group-item{padding:.3rem 1.25rem}.dmx-channel{display:flex;float:left;width:28px;border:1px solid white;margin-left:-1px;margin-bottom:-1px;padding-bottom:2px;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.dmx-channel-occupied{background-color:#63462e;border-left:none;border-right:none;cursor:move}.dmx-channel-occupied-start{border-left:1px solid white;border-top-left-radius:8px;border-bottom-left-radius:8px}.dmx-channel-occupied-end{border-right:1px solid white;border-top-right-radius:8px;border-bottom-right-radius:8px}.dmx-channel-selected{background-color:#fd7e14}.dmx-channel-overlapped{background-color:red;border:1px solid red;cursor:move}.card{overflow:auto}\n"] }]
5781
5857
  }], ctorParameters: () => [{ type: i1$1.BsModalRef }, { type: FixtureService }, { type: UuidService }, { type: ProjectService }, { type: PreviewService }, { type: i2.TranslateService }, { type: i3$1.ToastrService }, { type: PresetService }, { type: ConfigService }, { type: i1$1.BsModalService }, { type: LivePreviewService }], propDecorators: { mouseUp: [{
5782
5858
  type: HostListener,
5783
5859
  args: ['window:mouseup', ['$event']]
@@ -8691,6 +8767,7 @@ class DesignerModule {
8691
8767
  SceneSettingsComponent,
8692
8768
  ProjectSaveComponent,
8693
8769
  FixturePoolCreateFromFileComponent,
8770
+ FixturePoolEditUniversesComponent,
8694
8771
  DropzoneComponent,
8695
8772
  SortablejsDirective], imports: [i3.RouterModule, BrowserModule,
8696
8773
  BrowserAnimationsModule,
@@ -8757,6 +8834,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImpo
8757
8834
  SceneSettingsComponent,
8758
8835
  ProjectSaveComponent,
8759
8836
  FixturePoolCreateFromFileComponent,
8837
+ FixturePoolEditUniversesComponent,
8760
8838
  DropzoneComponent,
8761
8839
  SortablejsDirective,
8762
8840
  ],