mirror of
https://github.com/PR0M3TH3AN/unredact.git
synced 2026-11-06 21:50:29 +00:00
feat: add toast notification system
Co-Authored-By: Claude Opus 4.6 <[email protected]>
This commit is contained in:
1 parent
fe9eb289ac
commit
3d739b13f5
3 files changed
+97
-71
No files matched your search
+54
-19
@@ -19,8 +19,6 @@ const rightPanel = document.getElementById("right-panel");
|
||||
const docContainer = document.getElementById("doc-container");
|
||||
const popover = document.getElementById("popover");
|
||||
const popoverClose = document.getElementById("popover-close");
|
||||
const popoverContext = document.getElementById("popover-context");
|
||||
const popoverFontInfo = document.getElementById("popover-font-info");
|
||||
const solveCharset = document.getElementById("solve-charset");
|
||||
const solveTolerance = document.getElementById("solve-tolerance");
|
||||
const solveTolValue = document.getElementById("solve-tol-value");
|
||||
@@ -53,6 +51,15 @@ const leftTextInput = document.getElementById("left-text-input");
|
||||
const rightTextInput = document.getElementById("right-text-input");
|
||||
const redactionMarker = document.getElementById("redaction-marker");
|
||||
const textReset = document.getElementById("text-reset");
|
||||
const toastContainer = document.getElementById("toast-container");
|
||||
|
||||
function showToast(message, type = "info") {
|
||||
const el = document.createElement("div");
|
||||
el.className = `toast ${type}`;
|
||||
el.textContent = message;
|
||||
toastContainer.appendChild(el);
|
||||
setTimeout(() => el.remove(), 3000);
|
||||
}
|
||||
|
||||
// ── State ──
|
||||
|
||||
@@ -369,22 +376,6 @@ function openPopover(id) {
|
||||
const r = state.redactions[id];
|
||||
if (!r || !r.analysis) return;
|
||||
|
||||
const a = r.analysis;
|
||||
|
||||
// Context line
|
||||
const segs = a.segments;
|
||||
const leftText = segs.length > 0 ? segs[0].text : "";
|
||||
const rightText = segs.length > 1 ? segs[1].text : "";
|
||||
popoverContext.innerHTML = `<span class="ctx-left">${escapeHtml(leftText)}</span><span class="ctx-gap">\u2588\u2588\u2588</span><span class="ctx-right">${escapeHtml(rightText)}</span>`;
|
||||
|
||||
// Font info
|
||||
const font = a.font;
|
||||
popoverFontInfo.innerHTML = `
|
||||
<span>${escapeHtml(font.name)} ${font.size}px</span>
|
||||
<span class="font-score">score: ${font.score.toFixed(1)}</span>
|
||||
<span class="gap-width">gap: ${Math.round(a.gap.w)}px</span>
|
||||
`;
|
||||
|
||||
// Pre-fill prefix/suffix from context
|
||||
solveFilterPrefix.value = "";
|
||||
solveFilterSuffix.value = "";
|
||||
@@ -1319,7 +1310,7 @@ function showAssocDetail(assocMatches, anchorEl) {
|
||||
let drawDrag = null;
|
||||
|
||||
canvas.addEventListener("mousedown", (e) => {
|
||||
if (!e.shiftKey || e.button !== 0) return;
|
||||
if (!e.shiftKey || e.ctrlKey || e.button !== 0) return;
|
||||
|
||||
const rect = rightPanel.getBoundingClientRect();
|
||||
const sx = e.clientX - rect.left;
|
||||
@@ -1389,6 +1380,50 @@ window.addEventListener("mouseup", (e) => {
|
||||
activateRedaction(id);
|
||||
});
|
||||
|
||||
// ── Ctrl+drag offset / Ctrl+Shift+drag gap width ──
|
||||
|
||||
let ctrlDrag = null;
|
||||
|
||||
canvas.addEventListener("mousedown", (e) => {
|
||||
if (!e.ctrlKey || e.button !== 0) return;
|
||||
const r = state.redactions[state.activeRedaction];
|
||||
if (!r?.overrides) return;
|
||||
|
||||
ctrlDrag = {
|
||||
startX: e.clientX,
|
||||
startY: e.clientY,
|
||||
startOffsetX: r.overrides.offsetX,
|
||||
startOffsetY: r.overrides.offsetY,
|
||||
startGapWidth: r.overrides.gapWidth,
|
||||
widthMode: e.shiftKey,
|
||||
};
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}, { capture: true });
|
||||
|
||||
window.addEventListener("mousemove", (e) => {
|
||||
if (!ctrlDrag) return;
|
||||
const r = state.redactions[state.activeRedaction];
|
||||
if (!r?.overrides) return;
|
||||
|
||||
const dx = (e.clientX - ctrlDrag.startX) / state.zoom;
|
||||
const dy = (e.clientY - ctrlDrag.startY) / state.zoom;
|
||||
|
||||
if (ctrlDrag.widthMode) {
|
||||
r.overrides.gapWidth = Math.max(1, ctrlDrag.startGapWidth + dx);
|
||||
gapValue.textContent = Math.round(r.overrides.gapWidth);
|
||||
} else {
|
||||
r.overrides.offsetX = ctrlDrag.startOffsetX + dx;
|
||||
r.overrides.offsetY = ctrlDrag.startOffsetY + dy;
|
||||
updatePosDisplay();
|
||||
}
|
||||
renderCanvas();
|
||||
});
|
||||
|
||||
window.addEventListener("mouseup", () => {
|
||||
if (ctrlDrag) ctrlDrag = null;
|
||||
});
|
||||
|
||||
// ── Utility ──
|
||||
|
||||
function escapeHtml(text) {
|
||||
|
||||
@@ -78,8 +78,6 @@
|
||||
<span class="popover-title">Analyze Redaction</span>
|
||||
<button id="popover-close" class="popover-close-btn">X</button>
|
||||
</div>
|
||||
<div id="popover-context" class="popover-context"></div>
|
||||
<div id="popover-font-info" class="popover-font-info"></div>
|
||||
<div class="solve-controls">
|
||||
<label>
|
||||
Charset
|
||||
@@ -148,6 +146,7 @@
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<div id="toast-container"></div>
|
||||
<script src="/static/app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
+42
-50
@@ -235,7 +235,7 @@ main {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
bottom: 12px;
|
||||
bottom: 3rem;
|
||||
width: 320px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -244,7 +244,7 @@ main {
|
||||
border-radius: 8px;
|
||||
padding: 12px;
|
||||
z-index: 30;
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
@@ -276,54 +276,6 @@ main {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.popover-context {
|
||||
padding: 6px 8px;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border-radius: 4px;
|
||||
margin-bottom: 8px;
|
||||
font-family: monospace;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.4;
|
||||
white-space: nowrap;
|
||||
overflow-x: auto;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ctx-left {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.ctx-gap {
|
||||
color: #d32f2f;
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
.ctx-right {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.popover-font-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 4px 8px;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border-radius: 4px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 0.75rem;
|
||||
color: #aaa;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.font-score {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.gap-width {
|
||||
color: #42a5f5;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Solve controls (shared between old and new) */
|
||||
.solve-controls {
|
||||
display: flex;
|
||||
@@ -686,3 +638,43 @@ main {
|
||||
background: #b8860b;
|
||||
color: #ffe082;
|
||||
}
|
||||
|
||||
/* Toast notifications */
|
||||
#toast-container {
|
||||
position: fixed;
|
||||
bottom: 2rem;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.toast {
|
||||
background: rgba(30, 30, 50, 0.95);
|
||||
color: #e0e0e0;
|
||||
padding: 0.6rem 1.2rem;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #333;
|
||||
font-size: 0.85rem;
|
||||
backdrop-filter: blur(6px);
|
||||
animation: toast-in 0.2s ease-out, toast-out 0.3s ease-in 2.5s forwards;
|
||||
}
|
||||
|
||||
.toast.error {
|
||||
border-color: #d32f2f;
|
||||
color: #ef9a9a;
|
||||
}
|
||||
|
||||
@keyframes toast-in {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes toast-out {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
||||
Reference in new issue
Block a user