Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 33 additions & 10 deletions photomap/frontend/static/javascript/curation.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,34 @@ const lowFreqIndices = new Set(); // < 70%
// Metadata Map for Persistent CSV Export (Index -> {filename, subfolder, frequency, count})
const globalMetadataMap = new Map();

// Grid-highlight refresh interval. Runs only while the curation panel is
// visible — previously this was a perma-setInterval(1000) started at module
// init time that woke up every second forever, even with the panel closed.
let _gridHighlightInterval = null;

function _startGridHighlightInterval() {
if (_gridHighlightInterval !== null) {
return;
}
_gridHighlightInterval = setInterval(() => {
const gridContainer = document.getElementById("gridViewContainer");
if (
(currentSelectionIndices.size > 0 || excludedIndices.size > 0) &&
gridContainer &&
gridContainer.style.display !== "none"
) {
applyGridHighlights();
}
}, 1000);
}

function _stopGridHighlightInterval() {
if (_gridHighlightInterval !== null) {
clearInterval(_gridHighlightInterval);
_gridHighlightInterval = null;
}
}

window.toggleCurationPanel = function () {
const panel = document.getElementById("curationPanel");
if (panel) {
Expand All @@ -36,9 +64,11 @@ window.toggleCurationPanel = function () {
backStack.markNextAsJump("curation");
slideState.navigateToIndex(index, false);
});
_startGridHighlightInterval();
} else {
// When panel is closed, restore default cluster selection behavior
setUmapClickCallback(null);
_stopGridHighlightInterval();
}

// Force update of current image marker (yellow dot) to show it
Expand Down Expand Up @@ -570,16 +600,9 @@ function setupEventListeners() {
};
}

setInterval(() => {
const gridContainer = document.getElementById("gridViewContainer");
if (
(currentSelectionIndices.size > 0 || excludedIndices.size > 0) &&
gridContainer &&
gridContainer.style.display !== "none"
) {
applyGridHighlights();
}
}, 1000);
// (The grid-highlight refresh interval is now driven by ``toggleCurationPanel``
// so it only runs while the panel is actually visible.)

// Listen for UMAP redraws (e.g. Cluster Strength change) to restore curation highlights
window.addEventListener("umapRedrawn", () => {
const panel = document.getElementById("curationPanel");
Expand Down
5 changes: 4 additions & 1 deletion photomap/frontend/static/javascript/grid-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,10 @@ class GridViewManager {
if (this.gridGeometryChanged(newGeometry)) {
const currentGlobalIndex = slideState.getCurrentSlide().globalIndex;

this.resetAllSlides();
// ``resetAllSlides`` is async — without awaiting, the next line's
// ``initializeGridSwiper`` would destroy the swiper while the reset
// was still mid-await, leading to flickers and stale slide DOM.
await this.resetAllSlides();
this.initializeGridSwiper();
this.setBatchLoading(true);
await this.loadBatch(currentGlobalIndex);
Expand Down
64 changes: 48 additions & 16 deletions photomap/frontend/static/javascript/weight-slider.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,25 +46,57 @@ export class WeightSlider {
this.setValueFromEvent(e);
});

// Drag to set value
this.bar.addEventListener("mousedown", (e) => {
this.isDragging = true;
this.setValueFromEvent(e);
document.body.style.userSelect = "none";
});
// Drag to set value. Document-level move/up listeners are added on the
// mousedown (or touchstart) that begins a drag and removed on release,
// instead of being installed perma-listening at render time — multiple
// slider instances used to leak one mousemove + one mouseup listener
// each onto window, and the slider was unusable on touch devices.
this.bar.addEventListener("mousedown", (e) => this._beginDrag(e, false));
this.bar.addEventListener(
"touchstart",
(e) => {
if (e.touches.length !== 1) {
return;
}
this._beginDrag(e.touches[0], true);
e.preventDefault();
},
{ passive: false }
);
}

window.addEventListener("mousemove", (e) => {
if (this.isDragging) {
this.setValueFromEvent(e);
}
});
_beginDrag(event, isTouch) {
this.isDragging = true;
this.setValueFromEvent(event);
document.body.style.userSelect = "none";

window.addEventListener("mouseup", () => {
if (this.isDragging) {
this.isDragging = false;
document.body.style.userSelect = "";
const onMove = (e) => {
if (!this.isDragging) {
return;
}
});
const point = isTouch && e.touches ? e.touches[0] : e;
this.setValueFromEvent(point);
if (isTouch) {
e.preventDefault();
}
};

const onEnd = () => {
this.isDragging = false;
document.body.style.userSelect = "";
document.removeEventListener(isTouch ? "touchmove" : "mousemove", onMove);
document.removeEventListener(isTouch ? "touchend" : "mouseup", onEnd);
document.removeEventListener("touchcancel", onEnd);
};

if (isTouch) {
document.addEventListener("touchmove", onMove, { passive: false });
document.addEventListener("touchend", onEnd);
document.addEventListener("touchcancel", onEnd);
} else {
document.addEventListener("mousemove", onMove);
document.addEventListener("mouseup", onEnd);
}
}

setValueFromEvent(e) {
Expand Down
14 changes: 9 additions & 5 deletions tests/frontend/weight-slider.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -227,13 +227,14 @@ describe("weight-slider.js", () => {
width: 100,
}));

// Start drag
// Start drag — listeners are now attached on document (and only while
// dragging) instead of perma-bound to window.
const mousedownEvent = new MouseEvent("mousedown", { clientX: 50 });
bar.dispatchEvent(mousedownEvent);

// End drag
const mouseupEvent = new MouseEvent("mouseup");
window.dispatchEvent(mouseupEvent);
document.dispatchEvent(mouseupEvent);

expect(slider.isDragging).toBe(false);
});
Expand All @@ -256,7 +257,7 @@ describe("weight-slider.js", () => {

// Move mouse
const mousemoveEvent = new MouseEvent("mousemove", { clientX: 70 });
window.dispatchEvent(mousemoveEvent);
document.dispatchEvent(mousemoveEvent);

expect(slider.value).toBe(0.7);
expect(onChangeMock).toHaveBeenCalledWith(0.7);
Expand All @@ -270,9 +271,12 @@ describe("weight-slider.js", () => {
width: 100,
}));

// Move mouse without starting drag
// Move mouse without starting drag — the new code attaches the
// mousemove listener only on mousedown, so this dispatch is a no-op
// (preserves the test's intent that ``value`` stays at its initial
// setting).
const mousemoveEvent = new MouseEvent("mousemove", { clientX: 70 });
window.dispatchEvent(mousemoveEvent);
document.dispatchEvent(mousemoveEvent);

expect(slider.value).toBe(0.5);
expect(onChangeMock).not.toHaveBeenCalled();
Expand Down
Loading