💻 React !

This commit is contained in:
2025-12-04 22:00:08 +01:00
parent af26eac13d
commit 98c73f61a0
11 changed files with 318 additions and 19 deletions

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
node-linker=hoisted

View File

@@ -6,7 +6,7 @@
"editor.formatOnSave": true
},
"[typescriptreact]": {
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"eslint.run": "onType",

View File

@@ -39,7 +39,9 @@
"@flamework/components": "^1.3.2",
"@flamework/core": "^1.3.2",
"@flamework/networking": "^1.3.2",
"@rbxts/fusion-0.3-temp": "^0.7.2",
"@rbxts/object-utils": "^1.0.4"
"@rbxts/object-utils": "^1.0.4",
"@rbxts/pretty-react-hooks": "^0.6.4",
"@rbxts/react": "^17.2.3",
"@rbxts/react-roblox": "^17.2.3"
}
}

70
pnpm-lock.yaml generated
View File

@@ -17,12 +17,18 @@ importers:
'@flamework/networking':
specifier: ^1.3.2
version: 1.3.2(@flamework/core@1.3.2)
'@rbxts/fusion-0.3-temp':
specifier: ^0.7.2
version: 0.7.2
'@rbxts/object-utils':
specifier: ^1.0.4
version: 1.0.4
'@rbxts/pretty-react-hooks':
specifier: ^0.6.4
version: 0.6.4(@rbxts/react-roblox@17.2.3)(@rbxts/react@17.2.3)
'@rbxts/react':
specifier: ^17.2.3
version: 17.2.3
'@rbxts/react-roblox':
specifier: ^17.2.3
version: 17.2.3
devDependencies:
'@eslint/eslintrc':
specifier: ^3.3.1
@@ -173,18 +179,36 @@ packages:
'@rbxts/compiler-types@3.0.0-types.0':
resolution: {integrity: sha512-VGOHJPoL7+56NTatMGqQj3K7xWuzEV+aP4QD5vZiHu+bcff3kiTmtoadaF6NkJrmwfFAvbsd4Dg764ZjWNceag==}
'@rbxts/fusion-0.3-temp@0.7.2':
resolution: {integrity: sha512-204MueZ6/3sps9bf4H+usqAL3VO+M5cmHwQnRci0LEF/Q4yV1re/ID96BTYTaWlohI0Vn+yKE45iFYNoclhk5Q==}
'@rbxts/maid@1.1.0':
resolution: {integrity: sha512-bVWXZ0p2M3OJzPzvN5fY0T4s37ezUMY7EX31Xspp7Ds4C/K9yE4MHMRXjtlNvsYVPmoc5tdhAbpZY02Veix5lg==}
'@rbxts/object-utils@1.0.4':
resolution: {integrity: sha512-dLLhf022ipV+9i910sOE7kl9losKHoon0WgeerHqVMQA5EYsLUsVT2AxhJuhk8MiDn5oJ2GiFofE/LadY9TpJQ==}
'@rbxts/pretty-react-hooks@0.6.4':
resolution: {integrity: sha512-yiuM3kOD0s2x0GFCUVfFdeSDJRX2ZycS88WOvqydaYwPIZ5rYDjzHjOzRgSpAzGcTVOFXsO64QOtD6svAdadzg==}
peerDependencies:
'@rbxts/react': '*'
'@rbxts/react-roblox': '*'
'@rbxts/react-roblox@17.2.3':
resolution: {integrity: sha512-YgS+C3tf92iM1NTK1f6R2O2G8+nbcKFnblA0mTs2Nt878OsWLgxhnhkCBZ6wYv+yeDGD9/sssKHZ6s2LzUWTrQ==}
'@rbxts/react-vendor@17.2.3':
resolution: {integrity: sha512-dWCiVelvTK6h920geaFH/OQ5zoWHyhyqyfH51hKx2/iVZ6OiiyNVJ5UqEIkJMG4HsJ8JnkTORizV3+MtQ41BtA==}
'@rbxts/react@17.2.3':
resolution: {integrity: sha512-IoOB8e2W8MxT0MKWFf0Ls0vT61ImYL6ctHQgz5+oazma6z8bqFtJjgPxOuy6jmMSuyfBe7rYp/muou0kIjZT+Q==}
'@rbxts/ripple@0.9.3':
resolution: {integrity: sha512-GmwjQl7a8pvGPGQYLmaAcf2RBPpneOL5P1+U8CzTHt9tBhCdk0TEJ/MQoTv9JnHD62RMBE1jogQBvuGgl9VXAg==}
'@rbxts/services@1.6.0':
resolution: {integrity: sha512-YH02E1/tGHeMXB0Mam8qPdAZa2fjQWdylEscSL4Zn0RIaQeByYImA2qUsXM3nBdhcyKHDwltzSznDKyCGfFEWA==}
'@rbxts/set-timeout@1.1.2':
resolution: {integrity: sha512-P/A0IiH9wuZdSJYr4Us0MDFm61nvIFR0acfKFHLkcOsgvIgELC90Up9ugiSsaMEHRIcIcO5UjE39LuS3xTzQHw==}
'@rbxts/signal@1.1.1':
resolution: {integrity: sha512-WX+ONE+ld4pG9PvRkR8OgDld9NpaV1RfXyUIw+Q2oXP/5rehkYzvt20NWtrLAP3NhMc5inYInLd+hnufey6nFw==}
@@ -197,9 +221,6 @@ packages:
'@rbxts/types@1.0.887':
resolution: {integrity: sha512-vhp4vIKEfl1TaBcKxBxU5dTFsVNqLkvefoF9pw/Epb+Jk9T+0g4SFQQtBdCu3UdZcBGK/PWk/IJeEyoRNDMkSw==}
'@rbxts/ui-labs@2.4.2':
resolution: {integrity: sha512-9cyzDYN4mM7KSupZpbHRCUWCfSHzy8LCqa9Czys2jaKYUCn8oV4j5AE+5jTrHD/8RcltP5EvCSFIFwB0N/NfxA==}
'@roblox-ts/luau-ast@2.0.0':
resolution: {integrity: sha512-cmMi093IdwBOLVxwuordhM8AmtbyTIyRpsTbB0D/JauidW4SXsQRQowSwWjHo4QP0DRJBXvOIlxtqEQi50uNzQ==}
@@ -1078,16 +1099,37 @@ snapshots:
'@rbxts/compiler-types@3.0.0-types.0': {}
'@rbxts/fusion-0.3-temp@0.7.2':
dependencies:
'@rbxts/ui-labs': 2.4.2
'@rbxts/maid@1.1.0': {}
'@rbxts/object-utils@1.0.4': {}
'@rbxts/pretty-react-hooks@0.6.4(@rbxts/react-roblox@17.2.3)(@rbxts/react@17.2.3)':
dependencies:
'@rbxts/react': 17.2.3
'@rbxts/react-roblox': 17.2.3
'@rbxts/ripple': 0.9.3
'@rbxts/services': 1.6.0
'@rbxts/set-timeout': 1.1.2
'@rbxts/react-roblox@17.2.3':
dependencies:
'@rbxts/react': 17.2.3
'@rbxts/react-vendor': 17.2.3
'@rbxts/react-vendor@17.2.3': {}
'@rbxts/react@17.2.3':
dependencies:
'@rbxts/react-vendor': 17.2.3
'@rbxts/ripple@0.9.3': {}
'@rbxts/services@1.6.0': {}
'@rbxts/set-timeout@1.1.2':
dependencies:
'@rbxts/services': 1.6.0
'@rbxts/signal@1.1.1': {}
'@rbxts/t@2.2.1': {}
@@ -1096,8 +1138,6 @@ snapshots:
'@rbxts/types@1.0.887': {}
'@rbxts/ui-labs@2.4.2': {}
'@roblox-ts/luau-ast@2.0.0': {}
'@roblox-ts/path-translator@1.1.0':

View File

@@ -0,0 +1,30 @@
import { Controller, OnStart } from "@flamework/core";
import React from "@rbxts/react";
import { StrictMode } from "@rbxts/react";
import { createPortal, createRoot } from "@rbxts/react-roblox";
import { Players, StarterGui } from "@rbxts/services";
import PlayersBoard from "shared/gui/players_board";
@Controller()
class GuiController implements OnStart {
onStart(): void {
StarterGui.SetCoreGuiEnabled(Enum.CoreGuiType.All, false);
StarterGui.SetCoreGuiEnabled(Enum.CoreGuiType.Chat, true);
try {
StarterGui.SetCore("ResetButtonCallback", true);
} catch (error) {}
const root = createRoot(new Instance("Folder"));
root.render(
<StrictMode>
{createPortal(
<screengui>
<PlayersBoard />
</screengui>,
(Players.LocalPlayer as unknown as { PlayerGui: PlayerGui }).PlayerGui,
)}
</StrictMode>,
);
}
}

View File

@@ -0,0 +1,4 @@
import { hoarcekat } from "@rbxts/pretty-react-hooks";
import PlayersBoard from ".";
export = hoarcekat(PlayersBoard);

View File

@@ -0,0 +1,108 @@
import { lerp, useMotion } from "@rbxts/pretty-react-hooks";
import React, { Component, ReactNode, StrictMode, useEffect, useState } from "@rbxts/react";
import { Players } from "@rbxts/services";
import Profile from "./profile";
function PlayerEntry({
player,
selectedPlayer,
setSelectedPlayer,
}: {
player: Player;
setSelectedPlayer: React.Dispatch<React.SetStateAction<Player | undefined>>;
selectedPlayer: Player | undefined;
}) {
const [enable, enableMotor] = useMotion(0);
useEffect(() => {
if (player === selectedPlayer) {
enableMotor.spring(90);
} else {
enableMotor.spring(0);
}
}, [selectedPlayer]);
return (
<frame BackgroundTransparency={1}>
<uistroke Color={new Color3(1, 1, 1)} Thickness={3} Transparency={0.2} />
<textbutton
Text={player.DisplayName}
TextScaled={true}
BackgroundTransparency={0.8}
BackgroundColor3={new Color3(0.26, 0.65, 0.91)}
TextColor3={new Color3(1, 1, 1)}
TextTransparency={0.2}
BorderColor3={new Color3(1, 1, 1)}
Size={UDim2.fromScale(1, 1)}
Event={{
Activated: () => {
setSelectedPlayer(player === selectedPlayer ? undefined : player);
},
}}
></textbutton>
<textlabel
Text={">"}
TextColor3={new Color3(1, 1, 1)}
TextScaled={true}
BackgroundTransparency={1}
Size={new UDim2(0.5, 0, 0.5, 0)}
Position={UDim2.fromScale(0, 0.25)}
SizeConstraint={Enum.SizeConstraint.RelativeYY}
Rotation={enable.map((t) => t)}
></textlabel>
</frame>
);
}
export default function PlayersBoard() {
const [players, setPlayers] = useState<Player[]>(Players.GetPlayers());
const [selectedPlayer, setSelectedPlayer] = useState<Player>();
useEffect(() => {
const playerAdded = Players.PlayerAdded.Connect(() => {
setPlayers(Players.GetPlayers());
});
const playerRemoving = Players.PlayerRemoving.Connect(() => {
setPlayers(Players.GetPlayers());
});
return () => {
playerAdded.Disconnect();
playerRemoving.Disconnect();
};
}, []);
return (
<>
<frame
Size={UDim2.fromScale(0.2, 0.6)}
Position={UDim2.fromScale(0.8, 0.1)}
BackgroundColor3={new Color3(0, 0.72, 1)}
BackgroundTransparency={0.7}
>
<frame
BorderColor3={new Color3(1, 1, 1)}
BorderSizePixel={5}
BackgroundTransparency={1}
Position={new UDim2(0, 10, 0, 10)}
Size={new UDim2(1, -20, 1, -20)}
>
<scrollingframe
CanvasSize={UDim2.fromScale(1, 0)}
Size={new UDim2(1, 0, 1, 0)}
AutomaticCanvasSize={Enum.AutomaticSize.Y}
BackgroundTransparency={1}
ScrollingDirection={Enum.ScrollingDirection.Y}
>
<uigridlayout CellSize={new UDim2(1, 0, 0, 50)} CellPadding={new UDim2()} />
{players.map((p) => (
<PlayerEntry
player={p}
selectedPlayer={selectedPlayer}
setSelectedPlayer={setSelectedPlayer}
/>
))}
</scrollingframe>
<uistroke Color={new Color3(1, 1, 1)} Thickness={5} Transparency={0.2} />
</frame>
</frame>
{selectedPlayer ? <Profile player={selectedPlayer} /> : undefined}
</>
);
}

View File

@@ -0,0 +1,17 @@
import { StrictMode } from "@rbxts/react";
import Profile from ".";
import React from "@rbxts/react";
import { createRoot } from "@rbxts/react-roblox";
import { Players } from "@rbxts/services";
export = function (target: Instance) {
const root = createRoot(target);
root.render(
<StrictMode>
<Profile player={Players.GetPlayers()[0]} />
</StrictMode>,
);
return function () {
root.unmount();
};
};

View File

@@ -0,0 +1,97 @@
import React, { useEffect, useRef, useState } from "@rbxts/react";
import { LocalizationService, Players } from "@rbxts/services";
function TimePlayed({ targetTime }: { targetTime: number }) {
const [time, setTime] = useState();
const [id, setId] = useState<DateTime>();
useEffect(() => {
let killed = false;
setId(DateTime.fromUnixTimestamp(targetTime));
task.spawn(() => {
while (true) {
if (killed) return;
}
});
return () => {
killed = true;
};
}, [targetTime]);
}
export default function Profile({ player }: { player: Player }) {
const [userIcon, setUserIcon] = useState<string>();
const ref = useRef<ImageLabel>();
useEffect(() => {
const [image] = Players.GetUserThumbnailAsync(
player.UserId,
Enum.ThumbnailType.HeadShot,
Enum.ThumbnailSize.Size180x180,
);
setUserIcon(image);
}, [player]);
const reeloffset = ref.current?.AbsoluteSize.X ?? 0;
return (
<frame
Size={new UDim2(0.5, 0, 0.5, 0)}
Position={new UDim2(0.3, -10, 0.1, 0)}
BackgroundColor3={new Color3(0, 0.72, 1)}
BackgroundTransparency={0.7}
>
<frame
BorderColor3={new Color3(1, 1, 1)}
BorderSizePixel={5}
BackgroundTransparency={1}
Position={new UDim2(0, 10, 0, 10)}
Size={new UDim2(1, -20, 1, -20)}
>
<frame Size={new UDim2(1, 0, 0.4, 0)} BackgroundTransparency={1}>
<imagelabel
Image={userIcon}
Size={new UDim2(1, -20, 1, -20)}
Position={UDim2.fromOffset(10, 10)}
SizeConstraint={Enum.SizeConstraint.RelativeYY}
BackgroundTransparency={1}
ref={ref}
>
<uicorner CornerRadius={new UDim(1, 1)} />
<uistroke Thickness={5} />
</imagelabel>
<textlabel
Text={player.DisplayName}
Size={new UDim2(1, -reeloffset, 0.3, 0)}
BackgroundTransparency={1}
TextScaled={true}
Position={new UDim2(0, reeloffset, 0, 0)}
TextColor3={new Color3(0, 0, 0)}
/>
<textlabel
Text={"@" + player.Name}
Size={new UDim2(1, -reeloffset, 0.2, 0)}
BackgroundTransparency={1}
TextScaled={true}
Position={new UDim2(0, reeloffset, 0.25, 0)}
TextColor3={new Color3(0.13, 0.13, 0.13)}
/>
<textlabel
Text={DateTime.fromUnixTimestamp().FormatLocalTime("LTS", LocalizationService.RobloxLocaleId)}
Size={new UDim2(1, -reeloffset, 0.3, 0)}
BackgroundTransparency={1}
TextScaled={true}
Position={new UDim2(0, reeloffset, 0.45, 0)}
TextColor3={new Color3(0, 0, 0)}
/>
<textlabel
Text={`Lev : ${0}`}
Size={new UDim2(1, -reeloffset, 0.3, 0)}
BackgroundTransparency={1}
TextScaled={true}
Position={new UDim2(0, reeloffset, 0.7, 0)}
TextColor3={new Color3(0, 0, 0)}
/>
<uistroke Color={new Color3(1, 1, 1)} Thickness={5} Transparency={0.2} />
</frame>
<uistroke Color={new Color3(1, 1, 1)} Thickness={5} Transparency={0.2} />
</frame>
</frame>
);
}

View File

@@ -4,7 +4,7 @@
"allowSyntheticDefaultImports": true,
"downlevelIteration": true,
"jsx": "react",
"jsxFactory": "Fusion.jsx",
"jsxFactory": "React.createElement",
"jsxFragmentFactory": "React.Fragment",
"module": "commonjs",
"moduleResolution": "Node",