Added back Remote Cursors

This commit is contained in:
ExilProductions
2026-01-16 17:06:43 +01:00
parent fa0762643e
commit 8eed0a0a92
10 changed files with 307 additions and 34 deletions

View File

@@ -28,29 +28,93 @@ db.exec(`
x INTEGER NOT NULL,
y INTEGER NOT NULL,
color TEXT NOT NULL,
user_id INTEGER,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (x, y)
PRIMARY KEY (x, y),
FOREIGN KEY (user_id) REFERENCES users(id)
);
CREATE INDEX IF NOT EXISTS idx_pixels_updated ON pixels(updated_at);
`);
const tableInfo = db.pragma('table_info(pixels)') as { name: string }[];
const hasUserId = tableInfo.some(col => col.name === 'user_id');
if (!hasUserId) {
db.exec('ALTER TABLE pixels ADD COLUMN user_id INTEGER REFERENCES users(id)');
}
const indexes = db.pragma('index_list(pixels)') as { name: string }[];
const hasIndex = indexes.some(idx => idx.name === 'idx_pixels_updated');
if (!hasIndex) {
db.exec('CREATE INDEX idx_pixels_updated ON pixels(updated_at)');
}
export function generateUserId(): string {
return `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
}
export function upsertPixels(pixels: { x: number; y: number; color: string }[]): void {
function isValidColor(color: string): boolean {
return /^#[0-9A-Fa-f]{6}$/.test(color);
}
export function validatePixel(pixel: { x: number; y: number; color: string }): string | null {
if (!Number.isInteger(pixel.x) || pixel.x < -1000000 || pixel.x > 1000000) {
return 'Invalid x coordinate';
}
if (!Number.isInteger(pixel.y) || pixel.y < -1000000 || pixel.y > 1000000) {
return 'Invalid y coordinate';
}
if (!isValidColor(pixel.color)) {
return 'Invalid color format';
}
return null;
}
export function upsertPixelsWithConflictResolution(
pixels: { x: number; y: number; color: string; userId?: number }[],
clientTimestamp: number
): { saved: number; conflicts: number } {
const getStmt = db.prepare('SELECT updated_at FROM pixels WHERE x = ? AND y = ?');
const stmt = db.prepare(
'INSERT OR REPLACE INTO pixels (x, y, color, updated_at) VALUES (?, ?, ?, CURRENT_TIMESTAMP)'
'INSERT OR REPLACE INTO pixels (x, y, color, user_id, updated_at) VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)'
);
let saved = 0;
let conflicts = 0;
const transaction = db.transaction((pixels: any[]) => {
for (const pixel of pixels) {
stmt.run(pixel.x, pixel.y, pixel.color);
try {
const existing = getStmt.get(pixel.x, pixel.y) as { updated_at: string } | undefined;
if (existing) {
const existingTime = new Date(existing.updated_at).getTime();
if (clientTimestamp && existingTime > clientTimestamp) {
conflicts++;
continue;
}
}
stmt.run(pixel.x, pixel.y, pixel.color, pixel.userId || null);
saved++;
} catch (err) {
console.error('Error upserting pixel:', err);
}
}
});
transaction(pixels);
return { saved, conflicts };
}
export function getPixelsInRange(
minX: number,
maxX: number,
minY: number,
maxY: number
): { x: number; y: number; color: string }[] {
const stmt = db.prepare(
'SELECT x, y, color FROM pixels WHERE x >= ? AND x <= ? AND y >= ? AND y <= ? ORDER BY x, y'
);
return stmt.all(minX, maxX, minY, maxY) as { x: number; y: number; color: string }[];
}
export function getAllPixels(): { x: number; y: number; color: string }[] {