128 lines
3.6 KiB
Svelte
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>
|
|
|
|
<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>
|
|
-
|
|
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>
|