1
0
Fork 0
mirror of https://gitlab.com/futo-org/fcast.git synced 2025-06-24 21:25:23 +00:00

Receivers: Added responsive UI design

This commit is contained in:
Michael Hollister 2025-04-24 16:25:56 -05:00
parent 6dd58cd620
commit 47f388b5d4
7 changed files with 546 additions and 85 deletions

View file

@ -3,12 +3,16 @@ import QRCode from 'modules/qrcode';
import { onQRCodeRendered } from 'src/main/Renderer'; import { onQRCodeRendered } from 'src/main/Renderer';
import { toast, ToastIcon } from '../components/Toast'; import { toast, ToastIcon } from '../components/Toast';
const connectionStatusText = document.getElementById("connection-status-text"); const connectionStatusText = document.getElementById('connection-status-text');
const connectionStatusSpinner = document.getElementById("connection-spinner"); const connectionStatusSpinner = document.getElementById('connection-spinner');
const connectionStatusCheck = document.getElementById("connection-check"); const connectionStatusCheck = document.getElementById('connection-check');
let connections = []; let connections = [];
let renderedConnectionInfo = false; let renderedConnectionInfo = false;
let renderedAddresses = null; let renderedAddresses = null;
let qrCodeUrl = null;
let qrWidth = null;
window.addEventListener('resize', (event) => calculateQRCodeWidth());
// Window might be re-created while devices are still connected // Window might be re-created while devices are still connected
window.targetAPI.onPing((_event, value: any) => { window.targetAPI.onPing((_event, value: any) => {
@ -33,17 +37,17 @@ window.targetAPI.onDisconnect((_event, value: any) => {
connectionStatusText.textContent = 'Waiting for a connection'; connectionStatusText.textContent = 'Waiting for a connection';
connectionStatusSpinner.style.display = 'inline-block'; connectionStatusSpinner.style.display = 'inline-block';
connectionStatusCheck.style.display = 'none'; connectionStatusCheck.style.display = 'none';
toast("Device disconnected", ToastIcon.INFO); toast('Device disconnected', ToastIcon.INFO);
} }
else { else {
connectionStatusText.textContent = connections.length > 1 ? 'Multiple devices connected:\r\n Ready to cast' : 'Connected: Ready to cast'; connectionStatusText.textContent = connections.length > 1 ? 'Multiple devices connected:\r\n Ready to cast' : 'Connected: Ready to cast';
toast("A device has disconnected", ToastIcon.INFO); toast('A device has disconnected', ToastIcon.INFO);
} }
} }
}); });
if(window.targetAPI.getDeviceInfo()) { if(window.targetAPI.getDeviceInfo()) {
console.log("device info already present"); console.log('device info already present');
renderIPsAndQRCode(); renderIPsAndQRCode();
} }
@ -56,7 +60,7 @@ function onConnect(value: any) {
function renderIPsAndQRCode() { function renderIPsAndQRCode() {
const value = window.targetAPI.getDeviceInfo(); const value = window.targetAPI.getDeviceInfo();
console.log("device info", value); console.log(`Network Interface Info: ${value}`);
const addresses = []; const addresses = [];
value.interfaces.forEach((e) => addresses.push(e.address)); value.interfaces.forEach((e) => addresses.push(e.address));
@ -64,14 +68,14 @@ function renderIPsAndQRCode() {
const connError = document.getElementById('connection-error'); const connError = document.getElementById('connection-error');
if (renderedAddresses !== null && addresses.length > 0) { if (renderedAddresses !== null && addresses.length > 0) {
toast("Network connections has changed, please reconnect sender devices to receiver if you experience issues", ToastIcon.WARNING); toast('Network connections has changed, please reconnect sender devices to receiver if you experience issues', ToastIcon.WARNING);
} }
else if (addresses.length === 0) { else if (addresses.length === 0) {
connInfo.style.display = 'none'; connInfo.style.display = 'none';
connError.style.display = 'block'; connError.style.display = 'block';
if (renderedAddresses !== null) { if (renderedAddresses !== null) {
toast("Lost network connection, please reconnect to a network", ToastIcon.ERROR); toast('Lost network connection, please reconnect to a network', ToastIcon.ERROR);
} }
renderedAddresses = [] renderedAddresses = []
@ -102,36 +106,15 @@ function renderIPsAndQRCode() {
const json = JSON.stringify(fcastConfig); const json = JSON.stringify(fcastConfig);
let base64 = btoa(json); let base64 = btoa(json);
base64 = base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); base64 = base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
const url = `fcast://r/${base64}`; qrCodeUrl = `fcast://r/${base64}`;
console.log("qr", {json, url, base64}); console.log('QR Code:', {json, qrCodeUrl, base64});
const qrCodeElement = document.getElementById('qr-code');
QRCode.toCanvas(qrCodeElement, url, {
margin: 0,
width: 256,
color: {
dark : "#000000",
light : "#ffffff",
},
errorCorrectionLevel : "M",
},
(err) => {
if (err) {
console.error(`Error rendering QR Code: ${err}`);
toast(`Error rendering QR Code: ${err}`, ToastIcon.ERROR);
}
else {
console.log(`Rendered QR Code`);
}
});
calculateQRCodeWidth();
if (!renderedConnectionInfo) { if (!renderedConnectionInfo) {
const connInfoLoading = document.getElementById('connection-information-loading'); const connInfoLoading = document.getElementById('connection-information-loading');
connInfoLoading.style.display = 'none'; connInfoLoading.style.display = 'none';
connInfo.style.display = 'block'; connInfo.style.display = 'block';
} }
onQRCodeRendered();
} }
function renderIPs(interfaces: any) { function renderIPs(interfaces: any) {
@ -148,7 +131,7 @@ function renderIPs(interfaces: any) {
ipsNameColumn.innerHTML = ''; ipsNameColumn.innerHTML = '';
for (const iface of interfaces) { for (const iface of interfaces) {
const ipIcon = document.createElement("div"); const ipIcon = document.createElement('div');
let icon = 'iconSize '; let icon = 'iconSize ';
if (iface.type === 'wired') { if (iface.type === 'wired') {
icon += 'ip-wired-icon'; icon += 'ip-wired-icon';
@ -172,15 +155,63 @@ function renderIPs(interfaces: any) {
ipIcon.className = icon; ipIcon.className = icon;
ipsIconColumn.append(ipIcon); ipsIconColumn.append(ipIcon);
const ipText = document.createElement("div"); const ipText = document.createElement('div');
ipText.className = 'ip-entry-text'; ipText.className = 'ip-entry-text';
ipText.textContent = iface.address; ipText.textContent = iface.address;
ipsTextColumn.append(ipText); ipsTextColumn.append(ipText);
const ipName = document.createElement("div"); const ipName = document.createElement('div');
ipName.className = 'ip-entry-text'; ipName.className = 'ip-entry-text';
ipName.textContent = iface.name; ipName.textContent = iface.name;
ipsNameColumn.append(ipName); ipsNameColumn.append(ipName);
} }
} }
} }
function calculateQRCodeWidth() {
if (qrCodeUrl !== null) {
let changedQrWidth = null;
if ((window.innerWidth >= 2560) || (window.innerHeight >= 1440)) {
changedQrWidth = 384;
}
if ((window.innerWidth >= 1920 && window.innerWidth < 2560) || (window.innerHeight >= 1080 && window.innerHeight < 1440)) {
changedQrWidth = 256;
}
if ((window.innerWidth >= 1280 && window.innerWidth < 1920) || (window.innerHeight >= 720 && window.innerHeight < 1080)) {
changedQrWidth = 192;
}
if (window.innerWidth < 1280 || window.innerHeight < 720) {
changedQrWidth = 128;
}
if (qrWidth !== changedQrWidth) {
qrWidth = changedQrWidth;
renderQRCode(qrCodeUrl);
}
}
}
function renderQRCode(url: string) {
const qrCodeElement = document.getElementById('qr-code');
QRCode.toCanvas(qrCodeElement, url, {
margin: 0,
width: qrWidth,
color: {
dark : '#000000',
light : '#ffffff',
},
errorCorrectionLevel : 'M',
},
(err) => {
if (err) {
console.error(`Error rendering QR Code: ${err}`);
toast(`Error rendering QR Code: ${err}`, ToastIcon.ERROR);
}
else {
console.log(`Rendered QR Code`);
}
});
onQRCodeRendered();
}

View file

@ -25,7 +25,6 @@ body, html {
text-align: center; text-align: center;
background-color: rgba(20, 20, 20, 0.5); background-color: rgba(20, 20, 20, 0.5);
padding: 25px;
border-radius: 10px; border-radius: 10px;
border: 1px solid #2E2E2E; border: 1px solid #2E2E2E;
scrollbar-width: thin; scrollbar-width: thin;
@ -34,8 +33,6 @@ body, html {
.card-title { .card-title {
font-weight: 700; font-weight: 700;
line-height: 24px;
margin: 10px;
} }
.card-title-separator { .card-title-separator {
@ -46,8 +43,6 @@ body, html {
} }
.iconSize { .iconSize {
width: 32px;
height: 32px;
background-size: cover; background-size: cover;
} }
@ -69,10 +64,8 @@ body, html {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
color: white; color: white;
gap: 15vw;
font-family: InterVariable; font-family: InterVariable;
font-size: 28px;
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
} }
@ -89,7 +82,6 @@ body, html {
#title-text { #title-text {
font-family: Outfit; font-family: Outfit;
font-size: 140px;
font-weight: 800; font-weight: 800;
text-align: center; text-align: center;
@ -100,16 +92,11 @@ body, html {
} }
#title-icon { #title-icon {
width: 124px;
height: 124px;
background-image: url(../assets/icons/app/icon.svg); background-image: url(../assets/icons/app/icon.svg);
background-size: cover; background-size: cover;
margin-right: 25px;
} }
#connection-status { #connection-status {
padding: 25px;
text-align: center; text-align: center;
} }
@ -130,7 +117,6 @@ body, html {
width: 84px; width: 84px;
height: 84px; height: 84px;
margin: auto; margin: auto;
margin-top: 20px;
background-image: url(../assets/icons/app/error.svg); background-image: url(../assets/icons/app/error.svg);
background-size: cover; background-size: cover;
@ -150,7 +136,6 @@ body, html {
#connection-information-loading-text { #connection-information-loading-text {
width: auto; width: auto;
height: auto; height: auto;
margin: 10px 20px;
} }
#connection-information { #connection-information {
@ -161,16 +146,13 @@ body, html {
} }
#scan-to-connect { #scan-to-connect {
margin-top: 20px;
font-weight: bold; font-weight: bold;
} }
#qr-code { #qr-code {
display: flex; display: flex;
margin: 20px auto;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
padding: 20px;
background-color: white; background-color: white;
} }
@ -200,11 +182,6 @@ body, html {
align-items: start; align-items: start;
} }
.ip-entry-text {
margin-top: 4px;
margin-bottom: 4px;
}
.ip-wired-icon { .ip-wired-icon {
background-image: url(../assets/icons/app/network-light.svg); background-image: url(../assets/icons/app/network-light.svg);
} }
@ -233,10 +210,8 @@ body, html {
color: #666666; color: #666666;
position: absolute; position: absolute;
bottom: 0; bottom: 0;
margin-bottom: 20px;
font-family: InterVariable; font-family: InterVariable;
font-size: 24px;
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
} }
@ -244,15 +219,11 @@ body, html {
.lds-ring { .lds-ring {
display: inline-block; display: inline-block;
position: relative; position: relative;
width: 120px;
height: 120px;
} }
.lds-ring div { .lds-ring div {
box-sizing: border-box; box-sizing: border-box;
display: block; display: block;
position: absolute; position: absolute;
width: 104px;
height: 104px;
margin: 8px; margin: 8px;
border: 8px solid #fff; border: 8px solid #fff;
border-radius: 50%; border-radius: 50%;
@ -281,8 +252,6 @@ body, html {
/* display: inline-block; */ /* display: inline-block; */
display: none; display: none;
position: relative; position: relative;
width: 104px;
height: 104px;
margin: 18px; margin: 18px;
padding: 10px; padding: 10px;
@ -381,3 +350,418 @@ body, html {
opacity: 0; opacity: 0;
} }
} }
/* Display scaling (Minimum supported resolution is 960x540) */
@media only screen and ((min-width: 2560px) or (min-height: 1440px)) {
.card {
padding: 25px;
}
.card-title {
line-height: 24px;
margin: 10px;
}
.card-title-separator {
margin: 5px 0px;
margin-top: 10px;
}
.iconSize {
width: 48px;
height: 48px;
}
#overlay {
gap: 15vw;
font-size: 32px;
}
#title-text {
font-size: 180px;
}
#title-icon {
width: 164px;
height: 164px;
margin-right: 30px;
}
#connection-status {
padding: 35px;
}
#connection-error-icon {
margin-top: 20px;
}
#connection-information-loading-text {
margin: 20px;
}
#scan-to-connect {
margin-top: 20px;
}
#qr-code {
width: 384px;
height: 384px;
margin: 25px auto;
padding: 20px;
}
#connection-details-separator {
margin-top: 15px;
}
#ips {
margin-top: 20px;
gap: 15px;
}
.ip-entry-text {
margin-top: 4.5px;
margin-bottom: 4.5px;
}
#window-can-be-closed {
margin-bottom: 20px;
font-size: 28px;
}
.lds-ring {
width: 140px;
height: 140px;
}
.lds-ring div {
width: 124px;
height: 124px;
}
#connection-check {
width: 124px;
height: 124px;
}
#toast-notification {
padding: 16px;
top: -225px;
}
#toast-icon {
width: 108px;
height: 108px;
margin: 5px 10px;
}
#toast-text {
font-size: 32px;
}
}
@media only screen and ((max-width: 2559px) or (max-height: 1439px)) {
.card {
padding: 25px;
}
.card-title {
line-height: 24px;
margin: 10px;
}
.card-title-separator {
margin: 3px 0px;
}
.iconSize {
width: 48px;
height: 48px;
}
#overlay {
gap: 15vw;
font-size: 28px;
}
#title-text {
font-size: 140px;
}
#title-icon {
width: 124px;
height: 124px;
margin-right: 25px;
}
#connection-status {
padding: 25px;
}
#connection-error-icon {
margin-top: 20px;
}
#connection-information-loading-text {
margin: 20px;
}
#scan-to-connect {
margin-top: 20px;
}
#qr-code {
width: 256px;
height: 256px;
margin: 20px auto;
padding: 16px;
}
#connection-details-separator {
margin-top: 15px;
}
#ips {
margin-top: 20px;
gap: 15px;
}
.ip-entry-text {
margin-top: 7px;
margin-bottom: 7px;
}
#window-can-be-closed {
margin-bottom: 20px;
font-size: 24px;
}
.lds-ring {
width: 120px;
height: 120px;
}
.lds-ring div {
width: 104px;
height: 104px;
}
#connection-check {
width: 104px;
height: 104px;
}
#toast-notification {
padding: 16px;
top: -200px;
}
#toast-icon {
width: 88px;
height: 88px;
margin: 5px 10px;
}
#toast-text {
font-size: 28px;
}
}
@media only screen and ((max-width: 1919px) or (max-height: 1079px)) {
.card {
padding: 15px;
}
.card-title {
line-height: 20px;
margin: 5px;
margin-bottom: 10px;
}
.card-title-separator {
margin: 3px 0px;
}
.iconSize {
width: 32px;
height: 32px;
}
#overlay {
gap: 12.5vw;
font-size: 20px;
}
#title-text {
font-size: 100px;
}
#title-icon {
width: 84px;
height: 84px;
margin-right: 15px;
}
#connection-status {
padding: 15px;
}
#connection-error-icon {
margin-top: 10px;
}
#connection-information-loading-text {
margin: 10px;
}
#scan-to-connect {
margin-top: 10px;
}
#qr-code {
width: 192px;
height: 192px;
margin: 15px auto;
padding: 12px;
}
#ips {
margin-top: 10px;
}
.ip-entry-text {
margin-top: 4px;
margin-bottom: 4px;
}
#window-can-be-closed {
margin-bottom: 15px;
font-size: 18px;
}
.lds-ring {
width: 100px;
height: 100px;
}
.lds-ring div {
width: 84px;
height: 84px;
}
#connection-check {
width: 84px;
height: 84px;
}
#toast-notification {
padding: 12px;
top: -150px;
}
#toast-icon {
width: 68px;
height: 68px;
}
#toast-text {
font-size: 20px;
}
}
@media only screen and ((max-width: 1279px) or (max-height: 719px)) {
.card {
padding: 15px;
}
.card-title {
line-height: 18px;
margin: 5px;
}
.card-title-separator {
margin: 3px 0px;
}
.iconSize {
width: 24px;
height: 24px;
}
#overlay {
gap: 10vw;
font-size: 18px;
}
#title-text {
font-size: 80px;
}
#title-icon {
width: 64px;
height: 64px;
margin-right: 15px;
}
#connection-status {
padding: 15px;
}
#connection-error-icon {
margin-top: 10px;
}
#connection-information-loading-text {
margin: 10px;
}
#scan-to-connect {
margin-top: 10px;
}
#qr-code {
width: 128px;
height: 128px;
margin: 15px auto;
padding: 8px;
}
#ips {
margin-top: 10px;
}
.ip-entry-text {
margin-top: 1.5px;
margin-bottom: 1.5px;
}
#window-can-be-closed {
margin-bottom: 10px;
font-size: 16px;
}
.lds-ring {
width: 80px;
height: 80px;
}
.lds-ring div {
width: 64px;
height: 64px;
}
#connection-check {
width: 64px;
height: 64px;
}
#toast-notification {
padding: 8px;
top: -125px;
}
#toast-icon {
width: 48px;
height: 48px;
}
#toast-text {
font-size: 18px;
}
}

View file

@ -405,11 +405,10 @@ body {
position: absolute; position: absolute;
bottom: 80px; bottom: 80px;
right: 60px; right: 60px;
height: calc(55vh);
max-height: 368px; max-height: 368px;
background-color: #141414; background-color: #141414;
padding: 12px; padding: 8px;
border-radius: 10px; border-radius: 10px;
border: 1px solid #2E2E2E; border: 1px solid #2E2E2E;
scrollbar-width: thin; scrollbar-width: thin;
@ -427,11 +426,13 @@ body {
font-weight: 700; font-weight: 700;
line-height: 24px; line-height: 24px;
margin: 10px; margin: 10px;
user-select: none;
} }
.speedMenuEntry { .speedMenuEntry {
display: flex; display: flex;
padding: 10px 15px; padding: 5px 10px;
user-select: none;
} }
.speedMenuEntry:hover { .speedMenuEntry:hover {

View file

@ -155,8 +155,6 @@ export class Main {
Main.playerWindow = new BrowserWindow({ Main.playerWindow = new BrowserWindow({
fullscreen: true, fullscreen: true,
autoHideMenuBar: true, autoHideMenuBar: true,
minWidth: 515,
minHeight: 290,
icon: path.join(__dirname, 'icon512.png'), icon: path.join(__dirname, 'icon512.png'),
webPreferences: { webPreferences: {
preload: path.join(__dirname, 'player/preload.js') preload: path.join(__dirname, 'player/preload.js')
@ -295,8 +293,6 @@ export class Main {
fullscreen: Main.startFullscreen, fullscreen: Main.startFullscreen,
autoHideMenuBar: true, autoHideMenuBar: true,
icon: path.join(__dirname, 'icon512.png'), icon: path.join(__dirname, 'icon512.png'),
minWidth: 1100,
minHeight: 800,
webPreferences: { webPreferences: {
preload: path.join(__dirname, 'main/preload.js') preload: path.join(__dirname, 'main/preload.js')
} }

View file

@ -3,6 +3,7 @@
<head> <head>
<title>FCast Receiver</title> <title>FCast Receiver</title>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="../assets/fonts/outfit.css" /> <link rel="stylesheet" href="../assets/fonts/outfit.css" />
<link rel="stylesheet" href="../assets/fonts/inter.css" /> <link rel="stylesheet" href="../assets/fonts/inter.css" />
<link rel="stylesheet" href="./common.css" /> <link rel="stylesheet" href="./common.css" />
@ -61,8 +62,8 @@
<div id="app-download" class="non-selectable app-download">Need a sender app?<br>Download Grayjay at <a href="https://grayjay.app" target="_blank">https://grayjay.app</a></div> <div id="app-download" class="non-selectable app-download">Need a sender app?<br>Download Grayjay at <a href="https://grayjay.app" target="_blank">https://grayjay.app</a></div>
<br /> <br />
<div class="non-selectable card-title">Connection Details</div> <div id="connection-details" class="non-selectable card-title">Connection Details</div>
<div class="card-title-separator"></div> <div id="connection-details-separator" class="card-title-separator"></div>
<div> <div>
<div id="ips"> <div id="ips">
<div id="ips-iface-icon"></div> <div id="ips-iface-icon"></div>

View file

@ -2,12 +2,10 @@
display: inline-block; display: inline-block;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 16px;
gap: 6px; gap: 6px;
flex: 1 0 0; flex: 1 0 30%;
border-radius: 6px; border-radius: 6px;
margin: 20px 10px;
cursor: pointer; cursor: pointer;
user-select: none; user-select: none;
} }
@ -36,15 +34,14 @@
background: #3E3E3E; background: #3E3E3E;
} }
#update-text {
margin-top: 20px;
width: 320px;
}
#update-view { #update-view {
display: none; display: none;
} }
#update-button {
margin-left: -4%;
}
#restart-button { #restart-button {
display: none; display: none;
} }
@ -52,12 +49,12 @@
#update-button-container { #update-button-container {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
margin: auto;
} }
#progress-bar { #progress-bar {
display: none; display: none;
width: 320px;
height: 40px; height: 40px;
margin-top: 20px; margin-top: 20px;
border-radius: 50px; border-radius: 50px;
@ -82,3 +79,53 @@
background-position: 0 0; background-position: 0 0;
} }
} }
/* Display scaling (Minimum supported resolution is 960x540) */
@media only screen and ((min-width: 2560px) or (min-height: 1440px)) {
.button {
padding: 12px 35px;
margin: 20px;
margin-top: 25px;
margin-bottom: 15px;
}
#update-text {
margin-top: 20px;
}
}
@media only screen and ((max-width: 2559px) or (max-height: 1439px)) {
.button {
padding: 12px 30px;
margin: 20px 15px;
margin-bottom: 10px;
}
#update-text {
margin-top: 20px;
}
}
@media only screen and ((max-width: 1919px) or (max-height: 1079px)) {
.button {
padding: 8px 25px;
margin: 20px 10px;
margin-bottom: 10px;
}
#update-text {
margin-top: 10px;
}
}
@media only screen and ((max-width: 1279px) or (max-height: 719px)) {
.button {
padding: 8px 20px;
margin: 15px 10px;
margin-bottom: 5px;
}
#update-text {
margin-top: 10px;
}
}

View file

@ -3,6 +3,7 @@
<head> <head>
<title>FCast Receiver</title> <title>FCast Receiver</title>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="../assets/fonts/inter.css" /> <link rel="stylesheet" href="../assets/fonts/inter.css" />
<link rel="stylesheet" href="./common.css" /> <link rel="stylesheet" href="./common.css" />
<link rel="stylesheet" href="./style.css" /> <link rel="stylesheet" href="./style.css" />