implement canvas freezing

This commit is contained in:
Grant 2024-07-11 12:05:47 -06:00
parent 93dc27b17a
commit 3dd4d7e0d0
10 changed files with 137 additions and 2 deletions

View File

@ -2,6 +2,7 @@ import { Button } from "@nextui-org/react";
import { useAppContext } from "../../contexts/AppContext"; import { useAppContext } from "../../contexts/AppContext";
import network from "../../lib/network"; import network from "../../lib/network";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { toast } from "react-toastify";
export const UndoButton = () => { export const UndoButton = () => {
const { undo, config } = useAppContext<true>(); const { undo, config } = useAppContext<true>();
@ -34,7 +35,21 @@ export const UndoButton = () => {
// ref-ify this? // ref-ify this?
function execUndo() { function execUndo() {
network.socket.emitWithAck("undo").then((data) => { network.socket.emitWithAck("undo").then((data) => {
console.log("undo", data); if (data.success) {
console.log("Undo pixel successful");
} else {
console.log("Undo pixel error", data);
switch (data.error) {
case "pixel_covered":
toast.error("You cannot undo a covered pixel");
break;
case "unavailable":
toast.error("You have no undo available");
break;
default:
toast.error("Undo error: " + data.error);
}
}
}); });
} }

View File

@ -40,6 +40,7 @@ export interface ClientToServerEvents {
ack: ( ack: (
_: PacketAck< _: PacketAck<
Pixel, Pixel,
| "canvas_frozen"
| "no_user" | "no_user"
| "invalid_pixel" | "invalid_pixel"
| "pixel_cooldown" | "pixel_cooldown"
@ -50,7 +51,12 @@ export interface ClientToServerEvents {
) => void ) => void
) => void; ) => void;
undo: ( undo: (
ack: (_: PacketAck<{}, "no_user" | "unavailable" | "pixel_covered">) => void ack: (
_: PacketAck<
{},
"canvas_frozen" | "no_user" | "unavailable" | "pixel_covered"
>
) => void
) => void; ) => void;
subscribe: (topic: Subscription) => void; subscribe: (topic: Subscription) => void;
@ -141,6 +147,7 @@ export type PalleteColor = {
export type CanvasConfig = { export type CanvasConfig = {
size: [number, number]; size: [number, number];
frozen: boolean;
zoom: number; zoom: number;
pixel: { pixel: {
maxStack: number; maxStack: number;

View File

@ -136,6 +136,8 @@ Enum AuditLogAction {
BAN_DELETE BAN_DELETE
CANVAS_SIZE CANVAS_SIZE
CANVAS_FILL CANVAS_FILL
CANVAS_FREEZE
CANVAS_UNFREEZE
} }
Ref: Pixel.userId > User.sub Ref: Pixel.userId > User.sub

View File

@ -0,0 +1,10 @@
-- AlterEnum
-- This migration adds more than one value to an enum.
-- With PostgreSQL versions 11 and earlier, this is not possible
-- in a single migration. This can be worked around by creating
-- multiple migrations, each migration adding only one value to
-- the enum.
ALTER TYPE "AuditLogAction" ADD VALUE 'CANVAS_FREEZE';
ALTER TYPE "AuditLogAction" ADD VALUE 'CANVAS_UNFREEZE';

View File

@ -154,6 +154,8 @@ enum AuditLogAction {
BAN_DELETE BAN_DELETE
CANVAS_SIZE CANVAS_SIZE
CANVAS_FILL CANVAS_FILL
CANVAS_FREEZE
CANVAS_UNFREEZE
} }
model AuditLog { model AuditLog {

View File

@ -96,6 +96,49 @@ app.post("/canvas/size", async (req, res) => {
res.send({ success: true, auditLog }); res.send({ success: true, auditLog });
}); });
/**
* Get canvas frozen status
*/
app.get("/canvas/freeze", async (req, res) => {
res.send({ success: true, frozen: Canvas.frozen });
});
/**
* Freeze the canvas
*
* @header X-Audit
*/
app.post("/canvas/freeze", async (req, res) => {
await Canvas.setFrozen(true);
const user = (await User.fromAuthSession(req.session.user!))!;
const auditLog = AuditLog.Factory(user.sub)
.doing("CANVAS_FREEZE")
.reason(req.header("X-Audit") || null)
.withComment(`Freezed the canvas`)
.create();
res.send({ success: true, auditLog });
});
/**
* Unfreeze the canvas
*
* @header X-Audit
*/
app.delete("/canvas/freeze", async (req, res) => {
await Canvas.setFrozen(false);
const user = (await User.fromAuthSession(req.session.user!))!;
const auditLog = AuditLog.Factory(user.sub)
.doing("CANVAS_UNFREEZE")
.reason(req.header("X-Audit") || null)
.withComment(`Un-Freezed the canvas`)
.create();
res.send({ success: true, auditLog });
});
app.put("/canvas/heatmap", async (req, res) => { app.put("/canvas/heatmap", async (req, res) => {
try { try {
await Canvas.generateHeatmap(); await Canvas.generateHeatmap();

View File

@ -13,14 +13,17 @@ class Canvas {
* Size of the canvas * Size of the canvas
*/ */
private canvasSize: [width: number, height: number]; private canvasSize: [width: number, height: number];
private isFrozen: boolean;
constructor() { constructor() {
this.canvasSize = [100, 100]; this.canvasSize = [100, 100];
this.isFrozen = false;
} }
getCanvasConfig(): CanvasConfig { getCanvasConfig(): CanvasConfig {
return { return {
size: this.canvasSize, size: this.canvasSize,
frozen: this.isFrozen,
zoom: 7, zoom: 7,
pixel: { pixel: {
cooldown: 10, cooldown: 10,
@ -33,6 +36,34 @@ class Canvas {
}; };
} }
get frozen() {
return this.isFrozen;
}
async setFrozen(frozen: boolean) {
this.isFrozen = frozen;
await prisma.setting.upsert({
where: { key: "canvas.frozen" },
create: {
key: "canvas.frozen",
value: JSON.stringify(frozen),
},
update: {
key: "canvas.frozen",
value: JSON.stringify(frozen),
},
});
if (SocketServer.instance) {
SocketServer.instance.broadcastConfig();
} else {
Logger.warn(
"[Canvas#setFrozen] SocketServer is not instantiated, cannot broadcast config"
);
}
}
/** /**
* Change size of the canvas * Change size of the canvas
* *

View File

@ -26,6 +26,17 @@ export const loadSettings = async (frozen = false) => {
Logger.warn("Setting canvas.size is not set, did you run init_settings?"); Logger.warn("Setting canvas.size is not set, did you run init_settings?");
} }
// canvas frozen
const canvasFrozen = await prisma.setting.findFirst({
where: { key: "canvas.frozen" },
});
if (canvasFrozen) {
const data = JSON.parse(canvasFrozen.value);
Logger.info(`Canvas frozen loaded as ${data}`);
Canvas.setFrozen(data);
}
Logger.info( Logger.info(
"Settings loaded into memory, waiting for side effects to finish..." "Settings loaded into memory, waiting for side effects to finish..."
); );

View File

@ -233,6 +233,11 @@ export class SocketServer {
}); });
socket.on("place", async (pixel, bypassCooldown, ack) => { socket.on("place", async (pixel, bypassCooldown, ack) => {
if (getClientConfig().canvas.frozen) {
ack({ success: false, error: "canvas_frozen" });
return;
}
if (!user) { if (!user) {
ack({ success: false, error: "no_user" }); ack({ success: false, error: "no_user" });
return; return;
@ -317,6 +322,11 @@ export class SocketServer {
}); });
socket.on("undo", async (ack) => { socket.on("undo", async (ack) => {
if (getClientConfig().canvas.frozen) {
ack({ success: false, error: "canvas_frozen" });
return;
}
if (!user) { if (!user) {
ack({ success: false, error: "no_user" }); ack({ success: false, error: "no_user" });
return; return;

View File

@ -14,6 +14,10 @@ async function main() {
height: 100, height: 100,
}, },
}, },
{
key: "canvas.frozen",
defaultValue: false,
},
]; ];
for (const setting of SETTINGS) { for (const setting of SETTINGS) {