feat: remove more buttons from android

This commit is contained in:
neoapps-dev 2026-08-07 22:18:29 +03:00
parent af1897a333
commit 0f44a3761c
6 changed files with 321 additions and 242 deletions

View file

@ -144,13 +144,37 @@ const CapePreview = memo(function CapePreview({
render();
}
};
const onTouchStart = (e: TouchEvent) => {
const t = e.touches[0];
if (!t) return;
isDragging = true;
previousMousePosition = { x: t.clientX, y: t.clientY };
};
const onTouchMove = (e: TouchEvent) => {
const t = e.touches[0];
if (isDragging && groupRef.current && t) {
e.preventDefault();
groupRef.current.rotation.y +=
(t.clientX - previousMousePosition.x) * 0.01;
previousMousePosition = { x: t.clientX, y: t.clientY };
render();
}
};
const onTouchEnd = () => {
isDragging = false;
};
renderer.domElement.addEventListener("mousedown", onMouseDown);
window.addEventListener("mousemove", onMouseMove);
window.addEventListener("mouseup", onMouseUp);
renderer.domElement.addEventListener("touchstart", onTouchStart, { passive: true });
window.addEventListener("touchmove", onTouchMove, { passive: false });
window.addEventListener("touchend", onTouchEnd);
return () => {
active = false;
window.removeEventListener("mousemove", onMouseMove);
window.removeEventListener("mouseup", onMouseUp);
window.removeEventListener("touchmove", onTouchMove);
window.removeEventListener("touchend", onTouchEnd);
scene.traverse((object) => {
if (object instanceof THREE.Mesh) {
if (object.geometry) object.geometry.dispose();

View file

@ -404,15 +404,39 @@ const SkinViewer = memo(function SkinViewer({
requestRenderRef.current?.();
}
};
const onTouchStart = (e: TouchEvent) => {
const t = e.touches[0];
if (!t) return;
isDragging = true;
previousMousePosition = { x: t.clientX, y: t.clientY };
};
const onTouchMove = (e: TouchEvent) => {
const t = e.touches[0];
if (isDragging && t) {
e.preventDefault();
const delta = (t.clientX - previousMousePosition.x) * 0.01;
playerGroup.rotation.y += delta;
previousMousePosition = { x: t.clientX, y: t.clientY };
requestRenderRef.current?.();
}
};
const onTouchEnd = () => {
isDragging = false;
};
requestRenderRef.current = () => renderer.render(scene, camera);
requestRenderRef.current();
renderer.domElement.addEventListener("mousedown", onMouseDown);
window.addEventListener("mousemove", onMouseMove);
window.addEventListener("mouseup", onMouseUp);
renderer.domElement.addEventListener("touchstart", onTouchStart, { passive: true });
window.addEventListener("touchmove", onTouchMove, { passive: false });
window.addEventListener("touchend", onTouchEnd);
return () => {
window.removeEventListener("mousemove", onMouseMove);
window.removeEventListener("mouseup", onMouseUp);
window.removeEventListener("touchmove", onTouchMove);
window.removeEventListener("touchend", onTouchEnd);
scene.traverse((object) => {
if (object instanceof THREE.Mesh) {
if (object.geometry) object.geometry.dispose();

View file

@ -58,7 +58,7 @@ const SettingsView = memo(function SettingsView() {
runnerDownloadProgress,
downloadRunner,
} = useGame();
const { isLinux, isMac } = usePlatform();
const { isLinux, isMac, isAndroid } = usePlatform();
const [focusIndex, setFocusIndex] = useState<number | null>(null);
const [currentSubMenu, setCurrentSubMenu] = useState<
"main" | "audio" | "video" | "launcher" | "game" | "plugins"
@ -430,12 +430,14 @@ const SettingsView = memo(function SettingsView() {
type: "button",
onClick: handleFullscreenToggle,
});
items.push({
id: "rpc",
label: `Discord RPC: ${rpcEnabled ? "ON" : "OFF"}`,
type: "button",
onClick: handleRpcToggle,
});
if (!isAndroid) {
items.push({
id: "rpc",
label: `Discord RPC: ${rpcEnabled ? "ON" : "OFF"}`,
type: "button",
onClick: handleRpcToggle,
});
}
items.push({
id: "skip_intro",
label: `Skip Intro: ${skipIntro ? "ON" : "OFF"}`,
@ -448,7 +450,7 @@ const SettingsView = memo(function SettingsView() {
type: "button",
onClick: handleLegacyToggle,
});
if (isLinux) {
if (isLinux && !isAndroid) {
items.push({
id: "runner",
label: `Runner: ${selectedRunnerName}`,
@ -479,33 +481,37 @@ const SettingsView = memo(function SettingsView() {
});
}
items.push({
id: "export_settings",
label: "Export Settings",
type: "button",
onClick: async () => {
playPressSound();
try {
await TauriService.exportSettings();
} catch (e) {
if (e !== "CANCELED") console.error(e);
}
},
});
items.push({
id: "import_settings",
label: "Import Settings",
type: "button",
onClick: async () => {
playPressSound();
try {
await TauriService.importSettings();
window.location.reload();
} catch (e) {
if (e !== "CANCELED") console.error(e);
}
},
});
if (!isAndroid) {
items.push({
id: "export_settings",
label: "Export Settings",
type: "button",
onClick: async () => {
playPressSound();
try {
await TauriService.exportSettings();
} catch (e) {
if (e !== "CANCELED") console.error(e);
}
},
});
}
if (!isAndroid) {
items.push({
id: "import_settings",
label: "Import Settings",
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: "Reset Setup",
@ -553,6 +559,7 @@ const SettingsView = memo(function SettingsView() {
animationsEnabled,
layout,
isLinux,
isAndroid,
mangohudEnabled,
selectedRunnerName,
isRunnerDownloading,

View file

@ -7,7 +7,7 @@ interface SetupViewProps {
}
const SetupView: React.FC<SetupViewProps> = ({ onComplete }) => {
const { isLinux, isMac } = usePlatform();
const { isLinux, isMac, isAndroid } = usePlatform();
const {
username, setUsername,
setHasCompletedSetup,
@ -82,7 +82,7 @@ const SetupView: React.FC<SetupViewProps> = ({ onComplete }) => {
setCurrentStep(2);
setFocusIndex(0);
} else if (currentStep === 2) {
setConfigRpc(enableDiscordRPC);
if (!isAndroid) setConfigRpc(enableDiscordRPC);
setCurrentStep(3);
setFocusIndex(0);
} else if (currentStep === 3) {
@ -108,7 +108,7 @@ const SetupView: React.FC<SetupViewProps> = ({ onComplete }) => {
if (isLinux) count = runners.length + 2;
else if (isMac) count = 3;
else count = 2;
} else if (currentStep === 2) count = 4;
} else if (currentStep === 2) count = isAndroid ? 3 : 4;
else if (currentStep === 3) count = 2;
if (e.key === "ArrowDown" || e.key === "Tab") {
e.preventDefault();
@ -134,10 +134,16 @@ const SetupView: React.FC<SetupViewProps> = ({ onComplete }) => {
else if (focusIndex === 1) handleNext();
}
} else if (currentStep === 2) {
if (focusIndex === 0) { setEnableDiscordRPC(!enableDiscordRPC); playPressSound(); }
else if (focusIndex === 1) { playPressSound(); }
else if (focusIndex === 2) handleBack();
else if (focusIndex === 3) handleNext();
if (isAndroid) {
if (focusIndex === 0) { playPressSound(); }
else if (focusIndex === 1) handleBack();
else if (focusIndex === 2) handleNext();
} else {
if (focusIndex === 0) { setEnableDiscordRPC(!enableDiscordRPC); playPressSound(); }
else if (focusIndex === 1) { playPressSound(); }
else if (focusIndex === 2) handleBack();
else if (focusIndex === 3) handleNext();
}
} else if (currentStep === 3) {
if (focusIndex === 0) handleBack();
else if (focusIndex === 1) handleNext();
@ -146,7 +152,7 @@ const SetupView: React.FC<SetupViewProps> = ({ onComplete }) => {
};
window.addEventListener("keydown", handleKey);
return () => window.removeEventListener("keydown", handleKey);
}, [currentStep, focusIndex, runners, enableDiscordRPC, isLinux, isMac, tempUsername]);
}, [currentStep, focusIndex, runners, enableDiscordRPC, isLinux, isMac, isAndroid, tempUsername]);
const handleMacosSetup = async () => {
playPressSound();
@ -412,44 +418,46 @@ const SetupView: React.FC<SetupViewProps> = ({ onComplete }) => {
<div
className="p-5 flex flex-col gap-2 mc-options-bg"
>
<button
onClick={() => { playPressSound(); setEnableDiscordRPC(!enableDiscordRPC); }}
onMouseEnter={() => setFocusIndex(0)}
className={`w-full h-10 flex items-center justify-between px-4 transition-all outline-none border-none rounded
${focusIndex === 0 ? "bg-gray-200" : "bg-transparent"} hover:bg-gray-200`}
>
<span className={`tracking-widest uppercase text-lg ${focusIndex === 0 ? "text-[#FFFF55]" : "text-gray-800"}`}>
Discord RPC
</span>
<div className="relative w-6 h-6 shrink-0">
<img
src={focusIndex === 0 ? "/images/checkbox_highlighted.png" : "/images/checkbox.png"}
alt="checkbox"
className="w-full h-full object-contain"
style={{ imageRendering: "pixelated" }}
/>
{enableDiscordRPC && (
{!isAndroid && (
<button
onClick={() => { playPressSound(); setEnableDiscordRPC(!enableDiscordRPC); }}
onMouseEnter={() => setFocusIndex(0)}
className={`w-full h-10 flex items-center justify-between px-4 transition-all outline-none border-none rounded
${focusIndex === 0 ? "bg-gray-200" : "bg-transparent"} hover:bg-gray-200`}
>
<span className={`tracking-widest uppercase text-lg ${focusIndex === 0 ? "text-[#FFFF55]" : "text-gray-800"}`}>
Discord RPC
</span>
<div className="relative w-6 h-6 shrink-0">
<img
src="/images/check.png"
alt="checked"
className="absolute inset-0 w-full h-full object-contain"
src={focusIndex === 0 ? "/images/checkbox_highlighted.png" : "/images/checkbox.png"}
alt="checkbox"
className="w-full h-full object-contain"
style={{ imageRendering: "pixelated" }}
/>
)}
</div>
</button>
{enableDiscordRPC && (
<img
src="/images/check.png"
alt="checked"
className="absolute inset-0 w-full h-full object-contain"
style={{ imageRendering: "pixelated" }}
/>
)}
</div>
</button>
)}
<button
onClick={() => { playPressSound(); }}
onMouseEnter={() => setFocusIndex(1)}
onMouseEnter={() => setFocusIndex(isAndroid ? 0 : 1)}
className={`w-full h-10 flex items-center justify-between px-4 transition-all outline-none border-none rounded
${focusIndex === 1 ? "bg-gray-200" : "bg-transparent"} hover:bg-gray-200`}
${focusIndex === (isAndroid ? 0 : 1) ? "bg-gray-200" : "bg-transparent"} hover:bg-gray-200`}
>
<span className={`tracking-widest uppercase text-lg ${focusIndex === 1 ? "text-[#FFFF55]" : "text-gray-800"}`}>
<span className={`tracking-widest uppercase text-lg ${focusIndex === (isAndroid ? 0 : 1) ? "text-[#FFFF55]" : "text-gray-800"}`}>
Animations
</span>
<div className="relative w-6 h-6 shrink-0">
<img
src={focusIndex === 1 ? "/images/checkbox_highlighted.png" : "/images/checkbox.png"}
src={focusIndex === (isAndroid ? 0 : 1) ? "/images/checkbox_highlighted.png" : "/images/checkbox.png"}
alt="checkbox"
className="w-full h-full object-contain"
style={{ imageRendering: "pixelated" }}
@ -513,25 +521,27 @@ const SetupView: React.FC<SetupViewProps> = ({ onComplete }) => {
)}
</div>
</div>
<div className="flex items-center justify-between px-4 h-10">
<span className="text-gray-600 text-sm uppercase tracking-widest">Discord RPC</span>
<div className="relative w-5 h-5">
<img
src="/images/checkbox.png"
alt="checkbox"
className="w-full h-full object-contain"
style={{ imageRendering: "pixelated" }}
/>
{enableDiscordRPC && (
{!isAndroid && (
<div className="flex items-center justify-between px-4 h-10">
<span className="text-gray-600 text-sm uppercase tracking-widest">Discord RPC</span>
<div className="relative w-5 h-5">
<img
src="/images/check.png"
alt="checked"
className="absolute inset-0 w-full h-full object-contain"
src="/images/checkbox.png"
alt="checkbox"
className="w-full h-full object-contain"
style={{ imageRendering: "pixelated" }}
/>
)}
{enableDiscordRPC && (
<img
src="/images/check.png"
alt="checked"
className="absolute inset-0 w-full h-full object-contain"
style={{ imageRendering: "pixelated" }}
/>
)}
</div>
</div>
</div>
)}
</div>
</div>
)}
@ -543,16 +553,16 @@ const SetupView: React.FC<SetupViewProps> = ({ onComplete }) => {
onClick={handleBack}
onMouseEnter={() => {
if (currentStep === 3) setFocusIndex(0);
else if (currentStep === 2) setFocusIndex(2);
else if (currentStep === 2) setFocusIndex(isAndroid ? 1 : 2);
else if (currentStep === 1) setFocusIndex(isLinux ? runners.length : (isMac ? 1 : 0));
}}
className={`w-36 h-10 flex items-center justify-center transition-colors mc-text-shadow outline-none border-none
${(currentStep === 3 && focusIndex === 0) || (currentStep === 2 && focusIndex === 2) ||
${(currentStep === 3 && focusIndex === 0) || (currentStep === 2 && focusIndex === (isAndroid ? 1 : 2)) ||
(currentStep === 1 && ((isLinux && focusIndex === runners.length) || (isMac && focusIndex === 1) || (!isLinux && !isMac && focusIndex === 0)))
? "text-[#FFFF55]" : "text-white"}`}
style={navBtnStyle(
(currentStep === 3 && focusIndex === 0) ||
(currentStep === 2 && focusIndex === 2) ||
(currentStep === 2 && focusIndex === (isAndroid ? 1 : 2)) ||
(currentStep === 1 && ((isLinux && focusIndex === runners.length) || (isMac && focusIndex === 1) || (!isLinux && !isMac && focusIndex === 0)))
)}
>
@ -567,7 +577,7 @@ const SetupView: React.FC<SetupViewProps> = ({ onComplete }) => {
onMouseEnter={() => {
if (currentStep === 0) setFocusIndex(1);
else if (currentStep === 1) setFocusIndex(isLinux ? runners.length + 1 : (isMac ? 2 : 1));
else if (currentStep === 2) setFocusIndex(3);
else if (currentStep === 2) setFocusIndex(isAndroid ? 2 : 3);
else if (currentStep === 3) setFocusIndex(1);
}}
disabled={!canProceed()}
@ -575,13 +585,13 @@ const SetupView: React.FC<SetupViewProps> = ({ onComplete }) => {
disabled:opacity-50 disabled:cursor-not-allowed
${(currentStep === 0 && focusIndex === 1) ||
(currentStep === 1 && ((isLinux && focusIndex === runners.length + 1) || (isMac && focusIndex === 2) || (!isLinux && !isMac && focusIndex === 1))) ||
(currentStep === 2 && focusIndex === 3) ||
(currentStep === 2 && focusIndex === (isAndroid ? 2 : 3)) ||
(currentStep === 3 && focusIndex === 1)
? "text-[#FFFF55]" : "text-white"}`}
style={navBtnStyle(
(currentStep === 0 && focusIndex === 1) ||
(currentStep === 1 && ((isLinux && focusIndex === runners.length + 1) || (isMac && focusIndex === 2) || (!isLinux && !isMac && focusIndex === 1))) ||
(currentStep === 2 && focusIndex === 3) ||
(currentStep === 2 && focusIndex === (isAndroid ? 2 : 3)) ||
(currentStep === 3 && focusIndex === 1)
)}
>

View file

@ -14,6 +14,7 @@ import {
useGame,
} from "../../context/LauncherContext";
import { ScreenshotImage } from "../common/ScreenshotImage";
import { usePlatform } from "../../hooks/usePlatform";
import type { Edition } from "../../types/edition";
interface DeleteConfirmButtonProps {
label: string;
@ -84,6 +85,7 @@ const VersionsView = memo(function VersionsView() {
saveCustomPath,
} = useGame();
const { isDayTime } = useConfig();
const { isAndroid } = usePlatform();
const [focusIndex, setFocusIndex] = useState<number>(0);
const [focusBtn, setFocusBtn] = useState<number>(0);
const [isImportModalOpen, setIsImportModalOpen] = useState(false);
@ -510,7 +512,7 @@ const VersionsView = memo(function VersionsView() {
Update Available!
</button>
)}
{!isInstalled && (
{!isAndroid && !isInstalled && (
<button
onClick={async (e) => {
e.stopPropagation();
@ -605,157 +607,169 @@ const VersionsView = memo(function VersionsView() {
</button>
)}
<div className="h-[1px] bg-white/5 my-0.5 mx-1" />
<button
onClick={(e) => {
e.stopPropagation();
playPressSound();
TauriService.openInstanceFolder(edition.instanceId);
setOpenMenuId(null);
}}
className="w-full text-left px-3 py-2 text-xs text-[#dddddd] flex items-center gap-2 mc-text-shadow"
>
<img
src="/images/Folder_Icon.png"
alt=""
className="w-3.5 h-3.5 object-contain"
style={{ imageRendering: "pixelated" }}
/>
Open Folder
</button>
<button
onClick={(e) => {
e.stopPropagation();
playPressSound();
const PANORAMA_PROFILES = [
"legacy_evolved",
"vanilla_tu19",
"360revived",
"vanilla_tu24",
];
const panoId = PANORAMA_PROFILES.includes(
edition.id,
)
? edition.id
: "vanilla_tu19";
const panoramaUrl = `/panorama/${panoId}_Panorama_Background_${isDayTime ? "Day" : "Night"}.png`;
addToSteam(
edition.instanceId,
edition.name,
edition.titleImage ?? "",
panoramaUrl,
);
setOpenMenuId(null);
}}
className="w-full text-left px-3 py-2 text-xs text-[#dddddd] flex items-center gap-2 mc-text-shadow"
>
<img
src="/images/steam.png"
alt=""
className="w-3.5 h-3.5 object-contain invert brightness-0"
style={{ imageRendering: "pixelated" }}
/>
Add to Steam
</button>
<button
onClick={(e) => {
e.stopPropagation();
playPressSound();
handleImportWorld(edition.instanceId);
setOpenMenuId(null);
}}
className="w-full text-left px-3 py-2 text-xs text-[#dddddd] flex items-center gap-2 mc-text-shadow"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
className="w-3.5 h-3.5"
{!isAndroid && (
<button
onClick={(e) => {
e.stopPropagation();
playPressSound();
TauriService.openInstanceFolder(edition.instanceId);
setOpenMenuId(null);
}}
className="w-full text-left px-3 py-2 text-xs text-[#dddddd] flex items-center gap-2 mc-text-shadow"
>
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
<polyline points="7 10 12 15 17 10" />
<line x1="12" y1="15" x2="12" y2="3" />
</svg>
Import World
</button>
<button
onClick={(e) => {
e.stopPropagation();
playPressSound();
TauriService.backupInstance(edition.instanceId).catch((err) => {
if (err !== "CANCELED") console.error(err);
});
setOpenMenuId(null);
}}
className="w-full text-left px-3 py-2 text-xs text-[#dddddd] flex items-center gap-2 mc-text-shadow"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
className="w-3.5 h-3.5"
<img
src="/images/Folder_Icon.png"
alt=""
className="w-3.5 h-3.5 object-contain"
style={{ imageRendering: "pixelated" }}
/>
Open Folder
</button>
)}
{!isAndroid && (
<button
onClick={(e) => {
e.stopPropagation();
playPressSound();
const PANORAMA_PROFILES = [
"legacy_evolved",
"vanilla_tu19",
"360revived",
"vanilla_tu24",
];
const panoId = PANORAMA_PROFILES.includes(
edition.id,
)
? edition.id
: "vanilla_tu19";
const panoramaUrl = `/panorama/${panoId}_Panorama_Background_${isDayTime ? "Day" : "Night"}.png`;
addToSteam(
edition.instanceId,
edition.name,
edition.titleImage ?? "",
panoramaUrl,
);
setOpenMenuId(null);
}}
className="w-full text-left px-3 py-2 text-xs text-[#dddddd] flex items-center gap-2 mc-text-shadow"
>
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" />
<polyline points="17 21 17 13 7 13 7 21" />
<polyline points="7 3 7 8 15 8" />
</svg>
Backup
</button>
<button
onClick={async (e) => {
e.stopPropagation();
playPressSound();
setOpenMenuId(null);
try {
await TauriService.restoreInstance();
} catch (err) {
if (err !== "CANCELED") console.error(err);
}
}}
className="w-full text-left px-3 py-2 text-xs text-[#dddddd] flex items-center gap-2 mc-text-shadow"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
className="w-3.5 h-3.5"
<img
src="/images/steam.png"
alt=""
className="w-3.5 h-3.5 object-contain invert brightness-0"
style={{ imageRendering: "pixelated" }}
/>
Add to Steam
</button>
)}
{!isAndroid && (
<button
onClick={(e) => {
e.stopPropagation();
playPressSound();
handleImportWorld(edition.instanceId);
setOpenMenuId(null);
}}
className="w-full text-left px-3 py-2 text-xs text-[#dddddd] flex items-center gap-2 mc-text-shadow"
>
<path d="M21 12a9 9 0 1 1-9-9 9 9 0 0 1 9 9z" />
<polyline points="12 7 12 12 15 15" />
</svg>
Restore
</button>
<button
onClick={(e) => {
e.stopPropagation();
playPressSound();
setCustomizeTarget(edition);
setIsCustomizeModalOpen(true);
setOpenMenuId(null);
}}
className="w-full text-left px-3 py-2 text-xs text-[#dddddd] flex items-center gap-2 mc-text-shadow"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
className="w-3.5 h-3.5"
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
className="w-3.5 h-3.5"
>
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" />
<polyline points="7 10 12 15 17 10" />
<line x1="12" y1="15" x2="12" y2="3" />
</svg>
Import World
</button>
)}
{!isAndroid && (
<button
onClick={(e) => {
e.stopPropagation();
playPressSound();
TauriService.backupInstance(edition.instanceId).catch((err) => {
if (err !== "CANCELED") console.error(err);
});
setOpenMenuId(null);
}}
className="w-full text-left px-3 py-2 text-xs text-[#dddddd] flex items-center gap-2 mc-text-shadow"
>
<path d="M12 20h9M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z" />
</svg>
Customize
</button>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
className="w-3.5 h-3.5"
>
<path d="M19 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11l5 5v11a2 2 0 0 1-2 2z" />
<polyline points="17 21 17 13 7 13 7 21" />
<polyline points="7 3 7 8 15 8" />
</svg>
Backup
</button>
)}
{!isAndroid && (
<button
onClick={async (e) => {
e.stopPropagation();
playPressSound();
setOpenMenuId(null);
try {
await TauriService.restoreInstance();
} catch (err) {
if (err !== "CANCELED") console.error(err);
}
}}
className="w-full text-left px-3 py-2 text-xs text-[#dddddd] flex items-center gap-2 mc-text-shadow"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
className="w-3.5 h-3.5"
>
<path d="M21 12a9 9 0 1 1-9-9 9 9 0 0 1 9 9z" />
<polyline points="12 7 12 12 15 15" />
</svg>
Restore
</button>
)}
{!isAndroid && (
<button
onClick={(e) => {
e.stopPropagation();
playPressSound();
setCustomizeTarget(edition);
setIsCustomizeModalOpen(true);
setOpenMenuId(null);
}}
className="w-full text-left px-3 py-2 text-xs text-[#dddddd] flex items-center gap-2 mc-text-shadow"
>
<svg
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
className="w-3.5 h-3.5"
>
<path d="M12 20h9M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z" />
</svg>
Customize
</button>
)}
<button
onClick={(e) => {
e.stopPropagation();

View file

@ -4,10 +4,10 @@ export function usePlatform() {
if (typeof window === 'undefined') return { isLinux: false, isMac: false, isWindows: false, isAndroid: false };
const ua = window.navigator.userAgent.toLowerCase();
const plat = window.navigator.platform.toLowerCase();
const isLinux = plat.includes('linux') || ua.includes('linux');
const isMac = plat.includes('mac') || ua.includes('mac');
const isWindows = plat.includes('win') || ua.includes('win');
const isAndroid = ua.includes('android');
const isLinux = !isAndroid && (plat.includes('linux') || ua.includes('linux'));
const isMac = !isAndroid && (plat.includes('mac') || ua.includes('mac'));
const isWindows = !isAndroid && (plat.includes('win') || ua.includes('win'));
return { isLinux, isMac, isWindows, isAndroid };
}, []);