toolkit/src/routes/object-id/+page.svelte
2026-03-24 22:53:13 -03:00

128 lines
3.6 KiB
Svelte

<svelte:head>
<title>ObjectId Tools</title>
</svelte:head>
<script lang="js">
/**
* @typedef {{
* timestamp:number,
* date:string,
* dateLocal:string,
* randomPart:string,
* counterPart:string}} ParsedObjectId
*/
// ObjectId: 4 bytes timestamp | 5 bytes random | 3 bytes counter
function generateObjectId() {
const timestamp = Math.floor(Date.now() / 1000);
const tsHex = timestamp.toString(16).padStart(8, '0');
const random = Array.from({ length: 10 }, () =>
Math.floor(Math.random() * 16).toString(16)
).join('');
const counter = Math.floor(Math.random() * 0xffffff);
const counterHex = counter.toString(16).padStart(6, '0');
return tsHex + random + counterHex;
}
/**
* @param {string} id
* @returns {ParsedObjectId | { error: string }}
*/
function parseObjectId(id) {
if (!/^[0-9a-fA-F]{24}$/.test(id)) {
return { error: 'Invalid ObjectId: must be 24 hex characters' };
}
const tsHex = id.slice(0, 8);
const randomPart = id.slice(8, 18);
const counterPart = id.slice(18, 24);
const timestampSec = parseInt(tsHex, 16);
const date = new Date(timestampSec * 1000);
return {
timestamp: timestampSec,
date: date.toISOString(),
dateLocal: date.toLocaleString(),
randomPart,
counterPart
};
}
let generated = $state(generateObjectId());
let parseInput = $state('');
const parseResult = $derived(parseInput.trim().length === 24 ? parseObjectId(parseInput.trim()) : null);
const parsed = $derived(parseResult && !('error' in parseResult) ? parseResult : null);
const parseError = $derived(parseResult && 'error' in parseResult ? parseResult.error : '');
function onGenerate() {
generated = generateObjectId();
}
import { copy as copyToClipboard } from '$lib/clipboard.js';
</script>
<h1>ObjectId</h1>
<h2>Generate</h2>
<p>
<code>
<span class="ts" title="timestamp">{generated.slice(0, 8)}</span><span
class="rnd" title="random">{generated.slice(8, 18)}</span><span
class="cnt" title="counter">{generated.slice(18, 24)}</span>
</code>
&nbsp;
<button onclick={onGenerate}>New</button>
<button onclick={() => copyToClipboard(generated)}>Copy</button>
</p>
<p class="legend">
<span class="ts">■ timestamp</span>
<span class="rnd">■ random</span>
<span class="cnt">■ counter</span>
&nbsp;-&nbsp;
Generated: {new Date(parseInt(generated.slice(0, 8), 16) * 1000).toLocaleString()}
</p>
<h2>Parse</h2>
<p>
<input
type="text"
size="26"
placeholder="507f1f77bcf86cd799439011"
bind:value={parseInput}
spellcheck="false"
/>
</p>
{#if parseError}
<p class="error">{parseError}</p>
{/if}
{#if parsed}
<table>
<tbody>
<tr><td>Timestamp (unix)</td><td><code>{parsed.timestamp}</code></td><td><button onclick={() => copyToClipboard(String(parsed.timestamp))}>copy</button></td></tr>
<tr><td>Date (UTC)</td><td><code>{parsed.date}</code></td><td><button onclick={() => copyToClipboard(parsed.date)}>copy</button></td></tr>
<tr><td>Date (local)</td><td><code>{parsed.dateLocal}</code></td><td><button onclick={() => copyToClipboard(parsed.dateLocal)}>copy</button></td></tr>
<tr><td>Random part</td><td><code class="rnd">{parsed.randomPart}</code></td><td><button onclick={() => copyToClipboard(parsed.randomPart)}>copy</button></td></tr>
<tr><td>Counter</td><td><code class="cnt">{parsed.counterPart}</code> ({parseInt(parsed.counterPart, 16)})</td><td><button onclick={() => copyToClipboard(parsed.counterPart)}>copy</button></td></tr>
</tbody>
</table>
{/if}
<style>
.ts { color: var(--red); }
.rnd { color: var(--blue); }
.cnt { color: var(--green); }
.legend {
font-size: 12px;
color: var(--fg-dim);
}
.error {
color: var(--err);
}
</style>