rafmacalaba commited on
Commit
5d4cfb5
·
verified ·
1 Parent(s): be66719

name lock, checkpoint toasts, modern selects, header export

Browse files
Files changed (1) hide show
  1. review.html +58 -6
review.html CHANGED
@@ -34,8 +34,23 @@ button.drop { border-color: var(--drop); } button.drop.on { background: var(--dr
34
  button.nav { padding: 10px 14px; }
35
  #mine { color: var(--mut); font-size: 13px; margin-top: 8px; min-height: 20px; }
36
  kbd { background: var(--chip); border-radius: 4px; padding: 1px 6px; font-size: 12px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  footer { position: fixed; bottom: 0; left: 0; right: 0; background: var(--card); border-top: 1px solid var(--line); padding: 8px 16px; display: flex; gap: 10px; align-items: center; font-size: 13px; color: var(--mut); }
38
- footer .sp { flex: 1; }
39
  </style>
40
  </head>
41
  <body>
@@ -51,10 +66,12 @@ footer .sp { flex: 1; }
51
  <option value="rafael">rafael · 240</option>
52
  </select>
53
  <select id="src" onchange="reset()"></select>
54
- <input id="who" placeholder="your name to annotate" autocomplete="off"
55
- style="font:inherit;color:var(--fg);background:var(--bg);border:1px solid var(--line);border-radius:8px;padding:8px 10px;max-width:190px">
 
56
  <button onclick="theme()" title="toggle theme">◐</button>
57
  </header>
 
58
  <div style="display:flex;align-items:center;gap:10px;padding:6px 16px;background:var(--card);border-bottom:1px solid var(--line);font-size:13px;color:var(--mut)">
59
  <span id="ruledLab" style="white-space:nowrap">0 ruled</span>
60
  <div style="flex:1;height:6px;background:var(--chip);border-radius:3px;overflow:hidden"><div id="ruledFill" style="height:100%;width:0;background:var(--keep)"></div></div>
@@ -129,8 +146,25 @@ function loadWho() {
129
  });
130
  const ht = $('hubtok');
131
  if (ht) { ht.value = localStorage.getItem('review_hubtok') || ''; ht.addEventListener('input', () => localStorage.setItem('review_hubtok', ht.value.trim())); }
 
132
  paintGate();
133
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  function paintGate() {
135
  const ok = who().length > 0;
136
  $('who').style.borderColor = ok ? '' : 'var(--drop)';
@@ -228,12 +262,10 @@ function renderRef(sp) {
228
  }
229
  $('ref').innerHTML = ref;
230
  }
231
- let lastSaved = '';
232
  function lightMeta() {
233
  const mine = R[span().key];
234
  $('bKeep').className = 'keep' + (mine === 'DATA_MENTION' ? ' on' : '');
235
  $('bDrop').className = 'drop' + (mine === 'NON_MENTION' ? ' on' : '');
236
- $('mine').textContent = mine ? `your ruling: ${mine === 'DATA_MENTION' ? 'keep' : 'drop'}` : '';
237
  $('count').textContent = Object.keys(R).length + ' ruled' + (lastSaved ? ' · last save: ' + lastSaved : '');
238
  paintRuled();
239
  }
@@ -242,8 +274,27 @@ function paintRuled() {
242
  for (const o of ITEMS) for (const x of o.spans) { tot++; if (R[x.key]) done++; }
243
  $('ruledFill').style.width = (tot ? 100 * done / tot : 0) + '%';
244
  $('ruledLab').textContent = `${done}/${tot} mentions ruled`;
245
- $('ruledPct').textContent = tot ? Math.round(100 * done / tot) + '%' : '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
246
  }
 
247
  function bindMarks(o) {
248
  document.querySelectorAll('#passage mark').forEach(m => m.onclick = () => {
249
  show(i, o.spans.findIndex(x => x.key === m.dataset.k));
@@ -287,6 +338,7 @@ async function rule(v) {
287
  RQ[k] = $('ds').value;
288
  localStorage.setItem(LS, JSON.stringify(R));
289
  localStorage.setItem('review_queues', JSON.stringify(RQ));
 
290
  paintGate();
291
  const entry = {key: k, ruling: v, queue: $('ds').value, annotator: who()};
292
  let saved = 'this browser only';
 
34
  button.nav { padding: 10px 14px; }
35
  #mine { color: var(--mut); font-size: 13px; margin-top: 8px; min-height: 20px; }
36
  kbd { background: var(--chip); border-radius: 4px; padding: 1px 6px; font-size: 12px; }
37
+ select { appearance: none; -webkit-appearance: none; background-color: var(--card);
38
+ border: 1px solid var(--line); border-radius: 8px; padding: 8px 30px 8px 12px; cursor: pointer;
39
+ background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%23888' stroke-width='1.6' fill='none' stroke-linecap='round'/%3E%3C/svg%3E");
40
+ background-repeat: no-repeat; background-position: right 10px center; max-width: 220px; }
41
+ select:focus-visible, button:focus-visible, input:focus-visible { outline: 2px solid var(--hl); outline-offset: 1px; }
42
+ button { transition: background .12s ease, transform .05s ease; }
43
+ button:active:not(:disabled) { transform: scale(.97); }
44
+ button:disabled { cursor: not-allowed; }
45
+ input[type="text"], input:not([type]) { font: inherit; color: var(--fg); background: var(--bg);
46
+ border: 1px solid var(--line); border-radius: 8px; padding: 8px 10px; }
47
+ #who { max-width: 170px; }
48
+ #who.locked { background: var(--chip); color: var(--mut); border-style: dashed; }
49
+ #lockBtn { padding: 8px 10px; }
50
+ #toast { position: fixed; top: 110px; left: 50%; transform: translateX(-50%); background: var(--card);
51
+ border: 1px solid var(--hl); border-radius: 12px; padding: 12px 16px; display: none; gap: 12px;
52
+ align-items: center; z-index: 50; box-shadow: 0 8px 30px rgba(0,0,0,.25); font-size: 14px; }
53
  footer { position: fixed; bottom: 0; left: 0; right: 0; background: var(--card); border-top: 1px solid var(--line); padding: 8px 16px; display: flex; gap: 10px; align-items: center; font-size: 13px; color: var(--mut); }
 
54
  </style>
55
  </head>
56
  <body>
 
66
  <option value="rafael">rafael · 240</option>
67
  </select>
68
  <select id="src" onchange="reset()"></select>
69
+ <input id="who" placeholder="your name to annotate" autocomplete="off">
70
+ <button id="lockBtn" onclick="toggleLock()" title="lock / change name">unlock</button>
71
+ <button onclick="exp()" title="export rulings now">export</button>
72
  <button onclick="theme()" title="toggle theme">◐</button>
73
  </header>
74
+ <div id="toast" role="status"></div>
75
  <div style="display:flex;align-items:center;gap:10px;padding:6px 16px;background:var(--card);border-bottom:1px solid var(--line);font-size:13px;color:var(--mut)">
76
  <span id="ruledLab" style="white-space:nowrap">0 ruled</span>
77
  <div style="flex:1;height:6px;background:var(--chip);border-radius:3px;overflow:hidden"><div id="ruledFill" style="height:100%;width:0;background:var(--keep)"></div></div>
 
146
  });
147
  const ht = $('hubtok');
148
  if (ht) { ht.value = localStorage.getItem('review_hubtok') || ''; ht.addEventListener('input', () => localStorage.setItem('review_hubtok', ht.value.trim())); }
149
+ if (localStorage.getItem('review_locked') === '1' && who()) setLocked(true);
150
  paintGate();
151
  }
152
+ function setLocked(on) {
153
+ $('who').disabled = on;
154
+ $('who').classList.toggle('locked', on);
155
+ $('lockBtn').textContent = on ? 'locked' : 'unlock';
156
+ localStorage.setItem('review_locked', on ? '1' : '0');
157
+ }
158
+ function toggleLock() {
159
+ if ($('who').disabled) {
160
+ if (confirm('Change annotator name? Existing rulings stay under the old name.')) {
161
+ setLocked(false); $('who').focus();
162
+ }
163
+ return;
164
+ }
165
+ if (!who()) { needName(); return; }
166
+ setLocked(true);
167
+ }
168
  function paintGate() {
169
  const ok = who().length > 0;
170
  $('who').style.borderColor = ok ? '' : 'var(--drop)';
 
262
  }
263
  $('ref').innerHTML = ref;
264
  }
 
265
  function lightMeta() {
266
  const mine = R[span().key];
267
  $('bKeep').className = 'keep' + (mine === 'DATA_MENTION' ? ' on' : '');
268
  $('bDrop').className = 'drop' + (mine === 'NON_MENTION' ? ' on' : '');
 
269
  $('count').textContent = Object.keys(R).length + ' ruled' + (lastSaved ? ' · last save: ' + lastSaved : '');
270
  paintRuled();
271
  }
 
274
  for (const o of ITEMS) for (const x of o.spans) { tot++; if (R[x.key]) done++; }
275
  $('ruledFill').style.width = (tot ? 100 * done / tot : 0) + '%';
276
  $('ruledLab').textContent = `${done}/${tot} mentions ruled`;
277
+ const pct = tot ? Math.round(100 * done / tot) : 0;
278
+ $('ruledPct').textContent = tot ? pct + '%' : '';
279
+ // checkpoint nudge at each new 10% mark (per dataset)
280
+ const mark = Math.floor(pct / 10) * 10;
281
+ const mem = 'review_decile_' + $('ds').value;
282
+ if (tot && mark >= 10 && +(localStorage.getItem(mem) || 0) < mark) {
283
+ localStorage.setItem(mem, String(mark));
284
+ toast(`${mark}% annotated — export a checkpoint?`, true);
285
+ }
286
+ }
287
+ let toastTimer = null;
288
+ function toast(msg, withExport) {
289
+ const t = $('toast');
290
+ t.innerHTML = `<span>${esc(msg)}</span>` +
291
+ (withExport ? `<button onclick="exp();hideToast()">export now</button>` : '') +
292
+ `<button onclick="hideToast()">dismiss</button>`;
293
+ t.style.display = 'flex';
294
+ clearTimeout(toastTimer);
295
+ toastTimer = setTimeout(hideToast, 15000);
296
  }
297
+ function hideToast() { $('toast').style.display = 'none'; }
298
  function bindMarks(o) {
299
  document.querySelectorAll('#passage mark').forEach(m => m.onclick = () => {
300
  show(i, o.spans.findIndex(x => x.key === m.dataset.k));
 
338
  RQ[k] = $('ds').value;
339
  localStorage.setItem(LS, JSON.stringify(R));
340
  localStorage.setItem('review_queues', JSON.stringify(RQ));
341
+ setLocked(true);
342
  paintGate();
343
  const entry = {key: k, ruling: v, queue: $('ds').value, annotator: who()};
344
  let saved = 'this browser only';