add mod menu & ignore place limit (fixes #14)

This commit is contained in:
Grant 2024-06-19 15:38:53 -06:00
parent a35f8ff59b
commit 78d97b52e3
8 changed files with 107 additions and 8 deletions

View File

@ -17,6 +17,7 @@ import { KeybindModal } from "./KeybindModal";
import { ProfileModal } from "./Profile/ProfileModal";
import { WelcomeModal } from "./Welcome/WelcomeModal";
import { InfoSidebar } from "./Info/InfoSidebar";
import { ModModal } from "./Moderation/ModModal";
const Chat = lazy(() => import("./Chat/Chat"));
@ -148,6 +149,7 @@ const AppInner = () => {
<ProfileModal />
<WelcomeModal />
<ModModal />
<ToastContainer position="top-left" />
</>

View File

@ -0,0 +1,64 @@
import {
Modal,
ModalBody,
ModalContent,
ModalFooter,
ModalHeader,
Switch,
} from "@nextui-org/react";
import { useAppContext } from "../../contexts/AppContext";
import { useCallback, useEffect, useState } from "react";
import { KeybindManager } from "../../lib/keybinds";
import { Canvas } from "../../lib/canvas";
export const ModModal = () => {
const { showModModal, setShowModModal, hasAdmin } = useAppContext();
const [bypassCooldown, setBypassCooldown_] = useState(false);
useEffect(() => {
setBypassCooldown_(Canvas.instance?.getCooldownBypass() || false);
const handleKeybind = () => {
if (!hasAdmin) {
console.warn("Unable to open mod menu; hasAdmin is not set");
return;
}
setShowModModal((m) => !m);
};
KeybindManager.on("TOGGLE_MOD_MENU", handleKeybind);
return () => {
KeybindManager.off("TOGGLE_MOD_MENU", handleKeybind);
};
}, []);
const setBypassCooldown = useCallback(
(value: boolean) => {
setBypassCooldown_(value);
Canvas.instance?.setCooldownBypass(value);
},
[setBypassCooldown_]
);
return (
<Modal isOpen={showModModal} onOpenChange={setShowModModal}>
<ModalContent>
{(onClose) => (
<>
<ModalHeader>Mod Menu</ModalHeader>
<ModalBody>
<Switch
isSelected={bypassCooldown}
onValueChange={setBypassCooldown}
>
Bypass placement cooldown
</Switch>
</ModalBody>
</>
)}
</ModalContent>
</Modal>
);
};

View File

@ -1,4 +1,4 @@
import {
import React, {
PropsWithChildren,
createContext,
useContext,
@ -43,6 +43,8 @@ interface IAppContext {
setProfile: (v?: string) => void;
hasAdmin: boolean;
showModModal: boolean;
setShowModModal: React.Dispatch<React.SetStateAction<boolean>>;
}
interface ICanvasPosition {
@ -119,6 +121,7 @@ export const AppContext = ({ children }: PropsWithChildren) => {
const [profile, setProfile] = useState<string>();
const [hasAdmin, setHasAdmin] = useState(false);
const [showModModal, setShowModModal] = useState(false);
useEffect(() => {
function loadSettings() {
@ -224,6 +227,8 @@ export const AppContext = ({ children }: PropsWithChildren) => {
setProfile,
infoSidebar,
setInfoSidebar,
showModModal,
setShowModModal,
}}
>
{!config && (

View File

@ -39,6 +39,8 @@ export class Canvas extends EventEmitter<CanvasEvents> {
} = {};
lastPlace: number | undefined;
private bypassCooldown = false;
constructor(canvas: HTMLCanvasElement, PanZoom: PanZoom) {
super();
Canvas.instance = this;
@ -99,6 +101,14 @@ export class Canvas extends EventEmitter<CanvasEvents> {
return this.PanZoom;
}
setCooldownBypass(value: boolean) {
this.bypassCooldown = value;
}
getCooldownBypass() {
return this.bypassCooldown;
}
getAllPixels() {
let pixels: {
x: number;
@ -252,11 +262,15 @@ export class Canvas extends EventEmitter<CanvasEvents> {
// }
Network.socket
.emitWithAck("place", {
x,
y,
color: this.Pallete.getSelectedColor()!.id,
})
.emitWithAck(
"place",
{
x,
y,
color: this.Pallete.getSelectedColor()!.id,
},
this.bypassCooldown
)
.then((ack) => {
if (ack.success) {
this.lastPlace = Date.now();

View File

@ -49,6 +49,11 @@ const KEYBINDS = enforceObjectType({
key: "KeyH",
},
],
TOGGLE_MOD_MENU: [
{
key: "KeyM",
},
],
});
class KeybindManager_ extends EventEmitter<{

View File

@ -27,6 +27,7 @@ export interface ServerToClientEvents {
export interface ClientToServerEvents {
place: (
pixel: Pixel,
bypassCooldown: boolean,
ack: (
_: PacketAck<
Pixel,

View File

@ -193,7 +193,7 @@ export class SocketServer {
});
});
socket.on("place", async (pixel, ack) => {
socket.on("place", async (pixel, bypassCooldown, ack) => {
if (!user) {
ack({ success: false, error: "no_user" });
return;
@ -212,7 +212,13 @@ export class SocketServer {
// force a user data update
await user.update(true);
if (user.pixelStack < 1) {
if (bypassCooldown && !user.isModerator) {
// only moderators can do this
ack({ success: false, error: "invalid_pixel" });
return;
}
if (!bypassCooldown && user.pixelStack < 1) {
ack({ success: false, error: "pixel_cooldown" });
return;
}

View File

@ -60,6 +60,8 @@ export class User {
this.lastPixelTime = userData.lastPixelTime;
this.pixelStack = userData.pixelStack;
this.undoExpires = userData.undoExpires || undefined;
this.isAdmin = userData.isAdmin;
this.isModerator = userData.isModerator;
}
async modifyStack(modifyBy: number): Promise<any> {