speed up discord loading time
this shouldn't break anything on mine, and I tested it, but still may give some issues
This commit is contained in:
parent
4c93b98a68
commit
da0a9bebe4
2 changed files with 155 additions and 24 deletions
|
|
@ -22,13 +22,14 @@ import {
|
|||
} from "@metro/common/components";
|
||||
import { ErrorBoundary, Search } from "@ui/components";
|
||||
import { isNotNil } from "es-toolkit";
|
||||
import fuzzysort from "fuzzysort";
|
||||
import {
|
||||
ComponentType,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { Image, ScrollView, View } from "react-native";
|
||||
|
||||
|
|
@ -198,6 +199,27 @@ export default function AddonPage<T extends object>({
|
|||
}
|
||||
}, [navigation]);
|
||||
|
||||
const fuzzysortRef = useRef<any>(null);
|
||||
const [fuzzLoaded, setFuzzLoaded] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
if (!fuzzysortRef.current) {
|
||||
import("fuzzysort")
|
||||
.then((m) => {
|
||||
if (cancelled) return;
|
||||
fuzzysortRef.current = m.default || m;
|
||||
setFuzzLoaded(true);
|
||||
})
|
||||
.catch(() => {
|
||||
// ignore dynamic import failures; we'll fallback to simple filtering
|
||||
});
|
||||
}
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const results = useMemo(() => {
|
||||
let values = props.items;
|
||||
if (props.resolveItem)
|
||||
|
|
@ -205,11 +227,84 @@ export default function AddonPage<T extends object>({
|
|||
const items = values.filter((i) => isNotNil(i) && typeof i === "object");
|
||||
if (!search && sortFn) items.sort(sortFn);
|
||||
|
||||
return fuzzysort.go(search, items, {
|
||||
keys: props.searchKeywords,
|
||||
all: true,
|
||||
// Prefer fuzzysort when loaded and a search query exists
|
||||
if (fuzzysortRef.current && search) {
|
||||
try {
|
||||
return fuzzysortRef.current.go(search, items, {
|
||||
keys: props.searchKeywords,
|
||||
all: true,
|
||||
});
|
||||
} catch {
|
||||
// If fuzzysort throws for some reason, fall back to the simple filter below
|
||||
}
|
||||
}
|
||||
|
||||
// If there is no search query, return items shaped similarly to fuzzysort results
|
||||
const makeFallbackResult = (obj: any, keys: any[]) => {
|
||||
const r: any = { obj };
|
||||
// current search query in the outer scope; only highlight when there's an active query
|
||||
const q = String(search ?? "")
|
||||
.toLowerCase()
|
||||
.trim();
|
||||
for (let k = 0; k < keys.length; k++) {
|
||||
const key = keys[k];
|
||||
let text = "";
|
||||
try {
|
||||
text =
|
||||
typeof key === "string" ? String(obj[key] ?? "") : String(key(obj));
|
||||
} catch {
|
||||
text = "";
|
||||
}
|
||||
r[k] = {
|
||||
target: text,
|
||||
highlight: (cb: any) => {
|
||||
// Do not highlight anything when there is no active search.
|
||||
if (!q) return [];
|
||||
const lower = text.toLowerCase();
|
||||
const idx = lower.indexOf(q);
|
||||
if (idx === -1) return [];
|
||||
// Split into before/match/after and invoke the callback for each segment so
|
||||
// consumers can render matched substring(s) with highlight styling while
|
||||
// preserving surrounding context.
|
||||
const parts: any[] = [];
|
||||
let i = 0;
|
||||
const before = text.slice(0, idx);
|
||||
const match = text.slice(idx, idx + q.length);
|
||||
const after = text.slice(idx + q.length);
|
||||
if (before) parts.push(cb(before, i++));
|
||||
parts.push(cb(match, i++));
|
||||
if (after) parts.push(cb(after, i++));
|
||||
return parts;
|
||||
},
|
||||
};
|
||||
}
|
||||
return r;
|
||||
};
|
||||
if (!search) {
|
||||
return items.map((obj: any) =>
|
||||
makeFallbackResult(obj, props.searchKeywords),
|
||||
);
|
||||
}
|
||||
|
||||
// Basic fallback: case-insensitive substring match across configured keys
|
||||
const q = String(search).toLowerCase();
|
||||
const filtered = items.filter((it: any) => {
|
||||
for (const key of props.searchKeywords) {
|
||||
try {
|
||||
const text =
|
||||
typeof key === "string" ? String(it[key] ?? "") : String(key(it));
|
||||
if (text.toLowerCase().includes(q)) return true;
|
||||
} catch {
|
||||
// ignore any accessor errors and continue
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}, [props.items, sortFn, search]);
|
||||
|
||||
return filtered.map((obj: any) =>
|
||||
makeFallbackResult(obj, props.searchKeywords),
|
||||
);
|
||||
}, [props.items, sortFn, search, fuzzLoaded]);
|
||||
|
||||
const onInstallPress = useCallback(() => {
|
||||
if (!props.installAction) return () => {};
|
||||
|
|
|
|||
|
|
@ -16,9 +16,8 @@ import {
|
|||
Text,
|
||||
} from "@metro/common/components";
|
||||
import { findByProps } from "@metro";
|
||||
import { useMemo, useState } from "react";
|
||||
import { useMemo, useState, useEffect } from "react";
|
||||
import { PixelRatio, View } from "react-native";
|
||||
import { WebView } from "react-native-webview";
|
||||
import previewHtml from "./preview.html";
|
||||
|
||||
import FontEditor from "./FontEditor";
|
||||
|
|
@ -36,6 +35,7 @@ const useStyles = createStyles({
|
|||
|
||||
function FontPreview({ font }: { font: FontDefinition }) {
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
const [WebViewComponent, setWebViewComponent] = useState<any>(null);
|
||||
const styles = useStyles();
|
||||
|
||||
const TEXT_DEFAULT = useToken(tokens.colors.TEXT_DEFAULT);
|
||||
|
|
@ -53,25 +53,61 @@ function FontPreview({ font }: { font: FontDefinition }) {
|
|||
[font.main, fontFamily, fontSize, TEXT_DEFAULT],
|
||||
);
|
||||
|
||||
// Memoize generated HTML to avoid string replacement on every render
|
||||
const html = useMemo(
|
||||
() => previewHtml.replace("$$props", JSON.stringify(props)),
|
||||
[props],
|
||||
);
|
||||
|
||||
// Lazy-load the WebView implementation so it's not included in the main bundle
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
import("react-native-webview")
|
||||
.then((m) => {
|
||||
if (cancelled) return;
|
||||
// support both default export and named WebView export
|
||||
setWebViewComponent(m.WebView || m.default || m);
|
||||
})
|
||||
.catch(() => {
|
||||
// ignore dynamic import failures; component will show placeholder
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<View style={{ width: "100%", height: 64 }}>
|
||||
<WebView
|
||||
onMessage={() => setLoaded(true)}
|
||||
source={{
|
||||
html: previewHtml.replace("$$props", JSON.stringify(props)),
|
||||
}}
|
||||
javaScriptEnabled
|
||||
scrollEnabled={false}
|
||||
overScrollMode="never"
|
||||
showsHorizontalScrollIndicator={false}
|
||||
showsVerticalScrollIndicator={false}
|
||||
pointerEvents="none"
|
||||
style={[
|
||||
styles.full,
|
||||
{ backgroundColor: "transparent", opacity: Number(loaded) },
|
||||
]}
|
||||
/>
|
||||
{!loaded && (
|
||||
{WebViewComponent ? (
|
||||
<>
|
||||
<WebViewComponent
|
||||
onMessage={() => setLoaded(true)}
|
||||
source={{ html }}
|
||||
javaScriptEnabled
|
||||
scrollEnabled={false}
|
||||
overScrollMode="never"
|
||||
showsHorizontalScrollIndicator={false}
|
||||
showsVerticalScrollIndicator={false}
|
||||
pointerEvents="none"
|
||||
style={[
|
||||
styles.full,
|
||||
{ backgroundColor: "transparent", opacity: Number(loaded) },
|
||||
]}
|
||||
/>
|
||||
{!loaded && (
|
||||
<View
|
||||
style={[
|
||||
styles.full,
|
||||
{ justifyContent: "center", alignItems: "center" },
|
||||
]}
|
||||
>
|
||||
<Text color="text-muted" variant="heading-lg/semibold">
|
||||
Loading...
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<View
|
||||
style={[
|
||||
styles.full,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue