a11yn-annotation / templates /annotate.html
jeffrobot's picture
initial deploy
b98fd59 verified
Raw
History Blame Contribute Delete
6.41 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>{{ index }}/{{ total }} &mdash; {{ test.name }}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body class="annotate">
<header class="topbar">
<div class="topbar-left">
<a href="{{ url_for('index') }}" class="home-link">&larr; Home</a>
<span class="test-label">Test {{ test_id }}: {{ test.name }}</span>
</div>
<div class="topbar-center">
<span class="progress">
<b>{{ index }}</b> / {{ total }}
&nbsp;·&nbsp; <span class="muted">{{ done_count }} annotated</span>
</span>
</div>
<div class="topbar-right">
<span class="annotator-tag">{{ annotator }}</span>
<form class="jump-form" onsubmit="return jumpTo(event)">
<input type="number" id="jump" min="1" max="{{ total }}"
placeholder="jump…" value="{{ index }}">
</form>
</div>
</header>
<section class="request-box">
<h2>User request <span class="muted small">({{ item.request_id }})</span></h2>
<p>{{ item.user_request }}</p>
<details>
<summary>metadata</summary>
<ul>
<li><b>page_type:</b> {{ item.metadata.page_type }}</li>
<li><b>domain:</b> {{ item.metadata.domain }}</li>
<li><b>purpose:</b> {{ item.metadata.purpose }}</li>
<li><b>components:</b>
<ul>
{% for c in item.metadata.components %}<li>{{ c }}</li>{% endfor %}
</ul>
</li>
</ul>
</details>
</section>
<section class="pair">
<figure class="ui-card">
<figcaption>Left (A)</figcaption>
<a href="{{ url_for('image', model=mapping.left_model, filename=filename) }}"
target="_blank" rel="noopener">
<img src="{{ url_for('image', model=mapping.left_model, filename=filename) }}"
alt="Left UI">
</a>
</figure>
<figure class="ui-card">
<figcaption>Right (B)</figcaption>
<a href="{{ url_for('image', model=mapping.right_model, filename=filename) }}"
target="_blank" rel="noopener">
<img src="{{ url_for('image', model=mapping.right_model, filename=filename) }}"
alt="Right UI">
</a>
</figure>
</section>
<section class="rating">
<h3>Which UI better matches the request?</h3>
<div class="rating-buttons">
{% set labels = {
'strong_left': ('1', 'Strong Left', 'Left is clearly better'),
'weak_left': ('2', 'Weak Left', 'Left is slightly better'),
'similar': ('3', 'Similar', 'About the same'),
'weak_right': ('4', 'Weak Right', 'Right is slightly better'),
'strong_right': ('5', 'Strong Right', 'Right is clearly better')
} %}
{% for c in choices %}
{% set key, lbl, desc = labels[c] %}
<button type="button"
class="choice {% if current_choice == c %}selected{% endif %}"
data-choice="{{ c }}"
title="{{ desc }} (key {{ key }})">
<span class="key">{{ key }}</span>
<span class="lbl">{{ lbl }}</span>
<span class="desc">{{ desc }}</span>
</button>
{% endfor %}
</div>
<p class="status muted" id="status">
{% if current_choice %}Saved: <b>{{ current_choice }}</b>. Pick again to overwrite.{% else %}Pick one (or press 1&ndash;5). Auto-advances on save.{% endif %}
</p>
</section>
<nav class="navbar">
{% if index > 1 %}
<a class="nav prev" href="{{ url_for('annotate', test_id=test_id, index=index-1, annotator=annotator) }}">&larr; Prev (←)</a>
{% else %}
<span class="nav prev disabled">&larr; Prev</span>
{% endif %}
{% if index < total %}
<a class="nav next" href="{{ url_for('annotate', test_id=test_id, index=index+1, annotator=annotator) }}">Next (→) &rarr;</a>
{% else %}
<span class="nav next disabled">Next &rarr;</span>
{% endif %}
</nav>
<script>
const CTX = {
annotator: {{ annotator|tojson }},
test_id: {{ test_id|tojson }},
index: {{ index|tojson }},
total: {{ total|tojson }},
};
function nextUrl(i) {
const u = new URL(window.location.origin + "{{ url_for('annotate', test_id=test_id, index=0, annotator=annotator) }}".replace('/0', '/' + i));
return u.toString();
}
async function submitChoice(choice) {
const status = document.getElementById('status');
status.textContent = 'Saving…';
try {
const res = await fetch("{{ url_for('submit') }}", {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
annotator: CTX.annotator,
test_id: CTX.test_id,
index: CTX.index,
choice: choice,
}),
});
const data = await res.json();
if (!data.ok) throw new Error(data.error || 'save failed');
document.querySelectorAll('.choice').forEach(b => {
b.classList.toggle('selected', b.dataset.choice === choice);
});
if (CTX.index < CTX.total) {
window.location.href = nextUrl(CTX.index + 1);
} else {
status.textContent = 'Saved. This was the last pair.';
}
} catch (e) {
status.textContent = 'Error: ' + e.message;
}
}
document.querySelectorAll('.choice').forEach(btn => {
btn.addEventListener('click', () => submitChoice(btn.dataset.choice));
});
const KEY_MAP = {
'1': 'strong_left',
'2': 'weak_left',
'3': 'similar',
'4': 'weak_right',
'5': 'strong_right',
};
document.addEventListener('keydown', (e) => {
if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA') return;
if (KEY_MAP[e.key]) {
e.preventDefault();
submitChoice(KEY_MAP[e.key]);
} else if (e.key === 'ArrowLeft' && CTX.index > 1) {
window.location.href = nextUrl(CTX.index - 1);
} else if (e.key === 'ArrowRight' && CTX.index < CTX.total) {
window.location.href = nextUrl(CTX.index + 1);
}
});
function jumpTo(e) {
e.preventDefault();
const v = parseInt(document.getElementById('jump').value, 10);
if (v >= 1 && v <= CTX.total) window.location.href = nextUrl(v);
return false;
}
</script>
</body>
</html>