From f6fb6f72f4ba8e5e18bec6228a928c342737808e Mon Sep 17 00:00:00 2001 From: Blallo Date: Tue, 7 Mar 2023 23:08:22 +0100 Subject: [PATCH] Improve ui appearance --- ui/package.json | 1 + ui/src/App.tsx | 2 - ui/src/features/radio/Radio.module.css | 79 ---------------- ui/src/features/radio/Radio.module.scss | 116 ++++++++++++++++++++++++ ui/src/features/radio/Radio.tsx | 43 +++++---- ui/yarn.lock | 18 +++- 6 files changed, 160 insertions(+), 99 deletions(-) delete mode 100644 ui/src/features/radio/Radio.module.css create mode 100644 ui/src/features/radio/Radio.module.scss diff --git a/ui/package.json b/ui/package.json index 2b52fcd..5a87da3 100644 --- a/ui/package.json +++ b/ui/package.json @@ -16,6 +16,7 @@ "react-redux": "^8.0.1", "react-scripts": "5.0.1", "react-use-websocket": "^4.3.1", + "sass": "^1.58.3", "typescript": "^4.6.0", "web-vitals": "^2.1.0" }, diff --git a/ui/src/App.tsx b/ui/src/App.tsx index ee07272..fcd8b00 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -1,5 +1,4 @@ import React from 'react'; -import logo from './logo.svg'; import { RadioToggler } from './features/radio/Radio'; import './App.css'; @@ -7,7 +6,6 @@ function App() { return (
- logo
diff --git a/ui/src/features/radio/Radio.module.css b/ui/src/features/radio/Radio.module.css deleted file mode 100644 index 025bb72..0000000 --- a/ui/src/features/radio/Radio.module.css +++ /dev/null @@ -1,79 +0,0 @@ -.row { - display: flex; - align-items: center; - justify-content: center; -} - -.row > button { - margin-left: 4px; - margin-right: 8px; -} - -.row:not(:last-child) { - margin-bottom: 16px; -} - -.value { - font-size: 78px; - padding-left: 16px; - padding-right: 16px; - margin-top: 2px; - font-family: 'Courier New', Courier, monospace; -} - -.button { - appearance: none; - background: none; - font-size: 32px; - padding-left: 12px; - padding-right: 12px; - outline: none; - border: 2px solid transparent; - color: rgb(112, 76, 182); - padding-bottom: 4px; - cursor: pointer; - background-color: rgba(112, 76, 182, 0.1); - border-radius: 2px; - transition: all 0.15s; -} - -.textbox { - font-size: 32px; - padding: 2px; - width: 64px; - text-align: center; - margin-right: 4px; -} - -.button:hover, -.button:focus { - border: 2px solid rgba(112, 76, 182, 0.4); -} - -.button:active { - background-color: rgba(112, 76, 182, 0.2); -} - -.asyncButton { - composes: button; - position: relative; -} - -.asyncButton:after { - content: ''; - background-color: rgba(112, 76, 182, 0.15); - display: block; - position: absolute; - width: 100%; - height: 100%; - left: 0; - top: 0; - opacity: 0; - transition: width 1s linear, opacity 0.5s ease 1s; -} - -.asyncButton:active:after { - width: 0%; - opacity: 1; - transition: 0s; -} diff --git a/ui/src/features/radio/Radio.module.scss b/ui/src/features/radio/Radio.module.scss new file mode 100644 index 0000000..fb01bcc --- /dev/null +++ b/ui/src/features/radio/Radio.module.scss @@ -0,0 +1,116 @@ +@-webkit-keyframes rotor{ + from{ -webkit-transform: rotate(0deg); } + to{ -webkit-transform: rotate(360deg); } +} +@-moz-keyframes rotor{ + from{ -moz-transform: rotate(0deg); } + to{ -moz-transform: rotate(360deg); } +} +@-o-keyframes rotor{ + from{ -o-transform: rotate(0deg); } + to{ -o-transform: rotate(360deg); } +} +@keyframes rotor{ + from{ transform: rotate(0deg); } + to{ transform: rotate(360deg); } +} + +$circle-size: 350px; +$inner-size: 310px; +$border-radius: 160px; +$margin: 20px; +$shadow-size: 20px; +$rotor-color-active: #6eeb34; +$rotor-color-stopped: #ff0000; +$rotor-color-loading: #fcc203; +$disconnected-color: #8a8987; +$hover-color: #fff7b0; + +@mixin wedge($color) { + background-image: linear-gradient(45deg, + white 0%, + white 30%, + $color 30%, + $color 70%, + white 70%, + white 100%); +} + +@mixin rotor-animation { + -webkit-animation: rotor 1.5s linear 0s infinite normal; + -mox-animation: rotor 1.5s linear 0s infinite normal; + -o-animation: rotor 1.5s linear 0s infinite normal; + animation: rotor 1.5s linear 0s infinite normal; +} + +@mixin circle-shape { + width: $circle-size; + height: $circle-size; + border-radius: $border-radius; + /*** outer circle position: under */ + z-index: 1; + position: absolute; +} + +.buttonWrap { + width: $circle-size; + height: $circle-size; + margin: $margin 0; + display: inline-block; + position: relative; + + /* Inner circle */ + .clicker { + width: $inner-size; + height: $inner-size; /* 20px smaller b/c of margin below */ + margin: $margin; + background-color: #fff; + border-radius: $border-radius; + + /* Overlays this circle on the .circle */ + z-index: 2; + position: absolute; + + /* centers the text: adjust to desired size */ + display: flex; + justify-content: center; + align-content: center; + flex-direction: column; + text-align: center; + font-size: 60px; + user-select: none; + + /* shadow */ + -webkit-box-shadow: 0px 0px $shadow-size 0px rgba(0, 0, 0, 0.5); + -moz-box-shadow: 0px 0px $shadow-size 0px rgba(0, 0, 0, 0.5); + -o-box-shadow: 0px 0px $shadow-size 0px rgba(0, 0, 0, 0.5); + box-shadow: 0px 0px $shadow-size 0px rgba(0, 0, 0, 0.5); + + &:hover { + background-color: $hover-color; + } + } + + /** outer circle **/ + .circleDisconnected { + @include circle-shape; + background-color: $disconnected-color; + } + + .circleStarted { + @include circle-shape; + @include wedge($rotor-color-active); + @include rotor-animation; + } + + .circleLoading { + @include circle-shape; + @include wedge($rotor-color-loading); + @include rotor-animation; + } + + .circleStopped { + @include circle-shape; + background-color: $rotor-color-stopped; + } +} diff --git a/ui/src/features/radio/Radio.tsx b/ui/src/features/radio/Radio.tsx index 982b894..b8e0a4d 100644 --- a/ui/src/features/radio/Radio.tsx +++ b/ui/src/features/radio/Radio.tsx @@ -4,12 +4,7 @@ import useWebSocket from 'react-use-websocket'; import { useAppSelector, useAppDispatch } from '../../app/hooks'; import { RadioState, ConnectionState } from '../../app/types'; import { - setRadioStarted, - setRadioStopped, toggleRadioState, - setConnected, - setDisconnected, - toggleConnection, selectLoading, selectRadio, selectConnection, @@ -19,6 +14,7 @@ import { getOnWSClose, getOnWSMessage, } from './radioWS'; +import styles from './Radio.module.scss'; const getWSUrl = async () => { const wsUrl = process.env.WS_ADDR ? process.env.WS_ADDR : "ws://localhost/liveness"; @@ -27,7 +23,7 @@ const getWSUrl = async () => { }; export function RadioToggler() { - const loadingState = useAppSelector(selectLoading); + const loading = useAppSelector(selectLoading); const radioState = useAppSelector(selectRadio); const connectionState = useAppSelector(selectConnection); @@ -42,24 +38,39 @@ export function RadioToggler() { const onWSMessage = getOnWSMessage(dispatch); useEffect(() => { if (lastJsonMessage != null) { - onWSMessage(lastJsonMessage); + if (!loading) { + onWSMessage(lastJsonMessage); + } } }); - if (loadingState) { - return
Loading...
; + let buttonStatus = ""; + let circleStyle = styles.circleDisconnected; + if (loading) { + buttonStatus = "Loading"; + circleStyle = styles.circleLoading; + } else if (connectionState) { + switch (radioState) { + case RadioState.STARTED: + buttonStatus = "ON AIR"; + circleStyle = styles.circleStarted; + break; + case RadioState.STOPPED: + buttonStatus = "OFFLINE"; + circleStyle = styles.circleStopped; + break; + } } return (
-
- -
Radio: {radioState}
-
Connection: {connectionState}
+ >{buttonStatus}
+
- + ); } diff --git a/ui/yarn.lock b/ui/yarn.lock index c756b1e..101cca7 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -3053,7 +3053,7 @@ check-types@^11.1.1: resolved "https://registry.yarnpkg.com/check-types/-/check-types-11.2.2.tgz#7afc0b6a860d686885062f2dba888ba5710335b4" integrity sha512-HBiYvXvn9Z70Z88XKjz3AEKd4HJhBXsa3j7xFnITAzoS8+q6eIGi8qDB8FKPBAjtuxjI/zFpwuiCb8oDtKOYrA== -chokidar@^3.4.2, chokidar@^3.5.3: +"chokidar@>=3.0.0 <4.0.0", chokidar@^3.4.2, chokidar@^3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== @@ -4997,6 +4997,11 @@ immer@^9.0.16, immer@^9.0.7: resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.19.tgz#67fb97310555690b5f9cd8380d38fc0aabb6b38b" integrity sha512-eY+Y0qcsB4TZKwgQzLaE/lqYMlKhv5J9dyd2RhhtGhNo2njPXDqU9XPfcNfa3MIDsdtZt5KlkIsirlo4dHsWdQ== +immutable@^4.0.0: + version "4.2.4" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.2.4.tgz#83260d50889526b4b531a5e293709a77f7c55a2a" + integrity sha512-WDxL3Hheb1JkRN3sQkyujNlL/xRjAo3rJtaU5xeufUauG66JdMr32bLj4gF+vWl84DIA3Zxw7tiAjneYzRRw+w== + import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" @@ -7899,6 +7904,15 @@ sass-loader@^12.3.0: klona "^2.0.4" neo-async "^2.6.2" +sass@^1.58.3: + version "1.58.3" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.58.3.tgz#2348cc052061ba4f00243a208b09c40e031f270d" + integrity sha512-Q7RaEtYf6BflYrQ+buPudKR26/lH+10EmO9bBqbmPh/KeLqv8bjpTNqxe71ocONqXq+jYiCbpPUmQMS+JJPk4A== + dependencies: + chokidar ">=3.0.0 <4.0.0" + immutable "^4.0.0" + source-map-js ">=0.6.2 <2.0.0" + sax@~1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" @@ -8105,7 +8119,7 @@ source-list-map@^2.0.0, source-list-map@^2.0.1: resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== -source-map-js@^1.0.1, source-map-js@^1.0.2: +"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1, source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==