@twab/visualization 1.14.1 → 1.15.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.
Files changed (2) hide show
  1. package/dist/visualization.js +165 -34
  2. package/package.json +1 -1
@@ -1743,6 +1743,21 @@ FramesInterface.prototype.initLight = async function () {
1743
1743
  };
1744
1744
 
1745
1745
  FramesInterface.prototype.changeTime = async function (time) {
1746
+ function isInvalid(timestampSeconds, frames) {
1747
+ const date = new Date(timestampSeconds * 1000);
1748
+
1749
+ const year = date.getUTCFullYear();
1750
+ const month = date.getUTCMonth();
1751
+ const day = date.getUTCDate();
1752
+
1753
+ const cutoff = new Date(Date.UTC(year, month, day, 2, frames - 100, 0));
1754
+ return date < cutoff
1755
+ }
1756
+
1757
+ if (this.numberOfRows > 1 && !isInvalid(time, this.framesPerRow)) {
1758
+ time = time - this.framesPerRow;
1759
+ }
1760
+
1746
1761
  await this.framesManager.goTo(time, true);
1747
1762
  await this.loadFrames();
1748
1763
  };
@@ -1843,9 +1858,19 @@ FramesInterface.prototype.getFrames = function (position) {
1843
1858
  const positions = ['prev', 'current', 'next'];
1844
1859
  const images = this.frames[positions[position]];
1845
1860
  if (images && images.length > 0) {
1846
- return images
1861
+ return images.map((img, index) => ({
1862
+ ...img,
1863
+ key: `${positions[position]}-${index}-${img.id || ''}`,
1864
+ }))
1847
1865
  }
1848
- return Array.from(Array(this.numberOfRows * this.framesPerRow).keys())
1866
+ return Array.from(
1867
+ { length: this.numberOfRows * this.framesPerRow },
1868
+ (_, index) => ({
1869
+ key: `${positions[position]}-empty-${index}`,
1870
+ isEmpty: true,
1871
+ index: index,
1872
+ })
1873
+ )
1849
1874
  };
1850
1875
 
1851
1876
  FramesInterface.prototype.getCurrentTime = function () {
@@ -1927,6 +1952,7 @@ FramesInterface.prototype.changeSize = async function (
1927
1952
  this.numberOfRows = numberOfRows;
1928
1953
  this.framesPerRow = framesPerRow;
1929
1954
  this.framesManager.size = this.numberOfRows * this.framesPerRow;
1955
+ await this.framesManager.goTo(this.getCurrentTime());
1930
1956
  await this.loadFrames();
1931
1957
  };
1932
1958
 
@@ -2934,7 +2960,7 @@ var GridImages = {
2934
2960
  '6x2':
2935
2961
  'PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyOC4xLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iQ2FtYWRhXzEiIHhtbG5zOnY9Imh0dHBzOi8vdmVjdGEuaW8vbmFubyINCgkgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiIHZpZXdCb3g9IjAgMCA1NTguMiAxODMuNiINCgkgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNTU4LjIgMTgzLjY7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4NCjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+DQoJLnN0MHtmaWxsOiMxNTY1QzA7fQ0KPC9zdHlsZT4NCjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik01NTguMiwwdjkwLjFoLTg4LjVjLTEuNiwwLTEuNiwwLTEuNi0xLjZWMS43YzAtMS42LDAtMS42LDEuNi0xLjZDNDY5LjcsMC4xLDU1OC4yLDAsNTU4LjIsMHoiLz4NCjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik05MC4yLDE4My41SDBWOTUuMWMwLTEuNywwLTEuNywxLjctMS43aDg2LjdjMS44LDAsMS44LDAsMS44LDEuOFYxODMuNXogTTE4My44LDE4My41SDkzLjZWOTUuMw0KCWMwLTEuOSwwLTEuOSwxLjktMS45SDE4MmMxLjgsMCwxLjgsMCwxLjgsMS44TDE4My44LDE4My41TDE4My44LDE4My41eiBNNDY0LjcsMTgzLjVoLTkwLjJWOTUuMWMwLTEuNywwLTEuNywxLjYtMS43SDQ2Mw0KCWMxLjYsMCwxLjYsMCwxLjYsMS43djY0LjFMNDY0LjcsMTgzLjVMNDY0LjcsMTgzLjV6IE01NTguMiw5My41djg5YzAsMC45LTAuMiwxLjEtMS4xLDEuMUg0NjhWOTUuMmMwLTEuNywwLTEuNywxLjctMS43SDU1OC4yDQoJTDU1OC4yLDkzLjV6IE0yNzcuMywxODMuNWgtOTBWOTQuOGMwLTAuOSwwLjEtMS41LDEuMi0xLjVIMjc2YzAuOSwwLDEuMiwwLjMsMS4yLDEuMkwyNzcuMywxODMuNUwyNzcuMywxODMuNXogTTM3MC45LDE4My41aC05MA0KCVY5NS4xYzAtMS43LDAtMS43LDEuNy0xLjdoODYuNWMxLjgsMCwxLjgsMCwxLjgsMS44VjE4My41eiIvPg0KPHBhdGggY2xhc3M9InN0MCIgZD0iTTAsNDUuMVYxLjVDMCwwLjMsMC4zLDAsMS40LDBoODcuM2MxLjIsMCwxLjUsMC4zLDEuNCwxLjV2ODcuMWMwLDEuMi0wLjMsMS41LTEuNSwxLjVIMS40DQoJYy0xLjMsMC0xLjUtMC40LTEuNS0xLjZMMCw0NS4xeiBNOTMuNiw0NVYxLjVDOTMuNiwwLjMsOTMuOSwwLDk1LDBoODcuM2MxLjIsMCwxLjQsMC4zLDEuNCwxLjV2ODcuMmMwLDEuMi0wLjQsMS40LTEuNSwxLjRIOTUNCgljLTEuMiwwLTEuNS0wLjQtMS41LTEuNUw5My42LDQ1eiBNNDE5LjcsMGg0My40YzEuMiwwLDEuNiwwLjIsMS42LDEuNXY4Ny4xYzAsMS4yLTAuMywxLjUtMS41LDEuNUgzNzZjLTEuMSwwLTEuNS0wLjMtMS41LTEuNFYxLjUNCgljMC0xLjIsMC4zLTEuNCwxLjUtMS40QzM3NiwwLjEsNDE5LjcsMCw0MTkuNywweiBNMjc3LjMsNDUuMXY0My4zYzAsMS42LDAsMS42LTEuNiwxLjZoLTg2LjhjLTEuNywwLTEuNywwLTEuNy0xLjZWMS41DQoJYzAtMS4yLDAuMy0xLjUsMS41LTEuNWg4Ny4yYzEuMiwwLDEuNCwwLjQsMS40LDEuNUwyNzcuMyw0NS4xTDI3Ny4zLDQ1LjF6IE0yODAuOSw0NC45VjEuNGMwLTEuMSwwLjMtMS41LDEuNC0xLjVoODcuMg0KCWMxLjMsMCwxLjUsMC40LDEuNSwxLjZ2ODYuOWMwLDEuNiwwLDEuNi0xLjUsMS42aC04N2MtMS4xLDAtMS41LTAuMy0xLjUtMS40TDI4MC45LDQ0Ljl6Ii8+DQo8L3N2Zz4NCg==',
2936
2962
  custom:
2937
- 'PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgNTU4LjIgMTgzLjYiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDU1OC4yIDE4My42OyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+Cgkuc3Qwe2ZpbGw6IzE1NjVDMDt9Cgkuc3Qxe2ZpbGw6bm9uZTtzdHJva2U6IzE1NjVDMDtzdHJva2Utd2lkdGg6Nzt9Cgkuc3Qye2ZpbGw6I0NDQ0NDQzt9Cjwvc3R5bGU+CjwhLS0gR3JleSBzcXVhcmVzIGluIHRvcCByb3cgd2l0aCAxMHB4IHBhZGRpbmcgLS0+CjxyZWN0IHg9IjEwIiB5PSIxMCIgd2lkdGg9IjgwIiBoZWlnaHQ9IjgwIiBjbGFzcz0ic3QyIi8+CjxyZWN0IHg9IjEwMy42IiB5PSIxMCIgd2lkdGg9IjgwIiBoZWlnaHQ9IjgwIiBjbGFzcz0ic3QyIi8+CjxyZWN0IHg9IjE5Ny4yIiB5PSIxMCIgd2lkdGg9IjgwIiBoZWlnaHQ9IjgwIiBjbGFzcz0ic3QyIi8+CjxyZWN0IHg9IjI5MC45IiB5PSIxMCIgd2lkdGg9IjgwIiBoZWlnaHQ9IjgwIiBjbGFzcz0ic3QyIi8+CjxyZWN0IHg9IjM4NC41IiB5PSIxMCIgd2lkdGg9IjgwIiBoZWlnaHQ9IjgwIiBjbGFzcz0ic3QyIi8+CjxyZWN0IHg9IjQ3OCIgeT0iMTAiIHdpZHRoPSI3MC4yIiBoZWlnaHQ9IjgwIiBjbGFzcz0ic3QyIi8+Cgo8IS0tIEdyZXkgc3F1YXJlcyBpbiBib3R0b20gcm93IHdpdGggMTBweCBwYWRkaW5nIC0tPgo8cmVjdCB4PSIxMCIgeT0iOTMuNiIgd2lkdGg9IjgwIiBoZWlnaHQ9IjgwIiBjbGFzcz0ic3QyIi8+CjxyZWN0IHg9IjEwMy42IiB5PSI5My42IiB3aWR0aD0iODAiIGhlaWdodD0iODAiIGNsYXNzPSJzdDIiLz4KPHJlY3QgeD0iMTk3LjIiIHk9IjkzLjYiIHdpZHRoPSI4MCIgaGVpZ2h0PSI4MCIgY2xhc3M9InN0MiIvPgo8cmVjdCB4PSIyOTAuOSIgeT0iOTMuNiIgd2lkdGg9IjgwIiBoZWlnaHQ9IjgwIiBjbGFzcz0ic3QyIi8+CjxyZWN0IHg9IjM4NC41IiB5PSI5My42IiB3aWR0aD0iODAiIGhlaWdodD0iODAiIGNsYXNzPSJzdDIiLz4KPHJlY3QgeD0iNDc4IiB5PSI5My42IiB3aWR0aD0iNzAuMiIgaGVpZ2h0PSI4MCIgY2xhc3M9InN0MiIvPgoKPCEtLSBCb3JkZXIgLS0+CjxyZWN0IHg9IjEuNSIgeT0iMS41IiB3aWR0aD0iNTU1LjIiIGhlaWdodD0iMTgwLjYiIGNsYXNzPSJzdDEiLz4KCjwhLS0gVGV4dCAtLT4KPHRleHQgeD0iNTAlIiB5PSI1MCUiIGRvbWluYW50LWJhc2VsaW5lPSJtaWRkbGUiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGNsYXNzPSJzdDAiIGZvbnQtZmFtaWx5PSJBcmlhbCwgc2Fucy1zZXJpZiIgZm9udC1zaXplPSI3MiIgZm9udC13ZWlnaHQ9ImJvbGQiPmN1c3RvbTwvdGV4dD4KPC9zdmc+',
2963
+ 'PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgNTU4LjIgMTgzLjYiIHN0eWxlPSJlbmFibGUtYmFja2dyb3VuZDpuZXcgMCAwIDU1OC4yIDE4My42OyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+Cgkuc3Qwe2ZpbGw6IzE1NjVDMDt9Cgkuc3Qxe2ZpbGw6bm9uZTtzdHJva2U6IzE1NjVDMDtzdHJva2Utd2lkdGg6Njt9Cgkuc3Qye2ZpbGw6I0NDQ0NDQzt9Cjwvc3R5bGU+CjwhLS0gVG9wIHJvdywgY2VudGVyZWQgLS0+CjxyZWN0IHg9IjEzLjUiICB5PSIxMCIgICAgd2lkdGg9IjgyIiBoZWlnaHQ9IjgwIiBjbGFzcz0ic3QyIi8+CjxyZWN0IHg9IjEwMy41IiB5PSIxMCIgICAgd2lkdGg9IjgyIiBoZWlnaHQ9IjgwIiBjbGFzcz0ic3QyIi8+CjxyZWN0IHg9IjE5My41IiB5PSIxMCIgICAgd2lkdGg9IjgyIiBoZWlnaHQ9IjgwIiBjbGFzcz0ic3QyIi8+CjxyZWN0IHg9IjI4My41IiB5PSIxMCIgICAgd2lkdGg9IjgyIiBoZWlnaHQ9IjgwIiBjbGFzcz0ic3QyIi8+CjxyZWN0IHg9IjM3My41IiB5PSIxMCIgICAgd2lkdGg9IjgyIiBoZWlnaHQ9IjgwIiBjbGFzcz0ic3QyIi8+CjxyZWN0IHg9IjQ2My41IiB5PSIxMCIgICAgd2lkdGg9IjgyIiBoZWlnaHQ9IjgwIiBjbGFzcz0ic3QyIi8+Cgo8IS0tIEJvdHRvbSByb3csIHNhbWUgeCB2YWx1ZXMgLS0+CjxyZWN0IHg9IjEzLjUiICB5PSI5My42IiAgd2lkdGg9IjgyIiBoZWlnaHQ9IjgwIiBjbGFzcz0ic3QyIi8+CjxyZWN0IHg9IjEwMy41IiB5PSI5My42IiAgd2lkdGg9IjgyIiBoZWlnaHQ9IjgwIiBjbGFzcz0ic3QyIi8+CjxyZWN0IHg9IjE5My41IiB5PSI5My42IiAgd2lkdGg9IjgyIiBoZWlnaHQ9IjgwIiBjbGFzcz0ic3QyIi8+CjxyZWN0IHg9IjI4My41IiB5PSI5My42IiAgd2lkdGg9IjgyIiBoZWlnaHQ9IjgwIiBjbGFzcz0ic3QyIi8+CjxyZWN0IHg9IjM3My41IiB5PSI5My42IiAgd2lkdGg9IjgyIiBoZWlnaHQ9IjgwIiBjbGFzcz0ic3QyIi8+CjxyZWN0IHg9IjQ2My41IiB5PSI5My42IiAgd2lkdGg9IjgyIiBoZWlnaHQ9IjgwIiBjbGFzcz0ic3QyIi8+CgoKPCEtLSBCb3JkZXIgLS0+CjxyZWN0IHg9IjEuNSIgeT0iMS41IiB3aWR0aD0iNTU2IiBoZWlnaHQ9IjE4MC42IiBjbGFzcz0ic3QxIi8+Cgo8IS0tIFRleHQgLS0+Cjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBkb21pbmFudC1iYXNlbGluZT0ibWlkZGxlIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBjbGFzcz0ic3QwIiBmb250LWZhbWlseT0iQXJpYWwsIHNhbnMtc2VyaWYiIGZvbnQtc2l6ZT0iNzIiIGZvbnQtd2VpZ2h0PSJib2xkIj5jdXN0b208L3RleHQ+Cjwvc3ZnPg==',
2938
2964
  };
2939
2965
 
2940
2966
  //
@@ -4214,7 +4240,7 @@ var script = {
4214
4240
  showCustomInputs: false,
4215
4241
  stretchFrame: false,
4216
4242
  scrollCounter: 300,
4217
- eventsArray: [],
4243
+ eventsArray: [],
4218
4244
  }
4219
4245
  },
4220
4246
  async created() {
@@ -4302,7 +4328,7 @@ var script = {
4302
4328
 
4303
4329
  if (matchedConfig) {
4304
4330
  event.preventDefault();
4305
- this.toggleEvent(matchedConfig.event);
4331
+ this.toggleEvent(matchedConfig.event, event.key.toUpperCase());
4306
4332
  }
4307
4333
  },
4308
4334
  scrollEvent(e) {
@@ -4658,11 +4684,14 @@ var script = {
4658
4684
  const div = document.createElement('div');
4659
4685
  div.innerHTML = this.frames[0].image;
4660
4686
 
4661
- this.aspectRatio =
4662
- div.getElementsByTagName('img')[0].naturalWidth /
4663
- div.getElementsByTagName('img')[0].naturalHeight;
4687
+ if (div.getElementsByTagName('img')[0]) {
4688
+ this.aspectRatio =
4689
+ div.getElementsByTagName('img')[0].naturalWidth /
4690
+ div.getElementsByTagName('img')[0].naturalHeight;
4691
+ }
4664
4692
 
4665
- this.activeFrame = this.getIndex(1, 0, Positions.current);
4693
+ this.activeFrame =
4694
+ this.getIndex(1, 0, Positions.current) + this.framesPerRow;
4666
4695
 
4667
4696
  this.activeVideo = null;
4668
4697
  },
@@ -4693,7 +4722,7 @@ var script = {
4693
4722
  });
4694
4723
  if (foundEvent) {
4695
4724
  return `event-${foundEvent.type}`
4696
- }else {
4725
+ } else {
4697
4726
  return 'event-gap'
4698
4727
  }
4699
4728
  },
@@ -4757,7 +4786,7 @@ var script = {
4757
4786
  this.activeFrame = index;
4758
4787
  }
4759
4788
  },
4760
- toggleEvent(eventConfig) {
4789
+ toggleEvent(eventConfig, shortcutKey) {
4761
4790
  const currentFrame = this.$refs.frames?.find(
4762
4791
  (f) => f.index === this.activeFrame
4763
4792
  );
@@ -4775,44 +4804,98 @@ var script = {
4775
4804
  }
4776
4805
 
4777
4806
  if (eventType === 'A') {
4778
- this.handleTypeA(frameTime, eventConfig);
4807
+ this.handleTypeA(frameTime, eventConfig, shortcutKey);
4779
4808
  } else if (eventType === 'B') {
4780
- this.handleTypeB(frameTime, eventConfig);
4809
+ this.handleTypeB(frameTime, eventConfig, shortcutKey);
4781
4810
  } else if (eventType === 'C') {
4782
- this.handleTypeC(frameTime, eventConfig);
4811
+ this.handleTypeC(frameTime, eventConfig, shortcutKey);
4783
4812
  }
4784
4813
 
4785
4814
  this.canInsertTime = this.hasAnyEvents();
4786
4815
  document.getElementById(`frame-${this.activeFrame}`).click();
4787
4816
  },
4788
- handleTypeA(frameTime, eventConfig) {
4817
+ handleTypeA(frameTime, eventConfig, shortcutKey) {
4789
4818
  if (this.currentEventA && this.currentEventA.hour_ini) {
4790
- // Close the event A
4791
- this.currentEventA.hour_end = frameTime;
4792
- this.events.A.push({ ...this.currentEventA });
4793
- this.currentEventA = null;
4794
- delete this.activeEvents.A;
4819
+ // Check if the shortcut matches the one used to start the event
4820
+ if (this.currentEventA.shortcutKey !== shortcutKey) {
4821
+ console.warn('Use the same shortcut key to close this event');
4822
+ return
4823
+ }
4824
+ // If clicking on the same frame where it started, remove the selection
4825
+ if (this.currentEventA.hour_ini === frameTime) {
4826
+ this.currentEventA = null;
4827
+ delete this.activeEvents.A;
4828
+ } else {
4829
+ // Close the event A
4830
+ this.currentEventA.hour_end = frameTime;
4831
+ this.events.A.push({ ...this.currentEventA });
4832
+ this.currentEventA = null;
4833
+ delete this.activeEvents.A;
4834
+ }
4795
4835
  } else {
4836
+ // Check if clicking on hour_end of a completed event to reopen it
4837
+ const completedEvent = this.events.A.find(
4838
+ (e) => e.hour_end === frameTime && e.shortcutKey === shortcutKey
4839
+ );
4840
+ if (completedEvent) {
4841
+ // Remove from completed events and reopen it
4842
+ const index = this.events.A.indexOf(completedEvent);
4843
+ this.events.A.splice(index, 1);
4844
+ this.currentEventA = {
4845
+ ...completedEvent,
4846
+ hour_end: null,
4847
+ };
4848
+ this.activeEvents.A = true;
4849
+ return
4850
+ }
4851
+
4796
4852
  // Start a new event A
4797
4853
  // Type A cannot be nested inside another Type A (already checked by currentEventA)
4798
4854
  this.currentEventA = {
4799
4855
  ...eventConfig,
4800
4856
  hour_ini: frameTime,
4801
4857
  hour_end: null,
4858
+ shortcutKey: shortcutKey,
4802
4859
  };
4803
4860
  this.activeEvents.A = true;
4804
4861
  }
4805
4862
  },
4806
- handleTypeB(frameTime, eventConfig) {
4863
+ handleTypeB(frameTime, eventConfig, shortcutKey) {
4807
4864
  const lastEventB = this.events.B[this.events.B.length - 1];
4808
4865
  const lastEventC = this.events.C[this.events.C.length - 1];
4809
4866
 
4810
4867
  if (lastEventB && !lastEventB.hour_end) {
4811
- // Close the event B
4812
- lastEventB.hour_end = frameTime;
4813
- delete this.activeEvents.B;
4814
- this.eventBlocked = false; // Unblock events
4868
+ // Check if the shortcut matches the one used to start the event
4869
+ if (lastEventB.shortcutKey !== shortcutKey) {
4870
+ console.warn('Use the same shortcut key to close this event');
4871
+ return
4872
+ }
4873
+ // If clicking on the same frame where it started, remove the selection
4874
+ if (lastEventB.hour_ini === frameTime) {
4875
+ this.events.B.pop(); // Remove the last event
4876
+ delete this.activeEvents.B;
4877
+ this.eventBlocked = false; // Unblock events
4878
+ } else {
4879
+ // Close the event B
4880
+ lastEventB.hour_end = frameTime;
4881
+ delete this.activeEvents.B;
4882
+ this.eventBlocked = false; // Unblock events
4883
+ }
4815
4884
  } else {
4885
+ // Check if clicking on hour_end of a completed event to reopen it
4886
+ const completedEvent = this.events.B.find(
4887
+ (e) => e.hour_end === frameTime && e.shortcutKey === shortcutKey
4888
+ );
4889
+ if (completedEvent) {
4890
+ // Remove hour_end to reopen the event
4891
+ completedEvent.hour_end = null;
4892
+ this.activeEvents.B = true;
4893
+ if (!this.currentEventA) {
4894
+ this.eventBlocked = true;
4895
+ }
4896
+ return
4897
+ }
4898
+
4816
4899
  // Check if we're outside Type A and already have an unclosed B or C
4817
4900
  if (!this.currentEventA) {
4818
4901
  if (
@@ -4839,19 +4922,47 @@ var script = {
4839
4922
  ...eventConfig,
4840
4923
  hour_ini: frameTime,
4841
4924
  hour_end: null,
4925
+ shortcutKey: shortcutKey,
4842
4926
  });
4843
4927
  this.activeEvents.B = true;
4844
4928
  }
4845
4929
  },
4846
- handleTypeC(frameTime, eventConfig) {
4930
+ handleTypeC(frameTime, eventConfig, shortcutKey) {
4847
4931
  const lastEventC = this.events.C[this.events.C.length - 1];
4848
4932
  const lastEventB = this.events.B[this.events.B.length - 1];
4849
4933
 
4850
4934
  if (lastEventC && !lastEventC.hour_end) {
4851
- // Close the event C
4852
- lastEventC.hour_end = frameTime;
4853
- delete this.activeEvents.C;
4935
+ // Check if the shortcut matches the one used to start the event
4936
+ if (lastEventC.shortcutKey !== shortcutKey) {
4937
+ console.warn('Use the same shortcut key to close this event');
4938
+ return
4939
+ }
4940
+ // If clicking on the same frame where it started, remove the selection
4941
+ if (lastEventC.hour_ini === frameTime) {
4942
+ this.events.C.pop(); // Remove the last event
4943
+ delete this.activeEvents.C;
4944
+ this.eventBlocked = false; // Unblock events if it was outside Type A
4945
+ } else {
4946
+ // Close the event C
4947
+ lastEventC.hour_end = frameTime;
4948
+ delete this.activeEvents.C;
4949
+ this.eventBlocked = false; // Unblock events if it was outside Type A
4950
+ }
4854
4951
  } else {
4952
+ // Check if clicking on hour_end of a completed event to reopen it
4953
+ const completedEvent = this.events.C.find(
4954
+ (e) => e.hour_end === frameTime && e.shortcutKey === shortcutKey
4955
+ );
4956
+ if (completedEvent) {
4957
+ // Remove hour_end to reopen the event
4958
+ completedEvent.hour_end = null;
4959
+ this.activeEvents.C = true;
4960
+ if (!this.currentEventA) {
4961
+ this.eventBlocked = true;
4962
+ }
4963
+ return
4964
+ }
4965
+
4855
4966
  // Check if we're outside Type A and already have an unclosed B or C
4856
4967
  if (!this.currentEventA) {
4857
4968
  if (
@@ -4863,6 +4974,8 @@ var script = {
4863
4974
  );
4864
4975
  return
4865
4976
  }
4977
+ // Outside Type A: block other events when C is active
4978
+ this.eventBlocked = true;
4866
4979
  }
4867
4980
 
4868
4981
  // Auto-close any unclosed B event before starting C
@@ -4876,10 +4989,21 @@ var script = {
4876
4989
  ...eventConfig,
4877
4990
  hour_ini: frameTime,
4878
4991
  hour_end: null,
4992
+ shortcutKey: shortcutKey,
4879
4993
  });
4880
4994
  this.activeEvents.C = true;
4881
4995
  }
4882
4996
  },
4997
+ clearAllFrameSelections() {
4998
+ // Clear all events
4999
+ this.events.A = [];
5000
+ this.events.B = [];
5001
+ this.events.C = [];
5002
+ this.currentEventA = null;
5003
+ this.activeEvents = {};
5004
+ this.eventBlocked = false;
5005
+ this.canInsertTime = false;
5006
+ },
4883
5007
  hasAnyEvents() {
4884
5008
  return (
4885
5009
  this.events.A.length > 0 ||
@@ -5121,8 +5245,8 @@ var script = {
5121
5245
 
5122
5246
  this.getFramesArray();
5123
5247
 
5124
- this.activeFrame = this.getIndex(1, 0, Positions.current);
5125
-
5248
+ this.activeFrame =
5249
+ this.getIndex(1, 0, Positions.current) + this.framesPerRow;
5126
5250
  this.activeVideo = null;
5127
5251
 
5128
5252
  return true
@@ -5368,7 +5492,7 @@ var script = {
5368
5492
 
5369
5493
  if (this.jumpOnInsert) {
5370
5494
  this.changeHour(this.convertToAudienceTime(biggestHourEnd)).then(() => {
5371
- this.activeFrame = this.getIndex(1, 1, Positions.current);
5495
+ this.activeFrame = this.getIndex(2, 1, Positions.current);
5372
5496
  });
5373
5497
  }
5374
5498
 
@@ -5580,6 +5704,13 @@ var __vue_render__ = function () {
5580
5704
  ? _c("GlobalEvents", {
5581
5705
  on: {
5582
5706
  keydown: [
5707
+ function ($event) {
5708
+ if (!$event.type.indexOf("key") && $event.keyCode !== 46) {
5709
+ return null
5710
+ }
5711
+ $event.preventDefault();
5712
+ return _vm.clearAllFrameSelections.apply(null, arguments)
5713
+ },
5583
5714
  function ($event) {
5584
5715
  if (
5585
5716
  !$event.type.indexOf("key") &&
@@ -6121,11 +6252,11 @@ __vue_render__._withStripped = true;
6121
6252
  /* style */
6122
6253
  const __vue_inject_styles__ = function (inject) {
6123
6254
  if (!inject) return
6124
- inject("data-v-83e856c6_0", { source: "\n.visualization-row[data-v-83e856c6] {\r\n display: flex;\r\n flex-wrap: wrap;\r\n flex: 1 1 auto;\n}\n.visualization-col[data-v-83e856c6] {\r\n flex-basis: 0;\r\n flex-grow: 1;\r\n max-width: 100%;\r\n padding: 5px;\n}\n.visualization-divider[data-v-83e856c6] {\r\n display: block;\r\n flex: 1 1 100%;\r\n height: 0px;\r\n max-height: 0px;\r\n opacity: 1;\r\n transition: inherit;\r\n border-style: solid;\r\n border-width: thin 0 0 0;\r\n border-color: rgba(0, 0, 0, 0.12);\r\n margin: 0;\n}\n.visualization-divider.vertical[data-v-83e856c6] {\r\n align-self: stretch;\r\n border-width: 0 thin 0 0;\r\n display: inline-flex;\r\n height: inherit;\r\n margin-left: -1px;\r\n max-height: 100%;\r\n max-width: 0px;\r\n vertical-align: text-bottom;\r\n width: 0px;\n}\n.visualization-card[data-v-83e856c6] {\r\n flex-basis: 0;\r\n flex-grow: 1;\r\n max-width: 100%;\r\n padding: 12px;\r\n width: 100%;\r\n transition-property: box-shadow, opacity, -webkit-box-shadow;\r\n overflow-wrap: break-word;\r\n /*box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14),\r\n 0 1px 5px 0 rgba(0, 0, 0, 0.12);*/\n}\n.visualization-justify-center[data-v-83e856c6],\r\n*[data-v-83e856c6] .visualization-justify-center {\r\n justify-content: center;\n}\n.visualization-align-center[data-v-83e856c6] {\r\n align-items: center;\n}\n#visualization-container[data-v-83e856c6] {\r\n max-width: 100% !important;\r\n margin: 0 auto !important;\r\n height: 100%;\r\n border-bottom: none;\n}\n#visualization-container > .card[data-v-83e856c6] {\r\n border-radius: 0 !important;\r\n font-size: 12px;\r\n width: 100%;\r\n box-shadow: none;\r\n height: 100%;\n}\n#command-bar[data-v-83e856c6],\r\n#info-bar[data-v-83e856c6] {\r\n background-color: #f5f5f5;\r\n box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14),\r\n 0 1px 5px 0 rgba(0, 0, 0, 0.12);\n}\n#command-bar button[data-v-83e856c6] {\r\n width: 42px;\r\n height: 36px;\r\n border: none;\r\n background: none;\n}\n#command-bar button[data-v-83e856c6]:hover {\r\n cursor: pointer;\r\n background: rgba(0, 0, 0, 0.12);\n}\n#command-bar svg[data-v-83e856c6] {\r\n font-size: 16px;\n}\n#command-bar[data-v-83e856c6] {\r\n padding: 0 !important;\n}\n#info-bar[data-v-83e856c6] {\r\n padding: 4px;\r\n font-size: 14px;\r\n position: relative;\n}\n.settings-container[data-v-83e856c6] {\r\n position: absolute;\r\n right: 14px;\r\n top: 50%;\r\n transform: translateY(-50%);\n}\n.settings-container > *[data-v-83e856c6] {\r\n margin: 0 2px;\r\n cursor: pointer;\n}\n#info-bar svg[data-v-83e856c6] {\r\n font-size: 16px;\n}\n#info-bar .divider[data-v-83e856c6] {\r\n margin: 0 8px;\n}\nsvg[data-v-83e856c6]:focus {\r\n border: none;\n}\n.visualization-card[data-v-83e856c6] {\r\n border-left: 8px solid #eee;\n}\n.active-tab[data-v-83e856c6] {\r\n border-left: 8px solid var(--visualization-primary) !important;\r\n border-image-slice: 1;\n}\n[id^='frame-'][data-v-83e856c6] {\r\n padding: 1px;\r\n display: flex;\r\n flex-flow: column;\n}\n.tooltip[data-v-83e856c6] {\r\n display: block !important;\r\n z-index: 10000;\n}\n.tooltip .tooltip-inner[data-v-83e856c6] {\r\n background: var(--visualization-primary);\r\n color: white;\r\n border-radius: 16px;\r\n padding: 5px 10px 4px;\n}\n.tooltip .tooltip-arrow[data-v-83e856c6] {\r\n width: 0;\r\n height: 0;\r\n border-style: solid;\r\n position: absolute;\r\n margin: 5px;\r\n border-color: var(--visualization-primary);\r\n z-index: 1;\n}\n.tooltip[x-placement^='top'][data-v-83e856c6] {\r\n margin-bottom: 5px;\n}\n.tooltip[x-placement^='top'] .tooltip-arrow[data-v-83e856c6] {\r\n border-width: 5px 5px 0 5px;\r\n border-left-color: transparent !important;\r\n border-right-color: transparent !important;\r\n border-bottom-color: transparent !important;\r\n bottom: -5px;\r\n left: calc(50% - 5px);\r\n margin-top: 0;\r\n margin-bottom: 0;\n}\n.tooltip[x-placement^='bottom'][data-v-83e856c6] {\r\n margin-top: 5px;\n}\n.tooltip[x-placement^='bottom'] .tooltip-arrow[data-v-83e856c6] {\r\n border-width: 0 5px 5px 5px;\r\n border-left-color: transparent !important;\r\n border-right-color: transparent !important;\r\n border-top-color: transparent !important;\r\n top: -5px;\r\n left: calc(50% - 5px);\r\n margin-top: 0;\r\n margin-bottom: 0;\n}\n.tooltip[x-placement^='right'][data-v-83e856c6] {\r\n margin-left: 5px;\n}\n.tooltip[x-placement^='right'] .tooltip-arrow[data-v-83e856c6] {\r\n border-width: 5px 5px 5px 0;\r\n border-left-color: transparent !important;\r\n border-top-color: transparent !important;\r\n border-bottom-color: transparent !important;\r\n left: -5px;\r\n top: calc(50% - 5px);\r\n margin-left: 0;\r\n margin-right: 0;\n}\n.tooltip[x-placement^='left'][data-v-83e856c6] {\r\n margin-right: 5px;\n}\n.tooltip[x-placement^='left'] .tooltip-arrow[data-v-83e856c6] {\r\n border-width: 5px 0 5px 5px;\r\n border-top-color: transparent !important;\r\n border-right-color: transparent !important;\r\n border-bottom-color: transparent !important;\r\n right: -5px;\r\n top: calc(50% - 5px);\r\n margin-left: 0;\r\n margin-right: 0;\n}\n.tooltip.popover .popover-inner[data-v-83e856c6] {\r\n background: #f9f9f9;\r\n color: black;\r\n padding: 24px;\r\n border-radius: 5px;\r\n box-shadow: 0 5px 30px rgba(black, 0.1);\n}\n.tooltip.popover .popover-arrow[data-v-83e856c6] {\r\n border-color: #f9f9f9;\n}\n.tooltip[aria-hidden='true'][data-v-83e856c6] {\r\n visibility: hidden;\r\n opacity: 0;\r\n transition: opacity 0.15s, visibility 0.15s;\n}\n.tooltip[aria-hidden='false'][data-v-83e856c6] {\r\n visibility: visible;\r\n opacity: 1;\r\n transition: opacity 0.15s;\n}\n.custom-frametime[data-v-83e856c6] {\r\n font-size: smaller;\n}\r\n\r\n/* DAR CORES AOS TEMPOS DEPENDENDO DO TIPO DE EVENTO */\n.event-A[data-v-83e856c6] {\r\n color: #000; /* black for A */\n}\n.event-B[data-v-83e856c6]{\r\n color: rgb(0, 115, 230); /* blue for B */\n}\n.event-C[data-v-83e856c6]{\r\n color: rgb(0, 179, 0); /* green for C */\n}\n.event-gap[data-v-83e856c6] {\r\n color: #555; /* Default color for gaps */\n}\r\n", map: {"version":3,"sources":["C:\\Workspace\\visualization\\src\\Visualization.vue"],"names":[],"mappings":";AA2tDA;EACA,aAAA;EACA,eAAA;EACA,cAAA;AACA;AAEA;EACA,aAAA;EACA,YAAA;EACA,eAAA;EACA,YAAA;AACA;AAEA;EACA,cAAA;EACA,cAAA;EACA,WAAA;EACA,eAAA;EACA,UAAA;EACA,mBAAA;EACA,mBAAA;EACA,wBAAA;EACA,iCAAA;EACA,SAAA;AACA;AAEA;EACA,mBAAA;EACA,wBAAA;EACA,oBAAA;EACA,eAAA;EACA,iBAAA;EACA,gBAAA;EACA,cAAA;EACA,2BAAA;EACA,UAAA;AACA;AAEA;EACA,aAAA;EACA,YAAA;EACA,eAAA;EACA,aAAA;EACA,WAAA;EACA,4DAAA;EACA,yBAAA;EACA;qCACA;AACA;AAEA;;EAEA,uBAAA;AACA;AAEA;EACA,mBAAA;AACA;AAEA;EACA,0BAAA;EACA,yBAAA;EACA,YAAA;EACA,mBAAA;AACA;AACA;EACA,2BAAA;EACA,eAAA;EACA,WAAA;EACA,gBAAA;EACA,YAAA;AACA;AAEA;;EAEA,yBAAA;EACA;mCACA;AACA;AACA;EACA,WAAA;EACA,YAAA;EACA,YAAA;EACA,gBAAA;AACA;AAEA;EACA,eAAA;EACA,+BAAA;AACA;AAEA;EACA,eAAA;AACA;AAEA;EACA,qBAAA;AACA;AAEA;EACA,YAAA;EACA,eAAA;EACA,kBAAA;AACA;AAEA;EACA,kBAAA;EACA,WAAA;EACA,QAAA;EACA,2BAAA;AACA;AAEA;EACA,aAAA;EACA,eAAA;AACA;AAEA;EACA,eAAA;AACA;AAEA;EACA,aAAA;AACA;AAEA;EACA,YAAA;AACA;AAEA;EACA,2BAAA;AACA;AAEA;EACA,8DAAA;EACA,qBAAA;AACA;AAEA;EACA,YAAA;EACA,aAAA;EACA,iBAAA;AACA;AAEA;EACA,yBAAA;EACA,cAAA;AACA;AAEA;EACA,wCAAA;EACA,YAAA;EACA,mBAAA;EACA,qBAAA;AACA;AAEA;EACA,QAAA;EACA,SAAA;EACA,mBAAA;EACA,kBAAA;EACA,WAAA;EACA,0CAAA;EACA,UAAA;AACA;AAEA;EACA,kBAAA;AACA;AAEA;EACA,2BAAA;EACA,yCAAA;EACA,0CAAA;EACA,2CAAA;EACA,YAAA;EACA,qBAAA;EACA,aAAA;EACA,gBAAA;AACA;AAEA;EACA,eAAA;AACA;AAEA;EACA,2BAAA;EACA,yCAAA;EACA,0CAAA;EACA,wCAAA;EACA,SAAA;EACA,qBAAA;EACA,aAAA;EACA,gBAAA;AACA;AAEA;EACA,gBAAA;AACA;AAEA;EACA,2BAAA;EACA,yCAAA;EACA,wCAAA;EACA,2CAAA;EACA,UAAA;EACA,oBAAA;EACA,cAAA;EACA,eAAA;AACA;AAEA;EACA,iBAAA;AACA;AAEA;EACA,2BAAA;EACA,wCAAA;EACA,0CAAA;EACA,2CAAA;EACA,WAAA;EACA,oBAAA;EACA,cAAA;EACA,eAAA;AACA;AAEA;EACA,mBAAA;EACA,YAAA;EACA,aAAA;EACA,kBAAA;EACA,uCAAA;AACA;AAEA;EACA,qBAAA;AACA;AAEA;EACA,kBAAA;EACA,UAAA;EACA,2CAAA;AACA;AAEA;EACA,mBAAA;EACA,UAAA;EACA,yBAAA;AACA;AAEA;EACA,kBAAA;AACA;;AAEA,sDAAA;AACA;EACA,WAAA,EAAA,gBAAA;AACA;AACA;EACA,uBAAA,EAAA,eAAA;AACA;AACA;EACA,qBAAA,EAAA,gBAAA;AACA;AACA;EACA,WAAA,EAAA,2BAAA;AACA","file":"Visualization.vue","sourcesContent":["<template>\r\n <div\r\n class=\"visualization-row\"\r\n id=\"visualization-container\"\r\n @click=\"framesClicked\"\r\n @scroll=\"scrollEvent\"\r\n >\r\n <GlobalEvents\r\n v-if=\"!readOnly && canInsertTime && settingsClosed\"\r\n :filter=\"(event) => !event.shiftKey && !event.ctrlKey\"\r\n @keydown.45=\"insertTime\"\r\n />\r\n <GlobalEvents\r\n v-if=\"active && settingsClosed\"\r\n @keydown.left.prevent=\"arrowLeft\"\r\n @keydown.right.prevent=\"arrowRight\"\r\n @keydown.up.prevent=\"arrowUp\"\r\n @keydown.down.prevent=\"arrowDown\"\r\n @keydown.shift.page-down.prevent=\"nextLoopActivate\"\r\n @keydown.page-down.prevent=\"() => next()\"\r\n @keydown.page-up.prevent=\"() => prev()\"\r\n @keydown.shift.page-up.prevent=\"prevLoopActivate\"\r\n @keydown.36.prevent=\"goToFirstFrame\"\r\n @keydown.35.prevent=\"goToLastFrame\"\r\n @keydown.71.prevent=\"dialogs.goTo = true\"\r\n @keydown.73.prevent=\"dialogs.secondsPerFrame = true\"\r\n @keydown.76.prevent=\"dialogs.frames = true\"\r\n @keydown.49.97=\"() => (secondsPerFrame = 1)\"\r\n @keydown.50.98=\"() => (secondsPerFrame = 2)\"\r\n @keydown.51.99=\"() => (secondsPerFrame = 3)\"\r\n @keydown.52.100=\"() => (secondsPerFrame = 4)\"\r\n @keydown.53.101=\"() => (secondsPerFrame = 5)\"\r\n />\r\n <GlobalEvents\r\n v-if=\"prevLoop || nextLoop\"\r\n @keydown=\"breakLoop\"\r\n v-on:click=\"breakLoop\"\r\n />\r\n <settings\r\n v-if=\"active\"\r\n :dialogs-visibility=\"dialogs\"\r\n :playback-rate=\"playbackRate\"\r\n :seconds-per-frame=\"secondsPerFrame\"\r\n :frames-per-row=\"framesPerRow\"\r\n :number-of-rows=\"numberOfRows\"\r\n :max-steps=\"maxSteps\"\r\n :shift-frames=\"shiftFrames\"\r\n :customGridSize=\"customGridSize\"\r\n :stretchFrames=\"stretchFrame\"\r\n @change-playback-rate=\"(value) => (playbackRate = value)\"\r\n @change-go-to=\"changeHour\"\r\n @change-seconds-per-frame=\"(value) => (secondsPerFrame = value)\"\r\n @change-shift-frames=\"(value) => (shiftFrames = value)\"\r\n @set-frames-selection=\"setFrameSelection\"\r\n @set-custom-grid-values=\"setCustomGridValues\"\r\n @close=\"(dialog) => (dialogs[dialog] = false)\"\r\n />\r\n <div\r\n :class=\"{ 'visualization-card': true, 'active-tab': active }\"\r\n style=\"width: 100%; padding: 0\"\r\n >\r\n <command-bar\r\n v-show=\"commandBarShow\"\r\n :read-only=\"readOnly\"\r\n :video-playing=\"videoPlaying\"\r\n :video-paused=\"paused\"\r\n :insert-time=\"canInsertTime\"\r\n @prev-loop-activate=\"prevLoopActivate\"\r\n @next-loop-activate=\"nextLoopActivate\"\r\n @prev=\"prev\"\r\n @next=\"next\"\r\n @go-to=\"dialogs.goTo = true\"\r\n @open-frame-selection=\"dialogs.frames = true\"\r\n @open-frames-per-second=\"dialogs.secondsPerFrame = true\"\r\n @open-blocks=\"openBlocks\"\r\n @open-playback-rate=\"dialogs.playbackRate = true\"\r\n @play-or-pause=\"playOrPause\"\r\n @stop-playing=\"stopPlayingBar\"\r\n @insert-time=\"insertTime\"\r\n @shift-frames=\"dialogs.shiftFrames = true\"\r\n />\r\n <video-progress\r\n v-if=\"videoProgressBar\"\r\n v-show=\"videoPlaying\"\r\n :video-time=\"videoTime\"\r\n />\r\n <info-bar\r\n :playback-rate=\"playbackRate\"\r\n :seconds-per-frame=\"secondsPerFrame\"\r\n :commands-show=\"commandBarShow\"\r\n :cache=\"useCache\"\r\n :block-start-time=\"blockStartTime\"\r\n :video-total-duration=\"videoSliderTotalDuration\"\r\n :alternative-server=\"alternativeServer\"\r\n :frames-per-row=\"framesPerRow\"\r\n :number-of-rows=\"numberOfRows\"\r\n :show-stretch-frame=\"showStretchFrame\"\r\n @toogle-commands-visibility=\"toogleCommandsVisibility\"\r\n @toogle-cache=\"useCache = !useCache\"\r\n @change-server=\"changeServerClick\"\r\n @update-stretch-frames=\"(value) => (stretchFrame = value)\"\r\n />\r\n <div\r\n class=\"visualization-row\"\r\n v-for=\"rowNumber in numberOfRows\"\r\n :id=\"'row-' + rowNumber\"\r\n :key=\"'row-' + rowNumber\"\r\n style=\"padding: 0px 4px\"\r\n >\r\n <div\r\n v-for=\"(frame, frameNumber) in previousFrames\"\r\n :key=\"\r\n numberOfRows +\r\n '-' +\r\n framesPerRow +\r\n '-' +\r\n getIndex(rowNumber, frameNumber, Positions.previous)\r\n \"\r\n style=\"display: none\"\r\n >\r\n <span v-html=\"frame.img\" />\r\n </div>\r\n <div\r\n v-for=\"(frame, frameNumber) in nextFrames\"\r\n :key=\"\r\n numberOfRows +\r\n '-' +\r\n framesPerRow +\r\n '-' +\r\n getIndex(rowNumber, frameNumber, Positions.next)\r\n \"\r\n style=\"display: none\"\r\n >\r\n <span v-html=\"frame.img\" />\r\n </div>\r\n <div\r\n class=\"visualization-col\"\r\n v-for=\"(frame, frameNumber) in frames.slice(\r\n framesPerRow * (rowNumber - 1),\r\n framesPerRow * rowNumber\r\n )\"\r\n :key=\"'row-' + rowNumber + '-frame-' + frameNumber + '-' + frame.time\"\r\n :id=\"`frame-${getIndex(rowNumber, frameNumber, Positions.current)}`\"\r\n :class=\"{ loaderImg: !!frame.img }\"\r\n @click=\"\r\n frame.time\r\n ? selectFrame(\r\n getIndex(rowNumber, frameNumber, Positions.current),\r\n frame\r\n )\r\n : null\r\n \">\r\n <span :class=\"giveMeType(frame.time)\" :id=\"activeFrame ? 'aa' : 0\" style=\"text-align: center\">\r\n <b>\r\n {{\r\n getAudienceTime(\r\n frame.time,\r\n rowNumber,\r\n frameNumber,\r\n Positions.current\r\n )\r\n \r\n }}\r\n </b>\r\n </span>\r\n\r\n <frame\r\n ref=\"frames\"\r\n :frame=\"frame\"\r\n :index=\"getIndex(rowNumber, frameNumber, Positions.current)\"\r\n :grid-settings=\"{ numberOfRows, framesPerRow }\"\r\n :initialTime=\"isEventStart(frame.time)\"\r\n :endTime=\"isEventEnd(frame.time)\"\r\n :checkpointTime=\"false\"\r\n :betweenTime=\"isBetweenEvent(frame.time)\"\r\n :eventType=\"getEventType(frame.time)\"\r\n :active=\"\r\n getIndex(rowNumber, frameNumber, Positions.current) ===\r\n activeFrame\r\n \"\r\n :activeTab=\"active\"\r\n :videoUrl=\"fInterface ? fInterface.getVideoUrl(frame) : ''\"\r\n :videoControls=\"videoControls\"\r\n @startPlaying=\"startPlaying\"\r\n @stopPlaying=\"stopPlaying\"\r\n @playPauseStatus=\"changePlayPause\"\r\n @updateSlider=\"updateSlider\"\r\n :playback-rate=\"playbackRate\"\r\n :aspect-ratio=\"aspectRatio\"\r\n :stretchFrame=\"stretchFrame\"\r\n style=\"margin: 0 auto\"\r\n ></frame>\r\n </div>\r\n </div>\r\n </div>\r\n <!-- <settings\r\n ref=\"settings2\"\r\n :active=\"active\"\r\n @goToTime=\"changeHour\"\r\n @goToBlockInterval=\"changeBlockInterval\"\r\n @setSplitTime=\"setSplitTime\"\r\n @setFrameSelection=\"setFrameSelection\"\r\n @setPlaybackRate=\"\r\n (rate) => {\r\n playbackRate = rate\r\n }\r\n \"\r\n >\r\n </settings> -->\r\n <!-- <v-dialog v-model=\"dialog\" width=\"500\">\r\n <div class=\"card\">\r\n <div class=\"card\"-title class=\"text-h5 grey lighten-2\">\r\n {{ ' Último bloco disponível até: ' }}\r\n <v-btn\r\n @click=\"goToStartBlock\"\r\n class=\"ml-2\"\r\n dark\r\n color=\"success\"\r\n depressed\r\n >\r\n <v-icon left> fa-clock </v-icon>\r\n {{ timeLastBlock }}\r\n </v-btn>\r\n <v-spacer></v-spacer>\r\n <v-btn color=\"error\" fab small class=\"ml-5\" @click=\"dialog = false\">\r\n <v-icon dark> fa fa-xmark </v-icon>\r\n </v-btn>\r\n </div-title>\r\n </div>\r\n </v-dialog>\r\n <Help :media=\"media\" @close=\"media = null\" />\r\n <v-dialog\r\n v-if=\"userMultiTabsGrid\"\r\n v-model=\"userMultiTabsGridsModel\"\r\n persistent\r\n width=\"60%\"\r\n >\r\n <div class=\"card\">\r\n <div class=\"card\"-title class=\"warning text-h5\" primary-title>\r\n <div class=\"row\" class=\"ma-0\" justify=\"center\" align=\"center\">\r\n <v-icon dark left style=\"font-size: 24px !important\">\r\n fa fa-exclamation-triangle\r\n </v-icon>\r\n <div style=\"color: white\">{{ $t('form.alert') }}</div>\r\n </div>\r\n </div-title>\r\n <div class=\"card\"-text class=\"justify-center pa-6 grey lighten-2\">\r\n <h3>\r\n {{ $t('alerts.userMultiTabsGrid') }}\r\n </h3>\r\n </div-text>\r\n <hr class=\"divider\" class=\"grey lighten-1\"></span>\r\n <div class=\"card\"-actions class=\"grey lighten-2 justify-center\">\r\n <v-btn color=\"error\" ml-5 @click=\"userMultiTabsGrid = false\">\r\n <v-icon left color=\"white\">fa fa-times</v-icon>\r\n {{ $t('form.close') }}\r\n </v-btn>\r\n </div-actions>\r\n </div>\r\n </v-dialog> -->\r\n </div>\r\n</template>\r\n<script>\r\nimport Frame from './components/Frame.vue'\r\nimport FramesInterface from './utils/FramesInterface.js'\r\nimport FramesService from './services/FramesService.js'\r\n\r\nimport Commands from './components/Commands.vue'\r\nimport Infos from './components/Infos.vue'\r\nimport VideoProgress from './components/VideoProgress.vue'\r\nimport Settings from './components/Settings.vue'\r\n\r\nconst Positions = Object.freeze({\r\n previous: 0,\r\n current: 1,\r\n next: 2,\r\n})\r\n\r\nexport default {\r\n name: 'visualization-container',\r\n props: {\r\n value: {\r\n type: Boolean,\r\n required: true,\r\n },\r\n date: {\r\n type: String,\r\n required: true,\r\n },\r\n channel: {\r\n type: Number,\r\n required: true,\r\n },\r\n startAudienceTime: {\r\n type: String,\r\n required: true,\r\n },\r\n endAudienceTime: {\r\n type: String,\r\n required: true,\r\n },\r\n videoProgressBar: {\r\n type: Boolean,\r\n default: false,\r\n },\r\n jumpOnInsert: {\r\n type: Boolean,\r\n default: false,\r\n },\r\n removeSelectionOnInsert: {\r\n type: Boolean,\r\n default: false,\r\n },\r\n framesFormat: {\r\n type: [Number, String],\r\n default: 7,\r\n },\r\n maxSize: {\r\n type: Number,\r\n },\r\n videoControls: {\r\n type: Boolean,\r\n default: false,\r\n },\r\n readOnly: {\r\n type: Boolean,\r\n default: false,\r\n },\r\n maxSteps: {\r\n type: Number,\r\n default: 1,\r\n },\r\n customGridSize: {\r\n type: Boolean,\r\n default: false,\r\n },\r\n showStretchFrame: {\r\n type: Boolean,\r\n default: false,\r\n },\r\n visualizationInsArray: {\r\n type: Array,\r\n default: () => [],\r\n },\r\n insertDefaults: {\r\n type: Array,\r\n default: () => [],\r\n },\r\n },\r\n components: {\r\n Frame,\r\n CommandBar: Commands,\r\n InfoBar: Infos,\r\n VideoProgress,\r\n Settings,\r\n // Help,\r\n },\r\n data() {\r\n return {\r\n Positions,\r\n updatingChannel: null,\r\n dialog: false,\r\n timeLastBlock: null,\r\n alternativeServer: false,\r\n useCache: true,\r\n numberOfRows: 1,\r\n framesPerRow: 5,\r\n secondsPerFrame: 1,\r\n fInterface: null,\r\n velocity: 1,\r\n frames: [],\r\n previousFrames: [],\r\n nextFrames: [],\r\n channelCode: 0,\r\n videoPlaying: false,\r\n activeFrame: null,\r\n activeVideo: null,\r\n videoTime: 0,\r\n videoTotalTime: null,\r\n progressVideoDrag: false,\r\n events: {\r\n A: [],\r\n B: [],\r\n C: [],\r\n },\r\n currentEventA: null,\r\n activeEvents: {}, // Track active events by type\r\n eventBlocked: false, // Track if event creation is blocked\r\n canInsertTime: false,\r\n lastHeight: 0,\r\n loopInterval: null,\r\n nextLoop: false,\r\n prevLoop: false,\r\n videoSliderTotalDuration: 900,\r\n blockStartTime: null,\r\n media: null,\r\n changeServer: false,\r\n userMultiTabsGrid: false,\r\n userMultiTabsGridsModel: true,\r\n playbackRate: 1,\r\n paused: false,\r\n commandBarShow: true,\r\n dialogs: {\r\n playbackRate: false,\r\n goTo: false,\r\n secondsPerFrame: false,\r\n frames: false,\r\n shiftFrames: false,\r\n },\r\n lastNext: 0,\r\n lastPrev: 0,\r\n shiftFrames: 0,\r\n aspectRatio: 11 / 9,\r\n showCustomInputs: false,\r\n stretchFrame: false,\r\n scrollCounter: 300,\r\n eventsArray: [], \r\n }\r\n },\r\n async created() {\r\n this.changeServer = this.serverOfFrames === 'alternative'\r\n this.alternativeServer = this.serverOfFrames === 'alternative'\r\n //console.log('visualization array updated: ', this.visualizationInsArray)\r\n const settings = [\r\n {\r\n framesPerRow: 1,\r\n numberOfRows: 1,\r\n },\r\n {\r\n framesPerRow: 2,\r\n numberOfRows: 1,\r\n },\r\n {\r\n framesPerRow: 3,\r\n numberOfRows: 1,\r\n },\r\n {\r\n framesPerRow: 3,\r\n numberOfRows: 2,\r\n },\r\n {\r\n framesPerRow: 4,\r\n numberOfRows: 1,\r\n },\r\n {\r\n framesPerRow: 4,\r\n numberOfRows: 2,\r\n },\r\n {\r\n framesPerRow: 5,\r\n numberOfRows: 1,\r\n },\r\n {\r\n framesPerRow: 5,\r\n numberOfRows: 2,\r\n },\r\n {\r\n framesPerRow: 6,\r\n numberOfRows: 1,\r\n },\r\n {\r\n framesPerRow: 6,\r\n numberOfRows: 2,\r\n },\r\n {\r\n //Custom (item 11)\r\n framesPerRow: 7,\r\n numberOfRows: 3,\r\n },\r\n ]\r\n\r\n // Os valores custom são guardados no local storage e não na base de dados\r\n const customValues = JSON.parse(localStorage.getItem('customGridValues'))\r\n\r\n if (this.framesFormat == '11' && !!customValues) {\r\n this.numberOfRows = customValues[0]\r\n this.framesPerRow = customValues[1]\r\n } else {\r\n const storedOnDb = settings[parseInt(this.framesFormat) - 1]\r\n this.framesPerRow = storedOnDb.framesPerRow\r\n this.numberOfRows = storedOnDb.numberOfRows\r\n }\r\n\r\n document.addEventListener('wheel', this.scrollEvent)\r\n document.addEventListener('keydown', this.handleKeydown)\r\n\r\n await this.createFramesInterface()\r\n this.$nextTick(this.resize)\r\n },\r\n beforeDestroy() {\r\n document.removeEventListener('wheel', this.scrollEvent)\r\n document.removeEventListener('keydown', this.handleKeydown)\r\n },\r\n methods: {\r\n handleKeydown(event) {\r\n if (!this.active || !this.settingsClosed || this.readOnly) return\r\n\r\n // Check if the pressed key matches any shortcut in insertDefaults\r\n const matchedConfig = this.insertDefaults.find(\r\n (config) => config.shortcut.toUpperCase() === event.key.toUpperCase()\r\n )\r\n\r\n if (matchedConfig) {\r\n event.preventDefault()\r\n this.toggleEvent(matchedConfig.event)\r\n }\r\n },\r\n scrollEvent(e) {\r\n if (this.active) {\r\n if (Math.sign(e.deltaY) !== Math.sign(this.scrollCounter)) {\r\n if (Math.sign(e.deltaY) > 0) {\r\n this.next()\r\n } else {\r\n this.prev()\r\n }\r\n this.scrollCounter = e.deltaY\r\n return\r\n }\r\n\r\n this.scrollCounter += e.deltaY\r\n if (Math.abs(this.scrollCounter) >= 400) {\r\n if (Math.sign(e.deltaY) > 0) {\r\n this.next()\r\n } else {\r\n this.prev()\r\n }\r\n this.scrollCounter = 0\r\n }\r\n }\r\n },\r\n stopVideoPlaying(array) {\r\n for (const frame of array || this.$refs.frames) {\r\n if (\r\n frame.videoStatus === frame.Status.playing ||\r\n frame.videoStatus === frame.Status.paused\r\n ) {\r\n frame.stop(false)\r\n }\r\n }\r\n this.activeVideo = null\r\n },\r\n toogleCommandsVisibility() {\r\n this.commandBarShow = !this.commandBarShow\r\n this.$nextTick(this.resize)\r\n },\r\n framesClicked(e) {\r\n if (e.target.parentNode.id != 'insert') {\r\n this.active = true\r\n }\r\n },\r\n async goToStartBlock() {\r\n try {\r\n const d = new Date()\r\n let timestamp = Date.UTC(\r\n d.getFullYear(),\r\n d.getMonth(),\r\n d.getDate(),\r\n d.getHours(),\r\n d.getMinutes(),\r\n d.getSeconds()\r\n )\r\n\r\n const response = (\r\n await FramesService.getNextAvailableBlock({\r\n channel: this.channel,\r\n time: timestamp / 1000,\r\n })\r\n ).data\r\n\r\n this.dialog = false\r\n this.changeHour(this.convertToAudienceTime(response.data.start, ':'))\r\n } catch (err) {\r\n // console.error(err)\r\n }\r\n },\r\n async checkAvailableBlock() {\r\n try {\r\n const d = new Date()\r\n let timestamp = Date.UTC(\r\n d.getFullYear(),\r\n d.getMonth(),\r\n d.getDate(),\r\n d.getHours(),\r\n d.getMinutes(),\r\n d.getSeconds()\r\n )\r\n\r\n const response = (\r\n await FramesService.getNextAvailableBlock({\r\n channel: this.channel,\r\n time: timestamp / 1000,\r\n })\r\n ).data\r\n\r\n this.timeLastBlock = this.convertToAudienceTime(response.data.end, ':')\r\n this.dialog = true\r\n if (!response.status) {\r\n this.timeLastBlock = 'N/D'\r\n }\r\n } catch (err) {\r\n // console.error(err)\r\n }\r\n },\r\n updateSlider(videoStartTime, time) {\r\n // * atualizar slider se estiver fora do range definido previamente\r\n if (\r\n time < this.blockStartTime ||\r\n time > this.blockStartTime ||\r\n videoStartTime > this.blockStartTime + this.videoSliderTotalDuration\r\n ) {\r\n this.blockStartTime = videoStartTime\r\n this.videoSliderTotalDuration = 900\r\n }\r\n },\r\n nextLoopActivate() {\r\n this.breakLoop()\r\n this.loopInterval = setInterval(\r\n () => this.next({ ignoreTime: true }),\r\n this.swapImagesDelay\r\n )\r\n setTimeout(() => {\r\n this.nextLoop = true\r\n }, 0)\r\n },\r\n prevLoopActivate() {\r\n this.breakLoop()\r\n this.loopInterval = setInterval(\r\n () => this.prev({ ignoreTime: true }),\r\n this.swapImagesDelay\r\n )\r\n setTimeout(() => {\r\n this.prevLoop = true\r\n }, 0)\r\n },\r\n breakLoop() {\r\n clearInterval(this.loopInterval)\r\n this.loopInterval = null\r\n this.nextLoop = false\r\n this.prevLoop = false\r\n },\r\n changePlayPause(status) {\r\n this.paused = !status\r\n },\r\n resize(height = this.lastHeight) {\r\n this.lastHeight = height\r\n if (this.$refs.frames) {\r\n for (let frame of this.$refs.frames) {\r\n frame.resize(height)\r\n }\r\n }\r\n this.$emit('resized')\r\n },\r\n async goToFirstFrame() {\r\n let frames = this.$refs.frames\r\n\r\n let audienceTime = null\r\n if (frames.length > 0) {\r\n let frame = frames[0].frame\r\n audienceTime = this.getAudienceTime(frame.time, 0, 0, 0)\r\n }\r\n if (audienceTime) {\r\n const [hours, minutes, seconds] = audienceTime.split(':')\r\n const totalSeconds =\r\n parseInt(hours) * 3600 + parseInt(minutes) * 60 + parseInt(seconds)\r\n if (totalSeconds >= 9000)\r\n this.changeHour(this.getLastFirtsBlockTime(audienceTime, true))\r\n else this.changeHour(this.getLastFirtsBlockTime('02:30:00', true))\r\n }\r\n },\r\n async goToLastFrame() {\r\n let frames = this.$refs.frames\r\n let audienceTime = null\r\n if (frames.length > 0) {\r\n let frame = frames[0].frame\r\n\r\n audienceTime = this.getAudienceTime(frame.time, 0, 0, 0)\r\n }\r\n if (audienceTime) {\r\n this.changeHour(this.getLastFirtsBlockTime(audienceTime))\r\n }\r\n },\r\n getLastFirtsBlockTime(time, first = false) {\r\n if (time.indexOf(':') !== -1) {\r\n time = time.replace(/:/g, '')\r\n }\r\n let h, m, newTime\r\n const t = time.match(/.{1,2}/g)\r\n if (t[0] && t[1]) {\r\n h = parseInt(t[0])\r\n m = parseInt(t[1])\r\n }\r\n if (h < 26) {\r\n if (m < 15)\r\n if (first) newTime = t[0] + ':00:00'\r\n else newTime = t[0] + ':14:59'\r\n else if (m < 30)\r\n if (first) newTime = t[0] + ':15:00'\r\n else newTime = t[0] + ':29:59'\r\n else if (m < 45)\r\n if (first) newTime = t[0] + ':30:00'\r\n else newTime = t[0] + ':44:59'\r\n else if (first) newTime = t[0] + ':45:00'\r\n else newTime = t[0] + ':59:59'\r\n } else {\r\n if (m < 15)\r\n if (first) newTime = '26:00:00'\r\n else newTime = '26:14:59'\r\n else {\r\n if (first) newTime = '26:15:00'\r\n else newTime = '26:29:59'\r\n }\r\n }\r\n return newTime\r\n },\r\n openBlocks() {\r\n this.$refs.settings2?.openBlocks()\r\n },\r\n playOrPause() {\r\n const array = this.$refs.frames.filter((fc) => !!fc.active)\r\n if (array.length === 1) {\r\n const frame = array[0]\r\n frame.playOrPause(this.playbackRate)\r\n }\r\n },\r\n stopPlayingBar() {\r\n for (let ref of this.$refs.frames) {\r\n if (\r\n ref.videoStatus === ref.Status.playing ||\r\n ref.videoStatus === ref.Status.paused\r\n ) {\r\n ref.stop(false)\r\n }\r\n }\r\n },\r\n async setFrameSelection(selected) {\r\n this.frames = this.loadingArray\r\n const settings = [\r\n {\r\n framesPerRow: 1,\r\n numberOfRows: 1,\r\n },\r\n {\r\n framesPerRow: 2,\r\n numberOfRows: 1,\r\n },\r\n {\r\n framesPerRow: 3,\r\n numberOfRows: 1,\r\n },\r\n {\r\n framesPerRow: 3,\r\n numberOfRows: 2,\r\n },\r\n {\r\n framesPerRow: 4,\r\n numberOfRows: 1,\r\n },\r\n {\r\n framesPerRow: 4,\r\n numberOfRows: 2,\r\n },\r\n {\r\n framesPerRow: 5,\r\n numberOfRows: 1,\r\n },\r\n {\r\n framesPerRow: 5,\r\n numberOfRows: 2,\r\n },\r\n {\r\n framesPerRow: 6,\r\n numberOfRows: 1,\r\n },\r\n {\r\n framesPerRow: 6,\r\n numberOfRows: 2,\r\n },\r\n {\r\n //Custom (item 11)\r\n framesPerRow: 7,\r\n numberOfRows: 3,\r\n },\r\n ]\r\n\r\n const formatSelected = settings[selected - 1]\r\n this.framesPerRow = formatSelected.framesPerRow\r\n this.numberOfRows = formatSelected.numberOfRows\r\n\r\n await this.fInterface.changeSize(this.numberOfRows, this.framesPerRow)\r\n this.getFramesArray()\r\n this.$nextTick(this.resize)\r\n this.$emit('frames-format-changed', selected)\r\n },\r\n async setCustomGridValues(customValues) {\r\n this.numberOfRows = customValues[0]\r\n this.framesPerRow = customValues[1]\r\n localStorage.setItem('customGridValues', JSON.stringify(customValues))\r\n await this.fInterface.changeSize(this.numberOfRows, this.framesPerRow)\r\n this.getFramesArray()\r\n this.$nextTick(this.resize)\r\n this.$emit('frames-format-changed', 11)\r\n },\r\n getFramesArray() {\r\n this.frames = this.fInterface.getFrames(Positions.current)\r\n this.nextFrames = this.fInterface.getFrames(Positions.next)\r\n this.previousFrames = this.fInterface.getFrames(Positions.previous)\r\n\r\n const div = document.createElement('div')\r\n div.innerHTML = this.frames[0].image\r\n const newAspectRatio =\r\n div.getElementsByTagName('img')[0]?.naturalWidth /\r\n div.getElementsByTagName('img')[0]?.naturalHeight\r\n\r\n this.aspectRatio = isNaN(newAspectRatio)\r\n ? this.aspectRatio\r\n : newAspectRatio\r\n\r\n this.$nextTick(this.resize)\r\n\r\n const frame = this.frames.find((f) => f.blockStart)\r\n if (frame && this.alternativeServer) {\r\n this.$emit(\r\n 'new-block',\r\n frame.title?.match(/[0-9]{3}\\/(?:[0-9]+_?)+/)[0]\r\n )\r\n }\r\n },\r\n async createFramesInterface(startTime = this.startAudienceTime) {\r\n this.frames = this.loadingArray\r\n // let ch = this.channel\r\n // let associationTMP = {\r\n // 1735073: 1,\r\n // 1735074: 139,\r\n // 1735075: 3,\r\n // 1735076: 132,\r\n // }\r\n // //\r\n // this.channelCode = associationTMP[ch] ? associationTMP[ch] : ch\r\n\r\n const t = startTime.match(/.{1,2}/g)\r\n const d = this.getDateParts()\r\n const time = Date.UTC(d.year, d.month, d.day, t[0], t[1], t[2]) / 1000\r\n // * iniciar slider\r\n this.blockStartTime = time\r\n this.fInterface = await new FramesInterface(\r\n this.channel,\r\n this.numberOfRows,\r\n this.framesPerRow,\r\n time,\r\n this.startAudienceTime,\r\n this.useCache,\r\n this.shiftFrames\r\n )\r\n await this.fInterface.init()\r\n\r\n this.getFramesArray()\r\n\r\n const div = document.createElement('div')\r\n div.innerHTML = this.frames[0].image\r\n\r\n this.aspectRatio =\r\n div.getElementsByTagName('img')[0].naturalWidth /\r\n div.getElementsByTagName('img')[0].naturalHeight\r\n\r\n this.activeFrame = this.getIndex(1, 0, Positions.current)\r\n\r\n this.activeVideo = null\r\n },\r\n getIndex(rowNumber, frameIndex, position) {\r\n const baseOffset = this.framesPerRow * this.numberOfRows * position\r\n const rowOffset = (rowNumber - 1) * this.framesPerRow\r\n return baseOffset + rowOffset + frameIndex\r\n },\r\n getAudienceTime(frameTime, rowNumber, frameNumber, position) {\r\n if (!frameTime) {\r\n return 'Loading...'\r\n } else if (\r\n this.getIndex(rowNumber, frameNumber, position) === this.activeVideo\r\n ) {\r\n return this.convertToAudienceTime(this.videoTime)\r\n } else {\r\n return this.convertToAudienceTime(frameTime)\r\n }\r\n },\r\n giveMeType(frameTime) {\r\n if (!frameTime) {\r\n return ''\r\n }\r\n let time = this.convertToAudienceTime(frameTime)\r\n time = time.replace(/:/g, '')\r\n const foundEvent = this.eventsArray.find((event) => {\r\n return time >= event.hour_ini && time <= event.hour_end\r\n })\r\n if (foundEvent) {\r\n return `event-${foundEvent.type}`\r\n }else{\r\n return 'event-gap'\r\n }\r\n },\r\n dateInUtc(miliSeconds) {\r\n var date = new Date(miliSeconds)\r\n var utc = new Date(\r\n date.getUTCFullYear(),\r\n date.getUTCMonth(),\r\n date.getUTCDate(),\r\n date.getUTCHours(),\r\n date.getUTCMinutes(),\r\n date.getUTCSeconds()\r\n )\r\n return utc\r\n },\r\n convertToAudienceTime(time, separator = ':') {\r\n const d = this.getDateParts()\r\n const limit = Date.UTC(d.year, d.month, d.day, 23, 59, 59) / 1000\r\n\r\n let hour = this.dateInUtc(time * 1000)\r\n .toTimeString()\r\n .split(' ')[0]\r\n .split(':')\r\n .map(Number)\r\n\r\n if (time > limit && time <= limit + this.startAudienceSeconds) {\r\n hour[0] = 24 + hour[0]\r\n }\r\n return hour\r\n .map((part) => (part > 9 ? part.toString() : '0' + part))\r\n .join(separator)\r\n },\r\n getDateParts(date = this.date) {\r\n const data = /(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})/.exec(\r\n date\r\n )?.groups\r\n if (data) {\r\n return {\r\n year: parseInt(data.year),\r\n month: parseInt(data.month) - 1,\r\n day: parseInt(data.day),\r\n }\r\n }\r\n return {\r\n year: null,\r\n month: null,\r\n day: null,\r\n }\r\n },\r\n selectFrame(index, frame) {\r\n if (this.activeFrame !== index) {\r\n // Stop playing video if clicking on a different frame\r\n const array = this.$refs.frames.filter(\r\n (fc) => fc.videoStatus === fc.Status.playing\r\n )\r\n if (array.length === 1) {\r\n const frame = array[0]\r\n frame.playOrPause()\r\n }\r\n this.activeVideo = null\r\n this.activeFrame = index\r\n }\r\n },\r\n toggleEvent(eventConfig) {\r\n const currentFrame = this.$refs.frames?.find(\r\n (f) => f.index === this.activeFrame\r\n )\r\n if (!currentFrame || !currentFrame.frame.time) return\r\n\r\n const frameTime = currentFrame.frame.time\r\n const eventType = eventConfig.type\r\n\r\n // Check if events are blocked (when type B is active outside Type A)\r\n if (this.eventBlocked && eventType !== 'B' && eventType !== 'C') {\r\n console.warn(\r\n 'Events are blocked. Close the active type B/C event first.'\r\n )\r\n return\r\n }\r\n\r\n if (eventType === 'A') {\r\n this.handleTypeA(frameTime, eventConfig)\r\n } else if (eventType === 'B') {\r\n this.handleTypeB(frameTime, eventConfig)\r\n } else if (eventType === 'C') {\r\n this.handleTypeC(frameTime, eventConfig)\r\n }\r\n\r\n this.canInsertTime = this.hasAnyEvents()\r\n document.getElementById(`frame-${this.activeFrame}`).click()\r\n },\r\n handleTypeA(frameTime, eventConfig) {\r\n if (this.currentEventA && this.currentEventA.hour_ini) {\r\n // Close the event A\r\n this.currentEventA.hour_end = frameTime\r\n this.events.A.push({ ...this.currentEventA })\r\n this.currentEventA = null\r\n delete this.activeEvents.A\r\n } else {\r\n // Start a new event A\r\n // Type A cannot be nested inside another Type A (already checked by currentEventA)\r\n this.currentEventA = {\r\n ...eventConfig,\r\n hour_ini: frameTime,\r\n hour_end: null,\r\n }\r\n this.activeEvents.A = true\r\n }\r\n },\r\n handleTypeB(frameTime, eventConfig) {\r\n const lastEventB = this.events.B[this.events.B.length - 1]\r\n const lastEventC = this.events.C[this.events.C.length - 1]\r\n\r\n if (lastEventB && !lastEventB.hour_end) {\r\n // Close the event B\r\n lastEventB.hour_end = frameTime\r\n delete this.activeEvents.B\r\n this.eventBlocked = false // Unblock events\r\n } else {\r\n // Check if we're outside Type A and already have an unclosed B or C\r\n if (!this.currentEventA) {\r\n if (\r\n (lastEventB && !lastEventB.hour_end) ||\r\n (lastEventC && !lastEventC.hour_end)\r\n ) {\r\n console.warn(\r\n 'Only 1 B/C event allowed outside Type A. Close the current event first.'\r\n )\r\n return\r\n }\r\n // Outside Type A: block other events when B is active\r\n this.eventBlocked = true\r\n }\r\n\r\n // Auto-close any unclosed C event before starting B\r\n if (lastEventC && !lastEventC.hour_end) {\r\n lastEventC.hour_end = frameTime - 1\r\n delete this.activeEvents.C\r\n }\r\n\r\n // Start a new event B\r\n this.events.B.push({\r\n ...eventConfig,\r\n hour_ini: frameTime,\r\n hour_end: null,\r\n })\r\n this.activeEvents.B = true\r\n }\r\n },\r\n handleTypeC(frameTime, eventConfig) {\r\n const lastEventC = this.events.C[this.events.C.length - 1]\r\n const lastEventB = this.events.B[this.events.B.length - 1]\r\n\r\n if (lastEventC && !lastEventC.hour_end) {\r\n // Close the event C\r\n lastEventC.hour_end = frameTime\r\n delete this.activeEvents.C\r\n } else {\r\n // Check if we're outside Type A and already have an unclosed B or C\r\n if (!this.currentEventA) {\r\n if (\r\n (lastEventB && !lastEventB.hour_end) ||\r\n (lastEventC && !lastEventC.hour_end)\r\n ) {\r\n console.warn(\r\n 'Only 1 B/C event allowed outside Type A. Close the current event first.'\r\n )\r\n return\r\n }\r\n }\r\n\r\n // Auto-close any unclosed B event before starting C\r\n if (lastEventB && !lastEventB.hour_end) {\r\n lastEventB.hour_end = frameTime - 1\r\n delete this.activeEvents.B\r\n }\r\n\r\n // Start a new event C\r\n this.events.C.push({\r\n ...eventConfig,\r\n hour_ini: frameTime,\r\n hour_end: null,\r\n })\r\n this.activeEvents.C = true\r\n }\r\n },\r\n hasAnyEvents() {\r\n return (\r\n this.events.A.length > 0 ||\r\n this.events.B.length > 0 ||\r\n this.events.C.length > 0 ||\r\n !!(this.currentEventA && this.currentEventA.hour_ini)\r\n )\r\n },\r\n isEventStart(frameTime) {\r\n if (!frameTime) return false\r\n\r\n const allEvents = [...this.events.A, ...this.events.B, ...this.events.C]\r\n\r\n if (this.currentEventA && this.currentEventA.hour_ini) {\r\n allEvents.push(this.currentEventA)\r\n }\r\n\r\n return allEvents.some((e) => e.hour_ini === frameTime)\r\n },\r\n isEventEnd(frameTime) {\r\n if (!frameTime) return false\r\n\r\n const allEvents = [...this.events.A, ...this.events.B, ...this.events.C]\r\n\r\n return allEvents.some((e) => e.hour_end === frameTime)\r\n },\r\n isBetweenEvent(frameTime) {\r\n if (!frameTime) return false\r\n\r\n const allEvents = [...this.events.A, ...this.events.B, ...this.events.C]\r\n\r\n // Only check completed events\r\n return allEvents.some((e) => {\r\n if (!e.hour_end) return false\r\n const start = e.hour_ini\r\n const end = e.hour_end\r\n return frameTime > start && frameTime < end\r\n })\r\n },\r\n isInEventType(frameTime, type) {\r\n if (!frameTime) return false\r\n\r\n let eventsOfType = this.events[type] || []\r\n\r\n // Only include completed events (with both start and end)\r\n return eventsOfType.some((e) => {\r\n if (!e.hour_end) return false // Event not completed yet\r\n const start = e.hour_ini\r\n const end = e.hour_end\r\n return frameTime >= start && frameTime <= end\r\n })\r\n },\r\n getEventType(frameTime) {\r\n if (!frameTime) return null\r\n\r\n // Check for incomplete (currently being marked) events first\r\n if (this.currentEventA && this.currentEventA.hour_ini === frameTime) {\r\n return 'A'\r\n }\r\n\r\n // Check for incomplete B or C events\r\n const lastEventB = this.events.B[this.events.B.length - 1]\r\n if (\r\n lastEventB &&\r\n !lastEventB.hour_end &&\r\n lastEventB.hour_ini === frameTime\r\n ) {\r\n return 'B'\r\n }\r\n\r\n const lastEventC = this.events.C[this.events.C.length - 1]\r\n if (\r\n lastEventC &&\r\n !lastEventC.hour_end &&\r\n lastEventC.hour_ini === frameTime\r\n ) {\r\n return 'C'\r\n }\r\n\r\n // Check in priority order: B, C, then A for completed events\r\n // This ensures nested events show their specific color\r\n if (this.isInEventType(frameTime, 'B')) return 'B'\r\n if (this.isInEventType(frameTime, 'C')) return 'C'\r\n if (this.isInEventType(frameTime, 'A')) return 'A'\r\n\r\n return null\r\n },\r\n //* Navegação\r\n arrowRight() {\r\n if (this.checkLimitRight(false)) {\r\n if (\r\n this.activeFrame ===\r\n this.numberOfRows * this.framesPerRow * 2 - 1\r\n ) {\r\n this.next()\r\n } else {\r\n this.activeFrame++\r\n }\r\n }\r\n },\r\n arrowLeft() {\r\n if (this.checkLimitLeft(false)) {\r\n if (this.activeFrame === this.numberOfRows * this.framesPerRow) {\r\n this.prev({ stayOnLast: true })\r\n } else {\r\n this.activeFrame--\r\n }\r\n }\r\n },\r\n arrowUp() {\r\n const relativePosition =\r\n this.activeFrame - this.numberOfRows * this.framesPerRow\r\n const currentRow = Math.floor(relativePosition / this.framesPerRow)\r\n if (currentRow > 0) {\r\n this.activeFrame -= this.framesPerRow\r\n }\r\n },\r\n\r\n arrowDown() {\r\n const relativePosition =\r\n this.activeFrame - this.numberOfRows * this.framesPerRow\r\n const currentRow = Math.floor(relativePosition / this.framesPerRow)\r\n\r\n if (currentRow < this.numberOfRows - 1) {\r\n this.activeFrame += this.framesPerRow\r\n }\r\n },\r\n checkLimitRight(value) {\r\n const hours = this.endAudienceTime.match(/.{1,2}/g)\r\n const d = this.getDateParts()\r\n const high = Date.UTC(\r\n d.year,\r\n d.month,\r\n d.day,\r\n hours[0],\r\n parseInt(hours[1]) + 30,\r\n hours[2]\r\n )\r\n\r\n if (value) {\r\n return (\r\n high >\r\n (this.fInterface.getCurrentTime() +\r\n this.numberOfRows * this.framesPerRow -\r\n 1) *\r\n 1000\r\n )\r\n } else {\r\n return high > this.fInterface.getCurrentTime() * 1000\r\n }\r\n },\r\n checkLimitLeft(value) {\r\n const hours = this.startAudienceTime.match(/.{1,2}/g)\r\n const d = this.getDateParts()\r\n const low = Date.UTC(d.year, d.month, d.day, hours[0], hours[1], hours[2])\r\n\r\n if (value) {\r\n return low <= (this.fInterface.getCurrentTime() - 1) * 1000\r\n } else {\r\n return (\r\n low <\r\n (this.fInterface.getCurrentTime() +\r\n this.activeFrame -\r\n this.numberOfRows * this.framesPerRow) *\r\n 1000\r\n )\r\n }\r\n },\r\n async next(config = {}) {\r\n if (\r\n (config.ignoreTime ||\r\n Date.now() - this.lastNext > this.swapImagesDelay) &&\r\n this.checkLimitRight(true) &&\r\n !this.navigationPending\r\n ) {\r\n this.navigationPending = true\r\n this.stopVideoPlaying()\r\n\r\n this.navigationPending = await new Promise((resolve) => {\r\n this.fInterface\r\n .loadNextFrames()\r\n .then(() => {\r\n this.activeFrame = this.getIndex(1, 0, Positions.current)\r\n\r\n this.getFramesArray()\r\n this.lastNext = Date.now()\r\n resolve(false)\r\n })\r\n .catch(() => {\r\n resolve(false)\r\n })\r\n })\r\n } else {\r\n // console.error('next blocked')\r\n }\r\n },\r\n async prev(config = {}) {\r\n if (\r\n (config.ignoreTime ||\r\n Date.now() - this.lastPrev > this.swapImagesDelay) &&\r\n this.checkLimitLeft(true) &&\r\n !this.navigationPending\r\n ) {\r\n this.navigationPending = true\r\n this.stopVideoPlaying()\r\n\r\n this.navigationPending = await new Promise((resolve) => {\r\n this.fInterface\r\n .loadPrevFrames()\r\n .then(() => {\r\n if (config.stayOnLast) {\r\n this.activeFrame = this.getIndex(\r\n this.numberOfRows,\r\n this.framesPerRow - 1,\r\n Positions.current\r\n )\r\n } else {\r\n this.activeFrame = this.getIndex(1, 0, Positions.current)\r\n }\r\n\r\n this.getFramesArray()\r\n this.lastPrev = Date.now()\r\n resolve(false)\r\n })\r\n .catch(() => {\r\n resolve(false)\r\n })\r\n })\r\n } else {\r\n // console.error('prev blocked')\r\n }\r\n },\r\n async setStartTime(time) {\r\n if (time.indexOf(':') !== -1) {\r\n time = time.replace(/:/g, '')\r\n }\r\n const t = time.match(/.{1,2}/g)\r\n const d = this.getDateParts()\r\n const setTime = Date.UTC(d.year, d.month, d.day, t[0], t[1], t[2]) / 1000\r\n // this.frames = this.loadingArray\r\n\r\n await this.fInterface.changeTime(setTime)\r\n\r\n this.getFramesArray()\r\n\r\n this.activeFrame = this.getIndex(1, 0, Positions.current)\r\n\r\n this.activeVideo = null\r\n\r\n return true\r\n },\r\n hourToTimeStamp(time) {\r\n if (time.indexOf(':') !== -1) {\r\n time = time.replace(/:/g, '')\r\n }\r\n const t = time.match(/.{1,2}/g)\r\n const d = this.getDateParts()\r\n const setTime = Date.UTC(d.year, d.month, d.day, t[0], t[1], t[2]) / 1000\r\n\r\n return setTime\r\n },\r\n changeHour(value) {\r\n if (value) {\r\n return new Promise((resolve) => {\r\n setTimeout(async () => {\r\n this.stopVideoPlaying()\r\n\r\n await this.setStartTime(value, true)\r\n resolve()\r\n }, 0)\r\n })\r\n }\r\n },\r\n changeBlockInterval(value) {\r\n this.changeHour(value.ini)\r\n let time_ini, time_end\r\n time_ini = this.hourToTimeStamp(value.ini)\r\n time_end = this.hourToTimeStamp(value.end)\r\n this.videoSliderTotalDuration = time_end - time_ini\r\n this.$refs.frames[0].changeSettings(time_ini)\r\n this.blockStartTime = time_ini\r\n },\r\n //eslint-disable-next-line\r\n async updateVideoTime(index, videoTime) {\r\n this.activeVideo = index\r\n this.videoTime = videoTime\r\n },\r\n //eslint-disable-next-line\r\n updateVideoStatus(currentTime) {\r\n if (!this.progressVideoDrag) {\r\n // ESTA FUNÇÃO PASSOU PARA DENTRO DOS COMMANDS\r\n // this.updateProgress(null, currentTime)\r\n }\r\n },\r\n async startPlaying(frame, totalTime) {\r\n const array = this.$refs.frames.filter(\r\n (fc) => fc.frame.time !== frame.time\r\n )\r\n this.stopVideoPlaying(array)\r\n\r\n this.videoTotalTime = totalTime\r\n this.videoPlaying = true\r\n },\r\n stopPlaying() {\r\n this.videoTotalTime = null\r\n this.videoPlaying = false\r\n this.paused = false\r\n },\r\n insertTime() {\r\n // Calculate the overall D event (lowest hour_ini to biggest hour_end)\r\n const allEvents = [...this.events.A, ...this.events.B, ...this.events.C]\r\n\r\n if (this.currentEventA && this.currentEventA.hour_ini) {\r\n allEvents.push(this.currentEventA)\r\n }\r\n\r\n if (allEvents.length === 0) {\r\n console.warn('No events to insert')\r\n return\r\n }\r\n\r\n const allTimes = allEvents.flatMap((e) =>\r\n [e.hour_ini, e.hour_end].filter(Boolean)\r\n )\r\n const lowestHourIni = Math.min(...allTimes)\r\n const biggestHourEnd = Math.max(...allTimes)\r\n\r\n // Get all B and C events (sorted by start time)\r\n const bcEvents = [...this.events.B, ...this.events.C]\r\n .filter((e) => e.hour_end) // Only complete events\r\n .sort((a, b) => a.hour_ini - b.hour_ini)\r\n\r\n // Generate A events to fill the gaps\r\n const generatedAEvents = []\r\n const standaloneBCEvents = []\r\n\r\n // Get all A events (both completed and current)\r\n const allAEvents = [...this.events.A]\r\n if (this.currentEventA && this.currentEventA.hour_ini) {\r\n allAEvents.push({\r\n ...this.currentEventA,\r\n hour_end: this.currentEventA.hour_end || biggestHourEnd,\r\n })\r\n }\r\n\r\n // Separate B/C events into those inside Type A and standalone\r\n for (const bcEvent of bcEvents) {\r\n const isInsideA = allAEvents.some(\r\n (aEvent) =>\r\n bcEvent.hour_ini >= aEvent.hour_ini &&\r\n bcEvent.hour_end <= aEvent.hour_end\r\n )\r\n if (!isInsideA) {\r\n standaloneBCEvents.push(bcEvent)\r\n }\r\n }\r\n\r\n // For each A event, generate gap-filling A events around B/C events\r\n for (const aEvent of allAEvents) {\r\n const aStart = aEvent.hour_ini\r\n const aEnd = aEvent.hour_end\r\n\r\n // Get B/C events that are within this A event\r\n const bcEventsInA = bcEvents.filter(\r\n (bc) => bc.hour_ini >= aStart && bc.hour_end <= aEnd\r\n )\r\n\r\n if (bcEventsInA.length === 0) {\r\n // No B or C events inside this A, keep the A event as is\r\n generatedAEvents.push({\r\n ...aEvent,\r\n hour_ini: aStart,\r\n hour_end: aEnd,\r\n })\r\n } else {\r\n let currentTime = aStart\r\n\r\n for (const bcEvent of bcEventsInA) {\r\n // Create A event before this B/C event\r\n if (currentTime < bcEvent.hour_ini) {\r\n generatedAEvents.push({\r\n ...aEvent,\r\n hour_ini: currentTime,\r\n hour_end: bcEvent.hour_ini - 1,\r\n })\r\n }\r\n currentTime = bcEvent.hour_end + 1\r\n }\r\n\r\n // Create final A event after last B/C event\r\n if (currentTime <= aEnd) {\r\n generatedAEvents.push({\r\n ...aEvent,\r\n hour_ini: currentTime,\r\n hour_end: aEnd,\r\n })\r\n }\r\n }\r\n }\r\n\r\n // Create D events for each manually marked A event\r\n // const dEvents = allAEvents.map((aEvent) => ({\r\n // type: 'D',\r\n // hour_ini: this.convertToAudienceTime(aEvent.hour_ini, ''),\r\n // hour_end: this.convertToAudienceTime(aEvent.hour_end, ''),\r\n // timestamp: aEvent.hour_ini, // For sorting\r\n // }))\r\n\r\n // Convert generated A events\r\n const aEventsFormatted = generatedAEvents.map((e) => {\r\n const formatted = {\r\n type: e.type,\r\n hour_ini: this.convertToAudienceTime(e.hour_ini, ''),\r\n hour_end: this.convertToAudienceTime(e.hour_end, ''),\r\n timestamp: e.hour_ini, // For sorting\r\n }\r\n // Add any extra properties from the original event\r\n Object.keys(e).forEach((key) => {\r\n if (!['type', 'hour_ini', 'hour_end', 'timestamp'].includes(key)) {\r\n formatted[key] = e[key]\r\n }\r\n })\r\n return formatted\r\n })\r\n\r\n // Convert B/C events inside Type A\r\n const bcEventsInsideA = bcEvents.filter((bcEvent) =>\r\n allAEvents.some(\r\n (aEvent) =>\r\n bcEvent.hour_ini >= aEvent.hour_ini &&\r\n bcEvent.hour_end <= aEvent.hour_end\r\n )\r\n )\r\n\r\n const bcEventsFormatted = bcEventsInsideA.map((e) => {\r\n const formatted = {\r\n type: e.type,\r\n hour_ini: this.convertToAudienceTime(e.hour_ini, ''),\r\n hour_end: this.convertToAudienceTime(e.hour_end, ''),\r\n timestamp: e.hour_ini, // For sorting\r\n }\r\n // Add any extra properties from the original event\r\n Object.keys(e).forEach((key) => {\r\n if (!['type', 'hour_ini', 'hour_end', 'timestamp'].includes(key)) {\r\n formatted[key] = e[key]\r\n }\r\n })\r\n return formatted\r\n })\r\n\r\n // Convert standalone B/C events (outside Type A)\r\n const standaloneBCFormatted = standaloneBCEvents.map((e) => {\r\n const formatted = {\r\n type: e.type,\r\n hour_ini: this.convertToAudienceTime(e.hour_ini, ''),\r\n hour_end: this.convertToAudienceTime(e.hour_end, ''),\r\n timestamp: e.hour_ini, // For sorting\r\n }\r\n // Add any extra properties from the original event\r\n Object.keys(e).forEach((key) => {\r\n if (!['type', 'hour_ini', 'hour_end', 'timestamp'].includes(key)) {\r\n formatted[key] = e[key]\r\n }\r\n })\r\n return formatted\r\n })\r\n\r\n // Combine all events and sort chronologically\r\n // D events come before A events when they have the same timestamp\r\n const allEventsToSend = [\r\n // ...dEvents,\r\n ...aEventsFormatted,\r\n ...bcEventsFormatted,\r\n ...standaloneBCFormatted,\r\n ].sort((a, b) => {\r\n if (a.timestamp === b.timestamp) {\r\n // If same timestamp, D comes before A, A comes before B/C\r\n const typeOrder = { D: 0, A: 1, B: 2, C: 2 }\r\n return typeOrder[a.type] - typeOrder[b.type]\r\n }\r\n return a.timestamp - b.timestamp\r\n })\r\n\r\n // Remove the timestamp field used for sorting\r\n const eventsToSend = allEventsToSend.map(\r\n ({ timestamp, ...event }) => event\r\n )\r\n\r\n this.$emit('timeToInsert', {\r\n channel: this.channel,\r\n events: eventsToSend,\r\n force: false,\r\n })\r\n\r\n if (this.jumpOnInsert) {\r\n this.changeHour(this.convertToAudienceTime(biggestHourEnd)).then(() => {\r\n this.activeFrame = this.getIndex(1, 1, Positions.current)\r\n })\r\n }\r\n\r\n if (this.removeSelectionOnInsert) {\r\n this.events = {\r\n A: [],\r\n B: [],\r\n C: [],\r\n }\r\n this.currentEventA = null\r\n this.canInsertTime = false\r\n }\r\n },\r\n insertTimeForce() {\r\n const allEvents = [...this.events.A, ...this.events.B, ...this.events.C]\r\n\r\n if (this.currentEventA && this.currentEventA.hour_ini) {\r\n allEvents.push(this.currentEventA)\r\n }\r\n\r\n if (allEvents.length === 0) {\r\n console.warn('No events to insert')\r\n return\r\n }\r\n\r\n const allTimes = allEvents.flatMap((e) =>\r\n [e.hour_ini, e.hour_end].filter(Boolean)\r\n )\r\n const lowestHourIni = Math.min(...allTimes)\r\n const biggestHourEnd = Math.max(...allTimes)\r\n\r\n const eventsToSend = [\r\n // {\r\n // type: 'D',\r\n // hour_ini: this.convertToAudienceTime(lowestHourIni, ''),\r\n // hour_end: this.convertToAudienceTime(biggestHourEnd, ''),\r\n // },\r\n ...allEvents\r\n .filter((e) => e.hour_end)\r\n .map((e) => ({\r\n type: e.type,\r\n hour_ini: this.convertToAudienceTime(e.hour_ini, ''),\r\n hour_end: this.convertToAudienceTime(e.hour_end, ''),\r\n })),\r\n ]\r\n\r\n this.$emit('timeToInsert', {\r\n channel: this.channel,\r\n events: eventsToSend,\r\n force: true,\r\n })\r\n\r\n if (this.removeSelectionOnInsert) {\r\n this.events = {\r\n A: [],\r\n B: [],\r\n C: [],\r\n }\r\n this.currentEventA = null\r\n this.canInsertTime = false\r\n }\r\n },\r\n async getChannelMedia() {\r\n // this.media = (await ChannelService.show(this.channel)).data.MEDIA\r\n },\r\n async changeServerClick() {\r\n this.changeServer = !this.changeServer\r\n this.alternativeServer = this.changeServer\r\n\r\n sessionStorage.setItem(\r\n 'server',\r\n this.changeServer ? 'alternative' : 'default'\r\n )\r\n\r\n await this.createFramesInterface(\r\n this.convertToAudienceTime(\r\n this.$refs.frames.find((frame) => frame.index === this.activeFrame)\r\n .frame.time,\r\n ''\r\n )\r\n )\r\n\r\n this.$nextTick(this.resize)\r\n },\r\n },\r\n computed: {\r\n swapImagesDelay() {\r\n return 0\r\n // return this.numberOfRows * this.framesPerRow * 15\r\n },\r\n active: {\r\n get() {\r\n return this.value\r\n },\r\n set(value) {\r\n this.$emit('input', value)\r\n },\r\n },\r\n settingsClosed() {\r\n return !Object.values(this.dialogs).find((v) => v)\r\n },\r\n startAudienceSeconds() {\r\n const t = this.startAudienceTime.match(/.{1,2}/g)\r\n return parseInt(t[0] * 3600 + t[1] * 60 + t[2])\r\n },\r\n loadingArray() {\r\n return Array.from(Array(this.numberOfRows * this.framesPerRow).keys())\r\n },\r\n serverOfFrames() {\r\n return sessionStorage.getItem('server')\r\n },\r\n },\r\n beforeDestroy() {\r\n sessionStorage.setItem('server', 'default')\r\n },\r\n watch: {\r\n async secondsPerFrame() {\r\n const activeF =\r\n this.frames[this.activeFrame - this.numberOfRows * this.framesPerRow]\r\n\r\n if (activeF) {\r\n this.changeHour(this.getAudienceTime(activeF.time, 0, 0, 0))\r\n this.fInterface.setCurrentStep(this.secondsPerFrame)\r\n await this.fInterface.loadFrames()\r\n this.getFramesArray()\r\n }\r\n },\r\n async shiftFrames() {\r\n this.createFramesInterface()\r\n },\r\n framesFormat(value) {\r\n this.setFrameSelection(value)\r\n },\r\n active() {\r\n // Reset events when switching tabs\r\n if (this.removeSelectionOnInsert) {\r\n this.events = {\r\n A: [],\r\n B: [],\r\n C: [],\r\n }\r\n this.currentEventA = null\r\n }\r\n },\r\n useCache() {\r\n this.createFramesInterface()\r\n },\r\n activeFrame(value) {\r\n if (value) {\r\n this.stopPlayingBar()\r\n }\r\n },\r\n channel() {\r\n this.updatingChannel = new Promise((resolve, reject) => {\r\n try {\r\n this.createFramesInterface()\r\n resolve(true)\r\n } catch (err) {\r\n reject(err)\r\n }\r\n })\r\n },\r\n stretchFrame() {\r\n this.$nextTick(this.resize)\r\n },\r\n visualizationInsArray: {\r\n handler: function (newValue) {\r\n this.eventsArray = newValue\r\n //console.log('visualizationInsArray changed', newValue)\r\n },\r\n deep: true,\r\n },\r\n },\r\n}\r\n</script>\r\n<style scoped>\r\n.visualization-row {\r\n display: flex;\r\n flex-wrap: wrap;\r\n flex: 1 1 auto;\r\n}\r\n\r\n.visualization-col {\r\n flex-basis: 0;\r\n flex-grow: 1;\r\n max-width: 100%;\r\n padding: 5px;\r\n}\r\n\r\n.visualization-divider {\r\n display: block;\r\n flex: 1 1 100%;\r\n height: 0px;\r\n max-height: 0px;\r\n opacity: 1;\r\n transition: inherit;\r\n border-style: solid;\r\n border-width: thin 0 0 0;\r\n border-color: rgba(0, 0, 0, 0.12);\r\n margin: 0;\r\n}\r\n\r\n.visualization-divider.vertical {\r\n align-self: stretch;\r\n border-width: 0 thin 0 0;\r\n display: inline-flex;\r\n height: inherit;\r\n margin-left: -1px;\r\n max-height: 100%;\r\n max-width: 0px;\r\n vertical-align: text-bottom;\r\n width: 0px;\r\n}\r\n\r\n.visualization-card {\r\n flex-basis: 0;\r\n flex-grow: 1;\r\n max-width: 100%;\r\n padding: 12px;\r\n width: 100%;\r\n transition-property: box-shadow, opacity, -webkit-box-shadow;\r\n overflow-wrap: break-word;\r\n /*box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14),\r\n 0 1px 5px 0 rgba(0, 0, 0, 0.12);*/\r\n}\r\n\r\n.visualization-justify-center,\r\n* >>> .visualization-justify-center {\r\n justify-content: center;\r\n}\r\n\r\n.visualization-align-center {\r\n align-items: center;\r\n}\r\n\r\n#visualization-container {\r\n max-width: 100% !important;\r\n margin: 0 auto !important;\r\n height: 100%;\r\n border-bottom: none;\r\n}\r\n#visualization-container > .card {\r\n border-radius: 0 !important;\r\n font-size: 12px;\r\n width: 100%;\r\n box-shadow: none;\r\n height: 100%;\r\n}\r\n\r\n#command-bar,\r\n#info-bar {\r\n background-color: #f5f5f5;\r\n box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14),\r\n 0 1px 5px 0 rgba(0, 0, 0, 0.12);\r\n}\r\n#command-bar button {\r\n width: 42px;\r\n height: 36px;\r\n border: none;\r\n background: none;\r\n}\r\n\r\n#command-bar button:hover {\r\n cursor: pointer;\r\n background: rgba(0, 0, 0, 0.12);\r\n}\r\n\r\n#command-bar svg {\r\n font-size: 16px;\r\n}\r\n\r\n#command-bar {\r\n padding: 0 !important;\r\n}\r\n\r\n#info-bar {\r\n padding: 4px;\r\n font-size: 14px;\r\n position: relative;\r\n}\r\n\r\n.settings-container {\r\n position: absolute;\r\n right: 14px;\r\n top: 50%;\r\n transform: translateY(-50%);\r\n}\r\n\r\n.settings-container > * {\r\n margin: 0 2px;\r\n cursor: pointer;\r\n}\r\n\r\n#info-bar svg {\r\n font-size: 16px;\r\n}\r\n\r\n#info-bar .divider {\r\n margin: 0 8px;\r\n}\r\n\r\nsvg:focus {\r\n border: none;\r\n}\r\n\r\n.visualization-card {\r\n border-left: 8px solid #eee;\r\n}\r\n\r\n.active-tab {\r\n border-left: 8px solid var(--visualization-primary) !important;\r\n border-image-slice: 1;\r\n}\r\n\r\n[id^='frame-'] {\r\n padding: 1px;\r\n display: flex;\r\n flex-flow: column;\r\n}\r\n\r\n.tooltip {\r\n display: block !important;\r\n z-index: 10000;\r\n}\r\n\r\n.tooltip .tooltip-inner {\r\n background: var(--visualization-primary);\r\n color: white;\r\n border-radius: 16px;\r\n padding: 5px 10px 4px;\r\n}\r\n\r\n.tooltip .tooltip-arrow {\r\n width: 0;\r\n height: 0;\r\n border-style: solid;\r\n position: absolute;\r\n margin: 5px;\r\n border-color: var(--visualization-primary);\r\n z-index: 1;\r\n}\r\n\r\n.tooltip[x-placement^='top'] {\r\n margin-bottom: 5px;\r\n}\r\n\r\n.tooltip[x-placement^='top'] .tooltip-arrow {\r\n border-width: 5px 5px 0 5px;\r\n border-left-color: transparent !important;\r\n border-right-color: transparent !important;\r\n border-bottom-color: transparent !important;\r\n bottom: -5px;\r\n left: calc(50% - 5px);\r\n margin-top: 0;\r\n margin-bottom: 0;\r\n}\r\n\r\n.tooltip[x-placement^='bottom'] {\r\n margin-top: 5px;\r\n}\r\n\r\n.tooltip[x-placement^='bottom'] .tooltip-arrow {\r\n border-width: 0 5px 5px 5px;\r\n border-left-color: transparent !important;\r\n border-right-color: transparent !important;\r\n border-top-color: transparent !important;\r\n top: -5px;\r\n left: calc(50% - 5px);\r\n margin-top: 0;\r\n margin-bottom: 0;\r\n}\r\n\r\n.tooltip[x-placement^='right'] {\r\n margin-left: 5px;\r\n}\r\n\r\n.tooltip[x-placement^='right'] .tooltip-arrow {\r\n border-width: 5px 5px 5px 0;\r\n border-left-color: transparent !important;\r\n border-top-color: transparent !important;\r\n border-bottom-color: transparent !important;\r\n left: -5px;\r\n top: calc(50% - 5px);\r\n margin-left: 0;\r\n margin-right: 0;\r\n}\r\n\r\n.tooltip[x-placement^='left'] {\r\n margin-right: 5px;\r\n}\r\n\r\n.tooltip[x-placement^='left'] .tooltip-arrow {\r\n border-width: 5px 0 5px 5px;\r\n border-top-color: transparent !important;\r\n border-right-color: transparent !important;\r\n border-bottom-color: transparent !important;\r\n right: -5px;\r\n top: calc(50% - 5px);\r\n margin-left: 0;\r\n margin-right: 0;\r\n}\r\n\r\n.tooltip.popover .popover-inner {\r\n background: #f9f9f9;\r\n color: black;\r\n padding: 24px;\r\n border-radius: 5px;\r\n box-shadow: 0 5px 30px rgba(black, 0.1);\r\n}\r\n\r\n.tooltip.popover .popover-arrow {\r\n border-color: #f9f9f9;\r\n}\r\n\r\n.tooltip[aria-hidden='true'] {\r\n visibility: hidden;\r\n opacity: 0;\r\n transition: opacity 0.15s, visibility 0.15s;\r\n}\r\n\r\n.tooltip[aria-hidden='false'] {\r\n visibility: visible;\r\n opacity: 1;\r\n transition: opacity 0.15s;\r\n}\r\n\r\n.custom-frametime {\r\n font-size: smaller;\r\n}\r\n\r\n/* DAR CORES AOS TEMPOS DEPENDENDO DO TIPO DE EVENTO */\r\n.event-A {\r\n color: #000; /* black for A */\r\n}\r\n.event-B{\r\n color: rgb(0, 115, 230); /* blue for B */\r\n}\r\n.event-C{\r\n color: rgb(0, 179, 0); /* green for C */\r\n}\r\n.event-gap {\r\n color: #555; /* Default color for gaps */\r\n}\r\n</style>\r\n"]}, media: undefined });
6255
+ inject("data-v-01555ebc_0", { source: "\n.visualization-row[data-v-01555ebc] {\r\n display: flex;\r\n flex-wrap: wrap;\r\n flex: 1 1 auto;\n}\n.visualization-col[data-v-01555ebc] {\r\n flex-basis: 0;\r\n flex-grow: 1;\r\n max-width: 100%;\r\n padding: 5px;\n}\n.visualization-divider[data-v-01555ebc] {\r\n display: block;\r\n flex: 1 1 100%;\r\n height: 0px;\r\n max-height: 0px;\r\n opacity: 1;\r\n transition: inherit;\r\n border-style: solid;\r\n border-width: thin 0 0 0;\r\n border-color: rgba(0, 0, 0, 0.12);\r\n margin: 0;\n}\n.visualization-divider.vertical[data-v-01555ebc] {\r\n align-self: stretch;\r\n border-width: 0 thin 0 0;\r\n display: inline-flex;\r\n height: inherit;\r\n margin-left: -1px;\r\n max-height: 100%;\r\n max-width: 0px;\r\n vertical-align: text-bottom;\r\n width: 0px;\n}\n.visualization-card[data-v-01555ebc] {\r\n flex-basis: 0;\r\n flex-grow: 1;\r\n max-width: 100%;\r\n padding: 12px;\r\n width: 100%;\r\n transition-property: box-shadow, opacity, -webkit-box-shadow;\r\n overflow-wrap: break-word;\r\n /*box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14),\r\n 0 1px 5px 0 rgba(0, 0, 0, 0.12);*/\n}\n.visualization-justify-center[data-v-01555ebc],\r\n*[data-v-01555ebc] .visualization-justify-center {\r\n justify-content: center;\n}\n.visualization-align-center[data-v-01555ebc] {\r\n align-items: center;\n}\n#visualization-container[data-v-01555ebc] {\r\n max-width: 100% !important;\r\n margin: 0 auto !important;\r\n height: 100%;\r\n border-bottom: none;\n}\n#visualization-container > .card[data-v-01555ebc] {\r\n border-radius: 0 !important;\r\n font-size: 12px;\r\n width: 100%;\r\n box-shadow: none;\r\n height: 100%;\n}\n#command-bar[data-v-01555ebc],\r\n#info-bar[data-v-01555ebc] {\r\n background-color: #f5f5f5;\r\n box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14),\r\n 0 1px 5px 0 rgba(0, 0, 0, 0.12);\n}\n#command-bar button[data-v-01555ebc] {\r\n width: 42px;\r\n height: 36px;\r\n border: none;\r\n background: none;\n}\n#command-bar button[data-v-01555ebc]:hover {\r\n cursor: pointer;\r\n background: rgba(0, 0, 0, 0.12);\n}\n#command-bar svg[data-v-01555ebc] {\r\n font-size: 16px;\n}\n#command-bar[data-v-01555ebc] {\r\n padding: 0 !important;\n}\n#info-bar[data-v-01555ebc] {\r\n padding: 4px;\r\n font-size: 14px;\r\n position: relative;\n}\n.settings-container[data-v-01555ebc] {\r\n position: absolute;\r\n right: 14px;\r\n top: 50%;\r\n transform: translateY(-50%);\n}\n.settings-container > *[data-v-01555ebc] {\r\n margin: 0 2px;\r\n cursor: pointer;\n}\n#info-bar svg[data-v-01555ebc] {\r\n font-size: 16px;\n}\n#info-bar .divider[data-v-01555ebc] {\r\n margin: 0 8px;\n}\nsvg[data-v-01555ebc]:focus {\r\n border: none;\n}\n.visualization-card[data-v-01555ebc] {\r\n border-left: 8px solid #eee;\n}\n.active-tab[data-v-01555ebc] {\r\n border-left: 8px solid var(--visualization-primary) !important;\r\n border-image-slice: 1;\n}\n[id^='frame-'][data-v-01555ebc] {\r\n padding: 1px;\r\n display: flex;\r\n flex-flow: column;\n}\n.tooltip[data-v-01555ebc] {\r\n display: block !important;\r\n z-index: 10000;\n}\n.tooltip .tooltip-inner[data-v-01555ebc] {\r\n background: var(--visualization-primary);\r\n color: white;\r\n border-radius: 16px;\r\n padding: 5px 10px 4px;\n}\n.tooltip .tooltip-arrow[data-v-01555ebc] {\r\n width: 0;\r\n height: 0;\r\n border-style: solid;\r\n position: absolute;\r\n margin: 5px;\r\n border-color: var(--visualization-primary);\r\n z-index: 1;\n}\n.tooltip[x-placement^='top'][data-v-01555ebc] {\r\n margin-bottom: 5px;\n}\n.tooltip[x-placement^='top'] .tooltip-arrow[data-v-01555ebc] {\r\n border-width: 5px 5px 0 5px;\r\n border-left-color: transparent !important;\r\n border-right-color: transparent !important;\r\n border-bottom-color: transparent !important;\r\n bottom: -5px;\r\n left: calc(50% - 5px);\r\n margin-top: 0;\r\n margin-bottom: 0;\n}\n.tooltip[x-placement^='bottom'][data-v-01555ebc] {\r\n margin-top: 5px;\n}\n.tooltip[x-placement^='bottom'] .tooltip-arrow[data-v-01555ebc] {\r\n border-width: 0 5px 5px 5px;\r\n border-left-color: transparent !important;\r\n border-right-color: transparent !important;\r\n border-top-color: transparent !important;\r\n top: -5px;\r\n left: calc(50% - 5px);\r\n margin-top: 0;\r\n margin-bottom: 0;\n}\n.tooltip[x-placement^='right'][data-v-01555ebc] {\r\n margin-left: 5px;\n}\n.tooltip[x-placement^='right'] .tooltip-arrow[data-v-01555ebc] {\r\n border-width: 5px 5px 5px 0;\r\n border-left-color: transparent !important;\r\n border-top-color: transparent !important;\r\n border-bottom-color: transparent !important;\r\n left: -5px;\r\n top: calc(50% - 5px);\r\n margin-left: 0;\r\n margin-right: 0;\n}\n.tooltip[x-placement^='left'][data-v-01555ebc] {\r\n margin-right: 5px;\n}\n.tooltip[x-placement^='left'] .tooltip-arrow[data-v-01555ebc] {\r\n border-width: 5px 0 5px 5px;\r\n border-top-color: transparent !important;\r\n border-right-color: transparent !important;\r\n border-bottom-color: transparent !important;\r\n right: -5px;\r\n top: calc(50% - 5px);\r\n margin-left: 0;\r\n margin-right: 0;\n}\n.tooltip.popover .popover-inner[data-v-01555ebc] {\r\n background: #f9f9f9;\r\n color: black;\r\n padding: 24px;\r\n border-radius: 5px;\r\n box-shadow: 0 5px 30px rgba(black, 0.1);\n}\n.tooltip.popover .popover-arrow[data-v-01555ebc] {\r\n border-color: #f9f9f9;\n}\n.tooltip[aria-hidden='true'][data-v-01555ebc] {\r\n visibility: hidden;\r\n opacity: 0;\r\n transition: opacity 0.15s, visibility 0.15s;\n}\n.tooltip[aria-hidden='false'][data-v-01555ebc] {\r\n visibility: visible;\r\n opacity: 1;\r\n transition: opacity 0.15s;\n}\n.custom-frametime[data-v-01555ebc] {\r\n font-size: smaller;\n}\r\n\r\n/* DAR CORES AOS TEMPOS DEPENDENDO DO TIPO DE EVENTO */\n.event-A[data-v-01555ebc] {\r\n color: #000; /* black for A */\n}\n.event-B[data-v-01555ebc] {\r\n color: rgb(0, 115, 230); /* blue for B */\n}\n.event-C[data-v-01555ebc] {\r\n color: rgb(0, 179, 0); /* green for C */\n}\n.event-gap[data-v-01555ebc] {\r\n color: #555; /* Default color for gaps */\n}\r\n", map: {"version":3,"sources":["C:\\Workspace\\visualization\\src\\Visualization.vue"],"names":[],"mappings":";AAu0DA;EACA,aAAA;EACA,eAAA;EACA,cAAA;AACA;AAEA;EACA,aAAA;EACA,YAAA;EACA,eAAA;EACA,YAAA;AACA;AAEA;EACA,cAAA;EACA,cAAA;EACA,WAAA;EACA,eAAA;EACA,UAAA;EACA,mBAAA;EACA,mBAAA;EACA,wBAAA;EACA,iCAAA;EACA,SAAA;AACA;AAEA;EACA,mBAAA;EACA,wBAAA;EACA,oBAAA;EACA,eAAA;EACA,iBAAA;EACA,gBAAA;EACA,cAAA;EACA,2BAAA;EACA,UAAA;AACA;AAEA;EACA,aAAA;EACA,YAAA;EACA,eAAA;EACA,aAAA;EACA,WAAA;EACA,4DAAA;EACA,yBAAA;EACA;qCACA;AACA;AAEA;;EAEA,uBAAA;AACA;AAEA;EACA,mBAAA;AACA;AAEA;EACA,0BAAA;EACA,yBAAA;EACA,YAAA;EACA,mBAAA;AACA;AACA;EACA,2BAAA;EACA,eAAA;EACA,WAAA;EACA,gBAAA;EACA,YAAA;AACA;AAEA;;EAEA,yBAAA;EACA;mCACA;AACA;AACA;EACA,WAAA;EACA,YAAA;EACA,YAAA;EACA,gBAAA;AACA;AAEA;EACA,eAAA;EACA,+BAAA;AACA;AAEA;EACA,eAAA;AACA;AAEA;EACA,qBAAA;AACA;AAEA;EACA,YAAA;EACA,eAAA;EACA,kBAAA;AACA;AAEA;EACA,kBAAA;EACA,WAAA;EACA,QAAA;EACA,2BAAA;AACA;AAEA;EACA,aAAA;EACA,eAAA;AACA;AAEA;EACA,eAAA;AACA;AAEA;EACA,aAAA;AACA;AAEA;EACA,YAAA;AACA;AAEA;EACA,2BAAA;AACA;AAEA;EACA,8DAAA;EACA,qBAAA;AACA;AAEA;EACA,YAAA;EACA,aAAA;EACA,iBAAA;AACA;AAEA;EACA,yBAAA;EACA,cAAA;AACA;AAEA;EACA,wCAAA;EACA,YAAA;EACA,mBAAA;EACA,qBAAA;AACA;AAEA;EACA,QAAA;EACA,SAAA;EACA,mBAAA;EACA,kBAAA;EACA,WAAA;EACA,0CAAA;EACA,UAAA;AACA;AAEA;EACA,kBAAA;AACA;AAEA;EACA,2BAAA;EACA,yCAAA;EACA,0CAAA;EACA,2CAAA;EACA,YAAA;EACA,qBAAA;EACA,aAAA;EACA,gBAAA;AACA;AAEA;EACA,eAAA;AACA;AAEA;EACA,2BAAA;EACA,yCAAA;EACA,0CAAA;EACA,wCAAA;EACA,SAAA;EACA,qBAAA;EACA,aAAA;EACA,gBAAA;AACA;AAEA;EACA,gBAAA;AACA;AAEA;EACA,2BAAA;EACA,yCAAA;EACA,wCAAA;EACA,2CAAA;EACA,UAAA;EACA,oBAAA;EACA,cAAA;EACA,eAAA;AACA;AAEA;EACA,iBAAA;AACA;AAEA;EACA,2BAAA;EACA,wCAAA;EACA,0CAAA;EACA,2CAAA;EACA,WAAA;EACA,oBAAA;EACA,cAAA;EACA,eAAA;AACA;AAEA;EACA,mBAAA;EACA,YAAA;EACA,aAAA;EACA,kBAAA;EACA,uCAAA;AACA;AAEA;EACA,qBAAA;AACA;AAEA;EACA,kBAAA;EACA,UAAA;EACA,2CAAA;AACA;AAEA;EACA,mBAAA;EACA,UAAA;EACA,yBAAA;AACA;AAEA;EACA,kBAAA;AACA;;AAEA,sDAAA;AACA;EACA,WAAA,EAAA,gBAAA;AACA;AACA;EACA,uBAAA,EAAA,eAAA;AACA;AACA;EACA,qBAAA,EAAA,gBAAA;AACA;AACA;EACA,WAAA,EAAA,2BAAA;AACA","file":"Visualization.vue","sourcesContent":["<template>\r\n <div\r\n class=\"visualization-row\"\r\n id=\"visualization-container\"\r\n @click=\"framesClicked\"\r\n @scroll=\"scrollEvent\"\r\n >\r\n <GlobalEvents\r\n v-if=\"!readOnly && canInsertTime && settingsClosed\"\r\n :filter=\"(event) => !event.shiftKey && !event.ctrlKey\"\r\n @keydown.45=\"insertTime\"\r\n />\r\n <!-- <GlobalEvents\r\n v-if=\"!readOnly && active && settingsClosed\"\r\n @keydown.113.prevent=\"toggleEventA\"\r\n @keydown.114.prevent=\"toggleEventC\"\r\n @keydown.116.prevent=\"toggleEventB\" /> -->\r\n <GlobalEvents\r\n v-if=\"active && settingsClosed\"\r\n @keydown.46.prevent=\"clearAllFrameSelections\"\r\n @keydown.left.prevent=\"arrowLeft\"\r\n @keydown.right.prevent=\"arrowRight\"\r\n @keydown.up.prevent=\"arrowUp\"\r\n @keydown.down.prevent=\"arrowDown\"\r\n @keydown.shift.page-down.prevent=\"nextLoopActivate\"\r\n @keydown.page-down.prevent=\"() => next()\"\r\n @keydown.page-up.prevent=\"() => prev()\"\r\n @keydown.shift.page-up.prevent=\"prevLoopActivate\"\r\n @keydown.36.prevent=\"goToFirstFrame\"\r\n @keydown.35.prevent=\"goToLastFrame\"\r\n @keydown.71.prevent=\"dialogs.goTo = true\"\r\n @keydown.73.prevent=\"dialogs.secondsPerFrame = true\"\r\n @keydown.76.prevent=\"dialogs.frames = true\"\r\n @keydown.49.97=\"() => (secondsPerFrame = 1)\"\r\n @keydown.50.98=\"() => (secondsPerFrame = 2)\"\r\n @keydown.51.99=\"() => (secondsPerFrame = 3)\"\r\n @keydown.52.100=\"() => (secondsPerFrame = 4)\"\r\n @keydown.53.101=\"() => (secondsPerFrame = 5)\"\r\n />\r\n <GlobalEvents\r\n v-if=\"prevLoop || nextLoop\"\r\n @keydown=\"breakLoop\"\r\n v-on:click=\"breakLoop\"\r\n />\r\n <settings\r\n v-if=\"active\"\r\n :dialogs-visibility=\"dialogs\"\r\n :playback-rate=\"playbackRate\"\r\n :seconds-per-frame=\"secondsPerFrame\"\r\n :frames-per-row=\"framesPerRow\"\r\n :number-of-rows=\"numberOfRows\"\r\n :max-steps=\"maxSteps\"\r\n :shift-frames=\"shiftFrames\"\r\n :customGridSize=\"customGridSize\"\r\n :stretchFrames=\"stretchFrame\"\r\n @change-playback-rate=\"(value) => (playbackRate = value)\"\r\n @change-go-to=\"changeHour\"\r\n @change-seconds-per-frame=\"(value) => (secondsPerFrame = value)\"\r\n @change-shift-frames=\"(value) => (shiftFrames = value)\"\r\n @set-frames-selection=\"setFrameSelection\"\r\n @set-custom-grid-values=\"setCustomGridValues\"\r\n @close=\"(dialog) => (dialogs[dialog] = false)\"\r\n />\r\n <div\r\n :class=\"{ 'visualization-card': true, 'active-tab': active }\"\r\n style=\"width: 100%; padding: 0\"\r\n >\r\n <command-bar\r\n v-show=\"commandBarShow\"\r\n :read-only=\"readOnly\"\r\n :video-playing=\"videoPlaying\"\r\n :video-paused=\"paused\"\r\n :insert-time=\"canInsertTime\"\r\n @prev-loop-activate=\"prevLoopActivate\"\r\n @next-loop-activate=\"nextLoopActivate\"\r\n @prev=\"prev\"\r\n @next=\"next\"\r\n @go-to=\"dialogs.goTo = true\"\r\n @open-frame-selection=\"dialogs.frames = true\"\r\n @open-frames-per-second=\"dialogs.secondsPerFrame = true\"\r\n @open-blocks=\"openBlocks\"\r\n @open-playback-rate=\"dialogs.playbackRate = true\"\r\n @play-or-pause=\"playOrPause\"\r\n @stop-playing=\"stopPlayingBar\"\r\n @insert-time=\"insertTime\"\r\n @shift-frames=\"dialogs.shiftFrames = true\"\r\n />\r\n <video-progress\r\n v-if=\"videoProgressBar\"\r\n v-show=\"videoPlaying\"\r\n :video-time=\"videoTime\"\r\n />\r\n <info-bar\r\n :playback-rate=\"playbackRate\"\r\n :seconds-per-frame=\"secondsPerFrame\"\r\n :commands-show=\"commandBarShow\"\r\n :cache=\"useCache\"\r\n :block-start-time=\"blockStartTime\"\r\n :video-total-duration=\"videoSliderTotalDuration\"\r\n :alternative-server=\"alternativeServer\"\r\n :frames-per-row=\"framesPerRow\"\r\n :number-of-rows=\"numberOfRows\"\r\n :show-stretch-frame=\"showStretchFrame\"\r\n @toogle-commands-visibility=\"toogleCommandsVisibility\"\r\n @toogle-cache=\"useCache = !useCache\"\r\n @change-server=\"changeServerClick\"\r\n @update-stretch-frames=\"(value) => (stretchFrame = value)\"\r\n />\r\n <div\r\n class=\"visualization-row\"\r\n v-for=\"rowNumber in numberOfRows\"\r\n :id=\"'row-' + rowNumber\"\r\n :key=\"'row-' + rowNumber\"\r\n style=\"padding: 0px 4px\"\r\n >\r\n <div\r\n v-for=\"(frame, frameNumber) in previousFrames\"\r\n :key=\"\r\n numberOfRows +\r\n '-' +\r\n framesPerRow +\r\n '-' +\r\n getIndex(rowNumber, frameNumber, Positions.previous)\r\n \"\r\n style=\"display: none\"\r\n >\r\n <span v-html=\"frame.img\" />\r\n </div>\r\n <div\r\n v-for=\"(frame, frameNumber) in nextFrames\"\r\n :key=\"\r\n numberOfRows +\r\n '-' +\r\n framesPerRow +\r\n '-' +\r\n getIndex(rowNumber, frameNumber, Positions.next)\r\n \"\r\n style=\"display: none\"\r\n >\r\n <span v-html=\"frame.img\" />\r\n </div>\r\n <div\r\n class=\"visualization-col\"\r\n v-for=\"(frame, frameNumber) in frames.slice(\r\n framesPerRow * (rowNumber - 1),\r\n framesPerRow * rowNumber\r\n )\"\r\n :key=\"'row-' + rowNumber + '-frame-' + frameNumber + '-' + frame.time\"\r\n :id=\"`frame-${getIndex(rowNumber, frameNumber, Positions.current)}`\"\r\n :class=\"{ loaderImg: !!frame.img }\"\r\n @click=\"\r\n frame.time\r\n ? selectFrame(\r\n getIndex(rowNumber, frameNumber, Positions.current),\r\n frame\r\n )\r\n : null\r\n \"\r\n >\r\n <span\r\n :class=\"giveMeType(frame.time)\"\r\n :id=\"activeFrame ? 'aa' : 0\"\r\n style=\"text-align: center\"\r\n >\r\n <b>\r\n {{\r\n getAudienceTime(\r\n frame.time,\r\n rowNumber,\r\n frameNumber,\r\n Positions.current\r\n )\r\n }}\r\n </b>\r\n </span>\r\n\r\n <frame\r\n ref=\"frames\"\r\n :frame=\"frame\"\r\n :index=\"getIndex(rowNumber, frameNumber, Positions.current)\"\r\n :grid-settings=\"{ numberOfRows, framesPerRow }\"\r\n :initialTime=\"isEventStart(frame.time)\"\r\n :endTime=\"isEventEnd(frame.time)\"\r\n :checkpointTime=\"false\"\r\n :betweenTime=\"isBetweenEvent(frame.time)\"\r\n :eventType=\"getEventType(frame.time)\"\r\n :active=\"\r\n getIndex(rowNumber, frameNumber, Positions.current) ===\r\n activeFrame\r\n \"\r\n :activeTab=\"active\"\r\n :videoUrl=\"fInterface ? fInterface.getVideoUrl(frame) : ''\"\r\n :videoControls=\"videoControls\"\r\n @startPlaying=\"startPlaying\"\r\n @stopPlaying=\"stopPlaying\"\r\n @playPauseStatus=\"changePlayPause\"\r\n @updateSlider=\"updateSlider\"\r\n :playback-rate=\"playbackRate\"\r\n :aspect-ratio=\"aspectRatio\"\r\n :stretchFrame=\"stretchFrame\"\r\n style=\"margin: 0 auto\"\r\n ></frame>\r\n </div>\r\n </div>\r\n </div>\r\n <!-- <settings\r\n ref=\"settings2\"\r\n :active=\"active\"\r\n @goToTime=\"changeHour\"\r\n @goToBlockInterval=\"changeBlockInterval\"\r\n @setSplitTime=\"setSplitTime\"\r\n @setFrameSelection=\"setFrameSelection\"\r\n @setPlaybackRate=\"\r\n (rate) => {\r\n playbackRate = rate\r\n }\r\n \"\r\n >\r\n </settings> -->\r\n <!-- <v-dialog v-model=\"dialog\" width=\"500\">\r\n <div class=\"card\">\r\n <div class=\"card\"-title class=\"text-h5 grey lighten-2\">\r\n {{ ' Último bloco disponível até: ' }}\r\n <v-btn\r\n @click=\"goToStartBlock\"\r\n class=\"ml-2\"\r\n dark\r\n color=\"success\"\r\n depressed\r\n >\r\n <v-icon left> fa-clock </v-icon>\r\n {{ timeLastBlock }}\r\n </v-btn>\r\n <v-spacer></v-spacer>\r\n <v-btn color=\"error\" fab small class=\"ml-5\" @click=\"dialog = false\">\r\n <v-icon dark> fa fa-xmark </v-icon>\r\n </v-btn>\r\n </div-title>\r\n </div>\r\n </v-dialog>\r\n <Help :media=\"media\" @close=\"media = null\" />\r\n <v-dialog\r\n v-if=\"userMultiTabsGrid\"\r\n v-model=\"userMultiTabsGridsModel\"\r\n persistent\r\n width=\"60%\"\r\n >\r\n <div class=\"card\">\r\n <div class=\"card\"-title class=\"warning text-h5\" primary-title>\r\n <div class=\"row\" class=\"ma-0\" justify=\"center\" align=\"center\">\r\n <v-icon dark left style=\"font-size: 24px !important\">\r\n fa fa-exclamation-triangle\r\n </v-icon>\r\n <div style=\"color: white\">{{ $t('form.alert') }}</div>\r\n </div>\r\n </div-title>\r\n <div class=\"card\"-text class=\"justify-center pa-6 grey lighten-2\">\r\n <h3>\r\n {{ $t('alerts.userMultiTabsGrid') }}\r\n </h3>\r\n </div-text>\r\n <hr class=\"divider\" class=\"grey lighten-1\"></span>\r\n <div class=\"card\"-actions class=\"grey lighten-2 justify-center\">\r\n <v-btn color=\"error\" ml-5 @click=\"userMultiTabsGrid = false\">\r\n <v-icon left color=\"white\">fa fa-times</v-icon>\r\n {{ $t('form.close') }}\r\n </v-btn>\r\n </div-actions>\r\n </div>\r\n </v-dialog> -->\r\n </div>\r\n</template>\r\n<script>\r\nimport Frame from './components/Frame.vue'\r\nimport FramesInterface from './utils/FramesInterface.js'\r\nimport FramesService from './services/FramesService.js'\r\n\r\nimport Commands from './components/Commands.vue'\r\nimport Infos from './components/Infos.vue'\r\nimport VideoProgress from './components/VideoProgress.vue'\r\nimport Settings from './components/Settings.vue'\r\n\r\nconst Positions = Object.freeze({\r\n previous: 0,\r\n current: 1,\r\n next: 2,\r\n})\r\n\r\nexport default {\r\n name: 'visualization-container',\r\n props: {\r\n value: {\r\n type: Boolean,\r\n required: true,\r\n },\r\n date: {\r\n type: String,\r\n required: true,\r\n },\r\n channel: {\r\n type: Number,\r\n required: true,\r\n },\r\n startAudienceTime: {\r\n type: String,\r\n required: true,\r\n },\r\n endAudienceTime: {\r\n type: String,\r\n required: true,\r\n },\r\n videoProgressBar: {\r\n type: Boolean,\r\n default: false,\r\n },\r\n jumpOnInsert: {\r\n type: Boolean,\r\n default: false,\r\n },\r\n removeSelectionOnInsert: {\r\n type: Boolean,\r\n default: false,\r\n },\r\n framesFormat: {\r\n type: [Number, String],\r\n default: 7,\r\n },\r\n maxSize: {\r\n type: Number,\r\n },\r\n videoControls: {\r\n type: Boolean,\r\n default: false,\r\n },\r\n readOnly: {\r\n type: Boolean,\r\n default: false,\r\n },\r\n maxSteps: {\r\n type: Number,\r\n default: 1,\r\n },\r\n customGridSize: {\r\n type: Boolean,\r\n default: false,\r\n },\r\n showStretchFrame: {\r\n type: Boolean,\r\n default: false,\r\n },\r\n visualizationInsArray: {\r\n type: Array,\r\n default: () => [],\r\n },\r\n insertDefaults: {\r\n type: Array,\r\n default: () => [],\r\n },\r\n },\r\n components: {\r\n Frame,\r\n CommandBar: Commands,\r\n InfoBar: Infos,\r\n VideoProgress,\r\n Settings,\r\n // Help,\r\n },\r\n data() {\r\n return {\r\n Positions,\r\n updatingChannel: null,\r\n dialog: false,\r\n timeLastBlock: null,\r\n alternativeServer: false,\r\n useCache: true,\r\n numberOfRows: 1,\r\n framesPerRow: 5,\r\n secondsPerFrame: 1,\r\n fInterface: null,\r\n velocity: 1,\r\n frames: [],\r\n previousFrames: [],\r\n nextFrames: [],\r\n channelCode: 0,\r\n videoPlaying: false,\r\n activeFrame: null,\r\n activeVideo: null,\r\n videoTime: 0,\r\n videoTotalTime: null,\r\n progressVideoDrag: false,\r\n events: {\r\n A: [],\r\n B: [],\r\n C: [],\r\n },\r\n currentEventA: null,\r\n activeEvents: {}, // Track active events by type\r\n eventBlocked: false, // Track if event creation is blocked\r\n canInsertTime: false,\r\n lastHeight: 0,\r\n loopInterval: null,\r\n nextLoop: false,\r\n prevLoop: false,\r\n videoSliderTotalDuration: 900,\r\n blockStartTime: null,\r\n media: null,\r\n changeServer: false,\r\n userMultiTabsGrid: false,\r\n userMultiTabsGridsModel: true,\r\n playbackRate: 1,\r\n paused: false,\r\n commandBarShow: true,\r\n dialogs: {\r\n playbackRate: false,\r\n goTo: false,\r\n secondsPerFrame: false,\r\n frames: false,\r\n shiftFrames: false,\r\n },\r\n lastNext: 0,\r\n lastPrev: 0,\r\n shiftFrames: 0,\r\n aspectRatio: 11 / 9,\r\n showCustomInputs: false,\r\n stretchFrame: false,\r\n scrollCounter: 300,\r\n eventsArray: [],\r\n }\r\n },\r\n async created() {\r\n this.changeServer = this.serverOfFrames === 'alternative'\r\n this.alternativeServer = this.serverOfFrames === 'alternative'\r\n //console.log('visualization array updated: ', this.visualizationInsArray)\r\n const settings = [\r\n {\r\n framesPerRow: 1,\r\n numberOfRows: 1,\r\n },\r\n {\r\n framesPerRow: 2,\r\n numberOfRows: 1,\r\n },\r\n {\r\n framesPerRow: 3,\r\n numberOfRows: 1,\r\n },\r\n {\r\n framesPerRow: 3,\r\n numberOfRows: 2,\r\n },\r\n {\r\n framesPerRow: 4,\r\n numberOfRows: 1,\r\n },\r\n {\r\n framesPerRow: 4,\r\n numberOfRows: 2,\r\n },\r\n {\r\n framesPerRow: 5,\r\n numberOfRows: 1,\r\n },\r\n {\r\n framesPerRow: 5,\r\n numberOfRows: 2,\r\n },\r\n {\r\n framesPerRow: 6,\r\n numberOfRows: 1,\r\n },\r\n {\r\n framesPerRow: 6,\r\n numberOfRows: 2,\r\n },\r\n {\r\n //Custom (item 11)\r\n framesPerRow: 7,\r\n numberOfRows: 3,\r\n },\r\n ]\r\n\r\n // Os valores custom são guardados no local storage e não na base de dados\r\n const customValues = JSON.parse(localStorage.getItem('customGridValues'))\r\n\r\n if (this.framesFormat == '11' && !!customValues) {\r\n this.numberOfRows = customValues[0]\r\n this.framesPerRow = customValues[1]\r\n } else {\r\n const storedOnDb = settings[parseInt(this.framesFormat) - 1]\r\n this.framesPerRow = storedOnDb.framesPerRow\r\n this.numberOfRows = storedOnDb.numberOfRows\r\n }\r\n\r\n document.addEventListener('wheel', this.scrollEvent)\r\n document.addEventListener('keydown', this.handleKeydown)\r\n\r\n await this.createFramesInterface()\r\n this.$nextTick(this.resize)\r\n },\r\n beforeDestroy() {\r\n document.removeEventListener('wheel', this.scrollEvent)\r\n document.removeEventListener('keydown', this.handleKeydown)\r\n },\r\n methods: {\r\n handleKeydown(event) {\r\n if (!this.active || !this.settingsClosed || this.readOnly) return\r\n\r\n // Check if the pressed key matches any shortcut in insertDefaults\r\n const matchedConfig = this.insertDefaults.find(\r\n (config) => config.shortcut.toUpperCase() === event.key.toUpperCase()\r\n )\r\n\r\n if (matchedConfig) {\r\n event.preventDefault()\r\n this.toggleEvent(matchedConfig.event, event.key.toUpperCase())\r\n }\r\n },\r\n scrollEvent(e) {\r\n if (this.active) {\r\n if (Math.sign(e.deltaY) !== Math.sign(this.scrollCounter)) {\r\n if (Math.sign(e.deltaY) > 0) {\r\n this.next()\r\n } else {\r\n this.prev()\r\n }\r\n this.scrollCounter = e.deltaY\r\n return\r\n }\r\n\r\n this.scrollCounter += e.deltaY\r\n if (Math.abs(this.scrollCounter) >= 400) {\r\n if (Math.sign(e.deltaY) > 0) {\r\n this.next()\r\n } else {\r\n this.prev()\r\n }\r\n this.scrollCounter = 0\r\n }\r\n }\r\n },\r\n stopVideoPlaying(array) {\r\n for (const frame of array || this.$refs.frames) {\r\n if (\r\n frame.videoStatus === frame.Status.playing ||\r\n frame.videoStatus === frame.Status.paused\r\n ) {\r\n frame.stop(false)\r\n }\r\n }\r\n this.activeVideo = null\r\n },\r\n toogleCommandsVisibility() {\r\n this.commandBarShow = !this.commandBarShow\r\n this.$nextTick(this.resize)\r\n },\r\n framesClicked(e) {\r\n if (e.target.parentNode.id != 'insert') {\r\n this.active = true\r\n }\r\n },\r\n async goToStartBlock() {\r\n try {\r\n const d = new Date()\r\n let timestamp = Date.UTC(\r\n d.getFullYear(),\r\n d.getMonth(),\r\n d.getDate(),\r\n d.getHours(),\r\n d.getMinutes(),\r\n d.getSeconds()\r\n )\r\n\r\n const response = (\r\n await FramesService.getNextAvailableBlock({\r\n channel: this.channel,\r\n time: timestamp / 1000,\r\n })\r\n ).data\r\n\r\n this.dialog = false\r\n this.changeHour(this.convertToAudienceTime(response.data.start, ':'))\r\n } catch (err) {\r\n // console.error(err)\r\n }\r\n },\r\n async checkAvailableBlock() {\r\n try {\r\n const d = new Date()\r\n let timestamp = Date.UTC(\r\n d.getFullYear(),\r\n d.getMonth(),\r\n d.getDate(),\r\n d.getHours(),\r\n d.getMinutes(),\r\n d.getSeconds()\r\n )\r\n\r\n const response = (\r\n await FramesService.getNextAvailableBlock({\r\n channel: this.channel,\r\n time: timestamp / 1000,\r\n })\r\n ).data\r\n\r\n this.timeLastBlock = this.convertToAudienceTime(response.data.end, ':')\r\n this.dialog = true\r\n if (!response.status) {\r\n this.timeLastBlock = 'N/D'\r\n }\r\n } catch (err) {\r\n // console.error(err)\r\n }\r\n },\r\n updateSlider(videoStartTime, time) {\r\n // * atualizar slider se estiver fora do range definido previamente\r\n if (\r\n time < this.blockStartTime ||\r\n time > this.blockStartTime ||\r\n videoStartTime > this.blockStartTime + this.videoSliderTotalDuration\r\n ) {\r\n this.blockStartTime = videoStartTime\r\n this.videoSliderTotalDuration = 900\r\n }\r\n },\r\n nextLoopActivate() {\r\n this.breakLoop()\r\n this.loopInterval = setInterval(\r\n () => this.next({ ignoreTime: true }),\r\n this.swapImagesDelay\r\n )\r\n setTimeout(() => {\r\n this.nextLoop = true\r\n }, 0)\r\n },\r\n prevLoopActivate() {\r\n this.breakLoop()\r\n this.loopInterval = setInterval(\r\n () => this.prev({ ignoreTime: true }),\r\n this.swapImagesDelay\r\n )\r\n setTimeout(() => {\r\n this.prevLoop = true\r\n }, 0)\r\n },\r\n breakLoop() {\r\n clearInterval(this.loopInterval)\r\n this.loopInterval = null\r\n this.nextLoop = false\r\n this.prevLoop = false\r\n },\r\n changePlayPause(status) {\r\n this.paused = !status\r\n },\r\n resize(height = this.lastHeight) {\r\n this.lastHeight = height\r\n if (this.$refs.frames) {\r\n for (let frame of this.$refs.frames) {\r\n frame.resize(height)\r\n }\r\n }\r\n this.$emit('resized')\r\n },\r\n async goToFirstFrame() {\r\n let frames = this.$refs.frames\r\n\r\n let audienceTime = null\r\n if (frames.length > 0) {\r\n let frame = frames[0].frame\r\n audienceTime = this.getAudienceTime(frame.time, 0, 0, 0)\r\n }\r\n if (audienceTime) {\r\n const [hours, minutes, seconds] = audienceTime.split(':')\r\n const totalSeconds =\r\n parseInt(hours) * 3600 + parseInt(minutes) * 60 + parseInt(seconds)\r\n if (totalSeconds >= 9000)\r\n this.changeHour(this.getLastFirtsBlockTime(audienceTime, true))\r\n else this.changeHour(this.getLastFirtsBlockTime('02:30:00', true))\r\n }\r\n },\r\n async goToLastFrame() {\r\n let frames = this.$refs.frames\r\n let audienceTime = null\r\n if (frames.length > 0) {\r\n let frame = frames[0].frame\r\n\r\n audienceTime = this.getAudienceTime(frame.time, 0, 0, 0)\r\n }\r\n if (audienceTime) {\r\n this.changeHour(this.getLastFirtsBlockTime(audienceTime))\r\n }\r\n },\r\n getLastFirtsBlockTime(time, first = false) {\r\n if (time.indexOf(':') !== -1) {\r\n time = time.replace(/:/g, '')\r\n }\r\n let h, m, newTime\r\n const t = time.match(/.{1,2}/g)\r\n if (t[0] && t[1]) {\r\n h = parseInt(t[0])\r\n m = parseInt(t[1])\r\n }\r\n if (h < 26) {\r\n if (m < 15)\r\n if (first) newTime = t[0] + ':00:00'\r\n else newTime = t[0] + ':14:59'\r\n else if (m < 30)\r\n if (first) newTime = t[0] + ':15:00'\r\n else newTime = t[0] + ':29:59'\r\n else if (m < 45)\r\n if (first) newTime = t[0] + ':30:00'\r\n else newTime = t[0] + ':44:59'\r\n else if (first) newTime = t[0] + ':45:00'\r\n else newTime = t[0] + ':59:59'\r\n } else {\r\n if (m < 15)\r\n if (first) newTime = '26:00:00'\r\n else newTime = '26:14:59'\r\n else {\r\n if (first) newTime = '26:15:00'\r\n else newTime = '26:29:59'\r\n }\r\n }\r\n return newTime\r\n },\r\n openBlocks() {\r\n this.$refs.settings2?.openBlocks()\r\n },\r\n playOrPause() {\r\n const array = this.$refs.frames.filter((fc) => !!fc.active)\r\n if (array.length === 1) {\r\n const frame = array[0]\r\n frame.playOrPause(this.playbackRate)\r\n }\r\n },\r\n stopPlayingBar() {\r\n for (let ref of this.$refs.frames) {\r\n if (\r\n ref.videoStatus === ref.Status.playing ||\r\n ref.videoStatus === ref.Status.paused\r\n ) {\r\n ref.stop(false)\r\n }\r\n }\r\n },\r\n async setFrameSelection(selected) {\r\n this.frames = this.loadingArray\r\n const settings = [\r\n {\r\n framesPerRow: 1,\r\n numberOfRows: 1,\r\n },\r\n {\r\n framesPerRow: 2,\r\n numberOfRows: 1,\r\n },\r\n {\r\n framesPerRow: 3,\r\n numberOfRows: 1,\r\n },\r\n {\r\n framesPerRow: 3,\r\n numberOfRows: 2,\r\n },\r\n {\r\n framesPerRow: 4,\r\n numberOfRows: 1,\r\n },\r\n {\r\n framesPerRow: 4,\r\n numberOfRows: 2,\r\n },\r\n {\r\n framesPerRow: 5,\r\n numberOfRows: 1,\r\n },\r\n {\r\n framesPerRow: 5,\r\n numberOfRows: 2,\r\n },\r\n {\r\n framesPerRow: 6,\r\n numberOfRows: 1,\r\n },\r\n {\r\n framesPerRow: 6,\r\n numberOfRows: 2,\r\n },\r\n {\r\n //Custom (item 11)\r\n framesPerRow: 7,\r\n numberOfRows: 3,\r\n },\r\n ]\r\n\r\n const formatSelected = settings[selected - 1]\r\n this.framesPerRow = formatSelected.framesPerRow\r\n this.numberOfRows = formatSelected.numberOfRows\r\n\r\n await this.fInterface.changeSize(this.numberOfRows, this.framesPerRow)\r\n this.getFramesArray()\r\n this.$nextTick(this.resize)\r\n this.$emit('frames-format-changed', selected)\r\n },\r\n async setCustomGridValues(customValues) {\r\n this.numberOfRows = customValues[0]\r\n this.framesPerRow = customValues[1]\r\n localStorage.setItem('customGridValues', JSON.stringify(customValues))\r\n await this.fInterface.changeSize(this.numberOfRows, this.framesPerRow)\r\n this.getFramesArray()\r\n this.$nextTick(this.resize)\r\n this.$emit('frames-format-changed', 11)\r\n },\r\n getFramesArray() {\r\n this.frames = this.fInterface.getFrames(Positions.current)\r\n this.nextFrames = this.fInterface.getFrames(Positions.next)\r\n this.previousFrames = this.fInterface.getFrames(Positions.previous)\r\n\r\n const div = document.createElement('div')\r\n div.innerHTML = this.frames[0].image\r\n const newAspectRatio =\r\n div.getElementsByTagName('img')[0]?.naturalWidth /\r\n div.getElementsByTagName('img')[0]?.naturalHeight\r\n\r\n this.aspectRatio = isNaN(newAspectRatio)\r\n ? this.aspectRatio\r\n : newAspectRatio\r\n\r\n this.$nextTick(this.resize)\r\n\r\n const frame = this.frames.find((f) => f.blockStart)\r\n if (frame && this.alternativeServer) {\r\n this.$emit(\r\n 'new-block',\r\n frame.title?.match(/[0-9]{3}\\/(?:[0-9]+_?)+/)[0]\r\n )\r\n }\r\n },\r\n async createFramesInterface(startTime = this.startAudienceTime) {\r\n this.frames = this.loadingArray\r\n // let ch = this.channel\r\n // let associationTMP = {\r\n // 1735073: 1,\r\n // 1735074: 139,\r\n // 1735075: 3,\r\n // 1735076: 132,\r\n // }\r\n // //\r\n // this.channelCode = associationTMP[ch] ? associationTMP[ch] : ch\r\n\r\n const t = startTime.match(/.{1,2}/g)\r\n const d = this.getDateParts()\r\n const time = Date.UTC(d.year, d.month, d.day, t[0], t[1], t[2]) / 1000\r\n // * iniciar slider\r\n this.blockStartTime = time\r\n this.fInterface = await new FramesInterface(\r\n this.channel,\r\n this.numberOfRows,\r\n this.framesPerRow,\r\n time,\r\n this.startAudienceTime,\r\n this.useCache,\r\n this.shiftFrames\r\n )\r\n await this.fInterface.init()\r\n\r\n this.getFramesArray()\r\n\r\n const div = document.createElement('div')\r\n div.innerHTML = this.frames[0].image\r\n\r\n if (div.getElementsByTagName('img')[0]) {\r\n this.aspectRatio =\r\n div.getElementsByTagName('img')[0].naturalWidth /\r\n div.getElementsByTagName('img')[0].naturalHeight\r\n }\r\n\r\n this.activeFrame =\r\n this.getIndex(1, 0, Positions.current) + this.framesPerRow\r\n\r\n this.activeVideo = null\r\n },\r\n getIndex(rowNumber, frameIndex, position) {\r\n const baseOffset = this.framesPerRow * this.numberOfRows * position\r\n const rowOffset = (rowNumber - 1) * this.framesPerRow\r\n return baseOffset + rowOffset + frameIndex\r\n },\r\n getAudienceTime(frameTime, rowNumber, frameNumber, position) {\r\n if (!frameTime) {\r\n return 'Loading...'\r\n } else if (\r\n this.getIndex(rowNumber, frameNumber, position) === this.activeVideo\r\n ) {\r\n return this.convertToAudienceTime(this.videoTime)\r\n } else {\r\n return this.convertToAudienceTime(frameTime)\r\n }\r\n },\r\n giveMeType(frameTime) {\r\n if (!frameTime) {\r\n return ''\r\n }\r\n let time = this.convertToAudienceTime(frameTime)\r\n time = time.replace(/:/g, '')\r\n const foundEvent = this.eventsArray.find((event) => {\r\n return time >= event.hour_ini && time <= event.hour_end\r\n })\r\n if (foundEvent) {\r\n return `event-${foundEvent.type}`\r\n } else {\r\n return 'event-gap'\r\n }\r\n },\r\n dateInUtc(miliSeconds) {\r\n var date = new Date(miliSeconds)\r\n var utc = new Date(\r\n date.getUTCFullYear(),\r\n date.getUTCMonth(),\r\n date.getUTCDate(),\r\n date.getUTCHours(),\r\n date.getUTCMinutes(),\r\n date.getUTCSeconds()\r\n )\r\n return utc\r\n },\r\n convertToAudienceTime(time, separator = ':') {\r\n const d = this.getDateParts()\r\n const limit = Date.UTC(d.year, d.month, d.day, 23, 59, 59) / 1000\r\n\r\n let hour = this.dateInUtc(time * 1000)\r\n .toTimeString()\r\n .split(' ')[0]\r\n .split(':')\r\n .map(Number)\r\n\r\n if (time > limit && time <= limit + this.startAudienceSeconds) {\r\n hour[0] = 24 + hour[0]\r\n }\r\n return hour\r\n .map((part) => (part > 9 ? part.toString() : '0' + part))\r\n .join(separator)\r\n },\r\n getDateParts(date = this.date) {\r\n const data = /(?<year>\\d{4})-(?<month>\\d{2})-(?<day>\\d{2})/.exec(\r\n date\r\n )?.groups\r\n if (data) {\r\n return {\r\n year: parseInt(data.year),\r\n month: parseInt(data.month) - 1,\r\n day: parseInt(data.day),\r\n }\r\n }\r\n return {\r\n year: null,\r\n month: null,\r\n day: null,\r\n }\r\n },\r\n selectFrame(index, frame) {\r\n if (this.activeFrame !== index) {\r\n // Stop playing video if clicking on a different frame\r\n const array = this.$refs.frames.filter(\r\n (fc) => fc.videoStatus === fc.Status.playing\r\n )\r\n if (array.length === 1) {\r\n const frame = array[0]\r\n frame.playOrPause()\r\n }\r\n this.activeVideo = null\r\n this.activeFrame = index\r\n }\r\n },\r\n toggleEvent(eventConfig, shortcutKey) {\r\n const currentFrame = this.$refs.frames?.find(\r\n (f) => f.index === this.activeFrame\r\n )\r\n if (!currentFrame || !currentFrame.frame.time) return\r\n\r\n const frameTime = currentFrame.frame.time\r\n const eventType = eventConfig.type\r\n\r\n // Check if events are blocked (when type B is active outside Type A)\r\n if (this.eventBlocked && eventType !== 'B' && eventType !== 'C') {\r\n console.warn(\r\n 'Events are blocked. Close the active type B/C event first.'\r\n )\r\n return\r\n }\r\n\r\n if (eventType === 'A') {\r\n this.handleTypeA(frameTime, eventConfig, shortcutKey)\r\n } else if (eventType === 'B') {\r\n this.handleTypeB(frameTime, eventConfig, shortcutKey)\r\n } else if (eventType === 'C') {\r\n this.handleTypeC(frameTime, eventConfig, shortcutKey)\r\n }\r\n\r\n this.canInsertTime = this.hasAnyEvents()\r\n document.getElementById(`frame-${this.activeFrame}`).click()\r\n },\r\n handleTypeA(frameTime, eventConfig, shortcutKey) {\r\n if (this.currentEventA && this.currentEventA.hour_ini) {\r\n // Check if the shortcut matches the one used to start the event\r\n if (this.currentEventA.shortcutKey !== shortcutKey) {\r\n console.warn('Use the same shortcut key to close this event')\r\n return\r\n }\r\n // If clicking on the same frame where it started, remove the selection\r\n if (this.currentEventA.hour_ini === frameTime) {\r\n this.currentEventA = null\r\n delete this.activeEvents.A\r\n } else {\r\n // Close the event A\r\n this.currentEventA.hour_end = frameTime\r\n this.events.A.push({ ...this.currentEventA })\r\n this.currentEventA = null\r\n delete this.activeEvents.A\r\n }\r\n } else {\r\n // Check if clicking on hour_end of a completed event to reopen it\r\n const completedEvent = this.events.A.find(\r\n (e) => e.hour_end === frameTime && e.shortcutKey === shortcutKey\r\n )\r\n if (completedEvent) {\r\n // Remove from completed events and reopen it\r\n const index = this.events.A.indexOf(completedEvent)\r\n this.events.A.splice(index, 1)\r\n this.currentEventA = {\r\n ...completedEvent,\r\n hour_end: null,\r\n }\r\n this.activeEvents.A = true\r\n return\r\n }\r\n\r\n // Start a new event A\r\n // Type A cannot be nested inside another Type A (already checked by currentEventA)\r\n this.currentEventA = {\r\n ...eventConfig,\r\n hour_ini: frameTime,\r\n hour_end: null,\r\n shortcutKey: shortcutKey,\r\n }\r\n this.activeEvents.A = true\r\n }\r\n },\r\n handleTypeB(frameTime, eventConfig, shortcutKey) {\r\n const lastEventB = this.events.B[this.events.B.length - 1]\r\n const lastEventC = this.events.C[this.events.C.length - 1]\r\n\r\n if (lastEventB && !lastEventB.hour_end) {\r\n // Check if the shortcut matches the one used to start the event\r\n if (lastEventB.shortcutKey !== shortcutKey) {\r\n console.warn('Use the same shortcut key to close this event')\r\n return\r\n }\r\n // If clicking on the same frame where it started, remove the selection\r\n if (lastEventB.hour_ini === frameTime) {\r\n this.events.B.pop() // Remove the last event\r\n delete this.activeEvents.B\r\n this.eventBlocked = false // Unblock events\r\n } else {\r\n // Close the event B\r\n lastEventB.hour_end = frameTime\r\n delete this.activeEvents.B\r\n this.eventBlocked = false // Unblock events\r\n }\r\n } else {\r\n // Check if clicking on hour_end of a completed event to reopen it\r\n const completedEvent = this.events.B.find(\r\n (e) => e.hour_end === frameTime && e.shortcutKey === shortcutKey\r\n )\r\n if (completedEvent) {\r\n // Remove hour_end to reopen the event\r\n completedEvent.hour_end = null\r\n this.activeEvents.B = true\r\n if (!this.currentEventA) {\r\n this.eventBlocked = true\r\n }\r\n return\r\n }\r\n\r\n // Check if we're outside Type A and already have an unclosed B or C\r\n if (!this.currentEventA) {\r\n if (\r\n (lastEventB && !lastEventB.hour_end) ||\r\n (lastEventC && !lastEventC.hour_end)\r\n ) {\r\n console.warn(\r\n 'Only 1 B/C event allowed outside Type A. Close the current event first.'\r\n )\r\n return\r\n }\r\n // Outside Type A: block other events when B is active\r\n this.eventBlocked = true\r\n }\r\n\r\n // Auto-close any unclosed C event before starting B\r\n if (lastEventC && !lastEventC.hour_end) {\r\n lastEventC.hour_end = frameTime - 1\r\n delete this.activeEvents.C\r\n }\r\n\r\n // Start a new event B\r\n this.events.B.push({\r\n ...eventConfig,\r\n hour_ini: frameTime,\r\n hour_end: null,\r\n shortcutKey: shortcutKey,\r\n })\r\n this.activeEvents.B = true\r\n }\r\n },\r\n handleTypeC(frameTime, eventConfig, shortcutKey) {\r\n const lastEventC = this.events.C[this.events.C.length - 1]\r\n const lastEventB = this.events.B[this.events.B.length - 1]\r\n\r\n if (lastEventC && !lastEventC.hour_end) {\r\n // Check if the shortcut matches the one used to start the event\r\n if (lastEventC.shortcutKey !== shortcutKey) {\r\n console.warn('Use the same shortcut key to close this event')\r\n return\r\n }\r\n // If clicking on the same frame where it started, remove the selection\r\n if (lastEventC.hour_ini === frameTime) {\r\n this.events.C.pop() // Remove the last event\r\n delete this.activeEvents.C\r\n this.eventBlocked = false // Unblock events if it was outside Type A\r\n } else {\r\n // Close the event C\r\n lastEventC.hour_end = frameTime\r\n delete this.activeEvents.C\r\n this.eventBlocked = false // Unblock events if it was outside Type A\r\n }\r\n } else {\r\n // Check if clicking on hour_end of a completed event to reopen it\r\n const completedEvent = this.events.C.find(\r\n (e) => e.hour_end === frameTime && e.shortcutKey === shortcutKey\r\n )\r\n if (completedEvent) {\r\n // Remove hour_end to reopen the event\r\n completedEvent.hour_end = null\r\n this.activeEvents.C = true\r\n if (!this.currentEventA) {\r\n this.eventBlocked = true\r\n }\r\n return\r\n }\r\n\r\n // Check if we're outside Type A and already have an unclosed B or C\r\n if (!this.currentEventA) {\r\n if (\r\n (lastEventB && !lastEventB.hour_end) ||\r\n (lastEventC && !lastEventC.hour_end)\r\n ) {\r\n console.warn(\r\n 'Only 1 B/C event allowed outside Type A. Close the current event first.'\r\n )\r\n return\r\n }\r\n // Outside Type A: block other events when C is active\r\n this.eventBlocked = true\r\n }\r\n\r\n // Auto-close any unclosed B event before starting C\r\n if (lastEventB && !lastEventB.hour_end) {\r\n lastEventB.hour_end = frameTime - 1\r\n delete this.activeEvents.B\r\n }\r\n\r\n // Start a new event C\r\n this.events.C.push({\r\n ...eventConfig,\r\n hour_ini: frameTime,\r\n hour_end: null,\r\n shortcutKey: shortcutKey,\r\n })\r\n this.activeEvents.C = true\r\n }\r\n },\r\n clearAllFrameSelections() {\r\n // Clear all events\r\n this.events.A = []\r\n this.events.B = []\r\n this.events.C = []\r\n this.currentEventA = null\r\n this.activeEvents = {}\r\n this.eventBlocked = false\r\n this.canInsertTime = false\r\n },\r\n hasAnyEvents() {\r\n return (\r\n this.events.A.length > 0 ||\r\n this.events.B.length > 0 ||\r\n this.events.C.length > 0 ||\r\n !!(this.currentEventA && this.currentEventA.hour_ini)\r\n )\r\n },\r\n isEventStart(frameTime) {\r\n if (!frameTime) return false\r\n\r\n const allEvents = [...this.events.A, ...this.events.B, ...this.events.C]\r\n\r\n if (this.currentEventA && this.currentEventA.hour_ini) {\r\n allEvents.push(this.currentEventA)\r\n }\r\n\r\n return allEvents.some((e) => e.hour_ini === frameTime)\r\n },\r\n isEventEnd(frameTime) {\r\n if (!frameTime) return false\r\n\r\n const allEvents = [...this.events.A, ...this.events.B, ...this.events.C]\r\n\r\n return allEvents.some((e) => e.hour_end === frameTime)\r\n },\r\n isBetweenEvent(frameTime) {\r\n if (!frameTime) return false\r\n\r\n const allEvents = [...this.events.A, ...this.events.B, ...this.events.C]\r\n\r\n // Only check completed events\r\n return allEvents.some((e) => {\r\n if (!e.hour_end) return false\r\n const start = e.hour_ini\r\n const end = e.hour_end\r\n return frameTime > start && frameTime < end\r\n })\r\n },\r\n isInEventType(frameTime, type) {\r\n if (!frameTime) return false\r\n\r\n let eventsOfType = this.events[type] || []\r\n\r\n // Only include completed events (with both start and end)\r\n return eventsOfType.some((e) => {\r\n if (!e.hour_end) return false // Event not completed yet\r\n const start = e.hour_ini\r\n const end = e.hour_end\r\n return frameTime >= start && frameTime <= end\r\n })\r\n },\r\n getEventType(frameTime) {\r\n if (!frameTime) return null\r\n\r\n // Check for incomplete (currently being marked) events first\r\n if (this.currentEventA && this.currentEventA.hour_ini === frameTime) {\r\n return 'A'\r\n }\r\n\r\n // Check for incomplete B or C events\r\n const lastEventB = this.events.B[this.events.B.length - 1]\r\n if (\r\n lastEventB &&\r\n !lastEventB.hour_end &&\r\n lastEventB.hour_ini === frameTime\r\n ) {\r\n return 'B'\r\n }\r\n\r\n const lastEventC = this.events.C[this.events.C.length - 1]\r\n if (\r\n lastEventC &&\r\n !lastEventC.hour_end &&\r\n lastEventC.hour_ini === frameTime\r\n ) {\r\n return 'C'\r\n }\r\n\r\n // Check in priority order: B, C, then A for completed events\r\n // This ensures nested events show their specific color\r\n if (this.isInEventType(frameTime, 'B')) return 'B'\r\n if (this.isInEventType(frameTime, 'C')) return 'C'\r\n if (this.isInEventType(frameTime, 'A')) return 'A'\r\n\r\n return null\r\n },\r\n //* Navegação\r\n arrowRight() {\r\n if (this.checkLimitRight(false)) {\r\n if (\r\n this.activeFrame ===\r\n this.numberOfRows * this.framesPerRow * 2 - 1\r\n ) {\r\n this.next()\r\n } else {\r\n this.activeFrame++\r\n }\r\n }\r\n },\r\n arrowLeft() {\r\n if (this.checkLimitLeft(false)) {\r\n if (this.activeFrame === this.numberOfRows * this.framesPerRow) {\r\n this.prev({ stayOnLast: true })\r\n } else {\r\n this.activeFrame--\r\n }\r\n }\r\n },\r\n arrowUp() {\r\n const relativePosition =\r\n this.activeFrame - this.numberOfRows * this.framesPerRow\r\n const currentRow = Math.floor(relativePosition / this.framesPerRow)\r\n if (currentRow > 0) {\r\n this.activeFrame -= this.framesPerRow\r\n }\r\n },\r\n\r\n arrowDown() {\r\n const relativePosition =\r\n this.activeFrame - this.numberOfRows * this.framesPerRow\r\n const currentRow = Math.floor(relativePosition / this.framesPerRow)\r\n\r\n if (currentRow < this.numberOfRows - 1) {\r\n this.activeFrame += this.framesPerRow\r\n }\r\n },\r\n checkLimitRight(value) {\r\n const hours = this.endAudienceTime.match(/.{1,2}/g)\r\n const d = this.getDateParts()\r\n const high = Date.UTC(\r\n d.year,\r\n d.month,\r\n d.day,\r\n hours[0],\r\n parseInt(hours[1]) + 30,\r\n hours[2]\r\n )\r\n\r\n if (value) {\r\n return (\r\n high >\r\n (this.fInterface.getCurrentTime() +\r\n this.numberOfRows * this.framesPerRow -\r\n 1) *\r\n 1000\r\n )\r\n } else {\r\n return high > this.fInterface.getCurrentTime() * 1000\r\n }\r\n },\r\n checkLimitLeft(value) {\r\n const hours = this.startAudienceTime.match(/.{1,2}/g)\r\n const d = this.getDateParts()\r\n const low = Date.UTC(d.year, d.month, d.day, hours[0], hours[1], hours[2])\r\n\r\n if (value) {\r\n return low <= (this.fInterface.getCurrentTime() - 1) * 1000\r\n } else {\r\n return (\r\n low <\r\n (this.fInterface.getCurrentTime() +\r\n this.activeFrame -\r\n this.numberOfRows * this.framesPerRow) *\r\n 1000\r\n )\r\n }\r\n },\r\n async next(config = {}) {\r\n if (\r\n (config.ignoreTime ||\r\n Date.now() - this.lastNext > this.swapImagesDelay) &&\r\n this.checkLimitRight(true) &&\r\n !this.navigationPending\r\n ) {\r\n this.navigationPending = true\r\n this.stopVideoPlaying()\r\n\r\n this.navigationPending = await new Promise((resolve) => {\r\n this.fInterface\r\n .loadNextFrames()\r\n .then(() => {\r\n this.activeFrame = this.getIndex(1, 0, Positions.current)\r\n\r\n this.getFramesArray()\r\n this.lastNext = Date.now()\r\n resolve(false)\r\n })\r\n .catch(() => {\r\n resolve(false)\r\n })\r\n })\r\n } else {\r\n // console.error('next blocked')\r\n }\r\n },\r\n async prev(config = {}) {\r\n if (\r\n (config.ignoreTime ||\r\n Date.now() - this.lastPrev > this.swapImagesDelay) &&\r\n this.checkLimitLeft(true) &&\r\n !this.navigationPending\r\n ) {\r\n this.navigationPending = true\r\n this.stopVideoPlaying()\r\n\r\n this.navigationPending = await new Promise((resolve) => {\r\n this.fInterface\r\n .loadPrevFrames()\r\n .then(() => {\r\n if (config.stayOnLast) {\r\n this.activeFrame = this.getIndex(\r\n this.numberOfRows,\r\n this.framesPerRow - 1,\r\n Positions.current\r\n )\r\n } else {\r\n this.activeFrame = this.getIndex(1, 0, Positions.current)\r\n }\r\n\r\n this.getFramesArray()\r\n this.lastPrev = Date.now()\r\n resolve(false)\r\n })\r\n .catch(() => {\r\n resolve(false)\r\n })\r\n })\r\n } else {\r\n // console.error('prev blocked')\r\n }\r\n },\r\n async setStartTime(time) {\r\n if (time.indexOf(':') !== -1) {\r\n time = time.replace(/:/g, '')\r\n }\r\n const t = time.match(/.{1,2}/g)\r\n const d = this.getDateParts()\r\n const setTime = Date.UTC(d.year, d.month, d.day, t[0], t[1], t[2]) / 1000\r\n // this.frames = this.loadingArray\r\n\r\n await this.fInterface.changeTime(setTime)\r\n\r\n this.getFramesArray()\r\n\r\n this.activeFrame =\r\n this.getIndex(1, 0, Positions.current) + this.framesPerRow\r\n this.activeVideo = null\r\n\r\n return true\r\n },\r\n hourToTimeStamp(time) {\r\n if (time.indexOf(':') !== -1) {\r\n time = time.replace(/:/g, '')\r\n }\r\n const t = time.match(/.{1,2}/g)\r\n const d = this.getDateParts()\r\n const setTime = Date.UTC(d.year, d.month, d.day, t[0], t[1], t[2]) / 1000\r\n\r\n return setTime\r\n },\r\n changeHour(value) {\r\n if (value) {\r\n return new Promise((resolve) => {\r\n setTimeout(async () => {\r\n this.stopVideoPlaying()\r\n\r\n await this.setStartTime(value, true)\r\n resolve()\r\n }, 0)\r\n })\r\n }\r\n },\r\n changeBlockInterval(value) {\r\n this.changeHour(value.ini)\r\n let time_ini, time_end\r\n time_ini = this.hourToTimeStamp(value.ini)\r\n time_end = this.hourToTimeStamp(value.end)\r\n this.videoSliderTotalDuration = time_end - time_ini\r\n this.$refs.frames[0].changeSettings(time_ini)\r\n this.blockStartTime = time_ini\r\n },\r\n //eslint-disable-next-line\r\n async updateVideoTime(index, videoTime) {\r\n this.activeVideo = index\r\n this.videoTime = videoTime\r\n },\r\n //eslint-disable-next-line\r\n updateVideoStatus(currentTime) {\r\n if (!this.progressVideoDrag) {\r\n // ESTA FUNÇÃO PASSOU PARA DENTRO DOS COMMANDS\r\n // this.updateProgress(null, currentTime)\r\n }\r\n },\r\n async startPlaying(frame, totalTime) {\r\n const array = this.$refs.frames.filter(\r\n (fc) => fc.frame.time !== frame.time\r\n )\r\n this.stopVideoPlaying(array)\r\n\r\n this.videoTotalTime = totalTime\r\n this.videoPlaying = true\r\n },\r\n stopPlaying() {\r\n this.videoTotalTime = null\r\n this.videoPlaying = false\r\n this.paused = false\r\n },\r\n insertTime() {\r\n // Calculate the overall D event (lowest hour_ini to biggest hour_end)\r\n const allEvents = [...this.events.A, ...this.events.B, ...this.events.C]\r\n\r\n if (this.currentEventA && this.currentEventA.hour_ini) {\r\n allEvents.push(this.currentEventA)\r\n }\r\n\r\n if (allEvents.length === 0) {\r\n console.warn('No events to insert')\r\n return\r\n }\r\n\r\n const allTimes = allEvents.flatMap((e) =>\r\n [e.hour_ini, e.hour_end].filter(Boolean)\r\n )\r\n const lowestHourIni = Math.min(...allTimes)\r\n const biggestHourEnd = Math.max(...allTimes)\r\n\r\n // Get all B and C events (sorted by start time)\r\n const bcEvents = [...this.events.B, ...this.events.C]\r\n .filter((e) => e.hour_end) // Only complete events\r\n .sort((a, b) => a.hour_ini - b.hour_ini)\r\n\r\n // Generate A events to fill the gaps\r\n const generatedAEvents = []\r\n const standaloneBCEvents = []\r\n\r\n // Get all A events (both completed and current)\r\n const allAEvents = [...this.events.A]\r\n if (this.currentEventA && this.currentEventA.hour_ini) {\r\n allAEvents.push({\r\n ...this.currentEventA,\r\n hour_end: this.currentEventA.hour_end || biggestHourEnd,\r\n })\r\n }\r\n\r\n // Separate B/C events into those inside Type A and standalone\r\n for (const bcEvent of bcEvents) {\r\n const isInsideA = allAEvents.some(\r\n (aEvent) =>\r\n bcEvent.hour_ini >= aEvent.hour_ini &&\r\n bcEvent.hour_end <= aEvent.hour_end\r\n )\r\n if (!isInsideA) {\r\n standaloneBCEvents.push(bcEvent)\r\n }\r\n }\r\n\r\n // For each A event, generate gap-filling A events around B/C events\r\n for (const aEvent of allAEvents) {\r\n const aStart = aEvent.hour_ini\r\n const aEnd = aEvent.hour_end\r\n\r\n // Get B/C events that are within this A event\r\n const bcEventsInA = bcEvents.filter(\r\n (bc) => bc.hour_ini >= aStart && bc.hour_end <= aEnd\r\n )\r\n\r\n if (bcEventsInA.length === 0) {\r\n // No B or C events inside this A, keep the A event as is\r\n generatedAEvents.push({\r\n ...aEvent,\r\n hour_ini: aStart,\r\n hour_end: aEnd,\r\n })\r\n } else {\r\n let currentTime = aStart\r\n\r\n for (const bcEvent of bcEventsInA) {\r\n // Create A event before this B/C event\r\n if (currentTime < bcEvent.hour_ini) {\r\n generatedAEvents.push({\r\n ...aEvent,\r\n hour_ini: currentTime,\r\n hour_end: bcEvent.hour_ini - 1,\r\n })\r\n }\r\n currentTime = bcEvent.hour_end + 1\r\n }\r\n\r\n // Create final A event after last B/C event\r\n if (currentTime <= aEnd) {\r\n generatedAEvents.push({\r\n ...aEvent,\r\n hour_ini: currentTime,\r\n hour_end: aEnd,\r\n })\r\n }\r\n }\r\n }\r\n\r\n // Create D events for each manually marked A event\r\n // const dEvents = allAEvents.map((aEvent) => ({\r\n // type: 'D',\r\n // hour_ini: this.convertToAudienceTime(aEvent.hour_ini, ''),\r\n // hour_end: this.convertToAudienceTime(aEvent.hour_end, ''),\r\n // timestamp: aEvent.hour_ini, // For sorting\r\n // }))\r\n\r\n // Convert generated A events\r\n const aEventsFormatted = generatedAEvents.map((e) => {\r\n const formatted = {\r\n type: e.type,\r\n hour_ini: this.convertToAudienceTime(e.hour_ini, ''),\r\n hour_end: this.convertToAudienceTime(e.hour_end, ''),\r\n timestamp: e.hour_ini, // For sorting\r\n }\r\n // Add any extra properties from the original event\r\n Object.keys(e).forEach((key) => {\r\n if (!['type', 'hour_ini', 'hour_end', 'timestamp'].includes(key)) {\r\n formatted[key] = e[key]\r\n }\r\n })\r\n return formatted\r\n })\r\n\r\n // Convert B/C events inside Type A\r\n const bcEventsInsideA = bcEvents.filter((bcEvent) =>\r\n allAEvents.some(\r\n (aEvent) =>\r\n bcEvent.hour_ini >= aEvent.hour_ini &&\r\n bcEvent.hour_end <= aEvent.hour_end\r\n )\r\n )\r\n\r\n const bcEventsFormatted = bcEventsInsideA.map((e) => {\r\n const formatted = {\r\n type: e.type,\r\n hour_ini: this.convertToAudienceTime(e.hour_ini, ''),\r\n hour_end: this.convertToAudienceTime(e.hour_end, ''),\r\n timestamp: e.hour_ini, // For sorting\r\n }\r\n // Add any extra properties from the original event\r\n Object.keys(e).forEach((key) => {\r\n if (!['type', 'hour_ini', 'hour_end', 'timestamp'].includes(key)) {\r\n formatted[key] = e[key]\r\n }\r\n })\r\n return formatted\r\n })\r\n\r\n // Convert standalone B/C events (outside Type A)\r\n const standaloneBCFormatted = standaloneBCEvents.map((e) => {\r\n const formatted = {\r\n type: e.type,\r\n hour_ini: this.convertToAudienceTime(e.hour_ini, ''),\r\n hour_end: this.convertToAudienceTime(e.hour_end, ''),\r\n timestamp: e.hour_ini, // For sorting\r\n }\r\n // Add any extra properties from the original event\r\n Object.keys(e).forEach((key) => {\r\n if (!['type', 'hour_ini', 'hour_end', 'timestamp'].includes(key)) {\r\n formatted[key] = e[key]\r\n }\r\n })\r\n return formatted\r\n })\r\n\r\n // Combine all events and sort chronologically\r\n // D events come before A events when they have the same timestamp\r\n const allEventsToSend = [\r\n // ...dEvents,\r\n ...aEventsFormatted,\r\n ...bcEventsFormatted,\r\n ...standaloneBCFormatted,\r\n ].sort((a, b) => {\r\n if (a.timestamp === b.timestamp) {\r\n // If same timestamp, D comes before A, A comes before B/C\r\n const typeOrder = { D: 0, A: 1, B: 2, C: 2 }\r\n return typeOrder[a.type] - typeOrder[b.type]\r\n }\r\n return a.timestamp - b.timestamp\r\n })\r\n\r\n // Remove the timestamp field used for sorting\r\n const eventsToSend = allEventsToSend.map(\r\n ({ timestamp, ...event }) => event\r\n )\r\n\r\n this.$emit('timeToInsert', {\r\n channel: this.channel,\r\n events: eventsToSend,\r\n force: false,\r\n })\r\n\r\n if (this.jumpOnInsert) {\r\n this.changeHour(this.convertToAudienceTime(biggestHourEnd)).then(() => {\r\n this.activeFrame = this.getIndex(2, 1, Positions.current)\r\n })\r\n }\r\n\r\n if (this.removeSelectionOnInsert) {\r\n this.events = {\r\n A: [],\r\n B: [],\r\n C: [],\r\n }\r\n this.currentEventA = null\r\n this.canInsertTime = false\r\n }\r\n },\r\n insertTimeForce() {\r\n const allEvents = [...this.events.A, ...this.events.B, ...this.events.C]\r\n\r\n if (this.currentEventA && this.currentEventA.hour_ini) {\r\n allEvents.push(this.currentEventA)\r\n }\r\n\r\n if (allEvents.length === 0) {\r\n console.warn('No events to insert')\r\n return\r\n }\r\n\r\n const allTimes = allEvents.flatMap((e) =>\r\n [e.hour_ini, e.hour_end].filter(Boolean)\r\n )\r\n const lowestHourIni = Math.min(...allTimes)\r\n const biggestHourEnd = Math.max(...allTimes)\r\n\r\n const eventsToSend = [\r\n // {\r\n // type: 'D',\r\n // hour_ini: this.convertToAudienceTime(lowestHourIni, ''),\r\n // hour_end: this.convertToAudienceTime(biggestHourEnd, ''),\r\n // },\r\n ...allEvents\r\n .filter((e) => e.hour_end)\r\n .map((e) => ({\r\n type: e.type,\r\n hour_ini: this.convertToAudienceTime(e.hour_ini, ''),\r\n hour_end: this.convertToAudienceTime(e.hour_end, ''),\r\n })),\r\n ]\r\n\r\n this.$emit('timeToInsert', {\r\n channel: this.channel,\r\n events: eventsToSend,\r\n force: true,\r\n })\r\n\r\n if (this.removeSelectionOnInsert) {\r\n this.events = {\r\n A: [],\r\n B: [],\r\n C: [],\r\n }\r\n this.currentEventA = null\r\n this.canInsertTime = false\r\n }\r\n },\r\n async getChannelMedia() {\r\n // this.media = (await ChannelService.show(this.channel)).data.MEDIA\r\n },\r\n async changeServerClick() {\r\n this.changeServer = !this.changeServer\r\n this.alternativeServer = this.changeServer\r\n\r\n sessionStorage.setItem(\r\n 'server',\r\n this.changeServer ? 'alternative' : 'default'\r\n )\r\n\r\n await this.createFramesInterface(\r\n this.convertToAudienceTime(\r\n this.$refs.frames.find((frame) => frame.index === this.activeFrame)\r\n .frame.time,\r\n ''\r\n )\r\n )\r\n\r\n this.$nextTick(this.resize)\r\n },\r\n },\r\n computed: {\r\n swapImagesDelay() {\r\n return 0\r\n // return this.numberOfRows * this.framesPerRow * 15\r\n },\r\n active: {\r\n get() {\r\n return this.value\r\n },\r\n set(value) {\r\n this.$emit('input', value)\r\n },\r\n },\r\n settingsClosed() {\r\n return !Object.values(this.dialogs).find((v) => v)\r\n },\r\n startAudienceSeconds() {\r\n const t = this.startAudienceTime.match(/.{1,2}/g)\r\n return parseInt(t[0] * 3600 + t[1] * 60 + t[2])\r\n },\r\n loadingArray() {\r\n return Array.from(Array(this.numberOfRows * this.framesPerRow).keys())\r\n },\r\n serverOfFrames() {\r\n return sessionStorage.getItem('server')\r\n },\r\n },\r\n beforeDestroy() {\r\n sessionStorage.setItem('server', 'default')\r\n },\r\n watch: {\r\n async secondsPerFrame() {\r\n const activeF =\r\n this.frames[this.activeFrame - this.numberOfRows * this.framesPerRow]\r\n\r\n if (activeF) {\r\n this.changeHour(this.getAudienceTime(activeF.time, 0, 0, 0))\r\n this.fInterface.setCurrentStep(this.secondsPerFrame)\r\n await this.fInterface.loadFrames()\r\n this.getFramesArray()\r\n }\r\n },\r\n async shiftFrames() {\r\n this.createFramesInterface()\r\n },\r\n framesFormat(value) {\r\n this.setFrameSelection(value)\r\n },\r\n active() {\r\n // Reset events when switching tabs\r\n if (this.removeSelectionOnInsert) {\r\n this.events = {\r\n A: [],\r\n B: [],\r\n C: [],\r\n }\r\n this.currentEventA = null\r\n }\r\n },\r\n useCache() {\r\n this.createFramesInterface()\r\n },\r\n activeFrame(value) {\r\n if (value) {\r\n this.stopPlayingBar()\r\n }\r\n },\r\n channel() {\r\n this.updatingChannel = new Promise((resolve, reject) => {\r\n try {\r\n this.createFramesInterface()\r\n resolve(true)\r\n } catch (err) {\r\n reject(err)\r\n }\r\n })\r\n },\r\n stretchFrame() {\r\n this.$nextTick(this.resize)\r\n },\r\n visualizationInsArray: {\r\n handler: function (newValue) {\r\n this.eventsArray = newValue\r\n //console.log('visualizationInsArray changed', newValue)\r\n },\r\n deep: true,\r\n },\r\n },\r\n}\r\n</script>\r\n<style scoped>\r\n.visualization-row {\r\n display: flex;\r\n flex-wrap: wrap;\r\n flex: 1 1 auto;\r\n}\r\n\r\n.visualization-col {\r\n flex-basis: 0;\r\n flex-grow: 1;\r\n max-width: 100%;\r\n padding: 5px;\r\n}\r\n\r\n.visualization-divider {\r\n display: block;\r\n flex: 1 1 100%;\r\n height: 0px;\r\n max-height: 0px;\r\n opacity: 1;\r\n transition: inherit;\r\n border-style: solid;\r\n border-width: thin 0 0 0;\r\n border-color: rgba(0, 0, 0, 0.12);\r\n margin: 0;\r\n}\r\n\r\n.visualization-divider.vertical {\r\n align-self: stretch;\r\n border-width: 0 thin 0 0;\r\n display: inline-flex;\r\n height: inherit;\r\n margin-left: -1px;\r\n max-height: 100%;\r\n max-width: 0px;\r\n vertical-align: text-bottom;\r\n width: 0px;\r\n}\r\n\r\n.visualization-card {\r\n flex-basis: 0;\r\n flex-grow: 1;\r\n max-width: 100%;\r\n padding: 12px;\r\n width: 100%;\r\n transition-property: box-shadow, opacity, -webkit-box-shadow;\r\n overflow-wrap: break-word;\r\n /*box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14),\r\n 0 1px 5px 0 rgba(0, 0, 0, 0.12);*/\r\n}\r\n\r\n.visualization-justify-center,\r\n* >>> .visualization-justify-center {\r\n justify-content: center;\r\n}\r\n\r\n.visualization-align-center {\r\n align-items: center;\r\n}\r\n\r\n#visualization-container {\r\n max-width: 100% !important;\r\n margin: 0 auto !important;\r\n height: 100%;\r\n border-bottom: none;\r\n}\r\n#visualization-container > .card {\r\n border-radius: 0 !important;\r\n font-size: 12px;\r\n width: 100%;\r\n box-shadow: none;\r\n height: 100%;\r\n}\r\n\r\n#command-bar,\r\n#info-bar {\r\n background-color: #f5f5f5;\r\n box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14),\r\n 0 1px 5px 0 rgba(0, 0, 0, 0.12);\r\n}\r\n#command-bar button {\r\n width: 42px;\r\n height: 36px;\r\n border: none;\r\n background: none;\r\n}\r\n\r\n#command-bar button:hover {\r\n cursor: pointer;\r\n background: rgba(0, 0, 0, 0.12);\r\n}\r\n\r\n#command-bar svg {\r\n font-size: 16px;\r\n}\r\n\r\n#command-bar {\r\n padding: 0 !important;\r\n}\r\n\r\n#info-bar {\r\n padding: 4px;\r\n font-size: 14px;\r\n position: relative;\r\n}\r\n\r\n.settings-container {\r\n position: absolute;\r\n right: 14px;\r\n top: 50%;\r\n transform: translateY(-50%);\r\n}\r\n\r\n.settings-container > * {\r\n margin: 0 2px;\r\n cursor: pointer;\r\n}\r\n\r\n#info-bar svg {\r\n font-size: 16px;\r\n}\r\n\r\n#info-bar .divider {\r\n margin: 0 8px;\r\n}\r\n\r\nsvg:focus {\r\n border: none;\r\n}\r\n\r\n.visualization-card {\r\n border-left: 8px solid #eee;\r\n}\r\n\r\n.active-tab {\r\n border-left: 8px solid var(--visualization-primary) !important;\r\n border-image-slice: 1;\r\n}\r\n\r\n[id^='frame-'] {\r\n padding: 1px;\r\n display: flex;\r\n flex-flow: column;\r\n}\r\n\r\n.tooltip {\r\n display: block !important;\r\n z-index: 10000;\r\n}\r\n\r\n.tooltip .tooltip-inner {\r\n background: var(--visualization-primary);\r\n color: white;\r\n border-radius: 16px;\r\n padding: 5px 10px 4px;\r\n}\r\n\r\n.tooltip .tooltip-arrow {\r\n width: 0;\r\n height: 0;\r\n border-style: solid;\r\n position: absolute;\r\n margin: 5px;\r\n border-color: var(--visualization-primary);\r\n z-index: 1;\r\n}\r\n\r\n.tooltip[x-placement^='top'] {\r\n margin-bottom: 5px;\r\n}\r\n\r\n.tooltip[x-placement^='top'] .tooltip-arrow {\r\n border-width: 5px 5px 0 5px;\r\n border-left-color: transparent !important;\r\n border-right-color: transparent !important;\r\n border-bottom-color: transparent !important;\r\n bottom: -5px;\r\n left: calc(50% - 5px);\r\n margin-top: 0;\r\n margin-bottom: 0;\r\n}\r\n\r\n.tooltip[x-placement^='bottom'] {\r\n margin-top: 5px;\r\n}\r\n\r\n.tooltip[x-placement^='bottom'] .tooltip-arrow {\r\n border-width: 0 5px 5px 5px;\r\n border-left-color: transparent !important;\r\n border-right-color: transparent !important;\r\n border-top-color: transparent !important;\r\n top: -5px;\r\n left: calc(50% - 5px);\r\n margin-top: 0;\r\n margin-bottom: 0;\r\n}\r\n\r\n.tooltip[x-placement^='right'] {\r\n margin-left: 5px;\r\n}\r\n\r\n.tooltip[x-placement^='right'] .tooltip-arrow {\r\n border-width: 5px 5px 5px 0;\r\n border-left-color: transparent !important;\r\n border-top-color: transparent !important;\r\n border-bottom-color: transparent !important;\r\n left: -5px;\r\n top: calc(50% - 5px);\r\n margin-left: 0;\r\n margin-right: 0;\r\n}\r\n\r\n.tooltip[x-placement^='left'] {\r\n margin-right: 5px;\r\n}\r\n\r\n.tooltip[x-placement^='left'] .tooltip-arrow {\r\n border-width: 5px 0 5px 5px;\r\n border-top-color: transparent !important;\r\n border-right-color: transparent !important;\r\n border-bottom-color: transparent !important;\r\n right: -5px;\r\n top: calc(50% - 5px);\r\n margin-left: 0;\r\n margin-right: 0;\r\n}\r\n\r\n.tooltip.popover .popover-inner {\r\n background: #f9f9f9;\r\n color: black;\r\n padding: 24px;\r\n border-radius: 5px;\r\n box-shadow: 0 5px 30px rgba(black, 0.1);\r\n}\r\n\r\n.tooltip.popover .popover-arrow {\r\n border-color: #f9f9f9;\r\n}\r\n\r\n.tooltip[aria-hidden='true'] {\r\n visibility: hidden;\r\n opacity: 0;\r\n transition: opacity 0.15s, visibility 0.15s;\r\n}\r\n\r\n.tooltip[aria-hidden='false'] {\r\n visibility: visible;\r\n opacity: 1;\r\n transition: opacity 0.15s;\r\n}\r\n\r\n.custom-frametime {\r\n font-size: smaller;\r\n}\r\n\r\n/* DAR CORES AOS TEMPOS DEPENDENDO DO TIPO DE EVENTO */\r\n.event-A {\r\n color: #000; /* black for A */\r\n}\r\n.event-B {\r\n color: rgb(0, 115, 230); /* blue for B */\r\n}\r\n.event-C {\r\n color: rgb(0, 179, 0); /* green for C */\r\n}\r\n.event-gap {\r\n color: #555; /* Default color for gaps */\r\n}\r\n</style>\r\n"]}, media: undefined });
6125
6256
 
6126
6257
  };
6127
6258
  /* scoped */
6128
- const __vue_scope_id__ = "data-v-83e856c6";
6259
+ const __vue_scope_id__ = "data-v-01555ebc";
6129
6260
  /* module identifier */
6130
6261
  const __vue_module_identifier__ = undefined;
6131
6262
  /* functional template */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twab/visualization",
3
- "version": "1.14.1",
3
+ "version": "1.15.0",
4
4
  "main": "dist/visualization.js",
5
5
  "files": [
6
6
  "dist"