diff --git a/.npmrc b/.npmrc
new file mode 100644
index 0000000..cc8df9d
--- /dev/null
+++ b/.npmrc
@@ -0,0 +1 @@
+node-linker=hoisted
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 5dfb1c8..c5dfdea 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -6,7 +6,7 @@
"editor.formatOnSave": true
},
"[typescriptreact]": {
- "editor.defaultFormatter": "dbaeumer.vscode-eslint",
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true
},
"eslint.run": "onType",
diff --git a/package.json b/package.json
index 00fdaf5..c7fe1b2 100644
--- a/package.json
+++ b/package.json
@@ -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"
}
}
\ No newline at end of file
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 03ec599..f14ef63 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -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':
diff --git a/src/client/controllers/gui.ts b/src/client/controllers/gui.ts
deleted file mode 100644
index e69de29..0000000
diff --git a/src/client/controllers/gui.tsx b/src/client/controllers/gui.tsx
new file mode 100644
index 0000000..79acb6a
--- /dev/null
+++ b/src/client/controllers/gui.tsx
@@ -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(
+
+ {createPortal(
+
+
+ ,
+ (Players.LocalPlayer as unknown as { PlayerGui: PlayerGui }).PlayerGui,
+ )}
+ ,
+ );
+ }
+}
diff --git a/src/shared/gui/players_board/index.story.ts b/src/shared/gui/players_board/index.story.ts
new file mode 100644
index 0000000..65d8e07
--- /dev/null
+++ b/src/shared/gui/players_board/index.story.ts
@@ -0,0 +1,4 @@
+import { hoarcekat } from "@rbxts/pretty-react-hooks";
+import PlayersBoard from ".";
+
+export = hoarcekat(PlayersBoard);
diff --git a/src/shared/gui/players_board/index.tsx b/src/shared/gui/players_board/index.tsx
new file mode 100644
index 0000000..f0e2bb9
--- /dev/null
+++ b/src/shared/gui/players_board/index.tsx
@@ -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>;
+ selectedPlayer: Player | undefined;
+}) {
+ const [enable, enableMotor] = useMotion(0);
+ useEffect(() => {
+ if (player === selectedPlayer) {
+ enableMotor.spring(90);
+ } else {
+ enableMotor.spring(0);
+ }
+ }, [selectedPlayer]);
+ return (
+
+
+ {
+ setSelectedPlayer(player === selectedPlayer ? undefined : player);
+ },
+ }}
+ >
+ "}
+ 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)}
+ >
+
+ );
+}
+
+export default function PlayersBoard() {
+ const [players, setPlayers] = useState(Players.GetPlayers());
+ const [selectedPlayer, setSelectedPlayer] = useState();
+ useEffect(() => {
+ const playerAdded = Players.PlayerAdded.Connect(() => {
+ setPlayers(Players.GetPlayers());
+ });
+ const playerRemoving = Players.PlayerRemoving.Connect(() => {
+ setPlayers(Players.GetPlayers());
+ });
+ return () => {
+ playerAdded.Disconnect();
+ playerRemoving.Disconnect();
+ };
+ }, []);
+
+ return (
+ <>
+
+
+
+
+ {players.map((p) => (
+
+ ))}
+
+
+
+
+ {selectedPlayer ? : undefined}
+ >
+ );
+}
diff --git a/src/shared/gui/players_board/profile/index.story.tsx b/src/shared/gui/players_board/profile/index.story.tsx
new file mode 100644
index 0000000..6123153
--- /dev/null
+++ b/src/shared/gui/players_board/profile/index.story.tsx
@@ -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(
+
+
+ ,
+ );
+ return function () {
+ root.unmount();
+ };
+};
diff --git a/src/shared/gui/players_board/profile/index.tsx b/src/shared/gui/players_board/profile/index.tsx
new file mode 100644
index 0000000..61865f8
--- /dev/null
+++ b/src/shared/gui/players_board/profile/index.tsx
@@ -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();
+ 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();
+ const ref = useRef();
+ useEffect(() => {
+ const [image] = Players.GetUserThumbnailAsync(
+ player.UserId,
+ Enum.ThumbnailType.HeadShot,
+ Enum.ThumbnailSize.Size180x180,
+ );
+ setUserIcon(image);
+ }, [player]);
+ const reeloffset = ref.current?.AbsoluteSize.X ?? 0;
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/tsconfig.json b/tsconfig.json
index bc844de..69e7f35 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -4,7 +4,7 @@
"allowSyntheticDefaultImports": true,
"downlevelIteration": true,
"jsx": "react",
- "jsxFactory": "Fusion.jsx",
+ "jsxFactory": "React.createElement",
"jsxFragmentFactory": "React.Fragment",
"module": "commonjs",
"moduleResolution": "Node",