this repo has no description

Merge pull request #32 from redstonekasi/moonbase-wp

Allow for folders in extension webpack modules

+17 -5
build.mjs
···
const wpModulesDir = `packages/core-extensions/src/${ext}/webpackModules`;
if (fs.existsSync(wpModulesDir) && side === "index") {
-
const wpModules = fs.readdirSync(wpModulesDir);
-
for (const wpModule of wpModules) {
-
entryPoints.push(
-
`packages/core-extensions/src/${ext}/webpackModules/${wpModule}`
-
);
+
const wpModules = fs.opendirSync(wpModulesDir);
+
for await (const wpModule of wpModules) {
+
if (wpModule.isFile()) {
+
entryPoints.push(
+
`packages/core-extensions/src/${ext}/webpackModules/${wpModule.name}`
+
);
+
} else {
+
for (const fileExt of ["ts", "tsx"]) {
+
const path = `packages/core-extensions/src/${ext}/webpackModules/${wpModule.name}/index.${fileExt}`;
+
if (fs.existsSync(path)) {
+
entryPoints.push({
+
in: path,
+
out: `webpackModules/${wpModule.name}`
+
});
+
}
+
}
+
}
}
}
-1
env.d.ts
···
-
/// <reference types="./packages/types/src/index" />
+12 -78
packages/core-extensions/src/moonbase/index.tsx
···
-
import { ExtensionWebExports, WebpackRequireType } from "@moonlight-mod/types";
-
import extensionsPage from "./ui/extensions";
-
import configPage from "./ui/config";
+
import { ExtensionWebExports } from "@moonlight-mod/types";
import { CircleXIconSVG, DownloadIconSVG, TrashIconSVG } from "./types";
-
import ui from "./ui";
-
-
export const pageModules: (require: WebpackRequireType) => Record<
-
string,
-
{
-
name: string;
-
element: React.FunctionComponent;
-
}
-
> = (require) => ({
-
extensions: {
-
name: "Extensions",
-
element: extensionsPage(require)
-
},
-
config: {
-
name: "Config",
-
element: configPage(require)
-
}
-
});
export const webpackModules: ExtensionWebExports["webpackModules"] = {
stores: {
···
]
},
-
moonbase: {
+
ui: {
dependencies: [
{ ext: "spacepack", id: "spacepack" },
-
{ ext: "settings", id: "settings" },
{ ext: "common", id: "react" },
{ ext: "common", id: "components" },
{ ext: "moonbase", id: "stores" },
···
"removeButtonContainer:",
'"Missing channel in Channel.openChannelContextMenu"',
".default.HEADER_BAR"
-
],
-
entrypoint: true,
-
run: (module, exports, require) => {
-
const settings = require("settings_settings").Settings;
-
const React = require("common_react");
-
const spacepack = require("spacepack_spacepack").spacepack;
-
const { MoonbaseSettingsStore } =
-
require("moonbase_stores") as typeof import("./webpackModules/stores");
-
-
const addSection = (name: string, element: React.FunctionComponent) => {
-
settings.addSection(name, name, element, null, -2, {
-
stores: [MoonbaseSettingsStore],
-
element: () => {
-
// Require it here because lazy loading SUX
-
const SettingsNotice =
-
spacepack.findByCode("onSaveButtonColor")[0].exports.default;
-
return (
-
<SettingsNotice
-
submitting={MoonbaseSettingsStore.submitting}
-
onReset={() => {
-
MoonbaseSettingsStore.reset();
-
}}
-
onSave={() => {
-
MoonbaseSettingsStore.writeConfig();
-
}}
-
/>
-
);
-
}
-
});
-
};
+
]
+
},
-
if (moonlight.getConfigOption<boolean>("moonbase", "sections")) {
-
const pages = pageModules(require);
-
-
const { Text } = require("common_components");
-
const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports;
-
-
settings.addHeader("Moonbase", -2);
-
for (const page of Object.values(pages)) {
-
addSection(page.name, () => (
-
<>
-
<Text
-
className={Margins.marginBottom20}
-
variant="heading-lg/semibold"
-
tag="h2"
-
>
-
Extensions
-
</Text>
-
<page.element />
-
</>
-
));
-
}
-
} else {
-
addSection("Moonbase", ui(require));
-
}
-
}
+
moonbase: {
+
dependencies: [
+
{ ext: "spacepack", id: "spacepack" },
+
{ ext: "settings", id: "settings" },
+
{ ext: "common", id: "react" },
+
{ ext: "moonbase", id: "ui" }
+
],
+
entrypoint: true
}
};
-157
packages/core-extensions/src/moonbase/ui/config/index.tsx
···
-
import { LogLevel, WebpackRequireType } from "@moonlight-mod/types";
-
import { CircleXIconSVG } from "../../types";
-
-
const logLevels = Object.values(LogLevel).filter(
-
(v) => typeof v === "string"
-
) as string[];
-
-
export default (require: WebpackRequireType) => {
-
const React = require("common_react");
-
const spacepack = require("spacepack_spacepack").spacepack;
-
const CommonComponents = require("common_components");
-
const {
-
FormDivider,
-
FormItem,
-
FormText,
-
FormSwitch,
-
TextInput,
-
Flex,
-
Button,
-
SingleSelect
-
} = CommonComponents;
-
-
const { MoonbaseSettingsStore } =
-
require("moonbase_stores") as typeof import("../../webpackModules/stores");
-
-
const FormClasses = spacepack.findByCode("dividerDefault:")[0].exports;
-
const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports;
-
-
const RemoveButtonClasses = spacepack.findByCode("removeButtonContainer")[0]
-
.exports;
-
const CircleXIcon = spacepack.findByCode(CircleXIconSVG)[0].exports.default;
-
function RemoveEntryButton({ onClick }: { onClick: () => void }) {
-
const { Tooltip, Clickable } = CommonComponents;
-
return (
-
<div className={RemoveButtonClasses.removeButtonContainer}>
-
<Tooltip text="Remove entry" position="top">
-
{(props: any) => (
-
<Clickable
-
{...props}
-
className={RemoveButtonClasses.removeButton}
-
onClick={onClick}
-
>
-
<CircleXIcon width={24} height={24} />
-
</Clickable>
-
)}
-
</Tooltip>
-
</div>
-
);
-
}
-
-
function ArrayFormItem({
-
config
-
}: {
-
config: "repositories" | "devSearchPaths";
-
}) {
-
const items = MoonbaseSettingsStore.getConfigOption(config) ?? [];
-
return (
-
<Flex
-
style={{
-
gap: "20px"
-
}}
-
direction={Flex.Direction.VERTICAL}
-
>
-
{items.map((val, i) => (
-
<div
-
key={i}
-
style={{
-
display: "grid",
-
height: "32px",
-
gap: "8px",
-
gridTemplateColumns: "1fr 32px",
-
alignItems: "center"
-
}}
-
>
-
<TextInput
-
size={TextInput.Sizes.DEFAULT}
-
value={val}
-
onChange={(newVal: string) => {
-
items[i] = newVal;
-
MoonbaseSettingsStore.setConfigOption(config, items);
-
}}
-
/>
-
<RemoveEntryButton
-
onClick={() => {
-
items.splice(i, 1);
-
MoonbaseSettingsStore.setConfigOption(config, items);
-
}}
-
/>
-
</div>
-
))}
-
-
<Button
-
look={Button.Looks.FILLED}
-
color={Button.Colors.GREEN}
-
size={Button.Sizes.SMALL}
-
style={{
-
marginTop: "10px"
-
}}
-
onClick={() => {
-
items.push("");
-
MoonbaseSettingsStore.setConfigOption(config, items);
-
}}
-
>
-
Add new entry
-
</Button>
-
</Flex>
-
);
-
}
-
-
return function ConfigPage() {
-
return (
-
<>
-
<FormItem title="Repositories">
-
<FormText className={Margins.marginBottom4}>
-
A list of remote repositories to display extensions from
-
</FormText>
-
<ArrayFormItem config="repositories" />
-
</FormItem>
-
<FormDivider className={FormClasses.dividerDefault} />
-
<FormItem
-
title="Extension search paths"
-
className={Margins.marginTop20}
-
>
-
<FormText className={Margins.marginBottom4}>
-
A list of local directories to search for built extensions
-
</FormText>
-
<ArrayFormItem config="devSearchPaths" />
-
</FormItem>
-
<FormDivider className={FormClasses.dividerDefault} />
-
<FormSwitch
-
className={Margins.marginTop20}
-
value={MoonbaseSettingsStore.getConfigOption("patchAll")}
-
onChange={(value: boolean) => {
-
MoonbaseSettingsStore.setConfigOption("patchAll", value);
-
}}
-
note="Wraps every webpack module in a function, separating them in DevTools"
-
>
-
Patch all
-
</FormSwitch>
-
<FormItem title="Log level">
-
<SingleSelect
-
autofocus={false}
-
clearable={false}
-
value={MoonbaseSettingsStore.getConfigOption("loggerLevel")}
-
options={logLevels.map((o) => ({
-
value: o.toLowerCase(),
-
label: o[0] + o.slice(1).toLowerCase()
-
}))}
-
onChange={(v) =>
-
MoonbaseSettingsStore.setConfigOption("loggerLevel", v)
-
}
-
/>
-
</FormItem>
-
</>
-
);
-
};
-
};
-223
packages/core-extensions/src/moonbase/ui/extensions/card.tsx
···
-
import WebpackRequire from "@moonlight-mod/types/discord/require";
-
import {
-
DangerIconSVG,
-
DownloadIconSVG,
-
ExtensionState,
-
TrashIconSVG
-
} from "../../types";
-
import { ExtensionLoadSource } from "@moonlight-mod/types";
-
import info from "./info";
-
import settings from "./settings";
-
-
export enum ExtensionPage {
-
Info,
-
Description,
-
Settings
-
}
-
-
export default (require: typeof WebpackRequire) => {
-
const React = require("common_react");
-
const spacepack = require("spacepack_spacepack").spacepack;
-
const CommonComponents = require("common_components");
-
const Flux = require("common_flux");
-
-
const { ExtensionInfo } = info(require);
-
const Settings = settings(require);
-
const { MoonbaseSettingsStore } =
-
require("moonbase_stores") as typeof import("../../webpackModules/stores");
-
-
const UserProfileClasses = spacepack.findByCode(
-
"tabBarContainer",
-
"topSection"
-
)[0].exports;
-
-
const DownloadIcon =
-
spacepack.findByCode(DownloadIconSVG)[0].exports.DownloadIcon;
-
const TrashIcon = spacepack.findByCode(TrashIconSVG)[0].exports.default;
-
const DangerIcon =
-
spacepack.findByCode(DangerIconSVG)[0].exports.CircleExclamationPointIcon;
-
-
const PanelButton =
-
spacepack.findByCode("Masks.PANEL_BUTTON")[0].exports.default;
-
-
return function ExtensionCard({ id }: { id: string }) {
-
const [tab, setTab] = React.useState(ExtensionPage.Info);
-
const [restartNeeded, setRestartNeeded] = React.useState(false);
-
-
const { ext, enabled, busy, update } = Flux.useStateFromStores(
-
[MoonbaseSettingsStore],
-
() => {
-
return {
-
ext: MoonbaseSettingsStore.getExtension(id),
-
enabled: MoonbaseSettingsStore.getExtensionEnabled(id),
-
busy: MoonbaseSettingsStore.busy,
-
update: MoonbaseSettingsStore.getExtensionUpdate(id)
-
};
-
}
-
);
-
-
// Why it work like that :sob:
-
if (ext == null) return <></>;
-
-
const {
-
Card,
-
CardClasses,
-
Flex,
-
Text,
-
MarkdownParser,
-
Switch,
-
TabBar,
-
Button
-
} = CommonComponents;
-
-
const tagline = ext.manifest?.meta?.tagline;
-
const settings = ext.manifest?.settings;
-
const description = ext.manifest?.meta?.description;
-
-
return (
-
<Card editable={true} className={CardClasses.card}>
-
<div className={CardClasses.cardHeader}>
-
<Flex direction={Flex.Direction.VERTICAL}>
-
<Flex direction={Flex.Direction.HORIZONTAL}>
-
<Text variant="text-md/semibold">
-
{ext.manifest?.meta?.name ?? ext.id}
-
</Text>
-
</Flex>
-
-
{tagline != null && (
-
<Text variant="text-sm/normal">
-
{MarkdownParser.parse(tagline)}
-
</Text>
-
)}
-
</Flex>
-
-
<Flex
-
direction={Flex.Direction.HORIZONTAL}
-
align={Flex.Align.END}
-
justify={Flex.Justify.END}
-
>
-
{ext.state === ExtensionState.NotDownloaded ? (
-
<Button
-
color={Button.Colors.BRAND}
-
submitting={busy}
-
onClick={() => {
-
MoonbaseSettingsStore.installExtension(id);
-
}}
-
>
-
Install
-
</Button>
-
) : (
-
<div
-
// too lazy to learn how <Flex /> works lmao
-
style={{
-
display: "flex",
-
alignItems: "center",
-
gap: "1rem"
-
}}
-
>
-
{ext.source.type === ExtensionLoadSource.Normal && (
-
<PanelButton
-
icon={TrashIcon}
-
tooltipText="Delete"
-
onClick={() => {
-
MoonbaseSettingsStore.deleteExtension(id);
-
}}
-
/>
-
)}
-
-
{update !== null && (
-
<PanelButton
-
icon={DownloadIcon}
-
tooltipText="Update"
-
onClick={() => {
-
MoonbaseSettingsStore.installExtension(id);
-
}}
-
/>
-
)}
-
-
{restartNeeded && (
-
<PanelButton
-
icon={() => (
-
<DangerIcon
-
color={CommonComponents.tokens.colors.STATUS_DANGER}
-
/>
-
)}
-
onClick={() => window.location.reload()}
-
tooltipText="You will need to reload/restart your client for this extension to work properly."
-
/>
-
)}
-
-
<Switch
-
checked={enabled}
-
onChange={() => {
-
setRestartNeeded(true);
-
MoonbaseSettingsStore.setExtensionEnabled(id, !enabled);
-
}}
-
/>
-
</div>
-
)}
-
</Flex>
-
</div>
-
-
<div className={UserProfileClasses.body}>
-
{(description != null || settings != null) && (
-
<div
-
className={UserProfileClasses.tabBarContainer}
-
style={{
-
padding: "0 10px"
-
}}
-
>
-
<TabBar
-
selectedItem={tab}
-
type="top"
-
onItemSelect={setTab}
-
className={UserProfileClasses.tabBar}
-
>
-
<TabBar.Item
-
className={UserProfileClasses.tabBarItem}
-
id={ExtensionPage.Info}
-
>
-
Info
-
</TabBar.Item>
-
-
{description != null && (
-
<TabBar.Item
-
className={UserProfileClasses.tabBarItem}
-
id={ExtensionPage.Description}
-
>
-
Description
-
</TabBar.Item>
-
)}
-
-
{settings != null && (
-
<TabBar.Item
-
className={UserProfileClasses.tabBarItem}
-
id={ExtensionPage.Settings}
-
>
-
Settings
-
</TabBar.Item>
-
)}
-
</TabBar>
-
</div>
-
)}
-
-
<Flex
-
justify={Flex.Justify.START}
-
wrap={Flex.Wrap.WRAP}
-
style={{
-
padding: "16px 16px"
-
}}
-
>
-
{tab === ExtensionPage.Info && <ExtensionInfo ext={ext} />}
-
{tab === ExtensionPage.Description && (
-
<Text variant="text-md/normal">
-
{MarkdownParser.parse(description ?? "*No description*")}
-
</Text>
-
)}
-
{tab === ExtensionPage.Settings && <Settings ext={ext} />}
-
</Flex>
-
</div>
-
</Card>
-
);
-
};
-
};
-373
packages/core-extensions/src/moonbase/ui/extensions/filterBar.tsx
···
-
import { WebpackRequireType } from "@moonlight-mod/types";
-
import { tagNames } from "./info";
-
import {
-
ArrowsUpDownIconSVG,
-
ChevronSmallDownIconSVG,
-
ChevronSmallUpIconSVG
-
} from "../../types";
-
-
export enum Filter {
-
Core = 1 << 0,
-
Normal = 1 << 1,
-
Developer = 1 << 2,
-
Enabled = 1 << 3,
-
Disabled = 1 << 4,
-
Installed = 1 << 5,
-
Repository = 1 << 6
-
}
-
export const defaultFilter = ~(~0 << 7);
-
-
export default async (require: WebpackRequireType) => {
-
const spacepack = require("spacepack_spacepack").spacepack;
-
const React = require("common_react");
-
const Flux = require("common_flux");
-
const { WindowStore } = require("common_stores");
-
-
const {
-
Button,
-
Text,
-
Heading,
-
Popout,
-
Dialog
-
} = require("common_components");
-
-
const channelModule =
-
require.m[
-
spacepack.findByCode(
-
'"Missing channel in Channel.openChannelContextMenu"'
-
)[0].id
-
].toString();
-
const moduleId = channelModule.match(/webpackId:"(.+?)"/)![1];
-
await require.el(moduleId);
-
-
const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports;
-
const SortMenuClasses = spacepack.findByCode("container:", "clearText:")[0]
-
.exports;
-
const FilterDialogClasses = spacepack.findByCode(
-
"countContainer:",
-
"tagContainer:"
-
)[0].exports;
-
const FilterBarClasses = spacepack.findByCode("tagsButtonWithCount:")[0]
-
.exports;
-
-
const TagItem = spacepack.findByCode("IncreasedActivityForumTagPill:")[0]
-
.exports.default;
-
-
const ChevronSmallDownIcon = spacepack.findByCode(ChevronSmallDownIconSVG)[0]
-
.exports.default;
-
const ChevronSmallUpIcon = spacepack.findByCode(ChevronSmallUpIconSVG)[0]
-
.exports.default;
-
const ArrowsUpDownIcon =
-
spacepack.findByCode(ArrowsUpDownIconSVG)[0].exports.default;
-
-
function toggleTag(
-
selectedTags: Set<string>,
-
setSelectedTags: (tags: Set<string>) => void,
-
tag: string
-
) {
-
const newState = new Set(selectedTags);
-
if (newState.has(tag)) newState.delete(tag);
-
else newState.add(tag);
-
setSelectedTags(newState);
-
}
-
-
function FilterButtonPopout({
-
filter,
-
setFilter,
-
closePopout
-
}: {
-
filter: Filter;
-
setFilter: (filter: Filter) => void;
-
closePopout: () => void;
-
}) {
-
const {
-
Menu,
-
MenuItem,
-
MenuGroup,
-
MenuCheckboxItem
-
} = require("common_components");
-
-
const toggleFilter = (set: Filter) =>
-
setFilter(filter & set ? filter & ~set : filter | set);
-
-
return (
-
<div className={SortMenuClasses.container}>
-
<Menu navId="sort-filter" hideScrollbar={true} onClose={closePopout}>
-
<MenuGroup label="Type">
-
<MenuCheckboxItem
-
id="t-core"
-
label="Core"
-
checked={filter & Filter.Core}
-
action={() => toggleFilter(Filter.Core)}
-
/>
-
<MenuCheckboxItem
-
id="t-normal"
-
label="Normal"
-
checked={filter & Filter.Normal}
-
action={() => toggleFilter(Filter.Normal)}
-
/>
-
<MenuCheckboxItem
-
id="t-developer"
-
label="Developer"
-
checked={filter & Filter.Developer}
-
action={() => toggleFilter(Filter.Developer)}
-
/>
-
</MenuGroup>
-
<MenuGroup label="State">
-
<MenuCheckboxItem
-
id="s-enabled"
-
label="Enabled"
-
checked={filter & Filter.Enabled}
-
action={() => toggleFilter(Filter.Enabled)}
-
/>
-
<MenuCheckboxItem
-
id="s-disabled"
-
label="Disabled"
-
checked={filter & Filter.Disabled}
-
action={() => toggleFilter(Filter.Disabled)}
-
/>
-
</MenuGroup>
-
<MenuGroup label="Location">
-
<MenuCheckboxItem
-
id="l-installed"
-
label="Installed"
-
checked={filter & Filter.Installed}
-
action={() => toggleFilter(Filter.Installed)}
-
/>
-
<MenuCheckboxItem
-
id="l-repository"
-
label="Repository"
-
checked={filter & Filter.Repository}
-
action={() => toggleFilter(Filter.Repository)}
-
/>
-
</MenuGroup>
-
<MenuGroup>
-
<MenuItem
-
id="reset-all"
-
className={SortMenuClasses.clearText}
-
label={
-
<Text variant="text-sm/medium" color="none">
-
Reset to default
-
</Text>
-
}
-
action={() => {
-
setFilter(defaultFilter);
-
closePopout();
-
}}
-
/>
-
</MenuGroup>
-
</Menu>
-
</div>
-
);
-
}
-
-
function TagButtonPopout({
-
selectedTags,
-
setSelectedTags,
-
setPopoutRef,
-
closePopout
-
}: any) {
-
return (
-
<Dialog ref={setPopoutRef} className={FilterDialogClasses.container}>
-
<div className={FilterDialogClasses.header}>
-
<div className={FilterDialogClasses.headerLeft}>
-
<Heading
-
color="interactive-normal"
-
variant="text-xs/bold"
-
className={FilterDialogClasses.headerText}
-
>
-
Select tags
-
</Heading>
-
<div className={FilterDialogClasses.countContainer}>
-
<Text
-
className={FilterDialogClasses.countText}
-
color="none"
-
variant="text-xs/medium"
-
>
-
{selectedTags.size}
-
</Text>
-
</div>
-
</div>
-
</div>
-
<div className={FilterDialogClasses.tagContainer}>
-
{Object.keys(tagNames).map((tag) => (
-
<TagItem
-
key={tag}
-
className={FilterDialogClasses.tag}
-
tag={{ name: tagNames[tag as keyof typeof tagNames] }}
-
onClick={() => toggleTag(selectedTags, setSelectedTags, tag)}
-
selected={selectedTags.has(tag)}
-
/>
-
))}
-
</div>
-
<div className={FilterDialogClasses.separator} />
-
<Button
-
look={Button.Looks.LINK}
-
size={Button.Sizes.MIN}
-
color={Button.Colors.CUSTOM}
-
className={FilterDialogClasses.clear}
-
onClick={() => {
-
setSelectedTags(new Set());
-
closePopout();
-
}}
-
>
-
<Text variant="text-sm/medium" color="text-link">
-
Clear all
-
</Text>
-
</Button>
-
</Dialog>
-
);
-
}
-
-
return function FilterBar({
-
filter,
-
setFilter,
-
selectedTags,
-
setSelectedTags
-
}: {
-
filter: Filter;
-
setFilter: (filter: Filter) => void;
-
selectedTags: Set<string>;
-
setSelectedTags: (tags: Set<string>) => void;
-
}) {
-
const windowSize = Flux.useStateFromStores([WindowStore], () =>
-
WindowStore.windowSize()
-
);
-
-
const tagsContainer = React.useRef<HTMLDivElement>(null);
-
const tagListInner = React.useRef<HTMLDivElement>(null);
-
const [tagsButtonOffset, setTagsButtonOffset] = React.useState(0);
-
React.useLayoutEffect(() => {
-
if (tagsContainer.current === null || tagListInner.current === null)
-
return;
-
const { left: containerX, top: containerY } =
-
tagsContainer.current.getBoundingClientRect();
-
let offset = 0;
-
for (const child of tagListInner.current.children) {
-
const {
-
right: childX,
-
top: childY,
-
height
-
} = child.getBoundingClientRect();
-
if (childY - containerY > height) break;
-
const newOffset = childX - containerX;
-
if (newOffset > offset) {
-
offset = newOffset;
-
}
-
}
-
setTagsButtonOffset(offset);
-
}, [windowSize]);
-
-
return (
-
<div
-
ref={tagsContainer}
-
style={{
-
paddingTop: "12px"
-
}}
-
className={`${FilterBarClasses.tagsContainer} ${Margins.marginBottom8}`}
-
>
-
<Popout
-
renderPopout={({ closePopout }: any) => (
-
<FilterButtonPopout
-
filter={filter}
-
setFilter={setFilter}
-
closePopout={closePopout}
-
/>
-
)}
-
position="bottom"
-
align="left"
-
>
-
{(props: any, { isShown }: { isShown: boolean }) => (
-
<Button
-
{...props}
-
size={Button.Sizes.MIN}
-
color={Button.Colors.CUSTOM}
-
className={FilterBarClasses.sortDropdown}
-
innerClassName={FilterBarClasses.sortDropdownInner}
-
>
-
<ArrowsUpDownIcon />
-
<Text
-
className={FilterBarClasses.sortDropdownText}
-
variant="text-sm/medium"
-
color="interactive-normal"
-
>
-
Sort & filter
-
</Text>
-
{isShown ? (
-
<ChevronSmallUpIcon size={20} />
-
) : (
-
<ChevronSmallDownIcon size={20} />
-
)}
-
</Button>
-
)}
-
</Popout>
-
<div className={FilterBarClasses.divider} />
-
<div className={FilterBarClasses.tagList}>
-
<div ref={tagListInner} className={FilterBarClasses.tagListInner}>
-
{Object.keys(tagNames).map((tag) => (
-
<TagItem
-
key={tag}
-
className={FilterBarClasses.tag}
-
tag={{ name: tagNames[tag as keyof typeof tagNames] }}
-
onClick={() => toggleTag(selectedTags, setSelectedTags, tag)}
-
selected={selectedTags.has(tag)}
-
/>
-
))}
-
</div>
-
</div>
-
<Popout
-
renderPopout={({ setPopoutRef, closePopout }: any) => (
-
<TagButtonPopout
-
selectedTags={selectedTags}
-
setSelectedTags={setSelectedTags}
-
setPopoutRef={setPopoutRef}
-
closePopout={closePopout}
-
/>
-
)}
-
position="bottom"
-
align="right"
-
>
-
{(props: any, { isShown }: { isShown: boolean }) => (
-
<Button
-
{...props}
-
size={Button.Sizes.MIN}
-
color={Button.Colors.CUSTOM}
-
style={{
-
left: tagsButtonOffset
-
}}
-
// TODO: Use Discord's class name utility
-
className={`${FilterBarClasses.tagsButton} ${
-
selectedTags.size > 0
-
? FilterBarClasses.tagsButtonWithCount
-
: ""
-
}`}
-
innerClassName={FilterBarClasses.tagsButtonInner}
-
>
-
{selectedTags.size > 0 ? (
-
<div
-
style={{ boxSizing: "content-box" }}
-
className={FilterBarClasses.countContainer}
-
>
-
<Text
-
className={FilterBarClasses.countText}
-
color="none"
-
variant="text-xs/medium"
-
>
-
{selectedTags.size}
-
</Text>
-
</div>
-
) : (
-
<>All</>
-
)}
-
{isShown ? (
-
<ChevronSmallUpIcon size={20} />
-
) : (
-
<ChevronSmallDownIcon size={20} />
-
)}
-
</Button>
-
)}
-
</Popout>
-
</div>
-
);
-
};
-
};
-118
packages/core-extensions/src/moonbase/ui/extensions/index.tsx
···
-
import {
-
ExtensionLoadSource,
-
ExtensionTag,
-
WebpackRequireType
-
} from "@moonlight-mod/types";
-
import { ExtensionState } from "../../types";
-
import filterBar, { Filter, defaultFilter } from "./filterBar";
-
import card from "./card";
-
-
export default (require: WebpackRequireType) => {
-
const React = require("common_react");
-
const spacepack = require("spacepack_spacepack").spacepack;
-
const Flux = require("common_flux");
-
-
const { MoonbaseSettingsStore } =
-
require("moonbase_stores") as typeof import("../../webpackModules/stores");
-
-
const ExtensionCard = card(require);
-
const FilterBar = React.lazy(() =>
-
filterBar(require).then((c) => ({ default: c }))
-
);
-
-
const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports;
-
const SearchBar = spacepack.findByCode("Messages.SEARCH", "hideSearchIcon")[0]
-
.exports.default;
-
-
return function ExtensionsPage() {
-
const { extensions, savedFilter } = Flux.useStateFromStoresObject(
-
[MoonbaseSettingsStore],
-
() => {
-
return {
-
extensions: MoonbaseSettingsStore.extensions,
-
savedFilter: MoonbaseSettingsStore.getExtensionConfig(
-
"moonbase",
-
"filter"
-
)
-
};
-
}
-
);
-
-
const [query, setQuery] = React.useState("");
-
-
let filter: Filter, setFilter: (filter: Filter) => void;
-
if (moonlight.getConfigOption<boolean>("moonbase", "saveFilter")) {
-
filter = savedFilter ?? defaultFilter;
-
setFilter = (filter) =>
-
MoonbaseSettingsStore.setExtensionConfig("moonbase", "filter", filter);
-
} else {
-
const state = React.useState(defaultFilter);
-
filter = state[0];
-
setFilter = state[1];
-
}
-
const [selectedTags, setSelectedTags] = React.useState(new Set<string>());
-
const sorted = Object.values(extensions).sort((a, b) => {
-
const aName = a.manifest.meta?.name ?? a.id;
-
const bName = b.manifest.meta?.name ?? b.id;
-
return aName.localeCompare(bName);
-
});
-
-
const filtered = sorted.filter(
-
(ext) =>
-
(ext.manifest.meta?.name?.toLowerCase().includes(query) ||
-
ext.manifest.meta?.tagline?.toLowerCase().includes(query) ||
-
ext.manifest.meta?.description?.toLowerCase().includes(query)) &&
-
[...selectedTags.values()].every(
-
(tag) => ext.manifest.meta?.tags?.includes(tag as ExtensionTag)
-
) &&
-
// This seems very bad, sorry
-
!(
-
(!(filter & Filter.Core) &&
-
ext.source.type === ExtensionLoadSource.Core) ||
-
(!(filter & Filter.Normal) &&
-
ext.source.type === ExtensionLoadSource.Normal) ||
-
(!(filter & Filter.Developer) &&
-
ext.source.type === ExtensionLoadSource.Developer) ||
-
(!(filter & Filter.Enabled) &&
-
MoonbaseSettingsStore.getExtensionEnabled(ext.id)) ||
-
(!(filter & Filter.Disabled) &&
-
!MoonbaseSettingsStore.getExtensionEnabled(ext.id)) ||
-
(!(filter & Filter.Installed) &&
-
ext.state !== ExtensionState.NotDownloaded) ||
-
(!(filter & Filter.Repository) &&
-
ext.state === ExtensionState.NotDownloaded)
-
)
-
);
-
-
return (
-
<>
-
<SearchBar
-
size={SearchBar.Sizes.MEDIUM}
-
query={query}
-
onChange={(v: string) => setQuery(v.toLowerCase())}
-
onClear={() => setQuery("")}
-
autoFocus={true}
-
autoComplete="off"
-
inputProps={{
-
autoCapitalize: "none",
-
autoCorrect: "off",
-
spellCheck: "false"
-
}}
-
/>
-
<React.Suspense
-
fallback={<div className={Margins.marginBottom20}></div>}
-
>
-
<FilterBar
-
filter={filter}
-
setFilter={setFilter}
-
selectedTags={selectedTags}
-
setSelectedTags={setSelectedTags}
-
/>
-
</React.Suspense>
-
{filtered.map((ext) => (
-
<ExtensionCard id={ext.id} key={ext.id} />
-
))}
-
</>
-
);
-
};
-
};
-209
packages/core-extensions/src/moonbase/ui/extensions/info.tsx
···
-
import WebpackRequire from "@moonlight-mod/types/discord/require";
-
import { ExtensionTag } from "@moonlight-mod/types";
-
import { MoonbaseExtension } from "../../types";
-
-
type Dependency = {
-
id: string;
-
type: DependencyType;
-
};
-
-
enum DependencyType {
-
Dependency = "dependency",
-
Optional = "optional",
-
Incompatible = "incompatible"
-
}
-
-
export const tagNames: Record<ExtensionTag, string> = {
-
[ExtensionTag.Accessibility]: "Accessibility",
-
[ExtensionTag.Appearance]: "Appearance",
-
[ExtensionTag.Chat]: "Chat",
-
[ExtensionTag.Commands]: "Commands",
-
[ExtensionTag.ContextMenu]: "Context Menu",
-
[ExtensionTag.DangerZone]: "Danger Zone",
-
[ExtensionTag.Development]: "Development",
-
[ExtensionTag.Fixes]: "Fixes",
-
[ExtensionTag.Fun]: "Fun",
-
[ExtensionTag.Markdown]: "Markdown",
-
[ExtensionTag.Voice]: "Voice",
-
[ExtensionTag.Privacy]: "Privacy",
-
[ExtensionTag.Profiles]: "Profiles",
-
[ExtensionTag.QualityOfLife]: "Quality of Life",
-
[ExtensionTag.Library]: "Library"
-
};
-
-
export default (require: typeof WebpackRequire) => {
-
const React = require("common_react");
-
const spacepack = require("spacepack_spacepack").spacepack;
-
-
const CommonComponents = require("common_components");
-
const UserInfoClasses = spacepack.findByCode(
-
"infoScroller",
-
"userInfoSection",
-
"userInfoSectionHeader"
-
)[0].exports;
-
-
const { MoonbaseSettingsStore } =
-
require("moonbase_stores") as typeof import("../../webpackModules/stores");
-
-
function InfoSection({
-
title,
-
children
-
}: {
-
title: string;
-
children: React.ReactNode;
-
}) {
-
return (
-
<div
-
style={{
-
marginRight: "1em"
-
}}
-
>
-
<CommonComponents.Text
-
variant="eyebrow"
-
className={UserInfoClasses.userInfoSectionHeader}
-
>
-
{title}
-
</CommonComponents.Text>
-
-
<CommonComponents.Text variant="text-sm/normal">
-
{children}
-
</CommonComponents.Text>
-
</div>
-
);
-
}
-
-
function Badge({
-
color,
-
children
-
}: {
-
color: string;
-
children: React.ReactNode;
-
}) {
-
return (
-
<span
-
style={{
-
borderRadius: ".1875rem",
-
padding: "0 0.275rem",
-
marginRight: "0.4em",
-
backgroundColor: color,
-
color: "#fff"
-
}}
-
>
-
{children}
-
</span>
-
);
-
}
-
-
function ExtensionInfo({ ext }: { ext: MoonbaseExtension }) {
-
const authors = ext.manifest?.meta?.authors;
-
const tags = ext.manifest?.meta?.tags;
-
const version = ext.manifest?.version;
-
-
const dependencies: Dependency[] = [];
-
if (ext.manifest.dependencies != null) {
-
dependencies.push(
-
...ext.manifest.dependencies.map((dep) => ({
-
id: dep,
-
type: DependencyType.Dependency
-
}))
-
);
-
}
-
-
if (ext.manifest.suggested != null) {
-
dependencies.push(
-
...ext.manifest.suggested.map((dep) => ({
-
id: dep,
-
type: DependencyType.Optional
-
}))
-
);
-
}
-
-
if (ext.manifest.incompatible != null) {
-
dependencies.push(
-
...ext.manifest.incompatible.map((dep) => ({
-
id: dep,
-
type: DependencyType.Incompatible
-
}))
-
);
-
}
-
-
return (
-
<>
-
{authors != null && (
-
<InfoSection title="Authors">
-
{authors.map((author, i) => {
-
const comma = i !== authors.length - 1 ? ", " : "";
-
if (typeof author === "string") {
-
return (
-
<span key={i}>
-
{author}
-
{comma}
-
</span>
-
);
-
} else {
-
// TODO: resolve IDs
-
return (
-
<span key={i}>
-
{author.name}
-
{comma}
-
</span>
-
);
-
}
-
})}
-
</InfoSection>
-
)}
-
-
{tags != null && (
-
<InfoSection title="Tags">
-
{tags.map((tag, i) => {
-
const name = tagNames[tag];
-
-
return (
-
<Badge
-
key={i}
-
color={
-
tag === ExtensionTag.DangerZone
-
? "var(--red-400)"
-
: "var(--brand-500)"
-
}
-
>
-
{name}
-
</Badge>
-
);
-
})}
-
</InfoSection>
-
)}
-
-
{dependencies.length > 0 && (
-
<InfoSection title="Dependencies">
-
{dependencies.map((dep) => {
-
const colors = {
-
[DependencyType.Dependency]: "var(--brand-500)",
-
[DependencyType.Optional]: "var(--orange-400)",
-
[DependencyType.Incompatible]: "var(--red-400)"
-
};
-
const color = colors[dep.type];
-
const name = MoonbaseSettingsStore.getExtensionName(dep.id);
-
return (
-
<Badge color={color} key={dep.id}>
-
{name}
-
</Badge>
-
);
-
})}
-
</InfoSection>
-
)}
-
-
{version != null && (
-
<InfoSection title="Version">
-
<span>{version}</span>
-
</InfoSection>
-
)}
-
</>
-
);
-
}
-
-
return {
-
InfoSection,
-
ExtensionInfo
-
};
-
};
-396
packages/core-extensions/src/moonbase/ui/extensions/settings.tsx
···
-
import {
-
ExtensionSettingType,
-
ExtensionSettingsManifest,
-
MultiSelectSettingType,
-
NumberSettingType,
-
SelectOption,
-
SelectSettingType
-
} from "@moonlight-mod/types/config";
-
import WebpackRequire from "@moonlight-mod/types/discord/require";
-
import { CircleXIconSVG, ExtensionState, MoonbaseExtension } from "../../types";
-
-
type SettingsProps = {
-
ext: MoonbaseExtension;
-
name: string;
-
setting: ExtensionSettingsManifest;
-
disabled: boolean;
-
};
-
-
type SettingsComponent = React.ComponentType<SettingsProps>;
-
-
export default (require: typeof WebpackRequire) => {
-
const React = require("common_react");
-
const CommonComponents = require("common_components");
-
const Flux = require("common_flux");
-
const spacepack = require("spacepack_spacepack").spacepack;
-
-
const { MoonbaseSettingsStore } =
-
require("moonbase_stores") as typeof import("../../webpackModules/stores");
-
-
const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports;
-
-
function useConfigEntry<T>(id: string, name: string) {
-
return Flux.useStateFromStores(
-
[MoonbaseSettingsStore],
-
() => {
-
return {
-
value: MoonbaseSettingsStore.getExtensionConfig<T>(id, name),
-
displayName: MoonbaseSettingsStore.getExtensionConfigName(id, name),
-
description: MoonbaseSettingsStore.getExtensionConfigDescription(
-
id,
-
name
-
)
-
};
-
},
-
[id, name]
-
);
-
}
-
-
function Boolean({ ext, name, setting, disabled }: SettingsProps) {
-
const { FormSwitch } = CommonComponents;
-
const { value, displayName, description } = useConfigEntry<boolean>(
-
ext.id,
-
name
-
);
-
-
return (
-
<FormSwitch
-
value={value ?? false}
-
hideBorder={true}
-
disabled={disabled}
-
onChange={(value: boolean) => {
-
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value);
-
}}
-
note={description}
-
className={`${Margins.marginReset} ${Margins.marginTop20}`}
-
>
-
{displayName}
-
</FormSwitch>
-
);
-
}
-
-
function Number({ ext, name, setting, disabled }: SettingsProps) {
-
const { FormItem, FormText, Slider } = CommonComponents;
-
const { value, displayName, description } = useConfigEntry<number>(
-
ext.id,
-
name
-
);
-
-
const castedSetting = setting as NumberSettingType;
-
const min = castedSetting.min ?? 0;
-
const max = castedSetting.max ?? 100;
-
-
return (
-
<FormItem className={Margins.marginTop20} title={displayName}>
-
{description && <FormText>{description}</FormText>}
-
<Slider
-
initialValue={value ?? 0}
-
disabled={disabled}
-
minValue={castedSetting.min ?? 0}
-
maxValue={castedSetting.max ?? 100}
-
onValueChange={(value: number) => {
-
const rounded = Math.max(min, Math.min(max, Math.round(value)));
-
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, rounded);
-
}}
-
/>
-
</FormItem>
-
);
-
}
-
-
function String({ ext, name, setting, disabled }: SettingsProps) {
-
const { FormItem, FormText, TextInput } = CommonComponents;
-
const { value, displayName, description } = useConfigEntry<string>(
-
ext.id,
-
name
-
);
-
-
return (
-
<FormItem className={Margins.marginTop20} title={displayName}>
-
{description && (
-
<FormText className={Margins.marginBottom8}>{description}</FormText>
-
)}
-
<TextInput
-
value={value ?? ""}
-
onChange={(value: string) => {
-
if (disabled) return;
-
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value);
-
}}
-
/>
-
</FormItem>
-
);
-
}
-
-
function Select({ ext, name, setting, disabled }: SettingsProps) {
-
const { FormItem, FormText, SingleSelect } = CommonComponents;
-
const { value, displayName, description } = useConfigEntry<string>(
-
ext.id,
-
name
-
);
-
-
const castedSetting = setting as SelectSettingType;
-
const options = castedSetting.options;
-
-
return (
-
<FormItem className={Margins.marginTop20} title={displayName}>
-
{description && (
-
<FormText className={Margins.marginBottom8}>{description}</FormText>
-
)}
-
<SingleSelect
-
autofocus={false}
-
clearable={false}
-
value={value ?? ""}
-
options={options.map((o: SelectOption) =>
-
typeof o === "string" ? { value: o, label: o } : o
-
)}
-
onChange={(value: string) => {
-
if (disabled) return;
-
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value);
-
}}
-
/>
-
</FormItem>
-
);
-
}
-
-
function MultiSelect({ ext, name, setting, disabled }: SettingsProps) {
-
const { FormItem, FormText, Select, useVariableSelect, multiSelect } =
-
CommonComponents;
-
const { value, displayName, description } = useConfigEntry<
-
string | string[]
-
>(ext.id, name);
-
-
const castedSetting = setting as MultiSelectSettingType;
-
const options = castedSetting.options;
-
-
return (
-
<FormItem className={Margins.marginTop20} title={displayName}>
-
{description && (
-
<FormText className={Margins.marginBottom8}>{description}</FormText>
-
)}
-
<Select
-
autofocus={false}
-
clearable={false}
-
closeOnSelect={false}
-
options={options.map((o: SelectOption) =>
-
typeof o === "string" ? { value: o, label: o } : o
-
)}
-
{...useVariableSelect({
-
onSelectInteraction: multiSelect,
-
value: new Set(Array.isArray(value) ? value : [value]),
-
onChange: (value: string) => {
-
if (disabled) return;
-
MoonbaseSettingsStore.setExtensionConfig(
-
ext.id,
-
name,
-
Array.from(value)
-
);
-
}
-
})}
-
/>
-
</FormItem>
-
);
-
}
-
-
const RemoveButtonClasses = spacepack.findByCode("removeButtonContainer")[0]
-
.exports;
-
const CircleXIcon = spacepack.findByCode(CircleXIconSVG)[0].exports.default;
-
function RemoveEntryButton({
-
onClick,
-
disabled
-
}: {
-
onClick: () => void;
-
disabled: boolean;
-
}) {
-
const { Tooltip, Clickable } = CommonComponents;
-
return (
-
<div className={RemoveButtonClasses.removeButtonContainer}>
-
<Tooltip text="Remove entry" position="top">
-
{(props: any) => (
-
<Clickable
-
{...props}
-
className={RemoveButtonClasses.removeButton}
-
onClick={onClick}
-
>
-
<CircleXIcon width={16} height={16} />
-
</Clickable>
-
)}
-
</Tooltip>
-
</div>
-
);
-
}
-
-
function List({ ext, name, setting, disabled }: SettingsProps) {
-
const { FormItem, FormText, TextInput, Button, Flex } = CommonComponents;
-
const { value, displayName, description } = useConfigEntry<string[]>(
-
ext.id,
-
name
-
);
-
-
const entries = value ?? [];
-
const updateConfig = () =>
-
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, entries);
-
-
return (
-
<FormItem className={Margins.marginTop20} title={displayName}>
-
{description && (
-
<FormText className={Margins.marginBottom4}>{description}</FormText>
-
)}
-
<Flex direction={Flex.Direction.VERTICAL}>
-
{entries.map((val, i) => (
-
// FIXME: stylesheets
-
<div
-
key={i}
-
style={{
-
display: "grid",
-
height: "32px",
-
gap: "8px",
-
gridTemplateColumns: "1fr 32px",
-
alignItems: "center"
-
}}
-
>
-
<TextInput
-
size={TextInput.Sizes.MINI}
-
value={val}
-
disabled={disabled}
-
onChange={(newVal: string) => {
-
entries[i] = newVal;
-
updateConfig();
-
}}
-
/>
-
<RemoveEntryButton
-
disabled={disabled}
-
onClick={() => {
-
entries.splice(i, 1);
-
updateConfig();
-
}}
-
/>
-
</div>
-
))}
-
-
<Button
-
look={Button.Looks.FILLED}
-
color={Button.Colors.GREEN}
-
size={Button.Sizes.SMALL}
-
disabled={disabled}
-
className={Margins.marginTop8}
-
onClick={() => {
-
entries.push("");
-
updateConfig();
-
}}
-
>
-
Add new entry
-
</Button>
-
</Flex>
-
</FormItem>
-
);
-
}
-
-
function Dictionary({ ext, name, setting, disabled }: SettingsProps) {
-
const { FormItem, FormText, TextInput, Button, Flex } = CommonComponents;
-
const { value, displayName, description } = useConfigEntry<
-
Record<string, string>
-
>(ext.id, name);
-
-
const entries = Object.entries(value ?? {});
-
const updateConfig = () =>
-
MoonbaseSettingsStore.setExtensionConfig(
-
ext.id,
-
name,
-
Object.fromEntries(entries)
-
);
-
-
return (
-
<FormItem className={Margins.marginTop20} title={displayName}>
-
{description && (
-
<FormText className={Margins.marginBottom4}>{description}</FormText>
-
)}
-
<Flex direction={Flex.Direction.VERTICAL}>
-
{entries.map(([key, val], i) => (
-
// FIXME: stylesheets
-
<div
-
key={i}
-
style={{
-
display: "grid",
-
height: "32px",
-
gap: "8px",
-
gridTemplateColumns: "1fr 1fr 32px",
-
alignItems: "center"
-
}}
-
>
-
<TextInput
-
size={TextInput.Sizes.MINI}
-
value={key}
-
disabled={disabled}
-
onChange={(newKey: string) => {
-
entries[i][0] = newKey;
-
updateConfig();
-
}}
-
/>
-
<TextInput
-
size={TextInput.Sizes.MINI}
-
value={val}
-
disabled={disabled}
-
onChange={(newValue: string) => {
-
entries[i][1] = newValue;
-
updateConfig();
-
}}
-
/>
-
<RemoveEntryButton
-
disabled={disabled}
-
onClick={() => {
-
entries.splice(i, 1);
-
updateConfig();
-
}}
-
/>
-
</div>
-
))}
-
-
<Button
-
look={Button.Looks.FILLED}
-
color={Button.Colors.GREEN}
-
size={Button.Sizes.SMALL}
-
className={Margins.marginTop8}
-
disabled={disabled}
-
onClick={() => {
-
entries.push([`entry-${entries.length}`, ""]);
-
updateConfig();
-
}}
-
>
-
Add new entry
-
</Button>
-
</Flex>
-
</FormItem>
-
);
-
}
-
-
function Setting({ ext, name, setting, disabled }: SettingsProps) {
-
const elements: Partial<Record<ExtensionSettingType, SettingsComponent>> = {
-
[ExtensionSettingType.Boolean]: Boolean,
-
[ExtensionSettingType.Number]: Number,
-
[ExtensionSettingType.String]: String,
-
[ExtensionSettingType.Select]: Select,
-
[ExtensionSettingType.MultiSelect]: MultiSelect,
-
[ExtensionSettingType.List]: List,
-
[ExtensionSettingType.Dictionary]: Dictionary
-
};
-
const element = elements[setting.type];
-
if (element == null) return <></>;
-
return React.createElement(element, { ext, name, setting, disabled });
-
}
-
-
return function Settings({ ext }: { ext: MoonbaseExtension }) {
-
const { Flex } = CommonComponents;
-
return (
-
<Flex className="moonbase-settings" direction={Flex.Direction.VERTICAL}>
-
{Object.entries(ext.manifest.settings!).map(([name, setting]) => (
-
<Setting
-
ext={ext}
-
key={name}
-
name={name}
-
setting={setting}
-
disabled={ext.state === ExtensionState.NotDownloaded}
-
/>
-
))}
-
</Flex>
-
);
-
};
-
};
-54
packages/core-extensions/src/moonbase/ui/index.tsx
···
-
import { WebpackRequireType } from "@moonlight-mod/types";
-
import { pageModules } from "..";
-
-
export default (require: WebpackRequireType) => {
-
const React = require("common_react");
-
const spacepack = require("spacepack_spacepack").spacepack;
-
-
const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports;
-
-
const { Divider } = spacepack.findByCode(".default.HEADER_BAR")[0].exports
-
.default;
-
const TitleBarClasses = spacepack.findByCode("iconWrapper:", "children:")[0]
-
.exports;
-
const TabBarClasses = spacepack.findByCode("nowPlayingColumn:")[0].exports;
-
-
const pages = pageModules(require);
-
-
return function Moonbase() {
-
const { Text, TabBar } = require("common_components");
-
-
const [selectedTab, setSelectedTab] = React.useState(Object.keys(pages)[0]);
-
-
return (
-
<>
-
<div
-
className={`${TitleBarClasses.children} ${Margins.marginBottom20}`}
-
>
-
<Text
-
className={TitleBarClasses.titleWrapper}
-
variant="heading-lg/semibold"
-
tag="h2"
-
>
-
Moonbase
-
</Text>
-
<Divider />
-
<TabBar
-
selectedItem={selectedTab}
-
onItemSelect={setSelectedTab}
-
type="top-pill"
-
className={TabBarClasses.tabBar}
-
>
-
{Object.entries(pages).map(([id, page]) => (
-
<TabBar.Item key={id} id={id} className={TabBarClasses.item}>
-
{page.name}
-
</TabBar.Item>
-
))}
-
</TabBar>
-
</div>
-
-
{React.createElement(pages[selectedTab].element)}
-
</>
-
);
-
};
-
};
+51
packages/core-extensions/src/moonbase/webpackModules/moonbase.tsx
···
+
import settings from "@moonlight-mod/wp/settings_settings";
+
import React from "@moonlight-mod/wp/common_react";
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
+
import { Moonbase, pages } from "@moonlight-mod/wp/moonbase_ui";
+
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
+
import { Text } from "@moonlight-mod/wp/common_components";
+
+
function addSection(name: string, element: React.FunctionComponent) {
+
settings.addSection(name, name, element, null, -2, {
+
stores: [MoonbaseSettingsStore],
+
element: () => {
+
// Require it here because lazy loading SUX
+
const SettingsNotice =
+
spacepack.findByCode("onSaveButtonColor")[0].exports.default;
+
return (
+
<SettingsNotice
+
submitting={MoonbaseSettingsStore.submitting}
+
onReset={() => {
+
MoonbaseSettingsStore.reset();
+
}}
+
onSave={() => {
+
MoonbaseSettingsStore.writeConfig();
+
}}
+
/>
+
);
+
}
+
});
+
}
+
+
if (moonlight.getConfigOption<boolean>("moonbase", "sections")) {
+
const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports;
+
+
settings.addHeader("Moonbase", -2);
+
for (const page of Object.values(pages)) {
+
addSection(page.name, () => (
+
<>
+
<Text
+
className={Margins.marginBottom20}
+
variant="heading-lg/semibold"
+
tag="h2"
+
>
+
Extensions
+
</Text>
+
<page.element />
+
</>
+
));
+
}
+
} else {
+
addSection("Moonbase", Moonbase);
+
}
+151
packages/core-extensions/src/moonbase/webpackModules/ui/config/index.tsx
···
+
import { LogLevel } from "@moonlight-mod/types";
+
import { CircleXIconSVG } from "../../../types";
+
+
const logLevels = Object.values(LogLevel).filter(
+
(v) => typeof v === "string"
+
) as string[];
+
+
import React from "@moonlight-mod/wp/common_react";
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
+
import {
+
FormDivider,
+
FormItem,
+
FormText,
+
FormSwitch,
+
TextInput,
+
Flex,
+
Button,
+
SingleSelect,
+
Tooltip,
+
Clickable
+
} from "@moonlight-mod/wp/common_components";
+
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
+
+
const FormClasses = spacepack.findByCode("dividerDefault:")[0].exports;
+
const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports;
+
+
const RemoveButtonClasses = spacepack.findByCode("removeButtonContainer")[0]
+
.exports;
+
const CircleXIcon = spacepack.findByCode(CircleXIconSVG)[0].exports.default;
+
function RemoveEntryButton({ onClick }: { onClick: () => void }) {
+
return (
+
<div className={RemoveButtonClasses.removeButtonContainer}>
+
<Tooltip text="Remove entry" position="top">
+
{(props: any) => (
+
<Clickable
+
{...props}
+
className={RemoveButtonClasses.removeButton}
+
onClick={onClick}
+
>
+
<CircleXIcon width={24} height={24} />
+
</Clickable>
+
)}
+
</Tooltip>
+
</div>
+
);
+
}
+
+
function ArrayFormItem({
+
config
+
}: {
+
config: "repositories" | "devSearchPaths";
+
}) {
+
const items = MoonbaseSettingsStore.getConfigOption(config) ?? [];
+
return (
+
<Flex
+
style={{
+
gap: "20px"
+
}}
+
direction={Flex.Direction.VERTICAL}
+
>
+
{items.map((val, i) => (
+
<div
+
key={i}
+
style={{
+
display: "grid",
+
height: "32px",
+
gap: "8px",
+
gridTemplateColumns: "1fr 32px",
+
alignItems: "center"
+
}}
+
>
+
<TextInput
+
size={TextInput.Sizes.DEFAULT}
+
value={val}
+
onChange={(newVal: string) => {
+
items[i] = newVal;
+
MoonbaseSettingsStore.setConfigOption(config, items);
+
}}
+
/>
+
<RemoveEntryButton
+
onClick={() => {
+
items.splice(i, 1);
+
MoonbaseSettingsStore.setConfigOption(config, items);
+
}}
+
/>
+
</div>
+
))}
+
+
<Button
+
look={Button.Looks.FILLED}
+
color={Button.Colors.GREEN}
+
size={Button.Sizes.SMALL}
+
style={{
+
marginTop: "10px"
+
}}
+
onClick={() => {
+
items.push("");
+
MoonbaseSettingsStore.setConfigOption(config, items);
+
}}
+
>
+
Add new entry
+
</Button>
+
</Flex>
+
);
+
}
+
+
export default function ConfigPage() {
+
return (
+
<>
+
<FormItem title="Repositories">
+
<FormText className={Margins.marginBottom4}>
+
A list of remote repositories to display extensions from
+
</FormText>
+
<ArrayFormItem config="repositories" />
+
</FormItem>
+
<FormDivider className={FormClasses.dividerDefault} />
+
<FormItem title="Extension search paths" className={Margins.marginTop20}>
+
<FormText className={Margins.marginBottom4}>
+
A list of local directories to search for built extensions
+
</FormText>
+
<ArrayFormItem config="devSearchPaths" />
+
</FormItem>
+
<FormDivider className={FormClasses.dividerDefault} />
+
<FormSwitch
+
className={Margins.marginTop20}
+
value={MoonbaseSettingsStore.getConfigOption("patchAll")}
+
onChange={(value: boolean) => {
+
MoonbaseSettingsStore.setConfigOption("patchAll", value);
+
}}
+
note="Wraps every webpack module in a function, separating them in DevTools"
+
>
+
Patch all
+
</FormSwitch>
+
<FormItem title="Log level">
+
<SingleSelect
+
autofocus={false}
+
clearable={false}
+
value={MoonbaseSettingsStore.getConfigOption("loggerLevel")}
+
options={logLevels.map((o) => ({
+
value: o.toLowerCase(),
+
label: o[0] + o.slice(1).toLowerCase()
+
}))}
+
onChange={(v) =>
+
MoonbaseSettingsStore.setConfigOption("loggerLevel", v)
+
}
+
/>
+
</FormItem>
+
</>
+
);
+
}
+218
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx
···
+
import {
+
DangerIconSVG,
+
DownloadIconSVG,
+
ExtensionState,
+
TrashIconSVG
+
} from "../../../types";
+
import { ExtensionLoadSource } from "@moonlight-mod/types";
+
+
import React from "@moonlight-mod/wp/common_react";
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
+
import CommonComponents from "@moonlight-mod/wp/common_components";
+
import * as Flux from "@moonlight-mod/wp/common_flux";
+
+
import ExtensionInfo from "./info";
+
import Settings from "./settings";
+
+
export enum ExtensionPage {
+
Info,
+
Description,
+
Settings
+
}
+
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
+
+
const UserProfileClasses = spacepack.findByCode(
+
"tabBarContainer",
+
"topSection"
+
)[0].exports;
+
+
const DownloadIcon =
+
spacepack.findByCode(DownloadIconSVG)[0].exports.DownloadIcon;
+
const TrashIcon = spacepack.findByCode(TrashIconSVG)[0].exports.default;
+
const DangerIcon =
+
spacepack.findByCode(DangerIconSVG)[0].exports.CircleExclamationPointIcon;
+
+
const PanelButton =
+
spacepack.findByCode("Masks.PANEL_BUTTON")[0].exports.default;
+
+
export default function ExtensionCard({ id }: { id: string }) {
+
const [tab, setTab] = React.useState(ExtensionPage.Info);
+
const [restartNeeded, setRestartNeeded] = React.useState(false);
+
+
const { ext, enabled, busy, update } = Flux.useStateFromStores(
+
[MoonbaseSettingsStore],
+
() => {
+
return {
+
ext: MoonbaseSettingsStore.getExtension(id),
+
enabled: MoonbaseSettingsStore.getExtensionEnabled(id),
+
busy: MoonbaseSettingsStore.busy,
+
update: MoonbaseSettingsStore.getExtensionUpdate(id)
+
};
+
}
+
);
+
+
// Why it work like that :sob:
+
if (ext == null) return <></>;
+
+
const {
+
Card,
+
CardClasses,
+
Flex,
+
Text,
+
MarkdownParser,
+
Switch,
+
TabBar,
+
Button
+
} = CommonComponents;
+
+
const tagline = ext.manifest?.meta?.tagline;
+
const settings = ext.manifest?.settings;
+
const description = ext.manifest?.meta?.description;
+
+
return (
+
<Card editable={true} className={CardClasses.card}>
+
<div className={CardClasses.cardHeader}>
+
<Flex direction={Flex.Direction.VERTICAL}>
+
<Flex direction={Flex.Direction.HORIZONTAL}>
+
<Text variant="text-md/semibold">
+
{ext.manifest?.meta?.name ?? ext.id}
+
</Text>
+
</Flex>
+
+
{tagline != null && (
+
<Text variant="text-sm/normal">
+
{MarkdownParser.parse(tagline)}
+
</Text>
+
)}
+
</Flex>
+
+
<Flex
+
direction={Flex.Direction.HORIZONTAL}
+
align={Flex.Align.END}
+
justify={Flex.Justify.END}
+
>
+
{ext.state === ExtensionState.NotDownloaded ? (
+
<Button
+
color={Button.Colors.BRAND}
+
submitting={busy}
+
onClick={() => {
+
MoonbaseSettingsStore.installExtension(id);
+
}}
+
>
+
Install
+
</Button>
+
) : (
+
<div
+
// too lazy to learn how <Flex /> works lmao
+
style={{
+
display: "flex",
+
alignItems: "center",
+
gap: "1rem"
+
}}
+
>
+
{ext.source.type === ExtensionLoadSource.Normal && (
+
<PanelButton
+
icon={TrashIcon}
+
tooltipText="Delete"
+
onClick={() => {
+
MoonbaseSettingsStore.deleteExtension(id);
+
}}
+
/>
+
)}
+
+
{update !== null && (
+
<PanelButton
+
icon={DownloadIcon}
+
tooltipText="Update"
+
onClick={() => {
+
MoonbaseSettingsStore.installExtension(id);
+
}}
+
/>
+
)}
+
+
{restartNeeded && (
+
<PanelButton
+
icon={() => (
+
<DangerIcon
+
color={CommonComponents.tokens.colors.STATUS_DANGER}
+
/>
+
)}
+
onClick={() => window.location.reload()}
+
tooltipText="You will need to reload/restart your client for this extension to work properly."
+
/>
+
)}
+
+
<Switch
+
checked={enabled}
+
onChange={() => {
+
setRestartNeeded(true);
+
MoonbaseSettingsStore.setExtensionEnabled(id, !enabled);
+
}}
+
/>
+
</div>
+
)}
+
</Flex>
+
</div>
+
+
<div className={UserProfileClasses.body}>
+
{(description != null || settings != null) && (
+
<div
+
className={UserProfileClasses.tabBarContainer}
+
style={{
+
padding: "0 10px"
+
}}
+
>
+
<TabBar
+
selectedItem={tab}
+
type="top"
+
onItemSelect={setTab}
+
className={UserProfileClasses.tabBar}
+
>
+
<TabBar.Item
+
className={UserProfileClasses.tabBarItem}
+
id={ExtensionPage.Info}
+
>
+
Info
+
</TabBar.Item>
+
+
{description != null && (
+
<TabBar.Item
+
className={UserProfileClasses.tabBarItem}
+
id={ExtensionPage.Description}
+
>
+
Description
+
</TabBar.Item>
+
)}
+
+
{settings != null && (
+
<TabBar.Item
+
className={UserProfileClasses.tabBarItem}
+
id={ExtensionPage.Settings}
+
>
+
Settings
+
</TabBar.Item>
+
)}
+
</TabBar>
+
</div>
+
)}
+
+
<Flex
+
justify={Flex.Justify.START}
+
wrap={Flex.Wrap.WRAP}
+
style={{
+
padding: "16px 16px"
+
}}
+
>
+
{tab === ExtensionPage.Info && <ExtensionInfo ext={ext} />}
+
{tab === ExtensionPage.Description && (
+
<Text variant="text-md/normal">
+
{MarkdownParser.parse(description ?? "*No description*")}
+
</Text>
+
)}
+
{tab === ExtensionPage.Settings && <Settings ext={ext} />}
+
</Flex>
+
</div>
+
</Card>
+
);
+
}
+373
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/filterBar.tsx
···
+
import { tagNames } from "./info";
+
import {
+
ArrowsUpDownIconSVG,
+
ChevronSmallDownIconSVG,
+
ChevronSmallUpIconSVG
+
} from "../../../types";
+
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
+
import React from "@moonlight-mod/wp/common_react";
+
import * as Flux from "@moonlight-mod/wp/common_flux";
+
import { WindowStore } from "@moonlight-mod/wp/common_stores";
+
import {
+
Button,
+
Text,
+
Heading,
+
Popout,
+
Dialog,
+
Menu,
+
MenuGroup,
+
MenuCheckboxItem,
+
MenuItem
+
} from "@moonlight-mod/wp/common_components";
+
+
export enum Filter {
+
Core = 1 << 0,
+
Normal = 1 << 1,
+
Developer = 1 << 2,
+
Enabled = 1 << 3,
+
Disabled = 1 << 4,
+
Installed = 1 << 5,
+
Repository = 1 << 6
+
}
+
export const defaultFilter = ~(~0 << 7);
+
+
const channelModule =
+
spacepack.require.m[
+
spacepack.findByCode(
+
'"Missing channel in Channel.openChannelContextMenu"'
+
)[0].id
+
].toString();
+
const moduleId = channelModule.match(/webpackId:"(.+?)"/)![1];
+
const modPromise = spacepack.require.el(moduleId);
+
+
const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports;
+
const SortMenuClasses = spacepack.findByCode("container:", "clearText:")[0]
+
.exports;
+
const FilterDialogClasses = spacepack.findByCode(
+
"countContainer:",
+
"tagContainer:"
+
)[0].exports;
+
const FilterBarClasses = spacepack.findByCode("tagsButtonWithCount:")[0]
+
.exports;
+
+
const TagItem = spacepack.findByCode("IncreasedActivityForumTagPill:")[0]
+
.exports.default;
+
+
const ChevronSmallDownIcon = spacepack.findByCode(ChevronSmallDownIconSVG)[0]
+
.exports.default;
+
const ChevronSmallUpIcon = spacepack.findByCode(ChevronSmallUpIconSVG)[0]
+
.exports.default;
+
let ArrowsUpDownIcon: React.FunctionComponent;
+
+
function toggleTag(
+
selectedTags: Set<string>,
+
setSelectedTags: (tags: Set<string>) => void,
+
tag: string
+
) {
+
const newState = new Set(selectedTags);
+
if (newState.has(tag)) newState.delete(tag);
+
else newState.add(tag);
+
setSelectedTags(newState);
+
}
+
+
function FilterButtonPopout({
+
filter,
+
setFilter,
+
closePopout
+
}: {
+
filter: Filter;
+
setFilter: (filter: Filter) => void;
+
closePopout: () => void;
+
}) {
+
const toggleFilter = (set: Filter) =>
+
setFilter(filter & set ? filter & ~set : filter | set);
+
+
return (
+
<div className={SortMenuClasses.container}>
+
<Menu navId="sort-filter" hideScrollbar={true} onClose={closePopout}>
+
<MenuGroup label="Type">
+
<MenuCheckboxItem
+
id="t-core"
+
label="Core"
+
checked={filter & Filter.Core}
+
action={() => toggleFilter(Filter.Core)}
+
/>
+
<MenuCheckboxItem
+
id="t-normal"
+
label="Normal"
+
checked={filter & Filter.Normal}
+
action={() => toggleFilter(Filter.Normal)}
+
/>
+
<MenuCheckboxItem
+
id="t-developer"
+
label="Developer"
+
checked={filter & Filter.Developer}
+
action={() => toggleFilter(Filter.Developer)}
+
/>
+
</MenuGroup>
+
<MenuGroup label="State">
+
<MenuCheckboxItem
+
id="s-enabled"
+
label="Enabled"
+
checked={filter & Filter.Enabled}
+
action={() => toggleFilter(Filter.Enabled)}
+
/>
+
<MenuCheckboxItem
+
id="s-disabled"
+
label="Disabled"
+
checked={filter & Filter.Disabled}
+
action={() => toggleFilter(Filter.Disabled)}
+
/>
+
</MenuGroup>
+
<MenuGroup label="Location">
+
<MenuCheckboxItem
+
id="l-installed"
+
label="Installed"
+
checked={filter & Filter.Installed}
+
action={() => toggleFilter(Filter.Installed)}
+
/>
+
<MenuCheckboxItem
+
id="l-repository"
+
label="Repository"
+
checked={filter & Filter.Repository}
+
action={() => toggleFilter(Filter.Repository)}
+
/>
+
</MenuGroup>
+
<MenuGroup>
+
<MenuItem
+
id="reset-all"
+
className={SortMenuClasses.clearText}
+
label={
+
<Text variant="text-sm/medium" color="none">
+
Reset to default
+
</Text>
+
}
+
action={() => {
+
setFilter(defaultFilter);
+
closePopout();
+
}}
+
/>
+
</MenuGroup>
+
</Menu>
+
</div>
+
);
+
}
+
+
function TagButtonPopout({
+
selectedTags,
+
setSelectedTags,
+
setPopoutRef,
+
closePopout
+
}: any) {
+
return (
+
<Dialog ref={setPopoutRef} className={FilterDialogClasses.container}>
+
<div className={FilterDialogClasses.header}>
+
<div className={FilterDialogClasses.headerLeft}>
+
<Heading
+
color="interactive-normal"
+
variant="text-xs/bold"
+
className={FilterDialogClasses.headerText}
+
>
+
Select tags
+
</Heading>
+
<div className={FilterDialogClasses.countContainer}>
+
<Text
+
className={FilterDialogClasses.countText}
+
color="none"
+
variant="text-xs/medium"
+
>
+
{selectedTags.size}
+
</Text>
+
</div>
+
</div>
+
</div>
+
<div className={FilterDialogClasses.tagContainer}>
+
{Object.keys(tagNames).map((tag) => (
+
<TagItem
+
key={tag}
+
className={FilterDialogClasses.tag}
+
tag={{ name: tagNames[tag as keyof typeof tagNames] }}
+
onClick={() => toggleTag(selectedTags, setSelectedTags, tag)}
+
selected={selectedTags.has(tag)}
+
/>
+
))}
+
</div>
+
<div className={FilterDialogClasses.separator} />
+
<Button
+
look={Button.Looks.LINK}
+
size={Button.Sizes.MIN}
+
color={Button.Colors.CUSTOM}
+
className={FilterDialogClasses.clear}
+
onClick={() => {
+
setSelectedTags(new Set());
+
closePopout();
+
}}
+
>
+
<Text variant="text-sm/medium" color="text-link">
+
Clear all
+
</Text>
+
</Button>
+
</Dialog>
+
);
+
}
+
+
function FilterBar({
+
filter,
+
setFilter,
+
selectedTags,
+
setSelectedTags
+
}: {
+
filter: Filter;
+
setFilter: (filter: Filter) => void;
+
selectedTags: Set<string>;
+
setSelectedTags: (tags: Set<string>) => void;
+
}) {
+
const windowSize = Flux.useStateFromStores([WindowStore], () =>
+
WindowStore.windowSize()
+
);
+
+
const tagsContainer = React.useRef<HTMLDivElement>(null);
+
const tagListInner = React.useRef<HTMLDivElement>(null);
+
const [tagsButtonOffset, setTagsButtonOffset] = React.useState(0);
+
React.useLayoutEffect(() => {
+
if (tagsContainer.current === null || tagListInner.current === null) return;
+
const { left: containerX, top: containerY } =
+
tagsContainer.current.getBoundingClientRect();
+
let offset = 0;
+
for (const child of tagListInner.current.children) {
+
const {
+
right: childX,
+
top: childY,
+
height
+
} = child.getBoundingClientRect();
+
if (childY - containerY > height) break;
+
const newOffset = childX - containerX;
+
if (newOffset > offset) {
+
offset = newOffset;
+
}
+
}
+
setTagsButtonOffset(offset);
+
}, [windowSize]);
+
+
return (
+
<div
+
ref={tagsContainer}
+
style={{
+
paddingTop: "12px"
+
}}
+
className={`${FilterBarClasses.tagsContainer} ${Margins.marginBottom8}`}
+
>
+
<Popout
+
renderPopout={({ closePopout }: any) => (
+
<FilterButtonPopout
+
filter={filter}
+
setFilter={setFilter}
+
closePopout={closePopout}
+
/>
+
)}
+
position="bottom"
+
align="left"
+
>
+
{(props: any, { isShown }: { isShown: boolean }) => (
+
<Button
+
{...props}
+
size={Button.Sizes.MIN}
+
color={Button.Colors.CUSTOM}
+
className={FilterBarClasses.sortDropdown}
+
innerClassName={FilterBarClasses.sortDropdownInner}
+
>
+
<ArrowsUpDownIcon />
+
<Text
+
className={FilterBarClasses.sortDropdownText}
+
variant="text-sm/medium"
+
color="interactive-normal"
+
>
+
Sort & filter
+
</Text>
+
{isShown ? (
+
<ChevronSmallUpIcon size={20} />
+
) : (
+
<ChevronSmallDownIcon size={20} />
+
)}
+
</Button>
+
)}
+
</Popout>
+
<div className={FilterBarClasses.divider} />
+
<div className={FilterBarClasses.tagList}>
+
<div ref={tagListInner} className={FilterBarClasses.tagListInner}>
+
{Object.keys(tagNames).map((tag) => (
+
<TagItem
+
key={tag}
+
className={FilterBarClasses.tag}
+
tag={{ name: tagNames[tag as keyof typeof tagNames] }}
+
onClick={() => toggleTag(selectedTags, setSelectedTags, tag)}
+
selected={selectedTags.has(tag)}
+
/>
+
))}
+
</div>
+
</div>
+
<Popout
+
renderPopout={({ setPopoutRef, closePopout }: any) => (
+
<TagButtonPopout
+
selectedTags={selectedTags}
+
setSelectedTags={setSelectedTags}
+
setPopoutRef={setPopoutRef}
+
closePopout={closePopout}
+
/>
+
)}
+
position="bottom"
+
align="right"
+
>
+
{(props: any, { isShown }: { isShown: boolean }) => (
+
<Button
+
{...props}
+
size={Button.Sizes.MIN}
+
color={Button.Colors.CUSTOM}
+
style={{
+
left: tagsButtonOffset
+
}}
+
// TODO: Use Discord's class name utility
+
className={`${FilterBarClasses.tagsButton} ${
+
selectedTags.size > 0 ? FilterBarClasses.tagsButtonWithCount : ""
+
}`}
+
innerClassName={FilterBarClasses.tagsButtonInner}
+
>
+
{selectedTags.size > 0 ? (
+
<div
+
style={{ boxSizing: "content-box" }}
+
className={FilterBarClasses.countContainer}
+
>
+
<Text
+
className={FilterBarClasses.countText}
+
color="none"
+
variant="text-xs/medium"
+
>
+
{selectedTags.size}
+
</Text>
+
</div>
+
) : (
+
<>All</>
+
)}
+
{isShown ? (
+
<ChevronSmallUpIcon size={20} />
+
) : (
+
<ChevronSmallDownIcon size={20} />
+
)}
+
</Button>
+
)}
+
</Popout>
+
</div>
+
);
+
}
+
+
// TODO: spacepack lazy loading utils
+
export default React.lazy(() =>
+
modPromise.then(async () => {
+
await modPromise;
+
ArrowsUpDownIcon ??=
+
spacepack.findByCode(ArrowsUpDownIconSVG)[0].exports.default;
+
+
return { default: FilterBar };
+
})
+
);
+104
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/index.tsx
···
+
import { ExtensionLoadSource, ExtensionTag } from "@moonlight-mod/types";
+
import { ExtensionState } from "../../../types";
+
import FilterBar, { Filter, defaultFilter } from "./filterBar";
+
import ExtensionCard from "./card";
+
+
import React from "@moonlight-mod/wp/common_react";
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
+
import * as Flux from "@moonlight-mod/wp/common_flux";
+
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
+
+
const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports;
+
const SearchBar = spacepack.findByCode("Messages.SEARCH", "hideSearchIcon")[0]
+
.exports.default;
+
+
export default function ExtensionsPage() {
+
const { extensions, savedFilter } = Flux.useStateFromStoresObject(
+
[MoonbaseSettingsStore],
+
() => {
+
return {
+
extensions: MoonbaseSettingsStore.extensions,
+
savedFilter: MoonbaseSettingsStore.getExtensionConfig(
+
"moonbase",
+
"filter"
+
)
+
};
+
}
+
);
+
+
const [query, setQuery] = React.useState("");
+
+
let filter: Filter, setFilter: (filter: Filter) => void;
+
if (moonlight.getConfigOption<boolean>("moonbase", "saveFilter")) {
+
filter = savedFilter ?? defaultFilter;
+
setFilter = (filter) =>
+
MoonbaseSettingsStore.setExtensionConfig("moonbase", "filter", filter);
+
} else {
+
const state = React.useState(defaultFilter);
+
filter = state[0];
+
setFilter = state[1];
+
}
+
const [selectedTags, setSelectedTags] = React.useState(new Set<string>());
+
const sorted = Object.values(extensions).sort((a, b) => {
+
const aName = a.manifest.meta?.name ?? a.id;
+
const bName = b.manifest.meta?.name ?? b.id;
+
return aName.localeCompare(bName);
+
});
+
+
const filtered = sorted.filter(
+
(ext) =>
+
(ext.manifest.meta?.name?.toLowerCase().includes(query) ||
+
ext.manifest.meta?.tagline?.toLowerCase().includes(query) ||
+
ext.manifest.meta?.description?.toLowerCase().includes(query)) &&
+
[...selectedTags.values()].every(
+
(tag) => ext.manifest.meta?.tags?.includes(tag as ExtensionTag)
+
) &&
+
// This seems very bad, sorry
+
!(
+
(!(filter & Filter.Core) &&
+
ext.source.type === ExtensionLoadSource.Core) ||
+
(!(filter & Filter.Normal) &&
+
ext.source.type === ExtensionLoadSource.Normal) ||
+
(!(filter & Filter.Developer) &&
+
ext.source.type === ExtensionLoadSource.Developer) ||
+
(!(filter & Filter.Enabled) &&
+
MoonbaseSettingsStore.getExtensionEnabled(ext.id)) ||
+
(!(filter & Filter.Disabled) &&
+
!MoonbaseSettingsStore.getExtensionEnabled(ext.id)) ||
+
(!(filter & Filter.Installed) &&
+
ext.state !== ExtensionState.NotDownloaded) ||
+
(!(filter & Filter.Repository) &&
+
ext.state === ExtensionState.NotDownloaded)
+
)
+
);
+
+
return (
+
<>
+
<SearchBar
+
size={SearchBar.Sizes.MEDIUM}
+
query={query}
+
onChange={(v: string) => setQuery(v.toLowerCase())}
+
onClear={() => setQuery("")}
+
autoFocus={true}
+
autoComplete="off"
+
inputProps={{
+
autoCapitalize: "none",
+
autoCorrect: "off",
+
spellCheck: "false"
+
}}
+
/>
+
<React.Suspense fallback={<div className={Margins.marginBottom20}></div>}>
+
<FilterBar
+
filter={filter}
+
setFilter={setFilter}
+
selectedTags={selectedTags}
+
setSelectedTags={setSelectedTags}
+
/>
+
</React.Suspense>
+
{filtered.map((ext) => (
+
<ExtensionCard id={ext.id} key={ext.id} />
+
))}
+
</>
+
);
+
}
+200
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/info.tsx
···
+
import { ExtensionTag } from "@moonlight-mod/types";
+
import { MoonbaseExtension } from "../../../types";
+
+
import React from "@moonlight-mod/wp/common_react";
+
import CommonComponents from "@moonlight-mod/wp/common_components";
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
+
+
type Dependency = {
+
id: string;
+
type: DependencyType;
+
};
+
+
enum DependencyType {
+
Dependency = "dependency",
+
Optional = "optional",
+
Incompatible = "incompatible"
+
}
+
+
export const tagNames: Record<ExtensionTag, string> = {
+
[ExtensionTag.Accessibility]: "Accessibility",
+
[ExtensionTag.Appearance]: "Appearance",
+
[ExtensionTag.Chat]: "Chat",
+
[ExtensionTag.Commands]: "Commands",
+
[ExtensionTag.ContextMenu]: "Context Menu",
+
[ExtensionTag.DangerZone]: "Danger Zone",
+
[ExtensionTag.Development]: "Development",
+
[ExtensionTag.Fixes]: "Fixes",
+
[ExtensionTag.Fun]: "Fun",
+
[ExtensionTag.Markdown]: "Markdown",
+
[ExtensionTag.Voice]: "Voice",
+
[ExtensionTag.Privacy]: "Privacy",
+
[ExtensionTag.Profiles]: "Profiles",
+
[ExtensionTag.QualityOfLife]: "Quality of Life",
+
[ExtensionTag.Library]: "Library"
+
};
+
+
const UserInfoClasses = spacepack.findByCode(
+
"infoScroller",
+
"userInfoSection",
+
"userInfoSectionHeader"
+
)[0].exports;
+
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
+
+
function InfoSection({
+
title,
+
children
+
}: {
+
title: string;
+
children: React.ReactNode;
+
}) {
+
return (
+
<div
+
style={{
+
marginRight: "1em"
+
}}
+
>
+
<CommonComponents.Text
+
variant="eyebrow"
+
className={UserInfoClasses.userInfoSectionHeader}
+
>
+
{title}
+
</CommonComponents.Text>
+
+
<CommonComponents.Text variant="text-sm/normal">
+
{children}
+
</CommonComponents.Text>
+
</div>
+
);
+
}
+
+
function Badge({
+
color,
+
children
+
}: {
+
color: string;
+
children: React.ReactNode;
+
}) {
+
return (
+
<span
+
style={{
+
borderRadius: ".1875rem",
+
padding: "0 0.275rem",
+
marginRight: "0.4em",
+
backgroundColor: color,
+
color: "#fff"
+
}}
+
>
+
{children}
+
</span>
+
);
+
}
+
+
export default function ExtensionInfo({ ext }: { ext: MoonbaseExtension }) {
+
const authors = ext.manifest?.meta?.authors;
+
const tags = ext.manifest?.meta?.tags;
+
const version = ext.manifest?.version;
+
+
const dependencies: Dependency[] = [];
+
if (ext.manifest.dependencies != null) {
+
dependencies.push(
+
...ext.manifest.dependencies.map((dep) => ({
+
id: dep,
+
type: DependencyType.Dependency
+
}))
+
);
+
}
+
+
if (ext.manifest.suggested != null) {
+
dependencies.push(
+
...ext.manifest.suggested.map((dep) => ({
+
id: dep,
+
type: DependencyType.Optional
+
}))
+
);
+
}
+
+
if (ext.manifest.incompatible != null) {
+
dependencies.push(
+
...ext.manifest.incompatible.map((dep) => ({
+
id: dep,
+
type: DependencyType.Incompatible
+
}))
+
);
+
}
+
+
return (
+
<>
+
{authors != null && (
+
<InfoSection title="Authors">
+
{authors.map((author, i) => {
+
const comma = i !== authors.length - 1 ? ", " : "";
+
if (typeof author === "string") {
+
return (
+
<span key={i}>
+
{author}
+
{comma}
+
</span>
+
);
+
} else {
+
// TODO: resolve IDs
+
return (
+
<span key={i}>
+
{author.name}
+
{comma}
+
</span>
+
);
+
}
+
})}
+
</InfoSection>
+
)}
+
+
{tags != null && (
+
<InfoSection title="Tags">
+
{tags.map((tag, i) => {
+
const name = tagNames[tag];
+
+
return (
+
<Badge
+
key={i}
+
color={
+
tag === ExtensionTag.DangerZone
+
? "var(--red-400)"
+
: "var(--brand-500)"
+
}
+
>
+
{name}
+
</Badge>
+
);
+
})}
+
</InfoSection>
+
)}
+
+
{dependencies.length > 0 && (
+
<InfoSection title="Dependencies">
+
{dependencies.map((dep) => {
+
const colors = {
+
[DependencyType.Dependency]: "var(--brand-500)",
+
[DependencyType.Optional]: "var(--orange-400)",
+
[DependencyType.Incompatible]: "var(--red-400)"
+
};
+
const color = colors[dep.type];
+
const name = MoonbaseSettingsStore.getExtensionName(dep.id);
+
return (
+
<Badge color={color} key={dep.id}>
+
{name}
+
</Badge>
+
);
+
})}
+
</InfoSection>
+
)}
+
+
{version != null && (
+
<InfoSection title="Version">
+
<span>{version}</span>
+
</InfoSection>
+
)}
+
</>
+
);
+
}
+398
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/settings.tsx
···
+
import {
+
ExtensionSettingType,
+
ExtensionSettingsManifest,
+
MultiSelectSettingType,
+
NumberSettingType,
+
SelectOption,
+
SelectSettingType
+
} from "@moonlight-mod/types/config";
+
+
import {
+
CircleXIconSVG,
+
ExtensionState,
+
MoonbaseExtension
+
} from "../../../types";
+
+
import React from "@moonlight-mod/wp/common_react";
+
import CommonComponents from "@moonlight-mod/wp/common_components";
+
import * as Flux from "@moonlight-mod/wp/common_flux";
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
+
+
type SettingsProps = {
+
ext: MoonbaseExtension;
+
name: string;
+
setting: ExtensionSettingsManifest;
+
disabled: boolean;
+
};
+
+
type SettingsComponent = React.ComponentType<SettingsProps>;
+
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
+
+
const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports;
+
+
function useConfigEntry<T>(id: string, name: string) {
+
return Flux.useStateFromStores(
+
[MoonbaseSettingsStore],
+
() => {
+
return {
+
value: MoonbaseSettingsStore.getExtensionConfig<T>(id, name),
+
displayName: MoonbaseSettingsStore.getExtensionConfigName(id, name),
+
description: MoonbaseSettingsStore.getExtensionConfigDescription(
+
id,
+
name
+
)
+
};
+
},
+
[id, name]
+
);
+
}
+
+
function Boolean({ ext, name, setting, disabled }: SettingsProps) {
+
const { FormSwitch } = CommonComponents;
+
const { value, displayName, description } = useConfigEntry<boolean>(
+
ext.id,
+
name
+
);
+
+
return (
+
<FormSwitch
+
value={value ?? false}
+
hideBorder={true}
+
disabled={disabled}
+
onChange={(value: boolean) => {
+
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value);
+
}}
+
note={description}
+
className={`${Margins.marginReset} ${Margins.marginTop20}`}
+
>
+
{displayName}
+
</FormSwitch>
+
);
+
}
+
+
function Number({ ext, name, setting, disabled }: SettingsProps) {
+
const { FormItem, FormText, Slider } = CommonComponents;
+
const { value, displayName, description } = useConfigEntry<number>(
+
ext.id,
+
name
+
);
+
+
const castedSetting = setting as NumberSettingType;
+
const min = castedSetting.min ?? 0;
+
const max = castedSetting.max ?? 100;
+
+
return (
+
<FormItem className={Margins.marginTop20} title={displayName}>
+
{description && <FormText>{description}</FormText>}
+
<Slider
+
initialValue={value ?? 0}
+
disabled={disabled}
+
minValue={castedSetting.min ?? 0}
+
maxValue={castedSetting.max ?? 100}
+
onValueChange={(value: number) => {
+
const rounded = Math.max(min, Math.min(max, Math.round(value)));
+
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, rounded);
+
}}
+
/>
+
</FormItem>
+
);
+
}
+
+
function String({ ext, name, setting, disabled }: SettingsProps) {
+
const { FormItem, FormText, TextInput } = CommonComponents;
+
const { value, displayName, description } = useConfigEntry<string>(
+
ext.id,
+
name
+
);
+
+
return (
+
<FormItem className={Margins.marginTop20} title={displayName}>
+
{description && (
+
<FormText className={Margins.marginBottom8}>{description}</FormText>
+
)}
+
<TextInput
+
value={value ?? ""}
+
onChange={(value: string) => {
+
if (disabled) return;
+
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value);
+
}}
+
/>
+
</FormItem>
+
);
+
}
+
+
function Select({ ext, name, setting, disabled }: SettingsProps) {
+
const { FormItem, FormText, SingleSelect } = CommonComponents;
+
const { value, displayName, description } = useConfigEntry<string>(
+
ext.id,
+
name
+
);
+
+
const castedSetting = setting as SelectSettingType;
+
const options = castedSetting.options;
+
+
return (
+
<FormItem className={Margins.marginTop20} title={displayName}>
+
{description && (
+
<FormText className={Margins.marginBottom8}>{description}</FormText>
+
)}
+
<SingleSelect
+
autofocus={false}
+
clearable={false}
+
value={value ?? ""}
+
options={options.map((o: SelectOption) =>
+
typeof o === "string" ? { value: o, label: o } : o
+
)}
+
onChange={(value: string) => {
+
if (disabled) return;
+
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value);
+
}}
+
/>
+
</FormItem>
+
);
+
}
+
+
function MultiSelect({ ext, name, setting, disabled }: SettingsProps) {
+
const { FormItem, FormText, Select, useVariableSelect, multiSelect } =
+
CommonComponents;
+
const { value, displayName, description } = useConfigEntry<string | string[]>(
+
ext.id,
+
name
+
);
+
+
const castedSetting = setting as MultiSelectSettingType;
+
const options = castedSetting.options;
+
+
return (
+
<FormItem className={Margins.marginTop20} title={displayName}>
+
{description && (
+
<FormText className={Margins.marginBottom8}>{description}</FormText>
+
)}
+
<Select
+
autofocus={false}
+
clearable={false}
+
closeOnSelect={false}
+
options={options.map((o: SelectOption) =>
+
typeof o === "string" ? { value: o, label: o } : o
+
)}
+
{...useVariableSelect({
+
onSelectInteraction: multiSelect,
+
value: new Set(Array.isArray(value) ? value : [value]),
+
onChange: (value: string) => {
+
if (disabled) return;
+
MoonbaseSettingsStore.setExtensionConfig(
+
ext.id,
+
name,
+
Array.from(value)
+
);
+
}
+
})}
+
/>
+
</FormItem>
+
);
+
}
+
+
const RemoveButtonClasses = spacepack.findByCode("removeButtonContainer")[0]
+
.exports;
+
const CircleXIcon = spacepack.findByCode(CircleXIconSVG)[0].exports.default;
+
function RemoveEntryButton({
+
onClick,
+
disabled
+
}: {
+
onClick: () => void;
+
disabled: boolean;
+
}) {
+
const { Tooltip, Clickable } = CommonComponents;
+
return (
+
<div className={RemoveButtonClasses.removeButtonContainer}>
+
<Tooltip text="Remove entry" position="top">
+
{(props: any) => (
+
<Clickable
+
{...props}
+
className={RemoveButtonClasses.removeButton}
+
onClick={onClick}
+
>
+
<CircleXIcon width={16} height={16} />
+
</Clickable>
+
)}
+
</Tooltip>
+
</div>
+
);
+
}
+
+
function List({ ext, name, setting, disabled }: SettingsProps) {
+
const { FormItem, FormText, TextInput, Button, Flex } = CommonComponents;
+
const { value, displayName, description } = useConfigEntry<string[]>(
+
ext.id,
+
name
+
);
+
+
const entries = value ?? [];
+
const updateConfig = () =>
+
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, entries);
+
+
return (
+
<FormItem className={Margins.marginTop20} title={displayName}>
+
{description && (
+
<FormText className={Margins.marginBottom4}>{description}</FormText>
+
)}
+
<Flex direction={Flex.Direction.VERTICAL}>
+
{entries.map((val, i) => (
+
// FIXME: stylesheets
+
<div
+
key={i}
+
style={{
+
display: "grid",
+
height: "32px",
+
gap: "8px",
+
gridTemplateColumns: "1fr 32px",
+
alignItems: "center"
+
}}
+
>
+
<TextInput
+
size={TextInput.Sizes.MINI}
+
value={val}
+
disabled={disabled}
+
onChange={(newVal: string) => {
+
entries[i] = newVal;
+
updateConfig();
+
}}
+
/>
+
<RemoveEntryButton
+
disabled={disabled}
+
onClick={() => {
+
entries.splice(i, 1);
+
updateConfig();
+
}}
+
/>
+
</div>
+
))}
+
+
<Button
+
look={Button.Looks.FILLED}
+
color={Button.Colors.GREEN}
+
size={Button.Sizes.SMALL}
+
disabled={disabled}
+
className={Margins.marginTop8}
+
onClick={() => {
+
entries.push("");
+
updateConfig();
+
}}
+
>
+
Add new entry
+
</Button>
+
</Flex>
+
</FormItem>
+
);
+
}
+
+
function Dictionary({ ext, name, setting, disabled }: SettingsProps) {
+
const { FormItem, FormText, TextInput, Button, Flex } = CommonComponents;
+
const { value, displayName, description } = useConfigEntry<
+
Record<string, string>
+
>(ext.id, name);
+
+
const entries = Object.entries(value ?? {});
+
const updateConfig = () =>
+
MoonbaseSettingsStore.setExtensionConfig(
+
ext.id,
+
name,
+
Object.fromEntries(entries)
+
);
+
+
return (
+
<FormItem className={Margins.marginTop20} title={displayName}>
+
{description && (
+
<FormText className={Margins.marginBottom4}>{description}</FormText>
+
)}
+
<Flex direction={Flex.Direction.VERTICAL}>
+
{entries.map(([key, val], i) => (
+
// FIXME: stylesheets
+
<div
+
key={i}
+
style={{
+
display: "grid",
+
height: "32px",
+
gap: "8px",
+
gridTemplateColumns: "1fr 1fr 32px",
+
alignItems: "center"
+
}}
+
>
+
<TextInput
+
size={TextInput.Sizes.MINI}
+
value={key}
+
disabled={disabled}
+
onChange={(newKey: string) => {
+
entries[i][0] = newKey;
+
updateConfig();
+
}}
+
/>
+
<TextInput
+
size={TextInput.Sizes.MINI}
+
value={val}
+
disabled={disabled}
+
onChange={(newValue: string) => {
+
entries[i][1] = newValue;
+
updateConfig();
+
}}
+
/>
+
<RemoveEntryButton
+
disabled={disabled}
+
onClick={() => {
+
entries.splice(i, 1);
+
updateConfig();
+
}}
+
/>
+
</div>
+
))}
+
+
<Button
+
look={Button.Looks.FILLED}
+
color={Button.Colors.GREEN}
+
size={Button.Sizes.SMALL}
+
className={Margins.marginTop8}
+
disabled={disabled}
+
onClick={() => {
+
entries.push([`entry-${entries.length}`, ""]);
+
updateConfig();
+
}}
+
>
+
Add new entry
+
</Button>
+
</Flex>
+
</FormItem>
+
);
+
}
+
+
function Setting({ ext, name, setting, disabled }: SettingsProps) {
+
const elements: Partial<Record<ExtensionSettingType, SettingsComponent>> = {
+
[ExtensionSettingType.Boolean]: Boolean,
+
[ExtensionSettingType.Number]: Number,
+
[ExtensionSettingType.String]: String,
+
[ExtensionSettingType.Select]: Select,
+
[ExtensionSettingType.MultiSelect]: MultiSelect,
+
[ExtensionSettingType.List]: List,
+
[ExtensionSettingType.Dictionary]: Dictionary
+
};
+
const element = elements[setting.type];
+
if (element == null) return <></>;
+
return React.createElement(element, { ext, name, setting, disabled });
+
}
+
+
export default function Settings({ ext }: { ext: MoonbaseExtension }) {
+
const { Flex } = CommonComponents;
+
return (
+
<Flex className="moonbase-settings" direction={Flex.Direction.VERTICAL}>
+
{Object.entries(ext.manifest.settings!).map(([name, setting]) => (
+
<Setting
+
ext={ext}
+
key={name}
+
name={name}
+
setting={setting}
+
disabled={ext.state === ExtensionState.NotDownloaded}
+
/>
+
))}
+
</Flex>
+
);
+
}
+64
packages/core-extensions/src/moonbase/webpackModules/ui/index.tsx
···
+
import React from "@moonlight-mod/wp/common_react";
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
+
import { Text, TabBar } from "@moonlight-mod/wp/common_components";
+
+
import ExtensionsPage from "./extensions";
+
import ConfigPage from "./config";
+
+
const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports;
+
+
const { Divider } = spacepack.findByCode(".default.HEADER_BAR")[0].exports
+
.default;
+
const TitleBarClasses = spacepack.findByCode("iconWrapper:", "children:")[0]
+
.exports;
+
const TabBarClasses = spacepack.findByCode("nowPlayingColumn:")[0].exports;
+
+
export const pages: Record<
+
string,
+
{
+
name: string;
+
element: React.FunctionComponent;
+
}
+
> = {
+
extensions: {
+
name: "Extensions",
+
element: ExtensionsPage
+
},
+
config: {
+
name: "Config",
+
element: ConfigPage
+
}
+
};
+
+
export function Moonbase() {
+
const [selectedTab, setSelectedTab] = React.useState(Object.keys(pages)[0]);
+
+
return (
+
<>
+
<div className={`${TitleBarClasses.children} ${Margins.marginBottom20}`}>
+
<Text
+
className={TitleBarClasses.titleWrapper}
+
variant="heading-lg/semibold"
+
tag="h2"
+
>
+
Moonbase
+
</Text>
+
<Divider />
+
<TabBar
+
selectedItem={selectedTab}
+
onItemSelect={setSelectedTab}
+
type="top-pill"
+
className={TabBarClasses.tabBar}
+
>
+
{Object.entries(pages).map(([id, page]) => (
+
<TabBar.Item key={id} id={id} className={TabBarClasses.item}>
+
{page.name}
+
</TabBar.Item>
+
))}
+
</TabBar>
+
</div>
+
+
{React.createElement(pages[selectedTab].element)}
+
</>
+
);
+
}
+7
packages/core-extensions/src/moonbase/wp.d.ts
···
+
declare module "@moonlight-mod/wp/moonbase_ui" {
+
export * from "core-extensions/src/moonbase/webpackModules/ui";
+
}
+
+
declare module "@moonlight-mod/wp/moonbase_stores" {
+
export * from "core-extensions/src/moonbase/webpackModules/stores";
+
}
-1
packages/types/package.json
···
},
"dependencies": {
"@types/flux": "^3.1.12",
-
"@types/node": "^20.6.2",
"@types/react": "^18.2.22",
"csstype": "^3.1.2",
"standalone-electron-types": "^1.0.0"
+6 -5
packages/types/src/import.d.ts
···
declare module "@moonlight-mod/wp/common_components" {
import { CoreExtensions } from "@moonlight-mod/types";
-
const components: CoreExtensions.CommonComponents;
-
export default components;
-
export = components;
+
const CommonComponent: CoreExtensions.CommonComponents;
+
export = CommonComponent;
}
declare module "@moonlight-mod/wp/common_flux" {
import { CoreExtensions } from "@moonlight-mod/types";
const Flux: CoreExtensions.CommonFlux;
-
export default Flux;
+
// FIXME: This is wrong, the default export differs from the named exports.
+
export = Flux;
}
declare module "@moonlight-mod/wp/common_fluxDispatcher" {
···
const Dispatcher: CoreExtensions.CommonFluxDispatcher;
export default Dispatcher;
}
+
+
declare module "@moonlight-mod/wp/common_stores";
declare module "@moonlight-mod/wp/common_react" {
import React from "react";
···
import { CoreExtensions } from "@moonlight-mod/types";
export const Settings: CoreExtensions.Settings;
export default Settings;
-
export = Settings;
}
declare module "@moonlight-mod/wp/markdown_markdown" {
-1
packages/types/src/index.ts
···
-
/// <reference types="node" />
/// <reference types="standalone-electron-types" />
/// <reference types="react" />
/// <reference types="flux" />
-1
packages/types/tsconfig.json
···
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
-
"skipLibCheck": true,
"moduleResolution": "bundler",
"jsx": "react",
"declaration": true
-7
pnpm-lock.yaml
···
'@types/flux':
specifier: ^3.1.12
version: 3.1.12
-
'@types/node':
-
specifier: ^20.6.2
-
version: 20.6.2
'@types/react':
specifier: ^18.2.22
version: 18.2.22
···
/@types/node@18.17.17:
resolution: {integrity: sha512-cOxcXsQ2sxiwkykdJqvyFS+MLQPLvIdwh5l6gNg8qF6s+C7XSkEWOZjK+XhUZd+mYvHV/180g2cnCcIl4l06Pw==}
-
dev: false
-
-
/@types/node@20.6.2:
-
resolution: {integrity: sha512-Y+/1vGBHV/cYk6OI1Na/LHzwnlNCAfU3ZNGrc1LdRe/LAIbdDPTTv/HU3M7yXN448aTVDq3eKRm2cg7iKLb8gw==}
dev: false
/@types/prop-types@15.7.6:
-1
tsconfig.json
···
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
-
"skipLibCheck": true,
"moduleResolution": "bundler",
"baseUrl": "./packages/",
"jsx": "react",