({
- canvasWidth: config.canvasWidth,
- canvasHeight: config.canvasHeight,
- defaultCooldown: config.defaultCooldown,
- enableAutomaticEvents: config.enableAutomaticEvents,
- eventDuration: config.eventDuration,
- eventInterval: config.eventInterval,
- showGridByDefault: config.showGridByDefault,
- });
- const [isLoading, setIsLoading] = useState(false);
- const { toast } = useToast();
-
- useEffect(() => {
- setFormData({
- canvasWidth: config.canvasWidth,
- canvasHeight: config.canvasHeight,
- defaultCooldown: config.defaultCooldown,
- enableAutomaticEvents: config.enableAutomaticEvents,
- eventDuration: config.eventDuration,
- eventInterval: config.eventInterval,
- showGridByDefault: config.showGridByDefault,
- });
- }, [config]);
-
- const handleSave = async () => {
- setIsLoading(true);
- try {
- await onConfigUpdate(formData);
- toast({
- title: "Configuration saved",
- description: "Canvas settings have been updated successfully.",
- });
- setOpen(false);
- } catch (error) {
- toast({
- title: "Error",
- description: "Failed to save configuration. Please try again.",
- variant: "destructive",
- });
- } finally {
- setIsLoading(false);
- }
- };
-
- const handleReset = () => {
- setFormData({
- canvasWidth: 100,
- canvasHeight: 100,
- defaultCooldown: 5,
- enableAutomaticEvents: false,
- eventDuration: 30,
- eventInterval: 6,
- showGridByDefault: true,
- });
- };
-
- return (
-
- );
-}
diff --git a/client/src/index.css b/client/src/index.css
index ff7c05c..5ae255f 100644
--- a/client/src/index.css
+++ b/client/src/index.css
@@ -125,8 +125,8 @@
.grid-lines {
background-image:
- linear-gradient(to right, rgba(255,255,255,0.1) 1px, transparent 1px),
- linear-gradient(to bottom, rgba(255,255,255,0.1) 1px, transparent 1px);
+ linear-gradient(to right, rgba(255,255,255,0.2) 1px, transparent 1px),
+ linear-gradient(to bottom, rgba(255,255,255,0.2) 1px, transparent 1px);
}
.cooldown-overlay {
diff --git a/client/src/pages/canvas.tsx b/client/src/pages/canvas.tsx
index 3c4c08d..a6dd319 100644
--- a/client/src/pages/canvas.tsx
+++ b/client/src/pages/canvas.tsx
@@ -3,13 +3,13 @@ import { useQuery, useMutation } from "@tanstack/react-query";
import { queryClient } from "@/lib/queryClient";
import { Canvas } from "@/components/canvas";
import { ColorPalette } from "@/components/color-palette";
-import { ConfigModal } from "@/components/config-modal";
+
import { Button } from "@/components/ui/button";
import { useWebSocket } from "@/hooks/use-websocket";
import { useToast } from "@/hooks/use-toast";
import { Grid } from "lucide-react";
import { DEFAULT_SELECTED_COLOR, generateUserId, getUsername } from "@/lib/config";
-import { Pixel, CanvasConfig, InsertPixel, InsertCanvasConfig, WSMessage } from "@shared/schema";
+import { Pixel, CanvasConfig, InsertPixel, WSMessage } from "@shared/schema";
import { apiRequest } from "@/lib/queryClient";
export default function CanvasPage() {
@@ -48,14 +48,7 @@ export default function CanvasPage() {
});
break;
- case "config_updated":
- // Invalidate config cache
- queryClient.invalidateQueries({ queryKey: ['/api/config'] });
- toast({
- title: "Configuration updated",
- description: "Canvas settings have been changed by an administrator.",
- });
- break;
+
case "cooldown_update":
if (message.data.userId === userId) {
@@ -98,16 +91,14 @@ export default function CanvasPage() {
},
});
- // Config update mutation
- const updateConfigMutation = useMutation({
- mutationFn: async (configUpdate: InsertCanvasConfig) => {
- const response = await apiRequest("POST", "/api/config", configUpdate);
- return response.json();
- },
- onSuccess: () => {
- queryClient.invalidateQueries({ queryKey: ['/api/config'] });
- },
- });
+
+
+ // Set initial grid state from config
+ useEffect(() => {
+ if (config) {
+ setShowGrid(config.showGridByDefault);
+ }
+ }, [config]);
// Cooldown countdown
useEffect(() => {
@@ -148,9 +139,7 @@ export default function CanvasPage() {
});
};
- const handleConfigUpdate = async (configUpdate: InsertCanvasConfig) => {
- await updateConfigMutation.mutateAsync(configUpdate);
- };
+
if (pixelsLoading || configLoading || !config) {
return (
@@ -185,8 +174,7 @@ export default function CanvasPage() {
Grid
- {/* Admin Config Button */}
-
+
{/* User Info */}
diff --git a/config.cfg b/config.cfg
new file mode 100644
index 0000000..37fb8af
--- /dev/null
+++ b/config.cfg
@@ -0,0 +1,24 @@
+# r/place Canvas Configuration
+# Ändere diese Werte um die Canvas-Einstellungen anzupassen
+
+# Canvas Dimensionen
+CANVAS_WIDTH=100
+CANVAS_HEIGHT=100
+
+# Cooldown Einstellungen (in Sekunden)
+DEFAULT_COOLDOWN=5
+
+# Automatische Events (true/false)
+# Wenn aktiviert, gibt es keine Cooldowns
+ENABLE_AUTOMATIC_EVENTS=false
+
+# Event Einstellungen
+EVENT_DURATION_MINUTES=30
+EVENT_INTERVAL_HOURS=6
+
+# Grid Einstellungen
+SHOW_GRID_BY_DEFAULT=true
+
+# Export Einstellungen
+AUTO_EXPORT_INTERVAL_SECONDS=60
+EXPORT_PATH=./exports/
\ No newline at end of file
diff --git a/server/config.ts b/server/config.ts
new file mode 100644
index 0000000..07fc17e
--- /dev/null
+++ b/server/config.ts
@@ -0,0 +1,90 @@
+import { readFileSync } from "fs";
+import { join } from "path";
+
+interface Config {
+ canvasWidth: number;
+ canvasHeight: number;
+ defaultCooldown: number;
+ enableAutomaticEvents: boolean;
+ eventDurationMinutes: number;
+ eventIntervalHours: number;
+ showGridByDefault: boolean;
+ autoExportIntervalSeconds: number;
+ exportPath: string;
+}
+
+function parseConfigFile(): Config {
+ try {
+ const configPath = join(process.cwd(), "config.cfg");
+ const configContent = readFileSync(configPath, "utf-8");
+
+ const config: Partial = {};
+
+ configContent.split("\n").forEach(line => {
+ line = line.trim();
+ if (line.startsWith("#") || !line.includes("=")) return;
+
+ const [key, value] = line.split("=");
+ const trimmedKey = key.trim();
+ const trimmedValue = value.trim();
+
+ switch (trimmedKey) {
+ case "CANVAS_WIDTH":
+ config.canvasWidth = parseInt(trimmedValue);
+ break;
+ case "CANVAS_HEIGHT":
+ config.canvasHeight = parseInt(trimmedValue);
+ break;
+ case "DEFAULT_COOLDOWN":
+ config.defaultCooldown = parseInt(trimmedValue);
+ break;
+ case "ENABLE_AUTOMATIC_EVENTS":
+ config.enableAutomaticEvents = trimmedValue.toLowerCase() === "true";
+ break;
+ case "EVENT_DURATION_MINUTES":
+ config.eventDurationMinutes = parseInt(trimmedValue);
+ break;
+ case "EVENT_INTERVAL_HOURS":
+ config.eventIntervalHours = parseInt(trimmedValue);
+ break;
+ case "SHOW_GRID_BY_DEFAULT":
+ config.showGridByDefault = trimmedValue.toLowerCase() === "true";
+ break;
+ case "AUTO_EXPORT_INTERVAL_SECONDS":
+ config.autoExportIntervalSeconds = parseInt(trimmedValue);
+ break;
+ case "EXPORT_PATH":
+ config.exportPath = trimmedValue;
+ break;
+ }
+ });
+
+ // Set defaults for missing values
+ return {
+ canvasWidth: config.canvasWidth || 100,
+ canvasHeight: config.canvasHeight || 100,
+ defaultCooldown: config.defaultCooldown || 5,
+ enableAutomaticEvents: config.enableAutomaticEvents || false,
+ eventDurationMinutes: config.eventDurationMinutes || 30,
+ eventIntervalHours: config.eventIntervalHours || 6,
+ showGridByDefault: config.showGridByDefault !== undefined ? config.showGridByDefault : true,
+ autoExportIntervalSeconds: config.autoExportIntervalSeconds || 60,
+ exportPath: config.exportPath || "./exports/",
+ };
+ } catch (error) {
+ console.error("Error reading config file, using defaults:", error);
+ return {
+ canvasWidth: 100,
+ canvasHeight: 100,
+ defaultCooldown: 5,
+ enableAutomaticEvents: false,
+ eventDurationMinutes: 30,
+ eventIntervalHours: 6,
+ showGridByDefault: true,
+ autoExportIntervalSeconds: 60,
+ exportPath: "./exports/",
+ };
+ }
+}
+
+export const config = parseConfigFile();
\ No newline at end of file
diff --git a/server/export.ts b/server/export.ts
new file mode 100644
index 0000000..42633d0
--- /dev/null
+++ b/server/export.ts
@@ -0,0 +1,70 @@
+import { writeFileSync, mkdirSync, existsSync } from "fs";
+import { join } from "path";
+import { config } from "./config";
+import { type IStorage } from "./storage";
+
+export class CanvasExporter {
+ private storage: IStorage;
+ private exportInterval: NodeJS.Timeout | null = null;
+
+ constructor(storage: IStorage) {
+ this.storage = storage;
+ }
+
+ startAutoExport() {
+ // Ensure export directory exists
+ if (!existsSync(config.exportPath)) {
+ mkdirSync(config.exportPath, { recursive: true });
+ }
+
+ // Start export interval
+ this.exportInterval = setInterval(() => {
+ this.exportCanvas();
+ }, config.autoExportIntervalSeconds * 1000);
+
+ console.log(`Auto-export started: every ${config.autoExportIntervalSeconds} seconds`);
+ }
+
+ stopAutoExport() {
+ if (this.exportInterval) {
+ clearInterval(this.exportInterval);
+ this.exportInterval = null;
+ console.log("Auto-export stopped");
+ }
+ }
+
+ async exportCanvas() {
+ try {
+ const pixels = await this.storage.getAllPixels();
+
+ // Create simple SVG export instead of PNG for now
+ const svgContent = this.createSVG(pixels);
+
+ // Generate filename with timestamp
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
+ const filename = `canvas-${timestamp}.svg`;
+ const filepath = join(config.exportPath, filename);
+
+ // Save SVG
+ writeFileSync(filepath, svgContent);
+
+ console.log(`Canvas exported: ${filename} (${pixels.length} pixels)`);
+ } catch (error) {
+ console.error("Failed to export canvas:", error);
+ }
+ }
+
+ private createSVG(pixels: any[]): string {
+ const { canvasWidth, canvasHeight } = config;
+
+ let svgContent = ``;
+ return svgContent;
+ }
+}
\ No newline at end of file
diff --git a/server/routes.ts b/server/routes.ts
index 064c023..188cab4 100644
--- a/server/routes.ts
+++ b/server/routes.ts
@@ -2,14 +2,18 @@ import type { Express } from "express";
import { createServer, type Server } from "http";
import { WebSocketServer, WebSocket } from "ws";
import { storage } from "./storage";
-import { insertPixelSchema, insertCanvasConfigSchema, insertUserCooldownSchema, type WSMessage } from "@shared/schema";
-import { randomUUID } from "crypto";
+import { insertPixelSchema, insertUserCooldownSchema, type WSMessage } from "@shared/schema";
+import { CanvasExporter } from "./export";
export async function registerRoutes(app: Express): Promise {
const httpServer = createServer(app);
const wss = new WebSocketServer({ server: httpServer, path: '/ws' });
let connectedUsers = new Set();
+
+ // Initialize canvas exporter
+ const exporter = new CanvasExporter(storage);
+ exporter.startAutoExport();
// API Routes
app.get("/api/pixels", async (req, res) => {
@@ -30,22 +34,8 @@ export async function registerRoutes(app: Express): Promise {
}
});
- app.post("/api/config", async (req, res) => {
- try {
- const configData = insertCanvasConfigSchema.parse(req.body);
- const config = await storage.updateCanvasConfig(configData);
-
- // Broadcast config update to all connected clients
- broadcast({
- type: "config_updated",
- data: configData,
- });
-
- res.json(config);
- } catch (error) {
- res.status(400).json({ message: "Invalid config data" });
- }
- });
+ // Config is now read-only from file
+ // Remove the POST endpoint for config updates
app.post("/api/pixels", async (req, res) => {
try {
diff --git a/server/storage.ts b/server/storage.ts
index c59fe0f..dd86f82 100644
--- a/server/storage.ts
+++ b/server/storage.ts
@@ -1,5 +1,6 @@
import { type Pixel, type InsertPixel, type CanvasConfig, type InsertCanvasConfig, type UserCooldown, type InsertUserCooldown } from "@shared/schema";
import { randomUUID } from "crypto";
+import { config } from "./config";
export interface IStorage {
// Pixel operations
@@ -29,13 +30,13 @@ export class MemStorage implements IStorage {
this.userCooldowns = new Map();
this.config = {
id: randomUUID(),
- canvasWidth: 100,
- canvasHeight: 100,
- defaultCooldown: 5,
- enableAutomaticEvents: false,
- eventDuration: 30,
- eventInterval: 6,
- showGridByDefault: true,
+ canvasWidth: config.canvasWidth,
+ canvasHeight: config.canvasHeight,
+ defaultCooldown: config.defaultCooldown,
+ enableAutomaticEvents: config.enableAutomaticEvents,
+ eventDuration: config.eventDurationMinutes,
+ eventInterval: config.eventIntervalHours,
+ showGridByDefault: config.showGridByDefault,
updatedAt: new Date(),
};
}
diff --git a/shared/schema.ts b/shared/schema.ts
index 5bb1e03..9a2b38e 100644
--- a/shared/schema.ts
+++ b/shared/schema.ts
@@ -73,10 +73,7 @@ export const wsMessageSchema = z.union([
count: z.number(),
}),
}),
- z.object({
- type: z.literal("config_updated"),
- data: insertCanvasConfigSchema,
- }),
+
z.object({
type: z.literal("cooldown_update"),
data: z.object({