Compare commits

..

398 Commits

Author SHA1 Message Date
sparks-alec d377df5803 styles for tiny viewports 2023-05-24 01:19:01 -04:00
sparks-alec 1eea4bf342 handle objects better 2023-05-24 01:18:36 -04:00
sparks-alec d3bd8cc88e confirm OCA device is d&b 2023-05-24 00:23:33 -04:00
sparks-alec 76e0a6a87a add basic hostname validation for bonjour devices 2023-05-24 00:22:00 -04:00
sparks-alec 50ccd0bb5a initial d&b commit 2023-05-23 23:37:25 -04:00
Joel Wetzell d5cec8799a fix for mac notarization 2023-05-10 16:30:05 -05:00
jwetzell 39d95bb87f Merge pull request #97 from stagehacks/dependabot/npm_and_yarn/electron-builder-24.4.0
Bump electron-builder from 24.2.0 to 24.4.0
2023-05-10 15:21:55 -05:00
dependabot[bot] 3e9b433056 Bump electron-builder from 24.2.0 to 24.4.0
Bumps [electron-builder](https://github.com/electron-userland/electron-builder/tree/HEAD/packages/electron-builder) from 24.2.0 to 24.4.0.
- [Release notes](https://github.com/electron-userland/electron-builder/releases)
- [Changelog](https://github.com/electron-userland/electron-builder/blob/master/packages/electron-builder/CHANGELOG.md)
- [Commits](https://github.com/electron-userland/electron-builder/commits/v24.4.0/packages/electron-builder)

---
updated-dependencies:
- dependency-name: electron-builder
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-10 20:13:01 +00:00
jwetzell dbadb6d1ad Merge pull request #96 from stagehacks/dependabot/npm_and_yarn/eslint-8.40.0
Bump eslint from 8.38.0 to 8.40.0
2023-05-10 15:11:26 -05:00
jwetzell de7c2674bd Merge pull request #92 from stagehacks/dependabot/npm_and_yarn/prettier-2.8.8
Bump prettier from 2.8.7 to 2.8.8
2023-05-10 15:09:36 -05:00
dependabot[bot] 5f2967afae Bump eslint from 8.38.0 to 8.40.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.38.0 to 8.40.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.38.0...v8.40.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-05-08 10:03:22 +00:00
dependabot[bot] 711eaaf69e Bump prettier from 2.8.7 to 2.8.8
Bumps [prettier](https://github.com/prettier/prettier) from 2.8.7 to 2.8.8.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/2.8.7...2.8.8)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-24 10:03:33 +00:00
jwetzell b6173695ce Merge pull request #88 from stagehacks/dependabot/npm_and_yarn/minimatch-and-electron-builder-3.1.2
Bump minimatch and electron-builder
2023-04-10 11:19:13 -05:00
dependabot[bot] a91c67a323 Bump minimatch and electron-builder
Bumps [minimatch](https://github.com/isaacs/minimatch) to 3.1.2 and updates ancestor dependency [electron-builder](https://github.com/electron-userland/electron-builder/tree/HEAD/packages/electron-builder). These dependencies need to be updated together.


Updates `minimatch` from 3.0.4 to 3.1.2
- [Release notes](https://github.com/isaacs/minimatch/releases)
- [Changelog](https://github.com/isaacs/minimatch/blob/main/changelog.md)
- [Commits](https://github.com/isaacs/minimatch/compare/v3.0.4...v3.1.2)

Updates `electron-builder` from 23.6.0 to 24.2.0
- [Release notes](https://github.com/electron-userland/electron-builder/releases)
- [Changelog](https://github.com/electron-userland/electron-builder/blob/master/packages/electron-builder/CHANGELOG.md)
- [Commits](https://github.com/electron-userland/electron-builder/commits/v24.2.0/packages/electron-builder)

---
updated-dependencies:
- dependency-name: minimatch
  dependency-type: indirect
- dependency-name: electron-builder
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-10 16:10:51 +00:00
jwetzell 41384cb66c Merge pull request #87 from stagehacks/dependabot/npm_and_yarn/eslint-8.38.0
Bump eslint from 8.37.0 to 8.38.0
2023-04-10 11:10:05 -05:00
dependabot[bot] 7dc37a7fcb Bump eslint from 8.37.0 to 8.38.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.37.0 to 8.38.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.37.0...v8.38.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-10 10:03:19 +00:00
jwetzell 7769ad9059 Merge pull request #85 from stagehacks/dependabot/npm_and_yarn/eslint-8.37.0
Bump eslint from 8.36.0 to 8.37.0
2023-04-04 11:46:35 -05:00
jwetzell 159da195d3 Merge pull request #78 from stagehacks/plugin-atem
This has passed tests and has been cleaned up, send it!
2023-04-04 10:51:36 -05:00
dependabot[bot] a8d8092e65 Bump eslint from 8.36.0 to 8.37.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.36.0 to 8.37.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.36.0...v8.37.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-03 10:04:27 +00:00
jwetzell 88ad9207e2 Merge branch 'plugin-atem' of github.com:stagehacks/Cue-View into plugin-atem 2023-03-29 06:55:00 -05:00
sparks-alec 37a3fde2e0 remove old comments
remove old comments

remove comments
2023-03-29 05:38:33 -04:00
sparks-alec c2fcdfd6c9 add basic config help 2023-03-29 05:38:33 -04:00
Joel Wetzell bd2b190fac Merge branch 'main' into plugin-atem 2023-03-28 13:50:13 -05:00
Joel Wetzell 50d8363a4a regenerate package-lock using node v18.13.0 2023-03-28 13:41:04 -05:00
sparks-alec 68e7df1308 add button images 2023-03-28 10:46:39 -04:00
sparks-alec 209dc2d07d gray out disabled buttons, change element tree a bit 2023-03-28 10:46:18 -04:00
sparks-alec e742f129a1 update rate displays to use seconds:frames 2023-03-28 10:43:28 -04:00
sparks-alec 09f827a374 add images 2023-03-28 10:42:55 -04:00
sparks-alec 3e19954ae9 Update package-lock.json 2023-03-28 10:41:51 -04:00
jwetzell 90e06cab99 Merge pull request #79 from stagehacks/dependabot/npm_and_yarn/osc-2.4.4
Bump osc from 2.4.3 to 2.4.4
2023-03-28 07:18:53 -05:00
jwetzell 98a0c9c31b Merge pull request #81 from stagehacks/dependabot/npm_and_yarn/eslint-config-prettier-8.8.0
Bump eslint-config-prettier from 8.7.0 to 8.8.0
2023-03-28 07:18:41 -05:00
jwetzell 5c28a0c6b3 Merge pull request #83 from stagehacks/dependabot/npm_and_yarn/prettier-2.8.7
Bump prettier from 2.8.4 to 2.8.7
2023-03-28 07:18:31 -05:00
dependabot[bot] 095d7fb89f Bump prettier from 2.8.4 to 2.8.7
Bumps [prettier](https://github.com/prettier/prettier) from 2.8.4 to 2.8.7.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/2.8.4...2.8.7)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-27 10:05:23 +00:00
dependabot[bot] f5e8e729e8 Bump eslint-config-prettier from 8.7.0 to 8.8.0
Bumps [eslint-config-prettier](https://github.com/prettier/eslint-config-prettier) from 8.7.0 to 8.8.0.
- [Release notes](https://github.com/prettier/eslint-config-prettier/releases)
- [Changelog](https://github.com/prettier/eslint-config-prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/eslint-config-prettier/compare/v8.7.0...v8.8.0)

---
updated-dependencies:
- dependency-name: eslint-config-prettier
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-27 10:04:15 +00:00
dependabot[bot] 3b24dcb063 Bump osc from 2.4.3 to 2.4.4
Bumps [osc](https://github.com/colinbdclark/osc.js) from 2.4.3 to 2.4.4.
- [Release notes](https://github.com/colinbdclark/osc.js/releases)
- [Commits](https://github.com/colinbdclark/osc.js/compare/2.4.3...2.4.4)

---
updated-dependencies:
- dependency-name: osc
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-20 10:08:41 +00:00
jwetzell 9f8e0e260d Merge branch 'main' into plugin-atem 2023-03-17 09:47:26 -05:00
jwetzell b0790d50aa fix package-lock.json 2023-03-17 09:43:03 -05:00
jwetzell b314735604 Merge pull request #76 from stagehacks/dependabot/npm_and_yarn/eslint-8.36.0
Bump eslint from 8.35.0 to 8.36.0
2023-03-16 13:31:54 -05:00
dependabot[bot] f584e63940 Bump eslint from 8.35.0 to 8.36.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.35.0 to 8.36.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.35.0...v8.36.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-13 10:08:07 +00:00
jwetzell 84079906b5 Merge pull request #74 from stagehacks/dependabot/npm_and_yarn/http-cache-semantics-4.1.1
Bump http-cache-semantics from 4.1.0 to 4.1.1
2023-03-11 10:32:19 -06:00
dependabot[bot] 56a0ae1846 Bump http-cache-semantics from 4.1.0 to 4.1.1
Bumps [http-cache-semantics](https://github.com/kornelski/http-cache-semantics) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/kornelski/http-cache-semantics/releases)
- [Commits](https://github.com/kornelski/http-cache-semantics/compare/v4.1.0...v4.1.1)

---
updated-dependencies:
- dependency-name: http-cache-semantics
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-11 16:22:52 +00:00
jwetzell ffe9920a32 Merge pull request #73 from stagehacks/dependabot/npm_and_yarn/eslint-config-prettier-8.7.0
Bump eslint-config-prettier from 8.6.0 to 8.7.0
2023-03-11 10:22:27 -06:00
dependabot[bot] cadcf89a3a Bump eslint-config-prettier from 8.6.0 to 8.7.0
Bumps [eslint-config-prettier](https://github.com/prettier/eslint-config-prettier) from 8.6.0 to 8.7.0.
- [Release notes](https://github.com/prettier/eslint-config-prettier/releases)
- [Changelog](https://github.com/prettier/eslint-config-prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/eslint-config-prettier/compare/v8.6.0...v8.7.0)

---
updated-dependencies:
- dependency-name: eslint-config-prettier
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-03-06 10:13:09 +00:00
jwetzell a4c2c4a3b2 Merge pull request #70 from stagehacks/dependabot/npm_and_yarn/eslint-8.35.0
Bump eslint from 8.34.0 to 8.35.0
2023-02-27 09:56:29 -06:00
dependabot[bot] ec45f6fbf5 Bump eslint from 8.34.0 to 8.35.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.34.0 to 8.35.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.34.0...v8.35.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-27 09:30:08 +00:00
jwetzell 6d335da4d7 add cache to release.yaml 2023-02-20 15:56:54 -06:00
jwetzell 00ce8b32bc flashing ftb, wire up missing button and styling 2023-02-18 11:54:41 -06:00
jwetzell 96b00def56 Merge pull request #65 from stagehacks/dependabot/npm_and_yarn/prettier-2.8.4
Bump prettier from 2.8.3 to 2.8.4
2023-02-13 05:57:50 -06:00
jwetzell c051c271c4 Merge pull request #66 from stagehacks/dependabot/npm_and_yarn/eslint-8.34.0
Bump eslint from 8.33.0 to 8.34.0
2023-02-13 05:57:23 -06:00
dependabot[bot] 6437099ff5 Bump eslint from 8.33.0 to 8.34.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.33.0 to 8.34.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.33.0...v8.34.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-13 10:15:24 +00:00
dependabot[bot] 6b6665a23c Bump prettier from 2.8.3 to 2.8.4
Bumps [prettier](https://github.com/prettier/prettier) from 2.8.3 to 2.8.4.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/2.8.3...2.8.4)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-13 10:14:54 +00:00
jwetzell 5d551e9ed9 make buttons more buttony 2023-02-10 12:44:58 -06:00
jwetzell d2958966be make atem more responsive and more gradients 2023-02-09 19:40:07 -06:00
jwetzell 6e4133ae8f call atem update on interval 2023-02-08 17:03:15 -06:00
jwetzell 97a03b9dbb atem element size adjustments 2023-02-08 17:03:02 -06:00
jwetzell be657f2341 remove rogue semi-colon
Man this was driving me insane trying to find the source of this semi-colon
2023-02-08 16:46:54 -06:00
jwetzell c8d1b7b76b space out transition settings portion 2023-02-08 16:21:08 -06:00
Joel Wetzell 7709792fb5 update transition section layout with dsk 2023-02-08 16:15:45 -06:00
Joel Wetzell 5ea17755d1 update transition layout 2023-02-08 15:58:02 -06:00
jwetzell 56cc1b0cf8 transition and style wired up and prune inputs 2023-02-08 15:10:56 -06:00
Joel Wetzell f605125f74 populate next transition and transition style 2023-02-08 12:27:38 -06:00
jwetzell 1ef470e115 atem styling updates 2023-02-07 22:08:16 -06:00
jwetzell be3899a8f6 add search for sending udp direct to IPs for ATEM 2023-02-07 12:54:10 -06:00
jwetzell bd6757c99f convert to different atem library. Add FTB and DSK 2023-02-07 00:35:12 -06:00
jwetzell 5f10edfae8 Merge pull request #64 from stagehacks/dependabot/npm_and_yarn/electron-22.2.0
Bump electron from 22.1.0 to 22.2.0
2023-02-06 07:03:48 -06:00
dependabot[bot] eeab8eebde Bump electron from 22.1.0 to 22.2.0
Bumps [electron](https://github.com/electron/electron) from 22.1.0 to 22.2.0.
- [Release notes](https://github.com/electron/electron/releases)
- [Changelog](https://github.com/electron/electron/blob/main/docs/breaking-changes.md)
- [Commits](https://github.com/electron/electron/compare/v22.1.0...v22.2.0)

---
updated-dependencies:
- dependency-name: electron
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-02-06 09:18:28 +00:00
jwetzell 042777a541 Merge pull request #62 from stagehacks/dependabot/npm_and_yarn/electron-22.1.0
Bump electron from 22.0.3 to 22.1.0
2023-01-30 08:20:12 -06:00
jwetzell 8edacd2511 Merge pull request #63 from stagehacks/dependabot/npm_and_yarn/eslint-8.33.0
Bump eslint from 8.32.0 to 8.33.0
2023-01-30 08:19:59 -06:00
dependabot[bot] be141280a4 Bump eslint from 8.32.0 to 8.33.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.32.0 to 8.33.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.32.0...v8.33.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-30 09:21:21 +00:00
dependabot[bot] a21dd719f7 Bump electron from 22.0.3 to 22.1.0
Bumps [electron](https://github.com/electron/electron) from 22.0.3 to 22.1.0.
- [Release notes](https://github.com/electron/electron/releases)
- [Changelog](https://github.com/electron/electron/blob/main/docs/breaking-changes.md)
- [Commits](https://github.com/electron/electron/compare/v22.0.3...v22.1.0)

---
updated-dependencies:
- dependency-name: electron
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-30 09:21:05 +00:00
jwetzell 8d70d9d246 add atem transition bar 2023-01-26 02:02:33 -06:00
jwetzell 6f0e528164 basic atem connection/display working 2023-01-26 00:48:06 -06:00
jwetzell 65996e0e7a fix closing div tag 2023-01-26 00:18:59 -06:00
jwetzell 61e6d24e0f chain release jobs 2023-01-24 22:40:00 -06:00
jwetzell 6c2e1f204e Merge pull request #58 from stagehacks/dependabot/npm_and_yarn/eslint-plugin-import-2.27.5
Bump eslint-plugin-import from 2.27.4 to 2.27.5
2023-01-24 22:34:01 -06:00
dependabot[bot] dcc9caeb61 Bump eslint-plugin-import from 2.27.4 to 2.27.5
Bumps [eslint-plugin-import](https://github.com/import-js/eslint-plugin-import) from 2.27.4 to 2.27.5.
- [Release notes](https://github.com/import-js/eslint-plugin-import/releases)
- [Changelog](https://github.com/import-js/eslint-plugin-import/blob/main/CHANGELOG.md)
- [Commits](https://github.com/import-js/eslint-plugin-import/compare/v2.27.4...v2.27.5)

---
updated-dependencies:
- dependency-name: eslint-plugin-import
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-25 04:33:26 +00:00
jwetzell e5635b05b3 Merge pull request #59 from stagehacks/dependabot/npm_and_yarn/eslint-8.32.0
Bump eslint from 8.31.0 to 8.32.0
2023-01-24 22:32:56 -06:00
jwetzell af82caddf9 Merge pull request #60 from stagehacks/dependabot/npm_and_yarn/prettier-2.8.3
Bump prettier from 2.8.2 to 2.8.3
2023-01-24 22:32:44 -06:00
jwetzell ee2d5ab533 Merge pull request #61 from stagehacks/dependabot/npm_and_yarn/electron-22.0.3
Bump electron from 22.0.2 to 22.0.3
2023-01-24 22:32:12 -06:00
dependabot[bot] 37594515e9 Bump electron from 22.0.2 to 22.0.3
Bumps [electron](https://github.com/electron/electron) from 22.0.2 to 22.0.3.
- [Release notes](https://github.com/electron/electron/releases)
- [Changelog](https://github.com/electron/electron/blob/main/docs/breaking-changes.md)
- [Commits](https://github.com/electron/electron/compare/v22.0.2...v22.0.3)

---
updated-dependencies:
- dependency-name: electron
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-25 04:26:48 +00:00
dependabot[bot] 35c2326ec9 Bump prettier from 2.8.2 to 2.8.3
Bumps [prettier](https://github.com/prettier/prettier) from 2.8.2 to 2.8.3.
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](https://github.com/prettier/prettier/compare/2.8.2...2.8.3)

---
updated-dependencies:
- dependency-name: prettier
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-25 04:26:19 +00:00
dependabot[bot] 4b3c679842 Bump eslint from 8.31.0 to 8.32.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.31.0 to 8.32.0.
- [Release notes](https://github.com/eslint/eslint/releases)
- [Changelog](https://github.com/eslint/eslint/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint/eslint/compare/v8.31.0...v8.32.0)

---
updated-dependencies:
- dependency-name: eslint
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-25 04:26:10 +00:00
jwetzell 51b6f47c81 Create dependabot.yml 2023-01-24 22:25:25 -06:00
jwetzell e7b0e557f6 Add issue templates 2023-01-23 14:48:28 -06:00
jwetzell aa4397ba56 cleanup templating 2023-01-22 13:01:35 -06:00
jwetzell 2c02b4751d default to port 3037 for eos third party osc 2023-01-18 22:03:43 -06:00
jwetzell 12d357081d fix eos updating when new cue lists added 2023-01-18 22:02:59 -06:00
jwetzell ebc17e599a adjust styling for eos closes #56 2023-01-18 16:17:56 -06:00
jwetzell cbde42a2e7 X32 cleanup 2023-01-18 15:42:01 -06:00
jwetzell cf9797c3af fix variable reference in template 2023-01-14 10:54:08 -06:00
jwetzell bbc2ae7159 fix mistake made when replacing indexOf 2023-01-14 10:48:36 -06:00
jwetzell 0275381255 bump Cue-View version 2023-01-13 16:26:04 -06:00
jwetzell 0aa25d8e70 add link to release page to update download prompt 2023-01-13 16:25:47 -06:00
jwetzell 53cd180b72 update electron 2023-01-13 15:42:24 -06:00
jwetzell 88c7401f64 update node version 2023-01-13 15:37:25 -06:00
sparks-alec 3c842d3bee Update main.js 2023-01-09 19:27:13 -05:00
sparks-alec f31ba8ae73 remove old debug 2023-01-09 19:23:28 -05:00
jwetzell 0d235b5c61 remove logging and commented code 2023-01-09 09:11:52 -06:00
jwetzell 5ccb3c0828 remove menu from network info window 2023-01-09 08:46:17 -06:00
sparks-alec 9b951452a5 fix netinfo window styles on windows 2023-01-09 01:18:03 -05:00
sparks-alec 6ccfd92db3 make device active when its view is clicked 2023-01-09 00:58:29 -05:00
sparks-alec 7d180c889c add per-address priority information for Net3 sACN 2023-01-09 00:57:50 -05:00
sparks-alec 1d689ef6c5 Revert "clicking on a device column makes it the active the device"
This reverts commit 205f407266.
2023-01-09 00:12:52 -05:00
sparks-alec 205f407266 clicking on a device column makes it the active the device 2023-01-08 23:51:55 -05:00
sparks-alec ee3e79bef0 remove alec's terrible old code 2023-01-08 23:24:04 -05:00
sparks-alec ca37a98a7e thump faster 2023-01-08 23:23:49 -05:00
sparks-alec f73cd9dbdb Update main.js 2023-01-08 23:12:24 -05:00
sparks-alec f9e4130837 Update main.js 2023-01-08 23:11:52 -05:00
sparks-alec ababf93e2b bugfix basically everything about heartbeats 2023-01-08 23:11:07 -05:00
sparks-alec 185b360d8a update config format
addresses #50
2023-01-08 22:32:46 -05:00
sparks-alec ddcc177bec update config format 2023-01-08 22:32:17 -05:00
sparks-alec d810f3ed40 make a newly manually added device the active device 2023-01-08 22:17:14 -05:00
sparks-alec 1d7ff53c9f remove lodash! 2023-01-08 22:13:26 -05:00
sparks-alec c8a23ee2b3 close TCP sockets before deleting device
prevents a situation where active TCP traffic would prevent a device from being visually deleted
2023-01-08 22:12:31 -05:00
sparks-alec 2393e8b459 bugfix make sure cue exists before modifying wait data 2023-01-08 22:11:44 -05:00
sparks-alec 9d3980cda9 Merge branch 'main' of https://github.com/stagehacks/Cue-View 2023-01-08 21:47:17 -05:00
sparks-alec e5cba677db bugfix #46 dupe device loading 2023-01-08 21:47:15 -05:00
jwetzell 341d093645 organize assets like images and stylesheets 2023-01-06 09:42:01 -06:00
jwetzell 2a4ab214b8 performance tweaks for sacn/artnet drawing 2023-01-05 15:26:40 -06:00
jwetzell 778173b118 replace indexOf with includes where possible 2023-01-05 08:51:31 -06:00
jwetzell a7256ba894 Change to manual workflow 2023-01-05 08:06:38 -06:00
sparks-alec a54298dc88 faster heartbeat for more frequent full draws
most of the device's UI isn't drawn until a full draw comes around, should do that more frequently
2023-01-05 02:47:11 -05:00
sparks-alec c3f64cd842 clearer feedback when too many hosts
Instead of preventing search when the subnet is too large, restrict the search area to part of the subnet and provide feedback about which hosts are to be searched.
2023-01-05 02:30:47 -05:00
sparks-alec 2d9d2fea07 menu item for Network Information window 2023-01-05 02:07:17 -05:00
sparks-alec 32c655fb43 prevent title text from overlapping the pin icon 2023-01-05 01:57:29 -05:00
sparks-alec 8e0fb1b474 correct system font 2023-01-05 01:29:41 -05:00
sparks-alec e91e5a1a43 add Network Info window 2023-01-05 01:29:31 -05:00
sparks-alec 369dcd5713 Merge branch 'main' of https://github.com/stagehacks/Cue-View 2023-01-05 01:06:15 -05:00
sparks-alec cdccffbb08 Merge pull request #44 from stagehacks/plugin-shure-wireless
Add Plugin: Shure Wireless
2023-01-05 01:05:53 -05:00
sparks-alec e978f86200 prettier 2023-01-04 22:57:42 -05:00
sparks-alec d59eb18de7 prettier 2023-01-04 22:56:32 -05:00
sparks-alec c5f79f5a50 add multicast on all NICs 2023-01-04 22:44:01 -05:00
sparks-alec e0eb2cfaaf Merge branch 'main' of https://github.com/stagehacks/Cue-View 2023-01-04 21:26:03 -05:00
sparks-alec da31f9b199 UDP use proper broadcast IPs
instead of generic 255.255.255.255 to search all interfaces, search all interfaces one by one using its appropriate broadcast IP
2023-01-04 21:25:59 -05:00
sparks-alec cd2fddea6e handle incorrect passwords better 2023-01-04 21:24:06 -05:00
jwetzell ce2d493216 move channel to class 2023-01-04 08:53:11 -06:00
jwetzell 5c02bc1fc4 package-lock.json shouldn't be ignored 2023-01-04 08:42:03 -06:00
sparks-alec fe57a2af52 check .send exists before running heartbeat 2023-01-04 05:15:46 -05:00
sparks-alec 77e0d59864 tcp search bugfix
when TCP device is discovered, wait to register it till socket closes. this way we don't have two sockets going at once!
2023-01-04 05:14:42 -05:00
sparks-alec 5a44a7d751 bunch o small changes 2023-01-04 05:06:03 -05:00
sparks-alec 7a4e2c96bb prettier width 2023-01-04 04:37:21 -05:00
sparks-alec 528f1cbef5 hardier time display 2023-01-04 04:36:47 -05:00
sparks-alec 2afb03d543 prettier width 2023-01-04 04:36:30 -05:00
sparks-alec e92a486088 bugfix variable names 2023-01-04 04:36:13 -05:00
sparks-alec 59257ff345 prettier wider 2023-01-04 04:35:36 -05:00
sparks-alec 845b7c3c9f make QLab more helpful when no data
when passcode is wrong or no workspaces are open
2023-01-04 01:36:05 -05:00
sparks-alec 190e771f4b add button style 2023-01-04 01:35:04 -05:00
sparks-alec de03d71893 prettier doesn't support ejs at all apparently 2023-01-04 01:34:47 -05:00
sparks-alec 354926c178 reformat new lint 2023-01-04 01:01:06 -05:00
sparks-alec 7eeec9bc50 add rf antenna A/B indicators 2023-01-04 00:52:34 -05:00
sparks-alec b5e93cb5ce smarter updates, fewer full draws 2023-01-04 00:52:17 -05:00
sparks-alec 9f9fc8cca2 add runtime for transmitters that support it 2023-01-03 23:51:49 -05:00
sparks-alec 1b6a3ce1b4 add runtime for transmitters that support it 2023-01-03 23:51:39 -05:00
sparks-alec af0e1eb8b8 make prettier 2023-01-03 23:51:23 -05:00
sparks-alec f8aad47858 accept templates inside HTML style attributes 2023-01-03 23:05:20 -05:00
jwetzell 0ed345b810 Merge pull request #43 from stagehacks/save-slot-cleanup
clean up slot/device saving. closes #40
2022-12-31 10:43:25 -06:00
jwetzell 6187af6f69 Merge pull request #42 from stagehacks/active-arrangement-indicator
Active arrangement indicator
2022-12-31 10:40:13 -06:00
jwetzell e166d09326 check qlab reply status before processing it 2022-12-31 10:37:42 -06:00
jwetzell a0e8789ca4 cleanup variable names 2022-12-30 15:17:28 -06:00
jwetzell ec2b477c9f add pretty script 2022-12-30 15:15:26 -06:00
jwetzell 0b459216ca update prettier settings 2022-12-30 15:15:09 -06:00
jwetzell 620e0f7c04 fix UDPSocket bind for new config layout 2022-12-30 15:02:48 -06:00
jwetzell f08f3b53df cleanup variable names 2022-12-30 15:02:32 -06:00
jwetzell b3ba6d2491 clean up slot/device saving 2022-12-30 14:33:17 -06:00
jwetzell a2cd653730 update artnet config layout 2022-12-29 16:30:02 -06:00
jwetzell 8728692d31 cleanup logging 2022-12-29 16:29:44 -06:00
jwetzell d0a8910f90 clean up save-slot click handling 2022-12-29 16:08:46 -06:00
jwetzell c4a7db2fb2 fix styling for active save slot 2022-12-29 16:08:15 -06:00
sparks-alec cb51bfaf72 improve clarity 2022-12-29 12:41:45 -05:00
sparks-alec b222617745 remove unavailable vars 2022-12-29 12:41:38 -05:00
sparks-alec a89d48106b delint 2022-12-29 12:18:10 -05:00
sparks-alec 075f6370d4 style tweaks 2022-12-29 12:17:56 -05:00
sparks-alec e48c982700 make Shure prettier 2022-12-29 10:48:55 -05:00
sparks-alec 83ca5aed73 add search by TCP port 2022-12-29 09:12:15 -05:00
sparks-alec 09f396c853 initial commit adding Shure Wireless 2022-12-29 08:01:09 -05:00
sparks-alec 93be649ce2 be smarter about changing the type of a device 2022-12-28 16:32:53 -05:00
sparks-alec c92960a87f add QLab 6 2022-12-17 02:19:01 -05:00
jwetzell 52e2cf7f4d Merge pull request #29 from stagehacks/lint-n-pretty
Lint n pretty
2022-12-15 13:14:16 -06:00
jwetzell 9784248865 Merge branch 'main' into lint-n-pretty 2022-12-15 13:14:04 -06:00
jwetzell cec2c14b3c remove broken badge 2022-12-15 13:11:39 -06:00
jwetzell 8e54d955e8 add vscode settings 2022-12-13 08:57:26 -06:00
jwetzell 8df019aee4 lint src files 2022-12-12 14:58:54 -06:00
jwetzell b754eece0d pretty loose files 2022-12-12 14:55:06 -06:00
jwetzell 29bdf40ffb pretty src directory 2022-12-12 14:54:24 -06:00
jwetzell c5cc0b4bea lint sacn plugin 2022-12-12 14:41:50 -06:00
jwetzell 958a37af0b lint qlab plugin 2022-12-12 14:41:37 -06:00
jwetzell 298e385867 lint pjlink plugin 2022-12-12 14:41:29 -06:00
jwetzell 6d2bdcae74 pretty xair plugin 2022-12-12 14:37:04 -06:00
jwetzell 7ecc379473 pretty x32 plugin 2022-12-12 14:36:54 -06:00
jwetzell 38b4274e85 pretty watchout plugin 2022-12-12 14:36:40 -06:00
jwetzell 3f5cefa4f4 pretty sacn plugin 2022-12-12 14:36:26 -06:00
jwetzell 2f0e2e387b pretty qlab plugin 2022-12-12 14:36:07 -06:00
jwetzell e84fa48a82 pretty pjlink plugin 2022-12-12 14:35:56 -06:00
jwetzell 1645480a08 pretty eos plugin 2022-12-12 14:35:24 -06:00
jwetzell ab32b67564 pretty artnet plugin 2022-12-12 14:32:31 -06:00
jwetzell b450d79d8e disable select on non-inputs closes #26 2022-12-12 11:49:27 -06:00
jwetzell 040b81dcbe add meyer processor as a future device 2022-12-11 20:32:37 -06:00
jwetzell ab09029d77 only show no update dialog when started from menu 2022-12-11 17:21:30 -06:00
sparks-alec 57c59da0b6 update QLab help text 2022-12-11 05:03:20 -05:00
sparks-alec 238b27bae6 bugfix number formatting 2022-12-11 04:58:09 -05:00
sparks-alec 72601b3c97 save field values with other save data 2022-12-11 04:45:12 -05:00
sparks-alec 419bf52ebb load default port on manually added devices 2022-12-11 04:30:02 -05:00
sparks-alec 6f690bb48c select a new device after deleting active 2022-12-11 04:25:21 -05:00
sparks-alec 401b599d33 load plugin info when device is created 2022-12-11 04:24:59 -05:00
sparks-alec 09ced85677 Update cue.ejs 2022-12-11 04:10:48 -05:00
sparks-alec 368f1700ee clarify when bad passcode 2022-12-11 04:08:07 -05:00
sparks-alec 97bd436785 bugfixfix #21 2022-12-11 03:55:41 -05:00
sparks-alec eb50088c06 Merge pull request #23 from stagehacks/new-plugin-format
New plugin format
2022-12-11 03:50:09 -05:00
sparks-alec 8d6a9614e8 bugfix enabling blank passcodes 2022-12-10 23:36:13 -05:00
sparks-alec fb9fc42b59 remove 169.x.x.x interfaces from search 2022-12-10 23:35:58 -05:00
sparks-alec 6f0a31b3bc increase search timeout 2022-12-10 23:35:30 -05:00
sparks-alec 936a42ae31 bugfix with loading plugin data on non-osc devices 2022-12-10 23:34:47 -05:00
sparks-alec a1877873b2 make default passcode blank 2022-12-10 23:34:16 -05:00
sparks-alec a5fa08ccfa support PJLink passwords 2022-12-10 23:34:01 -05:00
jwetzell 997c4f8ee5 Merge pull request #22 from stagehacks/macos-signing
macOS Signing
2022-12-10 14:22:47 -06:00
jwetzell 1963e40efb cleanup notarize script 2022-12-10 14:19:00 -06:00
jwetzell f18539c703 remove macOS auto update warning 2022-12-10 14:16:14 -06:00
jwetzell 934dd89b21 add script for notarizing macOS 2022-12-10 14:15:58 -06:00
jwetzell 4068199a03 Add env variables for signing and notarizing MacOS 2022-12-10 14:13:47 -06:00
jwetzell 13bcee1181 add environment variable for code signing 2022-12-10 13:32:42 -06:00
jwetzell 7089b1d3fa fix typo in eos templat. closes #20 2022-12-10 11:12:39 -06:00
sparks-alec 3daf347062 Update README.md 2022-12-10 06:58:46 -05:00
sparks-alec 5e21544a81 new universal plugin configuration object 2022-12-10 06:53:56 -05:00
sparks-alec 93225fc00e bigger inspector corner, nice mouseover cursors 2022-12-10 06:53:17 -05:00
sparks-alec 00bffef062 make inspector corner bigger 2022-12-10 06:51:50 -05:00
sparks-alec d88f0e2350 Merge pull request #19 from stagehacks/qlab-qlab5
QLab 5 Support
2022-12-10 04:18:36 -05:00
sparks-alec e781b783f3 fix error thrown by empty/unreachable workspaces 2022-12-10 04:15:23 -05:00
jwetzell 487123538a update build status badge 2022-12-09 14:13:40 -06:00
jwetzell 2df89da36d add build status badge to README 2022-12-09 14:09:07 -06:00
jwetzell 421fe8e3f3 don't run action if just readme is updated 2022-12-09 14:07:09 -06:00
jwetzell 1690661868 add name to workflow 2022-12-09 13:57:21 -06:00
jwetzell ba2b7aadd8 Merge pull request #11 from stagehacks/github-actions
Everything checks out with Alec. Merging
2022-11-30 14:15:38 -06:00
jwetzell 4b00c4f807 change github action name and branch 2022-11-30 08:02:43 -06:00
sparks-alec 54d04a7c44 bugfix for cues with prewaits
on small viewport sizes, cues with prewaits would be missing a column compared to their non-waited pals
2022-11-30 08:39:31 -05:00
sparks-alec c38f5c34a3 v5 support 2022-11-30 08:31:55 -05:00
sparks-alec 32ce970b05 display fancy mode-specific group cue icons 2022-11-30 08:31:33 -05:00
sparks-alec ffe6f00a83 v5 icons 2022-11-30 08:30:53 -05:00
sparks-alec 4e0d531658 add new styles for Playlist group cues 2022-11-30 07:51:51 -05:00
sparks-alec 413374f633 Merge pull request #17 from stagehacks/qlab-groupfix
Qlab groupfix
2022-11-30 03:33:48 -05:00
sparks-alec 6a01863ea6 new group cue icon 2022-11-30 03:14:18 -05:00
sparks-alec 26ed323bb4 fix group cue border drawing 2022-11-30 02:52:57 -05:00
jwetzell 5d69d05b85 Merge pull request #16 from stagehacks/eos-cleanup
Minor eos cleanup
2022-11-28 20:41:29 -06:00
jwetzell ee26671997 minor eos cleanup 2022-11-28 20:39:56 -06:00
jwetzell e69ea6ca6d Merge pull request #15 from stagehacks/pjlink-cleanup
Cleanup pjlink variables and logic
2022-11-28 20:04:33 -06:00
jwetzell f0d36ce2dd Merge pull request #14 from stagehacks/watchout-cleanup
Watchout cleanup
2022-11-28 20:03:10 -06:00
jwetzell 3c19746b7a Merge pull request #13 from stagehacks/sacn-cleanup
cleanup sacn variables/logic
2022-11-28 20:01:43 -06:00
jwetzell e9adf29897 cleanup watchout template 2022-11-28 20:00:44 -06:00
jwetzell 65844d25fe cleanup pjlink variables and logic 2022-11-28 19:59:55 -06:00
jwetzell e2ac66b564 cleanup watchout variable names 2022-11-28 19:42:00 -06:00
jwetzell 4f302bcdf5 cleanup sacn variables/logic 2022-11-28 19:36:58 -06:00
jwetzell 54046cc53a Merge pull request #12 from stagehacks/artnet-cleanup
artnet cleanup
2022-11-27 18:45:47 -06:00
jwetzell 95dc21a55c artnet cleanup 2022-11-27 18:44:41 -06:00
jwetzell c74e208144 cleanup logging 2022-11-27 14:04:14 -06:00
jwetzell 1aaf5857b6 create menu item for toggling auto update checks 2022-11-27 14:03:43 -06:00
jwetzell 63c501e71f change download prompt wording 2022-11-26 19:50:35 -06:00
jwetzell 9813ea0986 bump version to test autoUpdater 2022-11-26 19:39:34 -06:00
jwetzell f524d23dc7 auto update menu item and prompts 2022-11-26 19:33:05 -06:00
jwetzell daa7b7f177 update release to test autoupdate 2022-11-26 17:49:34 -06:00
jwetzell 940b366b29 add auto update 2022-11-26 17:37:20 -06:00
jwetzell 5c4938458d update appId 2022-11-26 17:37:01 -06:00
jwetzell 6732fc5495 update electron-builder config 2022-11-26 15:45:38 -06:00
jwetzell 0852479e08 add macos job 2022-11-26 15:04:59 -06:00
jwetzell 447f4d3b1f change branch to monitor to main 2022-11-26 14:52:47 -06:00
jwetzell fd3ae81e84 try adding windows 2022-11-26 14:17:17 -06:00
jwetzell af86f5cb82 try setting gh_token environment variable 2022-11-26 14:13:52 -06:00
jwetzell 8cc4c5cc53 change branch for testing purpose 2022-11-26 14:06:05 -06:00
jwetzell fc0af55d4e start work on github action 2022-11-26 14:05:44 -06:00
jwetzell 51ef8e01ba add nvmrc for node version 2022-11-26 14:04:51 -06:00
jwetzell eabc05bef3 Merge pull request #8 from stagehacks/qlab-cleanup
Qlab cleanup
2022-11-19 10:34:49 -06:00
jwetzell e4ba6f3548 move global variables to start of file 2022-11-19 10:25:52 -06:00
jwetzell 0f31a79105 cleanup qlab template 2022-11-19 10:25:34 -06:00
jwetzell bd08b718f1 start on QLab cleanup 2022-11-18 09:37:48 -06:00
jwetzell d3f3d96e6b update dependencies 2022-11-17 19:10:28 -06:00
jwetzell 457bca2396 add electron-builder as dev dependency 2022-11-17 19:05:48 -06:00
jwetzell d79ee8dea0 add npm scripts for individual build 2022-11-17 19:03:30 -06:00
jwetzell 1959169fb9 Merge pull request #7 from jwetzell/eos-cleanup
Eos cleanup
2022-11-17 18:37:46 -06:00
Joel Wetzell 0c04207a16 cleanup variable names 2022-10-25 15:17:10 -05:00
jwetzell a32cc32194 eos cleanup 2022-05-28 10:13:07 -05:00
jwetzell b27e836227 Update packages 2022-05-28 10:12:59 -05:00
sparks-alec 6cbb1c1f76 bad version num 2022-05-07 01:28:14 -04:00
sparks-alec a4c71e9313 version bump 2022-05-07 01:27:46 -04:00
sparks-alec d40f2ac7ee correct a ton of path syntax 2022-05-07 01:27:37 -04:00
sparks-alec 2b7db257ed Merge pull request #4 from jwetzell/x32-use-osc
convert x32 to use actual osc
2022-05-06 23:28:53 -04:00
jwetzell 5ae4f71a78 convert x32 to use actual osc 2022-03-01 23:27:54 -06:00
sparks-alec 85af9a796c add Art-Net and sACN to list of supported devices 2022-02-27 19:48:01 -05:00
sparks-alec 7734406107 update to 0.9.5 2022-02-27 19:25:19 -05:00
sparks-alec 073ee5adcc make it so Cue View doesn't discover itself! 2022-02-27 19:10:51 -05:00
sparks-alec 70da3ef636 fix string format issue with elapsed time display 2022-02-27 19:10:36 -05:00
sparks-alec 017fa6b6d3 Merge pull request #3 from jwetzell/x32-cleanup
Mega Awesome X32 Improvements
2022-02-19 19:51:54 -05:00
jwetzell b1bb0da2a1 Clean up data layout and add stereo LR meters 2022-02-19 13:04:41 -06:00
jwetzell c0f179a018 add color gradient to meters 2022-02-17 20:36:35 -06:00
jwetzell d8847c87b8 X32 basic meters 2022-02-17 19:38:21 -06:00
jwetzell 5f4c544e99 clean up null characters 2022-02-17 19:37:58 -06:00
jwetzell 13d7cbd718 apply eslint 2022-02-17 09:34:13 -06:00
jwetzell 45b9c600df apply prettier 2022-02-17 09:31:46 -06:00
jwetzell 9a97a92d97 Fix up X32 displaying and Main LR info 2022-02-17 09:13:18 -06:00
jwetzell e6afd15db5 Fix infin symbol look 2022-02-17 09:11:41 -06:00
sparks-alec a3e66591ce remove debugging console.logs 2022-02-17 01:40:06 -05:00
sparks-alec 9c60b9dd8a linting 2022-02-17 01:38:23 -05:00
sparks-alec d1f31e5095 rename plugin 2022-02-17 01:38:12 -05:00
sparks-alec a8def92103 fix UI not being updated with new info 2022-02-17 01:37:47 -05:00
sparks-alec 722e8fdada add plugin info field to help users connect their devices 2022-02-14 03:39:53 -05:00
sparks-alec 617ede46e0 add search type Multicast 2022-02-14 03:39:28 -05:00
sparks-alec e17e9d3d50 add info field to templates to help users connect their devices 2022-02-14 03:39:05 -05:00
sparks-alec 1701e16dc0 more efficient UDP connections 2022-02-14 03:38:36 -05:00
sparks-alec ad099f909d remove debug log 2022-02-14 03:38:03 -05:00
sparks-alec 609292668f fix bug when processing projector data 2022-02-14 03:36:42 -05:00
sparks-alec c56d8d39b7 remove caps from package name 2022-02-14 03:28:12 -05:00
sparks-alec 454b134745 lint all the things 2022-02-14 03:25:37 -05:00
sparks-alec e5c8f242ab additional formatting for troubleshooting text 2022-02-14 03:24:19 -05:00
sparks-alec cb724b97cd eslint stuff 2022-02-14 03:23:58 -05:00
sparks-alec 60664a5f78 additional linting rules
Mostly related to how Cue View involves dealing with byte data more frequently than ~~real apps~~ apparently
2022-02-14 03:22:29 -05:00
sparks-alec cdad432e30 lil whitespace, improve legibility 2022-02-08 22:57:21 -05:00
sparks-alec d56fb07082 add support for multiple workspaces 2022-02-07 23:11:24 -05:00
sparks-alec fd2b3c3980 formatting tweaks to cue carts 2022-02-07 23:11:12 -05:00
sparks-alec b8237423d6 formatting tweaks 2022-02-07 23:10:51 -05:00
sparks-alec 46de005b30 scroll playbackPosition into view when changed 2022-02-07 02:13:13 -05:00
sparks-alec 4d0536b1a4 Update view.js 2022-02-07 02:12:52 -05:00
sparks-alec 9b7a4d9166 Update main.js 2022-02-07 02:12:41 -05:00
sparks-alec fae4775c8a fasted data updates/draws 2022-02-07 02:05:29 -05:00
sparks-alec 557c051871 change plugin filenames 2022-02-06 23:16:33 -05:00
sparks-alec 72fc8642f6 Update plugins.js 2022-02-06 23:16:11 -05:00
sparks-alec c703609829 Update view.js 2022-02-06 23:16:04 -05:00
sparks-alec e08a4f71dd Update device.js 2022-02-06 23:15:25 -05:00
sparks-alec 2878c65e16 Update cuelist.ejs 2022-02-06 02:12:37 -05:00
sparks-alec 6dcfc266a9 fix for extremely long text in headers 2022-02-06 02:09:10 -05:00
sparks-alec 3fc0457ec9 Update qlab.html 2022-02-06 02:05:59 -05:00
sparks-alec c5eef2b2c9 Update defaultPlugin.css 2022-02-06 02:05:53 -05:00
sparks-alec bc7905a509 tweak active device highlight 2022-02-06 02:05:45 -05:00
sparks-alec 02a78871bc Update .gitignore 2022-02-06 01:32:27 -05:00
sparks-alec 09d807b0c1 Update .gitignore 2022-02-06 01:32:07 -05:00
sparks-alec 04c109b014 Merge branch 'main' of https://github.com/stagehacks/Cue-View 2022-02-06 01:31:38 -05:00
sparks-alec 0014da18b3 Update .gitignore 2022-02-06 01:31:18 -05:00
sparks-alec 59dfec67bc change template to lodash 2022-02-06 01:31:12 -05:00
sparks-alec 16dfa05349 add heartbeatTimeout
for slow-to-respond Projectors
2022-02-06 01:30:05 -05:00
sparks-alec 2a539ebfc5 add Cue Carts 2022-02-06 01:29:41 -05:00
sparks-alec ba19f71d67 change templates to ejs 2022-02-06 01:29:33 -05:00
sparks-alec cbf212fbd6 remove ejs 2022-02-06 01:28:56 -05:00
sparks-alec 849bce75f6 remove missing font 2022-02-06 01:28:49 -05:00
sparks-alec 453b5d064e add Cue Carts 2022-02-06 01:28:39 -05:00
sparks-alec 7dd1d1d459 update netmask, remove ejs 2022-02-06 01:27:13 -05:00
sparks-alec 6822b0a131 Update README.md 2022-02-06 01:18:35 -05:00
sparks-alec bec3ff1ffe bye 2022-01-15 18:47:27 -05:00
sparks-alec 4c94f3981b build settings tweaks 2022-01-15 18:47:15 -05:00
sparks-alec fa143fb8fd put menu template back 2022-01-15 18:46:59 -05:00
sparks-alec d2edcf81fa style fix for dropdowns on Windows 2022-01-15 16:10:49 -05:00
sparks-alec 127dba77b6 Update package-lock.json 2022-01-15 16:10:34 -05:00
sparks-alec 98e2d0928c Update .gitignore 2022-01-15 16:10:06 -05:00
sparks-alec 8aa8021272 fix menus to work on all platforms 2022-01-15 16:10:02 -05:00
sparks-alec b31e0af2b7 switch to electron-builder
removing electron-forge
2022-01-15 16:09:34 -05:00
sparks-alec 3100ed53c9 cross-platform icons 2022-01-15 16:08:18 -05:00
sparks-alec 15e419e633 Delete out directory 2022-01-15 15:56:51 -05:00
sparks-alec 7ce39a1e37 Delete .vscode directory 2022-01-15 10:30:21 -05:00
sparks-alec c2c25125ae Update .gitignore 2022-01-15 10:30:04 -05:00
sparks-alec c1e07cde2a Merge branch 'main' of https://github.com/stagehacks/Cue-View 2022-01-15 10:29:53 -05:00
sparks-alec 9d9c1d1e6f this is not supposed to be here
oops
2022-01-15 10:29:41 -05:00
sparks-alec 91ae8da9a2 fix quotes 2022-01-15 10:23:49 -05:00
sparks-alec 22ad1c3f71 make device header background not transparent
looks like butt, honestly
2022-01-15 10:23:39 -05:00
sparks-alec 0744e9e3c4 add scrollbar-corner background color
game-changing style fix for about 6 pixels in the corner of very narrow views
2022-01-15 10:22:48 -05:00
sparks-alec 4b83430bb9 Update main.js
make pretty on Windows
2022-01-15 10:11:31 -05:00
sparks-alec dd8e15cadc new UDPsocket send syntax 2022-01-15 10:11:16 -05:00
sparks-alec 245ae98a4e fix code block 2022-01-15 10:08:56 -05:00
sparks-alec cdb9b64af0 first stab at documenting plugins 2022-01-15 10:07:52 -05:00
sparks-alec cfa47f0064 autoscroll now lives in view.js instead of in the plugin itself 2022-01-15 00:26:42 -05:00
sparks-alec 6be6b8d944 add Cue Carts, finally 2022-01-15 00:25:58 -05:00
sparks-alec 0ba876384a make device rendering faster! 2022-01-15 00:25:27 -05:00
sparks-alec f1b4d2a49a remove unnecessary debug info 2022-01-09 11:27:33 -05:00
sparks-alec f0e0c1c30e remove unnecessary debug info 2022-01-09 11:27:23 -05:00
sparks-alec b3ba244bf7 remove extra quote that broke Eos plugin
computers are the worst
2022-01-09 11:27:10 -05:00
sparks-alec 580aa96886 add ignore for .vscode 2022-01-09 11:19:08 -05:00
sparks-alec ad9a21c2e8 add require for search.js
UI search buttons don't work without it!
2022-01-09 11:13:05 -05:00
sparks-alec 27f8f79d6f Merge branch 'main' of https://github.com/stagehacks/Cue-View 2022-01-09 10:48:44 -05:00
sparks-alec e394b4cec8 add win32 build 2022-01-09 10:48:40 -05:00
sparks-alec 173f5466ff add ignore for win32 2022-01-09 10:47:46 -05:00
sparks-alec 1dbd0e1a25 add win32 makers 2022-01-09 10:46:38 -05:00
sparks-alec 711b51829c Merge pull request #1 from samschloegel/main
Add linting and formatting to improve open source contributability
2022-01-09 10:43:46 -05:00
Sam Schloegel c2fdf0e5c6 Fix missing ejs requirement 2022-01-05 21:35:44 -05:00
Sam Schloegel 0e97bd6abc Define variables before use 2022-01-05 21:27:38 -05:00
Sam Schloegel 9c16c9c220 Reduce variable scope pollution with const/let 2022-01-05 21:20:35 -05:00
Sam Schloegel 35340b86db Consolidate requires 2022-01-05 20:25:42 -05:00
Sam Schloegel 221341672a Remove unused requires 2022-01-05 20:14:44 -05:00
Sam Schloegel bf2f24f924 Use const for require statements 2022-01-03 15:20:51 -05:00
Sam Schloegel 22be9dbccf Disable import/extensions rule to match convention 2022-01-03 15:20:00 -05:00
Sam Schloegel 1bbc28f817 Fix global-require next-line exception 2022-01-03 15:18:45 -05:00
Sam Schloegel d0dc2380ea eslint fix prefer-arrow-callback errors 2022-01-03 14:48:13 -05:00
Sam Schloegel 83c46db661 eslint fix spaced-comment errors 2022-01-03 14:40:10 -05:00
Sam Schloegel aec191e178 Change string concats to template literals 2022-01-03 14:31:13 -05:00
Sam Schloegel c5691b56c9 Initial prettify 2022-01-03 10:51:25 -05:00
Sam Schloegel 551d29da60 Add bracketSameLine for html 2022-01-03 10:42:39 -05:00
Sam Schloegel a4bff71dbe Adjust to better match existing conventions 2022-01-03 00:28:11 -05:00
Sam Schloegel d642c01b8c Add airbnb to eslint 2022-01-02 23:04:14 -05:00
Sam Schloegel f9cf6ecc52 Initialize eslint, prettier, format on save 2022-01-02 22:41:15 -05:00
sparks-alec 9a6d8875e8 remove unused font 2021-11-13 17:32:27 -05:00
sparks-alec 65cb11c0cc remove debug console log 2021-11-13 17:22:39 -05:00
sparks-alec d2a52f079e remove unused font link 2021-11-13 17:21:57 -05:00
sparks-alec 949b78ec96 fix issues processing info from XAir 2021-11-13 15:59:52 -05:00
sparks-alec 9255cfcdfe Update package-lock.json 2021-11-13 15:59:09 -05:00
sparks-alec 329e55ec25 Update README.md 2021-11-03 14:18:01 -04:00
138 changed files with 12589 additions and 12992 deletions
+25
View File
@@ -0,0 +1,25 @@
module.exports = {
env: {
browser: true,
commonjs: true,
es2021: true,
node: true,
},
extends: ['airbnb-base', 'prettier'],
parserOptions: {
ecmaVersion: 13,
},
rules: {
'import/no-extraneous-dependencies': 0,
'no-unused-vars': ['error', { args: 'none' }],
'no-console': 'off',
'no-alert': 'off',
'no-plusplus': 'off',
'import/extensions': 'off',
'prefer-destructuring': 'off',
'no-use-before-define': 'off',
'space-infix-ops': 'warn',
'no-bitwise': 'off',
'no-restricted-globals': 'off',
},
};
+31
View File
@@ -0,0 +1,31 @@
---
name: Bug report
about: Let us know what isn't working
title: ''
labels: bug
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. Windows 10]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.
+17
View File
@@ -0,0 +1,17 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Additional context**
Add any other context or screenshots about the feature request here.
+6
View File
@@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
+45
View File
@@ -0,0 +1,45 @@
name: release
on:
workflow_dispatch:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
jobs:
build-linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
- run: npm ci
- run: npm run release
build-windows:
needs: build-linux
runs-on: windows-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
- run: npm ci
- run: npm run release
build-macos:
needs: build-windows
runs-on: macos-latest
env:
CSC_LINK: ${{ secrets.MACOS_CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.MACOS_CSC_KEY_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 18
cache: 'npm'
- run: npm ci
- run: npm run release
+1 -2
View File
@@ -1,4 +1,3 @@
.DS_Store
node_modules
out/Cue View-darwin-x64
/dist
+1
View File
@@ -0,0 +1 @@
v18.13.0
+4
View File
@@ -0,0 +1,4 @@
node_modules
out
dist
*.ejs
+8
View File
@@ -0,0 +1,8 @@
module.exports = {
trailingComma: 'es5',
tabWidth: 2,
semi: true,
singleQuote: true,
bracketSameLine: true,
printWidth: 120,
};
+3
View File
@@ -0,0 +1,3 @@
{
"recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
}
+9
View File
@@ -0,0 +1,9 @@
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"eslint.validate": ["javascript"],
"html.validate.styles": false // accept templates inside HTML style attributes
}
+65 -11
View File
@@ -1,35 +1,89 @@
# Cue View
A dashboard for everything in your show.
![Screen Shot 2021-10-26 at 11 57 21 PM](https://user-images.githubusercontent.com/919746/138997636-dfca293a-7c98-459d-85a3-405c9b11ce8a.png)
## Features
- Tons of supported equipment
- Auto discover devices on the network
- Live updating
- Configurable layout
## Supported Devices
- QLab 4
- QLab 4 & 5
- ETC Eos Consoles
- Watchout
- PJLink Projectors
- X32 Audio Consoles
- XAir Audio Consoles
- Art-Net Universes
- sACN Universes
#### Future Devices
- Spikemark
- ATEM Video Mixers
- Art-Net, sACN Universes
- Epson Pro series Projectors
- DiGiCo Consoles
- d&b DS100, amps
- Hog
- Meyer Galileo Processors
# Plugins
## Known Issues
- QLab Cue Carts are not supported yet.
A Cue View "plugin" is a system for communicating with a type of device, for example QLab or Watchout. It consists of a JS file that describes how to communicate with the device, an HTML template for displaying the device's data, and a CSS file to style the HTML.
### plugin.js
```js
exports.config = {
defaultName: 'Example Plugin',
connectionType: 'osc' or 'TCPsocket' or 'UDPsocket',
searchOptions: {
type: 'Bonjour',
bonjourNane: 'device'
},
searchOptions: {
type: 'UDPsocket',
searchBuffer: Buffer.from([0x00, 0x01, 0x02]),
devicePort: 1234, // port the device receives messages on
listenPort: 2345, // port Cue View should listen for responses on
validateResponse: function(msg, info){
// if this function returns true, Cue View adds the responding IP address to the list
}
},
searchOptions: {
type: 'TCPport',
searchBuffer: Buffer.from('are you there'),
testPort: 1234, // port the device receives messages on
validateResponse: function(msg, info){
// if this function returns true, Cue View adds the responding IP address to the list
}
},
defaultPort: 1234, // only available for TCPsocket and UDPsocket devices
heartbeatInterval: 5000 // how frequently, in ms, to send the heartbeat message
}
exports.ready = function (device){
// runs when Cue View identifies a new device. send all data requests here
device.send(); // method for sending a message to the device requesting more info. arguments change based on connectionType
device.send(`/this/is/osc`); // osc
device.send(`/this/is/osc`, [{ type: 'i', value: 20 }, { type: 's', value: 'foo' }]); // osc with arguments
device.send(`hello`); // UDPsocket and TCPsocket
}
exports.data = function (device, buf){
// runs when Cue View receives a message from the device.
device.draw(); // flag for QLab to update the device's view
device.send();
}
exports.heartbeat = function (device){
// runs every n milliseconds, defined by exports.heartbeatInterval
devide.send();
}
```
+60 -57
View File
@@ -1,73 +1,76 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta charset="UTF-8" />
<title>Cue View</title>
<link rel="stylesheet" href="src/index.css">
<link rel="stylesheet" href="src/assets/css/index.css" />
</head>
<body>
<div id="main">
<div class="col" id="device-list-col">
<div id="view-buttons-bar">
<button id="save-slot-1" class="save-slot" title="Window arrangement 1">1</button>
<button id="save-slot-2" class="save-slot" title="Window arrangement 2">2</button>
<button id="save-slot-3" class="save-slot" title="Window arrangement 3">3</button>
</div>
<div id="device-list">
<h3 class="init">Click the Search button to find devices on the network.</h3>
</div>
<div id="device-tools">
<select class="left button" id="add-device-button" title="Add Device..."></select>
<button class="left" id="search-button" title="Search network for devices"><img src="src/img/outline_search_white_18dp.png"></button>
<button class="left" id="refresh-device-button" title="Refresh Device" disabled><img src="src/img/outline_refresh_white_18dp.png"></button>
</div>
<div id="device-settings">
<table id="device-settings-table">
<tr>
<th>Name:</th>
<td colspan="3"><input id="device-settings-name" type="text" maxlength="30"></td>
</tr>
<tr>
<th>Type:</th>
<td colspan="3"><select id="device-settings-plugin-dropdown"></select></td>
</tr>
<tr>
<th>Addr:</th>
<td colspan="3"><input id="device-settings-ip" type="text" maxlength="30"></td>
</tr>
<tr>
<th>Port:</th>
<td><input id="device-settings-port" type="number" value="53000" maxlength="5"></td>
<th>Pin:</th>
<td><input id="device-settings-pin" type="checkbox"></td>
</table>
<h3>No Device Selected</h3>
</div>
<div class="col" id="device-list-col">
<div id="view-buttons-bar">
<button id="save-slot-1" class="save-slot" title="Window arrangement 1">1</button>
<button id="save-slot-2" class="save-slot" title="Window arrangement 2">2</button>
<button id="save-slot-3" class="save-slot" title="Window arrangement 3">3</button>
</div>
<div id="all-devices">
<div id="device-list">
<h3 class="init">Click the Search button to find devices on the network.</h3>
</div>
<div id="device-inspector">
<div id="device-tools">
<select class="left button" id="add-device-button" title="Add Device..."></select>
<button class="left" id="search-button" title="Search network for devices">
<img src="src/assets/img/outline_search_white_18dp.png" />
</button>
<button class="left" id="refresh-device-button" title="Refresh Device" disabled>
<img src="src/assets/img/outline_refresh_white_18dp.png" />
</button>
<button class="right" id="network-info-button">
<img src="src/assets/img/outline_info_white_18dp.png" />
</button>
</div>
<div id="device-settings">
<table id="device-settings-table">
<tr>
<th>Name:</th>
<td colspan="3">
<input id="device-settings-name" type="text" maxlength="30" />
</td>
</tr>
<tr>
<th>Type:</th>
<td colspan="3">
<select id="device-settings-plugin-dropdown"></select>
</td>
</tr>
<tr>
<th>Addr:</th>
<td colspan="3">
<input id="device-settings-ip" type="text" maxlength="30" />
</td>
</tr>
<tr>
<th>Port:</th>
<td>
<input id="device-settings-port" type="number" value="53000" maxlength="5" />
</td>
<th>Pin:</th>
<td><input id="device-settings-pin" type="checkbox" /></td>
</tr>
<tbody id="device-settings-fields"></tbody>
</table>
<h3>No Device Selected</h3>
</div>
</div>
</div>
<div id="all-devices"></div>
</div>
<script src="src/index.js"></script>
</body>
</html>
+372 -156
View File
@@ -1,68 +1,238 @@
const { app, BrowserWindow, Menu } = require('electron');
const { app, BrowserWindow, Menu, ipcMain, nativeTheme, dialog, shell } = require('electron');
const { autoUpdater } = require('electron-updater');
const path = require('path');
const packageInfo = require('./package.json');
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
if (require('electron-squirrel-startup')) { // eslint-disable-line global-require
app.quit();
}
const isMac = process.platform === 'darwin';
const isWin = process.platform === 'win32';
const isLinux = process.platform === 'linux';
if (require('electron-squirrel-startup')) {
app.quit();
}
var menu;
let autoUpdate = false;
let manualUpdateCheck = false;
let menuObj;
let mainWindow;
let networkInfoWindow;
const menuTemplate = [
...(isMac ? [{ role: 'appMenu' }] : []),
{
label: 'File',
submenu: [
{
label: 'Clear Saved Data',
id: 'clearSavedData',
click(menuItem, window, event) {
mainWindow.webContents.send('clearSavedData');
},
},
{
label: 'Reload App',
role: 'reload',
},
{ type: 'separator' },
...(isMac ? [{ role: 'close' }] : [{ role: 'quit' }]),
],
},
{ role: 'editMenu' },
{
label: 'View',
submenu: [
{ role: 'togglefullscreen' },
{ type: 'separator' },
{
label: 'Arrangement 1',
accelerator: 'CommandOrControl+1',
id: 'window1',
enabled: true,
click(menuItem, window, event) {
mainWindow.webContents.send('loadSlot', 1);
},
},
{
label: 'Arrangement 2',
accelerator: 'CommandOrControl+2',
id: 'window2',
enabled: true,
click(menuItem, window, event) {
mainWindow.webContents.send('loadSlot', 2);
},
},
{
label: 'Arrangement 3',
accelerator: 'CommandOrControl+3',
id: 'window3',
enabled: true,
click(menuItem, window, event) {
mainWindow.webContents.send('loadSlot', 3);
},
},
{ type: 'separator' },
{
label: 'Network Information...',
accelerator: 'CommandOrControl+I',
id: 'window4',
enabled: true,
click(menuItem, window, event) {
openNetworkInfoWindow();
},
},
{ type: 'separator' },
{ role: 'toggleDevTools' },
],
},
{
label: 'Device',
submenu: [
{
label: 'Search for Devices',
accelerator: 'CommandOrControl+F',
id: 'deviceSearch',
enabled: true,
click(menuItem, window, event) {
mainWindow.webContents.send('searchAll');
},
},
{ type: 'separator' },
{
label: 'Pinned',
type: 'checkbox',
checked: true,
accelerator: 'CommandOrControl+P',
id: 'devicePin',
enabled: false,
click(menuItem, window, event) {
mainWindow.webContents.send('setActiveDevicePinned', menuItem.checked);
},
},
{
label: 'Delete',
accelerator: 'CommandOrControl+Backspace',
id: 'deviceDelete',
enabled: false,
click(menuItem, window, event) {
mainWindow.webContents.send('deleteActive');
},
},
],
},
{
label: 'Help',
role: 'help',
submenu: [
{
label: 'About',
role: 'about',
},
{
label: 'Check for Updates',
click: () => {
// set manual update flag
manualUpdateCheck = true;
autoUpdater.checkForUpdates();
},
},
{
label: 'Enable Auto Update',
click: () => {
mainWindow.webContents.send('setAutoUpdate', !autoUpdate);
},
},
],
},
];
const windowMac = {
width: 1500,
height: 900,
titleBarStyle: 'hiddenInset',
transparent: true,
frame: false,
show: false,
vibrancy: 'window',
visualEffectState: 'followWindow',
webPreferences: {
contextIsolation: false,
nodeIntegration: true,
preload: path.join(__dirname, 'preload.js'),
},
};
const windowWin = {
width: 1500,
height: 900,
backgroundColor: '#333333',
webPreferences: {
contextIsolation: false,
nodeIntegration: true,
preload: path.join(__dirname, 'preload.js'),
},
};
const networkInfoMac = {
width: 700,
height: 350,
// transparent: true,
frame: true,
show: false,
vibrancy: 'window',
visualEffectState: 'followWindow',
webPreferences: {
contextIsolation: false,
nodeIntegration: true,
preload: path.join(__dirname, 'networkInterfaces.js'),
},
};
const networkInfoWin = {
width: 700,
height: 350,
backgroundColor: '#333333',
show: false,
webPreferences: {
contextIsolation: false,
nodeIntegration: true,
preload: path.join(__dirname, 'networkInterfaces.js'),
},
};
if (isWin) {
app.setAppUserModelId(app.name);
}
const createWindow = () => {
mainWindow = new BrowserWindow({
width: 1500,
height: 900,
titleBarStyle: 'hiddenInset',
transparent: true,
frame: false,
show: false,
//backgroundColor: "#333333",
vibrancy: "window",
visualEffectState: "followWindow",
webPreferences: {
//enableRemoteModule: true,
contextIsolation: false,
nodeIntegration: true,
preload: path.join(__dirname, 'preload.js')
}
});
nativeTheme.themeSource = 'dark';
if (isMac) {
mainWindow = new BrowserWindow(windowMac);
} else {
mainWindow = new BrowserWindow(windowWin);
}
if (isLinux) {
mainWindow.setIcon(path.join(__dirname, 'src', 'assets', 'img', 'icon.png'));
}
mainWindow.loadFile('index.html');
mainWindow.on('ready-to-show', () => {
mainWindow.show();
mainWindow.show();
});
if(process.defaultApp){
if (process.defaultApp) {
mainWindow.webContents.openDevTools();
}
menu = Menu.buildFromTemplate(menuTemplate);
Menu.setApplicationMenu(menu);
menuObj = Menu.buildFromTemplate(menuTemplate);
Menu.setApplicationMenu(menuObj);
};
app.whenReady().then(() => {
createWindow()
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
createWindow();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
@@ -70,126 +240,172 @@ app.on('window-all-closed', () => {
}
});
const { ipcMain } = require('electron')
ipcMain.on('enableDeviceDropdown', (event, arg) => {
menu.getMenuItemById('devicePin').enabled = true;
menu.getMenuItemById('deviceDelete').enabled = true;
menuObj.getMenuItemById('devicePin').enabled = true;
menuObj.getMenuItemById('deviceDelete').enabled = true;
});
ipcMain.on('disableDeviceDropdown', (event, arg) => {
menu.getMenuItemById('devicePin').enabled = false;
menu.getMenuItemById('deviceDelete').enabled = false;
menuObj.getMenuItemById('devicePin').enabled = false;
menuObj.getMenuItemById('deviceDelete').enabled = false;
});
ipcMain.on('enableSearchAll', (event, arg) => {
menu.getMenuItemById('deviceSearch').enabled = true;
menuObj.getMenuItemById('deviceSearch').enabled = true;
});
ipcMain.on('disableSearchAll', (event, arg) => {
menu.getMenuItemById('deviceSearch').enabled = false;
menuObj.getMenuItemById('deviceSearch').enabled = false;
});
ipcMain.on('setDevicePin', function(event, arg){
menu.getMenuItemById('devicePin').checked = arg;
})
ipcMain.on('setDevicePin', (event, arg) => {
menuObj.getMenuItemById('devicePin').checked = arg;
});
const isMac = process.platform === 'darwin'
ipcMain.on('openNetworkInfoWindow', (event, arg) => {
openNetworkInfoWindow();
});
const menuTemplate = [
{ role: 'appMenu' },
{
label: "File",
submenu: [
{
label: "Clear Saved Data",
id: "resetViews",
click: function(menuItem, window, event){
mainWindow.webContents.send("resetViews");
}
},
{
label: "Reload App",
role: "reload"
},
{
type: "separator"
},
isMac ? { role: 'close' } : { role: 'quit' },
]
},
{ role: 'editMenu' },
{
label: "View",
submenu: [
{ role: 'togglefullscreen' },
{ type: 'separator' },
{
label: "Arrangement 1",
accelerator: "CommandOrControl+1",
id: "window1",
enabled: true,
click: function(menuItem, window, event){
mainWindow.webContents.send("doSlots1");
}
},
{
label: "Arrangement 2",
accelerator: "CommandOrControl+2",
id: "window2",
enabled: true,
click: function(menuItem, window, event){
mainWindow.webContents.send("doSlots2");
}
},
{
label: "Arrangement 3",
accelerator: "CommandOrControl+3",
id: "window3",
enabled: true,
click: function(menuItem, window, event){
mainWindow.webContents.send("doSlots3");
}
},
{ type: 'separator' },
{ role: 'toggleDevTools' }
]
},
{
label: "Device",
submenu: [
{
label: "Search for Devices",
accelerator: "CommandOrControl+F",
id: "deviceSearch",
enabled: true,
click: function(menuItem, window, event){
mainWindow.webContents.send("doSearch");
}
},
{
type: "separator"
},
{
label: "Pinned",
type: "checkbox",
checked: true,
accelerator: "CommandOrControl+P",
id: "devicePin",
enabled: false,
click: function(menuItem, window, event){
mainWindow.webContents.send("setActiveDevicePinned", menuItem.checked);
}
},
{
label: "Delete",
accelerator: "CommandOrControl+Backspace",
id: "deviceDelete",
enabled: false,
click: function(menuItem, window, event){
mainWindow.webContents.send("doDelete");
}
}
]
function openNetworkInfoWindow() {
if (!networkInfoWindow || (networkInfoWindow && networkInfoWindow.isDestroyed())) {
if (isMac) {
networkInfoWindow = new BrowserWindow(networkInfoMac);
} else {
networkInfoWindow = new BrowserWindow(networkInfoWin);
networkInfoWindow.removeMenu();
}
networkInfoWindow.loadFile('networkInterfaces.html');
}
]
networkInfoWindow.show();
}
// ONLY Autoupdate logic below
ipcMain.on('checkForUpdates', (event, arg) => {
autoUpdater.checkForUpdates();
});
ipcMain.on('setAutoUpdate', (event, _autoUpdate) => {
autoUpdate = _autoUpdate;
// update menu item for enabling/disabling auto update
menuTemplate[menuTemplate.length - 1].submenu[2].label = autoUpdate ? 'Disable Auto Update' : 'Enable Auto Update';
menuObj = Menu.buildFromTemplate(menuTemplate);
Menu.setApplicationMenu(menuObj);
});
// this can be set to true to bypass the download update dialog and skip straight to install prompt
autoUpdater.autoDownload = false;
autoUpdater.on('update-available', (updateInfo) => {
// skip prompting to download if autoDownload is set
if (autoUpdater.autoDownload) {
return;
}
const msg = `Version v${updateInfo.version} is available. Would you like to download it?`;
const title = 'Update Available';
let dialogOpts = {};
if (isMac) {
dialogOpts = {
type: 'info',
buttons: ['Download', 'Cancel', 'View Release Notes'],
title: 'Update Available',
message: title,
detail: msg,
};
} else {
dialogOpts = {
type: 'info',
buttons: ['Download', 'Cancel', 'View Release Notes'],
title: 'Update Available',
message: msg,
};
}
dialog.showMessageBox(mainWindow, dialogOpts).then((returnValue) => {
if (returnValue.response === 0) {
// download was clicked
autoUpdater.downloadUpdate();
} else if (returnValue.response === 2) {
// view release notes clicked
shell.openExternal(`${packageInfo.repository}/releases/tag/v${updateInfo.version}`);
}
});
});
autoUpdater.on('update-downloaded', (event) => {
const title = 'Update Downloaded';
const msg = `Version v${event.version} has been downloaded. Would you like to install this update now?`;
let dialogOpts = {};
if (isMac) {
dialogOpts = {
type: 'info',
buttons: ['Install', 'Later'],
title: 'Update Downloaded',
message: title,
detail: msg,
};
} else {
dialogOpts = {
type: 'info',
buttons: ['Install', 'Later'],
title: 'Update Downloaded',
message: msg,
};
}
dialog.showMessageBox(mainWindow, dialogOpts).then((returnValue) => {
if (returnValue.response === 0) autoUpdater.quitAndInstall();
});
});
autoUpdater.on('update-not-available', (updateInfo) => {
if (manualUpdateCheck) {
let dialogOpts = {};
const msg = `There is no update available at this time. Latest version is v${updateInfo.version}`;
if (isMac) {
dialogOpts = {
type: 'info',
buttons: ['Ok'],
title: 'No Update Available',
message: 'No Update Available',
detail: msg,
};
} else {
dialogOpts = {
type: 'info',
buttons: ['Ok'],
title: 'No Update Available',
message: msg,
};
}
dialog.showMessageBox(mainWindow, dialogOpts);
// revert manual update flag
manualUpdateCheck = false;
}
});
autoUpdater.on('error', (error, message) => {
let dialogOpts = {};
if (isMac) {
dialogOpts = {
type: 'error',
buttons: ['Ok'],
title: 'Update Error',
message: 'Update Error',
detail: error.message,
};
} else {
dialogOpts = {
type: 'error',
buttons: ['Ok'],
title: 'Update Error',
message: error.message,
};
}
dialog.showMessageBox(mainWindow, dialogOpts);
});
+92
View File
@@ -0,0 +1,92 @@
<html>
<head>
<title>Network Information</title>
<style type="text/css">
body {
margin: 0px;
color: white;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont;
user-select: none;
background-color: transparent;
}
table {
width: 100%;
border-collapse: collapse;
}
th,
td {
padding: 12px;
padding-left: 10px;
}
th {
text-align: left;
background-color: rgba(100, 100, 100, 0.3);
border-top: #868686 1px solid;
border-bottom: #444 1px solid;
border-right: #868686 1px solid;
font-weight: normal;
height: 36px;
}
td {
border-bottom: dimgray 1px solid;
}
p {
color: #a0a0a0;
margin: 17px 13px;
font-size: 14px;
}
button {
box-sizing: content-box;
height: 25px;
border: none;
outline: none;
padding: 1px 10px;
position: absolute;
right: 10px;
top: 10px;
color: white;
background: rgba(255, 255, 255, 0.15);
border-radius: 4px;
font-size: 14px;
}
button:hover {
background-color: rgba(255, 255, 255, 0.2);
}
button:focus {
outline: none;
}
span {
border-radius: 4px;
padding: 2px 4px;
}
.if-en {
background-color: #1565c0;
}
.if-ut {
background-color: #bf360c;
}
.red {
color: #ca6f1e;
}
.green {
color: #28b463;
}
</style>
</head>
</html>
<p>Attached Network Interfaces:</p>
<button onclick="location.reload()">Refresh</button>
<table>
<tr>
<th>ID</th>
<th>IP Address</th>
<th>Subnet Mask</th>
<th>Searchable Address Ranges</th>
</tr>
<tbody id="network-interfaces"></tbody>
</table>
<script>
window.init();
</script>
+31
View File
@@ -0,0 +1,31 @@
const SEARCH = require('./src/search.js');
window.init = function init() {
const networkInterfaces = SEARCH.getNetworkInterfaces();
let html = '';
for (let i = 0; i < Object.keys(networkInterfaces).length; i++) {
const interfaceID = Object.keys(networkInterfaces)[i];
const interfaceObj = networkInterfaces[interfaceID];
html += `
<tr>
<td><span class="if-${interfaceID.substring(0, 2)}">${interfaceID}</span></td>
<td>${interfaceObj[0].address}</td>
<td>${interfaceObj[0].netmask}</td>
`;
if (interfaceObj[0].searchTruncated) {
html += `<td class='red'>`;
} else {
html += `<td class='green'>`;
}
html += `
${interfaceObj[0].firstSearchAddress} - ${interfaceObj[0].lastSearchAddress}</td>
</tr>
`;
document.getElementById('network-interfaces').innerHTML = html;
}
};
+44
View File
@@ -0,0 +1,44 @@
const notarize = require('@electron/notarize');
const fs = require('fs');
const path = require('path');
module.exports = async function (params) {
if (process.platform !== 'darwin') {
console.log('Only need to notarize MacOS, skipping');
return;
}
const appId = 'com.stagehacks.cueview';
const appPath = path.join(
params.appOutDir,
`${params.packager.appInfo.productFilename}.app`
);
if (!fs.existsSync(appPath)) {
console.error('App file not found skipping notarization.');
return;
}
if (
process.env.APPLE_ID === undefined ||
process.env.APPLE_ID_PASSWORD === undefined
) {
console.log('Apple ID and Password must be set in order to notarize.');
return;
}
try {
console.log(`Notarizing ${appId} found at ${appPath}`);
await notarize.notarize({
appBundleId: appId,
appPath,
appleId: process.env.APPLE_ID,
appleIdPassword: process.env.APPLE_ID_PASSWORD,
});
console.log(`Done notarizing ${appId}`);
} catch (error) {
console.log('There was an error notarizing.');
console.error(error);
}
};
+5649 -9510
View File
File diff suppressed because it is too large Load Diff
+59 -46
View File
@@ -1,62 +1,75 @@
{
"name": "Cue View",
"version": "0.9.2",
"name": "cue-view",
"productName": "Cue View",
"version": "0.9.9-pre",
"description": "A dashboard for everything in your show",
"main": "main.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "electron-forge start",
"package": "electron-forge package",
"make": "electron-forge make"
"pretty": "prettier --write **/*",
"start": "electron .",
"build": "electron-builder -m -w -l",
"build:mac": "electron-builder -m",
"build:windows": "electron-builder -w",
"build:linux": "electron-builder -l",
"build:arm": "electron-builder -l --arm64",
"release": "electron-builder"
},
"author": "Stage Hacks LLC",
"author": "alec@stagehacks.com",
"license": "CC BY-SA 4.0",
"homepage": "https://github.com/stagehacks/Cue-View",
"repository": "https://github.com/stagehacks/Cue-View",
"devDependencies": {
"@electron-forge/cli": "^6.0.0-beta.61",
"@electron-forge/maker-deb": "^6.0.0-beta.61",
"@electron-forge/maker-rpm": "^6.0.0-beta.61",
"@electron-forge/maker-squirrel": "^6.0.0-beta.61",
"@electron-forge/maker-zip": "^6.0.0-beta.61",
"electron": "^15.2.0"
"@electron/notarize": "^1.2.3",
"electron": "^22.2.0",
"electron-builder": "^24.4.0",
"eslint": "^8.40.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-import": "^2.27.5",
"prettier": "^2.8.8"
},
"dependencies": {
"atem-connection": "^3.1.3",
"bonjour": "^3.5.0",
"ejs": "^3.1.5",
"electron-reload": "^1.5.0",
"electron-squirrel-startup": "^1.0.0",
"lodash": "^4.17.20",
"electron-updater": "^5.3.0",
"lodash": "^4.17.21",
"md5": "^2.3.0",
"netmask": "^1.0.6",
"osc": "^2.4.1",
"uuid": "^8.3.2"
"netmask": "^2.0.2",
"osc": "^2.4.4",
"uuid": "^9.0.0"
},
"config": {
"forge": {
"packagerConfig": {
"icon": "./src/img/icon.icns"
},
"makers": [
{
"name": "@electron-forge/maker-squirrel",
"config": {
"name": "Cue View",
"iconUrl": "./src/img/win-icon.ico"
}
},
{
"name": "@electron-forge/maker-zip",
"platforms": [
"darwin"
]
},
{
"name": "@electron-forge/maker-deb",
"config": {}
},
{
"name": "@electron-forge/maker-rpm",
"config": {}
}
"build": {
"appId": "com.stagehacks.cueview",
"icon": "src/assets/img/",
"artifactName": "${name}.${os}.v${version}.${ext}",
"mac": {
"category": "Utilities",
"icon": "src/assets/img/icon.icns",
"hardenedRuntime": true,
"electronLanguages": [
"en"
],
"publish": [
"github"
]
},
"win": {
"target": "NSIS",
"icon": "src/assets/img/icon.ico",
"publish": [
"github"
]
},
"nsis": {
"oneClick": false
},
"linux": {
"target": "AppImage",
"maintainer": "alec@stagehacks.com",
"category": "Utility",
"publish": [
"github"
]
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.
+2
View File
@@ -0,0 +1,2 @@
<p><em>Art-Net</em> requires no configuration.</p>
<p>Art-Net™ Designed by and Copyright Artistic Licence Holdings Ltd</p>
+95
View File
@@ -0,0 +1,95 @@
exports.config = {
defaultName: 'Art-Net',
connectionType: 'UDPsocket',
defaultPort: 6454,
mayChangePort: false,
heartbeatInterval: 5000,
heartbeatTimeout: 15000,
searchOptions: {
type: 'UDPsocket',
searchBuffer: Buffer.from([0x00]),
devicePort: 6454,
listenPort: 6454,
validateResponse(msg, info, devices) {
return msg.toString('utf8', 0, 7) === 'Art-Net';
},
},
};
exports.ready = function ready(_device) {
const device = _device;
device.data.universes = {};
device.data.orderedUniverses = [];
};
exports.data = function data(_device, buf) {
if (buf.length < 18) {
return;
}
const universeIndex = buf.readUInt8(14);
const device = _device;
let universe = device.data.universes[universeIndex];
if (!universe) {
device.data.universes[universeIndex] = {};
universe = device.data.universes[universeIndex];
}
universe.sequence = buf.readUInt8(12);
universe.subnet = buf.readUInt8(15);
universe.opCode = buf.readUInt8(9);
universe.version = buf.readUInt16BE(10);
universe.slots = buf.slice(18);
device.data.ip = device.addresses[0];
if (!device.data.orderedUniverses.includes(universeIndex)) {
device.data.orderedUniverses.push(universeIndex);
device.data.orderedUniverses.sort();
universe.slotElems = [];
universe.slotElemsSet = false;
device.draw();
device.update('elementCache');
}
device.update('universeData', {
universeIndex,
universe,
});
};
exports.heartbeat = function heartbeat(device) {};
let lastUpdate = Date.now();
exports.update = function update(_device, _document, updateType, updateData) {
const device = _device;
const data = updateData;
const document = _document;
if (updateType === 'universeData' && data.universe) {
if (Date.now() - lastUpdate > 1000) {
lastUpdate = Date.now();
device.update('elementCache');
}
const $elem = document.getElementById(`universe-${data.universeIndex}`);
if ($elem && data.universe.slotElemsSet) {
for (let i = 0; i < 512; i++) {
data.universe.slotElems[i].textContent = data.universe.slots[i];
}
document.getElementById(`universe-${data.universeIndex}-sequence`).textContent = data.universe.sequence;
} else {
device.draw();
device.update('elementCache');
}
} else if (updateType === 'elementCache') {
device.data.orderedUniverses.forEach((universeIndex) => {
for (let i = 0; i < 512; i++) {
device.data.universes[universeIndex].slotElems[i] = document.getElementById(`${universeIndex}-${i}`);
}
device.data.universes[universeIndex].slotElemsSet = true;
});
}
};
+29
View File
@@ -0,0 +1,29 @@
table {
margin-bottom: 50px;
background-color: #333;
}
td,
th {
width: 35px;
}
td {
background-color: black;
text-align: center;
border-radius: 3px;
color: white;
font-size: 13px;
}
th {
background-color: #222;
color: #ccc;
font-size: 11px;
}
td.data {
padding: 7px;
font-size: 12px;
text-align: left;
}
td.data em {
font-size: 14px;
color: #008bd2;
}
+59
View File
@@ -0,0 +1,59 @@
<header>
<h1><%= data.source %> Art-Net</h1>
</header>
<div id="universes">
<% data.orderedUniverses.forEach(universeIndex => {
let universe = data.universes[universeIndex];
%>
<table id="universe-<%= universeIndex %>">
<tr>
<td class="data" colspan="2">
Universe
<br><em><%= universeIndex %></em>
</td>
<td class="data" colspan="2">
Sub-Net
<br><em><%= universe.subnet %></em>
</td>
<td class="data" colspan="2">
Sequence
<br><em id="universe-<%= universeIndex %>-sequence"><%= universe.sequence %></em>
</td>
<td class="data" colspan="2">
OpCode
<br><em><%= universe.opCode %></em>
</td>
<td class="data" colspan="2">
Version
<br><em><%= universe.version %></em>
</td>
<td class="data" colspan="4">
IP
<br><em><%= data.ip %></em>
</td>
<td class="data" colspan="3"></td>
</tr>
<tr>
<th></th>
<% for(col=1; col<=16; col++){ %>
<th><%= col %></th>
<% } %>
</tr>
<% let slot = 0; %>
<% for(row=0; row<32; row++){ %>
<tr>
<th><%= row*16+1 %></th>
<% for(col=0; col<16; col++){ %>
<% slot++%>
<td id="<%= universeIndex %>-<%= row*16+col %>" title="<%= universeIndex %>/<%= slot %>"><%= universe.slots[slot-1] %></td>
<% } %>
</tr>
<% } %>
</table>
<% }); %>
</div>
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

+11
View File
@@ -0,0 +1,11 @@
<h3>ATEM Configuration</h3>
<ul>
<li>Set the IP Address, Subnet Mask, and Gateway of the ATEM using the <em>ATEM Setup utility</em></li>
<li>The ATEM must be connected to a computer using a USB cable to change its Network settings</li>
</ul>
<h3>ATEM Software Download</h3>
<button href="https://www.blackmagicdesign.com/support/family/atem-live-production-switchers">
Blackmagic Support
</button>
(Look for <em>ATEM Switchers Update</em>)
+240
View File
@@ -0,0 +1,240 @@
const { TransitionStyle, TransitionSelection } = require('atem-connection/dist/enums');
let timerFrameRate;
exports.config = {
defaultName: 'ATEM',
connectionType: 'atem',
defaultPort: 9910,
mayChangePort: false,
searchOptions: {
type: 'UDPScan',
searchBuffer: Buffer.from([
0x10, 0x14, 0x53, 0xab, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00,
]),
listenPort: 0,
devicePort: 9910,
validateResponse(msg, info) {
// This is a tad bit hacky but works from what I can see.
return Buffer.compare(msg.slice(0, 4), Buffer.from([16, 20, 83, 171])) === 0;
},
},
};
exports.ready = function ready(_device) {
console.log('atem ready');
const device = _device;
device.data = device.connection.state;
device.draw();
// TODO: this is a little hacky but it works? gotta be a way to hook into device.draw() to trigger update
setInterval(() => {
device.update('inputs', device.data);
device.update('fadeToBlack', device.data);
device.update(`transitionPosition`, device.data);
device.update('downstreamKeyers', device.data);
device.update('upstreamKeyers', device.data);
device.update('transitionProperties', device.data);
device.update('deviceInfo', device.data);
}, 1000);
};
exports.update = function update(device, _document, updateType, data) {
const document = _document;
if (updateType.includes('transitionPosition')) {
for (let i = 0; i < data.video.mixEffects.length; i++) {
const mixEffect = data.video.mixEffects[i];
const tbarId = `me-${i}-tbar-div`;
const tbarHandleId = `me-${i}-tbar-handle-div`;
if (document.getElementById(tbarId)) {
document.getElementById(tbarId).style.height = `${mixEffect.transitionPosition.handlePosition / 100}%`;
}
if (document.getElementById(tbarHandleId)) {
document.getElementById(tbarHandleId).style.bottom = `${mixEffect.transitionPosition.handlePosition / 100}%`;
}
document.getElementById(`me-${i}-transition-rate`).textContent = framesToTime(
mixEffect.transitionPosition.remainingFrames
);
if (mixEffect.transitionPosition.inTransition) {
document.getElementById(`me-${i}-auto`).classList.add('atem-red');
} else {
document.getElementById(`me-${i}-auto`).classList.remove('atem-red');
}
}
} else if (updateType.includes('downstreamKeyers')) {
for (let i = 0; i < data.video.downstreamKeyers.length; i++) {
const dsk = data.video.downstreamKeyers[i];
if (dsk.isAuto) {
document.getElementById(`dsk-${i}-auto`).classList.add('atem-red');
} else {
document.getElementById(`dsk-${i}-auto`).classList.remove('atem-red');
}
if (dsk.onAir) {
document.getElementById(`dsk-${i}-onair`).classList.add('atem-red');
} else {
document.getElementById(`dsk-${i}-onair`).classList.remove('atem-red');
}
if (dsk.properties.tie) {
document.getElementById(`dsk-${i}-tie`).classList.add('atem-yellow');
} else {
document.getElementById(`dsk-${i}-tie`).classList.remove('atem-yellow');
}
document.getElementById(`dsk-${i}-rate`).textContent = framesToTime(dsk.remainingFrames);
}
} else if (updateType.includes('fadeToBlack')) {
for (let i = 0; i < data.video.mixEffects.length; i++) {
const fadeToBlack = data.video.mixEffects[i].fadeToBlack;
const ftbRateId = `me-${i}-ftb-rate`;
const ftbId = `me-${i}-ftb`;
document.getElementById(ftbRateId).textContent = framesToTime(fadeToBlack.remainingFrames);
if (fadeToBlack.isFullyBlack) {
if (document.getElementById(ftbId).classList.contains('atem-red')) {
document.getElementById(ftbId).classList.remove('atem-red');
} else {
document.getElementById(ftbId).classList.add('atem-red');
}
} else if (fadeToBlack.inTransition) {
document.getElementById(ftbId).classList.add('atem-red');
} else {
document.getElementById(ftbId).classList.remove('atem-red');
}
}
} else if (
updateType.includes('programInput') ||
updateType.includes('previewInput') ||
updateType.includes('inputs')
) {
for (let i = 0; i < data.video.mixEffects.length; i++) {
const mixEffect = data.video.mixEffects[0];
Object.keys(device.data.inputs)
.map((key) => Number(key))
.forEach((inputId) => {
if (mixEffect.programInput === inputId) {
document.getElementById(`me-${i}-program-input-${inputId}`).classList.add('atem-red');
document.getElementById(`me-${i}-input-${inputId}`).classList.add('atem-red');
} else {
document.getElementById(`me-${i}-program-input-${inputId}`).classList.remove('atem-red');
document.getElementById(`me-${i}-input-${inputId}`).classList.remove('atem-red');
}
if (mixEffect.previewInput === inputId) {
document.getElementById(`me-${i}-preview-input-${inputId}`).classList.add('atem-green');
document.getElementById(`me-${i}-input-${inputId}`).classList.add('atem-green');
} else {
document.getElementById(`me-${i}-preview-input-${inputId}`).classList.remove('atem-green');
document.getElementById(`me-${i}-input-${inputId}`).classList.remove('atem-green');
}
});
}
} else if (updateType.includes('transitionProperties')) {
for (let i = 0; i < data.video.mixEffects.length; i++) {
const transitionProperties = data.video.mixEffects[i].transitionProperties;
Object.keys(TransitionStyle)
.filter((key) => !isNaN(Number(key)))
.map((key) => Number(key))
.forEach((style) => {
if (style === transitionProperties.style) {
document.getElementById(`me-${i}-transition-style-${style}`).classList.add('atem-yellow');
} else {
document.getElementById(`me-${i}-transition-style-${style}`).classList.remove('atem-yellow');
}
if (style === transitionProperties.nextStyle) {
document.getElementById(`me-${i}-transition-style-${style}`).classList.add('atem-yellow');
} else {
document.getElementById(`me-${i}-transition-style-${style}`).classList.remove('atem-yellow');
}
});
Object.keys(TransitionSelection)
.filter((key) => !isNaN(Number(key)))
.map((key) => Number(key))
.forEach((selection) => {
if (transitionProperties.selection.includes(selection)) {
document.getElementById(`me-${i}-transition-selection-${selection}`).classList.add('atem-yellow');
} else {
document.getElementById(`me-${i}-transition-selection-${selection}`).classList.remove('atem-yellow');
}
if (transitionProperties.nextSelection.includes(selection)) {
document.getElementById(`me-${i}-transition-selection-${selection}`).classList.add('atem-yellow');
} else {
document.getElementById(`me-${i}-transition-selection-${selection}`).classList.remove('atem-yellow');
}
});
}
} else if (updateType.includes('upstreamKeyers')) {
for (let i = 0; i < data.video.mixEffects.length; i++) {
const upstreamKeyers = data.video.mixEffects[i].upstreamKeyers;
upstreamKeyers.forEach((upstreamKeyer) => {
if (upstreamKeyer.onAir) {
document.getElementById(`me-${i}-key-${upstreamKeyer.upstreamKeyerId + 1}-onair`).classList.add('atem-red');
} else {
document
.getElementById(`me-${i}-key-${upstreamKeyer.upstreamKeyerId + 1}-onair`)
.classList.remove('atem-red');
}
});
}
} else if (updateType.includes('transitionPreview')) {
for (let i = 0; i < data.video.mixEffects.length; i++) {
const mixEffect = data.video.mixEffects[i];
if (mixEffect.transitionPreview) {
document.getElementById(`me-${i}-transition-preview`).classList.add('atem-red');
} else {
document.getElementById(`me-${i}-transition-preview`).classList.remove('atem-red');
}
}
} else if (updateType.includes('deviceInfo')) {
// videoModes pulled from https://github.com/nrkno/sofie-atem-connection/blob/master/src/enums/index.ts#L238
if ([27, 26, 23, 25, 19, 13, 11, 7, 5].includes(data.settings.videoMode)) {
timerFrameRate = 30;
} else if ([24, 22, 18, 16, 12, 10, 6, 4].includes(data.settings.videoMode)) {
timerFrameRate = 25;
} else if ([21, 20, 15, 14, 9, 8].includes(data.settings.videoMode)) {
timerFrameRate = 24;
} else {
timerFrameRate = 30;
}
if (!device.displayName) {
this.deviceInfoUpdate(device, 'displayName', data.info.productIdentifier);
}
} else {
console.log('unhandled update');
console.log(updateType);
console.log(device.data);
}
};
exports.data = function data(_device, msg) {
const device = _device;
this.deviceInfoUpdate(device, 'status', 'ok');
device.data = msg.state;
msg.pathToChange.forEach((path) => {
device.update(path, device.data);
});
};
function framesToTime(total) {
if (timerFrameRate) {
const seconds = Math.floor(total / timerFrameRate);
const frames = total - seconds * timerFrameRate;
let framesString = String(frames);
if (frames < 10) {
framesString = `0${framesString}`;
} else if (framesString === '0') {
framesString = '00';
}
return `${seconds}:${framesString}`;
}
return '0:00';
}
+203
View File
@@ -0,0 +1,203 @@
body {
background-color: #282828;
}
h1 {
font-family: 'Open Sans', sans-serif;
}
h3 {
color: #6d6d6d !important;
margin: 30px 0px 10px 9px;
font-size: 0.9em;
font-weight: 400;
font-family: 'Open Sans', sans-serif;
}
.me-label {
margin-top: 0;
margin-bottom: 2px;
}
.atem-input {
width: 52px;
height: 52px;
background-image: url('img/button_white.png');
}
.source-wrapper {
display: flex;
flex-wrap: wrap;
background-color: #1f1f1f;
border: #1a1a1a 2px solid;
border-radius: 8px;
max-width: 416px;
padding: 12px;
}
.atem-red {
background-image: url('img/button_red.png');
box-shadow: 0px 0px 10px 1px #ff0000;
z-index: 10000;
}
.atem-green {
background-image: url('img/button_green.png');
box-shadow: 0px 0px 10px 1px #06c300;
z-index: 10000;
}
.atem-yellow {
background-image: url('img/button_yellow.png');
box-shadow: 0px 0px 10px 1px #c7ca00;
z-index: 10000;
}
.atem-disabled {
background-image: url('img/button_off.png');
color: #3a3a3a;
}
.atem-gray {
color: #575757;
}
.source-label {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
font-size: 11px;
font-weight: bold;
}
.transition-container {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.transition-settings {
display: flex;
flex-direction: column;
justify-content: space-between;
margin-right: 30px;
}
.tbar-container {
display: flex;
flex-direction: column-reverse;
background-color: #1f1f1f;
border: 2px solid;
border-color: #1a1a1a;
border-radius: 6px;
width: 88px;
height: 429px;
position: relative;
margin-top: 58px;
margin-right: 60px;
}
.tbar-bg {
position: absolute;
background-image: url('img/tbar_bg.png');
width: 88px;
height: 429px;
}
.tbar-handle {
position: absolute;
background-image: url('img/tbar_handle.png');
width: 126px;
height: 50px;
left: -3px;
}
.tbar-div {
width: 100%;
background-color: #7aff58;
overflow: hidden;
}
.dsk {
width: 100%;
}
.dsk .source-wrapper {
display: flex;
flex-direction: column;
align-content: center;
width: 100%;
padding: 5px;
}
.dsk h3 {
text-align: center;
}
.fade-to-black-container {
display: flex;
flex-direction: column;
align-items: flex-end;
margin-right: 30px;
}
.fade-to-black .source-wrapper {
display: flex;
width: 100%;
padding: 5px;
}
.fade-to-black h3 {
text-align: center;
}
.rate-heading {
text-align: center;
font-size: small;
margin-bottom: 4px;
}
.dsk-rate,
.ftb-rate,
.transition-rate {
display: flex;
justify-content: center;
flex-direction: column;
}
.dsk-rate-label,
.ftb-rate-label,
.transition-rate-label {
color: rgb(235, 110, 0);
background-color: black;
border-radius: 3px;
padding: 6px 2px;
text-align: center;
}
.clear {
background: transparent;
border-color: transparent;
background-color: transparent;
}
.hide {
display: none;
}
.no-wrap {
flex-wrap: nowrap;
}
.show-small {
display: none;
}
@media screen and (min-width: 0px) and (max-width: 480px) {
.hide-small {
display: none;
}
.show-small {
display: block;
}
}
.float-left {
float: left;
}
+189
View File
@@ -0,0 +1,189 @@
<header>
<h1><%= listName %></h1>
</header>
<% if (data.video.mixEffects) { %>
<% Object.entries(data.video.mixEffects).forEach(([meIndex, state])=>{ %>
<div class="mixeffect_wrapper" id="me-<%= meIndex %>">
<h1 class="atem-gray me-label">ME<%= Number(meIndex) + 1 %></h1>
<div class="inputs-container show-small">
<h3 class="atem-gray">Preview/Program</h3>
<div class="source-wrapper">
<% Object.values(data.inputs).sort((a,b)=>a.internalPortType<b.internalPortType).forEach((input)=>{ %>
<% if ([0,1,2,3,4].includes(input.internalPortType)) { %>
<div id="me-<%= meIndex %>-input-<%= input.inputId %>" class="atem-input">
<div class="source-label"><%= input.shortName %></div>
</div>
<% } else {%>
<div id="me-<%= meIndex %>-input-<%= input.inputId %>" class="atem-input atem-disabled">
<div class="source-label"><%= input.shortName %></div>
</div>
<% }%>
<% }) %>
</div>
</div>
<div class="float-left" style="margin-right: 30px;">
<div class="program-container hide-small">
<h3 class="atem-gray">Program</h3>
<div class="source-wrapper">
<% Object.values(data.inputs).sort((a,b)=>a.internalPortType<b.internalPortType).forEach((input)=>{ %>
<% if ([0,1,2,3,4].includes(input.internalPortType)) { %>
<div id="me-<%= meIndex %>-program-input-<%= input.inputId %>" class="atem-input">
<div class="source-label"><%= input.shortName %></div>
</div>
<% } else {%>
<div id="me-<%= meIndex %>-program-input-<%= input.inputId %>" class="atem-input atem-disabled">
<div class="source-label"><%= input.shortName %></div>
</div>
<% }%>
<% }) %>
</div>
</div>
<div class="preview hide-small">
<h3 class="atem-gray">Preview</h3>
<div class="source-wrapper">
<% Object.values(data.inputs).forEach((input)=>{ %>
<% if ([0,1,2,3,4].includes(input.internalPortType)) { %>
<div id="me-<%= meIndex %>-preview-input-<%= input.inputId %>" class="atem-input">
<div class="source-label"><%= input.shortName %></div>
</div>
<% } else {%>
<div id="me-<%= meIndex %>-preview-input-<%= input.inputId %>" class="atem-input atem-disabled">
<div class="source-label"><%= input.shortName %></div>
</div>
<% }%>
<% }) %>
</div>
</div>
</div>
<div class="transition-container float-left">
<div class="transition-settings">
<div class="next-transition">
<h3>Next Transition</h3>
<div class="source-wrapper" style="width: 260px;">
<div class="atem-input clear"></div>
<div id="me-<%= meIndex %>-key-1-onair" class="atem-input">
<div class="source-label" style="flex-direction: column;">
<div>ON</div> <div>AIR</div>
</div>
</div>
<div id="me-<%= meIndex %>-key-2-onair" class="atem-input <% if(data.video.downstreamKeyers.length<2){ %> atem-disabled <% } %>">
<div class="source-label" style="flex-direction: column;">
<div>ON</div> <div>AIR</div>
</div>
</div>
<div id="me-<%= meIndex %>-key-3-onair" class="atem-input <% if(data.video.downstreamKeyers.length<3){ %> atem-disabled <% } %>">
<div class="source-label" style="flex-direction: column;">
<div>ON</div> <div>AIR</div>
</div>
</div>
<div id="me-<%= meIndex %>-key-4-onair" class="atem-input <% if(data.video.downstreamKeyers.length<4){ %> atem-disabled <% } %>">
<div class="source-label" style="flex-direction: column;">
<div>ON</div> <div>AIR</div>
</div>
</div>
<div id="me-<%= meIndex %>-transition-selection-1" class="atem-input">
<div class="source-label">BKGD</div>
</div>
<div id="me-<%= meIndex %>-transition-selection-2" class="atem-input">
<div class="source-label">KEY 1</div>
</div>
<div id="me-<%= meIndex %>-transition-selection-4" class="atem-input <% if(data.video.downstreamKeyers.length<2){ %> atem-disabled <% } %>">
<div class="source-label">KEY 2</div>
</div>
<div id="me-<%= meIndex %>-transition-selection-8" class="atem-input <% if(data.video.downstreamKeyers.length<3){ %> atem-disabled <% } %>">
<div class="source-label">KEY 3</div>
</div>
<div id="me-<%= meIndex %>-transition-selection-16" class="atem-input <% if(data.video.downstreamKeyers.length<4){ %> atem-disabled <% } %>">
<div class="source-label">KEY 4</div>
</div>
</div>
</div>
<div class="transition-style">
<h3>Transition Style</h3>
<div class="source-wrapper" style="width: 260px;">
<div id="me-<%= meIndex %>-transition-style-0" class="atem-input">
<div class="source-label">MIX</div>
</div>
<div id="me-<%= meIndex %>-transition-style-1" class="atem-input">
<div class="source-label">DIP</div>
</div>
<div id="me-<%= meIndex %>-transition-style-2" class="atem-input">
<div class="source-label">WIPE</div>
</div>
<div id="me-<%= meIndex %>-transition-style-4" class="atem-input">
<div class="source-label">STING</div>
</div>
<div id="me-<%= meIndex %>-transition-style-3" class="atem-input">
<div class="source-label">DVE</div>
</div>
<div id="me-<%= meIndex %>-transition-preview" class="atem-input">
<div class="source-label" style="flex-direction: column;">
<div>PREV</div> <div>TRANS</div>
</div>
</div>
<div class="atem-input clear"></div>
<div id="me-<%= meIndex %>-cut" class="atem-input">
<div class="source-label">CUT</div>
</div>
<div id="me-<%= meIndex %>-auto" class="atem-input">
<div class="source-label">AUTO</div>
</div>
<div class="transition-rate atem-input clear">
<div class="atem-gray rate-heading">Rate</div>
<div id="me-<%= meIndex %>-transition-rate" class="transition-rate-label">&nbsp;&nbsp;</div>
</div>
</div>
</div>
</div>
<div class="tbar-container">
<div class="tbar-bg"></div>
<div id="me-<%= meIndex %>-tbar-handle-div" class="tbar-handle"></div>
<div id="me-<%= meIndex %>-tbar-div" class="tbar-div" ></div>
</div>
<div class="fade-to-black-container">
<% if (data.video.downstreamKeyers) { %>
<% Object.entries(data.video.downstreamKeyers).forEach(([dskIndex, state])=>{ %>
<div class="dsk">
<h3 class="atem-gray">DSK<%= Number(dskIndex) + 1 %></h3>
<div class="source-wrapper">
<div id="dsk-<%= dskIndex %>-tie" class="atem-input">
<div class="source-label">TIE</div>
</div>
<div class="dsk-rate atem-input clear">
<div class="atem-gray rate-heading">Rate</div>
<div id="dsk-<%= dskIndex %>-rate" class="dsk-rate-label">&nbsp;&nbsp;</div>
</div>
<div id="dsk-<%= dskIndex %>-onair" class="atem-input">
<div class="source-label" style="flex-direction: column;">
<div>ON</div> <div>AIR</div>
</div>
</div>
<div id="dsk-<%= dskIndex %>-auto" class="atem-input">
<div class="source-label">AUTO</div>
</div>
</div>
</div>
<% }) %>
<% } %>
<div class="fade-to-black">
<h3 class="atem-gray">Fade to Black</h3>
<div class="source-wrapper">
<div class="ftb-rate atem-input clear">
<div class="atem-gray rate-heading">Rate</div>
<div id="me-<%= meIndex %>-ftb-rate" class="ftb-rate-label">&nbsp;&nbsp;</div>
</div>
<div id="me-<%= meIndex %>-ftb" class="atem-input">
<div class="source-label">FTB</div>
</div>
</div>
</div>
</div>
</div>
</div>
<% }) %>
<% } %>
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File
+441
View File
@@ -0,0 +1,441 @@
exports.config = {
defaultName: 'd&b Amps',
connectionType: 'TCPsocket',
defaultPort: 30013,
mayChangePort: false,
heartbeatInterval: 1000,
heartbeatTimeout: 5000,
searchOptions: {
type: 'Bonjour',
bonjourName: 'oca',
validateResponse(msg, info) {
return msg.includes('d&b audiotechnik');
},
},
};
exports.ready = function ready(_device) {
const device = _device;
device.data.oca = structuredClone(OCAStructure);
device.data.ocaKeys = Object.keys(device.data.oca);
device.data.ocaIDs = [];
for (let i = 0; i < device.data.ocaKeys.length; i++) {
const key = device.data.oca[device.data.ocaKeys[i]];
key.parameters = [0];
device.data.ocaIDs[i] = key;
ocaCommandRequest(i, key.target, key.method, device);
ocaSubscriptionRequest(i, key.target, key.method, device);
}
};
exports.data = function data(_device, _buffer) {
const device = _device;
if (_buffer[0] === 0x3b && _buffer[1] === 0 && _buffer[2] === 1) {
let start = 0;
while (start < _buffer.length - 3) {
const messageLength = _buffer.readInt32BE(start + 3) + 1;
const message = _buffer.slice(start, start + messageLength);
const messageType = message[7];
if (messageType === 0x04) {
// keep-alive message
} else if (messageType === 0x02) {
// console.log('NOTIFICATION!!');
console.log(message.slice(33));
} else if (messageType === 0x03) {
const messageHandle = _buffer.readInt32BE(start + 14);
const paramCount = message[19];
if (paramCount) {
let paramsStart = 20;
const ocaObj = device.data.ocaIDs[messageHandle];
ocaObj.parameters = [];
while (paramsStart < message.length) {
let paramLength = 0;
let param;
if (ocaObj.format === 'int16' || ocaObj.format === 'float' || ocaObj.format === 'int32') {
paramLength = 2;
param = message.slice(paramsStart, paramsStart + paramLength + 2);
} else if (ocaObj.format === 'int8') {
paramLength = 1;
param = message.slice(paramsStart, paramsStart + paramLength);
} else if (ocaObj.format === 'stringArray') {
const nextBreak = message.indexOf(0x00, paramsStart + 1);
if (nextBreak < 0) {
break;
}
paramLength = nextBreak - paramsStart + 1;
param = message.slice(paramsStart - 1, paramsStart + paramLength - 1).toString();
} else {
paramLength = message.readInt16BE(20);
param = message.slice(paramsStart + 2, paramsStart + 2 + paramLength);
}
if (ocaObj.format === 'string') {
ocaObj.parameters.push(param.toString());
} else if (ocaObj.format === 'stringArray') {
ocaObj.parameters.push(param.toString());
} else if (ocaObj.format === 'float') {
ocaObj.parameters.push(param.readFloatBE(0));
} else if (ocaObj.format === 'int16') {
ocaObj.parameters.push(param.readInt16BE(0));
} else if (ocaObj.format === 'int8') {
ocaObj.parameters.push(param.readInt8(0));
} else if (ocaObj.format === 'int32') {
ocaObj.parameters.push(param.readInt32BE(0));
} else {
ocaObj.parameters.push(param);
}
this.deviceInfoUpdate(device, 'status', 'ok');
paramsStart += paramLength + 2;
}
}
device.draw();
}
start += messageLength;
}
}
};
exports.heartbeat = function heartbeat(device) {
device.send(Buffer.from('3b00010000000b0400010001', 'hex'));
};
function ocaCommandRequest(handle, target, method, device) {
const buf = Buffer.from('3b00010000001a01000100000011AAAAAAAABBBBBBBBCCCCCCCC00', 'hex');
buf.writeInt32BE(handle, 14); // handle
buf.writeInt32BE(target, 18); // target
buf.writeInt32BE(method, 22); // method
device.send(buf);
return buf;
}
function ocaSubscriptionRequest(handle, target, method, device) {
const buf = Buffer.from(
'3b00010000002f01000100000026000000ae00000004000300010510008205000100010000041f000100010000010000',
'hex'
);
buf.writeInt32BE(handle, 14); // handle
buf.writeInt32BE(target, 27); // target
device.send(buf);
return buf;
}
const OCAStructure = {
ModelDescription: {
target: 0x1000010d,
method: 0x00050001,
format: 'string',
},
PwrOn: {
target: 0x10000100,
method: 0x00040001,
format: 'int16',
},
SubNet: {
target: 0x10000109,
method: 0x00050001,
format: 'int32',
},
DeviceId: {
target: 0x10000108,
method: 0x00050001,
format: 'int32',
},
DeviceType: {
target: 0x10000034,
method: 0x00050001,
format: 'string',
},
FirmwareRevision: {
target: 0x10000031,
method: 0x00050001,
format: 'string',
},
ChannelNameA: {
target: 0x10008215,
method: 0x00050001,
format: 'string',
},
ChannelNameB: {
target: 0x10010215,
method: 0x00050001,
format: 'string',
},
ChannelNameC: {
target: 0x10018215,
method: 0x00050001,
format: 'string',
},
ChannelNameD: {
target: 0x10020215,
method: 0x00050001,
format: 'string',
},
DelayA: {
target: 0x10008207,
method: 0x00050001,
format: 'float',
},
DelayB: {
target: 0x10010207,
method: 0x00050001,
format: 'float',
},
DelayC: {
target: 0x10018207,
method: 0x00050001,
format: 'float',
},
DelayD: {
target: 0x10020207,
method: 0x00050001,
format: 'float',
},
InputEnable1A: {
target: 0x10008220,
method: 0x00040001,
format: 'int16',
},
InputEnable2A: {
target: 0x10008221,
method: 0x00040001,
format: 'int16',
},
InputEnable3A: {
target: 0x10008222,
method: 0x00040001,
format: 'int16',
},
InputEnable4A: {
target: 0x10008223,
method: 0x00040001,
format: 'int16',
},
InputEnable5A: {
target: 0x10008224,
method: 0x00040001,
format: 'int16',
},
InputEnable6A: {
target: 0x10008225,
method: 0x00040001,
format: 'int16',
},
InputEnable7A: {
target: 0x10008226,
method: 0x00040001,
format: 'int16',
},
InputEnable8A: {
target: 0x10008227,
method: 0x00040001,
format: 'int16',
},
InputEnable1B: {
target: 0x10010220,
method: 0x00040001,
format: 'int16',
},
InputEnable2B: {
target: 0x10010221,
method: 0x00040001,
format: 'int16',
},
InputEnable3B: {
target: 0x10010222,
method: 0x00040001,
format: 'int16',
},
InputEnable4B: {
target: 0x10010223,
method: 0x00040001,
format: 'int16',
},
InputEnable5B: {
target: 0x10010224,
method: 0x00040001,
format: 'int16',
},
InputEnable6B: {
target: 0x10010225,
method: 0x00040001,
format: 'int16',
},
InputEnable7B: {
target: 0x10010226,
method: 0x00040001,
format: 'int16',
},
InputEnable8B: {
target: 0x10010227,
method: 0x00040001,
format: 'int16',
},
InputEnable1C: {
target: 0x10018220,
method: 0x00040001,
format: 'int16',
},
InputEnable2C: {
target: 0x10018221,
method: 0x00040001,
format: 'int16',
},
InputEnable3C: {
target: 0x10018222,
method: 0x00040001,
format: 'int16',
},
InputEnable4C: {
target: 0x10018223,
method: 0x00040001,
format: 'int16',
},
InputEnable5C: {
target: 0x10018224,
method: 0x00040001,
format: 'int16',
},
InputEnable6C: {
target: 0x10018225,
method: 0x00040001,
format: 'int16',
},
InputEnable7C: {
target: 0x10018226,
method: 0x00040001,
format: 'int16',
},
InputEnable8C: {
target: 0x10018227,
method: 0x00040001,
format: 'int16',
},
InputEnable1D: {
target: 0x10020220,
method: 0x00040001,
format: 'int16',
},
InputEnable2D: {
target: 0x10020221,
method: 0x00040001,
format: 'int16',
},
InputEnable3D: {
target: 0x10020222,
method: 0x00040001,
format: 'int16',
},
InputEnable4D: {
target: 0x10020223,
method: 0x00040001,
format: 'int16',
},
InputEnable5D: {
target: 0x10020224,
method: 0x00040001,
format: 'int16',
},
InputEnable6D: {
target: 0x10020225,
method: 0x00040001,
format: 'int16',
},
InputEnable7D: {
target: 0x10020226,
method: 0x00040001,
format: 'int16',
},
InputEnable8D: {
target: 0x10020227,
method: 0x00040001,
format: 'int16',
},
InputGainA: {
target: 0x10008206,
method: 0x00040001,
format: 'float',
},
InputGainB: {
target: 0x10010206,
method: 0x00040001,
format: 'float',
},
InputGainC: {
target: 0x10018206,
method: 0x00040001,
format: 'float',
},
InputGainD: {
target: 0x10020206,
method: 0x00040001,
format: 'float',
},
SpeakerIDA: {
target: 0x10008214,
method: 0x00040001,
format: 'int16',
},
SpeakerIDB: {
target: 0x10010214,
method: 0x00040001,
format: 'int16',
},
SpeakerIDC: {
target: 0x10018214,
method: 0x00040001,
format: 'int16',
},
SpeakerIDD: {
target: 0x10020214,
method: 0x00040001,
format: 'int16',
},
SpeakerIDList: {
target: 0x10010214,
method: 0x00040005,
format: 'stringArray',
},
OutputMode: {
target: 0x10000111,
method: 0x00040001,
format: 'int16',
},
OutputModeList: {
target: 0x10000111,
method: 0x00040005,
format: 'stringArray',
},
MuteA: {
target: 0x10008205,
method: 0x00040001,
format: 'int8',
},
MuteB: {
target: 0x10010205,
method: 0x00040001,
format: 'int8',
},
MuteC: {
target: 0x10018205,
method: 0x00040001,
format: 'int8',
},
MuteD: {
target: 0x10020205,
method: 0x00040001,
format: 'int8',
},
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.
+108
View File
@@ -0,0 +1,108 @@
table {
color: white;
background-color: #323232;
font-size: 14px;
}
.button {
padding: 4px;
height: 65px;
border-radius: 4px;
position: relative;
text-align: right;
margin: 2px;
box-sizing: border-box;
overflow: hidden;
white-space: nowrap;
}
.light-button {
background: linear-gradient(0deg, #484848, #737373);
outline: black 2px outset;
border-top: #8b8b8b 2px solid;
border-left: #8b8b8b 2px solid;
min-width: 100px;
}
.dark-button {
background: linear-gradient(0deg, #0e0e0e, #282828);
outline: #737373 2px solid;
text-overflow: ellipsis;
white-space: nowrap;
}
.mute {
text-align: center;
background-image: url('muteicon-on.png'), linear-gradient(0deg, #484848, #737373);
background-position: 52px 13px, 0px 0px;
background-repeat: no-repeat;
}
.gain-box {
background-color: #272727;
height: 60px;
width: 65px;
margin: 2px;
border: #656565 2px solid;
outline: #111111 2px solid;
text-align: right;
}
.gain-head {
box-sizing: border-box;
height: 12px;
width: 100%;
background: #686868;
border: #1a1a1a 2px solid;
outline: #656565 2px solid;
margin-bottom: 27px;
}
.gain-box span {
padding: 4px;
}
h3 {
margin: 7px 0px 3px;
margin-left: 12px;
text-align: left;
font-weight: normal;
font-size: 22px;
}
h4 {
text-align: left;
font-size: 16px;
margin: 0px;
}
.channel-input {
position: absolute;
left: 9px;
top: 27px;
border-right: white 1px solid;
padding-right: 40px;
text-align: left;
line-height: 14px;
}
.channel-delay {
position: absolute;
left: 170px;
top: 40px;
}
.power-1 {
background: linear-gradient(0deg, #5a8127, #789b50);
border: #506937 1px solid;
}
.indicate-1 {
background: #00ff00;
border: black 2px inset;
}
.mute-1 {
border-top: #772b26 2px solid;
border-left: #772b26 2px solid;
background-image: url('muteicon-mute.png'), linear-gradient(0deg, #b52714, #c4473e);
}
@media screen and (min-width: 0px) and (max-width: 450px) {
.hide-medium {
display: none;
}
}
@media screen and (min-width: 0px) and (max-width: 300px) {
.hide-small {
display: none;
}
}
+141
View File
@@ -0,0 +1,141 @@
<header>
<h1><%= listName %></h1>
<h2><%= data.oca.FirmwareRevision.parameters[0] %></h2>
</header>
<table>
<tr>
<td>
<div class="button dark-button indicate-<%=data.oca.PwrOn.parameters[0]%>"></div>
</td>
<td width="250px" class="hide-small">
<div class="button dark-button">
<h4><%= data.oca.ModelDescription.parameters[0] %></h4><br>
<span class="hide-medium"><%= data.oca.OutputModeList.parameters[data.oca.OutputMode.parameters[0]+1] %></span>
</div>
</td>
<td class="hide-medium">
<div class="button dark-button">
<h4>ID</h4><br>
<%= data.oca.SubNet.parameters[0] %>.<%= data.oca.DeviceId.parameters[0].toString().padStart(2, '0') %>
</div>
</td>
<td width="95px"><div class="button dark-button power-<%=data.oca.PwrOn.parameters[0]%>"><h4>Power</h4><br>On</div></td>
</tr>
<tr>
<td></td>
<td class="hide-small">
<div class="button light-button">
<h4><%= data.oca.ChannelNameA.parameters[0] %></h4>
<div class="channel-input">
Input<br>
<% if(data.oca.InputEnable1A.parameters[0]){ %> A1 <% } %>
<% if(data.oca.InputEnable2A.parameters[0]){ %> A2 <% } %>
<% if(data.oca.InputEnable3A.parameters[0]){ %> A3 <% } %>
<% if(data.oca.InputEnable4A.parameters[0]){ %> A4 <% } %>
<% if(data.oca.InputEnable5A.parameters[0]){ %> D1 <% } %>
<% if(data.oca.InputEnable6A.parameters[0]){ %> D2 <% } %>
<% if(data.oca.InputEnable7A.parameters[0]){ %> D3 <% } %>
<% if(data.oca.InputEnable8A.parameters[0]){ %> D4 <% } %>
</div>
<div class="channel-delay">
<%= data.oca.DelayA.parameters[0].toFixed(1) %>ms
</div>
</div>
</td>
<td class="hide-medium">
<div class="gain-box">
<div class="gain-head"></div>
<span><%= data.oca.InputGainA.parameters[0].toFixed(1) %>db</span>
</div>
</td>
<td><div class="button light-button mute mute-<%=data.oca.MuteA.parameters[0]%>"><h3>A</h3><%= data.oca.SpeakerIDList.parameters[data.oca.SpeakerIDA.parameters[0]] %></div></td>
</tr>
<tr>
<td></td>
<td class="hide-small">
<div class="button light-button">
<h4><%= data.oca.ChannelNameB.parameters[0] %></h4>
<div class="channel-input">
Input<br>
<% if(data.oca.InputEnable1B.parameters[0]){ %> A1 <% } %>
<% if(data.oca.InputEnable2B.parameters[0]){ %> A2 <% } %>
<% if(data.oca.InputEnable3B.parameters[0]){ %> A3 <% } %>
<% if(data.oca.InputEnable4B.parameters[0]){ %> A4 <% } %>
<% if(data.oca.InputEnable5B.parameters[0]){ %> D1 <% } %>
<% if(data.oca.InputEnable6B.parameters[0]){ %> D2 <% } %>
<% if(data.oca.InputEnable7B.parameters[0]){ %> D3 <% } %>
<% if(data.oca.InputEnable8B.parameters[0]){ %> D4 <% } %>
</div>
<div class="channel-delay">
<%= data.oca.DelayB.parameters[0].toFixed(1) %>ms
</div>
</div>
</td>
<td class="hide-medium">
<div class="gain-box">
<div class="gain-head"></div>
<span><%= data.oca.InputGainB.parameters[0].toFixed(1) %>db</span>
</div>
</td>
<td><div class="button light-button mute mute-<%=data.oca.MuteB.parameters[0]%>"><h3>B</h3><%= data.oca.SpeakerIDList.parameters[data.oca.SpeakerIDB.parameters[0]] %></div></td>
</tr>
<tr>
<td></td>
<td class="hide-small">
<div class="button light-button">
<h4><%= data.oca.ChannelNameC.parameters[0] %></h4>
<div class="channel-input">
Input<br>
<% if(data.oca.InputEnable1C.parameters[0]){ %> A1 <% } %>
<% if(data.oca.InputEnable2C.parameters[0]){ %> A2 <% } %>
<% if(data.oca.InputEnable3C.parameters[0]){ %> A3 <% } %>
<% if(data.oca.InputEnable4C.parameters[0]){ %> A4 <% } %>
<% if(data.oca.InputEnable5C.parameters[0]){ %> D1 <% } %>
<% if(data.oca.InputEnable6C.parameters[0]){ %> D2 <% } %>
<% if(data.oca.InputEnable7C.parameters[0]){ %> D3 <% } %>
<% if(data.oca.InputEnable8C.parameters[0]){ %> D4 <% } %>
</div>
<div class="channel-delay">
<%= data.oca.DelayC.parameters[0].toFixed(1) %>ms
</div>
</div>
</td>
<td class="hide-medium">
<div class="gain-box">
<div class="gain-head"></div>
<span><%= data.oca.InputGainC.parameters[0].toFixed(1) %>db</span>
</div>
</td>
<td><div class="button light-button mute mute-<%=data.oca.MuteC.parameters[0]%>"><h3>C</h3><%= data.oca.SpeakerIDList.parameters[data.oca.SpeakerIDC.parameters[0]] %></div></td>
</tr>
<tr>
<td></td>
<td class="hide-small">
<div class="button light-button">
<h4><%= data.oca.ChannelNameD.parameters[0] %></h4>
<div class="channel-input">
Input<br>
<% if(data.oca.InputEnable1D.parameters[0]){ %> A1 <% } %>
<% if(data.oca.InputEnable2D.parameters[0]){ %> A2 <% } %>
<% if(data.oca.InputEnable3D.parameters[0]){ %> A3 <% } %>
<% if(data.oca.InputEnable4D.parameters[0]){ %> A4 <% } %>
<% if(data.oca.InputEnable5D.parameters[0]){ %> D1 <% } %>
<% if(data.oca.InputEnable6D.parameters[0]){ %> D2 <% } %>
<% if(data.oca.InputEnable7D.parameters[0]){ %> D3 <% } %>
<% if(data.oca.InputEnable8D.parameters[0]){ %> D4 <% } %>
</div>
<div class="channel-delay">
<%= data.oca.DelayD.parameters[0].toFixed(1) %>ms
</div>
</div>
</td>
<td class="hide-medium">
<div class="gain-box">
<div class="gain-head"></div>
<span><%= data.oca.InputGainD.parameters[0].toFixed(1) %>db</span>
</div>
</td>
<td><div class="button light-button mute mute-<%=data.oca.MuteD.parameters[0]%>"><h3>D</h3><%= data.oca.SpeakerIDList.parameters[data.oca.SpeakerIDD.parameters[0]] %></div></td>
</tr>
</table>
+80
View File
@@ -0,0 +1,80 @@
<tbody id="<%= q[0].uid %>">
<% for(var partNumber in q){ %>
<% var part = q[partNumber] %>
<%if(part.scene.length>0 && partNumber==0){ %>
<tr>
<td></td>
<td colspan="12" class="scene">
<hr />
<span><%= part.scene %></span>
</td>
</tr>
<% } %>
<% if(isActive){ %>
<tr class="active-cue scroll-position">
<% }else{ %>
</tr>
<tr>
<% } %>
<% if(partNumber!=0){ %>
<td class="num" style="padding-left: 20px">
<div>P<%= partNumber %></div>
</td>
<% }else{ %>
<td class="num"><div><%= cueNumber %></div></td>
<% } %>
<% if(partNumber !== 0 || part.partCount === 0){ %>
<!-- Int Up/Int Down -->
<% if(part.uptimeDuration === part.downtimeDuration || part.downtimeDuration === -1){ %>
<td class="black" colspan="2">
<%= part.prettyDuration(part.uptimeDelay) || "" %>
<%= part.prettyDuration(part.uptimeDuration, true) %>
</td>
<% }else{ %>
<td class="black">
<%= part.prettyDuration(part.uptimeDelay) || "" %>
<%= part.prettyDuration(part.uptimeDuration, true) %>
</td>
<td class="black">
<%= part.prettyDuration(part.downtimeDuration, true) %>
</td>
<% } %>
<td class="black hide-medium">
<%= part.prettyDuration(part.focusTimeDuration, true) %>
</td>
<td class="black hide-medium">
<%= part.prettyDuration(part.colorTimeDuration, true) %>
</td>
<td class="black hide-medium">
<%= part.prettyDuration(part.beamTimeDuration, true) %>
</td>
<td class="hide-small"><%= part.prettyDuration(part.duration) %></td>
<% }else{ %>
<td class="black" colspan="2"></td>
<td class="black hide-medium"></td>
<td class="black hide-medium"></td>
<td class="black hide-medium"></td>
<td class="hide-small"></td>
<% } %>
<td class="black hide-small"><%= part.mark %></td>
<td class="black hide-small"><%= part.block %></td>
<td class="black hide-small"><%= part.assert %></td>
<td class="black hide-small">
<%= (part.follow>=0)? "F"+part.prettyDuration(part.follow) : "" %>
<%= (part.hang>=0)? "H"+part.prettyDuration(part.hang) : "" %>
</td>
<td class="black" style="text-align: left">
<%= (partNumber==0 ? "": "&nbsp;&nbsp;&nbsp;") %>
<%= part.label %>
</td>
<td class="black hide-medium"><%= part.extLinks %></td>
</tr>
<% } %>
</tbody>
+40
View File
@@ -0,0 +1,40 @@
/* eslint-disable class-methods-use-this */
class Cue {
constructor(args) {
this.uid = args[1];
this.label = args[2];
this.uptimeDuration = args[3];
this.uptimeDelay = args[4];
this.downtimeDuration = args[5];
this.downtimeDelay = args[6];
this.focusTimeDuration = args[7];
this.focusTimeDelay = args[8];
this.colorTimeDuration = args[9];
this.colorTimeDelay = args[10];
this.beamTimeDuration = args[11];
this.beamTimeDelay = args[12];
this.mark = args[16];
this.block = args[17];
this.assert = args[18];
this.follow = args[20];
this.hang = args[21];
this.partCount = args[26];
this.scene = args[28];
this.duration = Math.max(args[3], args[5], args[7], args[9], args[11]);
}
prettyDuration(milliseconds, box) {
if (milliseconds === -1) {
return '';
}
const num = Math.round(milliseconds / 100) / 10;
if (box) {
return `<div class="time">${num}</div>`;
}
return num;
}
}
module.exports = Cue;
-69
View File
@@ -1,69 +0,0 @@
table{
background-color: #141414;
border: #404040 1px solid;
border-radius: 4px;
color: #a59baa;
}
th{
font-size: 14px;
font-weight: normal;
padding: 5px;
}
td{
text-align: center;
padding: 6px;
}
td:first-child{
text-align: left;
}
td.black{
background-color: black;
border-right: #212021 1px solid;
}
td.num{
color: white;
font-weight: 600;
font-size: 16px;
}
.scene{
background-color: black;
border-top: #141414 2px solid;
border-bottom: #141414 2px solid;
}
.scene hr{
border: #001f10 4px solid;
}
.scene span{
display: table;
margin: 0px auto;
margin-top: -25px;
padding: 4px;
background-color: black;
font-weight: bold;
font-size: 14px;
}
.time{
border: #2f2b35 2px solid;
border-radius: 5px;
}
tr.active-cue{
color: #d49800;
}
tr.active-cue td.num{
color: #d49800;
}
tr.active-cue .time{
border-color: #d49800;
}
@media screen and (min-width: 0px) and (max-width: 750px) {
.hide-medium{
display: none;
}
}
@media screen and (min-width: 0px) and (max-width: 550px) {
.hide-small{
display: none;
}
}
-119
View File
@@ -1,119 +0,0 @@
<header>
<h1><%= listName %></h1>
<h2><%= data.version %></h2>
</header>
<% for(var i in data.cuelists){ %>
<strong>List <%= i %></strong>
<table cellspacing="0">
<tr>
<th width="70px">Cue</th>
<th width="30px">Int Up</th>
<th width="30px">Int Down</th>
<th width="40px" class="hide-medium">Focus</th>
<th width="40px" class="hide-medium">Color</th>
<th width="40px" class="hide-medium">Beam</th>
<th width="40px" class="hide-small">Dur</th>
<th width="20px" class="hide-small">M</th>
<th width="20px" class="hide-small">B</th>
<th width="20px" class="hide-small">A</th>
<th width="70px" class="hide-small">Fw/Hg</th>
<th width="150px">Label</th>
<th width="10px" class="hide-medium">Ext Links</th>
</tr>
<% var cues = Object.keys(data.cuelists[i]).sort(function(a, b){return Number(a)-Number(b)}) %>
<% for(var j=0; j<cues.length; j++){ %>
<% var q = data.cuelists[i][cues[j]] %>
<% for(var partNumber in q){ %>
<% var p = q[partNumber] %>
<% if(p.scene.length>0 && partNumber==0){ %>
<tr>
<td></td>
<td colspan="12" class="scene"><hr><span><%= p.scene %></span></td>
</tr>
<% } %>
<% if(cues[j]==data.activeCue+""){ %>
<tr class="active-cue">
<% }else{ %>
<tr>
<% } %>
<% if(partNumber!=0){ %>
<td class="num">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;P<%= partNumber %></td>
<% }else{ %>
<td class="num">&nbsp;<%= cues[j] %></td>
<% } %>
<% if(partNumber!=0 || p.partcount==0){ %>
<!-- Int Up/Int Down -->
<% if(p.uptimeduration == p.downtimeduration || p.downtimeduration==-1){ %>
<td class="black" colspan="2"><%- prettyDuration(p.uptimedelay) || "" %><%- prettyDuration(p.uptimeduration, true) %></td>
<% }else{ %>
<td class="black"><%- prettyDuration(p.uptimedelay) || "" %><%- prettyDuration(p.uptimeduration, true) %></td>
<td class="black "><%- prettyDuration(p.downtimeduration, true) %></td>
<% } %>
<td class="black hide-medium"><%- prettyDuration(p.focustimeduration, true) %></td>
<td class="black hide-medium"><%- prettyDuration(p.colortimeduration, true) %></td>
<td class="black hide-medium"><%- prettyDuration(p.beamtimeduration, true) %></td>
<td class="hide-small"><%= prettyDuration(p.duration) %></td>
<% }else{ %>
<td class="black" colspan="2"></td>
<td class="black hide-medium"></td>
<td class="black hide-medium"></td>
<td class="black hide-medium"></td>
<td class="hide-small"></td>
<% } %>
<td class="black hide-small"><%= p.mark %></td>
<td class="black hide-small"><%= p.block %></td>
<td class="black hide-small"><%= p.assert %></td>
<td class="black hide-small">
<%= (p.follow>=0)? "F"+prettyDuration(p.follow) : "" %>
<%= (p.hang>=0)? "H"+prettyDuration(p.hang) : "" %>
</td>
<td class="black" style="text-align: left">
<%- (partNumber==0 ? "": "&nbsp;&nbsp;&nbsp;") %>
<%= p.label %>
</td>
<td class="black hide-medium"><%= p.extlinks %></td>
</tr>
<% } %>
<% } %>
</table>
<% } %>
<script>
var $scrollTo = document.getElementsByClassName("active-cue");
if($scrollTo.length==1){
window.scroll({top: $scrollTo[0].offsetTop-200, behavior: 'smooth'});
}
</script>
<%
function prettyDuration(milliseconds, box){
if(milliseconds==-1){
return "";
}
var num = Math.round(milliseconds/100)/10;
if(box){
return "<div class='time'>"+num+"</div>";
}
return num;
}
%>
-115
View File
@@ -1,115 +0,0 @@
exports.defaultName = "ETC Eos";
exports.connectionType = "osc";
exports.searchOptions = {
type: "TCPport",
searchBuffer: Buffer.from("\xc0/eos/ping\x00\x00\x2c\x00\x00\x00\xc0", 'ascii'),
testPort: 3032,
validateResponse: function(msg, info){
return (msg.toString().indexOf("/eos/out"));
}
}
exports.defaultPort = 3032;
//"\xc0/eos/ping\x00\x00\x2c\x00\x00\x00\xc0"
exports.ready = function(device){
device.send("/eos/get/cuelist/count");
device.send("/eos/get/version");
device.send("/eos/subscribe", [{type: "i", value: 1}]);
}
exports.data = function(device, osc){
var address = osc.address;
var p = osc.address.split("/");
p.shift();
if(p[1]=="out" && p[2]=="show" && p[3]=="name"){
device.data.showName = osc.args[0];
device.data.cuelists = {};
this.deviceInfoUpdate(device, "defaultName", osc.args[0]);
}else if(osc.address=="/eos/out/get/cuelist/count"){
for(var i=0; i<osc.args[0]; i++){
device.send("/eos/get/cuelist/index/"+i);
}
}else if(p[1]=="out" && p[2]=="get" && p[3]=="cuelist" && p[5]=="list"){
device.data.cuelists[p[4]] = {};
device.send("/eos/get/cue/"+p[4]+"/count");
}else if(p[1]=="out" && p[2]=="get" && p[3]=="cue" && p[5]=="count"){
for(var i=0; i<osc.args[0]; i++){
device.send("/eos/get/cue/"+p[4]+"/index/"+i);
}
}else if(p[1]=="out" && p[2]=="get" && p[3]=="cue" && p[7]=="list"){
this.deviceInfoUpdate(device, "status", "ok");
if(device.data.cuelists[p[4]][p[5]]==undefined){
device.data.cuelists[p[4]][p[5]] = {};
}
device.data.cuelists[p[4]][p[5]][p[6]] = {
uid: osc.args[1],
label: osc.args[2],
uptimeduration: osc.args[3],
uptimedelay: osc.args[4],
downtimeduration: osc.args[5],
downtimedelay: osc.args[6],
focustimeduration: osc.args[7],
focustimedelay: osc.args[8],
colortimeduration: osc.args[9],
colortimedelay: osc.args[10],
beamtimeduration: osc.args[11],
beamtimedelay: osc.args[12],
mark: osc.args[16],
block: osc.args[17],
assert: osc.args[18],
follow: osc.args[20],
hang: osc.args[21],
partcount: osc.args[26],
scene: osc.args[28],
duration: Math.max(osc.args[3], osc.args[5], osc.args[7], osc.args[9], osc.args[11])
};
//console.log(p[4]+" "+p[5]+" "+p[6]+" updated")
device.draw();
}else if(p[1]=="out" && p[2]=="get" && p[3]=="cue" && p.length==6){
//console.log("cue "+p[4]+" "+p[5]+" deleted")
// There's no OSC notification of the deletion of a part. It just tells you to update the parent cue and remaining children.
// So: we should fetch all the parts of a cue somehow every time there's an update to a cue.
delete device.data.cuelists[p[4]][p[5]];
device.draw();
}else if(p[1]=="out" && p[2]=="get" && p[3]=="cue" && p[7]=="actions"){
if(osc.args.length==3){
device.data.cuelists[p[4]][p[5]][0].extlinks = osc.args[2];
}
}else if(p[1]=="out" && p[2]=="event" && p[3]=="cue" && p[6]=="fire"){
// explore replacing this with /eos/out/active/cue
device.data.activeCue = p[5];
device.draw();
}else if(p[1]=="out" && p[2]=="notify" && p[3]=="cue"){
var cueList = p[4];
var listIndex = p[6];
var listCount = p[7];
var cueNumber = osc.args[1];
device.send("/eos/get/cue/"+cueList+"/"+cueNumber);
//console.log("SENT /eos/get/cue/"+cueList+"/"+cueNumber);
}else if(p[2]=="cmd" || p[2]=="ping" || p[2]=="user" || p[2]=="softkey"){
}else if(p[3]=="version"){
device.data.version = osc.args[0];
}else{
//console.log(osc);
}
//console.log(p)
}
exports.heartbeat = function(device){
device.send("/eos/ping");
}
+16
View File
@@ -0,0 +1,16 @@
<h2>Connection Requirements</h2>
<h4>Shell/ECU &rarr; Network</h4>
<ul>
<li>Enable <em>&#10004; TCP OSC</em>. The TCP format should be "TCP Format for OSC 1.1 (SLIP)".</li>
<li>Enable <em>Third Party OSC</em></li>
</ul>
If the "Third Party OSC" option is not available, Eos must be updated to 3.1 or newer.
<h4>System Settings &rarr; Show Control &rarr; OSC</h4>
<ul>
<li>Enable <em>&#10004; OSC RX</em></li>
<li>Enable <em>&#10004; OSC TX</em></li>
<li>All other fields may be left to defaults or blank.</li>
<li>If a custom port is desired specify it in under OSC TCP Server Ports.</li>
</ul>
+138
View File
@@ -0,0 +1,138 @@
const _ = require('lodash');
const fs = require('fs');
const path = require('path');
const Cue = require('./cue');
exports.config = {
defaultName: 'ETC Eos',
connectionType: 'osc',
defaultPort: 3037,
mayChangePort: true,
heartbeatInterval: 5000,
heartbeatTimeout: 6000,
searchOptions: {
type: 'TCPport',
searchBuffer: Buffer.from('\xc0/eos/ping\x00\x00\x2c\x00\x00\x00\xc0', 'ascii'),
testPort: 3037,
validateResponse(msg, info) {
return msg.toString().includes('/eos/out');
},
},
};
exports.ready = function ready(_device) {
const device = _device;
device.data.EOS = new EOS();
device.templates = {
cue: _.template(fs.readFileSync(path.join(__dirname, `cue.ejs`))),
};
device.send('/eos/get/cuelist/count');
device.send('/eos/get/version');
device.send('/eos/subscribe', [{ type: 'i', value: 1 }]);
};
exports.data = function data(_device, osc) {
const device = _device;
this.deviceInfoUpdate(device, 'status', 'ok');
const addressParts = osc.address.split('/');
addressParts.shift();
if (match(addressParts, ['eos', 'out', 'show', 'name'])) {
device.data.EOS.showName = osc.args[0];
device.data.EOS.cueLists = {};
this.deviceInfoUpdate(device, 'defaultName', osc.args[0]);
} else if (match(addressParts, ['eos', 'out', 'get', 'cuelist', 'count'])) {
for (let i = 0; i < osc.args[0]; i++) {
device.send(`/eos/get/cuelist/index/${i}`);
}
} else if (match(addressParts, ['eos', 'out', 'get', 'cuelist', '*', 'list', '*', '*'])) {
device.data.EOS.cueLists[addressParts[4]] = {};
device.send(`/eos/get/cue/${addressParts[4]}/count`);
} else if (match(addressParts, ['eos', 'out', 'get', 'cue', '*', 'count'])) {
for (let i = 0; i < osc.args[0]; i++) {
device.send(`/eos/get/cue/${addressParts[4]}/index/${i}`);
}
} else if (match(addressParts, ['eos', 'out', 'get', 'cue', '*', '*', '*', 'list', '*', '*'])) {
if (device.data.EOS.cueLists[addressParts[4]] === undefined) {
device.data.EOS.cueLists[addressParts[4]] = {};
device.send(`/eos/get/cue/${addressParts[4]}/count`);
}
if (device.data.EOS.cueLists[addressParts[4]][addressParts[5]] === undefined) {
device.data.EOS.cueLists[addressParts[4]][addressParts[5]] = {};
}
device.data.EOS.cueLists[addressParts[4]][addressParts[5]][addressParts[6]] = new Cue(osc.args);
device.update('cueData', {
cue: device.data.EOS.cueLists[addressParts[4]][addressParts[5]],
cueNumber: addressParts[5],
uid: osc.args[1],
});
} else if (match(addressParts, ['eos', 'out', 'get', 'cue', '*', '*'])) {
// There's no OSC notification of the deletion of a part. It just tells you to update the parent cue and remaining children.
// So: we should fetch all the parts of a cue somehow every time there's an update to a cue.
delete device.data.EOS.cueLists[addressParts[4]][addressParts[5]];
device.draw();
} else if (match(addressParts, ['eos', 'out', 'get', 'cue', '*', '*', '*', 'actions', 'list', '*', '*'])) {
if (osc.args.length === 3) {
device.data.EOS.cueLists[addressParts[4]][addressParts[5]][0].extLinks = osc.args[2];
}
} else if (match(addressParts, ['eos', 'out', 'event', 'cue', '*', '*', 'fire'])) {
device.data.EOS.activeCue = addressParts[5];
device.draw();
device.update('activeCue', {
uid: device.data.EOS.cueLists[addressParts[4]][addressParts[5]][0].uid,
});
} else if (match(addressParts, ['eos', 'out', 'notify', 'cue', '*', '*', '*', '*'])) {
const cueList = addressParts[4];
const cueNumber = osc.args[1];
device.send(`/eos/get/cue/${cueList}/${cueNumber}`);
} else if (match(addressParts, ['eos', 'out', 'get', 'version'])) {
device.data.EOS.version = osc.args[0];
} else {
// console.log(osc);
}
};
exports.update = function update(device, doc, updateType, data) {
if (updateType === 'cueData') {
const $elem = doc.getElementById(data.uid);
if ($elem) {
$elem.outerHTML = device.templates.cue({
q: data.cue,
cueNumber: data.cueNumber,
isActive: false,
});
} else {
device.draw();
}
} else if (updateType === 'activeCue') {
const $elem = doc.getElementById(data.uid);
$elem.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
};
exports.heartbeat = function heartbeat(device) {
device.send('/eos/ping');
};
function match(testArray, patternArray) {
let out = true;
if (testArray.length !== patternArray.length) {
return false;
}
patternArray.forEach((patternPart, i) => {
if (testArray[i] !== patternPart && patternPart !== '*') {
out = false;
}
});
return out;
}
class EOS {
constructor() {
this.version = '';
this.showName = '';
this.cueLists = {};
this.activeCue = undefined;
}
}
+85
View File
@@ -0,0 +1,85 @@
table {
background-color: #141414;
border: #404040 1px solid;
border-radius: 4px;
color: #a59baa;
width: 100%;
}
th {
font-size: 14px;
font-weight: normal;
padding: 5px;
}
td {
text-align: center;
padding: 4px 6px;
}
td:first-child {
text-align: left;
}
td.black {
background-color: black;
border-right: #212021 1px solid;
}
td.num {
color: white;
font-weight: 600;
font-size: 16px;
}
.scene {
background-color: black;
border-top: #141414 2px solid;
border-bottom: #141414 2px solid;
}
.scene hr {
border: #001f10 4px solid;
}
.scene span {
display: table;
margin: 0px auto;
margin-top: -25px;
padding: 4px;
background-color: black;
font-weight: bold;
font-size: 14px;
}
.time {
border: #2f2b35 2px solid;
border-radius: 5px;
}
tr.active-cue {
color: #c78b07;
}
tr.active-cue td.num div {
margin: 0px;
padding: 1px 6px;
background-color: #c78b07;
border-radius: 4px;
color: black;
}
tr.active-cue .time {
border-color: #c78b07;
}
.list_name {
color: lightgray;
margin-left: 5px;
margin-bottom: 2px;
}
.list_container {
margin-bottom: 10px;
}
@media screen and (min-width: 0px) and (max-width: 750px) {
.hide-medium {
display: none;
}
}
@media screen and (min-width: 0px) and (max-width: 550px) {
.hide-small {
display: none;
}
}
+42
View File
@@ -0,0 +1,42 @@
<header>
<h1><%= listName %></h1>
<h2>Eos <%= data.version %></h2>
</header>
<% for(var i in data.EOS.cueLists){ %>
<div class="list_container">
<div class="list_name">List <%= i %></div>
<table cellspacing="0">
<tr>
<th width="70px">Cue</th>
<th width="30px">Int Up</th>
<th width="30px">Int Down</th>
<th width="40px" class="hide-medium">Focus</th>
<th width="40px" class="hide-medium">Color</th>
<th width="40px" class="hide-medium">Beam</th>
<th width="40px" class="hide-small">Dur</th>
<th width="20px" class="hide-small">M</th>
<th width="20px" class="hide-small">B</th>
<th width="20px" class="hide-small">A</th>
<th width="70px" class="hide-small">Fw/Hg</th>
<th width="150px">Label</th>
<th width="10px" class="hide-medium">Ext Links</th>
</tr>
<% var cues = Object.keys(data.EOS.cueLists[i]).sort(function(a, b){return Number(a)-Number(b)}) %>
<% for(var j=0; j<cues.length; j++){ %>
<% var q = data.EOS.cueLists[i][cues[j]] %>
<%= templates.cue({
q: q,
cues: cues,
cueNumber: cues[j],
isActive: (cues[j]==data.EOS.activeCue+"")
}) %>
<% } %>
</table>
</div>
<% } %>
+4
View File
@@ -0,0 +1,4 @@
<h3>Connection Requirements</h3>
<ul>
<li>Requires no password on the projector</li>
</ul>
+160
View File
@@ -0,0 +1,160 @@
const md5 = require('md5');
exports.config = {
defaultName: 'PJLink Projector',
connectionType: 'TCPsocket',
defaultPort: 4352,
mayChangePort: false,
heartbeatInterval: 5000,
heartbeatTimeout: 15000,
searchOptions: {
type: 'UDPsocket',
searchBuffer: Buffer.from([0x25, 0x32, 0x53, 0x52, 0x43, 0x48, 0x0d]),
devicePort: 4352,
listenPort: 4352,
validateResponse(msg, info) {
return msg.toString().includes('%2ACKN=');
},
},
fields: [
{
key: 'password',
label: 'Pass',
type: 'textinput',
value: '',
action(device) {
device.plugin.heartbeat(device);
},
},
],
};
exports.ready = function ready(device) {
// Power status query
// device.send("%1POWR ?\r");
};
const PJLinkCmds = [
'%1POWR=',
'%1INPT=',
'%1AVMT=',
'%1ERST=',
'%1LAMP=',
'%1NAME=',
'%1INF1=',
'%1INF2=',
'%2SNUM=',
'%2SVER=',
];
let passwordMD5 = false;
let passwordSeed = false;
function processPJLink(_device, str, that) {
const arr = str.split('%');
arr.shift();
const device = _device;
arr.forEach((response) => {
const parts = response.split('=');
const cmd = parts[0];
const value = parts[1];
switch (cmd) {
case '1POWR':
device.data.power = value;
break;
case '1INPT':
device.data.input = value;
break;
case '1AVMT':
device.data.avmute = value;
break;
case '1ERST':
device.data.fanError = value[0];
device.data.lampError = value[1];
device.data.tempError = value[2];
device.data.coverError = value[3];
device.data.filterError = value[4];
device.data.otherError = value[5];
break;
case '1LAMP':
device.data.lamp = value.split(' ');
break;
case '1NAME':
device.data.name = value;
that.deviceInfoUpdate(device, 'defaultName', device.data.name);
break;
case '1INF1':
device.data.info1 = value;
break;
case '1INF2':
device.data.info2 = value;
break;
case '2SNUM':
device.data.serial = value;
break;
case '2SVER':
device.data.version = value;
break;
default:
break;
}
});
device.draw();
}
exports.data = function data(_device, message) {
const device = _device;
this.deviceInfoUpdate(device, 'status', 'ok');
const msg = message.toString();
if (msg.substring(0, 8) === 'PJLINK 1') {
passwordSeed = msg.substring(9, 17);
passwordMD5 = md5(`${passwordSeed}${device.fields.password}`);
device.data.authentication = 'ON';
device.send(
`${passwordMD5}%1POWR ?\r%1INPT ?\r%1AVMT ?\r%1ERST ?\r%1LAMP ?\r%1NAME ?\r%1INF1 ?\r%1INF2 ?\r%2SNUM ?\r%2SVER ?\r`
);
device.draw();
} else if (msg.substring(0, 8) === 'PJLINK 0') {
device.data.authentication = 'OFF';
device.draw();
} else if (msg.startsWith('PJLINK ERRA')) {
device.data.passwordOK = false;
device.draw();
}
if (PJLinkCmds.includes(msg.substring(0, 7))) {
processPJLink(device, msg, this);
device.data.passwordOK = true;
}
};
exports.heartbeat = function heartbeat(device) {
passwordMD5 = md5(`${passwordSeed}${device.fields.password}`);
if (device.fields.password.length > 0 && device.data.passwordOK) {
device.send(
`${passwordMD5}%1POWR ?\r%1INPT ?\r%1AVMT ?\r%1ERST ?\r%1LAMP ?\r%1NAME ?\r%1INF1 ?\r%1INF2 ?\r%2SNUM ?\r%2SVER ?\r`
);
} else if (device.fields.password.length > 0) {
device.send(`${passwordMD5}%1POWR ?\r`);
} else if (device.data.passwordOK) {
device.send(`%1POWR ?\r%1INPT ?\r%1AVMT ?\r%1ERST ?\r%1LAMP ?\r%1NAME ?\r%1INF1 ?\r%1INF2 ?\r%2SNUM ?\r%2SVER ?\r`);
} else {
device.send(`%1POWR ?\r`);
}
device.draw();
};
-10
View File
@@ -1,10 +0,0 @@
.warning{
color: #e9873a;
}
.error{
color: #ed5f5d;
}
.ok{
color: #79b757;
}
-116
View File
@@ -1,116 +0,0 @@
exports.defaultName = "PJLink Projector";
exports.connectionType = "TCPsocket";
exports.heartbeatInterval = 5000;
exports.searchOptions = {
type: "UDPsocket",
searchBuffer: Buffer.from([0x25, 0x32, 0x53, 0x52, 0x43, 0x48, 0x0d]),
devicePort: 4352,
listenPort: 4352,
validateResponse: function(msg, info){
return (msg.toString().indexOf("%2ACKN=")>=0);
}
}
exports.defaultPort = 4352;
exports.ready = function(device){
//Power status query
//device.send("%1POWR ?\r");
}
var password = false;
exports.data = function(device, message){
this.deviceInfoUpdate(device, "status", "ok");
var msg = message.toString();
const md5 = require("md5");
if(msg.substring(0, 8)=="PJLINK 1"){
password = md5(msg.substring(9, 17)+"JBMIAProjectorLink")
device.send(password+"%1POWR ?\r%1INPT ?\r%1AVMT ?\r%1ERST ?\r%1LAMP ?\r%1NAME ?\r%1INF1 ?\r%1INF2 ?\r%2SNUM ?\r%2SVER ?\r");
}
if(msg.substring(0, 7)=="%1POWR="){
processPJLink(device, msg, this);
}
if(msg.substring(0, 7)=="%1INPT="){
processPJLink(device, msg, this);
}
if(msg.substring(0, 7)=="%1AVMT="){
processPJLink(device, msg, this);
}
if(msg.substring(0, 7)=="%1ERST="){
processPJLink(device, msg, this);
}
if(msg.substring(0, 7)=="%1LAMP="){
processPJLink(device, msg, this);
}
if(msg.substring(0, 7)=="%1NAME="){
processPJLink(device, msg, this);
}
if(msg.substring(0, 7)=="%1INF1="){
processPJLink(device, msg, this);
}
if(msg.substring(0, 7)=="%1INF2="){
processPJLink(device, msg, this);
}
if(msg.substring(0, 7)=="%2SNUM="){
processPJLink(device, msg, this);
}
if(msg.substring(0, 7)=="%2SVER="){
processPJLink(device, msg, this);
}
}
function processPJLink(device, str, that){
var arr = str.split("%");
for(var key in arr){
var split = arr[key].split("=");
var key = split[0];
var value = split[1];
switch(key){
case "1POWR":
device.data.power = value;
break;
case "1INPT":
device.data.input = value;
break;
case "1AVMT":
device.data.avmute = value;
break;
case "1ERST":
device.data.fanError = value[0];
device.data.lampError = value[1];
device.data.tempError = value[2];
device.data.coverError = value[3];
device.data.filterError = value[4];
device.data.otherError = value[5];
break;
case "1LAMP":
device.data.lamp = value.split(" ");
break;
case "1NAME":
device.data.name = value;
that.deviceInfoUpdate(device, "defaultName", device.data.name);
break;
case "1INF1":
device.data.info1 = value;
break;
case "1INF2":
device.data.info2 = value;
break;
case "2SNUM":
device.data.serial = value;
break;
case "2SVER":
device.data.version = value;
break;
}
}
device.draw();
}
exports.heartbeat = function(device){
if(password){
device.send(password+"%1POWR ?\r%1INPT ?\r%1AVMT ?\r%1ERST ?\r%1LAMP ?\r%1NAME ?\r%1INF1 ?\r%1INF2 ?\r%2SNUM ?\r%2SVER ?\r");
}
}
+13
View File
@@ -0,0 +1,13 @@
.warning {
color: #e9873a;
}
.error {
color: #ed5f5d;
}
.ok {
color: #79b757;
}
table {
margin-bottom: 30px;
width: 350px;
}
@@ -2,7 +2,6 @@
<h1><%= listName %></h1>
</header>
<table class="cv-table">
<tr>
<td>Power</td>
@@ -73,7 +72,6 @@
<% } %>
<% } %>
<tr>
<td>Name</td>
<td><div><%= data.name %></div></td>
@@ -94,4 +92,19 @@
<td>Version</td>
<td><div><%= data.version %></div></td>
</tr>
</table>
<table class="cv-table">
<tr>
<td>Authentication</td>
<td>
<% if(!data.passwordOK){ %>
<div class='error'>INCORRECT PASSWORD</div>
<% }else if(data.authentication=="ON"){ %>
<div class='ok'>ON</div>
<% }else{ %>
<div class='error'><%= data.authentication %></div>
<% } %>
</td>
</tr>
</table>
+45
View File
@@ -0,0 +1,45 @@
<div id="<%= cueList.uniqueID %>">
<h3><%= cueList.displayName %></h3>
<%
let width = 100/cueList.cartColumns;
let height = 600/(cueList.cartRows);
%>
<div class="cart">
<% for(let r=0; r<cueList.cartRows; r++){ %>
<% for(let c=0; c<cueList.cartColumns; c++){ %>
<% let style = `style="left:${width*c}%; top:${height*r}px; width:${width }%; height:${height}px;"`; %>
<div class="cartCueWrapper" <%= style %>><div class="cartCue cartBlank"></div></div>
<% } %>
<% } %>
<% for(var c in cueList.cues){
let cue = cueList.cues[c];
if(!cue.cartPosition){
cue.cartPosition = [0, 0];
}
let row = cue.cartPosition[0]-1;
let col = cue.cartPosition[1]-1;
let colPos = width*col;
let rowPos = height*row;
if(col>=cueList.cartColumns || row>=cueList.cartRows){
continue;
}
%>
<%= tileTemplate({cue: allCues[cue.uniqueID], allCues: allCues}) %>
<% } %>
</div>
</div>
+190
View File
@@ -0,0 +1,190 @@
<% let playbackPosition = workspace.playbackPosition==cue.uniqueID ? "playback-position" : "" %>
<tr class="q-<%= cue.colorName %> q-armed-<%= cue.armed %> <%= playbackPosition %>" id="<%= cue.uniqueID %>">
<td></td> <!-- playhead/color indicator -->
<td><center>
<% if(cue.broken){ %>
<img src="./plugins/qlab/img/status_broken.png" height="18px">
<% }else if(cue.running){ %>
<img src="./plugins/qlab/img/status_running.png" height="18px">
<% }else if(cue.paused){ %>
<img src="./plugins/qlab/img/status_paused.png" height="18px">
<% }else if(cue.loaded){ %>
<img src="./plugins/qlab/img/status_loaded.png" height="18px">
<% }else if(cue.flagged){ %>
<img src="./plugins/qlab/img/status_flagged.png" height="20px">
<% }else{ %>
<% } %>
</center></td>
<% if(cue.type == "Group" && cue.groupMode){ %>
<td><img src="./plugins/qlab/img/v5/group-<%= cue.groupMode %>.png" height="20px"></td>
<% }else{ %>
<td><img src="./plugins/qlab/img/<%= cue.type.toLowerCase().replace(' ','-') %>.png" height="20px"></td>
<% } %>
<td><center><%= cue.number || "&nbsp;" %></center></td>
<% for(let i=0; i<cue.nestedGroupModes.length; i++){ %>
<% if(cue.cues && cue.cues.length==0 && i==cue.nestedGroupModes.length-1){ %>
<td class="gMode-<%= cue.nestedGroupModes[i] %> gLeft gBot gTop group-arrow"></td>
<% }else if(cue.cues && i==cue.nestedGroupModes.length-1){ %>
<td class="gMode-<%= cue.nestedGroupModes[i] %> gLeft gTop group-arrow"></td>
<% }else if(cue.nestedGroupPosition[i]<=0){ %>
<% if(i<cue.nestedGroupModes.length-1){ %>
<td class="gMode-<%= cue.nestedGroupModes[i] %> gLeft gBot"></td>
<% }else{ %>
<td class="gMode-<%= cue.nestedGroupModes[i] %> gBot"></td>
<% } %>
<% }else if(i<cue.nestedGroupModes.length-1){ %>
<td class="gMode-<%= cue.nestedGroupModes[i] %> gLeft"></td>
<% }else{ %>
<td class="gMode-<%= cue.nestedGroupModes[i] %>"></td>
<% } %>
<% } %>
<!-- end spacer cells -->
<% if(cue.type=="Group"){ %>
<% if(cue.cues.length==0){ %>
<td id="cue-name" colspan="<%= 11-cue.nestedGroupModes.length %>" class="gMode-<%= cue.groupMode %> gTop gBot gRight"><%= cue.listName %></td>
<% }else{ %>
<td id="cue-name" colspan="<%= 11-cue.nestedGroupModes.length %>" class="gMode-<%= cue.groupMode %> gTop gRight"><%= cue.listName %></td>
<% } %>
<% }else if(cue.nestedGroupPosition[1]==0){ %>
<td colspan="<%= 11-cue.nestedGroupModes.length %>" class="cue-name gMode-<%= cue.nestedGroupModes[cue.nestedGroupModes.length-1] %> gBot gRight"><%= cue.listName %></td>
<% }else{ %>
<td colspan="<%= 11-cue.nestedGroupModes.length %>" class="cue-name gMode-<%= cue.nestedGroupModes[cue.nestedGroupModes.length-1] %> gRight"><%= cue.listName %></td>
<% } %>
<td class="q-time hide-medium"><%= cue.target %></td>
<% if(cue.preWait){ %>
<td class="q-time hide-small"><%= elapsedTime(cue.preWait, cue.preWaitElapsed, "preWait", cue) %></td>
<% }else{ %>
<td class="q-time q-gray-text hide-small">00:00.00</td>
<% } %>
<% let cueTypesWithAction = ["Audio", "Mic", "Video", "Camera", "Text", "Light", "Fade", "Network", "MIDI File", "Timecode", "Wait"]; %>
<% if((cue.type=="Group" && cue.groupMode==3) || cueTypesWithAction.indexOf(cue.type)>=0){ %>
<td class="q-time hide-medium"><%= elapsedTime(cue.duration, cue.actionElapsed, "action", cue) %></td>
<% }else{ %>
<td class="hide-medium"></td>
<% } %>
<% if(cue.postWait){ %>
<td class="q-time hide-medium"><%= elapsedTime(cue.postWait, cue.postWaitElapsed, "postWait", cue) %></td>
<% }else{ %>
<td class="q-time q-gray-text hide-medium">00:00.00</td>
<% } %>
<% if(cue.continueMode==2){ %>
<td class="hide-small"><center><img src="plugins/qlab/img/auto_follow.png" height="20px"></center></td>
<% }else if(cue.continueMode==1){ %>
<td class="hide-small"><center><img src="plugins/qlab/img/auto_continue.png" height="20px"></center></td>
<% }else{ %>
<td class="hide-small"></td>
<% } %>
</tr>
<%
function prettyFormatTime(seconds){
if(!seconds){
return "00:00.00";
}
var startIndex = 14;
if(seconds>=3600){
startIndex = 11;
}
var string = new Date(seconds * 1000).toISOString()
return string.substring(startIndex, string.length-2)
}
function elapsedTime(def, value, type, cue){
if(type=="action" && cue.paused){
value-=cue.preWait;
}
let border, fill;
if(cue.paused){
border = "#f6e737";
fill = "rgba(255, 240, 60, 0.7)";
}else{
border = "#48ba41";
fill = "rgba(0, 200, 50, 0.5)";
}
if(value>0){
let percent = value/def*100;
let bg = `style='background: black; background: linear-gradient(90deg, ${fill} ${percent}%, transparent ${percent}%);`;
if(cue.paused){
bg+= "outline-color: "+border+";";
bg+= "color: white;";
}
bg+="'";
return `<div class='q-time-elapsed' ${bg}>${prettyFormatTime(Math.min(def, value))}</span>`;
}else if(type=="postWait" || type=="action"){
return `<span class='q-gray-text'>${prettyFormatTime(def)}</span>`;
}else{
return prettyFormatTime(def);
}
}
%>
+52
View File
@@ -0,0 +1,52 @@
<h3 id="<%= cueList.uniqueID %>"><%= workspace.displayName %> &mdash; <%= cueList.displayName %></h3>
<table cellspacing="0">
<tr>
<th width="1px" style="border-left: none; border-right: none;"></th>
<th width="20px" style="border-left: none; border-right: none;"></th>
<th width="20px" style="border-left: none;"></th>
<th width="55px">Number</th>
<th width="25px" style="padding: 0px"></th>
<th width="25px" class="no-border">Q</th>
<th width="25px" class="no-border"></th>
<th width="25px" class="no-border"></th>
<th width="25px" class="no-border"></th>
<th width="25px" class="no-border"></th>
<th width="25px" class="no-border"></th>
<th width="25px" class="no-border"></th>
<th width="25px" class="no-border"></th>
<th width="25px" class="no-border"></th>
<th class="no-border"></th>
<th width="55px" class="hide-medium">Target</th>
<th width="80px" class="hide-small"style="border-right: none;">Pre Wait</th>
<th width="80px" class="hide-medium" style="border-left: none; border-right: none;">Action</th>
<th width="80px" class="hide-medium" style="border-left: none; border-right: none;">Post Wait</th>
<th width="20px" class="hide-small" style="border-left: none; border-right: none;">
<img src="plugins/qlab/img/auto_continue_stubby.png" height="20px">
</th>
</tr>
<tbody id="">
<% for(const cue_id in cueList.cues){ %>
<%= displayCueRow(cueList.cues[cue_id]) %>
<% } %>
</tbody>
</table>
<%
function displayCueRow(q){
let html = rowTemplate({cue: allCues[q.uniqueID], allCues: allCues, workspace: workspace});
if(q.cues){
q.cues.forEach(q =>{
html += displayCueRow(q);
})
}
return html;
}
%>
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

+5
View File
@@ -0,0 +1,5 @@
<h3>Connection Requirements</h3>
<ul>
<li><em>View</em> permission enabled in OSC Access</li>
<li>Provide passcode if necessary</li>
</ul>
+416
View File
@@ -0,0 +1,416 @@
const _ = require('lodash');
const fs = require('fs');
const path = require('path');
exports.config = {
defaultName: 'QLab',
connectionType: 'osc',
defaultPort: 53000,
mayChangePort: true,
heartbeatInterval: 100,
heartbeatTimeout: 5000,
searchOptions: {
type: 'Bonjour',
bonjourName: 'qlab',
},
fields: [
{
key: 'passcode',
label: 'Pass',
type: 'textinput',
value: '',
action(device) {
device.send('/workspaces');
},
},
],
};
let lastElapsedUpdate = Date.now();
let interval = 5;
let heartbeatCount = 0;
const valuesForKeysString =
'["uniqueID","number","name","listName","isBroken","isRunning","isLoaded","isFlagged",' +
'"type","children","preWait","postWait","currentDuration","colorName","continueMode",' +
'"mode","parent","cartRows","cartColumns","cartPosition","displayName","preWaitElapsed",' +
'"actionElapsed","postWaitElapsed","isPaused"]';
exports.ready = function ready(_device) {
const device = _device;
device.templates = {
cue: _.template(fs.readFileSync(path.join(__dirname, `cue.ejs`))),
tile: _.template(fs.readFileSync(path.join(__dirname, `tile.ejs`))),
cart: _.template(fs.readFileSync(path.join(__dirname, `cart.ejs`))),
cuelist: _.template(fs.readFileSync(path.join(__dirname, `cuelist.ejs`))),
};
device.send(`/version`);
device.send('/workspaces');
};
exports.data = function data(_device, oscData) {
const device = _device;
const oscAddressParts = oscData.address.split('/');
oscAddressParts.shift();
if (match(oscAddressParts, ['reply', 'version'])) {
const json = JSON.parse(oscData.args[0]);
device.data.version = json.data;
this.deviceInfoUpdate(device, 'status', 'ok');
} else if (match(oscAddressParts, ['reply', 'workspaces'])) {
const json = JSON.parse(oscData.args[0]);
device.data.workspaces = {};
json.data.forEach((wksp) => {
device.data.workspaces[wksp.uniqueID] = {
uniqueID: wksp.uniqueID,
displayName: wksp.displayName,
cueLists: {},
cues: {},
};
device.send(`/workspace/${wksp.uniqueID}/connect`, device.fields.passcode);
});
if (Object.keys(device.data.workspaces).length === 0) {
device.data.permission = 'no workspaces';
device.draw();
}
} else if (match(oscAddressParts, ['reply', 'workspace', '*', 'connect'])) {
device.send(`/workspace/${oscAddressParts[2]}/updates`, [{ type: 'i', value: 1 }]);
device.send(`/workspace/${oscAddressParts[2]}/cueLists`);
} else if (match(oscAddressParts, ['reply', 'workspace', '*', 'cueLists'])) {
this.deviceInfoUpdate(device, 'status', 'ok');
const workspace = device.data.workspaces[oscAddressParts[2]];
const json = JSON.parse(oscData.args[0]);
workspace.cueLists = {};
workspace.cues = {};
if (json.status === 'denied') {
device.data.permission = 'denied';
device.draw();
} else if (json.data) {
device.data.permission = 'ok';
json.data.forEach((cueList) => {
workspace.cueLists[cueList.uniqueID] = cueList;
addCueToWorkspace(workspace, cueList);
});
device.draw();
setTimeout(() => {
json.data.forEach((ql) => {
workspace.cueLists[ql.uniqueID] = ql;
getValuesForKeys(device, json.workspace_id, ql);
});
}, 0);
}
} else if (
match(oscAddressParts, ['reply', 'cue_id', '*', 'children']) ||
match(oscAddressParts, ['reply', 'workspace', '*', 'cue_id', '*', 'children'])
) {
const json = JSON.parse(oscData.args[0]);
const workspace = device.data.workspaces[json.workspace_id];
const cueID = json.address.substring(55, 91);
const cue = workspace.cues[cueID];
if (!_.isEqual(cue.cues, json.data)) {
workspace.cueLists[cueID].cues = json.data;
addCueToWorkspace(workspace, workspace.cueLists[cueID]);
device.draw();
getValuesForKeys(device, json.workspace_id, cue);
}
} else if (
match(oscAddressParts, ['reply', 'cue_id', '*', 'valuesForKeys']) ||
match(oscAddressParts, ['reply', 'workspace', '*', 'cue_id', '*', 'valuesForKeys'])
) {
const json = JSON.parse(oscData.args[0]);
const cueValues = json.data;
const workspace = device.data.workspaces[json.workspace_id];
let cue = workspace.cues[cueValues.uniqueID];
if (!cue) {
workspace.cues[cueValues.uniqueID] = {};
cue = workspace.cues[cueValues.uniqueID];
}
cue.uniqueID = cueValues.uniqueID;
cue.number = cueValues.number;
cue.name = cueValues.name;
cue.listName = cueValues.listName;
cue.displayName = cueValues.displayName;
cue.broken = cueValues.isBroken;
cue.running = cueValues.isRunning;
cue.loaded = cueValues.isLoaded;
cue.flagged = cueValues.isFlagged;
cue.paused = cueValues.isPaused;
cue.type = cueValues.type;
// cue.cues = cueValues.children;
cue.preWait = cueValues.preWait;
cue.postWait = cueValues.postWait;
cue.duration = cueValues.currentDuration;
cue.colorName = cueValues.colorName;
cue.continueMode = cueValues.continueMode;
cue.groupMode = cueValues.mode;
cue.parent = cueValues.parent;
cue.cartRows = cueValues.cartRows;
cue.cartColumns = cueValues.cartColumns;
cue.cartPosition = cueValues.cartPosition;
cue.preWaitElapsed = cueValues.preWaitElapsed;
cue.actionElapsed = cueValues.actionElapsed;
cue.postWaitElapsed = cueValues.postWaitElapsed;
// QLab 5 fix
if (cueValues.type === 'Group' || cueValues.type === 'Cue List') {
cue.cues = cueValues.children;
} else {
cue.cues = undefined;
}
const nestedGroupModes = [];
const nestedGroupPosition = [];
let obj = cue;
let sum = 0;
if (obj.cues) {
sum += obj.cues.length;
}
while (obj.parent !== '[root group of cue lists]') {
let pos = _.findIndex(workspace.cues[obj.parent].cues, {
uniqueID: obj.uniqueID,
});
pos = Math.abs(pos - workspace.cues[obj.parent].cues.length) - 1;
if (obj.cues === undefined) {
sum += pos;
}
nestedGroupPosition.unshift(sum);
if (obj.cues) {
sum += pos;
nestedGroupModes.unshift(obj.groupMode);
} else {
nestedGroupModes.unshift(workspace.cues[obj.parent].groupMode);
}
obj = workspace.cues[obj.parent];
}
cue.nestedGroupModes = nestedGroupModes;
cue.nestedGroupPosition = nestedGroupPosition;
device.update('updateCueData', {
cue,
allCues: workspace.cues,
workspace,
});
} else if (match(oscAddressParts, ['reply', 'cue_id', '*', 'preWaitElapsed'])) {
const oscArgs = JSON.parse(oscData.args[0]);
if (oscArgs.status !== 'error') {
const workspace = device.data.workspaces[oscArgs.workspace_id];
if (workspace) {
const cue = workspace.cues[oscArgs.address.substring(55, 91)];
if (cue) {
cue.preWaitElapsed = oscArgs.data;
lastElapsedUpdate = Date.now();
device.update('updateCueData', {
cue,
allCues: workspace.cues,
workspace,
});
}
}
}
} else if (match(oscAddressParts, ['reply', 'cue_id', '*', 'actionElapsed'])) {
const oscArgs = JSON.parse(oscData.args[0]);
if (oscArgs.status !== 'error') {
const workspace = device.data.workspaces[oscArgs.workspace_id];
if (workspace) {
const cue = workspace.cues[oscArgs.address.substring(55, 91)];
if (cue) {
cue.actionElapsed = oscArgs.data;
lastElapsedUpdate = Date.now();
device.update('updateCueData', {
cue,
allCues: workspace.cues,
workspace,
});
}
}
}
} else if (match(oscAddressParts, ['reply', 'cue_id', '*', 'postWaitElapsed'])) {
const oscArgs = JSON.parse(oscData.args[0]);
if (oscArgs.status !== 'error') {
const workspace = device.data.workspaces[oscArgs.workspace_id];
if (workspace) {
const cue = workspace.cues[oscArgs.address.substring(55, 91)];
if (cue) {
cue.postWaitElapsed = oscArgs.data;
lastElapsedUpdate = Date.now();
device.update('updateCueData', {
cue,
allCues: workspace.cues,
workspace,
});
}
}
}
} else if (match(oscAddressParts, ['update', 'workspace', '*', 'cue_id', '*'])) {
const workspace = device.data.workspaces[oscAddressParts[2]];
if (workspace) {
const cueLists = Object.keys(workspace.cueLists);
const cueID = oscAddressParts[4];
if (cueID !== '[root group of cue lists') {
if (cueLists.includes(cueID)) {
device.send(`/workspace/${workspace.uniqueID}/cue_id/${cueID}/children`);
}
device.send(`/workspace/${oscAddressParts[2]}/cue_id/${cueID}/valuesForKeys`, [
{ type: 's', value: valuesForKeysString },
]);
}
}
} else if (match(oscAddressParts, ['update', 'workspace', '*'])) {
// occurs when cue lists are reordered or a list is deleted
device.send(`/workspace/${oscAddressParts[2]}/cueLists`);
} else if (match(oscAddressParts, ['update', 'workspace', '*', 'dashboard'])) {
// this workspace might be new, let's check
if (device.data.workspaces[oscAddressParts[2]] === undefined) {
device.send('/workspaces');
}
} else if (match(oscAddressParts, ['update', 'workspace', '*', 'cueList', '*', 'playbackPosition'])) {
const workspace = device.data.workspaces[oscAddressParts[2]];
if (workspace) {
const cue = workspace.cues[oscData.args[0]];
if (cue) {
workspace.playbackPosition = oscData.args[0] ? cue.uniqueID : '';
device.update('updatePlaybackPosition', { cue });
}
}
} else if (match(oscAddressParts, ['update', 'workspace', '*', 'disconnect'])) {
delete device.data.workspaces[oscAddressParts[2]];
if (Object.keys(device.data.workspaces).length === 0) {
device.data.permission = 'no workspaces';
}
device.draw();
} else if (match(oscAddressParts, ['reply', 'thump'])) {
// intermittent connection check even if nothing's happening
} else {
// console.log(address)
}
};
exports.update = function update(device, doc, updateType, data) {
if (updateType === 'updateCueData') {
const $elem = doc.getElementById(data.cue.uniqueID);
if ($elem) {
if (data.cue.type === 'Cue List') {
$elem.outerHTML = `<h3>${data.workspace.displayName} &mdash; ${data.cue.name}</h3>`;
} else if (data.cue.type === 'Cart') {
$elem.outerHTML = device.templates.cart({
tileTemplate: device.templates.tile,
cueList: data.cue,
allCues: data.workspace.cues,
});
} else if (data.cue.cartPosition && data.cue.cartPosition[0] !== 0) {
// checking that the parent cue is a cart cue
const parentCue = data.workspace.cues[data.cue.parent];
if (parentCue && parentCue.type === 'Cart') {
$elem.outerHTML = device.templates.tile(data);
} else {
$elem.outerHTML = device.templates.cue(data);
}
} else {
$elem.outerHTML = device.templates.cue(data);
}
}
} else if (updateType === 'updatePlaybackPosition') {
Array.from(doc.getElementsByClassName('playback-position')).forEach(($elem, index, array) => {
$elem.classList.remove('playback-position');
});
const $elem = doc.getElementById(data.cue.uniqueID);
$elem.classList.add('playback-position');
$elem.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
};
function addCueToWorkspace(_workspace, cue) {
const workspace = _workspace;
workspace.cues[cue.uniqueID] = cue;
workspace.cues[cue.uniqueID].nestedGroupModes = [];
workspace.cues[cue.uniqueID].nestedGroupPosition = [];
if (cue.cues) {
// this cue has children so add them as well
cue.cues.forEach((childCue) => {
addCueToWorkspace(workspace, childCue);
});
}
}
function getValuesForKeys(device, workspaceID, cue) {
device.send(`/workspace/${workspaceID}/cue_id/${cue.uniqueID}/valuesForKeys`, [
{ type: 's', value: valuesForKeysString },
]);
if (cue.cues) {
cue.cues.forEach((childCue) => {
getValuesForKeys(device, workspaceID, childCue);
});
}
}
function match(testArray, patternArray) {
let out = true;
if (testArray.length !== patternArray.length) {
return false;
}
patternArray.forEach((patternPart, i) => {
if (testArray[i] !== patternPart && patternPart !== '*') {
out = false;
}
});
return out;
}
exports.heartbeat = function heartbeat(device) {
heartbeatCount++;
if (Date.now() - lastElapsedUpdate > 300) {
interval = 24;
} else {
interval = 1;
}
if (heartbeatCount % 20 === 0 && device.data.workspaces && Object.keys(device.data.workspaces).length === 0) {
device.send(`/version`);
device.send('/workspaces');
}
if (heartbeatCount % 16 === 0) {
device.send(`/thump`);
}
if (heartbeatCount % interval === 0 && device.data.workspaces && Object.keys(device.data.workspaces).length > 0) {
device.send(`/cue_id/active/preWaitElapsed`);
device.send(`/cue_id/active/actionElapsed`);
device.send(`/cue_id/active/postWaitElapsed`);
}
};
-189
View File
@@ -1,189 +0,0 @@
table{
background-color: #323232;
border: 0px;
color: #b8b8b8;
font-family: sans-serif;
width: 100%;
table-layout:fixed;
margin-bottom: 30px;
}
th{
font-size: 12px;
font-weight: normal;
padding: 0px 4px;
background-color: #2d2c2d;
border: #545454 1px solid;
border-right: none;
}
th.no-border{
border-left: none;
border-right: none;
padding: 0px;
}
td{
padding: 0px;
height: 25px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
td img{
padding-top: 4px;
}
tr:nth-child(even) {
background: #2c2c2c;
}
tr:nth-child(odd) {
background: #323232;
}
tr:nth-child(even).q-red{
background: linear-gradient(to top, #2c2c2c, #4c3130 1px);
}
tr:nth-child(odd).q-red{
background: linear-gradient(to top, #323232, #513635 1px);
}
.q-red td:first-of-type{
padding-left: 5px;
border-left: 5px #ff4242 solid;
}
tr:nth-child(even).q-orange{
background: linear-gradient(to top, #2c2c2c, #4b3e2a 1px);
}
tr:nth-child(odd).q-orange{
background: linear-gradient(to top, #323232, #514330 1px);
}
.q-orange td:first-of-type{
padding-left: 5px;
border-left: 5px #ffa500 solid;
}
tr:nth-child(even).q-green{
background: linear-gradient(to top, #2c2c2c, #2d452d 1px);
}
tr:nth-child(odd).q-green{
background: linear-gradient(to top, #323232, #324b33 1px);
}
.q-green td:first-of-type{
padding-left: 5px;
border-left: 5px #01d52f solid;
}
tr:nth-child(even).q-blue{
background: linear-gradient(to top, #2c2c2c, #323647 1px);
}
tr:nth-child(odd).q-blue{
background: linear-gradient(to top, #323232, #363b4b 1px);
}
.q-blue td:first-of-type{
padding-left: 5px;
border-left: 5px #536de0 solid;
}
tr:nth-child(even).q-purple{
background: linear-gradient(to top, #2c2c2c, #39273d 1px);
}
tr:nth-child(odd).q-purple{
background: linear-gradient(to top, #323232, #433147 1px);
}
.q-purple td:first-of-type{
padding-left: 5px;
border-left: 5px #a601c0 solid;
}
tr.q-armed-false td{
background: url("img/disarmed-pattern-light.png");
background-attachment: fixed;
}
tr.playback-position{
background-image: none !important;
background: #1557da !important;
color: white !important;
}
tr.playback-position .q-gray-text{
color: white !important;
}
.gLeft{
height: 24px;
width: 12px;
border-left: 2px solid;
}
.gTop{
border-top: 2px solid;
}
.gBot{
border-bottom: 2px solid;
}
.gRight{
border-right: 2px solid;
}
.gMode-1.gLeft.gTop{
border-radius: 6px 0px 0px 0px;
}
.gMode-1.gLeft.gBot{
border-radius: 0px 0px 0px 6px;
}
.gMode-1.gRight.gBot{
border-radius: 0px 0px 6px 0px;
}
.gMode-1.gLeft.gTop.gBot{
border-radius: 6px 0px 0px 6px;
}
.gMode-1.gLeft.gTop.gRight{
border-radius: 6px 6px 0px 0px;
}
.gMode-1.gTop.gRight.gBot{
border-radius: 0px 6px 6px 0px;
}
.gMode-1.gLeft.gTop.gRight.gBot{
border-radius: 6px;
}
.gMode-1{
border-color: #5a5a87;
}
.gMode-2{
border-color: #5a5a87;
}
.gMode-3{
border-color: #429242;
}
.gMode-4{
border-color: #925fc0;
}
.gMode-, .gMode-0{
border-color: rgba(0, 0, 0, 0);
}
.group-arrow{
width: 13px;
background-image: url("img/arrow-down.png");
background-position: center;
background-repeat: no-repeat;
}
.q-time{
font-size: 14px;
text-align: center;
}
.q-gray-text{
color: #424242;
}
@media screen and (min-width: 0px) and (max-width: 750px) {
.hide-medium{
display: none;
}
}
@media screen and (min-width: 0px) and (max-width: 550px) {
.hide-small{
display: none;
}
}
-196
View File
@@ -1,196 +0,0 @@
<% function cueTemplate(cueList, groupModes, depth, workspace){ %>
<% depth++ %>
<% for(var c in cueList.cues){ %>
<% var cue = cueList.cues[c] %>
<tr class="q-<%= cue.colorName %> q-armed-<%= cue.armed %> <%if(data.playbackPosition==cue.uniqueID){ %>playback-position scroll-position<% } %>" id="<%= cue.uniqueID %>">
<td></td> <!-- playhead/color indicator -->
<% if(cue.broken){ %>
<td><center><img src="plugins/qlab/img/status_broken.png" height="18px"></center></td>
<% }else if(cue.running){ %>
<td><center><img src="plugins/qlab/img/status_running.png" height="18px"></center></td>
<% }else if(cue.loaded){ %>
<td><center><img src="plugins/qlab/img/status_loaded.png" height="18px"></center></td>
<% }else if(cue.flagged){ %>
<td><center><img src="plugins/qlab/img/status_flagged.png" height="20px"></center></td>
<% }else{ %>
<td></td>
<% } %>
<td><img src="plugins/qlab/img/<%= cue.type.toLowerCase() %>.png" height="20px"></td>
<td><center><%- cue.number || "&nbsp;" %></center></td>
<%
var nextCueDepth = 0;
if(workspace.allCuesOrdered[cue.index+1]!=undefined){
nextCueDepth = workspace.allCuesOrdered[cue.index+1].groups.length-1;
}
%>
<!-- spacer cells before cue name -->
<% for(var i=0; i<depth; i++){ %>
<% if(i==depth-1 && cue.type=="Group"){ %>
<% if(cue.cues.length==0){ %>
<td width="15px" class="group-arrow gMode-<%= cue.mode %> gLeft gTop gBot"></td>
<% }else{ %>
<td width="15px" class="group-arrow gMode-<%= cue.mode %> gLeft gTop"></td>
<% } %>
<% }else if(i<nextCueDepth){ %>
<td width="15px" class="gMode-<%= groupModes[i] %> gLeft"></td>
<% }else if(i>=nextCueDepth && i<depth-1 ){ %>
<td width="15px" class="gMode-<%= groupModes[i] %> gLeft gBot"></td>
<% }else if(i==depth-1 && i>nextCueDepth){ %>
<td width="15px" class="gMode-<%= groupModes[groupModes.length-1] %> gBot"></td>
<% }else{ %>
<td width="15px" ></td>
<% } %>
<% } %>
<!-- cue name -->
<% if(cue.type=="Group"){ %>
<% if(cue.cues.length==0){ %>
<td colspan="<%= 11-depth %>" class="gMode-<%= cue.mode %> gTop gBot gRight"><%= cue.listName %></td>
<% }else{ %>
<td colspan="<%= 11-depth %>" class="gMode-<%= cue.mode %> gTop gRight"><%= cue.listName %></td>
<% } %>
<% }else if(depth>nextCueDepth+1){ %>
<td colspan="<%= 11-depth %>" class="gMode-<%= groupModes[groupModes.length-1] %> gBot gRight"><%= cue.listName %></td>
<% }else if(depth>1){ %>
<td colspan="<%= 11-depth %>" class="gMode-<%= groupModes[groupModes.length-1] %> gRight"><%= cue.listName %></td>
<% }else{ %>
<td colspan="<%= 11-depth %>"><%= cue.listName %></td>
<% } %>
<td class="q-time hide-medium"><%= cue.target %></td>
<% if(cue.preWait){ %>
<td class="q-time hide-small"><%= prettyFormatTime(cue.preWait) %></td>
<% }else{ %>
<td class="q-time q-gray-text hide-small">00:00.00</td>
<% } %>
<% if(cue.duration || (cue.type=="Group" && cue.mode==3)){ %>
<% %>
<td class="q-time hide-medium"><%= prettyFormatTime(cue.duration) %></td>
<% }else{ %>
<td class="hide-medium"></td>
<% } %>
<% if(cue.postWait){ %>
<td class="q-time q-gray-text hide-medium"><%= prettyFormatTime(cue.postWait) %></td>
<% }else{ %>
<td class="q-time q-gray-text hide-medium">00:00.00</td>
<% } %>
<% if(cue.continueMode==2){ %>
<td class="hide-small"><center><img src="plugins/qlab/img/auto_follow.png" height="20px"></center></td>
<% }else if(cue.continueMode==1){ %>
<td class="hide-small"><center><img src="plugins/qlab/img/auto_continue.png" height="20px"></center></td>
<% }else{ %>
<td class="hide-small"></td>
<% } %>
</tr>
<% if(cue.cues){ %>
<% groupModes.push(cue.mode) %>
<% cueTemplate(cue, groupModes, depth, workspace) %>
<% } %>
<% } %>
<% groupModes.pop(); %>
<% } %>
<header>
<h1><%= listName %></h1>
<h2><%= data.version || "" %></h2>
</header>
<% for(var i in data.workspaces){ %>
<% for(var j in data.workspaces[i].cueLists){ %>
<% let cueList = data.workspaces[i].cueLists[j] %>
<h3><%= cueList.listName %> - <%= data.workspaces[i].displayName %></h3>
<% cueListOffset = 0 %>
<table cellspacing="0">
<tr>
<th width="1px" style="border-left: none; border-right: none;"></th>
<th width="20px" style="border-left: none; border-right: none;"></th>
<th width="20px" style="border-left: none;"></th>
<th width="55px">Number</th>
<th width="25px" style="padding: 0px"></th>
<th width="25px" class="no-border">Q</th>
<th width="25px" class="no-border"></th>
<th width="25px" class="no-border"></th>
<th width="25px" class="no-border"></th>
<th width="25px" class="no-border"></th>
<th width="25px" class="no-border"></th>
<th width="25px" class="no-border"></th>
<th width="25px" class="no-border"></th>
<th width="25px" class="no-border"></th>
<th class="no-border"></th>
<th width="55px" class="hide-medium">Target</th>
<th width="80px" class="hide-small"style="border-right: none;">Pre Wait</th>
<th width="80px" class="hide-medium" style="border-left: none; border-right: none;">Action</th>
<th width="80px" class="hide-medium" style="border-left: none; border-right: none;">Post Wait</th>
<th width="20px" class="hide-small" style="border-left: none; border-right: none;">
<img src="plugins/qlab/img/auto_continue_stubby.png" height="20px">
</th>
</tr>
<% cueTemplate(cueList, [], [], data.workspaces[i]) %>
</table>
<% } %>
<% } %>
<script>
var $scrollTo = document.getElementsByClassName("scroll-position");
if($scrollTo.length==1){
window.scroll({top: $scrollTo[0].offsetTop-200, behavior: 'smooth'});
}
</script>
<%
function prettyFormatTime(seconds){
var startIndex = 14;
if(seconds>=3600){
startIndex = 11;
}
var string = new Date(seconds * 1000).toISOString()
return string.substring(startIndex, string.length-2)
}
%>
-133
View File
@@ -1,133 +0,0 @@
exports.defaultName = "QLab 4";
exports.connectionType = "osc";
exports.searchOptions = {
type: "Bonjour",
bonjourName: "qlab"
}
exports.ready = function(device){
device.send("/workspaces");
}
exports.data = function(device, osc){
var address = osc.address;
var p = osc.address.split("/");
p.shift();
//console.log(address)
if(osc.address=="/reply/workspaces"){
const d = JSON.parse(osc.args[0]).data;
device.data.workspaces = {};
for(var i=0; i<d.length; i++){
device.data.workspaces[d[i].uniqueID] = {
"displayName": d[i].displayName,
"selectionIsPlayhead": true,
}
device.data.version = d[i].version;
device.send("/workspace/"+d[i].uniqueID+"/cueLists");
device.send("/workspace/"+d[i].uniqueID+"/updates", [{type: "i", value: 1}]);
device.send("/cue/playbackPosition/uniqueID");
device.send("/workspace/"+d[i].uniqueID+"/selectionIsPlayhead");
}
}else if(p[1]=="workspace" && p[3]=="cueLists"){
var workspace = p[2];
var d = JSON.parse(osc.args[0]).data;
device.data.workspaces[workspace].cueLists = {};
device.data.workspaces[workspace].allCues = {};
device.data.workspaces[workspace].allCuesOrdered = [];
device.data.lastCueInGroup = {};
for(var i in d){
device.data.workspaces[workspace].cueLists[d[i].uniqueID]=d[i];
device.data.workspaces[workspace].cueLists[d[i].uniqueID].cues = d[i].cues;
getMoreCueInfo(d[i], []);
function getMoreCueInfo(group, groups_arg){
var groups = JSON.parse(JSON.stringify(groups_arg))
groups.push(group.uniqueID);
for(var cueIndex in group.cues){
var cue = group.cues[cueIndex];
//console.log(cue)
device.data.lastCueInGroup[group.uniqueID] = cue;
cue.groups = groups;
device.send("/workspace/"+workspace+"/cue_id/"+cue.uniqueID+"/valuesForKeys", [{type: "s", value: "[\"mode\", \"parent\", \"isBroken\", \"preWait\", \"duration\", \"postWait\", \"continueMode\", \"cueTargetNumber\", \"isLoaded\", \"isRunning\"]"}]);
cue.index = device.data.workspaces[workspace].allCuesOrdered.push(cue)-1;
if(cue.cues){
cue.mode = 0;
getMoreCueInfo(cue, groups);
}
device.data.workspaces[workspace].allCues[cue.uniqueID] = cue;
}
}
}
//console.log(device.data.lastCueInGroup)
device.draw();
}else if(p[0]=="update" && p[1]=="workspace" && p[3]=="cue_id"){
device.send("/workspace/"+p[2]+"/cueLists");
}else if(p[0]=="update" && p[1]=="workspace" && p[5]=="playbackPosition"){
console.log('pb move')
device.data.playbackPosition = osc.args[0];
device.draw();
}else if(p[0]=="reply" && p[1]=="cue_id"){
if(p[3] == "uniqueID"){
// response to /cue/playbackPosition/uniqueID
device.data.playbackPosition = JSON.parse(osc.args[0]).data;
}else if(p[3]=="mode"){
var cueID = p[2];
var mode = JSON.parse(osc.args[0]).data;
var workspaceID = JSON.parse(osc.args[0]).workspace_id;
device.data.workspaces[workspaceID].allCues[cueID].mode = mode;
}else if(p[3]=="valuesForKeys"){
this.deviceInfoUpdate(device, "status", "ok");
var d = JSON.parse(osc.args[0]);
var cueID = p[2];
var workspace = d.workspace_id;
var cueList = d.data.parent;
device.data.workspaces[workspace].allCues[cueID].mode = d.data.mode;
device.data.workspaces[workspace].allCues[cueID].preWait = d.data.preWait;
device.data.workspaces[workspace].allCues[cueID].duration = d.data.duration;
device.data.workspaces[workspace].allCues[cueID].postWait = d.data.postWait;
device.data.workspaces[workspace].allCues[cueID].continueMode = d.data.continueMode;
device.data.workspaces[workspace].allCues[cueID].target = d.data.cueTargetNumber;
device.data.workspaces[workspace].allCues[cueID].loaded = d.data.isLoaded;
device.data.workspaces[workspace].allCues[cueID].broken = d.data.isBroken;
device.data.workspaces[workspace].allCues[cueID].running = d.data.isRunning;
}
device.draw();
}else if(p[1]=="workspace" && p[3]=="selectionIsPlayhead"){
device.data.workspaces[p[2]].selectionIsPlayhead = JSON.parse(osc.args[0]).data;
}else{
}
}
exports.heartbeat = function(device){
try{
device.send("/workspace/"+(Object.keys(device.data.workspaces)[0])+"/thump");
}catch(err){}
}
+255
View File
@@ -0,0 +1,255 @@
table {
background-color: #323232;
border: 0px;
color: #b8b8b8;
font-family: sans-serif;
width: 100%;
table-layout: fixed;
margin-bottom: 30px;
}
th {
font-size: 12px;
font-weight: normal;
padding: 0px 4px;
background-color: #2d2c2d;
border: #545454 1px solid;
border-right: none;
}
th.no-border {
border-left: none;
border-right: none;
padding: 0px;
}
td {
padding: -1px;
height: 25px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
td img {
padding-top: 2px;
}
tr:nth-child(even) {
background: #323232;
}
tr:nth-child(odd) {
background: #2c2c2c;
}
tr:nth-child(even).q-red {
background: linear-gradient(to top, #2c2c2c, #4c3130 1px);
}
tr:nth-child(odd).q-red {
background: linear-gradient(to top, #323232, #513635 1px);
}
.q-red td:first-of-type {
padding-left: 5px;
border-left: 5px #ff4242 solid;
}
tr:nth-child(even).q-orange {
background: linear-gradient(to top, #2c2c2c, #4b3e2a 1px);
}
tr:nth-child(odd).q-orange {
background: linear-gradient(to top, #323232, #514330 1px);
}
.q-orange td:first-of-type {
padding-left: 5px;
border-left: 5px #ffa500 solid;
}
tr:nth-child(even).q-green {
background: linear-gradient(to top, #2c2c2c, #2d452d 1px);
}
tr:nth-child(odd).q-green {
background: linear-gradient(to top, #323232, #324b33 1px);
}
.q-green td:first-of-type {
padding-left: 5px;
border-left: 5px #01d52f solid;
}
tr:nth-child(even).q-blue {
background: linear-gradient(to top, #2c2c2c, #323647 1px);
}
tr:nth-child(odd).q-blue {
background: linear-gradient(to top, #323232, #363b4b 1px);
}
.q-blue td:first-of-type {
padding-left: 5px;
border-left: 5px #536de0 solid;
}
tr:nth-child(even).q-purple {
background: linear-gradient(to top, #2c2c2c, #39273d 1px);
}
tr:nth-child(odd).q-purple {
background: linear-gradient(to top, #323232, #433147 1px);
}
.q-purple td:first-of-type {
padding-left: 5px;
border-left: 5px #a601c0 solid;
}
tr.q-armed-false td {
background: url('img/disarmed-pattern-light.png');
background-attachment: fixed;
}
tr.playback-position {
background-image: none !important;
background: #1557da !important;
color: white !important;
}
tr.playback-position .q-gray-text {
color: white !important;
}
.gLeft {
height: 24px;
width: 12px;
border-left: 2px solid;
}
.gTop {
border-top: 2px solid;
}
.gBot {
border-bottom: 2px solid;
}
.gRight {
border-right: 2px solid;
}
.gMode-1.gLeft.gTop {
border-radius: 6px 0px 0px 0px;
}
.gMode-1.gLeft.gBot {
border-radius: 0px 0px 0px 6px;
}
.gMode-1.gRight.gBot {
border-radius: 0px 0px 6px 0px;
}
.gMode-1.gLeft.gTop.gBot {
border-radius: 6px 0px 0px 6px;
}
.gMode-1.gLeft.gTop.gRight {
border-radius: 6px 6px 0px 0px;
}
.gMode-1.gTop.gRight.gBot {
border-radius: 0px 6px 6px 0px;
}
.gMode-1.gLeft.gTop.gRight.gBot {
border-radius: 6px;
}
.gMode-1 {
border-color: #5a5a87;
}
.gMode-2 {
border-color: #5a5a87;
}
.gMode-3 {
border-color: #429242;
}
.gMode-4 {
border-color: #925fc0;
}
.gMode-4 {
border-color: #925fc0;
}
.gMode-6 {
border-color: #d05b15;
}
.gMode-,
.gMode-0 {
border-color: rgba(0, 0, 0, 0);
}
.group-arrow {
width: 13px;
background-image: url('img/new_group_arrow.png');
background-position: center;
background-repeat: no-repeat;
}
.q-time {
font-size: 14px;
text-align: center;
}
.q-gray-text {
color: #424242;
}
.q-time-elapsed {
margin: 0px 2px;
outline: #49c042 1px solid;
line-height: 18px;
}
.cart {
position: relative;
height: 600px;
width: 100%;
background-color: #2c2b2a;
}
.cartCueWrapper {
display: block;
position: absolute;
box-sizing: border-box;
padding: 3px;
}
.cartCue {
height: 100%;
box-sizing: border-box;
border: 3px solid;
border-radius: 6px;
color: white;
}
.cartCue p {
margin: 10px;
}
.cartCueIcon {
position: absolute;
right: 13px;
top: 13px;
}
.cartColor-red {
border-color: #ff4242;
background-color: #9b3726;
}
.cartColor-orange {
border-color: #ffa500;
background-color: #ad6026;
}
.cartColor-green {
border-color: #01d52f;
background-color: #397f27;
}
.cartColor-blue {
border-color: #536de0;
background-color: #304893;
}
.cartColor-purple {
border-color: #a601c0;
background-color: #592d74;
}
.cartColor-none {
border-color: #95929f;
background-color: #3b3b3b;
}
.cartBlank {
border-color: #1f1f1f;
background-color: #1f1f1f;
}
.cartCueWrapper.playback-position .cartCue {
border-color: #88b3db;
outline: #88b3db 1px solid;
box-shadow: 0px 0px 3px 3px #88b3db, inset 0px 0px 5px #88b3db;
}
@media screen and (min-width: 0px) and (max-width: 750px) {
.hide-medium {
display: none;
}
}
@media screen and (min-width: 0px) and (max-width: 550px) {
.hide-small {
display: none;
}
}
+28
View File
@@ -0,0 +1,28 @@
<header>
<h1><%= listName %></h1>
<h2>QLab <%= data.version || "" %></h2>
</header>
<% if(data.permission=="ok"){
for(const workspace_id in data.workspaces){
const workspace = data.workspaces[workspace_id];
for(const cueList_id in workspace.cueLists){
const ql = workspace.cueLists[cueList_id]; %>
<% if(ql.type=="Cue List"){ %>
<%= templates.cuelist({cueList: ql, allCues: workspace.cues, rowTemplate: templates.cue, workspace: workspace}) %>
<% }else if(ql.type=="Cart"){ %>
<%= templates.cart({cueList: ql, allCues: workspace.cues, tileTemplate: templates.tile}) %>
<% } %>
<% }}}else if(data.permission=="no workspaces"){ %>
<h3>QLab is launched but there are no open Workspaces</h3>
<% }else if(data.permission=="denied"){ %>
<h3>Incorrect Passcode or OSC Access Permissions</h3>
<button href="https://qlab.app/docs/v5/fundamentals/workspace-settings#the-osc-access-tab">QLab Documentation: OSC Access Tab</button>
<% } %>
+37
View File
@@ -0,0 +1,37 @@
<%
let height = 0;
let width = 0;
let left = 0;
let top = 0;
if(cue.parent){
width = 100/allCues[cue.parent].cartColumns;
height = 600/allCues[cue.parent].cartRows;
top = (cue.cartPosition[0]-1)*height;
left = (cue.cartPosition[1]-1)*width;
}
let style = `style="left:${left}%; top:${top}px; width:${width }%; height:${height}px;"`;
%>
<div id="<%= cue.uniqueID %>" class="cartCueWrapper" <%= style %> >
<div class="cartCueIcon">
<% if(cue.broken){ %>
<img src="plugins/qlab/img/status_broken_white.png" height="18px">
<% }else if(cue.running){ %>
<img src="plugins/qlab/img/pause_circled.png" height="24px">
<% }else{ %>
<img src="plugins/qlab/img/play_circled.png" height="24px">
<% } %>
</div>
<div class="cartCue cartColor-<%= cue.colorName %>">
<p><%= cue.number %> &bull; <%= cue.displayName %></p>
</div>
</div>
Binary file not shown.

After

Width:  |  Height:  |  Size: 713 B

Binary file not shown.
+1
View File
@@ -0,0 +1 @@
<p><em>sACN</em> requires no configuration.</p>
+143
View File
@@ -0,0 +1,143 @@
const _ = require('lodash');
exports.config = {
defaultName: 'sACN',
connectionType: 'multicast',
defaultPort: 5568,
mayChangePort: false,
heartbeatInterval: 5000,
heartbeatTimeout: 15000,
searchOptions: {
type: 'multicast',
address: getMulticastGroup(1),
port: 5568,
validateResponse(msg, info) {
return msg.toString('utf8', 4, 13) === 'ASC-E1.17';
},
},
};
exports.ready = function ready(device) {
const d = device;
d.data.universes = {};
d.data.priorities = {};
d.data.source = 'Unknown Source';
d.data.orderedUniverses = [];
const networkInterfaces = d.getNetworkInterfaces();
for (let i = 1; i <= 16; i++) {
for (let j = 0; j < Object.keys(networkInterfaces).length; j++) {
const networkInterfaceID = Object.keys(networkInterfaces)[j];
const networkInterface = networkInterfaces[networkInterfaceID];
d.connection.addMembership(getMulticastGroup(i), networkInterface[0].address);
}
}
};
exports.data = function data(_device, buf) {
const universeIndex = buf.readUInt16BE(113);
const device = _device;
let universe = device.data.universes[universeIndex];
let priorities = device.data.priorities[universeIndex];
if (!universe) {
device.data.universes[universeIndex] = {};
universe = device.data.universes[universeIndex];
}
if (!priorities) {
device.data.priorities[universeIndex] = new Array(512).fill(0);
priorities = device.data.priorities[universeIndex];
}
universe.sequence = buf.readUInt8(111);
universe.priority = buf.readUInt8(108);
universe.cid = buf.toString('hex', 22, 38);
universe.slots = buf.slice(126);
if (buf.readUInt8(125) !== 0) {
universe.startCode = buf.readUInt8(125);
}
device.data.source = buf.toString('utf8', 44, 108);
device.displayName = `${device.data.source} sACN`;
device.data.ip = device.addresses[0];
if (!_.includes(device.data.orderedUniverses, universeIndex)) {
device.data.orderedUniverses.push(universeIndex);
device.data.orderedUniverses.sort();
universe.slotElems = [];
universe.slotElemsSet = false;
if (universe.priority > 0) {
device.draw();
device.update('elementCache');
}
}
if (universe.priority > 0) {
device.update('universeData', {
universeIndex,
universe,
startCode: universe.startCode,
});
} else {
device.data.priorities[universeIndex] = buf.slice(126);
}
};
exports.heartbeat = function heartbeat(device) {};
let lastUpdate = Date.now();
exports.update = function update(_device, doc, updateType, updateData) {
const device = _device;
const data = updateData;
if (updateType === 'universeData' && data.universe) {
if (Date.now() - lastUpdate > 1000) {
lastUpdate = Date.now();
device.update('elementCache');
}
const $elem = doc.getElementById(`universe-${data.universeIndex}`);
if ($elem && data.universe.slotElemsSet) {
for (let i = 0; i < 512; i++) {
data.universe.slotElems[i].textContent = data.universe.slots[i];
}
const $code = doc.getElementById(`universe-${data.universeIndex}-code`);
if (data.startCode === 0xdd) {
$code.textContent = 'Net3';
} else if (data.startCode === 0x17) {
$code.textContent = 'Text';
} else if (data.startCode === 0xcf) {
$code.textContent = 'SIP';
} else if (data.startCode === 0xcc) {
$code.textContent = 'RDM';
}
} else {
device.draw();
device.update('elementCache');
}
} else if (updateType === 'elementCache') {
device.data.orderedUniverses.forEach((universeIndex) => {
const universe = device.data.universes[universeIndex];
if (doc.getElementById(`${universeIndex}-0`)) {
for (let i = 0; i < 512; i++) {
universe.slotElems[i] = doc.getElementById(`${universeIndex}-${i}`);
universe.slotElems[i].title = `${universeIndex}/${i} ${device.data.priorities[universeIndex][i]}`;
}
universe.slotElemsSet = true;
}
});
}
};
// From https://github.com/hhromic/e131-node/blob/master/lib/e131.js
function getMulticastGroup(universe) {
if (universe < 1 || universe > 63999) {
throw new RangeError('universe should be in the range [1-63999]');
}
return `239.255.${universe >> 8}.${universe & 0xff}`;
}
+29
View File
@@ -0,0 +1,29 @@
table {
margin-bottom: 50px;
background-color: #333;
}
td,
th {
width: 35px;
}
td {
background-color: black;
text-align: center;
border-radius: 3px;
color: white;
font-size: 13px;
}
th {
background-color: #222;
color: #ccc;
font-size: 11px;
}
td.data {
padding: 7px;
font-size: 12px;
text-align: left;
}
td.data em {
font-size: 14px;
color: #ff0033;
}
+58
View File
@@ -0,0 +1,58 @@
<header>
<h1><%= data.source %> sACN (E1.31)</h1>
</header>
<div id="universes">
<% data.orderedUniverses.forEach(universeIndex => {
let universe = data.universes[universeIndex];
%>
<table id="universe-<%= universeIndex %>">
<tr>
<td class="data" colspan="2">
Universe
<br><em><%= universeIndex %></em>
</td>
<td class="data" colspan="2">
Priority
<br><em><%= universe.priority %></em>
</td>
<td class="data" colspan="7">
CID
<br><em><%= universe.cid %></em>
</td>
<td class="data" colspan="4">
IP
<br><em><%= data.ip %></em>
</td>
<td class="data" colspan="2">
Flavor
<br><em id="universe-<%= universeIndex %>-code">0</em>
</td>
</tr>
<tr>
<th></th>
<% for(col=1; col<=16; col++){ %>
<th><%= col %></th>
<% } %>
</tr>
<% let slot = 0; %>
<% for(row=0; row<32; row++){ %>
<tr>
<th><%= row*16+1 %></th>
<% for(col=0; col<16; col++){ %>
<% slot++%>
<td id="<%= universeIndex %>-<%= row*16+col %>" title="<%= universeIndex %>/<%= slot %>"><%= universe.slots[slot-1] %></td>
<% } %>
</tr>
<% } %>
</table>
<% }); %>
</div>
+14
View File
@@ -0,0 +1,14 @@
class Channel {
constructor() {
this.chan_name = '?';
this.batt_bars = 255;
this.batt_run_time = 65535;
this.audio_gain = 0;
this.audio_lvl = 0;
this.rx_rf_lvl = 0;
this.rf_antenna = 0;
this.tx_type = 0;
}
}
module.exports = Channel;
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

View File
+122
View File
@@ -0,0 +1,122 @@
const Channel = require('./channel');
exports.config = {
defaultName: 'Shure Wireless',
connectionType: 'TCPsocket',
defaultPort: 2202,
mayChangePort: false,
heartbeatInterval: 5000,
heartbeatTimeout: 10000,
searchOptions: {
type: 'TCPport',
searchBuffer: Buffer.from('< GET DEVICE_ID >', 'ascii'),
testPort: 2202,
validateResponse(msg, info) {
return msg.toString().includes('DEVICE_ID');
},
},
};
exports.ready = function ready(_device) {
const device = _device;
device.data.channelCount = 0;
device.data.channels = [{}, new Channel(), new Channel(), new Channel(), new Channel()];
};
exports.data = function data(_device, message) {
const device = _device;
let msgStr = message.toString();
if (!msgStr.startsWith('< ')) {
return;
}
msgStr = msgStr.slice(2).slice(0, -1);
const msgs = msgStr.split('><');
msgs.forEach((ms, i) => {
const msg = ms.trim();
const msgParts = msg.split(' ');
const channelNumber = Number(msgParts[1]);
const channel = device.data.channels[channelNumber];
if (msgParts[0] === 'REP') {
if (msgParts[2] === 'CHAN_NAME') {
channel.chan_name = msg.substring(17).slice(0, -2).trim();
if (device.data.channelCount < channelNumber) {
device.data.channelCount = channelNumber;
}
device.draw();
} else if (msgParts[2] === 'BATT_BARS') {
channel.batt_bars = Number(msgParts[3]);
} else if (msgParts[2] === 'BATT_RUN_TIME') {
channel.batt_run_time = Number(msgParts[3]);
} else if (msgParts[2] === 'AUDIO_GAIN') {
channel.audio_gain = Number(msgParts[3]) - 18;
} else if (msgParts[2] === 'AUDIO_LVL') {
channel.audio_lvl = Number(msgParts[3]);
} else if (msgParts[2] === 'RX_RF_LVL') {
channel.rx_rf_lvl = Number(msgParts[3]) - 128;
} else if (msgParts[2] === 'RF_ANTENNA') {
channel.rf_antenna = msgParts[3];
} else if (msgParts[2] === 'TX_TYPE') {
channel.tx_type = msgParts[3];
device.draw();
} else if (msgParts[1] === 'DEVICE_ID') {
const id = msg.substring(15).slice(0, -1).trim();
this.deviceInfoUpdate(device, 'defaultName', id);
} else if (msgParts[1] === 'FW_VER') {
device.data.version = msgParts[2].substring(1);
}
} else if (msgParts[0] === 'SAMPLE') {
channel.rf_antenna = msgParts[3];
channel.rx_rf_lvl = Number(msgParts[4]) - 128;
channel.audio_lvl = Number(msgParts[5]);
if (channelNumber === 4) {
device.update('updateSample', {
channels: device.data.channels,
});
}
}
});
};
exports.update = function update(device, doc, updateType, data) {
for (let i = 1; i < data.channels.length; i++) {
const channel = data.channels[i];
const $rf = doc.getElementById(`ch-${i}-rf`);
if ($rf) {
$rf.style.height = 90 - (channel.rx_rf_lvl + 90) * 2;
}
const $audio = doc.getElementById(`ch-${i}-audio`);
if ($audio) {
$audio.style.height = 90 - channel.audio_lvl * 2;
}
const $rfA = doc.getElementById(`ch-${i}-a`);
const $rfB = doc.getElementById(`ch-${i}-b`);
if ($rfA) {
if (channel.rf_antenna.charAt(0) === 'A') {
$rfA.style.color = '#53c3c3';
} else {
$rfA.style.color = '#333';
}
}
if ($rfB) {
if (channel.rf_antenna.charAt(1) === 'B') {
$rfB.style.color = '#53c3c3';
} else {
$rfB.style.color = '#333';
}
}
}
};
exports.heartbeat = function heartbeat(device) {
device.send('< GET 0 ALL >');
device.send('< SET 0 METER_RATE 00100 >');
device.send('< SAMPLE 0 AUDIO_LVL>');
};
+101
View File
@@ -0,0 +1,101 @@
table.channel {
width: 120px;
height: 320px;
background-color: #1a1a1d;
border: #3b3c42 1px solid;
border-collapse: collapse;
color: white;
text-align: center;
float: left;
margin: 2px;
margin-bottom: 20px;
}
table td {
border: #3b3c42 1px solid;
vertical-align: top;
}
.chan_name {
width: 100px;
color: #b2ff33 !important;
font-size: 18px;
font-weight: bold;
}
.bar-wrapper {
width: 20px;
height: 90px;
margin: 0px auto;
margin-top: 5px;
margin-bottom: 5px;
border-radius: 6px;
outline: #1a1a1d 2px solid;
outline-offset: -1px;
overflow: hidden;
}
.bar-wrapper.green {
background: linear-gradient(0deg, #4742af, #554ed4 30%, #554ed4 80%, #df664d);
}
.bar-wrapper.pink {
background: linear-gradient(0deg, #383943, #4e505d 40%);
}
.bar-wrapper.orange {
background: linear-gradient(0deg, #50c3c3, #5bdfe0 30%, #5bdfe0 80%);
}
.bar {
width: 20px;
background-color: #202124;
outline: rgba(255, 255, 255, 0.1) 1px solid;
}
.batt-wrapper {
width: 84px;
height: 35px;
margin: 0px auto;
border: #333 2px solid;
border-radius: 5px;
padding: 2px;
margin: 10px auto;
position: relative;
}
.batt-wrapper.green {
border-color: greenyellow;
}
.batt-knob {
background-color: #333;
position: absolute;
right: -5px;
top: 10px;
width: 5px;
height: 16px;
border-radius: 2px;
}
.batt-knob.green {
background-color: greenyellow;
}
.batt-bar {
height: 26px;
background: linear-gradient(0deg, rgb(145, 220, 33), greenyellow 100%);
border-radius: 2px;
color: #1a1a1d;
padding-top: 9px;
}
.rf-indicator-wrapper {
line-height: 8px;
margin-bottom: 4px;
font-size: 6px;
padding-top: 4px;
letter-spacing: 1px;
}
.rf-indicator {
color: #333;
}
small {
color: dimgray;
}
small small {
color: #444;
}
+72
View File
@@ -0,0 +1,72 @@
<header>
<h1><%= listName %></h1>
<h2><%= data.version || "" %></h2>
</header>
<% for(let i=1; i<=data.channelCount; i++){ %> <% let ch = data.channels[i]; %>
<table class="channel">
<tr>
<td colspan="3"><small><%= i %></small></td>
</tr>
<tr>
<td colspan="3" class="chan_name"><%= ch.chan_name %></td>
</tr>
<tr>
<td style="width: 40px">
<div class="rf-indicator-wrapper">
<span class="rf-indicator" id="ch-<%= i %>-a">&#11044;</span>
<span class="rf-indicator" id="ch-<%= i %>-b">&#11044;</span>
</div>
<small>rf</small>
<div class="bar-wrapper orange">
<div
class="bar"
id="ch-<%= i %>-rf"
style="height: <%= 90- ((ch.rx_rf_lvl + 90) * 2) %>"></div>
</div>
<small><%= ch.rx_rf_lvl %><br /><small>dBm</small></small>
</td>
<td style="width: 40px">
<div class="rf-indicator-wrapper">&nbsp;</div>
<small>audio</small>
<div class="bar-wrapper green">
<div
class="bar"
id="ch-<%= i %>-audio"
style="height: <%= 90- (ch.audio_lvl * 2) %>"></div>
</div>
<small><%= ch.audio_lvl %><br /><small>dBFS</small></small>
</td>
<td style="width: 40px">
<div class="rf-indicator-wrapper">&nbsp;</div>
<small>gain</small>
<div class="bar-wrapper pink">
<div class="bar" style="height: <%= 60- (ch.audio_gain * 1.4) %>"></div>
</div>
<small><%= ch.audio_gain %><br /><small>dB</small></small>
</td>
</tr>
<tr>
<td colspan="3">
<% if(ch.batt_bars==255){ %>
<div class="batt-wrapper"><div class="batt-knob"></div></div>
<% }else{ %>
<div class="batt-wrapper green">
<div class="batt-bar" style="width: <%= ch.batt_bars * 16 %>">
<% if(ch.batt_run_time<=65532){ %> <%= Math.floor(ch.batt_run_time/60)
%>: <%= ch.batt_run_time%60 %> <% } %>
</div>
<div class="batt-knob green"></div>
</div>
<% } %>
</td>
</tr>
<tr>
<td colspan="3">
<% if(ch.tx_type=="UNKN"){ %> &nbsp;<small>No Transmitter</small>&nbsp; <%
}else{ %> <%= ch.tx_type %> <% } %>
</td>
</tr>
</table>
<% } %>
View File
+58
View File
@@ -0,0 +1,58 @@
exports.config = {
defaultName: 'Dataton Watchout',
connectionType: 'TCPsocket',
defaultPort: 3040,
mayChangePort: false,
heartbeatInterval: 250,
heartbeatTimeout: 5000,
searchOptions: {
type: 'TCPport',
searchBuffer: Buffer.from('authenticate 1\n', 'ascii'),
testPort: 3040,
validateResponse(msg, info) {
return msg.toString().substring(0, 5) === 'Ready';
},
},
};
exports.ready = function ready(device) {
device.send('authenticate 1\n');
};
exports.data = function data(_device, _message) {
const message = _message.toString();
const device = _device;
if (message.substring(0, 5) === 'Ready') {
// device.send('getStatus\n');
} else if (message.substring(0, 5) === 'Reply') {
const arr = message.split(' ');
device.data.showName = '';
let i = 0;
while (arr[i][arr[i].length - 1] !== '"') {
i++;
device.data.showName += `${arr[i]} `;
}
device.data.showName = device.data.showName.substring(1, device.data.showName.length - 2);
i--;
device.data.busy = arr[i + 2];
device.data.health = arr[i + 3];
device.data.displayOpen = arr[i + 4];
device.data.showActive = arr[i + 5];
device.data.programmerOnline = arr[i + 6];
device.data.position = Number(arr[i + 7]).toFixed(2);
device.data.rate = arr[i + 8];
device.data.standby = arr[i + 9];
this.deviceInfoUpdate(device, 'defaultName', device.data.showName);
device.draw();
}
};
exports.heartbeat = function heartbeat(device) {
device.send('getStatus\n');
};
+9
View File
@@ -0,0 +1,9 @@
.warning {
color: #e9873a;
}
.error {
color: #ed5f5d;
}
.ok {
color: #79b757;
}
@@ -2,7 +2,6 @@
<h1><%= listName %></h1>
</header>
<table class="cv-table">
<tr>
<td colspan="2">
@@ -14,19 +13,20 @@
</td>
</tr>
<tr>
<% var seconds = Math.floor(data.position/1000) %>
<% var minutes = Math.floor(seconds/60) %>
<% var millis = data.position %>
<td colspan="2"><h1><%= minutes %>:<%= ((seconds%60)+"").padStart(2, "0") %>.<%= millis%1000 %></h1></td>
<% if(data.position){ %>
<% var seconds = Math.floor(data.position/1000) %>
<% var minutes = Math.floor(seconds/60) %>
<% var millis = data.position %>
<td colspan="2"><h1><%= minutes %>:<%= ((seconds%60)+"").padStart(2, "0") %>.<%= millis%1000 %></h1></td>
<% }else{ %>
<td colspan="2"><h1>00:00.00</h1></td>
<% } %>
</tr>
<tr>
<td>Busy</td>
<td><div><%= data.busy %></div></td></td>
</tr>
</tr>
<tr>
<td>Display Open</td>
<td><div><%= data.displayOpen %></td></td>
-16
View File
@@ -1,16 +0,0 @@
table{
font-family: PlexMono !important;
}
.warning{
color: #e9873a;
}
.error{
color: #ed5f5d;
}
.ok{
color: #79b757;
}
-56
View File
@@ -1,56 +0,0 @@
exports.defaultName = "Dataton Watchout";
exports.connectionType = "TCPsocket";
exports.heartbeatInterval = 500;
exports.searchOptions = {
type: "TCPport",
searchBuffer: Buffer.from("authenticate 1\n", 'ascii'),
testPort: 3040,
validateResponse: function(msg, info){
return (msg.toString().substring(0, 5)=="Ready");
}
}
exports.defaultPort = 3040;
exports.ready = function(device){
device.send("authenticate 1\n");
}
exports.data = function(device, message){
var msg = message.toString();
if(msg.substring(0, 5)=="Ready"){
device.send("getStatus\n");
}
if(msg.substring(0, 5)=="Reply"){
var arr = msg.split(" ");
device.data.showName = "";
var i = 0;
while(arr[i][arr[i].length-1]!='"'){
i++;
device.data.showName+=arr[i]+" ";
}
device.data.showName = device.data.showName.substring(1, device.data.showName.length-2);
i--;
device.data.busy = arr[i+2];
device.data.health = arr[i+3];
device.data.displayOpen = arr[i+4];
device.data.showActive = arr[i+5];
device.data.programmerOnline = arr[i+6];
device.data.position = Number(arr[i+7]).toFixed(2);
device.data.rate = arr[i+8];
device.data.standby = arr[i+9];
this.deviceInfoUpdate(device, "defaultName", device.data.showName);
device.draw();
}
// if(msg.substring(0, 5)=="Error"){
// device.data.error = msg.substring(6, 7);
// }
//console.log(msg)
}
exports.heartbeat = function(device){
device.send("getStatus\n");
}
View File

Some files were not shown because too many files have changed in this diff Show More