this repo has no description

Merge pull request #95 from moonlight-mod/notnite/moonlight-updating

Moonbase update installation

Changed files
+521 -133
packages
+2 -1
packages/core-extensions/package.json
···
"private": true,
"dependencies": {
"@moonlight-mod/core": "workspace:*",
-
"@moonlight-mod/types": "workspace:*"
+
"@moonlight-mod/types": "workspace:*",
+
"nanotar": "^0.1.1"
}
}
+48 -3
packages/core-extensions/src/moonbase/index.tsx
···
}
};
+
const bg = "#222034";
+
const fg = "#FFFBA6";
+
export const styles = [
-
".moonbase-settings > :first-child { margin-top: 0px; }",
-
"textarea.moonbase-resizeable { resize: vertical }",
-
".moonbase-updates-notice { background-color: #222034; color: #FFFBA6; }"
+
`
+
.moonbase-settings > :first-child {
+
margin-top: 0px;
+
}
+
+
textarea.moonbase-resizeable {
+
resize: vertical
+
}
+
+
.moonbase-updates-notice {
+
background-color: ${bg};
+
color: ${fg};
+
line-height: unset;
+
height: 36px;
+
}
+
+
.moonbase-updates-notice button {
+
color: ${fg};
+
border-color: ${fg};
+
}
+
+
.moonbase-updates-notice_text-wrapper {
+
display: inline-flex;
+
align-items: center;
+
line-height: 36px;
+
gap: 2px;
+
}
+
+
.moonbase-update-section {
+
background-color: ${bg};
+
--info-help-foreground: ${fg};
+
border: none !important;
+
color: ${fg};
+
+
display: flex;
+
flex-direction: row;
+
justify-content: space-between;
+
}
+
+
.moonbase-update-section > button {
+
color: ${fg};
+
background-color: transparent;
+
border-color: ${fg};
+
}
+
`.trim()
];
+18 -2
packages/core-extensions/src/moonbase/manifest.json
···
"meta": {
"name": "Moonbase",
"tagline": "The official settings UI for moonlight",
-
"authors": ["Cynosphere", "NotNite"]
+
"authors": ["Cynosphere", "NotNite", "redstonekasi"]
},
"dependencies": ["spacepack", "settings", "common", "notices"],
"settings": {
···
"displayName": "Persist filter",
"description": "Save extension filter in config",
"type": "boolean"
+
},
+
"updateChecking": {
+
"displayName": "Automatic update checking",
+
"description": "Checks for updates to moonlight",
+
"type": "boolean",
+
"default": "true"
+
},
+
"updateBanner": {
+
"displayName": "Show update banner",
+
"description": "Shows a banner for moonlight and extension updates",
+
"type": "boolean",
+
"default": "true"
}
-
}
+
},
+
"cors": [
+
"https://github.com/moonlight-mod/moonlight/releases/download/",
+
"https://objects.githubusercontent.com/github-production-release-asset-"
+
]
}
+109 -13
packages/core-extensions/src/moonbase/native.ts
···
import { MoonlightBranch } from "@moonlight-mod/types";
import type { MoonbaseNatives, RepositoryManifest } from "./types";
import extractAsar from "@moonlight-mod/core/asar";
-
import { repoUrlFile } from "@moonlight-mod/types/constants";
+
import {
+
distDir,
+
repoUrlFile,
+
installedVersionFile
+
} from "@moonlight-mod/types/constants";
+
import { parseTarGzip } from "nanotar";
-
export const githubRepo = "moonlight-mod/moonlight";
-
export const nightlyRefUrl = "https://moonlight-mod.github.io/moonlight/ref";
+
const githubRepo = "moonlight-mod/moonlight";
+
const githubApiUrl = `https://api.github.com/repos/${githubRepo}/releases/latest`;
+
const artifactName = "dist.tar.gz";
+
+
const nightlyRefUrl = "https://moonlight-mod.github.io/moonlight/ref";
+
const nightlyZipUrl = "https://moonlight-mod.github.io/moonlight/dist.tar.gz";
+
export const userAgent = `moonlight/${moonlightNode.version} (https://github.com/moonlight-mod/moonlight)`;
+
async function getStableRelease(): Promise<{
+
name: string;
+
assets: {
+
name: string;
+
browser_download_url: string;
+
}[];
+
}> {
+
const req = await fetch(githubApiUrl, {
+
cache: "no-store",
+
headers: {
+
"User-Agent": userAgent
+
}
+
});
+
return await req.json();
+
}
+
export default function getNatives(): MoonbaseNatives {
const logger = moonlightNode.getLogger("moonbase/natives");
···
async checkForMoonlightUpdate() {
try {
if (moonlightNode.branch === MoonlightBranch.STABLE) {
-
const req = await fetch(
-
`https://api.github.com/repos/${githubRepo}/releases/latest?_=${Date.now()}`,
-
{
-
headers: {
-
"User-Agent": userAgent
-
}
-
}
-
);
-
const json: { name: string } = await req.json();
+
const json = await getStableRelease();
return json.name !== moonlightNode.version ? json.name : null;
} else if (moonlightNode.branch === MoonlightBranch.NIGHTLY) {
-
const req = await fetch(`${nightlyRefUrl}?_=${Date.now()}`, {
+
const req = await fetch(nightlyRefUrl, {
+
cache: "no-store",
headers: {
"User-Agent": userAgent
}
···
}
},
+
async updateMoonlight() {
+
// Note: this won't do anything on browser, we should probably disable it
+
// entirely when running in browser.
+
async function downloadStable(): Promise<[ArrayBuffer, string]> {
+
const json = await getStableRelease();
+
const asset = json.assets.find((a) => a.name === artifactName);
+
if (!asset) throw new Error("Artifact not found");
+
+
logger.debug(`Downloading ${asset.browser_download_url}`);
+
const req = await fetch(asset.browser_download_url, {
+
cache: "no-store",
+
headers: {
+
"User-Agent": userAgent
+
}
+
});
+
+
return [await req.arrayBuffer(), json.name];
+
}
+
+
async function downloadNightly(): Promise<[ArrayBuffer, string]> {
+
logger.debug(`Downloading ${nightlyZipUrl}`);
+
const zipReq = await fetch(nightlyZipUrl, {
+
cache: "no-store",
+
headers: {
+
"User-Agent": userAgent
+
}
+
});
+
+
const refReq = await fetch(nightlyRefUrl, {
+
cache: "no-store",
+
headers: {
+
"User-Agent": userAgent
+
}
+
});
+
const ref = (await refReq.text()).split("\n")[0];
+
+
return [await zipReq.arrayBuffer(), ref];
+
}
+
+
const [tar, ref] =
+
moonlightNode.branch === MoonlightBranch.STABLE
+
? await downloadStable()
+
: moonlightNode.branch === MoonlightBranch.NIGHTLY
+
? await downloadNightly()
+
: [null, null];
+
+
if (!tar || !ref) return;
+
+
const dist = moonlightFS.join(moonlightNode.getMoonlightDir(), distDir);
+
if (await moonlightFS.exists(dist)) await moonlightFS.rmdir(dist);
+
await moonlightFS.mkdir(dist);
+
+
logger.debug("Extracting update");
+
const files = await parseTarGzip(tar);
+
for (const file of files) {
+
if (!file.data) continue;
+
// @ts-expect-error What do you mean their own types are wrong
+
if (file.type !== "file") continue;
+
+
const fullFile = moonlightFS.join(dist, file.name);
+
const fullDir = moonlightFS.dirname(fullFile);
+
if (!(await moonlightFS.exists(fullDir)))
+
await moonlightFS.mkdir(fullDir);
+
await moonlightFS.writeFile(fullFile, file.data);
+
}
+
+
logger.debug("Writing version file:", ref);
+
const versionFile = moonlightFS.join(
+
moonlightNode.getMoonlightDir(),
+
installedVersionFile
+
);
+
await moonlightFS.writeFileString(versionFile, ref.trim());
+
+
logger.debug("Update extracted");
+
},
+
async fetchRepositories(repos) {
const ret: Record<string, RepositoryManifest[]> = {};
for (const repo of repos) {
try {
const req = await fetch(repo, {
+
cache: "no-store",
headers: {
"User-Agent": userAgent
}
+2
packages/core-extensions/src/moonbase/types.ts
···
export type MoonbaseNatives = {
checkForMoonlightUpdate(): Promise<string | null>;
+
updateMoonlight(): Promise<void>;
+
fetchRepositories(
repos: string[]
): Promise<Record<string, RepositoryManifest[]>>;
+78 -14
packages/core-extensions/src/moonbase/webpackModules/moonbase.tsx
···
import { Moonbase, pages } from "@moonlight-mod/wp/moonbase_ui";
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
-
import { MenuItem } from "@moonlight-mod/wp/discord/components/common/index";
+
import * as Components from "@moonlight-mod/wp/discord/components/common/index";
+
+
const { MenuItem, Text, Breadcrumbs } = Components;
+
+
const Margins = spacepack.require("discord/styles/shared/Margins.css");
const { open } = spacepack.findByExports("setSection", "clearSubsection")[0]
.exports.Z;
-
settings.addSection("moonbase", "Moonbase", Moonbase, null, -2, {
+
const notice = {
stores: [MoonbaseSettingsStore],
element: () => {
// Require it here because lazy loading SUX
···
/>
);
}
-
});
+
};
-
settings.addSectionMenuItems(
-
"moonbase",
-
...pages.map((page, i) => (
-
<MenuItem
-
key={page.id}
-
id={`moonbase-${page.id}`}
-
label={page.name}
-
action={() => open("moonbase", i)}
-
/>
-
))
-
);
+
function addSection(
+
id: string,
+
name: string,
+
element: React.FunctionComponent
+
) {
+
settings.addSection(`moonbase-${id}`, name, element, null, -2, notice);
+
}
+
+
// FIXME: move to component types
+
type Breadcrumb = {
+
id: string;
+
label: string;
+
};
+
+
function renderBreadcrumb(crumb: Breadcrumb, last: boolean) {
+
return (
+
<Text
+
variant="heading-lg/semibold"
+
tag="h2"
+
color={last ? "header-primary" : "header-secondary"}
+
>
+
{crumb.label}
+
</Text>
+
);
+
}
+
+
if (
+
MoonbaseSettingsStore.getExtensionConfigRaw<boolean>(
+
"moonbase",
+
"sections",
+
false
+
)
+
) {
+
settings.addHeader("Moonbase", -2);
+
+
for (const page of pages) {
+
addSection(page.id, page.name, () => {
+
const breadcrumbs = [
+
{ id: "moonbase", label: "Moonbase" },
+
{ id: page.id, label: page.name }
+
];
+
return (
+
<>
+
<Breadcrumbs
+
className={Margins.marginBottom20}
+
renderCustomBreadcrumb={renderBreadcrumb}
+
breadcrumbs={breadcrumbs}
+
activeId={page.id}
+
>
+
{page.name}
+
</Breadcrumbs>
+
<page.element />
+
</>
+
);
+
});
+
}
+
} else {
+
settings.addSection("moonbase", "Moonbase", Moonbase, null, -2, notice);
+
+
settings.addSectionMenuItems(
+
"moonbase",
+
...pages.map((page, i) => (
+
<MenuItem
+
key={page.id}
+
id={`moonbase-${page.id}`}
+
label={page.name}
+
action={() => open("moonbase", i)}
+
/>
+
))
+
);
+
}
+23 -5
packages/core-extensions/src/moonbase/webpackModules/stores.ts
···
this.emitChange();
})
-
.then(natives!.checkForMoonlightUpdate)
+
.then(() =>
+
this.getExtensionConfigRaw("moonbase", "updateChecking", true)
+
? natives!.checkForMoonlightUpdate()
+
: new Promise<null>((resolve) => resolve(null))
+
)
.then((version) => {
this.newVersion = version;
this.emitChange();
···
return cfg.config?.[key] ?? clonedDefaultValue;
}
+
getExtensionConfigRaw<T>(
+
id: string,
+
key: string,
+
defaultValue: T | undefined
+
): T | undefined {
+
const cfg = this.config.extensions[id];
+
+
if (cfg == null || typeof cfg === "boolean") return defaultValue;
+
return cfg.config?.[key] ?? defaultValue;
+
}
+
getExtensionConfigName(uniqueId: number, key: string) {
const ext = this.getExtension(uniqueId);
return ext.manifest.settings?.[key]?.displayName ?? key;
···
return ext.manifest.settings?.[key]?.description;
}
-
setExtensionConfig(uniqueId: number, key: string, value: any) {
-
const ext = this.getExtension(uniqueId);
-
const oldConfig = this.config.extensions[ext.id];
+
setExtensionConfig(id: string, key: string, value: any) {
+
const oldConfig = this.config.extensions[id];
const newConfig =
typeof oldConfig === "boolean"
? {
···
config: { ...(oldConfig?.config ?? {}), [key]: value }
};
-
this.config.extensions[ext.id] = newConfig;
+
this.config.extensions[id] = newConfig;
this.modified = this.isModified();
this.emitChange();
}
···
this.installing = false;
this.emitChange();
+
}
+
+
async updateMoonlight() {
+
await natives.updateMoonlight();
}
getConfigOption<K extends keyof Config>(key: K): Config[K] {
+18
packages/core-extensions/src/moonbase/webpackModules/ui/config/index.tsx
···
export default function ConfigPage() {
return (
<>
+
<FormSwitch
+
className={Margins.marginTop20}
+
value={MoonbaseSettingsStore.getExtensionConfigRaw<boolean>(
+
"moonbase",
+
"updateChecking",
+
true
+
)}
+
onChange={(value: boolean) => {
+
MoonbaseSettingsStore.setExtensionConfig(
+
"moonbase",
+
"updateChecking",
+
value
+
);
+
}}
+
note="Checks for updates to moonlight"
+
>
+
Automatic update checking
+
</FormSwitch>
<FormItem title="Repositories">
<FormText className={Margins.marginBottom4}>
A list of remote repositories to display extensions from
+12 -6
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/index.tsx
···
)[0];
export default function ExtensionsPage() {
-
const moonbaseId = MoonbaseSettingsStore.getExtensionUniqueId("moonbase")!;
const { extensions, savedFilter } = useStateFromStoresObject(
[MoonbaseSettingsStore],
() => {
return {
extensions: MoonbaseSettingsStore.extensions,
-
savedFilter: MoonbaseSettingsStore.getExtensionConfig(
-
moonbaseId,
-
"filter"
+
savedFilter: MoonbaseSettingsStore.getExtensionConfigRaw<number>(
+
"moonbase",
+
"filter",
+
defaultFilter
)
};
}
···
const [query, setQuery] = React.useState("");
let filter: Filter, setFilter: (filter: Filter) => void;
-
if (moonlight.getConfigOption<boolean>("moonbase", "saveFilter")) {
+
if (
+
MoonbaseSettingsStore.getExtensionConfigRaw<boolean>(
+
"moonbase",
+
"saveFilter",
+
false
+
)
+
) {
filter = savedFilter ?? defaultFilter;
setFilter = (filter) =>
-
MoonbaseSettingsStore.setExtensionConfig(moonbaseId, "filter", filter);
+
MoonbaseSettingsStore.setExtensionConfig("moonbase", "filter", filter);
} else {
const state = React.useState(defaultFilter);
filter = state[0];
+8 -8
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/settings.tsx
···
hideBorder={true}
disabled={disabled}
onChange={(value: boolean) => {
-
MoonbaseSettingsStore.setExtensionConfig(ext.uniqueId, name, value);
+
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value);
}}
note={description}
className={`${Margins.marginReset} ${Margins.marginTop20}`}
···
maxValue={castedSetting.max ?? 100}
onValueChange={(value: number) => {
const rounded = Math.max(min, Math.min(max, Math.round(value)));
-
MoonbaseSettingsStore.setExtensionConfig(ext.uniqueId, name, rounded);
+
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, rounded);
}}
/>
</FormItem>
···
value={value ?? ""}
onChange={(value: string) => {
if (disabled) return;
-
MoonbaseSettingsStore.setExtensionConfig(ext.uniqueId, name, value);
+
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value);
}}
/>
</FormItem>
···
className={"moonbase-resizeable"}
onChange={(value: string) => {
if (disabled) return;
-
MoonbaseSettingsStore.setExtensionConfig(ext.uniqueId, name, value);
+
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value);
}}
/>
</FormItem>
···
)}
onChange={(value: string) => {
if (disabled) return;
-
MoonbaseSettingsStore.setExtensionConfig(ext.uniqueId, name, value);
+
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value);
}}
/>
</FormItem>
···
onChange: (value: string) => {
if (disabled) return;
MoonbaseSettingsStore.setExtensionConfig(
-
ext.uniqueId,
+
ext.id,
name,
Array.from(value)
);
···
const entries = value ?? [];
const updateConfig = () =>
-
MoonbaseSettingsStore.setExtensionConfig(ext.uniqueId, name, entries);
+
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, entries);
return (
<FormItem className={Margins.marginTop20} title={displayName}>
···
const entries = Object.entries(value ?? {});
const updateConfig = () =>
MoonbaseSettingsStore.setExtensionConfig(
-
ext.uniqueId,
+
ext.id,
name,
Object.fromEntries(entries)
);
+3
packages/core-extensions/src/moonbase/webpackModules/ui/index.tsx
···
import ExtensionsPage from "./extensions";
import ConfigPage from "./config";
+
import Update from "./update";
const { Divider } = spacepack.findByCode(".forumOrHome]:")[0].exports.Z;
const TitleBarClasses = spacepack.findByCode("iconWrapper:", "children:")[0]
···
))}
</TabBar>
</div>
+
+
<Update />
{React.createElement(pages[subsection].element)}
</>
+84
packages/core-extensions/src/moonbase/webpackModules/ui/update.tsx
···
+
import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux";
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
+
import * as Components from "@moonlight-mod/wp/discord/components/common/index";
+
import React from "@moonlight-mod/wp/react";
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
+
import Flex from "@moonlight-mod/wp/discord/uikit/Flex";
+
+
enum UpdateState {
+
Ready,
+
Working,
+
Installed,
+
Failed
+
}
+
+
const { ThemeDarkIcon, Text, Button } = Components;
+
const Margins = spacepack.require("discord/styles/shared/Margins.css");
+
const HelpMessageClasses = spacepack.findByExports("positive", "iconDiv")[0]
+
.exports;
+
+
const logger = moonlight.getLogger("moonbase/ui/update");
+
+
const strings: Record<UpdateState, string> = {
+
[UpdateState.Ready]: "A new version of moonlight is available.",
+
[UpdateState.Working]: "Updating moonlight...",
+
[UpdateState.Installed]: "Updated. Restart Discord to apply changes.",
+
[UpdateState.Failed]:
+
"Failed to update moonlight. Please use the installer instead."
+
};
+
+
export default function Update() {
+
const [state, setState] = React.useState(UpdateState.Ready);
+
const { newVersion } = useStateFromStores([MoonbaseSettingsStore], () => ({
+
newVersion: MoonbaseSettingsStore.newVersion
+
}));
+
+
if (newVersion == null) return null;
+
+
// reimpl of HelpMessage but with a custom icon
+
return (
+
<div
+
className={`${Margins.marginBottom20} ${HelpMessageClasses.info} ${HelpMessageClasses.container} moonbase-update-section`}
+
>
+
<Flex direction={Flex.Direction.HORIZONTAL}>
+
<div
+
className={HelpMessageClasses.iconDiv}
+
style={{
+
alignItems: "center"
+
}}
+
>
+
<ThemeDarkIcon
+
size="sm"
+
color="currentColor"
+
className={HelpMessageClasses.icon}
+
/>
+
</div>
+
+
<Text
+
variant="text-sm/medium"
+
color="currentColor"
+
className={HelpMessageClasses.text}
+
>
+
{strings[state]}
+
</Text>
+
</Flex>
+
+
<Button
+
look={Button.Looks.OUTLINED}
+
color={Button.Colors.CUSTOM}
+
size={Button.Sizes.TINY}
+
disabled={state !== UpdateState.Ready}
+
onClick={() => {
+
MoonbaseSettingsStore.updateMoonlight()
+
.then(() => setState(UpdateState.Installed))
+
.catch((e) => {
+
logger.error(e);
+
setState(UpdateState.Failed);
+
});
+
}}
+
>
+
Update
+
</Button>
+
</div>
+
);
+
}
-81
packages/core-extensions/src/moonbase/webpackModules/updates.ts
···
-
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
-
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
-
import Notices from "@moonlight-mod/wp/notices_notices";
-
import { MoonlightBranch } from "types/src";
-
-
// FIXME: not indexed as importable
-
const Constants = spacepack.require("discord/Constants");
-
const UserSettingsSections = spacepack.findObjectFromKey(
-
Constants,
-
"APPEARANCE_THEME_PICKER"
-
);
-
-
function plural(str: string, num: number) {
-
return `${str}${num > 1 ? "s" : ""}`;
-
}
-
-
function listener() {
-
if (MoonbaseSettingsStore.shouldShowNotice) {
-
// @ts-expect-error epic type fail
-
MoonbaseSettingsStore.removeChangeListener(listener);
-
-
const version = MoonbaseSettingsStore.newVersion;
-
const extensionUpdateCount = Object.keys(
-
MoonbaseSettingsStore.updates
-
).length;
-
const hasExtensionUpdates = extensionUpdateCount > 0;
-
-
let message;
-
-
if (version != null) {
-
message =
-
moonlightNode.branch === MoonlightBranch.NIGHTLY
-
? `A new version of moonlight is available`
-
: `moonlight ${version} is available`;
-
}
-
-
if (hasExtensionUpdates) {
-
let concat = false;
-
if (message == null) {
-
message = "";
-
} else {
-
concat = true;
-
message += ", and ";
-
}
-
message += `${extensionUpdateCount} ${concat ? "" : "moonlight "}${plural(
-
"extension",
-
extensionUpdateCount
-
)} can be updated`;
-
}
-
-
if (message != null) message += ".";
-
-
Notices.addNotice({
-
element: message,
-
color: "moonbase-updates-notice",
-
buttons: hasExtensionUpdates
-
? [
-
{
-
name: "Open Moonbase",
-
onClick: () => {
-
const { open } = spacepack.findByExports(
-
"setSection",
-
"clearSubsection"
-
)[0].exports.Z;
-
-
// settings is lazy loaded thus lazily patched
-
// FIXME: figure out a way to detect if settings has been opened
-
// alreadyjust so the transition isnt as jarring
-
open(UserSettingsSections.ACCOUNT);
-
setTimeout(() => open("moonbase", 0), 0);
-
return true;
-
}
-
}
-
]
-
: []
-
});
-
}
-
}
-
-
// @ts-expect-error epic type fail
-
MoonbaseSettingsStore.addChangeListener(listener);
+107
packages/core-extensions/src/moonbase/webpackModules/updates.tsx
···
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
+
import Notices from "@moonlight-mod/wp/notices_notices";
+
import { MoonlightBranch } from "types/src";
+
import React from "@moonlight-mod/wp/react";
+
import * as Components from "@moonlight-mod/wp/discord/components/common/index";
+
+
// FIXME: not indexed as importable
+
const Constants = spacepack.require("discord/Constants");
+
const UserSettingsSections = spacepack.findObjectFromKey(
+
Constants,
+
"APPEARANCE_THEME_PICKER"
+
);
+
+
const { ThemeDarkIcon } = Components;
+
+
function plural(str: string, num: number) {
+
return `${str}${num > 1 ? "s" : ""}`;
+
}
+
+
function listener() {
+
if (
+
MoonbaseSettingsStore.shouldShowNotice &&
+
MoonbaseSettingsStore.getExtensionConfigRaw(
+
"moonbase",
+
"updateBanner",
+
true
+
)
+
) {
+
// @ts-expect-error epic type fail
+
MoonbaseSettingsStore.removeChangeListener(listener);
+
+
const version = MoonbaseSettingsStore.newVersion;
+
const extensionUpdateCount = Object.keys(
+
MoonbaseSettingsStore.updates
+
).length;
+
const hasExtensionUpdates = extensionUpdateCount > 0;
+
+
let message;
+
+
if (version != null) {
+
message =
+
moonlightNode.branch === MoonlightBranch.NIGHTLY
+
? `A new version of moonlight is available`
+
: `moonlight ${version} is available`;
+
}
+
+
if (hasExtensionUpdates) {
+
let concat = false;
+
if (message == null) {
+
message = "";
+
} else {
+
concat = true;
+
message += ", and ";
+
}
+
message += `${extensionUpdateCount} ${concat ? "" : "moonlight "}${plural(
+
"extension",
+
extensionUpdateCount
+
)} can be updated`;
+
}
+
+
if (message != null) message += ".";
+
+
Notices.addNotice({
+
element: (
+
<div className="moonbase-updates-notice_text-wrapper">
+
<ThemeDarkIcon size="sm" color="currentColor" />
+
{message}
+
</div>
+
),
+
color: "moonbase-updates-notice",
+
buttons: [
+
{
+
name: "Open Moonbase",
+
onClick: () => {
+
const { open } = spacepack.findByExports(
+
"setSection",
+
"clearSubsection"
+
)[0].exports.Z;
+
+
// settings is lazy loaded thus lazily patched
+
// FIXME: figure out a way to detect if settings has been opened
+
// alreadyjust so the transition isnt as jarring
+
open(UserSettingsSections.ACCOUNT);
+
setTimeout(() => {
+
if (
+
MoonbaseSettingsStore.getExtensionConfigRaw<boolean>(
+
"moonbase",
+
"sections",
+
false
+
)
+
) {
+
open("moonbase-extensions");
+
} else {
+
open("moonbase", 0);
+
}
+
}, 0);
+
return true;
+
}
+
}
+
]
+
});
+
}
+
}
+
+
// @ts-expect-error epic type fail
+
MoonbaseSettingsStore.addChangeListener(listener);
+1
packages/types/src/constants.ts
···
export const distDir = "dist";
export const coreExtensionsDir = "core-extensions";
export const repoUrlFile = ".moonlight-repo-url";
+
export const installedVersionFile = ".moonlight-installed-version";
export const ipcGetOldPreloadPath = "_moonlight_getOldPreloadPath";
export const ipcGetAppData = "_moonlight_getAppData";
+8
pnpm-lock.yaml
···
'@moonlight-mod/types':
specifier: workspace:*
version: link:../types
+
nanotar:
+
specifier: ^0.1.1
+
version: 0.1.1
packages/injector:
dependencies:
···
ms@2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
+
+
nanotar@0.1.1:
+
resolution: {integrity: sha512-AiJsGsSF3O0havL1BydvI4+wR76sKT+okKRwWIaK96cZUnXqH0uNBOsHlbwZq3+m2BR1VKqHDVudl3gO4mYjpQ==}
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
···
brace-expansion: 2.0.1
ms@2.1.2: {}
+
+
nanotar@0.1.1: {}
natural-compare@1.4.0: {}