From 836c06c9295da053c0325e02571d86489289b14e Mon Sep 17 00:00:00 2001 From: Saschl <19493808+Saschl@users.noreply.github.com> Date: Fri, 11 Oct 2024 10:15:00 +0200 Subject: [PATCH 1/3] fix: pin pdf-to-printer version to fix selection (#480) Co-authored-by: Saschl --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1c13f897..3780c109 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,7 +29,7 @@ "js-yaml-loader": "^1.2.2", "marked": "^2.0.0", "nanoid": "^4.0.0", - "pdf-to-printer": "^5.3.0", + "pdf-to-printer": "5.3.0", "raw-loader": "^4.0.2", "react": "^17.0.1", "react-bootstrap-icons": "^1.8.4", @@ -15031,9 +15031,9 @@ } }, "node_modules/pdf-to-printer": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/pdf-to-printer/-/pdf-to-printer-5.6.0.tgz", - "integrity": "sha512-yPZoLWFjbjnHoYNVLU8fyoVA5wPMmT6U4+W/ip+sTDZdt5hwcVuQSVe96rrqRB0kEaKznNcLU7BXSo42R7AHVQ==" + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/pdf-to-printer/-/pdf-to-printer-5.3.0.tgz", + "integrity": "sha512-st1pTOfks+vr+QpGe04x2XAaEaeYfinlOCUL5H/breP7C4Dwx0Z74i9Hcjv9Yljbns5IXoUb2wY7em3EQoZOpw==" }, "node_modules/pend": { "version": "1.2.0", diff --git a/package.json b/package.json index affe1ba9..ad29af8f 100644 --- a/package.json +++ b/package.json @@ -123,7 +123,7 @@ "js-yaml-loader": "^1.2.2", "marked": "^2.0.0", "nanoid": "^4.0.0", - "pdf-to-printer": "^5.3.0", + "pdf-to-printer": "5.3.0", "raw-loader": "^4.0.2", "react": "^17.0.1", "react-bootstrap-icons": "^1.8.4", From 775494d38c72eb31097c0eb4dd7c0d2c222382ae Mon Sep 17 00:00:00 2001 From: Benjamin Dupont <4503241+Benjozork@users.noreply.github.com> Date: Sun, 13 Oct 2024 17:03:25 -0400 Subject: [PATCH 2/3] feat: add possibility of getting latest track versions from fragmenter distribution manifest (#477) * build: bump version * feat: initial commit * refactor: clean up install state logic - moved duplicated logic to Installmanager - removed redundant logic in component render - removed unneeded boilerplate * chore: fix lint * refactor: improvements * refactor: address review comments * refactor: address review comments, prefix console logs * fix: various bug fixes * chore: typo * fix: don't finish update check if install in progress * fix: run checkForUpdates after refreshAddonInstallState to make sure we don't have Unknown status the first time --------- Co-authored-by: alepouna <98479040+alepouna@users.noreply.github.com> --- package-lock.json | 12 +- package.json | 4 +- .../AddonSection/Configure/TrackSelector.tsx | 15 +- .../AddonSection/Configure/index.tsx | 12 +- .../components/AddonSection/index.tsx | 95 +---- src/renderer/components/App/index.tsx | 69 +--- .../components/SettingsSection/Developer.tsx | 12 +- src/renderer/index.tsx | 4 +- src/renderer/redux/features/installStatus.ts | 13 +- src/renderer/rendererSettings.ts | 5 + src/renderer/utils/AddonData.ts | 130 +----- src/renderer/utils/InstallManager.tsx | 374 +++++++++++------- src/renderer/utils/InstallerConfiguration.ts | 19 +- 13 files changed, 325 insertions(+), 439 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3780c109..14be8c18 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "fbw-installer", - "version": "3.4.0-dev.1", + "version": "3.4.2-dev.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "fbw-installer", - "version": "3.4.0-dev.1", + "version": "3.4.2-dev.1", "license": "GPL-3.0", "dependencies": { - "@flybywiresim/fragmenter": "^0.7.4", + "@flybywiresim/fragmenter": "^0.8.0", "@reduxjs/toolkit": "^1.7.1", "@sentry/cli": "^2.31.0", "@sentry/electron": "^4.23.0", @@ -2778,9 +2778,9 @@ "dev": true }, "node_modules/@flybywiresim/fragmenter": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/@flybywiresim/fragmenter/-/fragmenter-0.7.4.tgz", - "integrity": "sha512-CBNKQyjp5w4pVyqT5H0ixI6SOyQdXBYvkLF9dQiZJ+1RxxpNBG0NN8pH+gNL38+BMdL3Z++0KpHOHvZNyy2uPg==", + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@flybywiresim/fragmenter/-/fragmenter-0.8.0.tgz", + "integrity": "sha512-IpFfUuJi/Ix4B8s57k0N9glMVmwVT8FOC6qT2LgL1bmrC9Xfjjfuty8IDjNZH0KjAHvolk/7TzTEhbh7qUPnpQ==", "dependencies": { "axios": "^0.27.2", "split-file": "^2.3.0", diff --git a/package.json b/package.json index ad29af8f..04aca9da 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "fbw-installer", "productName": "FlyByWire Installer", - "version": "3.4.0-dev.1", + "version": "3.4.2-dev.1", "description": "Desktop application to install and customize FlyByWire addons", "configUrls": { "production": "https://cdn.flybywiresim.com/installer/config/production.json", @@ -103,7 +103,7 @@ "webpack-dev-server": "^4.4.0" }, "dependencies": { - "@flybywiresim/fragmenter": "^0.7.4", + "@flybywiresim/fragmenter": "^0.8.0", "@reduxjs/toolkit": "^1.7.1", "@sentry/cli": "^2.31.0", "@sentry/electron": "^4.23.0", diff --git a/src/renderer/components/AddonSection/Configure/TrackSelector.tsx b/src/renderer/components/AddonSection/Configure/TrackSelector.tsx index 94d1fcaa..6f890b67 100644 --- a/src/renderer/components/AddonSection/Configure/TrackSelector.tsx +++ b/src/renderer/components/AddonSection/Configure/TrackSelector.tsx @@ -21,28 +21,25 @@ type TrackProps = { }; export const Track: React.FC = ({ isSelected, isInstalled, handleSelected, addon, track }) => { - const latestVersionName = useSelector( - (state) => state.latestVersionNames[addon.key]?.[track.key]?.name ?? '', + const latestVersionName = useSelector( + (state) => state.latestVersionNames[addon.key]?.[track.key]?.name, ); return (
handleSelected(track)} > -
{track.name} - - {latestVersionName} + + {latestVersionName ?? } + {isInstalled && }
- {isInstalled && }
); }; diff --git a/src/renderer/components/AddonSection/Configure/index.tsx b/src/renderer/components/AddonSection/Configure/index.tsx index ffe6af7f..629d616a 100644 --- a/src/renderer/components/AddonSection/Configure/index.tsx +++ b/src/renderer/components/AddonSection/Configure/index.tsx @@ -10,8 +10,8 @@ import './index.css'; export interface ConfigureProps { routeAspectKey: string; selectedAddon: Addon; - selectedTrack: AddonTrack; - installedTrack: AddonTrack; + selectedTrack: AddonTrack | null; + installedTrack: AddonTrack | null; onTrackSelection: (track: AddonTrack) => void; } @@ -40,8 +40,8 @@ export const Configure: FC = ({ addon={selectedAddon} key={track.key} track={track} - isSelected={selectedTrack === track} - isInstalled={installedTrack === track} + isSelected={selectedTrack?.key === track.key} + isInstalled={installedTrack?.key === track.key} handleSelected={() => onTrackSelection(track)} /> ))} @@ -57,8 +57,8 @@ export const Configure: FC = ({ addon={selectedAddon} key={track.key} track={track} - isSelected={selectedTrack === track} - isInstalled={installedTrack === track} + isSelected={selectedTrack?.key === track.key} + isInstalled={installedTrack?.key === track.key} handleSelected={() => onTrackSelection(track)} /> ))} diff --git a/src/renderer/components/AddonSection/index.tsx b/src/renderer/components/AddonSection/index.tsx index 0a177d26..51217782 100644 --- a/src/renderer/components/AddonSection/index.tsx +++ b/src/renderer/components/AddonSection/index.tsx @@ -2,10 +2,8 @@ import React, { FC, useCallback, useEffect, useState } from 'react'; import { setupInstallPath } from 'renderer/actions/install-path.utils'; import { DownloadItem } from 'renderer/redux/types'; import { useSelector } from 'react-redux'; -import { getCurrentInstall } from '@flybywiresim/fragmenter'; import { InstallerStore, useAppDispatch, useAppSelector } from '../../redux/store'; import { Addon, AddonCategoryDefinition, AddonTrack } from 'renderer/utils/InstallerConfiguration'; -import { Directories } from 'renderer/utils/Directories'; import { NavLink, Redirect, Route, useHistory, useParams } from 'react-router-dom'; import { Gear, InfoCircle, JournalText, Sliders } from 'react-bootstrap-icons'; import settings, { useSetting } from 'renderer/rendererSettings'; @@ -13,8 +11,6 @@ import { ipcRenderer } from 'electron'; import { AddonBar, AddonBarItem } from '../App/AddonBar'; import { NoAvailableAddonsSection } from '../NoAvailableAddonsSection'; import { ReleaseNotes } from './ReleaseNotes'; -import { setInstalledTrack } from 'renderer/redux/features/installedTrack'; -import { InstallState, setInstallStatus } from 'renderer/redux/features/installStatus'; import { setSelectedTrack } from 'renderer/redux/features/selectedTrack'; import { PromptModal, useModals } from 'renderer/components/Modal'; import ReactMarkdown from 'react-markdown'; @@ -128,13 +124,6 @@ export const AddonSection = (): JSX.Element => { const installedTrack = (installedTracks[selectedAddon.key] as AddonTrack) ?? null; - const setCurrentlyInstalledTrack = useCallback( - (newInstalledTrack: AddonTrack) => { - dispatch(setInstalledTrack({ addonKey: selectedAddon.key, installedTrack: newInstalledTrack })); - }, - [dispatch, selectedAddon.key], - ); - const setCurrentlySelectedTrack = useCallback( (newSelectedTrack: AddonTrack) => { dispatch(setSelectedTrack({ addonKey: selectedAddon.key, track: newSelectedTrack })); @@ -144,79 +133,12 @@ export const AddonSection = (): JSX.Element => { const selectedTrack = (selectedTracks[selectedAddon.key] as AddonTrack) ?? null; - const selectAndSetTrack = useCallback( - (key: string) => { - const newTrack = selectedAddon.tracks.find((track) => track.key === key); - setCurrentlySelectedTrack(newTrack); - }, - [selectedAddon.tracks, setCurrentlySelectedTrack], - ); - - const getCurrentInstallStatus = (): InstallState => { - try { - return installStates[selectedAddon.key]; - } catch (e) { - setCurrentInstallStatus({ status: InstallStatus.Unknown }); - return { status: InstallStatus.Unknown }; - } - }; - - const setCurrentInstallStatus = useCallback( - (new_state: InstallState) => { - dispatch(setInstallStatus({ addonKey: selectedAddon.key, installState: new_state })); - }, - [dispatch, selectedAddon.key], - ); - - const findInstalledTrack = useCallback((): AddonTrack => { - if (!Directories.isFragmenterInstall(selectedAddon)) { - console.log('Not installed'); - if (selectedTrack) { - selectAndSetTrack(selectedTrack.key); - return selectedTrack; - } else { - setCurrentlySelectedTrack(selectedAddon.tracks[0]); - return selectedAddon.tracks[0]; - } - } - - try { - const manifest = getCurrentInstall(Directories.inInstallLocation(selectedAddon.targetDirectory)); - console.log('Currently installed', manifest); - - let track = selectedAddon.tracks.find((track) => track.url.includes(manifest.source)); - if (!track) { - track = selectedAddon.tracks.find((track) => track.alternativeUrls?.includes(manifest.source)); - } - - console.log('Currently installed', track); - setCurrentlyInstalledTrack(track); - if (selectedTrack) { - selectAndSetTrack(selectedTrack.key); - return selectedTrack; - } else { - setCurrentlySelectedTrack(track); - return track; - } - } catch (e) { - console.error(e); - console.log('Not installed'); - if (selectedTrack) { - selectAndSetTrack(selectedTrack.key); - return selectedTrack; - } else { - setCurrentlySelectedTrack(selectedAddon.tracks[0]); - return selectedAddon.tracks[0]; - } - } - }, [selectAndSetTrack, selectedAddon, selectedTrack, setCurrentlyInstalledTrack, setCurrentlySelectedTrack]); - const download: DownloadItem = useSelector((state: InstallerStore) => state.downloads.find((download) => download.id === selectedAddon.key), ); const isDownloading = download?.progress.totalPercent >= 0; - const status = getCurrentInstallStatus()?.status; + const status = installStates[selectedAddon.key]?.status; const isInstalling = InstallStatusCategories.installing.includes(status); const isFinishingDependencyInstall = status === InstallStatus.InstallingDependencyEnding; @@ -254,11 +176,10 @@ export const AddonSection = (): JSX.Element => { }, [dispatch, publisherData, selectedAddon]); useEffect(() => { - findInstalledTrack(); if (!isInstalling) { - InstallManager.determineAddonInstallState(selectedAddon).then(setCurrentInstallStatus); + void InstallManager.refreshAddonInstallState(selectedAddon); } - }, [findInstalledTrack, isInstalling, selectedAddon, setCurrentInstallStatus]); + }, [isInstalling, selectedAddon]); useEffect(() => { if (download && isDownloading) { @@ -287,13 +208,19 @@ export const AddonSection = (): JSX.Element => { bodyText={track.warningContent} confirmColor={ButtonType.Caution} onConfirm={() => { - selectAndSetTrack(track.key); + setCurrentlySelectedTrack(track); + + // Update install state + void InstallManager.refreshAddonInstallState(selectedAddon); }} dontShowAgainSettingName="mainSettings.disableExperimentalWarning" />, ); } else { - selectAndSetTrack(track.key); + setCurrentlySelectedTrack(track); + + // Update install state + void InstallManager.refreshAddonInstallState(selectedAddon); } } }; diff --git a/src/renderer/components/App/index.tsx b/src/renderer/components/App/index.tsx index 3bb23564..54fa41a1 100644 --- a/src/renderer/components/App/index.tsx +++ b/src/renderer/components/App/index.tsx @@ -4,17 +4,13 @@ import SimpleBar from 'simplebar-react'; import { Logo } from 'renderer/components/Logo'; import { SettingsSection } from 'renderer/components/SettingsSection'; import { DebugSection } from 'renderer/components/DebugSection'; -import { GitVersions } from '@flybywiresim/api-client'; -import { DataCache } from '../../utils/DataCache'; import { InstallerUpdate } from 'renderer/components/InstallerUpdate'; import { WindowButtons } from 'renderer/components/WindowActionButtons'; -import { Addon, AddonVersion } from 'renderer/utils/InstallerConfiguration'; -import { AddonData } from 'renderer/utils/AddonData'; +import { Addon } from 'renderer/utils/InstallerConfiguration'; import { ErrorModal } from '../ErrorModal'; import { NavBar, NavBarPublisher } from 'renderer/components/App/NavBar'; import { Redirect, Route, Switch, useHistory, useLocation } from 'react-router-dom'; -import { store, useAppSelector } from 'renderer/redux/store'; -import { setAddonAndTrackLatestReleaseInfo } from 'renderer/redux/features/latestVersionNames'; +import { useAppSelector } from 'renderer/redux/store'; import settings from 'renderer/rendererSettings'; import './index.css'; import { ipcRenderer } from 'electron'; @@ -22,55 +18,7 @@ import channels from 'common/channels'; import { ModalContainer } from '../Modal'; import { PublisherSection } from 'renderer/components/PublisherSection'; import * as packageInfo from '../../../../package.json'; - -const releaseCache = new DataCache('releases', 1000 * 3600 * 24); - -/** - * Obtain releases for a specific addon - * - * @param addon - */ -export const getAddonReleases = async (addon: Addon): Promise => { - const releases = ( - await releaseCache.fetchOrCompute(async (): Promise => { - return (await GitVersions.getReleases(addon.repoOwner, addon.repoName)) - .filter((r) => /v\d/.test(r.name)) - .map((r) => ({ title: r.name, date: r.publishedAt, type: 'minor' })); - }) - ).map((r) => ({ ...r, date: new Date(r.date) })); // Local Data cache returns a string instead of Date - - releases.forEach((version, index) => { - const currentVersionTitle = version.title; - const otherVersionTitle = index === releases.length - 1 ? releases[index - 1].title : releases[index + 1].title; - - if (currentVersionTitle[1] !== otherVersionTitle[1]) { - releases[index].type = 'major'; - } else if (currentVersionTitle[3] !== otherVersionTitle[3]) { - releases[index].type = 'minor'; - } else if (currentVersionTitle[5] !== otherVersionTitle[5] && index === releases.length - 1) { - releases[index].type = 'minor'; - } else if (currentVersionTitle[5] !== otherVersionTitle[5]) { - releases[index].type = 'patch'; - } - }); - - return releases; -}; - -export const fetchLatestVersionNames = async (addon: Addon): Promise => { - const dispatch = store.dispatch; - - for (const track of addon.tracks) { - const trackLatestVersionName = await AddonData.latestVersionForTrack(addon, track); - dispatch( - setAddonAndTrackLatestReleaseInfo({ - addonKey: addon.key, - trackKey: track.key, - info: trackLatestVersionName, - }), - ); - } -}; +import { InstallManager } from 'renderer/utils/InstallManager'; const App = () => { const history = useHistory(); @@ -86,8 +34,9 @@ const App = () => { ); useEffect(() => { - addons.forEach(AddonData.configureInitialAddonState); - addons.forEach(fetchLatestVersionNames); + for (const addon of addons) { + void InstallManager.refreshAddonInstallState(addon).then(() => void InstallManager.checkForUpdates(addon)); + } if (settings.get('cache.main.lastShownSection')) { history.push(settings.get('cache.main.lastShownSection')); @@ -103,8 +52,10 @@ const App = () => { const updateCheck = setInterval( () => { ipcRenderer.send(channels.checkForInstallerUpdate); - addons.forEach(AddonData.checkForUpdates); - addons.forEach(fetchLatestVersionNames); + + for (const addon of addons) { + void InstallManager.checkForUpdates(addon); + } }, 5 * 60 * 1000, ); diff --git a/src/renderer/components/SettingsSection/Developer.tsx b/src/renderer/components/SettingsSection/Developer.tsx index 819bcfcc..d44de15e 100644 --- a/src/renderer/components/SettingsSection/Developer.tsx +++ b/src/renderer/components/SettingsSection/Developer.tsx @@ -1,5 +1,6 @@ import React, { FC, useCallback, useEffect, useState } from 'react'; import { useSetting } from 'renderer/rendererSettings'; +import { Toggle } from 'renderer/components/Toggle'; const SettingsItem: FC<{ name: string }> = ({ name, children }) => (
@@ -8,10 +9,12 @@ const SettingsItem: FC<{ name: string }> = ({ name, children }) => (
); -export const DeveloperSettings = (): JSX.Element => { +export const DeveloperSettings: React.FC = () => { const [configDownloadUrl, setConfigDownloadUrl] = useSetting('mainSettings.configDownloadUrl'); const [configDownloadUrlValid, setConfigDownloadUrlValid] = useState(false); + const [configForceUseLocal, setConfigForceUseLocal] = useSetting('mainSettings.configForceUseLocal'); + const validateUrl = useCallback(() => { try { fetch(configDownloadUrl).then((response) => { @@ -32,7 +35,7 @@ export const DeveloperSettings = (): JSX.Element => {

General Settings

-
+
{ />
+ +
+ +
+
diff --git a/src/renderer/index.tsx b/src/renderer/index.tsx index 10f1973d..8fa528b4 100644 --- a/src/renderer/index.tsx +++ b/src/renderer/index.tsx @@ -3,7 +3,7 @@ import ReactDOM from 'react-dom'; import * as Sentry from '@sentry/electron/renderer'; import { browserTracingIntegration } from '@sentry/browser'; import { Provider } from 'react-redux'; -import App, { fetchLatestVersionNames } from 'renderer/components/App'; +import App from 'renderer/components/App'; import { Configuration, InstallerConfiguration } from 'renderer/utils/InstallerConfiguration'; import { ipcRenderer } from 'electron'; import { Directories } from 'renderer/utils/Directories'; @@ -66,8 +66,6 @@ InstallerConfiguration.obtain() } else { store.dispatch(addReleases({ key: addon.key, releases: [] })); } - - fetchLatestVersionNames(addon); } } diff --git a/src/renderer/redux/features/installStatus.ts b/src/renderer/redux/features/installStatus.ts index f9663f18..4f137264 100644 --- a/src/renderer/redux/features/installStatus.ts +++ b/src/renderer/redux/features/installStatus.ts @@ -9,7 +9,10 @@ interface BaseInstallState { export interface GenericInstallState { status: Exclude< InstallStatus, - InstallStatus.InstallingDependency | InstallStatus.InstallingDependencyEnding | InstallStatus.Decompressing + | InstallStatus.InstallingDependency + | InstallStatus.InstallingDependencyEnding + | InstallStatus.Decompressing + | InstallStatus.DownloadCanceled >; } @@ -33,11 +36,17 @@ export interface DecompressingInstallState extends BaseInstallState { entry?: string; } +export interface CancelledInstallState extends BaseInstallState { + status: InstallStatus.DownloadCanceled; + timestamp: number; +} + export type InstallState = | GenericInstallState | InstallingDependencyInstallState | InstallingDependencyEndingInstallState - | DecompressingInstallState; + | DecompressingInstallState + | CancelledInstallState; const initialState: Record = {}; diff --git a/src/renderer/rendererSettings.ts b/src/renderer/rendererSettings.ts index 6790acd7..bda0b925 100644 --- a/src/renderer/rendererSettings.ts +++ b/src/renderer/rendererSettings.ts @@ -105,6 +105,7 @@ interface RendererSettings { allowSeasonalEffects: boolean; msfsBasePath: string; configDownloadUrl: string; + configForceUseLocal: boolean; }; cache: { main: { @@ -216,6 +217,10 @@ const schema: Schema = { type: 'string', default: packageInfo.configUrls.production, }, + configForceUseLocal: { + type: 'boolean', + default: false, + }, }, }, cache: { diff --git a/src/renderer/utils/AddonData.ts b/src/renderer/utils/AddonData.ts index 0e341bcb..c86a2487 100644 --- a/src/renderer/utils/AddonData.ts +++ b/src/renderer/utils/AddonData.ts @@ -1,15 +1,6 @@ import { Addon, AddonTrack, GithubBranchReleaseModel } from 'renderer/utils/InstallerConfiguration'; import { GitVersions } from '@flybywiresim/api-client'; -import { Directories } from './Directories'; -import fs from 'fs'; -import { getCurrentInstall, FragmenterUpdateChecker } from '@flybywiresim/fragmenter'; -import settings from 'renderer/rendererSettings'; -import { store } from 'renderer/redux/store'; -import { setInstalledTrack } from 'renderer/redux/features/installedTrack'; -import { setSelectedTrack } from 'renderer/redux/features/selectedTrack'; -import { InstallState, setInstallStatus } from 'renderer/redux/features/installStatus'; import yaml from 'js-yaml'; -import { InstallStatus } from 'renderer/components/AddonSection/Enums'; export type ReleaseInfo = { name: string; @@ -18,7 +9,7 @@ export type ReleaseInfo = { }; export class AddonData { - static async latestVersionForTrack(addon: Addon, track: AddonTrack): Promise { + static async latestNonFragmenterVersionForTrack(addon: Addon, track: AddonTrack): Promise { switch (track.releaseModel.type) { case 'githubRelease': return this.latestVersionForReleasedTrack(addon); @@ -58,123 +49,4 @@ export class AddonData { ).releases[0].date.getTime(), })); } - - static async configureInitialAddonState(addon: Addon): Promise { - const dispatch = store.dispatch; - - const setCurrentlyInstalledTrack = (newInstalledTrack: AddonTrack) => { - dispatch(setInstalledTrack({ addonKey: addon.key, installedTrack: newInstalledTrack })); - }; - - const setCurrentlySelectedTrack = (newSelectedTrack: AddonTrack) => { - dispatch(setSelectedTrack({ addonKey: addon.key, track: newSelectedTrack })); - }; - - const setCurrentInstallStatus = (new_state: InstallState) => { - dispatch(setInstallStatus({ addonKey: addon.key, installState: new_state })); - }; - - let selectedTrack: AddonTrack; - if (!Directories.isFragmenterInstall(addon)) { - console.log(addon.key, 'is not installed'); - selectedTrack = addon.tracks[0]; - setCurrentlySelectedTrack(selectedTrack); - } else { - console.log(addon.key, 'is installed'); - try { - const manifest = getCurrentInstall(Directories.inInstallLocation(addon.targetDirectory)); - console.log('Currently installed', manifest); - - let track = addon.tracks.find((track) => track.url.includes(manifest.source)); - if (!track) { - track = addon.tracks.find((track) => track.alternativeUrls?.includes(manifest.source)); - } - - console.log('Currently installed', track); - setCurrentlyInstalledTrack(track); - setCurrentlySelectedTrack(track); - selectedTrack = track; - } catch (e) { - console.error(e); - console.log('Not installed'); - setCurrentlySelectedTrack(addon.tracks[0]); - selectedTrack = addon.tracks[0]; - } - } - - const addonDiscovered = settings.get('cache.main.discoveredAddons.' + addon.key); - - if (addon.hidden && !addonDiscovered) { - setCurrentInstallStatus({ status: InstallStatus.Hidden }); - return; - } - - if (!selectedTrack) { - console.log(addon.key, 'has unknown install status'); - setCurrentInstallStatus({ status: InstallStatus.Unknown }); - return; - } - - console.log('Checking install status'); - - const installDir = Directories.inInstallLocation(addon.targetDirectory); - - if (!fs.existsSync(installDir)) { - console.log('no existing install dir for', addon.key); - setCurrentInstallStatus({ status: InstallStatus.NotInstalled }); - return; - } - - console.log('Checking for git install'); - if (Directories.isGitInstall(installDir)) { - setCurrentInstallStatus({ status: InstallStatus.GitInstall }); - return; - } - - try { - const updateInfo = await new FragmenterUpdateChecker().needsUpdate(selectedTrack.url, installDir, { - forceCacheBust: true, - }); - - if (updateInfo.isFreshInstall) { - setCurrentInstallStatus({ status: InstallStatus.NotInstalled }); - return; - } - - if (updateInfo.needsUpdate) { - setCurrentInstallStatus({ status: InstallStatus.NeedsUpdate }); - return; - } - - setCurrentInstallStatus({ status: InstallStatus.UpToDate }); - return; - } catch (e) { - console.error(e); - setCurrentInstallStatus({ status: InstallStatus.Unknown }); - return; - } - } - - static async checkForUpdates(addon: Addon): Promise { - console.log('Checking for updates for ' + addon.key); - - const dispatch = store.dispatch; - - const installDir = Directories.inInstallLocation(addon.targetDirectory); - - const state = store.getState(); - - if (state.installStatus[addon.key].status === InstallStatus.UpToDate) { - const updateInfo = await new FragmenterUpdateChecker().needsUpdate( - state.selectedTracks[addon.key].url, - installDir, - { - forceCacheBust: true, - }, - ); - if (updateInfo.needsUpdate) { - dispatch(setInstallStatus({ addonKey: addon.key, installState: { status: InstallStatus.NeedsUpdate } })); - } - } - } } diff --git a/src/renderer/utils/InstallManager.tsx b/src/renderer/utils/InstallManager.tsx index bf2cd484..de9e431d 100644 --- a/src/renderer/utils/InstallManager.tsx +++ b/src/renderer/utils/InstallManager.tsx @@ -12,13 +12,15 @@ import { } from 'renderer/redux/features/downloads'; import { Directories } from 'renderer/utils/Directories'; import fs from 'fs'; -import { ApplicationStatus, InstallStatus } from 'renderer/components/AddonSection/Enums'; +import { ApplicationStatus, InstallStatus, InstallStatusCategories } from 'renderer/components/AddonSection/Enums'; import { FragmenterContextEvents, FragmenterError, FragmenterInstallerEvents, FragmenterOperation, FragmenterUpdateChecker, + getCurrentInstall, + InstallManifest, } from '@flybywiresim/fragmenter'; import settings from 'renderer/rendererSettings'; import { store } from 'renderer/redux/store'; @@ -41,6 +43,8 @@ import { ErrorDialog } from 'renderer/components/Modal/ErrorDialog'; import { InstallSizeDialog } from 'renderer/components/Modal/InstallSizeDialog'; import { IncompatibleAddOnsCheck } from 'renderer/utils/IncompatibleAddOnsCheck'; import { FreeDiskSpace, FreeDiskSpaceStatus } from 'renderer/utils/FreeDiskSpace'; +import { setAddonAndTrackLatestReleaseInfo } from 'renderer/redux/features/latestVersionNames'; +import { AddonData, ReleaseInfo } from 'renderer/utils/AddonData'; type FragmenterEventArguments = Parameters< (FragmenterInstallerEvents & FragmenterContextEvents)[K] @@ -61,108 +65,10 @@ export class InstallManager { return arr; })(); - private static lowestAvailableAbortControllerID(): number { - for (let i = 0; i < this.abortControllers.length; i++) { - if ( - !store - .getState() - .downloads.map((download) => download.abortControllerID) - .includes(i) - ) { - return i; - } - } - } - - static async determineAddonInstallState(addon: Addon): Promise { - const addonSelectedTrack = this.getAddonSelectedTrack(addon); - const addonInstalledTrack = this.getAddonInstalledTrack(addon); - - if (!addonSelectedTrack) { - return { status: InstallStatus.Unknown }; - } - - console.log('Checking install status'); - - const installDir = Directories.inInstallLocation(addon.targetDirectory); - - if (!fs.existsSync(installDir)) { - return { status: InstallStatus.NotInstalled }; - } - - console.log('Checking for git install'); - if (Directories.isGitInstall(installDir)) { - return { status: InstallStatus.GitInstall }; - } - - try { - const updateInfo = await new FragmenterUpdateChecker().needsUpdate(addonSelectedTrack.url, installDir, { - forceCacheBust: true, - }); - console.log('Update info', updateInfo); - - if (addonSelectedTrack !== addonInstalledTrack && addonInstalledTrack) { - return { status: InstallStatus.TrackSwitch }; - } - if (updateInfo.isFreshInstall) { - return { status: InstallStatus.NotInstalled }; - } - - if (updateInfo.needsUpdate) { - return { status: InstallStatus.NeedsUpdate }; - } - - return { status: InstallStatus.UpToDate }; - } catch (e) { - console.error(e); - return { status: InstallStatus.Unknown }; - } - } - - static async getAddonInstallState(addon: Addon): Promise { - try { - return store.getState().installStatus[addon.key] as InstallState; - } catch (e) { - const state = await this.determineAddonInstallState(addon); - this.setCurrentInstallState(addon, state); - return state; - } - } - - static getAddonSelectedTrack(addon: Addon): AddonTrack { - try { - return store.getState().selectedTracks[addon.key] as AddonTrack; - } catch (e) { - this.setCurrentSelectedTrack(addon, null); - return null; - } - } - - static getAddonInstalledTrack(addon: Addon): AddonTrack { - try { - return store.getState().installedTracks[addon.key] as AddonTrack; - } catch (e) { - this.setCurrentlyInstalledTrack(addon, null); - return null; - } - } - - private static setCurrentInstallState(addon: Addon, installState: InstallState): void { - store.dispatch(setInstallStatus({ addonKey: addon.key, installState })); - } - - private static setCurrentSelectedTrack(addon: Addon, track: AddonTrack): void { - store.dispatch(setSelectedTrack({ addonKey: addon.key, track: track })); - } - - private static setCurrentlyInstalledTrack(addon: Addon, track: AddonTrack): void { - store.dispatch(setInstalledTrack({ addonKey: addon.key, installedTrack: track })); - } - - static async installAddon( + public static async installAddon( addon: Addon, publisher: Publisher, - showModal: (modal: JSX.Element) => Promise, + showModal: (modal: React.JSX.Element) => Promise, dependencyOf?: Addon, ): Promise { this.setCurrentInstallState(addon, { status: InstallStatus.DownloadPending }); @@ -172,13 +78,13 @@ export class InstallManager { }; const setCancelledState = () => { - this.setCurrentInstallState(addon, { status: InstallStatus.DownloadCanceled }); + this.setCurrentInstallState(addon, { status: InstallStatus.DownloadCanceled, timestamp: Date.now() }); }; const startResetStateTimer = (timeout = 3_000) => { setTimeout(async () => { store.dispatch(deleteDownload({ id: addon.key })); - this.setCurrentInstallState(addon, await this.determineAddonInstallState(addon)); + this.setCurrentInstallState(addon, await this.determineAddonInstallStatus(addon)); }, timeout); }; @@ -212,7 +118,10 @@ export class InstallManager { const dependencyAddon = Resolver.findAddon(publisherKey, addonKey); if (!dependencyAddon) { - console.error(`Addon specified dependency for unknown addon: @${publisherKey}/${addonKey}`); + console.error( + `[InstallManager](installAddon) Addon specified dependency for unknown addon: @${publisherKey}/${addonKey}`, + ); + return InstallResult.Failure; } @@ -261,21 +170,23 @@ export class InstallManager { const result = await this.installAddon(dependencyAddon, dependencyPublisher, showModal, addon); if (result === InstallResult.Failure) { - console.error('Error while installing dependency - aborting'); + console.error('[InstallManager](installAddon) Error while installing dependency - aborting'); setErrorState(); startResetStateTimer(); return InstallResult.Failure; } else if (result === InstallResult.Cancelled) { - console.log('Dependency install cancelled, canceling main addon too.'); + console.log('[InstallManager](installAddon) Dependency install cancelled, canceling main addon too.'); setCancelledState(); startResetStateTimer(); return InstallResult.Cancelled; } else { - console.log(`Dependency @${publisherKey}/${addonKey} installed successfully.`); + console.log( + `[InstallManager](installAddon) Dependency @${publisherKey}/${addonKey} installed successfully.`, + ); } } } @@ -354,17 +265,18 @@ export class InstallManager { ); if (tempDir === Directories.installLocation()) { - console.error('Community directory equals temp directory'); + console.error('[InstallManager](installAddon) Community directory equals temp directory'); + this.notifyDownload(addon, false); return InstallResult.Failure; } - console.log(`Installing track=${track.key}`); - console.log('Installing into:'); - console.log('---'); - console.log(`installDir: ${destDir}`); - console.log(`tempDir: ${tempDir}`); - console.log('---'); + console.log(`[InstallManager](installAddon) Installing track=${track.key}`); + console.log('[InstallManager](installAddon) Installing into:'); + console.log('[InstallManager](installAddon) ---'); + console.log(`[InstallManager](installAddon) installDir: ${destDir}`); + console.log(`[InstallManager](installAddon) tempDir: ${tempDir}`); + console.log('[InstallManager](installAddon) ---'); try { // Create dest dir if it doesn't exist @@ -393,7 +305,7 @@ export class InstallManager { case 'downloadStarted': { const [module] = args as FragmenterEventArguments; - console.log('Downloading started for module', module.name); + console.log('[InstallManager](installAddon) Downloading started for module', module.name); this.setCurrentInstallState(addon, { status: InstallStatus.Downloading }); @@ -460,7 +372,7 @@ export class InstallManager { case 'unzipStarted': { const [module] = args as FragmenterEventArguments; - console.log('Started unzipping module', module.name); + console.log('[InstallManager](installAddon) Started unzipping module', module.name); this.setCurrentInstallState(addon, { status: InstallStatus.Decompressing, percent: 0 }); if (dependencyOf) { @@ -497,7 +409,7 @@ export class InstallManager { case 'copyStarted': { const [module] = args as FragmenterEventArguments; - console.log('Started moving over module', module.name); + console.log('[InstallManager](installAddon) Started moving over module', module.name); if (module.name === 'full') { this.setCurrentInstallState(addon, { status: InstallStatus.DownloadEnding }); @@ -508,9 +420,9 @@ export class InstallManager { case 'retryScheduled': { const [module, retryCount, waitSeconds] = args as FragmenterEventArguments; - console.log('Scheduling a retry for module', module.name); - console.log('Retry count', retryCount); - console.log('Waiting for', waitSeconds, 'seconds'); + console.log('[InstallManager](installAddon) Scheduling a retry for module', module.name); + console.log('[InstallManager](installAddon) Retry count', retryCount); + console.log('[InstallManager](installAddon) Waiting for', waitSeconds, 'seconds'); store.dispatch(clearDownloadInterrupted({ id: addon.key })); @@ -520,20 +432,20 @@ export class InstallManager { case 'retryStarted': { const [module, retryCount] = args as FragmenterEventArguments; - console.log('Starting a retry for module', module.name); - console.log('Retry count', retryCount); + console.log('[InstallManager](installAddon) Starting a retry for module', module.name); + console.log('[InstallManager](installAddon) Retry count', retryCount); this.setCurrentInstallState(addon, { status: InstallStatus.Downloading }); break; } case 'cancelled': { - this.setCurrentInstallState(addon, { status: InstallStatus.DownloadCanceled }); + this.setCurrentInstallState(addon, { status: InstallStatus.DownloadCanceled, timestamp: Date.now() }); break; } case 'error': { const [error] = args as FragmenterEventArguments; - console.error('Error from Fragmenter:', error); + console.error('[InstallManager](installAddon) Error from Fragmenter:', error); Sentry.captureException(error); } } @@ -547,7 +459,7 @@ export class InstallManager { ipcRenderer.send(channels.installManager.cancelInstall, ourInstallID); }); - console.log('Starting fragmenter download for URL', track.url); + console.log('[InstallManager](installAddon) Starting fragmenter download for URL', track.url); const installResult = await ipcRenderer.invoke( channels.installManager.installFromUrl, @@ -562,21 +474,21 @@ export class InstallManager { throw installResult; } - console.log('Fragmenter download finished for URL', track.url); + console.log('[InstallManager](installAddon) Fragmenter download finished for URL', track.url); // Stop listening to forwarded fragmenter events ipcRenderer.removeListener(channels.installManager.fragmenterEvent, handleForwardedFragmenterEvent); // Remove installs existing under alternative names - console.log('Removing installs existing under alternative names'); + console.log('[InstallManager](installAddon) Removing installs existing under alternative names'); Directories.removeAlternativesForAddon(addon); - console.log('Finished removing installs existing under alternative names'); + console.log('[InstallManager](installAddon) Finished removing installs existing under alternative names'); this.notifyDownload(addon, true); // Flash completion text - this.setCurrentInstallState(addon, { status: InstallStatus.DownloadDone }); this.setCurrentlyInstalledTrack(addon, track); + this.setCurrentInstallState(addon, { status: InstallStatus.DownloadDone }); // If we have a background service, ask if we want to enable it if (addon.backgroundService && (addon.backgroundService.enableAutostartConfiguration ?? true)) { @@ -595,14 +507,14 @@ export class InstallManager { const isFragmenterError = FragmenterError.isFragmenterError(e); if (signal.aborted) { - console.warn('Download was cancelled'); + console.warn('[InstallManager](installAddon) Download was cancelled'); setCancelledState(); startResetStateTimer(); return InstallResult.Cancelled; } else { - console.error('Download failed, see exception below'); + console.error('[InstallManager](installAddon) Download failed, see exception below'); console.error(e); setErrorState(); @@ -624,7 +536,7 @@ export class InstallManager { return InstallResult.Success; } - static cancelDownload(addon: Addon): void { + public static cancelDownload(addon: Addon): void { let download = store.getState().downloads.find((it) => it.id === addon.key); if (!download) { for (const dependency of addon.dependencies ?? []) { @@ -641,7 +553,7 @@ export class InstallManager { } if (!download) { - throw new Error('Cannot cancel when no addon or dependency download is ongoing'); + throw new Error('[InstallManager](cancelDownload) Cannot cancel when no addon or dependency download is ongoing'); } const abortController = this.abortControllers[download.abortControllerID]; @@ -649,7 +561,7 @@ export class InstallManager { abortController?.abort(); } - static async uninstallAddon( + public static async uninstallAddon( addon: Addon, publisher: Publisher, showModal: (modal: JSX.Element) => Promise, @@ -688,11 +600,201 @@ export class InstallManager { this.setCurrentlyInstalledTrack(addon, null); } + private static getAddonInstall(directory: string): InstallManifest | null { + try { + return getCurrentInstall(directory); + } catch (e) { + return null; + } + } + + public static async getAddonInstallState(addon: Addon): Promise { + const status = store.getState().installStatus[addon.key] as InstallState; + + if (status) { + return status; + } + + return this.refreshAddonInstallState(addon); + } + + public static async refreshAddonInstallState(addon: Addon): Promise { + const currentState = store.getState().installStatus[addon.key] as InstallState; + + if (currentState?.status === InstallStatus.DownloadCanceled) { + setTimeout( + async () => { + const status = await this.determineAddonInstallStatus(addon); + this.setCurrentInstallState(addon, status); + }, + 3_000 - (Date.now() - currentState.timestamp), + ); + + return currentState; + } + + const status = await this.determineAddonInstallStatus(addon); + this.setCurrentInstallState(addon, status); + + return status; + } + + public static getAddonSelectedTrack(addon: Addon): AddonTrack { + const selectedTrack = store.getState().selectedTracks[addon.key] as AddonTrack; + + if (selectedTrack) { + return selectedTrack; + } + + this.setCurrentSelectedTrack(addon, addon.tracks[0]); + + return addon.tracks[0]; + } + + public static determineAddonInstalledTrack(addon: Addon): AddonTrack | null { + const installedTrack = store.getState().installedTracks[addon.key] as AddonTrack; + + if (installedTrack) { + return installedTrack; + } + + const install = this.getAddonInstall(Directories.inInstallLocation(addon.targetDirectory)); + + if (!install) { + return null; + } + + const matchingTrack = addon.tracks.find((it) => it.url === install.source); + + if (!matchingTrack) { + return null; + } + + this.setCurrentlyInstalledTrack(addon, matchingTrack); + this.setCurrentSelectedTrack(addon, matchingTrack); + + return matchingTrack; + } + + private static lowestAvailableAbortControllerID(): number { + for (let i = 0; i < this.abortControllers.length; i++) { + if ( + !store + .getState() + .downloads.map((download) => download.abortControllerID) + .includes(i) + ) { + return i; + } + } + } + + private static async determineAddonInstallStatus(addon: Addon): Promise { + console.log('[InstallManager](determineAddonInstallStatus) Checking install status'); + + const installDir = Directories.inInstallLocation(addon.targetDirectory); + const addonInstalledTrack = this.determineAddonInstalledTrack(addon); + const addonSelectedTrack = this.getAddonSelectedTrack(addon); + + if (!fs.existsSync(installDir)) { + console.log('[InstallManager](determineAddonInstallStatus) Is not installed'); + + return { status: InstallStatus.NotInstalled }; + } + + console.log('[InstallManager](determineAddonInstallStatus) Checking for git install'); + + if (Directories.isGitInstall(installDir)) { + console.log('[InstallManager](determineAddonInstallStatus) Is git install'); + + return { status: InstallStatus.GitInstall }; + } + + try { + const updateInfo = await new FragmenterUpdateChecker().needsUpdate(addonSelectedTrack.url, installDir, { + forceCacheBust: true, + }); + + console.log('[InstallManager](determineAddonInstallStatus) Update info', updateInfo); + + if (addonSelectedTrack !== addonInstalledTrack && addonInstalledTrack) { + return { status: InstallStatus.TrackSwitch }; + } + + if (updateInfo.isFreshInstall) { + return { status: InstallStatus.NotInstalled }; + } + + if (updateInfo.needsUpdate) { + return { status: InstallStatus.NeedsUpdate }; + } + + return { status: InstallStatus.UpToDate }; + } catch (e) { + console.error(e); + return { status: InstallStatus.Unknown }; + } + } + + public static async checkForUpdates(addon: Addon): Promise { + console.log('[InstallManager](checkForUpdates) Checking for updates for ' + addon.key); + + const installDir = Directories.inInstallLocation(addon.targetDirectory); + + const state = store.getState(); + + const addonInstallState = state.installStatus[addon.key] ?? { status: InstallStatus.Unknown }; + + if ( + InstallStatusCategories.installing.includes(addonInstallState.status) || + addonInstallState.status === InstallStatus.Unknown + ) { + return; + } + + const fragmenterUpdateChecker = new FragmenterUpdateChecker(); + + for (const track of addon.tracks) { + const updateInfo = await fragmenterUpdateChecker.needsUpdate(track.url, installDir, { forceCacheBust: true }); + + let info: ReleaseInfo; + if (track.releaseModel.type === 'fragmenter') { + info = { + name: updateInfo.distributionManifest.version, + changelogUrl: undefined, + releaseDate: Date.now(), + }; + } else { + info = await AddonData.latestNonFragmenterVersionForTrack(addon, track); + } + + store.dispatch(setAddonAndTrackLatestReleaseInfo({ addonKey: addon.key, trackKey: track.key, info })); + + if (track.key === state.installedTracks[addon.key]?.key && updateInfo.needsUpdate) { + store.dispatch(setInstallStatus({ addonKey: addon.key, installState: { status: InstallStatus.NeedsUpdate } })); + } + } + } + + private static setCurrentInstallState(addon: Addon, installState: InstallState): void { + store.dispatch(setInstallStatus({ addonKey: addon.key, installState })); + } + + private static setCurrentSelectedTrack(addon: Addon, track: AddonTrack): void { + store.dispatch(setSelectedTrack({ addonKey: addon.key, track: track })); + } + + private static setCurrentlyInstalledTrack(addon: Addon, track: AddonTrack): void { + store.dispatch(setInstalledTrack({ addonKey: addon.key, installedTrack: track })); + } + private static notifyDownload(addon: Addon, successful: boolean): void { - console.log('Requesting notification'); + console.log('[InstallManager](notifyDownload) Requesting notification'); + Notification.requestPermission() .then(() => { - console.log('Showing notification'); + console.log('InstallManager](notifyDownload) Showing notification'); + if (successful) { new Notification(`${addon.name} download complete!`, { icon: path.join(process.resourcesPath, 'extraResources', 'icon.ico'), diff --git a/src/renderer/utils/InstallerConfiguration.ts b/src/renderer/utils/InstallerConfiguration.ts index 1ce3a55d..9cfaee04 100644 --- a/src/renderer/utils/InstallerConfiguration.ts +++ b/src/renderer/utils/InstallerConfiguration.ts @@ -23,20 +23,31 @@ export type AddonVersion = { type: 'major' | 'minor' | 'patch'; }; +export type FragmenterReleaseModel = { + type: 'fragmenter'; +}; + export type GithubReleaseReleaseModel = { + /** @deprecated */ type: 'githubRelease'; }; export type GithubBranchReleaseModel = { + /** @deprecated */ type: 'githubBranch'; branch: string; }; export type CDNReleaseModel = { + /** @deprecated */ type: 'CDN'; }; -export type ReleaseModel = GithubReleaseReleaseModel | GithubBranchReleaseModel | CDNReleaseModel; +export type ReleaseModel = + | FragmenterReleaseModel + | GithubReleaseReleaseModel + | GithubBranchReleaseModel + | CDNReleaseModel; type BaseAddonTrack = { name: string; @@ -354,6 +365,12 @@ export interface Configuration { export class InstallerConfiguration { static async obtain(): Promise { + const forceUseLocalConfig = settings.get('mainSettings.configForceUseLocal') as boolean; + + if (forceUseLocalConfig) { + return this.loadConfigurationFromLocalStorage(); + } + return this.fetchConfigurationFromCdn() .then((config) => { if (this.isConfigurationValid(config)) { From bf58f63504c939e5a9753291402a1c0f8a7dc57f Mon Sep 17 00:00:00 2001 From: Saschl <19493808+Saschl@users.noreply.github.com> Date: Mon, 14 Oct 2024 23:39:06 +0200 Subject: [PATCH 3/3] feat: read config json from documents dir (#478) * feat: read config json from home dir * fix * use documents folder * fix: use correct import * fix: resources shortcut * revert unwanted change * enable temporary warning text for simbridge * fix: allow backwards compatibility * fix typo * fix: enable html rendering on addon description * fix: use old simbridge configuration automatically if new one not present * fix lint * fix typo --------- Co-authored-by: Saschl --- package-lock.json | 555 +++++++++++++++++- package.json | 1 + .../AddonSection/Configure/index.tsx | 2 + .../AddonSection/MyInstall/index.tsx | 9 + .../components/AddonSection/index.tsx | 7 +- .../components/LocalApiConfigEditUI/index.tsx | 19 +- src/renderer/data.ts | 7 +- src/renderer/utils/Directories.ts | 4 + src/renderer/utils/InstallerConfiguration.ts | 2 +- 9 files changed, 594 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 14be8c18..8dc37f0d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,6 +43,7 @@ "react-router-dom": "^5.2.0", "react-windows-controls": "^1.1.1", "redux": "^4.0.5", + "rehype-raw": "^7.0.0", "remark-gfm": "^2.0.0", "semver": "7.3.5", "simplebar-react": "^3.2.4", @@ -4920,8 +4921,7 @@ "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" }, "node_modules/@webassemblyjs/ast": { "version": "1.12.1", @@ -7973,6 +7973,18 @@ "node": ">=0.8.0" } }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/dezalgo": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", @@ -10759,6 +10771,360 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-from-parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.1.tgz", + "integrity": "sha512-Er/Iixbc7IEa7r/XLtuG52zoqn/b3Xng/w6aZQ0xGVxzhw5xUFxcRqdPzP6yFi/4HBYRaifaI5fQ1RH8n0ZeOQ==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "devlop": "^1.0.0", + "hastscript": "^8.0.0", + "property-information": "^6.0.0", + "vfile": "^6.0.0", + "vfile-location": "^5.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hast-util-from-parse5/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" + }, + "node_modules/hast-util-from-parse5/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5/node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-parse5/node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hast-util-raw": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/hast-util-raw/-/hast-util-raw-9.0.4.tgz", + "integrity": "sha512-LHE65TD2YiNsHD3YuXcKPHXPLuYh/gjp12mOfU8jxSrm1f/yJpsb0F/KKljS6U9LJoP0Ux+tCe8iJ2AsPzTdgA==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-from-parse5": "^8.0.0", + "hast-util-to-parse5": "^8.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "parse5": "^7.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hast-util-raw/node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/hast-util-raw/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" + }, + "node_modules/hast-util-raw/node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw/node_modules/micromark-util-character": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/hast-util-raw/node_modules/micromark-util-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz", + "integrity": "sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/hast-util-raw/node_modules/micromark-util-sanitize-uri": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz", + "integrity": "sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/hast-util-raw/node_modules/micromark-util-symbol": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz", + "integrity": "sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/hast-util-raw/node_modules/micromark-util-types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.0.tgz", + "integrity": "sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ] + }, + "node_modules/hast-util-raw/node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw/node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw/node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw/node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw/node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-raw/node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hast-util-to-parse5/-/hast-util-to-parse5-8.0.0.tgz", + "integrity": "sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0", + "web-namespaces": "^2.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-parse5/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/hast-util-whitespace": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.1.tgz", @@ -10768,6 +11134,30 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hastscript": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-8.0.0.tgz", + "integrity": "sha512-dMOtzCEd3ABUeSIISmrETiKuyydk1w0pa+gE/uormcTpSYuaNJPbX1NU3JLyscSLjwAQM8bWMhhIlnCqnRvDTw==", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^6.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/he": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", @@ -10939,6 +11329,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/html-webpack-plugin": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-5.6.0.tgz", @@ -14939,6 +15338,28 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -18327,6 +18748,71 @@ "jsesc": "bin/jsesc" } }, + "node_modules/rehype-raw": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", + "integrity": "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-raw": "^9.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-raw/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/rehype-raw/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" + }, + "node_modules/rehype-raw/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-raw/node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-raw/node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/relateurl": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", @@ -21149,6 +21635,62 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/vfile-location": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-5.0.3.tgz", + "integrity": "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" + }, + "node_modules/vfile-location/node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location/node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-location/node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/vfile-message": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.4.tgz", @@ -21282,6 +21824,15 @@ "minimalistic-assert": "^1.0.0" } }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", diff --git a/package.json b/package.json index 04aca9da..7b0dc989 100644 --- a/package.json +++ b/package.json @@ -137,6 +137,7 @@ "react-router-dom": "^5.2.0", "react-windows-controls": "^1.1.1", "redux": "^4.0.5", + "rehype-raw": "^7.0.0", "remark-gfm": "^2.0.0", "semver": "7.3.5", "simplebar-react": "^3.2.4", diff --git a/src/renderer/components/AddonSection/Configure/index.tsx b/src/renderer/components/AddonSection/Configure/index.tsx index 629d616a..bd429949 100644 --- a/src/renderer/components/AddonSection/Configure/index.tsx +++ b/src/renderer/components/AddonSection/Configure/index.tsx @@ -6,6 +6,7 @@ import { Track, Tracks } from './TrackSelector'; import { ConfigurationAspectDisplay } from 'renderer/components/AddonSection/Configure/ConfigurationAspectDisplay'; import './index.css'; +import rehypeRaw from 'rehype-raw'; export interface ConfigureProps { routeAspectKey: string; @@ -76,6 +77,7 @@ export const Configure: FC = ({ {selectedTrack.description} diff --git a/src/renderer/components/AddonSection/MyInstall/index.tsx b/src/renderer/components/AddonSection/MyInstall/index.tsx index 533725cb..18bc461d 100644 --- a/src/renderer/components/AddonSection/MyInstall/index.tsx +++ b/src/renderer/components/AddonSection/MyInstall/index.tsx @@ -49,6 +49,15 @@ export const MyInstall: FC = ({ addon }) => { return Directories.inInstallPackage(addon, def.location.path); case 'packageCache': return Directories.inPackageCache(addon, def.location.path); + case 'documents': { + const documents = Directories.inDocumentsFolder(def.location.path); + if (fs.existsSync(documents)) { + return documents; + } + // fallback for simbridge installations prior to 0.6 + // remove after transition period + return Directories.inInstallPackage(addon, 'resources'); + } } }; diff --git a/src/renderer/components/AddonSection/index.tsx b/src/renderer/components/AddonSection/index.tsx index 51217782..64543ef6 100644 --- a/src/renderer/components/AddonSection/index.tsx +++ b/src/renderer/components/AddonSection/index.tsx @@ -24,6 +24,7 @@ import { InstallManager } from 'renderer/utils/InstallManager'; import { StateSection } from 'renderer/components/AddonSection/StateSection'; import { ExternalApps } from 'renderer/utils/ExternalApps'; import { MyInstall } from 'renderer/components/AddonSection/MyInstall'; +import rehypeRaw from 'rehype-raw'; const abortControllers = new Array(20); abortControllers.fill(new AbortController()); @@ -464,7 +465,11 @@ const About: FC<{ addon: Addon }> = ({ addon }) => (

{addon.aircraftName}

- + {addon.description} diff --git a/src/renderer/components/LocalApiConfigEditUI/index.tsx b/src/renderer/components/LocalApiConfigEditUI/index.tsx index 5265eade..4335aab1 100644 --- a/src/renderer/components/LocalApiConfigEditUI/index.tsx +++ b/src/renderer/components/LocalApiConfigEditUI/index.tsx @@ -4,10 +4,12 @@ import { PromptModal, useModals } from '../Modal'; import { Button, ButtonType } from '../Button'; import fs from 'fs'; import path from 'path'; -import { Directories } from 'renderer/utils/Directories'; import { Toggle } from '../Toggle'; +import { app } from '@electron/remote'; +import { Directories } from 'renderer/utils/Directories'; -const SIMBRIDGE_DIRECTORY = 'flybywire-externaltools-simbridge'; +const LEGACY_SIMBRIDGE_DIRECTORY = 'flybywire-externaltools-simbridge'; +const SIMBRIDGE_DIRECTORY = '/FlyByWireSim/Simbridge'; interface LocalApiConfiguration { server: { @@ -36,12 +38,21 @@ const localApiDefaultConfiguration: LocalApiConfiguration = { }; class LocalApiConfigurationHandler { + private static get legacySimbridgeDirectory(): string { + return path.join(Directories.inInstallLocation(LEGACY_SIMBRIDGE_DIRECTORY)); + } + private static get simbridgeDirectory(): string { - return path.join(Directories.inInstallLocation(SIMBRIDGE_DIRECTORY)); + return path.join(app.getPath('documents'), SIMBRIDGE_DIRECTORY); } private static get simbridgeConfigPath(): string { - return path.join(this.simbridgeDirectory, 'resources', 'properties.json'); + const configPath = path.join(this.simbridgeDirectory, 'resources', 'properties.json'); + if (fs.existsSync(configPath)) { + return configPath; + } + // TODO remove this after a while once simbridge is released + return path.join(this.legacySimbridgeDirectory, 'resources', 'properties.json'); } static getConfiguration(): LocalApiConfiguration { diff --git a/src/renderer/data.ts b/src/renderer/data.ts index 8a30659a..a563b38d 100644 --- a/src/renderer/data.ts +++ b/src/renderer/data.ts @@ -343,8 +343,7 @@ export const defaultConfiguration: Configuration = { }, url: 'https://cdn.flybywiresim.com/addons/simbridge/release/', isExperimental: false, - description: - 'SimBridge is an external app that enables FlyByWire Simulations aircraft to communicate outside your simulator. From remote displays to external terrain display rendering, it is used for a variety of optional features.', + description: `⚠ Note: Starting with version 0.6.0 custom resources like PDF Charts and Company Routes need to be located inside the Documents folder. Please refer to the documentation. \n\n SimBridge is an external app that enables FlyByWire Simulations aircraft to communicate outside your simulator. From remote displays to external terrain display rendering, it is used for a variety of optional features.`, }, ], disallowedRunningExternalApps: ['@/simbridge-app'], @@ -363,8 +362,8 @@ export const defaultConfiguration: Configuration = { directories: [ { location: { - in: 'package', - path: 'resources', + in: 'documents', + path: 'FlyByWireSim/Simbridge/resources', }, title: 'Resources', }, diff --git a/src/renderer/utils/Directories.ts b/src/renderer/utils/Directories.ts index 0feb7a2b..fed9fbed 100644 --- a/src/renderer/utils/Directories.ts +++ b/src/renderer/utils/Directories.ts @@ -141,4 +141,8 @@ export class Directories { return false; } } + + static inDocumentsFolder(targetDir: string): string { + return path.join(app.getPath('documents'), this.sanitize(targetDir)); + } } diff --git a/src/renderer/utils/InstallerConfiguration.ts b/src/renderer/utils/InstallerConfiguration.ts index 9cfaee04..fabb4f3f 100644 --- a/src/renderer/utils/InstallerConfiguration.ts +++ b/src/renderer/utils/InstallerConfiguration.ts @@ -8,7 +8,7 @@ export interface ExternalLink { export interface DirectoryDefinition { location: { - in: 'community' | 'packageCache' | 'package'; + in: 'community' | 'packageCache' | 'package' | 'documents'; path: string; }; }