Canvas-only 2D game editor/runtime built on PixiJS v8 + Vite + TypeScript. - core: asset model (Scene/Camera/Sprite), scene objects with sparse overrides, property schema + resolve, command stack (undo/redo), serialization and project store interface (16 unit tests) - ui: custom in-canvas widget toolkit (panels, fields, color picker, list/scroll, popup) with a hidden <input> keyboard bridge - editor: asset/hierarchy panels, schema-driven inspector, toolbar - viewport: box rendering, camera frame, selection gizmo, drag/zoom/pan - runtime: world/entity instantiation for play mode, input, camera follow - api: PHP JSON project store with revision counter (409 on conflict) - tools: CDP-based e2e smoke test Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
42 lines
1.2 KiB
PHP
42 lines
1.2 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
/** JSON response helpers. Every API answer goes through here. */
|
|
final class Response
|
|
{
|
|
public static function json(mixed $data, int $status = 200): never
|
|
{
|
|
http_response_code($status);
|
|
header('Content-Type: application/json; charset=utf-8');
|
|
header('Cache-Control: no-store');
|
|
echo json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
|
exit;
|
|
}
|
|
|
|
public static function error(string $message, int $status, array $extra = []): never
|
|
{
|
|
self::json(['error' => $message] + $extra, $status);
|
|
}
|
|
|
|
public static function noContent(): never
|
|
{
|
|
http_response_code(204);
|
|
header('Cache-Control: no-store');
|
|
exit;
|
|
}
|
|
|
|
/** Decoded JSON request body or an error response when it is not an object. */
|
|
public static function body(): array
|
|
{
|
|
$raw = file_get_contents('php://input');
|
|
if ($raw === false || $raw === '') {
|
|
self::error('Request body must be JSON', 400);
|
|
}
|
|
$data = json_decode($raw, true);
|
|
if (!is_array($data)) {
|
|
self::error('Request body must be a JSON object', 400);
|
|
}
|
|
return $data;
|
|
}
|
|
}
|