split
This commit is contained in:
parent
88b5669961
commit
7fd7ba2e76
8 changed files with 512 additions and 147 deletions
|
|
@ -61,10 +61,25 @@ function thumbnail_html($json): string {
|
|||
if (!$rows) {
|
||||
for ($y = 0; $y < $height; $y++) $rows[] = substr($pixels, $y * $width, $width);
|
||||
}
|
||||
$palette = [
|
||||
'0'=>'#1f2330','1'=>'#ffffff','2'=>'#8f9aa8','3'=>'#d7c59a','4'=>'#7a4f35','5'=>'#3f6f3f','6'=>'#5c8f52','7'=>'#8fcf68','8'=>'#2f5f88','9'=>'#67b7dc',
|
||||
'a'=>'#f2d3a3','b'=>'#e09f67','c'=>'#c96b4b','d'=>'#8c3f3f','e'=>'#d94f70','f'=>'#9b5fc0','g'=>'#5b4aa3','h'=>'#3f7fbf','i'=>'#55b7a5','j'=>'#f0d84f'
|
||||
$codes = str_split('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' . "!#$%&'()*+,-/:;<=>?@[]^_`{|}~");
|
||||
$colors = [
|
||||
'#111827', '#374151', '#6b7280', '#d1d5db', '#fff7ed',
|
||||
'#7f1d1d', '#dc2626', '#f97316', '#facc15', '#84cc16',
|
||||
'#16a34a', '#14b8a6', '#06b6d4', '#2563eb', '#4f46e5',
|
||||
'#7c3aed', '#c026d3', '#ec4899', '#f5d0a9', '#7c4a2d',
|
||||
'#fafafa', '#f5f5f4', '#e7e5e4', '#d6d3d1', '#a8a29e', '#78716c', '#57534e', '#292524', '#0c0a09',
|
||||
'#fef3c7', '#fde68a', '#d6a25f', '#9a6a3a', '#5c3b24',
|
||||
'#fee2e2', '#fca5a5', '#ef4444', '#b91c1c', '#7f1d1d',
|
||||
'#ffedd5', '#fdba74', '#f97316', '#c2410c', '#7c2d12',
|
||||
'#fef9c3', '#fde047', '#eab308', '#a16207', '#713f12',
|
||||
'#ecfccb', '#bef264', '#84cc16', '#4d7c0f', '#365314', '#dcfce7', '#86efac', '#15803d',
|
||||
'#ccfbf1', '#5eead4', '#14b8a6', '#0f766e', '#134e4a',
|
||||
'#cffafe', '#67e8f9', '#22d3ee', '#06b6d4', '#0e7490', '#e0f2fe', '#38bdf8',
|
||||
'#dbeafe', '#93c5fd', '#3b82f6', '#2563eb', '#1d4ed8', '#1e3a8a',
|
||||
'#e0e7ff', '#a5b4fc', '#6366f1', '#4f46e5', '#3730a3', '#ede9fe', '#c4b5fd', '#8b5cf6',
|
||||
'#fae8ff', '#e879f9', '#c026d3', '#86198f', '#fce7f3', '#f9a8d4', '#ec4899', '#be185d'
|
||||
];
|
||||
$palette = array_combine($codes, array_slice($colors, 0, count($codes))) ?: [];
|
||||
$cells = '';
|
||||
foreach (array_slice($rows, 0, $height) as $y => $row) {
|
||||
$chars = preg_split('//u', (string)$row, -1, PREG_SPLIT_NO_EMPTY) ?: [];
|
||||
|
|
@ -72,7 +87,7 @@ function thumbnail_html($json): string {
|
|||
$ch = $chars[$x] ?? '.';
|
||||
if ($ch === '.') continue;
|
||||
$color = $palette[$ch] ?? sprintf('#%06x', (hexdec(substr(md5($ch), 0, 6)) & 0x7fffff) | 0x303030);
|
||||
$cells .= '<i style="left:' . h($x) . 'px;top:' . h($y) . 'px;background:' . h($color) . '"></i>';
|
||||
$cells .= '<i style="left:' . h(($x / $width) * 100) . '%;top:' . h(($y / $height) * 100) . '%;width:' . h(100 / $width) . '%;height:' . h(100 / $height) . '%;background:' . h($color) . '"></i>';
|
||||
}
|
||||
}
|
||||
return '<div class="thumb" style="--w:' . h($width) . ';--h:' . h($height) . '">' . $cells . '</div>';
|
||||
|
|
@ -108,6 +123,12 @@ function sqlite_db(string $path): PDO {
|
|||
$db->exec('PRAGMA busy_timeout = 5000');
|
||||
return $db;
|
||||
}
|
||||
function ensure_sqlite_anonymous_account(PDO $db): void {
|
||||
$now = now_ms();
|
||||
$hash = password_hash(bin2hex(random_bytes(16)), PASSWORD_DEFAULT);
|
||||
$db->prepare('INSERT OR IGNORE INTO accounts(id, name, password_hash, created_at, updated_at) VALUES(?,?,?,?,?)')
|
||||
->execute(['anonymous', 'Anonymous', $hash, $now, $now]);
|
||||
}
|
||||
function list_sqlite_accounts(string $path): array {
|
||||
$db = sqlite_db($path);
|
||||
if (!sqlite_tables_ready($db)) return [];
|
||||
|
|
@ -186,8 +207,25 @@ function apply_sqlite_admin_action(string $path, array $post): void {
|
|||
$accountId = clean_id($post['account_id'] ?? '');
|
||||
if ($accountId === '' || $accountId === 'island-team') return;
|
||||
$db->beginTransaction();
|
||||
$db->prepare('UPDATE assets SET deleted_at = ?, deleted_by = ?, updated_at = ? WHERE owner_account_id = ? AND deleted_at IS NULL')->execute([$now, 'admin', $now, $accountId]);
|
||||
$db->prepare('UPDATE objects SET deleted_at = ?, deleted_by = ?, updated_at = ? WHERE owner_account_id = ? AND deleted_at IS NULL')->execute([$now, 'admin', $now, $accountId]);
|
||||
ensure_sqlite_anonymous_account($db);
|
||||
$assetRows = $db->prepare('SELECT id, json FROM assets WHERE owner_account_id = ?');
|
||||
$assetRows->execute([$accountId]);
|
||||
foreach ($assetRows->fetchAll() ?: [] as $row) {
|
||||
$asset = decode_json_value($row['json'] ?? '');
|
||||
$asset['ownerAccountId'] = 'anonymous';
|
||||
$asset['author'] = 'Anonymous';
|
||||
$asset['updatedAt'] = $now;
|
||||
$db->prepare('UPDATE assets SET owner_account_id = ?, author_name = ?, json = ?, updated_at = ? WHERE id = ?')
|
||||
->execute(['anonymous', 'Anonymous', encode_json_value($asset), $now, $row['id']]);
|
||||
}
|
||||
$objectRows = $db->prepare('SELECT id, json FROM objects WHERE owner_account_id = ?');
|
||||
$objectRows->execute([$accountId]);
|
||||
foreach ($objectRows->fetchAll() ?: [] as $row) {
|
||||
$object = decode_json_value($row['json'] ?? '');
|
||||
$object['ownerAccountId'] = 'anonymous';
|
||||
$db->prepare('UPDATE objects SET owner_account_id = ?, json = ?, updated_at = ? WHERE id = ?')
|
||||
->execute(['anonymous', encode_json_value($object), $now, $row['id']]);
|
||||
}
|
||||
$db->prepare('DELETE FROM votes WHERE voter_account_id = ?')->execute([$accountId]);
|
||||
$db->prepare("UPDATE reports SET status = 'closed' WHERE reporter_account_id = ?")->execute([$accountId]);
|
||||
$db->prepare('DELETE FROM accounts WHERE id = ?')->execute([$accountId]);
|
||||
|
|
@ -267,6 +305,12 @@ function apply_filedb_admin_action(string $path, array $post): void {
|
|||
write_filedb($path, function(array &$db) use ($post) {
|
||||
$action = (string)($post['admin_action'] ?? $post['moderation_action'] ?? '');
|
||||
$now = now_ms();
|
||||
$ensureAnonymous = function() use (&$db, $now): void {
|
||||
$db['accounts'] = is_array($db['accounts'] ?? null) ? $db['accounts'] : [];
|
||||
if (!isset($db['accounts']['anonymous'])) {
|
||||
$db['accounts']['anonymous'] = ['id'=>'anonymous','name'=>'Anonymous','password_hash'=>password_hash(bin2hex(random_bytes(16)), PASSWORD_DEFAULT),'created_at'=>$now,'updated_at'=>$now,'disabled_at'=>null];
|
||||
}
|
||||
};
|
||||
if ($action === 'rename_account') {
|
||||
$accountId = clean_id($post['account_id'] ?? '');
|
||||
$name = clean_text($post['name'] ?? '', '', 32);
|
||||
|
|
@ -292,9 +336,25 @@ function apply_filedb_admin_action(string $path, array $post): void {
|
|||
} elseif ($action === 'delete_account') {
|
||||
$accountId = clean_id($post['account_id'] ?? '');
|
||||
if ($accountId === '' || $accountId === 'island-team') return;
|
||||
foreach ($db['assets'] as &$asset) if (($asset['owner_account_id'] ?? '') === $accountId && empty($asset['deleted_at'])) { $asset['deleted_at'] = $now; $asset['deleted_by'] = 'admin'; $asset['updated_at'] = $now; }
|
||||
$ensureAnonymous();
|
||||
foreach ($db['assets'] as &$asset) if (($asset['owner_account_id'] ?? '') === $accountId) {
|
||||
$asset['owner_account_id'] = 'anonymous';
|
||||
$asset['author_name'] = 'Anonymous';
|
||||
$asset['updated_at'] = $now;
|
||||
$json = decode_json_value($asset['json'] ?? []);
|
||||
$json['ownerAccountId'] = 'anonymous';
|
||||
$json['author'] = 'Anonymous';
|
||||
$json['updatedAt'] = $now;
|
||||
$asset['json'] = $json;
|
||||
}
|
||||
unset($asset);
|
||||
foreach ($db['objects'] as &$object) if (($object['owner_account_id'] ?? '') === $accountId && empty($object['deleted_at'])) { $object['deleted_at'] = $now; $object['deleted_by'] = 'admin'; $object['updated_at'] = $now; }
|
||||
foreach ($db['objects'] as &$object) if (($object['owner_account_id'] ?? '') === $accountId) {
|
||||
$object['owner_account_id'] = 'anonymous';
|
||||
$object['updated_at'] = $now;
|
||||
$json = decode_json_value($object['json'] ?? []);
|
||||
$json['ownerAccountId'] = 'anonymous';
|
||||
$object['json'] = $json;
|
||||
}
|
||||
unset($object);
|
||||
foreach ($db['reports'] as &$report) if (($report['reporter_account_id'] ?? '') === $accountId) $report['status'] = 'closed';
|
||||
unset($report);
|
||||
|
|
@ -373,8 +433,20 @@ $baseUrl = './?token=' . rawurlencode($given);
|
|||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Pixel Island Admin</title>
|
||||
<script>
|
||||
document.addEventListener('submit', () => {
|
||||
try { sessionStorage.setItem('pixel-admin-scroll', String(window.scrollY || 0)); } catch (error) {}
|
||||
});
|
||||
window.addEventListener('load', () => {
|
||||
try {
|
||||
const y = Number(sessionStorage.getItem('pixel-admin-scroll') || 0);
|
||||
sessionStorage.removeItem('pixel-admin-scroll');
|
||||
if (y > 0) requestAnimationFrame(() => scrollTo(0, y));
|
||||
} catch (error) {}
|
||||
});
|
||||
</script>
|
||||
<style>
|
||||
body{font-family:system-ui,sans-serif;margin:24px;background:#f7f4ea;color:#2b2b2b}h1{margin-bottom:4px}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(310px,1fr));gap:12px}.card{background:#fff;border:1px solid #ddd;border-radius:10px;padding:14px;margin:10px 0}.media{display:flex;gap:12px;align-items:flex-start}.muted{color:#666;font-size:13px}.error{background:#fff1f1;border:1px solid #e3aaaa;padding:12px;border-radius:8px}code{background:#eee;padding:1px 4px;border-radius:4px}input{max-width:210px;padding:6px;margin:2px 4px 2px 0}button{margin:2px 4px 2px 0;padding:6px 9px;border:1px solid #bbb;border-radius:6px;background:#f5f5f5}.danger{background:#b92323;color:white;border:0}.ok{background:#267a3e;color:white;border:0}.warn{background:#8b5b00;color:white;border:0}.pill{display:inline-block;padding:2px 7px;border-radius:999px;background:#eee;font-size:12px}.nav a{margin-right:10px}.thumb{position:relative;flex:0 0 auto;width:72px;height:72px;background:#f2eadb;border:1px solid #ddd;image-rendering:pixelated}.thumb i{position:absolute;width:calc(72px / var(--w));height:calc(72px / var(--h));transform:scale(1.02);transform-origin:0 0}.thumb.empty:after{content:'no image';position:absolute;inset:0;display:grid;place-items:center;color:#777;font-size:11px}
|
||||
body{font-family:system-ui,sans-serif;margin:24px;background:#f7f4ea;color:#2b2b2b}h1{margin-bottom:4px}.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(380px,1fr));gap:12px}.card{background:#fff;border:1px solid #ddd;border-radius:10px;padding:14px;margin:10px 0}.media{display:flex;gap:14px;align-items:flex-start}.muted{color:#666;font-size:13px}.error{background:#fff1f1;border:1px solid #e3aaaa;padding:12px;border-radius:8px}code{background:#eee;padding:1px 4px;border-radius:4px}input{max-width:210px;padding:6px;margin:2px 4px 2px 0}button{margin:2px 4px 2px 0;padding:6px 9px;border:1px solid #bbb;border-radius:6px;background:#f5f5f5}.danger{background:#b92323;color:white;border:0}.ok{background:#267a3e;color:white;border:0}.warn{background:#8b5b00;color:white;border:0}.pill{display:inline-block;padding:2px 7px;border-radius:999px;background:#eee;font-size:12px}.nav a{margin-right:10px}.thumb{position:relative;flex:0 0 auto;width:128px;height:128px;background:#f2eadb;border:1px solid #ddd;image-rendering:pixelated;overflow:hidden}.thumb i{position:absolute;transform:scale(1.02);transform-origin:0 0}.thumb.empty:after{content:'no image';position:absolute;inset:0;display:grid;place-items:center;color:#777;font-size:11px}@media(max-width:520px){.grid{grid-template-columns:1fr}.media{flex-direction:column}.thumb{width:112px;height:112px}}
|
||||
</style>
|
||||
<h1>Pixel Island Admin</h1>
|
||||
<p class="muted">Store: <?=h($storeLabel)?> / Users: <?=count($accounts)?> / Pictures: <?=count($assets)?> / Reports: <?=count($reports)?></p>
|
||||
|
|
@ -398,7 +470,7 @@ body{font-family:system-ui,sans-serif;margin:24px;background:#f7f4ea;color:#2b2b
|
|||
<input name="name" value="<?=h($a['name'] ?? '')?>" maxlength="32">
|
||||
<button class="ok" name="admin_action" value="rename_account">Save</button>
|
||||
<?php if (empty($a['disabled_at'])): ?><button class="warn" name="admin_action" value="disable_account">Disable</button><?php else: ?><button class="ok" name="admin_action" value="enable_account">Enable</button><?php endif; ?>
|
||||
<?php if (($a['id'] ?? '') !== 'island-team'): ?><button class="danger" name="admin_action" value="delete_account" onclick="return confirm('Delete this user and hide their pictures?')">Delete</button><?php endif; ?>
|
||||
<?php if (($a['id'] ?? '') !== 'island-team' && ($a['id'] ?? '') !== 'anonymous'): ?><button class="danger" name="admin_action" value="delete_account" onclick="return confirm('Delete this user? Their pictures become Anonymous.')">Delete</button><?php endif; ?>
|
||||
</form>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue