feat: LCEOnline instead of LCELive

This commit is contained in:
neoapps-dev 2026-07-10 18:39:54 +03:00
parent dc0354a28f
commit ab397f4bb9
2 changed files with 122 additions and 27 deletions

View file

@ -17,19 +17,20 @@ const LceOnlineView = memo(function LceOnlineView({
const { playPressSound, playBackSound } = useAudio();
const game = useGame();
const [isSignedIn, setIsSignedIn] = useState(lceOnlineService.signedIn);
const [currentTab, setCurrentTab] = useState<"friends" | "requests">(
const [currentTab, setCurrentTab] = useState<"friends" | "requests" | "invites">(
"friends",
);
const [focusIndex, setFocusIndex] = useState<number | null>(0);
const [friends, setFriends] = useState<string[]>([]);
const [incomingReqs, setIncomingReqs] = useState<string[]>([]);
const [outgoingReqs, setOutgoingReqs] = useState<string[]>([]);
const [invites, setInvites] = useState<Array<{ from: string; sessionid: string }>>([]);
const [isHosting, setIsHosting] = useState(false);
const [isAddingFriend, setIsAddingFriend] = useState(false);
const [addFriendUsername, setAddFriendUsername] = useState("");
const addFriendInputRef = useRef<HTMLInputElement>(null);
const [errorModal, setErrorModal] = useState<string | null>(null);
const [joinTarget, setJoinTarget] = useState<string | null>(null);
const [joinTarget, setJoinTarget] = useState<{ sessionId: string; hostName: string } | null>(null);
const [authMode, setAuthMode] = useState<"login" | "register">("login");
const [authUsername, setAuthUsername] = useState("");
const [authPassword, setAuthPassword] = useState("");
@ -48,6 +49,12 @@ const LceOnlineView = memo(function LceOnlineView({
} catch (e: unknown) {
console.error(e);
}
try {
const invitesData = await lceOnlineService.getInvites();
setInvites(invitesData);
} catch {
setInvites([]);
}
};
useEffect(() => {
@ -120,7 +127,7 @@ const LceOnlineView = memo(function LceOnlineView({
type MenuItem = {
id: string;
type: "button" | "friend" | "request_in" | "request_out";
type: "button" | "friend" | "request_in" | "request_out" | "invite";
label: string;
onClick: () => void;
onClickSecondary?: () => void;
@ -166,10 +173,9 @@ const LceOnlineView = memo(function LceOnlineView({
type: "friend",
label: f,
onClick: () => handleAction(() => lceOnlineService.removeFriend(f)),
onClickSecondary: () => {
playPressSound();
setJoinTarget(f);
},
onClickSecondary: isHosting
? () => handleAction(() => lceOnlineService.sendInvite(f))
: undefined,
});
});
} else if (currentTab === "requests") {
@ -193,6 +199,21 @@ const LceOnlineView = memo(function LceOnlineView({
handleAction(() => lceOnlineService.declineFriendRequest(r)),
});
});
} else if (currentTab === "invites") {
invites.forEach((inv) => {
items.push({
id: `invite_${inv.from}`,
type: "invite",
label: inv.from,
onClick: () =>
handleAction(async () => {
const sessionId = await lceOnlineService.acceptInvite(inv.from);
setJoinTarget({ sessionId, hostName: inv.from });
}),
onClickSecondary: () =>
handleAction(() => lceOnlineService.declineInvite(inv.from)),
});
});
}
return items;
@ -201,11 +222,12 @@ const LceOnlineView = memo(function LceOnlineView({
friends,
incomingReqs,
outgoingReqs,
invites,
playPressSound,
isHosting,
]);
const tabs: ("friends" | "requests")[] = ["friends", "requests"];
const tabs: ("friends" | "requests" | "invites")[] = ["friends", "requests", "invites"];
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (errorModal) {
@ -455,21 +477,23 @@ const LceOnlineView = memo(function LceOnlineView({
<div className="flex space-x-3 pr-2 shrink-0">
{item.type === "friend" && (
<>
<button
className={`px-6 h-12 flex items-center justify-center font-bold text-base outline-none uppercase tracking-widest mc-text-shadow ${isFocused ? "text-white shadow-md" : "text-gray-300"}`}
style={{
backgroundImage:
"url('/images/button_highlighted.png')",
backgroundSize: "100% 100%",
imageRendering: "pixelated",
}}
onClick={(e) => {
e.stopPropagation();
item.onClickSecondary?.();
}}
>
JOIN
</button>
{item.onClickSecondary && (
<button
className={`px-6 h-12 flex items-center justify-center font-bold text-base outline-none uppercase tracking-widest mc-text-shadow ${isFocused ? "text-white shadow-md" : "text-gray-300"}`}
style={{
backgroundImage:
"url('/images/button_highlighted.png')",
backgroundSize: "100% 100%",
imageRendering: "pixelated",
}}
onClick={(e) => {
e.stopPropagation();
item.onClickSecondary?.();
}}
>
INVITE
</button>
)}
<button
className={`px-6 h-12 flex items-center justify-center font-bold text-base outline-none uppercase tracking-widest mc-text-shadow ${isFocused ? "text-white shadow-md" : "text-gray-300"}`}
style={{
@ -498,6 +522,37 @@ const LceOnlineView = memo(function LceOnlineView({
CANCEL
</button>
)}
{item.type === "invite" && (
<>
<button
className={`px-6 h-12 flex items-center justify-center font-bold text-base outline-none uppercase tracking-widest mc-text-shadow ${isFocused ? "text-white shadow-md" : "text-gray-300"}`}
style={{
backgroundImage:
"url('/images/button_highlighted.png')",
backgroundSize: "100% 100%",
imageRendering: "pixelated",
}}
onClick={item.onClick}
>
ACCEPT
</button>
<button
className={`px-6 h-12 flex items-center justify-center font-bold text-base outline-none uppercase tracking-widest mc-text-shadow ${isFocused ? "text-white shadow-md" : "text-gray-300"}`}
style={{
backgroundImage:
"url('/images/Button_Background.png')",
backgroundSize: "100% 100%",
imageRendering: "pixelated",
}}
onClick={(e) => {
e.stopPropagation();
item.onClickSecondary?.();
}}
>
DECLINE
</button>
</>
)}
{item.type === "request_in" && (
<>
<button
@ -583,6 +638,13 @@ const LceOnlineView = memo(function LceOnlineView({
{incomingReqs.length}
</span>
)}
{t === "invites" && invites.length > 0 && (
<span
className={`ml-3 text-white text-base px-3 py-1 rounded-full shadow-inner border-2 font-normal ${currentTab === t ? "bg-[#d72f2f] border-[#8a1a1a]" : "bg-[#a81f1f] border-[#111]"}`}
>
{invites.length}
</span>
)}
</div>
</button>
))}
@ -717,12 +779,12 @@ const LceOnlineView = memo(function LceOnlineView({
editions={game.editions}
installs={game.installs}
invite={{
inviteId: joinTarget,
from: joinTarget,
inviteId: joinTarget.sessionId,
from: joinTarget.hostName,
hostIp: "",
hostPort: 0,
hostName: joinTarget,
signalingSessionId: joinTarget,
hostName: joinTarget.hostName,
signalingSessionId: joinTarget.sessionId,
status: "pending",
}}
/>

View file

@ -210,6 +210,39 @@ export class LceOnlineService {
throw new Error(res);
}
}
async sendInvite(target: string): Promise<void> {
const res = await this.request<string>("POST", "/invite", target);
if (typeof res === "string" && res !== "Invite Sent") {
throw new Error(res);
}
}
async acceptInvite(from: string): Promise<string> {
const res = await this.request<string>("POST", "/acceptinvite", from);
if (typeof res !== "string") throw new Error("Failed to accept invite");
return res;
}
async declineInvite(from: string): Promise<void> {
try {
await this.request("POST", "/declineinvite", from);
} catch (e: unknown) {
const msg = e instanceof Error ? e.message : "";
if (msg !== "Declined Invite") throw e;
}
}
async getInvites(): Promise<Array<{ from: string; sessionid: string }>> {
const res = await this.request<string>("GET", "/getinvites", null);
if (typeof res !== "string") return [];
try {
const parsed = JSON.parse(res);
return Array.isArray(parsed) ? parsed : [];
} catch {
return [];
}
}
}
export const lceOnlineService = new LceOnlineService();