Skip to content

Commit bb539fe

Browse files
committed
adding web application demo
1 parent 24619ce commit bb539fe

File tree

9 files changed

+1149
-0
lines changed

9 files changed

+1149
-0
lines changed

html/.nojekyll

Whitespace-only changes.

html/CNAME

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
jhash.21no.de

html/app.js

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import { JumpConsistentHash } from "./jch.js";
2+
3+
// Storage keys
4+
const STORE_KEY = "jch_entries_v1";
5+
const STORE_SLOTS = "jch_default_slots_v1";
6+
7+
// Elements
8+
const keyInput = document.getElementById("keyInput");
9+
const slotsInput = document.getElementById("slotsInput");
10+
const liveKey = document.getElementById("liveKey");
11+
const liveIndex = document.getElementById("liveIndex");
12+
const liveSlots = document.getElementById("liveSlots");
13+
const clearAllBtn = document.getElementById("clearAllBtn");
14+
const entriesBody = document.getElementById("entriesBody");
15+
16+
let jch = new JumpConsistentHash(5);
17+
let entries = [];
18+
19+
// Helpers to safely read/write value on native HTML inputs
20+
const getValueSafely = (el) => {
21+
if (!el) return "";
22+
return el.value || "";
23+
};
24+
25+
const setValueSafely = (el, v) => {
26+
if (!el) return;
27+
el.value = v;
28+
};
29+
30+
function loadSession() {
31+
try {
32+
const raw = sessionStorage.getItem(STORE_KEY);
33+
entries = raw ? JSON.parse(raw) : [];
34+
} catch {
35+
entries = [];
36+
}
37+
const savedSlots = parseInt(sessionStorage.getItem(STORE_SLOTS) || "5", 10);
38+
if (Number.isInteger(savedSlots) && savedSlots > 0) {
39+
setValueSafely(slotsInput, String(savedSlots));
40+
jch.setIndexes(savedSlots);
41+
liveSlots.textContent = String(savedSlots);
42+
}
43+
}
44+
45+
function saveSession() {
46+
sessionStorage.setItem(STORE_KEY, JSON.stringify(entries));
47+
sessionStorage.setItem(STORE_SLOTS, String(jch.size()));
48+
}
49+
50+
function fmtWhen(ts) {
51+
const d = new Date(ts);
52+
const now = new Date();
53+
const diffMs = now - d;
54+
const diffMins = Math.floor(diffMs / 60000);
55+
const diffHours = Math.floor(diffMs / 3600000);
56+
const diffDays = Math.floor(diffMs / 86400000);
57+
58+
if (diffMins < 1) return "Just now";
59+
if (diffMins < 60) return `${diffMins}m ago`;
60+
if (diffHours < 24) return `${diffHours}h ago`;
61+
if (diffDays < 7) return `${diffDays}d ago`;
62+
63+
return d.toLocaleDateString(undefined, {
64+
month: "short",
65+
day: "numeric",
66+
hour: "2-digit",
67+
minute: "2-digit",
68+
});
69+
}
70+
71+
function renderTable() {
72+
if (!entries.length) {
73+
entriesBody.innerHTML = '<tr class="empty"><td colspan="5">No entries yet. Type a key and press Enter.</td></tr>';
74+
return;
75+
}
76+
const rows = entries
77+
.slice()
78+
.sort((a, b) => b.ts - a.ts) // DESC
79+
.map(
80+
(e) => `
81+
<tr>
82+
<td class="key-cell" title="${e.key}">${escapeHtml(e.key)}</td>
83+
<td class="slots-cell">${e.slots}</td>
84+
<td class="index-cell"><span class="index-pill">${e.index}</span></td>
85+
<td class="when-cell">${fmtWhen(e.ts)}</td>
86+
<td class="action-cell"><md-icon-button aria-label="Remove entry" data-id="${
87+
e.id
88+
}"><md-icon>close</md-icon></md-icon-button></td>
89+
</tr>
90+
`
91+
)
92+
.join("");
93+
entriesBody.innerHTML = rows;
94+
// Hook delete buttons
95+
entriesBody.querySelectorAll("md-icon-button").forEach((btn) => {
96+
btn.addEventListener("click", () => {
97+
const id = btn.getAttribute("data-id");
98+
removeEntry(id);
99+
});
100+
});
101+
}
102+
103+
function escapeHtml(str) {
104+
return str.replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c]));
105+
}
106+
107+
async function updateLive() {
108+
const key = getValueSafely(keyInput).trim();
109+
const slots = parseInt(getValueSafely(slotsInput) || "5", 10);
110+
liveKey.textContent = key || "—";
111+
liveSlots.textContent = String(slots > 0 ? slots : 5);
112+
if (!key || !Number.isInteger(slots) || slots <= 0) {
113+
liveIndex.textContent = "—";
114+
return;
115+
}
116+
try {
117+
jch.setIndexes(slots);
118+
const idx = await jch.getIndex(key);
119+
liveIndex.textContent = String(idx);
120+
} catch (e) {
121+
liveIndex.textContent = "—";
122+
}
123+
}
124+
125+
async function commitEntry() {
126+
const key = getValueSafely(keyInput).trim();
127+
const slots = parseInt(getValueSafely(slotsInput) || "5", 10);
128+
if (!key || !Number.isInteger(slots) || slots <= 0) return;
129+
jch.setIndexes(slots);
130+
const index = await jch.getIndex(key);
131+
const ts = Date.now();
132+
entries.push({ id: `${ts}-${Math.random().toString(36).slice(2)}`, key, slots, index, ts });
133+
saveSession();
134+
renderTable();
135+
}
136+
137+
function removeEntry(id) {
138+
entries = entries.filter((e) => e.id !== id);
139+
saveSession();
140+
renderTable();
141+
}
142+
143+
function clearAll() {
144+
entries = [];
145+
// reset slots to default 5
146+
jch.setIndexes(5);
147+
setValueSafely(slotsInput, "5");
148+
liveSlots.textContent = "5";
149+
sessionStorage.setItem(STORE_SLOTS, "5");
150+
saveSession();
151+
renderTable();
152+
}
153+
154+
// Events
155+
keyInput.addEventListener("input", updateLive);
156+
keyInput.addEventListener("keydown", async (e) => {
157+
if (e.key === "Enter") {
158+
await commitEntry();
159+
keyInput.select();
160+
}
161+
});
162+
slotsInput.addEventListener("input", async () => {
163+
// sanitize
164+
let v = parseInt(getValueSafely(slotsInput) || "5", 10);
165+
if (!Number.isInteger(v) || v <= 0) v = 5;
166+
setValueSafely(slotsInput, String(v));
167+
jch.setIndexes(v);
168+
liveSlots.textContent = String(v);
169+
sessionStorage.setItem(STORE_SLOTS, String(v));
170+
await updateLive();
171+
});
172+
clearAllBtn.addEventListener("click", clearAll);
173+
174+
// Init
175+
loadSession();
176+
renderTable();
177+
updateLive();

html/apple-touch-icon.png

Lines changed: 74 additions & 0 deletions
Loading

html/favicon.png

Lines changed: 49 additions & 0 deletions
Loading

html/favicon.svg

Lines changed: 27 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)