mirror of
https://gitlab.com/futo-org/fcast.git
synced 2025-08-03 07:47:01 +00:00
webOS: Ported receiver to Electron 2.1.0 changes
This commit is contained in:
parent
61c1e9a9c1
commit
e3c437a280
16 changed files with 2402 additions and 978 deletions
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"id": "com.futo.fcast.receiver",
|
||||
"version": "1.0.1",
|
||||
"version": "1.1.0",
|
||||
"vendor": "FUTO",
|
||||
"type": "web",
|
||||
"main": "main_window/index.html",
|
||||
|
|
1773
receivers/webos/fcast-receiver/package-lock.json
generated
1773
receivers/webos/fcast-receiver/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "com.futo.fcast.receiver",
|
||||
"version": "1.0.1",
|
||||
"version": "1.1.0",
|
||||
"description": "An application implementing a FCast receiver.",
|
||||
"author": "FUTO",
|
||||
"license": "MIT",
|
||||
|
@ -9,7 +9,7 @@
|
|||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.10.0",
|
||||
"@eslint/js": "^9.25.0",
|
||||
"@types/jest": "^29.5.11",
|
||||
"@types/mdns": "^0.0.38",
|
||||
"@types/node-forge": "^1.3.10",
|
||||
|
@ -17,17 +17,17 @@
|
|||
"@types/webostvjs": "^1.2.6",
|
||||
"@types/workerpool": "^6.1.1",
|
||||
"@types/ws": "^8.5.10",
|
||||
"copy-webpack-plugin": "^12.0.2",
|
||||
"eslint": "^9.10.0",
|
||||
"globals": "^15.9.0",
|
||||
"copy-webpack-plugin": "^13.0.0",
|
||||
"eslint": "^9.25.0",
|
||||
"globals": "^16.0.0",
|
||||
"jest": "^29.7.0",
|
||||
"mdns-js": "github:mdns-js/node-mdns-js",
|
||||
"ts-jest": "^29.1.1",
|
||||
"ts-loader": "^9.4.2",
|
||||
"typescript": "^5.5.4",
|
||||
"typescript-eslint": "^8.4.0",
|
||||
"webpack": "^5.75.0",
|
||||
"webpack-cli": "^5.0.1"
|
||||
"webpack": "^5.99.6",
|
||||
"webpack-cli": "^6.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"bufferutil": "^4.0.8",
|
||||
|
@ -35,10 +35,9 @@
|
|||
"hls.js": "^1.5.15",
|
||||
"http": "^0.0.1-security",
|
||||
"https": "^1.0.0",
|
||||
"log4js": "^6.9.1",
|
||||
"qrcode": "^1.5.3",
|
||||
"url": "^0.11.3",
|
||||
"uuid": "^9.0.1",
|
||||
"ws": "^8.14.2"
|
||||
"url": "^0.11.4",
|
||||
"uuid": "^11.0.3",
|
||||
"ws": "^8.18.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import { preloadData } from 'common/main/Preload';
|
|||
import { toast, ToastIcon } from 'common/components/Toast';
|
||||
require('lib/webOSTVjs-1.2.10/webOSTV.js');
|
||||
require('lib/webOSTVjs-1.2.10/webOSTV-dev.js');
|
||||
const logger = window.targetAPI.logger;
|
||||
|
||||
enum RemoteKeyCode {
|
||||
Stop = 413,
|
||||
|
@ -15,24 +16,57 @@ enum RemoteKeyCode {
|
|||
}
|
||||
|
||||
try {
|
||||
const toastService = registerService('toast', (message: any) => { toast(message.value.message, message.value.icon, message.value.duration); });
|
||||
const getDeviceInfoService = registerService('getDeviceInfo', (message: any) => {
|
||||
console.log(`Main: getDeviceInfo ${JSON.stringify(message)}`);
|
||||
preloadData.deviceInfo = message.value;
|
||||
preloadData.onDeviceInfoCb();
|
||||
}, false);
|
||||
const onConnectService = registerService('connect', (message: any) => { preloadData.onConnectCb(null, message.value); });
|
||||
const onDisconnectService = registerService('disconnect', (message: any) => { preloadData.onDisconnectCb(null, message.value); });
|
||||
const onPingService = registerService('ping', (message: any) => { preloadData.onPingCb(null, message.value); });
|
||||
const playService = registerService('play', (message: any) => {
|
||||
let getSessions = null;
|
||||
|
||||
const toastService = requestService('toast', (message: any) => { toast(message.value.message, message.value.icon, message.value.duration); });
|
||||
const getDeviceInfoService = window.webOS.service.request('luna://com.palm.connectionmanager', {
|
||||
method: 'getStatus',
|
||||
parameters: {},
|
||||
onSuccess: (message: any) => {
|
||||
// logger.info('Network info status message', message);
|
||||
const deviceName = 'FCast-LGwebOSTV';
|
||||
const connections = [];
|
||||
|
||||
if (message.wired.state !== 'disconnected') {
|
||||
connections.push({ type: 'wired', name: 'Ethernet', address: message.wired.ipAddress })
|
||||
}
|
||||
|
||||
// wifiDirect never seems to be connected, despite being connected (which is needed for signalLevel...)
|
||||
// if (message.wifiDirect.state !== 'disconnected') {
|
||||
if (message.wifi.state !== 'disconnected') {
|
||||
connections.push({ type: 'wireless', name: message.wifi.ssid, address: message.wifi.ipAddress, signalLevel: 100 })
|
||||
}
|
||||
|
||||
preloadData.deviceInfo = { name: deviceName, interfaces: connections };
|
||||
preloadData.onDeviceInfoCb();
|
||||
},
|
||||
onFailure: (message: any) => {
|
||||
logger.error(`Main: com.palm.connectionmanager/getStatus ${JSON.stringify(message)}`);
|
||||
toast(`Main: com.palm.connectionmanager/getStatus ${JSON.stringify(message)}`, ToastIcon.ERROR);
|
||||
|
||||
},
|
||||
// onComplete: (message) => {},
|
||||
subscribe: true,
|
||||
resubscribe: true
|
||||
});
|
||||
|
||||
window.targetAPI.getSessions(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
getSessions = requestService('get_sessions', (message: any) => resolve(message.value), (message: any) => reject(message), false);
|
||||
});
|
||||
});
|
||||
|
||||
const onConnectService = requestService('connect', (message: any) => { preloadData.onConnectCb(null, message.value); });
|
||||
const onDisconnectService = requestService('disconnect', (message: any) => { preloadData.onDisconnectCb(null, message.value); });
|
||||
const playService = requestService('play', (message: any) => {
|
||||
if (message.value !== undefined && message.value.playData !== undefined) {
|
||||
console.log(`Main: Playing ${JSON.stringify(message)}`);
|
||||
logger.info(`Main: Playing ${JSON.stringify(message)}`);
|
||||
sessionStorage.setItem('playData', JSON.stringify(message.value.playData));
|
||||
getDeviceInfoService.cancel();
|
||||
getSessions?.cancel();
|
||||
toastService.cancel();
|
||||
onConnectService.cancel();
|
||||
onDisconnectService.cancel();
|
||||
onPingService.cancel();
|
||||
playService.cancel();
|
||||
|
||||
// WebOS 22 and earlier does not work well using the history API,
|
||||
|
@ -44,7 +78,7 @@ try {
|
|||
|
||||
const launchHandler = () => {
|
||||
const params = window.webOSDev.launchParams();
|
||||
console.log(`Main: (Re)launching FCast Receiver with args: ${JSON.stringify(params)}`);
|
||||
logger.info(`Main: (Re)launching FCast Receiver with args: ${JSON.stringify(params)}`);
|
||||
|
||||
const lastTimestamp = Number(localStorage.getItem('lastTimestamp'));
|
||||
if (params.playData !== undefined && params.timestamp != lastTimestamp) {
|
||||
|
@ -52,9 +86,9 @@ try {
|
|||
sessionStorage.setItem('playData', JSON.stringify(params.playData));
|
||||
toastService?.cancel();
|
||||
getDeviceInfoService?.cancel();
|
||||
getSessions?.cancel();
|
||||
onConnectService?.cancel();
|
||||
onDisconnectService?.cancel();
|
||||
onPingService?.cancel();
|
||||
playService?.cancel();
|
||||
|
||||
// WebOS 22 and earlier does not work well using the history API,
|
||||
|
@ -73,7 +107,7 @@ try {
|
|||
// };
|
||||
|
||||
document.addEventListener('keydown', (event: any) => {
|
||||
// console.log("KeyDown", event);
|
||||
// logger.info("KeyDown", event);
|
||||
|
||||
switch (event.keyCode) {
|
||||
// WebOS 22 and earlier does not work well using the history API,
|
||||
|
@ -87,11 +121,11 @@ try {
|
|||
});
|
||||
}
|
||||
catch (err) {
|
||||
console.error(`Main: preload ${JSON.stringify(err)}`);
|
||||
toast(`Main: preload ${JSON.stringify(err)}`, ToastIcon.ERROR);
|
||||
logger.error(`Main: preload ${JSON.stringify(err)}`);
|
||||
toast(`Error starting the application (preload): ${JSON.stringify(err)}`, ToastIcon.ERROR);
|
||||
}
|
||||
|
||||
function registerService(method: string, callback: (message: any) => void, subscribe: boolean = true): any {
|
||||
function requestService(method: string, successCallback: (message: any) => void, failureCallback?: (message: any) => void, subscribe: boolean = true): any {
|
||||
const serviceId = 'com.futo.fcast.receiver.service';
|
||||
|
||||
return window.webOS.service.request(`luna://${serviceId}/`, {
|
||||
|
@ -99,15 +133,18 @@ function registerService(method: string, callback: (message: any) => void, subsc
|
|||
parameters: {},
|
||||
onSuccess: (message: any) => {
|
||||
if (message.value?.subscribed === true) {
|
||||
console.log(`Main: Registered ${method} handler with service`);
|
||||
logger.info(`Main: Registered ${method} handler with service`);
|
||||
}
|
||||
else {
|
||||
callback(message);
|
||||
successCallback(message);
|
||||
}
|
||||
},
|
||||
onFailure: (message: any) => {
|
||||
console.error(`Main: ${method} ${JSON.stringify(message)}`);
|
||||
toast(`Main: ${method} ${JSON.stringify(message)}`, ToastIcon.ERROR);
|
||||
logger.error(`Main: ${method} ${JSON.stringify(message)}`);
|
||||
|
||||
if (failureCallback) {
|
||||
failureCallback(message);
|
||||
}
|
||||
},
|
||||
// onComplete: (message) => {},
|
||||
subscribe: subscribe,
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>FCast Receiver</title>
|
||||
<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/inter.css" />
|
||||
<link rel="stylesheet" href="./common.css" />
|
||||
<link rel="stylesheet" href="./style.css" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="loading-screen">
|
||||
<div id="loading-text" class="non-selectable">Loading FCast</div>
|
||||
|
@ -34,16 +33,34 @@
|
|||
</div>
|
||||
</div>
|
||||
<div id="detail-view" class="card">
|
||||
<div class="non-selectable card-title">Manual connection information</div>
|
||||
<div class="non-selectable card-title">Connection Information</div>
|
||||
<div class="card-title-separator"></div>
|
||||
<div>
|
||||
<div id="ips">IPs</div><br />
|
||||
<div id="ip-ports">Port<br>46899 (TCP), 46898 (WS)</div>
|
||||
|
||||
<div id="connection-information-loading">
|
||||
<div id="connection-information-loading-text" class="lds-ring">Fetching Network Info...</div>
|
||||
<div id="connection-information-loading-spinner" class="lds-ring"><div></div><div></div><div></div><div></div></div>
|
||||
</div>
|
||||
<div id="connection-error">
|
||||
<div id="connection-error-icon"></div>
|
||||
<div id="connection-error-text">Device not connected to a network</div>
|
||||
</div>
|
||||
<div id="connection-information">
|
||||
<div id="scan-to-connect" class="non-selectable">Scan with a FCast sender app</div>
|
||||
<canvas id="qr-code"></canvas>
|
||||
<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 />
|
||||
<div id="connection-details" class="non-selectable card-title">Connection Details</div>
|
||||
<div id="connection-details-separator" class="card-title-separator"></div>
|
||||
<div>
|
||||
<div id="ips">
|
||||
<div id="ips-iface-icon"></div>
|
||||
<div id="ips-iface-text"></div>
|
||||
<div id="ips-iface-name"></div>
|
||||
</div><br />
|
||||
<div id="ip-ports">Port<br>46899 (TCP), 46898 (WS)</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="automatic-discovery" class="non-selectable">Automatic discovery is available via mDNS</div>
|
||||
<canvas id="qr-code"></canvas>
|
||||
<div id="scan-to-connect" class="non-selectable">Scan with a FCast sender app.</div>
|
||||
<div id="app-download" class="non-selectable app-download">Need a sender app?<br>Download Grayjay at <u class="app-download">https://grayjay.app</u></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -57,5 +74,4 @@
|
|||
<script src="./preload.js"></script>
|
||||
<script src="./renderer.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
|
|
@ -4,15 +4,11 @@
|
|||
|
||||
#overlay {
|
||||
font-family: InterRegular;
|
||||
font-size: 28px;
|
||||
|
||||
/* gap not supported in WebOS 6.0 */
|
||||
gap: unset;
|
||||
}
|
||||
|
||||
#main-view {
|
||||
padding: 25px;
|
||||
|
||||
/* gap not supported in WebOS 6.0 */
|
||||
gap: unset;
|
||||
margin-right: 15vw;
|
||||
|
@ -20,16 +16,6 @@
|
|||
|
||||
#title-text {
|
||||
font-family: OutfitExtraBold;
|
||||
font-size: 140px;
|
||||
}
|
||||
|
||||
#title-icon {
|
||||
width: 124px;
|
||||
height: 124px;
|
||||
}
|
||||
|
||||
#manual-connection-info {
|
||||
font-family: InterBold;
|
||||
}
|
||||
|
||||
#scan-to-connect {
|
||||
|
@ -43,16 +29,6 @@
|
|||
|
||||
#window-can-be-closed {
|
||||
font-family: InterRegular;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.lds-ring {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
}
|
||||
.lds-ring div {
|
||||
width: 104px;
|
||||
height: 104px;
|
||||
}
|
||||
|
||||
#loading-screen {
|
||||
|
@ -74,23 +50,10 @@
|
|||
padding-right: 20px;
|
||||
}
|
||||
|
||||
#connection-check {
|
||||
width: 104px;
|
||||
height: 104px;
|
||||
}
|
||||
|
||||
#toast-notification {
|
||||
gap: unset;
|
||||
top: -250px;
|
||||
}
|
||||
|
||||
#toast-icon {
|
||||
width: 88px;
|
||||
height: 88px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
#toast-text {
|
||||
font-family: InterRegular;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
|
|
@ -5,9 +5,12 @@ import { PlaybackErrorMessage, PlaybackUpdateMessage, VolumeUpdateMessage } from
|
|||
import { toast, ToastIcon } from 'common/components/Toast';
|
||||
require('lib/webOSTVjs-1.2.10/webOSTV.js');
|
||||
require('lib/webOSTVjs-1.2.10/webOSTV-dev.js');
|
||||
const logger = window.targetAPI.logger;
|
||||
|
||||
try {
|
||||
const serviceId = 'com.futo.fcast.receiver.service';
|
||||
let getSessions = null;
|
||||
|
||||
window.webOSAPI = {
|
||||
pendingPlay: JSON.parse(sessionStorage.getItem('playData'))
|
||||
};
|
||||
|
@ -18,7 +21,7 @@ try {
|
|||
parameters: { error },
|
||||
onSuccess: () => {},
|
||||
onFailure: (message: any) => {
|
||||
console.error(`Player: send_playback_error ${JSON.stringify(message)}`);
|
||||
logger.error(`Player: send_playback_error ${JSON.stringify(message)}`);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -27,11 +30,11 @@ try {
|
|||
method: 'send_playback_update',
|
||||
parameters: { update },
|
||||
// onSuccess: (message: any) => {
|
||||
// console.log(`Player: send_playback_update ${JSON.stringify(message)}`);
|
||||
// logger.info(`Player: send_playback_update ${JSON.stringify(message)}`);
|
||||
// },
|
||||
onSuccess: () => {},
|
||||
onFailure: (message: any) => {
|
||||
console.error(`Player: send_playback_update ${JSON.stringify(message)}`);
|
||||
logger.error(`Player: send_playback_update ${JSON.stringify(message)}`);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -41,7 +44,7 @@ try {
|
|||
parameters: { update },
|
||||
onSuccess: () => {},
|
||||
onFailure: (message: any) => {
|
||||
console.error(`Player: send_volume_update ${JSON.stringify(message)}`);
|
||||
logger.error(`Player: send_volume_update ${JSON.stringify(message)}`);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -50,9 +53,9 @@ try {
|
|||
method:"play",
|
||||
parameters: {},
|
||||
onSuccess: (message: any) => {
|
||||
// console.log(JSON.stringify(message));
|
||||
// logger.info(JSON.stringify(message));
|
||||
if (message.value.subscribed === true) {
|
||||
console.log('Player: Registered play handler with service');
|
||||
logger.info('Player: Registered play handler with service');
|
||||
}
|
||||
|
||||
if (message.value.playData !== null) {
|
||||
|
@ -65,15 +68,15 @@ try {
|
|||
}
|
||||
},
|
||||
onFailure: (message: any) => {
|
||||
console.error(`Player: play ${JSON.stringify(message)}`);
|
||||
logger.error(`Player: play ${JSON.stringify(message)}`);
|
||||
},
|
||||
subscribe: true,
|
||||
resubscribe: true
|
||||
});
|
||||
|
||||
const pauseService = registerService('pause', () => { preloadData.onPauseCb(); });
|
||||
const resumeService = registerService('resume', () => { preloadData.onResumeCb(); });
|
||||
const stopService = registerService('stop', () => {
|
||||
const pauseService = requestService('pause', () => { preloadData.onPauseCb(); });
|
||||
const resumeService = requestService('resume', () => { preloadData.onResumeCb(); });
|
||||
const stopService = requestService('stop', () => {
|
||||
playService.cancel();
|
||||
pauseService.cancel();
|
||||
resumeService.cancel();
|
||||
|
@ -81,6 +84,9 @@ try {
|
|||
seekService.cancel();
|
||||
setVolumeService.cancel();
|
||||
setSpeedService.cancel();
|
||||
getSessions?.cancel();
|
||||
onConnectService.cancel();
|
||||
onDisconnectService.cancel();
|
||||
|
||||
// WebOS 22 and earlier does not work well using the history API,
|
||||
// so manually handling page navigation...
|
||||
|
@ -88,14 +94,23 @@ try {
|
|||
window.open('../main_window/index.html', '_self');
|
||||
});
|
||||
|
||||
const seekService = registerService('seek', (message: any) => { preloadData.onSeekCb(null, message.value); });
|
||||
const setVolumeService = registerService('setvolume', (message: any) => { preloadData.onSetVolumeCb(null, message.value); });
|
||||
const setSpeedService = registerService('setspeed', (message: any) => { preloadData.onSetSpeedCb(null, message.value); });
|
||||
const seekService = requestService('seek', (message: any) => { preloadData.onSeekCb(null, message.value); });
|
||||
const setVolumeService = requestService('setvolume', (message: any) => { preloadData.onSetVolumeCb(null, message.value); });
|
||||
const setSpeedService = requestService('setspeed', (message: any) => { preloadData.onSetSpeedCb(null, message.value); });
|
||||
|
||||
window.targetAPI.getSessions(() => {
|
||||
return new Promise((resolve, reject) => {
|
||||
getSessions = requestService('get_sessions', (message: any) => resolve(message.value), (message: any) => reject(message), false);
|
||||
});
|
||||
});
|
||||
|
||||
const onConnectService = requestService('connect', (message: any) => { preloadData.onConnectCb(null, message.value); });
|
||||
const onDisconnectService = requestService('disconnect', (message: any) => { preloadData.onDisconnectCb(null, message.value); });
|
||||
|
||||
const launchHandler = () => {
|
||||
// args don't seem to be passed in via event despite what documentation says...
|
||||
const params = window.webOSDev.launchParams();
|
||||
console.log(`Player: (Re)launching FCast Receiver with args: ${JSON.stringify(params)}`);
|
||||
logger.info(`Player: (Re)launching FCast Receiver with args: ${JSON.stringify(params)}`);
|
||||
|
||||
const lastTimestamp = Number(localStorage.getItem('lastTimestamp'));
|
||||
if (params.playData !== undefined && params.timestamp != lastTimestamp) {
|
||||
|
@ -108,6 +123,9 @@ try {
|
|||
seekService?.cancel();
|
||||
setVolumeService?.cancel();
|
||||
setSpeedService?.cancel();
|
||||
getSessions?.cancel();
|
||||
onConnectService?.cancel();
|
||||
onDisconnectService?.cancel();
|
||||
|
||||
// WebOS 22 and earlier does not work well using the history API,
|
||||
// so manually handling page navigation...
|
||||
|
@ -121,27 +139,30 @@ try {
|
|||
|
||||
}
|
||||
catch (err) {
|
||||
console.error(`Player: preload ${JSON.stringify(err)}`);
|
||||
toast(`Player: preload ${JSON.stringify(err)}`, ToastIcon.ERROR);
|
||||
logger.error(`Player: preload ${JSON.stringify(err)}`);
|
||||
toast(`Error starting the video player (preload): ${JSON.stringify(err)}`, ToastIcon.ERROR);
|
||||
}
|
||||
|
||||
function registerService(method: string, callback: (message: any) => void, subscribe: boolean = true): any {
|
||||
function requestService(method: string, successCallback: (message: any) => void, failureCallback?: (message: any) => void, subscribe: boolean = true): any {
|
||||
const serviceId = 'com.futo.fcast.receiver.service';
|
||||
|
||||
return window.webOS.service.request(`luna://${serviceId}/`, {
|
||||
method: method,
|
||||
parameters: {},
|
||||
onSuccess: (message: any) => {
|
||||
if (message.value.subscribed === true) {
|
||||
console.log(`Player: Registered ${method} handler with service`);
|
||||
if (message.value?.subscribed === true) {
|
||||
logger.info(`Player: Registered ${method} handler with service`);
|
||||
}
|
||||
else {
|
||||
callback(message);
|
||||
successCallback(message);
|
||||
}
|
||||
},
|
||||
onFailure: (message: any) => {
|
||||
console.error(`Player: ${method} ${JSON.stringify(message)}`);
|
||||
// toast(`Player: ${method} ${JSON.stringify(message)}`, ToastIcon.ERROR);
|
||||
logger.error(`Main: ${method} ${JSON.stringify(message)}`);
|
||||
|
||||
if (failureCallback) {
|
||||
failureCallback(message);
|
||||
}
|
||||
},
|
||||
// onComplete: (message) => {},
|
||||
subscribe: subscribe,
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
<head>
|
||||
<title>FCast Receiver</title>
|
||||
<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="./common.css" />
|
||||
<link rel="stylesheet" href="./style.css" />
|
||||
|
@ -93,6 +94,11 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div id="toast-notification">
|
||||
<div id="toast-icon"></div>
|
||||
<div id="toast-text"></div>
|
||||
</div>
|
||||
|
||||
<script src="./renderer.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue