diff --git a/src/main/index.ts b/src/main/index.ts index 8aff7e8b..cc59a9c7 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -76,10 +76,6 @@ app.on('activate', () => { // code. You can also put them in separate files and import them here. function setupDefaultInstallPath(app: App) { - if (!settings.has('mainSettings.apiRelease')) { - settings.set('mainSettings.apiRelease', 'production'); - } - if (!settings.has('mainSettings.msfsPackagePath')) { let userPath = null; diff --git a/src/renderer/components/AircraftSection/index.tsx b/src/renderer/components/AircraftSection/index.tsx index 6213bc38..c9e40090 100644 --- a/src/renderer/components/AircraftSection/index.tsx +++ b/src/renderer/components/AircraftSection/index.tsx @@ -14,7 +14,14 @@ import { DownloadProgress, UpdateButton, InstalledButton, - CancelButton, DetailsContainer, VersionHistoryContainer, LeftContainer, TopContainer, MSFSIsOpenButton + CancelButton, + DetailsContainer, + VersionHistoryContainer, + LeftContainer, + TopContainer, + MSFSIsOpenButton, + UpdateReason, + UpdateContainer } from './styles'; import Store from 'electron-store'; import * as fs from "fs"; @@ -40,10 +47,16 @@ type Props = { let controller: AbortController; let signal: AbortSignal; +const UpdateReasonMessages = { + NEW_RELEASE_AVAILABLE: "New release available", + VERSION_CHANGED: "New version selected", +}; + const index: React.FC = (props: Props) => { const [selectedVariant] = useState(props.mod.variants[0]); const [selectedTrack, setSelectedTrack] = useState(handleFindInstalledTrack()); const [needsUpdate, setNeedsUpdate] = useState(false); + const [needsUpdateReason, setNeedsUpdateReason] = useState(); const [isInstalled, setIsInstalled] = useState(false); const [isInstalledAsGitRepo, setIsInstalledAsGitRepo] = useState(false); @@ -67,10 +80,16 @@ const index: React.FC = (props: Props) => { const isDownloading = download?.progress >= 0; useEffect(() => { - checkForUpdates(selectedTrack); - checkIfMSFS(); + checkForUpdates(); + const checkMsfsInterval = setInterval(checkIfMSFS, 5_000); + + return () => clearInterval(checkMsfsInterval); }, []); + useEffect(() => { + checkForUpdates(); + }, [selectedTrack]); + function findBuildTime(installDir: string) { const buildInfo = `${installDir}\\build_info.json`; if (fs.existsSync(buildInfo)) { @@ -82,11 +101,12 @@ const index: React.FC = (props: Props) => { } } - async function checkForUpdates(track: ModTrack) { + async function checkForUpdates() { + const localLastTrack = settings.get('cache.' + props.mod.key + '.lastInstalledTrack'); const localLastUpdate = settings.get('cache.' + props.mod.key + '.lastUpdated'); const localLastBuildDate = settings.get('cache.' + props.mod.key + '.lastBuildTime'); - const res = await fetch(track.url, { method: 'HEAD' }); + const res = await fetch(selectedTrack.url, { method: 'HEAD' }); const webLastUpdate = res.headers.get('Last-Modified').toString(); @@ -94,39 +114,67 @@ const index: React.FC = (props: Props) => { if (fs.existsSync(installDir)) { setIsInstalled(true); + console.log('Installed'); // Check for git install + console.log('Checking for git install'); try { const symlinkPath = fs.readlinkSync(installDir); if (symlinkPath) { if (fs.existsSync(symlinkPath + '\\..\\.git\\')) { + console.log('Is git repo'); setIsInstalledAsGitRepo(true); return; } } } catch { + console.log('Is not git repo'); setIsInstalledAsGitRepo(false); } - if (typeof localLastUpdate === "string") { - if (typeof localLastBuildDate === "string") { - if ((localLastUpdate === webLastUpdate) && (localLastBuildDate === findBuildTime(installDir))) { - console.log("Is Updated"); - setNeedsUpdate(false); + console.log(`Checking for track '${selectedTrack.name}' being installed...`); + if (typeof localLastTrack === "string") { + if (localLastTrack !== selectedTrack.name) { + // The installed track is not the same - require update + + setNeedsUpdate(true); + setNeedsUpdateReason(UpdateReasonMessages.VERSION_CHANGED); + } else { + // We are still on the same track - check the installed build + + if (typeof localLastUpdate === "string") { + // There was an update before - check if that build is the latest + + if (typeof localLastBuildDate === "string") { + if ((localLastUpdate === webLastUpdate) && (localLastBuildDate === findBuildTime(installDir))) { + setNeedsUpdate(false); + console.log("Is Updated"); + } else { + setNeedsUpdate(true); + setNeedsUpdateReason(UpdateReasonMessages.NEW_RELEASE_AVAILABLE); + console.log("Is not Updated"); + } + } else { + setNeedsUpdate(true); + setNeedsUpdateReason(UpdateReasonMessages.NEW_RELEASE_AVAILABLE); + console.log("Needs update to register build file to cache"); + } } else { - setNeedsUpdate(true); - console.log("Is not Updated"); + setIsInstalled(false); + console.log("Failed"); } - } else { - setNeedsUpdate(true); - console.log("Needs update to register build file to cache"); } } else { - setIsInstalled(false); - console.log("Failed"); + // Don't know if the same track is installed - assume the worst + setNeedsUpdate(true); + setNeedsUpdateReason(UpdateReasonMessages.VERSION_CHANGED); + console.log('Don\'t know which track'); } + } else { setIsInstalled(false); + setNeedsUpdate(false); + console.log('Not installed'); } } @@ -234,7 +282,6 @@ const index: React.FC = (props: Props) => { async function findAndSetTrack(key: string) { const newTrack = selectedVariant.tracks.find(x => x.key === key); - await checkForUpdates(newTrack); setSelectedTrack(newTrack); } @@ -280,16 +327,6 @@ const index: React.FC = (props: Props) => { } } - // function handleLastInstalledTrackName() { - // const name = settings.get('cache.' + props.mod.key + '.lastInstalledTrack'); - // - // if (typeof name === "string") { - // return name; - // } else { - // return "Development"; - // } - // } - return ( @@ -302,7 +339,12 @@ const index: React.FC = (props: Props) => { {!msfsIsOpen && !isInstalledAsGitRepo && !isInstalled && !isDownloading && } {!msfsIsOpen && isInstalledAsGitRepo && isInstalled && } {!msfsIsOpen && !isInstalledAsGitRepo && isInstalled && !needsUpdate && !isDownloading && } - {!msfsIsOpen && !isInstalledAsGitRepo && needsUpdate && !isDownloading && } + {!msfsIsOpen && !isInstalledAsGitRepo && needsUpdate && !isDownloading && <> + + {needsUpdateReason} + + + } {isDownloading && {(Math.floor(download?.progress) >= 99) ? "Decompressing" : `${Math.floor(download?.progress)}% - Cancel`} } diff --git a/src/renderer/components/AircraftSection/styles.tsx b/src/renderer/components/AircraftSection/styles.tsx index 643c0fe4..da498f05 100644 --- a/src/renderer/components/AircraftSection/styles.tsx +++ b/src/renderer/components/AircraftSection/styles.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Select, Progress } from 'antd'; import styled from 'styled-components'; import { DownloadOutlined } from '@ant-design/icons'; -import { dropShadow } from "renderer/style/theme"; +import { colors, dropShadow, fontSizes } from "renderer/style/theme"; export const Container = styled.div<{ wait: number }>` visibility: ${props => props.wait ? 'hidden' : 'visible'}; @@ -181,12 +181,26 @@ const InstallButtonTemplate = styled(props => Install)``; +export const UpdateContainer = styled.div` + display: flex; + flex-direction: row; + justify-content: flex-end; + align-items: center; + + column-gap: 1.25em; +`; + +export const UpdateReason = styled(props => {props.children})` + font-size: ${fontSizes.huge} !important; + color: ${colors.titleContrast}; +`; + export const UpdateButton = styled( props => ('latest_version_dev', RELEASE_CACHE_LIMIT).fetchOrCompute(async () => { - return (await GitVersions.getNewestCommit('flybywiresim', 'a32nx', 'master')).sha.substring(0, 7); - }); + return DataCache.from('latest_version_dev', RELEASE_CACHE_LIMIT) + .fetchOrCompute(async () => (await GitVersions.getNewestCommit('flybywiresim', 'a32nx', 'master')).sha.substring(0, 7)); } }, { @@ -149,9 +148,8 @@ function App() { url: 'https://flybywiresim-packages.nyc3.cdn.digitaloceanspaces.com/stable/A32NX-stable.zip', isExperimental: false, get latestVersionName() { - return new DataCache('latest_version_stable', RELEASE_CACHE_LIMIT).fetchOrCompute(async () => { - return (await GitVersions.getReleases('flybywiresim', 'a32nx'))[0].name; - }); + return DataCache.from('latest_version_stable', RELEASE_CACHE_LIMIT) + .fetchOrCompute(async () => (await GitVersions.getReleases('flybywiresim', 'a32nx'))[0].name); }, }, { @@ -160,9 +158,8 @@ function App() { url: 'https://flybywiresim-packages.nyc3.cdn.digitaloceanspaces.com/vmaster-cfbw/A32NX-master-cfbw.zip', isExperimental: true, get latestVersionName() { - return new DataCache('latest_version_fbw', RELEASE_CACHE_LIMIT).fetchOrCompute(async () => { - return (await GitVersions.getNewestCommit('flybywiresim', 'a32nx', 'fbw')).sha.substring(0, 7); - }); + return DataCache.from('latest_version_fbw', RELEASE_CACHE_LIMIT) + .fetchOrCompute(async () => (await GitVersions.getNewestCommit('flybywiresim', 'a32nx', 'fbw')).sha.substring(0, 7)); }, } ], diff --git a/src/renderer/components/GeneralSettings/index.tsx b/src/renderer/components/GeneralSettings/index.tsx index 0f30a407..69f78a01 100644 --- a/src/renderer/components/GeneralSettings/index.tsx +++ b/src/renderer/components/GeneralSettings/index.tsx @@ -1,8 +1,7 @@ -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import Store from 'electron-store'; import { setupInstallPath } from 'renderer/actions/install-path.utils'; import { Container, PageTitle, SettingItemContent, SettingItemName, SettingsItem, SettingsItems, SettingButton } from './styles'; -import { NXApi } from "@flybywiresim/api-client"; const settings = new Store; @@ -23,53 +22,12 @@ function InstallPathSettingItem(): JSX.Element { ); } -function ApiReleaseSettingItem() { - const [isOnProduction, setIsOnProduction] = useState(true); - - useEffect(() => setIsOnProduction(settings.get('mainSettings.apiRelease') === 'true'), []); - - function handleClick() { - const isOnProduction = settings.get('mainSettings.apiRelease') === 'production'; - settings.set('mainSettings.apiRelease', isOnProduction ? 'staging' : 'production'); - - NXApi.url = new URL(isOnProduction ? 'https://api.flybywiresim.com' : 'https://api.staging.flybywiresim.com'); - - setIsOnProduction(isOnProduction); - } - - return ( - - API version - {isOnProduction ? 'Production' : 'Staging'} - - ); -} - -function ClearCacheSettingItem() { - function handleClick() { - for (const key in localStorage) { - if (/data_cache_.+/.test(key)) { - localStorage.removeItem(key); - } - } - } - - return ( - - Cache - Clear - - ); -} - function index(): JSX.Element { return ( General Settings - - ); diff --git a/src/renderer/style/theme.ts b/src/renderer/style/theme.ts index 719bbb7f..91987315 100644 --- a/src/renderer/style/theme.ts +++ b/src/renderer/style/theme.ts @@ -1,6 +1,12 @@ import { css } from "styled-components"; +export const fontSizes = { + huge: '20px', +}; + export const colors = { + positive: '#00b853', + title: '#FFFFFFDD', titleContrast: '#FFFFFF', diff --git a/src/renderer/utils/DataCache.ts b/src/renderer/utils/DataCache.ts index 20fc1879..d237eafc 100644 --- a/src/renderer/utils/DataCache.ts +++ b/src/renderer/utils/DataCache.ts @@ -8,6 +8,10 @@ export class DataCache { this.limit = limit; } + static from(key: string, limit: number): DataCache { + return new DataCache(key, limit); + } + async fetchOrCompute(fetcher: () => Promise): Promise { const cachedData = JSON.parse(localStorage.getItem(`data_cache_${this.key}`)); const cachedDataTimestamp = Number(localStorage.getItem(`data_cache_${this.key}_timestamp`));