2024-09-24 21:57:51 +00:00
|
|
|
<!DOCTYPE html>
|
|
|
|
<html lang="en">
|
|
|
|
<head>
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
<title>Text Evaluation Review</title>
|
|
|
|
<style>
|
|
|
|
body {
|
|
|
|
font-family: Arial, sans-serif;
|
|
|
|
background-color: #f9f9f9;
|
|
|
|
margin: 0;
|
|
|
|
padding: 20px;
|
|
|
|
}
|
|
|
|
h1 {
|
|
|
|
text-align: center;
|
|
|
|
font-size: 2em;
|
|
|
|
color: #333;
|
|
|
|
}
|
|
|
|
.container {
|
|
|
|
width: 100%;
|
2025-01-16 18:00:12 +00:00
|
|
|
max-width: 1600px;
|
2024-09-24 21:57:51 +00:00
|
|
|
margin: 0 auto;
|
|
|
|
}
|
|
|
|
.entry {
|
|
|
|
display: grid;
|
|
|
|
grid-template-columns: 1fr 1fr 1fr;
|
|
|
|
grid-gap: 20px;
|
|
|
|
margin-bottom: 20px;
|
|
|
|
padding: 20px;
|
|
|
|
background-color: #fff;
|
|
|
|
border-radius: 8px;
|
|
|
|
box-shadow: 0 0 10px rgba(0,0,0,0.1);
|
|
|
|
transition: background-color 0.3s ease;
|
|
|
|
}
|
|
|
|
.text-block {
|
|
|
|
padding: 10px;
|
|
|
|
background-color: #f1f1f1;
|
|
|
|
border-radius: 6px;
|
|
|
|
min-height: 100px;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
justify-content: space-between;
|
|
|
|
cursor: pointer;
|
2024-10-09 17:53:26 +00:00
|
|
|
position: relative;
|
2024-09-24 21:57:51 +00:00
|
|
|
}
|
|
|
|
.text-block:hover {
|
|
|
|
background-color: #e0e0e0;
|
|
|
|
}
|
|
|
|
.text-block.selected {
|
|
|
|
background-color: lightgreen;
|
|
|
|
border: 2px solid black;
|
|
|
|
}
|
|
|
|
.alignment {
|
|
|
|
font-size: 0.9em;
|
|
|
|
color: #777;
|
|
|
|
margin-top: 10px;
|
|
|
|
}
|
|
|
|
.reveal-box {
|
|
|
|
position: fixed;
|
|
|
|
top: 20px;
|
|
|
|
right: 20px;
|
|
|
|
padding: 15px;
|
|
|
|
background-color: white;
|
|
|
|
border: 1px solid #ccc;
|
|
|
|
border-radius: 8px;
|
|
|
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
|
|
|
z-index: 1000;
|
|
|
|
width: 200px;
|
|
|
|
}
|
|
|
|
.reveal-box input {
|
|
|
|
margin-right: 10px;
|
|
|
|
}
|
|
|
|
.reveal-info {
|
|
|
|
margin-top: 10px;
|
|
|
|
font-size: 0.9em;
|
|
|
|
color: #333;
|
|
|
|
}
|
|
|
|
.revealed .gold {
|
|
|
|
background-color: #fff9e6;
|
|
|
|
}
|
|
|
|
.revealed .eval {
|
|
|
|
background-color: #e6f3ff;
|
|
|
|
}
|
|
|
|
.revealed .text-block.selected {
|
|
|
|
border: 2px solid black;
|
|
|
|
}
|
|
|
|
.entry > div:first-child img {
|
|
|
|
width: 100%;
|
|
|
|
height: auto;
|
|
|
|
object-fit: cover;
|
|
|
|
border-radius: 6px;
|
|
|
|
cursor: pointer;
|
|
|
|
transition: transform 0.3s ease, box-shadow 0.3s ease;
|
|
|
|
}
|
|
|
|
/* Full-screen preview mode */
|
|
|
|
.full-screen img {
|
|
|
|
position: fixed;
|
|
|
|
top: 50%;
|
|
|
|
left: 50%;
|
|
|
|
transform: translate(-50%, -50%);
|
|
|
|
width: unset !important;
|
|
|
|
max-width: 90vw;
|
|
|
|
height: auto;
|
|
|
|
max-height: 90vh;
|
|
|
|
z-index: 1001;
|
|
|
|
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
|
|
|
|
}
|
|
|
|
.overlay {
|
|
|
|
position: fixed;
|
|
|
|
top: 0;
|
|
|
|
left: 0;
|
|
|
|
width: 100vw;
|
|
|
|
height: 100vh;
|
|
|
|
background-color: rgba(0, 0, 0, 0.7);
|
|
|
|
z-index: 1000;
|
|
|
|
display: none;
|
|
|
|
}
|
|
|
|
.overlay.active {
|
|
|
|
display: block;
|
|
|
|
}
|
|
|
|
/* Voting Buttons Styles */
|
|
|
|
.voting-buttons {
|
|
|
|
margin-top: 10px;
|
|
|
|
display: flex;
|
|
|
|
flex-direction: column;
|
|
|
|
gap: 5px;
|
|
|
|
}
|
|
|
|
.voting-buttons button {
|
|
|
|
padding: 8px 12px;
|
|
|
|
border: none;
|
|
|
|
border-radius: 4px;
|
|
|
|
cursor: pointer;
|
|
|
|
background-color: #007BFF;
|
|
|
|
color: white;
|
|
|
|
transition: background-color 0.3s ease, border 0.3s ease;
|
|
|
|
}
|
|
|
|
.voting-buttons button:hover {
|
|
|
|
background-color: #0056b3;
|
|
|
|
}
|
|
|
|
.voting-buttons button.invalid {
|
|
|
|
background-color: #dc3545;
|
|
|
|
}
|
|
|
|
.voting-buttons button.invalid:hover {
|
|
|
|
background-color: #a71d2a;
|
|
|
|
}
|
|
|
|
.voting-buttons button.both-good {
|
|
|
|
background-color: #28a745;
|
|
|
|
}
|
|
|
|
.voting-buttons button.both-good:hover {
|
|
|
|
background-color: #1e7e34;
|
|
|
|
}
|
|
|
|
.voting-buttons button.both-bad {
|
|
|
|
background-color: #ffc107;
|
|
|
|
color: #212529;
|
|
|
|
}
|
|
|
|
.voting-buttons button.both-bad:hover {
|
|
|
|
background-color: #e0a800;
|
|
|
|
}
|
|
|
|
/* Selected State for Voting Buttons */
|
|
|
|
.voting-buttons button.selected {
|
|
|
|
border: 3px solid #000;
|
|
|
|
}
|
2024-10-09 17:53:26 +00:00
|
|
|
/* for diffs */
|
|
|
|
.added {
|
|
|
|
background-color: #d4fcdc;
|
|
|
|
}
|
|
|
|
.removed {
|
|
|
|
background-color: #fcd4d4;
|
|
|
|
text-decoration: line-through;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Diff Toggle Styles */
|
|
|
|
body.diffed .right-text {
|
|
|
|
display: none;
|
|
|
|
}
|
|
|
|
body.diffed .diff-text {
|
|
|
|
display: block;
|
|
|
|
}
|
|
|
|
.diff-text {
|
|
|
|
display: none;
|
|
|
|
}
|
2024-09-24 21:57:51 +00:00
|
|
|
</style>
|
|
|
|
</head>
|
|
|
|
<body>
|
|
|
|
<h1>Text Evaluation Review</h1>
|
|
|
|
|
|
|
|
<!-- Floating Reveal Box -->
|
|
|
|
<div class="reveal-box">
|
2024-10-09 17:53:26 +00:00
|
|
|
<div>
|
|
|
|
<input type="checkbox" id="diff-toggle" />
|
|
|
|
<label for="diff-toggle">Toggle diff</label>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div>
|
|
|
|
<input type="checkbox" id="reveal-toggle" />
|
|
|
|
<label for="reveal-toggle">Reveal Gold/Eval</label>
|
|
|
|
</div>
|
2024-09-24 21:57:51 +00:00
|
|
|
<div class="reveal-info" id="vote-info">Votes</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="container">
|
|
|
|
{% for entry in entries %}
|
2025-01-14 22:57:17 +00:00
|
|
|
<div class="entry {{ entry.gold_class }} {{ entry.eval_class }}" data-entry-id="{{ entry.key }}" data-left-metadata="{{ entry.left_metadata }}" data-right-metadata="{{ entry.right_metadata }}">
|
2024-09-24 21:57:51 +00:00
|
|
|
<div class="image-container">
|
|
|
|
<img src="data:image/png;base64,{{ entry.page_image }}" alt="Render">
|
|
|
|
|
2024-10-09 17:53:26 +00:00
|
|
|
<div class="alignment">Alignment: {{ entry.alignment }}</div>
|
2024-09-24 21:57:51 +00:00
|
|
|
<a href="{{entry.signed_pdf_link}}#page={{ entry.page }}" target="_blank">{{ entry.s3_path }} (Page {{ entry.page }})</a>
|
|
|
|
|
|
|
|
<!-- Voting Buttons -->
|
|
|
|
<div class="voting-buttons">
|
|
|
|
<button class="both-good" data-vote="both_good">Both Good</button>
|
|
|
|
<button class="both-bad" data-vote="both_bad">Both Bad</button>
|
|
|
|
<button class="invalid" data-vote="invalid_pdf">Invalid PDF</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="text-block {{ entry.left_class }}" data-choice="left">
|
|
|
|
<div>{{ entry.left_text|safe }}</div>
|
|
|
|
</div>
|
2024-10-09 17:53:26 +00:00
|
|
|
<!-- Updated Right Text-Block with separate divs for right_text and diff_text -->
|
2024-09-24 21:57:51 +00:00
|
|
|
<div class="text-block {{ entry.right_class }}" data-choice="right">
|
2024-10-09 17:53:26 +00:00
|
|
|
<div class="right-text">{{ entry.right_text|safe }}</div>
|
|
|
|
<div class="diff-text">{{ entry.diff_text|safe }}</div>
|
2024-09-24 21:57:51 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{% endfor %}
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<!-- Overlay for full-screen preview -->
|
|
|
|
<div class="overlay"></div>
|
|
|
|
|
|
|
|
<script>
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
|
|
fetchDataAndUpdatePage();
|
|
|
|
|
|
|
|
// Toggle the full-screen image preview
|
|
|
|
const overlay = document.querySelector('.overlay');
|
|
|
|
document.querySelectorAll('.image-container img').forEach(img => {
|
|
|
|
img.addEventListener('click', () => {
|
|
|
|
const entry = img.closest('.entry');
|
|
|
|
entry.classList.toggle('full-screen');
|
|
|
|
overlay.classList.toggle('active');
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
overlay.addEventListener('click', () => {
|
|
|
|
document.querySelectorAll('.full-screen').forEach(entry => {
|
|
|
|
entry.classList.remove('full-screen');
|
|
|
|
});
|
|
|
|
overlay.classList.remove('active');
|
|
|
|
});
|
|
|
|
|
2024-10-09 17:53:26 +00:00
|
|
|
// Handle Reveal Gold/Eval Toggle
|
2024-09-24 21:57:51 +00:00
|
|
|
document.getElementById('reveal-toggle').addEventListener('change', (e) => {
|
|
|
|
document.body.classList.toggle('revealed', e.target.checked);
|
|
|
|
updateReveal();
|
|
|
|
});
|
|
|
|
|
2024-10-09 17:53:26 +00:00
|
|
|
// Handle Diff Toggle
|
|
|
|
document.getElementById('diff-toggle').addEventListener('change', (e) => {
|
|
|
|
document.body.classList.toggle('diffed', e.target.checked);
|
|
|
|
toggleDiff(e.target.checked);
|
|
|
|
});
|
|
|
|
|
2024-09-24 21:57:51 +00:00
|
|
|
// Handle text-block selections
|
|
|
|
document.querySelectorAll('.text-block').forEach(block => {
|
|
|
|
block.addEventListener('click', () => selectChoice(block));
|
|
|
|
});
|
|
|
|
|
|
|
|
// Handle voting buttons
|
|
|
|
document.querySelectorAll('.voting-buttons button').forEach(button => {
|
|
|
|
button.addEventListener('click', () => handleVote(button));
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
// Utility function to sanitize s3_path for use as a key
|
|
|
|
function sanitizeKey(key) {
|
|
|
|
return key.replace(/[^a-zA-Z0-9-_]/g, '_');
|
|
|
|
}
|
|
|
|
|
|
|
|
async function fetchDataAndUpdatePage() {
|
|
|
|
let datastore = await fetchDatastore();
|
|
|
|
|
|
|
|
document.querySelectorAll('.entry').forEach(entry => {
|
|
|
|
const entryKey = sanitizeKey(entry.getAttribute('data-entry-id'));
|
|
|
|
const leftBlock = entry.querySelector('.text-block[data-choice="left"]');
|
|
|
|
const rightBlock = entry.querySelector('.text-block[data-choice="right"]');
|
|
|
|
const voteButtons = entry.querySelectorAll('.voting-buttons button');
|
|
|
|
|
|
|
|
if (datastore[entryKey]) {
|
|
|
|
const choice = datastore[entryKey];
|
|
|
|
if (choice === 'left' || choice === 'right') {
|
|
|
|
const selectedBlock = choice === 'left' ? leftBlock : rightBlock;
|
|
|
|
selectChoice(selectedBlock, false);
|
|
|
|
} else {
|
|
|
|
// Handle additional voting choices
|
|
|
|
handleAdditionalVote(entry, choice, false);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2024-10-09 17:53:26 +00:00
|
|
|
// Ensure diff state is consistent on load
|
|
|
|
const diffToggle = document.getElementById('diff-toggle');
|
|
|
|
toggleDiff(diffToggle.checked);
|
2024-09-24 21:57:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async function selectChoice(block, save = true) {
|
|
|
|
let datastore = await fetchDatastore();
|
|
|
|
|
|
|
|
const entry = block.closest('.entry');
|
|
|
|
const entryKey = sanitizeKey(entry.getAttribute('data-entry-id'));
|
|
|
|
const blocks = entry.querySelectorAll('.text-block');
|
|
|
|
|
|
|
|
blocks.forEach(b => b.classList.remove('selected'));
|
|
|
|
block.classList.add('selected');
|
|
|
|
|
|
|
|
datastore[entryKey] = block.getAttribute('data-choice');
|
|
|
|
|
|
|
|
const numVotes = Object.keys(datastore).length;
|
|
|
|
document.getElementById("vote-info").innerText = `Total Votes: ${numVotes}`;
|
|
|
|
|
|
|
|
if (save) {
|
|
|
|
putDatastore(datastore); // Save entire datastore
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function handleVote(button, save = true) {
|
|
|
|
let datastore = await fetchDatastore();
|
|
|
|
|
|
|
|
const entry = button.closest('.entry');
|
|
|
|
const entryKey = sanitizeKey(entry.getAttribute('data-entry-id'));
|
|
|
|
const choice = button.getAttribute('data-vote');
|
|
|
|
|
|
|
|
// Deselect any selected voting buttons within this entry
|
|
|
|
const voteButtons = entry.querySelectorAll('.voting-buttons button');
|
|
|
|
voteButtons.forEach(b => b.classList.remove('selected'));
|
|
|
|
|
|
|
|
// Select the clicked button
|
|
|
|
button.classList.add('selected');
|
|
|
|
|
|
|
|
// Deselect any selected text-blocks
|
|
|
|
const textBlocks = entry.querySelectorAll('.text-block');
|
|
|
|
textBlocks.forEach(b => b.classList.remove('selected'));
|
|
|
|
|
|
|
|
datastore[entryKey] = choice;
|
|
|
|
|
|
|
|
const numVotes = Object.keys(datastore).length;
|
|
|
|
document.getElementById("vote-info").innerText = `Total Votes: ${numVotes}`;
|
|
|
|
|
|
|
|
if (save) {
|
|
|
|
putDatastore(datastore); // Save entire datastore
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function handleAdditionalVote(entry, choice, save = true) {
|
|
|
|
let datastore = await fetchDatastore();
|
|
|
|
|
|
|
|
const entryKey = sanitizeKey(entry.getAttribute('data-entry-id'));
|
|
|
|
|
|
|
|
// Select the appropriate voting button based on the choice
|
|
|
|
const voteButton = entry.querySelector(`.voting-buttons button[data-vote="${choice}"]`);
|
|
|
|
if (voteButton) {
|
|
|
|
// Deselect other voting buttons
|
|
|
|
const voteButtons = entry.querySelectorAll('.voting-buttons button');
|
|
|
|
voteButtons.forEach(b => b.classList.remove('selected'));
|
|
|
|
|
|
|
|
// Select the current button
|
|
|
|
voteButton.classList.add('selected');
|
|
|
|
}
|
|
|
|
|
|
|
|
datastore[entryKey] = choice;
|
|
|
|
|
|
|
|
if (save) {
|
|
|
|
putDatastore(datastore);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
async function updateReveal() {
|
|
|
|
let datastore = await fetchDatastore();
|
|
|
|
let goldVotes = 0;
|
|
|
|
let evalVotes = 0;
|
|
|
|
let bothGoodVotes = 0;
|
|
|
|
let bothBadVotes = 0;
|
|
|
|
let invalidPdfVotes = 0;
|
|
|
|
|
|
|
|
document.querySelectorAll('.entry').forEach(entry => {
|
|
|
|
const entryKey = sanitizeKey(entry.getAttribute('data-entry-id'));
|
|
|
|
const leftBlock = entry.querySelector('.text-block[data-choice="left"]');
|
|
|
|
const rightBlock = entry.querySelector('.text-block[data-choice="right"]');
|
|
|
|
|
|
|
|
const vote = datastore[entryKey];
|
|
|
|
if (vote === 'left') {
|
|
|
|
if (leftBlock.classList.contains('gold')) {
|
|
|
|
goldVotes++;
|
|
|
|
} else {
|
|
|
|
evalVotes++;
|
|
|
|
}
|
|
|
|
} else if (vote === 'right') {
|
|
|
|
if (rightBlock.classList.contains('gold')) {
|
|
|
|
goldVotes++;
|
|
|
|
} else {
|
|
|
|
evalVotes++;
|
|
|
|
}
|
|
|
|
} else if (vote === 'both_good') {
|
|
|
|
bothGoodVotes++;
|
|
|
|
} else if (vote === 'both_bad') {
|
|
|
|
bothBadVotes++;
|
|
|
|
} else if (vote === 'invalid_pdf') {
|
|
|
|
invalidPdfVotes++;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
const totalVotes = goldVotes + evalVotes + bothGoodVotes + bothBadVotes + invalidPdfVotes;
|
|
|
|
const goldPercentage = totalVotes > 0 ? Math.round((goldVotes / totalVotes) * 100) : 0;
|
|
|
|
const evalPercentage = totalVotes > 0 ? Math.round((evalVotes / totalVotes) * 100) : 0;
|
|
|
|
const bothGoodPercentage = totalVotes > 0 ? Math.round((bothGoodVotes / totalVotes) * 100) : 0;
|
|
|
|
const bothBadPercentage = totalVotes > 0 ? Math.round((bothBadVotes / totalVotes) * 100) : 0;
|
|
|
|
const invalidPdfPercentage = totalVotes > 0 ? Math.round((invalidPdfVotes / totalVotes) * 100) : 0;
|
|
|
|
|
|
|
|
document.getElementById("vote-info").innerText = `Gold: ${goldPercentage}% | Eval: ${evalPercentage}% | Both Good: ${bothGoodPercentage}% | Both Bad: ${bothBadPercentage}% | Invalid PDF: ${invalidPdfPercentage}%`;
|
|
|
|
|
|
|
|
document.querySelectorAll('.entry').forEach(entry => {
|
|
|
|
const entryKey = sanitizeKey(entry.getAttribute('data-entry-id'));
|
|
|
|
const vote = datastore[entryKey];
|
|
|
|
if (vote === 'left' || vote === 'right') {
|
|
|
|
const selectedBlock = vote === 'left' ? entry.querySelector('.text-block[data-choice="left"]') : entry.querySelector('.text-block[data-choice="right"]');
|
|
|
|
selectedBlock.classList.add('selected');
|
|
|
|
}
|
|
|
|
// Additional votes already handled in handleAdditionalVote
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-10-09 17:53:26 +00:00
|
|
|
// Function to toggle diff text
|
|
|
|
function toggleDiff(isDiffed) {
|
|
|
|
if (isDiffed) {
|
|
|
|
document.body.classList.add('diffed');
|
|
|
|
} else {
|
|
|
|
document.body.classList.remove('diffed');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-09-24 21:57:51 +00:00
|
|
|
</script>
|
|
|
|
</body>
|
|
|
|
</html>
|