feat(ui/fonts): rework

This commit is contained in:
amsyarasyiq 2024-05-24 16:44:06 +08:00
parent 51c6b62f5c
commit 8da38589a2
10 changed files with 324 additions and 115 deletions

View file

@ -21,6 +21,7 @@
"license": "BSD-3-Clause",
"devDependencies": {
"@react-native-clipboard/clipboard": "1.10.0",
"@shopify/react-native-skia": "^1.3.0",
"@swc/core": "^1.4.17",
"@swc/helpers": "^0.5.11",
"@types/chroma-js": "~2.4.0",

51
pnpm-lock.yaml generated
View file

@ -19,6 +19,9 @@ devDependencies:
'@react-native-clipboard/clipboard':
specifier: 1.10.0
version: 1.10.0
'@shopify/react-native-skia':
specifier: ^1.3.0
version: 1.3.0
'@swc/core':
specifier: ^1.4.17
version: 1.4.17(@swc/helpers@0.5.11)
@ -436,6 +439,25 @@ packages:
nullthrows: 1.1.1
dev: true
/@shopify/react-native-skia@1.3.0:
resolution: {integrity: sha512-TsQM6e9Cb8V2dukFwdjVurpynb1Avw3mxl6DZCLqDugGaVOCoWIkcJWPQEW62uXN/qumJ0dFOCKaXudo+Y4Z4A==}
hasBin: true
peerDependencies:
react: '>=18.0'
react-native: '>=0.64'
react-native-reanimated: '>=2.0.0'
peerDependenciesMeta:
react:
optional: true
react-native:
optional: true
react-native-reanimated:
optional: true
dependencies:
canvaskit-wasm: 0.39.1
react-reconciler: 0.27.0
dev: true
/@swc/core-darwin-arm64@1.4.17:
resolution: {integrity: sha512-HVl+W4LezoqHBAYg2JCqR+s9ife9yPfgWSj37iIawLWzOmuuJ7jVdIB7Ee2B75bEisSEKyxRlTl6Y1Oq3owBgw==}
engines: {node: '>=10'}
@ -753,6 +775,10 @@ packages:
resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
dev: true
/@webgpu/types@0.1.21:
resolution: {integrity: sha512-pUrWq3V5PiSGFLeLxoGqReTZmiiXwY3jRkIG5sLLKjyqNxrwm/04b4nw7LSmGWJcKk59XOM/YRTUwOzo4MMlow==}
dev: true
/acorn-jsx@5.3.2(acorn@8.11.3):
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
@ -933,6 +959,12 @@ packages:
engines: {node: '>=6'}
dev: true
/canvaskit-wasm@0.39.1:
resolution: {integrity: sha512-Gy3lCmhUdKq+8bvDrs9t8+qf7RvcjuQn+we7vTVVyqgOVO1UVfHpsnBxkTZw+R4ApEJ3D5fKySl9TU11hmjl/A==}
dependencies:
'@webgpu/types': 0.1.21
dev: true
/chalk@4.1.2:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
@ -2033,6 +2065,19 @@ packages:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
dev: true
/react-reconciler@0.27.0:
resolution: {integrity: sha512-HmMDKciQjYmBRGuuhIaKA1ba/7a+UsM5FzOZsMO2JYHt9Jh8reCb7j1eDC95NOyUlKM9KRyvdx0flBuDvYSBoA==}
engines: {node: '>=0.10.0'}
peerDependencies:
react: ^18.0.0
peerDependenciesMeta:
react:
optional: true
dependencies:
loose-envify: 1.4.0
scheduler: 0.21.0
dev: true
/reflect.getprototypeof@1.0.5:
resolution: {integrity: sha512-62wgfC8dJWrmxv44CA36pLDnP6KKl3Vhxb7PL+8+qrrFMMoJij4vgiMP8zV4O8+CBMXY1mHxI5fITGHXFHVmQQ==}
engines: {node: '>= 0.4'}
@ -2107,6 +2152,12 @@ packages:
is-regex: 1.1.4
dev: true
/scheduler@0.21.0:
resolution: {integrity: sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==}
dependencies:
loose-envify: 1.4.0
dev: true
/semver@6.3.1:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true

View file

@ -1,39 +1,117 @@
import { Strings } from "@core/i18n";
import { CardWrapper } from "@core/ui/components/AddonCard";
import { RemoveModeContext } from "@core/ui/components/AddonPage";
import { requireAssetIndex } from "@lib/api/assets";
import { BundleUpdaterManager } from "@lib/api/native/modules";
import { useProxy } from "@lib/api/storage";
import { FontDefinition, fonts, removeFont, selectFont } from "@lib/managers/fonts";
import { FormCheckbox, IconButton, TableRow, TableRowGroup } from "@metro/common/components";
import { showToast } from "@ui/toasts";
import { useContext } from "react";
import { Pressable, View } from "react-native";
import { FontDefinition, fonts, selectFont } from "@lib/managers/fonts";
import { showConfirmationAlert } from "@lib/ui/alerts";
import { TextStyleSheet } from "@lib/ui/styles";
import { lazyDestructure } from "@lib/utils/lazy";
import { ButtonColors } from "@lib/utils/types";
import { findByProps } from "@metro";
import { NavigationNative, Skia, tokens } from "@metro/common";
import { Button, Card, IconButton, Stack, Text } from "@metro/common/components";
import { useMemo } from "react";
import { View } from "react-native";
export default function FontCard({ item: font, index }: CardWrapper<FontDefinition>) {
import FontEditor from "./FontEditor";
const { useToken } = lazyDestructure(() => findByProps("useToken"));
function FontPreview({ font }: { font: FontDefinition; }) {
const TEXT_NORMAL = useToken(tokens.colors.TEXT_NORMAL);
const { fontFamily: fontFamilyList, fontSize } = TextStyleSheet["text-md/medium"];
const fontFamily = fontFamilyList!.split(/,/g)[0];
const typeface = Skia.useFont(font.main[fontFamily])?.getTypeface();
const paragraph = useMemo(() => {
if (!typeface) return null;
const fMgr = SkiaApi.TypefaceFontProvider.Make();
fMgr.registerFont(typeface, fontFamily);
return SkiaApi.ParagraphBuilder.Make({}, fMgr)
.pushStyle({
color: SkiaApi.Color(TEXT_NORMAL),
fontFamilies: [fontFamily],
fontSize,
})
.addText("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.")
.pop()
.build();
}, [typeface]);
return (
// This does not work, actually :woeis:
<View style={{ height: 64 }}>
{typeface
? <Skia.Canvas style={{ height: 64 }}>
<Skia.Paragraph paragraph={paragraph} x={0} y={0} width={300} />
</Skia.Canvas>
: <View style={{ justifyContent: "center", alignItems: "center" }}>
<Text color="text-muted" variant="heading-lg/semibold">
Loading...
</Text>
</View>}
</View>
);
}
export default function FontCard({ item: font }: CardWrapper<FontDefinition>) {
useProxy(fonts);
const removeMode = useContext(RemoveModeContext);
const navigation = NavigationNative.useNavigation();
const selected = fonts.__selected === font.name;
return <View key={index} style={{ marginVertical: 4 }}>
<TableRowGroup>
<TableRow
label={font.name}
trailing={
<Pressable onPress={() => {
selectFont(selected ? null : font.name).then(() => {
showToast(Strings.RESTART_REQUIRED_TO_TAKE_EFFECT, requireAssetIndex("WarningIcon"));
});
}}>
{!removeMode ? <FormCheckbox checked={selected} /> : <IconButton
size="sm"
variant="secondary"
icon={requireAssetIndex("TrashIcon")}
onPress={() => removeFont(font.name)}
/>}
</Pressable>
}
/>
</TableRowGroup>
</View>;
return (
<Card>
<Stack spacing={16}>
<View style={{ flexDirection: "row", alignItems: "center" }}>
<View>
<Text variant="heading-lg/semibold">
{font.name}
</Text>
{/* TODO: Text wrapping doesn't work well */}
{/* <Text color="text-muted" variant="text-sm/semibold">
{font.description}
</Text> */}
</View>
<View style={{ marginLeft: "auto" }}>
<Stack spacing={12} direction="horizontal">
<IconButton
onPress={() => {
navigation.push("VendettaCustomPage", {
title: "Edit Font",
render: () => <FontEditor name={font.name} />
});
}}
size="sm"
variant="secondary"
disabled={selected}
icon={requireAssetIndex("PencilIcon")}
/>
<Button
size="sm"
variant={selected ? "secondary" : "primary"}
text={selected ? "Unapply" : "Apply"}
onPress={async () => {
await selectFont(selected ? null : font.name);
showConfirmationAlert({
title: Strings.HOLD_UP,
content: "Reload Discord to apply changes?",
confirmText: Strings.RELOAD,
cancelText: Strings.CANCEL,
confirmColor: ButtonColors.RED,
onConfirm: BundleUpdaterManager.reload
});
}}
/>
</Stack>
</View>
</View>
<FontPreview font={font} />
</Stack>
</Card>
);
}

View file

@ -1,7 +1,7 @@
import { formatString, Strings } from "@core/i18n";
import { requireAssetIndex } from "@lib/api/assets";
import { createProxy, useProxy } from "@lib/api/storage";
import { FontDefinition, saveFont, validateFont } from "@lib/managers/fonts";
import { FontDefinition, fonts, removeFont, saveFont, validateFont } from "@lib/managers/fonts";
import { getCurrentTheme } from "@lib/managers/themes";
import { safeFetch } from "@lib/utils";
import { NavigationNative } from "@metro/common";
@ -79,9 +79,10 @@ function RevengeFontsExtractor({ fonts, setName }: {
</View>;
}
function JsonFontImporter({ fonts, setName }: {
function JsonFontImporter({ fonts, setName, setSource }: {
fonts: Record<string, string>;
setName: (name: string) => void;
setSource: (source: string) => void;
}) {
const [fontLink, setFontLink] = useState<string>("");
const [saving, setSaving] = useState(false);
@ -113,6 +114,8 @@ function JsonFontImporter({ fonts, setName }: {
validateFont(json);
setName(json.name);
setSource(fontLink);
Object.assign(fonts, json.main);
})()
.then(() => actionSheet.hideActionSheet())
@ -124,23 +127,62 @@ function JsonFontImporter({ fonts, setName }: {
</View>;
}
function promptActionSheet(Component: any, fontEntries: Record<string, string>, setName: (name: string) => void) {
function EntryEditorActionSheet(props: {
fontEntries: Record<string, string>;
name: string;
}) {
const [familyName, setFamilyName] = useState<string>(props.name);
const [fontUrl, setFontUrl] = useState<string>(props.fontEntries[props.name]);
return <View style={{ padding: 8, paddingBottom: 16, gap: 12 }}>
<TextInput
autoFocus
size="md"
label={"Family Name (to override)"}
value={familyName}
placeholder={"ggsans-Bold"}
onChange={setFamilyName}
/>
<TextInput
size="md"
label={"Font URL"}
value={fontUrl}
placeholder={"https://link.to/the/font.ttf"}
onChange={setFontUrl}
/>
<Button
size="md"
variant="primary"
text={"Apply"}
onPress={() => {
delete props.fontEntries[props.name];
props.fontEntries[familyName] = fontUrl;
}}
/>
</View>;
}
function promptActionSheet(
Component: any,
fontEntries: Record<string, string>,
props: any
) {
actionSheet.openLazy(
Promise.resolve({
default: () => (
<ErrorBoundary>
<ActionSheet>
<BottomSheetTitleHeader title={"Import Font"} />
<Component fonts={fontEntries} setName={setName} />
<BottomSheetTitleHeader title="Import Font" />
<Component fonts={fontEntries} {...props} />
</ActionSheet>
</ErrorBoundary>
)
}),
"FontImporterActionSheet"
"FontEditorActionSheet"
);
}
function EntryRow({ fontEntry }: { fontEntry: Record<string, string>; }) {
function NewEntryRow({ fontEntry }: { fontEntry: Record<string, string>; }) {
const nameRef = useRef<string>();
const urlRef = useRef<string>();
@ -197,53 +239,88 @@ function EntryRow({ fontEntry }: { fontEntry: Record<string, string>; }) {
</View>;
}
export default function FontImporter() {
const [name, setName] = useState<string>();
export default function FontEditor(props: {
name?: string;
}) {
const [name, setName] = useState<string | undefined>(props.name);
const [source, setSource] = useState<string>();
const [importing, setIsImporting] = useState<boolean>(false);
const memoEntry = useMemo(() => createProxy({}).proxy, []);
const memoEntry = useMemo(() => {
return createProxy(props.name ? { ...fonts[props.name].main } : {}).proxy;
}, [props.name]);
const fontEntries: Record<string, string> = useProxy(memoEntry);
const navigation = NavigationNative.useNavigation();
return <ScrollView style={{ flex: 1 }} contentContainerStyle={{ paddingBottom: 38 }}>
<Stack style={{ paddingVertical: 24, paddingHorizontal: 12 }} spacing={12}>
<TableRowGroup title="Import">
{/** @ts-ignore */}
{getCurrentTheme()?.data?.fonts && <TableRow
label={Strings.LABEL_EXTRACT_FONTS_FROM_THEME}
subLabel={Strings.DESC_EXTRACT_FONTS_FROM_THEME}
icon={<TableRow.Icon source={requireAssetIndex("HammerIcon")} />}
onPress={() => promptActionSheet(RevengeFontsExtractor, fontEntries, setName)}
/>}
<TableRow
label={"Import font entries from a link"}
subLabel={"Directly import from a link with a pre-configured JSON file"}
icon={<TableRow.Icon source={requireAssetIndex("LinkIcon")} />}
onPress={() => promptActionSheet(JsonFontImporter, fontEntries, setName)}
/>
</TableRowGroup>
{!props.name
? <TableRowGroup title="Import">
{/** @ts-ignore */}
{getCurrentTheme()?.data?.fonts && <TableRow
label={Strings.LABEL_EXTRACT_FONTS_FROM_THEME}
subLabel={Strings.DESC_EXTRACT_FONTS_FROM_THEME}
icon={<TableRow.Icon source={requireAssetIndex("HammerIcon")} />}
onPress={() => promptActionSheet(RevengeFontsExtractor, fontEntries, { setName })}
/>}
<TableRow
label={"Import font entries from a link"}
subLabel={"Directly import from a link with a pre-configured JSON file"}
icon={<TableRow.Icon source={requireAssetIndex("LinkIcon")} />}
onPress={() => promptActionSheet(JsonFontImporter, fontEntries, { setName, setSource })}
/>
</TableRowGroup>
: <TableRowGroup title="Actions">
<TableRow
label={"Refetch fonts from source"}
icon={<TableRow.Icon source={requireAssetIndex("RetryIcon")} />}
onPress={async () => {
const ftCopy = { ...fonts[props.name!] };
await removeFont(props.name!);
await saveFont(ftCopy);
navigation.goBack();
}}
/>
<TableRow
label={"Delete font pack"}
icon={<TableRow.Icon source={requireAssetIndex("TrashIcon")} />}
onPress={() => removeFont(props.name!).then(() => navigation.goBack())}
/>
</TableRowGroup>}
<TextInput
size="md"
size="lg"
value={name}
label={Strings.FONT_NAME}
placeholder={"ggsans"}
placeholder={"Whitney"}
onChange={setName}
/>
<TableRowGroup title={"Font Entries"}>
<TableRowGroup title="Font Entries">
{Object.entries(fontEntries).map(([name, url]) => {
return <TableRow
label={name}
subLabel={url}
trailing={<IconButton
size="sm"
variant="secondary"
icon={requireAssetIndex("TrashIcon")}
onPress={() => delete fontEntries[name]}
/>}
trailing={<Stack spacing={2} direction="horizontal">
<IconButton
size="sm"
variant="secondary"
icon={requireAssetIndex("PencilIcon")}
onPress={() => promptActionSheet(EntryEditorActionSheet, fontEntries, {
name,
fontEntries,
})}
/>
<IconButton
size="sm"
variant="secondary"
icon={requireAssetIndex("TrashIcon")}
onPress={() => delete fontEntries[name]}
/>
</Stack>}
/>;
})}
<TableRow label={<EntryRow fontEntry={fontEntries} />} />
<TableRow label={<NewEntryRow fontEntry={fontEntries} />} />
</TableRowGroup>
<View style={{ flexDirection: "row", justifyContent: "flex-end", bottom: 0, left: 0 }}>
<Button
@ -251,25 +328,35 @@ export default function FontImporter() {
loading={importing}
disabled={importing || !name || Object.keys(fontEntries).length === 0}
variant="primary"
text={"Import"}
text={props.name ? "Save" : "Import"}
onPress={async () => {
if (!name) return;
setIsImporting(true);
saveFont({
spec: 1,
name: name,
main: fontEntries
})
.then(() => navigation.goBack())
.finally(() => setIsImporting(false));
if (!props.name) {
saveFont({
spec: 1,
name: name,
main: fontEntries,
__source: source
})
.then(() => navigation.goBack())
.finally(() => setIsImporting(false));
} else {
Object.assign(fonts[props.name], {
name: name,
main: fontEntries,
__edited: true
});
setIsImporting(false);
navigation.goBack();
}
}}
icon={requireAssetIndex("DownloadIcon")}
icon={requireAssetIndex(props.name ? "toast_image_saved" : "DownloadIcon")}
style={{ marginLeft: 8 }}
/>
</View>
</Stack>
</ScrollView>;
}

View file

@ -1,50 +1,30 @@
import { Strings } from "@core/i18n";
import AddonPage from "@core/ui/components/AddonPage";
import FontImporter from "@core/ui/settings/pages/Fonts/FontImporter";
import FontEditor from "@core/ui/settings/pages/Fonts/FontEditor";
import { useProxy } from "@lib/api/storage";
import { FontDefinition, fonts, installFont } from "@lib/managers/fonts";
import { settings } from "@lib/settings";
import { NavigationNative } from "@metro/common";
import { LegacyFormText } from "@metro/common/components";
import { useEffect, useState } from "react";
import { TouchableOpacity } from "react-native";
import FontCard from "./FontCard";
export default function Plugins() {
export default function Fonts() {
useProxy(settings);
useProxy(fonts);
const [removeMode, setRemoveMode] = useState(false);
const navigation = NavigationNative.useNavigation();
useEffect(() => {
const onPressCallback = () => {
setRemoveMode(x => !x);
};
navigation.setOptions({
headerRight: () => <TouchableOpacity onPress={onPressCallback}>
<LegacyFormText style={{ marginRight: 12 }}>
{removeMode ? Strings.DONE : Strings.REMOVE}
</LegacyFormText>
</TouchableOpacity>
});
}, [removeMode]);
return (
<AddonPage<FontDefinition>
title={Strings.FONTS}
fetchFunction={installFont}
items={fonts as Record<string, FontDefinition>}
safeModeMessage={Strings.SAFE_MODE_NOTICE_FONTS}
isRemoveMode={removeMode}
card={FontCard}
onFABPress={() => {
navigation.push("VendettaCustomPage", {
title: "Font Importer",
render: () => <FontImporter />
title: "Import Font",
render: () => <FontEditor />
});
}}
/>

View file

@ -1,13 +1,13 @@
import { formatString, Strings } from "@core/i18n";
import { requireAssetIndex } from "@lib/api/assets";
import { purgeStorage } from "@lib/api/storage";
import { purgeStorage, useProxy } from "@lib/api/storage";
import { BunnyPlugin, fetchPlugin, getSettings, removePlugin, startPlugin, stopPlugin } from "@lib/managers/plugins";
import { showConfirmationAlert } from "@lib/ui/alerts";
import { hideSheet } from "@lib/ui/sheets";
import { showToast } from "@lib/ui/toasts";
import { ButtonColors } from "@lib/utils/types";
import { clipboard } from "@metro/common";
import { ActionSheet, IconButton, TableRow, TableRowGroup, Text } from "@metro/common/components";
import { ActionSheet, ActionSheetRow, IconButton, TableRow, Text } from "@metro/common/components";
import { ScrollView, View } from "react-native";
interface InfoProps {
@ -16,16 +16,18 @@ interface InfoProps {
}
export default function PluginInfoActionSheet({ plugin, navigation }: InfoProps) {
const settings = getSettings(plugin.id);
useProxy(plugin);
return <ActionSheet scrollable>
<ScrollView style={{ gap: 12 }}>
const Settings = getSettings(plugin.id);
return <ActionSheet>
<ScrollView>
<View style={{ flexDirection: "row", alignItems: "center", paddingVertical: 24 }}>
<Text variant="heading-xl/semibold">
{plugin.manifest.name}
</Text>
<View style={{ marginLeft: "auto" }}>
{settings && <IconButton
{Settings && <IconButton
size="md"
variant="secondary"
icon={requireAssetIndex("SettingsIcon")}
@ -33,14 +35,14 @@ export default function PluginInfoActionSheet({ plugin, navigation }: InfoProps)
hideSheet("PluginInfoActionSheet");
navigation.push("VendettaCustomPage", {
title: plugin.manifest.name,
render: settings,
render: Settings,
});
}}
/>}
</View>
</View>
<TableRowGroup>
<TableRow
<ActionSheetRow.Group>
<ActionSheetRow
label={Strings.REFETCH}
icon={<TableRow.Icon source={requireAssetIndex("RetryIcon")} />}
onPress={async () => {
@ -54,9 +56,10 @@ export default function PluginInfoActionSheet({ plugin, navigation }: InfoProps)
}
if (plugin.enabled) await startPlugin(plugin.id);
hideSheet("PluginInfoActionSheet");
}}
/>
<TableRow
<ActionSheetRow
label={Strings.COPY_URL}
icon={<TableRow.Icon source={requireAssetIndex("copy")} />}
onPress={() => {
@ -64,7 +67,7 @@ export default function PluginInfoActionSheet({ plugin, navigation }: InfoProps)
showToast.showCopyToClipboard();
}}
/>
<TableRow
<ActionSheetRow
label={plugin.update ? Strings.DISABLE_UPDATES : Strings.ENABLE_UPDATES}
icon={<TableRow.Icon source={requireAssetIndex("ic_download_24px")} />}
onPress={() => {
@ -75,7 +78,7 @@ export default function PluginInfoActionSheet({ plugin, navigation }: InfoProps)
}), requireAssetIndex("toast_image_saved"));
}}
/>
<TableRow
<ActionSheetRow
label={Strings.CLEAR_DATA}
icon={<TableRow.Icon source={requireAssetIndex("ic_duplicate")} />}
onPress={() => showConfirmationAlert({
@ -108,10 +111,11 @@ export default function PluginInfoActionSheet({ plugin, navigation }: InfoProps)
);
if (plugin.enabled) await startPlugin(plugin.id);
hideSheet("PluginInfoActionSheet");
}
})}
/>
<TableRow
<ActionSheetRow
label={Strings.DELETE}
icon={<TableRow.Icon source={requireAssetIndex("ic_message_delete")} />}
onPress={() => showConfirmationAlert({
@ -126,10 +130,11 @@ export default function PluginInfoActionSheet({ plugin, navigation }: InfoProps)
} catch (e) {
showToast(String(e), requireAssetIndex("Small"));
}
hideSheet("PluginInfoActionSheet");
}
})}
/>
</TableRowGroup>
</ActionSheetRow.Group>
</ScrollView>
</ActionSheet>;
}

2
src/global.d.ts vendored
View file

@ -1,11 +1,11 @@
declare global {
var React: typeof import("react");
var SkiaApi: typeof import("@shopify/react-native-skia").Skia;
interface Window {
[key: string]: any;
vendetta: any;
bunny: typeof import("@lib");
React: typeof import("react");
}
}

View file

@ -7,9 +7,10 @@ type FontMap = Record<string, string>;
export interface FontDefinition {
spec: 1;
name: string;
previewText?: string;
description?: string;
main: FontMap;
__source?: string;
__edited?: boolean;
}
type FontStorage = Record<string, FontDefinition> & { __selected?: string; };
@ -43,7 +44,7 @@ export async function saveFont(data: string | FontDefinition, selected = false)
if (typeof data === "string") {
try {
fontDefJson = await (await safeFetch(data, { cache: "no-store" })).json();
fontDefJson = await (await safeFetch(data)).json();
fontDefJson.__source = data;
} catch (e) {
throw new Error(`Failed to fetch fonts at ${data}`, { cause: e });

View file

@ -20,6 +20,9 @@ export const HelpMessage = findByNameProxy("HelpMessage");
// React Native's included SafeAreaView only adds padding on iOS.
export const SafeAreaView = proxyLazy(() => findByProps("useSafeAreaInsets").SafeAreaView);
// ActionSheet
export const ActionSheetRow = findProp("ActionSheetRow");
// Buttons
export const Button = findSingular("Button") as t.Button;
export const TwinButtons = findProp("TwinButtons");

View file

@ -36,6 +36,9 @@ export const chroma = findByPropsProxy("brewer") as typeof import("chroma-js");
// Lodash
export const lodash = findByPropsProxy("forEachRight") as typeof import("lodash");
// Skia
export const Skia = findByPropsProxy("useFont") as typeof import("@shopify/react-native-skia");
// The node:util polyfill for RN
// TODO: Find types for this
export const util = findByPropsProxy("inspect", "isNullOrUndefined");