Compare commits

...

252 Commits

Author SHA1 Message Date
jwetzell d6cc11f97d Merge pull request #100 from stagehacks/dependabot/npm_and_yarn/eslint-8.41.0
Bump eslint from 8.40.0 to 8.41.0
2023-05-22 11:31:37 -05:00
dependabot[bot] 5503ac9888 Bump eslint from 8.40.0 to 8.41.0
Bumps [eslint](https://github.com/eslint/eslint) from 8.40.0 to 8.41.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.40.0...v8.41.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-22 10:02:34 +00: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
94 changed files with 10769 additions and 2814 deletions
+1 -1
View File
@@ -20,6 +20,6 @@ module.exports = {
'no-use-before-define': 'off',
'space-infix-ops': 'warn',
'no-bitwise': 'off',
'no-restricted-globals': '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"
@@ -1,10 +1,9 @@
name: release
on:
push:
branches:
- github-actions
workflow_dispatch:
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
jobs:
build-linux:
@@ -14,24 +13,33 @@ jobs:
- 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
-4
View File
@@ -1,7 +1,3 @@
.DS_Store
node_modules
.vscode
/dist
package-lock.json
package-lock.json
+1 -1
View File
@@ -1 +1 @@
v18.12.1
v18.13.0
+2
View File
@@ -1,2 +1,4 @@
node_modules
out
dist
*.ejs
+1
View File
@@ -4,4 +4,5 @@ module.exports = {
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
}
+28 -27
View File
@@ -13,7 +13,7 @@ A dashboard for everything in your show.
## Supported Devices
- QLab 4
- QLab 4 & 5
- ETC Eos Consoles
- Watchout
- PJLink Projectors
@@ -24,12 +24,11 @@ A dashboard for everything in your show.
#### Future Devices
- Spikemark (not possible until additional OSC methods are added)
- ATEM Video Mixers
- Epson Pro series Projectors
- DiGiCo Consoles
- d&b DS100, amps
- Hog
- Meyer Galileo Processors
# Plugins
@@ -38,31 +37,33 @@ A Cue View "plugin" is a system for communicating with a type of device, for exa
### plugin.js
```js
exports.defaultName = 'Example Plugin';
exports.connectionType = 'osc' or 'TCPsocket' or 'UDPsocket'
exports.searchOptions = {
type: 'Bonjour',
bonjourNane: 'device'
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.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
}
}
exports.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
}
}
exports.defaultPort = 1234; // only available for TCPsocket and UDPsocket devices
exports.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
+50 -75
View File
@@ -3,93 +3,68 @@
<head>
<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>
<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>
<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-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>
</table>
<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>
<h3>No Device Selected</h3>
</div>
</div>
</div>
+215 -143
View File
@@ -1,13 +1,17 @@
const { app, BrowserWindow, Menu, ipcMain, nativeTheme, dialog, shell } = require('electron');
const { autoUpdater } = require('electron-updater');
const path = require('path');
const packageInfo = require('./package.json');
const isMac = process.platform === 'darwin';
const isWin = process.platform === 'win32';
const isLinux = process.platform === 'linux';
let autoUpdate = false;
let manualUpdateCheck = false;
let menuObj;
let mainWindow;
let networkInfoWindow;
const menuTemplate = [
...(isMac ? [{ role: 'appMenu' }] : []),
@@ -16,18 +20,18 @@ const menuTemplate = [
submenu: [
{
label: 'Clear Saved Data',
id: 'resetViews',
click (menuItem, window, event) {
mainWindow.webContents.send('resetViews');
}
id: 'clearSavedData',
click(menuItem, window, event) {
mainWindow.webContents.send('clearSavedData');
},
},
{
label: 'Reload App',
role: 'reload'
role: 'reload',
},
{ type: 'separator' },
...(isMac ? [{ role: 'close' }] : [{ role: 'quit' }])
]
...(isMac ? [{ role: 'close' }] : [{ role: 'quit' }]),
],
},
{ role: 'editMenu' },
{
@@ -40,31 +44,41 @@ const menuTemplate = [
accelerator: 'CommandOrControl+1',
id: 'window1',
enabled: true,
click (menuItem, window, event) {
mainWindow.webContents.send('doSlots1');
}
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('doSlots2');
}
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('doSlots3');
}
click(menuItem, window, event) {
mainWindow.webContents.send('loadSlot', 3);
},
},
{ type: 'separator' },
{ role: 'toggleDevTools' }
]
{
label: 'Network Information...',
accelerator: 'CommandOrControl+I',
id: 'window4',
enabled: true,
click(menuItem, window, event) {
openNetworkInfoWindow();
},
},
{ type: 'separator' },
{ role: 'toggleDevTools' },
],
},
{
label: 'Device',
@@ -74,9 +88,9 @@ const menuTemplate = [
accelerator: 'CommandOrControl+F',
id: 'deviceSearch',
enabled: true,
click (menuItem, window, event) {
mainWindow.webContents.send('doSearch');
}
click(menuItem, window, event) {
mainWindow.webContents.send('searchAll');
},
},
{ type: 'separator' },
{
@@ -86,23 +100,20 @@ const menuTemplate = [
accelerator: 'CommandOrControl+P',
id: 'devicePin',
enabled: false,
click (menuItem, window, event) {
mainWindow.webContents.send(
'setActiveDevicePinned',
menuItem.checked
);
}
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('doDelete');
}
}
]
click(menuItem, window, event) {
mainWindow.webContents.send('deleteActive');
},
},
],
},
{
label: 'Help',
@@ -110,62 +121,97 @@ const menuTemplate = [
submenu: [
{
label: 'About',
role: 'about'
role: 'about',
},
{
label: 'Check for Updates',
click: ()=>{
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,
// backgroundColor: "#333333",
vibrancy: 'window',
visualEffectState: 'followWindow',
webPreferences: {
contextIsolation: false,
nodeIntegration: true,
preload: path.join(__dirname, 'preload.js'),
},
}
const windowWin = {
width: 1500,
height: 900,
backgroundColor: "#333333",
titleBarStyle: 'hiddenInset',
transparent: true,
frame: false,
show: false,
vibrancy: 'window',
visualEffectState: 'followWindow',
webPreferences: {
contextIsolation: false,
nodeIntegration: true,
preload: path.join(__dirname, 'preload.js'),
},
}
};
if (isWin)
{
app.setAppUserModelId(app.name);
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 = () => {
nativeTheme.themeSource = 'dark';
if(isMac){
if (isMac) {
mainWindow = new BrowserWindow(windowMac);
}else{
} 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', () => {
@@ -182,7 +228,7 @@ const createWindow = () => {
app.whenReady().then(() => {
createWindow();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
@@ -214,126 +260,152 @@ ipcMain.on('setDevicePin', (event, arg) => {
menuObj.getMenuItemById('devicePin').checked = arg;
});
ipcMain.on('openNetworkInfoWindow', (event, arg) => {
openNetworkInfoWindow();
});
// Autoupdate logic
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)=>{
autoUpdater.on('update-available', (updateInfo) => {
// skip prompting to download if autoDownload is set
if(autoUpdater.autoDownload){
if (autoUpdater.autoDownload) {
return;
}
const msg = `Version ${updateInfo.version} is available: Would you like to download?`
const msg = `Version v${updateInfo.version} is available. Would you like to download it?`;
const title = 'Update Available';
let dialogOpts = {}
let dialogOpts = {};
if(isMac){
if (isMac) {
dialogOpts = {
type: 'info',
buttons: ['Download', 'Cancel'],
buttons: ['Download', 'Cancel', 'View Release Notes'],
title: 'Update Available',
message: title,
detail: `Auto updating is not yet automatic on MacOS. Please manually download and install version ${updateInfo.version}.`
}
}else{
detail: msg,
};
} else {
dialogOpts = {
type: 'info',
buttons: ['Download', 'Cancel'],
buttons: ['Download', 'Cancel', 'View Release Notes'],
title: 'Update Available',
message: msg
}
message: msg,
};
}
dialog.showMessageBox(mainWindow,dialogOpts).then((returnValue) => {
// download was clicked
if (returnValue.response === 0){
if(!isMac){
autoUpdater.downloadUpdate();
}else{
// TODO: get code signing working
// temp solution to direct user to download
shell.openExternal("https://github.com/stagehacks/Cue-View/releases/")
}
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)=>{
autoUpdater.on('update-downloaded', (event) => {
const title = 'Update Downloaded';
const msg = `Version ${event.version} has been downloaded. Would you like to install this update now?`
const msg = `Version v${event.version} has been downloaded. Would you like to install this update now?`;
let dialogOpts = {}
if(isMac){
let dialogOpts = {};
if (isMac) {
dialogOpts = {
type: 'info',
buttons: ['Install', 'Later'],
title: 'Update Available',
title: 'Update Downloaded',
message: title,
detail: msg
}
}else{
detail: msg,
};
} else {
dialogOpts = {
type: 'info',
buttons: ['Install', 'Later'],
title: 'Update Available',
message: msg
}
}
dialog.showMessageBox(mainWindow,dialogOpts).then((returnValue) => {
if (returnValue.response === 0) autoUpdater.quitAndInstall()
})
})
autoUpdater.on('update-not-available',(updateInfo)=>{
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
}
title: 'Update Downloaded',
message: msg,
};
}
dialog.showMessageBox(mainWindow,dialogOpts)
})
dialog.showMessageBox(mainWindow, dialogOpts).then((returnValue) => {
if (returnValue.response === 0) autoUpdater.quitAndInstall();
});
});
autoUpdater.on('error',(error,message)=>{
let dialogOpts = {}
if(isMac){
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{
detail: error.message,
};
} else {
dialogOpts = {
type: 'error',
buttons: ['Ok'],
title: 'Update Error',
message: error.message
}
message: error.message,
};
}
dialog.showMessageBox(mainWindow,dialogOpts)
})
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);
}
};
+7847 -1307
View File
File diff suppressed because it is too large Load Diff
+16 -12
View File
@@ -1,11 +1,12 @@
{
"name": "cue-view",
"productName": "Cue View",
"version": "0.9.7-pre",
"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",
"pretty": "prettier --write **/*",
"start": "electron .",
"build": "electron-builder -m -w -l",
"build:mac": "electron-builder -m",
@@ -19,30 +20,33 @@
"homepage": "https://github.com/stagehacks/Cue-View",
"repository": "https://github.com/stagehacks/Cue-View",
"devDependencies": {
"electron": "^21.3.0",
"electron-builder": "^23.6.0",
"eslint": "^8.27.0",
"@electron/notarize": "^1.2.3",
"electron": "^22.2.0",
"electron-builder": "^24.4.0",
"eslint": "^8.41.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"prettier": "^2.7.1"
"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",
"electron-updater": "^5.3.0",
"lodash": "^4.17.20",
"lodash": "^4.17.21",
"md5": "^2.3.0",
"netmask": "^2.0.2",
"osc": "^2.4.3",
"osc": "^2.4.4",
"uuid": "^9.0.0"
},
"build": {
"appId": "com.stagehacks.cueview",
"icon": "./src/img/",
"icon": "src/assets/img/",
"artifactName": "${name}.${os}.v${version}.${ext}",
"mac": {
"category": "Utilities",
"icon": "./src/img/icon.icns",
"icon": "src/assets/img/icon.icns",
"hardenedRuntime": true,
"electronLanguages": [
"en"
],
@@ -52,7 +56,7 @@
},
"win": {
"target": "NSIS",
"icon": "./src/img/icon.ico",
"icon": "src/assets/img/icon.ico",
"publish": [
"github"
]
+1 -1
View File
@@ -1,2 +1,2 @@
<p><em>Art-Net</em> requires no configuration.</p>
<p>Art-Net™ Designed by and Copyright Artistic Licence Holdings Ltd</p>
<p>Art-Net™ Designed by and Copyright Artistic Licence Holdings Ltd</p>
+68 -92
View File
@@ -1,119 +1,95 @@
const _ = require('lodash');
exports.defaultName = "Art-Net";
exports.connectionType = 'UDPsocket';
exports.heartbeatInterval = 10000;
exports.searchOptions = {
type: 'UDPsocket',
searchBuffer: Buffer.from([0x00]),
devicePort: 6454,
listenPort: 6454,
validateResponse (msg, info, devices) {
return msg.toString('utf8', 0, 7) === "Art-Net";
}
};
exports.defaultPort = 6454;
exports.ready = function ready(device) {
const d = device;
d.data.universes = {};
d.data.orderedUniverses = [];
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.data = function data(device, buf) {
if(buf.length < 18){
return
}
const univ = buf.readUInt8(14);
const d = device;
let u = d.data.universes[univ];
exports.ready = function ready(_device) {
const device = _device;
device.data.universes = {};
device.data.orderedUniverses = [];
};
if(!u){
d.data.universes[univ] = {};
u = d.data.universes[univ];
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];
}
u.sequence = buf.readUInt8(12);
u.net = buf.readUInt8(15);
u.opCode = buf.readUInt8(9);
u.version = buf.readUInt16BE(10);
u.slots = Array.prototype.slice.call(buf, 18);
d.data.ip = d.addresses[0];
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(!_.includes(d.data.orderedUniverses, univ)){
d.data.orderedUniverses.push(univ);
d.data.orderedUniverses.sort();
u.slotElems = [];
u.slotElemsSet = false;
d.draw();
updateElementsCache(d, d.data.universes, d.data.orderedUniverses);
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", {
u: univ,
universe: u
device.update('universeData', {
universeIndex,
universe,
});
};
exports.heartbeat = function heartbeat(device) {
};
exports.heartbeat = function heartbeat(device) {};
let lastUpdate = Date.now();
exports.update = function update(device, document, updateType, updateData){
exports.update = function update(_device, _document, updateType, updateData) {
const device = _device;
const data = updateData;
const doc = document;
const document = _document;
if(updateType === "universeData" && data.universe){
if(Date.now() - lastUpdate > 1000){
if (updateType === 'universeData' && data.universe) {
if (Date.now() - lastUpdate > 1000) {
lastUpdate = Date.now();
updateElementsCache(device, device.data.universes, device.data.orderedUniverses);
device.update('elementCache');
}
const $elem = doc.getElementById(`universe-${data.u}`);
const $elem = document.getElementById(`universe-${data.universeIndex}`);
if($elem && data.universe.slotElemsSet){
for(let i = 0; i < 512; i++){
data.universe.slotElems[i].innerText = data.universe.slots[i];
if ($elem && data.universe.slotElemsSet) {
for (let i = 0; i < 512; i++) {
data.universe.slotElems[i].textContent = data.universe.slots[i];
}
doc.getElementById(`universe-${data.u}-sequence`).innerText = data.universe.sequence;
}else{
document.getElementById(`universe-${data.universeIndex}-sequence`).textContent = data.universe.sequence;
} else {
device.draw();
updateElementsCache(device, device.data.universes, device.data.orderedUniverses);
device.update('elementCache');
}
}else if(updateType === "elementCache"){
data.orderedUniverses.forEach(univ => {
for(let i = 0; i < 512; i++){
data.universes[univ].slotElems[i] = doc.getElementById(`${univ}-${i}`);
} 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}`);
}
data.universes[univ].slotElemsSet = true;
device.data.universes[universeIndex].slotElemsSet = true;
});
}
}
function updateElementsCache(device, univ, ordered){
device.update("elementCache", {
universes: univ,
orderedUniverses: ordered
});
}
};
+7 -12
View File
@@ -4,23 +4,23 @@
<div id="universes">
<% data.orderedUniverses.forEach(univ => {
let universe = data.universes[univ];
<% data.orderedUniverses.forEach(universeIndex => {
let universe = data.universes[universeIndex];
%>
<table id="universe-<%= univ %>">
<table id="universe-<%= universeIndex %>">
<tr>
<td class="data" colspan="2">
Universe
<br><em><%= univ %></em>
<br><em><%= universeIndex %></em>
</td>
<td class="data" colspan="2">
Sub-Net
<br><em><%= universe.net %></em>
<br><em><%= universe.subnet %></em>
</td>
<td class="data" colspan="2">
Sequence
<br><em id="universe-<%= univ %>-sequence"><%= universe.sequence %></em>
<br><em id="universe-<%= universeIndex %>-sequence"><%= universe.sequence %></em>
</td>
<td class="data" colspan="2">
OpCode
@@ -35,7 +35,6 @@
<br><em><%= data.ip %></em>
</td>
<td class="data" colspan="3"></td>
</tr>
<tr>
@@ -51,14 +50,10 @@
<th><%= row*16+1 %></th>
<% for(col=0; col<16; col++){ %>
<% slot++%>
<td id="<%= univ %>-<%= row*16+col %>" title="<%= univ %>/<%= slot %>"><%= universe.slots[slot-1] %></td>
<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>
<% }) %>
<% } %>
+3 -3
View File
@@ -45,13 +45,13 @@
<% } %>
<td class="black hide-medium">
<%= part.prettyDuration(part.focusDimeDuration, true) %>
<%= part.prettyDuration(part.focusTimeDuration, true) %>
</td>
<td class="black hide-medium">
<%= part.prettyDuration(part.colorDimeDuration, true) %>
<%= part.prettyDuration(part.colorTimeDuration, true) %>
</td>
<td class="black hide-medium">
<%= part.prettyDuration(part.beamDimeDuration, true) %>
<%= part.prettyDuration(part.beamTimeDuration, true) %>
</td>
<td class="hide-small"><%= part.prettyDuration(part.duration) %></td>
+1 -1
View File
@@ -30,7 +30,7 @@ class Cue {
const num = Math.round(milliseconds / 100) / 10;
if (box) {
return `<div class="time">"${num}"</div>`;
return `<div class="time">${num}</div>`;
}
return num;
+7 -6
View File
@@ -2,14 +2,15 @@
<h4>Shell/ECU &rarr; Network</h4>
<ul>
<li>Enable <em>&#10004; TCP OSC</em>. The TCP format dropdown does not matter.</li>
<li>Enable <em>Third Party OSC</em></li>
<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>
</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>
+41 -50
View File
@@ -3,29 +3,38 @@ const fs = require('fs');
const path = require('path');
const Cue = require('./cue');
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(msg, info) {
return msg.toString().indexOf('/eos/out');
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.defaultPort = 3032;
exports.ready = function ready(device) {
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) {
exports.data = function data(_device, osc) {
const device = _device;
this.deviceInfoUpdate(device, 'status', 'ok');
const addressParts = osc.address.split('/');
addressParts.shift();
@@ -37,19 +46,18 @@ exports.data = function data(device, osc) {
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', '*', '*'])
) {
} 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', '*', '*'])
) {
this.deviceInfoUpdate(device, 'status', 'ok');
} 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]] = {};
}
@@ -65,28 +73,16 @@ exports.data = function data(device, osc) {
// 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',
'*',
'*',
])
) {
} 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 });
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];
@@ -98,16 +94,11 @@ exports.data = function data(device, osc) {
}
};
const cueTemplate = _.template(
fs.readFileSync(path.join(__dirname, `cue.ejs`))
);
exports.update = function update(device, doc, updateType, data) {
if (updateType === 'cueData') {
const $elem = doc.getElementById(data.uid);
if ($elem) {
$elem.outerHTML = cueTemplate({
$elem.outerHTML = device.templates.cue({
q: data.cue,
cueNumber: data.cueNumber,
isActive: false,
@@ -125,18 +116,18 @@ exports.heartbeat = function heartbeat(device) {
device.send('/eos/ping');
};
function match(addressParts, array) {
if (addressParts.length !== array.length) {
function match(testArray, patternArray) {
let out = true;
if (testArray.length !== patternArray.length) {
return false;
}
array.forEach((m, i) => {
if (addressParts[i] !== m && m !== '*') {
return false;
patternArray.forEach((patternPart, i) => {
if (testArray[i] !== patternPart && patternPart !== '*') {
out = false;
}
});
return true;
return out;
}
class EOS {
constructor() {
this.version = '';
+11
View File
@@ -3,6 +3,7 @@ table {
border: #404040 1px solid;
border-radius: 4px;
color: #a59baa;
width: 100%;
}
th {
font-size: 14px;
@@ -62,6 +63,16 @@ 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;
+31 -36
View File
@@ -3,45 +3,40 @@
<h2>Eos <%= data.version %></h2>
</header>
<%
const fs = require('fs');
const path = require('path');
let cueTemplate = _.template(fs.readFileSync(path.join(__dirname, `/plugins/eos/cue.ejs`)));
%>
<% for(var i in data.EOS.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>
<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)}) %>
<% 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]] %>
<%= cueTemplate({
q: q,
cues: cues,
cueNumber: cues[j],
isActive: (cues[j]==data.EOS.activeCue+"")
}) %>
<% 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>
<% } %>
</table>
</div>
<% } %>
+2 -2
View File
@@ -1,4 +1,4 @@
<h3>Connection Requirements</h3>
<ul>
<li>Requires no password on the projector</li>
</ul>
<li>Requires no password on the projector</li>
</ul>
+124 -90
View File
@@ -1,126 +1,160 @@
const md5 = require('md5');
exports.defaultName = 'PJLink Projector';
exports.connectionType = 'TCPsocket';
exports.heartbeatInterval = 5000;
exports.heartbeatTimeout = 15000;
exports.searchOptions = {
type: 'UDPsocket',
searchBuffer: Buffer.from([0x25, 0x32, 0x53, 0x52, 0x43, 0x48, 0x0d]),
devicePort: 4352,
listenPort: 4352,
validateResponse (msg, info) {
return msg.toString().indexOf('%2ACKN=') >= 0;
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.defaultPort = 4352;
exports.ready = function ready(device) {
// Power status query
// device.send("%1POWR ?\r");
};
let password = false;
function processPJLink(device, str, that) {
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 d = device;
const device = _device;
arr.forEach((s) => {
const split = s.split('=');
const key = split[0];
const value = split[1];
arr.forEach((response) => {
const parts = response.split('=');
const cmd = parts[0];
const value = parts[1];
if(key === '1POWR'){
d.data.power = value;
switch (cmd) {
case '1POWR':
device.data.power = value;
break;
}else if(key === '1INPT'){
d.data.input = value;
}else if(key === '1AVMT'){
d.data.avmute = value;
}else if(key === '1ERST'){
d.data.fanError = value[0];
d.data.lampError = value[1];
d.data.tempError = value[2];
d.data.coverError = value[3];
d.data.filterError = value[4];
d.data.otherError = value[5];
}else if(key === '1LAMP'){
d.data.lamp = value.split(' ');
}else if(key === '1NAME'){
d.data.name = value;
that.deviceInfoUpdate(d, 'defaultName', d.data.name);
}else if(key === '1INF1'){
d.data.info1 = value;
}else if(key === '1INF2'){
d.data.info2 = value;
}else if(key === '2SNUM'){
d.data.serial = value;
}else if(key === '2SVER'){
d.data.version = value;
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;
}
});
d.draw();
device.draw();
}
exports.data = function data(device, message) {
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') {
password = md5(`${msg.substring(9, 17)}JBMIAProjectorLink`);
passwordSeed = msg.substring(9, 17);
passwordMD5 = md5(`${passwordSeed}${device.fields.password}`);
device.data.authentication = 'ON';
device.send(
`${password}%1POWR ?\r%1INPT ?\r%1AVMT ?\r%1ERST ?\r%1LAMP ?\r%1NAME ?\r%1INF1 ?\r%1INF2 ?\r%2SNUM ?\r%2SVER ?\r`
`${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 (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=') {
if (PJLinkCmds.includes(msg.substring(0, 7))) {
processPJLink(device, msg, this);
device.data.passwordOK = true;
}
};
exports.heartbeat = function heartbeat(device) {
if (password) {
passwordMD5 = md5(`${passwordSeed}${device.fields.password}`);
if (device.fields.password.length > 0 && device.data.passwordOK) {
device.send(
`${password}%1POWR ?\r%1INPT ?\r%1AVMT ?\r%1ERST ?\r%1LAMP ?\r%1NAME ?\r%1INF1 ?\r%1INF2 ?\r%2SNUM ?\r%2SVER ?\r`
`${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();
};
+4
View File
@@ -7,3 +7,7 @@
.ok {
color: #79b757;
}
table {
margin-bottom: 30px;
width: 350px;
}
+15 -2
View File
@@ -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>
+31 -17
View File
@@ -24,31 +24,45 @@
<% } %>
</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><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>
<% for(let i=cue.nestedGroupModes.length; i>0; i--){ %>
<% }else if(cue.nestedGroupPosition[i]<=0){ %>
<% if(i<cue.nestedGroupModes.length-1){ %>
<td class="gMode-<%= cue.nestedGroupModes[i] %> gLeft gBot"></td>
<% if(cue.type=="Group" && i==1 && cue.cues.length==0){ %>
<td class="gMode-<%= cue.nestedGroupModes[i-1] %> gLeft gTop gBot group-arrow"></td>
<% }else{ %>
<td class="gMode-<%= cue.nestedGroupModes[i] %> gBot"></td>
<% }else if(cue.type=="Group" && i==1){ %>
<td class="gMode-<%= cue.nestedGroupModes[i-1] %> gLeft gTop group-arrow"></td>
<% } %>
<% }else if(cue.nestedGroupPosition[i-1]==0 && cue.nestedGroupPosition[i-2]==0){ %>
<td class="gMode-<%= cue.nestedGroupModes[i-1] %> gLeft gBot"></td>
<% }else if(i<cue.nestedGroupModes.length-1){ %>
<td class="gMode-<%= cue.nestedGroupModes[i] %> gLeft"></td>
<% }else if(i==1 && cue.nestedGroupPosition[i]==0){ %>
<td class="gMode-<%= cue.nestedGroupModes[i] %> gBot"></td>
<% }else{ %>
<td class="gMode-<%= cue.nestedGroupModes[i] %>"></td>
<% }else{ %>
<td class="gMode-<%= cue.nestedGroupModes[i-1] %> gLeft"></td>
<% } %>
<% } %>
<% } %>
@@ -67,10 +81,10 @@
<% } %>
<% }else if(cue.nestedGroupPosition[1]==0){ %>
<td colspan="<%= 11-cue.nestedGroupModes.length %>" class="cue-name gMode-<%= cue.nestedGroupModes[1] %> gBot gRight"><%= cue.listName %></td>
<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[1] %> gRight"><%= cue.listName %></td>
<td colspan="<%= 11-cue.nestedGroupModes.length %>" class="cue-name gMode-<%= cue.nestedGroupModes[cue.nestedGroupModes.length-1] %> gRight"><%= cue.listName %></td>
<% } %>
@@ -81,7 +95,7 @@
<% if(cue.preWait){ %>
<td class="q-time hide-medium"><%= elapsedTime(cue.preWait, cue.preWaitElapsed, "preWait", cue) %></td>
<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>
+2 -4
View File
@@ -40,13 +40,11 @@
function displayCueRow(q){
let html = "";
html+= rowTemplate({cue: allCues[q.uniqueID], allCues: allCues, workspace: workspace});
let html = rowTemplate({cue: allCues[q.uniqueID], allCues: allCues, workspace: workspace});
if(q.cues){
q.cues.forEach(q =>{
html+=displayCueRow(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: 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

+3 -2
View File
@@ -1,4 +1,5 @@
<h3>Connection Requirements</h3>
<ul>
<li>No Passcode</li>
</ul>
<li><em>View</em> permission enabled in OSC Access</li>
<li>Provide passcode if necessary</li>
</ul>
+239 -169
View File
@@ -2,30 +2,48 @@ 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"]';
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"]';
const cueTemplate = _.template(fs.readFileSync(path.join(__dirname, `cue.ejs`)));
const tileTemplate = _.template(fs.readFileSync(path.join(__dirname, `tile.ejs`)));
const cartTemplate = _.template(fs.readFileSync(path.join(__dirname, `cart.ejs`)));
exports.defaultName = 'QLab 4';
exports.connectionType = 'osc';
exports.heartbeatInterval = 50;
exports.heartbeatTimeout = 2000;
exports.searchOptions = {
type: 'Bonjour',
bonjourName: 'qlab',
};
exports.ready = function ready(device) {
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');
};
@@ -36,36 +54,33 @@ exports.data = function data(_device, oscData) {
const oscAddressParts = oscData.address.split('/');
oscAddressParts.shift();
if(match(oscAddressParts, ["reply", "version"])){
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"])){
} else if (match(oscAddressParts, ['reply', 'workspaces'])) {
const json = JSON.parse(oscData.args[0]);
device.data.workspaces = {};
json.data.forEach(wksp => {
json.data.forEach((wksp) => {
device.data.workspaces[wksp.uniqueID] = {
uniqueID: wksp.uniqueID,
displayName: wksp.displayName,
cueLists: {},
cues: {}
}
device.send(`/workspace/${wksp.uniqueID}/connect`);
cues: {},
};
device.send(`/workspace/${wksp.uniqueID}/connect`, device.fields.passcode);
});
}else if(match(oscAddressParts, ["reply", "workspace", "*", "connect"])){
device.send(`/workspace/${oscAddressParts[2]}/updates`, [
{ type: 'i', value: 1 },
]);
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"])){
} 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]);
@@ -73,42 +88,51 @@ exports.data = function data(_device, oscData) {
workspace.cueLists = {};
workspace.cues = {};
json.data.forEach(cueList => {
workspace.cueLists[cueList.uniqueID] = cueList;
addCueToWorkspace(workspace, cueList);
});
if (json.status === 'denied') {
device.data.permission = 'denied';
device.draw();
} else if (json.data) {
device.data.permission = 'ok';
device.draw();
setTimeout(() => {
json.data.forEach(ql => {
workspace.cueLists[ql.uniqueID] = ql;
getValuesForKeys(device, json.workspace_id, ql);
json.data.forEach((cueList) => {
workspace.cueLists[cueList.uniqueID] = cueList;
addCueToWorkspace(workspace, cueList);
});
}, 0);
}else if(match(oscAddressParts, ["reply", "cue_id", "*", "children"])){
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 = oscAddressParts[2];
const cueID = json.address.substring(55, 91);
const cue = workspace.cues[cueID];
if(!_.isEqual(cue.cues, json.data)){
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"])){
} 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){
if (!cue) {
workspace.cues[cueValues.uniqueID] = {};
cue = workspace.cues[cueValues.uniqueID];
}
@@ -124,7 +148,7 @@ exports.data = function data(_device, oscData) {
cue.flagged = cueValues.isFlagged;
cue.paused = cueValues.isPaused;
cue.type = cueValues.type;
cue.cues = cueValues.children;
// cue.cues = cueValues.children;
cue.preWait = cueValues.preWait;
cue.postWait = cueValues.postWait;
cue.duration = cueValues.currentDuration;
@@ -139,208 +163,254 @@ exports.data = function data(_device, oscData) {
cue.actionElapsed = cueValues.actionElapsed;
cue.postWaitElapsed = cueValues.postWaitElapsed;
const nestedGroupModes = [];
const nestedGroupPosition = [0];
// 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;
while(obj.parent !== "[root group of cue lists]"){
nestedGroupModes.push(obj.groupMode);
if (obj.cues) {
sum += obj.cues.length;
}
let pos = _.findIndex(workspace.cues[obj.parent].cues, {uniqueID: obj.uniqueID});
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;
nestedGroupPosition.push(pos)
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': cue, 'allCues': workspace.cues, 'workspace': workspace});
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)];
}else if(match(oscAddressParts, ["reply", "cue_id", "*", "preWaitElapsed"])){
if (cue) {
cue.preWaitElapsed = oscArgs.data;
lastElapsedUpdate = Date.now();
const json = JSON.parse(oscData.args[0]);
const workspace = device.data.workspaces[json.workspace_id];
const cue = workspace.cues[oscAddressParts[2]];
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)];
cue.preWaitElapsed = json.data;
lastElapsedUpdate = Date.now();
if (cue) {
cue.actionElapsed = oscArgs.data;
lastElapsedUpdate = Date.now();
device.update("updateCueData", {'cue': cue, 'allCues': workspace.cues, 'workspace': workspace});
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)];
}else if(match(oscAddressParts, ["reply", "cue_id", "*", "actionElapsed"])){
const json = JSON.parse(oscData.args[0]);
const workspace = device.data.workspaces[json.workspace_id];
const cue = workspace.cues[oscAddressParts[2]];
cue.actionElapsed = json.data;
lastElapsedUpdate = Date.now();
device.update("updateCueData", {'cue': cue, 'allCues': workspace.cues, 'workspace': workspace});
}else if(match(oscAddressParts, ["reply", "cue_id", "*", "postWaitElapsed"])){
const json = JSON.parse(oscData.args[0]);
const workspace = device.data.workspaces[json.workspace_id];
const cue = workspace.cues[oscAddressParts[2]];
cue.postWaitElapsed = json.data;
lastElapsedUpdate = Date.now();
device.update("updateCueData", {'cue': cue, 'allCues': workspace.cues, 'workspace': workspace});
}else if(match(oscAddressParts, ["update", "workspace", "*", "cue_id", "*"])){
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){
if (workspace) {
const cueLists = Object.keys(workspace.cueLists);
const cueID = oscAddressParts[4];
if(cueID !== "[root group of cue lists"){
if(cueLists.includes(cueID)){
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}
{ type: 's', value: valuesForKeysString },
]);
}
}
}else if(match(oscAddressParts, ["update", "workspace", "*"])){
} 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"])){
} else if (match(oscAddressParts, ['update', 'workspace', '*', 'dashboard'])) {
// this workspace might be new, let's check
if(device.data.workspaces[oscAddressParts[2]] === undefined){
console.log("new workspace!");
if (device.data.workspaces[oscAddressParts[2]] === undefined) {
device.send('/workspaces');
}
}else if(match(oscAddressParts, ["update", "workspace", "*", "cueList", "*", "playbackPosition"])){
} else if (match(oscAddressParts, ['update', 'workspace', '*', 'cueList', '*', 'playbackPosition'])) {
const workspace = device.data.workspaces[oscAddressParts[2]];
if(workspace){
if (workspace) {
const cue = workspace.cues[oscData.args[0]];
if(cue){
workspace.playbackPosition = oscData.args[0] ? cue.uniqueID : "";
device.update("updatePlaybackPosition", {'cue': cue});
if (cue) {
workspace.playbackPosition = oscData.args[0] ? cue.uniqueID : '';
device.update('updatePlaybackPosition', { cue });
}
}
}else if(match(oscAddressParts, ["update", "workspace", "*", "disconnect"])){
} 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{
} 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"){
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"){
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 = cartTemplate({'tileTemplate': tileTemplate, 'cueList': data.cue, 'allCues': data.workspace.cues});
}else if(data.cue.cartPosition && data.cue.cartPosition[0] !== 0){
} 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 = tileTemplate(data);
}else{
$elem.outerHTML = cueTemplate(data);
}
}else{
$elem.outerHTML = cueTemplate(data);
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");
} 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"});
$elem.classList.add('playback-position');
$elem.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}
};
function addCueToWorkspace(_workspace, cue){
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){
if (cue.cues) {
// this cue has children so add them as well
cue.cues.forEach(childCue => {
cue.cues.forEach((childCue) => {
addCueToWorkspace(workspace, childCue);
});
}
}
function getValuesForKeys(device, workspaceID, cue){
function getValuesForKeys(device, workspaceID, cue) {
device.send(`/workspace/${workspaceID}/cue_id/${cue.uniqueID}/valuesForKeys`, [
{type: 's', value: valuesForKeysString}
{ type: 's', value: valuesForKeysString },
]);
if(cue.cues){
cue.cues.forEach(childCue => {
if (cue.cues) {
cue.cues.forEach((childCue) => {
getValuesForKeys(device, workspaceID, childCue);
});
}
}
function match(testArray, patternArray){
function match(testArray, patternArray) {
let out = true;
if(testArray.length !== patternArray.length){
if (testArray.length !== patternArray.length) {
return false;
}
patternArray.forEach((patternPart, i)=> {
if(testArray[i] !== patternPart && patternPart !== "*"){
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 = 5;
}else{
if (Date.now() - lastElapsedUpdate > 300) {
interval = 24;
} else {
interval = 1;
}
if(heartbeatCount % interval === 0){
device.send(`/cue/active/preWaitElapsed`);
device.send(`/cue/active/actionElapsed`);
device.send(`/cue/active/postWaitElapsed`);
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`);
}
};
+7 -1
View File
@@ -151,13 +151,19 @@ tr.playback-position .q-gray-text {
.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/arrow-down.png');
background-image: url('img/new_group_arrow.png');
background-position: center;
background-repeat: no-repeat;
}
+18 -36
View File
@@ -3,44 +3,26 @@
<h2>QLab <%= data.version || "" %></h2>
</header>
<%
const fs = require('fs');
let _ = require('lodash');
const path = require('path');
<% 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}) %>
<% } %>
let cueTemplate = _.template(fs.readFileSync(path.join(__dirname, `/plugins/qlab/cue.ejs`)));
let tileTemplate = _.template(fs.readFileSync(path.join(__dirname, `/plugins/qlab/tile.ejs`)));
let cartTemplate = _.template(fs.readFileSync(path.join(__dirname, `/plugins/qlab/cart.ejs`)));
let listTemplate = _.template(fs.readFileSync(path.join(__dirname, `/plugins/qlab/cuelist.ejs`)));
<% }}}else if(data.permission=="no workspaces"){ %>
%>
<%
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"){ %>
<%= listTemplate({cueList: ql, allCues: workspace.cues, rowTemplate: cueTemplate, workspace: workspace}) %>
<% }else if(ql.type=="Cart"){ %>
<%= cartTemplate({cueList: ql, allCues: workspace.cues, tileTemplate: tileTemplate}) %>
<% } %>
<% }} %>
<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>
<% } %>
+1 -1
View File
@@ -1 +1 @@
<p><em>sACN</em> requires no configuration.</p>
<p><em>sACN</em> requires no configuration.</p>
+98 -113
View File
@@ -1,152 +1,138 @@
const _ = require('lodash');
exports.defaultName = 'sACN';
exports.connectionType = 'multicast';
exports.defaultPort = 5568;
exports.heartbeatInterval = 5000;
exports.searchOptions = {
type: "multicast",
address: getMulticastGroup(1),
port: 5568,
validateResponse (msg, info) {
return msg.toString('utf8', 4, 13) === "ASC-E1.17";
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.defaultPort = 5568;
exports.ready = function ready(device) {
const d = device;
d.data.universes = {};
d.data.source = "Unknown Source";
d.data.priorities = {};
d.data.source = 'Unknown Source';
d.data.orderedUniverses = [];
// device.draw();
for(let i = 1; i <= 16; i++){
d.connection.addMembership(getMulticastGroup(i));
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;
exports.data = function data(device, buf) {
let universe = device.data.universes[universeIndex];
let priorities = device.data.priorities[universeIndex];
const univ = buf.readUInt16BE(113);
const d = device;
let u = d.data.universes[univ];
if(!u){
d.data.universes[univ] = {};
u = d.data.universes[univ];
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];
}
u.sequence = buf.readUInt8(111);
u.priority = buf.readUInt8(108);
u.cid = buf.toString('hex', 22, 38);
u.slots = Array.prototype.slice.call(buf, 126);
if(buf.readUInt8(125) !== 0){
u.startCode = buf.readUInt8(125);
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];
d.data.source = buf.toString('utf8', 44, 108);
d.displayName = `${d.data.source} sACN`;
d.data.ip = d.addresses[0];
if(!_.includes(d.data.orderedUniverses, univ)){
d.data.orderedUniverses.push(univ);
d.data.orderedUniverses.sort();
u.slotElems = [];
u.slotElemsSet = false;
d.draw();
updateElementsCache(d, d.data.universes, d.data.orderedUniverses);
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);
}
d.update("universeData", {
u: univ,
universe: u,
startCode: u.startCode
});
};
exports.heartbeat = function heartbeat(device) {
};
exports.heartbeat = function heartbeat(device) {};
let lastUpdate = Date.now();
exports.update = function update(device, doc, updateType, updateData){
const d = device;
exports.update = function update(_device, doc, updateType, updateData) {
const device = _device;
const data = updateData;
if(updateType === "universeData" && data.universe){
if(Date.now() - lastUpdate > 1000){
if (updateType === 'universeData' && data.universe) {
if (Date.now() - lastUpdate > 1000) {
lastUpdate = Date.now();
updateElementsCache(d, d.data.universes, d.data.orderedUniverses);
device.update('elementCache');
}
const $elem = doc.getElementById(`universe-${data.u}`);
if($elem && data.universe.slotElemsSet){
if(data.universe.priority > 0){
for(let i = 0; i < 512; i++){
data.universe.slotElems[i].innerText = data.universe.slots[i];
}
const $code = doc.getElementById(`universe-${data.u}-code`);
if(data.startCode === 0xDD){
$code.innerText = "Net3"
}else if(data.startCode === 0x17){
$code.innerText = "Text"
}else if(data.startCode === 0xCF){
$code.innerText = "SIP"
}else if(data.startCode === 0xCC){
$code.innerText = "RDM"
}
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];
}
}else{
d.draw();
updateElementsCache(d, d.data.universes, d.data.orderedUniverses);
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];
}else if(updateType === "elementCache"){
data.orderedUniverses.forEach(univ => {
for(let i = 0; i < 512; i++){
data.universes[univ].slotElems[i] = doc.getElementById(`${univ}-${i}`);
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;
}
data.universes[univ].slotElemsSet = true;
});
}
}
function updateElementsCache(device, univ, ordered){
device.update("elementCache", {
universes: univ,
orderedUniverses: ordered
});
}
};
// From https://github.com/hhromic/e131-node/blob/master/lib/e131.js
function getMulticastGroup(universe) {
@@ -154,5 +140,4 @@ function getMulticastGroup(universe) {
throw new RangeError('universe should be in the range [1-63999]');
}
return `239.255.${universe >> 8}.${universe & 0xff}`;
}
}
+6 -6
View File
@@ -4,15 +4,15 @@
<div id="universes">
<% data.orderedUniverses.forEach(univ => {
let universe = data.universes[univ];
<% data.orderedUniverses.forEach(universeIndex => {
let universe = data.universes[universeIndex];
%>
<table id="universe-<%= univ %>">
<table id="universe-<%= universeIndex %>">
<tr>
<td class="data" colspan="2">
Universe
<br><em><%= univ %></em>
<br><em><%= universeIndex %></em>
</td>
<td class="data" colspan="2">
Priority
@@ -28,7 +28,7 @@
</td>
<td class="data" colspan="2">
Flavor
<br><em id="universe-<%= univ %>-code">0</em>
<br><em id="universe-<%= universeIndex %>-code">0</em>
</td>
</tr>
@@ -45,7 +45,7 @@
<th><%= row*16+1 %></th>
<% for(col=0; col<16; col++){ %>
<% slot++%>
<td id="<%= univ %>-<%= row*16+col %>" title="<%= univ %>/<%= slot %>"><%= universe.slots[slot-1] %></td>
<td id="<%= universeIndex %>-<%= row*16+col %>" title="<%= universeIndex %>/<%= slot %>"><%= universe.slots[slot-1] %></td>
<% } %>
</tr>
<% } %>
+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>
<% } %>
+35 -37
View File
@@ -1,58 +1,56 @@
exports.defaultName = 'Dataton Watchout';
exports.connectionType = 'TCPsocket';
exports.heartbeatInterval = 500;
exports.searchOptions = {
type: 'TCPport',
searchBuffer: Buffer.from('authenticate 1\n', 'ascii'),
testPort: 3040,
validateResponse (msg, info) {
return msg.toString().substring(0, 5) === 'Ready';
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.defaultPort = 3040;
exports.ready = function ready(device) {
device.send('authenticate 1\n');
};
exports.data = function data(device, message) {
const msg = message.toString();
const d = device;
exports.data = function data(_device, _message) {
const message = _message.toString();
const device = _device;
if (msg.substring(0, 5) === 'Ready') {
d.send('getStatus\n');
if (message.substring(0, 5) === 'Ready') {
// device.send('getStatus\n');
} else if (message.substring(0, 5) === 'Reply') {
const arr = message.split(' ');
}else if (msg.substring(0, 5) === 'Reply') {
const arr = msg.split(' ');
device.data.showName = '';
d.data.showName = '';
let i = 0;
while (arr[i][arr[i].length - 1] !== '"') {
i++;
d.data.showName += `${arr[i]} `;
device.data.showName += `${arr[i]} `;
}
d.data.showName = d.data.showName.substring(
1,
d.data.showName.length - 2
);
device.data.showName = device.data.showName.substring(1, device.data.showName.length - 2);
i--;
d.data.busy = arr[i + 2];
d.data.health = arr[i + 3];
d.data.displayOpen = arr[i + 4];
d.data.showActive = arr[i + 5];
d.data.programmerOnline = arr[i + 6];
d.data.position = Number(arr[i + 7]).toFixed(2);
d.data.rate = arr[i + 8];
d.data.standby = arr[i + 9];
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(d, 'defaultName', d.data.showName);
d.draw();
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 heartbeat(device) {
+9 -9
View File
@@ -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>
+56 -63
View File
@@ -1,21 +1,25 @@
exports.defaultName = 'X32 Mixer';
exports.connectionType = 'osc-udp';
exports.heartbeatInterval = 9000;
exports.searchOptions = {
type: 'UDPsocket',
searchBuffer: Buffer.from([0x2f, 0x78, 0x69, 0x6e, 0x66, 0x6f]),
devicePort: 10023,
listenPort: 0,
validateResponse(msg, info) {
return msg.toString().indexOf('/xinfo') === 0;
exports.config = {
defaultName: 'X32 Mixer',
connectionType: 'osc-udp',
defaultPort: 10023,
mayChangePort: false,
heartbeatInterval: 9000,
heartbeatTimeout: 11000,
searchOptions: {
type: 'UDPsocket',
searchBuffer: Buffer.from([0x2f, 0x78, 0x69, 0x6e, 0x66, 0x6f]),
devicePort: 10023,
listenPort: 0,
validateResponse(msg, info) {
return msg.toString().includes('/xinfo');
},
},
};
exports.defaultPort = 10023;
exports.ready = function ready(device) {
const d = device;
d.data.X32 = new Console();
d.send('/xinfo');
exports.ready = function ready(_device) {
const device = _device;
device.data = new Console();
device.send('/xinfo');
device.send('/batchsubscribe', [
{ type: 's', value: '/ch/meters' },
@@ -40,25 +44,25 @@ function parseAddress(msg) {
return addr;
}
exports.data = function data(device, oscData) {
this.deviceInfoUpdate(device, 'status', 'ok');
exports.data = function data(_device, oscData) {
this.deviceInfoUpdate(_device, 'status', 'ok');
const d = device;
const device = _device;
if (oscData.address === '/xinfo') {
d.data.X32.info.name = oscData.args[1];
d.data.X32.info.ip = oscData.args[0];
d.data.X32.info.firmware = oscData.args[3];
d.data.X32.info.model = oscData.args[2];
device.data.info.name = oscData.args[1];
device.data.info.ip = oscData.args[0];
device.data.info.firmware = oscData.args[3];
device.data.info.model = oscData.args[2];
this.deviceInfoUpdate(device, 'defaultName', d.data.X32.info.name);
this.deviceInfoUpdate(_device, 'defaultName', device.data.info.name);
d.send('/main/st/config/name');
device.send('/main/st/config/name');
for (let i = 0; i <= 32; i++) {
d.send(`/ch/${i.toString().padStart(2, '0')}/config/name`);
device.send(`/ch/${i.toString().padStart(2, '0')}/config/name`);
}
d.draw();
device.draw();
} else if (oscData.address.includes('/ch/meters')) {
const buf = Buffer.from(oscData.args[0]);
@@ -66,14 +70,12 @@ exports.data = function data(device, oscData) {
for (let i = 0; i < 70; i++) {
if (i >= 0 && i < 32) {
// These are channel meters
d.data.X32.inputs.channels[i].meter = Console.getBehringerDB(
buf.readFloatLE(offset)
);
device.data.inputs.channels[i].meter = Console.getBehringerDB(buf.readFloatLE(offset));
}
offset += 4;
}
d.draw();
device.draw();
} else if (oscData.address.includes('/main/meters')) {
const buf = Buffer.from(oscData.args[0]);
let offset = 4; // skip first 4 bytes they are the length bytes
@@ -81,14 +83,10 @@ exports.data = function data(device, oscData) {
for (let i = 0; i < 49; i++) {
if (i === 22) {
// STEREO LEFT METER
d.data.X32.main.stereo.meter[0] = Console.getBehringerDB(
buf.readFloatLE(offset)
);
device.data.main.stereo.meter[0] = Console.getBehringerDB(buf.readFloatLE(offset));
} else if (i === 23) {
// STEREO RIGHT METER
d.data.X32.main.stereo.meter[1] = Console.getBehringerDB(
buf.readFloatLE(offset)
);
device.data.main.stereo.meter[1] = Console.getBehringerDB(buf.readFloatLE(offset));
}
offset += 4;
}
@@ -97,58 +95,53 @@ exports.data = function data(device, oscData) {
if (addr[0] === 'ch') {
const channel = Number(addr[1]);
d.data.X32.inputs.channels[channel - 1].fader = oscData.args[0];
d.data.X32.inputs.channels[channel - 1].faderDB = Console.getBehringerDB(
oscData.args[0]
);
device.data.inputs.channels[channel - 1].fader = oscData.args[0];
device.data.inputs.channels[channel - 1].faderDB = Console.getBehringerDB(oscData.args[0]);
} else if (addr[0] === 'main') {
d.data.X32.main.stereo.fader = oscData.args[0];
d.data.X32.main.stereo.faderDB = Console.getBehringerDB(oscData.args[0]);
device.data.main.stereo.fader = oscData.args[0];
device.data.main.stereo.faderDB = Console.getBehringerDB(oscData.args[0]);
}
d.draw();
device.draw();
} else if (oscData.address.includes('/mix/on')) {
const addr = parseAddress(oscData.address);
if (addr[0] === 'ch') {
const channel = Number(addr[1]);
d.data.X32.inputs.channels[channel - 1].mute = oscData.args[0];
d.send(`/ch/${addr[1]}/mix/fader`);
device.data.inputs.channels[channel - 1].mute = oscData.args[0];
device.send(`/ch/${addr[1]}/mix/fader`);
} else if (addr[0] === 'main') {
d.data.X32.main.stereo.mute = oscData.args[0];
d.send(`/main/${addr[1]}/mix/fader`);
device.data.main.stereo.mute = oscData.args[0];
device.send(`/main/${addr[1]}/mix/fader`);
}
d.draw();
device.draw();
} else if (oscData.address.includes('/config/name')) {
const addr = parseAddress(oscData.address);
if (addr[0] === 'main') {
if (addr[1] === 'st') {
d.data.X32.main.stereo.name = oscData.args[0];
if (d.data.X32.main.stereo.name === '') {
d.data.X32.main.stereo.name = 'LR';
device.data.main.stereo.name = oscData.args[0];
if (device.data.main.stereo.name === '') {
device.data.main.stereo.name = 'LR';
}
d.send(`/main/${addr[1]}/config/color`);
device.send(`/main/${addr[1]}/config/color`);
}
} else if (addr[0] === 'ch') {
const channel = Number(addr[1]);
d.data.X32.inputs.channels[channel - 1].name = oscData.args[0];
d.send(`/ch/${addr[1]}/config/color`);
device.data.inputs.channels[channel - 1].name = oscData.args[0];
device.send(`/ch/${addr[1]}/config/color`);
}
d.draw();
device.draw();
} else if (oscData.address.includes('/config/color')) {
const addr = parseAddress(oscData.address);
if (addr[0] === 'main') {
d.data.X32.main.stereo.color = oscData.args[0];
d.send(`/main/${addr[1]}/mix/on`);
device.data.main.stereo.color = oscData.args[0];
device.send(`/main/${addr[1]}/mix/on`);
} else if (addr[0] === 'ch') {
const channel = Number(addr[1]);
d.data.X32.inputs.channels[channel - 1].color = oscData.args[0];
d.send(`/ch/${addr[1]}/mix/on`);
device.data.inputs.channels[channel - 1].color = oscData.args[0];
device.send(`/ch/${addr[1]}/mix/on`);
}
d.draw();
} else {
// console.log(oscData);
device.draw();
}
// console.log(msg)
};
exports.heartbeat = function heartbeat(device) {
+1 -7
View File
@@ -30,7 +30,6 @@ tr td:first-child {
padding: 1px 6px;
border-radius: 10px;
color: black;
font-family: PlexMono !important;
text-align: center;
text-overflow: ellipsis;
white-space: nowrap;
@@ -136,12 +135,7 @@ input[type='range']::-webkit-slider-runnable-track {
.meter {
height: 3px;
background: linear-gradient(
90deg,
rgba(19, 126, 30, 1) 0%,
rgba(255, 238, 30, 1) 85%,
rgba(255, 0, 0, 1) 100%
);
background: linear-gradient(90deg, rgba(19, 126, 30, 1) 0%, rgba(255, 238, 30, 1) 85%, rgba(255, 0, 0, 1) 100%);
}
/* need to find a way to match the color of this with the table row */
+14 -14
View File
@@ -1,6 +1,6 @@
<header>
<h1><%= listName %></h1>
<h2><%= data.model %></h2>
<h2><%= data.info.model %></h2>
</header>
<table class="cv-table">
@@ -13,15 +13,15 @@
</tr>
<tr>
<td width="40px">LR</td>
<td width="100px"><div class="color color-<%= data.X32.main.stereo.color %>"><%- data.X32.main.stereo.name%></div></td>
<td><div class="fader-mute mute-<%=data.X32.main.stereo.mute%>">M</div></td>
<td><%= formatAsDB(data.X32.main.stereo.faderDB) %></td>
<td width="100px"><div class="color color-<%= data.main.stereo.color %>"><%- data.main.stereo.name%></div></td>
<td><div class="fader-mute mute-<%=data.main.stereo.mute%>">M</div></td>
<td><%= formatAsDB(data.main.stereo.faderDB) %></td>
<td>
<% let style = `style=width:${Math.abs(data.X32.main.stereo.meter[0] - 10)}%`; %>
<% let style = `style=width:${Math.abs(data.main.stereo.meter[0] - 10)}%`; %>
<div class="meter">
<div class="meter-cover" <%= style %>></div>
</div>
<% style = `style=width:${Math.abs(data.X32.main.stereo.meter[1] - 10)}%`; %>
<% style = `style=width:${Math.abs(data.main.stereo.meter[1] - 10)}%`; %>
<div class="meter">
<div class="meter-cover" <%= style %>></div>
</div>
@@ -29,22 +29,22 @@
type="range"
min="0"
max="100"
value="<%= data.X32.main.stereo.fader*100 %>"
value="<%= data.main.stereo.fader*100 %>"
disabled />
</td>
</tr>
<% for(var i=0; i<32; i++){ %> <% if(data.X32.inputs.channels[i].name == "end"){break;} %>
<% for(var i=0; i<32; i++){ %> <% if(data.inputs.channels[i].name == "end"){break;} %>
<tr>
<td><%= i+1 %></td>
<td>
<div class="color color-<%= data.X32.inputs.channels[i].color %>">
<%- data.X32.inputs.channels[i].name || i+1 %>
<div class="color color-<%= data.inputs.channels[i].color %>">
<%- data.inputs.channels[i].name || i+1 %>
</div>
</td>
<td><div class="fader-mute mute-<%=data.X32.inputs.channels[i].mute%>">M</div></td>
<td><%= formatAsDB(data.X32.inputs.channels[i].faderDB) %></td>
<td><div class="fader-mute mute-<%=data.inputs.channels[i].mute%>">M</div></td>
<td><%= formatAsDB(data.inputs.channels[i].faderDB) %></td>
<td>
<% let style = `style=width:${Math.abs(data.X32.inputs.channels[i].meter - 10)}%`; %>
<% let style = `style=width:${Math.abs(data.inputs.channels[i].meter - 10)}%`; %>
<div class="meter">
<div class="meter-cover" <%= style %>></div>
</div>
@@ -52,7 +52,7 @@
type="range"
min="0"
max="100"
value="<%= data.X32.inputs.channels[i].fader*100 %>"
value="<%= data.inputs.channels[i].fader*100 %>"
disabled />
</td>
</tr>
+21 -27
View File
@@ -1,16 +1,20 @@
exports.defaultName = 'X Air Mixer';
exports.connectionType = 'UDPsocket';
exports.heartbeatInterval = 10000;
exports.searchOptions = {
type: 'UDPsocket',
searchBuffer: Buffer.from([0x2f, 0x78, 0x69, 0x6e, 0x66, 0x6f]),
devicePort: 10024,
listenPort: 0,
validateResponse(msg, info) {
return msg.toString().indexOf('/xinfo') === 0;
exports.config = {
defaultName: 'X Air Mixer',
connectionType: 'UDPsocket',
defaultPort: 10024,
mayChangePort: false,
heartbeatInterval: 9000,
heartbeatTimeout: 15000,
searchOptions: {
type: 'UDPsocket',
searchBuffer: Buffer.from([0x2f, 0x78, 0x69, 0x6e, 0x66, 0x6f]),
devicePort: 10024,
listenPort: 0,
validateResponse(msg, info) {
return msg.toString().startWith('/xinfo');
},
},
};
exports.defaultPort = 10024;
exports.ready = function ready(device) {
const d = device;
@@ -25,10 +29,6 @@ exports.ready = function ready(device) {
d.data.stereoMute = 0;
d.send('/xinfo');
// device.send(Buffer.from("\x2f\x62\x61\x74\x63\x68\x73\x75\x62\x73\x63\x72\x69\x62\x65\x00\x2c\x73\x73\x69\x69\x69\x00\x00\x6d\x65\x74\x65\x72\x73\x2f\x30\x00\x00\x00\x00\x2f\x6d\x65\x74\x65\x72\x73\x2f\x30\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"));
// device.send(Buffer.from("/batchsubscribe\x00,ssiii\x00\x00meters/0\x00\x00\x00\x00/meters/0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01"));
// device.send(Buffer.from("/subscribe\x00,si\x00/-stat/solosw/01\x001"));
};
function parseAddress(msg) {
@@ -71,31 +71,25 @@ exports.data = function data(device, buf) {
d.send('/lr/mix/on\x00\x00\x00\x00');
for (let i = 0; i <= 32; i++) {
d.send(
Buffer.from(
`/ch/${i.toString().padStart(2, '0')}/config/name\x00\x00\x00\x00`
)
);
d.send(Buffer.from(`/ch/${i.toString().padStart(2, '0')}/config/name\x00\x00\x00\x00`));
}
d.draw();
} else if (msg[0] === '/meters/0') {
// console.log(msg)
} else if (msg[0].indexOf('/mix/fader') >= 0) {
} else if (msg[0].includes('/mix/fader')) {
const addr = parseAddress(msg[0]);
const channel = Number(addr[1]);
if (addr[0] === 'ch') {
d.data.channelFaders[channel - 1] = buf.readFloatBE(24);
d.data.channelFadersDB[channel - 1] = convertToDBTheBehringerWay(
buf.readFloatBE(24)
);
d.data.channelFadersDB[channel - 1] = convertToDBTheBehringerWay(buf.readFloatBE(24));
} else if (addr[0] === 'lr') {
d.data.stereoFader = buf.readFloatBE(20);
d.data.stereoFaderDB = convertToDBTheBehringerWay(buf.readFloatBE(20));
}
d.draw();
} else if (msg[0].indexOf('/mix/on') >= 0) {
} else if (msg[0].includes('/mix/on')) {
const addr = parseAddress(msg[0]);
const channel = Number(addr[1]);
@@ -106,13 +100,13 @@ exports.data = function data(device, buf) {
}
device.draw();
device.send(`/ch/${addr[1]}/mix/fader\x00\x00\x00\x00`);
} else if (msg[0].indexOf('/config/name') > 0) {
} else if (msg[0].includes('/config/name')) {
const addr = parseAddress(msg[0]);
const channel = Number(addr[1]);
d.data.channelNames[channel - 1] = msg[4];
d.draw();
d.send(`/ch/${addr[1]}/config/color\x00\x00\x00\x00`);
} else if (msg[0].indexOf('/config/color') > 0) {
} else if (msg[0].includes('/config/color')) {
const addr = parseAddress(msg[0]);
const channel = Number(addr[1]);
d.data.channelColors[channel - 1] = buf.readInt8(27);
+1 -1
View File
@@ -128,7 +128,7 @@ input[type='range']::-webkit-slider-runnable-track {
border-radius: 4px;
}
.infin{
.infin {
font-size: 20px;
vertical-align: middle;
}
+63 -52
View File
@@ -15,6 +15,16 @@ window.init = function init() {
ipcRenderer.send('enableDeviceDropdown');
ipcRenderer.send('enableSearchAll');
// load autoUpdate setting from storage and send to main process
const autoUpdate = JSON.parse(localStorage.getItem('autoUpdate'));
if (autoUpdate !== undefined && autoUpdate !== null) {
if (autoUpdate) {
ipcRenderer.send('checkForUpdates');
}
// send message so main process knows the state of autoUpdate
ipcRenderer.send('setAutoUpdate', autoUpdate);
}
PLUGINS.init(() => {
VIEW.init();
SAVESLOTS.loadDevices();
@@ -28,12 +38,12 @@ window.init = function init() {
document.getElementById('device-settings-table').onclick = function settingsClick(e) {
e.stopPropagation();
};
document.getElementById('device-settings-name').onchange = function nameChange(e) {
e.stopPropagation();
DEVICE.changeActiveName(e.target.value);
};
document.getElementById('device-settings-plugin-dropdown').onchange = function dropdownChange(e) {
e.stopPropagation();
DEVICE.changeActiveType(e.target.value);
@@ -58,20 +68,18 @@ window.init = function init() {
}
};
document.getElementById('save-slot-1').onclick = function slot1click(e) {
e.stopPropagation();
SAVESLOTS.loadSlot(1);
};
const saveSlots = document.getElementsByClassName('save-slot');
document.getElementById('save-slot-2').onclick = function slot2click(e) {
e.stopPropagation();
SAVESLOTS.loadSlot(2);
};
document.getElementById('save-slot-3').onclick = function slot3click(e) {
e.stopPropagation();
SAVESLOTS.loadSlot(3);
};
for (let i = 0; i < saveSlots.length; i++) {
const saveSlot = saveSlots[i];
saveSlot.addEventListener('click', (event) => {
// get save slot from button id save-slot-1 = 1
const saveSlotIndex = parseInt(event.target.id.replace('save-slot-', ''), 10);
if (saveSlotIndex) {
SAVESLOTS.loadSlot(saveSlotIndex);
}
});
}
document.getElementById('refresh-device-button').onclick = function refreshClick(e) {
e.stopPropagation();
@@ -87,48 +95,46 @@ window.init = function init() {
};
document.getElementById('add-device-button').onchange = function addDeviceClick(e) {
DEVICE.registerDevice({
type: e.target.value,
defaultName: 'New Device',
port: undefined,
addresses: [],
});
const newDevice = DEVICE.registerDevice(
{
type: e.target.value,
defaultName: 'New Device',
port: undefined,
addresses: [],
},
'fromAddButton'
);
e.target.selectedIndex = 0;
VIEW.switchDevice(newDevice.id);
SAVESLOTS.saveAll();
};
document.onkeyup = function keyUp(e) {
if(e.key === 'ArrowUp'){
VIEW.selectPreviousDevice();
}else if(e.key === 'ArrowDown'){
VIEW.selectNextDevice();
document.getElementById('network-info-button').onclick = function fooBar(e) {
ipcRenderer.send('openNetworkInfoWindow');
};
}else if(e.key === 'Tab'){
if (
document.activeElement.tagName !== 'INPUT' &&
document.activeElement.tagName !== 'SELECT'
) {
document.onkeyup = function keyUp(e) {
if (e.key === 'ArrowUp') {
VIEW.selectPreviousDevice();
} else if (e.key === 'ArrowDown') {
VIEW.selectNextDevice();
} else if (e.key === 'Tab') {
if (document.activeElement.tagName !== 'INPUT' && document.activeElement.tagName !== 'SELECT') {
document.getElementById('device-settings-name').select();
}
}
};
document.getElementById('device-list-col').onclick = function deviceListClick(e) {
try {
document
.querySelector('#device-list .active-device')
.classList.remove('active-device');
document.querySelector('#device-list .active-device').classList.remove('active-device');
ipcRenderer.send('disableDeviceDropdown', '');
} catch (err) {
// console.log(err)
}
try {
document
.getElementsByClassName('active-device-outline')[0]
.classList.remove('active-device-outline');
document.getElementsByClassName('active-device-outline')[0].classList.remove('active-device-outline');
} catch (err) {
// console.log(err)
}
@@ -145,28 +151,33 @@ ipcRenderer.on('setActiveDevicePinned', (event, message) => {
}
});
ipcRenderer.on('doSearch', (event, message) => {
ipcRenderer.on('searchAll', (event, message) => {
window.searchAll();
});
ipcRenderer.on('doDelete', (event, message) => {
ipcRenderer.on('deleteActive', (event, message) => {
DEVICE.deleteActive();
VIEW.selectPreviousDevice();
});
ipcRenderer.on('resetViews', (event, message) => {
SAVESLOTS.resetSlots();
ipcRenderer.on('clearSavedData', (event, message) => {
SAVESLOTS.clearSavedData();
});
ipcRenderer.on('doSlots1', (event, message) => {
SAVESLOTS.loadSlot(1);
ipcRenderer.on('loadSlot', (event, slot) => {
if (slot) {
SAVESLOTS.loadSlot(slot);
}
});
ipcRenderer.on('doSlots2', (event, message) => {
SAVESLOTS.loadSlot(2);
});
ipcRenderer.on('doSlots3', (event, message) => {
SAVESLOTS.loadSlot(3);
// message from main process to set autoUpdate state
ipcRenderer.on('setAutoUpdate', (event, autoUpdate) => {
localStorage.setItem('autoUpdate', autoUpdate);
if (autoUpdate) {
ipcRenderer.send('checkForUpdates');
}
// message to main process that we have updated the state
ipcRenderer.send('setAutoUpdate', autoUpdate);
});
function switchClass(element, className) {
@@ -177,4 +188,4 @@ function switchClass(element, className) {
}
element.classList.add(className);
}
window.switchClass = switchClass;
window.switchClass = switchClass;
+33 -21
View File
@@ -27,6 +27,9 @@ a {
.left {
float: left;
}
.right {
float: right;
}
#main {
box-sizing: border-box;
@@ -53,13 +56,14 @@ a {
text-align: right;
padding-left: 100px;
padding-right: 7px;
user-select: text;
-webkit-app-region: drag;
z-index: 1000;
/*border-bottom: black 1px solid;*/
/*background: linear-gradient(#3f3f3f 0%, #343434 100%);*/
/*background-color: #363636;*/
/*background-color: rgba(0, 0, 0, 0.4);*/
}
#view-buttons-bar .active {
background-color: rgba(255, 255, 255, 0.3);
color: white;
border: #61c650;
border-style: solid;
}
#view-buttons-bar button {
background-color: rgba(255, 255, 255, 0.07);
@@ -123,12 +127,16 @@ a {
width: 210px;
pointer-events: none;
}
#device-tools {
#device-inspector {
position: absolute;
bottom: 210px;
left: 0px;
bottom: 0px;
width: 100%;
}
#device-tools {
/* position: absolute;
bottom: 210px;
left: 0px;*/
width: 262px;
height: 30px;
padding-right: 10px;
/*background: linear-gradient(#2f2f2f 0%, #2c2c2c 100%);*/
@@ -142,10 +150,7 @@ a {
}
#device-settings {
position: absolute;
left: 0px;
bottom: 0px;
height: 200px;
height: 300px;
padding: 5px;
color: white;
font-weight: 300;
@@ -165,7 +170,7 @@ a {
display: none;
}
#device-settings h3 {
padding-top: 20px;
padding-top: 90px;
text-align: center;
font-weight: 300;
}
@@ -187,7 +192,7 @@ a {
.device-pin {
position: absolute;
right: 15px;
top: 8px;
top: 4px;
height: 20px;
padding: inherit;
}
@@ -220,6 +225,7 @@ input {
color: #fff;
font-size: 16px;
font-weight: 500;
cursor: text;
}
select {
display: block;
@@ -250,6 +256,7 @@ input[type='checkbox'] {
width: 14px;
padding: 2px;
-webkit-appearance: none;
cursor: default;
}
input[type='checkbox']:checked {
background-color: #616064;
@@ -267,6 +274,10 @@ input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input:disabled {
color: gray;
cursor: not-allowed;
}
button,
select.button {
box-sizing: content-box;
@@ -291,10 +302,6 @@ button:hover {
button:focus {
outline: none;
}
button.active {
background-color: rgba(255, 255, 255, 0.3);
color: white;
}
button:disabled {
background: rgba(255, 255, 255, 0.03);
}
@@ -310,8 +317,8 @@ select.button:focus {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: local('img/Material Icons'), local('img/MaterialIcons-Regular'),
url(img/MaterialIcons-Regular.ttf) format('truetype');
src: local('Material Icons'), local('MaterialIcons-Regular'),
url(../font/MaterialIcons-Regular.ttf) format('truetype');
}
.material-icons {
font-family: 'Material Icons';
@@ -337,3 +344,8 @@ select.button:focus {
/* Support for IE. */
font-feature-settings: 'liga';
}
/* only allow select on input fields */
body :not(input):not(select):not(textarea) {
user-select: none;
}
@@ -1,5 +1,5 @@
body {
font-family: sans-serif;
font-family: system-ui;
margin: 0px;
margin-top: 40px;
user-select: none;
@@ -11,14 +11,14 @@ header {
position: fixed;
top: 0px;
left: 0px;
height: 36px;
height: 33px;
width: 100%;
box-sizing: content-box;
white-space: nowrap;
box-sizing: border-box;
padding-right: 34px;
overflow: hidden;
background-color: #111111;
border-bottom: black 1px solid;
-webkit-user-select: none;
-webkit-app-region: drag;
}
h1,
@@ -32,11 +32,13 @@ h6 {
header h1,
header h2 {
margin: 8px;
margin: 4px;
margin-left: 20px;
font-weight: normal;
font-size: 18px;
float: left;
height: 24px;
overflow: hidden;
}
header h2 {
color: #b6b6b6;
@@ -104,3 +106,16 @@ table.cv-table td {
color: #fdea08;
margin: 1px;
}
button {
display: block;
box-sizing: content-box;
background-color: rgba(255, 255, 255, 0.15);
height: 28px;
padding: 2px 20px;
margin: 0px;
border: rgba(255, 255, 255, 0.1) 1px solid;
border-radius: 4px;
color: #fff;
font-size: 16px;
font-weight: 500;
}

Before

Width:  |  Height:  |  Size: 401 KiB

After

Width:  |  Height:  |  Size: 401 KiB

Before

Width:  |  Height:  |  Size: 216 KiB

After

Width:  |  Height:  |  Size: 216 KiB

Before

Width:  |  Height:  |  Size: 224 B

After

Width:  |  Height:  |  Size: 224 B

Before

Width:  |  Height:  |  Size: 138 B

After

Width:  |  Height:  |  Size: 138 B

Before

Width:  |  Height:  |  Size: 294 B

After

Width:  |  Height:  |  Size: 294 B

Before

Width:  |  Height:  |  Size: 212 B

After

Width:  |  Height:  |  Size: 212 B

Before

Width:  |  Height:  |  Size: 171 B

After

Width:  |  Height:  |  Size: 171 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 B

Before

Width:  |  Height:  |  Size: 236 B

After

Width:  |  Height:  |  Size: 236 B

Before

Width:  |  Height:  |  Size: 415 B

After

Width:  |  Height:  |  Size: 415 B

Before

Width:  |  Height:  |  Size: 383 B

After

Width:  |  Height:  |  Size: 383 B

+93 -60
View File
@@ -2,16 +2,16 @@ const { v4: uuid } = require('uuid');
const osc = require('osc');
const net = require('net');
const udp = require('dgram');
const _ = require('lodash');
const { Atem } = require('atem-connection');
const PLUGINS = require('./plugins.js');
const VIEW = require('./view.js');
const SAVESLOTS = require('./saveSlots.js');
const SEARCH = require('./search.js');
const devices = {};
module.exports.all = devices;
function registerDevice(newDevice) {
function registerDevice(newDevice, discoveryMethod) {
if (PLUGINS.all[newDevice.type] === undefined) {
console.error(`Plugin for device ${newDevice.type} does not exist.`);
return true;
@@ -22,22 +22,11 @@ function registerDevice(newDevice) {
initElements[i].style.display = 'none';
}
// only register device if it hasn't already been added
if (newDevice.addresses.length > 0) {
const existing = _.find(devices, (e) => {
const typeMatch = e.type === newDevice.type;
const addressMatch =
JSON.stringify(e.addresses) === JSON.stringify(newDevice.addresses);
return typeMatch && addressMatch;
});
if (existing) {
return false;
}
// prevent duplicate devices from being added via search
if (isDeviceAlreadyAdded(newDevice) && discoveryMethod === 'fromSearch') {
return false;
}
// console.log("Registered new "+newDevice.type)
const id = newDevice.id || uuid();
devices[id] = {
id,
@@ -49,10 +38,14 @@ function registerDevice(newDevice) {
port: newDevice.port,
addresses: newDevice.addresses,
data: {},
templates: {},
fields: newDevice.fields || {},
pinIndex: false,
lastDrawn: 0,
lastHeartbeat: 0,
lastMessage: 0,
heartbeatInterval: PLUGINS.all[newDevice.type].heartbeatInterval,
heartbeatTimeout: PLUGINS.all[newDevice.type].heartbeatTimeout,
draw() {
VIEW.draw(this);
},
@@ -63,11 +56,22 @@ function registerDevice(newDevice) {
this.draw();
}
},
getNetworkInterfaces() {
return SEARCH.getNetworkInterfaces();
},
};
devices[id].plugin = PLUGINS.all[newDevice.type];
if (Object.keys(devices[id].fields).length === 0 && PLUGINS.all[newDevice.type].config.fields) {
PLUGINS.all[newDevice.type].config.fields.forEach((field) => {
devices[id].fields[field.key] = field.value;
});
}
VIEW.addDeviceToList(devices[id]);
initDeviceConnection(id);
return true;
return devices[id];
}
module.exports.registerDevice = registerDevice;
@@ -76,6 +80,10 @@ function initDeviceConnection(id) {
infoUpdate(device, 'status', 'new');
if (device.port === undefined) {
device.port = device.plugin.config.defaultPort;
}
if (device.port === undefined || device.addresses.length === 0) {
return true;
}
@@ -89,8 +97,8 @@ function initDeviceConnection(id) {
const { type } = devices[id];
const plugins = PLUGINS.all;
if (plugins[type].connectionType.includes('osc')) {
if (plugins[type].connectionType.includes('udp')) {
if (plugins[type].config.connectionType.includes('osc')) {
if (plugins[type].config.connectionType.includes('udp')) {
device.connection = new osc.UDPPort({
localAddress: '0.0.0.0',
remoteAddress: device.addresses[0],
@@ -124,10 +132,8 @@ function initDeviceConnection(id) {
device.send = (address, args) => {
device.connection.send({ address, args });
};
device.plugin = plugins[type];
} else if (plugins[type].connectionType === 'TCPsocket') {
} else if (plugins[type].config.connectionType === 'TCPsocket') {
device.connection = new net.Socket();
device.connection.connect(
{
port: device.port,
@@ -155,29 +161,25 @@ function initDeviceConnection(id) {
// log("SOCK OUT", data);
device.connection.write(data);
};
} else if (plugins[type].connectionType === 'UDPsocket') {
} else if (plugins[type].config.connectionType === 'UDPsocket') {
device.connection = udp.createSocket('udp4');
device.connection.bind({ port: plugins[type].defaultPort }, () => {
device.connection.bind({ port: plugins[type].config.defaultPort }, () => {
plugins[type].ready(device);
device.connection.on('message', (msg, info) => {
plugins[type].data(device, msg);
device.lastMessage = Date.now();
infoUpdate(device, 'status', 'ok');
});
});
device.send = (data) => {
device.connection.send(
Buffer.from(data),
device.port,
device.addresses[0],
(err) => {
// console.log(err);
}
);
device.connection.send(Buffer.from(data), device.port, device.addresses[0], (err) => {
// console.log(err);
});
};
} else if (plugins[type].connectionType === 'multicast') {
} else if (plugins[type].config.connectionType === 'multicast') {
device.connection = udp.createSocket('udp4');
device.connection.bind(device.port, () => {
@@ -185,11 +187,32 @@ function initDeviceConnection(id) {
device.connection.on('message', (msg, info) => {
plugins[type].data(device, msg);
device.lastMessage = Date.now();
infoUpdate(device, 'status', 'ok');
});
});
device.send = (data) => {};
} else if (plugins[type].config.connectionType === 'atem') {
device.connection = new Atem({
// this gets around the no workers nodejs error
disableMultithreaded: true,
});
device.connection.connect(device.addresses[0]);
device.connection.on('connected', () => {
infoUpdate(device, 'status', 'ok');
plugins[type].ready(device);
});
device.connection.on('stateChanged', (state, pathToChange) => {
device.lastMessage = Date.now();
plugins[type].data(device, {
pathToChange,
state,
});
infoUpdate(device, 'status', 'ok');
});
}
return true;
@@ -199,16 +222,16 @@ module.exports.initDeviceConnection = initDeviceConnection;
module.exports.deleteActive = function deleteActive() {
const device = VIEW.getActiveDevice();
const choice = confirm(
`Are you sure you want to delete ${device.type} device "${
device.displayName || device.defaultName
}"?`
`Are you sure you want to delete ${device.type} device "${device.displayName || device.defaultName}"?`
);
if (choice) {
if (device.plugin.connectionType === 'TCPsocket') {
device.connection.destroy();
}
VIEW.removeDeviceFromList(device);
delete devices[device.id];
SAVESLOTS.deleteFromSlots(device);
SAVESLOTS.saveAll();
SAVESLOTS.removeDevice(device);
SAVESLOTS.reloadActiveSlot();
}
};
@@ -216,10 +239,18 @@ module.exports.deleteActive = function deleteActive() {
module.exports.changeActiveType = function changeActiveType(newType) {
const device = VIEW.getActiveDevice();
device.type = newType;
device.fields = [];
device.plugin = PLUGINS.all[newType];
if (PLUGINS.all[device.type].config.fields) {
PLUGINS.all[newType].config.fields.forEach((field) => {
device.fields[field.key] = field.value;
});
}
initDeviceConnection(device.id);
VIEW.draw(device);
// SAVESLOTS.saveAll();
VIEW.updateFields();
};
module.exports.changeActiveIP = function changeActiveIP(newIP) {
@@ -255,7 +286,6 @@ module.exports.changeActivePinIndex = function changeActivePinIndex(newPin) {
module.exports.changePinIndex = function changePinIndex(device, newPin) {
const d = device;
d.pinIndex = newPin;
// SAVESLOTS.saveAll();
};
module.exports.refreshActive = function refreshActive() {
const device = VIEW.getActiveDevice();
@@ -287,7 +317,7 @@ function heartbeat() {
initDeviceConnection(deviceID);
} else if (Date.now() - d.lastMessage > d.heartbeatTimeout) {
infoUpdate(d, 'status', 'broken');
} else if (d.port !== undefined && d.addresses.length > 0) {
} else if (d.port !== undefined && d.addresses.length > 0 && d.send) {
PLUGINS.all[d.type].heartbeat(d);
} else {
//
@@ -295,23 +325,26 @@ function heartbeat() {
d.lastHeartbeat = Date.now();
}
});
// for (let i in devices) {
// const device = devices[i];
// if (Date.now() >= device.lastHeartbeat + device.heartbeatInterval) {
// if (device.status == 'broken') {
// initDeviceConnection(i);
// } else if (Date.now() - device.lastMessage > device.heartbeatTimeout) {
// infoUpdate(device, 'status', 'broken');
// } else {
// if (device.port != undefined && device.addresses.length > 0) {
// PLUGINS.all[device.type].heartbeat(device);
// } else {
// // console.error("Invalid IP/Port on device "+device.name)
// }
// }
// device.lastHeartbeat = Date.now();
// }
// }
}
setInterval(heartbeat, 100);
function isDeviceAlreadyAdded(newDevice) {
let deviceAlreadyAdded = false;
if (newDevice.addresses.length === 0) {
return false;
}
for (let i = 0; i < Object.keys(devices).length; i++) {
const device = devices[Object.keys(devices)[i]];
const typeMatch = device.type === newDevice.type;
const addressMatch = JSON.stringify(device.addresses) === JSON.stringify(newDevice.addresses);
const idMatch = device.id === newDevice.id;
if (typeMatch && addressMatch && !idMatch) {
deviceAlreadyAdded = true;
break;
}
}
return deviceAlreadyAdded;
}
module.exports.isDeviceAlreadyAdded = isDeviceAlreadyAdded;
+1 -1
View File
@@ -1 +1 @@
window.init();
window.init();
+14 -35
View File
@@ -1,4 +1,5 @@
/* eslint-disable global-require */
/* eslint-disable import/no-dynamic-require */
const fs = require('fs');
const _ = require('lodash');
const path = require('path');
@@ -10,59 +11,37 @@ const allPlugins = {};
module.exports.all = allPlugins;
module.exports.init = function init(callback) {
let pluginDirectoryPath = path.normalize(path.join(__dirname, `../plugins`));
const pluginDirectoryPath = path.normalize(path.join(__dirname, `../plugins`));
console.log(`Loading plugin files... ${pluginDirectoryPath}`);
fs.readdir(pluginDirectoryPath, (err, files) => {
files.forEach((plugin)=>{
if (plugin[0] !== '.') {
console.log(`${plugin} started`);
files.forEach((pluginDir) => {
if (pluginDir[0] !== '.') {
allPlugins[pluginDir] = require(path.join(pluginDirectoryPath, `/${pluginDir}/main.js`));
// eslint-disable-next-line import/no-dynamic-require
allPlugins[plugin] = require(path.join(pluginDirectoryPath, `/${plugin}/main.js`));
const plugin = allPlugins[pluginDir];
const p = allPlugins[plugin];
p.deviceInfoUpdate = function deviceInfoUpdate(device, param, value) {
plugin.deviceInfoUpdate = function deviceInfoUpdate(device, param, value) {
DEVICE.infoUpdate(device, param, value);
};
p.draw = (device) => {
plugin.draw = (device) => {
VIEW.draw(device);
};
p.template = _.template(
fs.readFileSync(path.join(pluginDirectoryPath, `/${plugin}/template.ejs`),
'utf8'
)
plugin.template = _.template(
fs.readFileSync(path.join(pluginDirectoryPath, `/${pluginDir}/template.ejs`), 'utf8')
);
p.info = _.template(
fs.readFileSync(path.join(pluginDirectoryPath, `/${plugin}/info.html`),
'utf8'
)
);
plugin.info = _.template(fs.readFileSync(path.join(pluginDirectoryPath, `/${pluginDir}/info.html`), 'utf8'));
plugin.heartbeatTimeout = plugin.config.heartbeatTimeout;
plugin.heartbeatInterval = plugin.config.heartbeatInterval;
if (p.heartbeatTimeout === undefined || p.heartbeatTimeout < 50) {
if(p.heartbeatInterval){
p.heartbeatTimeout = p.heartbeatInterval * 1.5;
}else{
p.heartbeatTimeout = 10000;
}
}
if (p.heartbeatInterval === undefined || p.heartbeatInterval < 50) {
p.heartbeatInterval = 5000;
}
console.log(`${pluginDir} loaded`);
}
});
callback();
});
};
+56 -35
View File
@@ -8,6 +8,13 @@ let savedDevices = [];
const storedSlots = localStorage.getItem('savedSlots');
if (storedSlots) {
savedSlots = JSON.parse(storedSlots);
// can probably be removed but I've noticed some older versions left some [null] slots in the savedSlots
savedSlots = savedSlots.map((savedSlot) => {
if (savedSlot.length === 1 && savedSlot[0] === null) {
return [];
}
return savedSlot;
});
}
const storedDevices = localStorage.getItem('savedDevices');
@@ -19,7 +26,7 @@ function loadSlot(slotIndex) {
VIEW.toggleSlotButtons(slotIndex);
activeSlot = slotIndex;
Object.keys(DEVICE.all).forEach((d)=>{
Object.keys(DEVICE.all).forEach((d) => {
DEVICE.changePinIndex(DEVICE.all[d], false);
});
VIEW.resetPinned();
@@ -33,7 +40,6 @@ function loadSlot(slotIndex) {
if (device.id === savedDevice.id) {
VIEW.pinDevice(device);
VIEW.switchDevice(device.id);
} else if (
device.addresses[0] === savedDevice.addresses[0] &&
device.type === savedDevice.type &&
@@ -43,44 +49,57 @@ function loadSlot(slotIndex) {
VIEW.switchDevice(device.id);
}
});
});
};
}
module.exports.loadSlot = loadSlot;
module.exports.loadDevices = function loadDevices() {
console.log(`Loading ${savedDevices.length} saved devices...`);
for (let i = 0; i < savedDevices.length; i++) {
DEVICE.registerDevice({
type: savedDevices[i].type,
displayName: savedDevices[i].displayName,
defaultName: savedDevices[i].defaultName,
port: savedDevices[i].port,
addresses: savedDevices[i].addresses,
id: savedDevices[i].id,
});
DEVICE.registerDevice(
{
type: savedDevices[i].type,
displayName: savedDevices[i].displayName,
defaultName: savedDevices[i].defaultName,
port: savedDevices[i].port,
addresses: savedDevices[i].addresses,
id: savedDevices[i].id,
fields: savedDevices[i].fields,
},
'fromSave'
);
}
};
module.exports.saveAll = function saveAll() {
console.log('Saving...');
const currentPins = VIEW.getPinnedDevices();
savedSlots[activeSlot] = [];
for (let i = 0; i < currentPins.length; i++) {
savedSlots[activeSlot][i] = {
addresses: currentPins[i].addresses,
type: currentPins[i].type,
id: currentPins[i].id,
};
if (currentPins[i]) {
// only include saved devices
if (currentPins[i].id in DEVICE.all) {
savedSlots[activeSlot][i] = {
addresses: currentPins[i].addresses,
type: currentPins[i].type,
id: currentPins[i].id,
};
}
}
}
// can probably be removed but I've noticed some older versions left some [null] slots in the savedSlots
savedSlots = savedSlots.map((savedSlot) => {
if (savedSlot.length === 1 && savedSlot[0] === null) {
return [];
}
return savedSlot;
});
localStorage.setItem('savedSlots', JSON.stringify(savedSlots));
console.log(
`Saved ${currentPins.length} pinned devices to slot ${activeSlot}!`
);
console.log(`Saved ${currentPins.length} pinned devices to slot ${activeSlot}!`);
savedDevices = [];
let i = 0;
@@ -93,31 +112,33 @@ module.exports.saveAll = function saveAll() {
defaultName: device.defaultName,
port: device.port,
id: device.id,
fields: device.fields,
};
i++;
});
localStorage.setItem('savedDevices', JSON.stringify(savedDevices));
console.log(`Saved ${savedDevices.length} devices to storage!`);
};
module.exports.removeDevice = function removeDevice(_device) {
// remove devices from local savedDevices
savedDevices = savedDevices.filter((device) => device.id !== _device.id);
module.exports.deleteFromSlots = function deleteFromSlots(device) {
for (let i = 1; i <= 3; i++) {
console.log(savedSlots);
for (let j = 0; j < savedSlots[i].length; j++) {
if (savedSlots[i][j].id === device.id) {
delete savedSlots[i][j];
}
}
// remove devices from all saved slots
for (let i = 1; i < savedSlots.length; i++) {
savedSlots[i] = savedSlots[i].filter((device) => device.id !== _device.id);
}
};
// things might have changed so run a save
this.saveAll();
};
module.exports.reloadActiveSlot = function reloadActiveSlot() {
loadSlot(activeSlot);
};
module.exports.resetSlots = function resetSlots() {
localStorage.clear();
module.exports.clearSavedData = function clearSavedData() {
localStorage.removeItem('savedSlots');
localStorage.removeItem('savedDevices');
};
+166 -129
View File
@@ -5,32 +5,58 @@ const net = require('net');
const os = require('os');
const ip = require('ip');
const { Netmask } = require('netmask');
const DEVICE = require('./device.js');
// const SEARCH = require('./search.js');
const PLUGINS = require('./plugins.js');
let searching = false;
let allServers = false;
let validInterfaces = {};
function getServers() {
const interfaces = os.networkInterfaces();
const result = [];
validInterfaces = {};
Object.keys(interfaces).forEach((key) => {
const addresses = interfaces[key];
for (let i = addresses.length; i--; ) {
const address = addresses[i];
if (address.family === 'IPv4' && !address.internal) {
const subnet = ip.subnet(address.address, address.netmask);
if (address.family === 'IPv4' && !address.internal && address.address.substring(0, 3) !== '169') {
let subnet = ip.subnet(address.address, address.netmask);
let current = ip.toLong(subnet.firstAddress);
const last = ip.toLong(subnet.lastAddress) - 1;
let last = ip.toLong(subnet.lastAddress) - 1;
address.searchTruncated = false;
if (last - current > 2296) {
subnet = ip.subnet(address.address, '255.255.248.0');
last = ip.toLong(subnet.lastAddress) - 1;
address.searchTruncated = true;
}
// console.log(`range ${subnet.firstAddress} - ${subnet.lastAddress}`);
address.broadcastAddress = subnet.broadcastAddress;
address.firstSearchAddress = subnet.firstAddress;
address.lastSearchAddress = subnet.lastAddress;
if (!validInterfaces[key]) {
validInterfaces[key] = [];
}
validInterfaces[key].push(address);
while (current++ < last) result.push(ip.fromLong(current));
}
}
});
return result;
}
function getNetworkInterfaces() {
getServers();
return validInterfaces;
}
module.exports.getNetworkInterfaces = getNetworkInterfaces;
const searchSockets = [];
function searchAll() {
@@ -40,46 +66,37 @@ function searchAll() {
searching = true;
ipcRenderer.send('disableSearchAll', '');
document.getElementById('search-button').style.opacity = 0.2;
Object.keys(DEVICE.all).forEach((i) => {
DEVICE.infoUpdate(DEVICE.all[i], 'status', 'refresh');
});
console.log('Searching...');
allServers = getServers();
let TCPFlag = true;
if (allServers.length > 2046) {
if (allServers.length > 2296) {
alert(
'Unable to search for TCP devices - subnet too large!\n\nCue View requires subnet 255.255.248.0 (/21) or smaller.'
);
TCPFlag = false;
}
Object.keys(PLUGINS.all).forEach((p) => {
const plugin = PLUGINS.all[p];
Object.keys(PLUGINS.all).forEach((pluginType) => {
const plugin = PLUGINS.all[pluginType];
try {
const t = plugin.searchOptions.type;
const searchType = plugin.config.searchOptions.type;
if(t === 'TCPport'){
if (searchType === 'TCPport') {
if (TCPFlag) {
searchTCP(p, plugin);
searchTCP(pluginType, plugin.config);
}
}else if(t === 'Bonjour'){
searchBonjour(p, plugin);
}else if(t === 'UDPsocket'){
searchUDP(p, plugin);
}else if(t === 'multicast'){
searchMulticast(p, plugin);
} else if (searchType === 'Bonjour') {
searchBonjour(pluginType, plugin.config);
} else if (searchType === 'UDPsocket') {
searchUDP(pluginType, plugin.config);
} else if (searchType === 'multicast') {
searchMulticast(pluginType, plugin.config);
} else if (searchType === 'UDPScan') {
searchUDPScan(pluginType, plugin.config);
}
} catch (err) {
console.error(`Unable to search for plugin ${p}`);
console.error(`Unable to search for plugin ${pluginType}`);
}
});
@@ -96,135 +113,155 @@ function searchAll() {
}
ipcRenderer.send('enableSearchAll', '');
}, 5000);
};
}, 10000);
}
module.exports.searchAll = searchAll;
function searchBonjour(pluginType, plugin) {
bonjour.find({ type: plugin.searchOptions.bonjourName }, (e) => {
function searchBonjour(pluginType, pluginConfig) {
bonjour.find({ type: pluginConfig.searchOptions.bonjourName }, (e) => {
const validAddresses = [];
e.addresses.forEach((address) => {
if (address.indexOf(':') === -1) {
if (!address.includes(':')) {
validAddresses.push(address);
}
});
DEVICE.registerDevice({
type: pluginType,
defaultName: e.name,
port: e.port,
addresses: validAddresses,
});
DEVICE.registerDevice(
{
type: pluginType,
defaultName: e.name,
port: e.port,
addresses: validAddresses,
},
'fromSearch'
);
});
}
};
function searchTCP(pluginType, plugin) {
function searchTCP(pluginType, pluginConfig) {
for (let i = 0; i < allServers.length; i++) {
TCPtest(allServers[i], pluginType, plugin);
TCPtest(allServers[i], pluginType, pluginConfig);
}
};
}
function TCPtest(ipAddr, pluginType, plugin) {
const client = net.createConnection(plugin.searchOptions.testPort, ipAddr, () => {
client.write(plugin.searchOptions.searchBuffer);
function TCPtest(ipAddr, pluginType, pluginConfig) {
const client = net.createConnection(pluginConfig.searchOptions.testPort, ipAddr, () => {
client.write(pluginConfig.searchOptions.searchBuffer);
});
client.on('data', (data) => {
if (plugin.searchOptions.validateResponse(data)) {
DEVICE.registerDevice({
type: pluginType,
defaultName: plugin.defaultName,
port: plugin.defaultPort,
addresses: [ipAddr],
});
if (pluginConfig.searchOptions.validateResponse(data)) {
client.end(
'',
'utf8',
DEVICE.registerDevice(
{
type: pluginType,
defaultName: pluginConfig.defaultName,
port: pluginConfig.defaultPort,
addresses: [ipAddr],
},
'fromSearch'
)
);
}
client.end();
});
client.on('error', (err) => {
// no device here
});
};
}
function searchUDPScan(pluginType, pluginConfig) {
for (let i = 0; i < Object.keys(validInterfaces).length; i++) {
const interfaceID = Object.keys(validInterfaces)[i];
const interfaceObj = validInterfaces[interfaceID];
interfaceObj.forEach((netInterface) => {
const udpSocket = dgram.createSocket('udp4');
udpSocket.bind(pluginConfig.searchOptions.listenPort, netInterface.address);
function searchUDP(pluginType, plugin) {
const i = searchSockets.push(dgram.createSocket('udp4')) - 1;
searchSockets[i].bind(plugin.searchOptions.listenPort, () => {
// searchSockets[i].send(
// plugin.searchOptions.searchBuffer,
// plugin.searchOptions.devicePort,
// '255.255.255.255',
// (err) => {
// // console.log(err)
// }
// );
// setTimeout(() => {
// searchSockets[i].send(
// plugin.searchOptions.searchBuffer,
// plugin.searchOptions.devicePort,
// '255.255.255.255',
// (err) => {
// // console.log(err)
// }
// );
// }, 100);
// setTimeout(() => {
// console.log('send')
// searchSockets[i].send(
// plugin.searchOptions.searchBuffer,
// plugin.searchOptions.devicePort,
// '255.255.255.255',
// (err) => {
// // console.log(err)
// }
// );
// }, 400);
searchSockets[i].on('message', (msg, info) => {
if (plugin.searchOptions.validateResponse(msg, info, DEVICE.all)) {
searchSockets[i].close();
DEVICE.registerDevice({
type: pluginType,
defaultName: plugin.defaultName,
port: plugin.defaultPort,
addresses: [info.address],
});
}
udpSocket.on('message', (msg, info) => {
if (pluginConfig.searchOptions.validateResponse(msg, info, DEVICE.all)) {
udpSocket.close();
DEVICE.registerDevice(
{
type: pluginType,
defaultName: pluginConfig.defaultName,
port: pluginConfig.defaultPort,
addresses: [info.address],
},
'fromSearch'
);
}
});
const interfaceBlock = new Netmask(netInterface.cidr);
interfaceBlock.forEach((address, long, index) => {
udpSocket.send(pluginConfig.searchOptions.searchBuffer, pluginConfig.searchOptions.devicePort, address);
});
});
});
searchSockets[i].on('listening', () => {
searchSockets[i].setBroadcast(true);
searchSockets[i].send(
plugin.searchOptions.searchBuffer,
plugin.searchOptions.devicePort,
'255.255.255.255',
}
}
function searchUDP(pluginType, pluginConfig) {
for (let i = 0; i < Object.keys(validInterfaces).length; i++) {
const interfaceID = Object.keys(validInterfaces)[i];
const interfaceObj = validInterfaces[interfaceID];
const j = searchSockets.push(dgram.createSocket('udp4')) - 1;
searchSockets[j].bind(pluginConfig.searchOptions.listenPort, () => {
searchSockets[j].on('message', (msg, info) => {
if (pluginConfig.searchOptions.validateResponse(msg, info, DEVICE.all)) {
searchSockets[j].close();
DEVICE.registerDevice(
{
type: pluginType,
defaultName: pluginConfig.defaultName,
port: pluginConfig.defaultPort,
addresses: [info.address],
},
'fromSearch'
);
}
});
});
searchSockets[j].on('listening', () => {
searchSockets[j].setBroadcast(true);
searchSockets[j].send(
pluginConfig.searchOptions.searchBuffer,
pluginConfig.searchOptions.devicePort,
interfaceObj[0].broadcastAddress,
(err) => {
// console.log(err)
// console.log(err);
}
);
});
};
});
}
}
function searchMulticast(pluginType, plugin) {
function searchMulticast(pluginType, pluginConfig) {
const socket = dgram.createSocket('udp4');
socket.on('message', (msg, info) => {
if (plugin.searchOptions.validateResponse(msg, info)) {
socket.close();
DEVICE.registerDevice({
type: pluginType,
defaultName: plugin.defaultName,
port: plugin.defaultPort,
addresses: [info.address],
if (pluginConfig.searchOptions.validateResponse(msg, info)) {
socket.close(() => {
DEVICE.registerDevice(
{
type: pluginType,
defaultName: pluginConfig.defaultName,
port: pluginConfig.defaultPort,
addresses: [info.address],
},
'fromSearch'
);
});
}
});
socket.bind(plugin.searchOptions.port, () => {
socket.addMembership(plugin.searchOptions.address);
socket.bind(pluginConfig.searchOptions.port, () => {
for (let i = 0; i < Object.keys(validInterfaces).length; i++) {
const interfaceID = Object.keys(validInterfaces)[i];
const interfaceObj = validInterfaces[interfaceID];
socket.addMembership(pluginConfig.searchOptions.address, interfaceObj[0].address);
}
});
}
+106 -98
View File
@@ -1,7 +1,7 @@
const { ipcRenderer } = require('electron');
const DEVICE = require('./device.js');
const PLUGINS = require('./plugins.js');
// const _ = require('lodash/function');
const { saveAll } = require('./saveSlots.js');
const pinnedDevices = [];
module.exports.pinnedDevices = pinnedDevices;
@@ -11,9 +11,7 @@ module.exports.init = function init() {
populatePluginLists();
};
function drawDeviceFrame(id) {
const $deviceDrawArea = document.getElementById(`device-${id}-draw-area`);
const $devicePinned = document.getElementById(`device-${id}-pinned`);
@@ -23,33 +21,34 @@ function drawDeviceFrame(id) {
const d = DEVICE.all[id];
let str = '<html><head>';
str += `<link href='./plugins/${d.type}/styles.css' rel='stylesheet' type='text/css'>`;
const str = `
<html>
<head>
<link href='./plugins/${d.type}/styles.css' rel='stylesheet' type='text/css'>
// scrollbar styles are inline to prevent the styles flickering in
str += '<style>';
str += '::-webkit-scrollbar {background-color: black;width: 12px;}';
str += '::-webkit-scrollbar-track, ::-webkit-scrollbar-corner {background-color: #2b2b2b;}';
str +=
'::-webkit-scrollbar-thumb {background-color: #6b6b6b;border-radius: 16px;border: 3px solid #2b2b2b;}';
str += '::-webkit-scrollbar-button {display:none;}';
str += 'body{visibility: hidden;}';
str += '</style>';
str += "<link href='src/defaultPlugin.css' rel='stylesheet' type='text/css'>";
str += '</head><body>';
str += generateBodyHTML(d);
// str += "<script src='node_modules/lodash/core.min.js'></script>";
// str += "<script src='./plugins/" +d.type +"/update.js'></script>";
str += '</body></html>';
<!--scrollbar styles are inline to prevent the styles flickering in-->
<style>
::-webkit-scrollbar {background-color: black;width: 12px;}
::-webkit-scrollbar-track, ::-webkit-scrollbar-corner {background-color: #2b2b2b;}
::-webkit-scrollbar-thumb {background-color: #6b6b6b;border-radius: 16px;border: 3px solid #2b2b2b;}
::-webkit-scrollbar-button {display:none;}
body{visibility: hidden;}
</style>
<link href='src/assets/css/plugin_default.css' rel='stylesheet' type='text/css'>
</head>
<body>
${generateBodyHTML(d)}
</body>
</html>
`;
$deviceDrawArea.setAttribute('class', `${d.type} draw-area`);
$deviceDrawArea.contentWindow.document.open();
$deviceDrawArea.contentWindow.document.write(str);
$deviceDrawArea.contentWindow.document.close();
$deviceDrawArea.contentWindow.document.onclick = function (e) {
switchDevice(d.id);
};
if (d.pinIndex) {
$devicePinned.style.display = 'block';
@@ -58,62 +57,58 @@ function drawDeviceFrame(id) {
}
return true;
}
};
function generateBodyHTML(d){
let str = "";
function generateBodyHTML(d) {
if (d.status === 'ok') {
try {
str += PLUGINS.all[d.type].template({
return PLUGINS.all[d.type].template({
templates: d.templates,
data: d.data,
listName: (d.displayName || d.defaultName)
listName: d.displayName || d.defaultName,
});
} catch (err) {
console.log(err);
str += '<h3>Plugin Template Error</h3>';
return '<h3>Plugin Template Error</h3>';
}
} else {
str += `<header><h1>${(d.displayName || d.defaultName)}</h1></header>`;
str += "<div class='not-responding'>";
str += `<h2><em>${d.type}</em> is not responding to requests for data.</h2>`;
str += `<h3>IP <em>${d.addresses[0]}</em></h3>`;
str += `<h3>Port <em>${d.port}</em></h3>`;
str += '<hr></div>';
str += `<div class="device-info">${PLUGINS.all[d.type].info()}<div>`;
return `
<header>
<h1>${d.displayName || d.defaultName}</h1>
</header>
<div class='not-responding'>
<h2><em>${d.type}</em> is not responding to requests for data.</h2>
<h3>IP <em>${d.addresses[0]}</em></h3>
<h3>Port <em>${d.port}</em></h3>
<hr>
</div>
<div class="device-info">
${PLUGINS.all[d.type].info()}
</div>
`;
}
return str;
}
module.exports.draw = function draw(device) {
const d = device;
const $deviceDrawArea = document.getElementById(`device-${d.id}-draw-area`);
if($deviceDrawArea){
if ($deviceDrawArea) {
const scriptEl = $deviceDrawArea.contentWindow.document.createRange().createContextualFragment(generateBodyHTML(d));
$deviceDrawArea.contentWindow.document.body.replaceChildren(scriptEl);
}else{
} else {
drawDeviceFrame(d.id);
}
d.drawn = true;
};
module.exports.update = function update(device, type, data){
module.exports.update = function update(device, type, data) {
const doc = document.getElementById(`device-${device.id}-draw-area`);
if(doc){
if (doc) {
PLUGINS.all[device.type].update(device, doc.contentWindow.document, type, data);
}
}
};
module.exports.addDeviceToList = function addDeviceToList(device) {
const d = device;
@@ -127,8 +122,10 @@ module.exports.addDeviceToList = function addDeviceToList(device) {
html += "<div class='status material-icons red'>clear</div>";
}
html += `<div class='type'><img height='18px' src='plugins/${d.type}/icon.png'></div>`;
html += `<div class='name'>${d.displayName || d.defaultName}</div>`;
html += `
<div class='type'><img height='18px' src='plugins/${d.type}/icon.png'></div>
<div class='name'>${d.displayName || d.defaultName}</div>
`;
const elem = document.getElementById(d.id);
if (elem == null) {
@@ -138,17 +135,13 @@ module.exports.addDeviceToList = function addDeviceToList(device) {
} else {
elem.innerHTML = html;
}
};
module.exports.removeDeviceFromList = function removeDeviceFromList(device) {
const d = device;
document.getElementById(d.id).remove();
};
function switchDevice(id) {
if (activeDevice && activeDevice.pinIndex === false) {
document.getElementById(`device-${activeDevice.id}`).remove();
@@ -160,30 +153,26 @@ function switchDevice(id) {
if (pinnedDevices.length > 0) {
cols += pinnedDevices.length;
}
if (pinnedDevices.indexOf(activeDevice) >= 0 || id === undefined) {
if (pinnedDevices.includes(activeDevice) || id === undefined) {
cols--;
}
document.getElementById('all-devices').style.gridTemplateColumns = `repeat(${cols}, 1fr)`;
if (id === undefined) {
// document.getElementById('refresh-device-button').style.opacity = 0.2;
document.getElementById('refresh-device-button').disabled = true;
document.getElementById('device-settings-table').style.display = 'none';
return;
}
document.getElementById('refresh-device-button').disabled = false;
// document.getElementById('refresh-device-button').style.opacity = 1;
const i = DEVICE.all[id].id;
let $deviceWrapper = document.getElementById(`device-${i}`);
const $deviceWrapper = document.getElementById(`device-${i}`);
if (!$deviceWrapper) {
const html = `<div class="col device-wrapper" id="device-${i}"><img id="device-${i}-pinned" class="device-pin" src="src/img/outline_push_pin_white_18dp.png"><iframe id="device-${i}-draw-area" class="draw-area"></iframe></div>`;
const html = `<div class="col device-wrapper" id="device-${i}"><img id="device-${i}-pinned" class="device-pin" src="src/assets/img/outline_push_pin_white_18dp.png"><iframe id="device-${i}-draw-area" class="draw-area"></iframe></div>`;
document.getElementById('all-devices').insertAdjacentHTML('afterbegin', html);
$deviceWrapper = document.getElementById(`device-${i}`);
}
window.switchClass(document.getElementById(id), 'active-device');
@@ -197,28 +186,62 @@ function switchDevice(id) {
document.getElementById('device-settings-port').value = activeDevice.port || '';
document.getElementById('device-settings-pin').checked = activeDevice.pinIndex;
if (activeDevice.plugin.config.mayChangePort) {
document.getElementById('device-settings-port').disabled = false;
} else {
document.getElementById('device-settings-port').disabled = true;
}
updateFields();
ipcRenderer.send('enableDeviceDropdown', '');
ipcRenderer.send('setDevicePin', !DEVICE.all[id].pinIndex === false);
};
}
module.exports.switchDevice = switchDevice;
function updateFields() {
document.getElementById('device-settings-fields').innerHTML = '';
if (activeDevice && activeDevice.plugin.config.fields) {
const fields = activeDevice.plugin.config.fields;
fields.forEach((field) => {
// generic input setup
const $elem = document.createElement('input');
$elem.value = activeDevice.fields[field.key];
$elem.name = field.key;
$elem.onchange = function onchange(e) {
activeDevice.fields[field.key] = $elem.value;
field.action(activeDevice);
saveAll();
};
// type specific setup
if (field.type === 'textinput') {
$elem.type = 'text';
const rowHTML = `<tr><th>${field.label}:</th><td colspan="3" id="${field.key}"></td></tr>`;
document.getElementById('device-settings-fields').insertAdjacentHTML('beforeend', rowHTML);
document.getElementById(field.key).appendChild($elem);
}
});
}
}
module.exports.updateFields = updateFields;
module.exports.getActiveDevice = function getActiveDevice() {
return activeDevice;
};
module.exports.pinActiveDevice = function pinActiveDevice() {
if (activeDevice === undefined) {
return;
}
if (pinnedDevices.indexOf(DEVICE.all[activeDevice.id]) === -1) {
if (!pinnedDevices.includes(DEVICE.all[activeDevice.id])) {
pinnedDevices.push(DEVICE.all[activeDevice.id]);
}
DEVICE.changeActivePinIndex(true);
};
module.exports.unpinActiveDevice = function unpinActiveDevice() {
if (activeDevice === undefined) {
return;
@@ -227,56 +250,43 @@ module.exports.unpinActiveDevice = function unpinActiveDevice() {
DEVICE.changeActivePinIndex(false);
};
module.exports.pinDevice = function pinDevice(device) {
pinnedDevices.push(device);
DEVICE.changePinIndex(device, true);
};
module.exports.unpinDevice = function unpinDevice(device) {
pinnedDevices.push(device);
DEVICE.changePinIndex(device, false);
};
module.exports.resetPinned = function resetPinned() {
pinnedDevices.length = 0;
activeDevice = false;
try {
document
.querySelector('#device-list .active-device')
.classList.remove('active-device');
document.querySelector('#device-list .active-device').classList.remove('active-device');
} catch (err) {
//
}
document.getElementById('all-devices').innerHTML = '';
};
module.exports.getPinnedDevices = function getPinnedDevices() {
return pinnedDevices;
};
module.exports.toggleSlotButtons = function toggleSlotButtons(slotIndex) {
if (slotIndex === 1) {
document.getElementById('save-slot-1').classList.add('active');
document.getElementById('save-slot-2').classList.remove('active');
document.getElementById('save-slot-3').classList.remove('active');
} else if (slotIndex === 2) {
document.getElementById('save-slot-1').classList.remove('active');
document.getElementById('save-slot-2').classList.add('active');
document.getElementById('save-slot-3').classList.remove('active');
} else if (slotIndex === 3) {
document.getElementById('save-slot-1').classList.remove('active');
document.getElementById('save-slot-2').classList.remove('active');
document.getElementById('save-slot-3').classList.add('active');
// remove all currently active classes
const currentActiveSlots = document.getElementsByClassName('active');
for (let i = 0; i < currentActiveSlots.length; i++) {
const slotButton = currentActiveSlots[i];
slotButton.classList.remove('active');
}
// add the active class to the newly selected slot
document.getElementById(`save-slot-${slotIndex}`).classList.add('active');
};
module.exports.selectPreviousDevice = function selectPreviousDevice() {
if (activeDevice === undefined) {
return;
@@ -286,7 +296,6 @@ module.exports.selectPreviousDevice = function selectPreviousDevice() {
switchDevice(keys[prevIndex]);
};
module.exports.selectNextDevice = function selectNextDevice() {
if (activeDevice === undefined) {
return;
@@ -296,17 +305,16 @@ module.exports.selectNextDevice = function selectNextDevice() {
switchDevice(keys[prevIndex]);
};
function populatePluginLists() {
let typeSelect = '';
let addSelect = '<option value="" disabled selected hidden>+</option>';
let addSelect = '<option value="" disabled selected hidden>&nbsp;+&nbsp;</option>';
Object.keys(PLUGINS.all).forEach((pluginType) => {
const plugin = PLUGINS.all[pluginType];
addSelect += `<option value='${pluginType}'>${plugin.defaultName}</option>`;
typeSelect += `<option value='${pluginType}'>${plugin.defaultName}</option>`;
addSelect += `<option value='${pluginType}'>${plugin.config.defaultName}</option>`;
typeSelect += `<option value='${pluginType}'>${plugin.config.defaultName}</option>`;
});
document.getElementById('device-settings-plugin-dropdown').innerHTML = typeSelect;
document.getElementById('add-device-button').innerHTML = addSelect;
};
}