feat(ui/fonts): rework
This commit is contained in:
parent
51c6b62f5c
commit
8da38589a2
10 changed files with 324 additions and 115 deletions
|
|
@ -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
51
pnpm-lock.yaml
generated
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>;
|
||||
}
|
||||
|
|
@ -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 />
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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
2
src/global.d.ts
vendored
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue