feat: more ui tweaks

This commit is contained in:
neoapps-dev
2026-09-04 15:51:09 +03:00
parent f92e1e77de
commit 9b24dcb53f
24 changed files with 352 additions and 113 deletions
+5 -5
View File
@@ -36,7 +36,7 @@ const SkinViewer = memo(function SkinViewer({
const mountRef = useRef<HTMLDivElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
const [focusIndex, setFocusIndex] = useState(0);
const { legacyMode } = useConfig();
const { legacyMode, animationsEnabled } = useConfig();
const overlaysRef = useRef<THREE.Mesh[]>([]);
const capeRef = useRef<THREE.Group | null>(null);
const capeOrigRef = useRef<{ y: number; rx: number; meshY: number } | null>(
@@ -679,10 +679,10 @@ const SkinViewer = memo(function SkinViewer({
return (
<motion.div
ref={containerRef}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{ duration: useConfig().animationsEnabled ? 0.3 : 0 }}
initial={{ opacity: animationsEnabled ? 0 : 1 }}
animate={{ opacity: 1 }}
exit={{ opacity: animationsEnabled ? 0 : 1 }}
transition={{ duration: animationsEnabled ? 0.3 : 0 }}
className={`absolute ${legacyMode ? "left-[calc(50vw-340px)]" : "left-16"} ${legacyMode ? "top-1/2" : "top-[40%]"} -translate-y-1/2 flex flex-col items-center gap-1 outline-none z-10`}
style={style}
>
+4 -4
View File
@@ -14,7 +14,7 @@ import type { Edition } from "../../types/edition";
const HomeView = memo(function HomeView() {
const { t } = useTranslation();
const { setActiveView, focusSection, onNavigateToSkin } = useUI();
const { profile, legacyMode } = useConfig();
const { profile, legacyMode, animationsEnabled } = useConfig();
const { playPressSound } = useAudio();
const {
handleLaunch,
@@ -147,10 +147,10 @@ const HomeView = memo(function HomeView() {
return (
<motion.div
tabIndex={-1}
initial={{ opacity: 0, y: 10 }}
initial={{ opacity: animationsEnabled ? 0 : 1, y: animationsEnabled ? 10 : 0 }}
animate={{ opacity: isFocusedSection ? 1 : 0.5, y: 0 }}
exit={{ opacity: 0, y: 10 }}
transition={{ duration: useConfig().animationsEnabled ? 0.3 : 0 }}
exit={{ opacity: animationsEnabled ? 0 : 1, y: animationsEnabled ? 10 : 0 }}
transition={{ duration: animationsEnabled ? 0.3 : 0 }}
className="relative w-full max-w-[540px] flex flex-col space-y-3 outline-none"
>
{buttonsVal.map((btn, i) => (
+5 -2
View File
@@ -257,7 +257,8 @@ export default function PckEditorView() {
await TauriService.writeBinaryFile(path, asset.data);
showNotification(t("pckEditor.exported", { name: fileName }));
} catch (err: unknown) {
if (err !== "CANCELED") showNotification(t("pckEditor.exportFailed"), "error");
if (err !== "CANCELED")
showNotification(t("pckEditor.exportFailed"), "error");
}
};
@@ -589,7 +590,9 @@ export default function PckEditorView() {
}}
className={`${pck.xmlSupport ? "text-[#FFFF55]" : "text-white/20"} text-sm uppercase hover:underline`}
>
{pck.xmlSupport ? t("pckEditor.enabled") : t("pckEditor.disabled")}
{pck.xmlSupport
? t("pckEditor.enabled")
: t("pckEditor.disabled")}
</button>
</div>
<div className="flex items-center gap-2 ml-auto">
+223 -72
View File
@@ -51,6 +51,8 @@ const SettingsView = memo(function SettingsView() {
setAndroidRunner: _setAndroidRuntime,
androidAudioBackend,
setAndroidAudioBackend,
proxy,
setProxy,
} = useConfig();
const { currentTrack, skipTrack, tracks, playPressSound, playBackSound } =
useAudio();
@@ -80,6 +82,9 @@ const SettingsView = memo(function SettingsView() {
const [argsInput, setArgsInput] = useState("");
const [prefixInput, setPrefixInput] = useState("");
const [envVarsInput, setEnvVarsInput] = useState("");
const [launcherSubMenu, setLauncherSubMenu] = useState<
"display" | "network" | "integrations" | "linux" | "data" | null
>(null);
const [showModal, setShowModal] = useState<
"args" | "prefix" | "envVars" | null
>(null);
@@ -287,6 +292,13 @@ const SettingsView = memo(function SettingsView() {
small?: boolean;
color?: string;
textured?: boolean;
}
| {
id: string;
label: string;
type: "textinput";
value: string;
onChange: (val: string) => void;
};
const settingsItems = useMemo<SettingsItem[]>(() => {
@@ -477,44 +489,122 @@ const SettingsView = memo(function SettingsView() {
},
});
} else if (currentSubMenu === "launcher") {
if (!isAndroid) {
if (!launcherSubMenu) {
items.push({
id: "fullscreen",
label: `${t("settings.startInFullscreen")}: ${startFullscreen ? t("common.on") : t("common.off")}`,
id: "cat_display",
label: t("settings.launcherDisplay"),
type: "button",
onClick: handleFullscreenToggle,
textured: true,
onClick: () => {
playPressSound();
setLauncherSubMenu("display");
setFocusIndex(0);
},
});
items.push({
id: "rpc",
label: `${t("settings.discordRpc")}: ${rpcEnabled ? t("common.on") : t("common.off")}`,
id: "cat_network",
label: t("settings.launcherNetwork"),
type: "button",
onClick: handleRpcToggle,
textured: true,
onClick: () => {
playPressSound();
setLauncherSubMenu("network");
setFocusIndex(0);
},
});
}
items.push({
id: "skip_intro",
label: `${t("settings.skipIntro")}: ${skipIntro ? t("common.on") : t("common.off")}`,
type: "button",
onClick: handleSkipIntroToggle,
});
items.push({
id: "legacy",
label: `${t("settings.legacyMode")}: ${legacyMode ? t("common.on") : t("common.off")}`,
type: "button",
onClick: handleLegacyToggle,
});
items.push({
id: "languages",
label: t("settings.languages"),
type: "button",
textured: true,
onClick: () => {
playPressSound();
setCurrentSubMenu("language");
setFocusIndex(0);
},
});
if (isLinux && !isAndroid) {
items.push({
id: "cat_integrations",
label: t("settings.launcherMisc"),
type: "button",
textured: true,
onClick: () => {
playPressSound();
setLauncherSubMenu("integrations");
setFocusIndex(0);
},
});
if (isLinux && !isAndroid) {
items.push({
id: "cat_linux",
label: t("settings.launcherLinux"),
type: "button",
textured: true,
onClick: () => {
playPressSound();
setLauncherSubMenu("linux");
setFocusIndex(0);
},
});
}
items.push({
id: "cat_data",
label: t("settings.launcherData"),
type: "button",
textured: true,
onClick: () => {
playPressSound();
setLauncherSubMenu("data");
setFocusIndex(0);
},
});
} else if (launcherSubMenu === "display") {
if (!isAndroid) {
items.push({
id: "fullscreen",
label: `${t("settings.startInFullscreen")}: ${startFullscreen ? t("common.on") : t("common.off")}`,
type: "button",
onClick: handleFullscreenToggle,
});
}
items.push({
id: "animations",
label: `${t("settings.animations")}: ${animationsEnabled ? t("common.on") : t("common.off")}`,
type: "button",
onClick: handleAnimationsToggle,
});
items.push({
id: "skip_intro",
label: `${t("settings.skipIntro")}: ${skipIntro ? t("common.on") : t("common.off")}`,
type: "button",
onClick: handleSkipIntroToggle,
});
items.push({
id: "legacy",
label: `${t("settings.legacyMode")}: ${legacyMode ? t("common.on") : t("common.off")}`,
type: "button",
onClick: handleLegacyToggle,
});
} else if (launcherSubMenu === "network") {
items.push({
id: "proxy",
label: t("settings.httpProxy"),
type: "textinput",
value: proxy ?? "",
onChange: (val: string) => {
setProxy(val.trim() || undefined);
},
});
} else if (launcherSubMenu === "integrations") {
if (!isAndroid) {
items.push({
id: "rpc",
label: `${t("settings.discordRpc")}: ${rpcEnabled ? t("common.on") : t("common.off")}`,
type: "button",
onClick: handleRpcToggle,
});
}
items.push({
id: "languages",
label: t("settings.languages"),
type: "button",
textured: true,
onClick: () => {
playPressSound();
setCurrentSubMenu("language");
setFocusIndex(0);
},
});
} else if (launcherSubMenu === "linux") {
items.push({
id: "runner",
label: `${t("settings.runner")}: ${selectedRunnerName}`,
@@ -545,46 +635,46 @@ const SettingsView = memo(function SettingsView() {
}
},
});
}
if (!isAndroid) {
} else if (launcherSubMenu === "data") {
if (!isAndroid) {
items.push({
id: "export_settings",
label: t("settings.exportSettings"),
type: "button",
textured: true,
onClick: async () => {
playPressSound();
try {
await TauriService.exportSettings();
} catch (e) {
if (e !== "CANCELED") console.error(e);
}
},
});
items.push({
id: "import_settings",
label: t("settings.importSettings"),
type: "button",
textured: true,
onClick: async () => {
playPressSound();
try {
await TauriService.importSettings();
window.location.reload();
} catch (e) {
if (e !== "CANCELED") console.error(e);
}
},
});
}
items.push({
id: "export_settings",
label: t("settings.exportSettings"),
id: "reset_setup",
label: t("settings.resetSetup"),
type: "button",
onClick: async () => {
playPressSound();
try {
await TauriService.exportSettings();
} catch (e) {
if (e !== "CANCELED") console.error(e);
}
},
onClick: handleResetSetup,
color: "orange",
});
}
if (!isAndroid) {
items.push({
id: "import_settings",
label: t("settings.importSettings"),
type: "button",
onClick: async () => {
playPressSound();
try {
await TauriService.importSettings();
window.location.reload();
} catch (e) {
if (e !== "CANCELED") console.error(e);
}
},
});
}
items.push({
id: "reset_setup",
label: t("settings.resetSetup"),
type: "button",
onClick: handleResetSetup,
color: "orange",
});
} else if (currentSubMenu === "language") {
const availableLanguages = [
//neo: here goes the list, dont ask why its here
@@ -682,6 +772,9 @@ const SettingsView = memo(function SettingsView() {
} else if (currentSubMenu === "language") {
setCurrentSubMenu("launcher");
setFocusIndex(0);
} else if (currentSubMenu === "launcher" && launcherSubMenu) {
setLauncherSubMenu(null);
setFocusIndex(0);
} else {
setCurrentSubMenu("main");
setFocusIndex(0);
@@ -745,7 +838,10 @@ const SettingsView = memo(function SettingsView() {
return;
}
playBackSound();
if (currentSubMenu !== "main") {
if (launcherSubMenu) {
setLauncherSubMenu(null);
setFocusIndex(0);
} else if (currentSubMenu !== "main") {
setCurrentSubMenu(
currentSubMenu === "language" ? "launcher" : "main",
);
@@ -758,6 +854,13 @@ const SettingsView = memo(function SettingsView() {
const itemCount = settingsItems.length;
if (
document.activeElement instanceof HTMLInputElement &&
document.activeElement.type === "text"
) {
return;
}
if (e.key === "ArrowDown") {
setFocusIndex((prev) =>
prev === null || prev >= itemCount - 1 ? 0 : prev + 1,
@@ -791,6 +894,7 @@ const SettingsView = memo(function SettingsView() {
setActiveView,
currentSubMenu,
showModal,
launcherSubMenu,
]);
useEffect(() => {
@@ -835,13 +939,14 @@ const SettingsView = memo(function SettingsView() {
<motion.div
ref={containerRef}
tabIndex={-1}
initial={{ opacity: 0, scale: 0.95 }}
initial={{ opacity: animationsEnabled ? 0 : 1, scale: animationsEnabled ? 0.95 : 1 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
exit={{ opacity: animationsEnabled ? 0 : 1, scale: animationsEnabled ? 0.95 : 1 }}
transition={{ duration: animationsEnabled ? 0.3 : 0 }}
className="flex flex-col items-center w-full max-w-5xl outline-none"
>
{currentSubMenu === "main" ? (
{currentSubMenu === "main" ||
(currentSubMenu === "launcher" && !launcherSubMenu) ? (
<div className="w-full max-w-[680px] space-y-2 mb-4 p-6 flex flex-col items-center overflow-y-auto max-h-[55vh] settings-scrollbar">
{settingsItems.map((item, index) => {
if (item.id === "back") return null;
@@ -877,6 +982,29 @@ const SettingsView = memo(function SettingsView() {
);
}
if (item.type === "textinput") {
return (
<div
key={item.id}
data-index={index}
onMouseEnter={() => setFocusIndex(index)}
className="relative w-[480px] flex flex-col cursor-pointer transition-all outline-none shrink-0"
>
<span className="text-black text-base font-[var(--font-base)] mb-1">
{item.label}
</span>
<div className="mc-textinput-outer">
<input
type="text"
value={item.value}
onChange={(e) => item.onChange(e.target.value)}
className="mc-textinput w-full h-10 px-3 text-white text-base outline-none font-[var(--font-base)]"
/>
</div>
</div>
);
}
const isRed =
"color" in item && (item as { color: string }).color === "red";
const isSmall =
@@ -1015,6 +1143,29 @@ const SettingsView = memo(function SettingsView() {
);
}
if (item.type === "textinput") {
return (
<div
key={item.id}
data-index={index}
onMouseEnter={() => setFocusIndex(index)}
className="relative w-[600px] flex flex-col cursor-pointer transition-all outline-none shrink-0"
>
<span className="text-black text-base font-[var(--font-base)] mb-1">
{item.label}
</span>
<div className="mc-textinput-outer">
<input
type="text"
value={item.value}
onChange={(e) => item.onChange(e.target.value)}
className="mc-textinput w-full h-10 px-3 text-white text-base outline-none font-[var(--font-base)]"
/>
</div>
</div>
);
}
const isRed = item.type === "button" && item.color === "red";
const isSmall = item.type === "button" && !!item.small;
const isTextured = item.type === "button" && !!item.textured;
+3 -3
View File
@@ -531,9 +531,9 @@ const SkinsView = memo(function SkinsView() {
<motion.div
ref={containerRef}
tabIndex={-1}
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: useConfig().animationsEnabled ? 0.3 : 0 }}
className="flex flex-col items-center w-full max-w-3xl h-full outline-none"
>
+3
View File
@@ -112,6 +112,7 @@ export function LauncherProvider({ children }: { children: React.ReactNode }) {
configRaw.androidRunner,
configRaw.androidAudioBackend,
configRaw.goldmapperEnabled,
configRaw.proxy,
],
);
@@ -215,6 +216,7 @@ export function LauncherProvider({ children }: { children: React.ReactNode }) {
skipIntro: config.skipIntro,
instanceLaunchArgs: config.instanceLaunchArgs,
goldmapperEnabled: config.goldmapperEnabled,
httpProxy: config.proxy,
}).catch(console.error);
}
}, [
@@ -242,6 +244,7 @@ export function LauncherProvider({ children }: { children: React.ReactNode }) {
config.skipIntro,
config.instanceLaunchArgs,
config.goldmapperEnabled,
config.proxy,
]);
useEffect(() => {
+15
View File
@@ -391,3 +391,18 @@ body {
border-top: 2px solid #373737;
margin: 1.5rem 0;
}
.mc-textinput-outer {
border: 2px solid transparent;
image-rendering: pixelated;
}
.mc-textinput-outer:focus-within {
border: 2px solid #ffff00;
}
.mc-textinput {
background-color: #646464 !important;
border: 2px solid #323232 !important;
image-rendering: pixelated;
}
+6 -1
View File
@@ -37,6 +37,7 @@ export function useAppConfig() {
const [androidAudioBackend, setAndroidAudioBackend] = useLocalStorage<"alsa" | "pulseaudio">("lce-android-audio", "pulseaudio");
const [goldmapperEnabled, setGoldmapperEnabled] = useLocalStorage("lce-goldmapper", true);
const [goldmapperMappings, setGoldmapperMappings] = useState<GoldMapperMapping[] | undefined>();
const [proxy, setProxy] = useState<string | undefined>();
useEffect(() => {
TauriService.loadConfig().then((config) => {
if (config.username) setUsername(config.username);
@@ -65,6 +66,7 @@ export function useAppConfig() {
if (config.androidAudioBackend) setAndroidAudioBackend(config.androidAudioBackend);
if (config.goldmapperEnabled !== undefined) setGoldmapperEnabled(config.goldmapperEnabled);
if (config.goldmapperMappings) setGoldmapperMappings(config.goldmapperMappings);
if (config.httpProxy !== undefined) setProxy(config.httpProxy);
setIsLoaded(true);
});
}, []);
@@ -97,9 +99,10 @@ export function useAppConfig() {
androidAudioBackend,
goldmapperEnabled,
goldmapperMappings,
httpProxy: proxy,
}).catch(console.error);
}
}, [username, theme, linuxRunner, perfBoost, profile, customEditions, customPaths, customizations, animationsEnabled, vfxEnabled, rpcEnabled, startFullscreen, musicVol, sfxVol, legacyMode, mangohudEnabled, extraLaunchArgs, launchPrefix, launchEnvVars, skipIntro, isLoaded, instanceLaunchArgs, androidRunner, androidAudioBackend, goldmapperEnabled, goldmapperMappings]);
}, [username, theme, linuxRunner, perfBoost, profile, customEditions, customPaths, customizations, animationsEnabled, vfxEnabled, rpcEnabled, startFullscreen, musicVol, sfxVol, legacyMode, mangohudEnabled, extraLaunchArgs, launchPrefix, launchEnvVars, skipIntro, isLoaded, instanceLaunchArgs, androidRunner, androidAudioBackend, goldmapperEnabled, goldmapperMappings, proxy]);
return {
username,
@@ -159,5 +162,7 @@ export function useAppConfig() {
setGoldmapperEnabled,
goldmapperMappings,
setGoldmapperMappings,
proxy,
setProxy,
};
}
+7
View File
@@ -71,6 +71,8 @@
"launchPrefixDesc": "Command that wraps the entire launch (e.g. gamemoderun)",
"launchEnvVars": "Launch Env Vars",
"launchEnvVarsDesc": "One KEY=VALUE per line",
"httpProxy": "HTTP Proxy",
"httpProxyDesc": "Proxy used for all downloads. Leave blank for direct connection",
"startInFullscreen": "Start in Fullscreen",
"discordRpc": "Discord RPC",
"skipIntro": "Skip Intro",
@@ -83,6 +85,11 @@
"importSettings": "Import Settings",
"resetSetup": "Reset Setup",
"languages": "Languages",
"launcherDisplay": "Display",
"launcherNetwork": "Network",
"launcherMisc": "Misc",
"launcherLinux": "Linux",
"launcherData": "Data",
"stopGameButton": "STOP GAME",
"noPluginsInstalled": "No plugins installed",
"byAuthor": "by {{author}}",
+7
View File
@@ -71,6 +71,8 @@
"launchPrefixDesc": "Commande qui entoure le lancement entier (exemple: gamemoderun)",
"launchEnvVars": "Lancement Env Vars",
"launchEnvVarsDesc": "Une KEY=VALUE par ligne",
"httpProxy": "Proxy HTTP",
"httpProxyDesc": "Proxy utilisé pour tous les téléchargements. Laisser vide pour une connexion directe",
"startInFullscreen": "Lancer en plein écran",
"discordRpc": "Présence Discord",
"skipIntro": "Sauter l'intro",
@@ -83,6 +85,11 @@
"importSettings": "Importer les paramètres",
"resetSetup": "Réinitialliser",
"languages": "Langues",
"launcherDisplay": "Affichage",
"launcherNetwork": "Réseau",
"launcherMisc": "Divers",
"launcherLinux": "Linux",
"launcherData": "Données",
"stopGameButton": "Arreter le jeu",
"noPluginsInstalled": "Aucun plugins installé",
"byAuthor": "par {{author}}",
+7
View File
@@ -71,6 +71,8 @@
"launchPrefixDesc": "Команда для обёртки запуска (нап. gamemoderun)",
"launchEnvVars": "Переменные окружения",
"launchEnvVarsDesc": "По одной паре КЛЮЧ=ЗНАЧЕНИЕ на строку",
"httpProxy": "HTTP-прокси",
"httpProxyDesc": "Прокси для всех загрузок. Оставьте пустым для прямого подключения",
"startInFullscreen": "Запустить на полный экран",
"discordRpc": "Интеграция Discord",
"skipIntro": "Пропустить заставку",
@@ -83,6 +85,11 @@
"importSettings": "Импорт настроек",
"resetSetup": "Сбросить настройки",
"languages": "Языки",
"launcherDisplay": "Дисплей",
"launcherNetwork": "Сеть",
"launcherMisc": "Прочее",
"launcherLinux": "Linux",
"launcherData": "Данные",
"stopGameButton": "ЗАКРЫТЬ ИГРУ",
"noPluginsInstalled": "Плагины не установлены",
"byAuthor": "от {{author}}",
+1
View File
@@ -61,6 +61,7 @@ export interface AppConfig {
androidAudioBackend?: "alsa" | "pulseaudio";
goldmapperEnabled?: boolean;
goldmapperMappings?: GoldMapperMapping[];
httpProxy?: string;
}
export interface ThemePalette {