Fix pixel persistence and improve mobile UX

- Fix pixel data storage to include user information (userId, username, timestamp)
- Enhance zoom controls to center properly without drift
- Improve mobile modal centering with flexbox layout
- Add dynamic backend URL detection for network access
- Fix CORS configuration for development mode
- Add mobile-optimized touch targets and safe area support
This commit is contained in:
martin 2025-08-22 20:14:48 +02:00
commit 194fc8da4c
14 changed files with 172 additions and 82 deletions

View file

@ -7,7 +7,7 @@ export const config = {
host: process.env.HOST || 'localhost',
nodeEnv: process.env.NODE_ENV || 'development',
jwtSecret: process.env.JWT_SECRET || 'your-super-secret-jwt-key-change-in-production',
corsOrigin: process.env.CORS_ORIGIN ? process.env.CORS_ORIGIN.split(',') : ['http://localhost:3000'],
corsOrigin: process.env.CORS_ORIGIN ? process.env.CORS_ORIGIN.split(',') : (process.env.NODE_ENV === 'development' ? true : ['http://localhost:3000']),
// Rate limiting
rateLimits: {

View file

@ -12,14 +12,21 @@ import { config } from '../config/env';
export class CanvasService {
private readonly keyPrefix = config.redis.keyPrefix;
async placePixel(canvasId: string, x: number, y: number, color: string, userId: string): Promise<boolean> {
async placePixel(canvasId: string, x: number, y: number, color: string, userId: string, username?: string): Promise<boolean> {
try {
const { chunkX, chunkY } = getChunkCoordinates(x, y);
const chunkKey = `${this.keyPrefix}canvas:${canvasId}:chunk:${getChunkKey(chunkX, chunkY)}`;
const pixelKey = getPixelKey(x % CANVAS_CONFIG.DEFAULT_CHUNK_SIZE, y % CANVAS_CONFIG.DEFAULT_CHUNK_SIZE);
// Simple approach without pipeline for better compatibility
await redisClient.hSet(chunkKey, pixelKey, color);
// Store pixel with user information as JSON
const pixelData = {
color,
userId,
username: username || userId,
timestamp: Date.now()
};
await redisClient.hSet(chunkKey, pixelKey, JSON.stringify(pixelData));
// Update chunk metadata
await redisClient.hSet(`${chunkKey}:meta`, {
@ -53,9 +60,16 @@ export class CanvasService {
return null;
}
const pixels = new Map<string, string>();
for (const [key, color] of Object.entries(pixelData)) {
pixels.set(key, String(color));
const pixels = new Map<string, any>();
for (const [key, data] of Object.entries(pixelData)) {
try {
// Try to parse as JSON (new format with user info)
const parsedData = JSON.parse(String(data));
pixels.set(key, parsedData);
} catch {
// Fallback for old format (just color string)
pixels.set(key, { color: String(data), userId: null, username: null, timestamp: 0 });
}
}
return {

View file

@ -151,7 +151,8 @@ export class WebSocketService {
message.x,
message.y,
message.color,
userId
userId,
socket.data.username
);
if (success) {
@ -187,12 +188,15 @@ export class WebSocketService {
const chunk = await this.canvasService.getChunk(canvasId, message.chunkX, message.chunkY);
if (chunk) {
const pixels = Array.from(chunk.pixels.entries()).map(([key, color]) => {
const pixels = Array.from(chunk.pixels.entries()).map(([key, pixelInfo]) => {
const [localX, localY] = key.split(',').map(Number);
return {
x: message.chunkX * 64 + localX,
y: message.chunkY * 64 + localY,
color
color: pixelInfo.color,
userId: pixelInfo.userId,
username: pixelInfo.username,
timestamp: pixelInfo.timestamp
};
});