Loading packages/client/src/components/Moderation/ModModal.tsx +73 −0 Original line number Diff line number Diff line import { Button, Modal, ModalBody, ModalContent, Loading @@ -9,10 +10,17 @@ import { useAppContext } from "../../contexts/AppContext"; import { useCallback, useEffect, useState } from "react"; import { KeybindManager } from "../../lib/keybinds"; import { Canvas } from "../../lib/canvas"; import { toast } from "react-toastify"; import { api, handleError } from "../../lib/utils"; export const ModModal = () => { const { showModModal, setShowModModal, hasAdmin } = useAppContext(); const [bypassCooldown, setBypassCooldown_] = useState(false); const [selectedCoords, setSelectedCoords] = useState<{ start: [x: number, y: number]; end: [x: number, y: number]; }>(); const [loading, setLoading] = useState(false); useEffect(() => { setBypassCooldown_(Canvas.instance?.getCooldownBypass() || false); Loading @@ -33,6 +41,26 @@ export const ModModal = () => { }; }, [hasAdmin]); useEffect(() => { const previousClicks = Canvas.instance?.previousCanvasClicks; if (previousClicks && previousClicks.length === 2) { let start: [number, number] = [previousClicks[0].x, previousClicks[0].y]; let end: [number, number] = [previousClicks[1].x, previousClicks[1].y]; if (start[0] < end[0] && start[1] < end[1]) { setSelectedCoords({ start, end, }); } else { setSelectedCoords(undefined); } } else { setSelectedCoords(undefined); } }, [showModModal]); const setBypassCooldown = useCallback( (value: boolean) => { setBypassCooldown_(value); Loading @@ -41,6 +69,39 @@ export const ModModal = () => { [setBypassCooldown_] ); const doUndoArea = useCallback(() => { if (!selectedCoords) return; if ( !confirm( `Are you sure you want to undo (${selectedCoords.start.join(",")}) -> (${selectedCoords.end.join(",")})\n\nThis will affect ~${(selectedCoords.end[0] - selectedCoords.start[0]) * (selectedCoords.end[1] - selectedCoords.start[1])} pixels!` ) ) { return; } setLoading(true); api("/api/admin/canvas/undo", "PUT", { start: { x: selectedCoords.start[0], y: selectedCoords.start[1] }, end: { x: selectedCoords.end[0], y: selectedCoords.end[1] }, }) .then(({ status, data }) => { if (status === 200) { if (data.success) { toast.success( `Successfully undid area (${selectedCoords.start.join(",")}) -> (${selectedCoords.end.join(",")})` ); } else { handleError({ status, data }); } } else { handleError({ status, data }); } }) .finally(() => { setLoading(false); }); }, [selectedCoords]); return ( <Modal isOpen={showModModal} onOpenChange={setShowModModal}> <ModalContent> Loading @@ -54,6 +115,18 @@ export const ModModal = () => { > Bypass placement cooldown </Switch> {selectedCoords && ( <Button onPress={doUndoArea} isLoading={loading}> Undo area ({selectedCoords.start.join(",")}) -> ( {selectedCoords.end.join(",")}) </Button> )} {!selectedCoords && ( <> right click two positions to get more options (first click needs to be the top left most position) </> )} </ModalBody> </> )} Loading packages/client/src/lib/canvas.ts +12 −0 Original line number Diff line number Diff line Loading @@ -194,6 +194,8 @@ export class Canvas extends EventEmitter<CanvasEvents> { ); }; previousCanvasClicks: { x: number; y: number }[] = []; handleMouseDown(e: ClickEvent) { if (!e.alt && !e.ctrl && !e.meta && !e.shift && e.button === "LCLICK") { const [x, y] = this.screenToPos(e.clientX, e.clientY); Loading @@ -207,6 +209,16 @@ export class Canvas extends EventEmitter<CanvasEvents> { // shift: e.meta // }, ) } if (e.button === "RCLICK" && !e.alt && !e.ctrl && !e.meta && !e.shift) { const [x, y] = this.screenToPos(e.clientX, e.clientY); // keep track of the last X pixels right clicked // used by the ModModal to determine areas selected this.previousCanvasClicks.push({ x, y }); this.previousCanvasClicks = this.previousCanvasClicks.slice(-2); } } handleMouseMove(e: HoverEvent) { Loading packages/client/src/lib/utils.ts +1 −1 Original line number Diff line number Diff line Loading @@ -34,7 +34,7 @@ export const rgbToHex = (r: number, g: number, b: number) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any export const api = async <T = unknown, Error = string>( endpoint: string, method: "GET" | "POST" = "GET", method: "GET" | "POST" | "PUT" = "GET", body?: unknown ): Promise<{ status: number; Loading packages/lib/src/renderer/PanZoom.ts +4 −0 Original line number Diff line number Diff line Loading @@ -529,6 +529,10 @@ export class PanZoom extends EventEmitter<PanZoomEvents> { registerMouseEvents() { console.debug("[PanZoom] Registering mouse events to $wrapper & document"); this.$wrapper.addEventListener("contextmenu", (e) => { e.preventDefault(); }); // zoom this.$wrapper.addEventListener("wheel", this._mouse_wheel, { passive: true, Loading packages/server/prisma/dbml/schema.dbml +1 −0 Original line number Diff line number Diff line Loading @@ -138,6 +138,7 @@ Enum AuditLogAction { CANVAS_FILL CANVAS_FREEZE CANVAS_UNFREEZE CANVAS_AREA_UNDO } Ref: Pixel.userId > User.sub Loading Loading
packages/client/src/components/Moderation/ModModal.tsx +73 −0 Original line number Diff line number Diff line import { Button, Modal, ModalBody, ModalContent, Loading @@ -9,10 +10,17 @@ import { useAppContext } from "../../contexts/AppContext"; import { useCallback, useEffect, useState } from "react"; import { KeybindManager } from "../../lib/keybinds"; import { Canvas } from "../../lib/canvas"; import { toast } from "react-toastify"; import { api, handleError } from "../../lib/utils"; export const ModModal = () => { const { showModModal, setShowModModal, hasAdmin } = useAppContext(); const [bypassCooldown, setBypassCooldown_] = useState(false); const [selectedCoords, setSelectedCoords] = useState<{ start: [x: number, y: number]; end: [x: number, y: number]; }>(); const [loading, setLoading] = useState(false); useEffect(() => { setBypassCooldown_(Canvas.instance?.getCooldownBypass() || false); Loading @@ -33,6 +41,26 @@ export const ModModal = () => { }; }, [hasAdmin]); useEffect(() => { const previousClicks = Canvas.instance?.previousCanvasClicks; if (previousClicks && previousClicks.length === 2) { let start: [number, number] = [previousClicks[0].x, previousClicks[0].y]; let end: [number, number] = [previousClicks[1].x, previousClicks[1].y]; if (start[0] < end[0] && start[1] < end[1]) { setSelectedCoords({ start, end, }); } else { setSelectedCoords(undefined); } } else { setSelectedCoords(undefined); } }, [showModModal]); const setBypassCooldown = useCallback( (value: boolean) => { setBypassCooldown_(value); Loading @@ -41,6 +69,39 @@ export const ModModal = () => { [setBypassCooldown_] ); const doUndoArea = useCallback(() => { if (!selectedCoords) return; if ( !confirm( `Are you sure you want to undo (${selectedCoords.start.join(",")}) -> (${selectedCoords.end.join(",")})\n\nThis will affect ~${(selectedCoords.end[0] - selectedCoords.start[0]) * (selectedCoords.end[1] - selectedCoords.start[1])} pixels!` ) ) { return; } setLoading(true); api("/api/admin/canvas/undo", "PUT", { start: { x: selectedCoords.start[0], y: selectedCoords.start[1] }, end: { x: selectedCoords.end[0], y: selectedCoords.end[1] }, }) .then(({ status, data }) => { if (status === 200) { if (data.success) { toast.success( `Successfully undid area (${selectedCoords.start.join(",")}) -> (${selectedCoords.end.join(",")})` ); } else { handleError({ status, data }); } } else { handleError({ status, data }); } }) .finally(() => { setLoading(false); }); }, [selectedCoords]); return ( <Modal isOpen={showModModal} onOpenChange={setShowModModal}> <ModalContent> Loading @@ -54,6 +115,18 @@ export const ModModal = () => { > Bypass placement cooldown </Switch> {selectedCoords && ( <Button onPress={doUndoArea} isLoading={loading}> Undo area ({selectedCoords.start.join(",")}) -> ( {selectedCoords.end.join(",")}) </Button> )} {!selectedCoords && ( <> right click two positions to get more options (first click needs to be the top left most position) </> )} </ModalBody> </> )} Loading
packages/client/src/lib/canvas.ts +12 −0 Original line number Diff line number Diff line Loading @@ -194,6 +194,8 @@ export class Canvas extends EventEmitter<CanvasEvents> { ); }; previousCanvasClicks: { x: number; y: number }[] = []; handleMouseDown(e: ClickEvent) { if (!e.alt && !e.ctrl && !e.meta && !e.shift && e.button === "LCLICK") { const [x, y] = this.screenToPos(e.clientX, e.clientY); Loading @@ -207,6 +209,16 @@ export class Canvas extends EventEmitter<CanvasEvents> { // shift: e.meta // }, ) } if (e.button === "RCLICK" && !e.alt && !e.ctrl && !e.meta && !e.shift) { const [x, y] = this.screenToPos(e.clientX, e.clientY); // keep track of the last X pixels right clicked // used by the ModModal to determine areas selected this.previousCanvasClicks.push({ x, y }); this.previousCanvasClicks = this.previousCanvasClicks.slice(-2); } } handleMouseMove(e: HoverEvent) { Loading
packages/client/src/lib/utils.ts +1 −1 Original line number Diff line number Diff line Loading @@ -34,7 +34,7 @@ export const rgbToHex = (r: number, g: number, b: number) => { // eslint-disable-next-line @typescript-eslint/no-explicit-any export const api = async <T = unknown, Error = string>( endpoint: string, method: "GET" | "POST" = "GET", method: "GET" | "POST" | "PUT" = "GET", body?: unknown ): Promise<{ status: number; Loading
packages/lib/src/renderer/PanZoom.ts +4 −0 Original line number Diff line number Diff line Loading @@ -529,6 +529,10 @@ export class PanZoom extends EventEmitter<PanZoomEvents> { registerMouseEvents() { console.debug("[PanZoom] Registering mouse events to $wrapper & document"); this.$wrapper.addEventListener("contextmenu", (e) => { e.preventDefault(); }); // zoom this.$wrapper.addEventListener("wheel", this._mouse_wheel, { passive: true, Loading
packages/server/prisma/dbml/schema.dbml +1 −0 Original line number Diff line number Diff line Loading @@ -138,6 +138,7 @@ Enum AuditLogAction { CANVAS_FILL CANVAS_FREEZE CANVAS_UNFREEZE CANVAS_AREA_UNDO } Ref: Pixel.userId > User.sub Loading