1
0
mirror of git://f0xx.org/android_cast synced 2026-07-29 04:18:09 +03:00

some candies

This commit is contained in:
Anton Afanasyeu
2026-05-22 18:34:01 +02:00
parent 7e15b54893
commit ab355e0512
4 changed files with 107 additions and 13 deletions

View File

@@ -27,4 +27,6 @@ return [
], ],
], ],
'session_name' => 'ac_crash_sess', 'session_name' => 'ac_crash_sess',
// When true, upload API may include a short error hint (do not enable on public prod long-term)
'debug' => false,
]; ];

View File

@@ -27,6 +27,22 @@ if ((int) ($data['schema_version'] ?? 0) !== 1) {
try { try {
ReportRepository::insert($data); ReportRepository::insert($data);
json_out(['ok' => true, 'report_id' => $data['report_id'] ?? null]); json_out(['ok' => true, 'report_id' => $data['report_id'] ?? null]);
} catch (PDOException $e) {
$msg = $e->getMessage();
if ($e->getCode() === '23000' || stripos($msg, 'UNIQUE') !== false) {
json_out(['ok' => false, 'error' => 'duplicate report_id'], 409);
}
error_log('crash upload PDO: ' . $msg);
$out = ['ok' => false, 'error' => 'store failed'];
if (cfg('debug')) {
$out['hint'] = $msg;
}
json_out($out, 500);
} catch (Throwable $e) { } catch (Throwable $e) {
json_out(['ok' => false, 'error' => 'store failed'], 500); error_log('crash upload: ' . $e->getMessage());
$out = ['ok' => false, 'error' => 'store failed'];
if (cfg('debug')) {
$out['hint'] = $e->getMessage();
}
json_out($out, 500);
} }

View File

@@ -24,13 +24,12 @@ a:hover { text-decoration: underline; }
width: 56px; width: 56px;
background: var(--surface); background: var(--surface);
border-right: 1px solid var(--border); border-right: 1px solid var(--border);
transition: width .2s ease; transition: width .22s ease;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
flex-shrink: 0; flex-shrink: 0;
} }
.nav-pane.open, .nav-pane.open { width: 200px; }
.nav-pane:hover { width: 72px; }
.nav-handle { .nav-handle {
position: absolute; position: absolute;
top: 8px; top: 8px;
@@ -38,6 +37,7 @@ a:hover { text-decoration: underline; }
transform: translateX(-50%); transform: translateX(-50%);
width: 40px; width: 40px;
height: 40px; height: 40px;
transition: left .22s ease, transform .22s ease;
padding: 0; padding: 0;
border: none; border: none;
border-radius: 10px; border-radius: 10px;
@@ -55,6 +55,10 @@ a:hover { text-decoration: underline; }
color: var(--text); color: var(--text);
outline: none; outline: none;
} }
.nav-pane.open .nav-handle {
left: 8px;
transform: none;
}
.nav-list { .nav-list {
list-style: none; list-style: none;
margin: 56px 0 0; margin: 56px 0 0;
@@ -72,7 +76,14 @@ a:hover { text-decoration: underline; }
border-radius: 10px; border-radius: 10px;
color: var(--muted); color: var(--muted);
text-decoration: none; text-decoration: none;
transition: background .15s ease, color .15s ease; transition: background .15s ease, color .15s ease, width .22s ease, padding .22s ease;
}
.nav-pane.open .nav-link {
width: 100%;
margin: 0;
padding: 0 12px;
justify-content: flex-start;
gap: 10px;
} }
.nav-link.active, .nav-link.active,
.nav-link:hover { .nav-link:hover {
@@ -81,15 +92,31 @@ a:hover { text-decoration: underline; }
text-decoration: none; text-decoration: none;
} }
.nav-label { .nav-label {
position: absolute; flex: 0 1 auto;
width: 1px; min-width: 0;
height: 1px; font-size: 14px;
padding: 0; font-weight: 500;
margin: -1px; line-height: 1.2;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap; white-space: nowrap;
border: 0; overflow: hidden;
opacity: 0;
max-width: 0;
transition: opacity .18s ease, max-width .22s ease;
}
.nav-pane.open .nav-label {
opacity: 1;
max-width: 9rem;
transition-delay: .06s;
}
@media (prefers-reduced-motion: reduce) {
.nav-pane,
.nav-link,
.nav-label {
transition: none;
}
.nav-pane.open .nav-label {
transition-delay: 0s;
}
} }
.nav-icon { .nav-icon {
display: block; display: block;
@@ -219,6 +246,12 @@ a:hover { text-decoration: underline; }
align-items: center; align-items: center;
gap: 6px; gap: 6px;
color: var(--muted); color: var(--muted);
padding: 0 8px;
box-sizing: border-box;
}
.nav-pane.open .nav-user {
align-items: stretch;
padding: 0 10px;
} }
.nav-user-name { .nav-user-name {
display: flex; display: flex;
@@ -227,6 +260,20 @@ a:hover { text-decoration: underline; }
width: 40px; width: 40px;
height: 40px; height: 40px;
color: var(--muted); color: var(--muted);
gap: 10px;
}
.nav-pane.open .nav-user-name {
width: 100%;
padding: 0 12px;
justify-content: flex-start;
box-sizing: border-box;
}
.nav-pane.open .nav-link--logout {
width: 100%;
margin: 0;
padding: 0 12px;
justify-content: flex-start;
gap: 10px;
} }
.nav-link--logout { color: var(--muted); } .nav-link--logout { color: var(--muted); }
.nav-link--logout:hover { color: var(--danger); background: rgba(248, 113, 113, .12); } .nav-link--logout:hover { color: var(--danger); background: rgba(248, 113, 113, .12); }

View File

@@ -14,6 +14,7 @@ declare(strict_types=1);
final class Database { final class Database {
private static ?PDO $pdo = null; private static ?PDO $pdo = null;
private static bool $schemaChecked = false;
public static function pdo(): PDO { public static function pdo(): PDO {
if (self::$pdo !== null) { if (self::$pdo !== null) {
@@ -45,6 +46,34 @@ final class Database {
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]); ]);
self::$pdo->exec('PRAGMA foreign_keys = ON'); self::$pdo->exec('PRAGMA foreign_keys = ON');
self::ensureSchema();
return self::$pdo; return self::$pdo;
} }
/** Idempotent: applies sql/schema.sqlite.sql if core tables are missing. */
public static function ensureSchema(): void {
if (self::$schemaChecked) {
return;
}
self::$schemaChecked = true;
$pdo = self::$pdo;
if ($pdo === null) {
return;
}
$need = static function (string $table) use ($pdo): bool {
$stmt = $pdo->prepare(
"SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ? LIMIT 1"
);
$stmt->execute([$table]);
return $stmt->fetchColumn() === false;
};
if (!$need('users') && !$need('reports')) {
return;
}
$schemaFile = __DIR__ . '/../sql/schema.sqlite.sql';
if (!is_readable($schemaFile)) {
throw new RuntimeException('schema file missing: ' . $schemaFile);
}
$pdo->exec((string) file_get_contents($schemaFile));
}
} }