this repo has no description

Extract notice code into separate library

Changed files
+372 -238
packages
core-extensions
types
src
+8 -40
packages/core-extensions/src/moonbase/index.tsx
···
-
import { ExtensionWebpackModule, Patch } from "@moonlight-mod/types";
+
import { ExtensionWebpackModule } from "@moonlight-mod/types";
export const webpackModules: Record<string, ExtensionWebpackModule> = {
stores: {
···
updates: {
dependencies: [
-
{ id: "discord/Dispatcher" },
-
{ ext: "moonbase", id: "stores" }
-
],
-
entrypoint: true
-
},
-
-
updatesNotice: {
-
dependencies: [
{ id: "react" },
-
{ id: "discord/Dispatcher" },
-
{ id: "discord/components/common/index" },
-
{ id: "discord/packages/flux" },
-
{ ext: "spacepack", id: "spacepack" },
-
{ ext: "moonbase", id: "stores" }
-
]
-
}
-
};
-
-
export const patches: Patch[] = [
-
{
-
find: ".GUILD_RAID_NOTIFICATION:",
-
replace: {
-
match:
-
/(?<=return(\(0,.\.jsx\))\(.+?\);)case .{1,2}\..{1,3}\.GUILD_RAID_NOTIFICATION:/,
-
replacement: (orig, createElement) =>
-
`case "__moonlight_updates":return${createElement}(require("moonbase_updatesNotice").default,{});${orig}`
-
}
-
},
-
{
-
find: '"NoticeStore"',
-
replace: [
-
{
-
match: /\[.{1,2}\..{1,3}\.CONNECT_SPOTIFY\]:{/,
-
replacement: (orig: string) =>
-
`__moonlight_updates:{predicate:()=>require("moonbase_stores").MoonbaseSettingsStore.shouldShowUpdateNotice()},${orig}`
-
},
+
{ ext: "moonbase", id: "stores" },
+
{ ext: "notices", id: "notices" },
{
-
match: /=\[(.{1,2}\..{1,3}\.QUARANTINED,)/g,
-
replacement: (_, orig) => `=["__moonlight_updates",${orig}`
+
ext: "spacepack",
+
id: "spacepack"
}
-
]
+
],
+
entrypoint: true
}
-
];
+
};
export const styles = [
".moonbase-settings > :first-child { margin-top: 0px; }",
+1 -1
packages/core-extensions/src/moonbase/manifest.json
···
"tagline": "The official settings UI for moonlight",
"authors": ["Cynosphere", "NotNite"]
},
-
"dependencies": ["spacepack", "settings", "common"],
+
"dependencies": ["spacepack", "settings", "common", "notices"],
"settings": {
"sections": {
"displayName": "Split into sections",
+23 -18
packages/core-extensions/src/moonbase/node.ts
···
const logger = moonlightNode.getLogger("moonbase");
async function checkForMoonlightUpdate() {
-
if (moonlightNode.branch === "stable") {
-
const req = await fetch(
-
`https://api.github.com/repos/${githubRepo}/releases/latest`,
-
{
+
try {
+
if (moonlightNode.branch === "stable") {
+
const req = await fetch(
+
`https://api.github.com/repos/${githubRepo}/releases/latest`,
+
{
+
headers: {
+
"User-Agent": userAgent
+
}
+
}
+
);
+
const json: { name: string } = await req.json();
+
return json.name !== moonlightNode.version ? json.name : null;
+
} else if (moonlightNode.branch === "nightly") {
+
const req = await fetch(nightlyRefUrl, {
headers: {
"User-Agent": userAgent
}
-
}
-
);
-
const json: { name: string } = await req.json();
-
return json.name !== moonlightNode.version ? json.name : null;
-
} else if (moonlightNode.branch === "nightly") {
-
const req = await fetch(nightlyRefUrl, {
-
headers: {
-
"User-Agent": userAgent
-
}
-
});
-
const ref = (await req.text()).split("\n")[0];
-
return ref !== moonlightNode.version ? ref : null;
-
}
+
});
+
const ref = (await req.text()).split("\n")[0];
+
return ref !== moonlightNode.version ? ref : null;
+
}
-
return null;
+
return null;
+
} catch (e) {
+
logger.error("Error checking for moonlight update", e);
+
return null;
+
}
}
async function fetchRepositories(repos: string[]) {
+65 -69
packages/core-extensions/src/moonbase/webpackModules/stores.ts
···
const browserFS = window._moonlightBrowserFS!;
natives = {
checkForMoonlightUpdate: async () => {
-
// TODO
-
if (moonlight.branch === "stable") {
-
const req = await fetch(
-
`https://api.github.com/repos/${githubRepo}/releases/latest`,
-
{
+
try {
+
if (moonlight.branch === "stable") {
+
const req = await fetch(
+
`https://api.github.com/repos/${githubRepo}/releases/latest`,
+
{
+
headers: {
+
"User-Agent": userAgent
+
}
+
}
+
);
+
const json: { name: string } = await req.json();
+
return json.name !== moonlight.version ? json.name : null;
+
} else if (moonlight.branch === "nightly") {
+
const req = await fetch(nightlyRefUrl, {
headers: {
"User-Agent": userAgent
}
-
}
-
);
-
const json: { name: string } = await req.json();
-
return json.name !== moonlight.version ? json.name : null;
-
} else if (moonlight.branch === "nightly") {
-
const req = await fetch(nightlyRefUrl, {
-
headers: {
-
"User-Agent": userAgent
-
}
-
});
-
const ref = (await req.text()).split("\n")[0];
-
return ref !== moonlight.version ? ref : null;
-
}
+
});
+
const ref = (await req.text()).split("\n")[0];
+
return ref !== moonlight.version ? ref : null;
+
}
-
return null;
+
return null;
+
} catch (e) {
+
logger.error("Error checking for moonlight update", e);
+
return null;
+
}
},
fetchRepositories: async (repos) => {
···
installing: boolean;
newVersion: string | null;
-
private updateNotice: boolean;
+
shouldShowNotice: boolean;
extensions: { [id: number]: MoonbaseExtension };
updates: { [id: number]: { version: string; download: string } };
···
this.installing = false;
this.newVersion = null;
-
this.updateNotice = false;
+
this.shouldShowNotice = false;
this.extensions = {};
this.updates = {};
···
};
}
-
natives!.fetchRepositories(this.config.repositories).then((ret) => {
-
for (const [repo, exts] of Object.entries(ret)) {
-
try {
-
for (const ext of exts) {
-
const level = ext.apiLevel ?? 1;
-
if (level !== window.moonlight.apiLevel) continue;
+
natives!
+
.fetchRepositories(this.config.repositories)
+
.then((ret) => {
+
for (const [repo, exts] of Object.entries(ret)) {
+
try {
+
for (const ext of exts) {
+
const level = ext.apiLevel ?? 1;
+
if (level !== window.moonlight.apiLevel) continue;
-
const uniqueId = this.extensionIndex++;
-
const extensionData = {
-
id: ext.id,
-
uniqueId,
-
manifest: ext,
-
source: { type: ExtensionLoadSource.Normal, url: repo },
-
state: ExtensionState.NotDownloaded
-
};
+
const uniqueId = this.extensionIndex++;
+
const extensionData = {
+
id: ext.id,
+
uniqueId,
+
manifest: ext,
+
source: { type: ExtensionLoadSource.Normal, url: repo },
+
state: ExtensionState.NotDownloaded
+
};
-
if (this.alreadyExists(extensionData)) {
-
if (this.hasUpdate(extensionData)) {
-
this.updates[uniqueId] = {
-
version: ext.version!,
-
download: ext.download
-
};
+
if (this.alreadyExists(extensionData)) {
+
if (this.hasUpdate(extensionData)) {
+
this.updates[uniqueId] = {
+
version: ext.version!,
+
download: ext.download
+
};
+
}
+
+
continue;
}
-
continue;
+
this.extensions[uniqueId] = extensionData;
}
-
-
this.extensions[uniqueId] = extensionData;
+
} catch (e) {
+
logger.error(`Error processing repository ${repo}`, e);
}
-
} catch (e) {
-
logger.error(`Error processing repository ${repo}`, e);
}
-
}
-
this.emitChange();
-
});
-
-
natives!.checkForMoonlightUpdate().then((version) => {
-
this.newVersion = version;
-
-
this.emitChange();
-
});
+
this.emitChange();
+
})
+
.then(natives!.checkForMoonlightUpdate)
+
.then((version) => {
+
this.newVersion = version;
+
this.emitChange();
+
})
+
.then(() => {
+
this.shouldShowNotice =
+
this.newVersion != null || Object.keys(this.updates).length > 0;
+
this.emitChange();
+
});
}
private alreadyExists(ext: MoonbaseExtension) {
···
this.modified = false;
this.config = this.clone(this.origConfig);
this.emitChange();
-
}
-
-
showUpdateNotice() {
-
this.updateNotice = true;
-
//this.emitChange();
-
}
-
-
dismissUpdateNotice() {
-
this.updateNotice = false;
-
//this.emitChange();
-
}
-
-
shouldShowUpdateNotice() {
-
return this.updateNotice;
}
// Required because electron likes to make it immutable sometimes.
+74 -15
packages/core-extensions/src/moonbase/webpackModules/updates.ts
···
-
import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher";
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
+
import Notices from "@moonlight-mod/wp/notices_notices";
-
function checkForUpdates() {
-
if (
-
MoonbaseSettingsStore.newVersion != null ||
-
Object.keys(MoonbaseSettingsStore.updates).length > 0
-
) {
-
MoonbaseSettingsStore.showUpdateNotice();
-
Dispatcher.dispatch({
-
type: "NOTICE_SHOW",
-
notice: { type: "__moonlight_updates" }
+
// 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 === "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;
+
}
+
}
+
]
+
: []
});
}
-
-
Dispatcher.unsubscribe("CONNECTION_OPEN", checkForUpdates);
-
Dispatcher.unsubscribe("CONNECTION_OPEN_SUPPLEMENTAL", checkForUpdates);
}
-
Dispatcher.subscribe("CONNECTION_OPEN", checkForUpdates);
-
Dispatcher.subscribe("CONNECTION_OPEN_SUPPLEMENTAL", checkForUpdates);
+
// @ts-expect-error epic type fail
+
MoonbaseSettingsStore.addChangeListener(listener);
-95
packages/core-extensions/src/moonbase/webpackModules/updatesNotice.tsx
···
-
import React from "@moonlight-mod/wp/react";
-
import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher";
-
import * as Components from "@moonlight-mod/wp/discord/components/common/index";
-
import { useStateFromStoresObject } from "@moonlight-mod/wp/discord/packages/flux";
-
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
-
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
-
-
// FIXME: not indexed as importable
-
const Constants = spacepack.require("discord/Constants");
-
const UserSettingsSections = spacepack.findObjectFromKey(
-
Constants,
-
"APPEARANCE_THEME_PICKER"
-
);
-
-
// FIXME: types
-
const { Notice, NoticeCloseButton, PrimaryCTANoticeButton } = Components;
-
-
const { open } = spacepack.findByExports("setSection", "clearSubsection")[0]
-
.exports.Z;
-
-
function dismiss() {
-
MoonbaseSettingsStore.dismissUpdateNotice();
-
Dispatcher.dispatch({
-
type: "NOTICE_DISMISS"
-
});
-
}
-
-
function openMoonbase() {
-
dismiss();
-
// settings is lazy loaded thus lazily patched
-
// FIXME: figure out a way to detect if settings has been opened already
-
// just so the transition isnt as jarring
-
open(UserSettingsSections.ACCOUNT);
-
setTimeout(() => open("moonbase", 0), 0);
-
}
-
-
function plural(str: string, num: number) {
-
return `${str}${num > 1 ? "s" : ""}`;
-
}
-
-
export default function MoonbaseUpdatesNotice() {
-
const { version, extensionUpdateCount } = useStateFromStoresObject(
-
[MoonbaseSettingsStore],
-
() => ({
-
version: MoonbaseSettingsStore.newVersion,
-
extensionUpdateCount: Object.keys(MoonbaseSettingsStore.updates).length
-
})
-
);
-
const hasExtensionUpdates = extensionUpdateCount > 0;
-
-
let message;
-
-
if (version != null) {
-
message =
-
moonlightNode.branch === "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 += ".";
-
-
let openButton;
-
-
if (hasExtensionUpdates)
-
openButton = (
-
<PrimaryCTANoticeButton
-
onClick={openMoonbase}
-
noticeType="__moonlight_updates"
-
>
-
Open Moonbase
-
</PrimaryCTANoticeButton>
-
);
-
-
return (
-
<Notice color="moonbase-updates-notice">
-
<NoticeCloseButton onClick={dismiss} noticeType="__moonlight_updates" />
-
{message}
-
{openButton}
-
</Notice>
-
);
-
}
+46
packages/core-extensions/src/notices/index.ts
···
+
import type { ExtensionWebpackModule, Patch } from "@moonlight-mod/types";
+
+
export const patches: Patch[] = [
+
{
+
find: ".GUILD_RAID_NOTIFICATION:",
+
replace: {
+
match:
+
/(?<=return(\(0,.\.jsx\))\(.+?\);)case .{1,2}\..{1,3}\.GUILD_RAID_NOTIFICATION:/,
+
replacement: (orig, createElement) =>
+
`case "__moonlight_notice":return${createElement}(require("notices_component").default,{});${orig}`
+
}
+
},
+
{
+
find: '"NoticeStore"',
+
replace: [
+
{
+
match: /\[.{1,2}\..{1,3}\.CONNECT_SPOTIFY\]:{/,
+
replacement: (orig: string) =>
+
`__moonlight_notice:{predicate:()=>require("notices_notices").default.shouldShowNotice()},${orig}`
+
},
+
{
+
match: /=\[(.{1,2}\..{1,3}\.QUARANTINED,)/g,
+
replacement: (_, orig) => `=["__moonlight_notice",${orig}`
+
}
+
]
+
}
+
];
+
+
export const webpackModules: Record<string, ExtensionWebpackModule> = {
+
notices: {
+
dependencies: [
+
{ id: "discord/packages/flux" },
+
{ id: "discord/Dispatcher" }
+
]
+
},
+
+
component: {
+
dependencies: [
+
{ id: "react" },
+
{ id: "discord/Dispatcher" },
+
{ id: "discord/components/common/index" },
+
{ id: "discord/packages/flux" },
+
{ ext: "notices", id: "notices" }
+
]
+
}
+
};
+10
packages/core-extensions/src/notices/manifest.json
···
+
{
+
"id": "notices",
+
"apiLevel": 2,
+
"meta": {
+
"name": "Notices",
+
"tagline": "An API for adding notices at the top of the page",
+
"authors": ["Cynosphere", "NotNite"],
+
"tags": ["library"]
+
}
+
}
+56
packages/core-extensions/src/notices/webpackModules/component.tsx
···
+
import React from "@moonlight-mod/wp/react";
+
import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher";
+
import * as Components from "@moonlight-mod/wp/discord/components/common/index";
+
import { useStateFromStoresObject } from "@moonlight-mod/wp/discord/packages/flux";
+
import NoticesStore from "@moonlight-mod/wp/notices_notices";
+
import type { Notice } from "@moonlight-mod/types/coreExtensions/notices";
+
+
// FIXME: types
+
const { Notice, NoticeCloseButton, PrimaryCTANoticeButton } = Components;
+
+
function popAndDismiss(notice: Notice) {
+
NoticesStore.popNotice();
+
if (notice?.onDismiss) {
+
notice.onDismiss();
+
}
+
if (!NoticesStore.shouldShowNotice()) {
+
Dispatcher.dispatch({
+
type: "NOTICE_DISMISS"
+
});
+
}
+
}
+
+
export default function UpdateNotice() {
+
const { notice } = useStateFromStoresObject([NoticesStore], () => ({
+
notice: NoticesStore.getCurrentNotice()
+
}));
+
+
if (notice == null) return <></>;
+
+
return (
+
<Notice color={notice.color}>
+
{notice.element}
+
+
{(notice.showClose ?? true) && (
+
<NoticeCloseButton
+
onClick={() => popAndDismiss(notice)}
+
noticeType="__moonlight_notice"
+
/>
+
)}
+
+
{(notice.buttons ?? []).map((button) => (
+
<PrimaryCTANoticeButton
+
key={button.name}
+
onClick={() => {
+
if (button.onClick()) {
+
popAndDismiss(notice);
+
}
+
}}
+
noticeType="__moonlight_notice"
+
>
+
{button.name}
+
</PrimaryCTANoticeButton>
+
))}
+
</Notice>
+
);
+
}
+58
packages/core-extensions/src/notices/webpackModules/notices.ts
···
+
import { Store } from "@moonlight-mod/wp/discord/packages/flux";
+
import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher";
+
import type {
+
Notice,
+
Notices
+
} from "@moonlight-mod/types/coreExtensions/notices";
+
+
// very lazy way of doing this, FIXME
+
let open = false;
+
+
class NoticesStore extends Store<any> {
+
private notices: Notice[] = [];
+
+
constructor() {
+
super(Dispatcher);
+
}
+
+
addNotice(notice: Notice) {
+
this.notices.push(notice);
+
if (open && this.notices.length !== 0) {
+
Dispatcher.dispatch({
+
type: "NOTICE_SHOW",
+
notice: { type: "__moonlight_notice" }
+
});
+
}
+
this.emitChange();
+
}
+
+
popNotice() {
+
this.notices.shift();
+
this.emitChange();
+
}
+
+
getCurrentNotice() {
+
return this.notices.length > 0 ? this.notices[0] : null;
+
}
+
+
shouldShowNotice() {
+
return this.notices.length > 0;
+
}
+
}
+
+
const store: Notices = new NoticesStore();
+
+
function showNotice() {
+
open = true;
+
if (store.shouldShowNotice()) {
+
Dispatcher.dispatch({
+
type: "NOTICE_SHOW",
+
notice: { type: "__moonlight_notice" }
+
});
+
}
+
}
+
+
Dispatcher.subscribe("CONNECTION_OPEN", showNotice);
+
Dispatcher.subscribe("CONNECTION_OPEN_SUPPLEMENTAL", showNotice);
+
+
export default store;
+1
packages/types/src/coreExtensions.ts
···
export * as Settings from "./coreExtensions/settings";
export * as Markdown from "./coreExtensions/markdown";
export * as ContextMenu from "./coreExtensions/contextMenu";
+
export * as Notices from "./coreExtensions/notices";
+21
packages/types/src/coreExtensions/notices.ts
···
+
import type { Store } from "@moonlight-mod/mappings/discord/packages/flux";
+
+
export type NoticeButton = {
+
name: string;
+
onClick: () => boolean; // return true to dismiss the notice after the button is clicked
+
};
+
+
export type Notice = {
+
element: React.ReactNode;
+
color?: string;
+
showClose?: boolean;
+
buttons?: NoticeButton[];
+
onDismiss?: () => void;
+
};
+
+
export type Notices = Store<any> & {
+
addNotice: (notice: Notice) => void;
+
popNotice: () => void;
+
getCurrentNotice: () => Notice | null;
+
shouldShowNotice: () => boolean;
+
};
+3
packages/types/src/discord/require.ts
···
import { Markdown } from "../coreExtensions/markdown";
import { Settings } from "../coreExtensions/settings";
import { Spacepack } from "../coreExtensions/spacepack";
+
import { Notices } from "../coreExtensions/notices";
declare function WebpackRequire(id: string): any;
···
declare function WebpackRequire(id: "contextMenu_contextMenu"): ContextMenu;
declare function WebpackRequire(id: "markdown_markdown"): Markdown;
+
+
declare function WebpackRequire(id: "notices_notices"): Notices;
declare function WebpackRequire(id: "settings_settings"): {
Settings: Settings;
+6
packages/types/src/import.d.ts
···
export = Markdown;
}
+
declare module "@moonlight-mod/wp/notices_notices" {
+
import { CoreExtensions } from "@moonlight-mod/types";
+
const Notices: CoreExtensions.Notices.Notices;
+
export = Notices;
+
}
+
declare module "@moonlight-mod/wp/settings_settings" {
import { CoreExtensions } from "@moonlight-mod/types";
export const Settings: CoreExtensions.Settings.Settings;