From c210c0e80cc5214f057f3657b0a185277ccd80a3 Mon Sep 17 00:00:00 2001 From: Balte de Wit Date: Mon, 13 Jul 2020 15:39:26 +0200 Subject: [PATCH] feat: flexible UI layout, AutoMix, Capabilities (and more) * (more) responsive layouts * touch optimization * AutoMix toggle * fader capabilities (override enabled features for a single fader) * VU disabled when unsupported --- client/assets/css/ChanStrip.css | 50 ++- client/assets/css/Channel.css | 61 +++- client/assets/css/Channels.css | 129 +++---- client/assets/css/MiniChanStrip.css | 153 ++++---- client/assets/css/NoUiSlider.css | 332 ++++++++++-------- client/assets/css/VuMeter.css | 7 +- client/components/App.tsx | 41 ++- client/components/ChanStrip.tsx | 210 +++++++---- client/components/Channel.tsx | 129 ++++++- client/components/Channels.tsx | 184 +++++----- client/components/VuMeter.tsx | 16 +- server/MainThreadHandler.ts | 10 + server/constants/MixerProtocolInterface.ts | 2 + server/constants/SOCKET_IO_DISPATCHERS.ts | 1 + server/constants/mixerProtocols/LawoRuby.ts | 16 +- server/reducers/faderActions.ts | 3 + server/reducers/fadersReducer.ts | 35 ++ server/utils/MixerConnection.ts | 13 + .../mixerConnections/LawoRubyConnection.ts | 97 ++++- 19 files changed, 976 insertions(+), 513 deletions(-) diff --git a/client/assets/css/ChanStrip.css b/client/assets/css/ChanStrip.css index 3048f67e..dff271c4 100644 --- a/client/assets/css/ChanStrip.css +++ b/client/assets/css/ChanStrip.css @@ -1,7 +1,7 @@ .chan-strip-body { position: relative; top: 5px; - width: 620px; + min-width: 320px; height: 940px; background-color: rgb(31, 31, 31); border-color: rgb(70, 70, 70); @@ -15,9 +15,11 @@ .chan-strip-body > .header { margin-left: 40px; margin-top: 10px; - width: 420px; + margin-right: 80px; + /* width: 420px; */ font-size: 240%; color: #fff; + white-space: nowrap; } .chan-strip-body > .parameters { @@ -29,6 +31,16 @@ color: #fff; } +.chan-strip-body .parameters .horizontal { + display: flex; + justify-content: space-evenly; +} +.chan-strip-body .parameters .horizontal .item { +} +.chan-strip-body .parameters .horizontal .item .content { + display: flex; +} + .parameters > .inp-comp-del-group { display: flex; top: 5px; @@ -44,7 +56,8 @@ height: 50px; } -.parameters > .eq-group { +.parameters > .eq-group, +.content > .eq-group { display: flex; top: 5px; left: 2px; @@ -53,6 +66,9 @@ text-align: center; color: #fff; } +.content > .eq-group { + margin-left: 0; +} .eq-group > .horizontal-space { width: 150px; @@ -88,7 +104,8 @@ margin-top: -150px; } -.parameters > .parameter-text { +.parameters > .parameter-text, +.content > .parameter-text { list-style-type: none; text-align: center; margin-top: 15px; @@ -101,6 +118,11 @@ margin-top: 24px; margin-left: 50px; } +.content > .delayButtons { + width: 30px; + margin-top: 24px; + margin-left: 50px; +} .delayButtons > .delayTime { outline: none; @@ -120,6 +142,17 @@ margin-top: 24px; margin-left: 50px; } +.inp-comp-del-group .input-buttons.disabled { + visibility: hidden; +} +.content > .input-buttons { + width: 30px; + margin-top: 24px; + margin-left: 50px; +} +.content > .input-buttons.disabled { + visibility: hidden; +} .input-buttons > .input-select { outline: none; @@ -137,7 +170,8 @@ background-color: #2f475b; } -.parameters > .monitor-sends { +.parameters > .monitor-sends, +.content > .monitor-sends { list-style-type: none; display: flex; text-align: center; @@ -149,7 +183,7 @@ } .header > .close { - position: fixed; + position: absolute; outline: none; border-color: rgb(99, 99, 99); background-color: rgb(27, 27, 27); @@ -158,8 +192,8 @@ width: 50px; height: 50px; font-size: 30px; - margin-top: 5px; - left: 550px; + /* margin-top: 5px; */ + right: 40px; } .header > button { diff --git a/client/assets/css/Channel.css b/client/assets/css/Channel.css index 4696d782..5317f5ba 100644 --- a/client/assets/css/Channel.css +++ b/client/assets/css/Channel.css @@ -13,6 +13,29 @@ height: 950px; position: relative; touch-action: none; + display: flex; + flex-direction: column; +} + +.channel-body > .channel-props, +.channel-body > .out-control, +.channel-body > .channel-control { + flex-grow: 0; +} +.channel-body > .fader { + flex-grow: 1; + display: flex; + flex-direction: row-reverse; + justify-content: center; + align-items: center; +} +.channel-body > .out-control { + padding-bottom: 5px; +} +.channel-body > .channel-control { + height: 160px; + display: flex; + flex-direction: column-reverse; } .channel-body.ignore-on { @@ -47,7 +70,7 @@ -moz-outline: none; margin-left: 3px; margin-right: 3px; - margin-top: 37px; + margin-top: 3px; color: white; height: 90px; width: 80px; @@ -71,7 +94,7 @@ -moz-outline: none; margin-left: 3px; margin-right: 3px; - margin-top: 5px; + margin-top: 3px; color: white; height: 90px; width: 80px; @@ -95,7 +118,7 @@ -moz-outline: none; margin-left: 3px; margin-right: 3px; - margin-top: 22px; + margin-top: 3px; color: white; height: 90px; width: 80px; @@ -114,7 +137,8 @@ background-color: rgb(163, 106, 0); } -.channel-mute-button { +.channel-mute-button, +.channel-amix-button { outline: none; -moz-outline: none; margin-left: 3px; @@ -134,6 +158,14 @@ color: rgb(48, 48, 48); } +.channel-amix-button.on { + background-color: rgb(78, 59, 97); + color: rgb(255, 255, 255); +} +.channel-amix-button.disabled { + visibility: hidden; +} + .channel-ignore-button { outline: none; -moz-outline: none; @@ -189,13 +221,26 @@ .channel-volume-fader { width: 10px; - height: 400px; - margin-left: 55px; - margin-top: 30px; + height: calc(100% - 60px); + margin: 30px 20px; background-color: rgb(15, 15, 15); border-color: rgba(0, 0, 0); } +.channel-volume-fader::before, +.channel-volume-fader::after { + content: '__'; + position: absolute; + color: rgb(134, 134, 134); + top: 92px; +} +.channel-volume-fader::before { + left: 9px; +} +.channel-volume-fader::after { + right: 9px; +} + :focus { outline: 0; } @@ -203,7 +248,7 @@ .channel-zero-indicator { color: rgb(134, 134, 134); position: absolute; - top: 214px; + top: 206px; left: 37px; } diff --git a/client/assets/css/Channels.css b/client/assets/css/Channels.css index 51604b87..9b4d6383 100644 --- a/client/assets/css/Channels.css +++ b/client/assets/css/Channels.css @@ -5,23 +5,56 @@ min-height: 760px; } +.channels-body > .channels-body-inner { + display: flex; + flex-direction: row; + flex-grow: 2; + overflow-x: auto; + height: calc(100vh); +} + .channels-body > .closedChanStrip { - margin-left: -630px; - transition: margin 300ms; + /* margin-left: -630px; */ + transition: transform 300ms; + position: absolute; + z-index: 2; + transform: translateX(-100%); } .channels-body > .openChanStrip { - margin-left: 0px; - transition: margin 800ms; + /* margin-left: 0px; + transition: margin 800ms; */ + transition: transform 300ms; + transform: unset; +} + +.button { + outline: none; + -moz-outline: none; + color: white; + height: 90px; + width: 90px; + border-color: rgb(71, 71, 71); + background-color: rgb(53, 53, 53); + margin-left: 5px; + margin-right: 4px; + margin-top: 10px; + border-radius: 7px; +} +.button.half { + height: 60px; +} +.button.active { + background-color: #2f475b; } .channels-mix-body { - position: fixed; - z-index: 2; - right: -4px; - padding-right: 0px; + display: flex; + flex-direction: column; + width: 100px; min-height: 950px; + max-height: calc(100vh - 10px); color: white; background: linear-gradient( #2f2f2f 0px, @@ -36,101 +69,46 @@ border-style: solid; border-width: 1px; } +.channels-mix-body > .mid { + flex-grow: 2; + display: flex; + flex-direction: column; + justify-content: center; +} +.channels-mix-body > .bot { + overflow-y: auto; +} .channels-show-mixer-online { - outline: none; - -moz-outline: none; - color: rgb(255, 255, 255); - height: 60px; - width: 90px; - border-color: rgb(71, 71, 71); background-color: rgb(219, 1, 1); - margin-left: 5px; - margin-right: 4px; - margin-top: 10px; - border-radius: 7px; } .channels-show-mixer-online.connected { background-color: rgb(9, 107, 0); - color: white; } .channels-show-snaps-button { - outline: none; - -moz-outline: none; - color: white; - height: 60px; - width: 90px; - border-color: rgb(71, 71, 71); - background-color: rgb(53, 53, 53); - margin-left: 5px; - margin-right: 4px; - margin-top: 10px; - border-radius: 7px; } .channels-show-settings-button { - outline: none; - -moz-outline: none; - color: white; - height: 60px; - width: 90px; - border-color: rgb(71, 71, 71); - background-color: rgb(53, 53, 53); - margin-left: 5px; - margin-right: 4px; - margin-top: 10px; - border-radius: 7px; } .channels-show-settings-button.active { background-color: #2f475b; } .channels-show-storage-button { - outline: none; - -moz-outline: none; - color: white; - height: 60px; - width: 90px; - border-color: rgb(71, 71, 71); - background-color: rgb(53, 53, 53); - margin-left: 5px; - margin-right: 4px; - margin-top: 10px; - border-radius: 7px; } .channels-mix-button { - outline: none; - -moz-outline: none; - color: white; - height: 90px; - width: 90px; - border-color: rgb(139, 139, 139); background-color: rgb(65, 65, 65); - margin-left: 4px; - margin-right: 4px; - margin-top: 50px; - border-radius: 7px; } .channels-clear-button { - outline: none; - -moz-outline: none; - color: white; - height: 90px; - width: 90px; - border-color: rgb(99, 99, 99); background-color: rgb(19, 19, 19); - margin-left: 4px; - margin-right: 4px; - margin-top: 50px; - border-radius: 7px; } .channels-snap-mix-body { - margin-top: 58px; + /* margin-top: 58px; */ } .channels-snap-mix-line { @@ -139,16 +117,11 @@ } .channels-snap-mix-button { - outline: none; - -moz-outline: none; background-color: rgb(199, 202, 0); color: rgb(44, 44, 44); margin-left: 20px; - margin-top: 4px; - margin-bottom: 4px; height: 25px; width: 60px; - border-radius: 7px; border-color: rgb(99, 99, 99); white-space: nowrap; } diff --git a/client/assets/css/MiniChanStrip.css b/client/assets/css/MiniChanStrip.css index b976320b..8be11526 100644 --- a/client/assets/css/MiniChanStrip.css +++ b/client/assets/css/MiniChanStrip.css @@ -1,115 +1,115 @@ .monitor-chan-strip-body { - position: absolute; - top: 2px; - left: 100px; - width: 620px; - height: 280px; + position: absolute; + top: 2px; + left: 100px; + width: 620px; + height: 280px; background-color: rgb(31, 31, 31); border-color: rgb(70, 70, 70); border-style: solid; - border-width: 4px; - border-radius: 8px; - text-align: left; - color: #fff; + border-width: 4px; + border-radius: 8px; + text-align: left; + color: #fff; } .monitor-chan-strip-body > .header { - margin-left: 40px; - margin-top: 10px; - width: 420px; - font-size: 240%; - color: #fff; + margin-left: 40px; + margin-top: 10px; + width: 420px; + font-size: 240%; + color: #fff; } .monitor-chan-strip-body > .parameters { - margin-top: 5px; - top: 5px; - left: 2px; - height: 900px; - overflow: auto; - text-align: center; - color: #fff; + margin-top: 5px; + top: 5px; + left: 2px; + height: 900px; + overflow: auto; + text-align: center; + color: #fff; } .parameters > .parameter-group { - display: flex; - top: 5px; - left: 2px; - margin-left: 90px; - overflow: auto; - text-align: center; - color: #fff; + display: flex; + top: 5px; + left: 2px; + margin-left: 90px; + overflow: auto; + text-align: center; + color: #fff; } .parameter-group > .horizontal-space { - width: 150px; - height: 50px; + width: 150px; + height: 50px; } .parameters > .group-text { - text-align: center; - line-height: 20px; - font-size: 110%; + text-align: center; + line-height: 20px; + font-size: 110%; } .zero-monitor { - width: 2px; - height: 20px; - margin-left: 10px; - margin-top: -150px; + width: 2px; + height: 20px; + margin-left: 10px; + margin-top: -150px; } .parameters > .parameter-text { - list-style-type: none; - text-align: center; - margin-top: 15px; - line-height: 10px; - font-size: 100%; + list-style-type: none; + text-align: center; + margin-top: 15px; + line-height: 10px; + font-size: 100%; } .parameters > .monitor-sends { - list-style-type: none; - display: flex; - text-align: center; - margin-top: 15px; - margin-left: 1px; - padding: 0px; - line-height: 10px; - font-size: 95%; + list-style-type: none; + display: flex; + text-align: center; + margin-top: 15px; + margin-left: 1px; + padding: 0px; + line-height: 10px; + font-size: 95%; } -.header > .close { - position: fixed; - outline : none; +.monitor-chan-strip-body .header > .close { + position: fixed; + outline: none; border-color: rgb(99, 99, 99); background-color: rgb(27, 27, 27); - border-radius: 20px; - color: #fff; - width: 50px; - height: 50px; - font-size: 30px; - margin-top: 5px; - left: 550px; + border-radius: 20px; + color: #fff; + width: 50px; + height: 50px; + font-size: 30px; + margin-top: 5px; + left: 550px; } .header > button { - outline : none; + outline: none; border-color: rgb(99, 99, 99); background-color: rgb(27, 27, 27); - border-radius: 7px; - margin-left: 10px; - margin-top: 5px; - width: 180px; - font-size: 12px; - line-height: 40px; - color: #fff; + border-radius: 7px; + margin-left: 10px; + margin-top: 5px; + width: 180px; + font-size: 12px; + line-height: 40px; + color: #fff; } .monitor-chan-strip-fader { width: 10px; - height: 200px; - margin-left: 35px; - margin-right: 30px; + height: 200px; + margin-left: 35px; + margin-right: 30px; margin-top: 10px; border-width: 0px; border-style: solid; @@ -123,5 +123,14 @@ height: 49px; border: 1px solid #c5c2c2; border-radius: 8px; - background: linear-gradient(to top, #3a3a3a 0%, #c2c2c2 20%, hsl(0, 0%, 57%) 50%, #00a 1px, #919191 52%, #c2c2c2 80%, #3a3a3a 100%); + background: linear-gradient( + to top, + #3a3a3a 0%, + #c2c2c2 20%, + hsl(0, 0%, 57%) 50%, + #00a 1px, + #919191 52%, + #c2c2c2 80%, + #3a3a3a 100% + ); } diff --git a/client/assets/css/NoUiSlider.css b/client/assets/css/NoUiSlider.css index 661323c4..02c74b45 100644 --- a/client/assets/css/NoUiSlider.css +++ b/client/assets/css/NoUiSlider.css @@ -5,308 +5,344 @@ */ .noUi-target, .noUi-target * { - -webkit-touch-callout: none; - -webkit-tap-highlight-color: rgba(0, 0, 0, 0); - -webkit-user-select: none; - -ms-touch-action: none; - touch-action: none; - -ms-user-select: none; - -moz-user-select: none; - user-select: none; - -moz-box-sizing: border-box; - box-sizing: border-box; + -webkit-touch-callout: none; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + -webkit-user-select: none; + -ms-touch-action: none; + touch-action: none; + -ms-user-select: none; + -moz-user-select: none; + user-select: none; + -moz-box-sizing: border-box; + box-sizing: border-box; } .noUi-target { - position: relative; + position: relative; } .noUi-base, .noUi-connects { - width: 100%; - height: 100%; - position: relative; - z-index: 1; + width: 100%; + height: 100%; + position: relative; + z-index: 1; } /* Wrapper for all connect elements. */ .noUi-connects { - overflow: hidden; - z-index: 0; + overflow: hidden; + z-index: 0; } .noUi-connect, .noUi-origin { - will-change: transform; - position: absolute; - z-index: 1; - top: 0; - right: 0; - -ms-transform-origin: 0 0; - -webkit-transform-origin: 0 0; - -webkit-transform-style: preserve-3d; - transform-origin: 0 0; - transform-style: flat; + will-change: transform; + position: absolute; + z-index: 1; + top: 0; + right: 0; + -ms-transform-origin: 0 0; + -webkit-transform-origin: 0 0; + -webkit-transform-style: preserve-3d; + transform-origin: 0 0; + transform-style: flat; } .noUi-connect { - height: 100%; - width: 100%; + height: 100%; + width: 100%; } .noUi-origin { - height: 10%; - width: 10%; + height: 10%; + width: 10%; } /* Offset direction */ .noUi-txt-dir-rtl.noUi-horizontal .noUi-origin { - left: 0; - right: auto; + left: 0; + right: auto; } /* Give origins 0 height/width so they don't interfere with clicking the * connect elements. */ .noUi-vertical .noUi-origin { - width: 0; + width: 0; } .noUi-horizontal .noUi-origin { - height: 0; + height: 0; } .noUi-handle { - -webkit-backface-visibility: hidden; - backface-visibility: hidden; - position: absolute; + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + position: absolute; } .noUi-touch-area { - height: 100%; - width: 100%; + height: 100%; + width: 100%; } .noUi-state-tap .noUi-connect, .noUi-state-tap .noUi-origin { - -webkit-transition: transform 0.3s; - transition: transform 0.3s; + -webkit-transition: transform 0.3s; + transition: transform 0.3s; } .noUi-state-drag * { - cursor: inherit !important; + cursor: inherit !important; } /* Slider size and handle placement; */ .noUi-horizontal { - height: 18px; + height: 18px; } .noUi-horizontal .noUi-handle { - width: 34px; - height: 28px; - right: -17px; - top: -6px; + width: 34px; + height: 28px; + right: -17px; + top: -6px; } .noUi-vertical { - width: 18px; + width: 18px; } .noUi-vertical .noUi-handle { - width: 49px; - height: 75px; - right: -20px; - top: -27px; + width: 49px; + height: 75px; + right: -20px; + top: -35px; } .noUi-txt-dir-rtl.noUi-horizontal .noUi-handle { - left: -17px; - right: auto; + left: -17px; + right: auto; } /* Styling; * Giving the connect element a border radius causes issues with using transform: scale */ .noUi-target { - background: #FAFAFA; - border-radius: 4px; - border: 1px solid #D3D3D3; - box-shadow: inset 0 1px 1px #F0F0F0, 0 3px 6px -5px #BBB; + background: #fafafa; + border-radius: 4px; + border: 1px solid #d3d3d3; + box-shadow: inset 0 1px 1px #f0f0f0, 0 3px 6px -5px #bbb; } .noUi-connects { - border-radius: 3px; + border-radius: 3px; } .noUi-connect { - background: #3FB8AF; + background: #3fb8af; } /* Handles and cursors; */ .noUi-draggable { - cursor: ew-resize; + cursor: ew-resize; } .noUi-vertical .noUi-draggable { - cursor: ns-resize; + cursor: ns-resize; } .noUi-handle { - border: 1px solid #c5c2c2; - border-radius: 8px; - background: linear-gradient(to top, #3a3a3a 0%, #c2c2c2 20%, hsl(0, 0%, 57%) 50%, #00a 1px, #919191 52%, #c2c2c2 80%, #3a3a3a 100%); - cursor: default; + border: 1px solid #c5c2c2; + border-radius: 8px; + background: linear-gradient( + to top, + #3a3a3a 0%, + #c2c2c2 20%, + hsl(0, 0%, 57%) 50%, + #00a 1px, + #919191 52%, + #c2c2c2 80%, + #3a3a3a 100% + ); + cursor: default; } .pgm-on .noUi-handle { - border: 1px solid rgb(253, 60, 60); - background: linear-gradient(to top, #3a1d1d 0%, #c04d4d 20%, #811919 50%, #00a 1px, #8a2626 52%, #bd5151 80%, #411f1f 100%); + border: 1px solid rgb(253, 60, 60); + background: linear-gradient( + to top, + #3a1d1d 0%, + #c04d4d 20%, + #811919 50%, + #00a 1px, + #8a2626 52%, + #bd5151 80%, + #411f1f 100% + ); } .vo-on .noUi-handle { - border: 1px solid rgb(252, 255, 86); - background: linear-gradient(to top, #353006 0%, #c59327 20%, #856b14 50%, #00a 1px, #866724 52%, #cca22d 80%, #463b0a 100%); + border: 1px solid rgb(252, 255, 86); + background: linear-gradient( + to top, + #353006 0%, + #c59327 20%, + #856b14 50%, + #00a 1px, + #866724 52%, + #cca22d 80%, + #463b0a 100% + ); } .mute-on .noUi-handle { - border: 1px solid rgb(58, 58, 58); - background: linear-gradient(to top, #111111 0%, #252525 20%, #2b2b2b 50%, rgb(56, 56, 56) 1px, #292929 52%, #222222 80%, #222222 100%); + border: 1px solid rgb(58, 58, 58); + background: linear-gradient( + to top, + #111111 0%, + #252525 20%, + #2b2b2b 50%, + rgb(56, 56, 56) 1px, + #292929 52%, + #222222 80%, + #222222 100% + ); } .noUi-active { - box-shadow: inset 0 0 1px #FFF, inset 0 1px 7px #DDD, 0 3px 6px -3px #BBB; + box-shadow: inset 0 0 1px #fff, inset 0 1px 7px #ddd, 0 3px 6px -3px #bbb; } /* Handle stripes; */ .noUi-handle:after { - content: ""; - display: block; - position: absolute; - height: 14px; - width: 1px; - background: rgba(0, 0, 0, 0); - left: 14px; - top: 6px; + content: ''; + display: block; + position: absolute; + height: 14px; + width: 1px; + background: rgba(0, 0, 0, 0); + left: 14px; + top: 6px; } .noUi-handle:after { - left: 17px; + left: 17px; } .noUi-vertical .noUi-handle:before, .noUi-vertical .noUi-handle:after { - width: 14px; - height: 1px; - left: 6px; - top: 14px; + width: 14px; + height: 1px; + left: 6px; + top: 14px; } .noUi-vertical .noUi-handle:after { - top: 17px; + top: 17px; } /* Disabled state; */ [disabled] .noUi-connect { - background: #B8B8B8; + background: #b8b8b8; } [disabled].noUi-target, [disabled].noUi-handle, [disabled] .noUi-handle { - cursor: not-allowed; + cursor: not-allowed; } /* Base; * */ .noUi-pips, .noUi-pips * { - -moz-box-sizing: border-box; - box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; } .noUi-pips { - position: absolute; - color: #999; + position: absolute; + color: #999; } /* Values; * */ .noUi-value { - position: absolute; - white-space: nowrap; - text-align: center; + position: absolute; + white-space: nowrap; + text-align: center; } .noUi-value-sub { - color: #ccc; - font-size: 10px; + color: #ccc; + font-size: 10px; } /* Markings; * */ .noUi-marker { - position: absolute; - background: #CCC; + position: absolute; + background: #ccc; } .noUi-marker-sub { - background: #AAA; + background: #aaa; } .noUi-marker-large { - background: #AAA; + background: #aaa; } /* Horizontal layout; * */ .noUi-pips-horizontal { - padding: 10px 0; - height: 80px; - top: 100%; - left: 0; - width: 100%; + padding: 10px 0; + height: 80px; + top: 100%; + left: 0; + width: 100%; } .noUi-value-horizontal { - -webkit-transform: translate(-50%, 50%); - transform: translate(-50%, 50%); + -webkit-transform: translate(-50%, 50%); + transform: translate(-50%, 50%); } .noUi-rtl .noUi-value-horizontal { - -webkit-transform: translate(50%, 50%); - transform: translate(50%, 50%); + -webkit-transform: translate(50%, 50%); + transform: translate(50%, 50%); } .noUi-marker-horizontal.noUi-marker { - margin-left: -1px; - width: 2px; - height: 5px; + margin-left: -1px; + width: 2px; + height: 5px; } .noUi-marker-horizontal.noUi-marker-sub { - height: 10px; + height: 10px; } .noUi-marker-horizontal.noUi-marker-large { - height: 15px; + height: 15px; } /* Vertical layout; * */ .noUi-pips-vertical { - padding: 0 10px; - height: 100%; - top: 0; - left: 100%; + padding: 0 10px; + height: 100%; + top: 0; + left: 100%; } .noUi-value-vertical { - -webkit-transform: translate(0, -50%); - transform: translate(0, -50%); - padding-left: 25px; + -webkit-transform: translate(0, -50%); + transform: translate(0, -50%); + padding-left: 25px; } .noUi-rtl .noUi-value-vertical { - -webkit-transform: translate(0, 50%); - transform: translate(0, 50%); + -webkit-transform: translate(0, 50%); + transform: translate(0, 50%); } .noUi-marker-vertical.noUi-marker { - width: 5px; - height: 2px; - margin-top: -1px; + width: 5px; + height: 2px; + margin-top: -1px; } .noUi-marker-vertical.noUi-marker-sub { - width: 10px; + width: 10px; } .noUi-marker-vertical.noUi-marker-large { - width: 15px; + width: 15px; } .noUi-tooltip { - display: block; - position: absolute; - border: 1px solid #D9D9D9; - border-radius: 3px; - background: #fff; - color: #000; - padding: 5px; - text-align: center; - white-space: nowrap; + display: block; + position: absolute; + border: 1px solid #d9d9d9; + border-radius: 3px; + background: #fff; + color: #000; + padding: 5px; + text-align: center; + white-space: nowrap; } .noUi-horizontal .noUi-tooltip { - -webkit-transform: translate(-50%, 0); - transform: translate(-50%, 0); - left: 50%; - bottom: 120%; + -webkit-transform: translate(-50%, 0); + transform: translate(-50%, 0); + left: 50%; + bottom: 120%; } .noUi-vertical .noUi-tooltip { - -webkit-transform: translate(0, -50%); - transform: translate(0, -50%); - top: 50%; - right: 120%; + -webkit-transform: translate(0, -50%); + transform: translate(0, -50%); + top: 50%; + right: 120%; } diff --git a/client/assets/css/VuMeter.css b/client/assets/css/VuMeter.css index def13ce2..06b1f0b1 100644 --- a/client/assets/css/VuMeter.css +++ b/client/assets/css/VuMeter.css @@ -1,20 +1,17 @@ .vumeter-body { - position: absolute; - top: 110px; width: 24px; - margin-top: 15px; - margin-left: 5px; + margin: 15px 5px; background-color: black; border-radius: 7px; height: 430px; } .vumeter-canvas { - position: absolute; width: 10px; margin-left: 6px; bottom: 0; height: 100%; + margin-top: 50%; } .vumeter-lower-part { diff --git a/client/components/App.tsx b/client/components/App.tsx index 23ab1da0..4c530f11 100644 --- a/client/components/App.tsx +++ b/client/components/App.tsx @@ -32,6 +32,7 @@ class App extends React.Component { window.socketIoClient.emit('get-store', 'update local store') window.socketIoClient.emit('get-settings', 'update local settings') this.iFrameFocusHandler() + this.contextMenuHandler() } public shouldComponentUpdate(nextProps: IAppProps) { @@ -86,24 +87,35 @@ class App extends React.Component { } } + /** + * disables context menu in order to enable multi touch support + */ + contextMenuHandler() { + document.addEventListener( + 'contextmenu', + function (e) { + e.preventDefault() + }, + false + ) + } + render() { return (
- {!this.props.store.settings[0].serverOnline ? ( + {!this.props.store.settings[0].serverOnline && (
- { this.props.t('TRYING TO CONNECT TO SISYFOS SERVER')} + {this.props.t('TRYING TO CONNECT TO SISYFOS SERVER')}
- ) : null} - {!window.location.search.includes('minimonitor=1') ? ( + )} + {!window.location.search.includes('minimonitor=1') && ( - ) : null} - {window.location.search.includes('minimonitor=1') ? ( + )} + {window.location.search.includes('minimonitor=1') && ( - ) : null} - {this.props.store.settings[0].showStorage ? : null} - {this.props.store.settings[0].showSettings ? ( - - ) : null} + )} + {this.props.store.settings[0].showStorage && } + {this.props.store.settings[0].showSettings && }
) } @@ -112,8 +124,11 @@ class App extends React.Component { const mapStateToProps = (state: any, t: any): IAppProps => { return { store: state, - t: t + t: t, } } -export default compose(connect(mapStateToProps), withTranslation()) (App) as any +export default compose( + connect(mapStateToProps), + withTranslation() +)(App) as any diff --git a/client/components/ChanStrip.tsx b/client/components/ChanStrip.tsx index bed21478..638edc13 100644 --- a/client/components/ChanStrip.tsx +++ b/client/components/ChanStrip.tsx @@ -24,6 +24,7 @@ import { } from '../../server/constants/SOCKET_IO_DISPATCHERS' import CcgChannelInputSettings from './CcgChannelSettings' import ReductionMeter from './ReductionMeter' +import ClassNames from 'classnames' interface IChanStripInjectProps { label: string @@ -46,6 +47,14 @@ class ChanStrip extends React.PureComponent< super(props) } + shouldComponentUpdate(nextProps: IChanStripInjectProps & IChanStripProps) { + if (nextProps.faderIndex > -1) { + return true + } else { + return false + } + } + handleShowRoutingOptions() { this.props.dispatch({ type: TOGGLE_SHOW_OPTION, @@ -152,7 +161,10 @@ class ChanStrip extends React.PureComponent< this.props.fader[this.props.faderIndex].inputSelector === index + 1 return ( @@ -254,25 +278,98 @@ class Channel extends React.Component< muteButton = () => { return ( - + ) + ) + } + + amixButton = () => { + return ( + window.mixerProtocol.channelTypes[0].toMixer.CHANNEL_AMIX && ( + + ) + ) + } + + render() { + return this.props.fader.showChannel === false ? null : ( +
{ - event.preventDefault() - this.handleMute() - }} - onTouchEnd={(event) => { - event.preventDefault() - this.handleMute() - }} + ref={this._domRef} > - MUTE - +
+ {this.ignoreButton()} + {/* TODO - amix and mute cannot be shown at the same time due to css. Depends on protocol right now. */} + {this.muteButton()} + {this.amixButton()} +
+
+ {this.fader()} + {window.mixerProtocol.channelTypes[0].fromMixer + .CHANNEL_VU && } +
+
+ {this.pgmButton()} + {this.props.settings.automationMode ? ( + + {this.voButton()} +
+
+ ) : null} +
+
+ {this.chanStripButton()} + {!this.props.settings.showPfl ? ( + {this.pstButton()} + ) : null} + {this.props.settings.showPfl ? ( + {this.pflButton()} + ) : null} +
+
) } - render() { + DONOTUSE_render() { return this.props.fader.showChannel === false ? null : (
{this.ignoreButton()} {this.muteButton()} + {/* {this.props.fader.faderLevel} */}

_____

{this.fader()} diff --git a/client/components/Channels.tsx b/client/components/Channels.tsx index 744c32e1..4baaf971 100644 --- a/client/components/Channels.tsx +++ b/client/components/Channels.tsx @@ -22,7 +22,6 @@ import { SOCKET_CLEAR_PST, SOCKET_RESTART_SERVER, } from '../../server/constants/SOCKET_IO_DISPATCHERS' -import { classNames } from 'react-select/src/utils' interface IChannelsInjectProps { channels: IChannels @@ -110,7 +109,7 @@ class Channels extends React.Component { curPage.type === PageType.CustomPage && curPage.id === p.id customPageButtons.push( - ) : ( - - )} - - {window.location.search.includes('settings=0') ? null : ( - - )} - {window.location.search.includes('settings=0') ? null : ( - - )} - -
+
+ {this.props.settings.mixerOnline ? ( + + ) : ( + + )} - { - - } -
- - {this.renderPageButtons()} + {window.location.search.includes( + 'settings=0' + ) ? null : ( + + )} + {window.location.search.includes( + 'settings=0' + ) ? null : ( + + )} +
+
+ {!this.props.settings.showPfl && ( + + + + + )} +
+
{this.renderPageButtons()}
) diff --git a/client/components/VuMeter.tsx b/client/components/VuMeter.tsx index bc8b7ca4..2aab6690 100644 --- a/client/components/VuMeter.tsx +++ b/client/components/VuMeter.tsx @@ -22,10 +22,16 @@ export class VuMeter extends React.PureComponent { windowLast: number = 0 WINDOW: number = 2000 + private _painting = false + constructor(props: any) { super(props) } + componentDidMount() { + if (this._painting) this.paintVuMeter() + } + totalHeight = () => { return ( ((this.props.showSnaps ? 1 : 2) * 200) / @@ -88,7 +94,11 @@ export class VuMeter extends React.PureComponent { } paintVuMeter = () => { - if (!this.canvas) return + if (!this.canvas) { + this._painting = false + return + } + this._painting = true const context = this.canvas.getContext('2d', { antialias: false, @@ -162,11 +172,11 @@ export class VuMeter extends React.PureComponent { this.canvas.clientWidth, 2 ) + + window.requestAnimationFrame(this.paintVuMeter) } render() { - this.paintVuMeter() - return (
{ + store.dispatch({ + type: TOGGLE_AMIX, + channel: faderIndex, + }) + mixerGenericConnection.updateAMixState(faderIndex) + this.updatePartialStore(faderIndex) + }) .on(SOCKET_TOGGLE_IGNORE, (faderIndex: any) => { store.dispatch({ type: IGNORE_AUTOMATION, diff --git a/server/constants/MixerProtocolInterface.ts b/server/constants/MixerProtocolInterface.ts index 83562352..f7787598 100644 --- a/server/constants/MixerProtocolInterface.ts +++ b/server/constants/MixerProtocolInterface.ts @@ -49,6 +49,7 @@ export interface IChannelTypes { AUX_LEVEL?: Array CHANNEL_MUTE_ON?: Array CHANNEL_MUTE_OFF?: Array + CHANNEL_AMIX?: Array } toMixer: { CHANNEL_INPUT_GAIN?: Array @@ -68,6 +69,7 @@ export interface IChannelTypes { AUX_LEVEL?: Array CHANNEL_MUTE_ON?: Array CHANNEL_MUTE_OFF?: Array + CHANNEL_AMIX?: Array } } diff --git a/server/constants/SOCKET_IO_DISPATCHERS.ts b/server/constants/SOCKET_IO_DISPATCHERS.ts index 911f8421..57620f44 100644 --- a/server/constants/SOCKET_IO_DISPATCHERS.ts +++ b/server/constants/SOCKET_IO_DISPATCHERS.ts @@ -21,6 +21,7 @@ export const SOCKET_TOGGLE_VO = 'toggleVo' export const SOCKET_TOGGLE_PST = 'togglePst' export const SOCKET_TOGGLE_PFL = 'togglePfl' export const SOCKET_TOGGLE_MUTE = 'toggleMute' +export const SOCKET_TOGGLE_AMIX = 'toggleAmix' export const SOCKET_TOGGLE_IGNORE = 'toggleIgnore' export const SOCKET_NEXT_MIX = 'nextMix' export const SOCKET_CLEAR_PST = 'clearPst' diff --git a/server/constants/mixerProtocols/LawoRuby.ts b/server/constants/mixerProtocols/LawoRuby.ts index c4449aa2..2ab78123 100644 --- a/server/constants/mixerProtocols/LawoRuby.ts +++ b/server/constants/mixerProtocols/LawoRuby.ts @@ -57,8 +57,8 @@ export const LawoRuby: IMixerProtocol = { value: 0, type: 'int', min: -191, - max: 255, - zero: 9, + max: 9, + zero: 0.75, }, ], CHANNEL_NAME: [ @@ -72,6 +72,11 @@ export const LawoRuby: IMixerProtocol = { }, ], PFL: [emptyMixerMessage()], + CHANNEL_AMIX: [ + { + mixerMessage: 'Ruby.Sources.{channel}.DSP.AutoMix.On', + }, + ], }, toMixer: { CHANNEL_INPUT_GAIN: [ @@ -140,13 +145,18 @@ export const LawoRuby: IMixerProtocol = { ], PFL_ON: [emptyMixerMessage()], PFL_OFF: [emptyMixerMessage()], + CHANNEL_AMIX: [ + { + mixerMessage: 'Ruby.Sources.{channel}.DSP.AutoMix.On', + }, + ], }, }, ], fader: { min: 0, max: 255, - zero: 204, + zero: 0.75, step: 5, }, meter: { diff --git a/server/reducers/faderActions.ts b/server/reducers/faderActions.ts index 32d29dc4..8bd775b0 100644 --- a/server/reducers/faderActions.ts +++ b/server/reducers/faderActions.ts @@ -36,3 +36,6 @@ export const FADE_TO_BLACK = 'FADE_TO_BLACK' export const CLEAR_PST = 'CLEAR_PST' export const SNAP_RECALL = 'SNAP_RECALL' export const SET_CHANNEL_DISABLED = 'SET_CHANNEL_DISABLED' +export const TOGGLE_AMIX = 'TOGGLE_AMIX' +export const SET_AMIX = 'SET_AMIX' +export const SET_CAPABILITY = 'SET_CAPABILITY' diff --git a/server/reducers/fadersReducer.ts b/server/reducers/fadersReducer.ts index c2d924df..87061ccf 100644 --- a/server/reducers/fadersReducer.ts +++ b/server/reducers/fadersReducer.ts @@ -38,6 +38,9 @@ import { SHOW_IN_MINI_MONITOR, SET_INPUT_SELECTOR, SET_CHANNEL_DISABLED, + TOGGLE_AMIX, + SET_AMIX, + SET_CAPABILITY, } from '../reducers/faderActions' export interface IFaders { @@ -56,6 +59,7 @@ export interface IFader { pstVoOn: boolean pflOn: boolean muteOn: boolean + amixOn: boolean low: number loMid: number mid: number @@ -69,6 +73,15 @@ export interface IFader { ignoreAutomation: boolean snapOn: Array disabled: boolean + + /** + * Assuming that the protocol has a "feature", can it be enabled on this fader? + * If the capibilities object does not exist, yes is assumed. + */ + capabilities?: { + hasAMix?: boolean + hasInputSelector?: boolean + } } export interface IVuMeters { @@ -96,6 +109,7 @@ const defaultFadersReducerState = (numberOfFaders: number): IFaders[] => { pstVoOn: false, pflOn: false, muteOn: false, + amixOn: false, low: 0.75, loMid: 0.75, mid: 0.75, @@ -335,6 +349,27 @@ export const faders = ( if (!nextState[0].fader[action.channel]) return nextState nextState[0].fader[action.channel].disabled = action.disabled return nextState + case TOGGLE_AMIX: //channel + nextState[0].fader[action.channel].amixOn = !nextState[0].fader[ + action.channel + ].amixOn + return nextState + case SET_AMIX: //channel + nextState[0].fader[action.channel].amixOn = action.state + return nextState + case SET_CAPABILITY: + nextState[0].fader[action.channel].capabilities = { + ...nextState[0].fader[action.channel].capabilities, + [action.capability]: action.enabled, + } + // remove object if empty: + if ( + Object.entries(nextState[0].fader[action.channel].capabilities) + .length === 0 + ) { + delete nextState[0].fader[action.channel].capabilities + } + return nextState default: return nextState } diff --git a/server/utils/MixerConnection.ts b/server/utils/MixerConnection.ts index 1fb36f91..dd471b25 100644 --- a/server/utils/MixerConnection.ts +++ b/server/utils/MixerConnection.ts @@ -189,6 +189,19 @@ export class MixerGenericConnection { ) } + updateAMixState(faderIndex: number) { + state.channels[0].channel.map( + (channel: IChannel, channelIndex: number) => { + if (faderIndex === channel.assignedFader) { + this.mixerConnection.updateAMixState( + channelIndex, + state.faders[0].fader[faderIndex].amixOn + ) + } + } + ) + } + updateNextAux(faderIndex: number) { let level = 0 if (state.faders[0].fader[faderIndex].pstOn) { diff --git a/server/utils/mixerConnections/LawoRubyConnection.ts b/server/utils/mixerConnections/LawoRubyConnection.ts index 25d3d437..78432dd4 100644 --- a/server/utils/mixerConnections/LawoRubyConnection.ts +++ b/server/utils/mixerConnections/LawoRubyConnection.ts @@ -10,6 +10,8 @@ import { SET_CHANNEL_DISABLED, SET_INPUT_GAIN, SET_INPUT_SELECTOR, + SET_CAPABILITY, + SET_AMIX, } from '../../reducers/faderActions' import { logger } from '../logger' import { SET_MIXER_ONLINE } from '../../reducers/settingsActions' @@ -205,6 +207,11 @@ export class LawoRubyMixerConnection { Number(typeIndex), channelTypeIndex ) + await this.subscribeAMixState( + ch, + Number(typeIndex), + channelTypeIndex + ) ch++ } catch (e) { logger.error( @@ -319,7 +326,16 @@ export class LawoRubyMixerConnection { try { const node = await this.emberConnection.getElementByPath(command) - if (node.contents.type !== Model.ElementType.Parameter) return + console.log('set_cap', ch, 'hasInputSel', true) + store.dispatch({ + type: SET_CAPABILITY, + channel: ch - 1, + capability: 'hasInputSelector', + enabled: true, + }) + if (node.contents.type !== Model.ElementType.Parameter) { + return + } logger.debug('Subscription of channel input selector: ' + command) this.emberConnection.subscribe(node, () => { @@ -341,6 +357,66 @@ export class LawoRubyMixerConnection { }) }) } catch (e) { + if (e.message.match(/could not find node/i)) { + console.log('set_cap', ch, 'hasInputSel', false) + store.dispatch({ + type: SET_CAPABILITY, + channel: ch - 1, + capability: 'hasInputSelector', + enabled: false, + }) + } + logger.debug('error when subscribing to input selector', e) + } + } + async subscribeAMixState( + ch: number, + typeIndex: number, + channelTypeIndex: number + ) { + const sourceName = this.faders[ch] + if (!sourceName) return + + let command = this.mixerProtocol.channelTypes[ + typeIndex + ].fromMixer.CHANNEL_AMIX[0].mixerMessage.replace( + '{channel}', + sourceName + ) + + try { + const node = await this.emberConnection.getElementByPath(command) + console.log('set_cap', ch - 1, 'hasAMix', true) + store.dispatch({ + type: SET_CAPABILITY, + channel: ch - 1, + capability: 'hasAMix', + enabled: true, + }) + if (node.contents.type !== Model.ElementType.Parameter) { + return + } + + logger.debug('Subscription of AMix state: ' + command) + this.emberConnection.subscribe(node, () => { + logger.verbose('Receiving AMix state from Ch ' + String(ch)) + store.dispatch({ + type: SET_AMIX, + channel: ch - 1, + state: (node.contents as Model.Parameter).value, + }) + global.mainThreadHandler.updatePartialStore(ch - 1) + }) + } catch (e) { + if (e.message.match(/could not find node/i)) { + console.log('set_cap', ch - 1, 'hasAMix', false) + store.dispatch({ + type: SET_CAPABILITY, + channel: ch - 1, + capability: 'hasAMix', + enabled: false, + }) + } logger.debug('error when subscribing to input selector', e) } } @@ -356,7 +432,7 @@ export class LawoRubyMixerConnection { sendOutMessage( mixerMessage: string, channel: number, - value: string | number, + value: string | number | boolean, type?: string ) { const channelString = this.faders[channel] @@ -371,7 +447,7 @@ export class LawoRubyMixerConnection { logger.verbose('Sending out message : ' + message) this.emberConnection.setValue( element, - typeof value === 'number' ? value : parseFloat(value) + typeof value === 'string' ? parseFloat(value) : value ) }) .catch((error: any) => { @@ -450,6 +526,21 @@ export class LawoRubyMixerConnection { return true } + updateAMixState(channelIndex: number, amixOn: boolean) { + const channel = state.channels[0].channel[channelIndex] + let channelType = channel.channelType + let channelTypeIndex = channel.channelTypeIndex + let protocol = this.mixerProtocol.channelTypes[channelType].toMixer + .CHANNEL_AMIX[0] + + this.sendOutMessage( + protocol.mixerMessage, + channelTypeIndex + 1, + amixOn, + '' + ) + } + updateNextAux(channelIndex: number, level: number) { return true }