mirror of
https://gitlab.com/futo-org/fcast.git
synced 2025-06-24 21:25:23 +00:00
New updater flow and UI
This commit is contained in:
parent
a967b7983a
commit
568c972492
6 changed files with 358 additions and 146 deletions
|
@ -53,41 +53,36 @@ export default class Main {
|
||||||
{
|
{
|
||||||
label: 'Check for updates',
|
label: 'Check for updates',
|
||||||
click: async () => {
|
click: async () => {
|
||||||
if (!Updater.isDownloading) {
|
if (Updater.updateDownloaded) {
|
||||||
try {
|
Main.mainWindow.webContents.send("download-complete");
|
||||||
if (await Updater.update()) {
|
return;
|
||||||
const restartPrompt = await dialog.showMessageBox({
|
}
|
||||||
type: 'info',
|
|
||||||
title: 'Update ready',
|
|
||||||
message: 'Update downloaded, restart now to apply the changes.',
|
|
||||||
buttons: ['Restart'],
|
|
||||||
defaultId: 0
|
|
||||||
});
|
|
||||||
|
|
||||||
// Restart the app if the user clicks the 'Restart' button
|
try {
|
||||||
if (restartPrompt.response === 0) {
|
const updateAvailable = await Updater.checkForUpdates();
|
||||||
Updater.restart();
|
|
||||||
}
|
if (updateAvailable) {
|
||||||
} else {
|
Main.mainWindow.webContents.send("update-available");
|
||||||
await dialog.showMessageBox({
|
}
|
||||||
type: 'info',
|
else {
|
||||||
title: 'Already up-to-date',
|
|
||||||
message: 'The application is already on the latest version.',
|
|
||||||
buttons: ['OK'],
|
|
||||||
defaultId: 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
await dialog.showMessageBox({
|
await dialog.showMessageBox({
|
||||||
type: 'error',
|
type: 'info',
|
||||||
title: 'Failed to update',
|
title: 'Already up-to-date',
|
||||||
message: err,
|
message: 'The application is already on the latest version.',
|
||||||
buttons: ['OK'],
|
buttons: ['OK'],
|
||||||
defaultId: 0
|
defaultId: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
Main.logger.error('Failed to update:', err);
|
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
await dialog.showMessageBox({
|
||||||
|
type: 'error',
|
||||||
|
title: 'Failed to check for updates',
|
||||||
|
message: err,
|
||||||
|
buttons: ['OK'],
|
||||||
|
defaultId: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
Main.logger.error('Failed to check for updates:', err);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -182,8 +177,34 @@ export default class Main {
|
||||||
ipcMain.on('send-volume-update', (event: IpcMainEvent, value: VolumeUpdateMessage) => {
|
ipcMain.on('send-volume-update', (event: IpcMainEvent, value: VolumeUpdateMessage) => {
|
||||||
l.send(Opcode.VolumeUpdate, value);
|
l.send(Opcode.VolumeUpdate, value);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.on('send-download-request', async () => {
|
||||||
|
if (!Updater.isDownloading) {
|
||||||
|
try {
|
||||||
|
await Updater.downloadUpdate();
|
||||||
|
Main.mainWindow.webContents.send("download-complete");
|
||||||
|
} catch (err) {
|
||||||
|
await dialog.showMessageBox({
|
||||||
|
type: 'error',
|
||||||
|
title: 'Failed to download update',
|
||||||
|
message: err,
|
||||||
|
buttons: ['OK'],
|
||||||
|
defaultId: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
Main.logger.error('Failed to download update:', err);
|
||||||
|
Main.mainWindow.webContents.send("download-failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('send-restart-request', async () => {
|
||||||
|
Updater.restart();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('updater-progress', async () => { return Updater.updateProgress; });
|
||||||
|
|
||||||
ipcMain.handle('is-full-screen', async () => {
|
ipcMain.handle('is-full-screen', async () => {
|
||||||
const window = Main.playerWindow;
|
const window = Main.playerWindow;
|
||||||
if (!window) {
|
if (!window) {
|
||||||
|
@ -214,6 +235,16 @@ export default class Main {
|
||||||
if (Main.shouldOpenMainWindow) {
|
if (Main.shouldOpenMainWindow) {
|
||||||
Main.openMainWindow();
|
Main.openMainWindow();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Updater.updateError) {
|
||||||
|
dialog.showMessageBox({
|
||||||
|
type: 'error',
|
||||||
|
title: 'Error applying update',
|
||||||
|
message: 'Please try again later or visit https://fcast.org to update.',
|
||||||
|
buttons: ['OK'],
|
||||||
|
defaultId: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -50,9 +50,17 @@ export class Updater {
|
||||||
private static updateDataPath: string = path.join(app.getPath('userData'), 'updater');
|
private static updateDataPath: string = path.join(app.getPath('userData'), 'updater');
|
||||||
private static updateMetadataPath = path.join(Updater.updateDataPath, './update.json');
|
private static updateMetadataPath = path.join(Updater.updateDataPath, './update.json');
|
||||||
private static baseUrl: string = 'https://dl.fcast.org/electron';
|
private static baseUrl: string = 'https://dl.fcast.org/electron';
|
||||||
private static channelVersion: string = null;
|
private static isRestarting: boolean = false;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
private static localPackageJson: any = null;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
private static releasesJson: any = null;
|
||||||
|
|
||||||
public static isDownloading: boolean = false;
|
public static isDownloading: boolean = false;
|
||||||
|
public static updateError: boolean = false;
|
||||||
|
public static updateDownloaded: boolean = false;
|
||||||
|
public static updateProgress: number = 0;
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
private static async fetchJSON(url: string): Promise<any> {
|
private static async fetchJSON(url: string): Promise<any> {
|
||||||
|
@ -80,7 +88,15 @@ export class Updater {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const file = fs.createWriteStream(destination);
|
const file = fs.createWriteStream(destination);
|
||||||
https.get(url, (response) => {
|
https.get(url, (response) => {
|
||||||
|
const downloadSize = Number(response.headers['content-length']);
|
||||||
|
logger.info(`Update size: ${downloadSize} bytes`);
|
||||||
response.pipe(file);
|
response.pipe(file);
|
||||||
|
let downloadedBytes = 0;
|
||||||
|
|
||||||
|
response.on('data', (chunk) => {
|
||||||
|
downloadedBytes += chunk.length;
|
||||||
|
Updater.updateProgress = downloadedBytes / downloadSize;
|
||||||
|
});
|
||||||
file.on('finish', () => {
|
file.on('finish', () => {
|
||||||
file.close();
|
file.close();
|
||||||
resolve();
|
resolve();
|
||||||
|
@ -169,19 +185,23 @@ export class Updater {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static restart() {
|
public static restart() {
|
||||||
const updateInfo: UpdateInfo = JSON.parse(fs.readFileSync(Updater.updateMetadataPath, 'utf8'));
|
if (!Updater.isRestarting) {
|
||||||
const extractionDir = process.platform === 'darwin' ? 'FCast Receiver.app' : `fcast-receiver-${process.platform}-${process.arch}`;
|
Updater.isRestarting = true;
|
||||||
const binaryName = process.platform === 'win32' ? 'fcast-receiver.exe' : 'fcast-receiver';
|
const updateInfo: UpdateInfo = JSON.parse(fs.readFileSync(Updater.updateMetadataPath, 'utf8'));
|
||||||
const updateBinPath = process.platform === 'darwin' ? path.join(updateInfo.tempPath, extractionDir) : path.join(updateInfo.tempPath, extractionDir, binaryName);
|
const extractionDir = process.platform === 'darwin' ? 'FCast Receiver.app' : `fcast-receiver-${process.platform}-${process.arch}`;
|
||||||
|
const binaryName = process.platform === 'win32' ? 'fcast-receiver.exe' : 'fcast-receiver';
|
||||||
|
const updateBinPath = process.platform === 'darwin' ? path.join(updateInfo.tempPath, extractionDir) : path.join(updateInfo.tempPath, extractionDir, binaryName);
|
||||||
|
|
||||||
|
Updater.relaunch(updateBinPath);
|
||||||
|
}
|
||||||
|
|
||||||
Updater.relaunch(updateBinPath);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static isUpdating(): boolean {
|
public static isUpdating(): boolean {
|
||||||
try {
|
try {
|
||||||
const updateInfo: UpdateInfo = JSON.parse(fs.readFileSync(Updater.updateMetadataPath, 'utf8'));
|
const updateInfo: UpdateInfo = JSON.parse(fs.readFileSync(Updater.updateMetadataPath, 'utf8'));
|
||||||
// TODO: In case of error inform user
|
Updater.updateError = true;
|
||||||
return updateInfo.updateState !== 'error';
|
return updateInfo.updateState !== 'error';
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
|
@ -190,12 +210,12 @@ export class Updater {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static getChannelVersion(): string {
|
public static getChannelVersion(): string {
|
||||||
if (Updater.channelVersion === null) {
|
if (Updater.localPackageJson === null) {
|
||||||
const localPackage = JSON.parse(fs.readFileSync(path.join(Updater.appPath, './package.json'), 'utf8'));
|
Updater.localPackageJson = JSON.parse(fs.readFileSync(path.join(Updater.appPath, './package.json'), 'utf8'));
|
||||||
Updater.channelVersion = localPackage.channelVersion ? localPackage.channelVersion : 0
|
Updater.localPackageJson.channelVersion = Updater.localPackageJson.channelVersion ? Updater.localPackageJson.channelVersion : 0
|
||||||
}
|
}
|
||||||
|
|
||||||
return Updater.channelVersion;
|
return Updater.localPackageJson.channelVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async processUpdate(): Promise<void> {
|
public static async processUpdate(): Promise<void> {
|
||||||
|
@ -215,9 +235,6 @@ export class Updater {
|
||||||
await Updater.applyUpdate(src, updateInfo.installPath);
|
await Updater.applyUpdate(src, updateInfo.installPath);
|
||||||
updateInfo.updateState = UpdateState.Cleanup;
|
updateInfo.updateState = UpdateState.Cleanup;
|
||||||
fs.writeFileSync(Updater.updateMetadataPath, JSON.stringify(updateInfo));
|
fs.writeFileSync(Updater.updateMetadataPath, JSON.stringify(updateInfo));
|
||||||
|
|
||||||
Updater.relaunch(installBinPath);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
logger.error('Error while applying update...');
|
logger.error('Error while applying update...');
|
||||||
|
@ -226,10 +243,9 @@ export class Updater {
|
||||||
updateInfo.updateState = UpdateState.Error;
|
updateInfo.updateState = UpdateState.Error;
|
||||||
updateInfo.error = JSON.stringify(err);
|
updateInfo.error = JSON.stringify(err);
|
||||||
fs.writeFileSync(Updater.updateMetadataPath, JSON.stringify(updateInfo));
|
fs.writeFileSync(Updater.updateMetadataPath, JSON.stringify(updateInfo));
|
||||||
log4js.shutdown();
|
|
||||||
app.exit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Updater.relaunch(installBinPath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,8 +288,44 @@ export class Updater {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async update(): Promise<boolean> {
|
public static async checkForUpdates(): Promise<boolean> {
|
||||||
logger.info('Updater invoked');
|
logger.info('Checking for updates...');
|
||||||
|
Updater.localPackageJson = JSON.parse(fs.readFileSync(path.join(Updater.appPath, './package.json'), 'utf8'));
|
||||||
|
|
||||||
|
try {
|
||||||
|
Updater.releasesJson = await Updater.fetchJSON(`${Updater.baseUrl}/releases_v${Updater.supportedReleasesJsonVersion}.json`.toString()) as ReleaseInfo;
|
||||||
|
|
||||||
|
let updaterSettings = Store.get('updater');
|
||||||
|
if (updaterSettings === null) {
|
||||||
|
updaterSettings = {
|
||||||
|
'channel': Updater.localPackageJson.channel,
|
||||||
|
}
|
||||||
|
|
||||||
|
Store.set('updater', updaterSettings);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Updater.localPackageJson.channel = updaterSettings.channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
const localChannelVersion: number = Updater.localPackageJson.channelVersion ? Updater.localPackageJson.channelVersion : 0;
|
||||||
|
const currentChannelVersion: number = Updater.releasesJson.channelCurrentVersions[Updater.localPackageJson.channel] ? Updater.releasesJson.channelCurrentVersions[Updater.localPackageJson.channel] : 0;
|
||||||
|
logger.info('Update check', { channel: Updater.localPackageJson.channel, channel_version: localChannelVersion, localVersion: Updater.localPackageJson.version,
|
||||||
|
currentVersion: Updater.releasesJson.currentVersion, currentChannelVersion: currentChannelVersion });
|
||||||
|
|
||||||
|
if (Updater.localPackageJson.version !== Updater.releasesJson.currentVersion || (Updater.localPackageJson.channel !== 'stable' && localChannelVersion < currentChannelVersion)) {
|
||||||
|
logger.info('Update available...');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (err) {
|
||||||
|
logger.error(`Failed to check for updates: ${err}`);
|
||||||
|
throw 'Please try again later or visit https://fcast.org for updates.';
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async downloadUpdate(): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
fs.accessSync(Updater.updateDataPath, fs.constants.F_OK);
|
fs.accessSync(Updater.updateDataPath, fs.constants.F_OK);
|
||||||
}
|
}
|
||||||
|
@ -282,70 +334,50 @@ export class Updater {
|
||||||
fs.mkdirSync(Updater.updateDataPath);
|
fs.mkdirSync(Updater.updateDataPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
const localPackage = JSON.parse(fs.readFileSync(path.join(Updater.appPath, './package.json'), 'utf8'));
|
|
||||||
try {
|
try {
|
||||||
const releases = await Updater.fetchJSON(`${Updater.baseUrl}/releases_v${Updater.supportedReleasesJsonVersion}.json`.toString()) as ReleaseInfo;
|
const channel = Updater.localPackageJson.version !== Updater.releasesJson.currentVersion ? 'stable' : Updater.localPackageJson.channel;
|
||||||
|
const fileInfo = Updater.releasesJson.currentReleases[channel][process.platform][process.arch]
|
||||||
|
const file = fileInfo.url.toString().split('/').pop();
|
||||||
|
|
||||||
let updaterSettings = Store.get('updater');
|
const destination = path.join(Updater.updateDataPath, file);
|
||||||
if (updaterSettings === null) {
|
logger.info(`Downloading '${fileInfo.url}' to '${destination}'.`);
|
||||||
updaterSettings = {
|
Updater.isDownloading = true;
|
||||||
'channel': localPackage.channel,
|
await Updater.downloadFile(fileInfo.url.toString(), destination);
|
||||||
}
|
|
||||||
|
|
||||||
Store.set('updater', updaterSettings);
|
const downloadedFile = await fs.promises.readFile(destination);
|
||||||
|
const hash = crypto.createHash('sha256').end(downloadedFile).digest('hex');
|
||||||
|
if (fileInfo.sha256Digest !== hash) {
|
||||||
|
const message = 'Update failed integrity check. Please try again later or visit https://fcast.org to for updates.';
|
||||||
|
logger.error(`Update failed integrity check. Expected hash: ${fileInfo.sha256Digest}, actual hash: ${hash}`);
|
||||||
|
throw message;
|
||||||
}
|
}
|
||||||
|
|
||||||
const localChannelVersion: number = localPackage.channelVersion ? localPackage.channelVersion : 0
|
// Electron runtime sees .asar file as directory and causes errors during extraction
|
||||||
const currentChannelVersion: number = releases.channelCurrentVersions[localPackage.channel] ? releases.channelCurrentVersions[localPackage.channel] : 0
|
logger.info('Extracting update...');
|
||||||
logger.info('Update check', { channel: localPackage.channel, channel_version: localChannelVersion, localVersion: localPackage.version,
|
process.noAsar = true;
|
||||||
currentVersion: releases.currentVersion, currentChannelVersion: currentChannelVersion });
|
await extract(destination, { dir: path.dirname(destination) });
|
||||||
|
process.noAsar = false;
|
||||||
|
|
||||||
if (localPackage.version !== releases.currentVersion || (localPackage.channel !== 'stable' && localChannelVersion < currentChannelVersion)) {
|
logger.info('Extraction complete.');
|
||||||
const channel = localPackage.version !== releases.currentVersion ? 'stable' : localPackage.channel;
|
const updateInfo: UpdateInfo = {
|
||||||
const fileInfo = releases.currentReleases[channel][process.platform][process.arch]
|
updateState: UpdateState.Copy,
|
||||||
const file = fileInfo.url.toString().split('/').pop();
|
installPath: Updater.installPath,
|
||||||
|
tempPath: path.dirname(destination),
|
||||||
|
currentVersion: Updater.releasesJson.currentVersion,
|
||||||
|
downloadFile: file,
|
||||||
|
};
|
||||||
|
|
||||||
const destination = path.join(Updater.updateDataPath, file);
|
fs.writeFileSync(Updater.updateMetadataPath, JSON.stringify(updateInfo));
|
||||||
logger.info(`Downloading '${fileInfo.url}' to '${destination}'.`);
|
logger.info('Written update metadata.');
|
||||||
Updater.isDownloading = true;
|
Updater.isDownloading = false;
|
||||||
await Updater.downloadFile(fileInfo.url.toString(), destination);
|
Updater.updateDownloaded = true;
|
||||||
|
return true;
|
||||||
const downloadedFile = await fs.promises.readFile(destination);
|
|
||||||
const hash = crypto.createHash('sha256').end(downloadedFile).digest('hex');
|
|
||||||
if (fileInfo.sha256Digest !== hash) {
|
|
||||||
const message = 'Update failed integrity check. Please try checking for updates again or downloading the update manually.';
|
|
||||||
logger.error(`Update failed integrity check. Expected hash: ${fileInfo.sha256Digest}, actual hash: ${hash}`);
|
|
||||||
throw message;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Electron runtime sees .asar file as directory and causes errors during extraction
|
|
||||||
logger.info('Extracting update...');
|
|
||||||
process.noAsar = true;
|
|
||||||
await extract(destination, { dir: path.dirname(destination) });
|
|
||||||
process.noAsar = false;
|
|
||||||
|
|
||||||
logger.info('Extraction complete.');
|
|
||||||
const updateInfo: UpdateInfo = {
|
|
||||||
updateState: UpdateState.Copy,
|
|
||||||
installPath: Updater.installPath,
|
|
||||||
tempPath: path.dirname(destination),
|
|
||||||
currentVersion: releases.currentVersion,
|
|
||||||
downloadFile: file,
|
|
||||||
};
|
|
||||||
|
|
||||||
fs.writeFileSync(Updater.updateMetadataPath, JSON.stringify(updateInfo));
|
|
||||||
logger.info('Written update metadata.');
|
|
||||||
Updater.isDownloading = false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (err) {
|
catch (err) {
|
||||||
Updater.isDownloading = false;
|
Updater.isDownloading = false;
|
||||||
process.noAsar = false;
|
process.noAsar = false;
|
||||||
logger.error(`Failed to check for updates: ${err}`);
|
logger.error(`Failed to download update: ${err}`);
|
||||||
throw 'Failed to check for updates. Please try again later or visit https://fcast.org for updates.';
|
throw 'Failed to download update. Please try again later or visit https://fcast.org to download.';
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,12 @@ ipcRenderer.on("device-info", (_event, value) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld('electronAPI', {
|
contextBridge.exposeInMainWorld('electronAPI', {
|
||||||
|
updaterProgress: () => ipcRenderer.invoke('updater-progress'),
|
||||||
onDeviceInfo: (callback) => ipcRenderer.on("device-info", callback),
|
onDeviceInfo: (callback) => ipcRenderer.on("device-info", callback),
|
||||||
|
onUpdateAvailable: (callback) => ipcRenderer.on("update-available", callback),
|
||||||
|
sendDownloadRequest: () => ipcRenderer.send('send-download-request'),
|
||||||
|
onDownloadComplete: (callback) => ipcRenderer.on("download-complete", callback),
|
||||||
|
onDownloadFailed: (callback) => ipcRenderer.on("download-failed", callback),
|
||||||
|
sendRestartRequest: () => ipcRenderer.send('send-restart-request'),
|
||||||
getDeviceInfo: () => deviceInfo,
|
getDeviceInfo: () => deviceInfo,
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,15 @@
|
||||||
import QRCode from 'qrcode';
|
import QRCode from 'qrcode';
|
||||||
|
|
||||||
|
const updateView = document.getElementById("update-view");
|
||||||
|
const updateViewTitle = document.getElementById("update-view-title");
|
||||||
|
const updateText = document.getElementById("update-text");
|
||||||
|
const updateButton = document.getElementById("update-button");
|
||||||
|
const restartButton = document.getElementById("restart-button");
|
||||||
|
const updateLaterButton = document.getElementById("update-later-button");
|
||||||
|
const progressBar = document.getElementById("progress-bar");
|
||||||
|
const progressBarProgress = document.getElementById("progress-bar-progress");
|
||||||
|
|
||||||
|
let updaterProgressUIUpdateTimer = null;
|
||||||
window.electronAPI.onDeviceInfo(renderIPsAndQRCode);
|
window.electronAPI.onDeviceInfo(renderIPsAndQRCode);
|
||||||
|
|
||||||
if(window.electronAPI.getDeviceInfo()) {
|
if(window.electronAPI.getDeviceInfo()) {
|
||||||
|
@ -45,3 +55,57 @@ function renderIPsAndQRCode() {
|
||||||
console.log(`Error rendering QR Code: ${e}`)
|
console.log(`Error rendering QR Code: ${e}`)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
window.electronAPI.onUpdateAvailable(() => {
|
||||||
|
console.log(`Received UpdateAvailable event`);
|
||||||
|
updateViewTitle.textContent = 'FCast update available';
|
||||||
|
|
||||||
|
updateText.textContent = 'Do you wish to update now?';
|
||||||
|
updateButton.setAttribute("style", "display: block");
|
||||||
|
updateLaterButton.setAttribute("style", "display: block");
|
||||||
|
restartButton.setAttribute("style", "display: none");
|
||||||
|
progressBar.setAttribute("style", "display: none");
|
||||||
|
updateView.setAttribute("style", "display: flex");
|
||||||
|
});
|
||||||
|
|
||||||
|
window.electronAPI.onDownloadComplete(() => {
|
||||||
|
console.log(`Received DownloadComplete event`);
|
||||||
|
window.clearTimeout(updaterProgressUIUpdateTimer);
|
||||||
|
updateViewTitle.textContent = 'FCast update ready';
|
||||||
|
|
||||||
|
updateText.textContent = 'Restart now to apply the changes?';
|
||||||
|
updateButton.setAttribute("style", "display: none");
|
||||||
|
progressBar.setAttribute("style", "display: none");
|
||||||
|
restartButton.setAttribute("style", "display: block");
|
||||||
|
updateLaterButton.setAttribute("style", "display: block");
|
||||||
|
updateView.setAttribute("style", "display: flex");
|
||||||
|
});
|
||||||
|
|
||||||
|
window.electronAPI.onDownloadFailed(() => {
|
||||||
|
console.log(`Received DownloadFailed event`);
|
||||||
|
window.clearTimeout(updaterProgressUIUpdateTimer);
|
||||||
|
updateView.setAttribute("style", "display: none");
|
||||||
|
});
|
||||||
|
|
||||||
|
updateLaterButton.onclick = () => { updateView.setAttribute("style", "display: none"); };
|
||||||
|
updateButton.onclick = () => {
|
||||||
|
updaterProgressUIUpdateTimer = window.setInterval( async () => {
|
||||||
|
const updateProgress = await window.electronAPI.updaterProgress();
|
||||||
|
|
||||||
|
if (updateProgress >= 1.0) {
|
||||||
|
updateText.textContent = "Preparing update...";
|
||||||
|
progressBarProgress.setAttribute("style", `width: 100%`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
progressBarProgress.setAttribute("style", `width: ${Math.max(12, updateProgress * 100)}%`);
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
updateText.textContent = 'Downloading...';
|
||||||
|
updateButton.setAttribute("style", "display: none");
|
||||||
|
updateLaterButton.setAttribute("style", "display: none");
|
||||||
|
progressBarProgress.setAttribute("style", "width: 12%");
|
||||||
|
progressBar.setAttribute("style", "display: block");
|
||||||
|
window.electronAPI.sendDownloadRequest();
|
||||||
|
};
|
||||||
|
restartButton.onclick = () => { window.electronAPI.sendRestartRequest(); };
|
||||||
|
|
|
@ -25,17 +25,25 @@
|
||||||
<div id="spinner" class="lds-ring"><div></div><div></div><div></div><div></div></div>
|
<div id="spinner" class="lds-ring"><div></div><div></div><div></div><div></div></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- <div id="update-dialog">There is an update available. Do you wish to update?</div>
|
<div id="update-view" class="card">
|
||||||
<div id="update-button">Update</div>
|
<div id="update-view-title" class="non-selectable card-title">FCast update available</div>
|
||||||
<div id="update-button">Later</div>
|
<div class="card-title-separator"></div>
|
||||||
<div id="progress-container">
|
|
||||||
<div id="update-spinner" class="lds-ring"><div></div><div></div><div></div><div></div></div>
|
<div id="update-text">Do you wish to update now?</div>
|
||||||
<div id="progress-text"></div>
|
<div id="update-button-container">
|
||||||
</div> -->
|
<div id="update-button" class="button button-primary">Update</div>
|
||||||
|
<div id="restart-button" class="button button-primary">Restart</div>
|
||||||
|
<div id="update-later-button" class="button button-secondary">Later</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="progress-bar">
|
||||||
|
<div id="progress-bar-progress"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="detail-view">
|
<div id="detail-view" class="card">
|
||||||
<div id="manual-connection-info" class="non-selectable">Manual connection information</div>
|
<div class="non-selectable card-title">Manual connection information</div>
|
||||||
<div id="manual-connection-info-separator"></div>
|
<div class="card-title-separator"></div>
|
||||||
<div>
|
<div>
|
||||||
<div id="ips">IPs</div><br />
|
<div id="ips">IPs</div><br />
|
||||||
<div>Port<br>46899 (TCP), 46898 (WS)</div>
|
<div>Port<br>46899 (TCP), 46898 (WS)</div>
|
||||||
|
|
|
@ -19,6 +19,70 @@ body, html {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
background-color: rgba(20, 20, 20, 0.5);
|
||||||
|
padding: 25px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid #2E2E2E;
|
||||||
|
scrollbar-width: thin;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title {
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 24px;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title-separator {
|
||||||
|
height: 1px;
|
||||||
|
background: #2E2E2E;
|
||||||
|
margin-top: 3px;
|
||||||
|
margin-bottom: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
display: inline-block;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 16px;
|
||||||
|
gap: 6px;
|
||||||
|
flex: 1 0 0;
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
margin: 20px 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-primary {
|
||||||
|
background: #008BD7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-primary:hover {
|
||||||
|
background: #0D9DDF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-primary:active {
|
||||||
|
background: #0069AA;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-secondary {
|
||||||
|
background: #3E3E3E;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-secondary:hover {
|
||||||
|
background: #555555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-secondary:active {
|
||||||
|
background: #3E3E3E;
|
||||||
|
}
|
||||||
|
|
||||||
#ui-container {
|
#ui-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -80,17 +144,6 @@ body, html {
|
||||||
padding: 25px;
|
padding: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#detail-view {
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
background-color: rgba(20, 20, 20, 0.5);
|
|
||||||
padding: 25px;
|
|
||||||
border-radius: 10px;
|
|
||||||
border: 1px solid #2E2E2E;
|
|
||||||
scrollbar-width: thin;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#manual-connection-info {
|
#manual-connection-info {
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 24px;
|
line-height: 24px;
|
||||||
|
@ -118,41 +171,59 @@ body, html {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
#update-dialog, #waiting-for-connection, #ips, #automatic-discovery {
|
#waiting-for-connection, #ips, #automatic-discovery {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#update-text {
|
||||||
|
margin-top: 20px;
|
||||||
|
width: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#update-view {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#restart-button {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
#spinner {
|
#spinner {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#update-button {
|
#update-button-container {
|
||||||
background: blue;
|
|
||||||
padding: 10px 28px;
|
|
||||||
margin-top: 20px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* .button {
|
|
||||||
display: inline-block;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
min-width: 100px;
|
|
||||||
padding: 18px 16px;
|
|
||||||
gap: 6px;
|
|
||||||
flex: 1 0 0;
|
|
||||||
border-radius: 6px;
|
|
||||||
} */
|
|
||||||
|
|
||||||
|
|
||||||
#progress-container {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-direction: row;
|
||||||
margin-top: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#progress-text {
|
#progress-bar {
|
||||||
margin-left: 8px;
|
display: none;
|
||||||
|
|
||||||
|
width: 320px;
|
||||||
|
height: 40px;
|
||||||
|
margin-top: 20px;
|
||||||
|
border-radius: 50px;
|
||||||
|
border: 1px solid #4E4E4E;
|
||||||
|
background: linear-gradient(rgba(20, 20, 20, 0.5), rgba(80, 80, 80, 0.5));
|
||||||
|
/* background-size: cover; */
|
||||||
|
}
|
||||||
|
|
||||||
|
#progress-bar-progress {
|
||||||
|
width: 12%;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50px;
|
||||||
|
background-image: linear-gradient(to bottom, #008BD7 35%, #0069AA);
|
||||||
|
transition: width .6s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes progress-bar-stripes {
|
||||||
|
from {
|
||||||
|
background-position: 1rem 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
background-position: 0 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#window-can-be-closed {
|
#window-can-be-closed {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue