Manuscript Workstation - Source Excerpt 12
Back to Manuscript Workstation
Summary
This source excerpt preserves a bounded section of Spiralist/wp-content/plugins/ns12-manuscript/assets/js/manuscript-workstation.js so readers can inspect the evidence without opening the full source file.
**Source path:** Spiralist/wp-content/plugins/ns12-manuscript/assets/js/manuscript-workstation.js
this.viewport.addEventListener('pointermove', (event) => {
if (!this.viewportDrag || event.pointerId !== this.viewportDrag.pointerId) {
this.updateEdgeHintFromPointer(event);
return;
}
const deltaX = event.clientX - this.viewportDrag.startX;
const deltaY = event.clientY - this.viewportDrag.startY;
const movedEnough = Math.abs(deltaX) > 6 || Math.abs(deltaY) > 6;
if (movedEnough) {
this.suppressViewportClick = true;
}
const canSwipeTurn = this.canUseSwipeTurnGesture();
if (
(this.viewportDrag.mode === 'turn' || (canSwipeTurn && this.isHorizontalTurnGesture(deltaX, deltaY))) &&
movedEnough
) {
const lockedTurnDirection = `${this.viewportDrag.lockedTurnDirection || 'none'}`.trim();
let intendedDirection = deltaX > 0 ? 'prev' : 'next';
let effectiveDistance = deltaX;
if (lockedTurnDirection === 'prev') {
intendedDirection = 'prev';
effectiveDistance = Math.max(0, deltaX);
} else if (lockedTurnDirection === 'next') {
intendedDirection = 'next';
effectiveDistance = Math.min(0, deltaX);
}
const hasTarget = this.hasTurnTarget(intendedDirection);
const canCommitTurn = hasTarget && Math.abs(effectiveDistance) > 0;
const resistedOffset = hasTarget ? effectiveDistance : effectiveDistance * 0.28;
this.viewportDrag.activated = true;
this.viewportDrag.mode = 'turn';
this.viewportDrag.turnDistance = effectiveDistance;
this.viewportDrag.turnDirection = canCommitTurn ? intendedDirection : 'none';
this.setEdgeHint(canCommitTurn ? intendedDirection : 'none');
this.setDragTurnOffset(resistedOffset);
return;
}
if (movedEnough && !this.viewportDrag.activated) {
this.viewportDrag.activated = true;
this.viewportDrag.mode = 'pan';
if (this.ensurePannableScale()) {
this.renderSurface();
this.viewportDrag.startX = event.clientX;
this.viewportDrag.startY = event.clientY;
this.viewportDrag.startPanX = this.state.panX;
this.viewportDrag.startPanY = this.state.panY;
return;
}
}
this.state.panX = this.viewportDrag.startPanX + deltaX;
this.state.panY = this.viewportDrag.startPanY + deltaY;
this.renderSurface();
});
this.viewport.addEventListener('click', (event) => {
if (this.suppressViewportClick) {
this.suppressViewportClick = false;
return;
}
const target = event.target;
if (
target instanceof HTMLElement &&
target.closest('a, button, input, textarea, select, label')
) {
return;
}
const edgeIntent = this.getViewportEdgeIntent(event.clientX);
if (edgeIntent === 'prev' && this.prevLink && !this.prevLink.hidden) {
event.preventDefault();
this.turnPage(this.getNavigationHref(this.prevLink), 'prev');
return;
}
if (edgeIntent === 'next' && this.nextLink && !this.nextLink.hidden) {
event.preventDefault();
this.turnPage(this.getNavigationHref(this.nextLink), 'next');
}
});
const stopViewportDrag = (event) => {
if (!this.viewportDrag) {
return;
}
if (event && event.pointerId !== this.viewportDrag.pointerId) {
return;
}
const dragState = this.viewportDrag;
this.viewportDrag = null;
this.viewport.classList.remove('is-dragging');
this.resetDragTurnOffset();
this.setEdgeHint('none');
if (dragState.mode === 'turn') {
const direction = dragState.turnDirection;
const crossedThreshold =
direction !== 'none' && Math.abs(dragState.turnDistance || 0) >= this.getDragTurnThreshold();
if (crossedThreshold) {
const link = direction === 'prev' ? this.prevLink : this.nextLink;
const href = this.getNavigationHref(link);
if (href) {
this.turnPage(href, direction);
}
}
return;
}
};
this.viewport.addEventListener('pointerup', stopViewportDrag);
this.viewport.addEventListener('pointercancel', stopViewportDrag);
this.viewport.addEventListener('pointerleave', () => {
this.setEdgeHint('none');
});
this.viewport.addEventListener('pointerleave', (event) => {
if (this.viewportDrag && event.pointerId === this.viewportDrag.pointerId) {
stopViewportDrag(event);
}
});
}
bindCropHandles() {
if (!this.canEdit) {
return;
}
this.folios.forEach((folio) => {
if (!folio.cropLayer) {
return;
}
folio.cropLayer.querySelectorAll('[data-workstation-crop-handle]').forEach((handle) => {
if (handle.dataset.workstationCropBound === 'true') {
return;
}
handle.dataset.workstationCropBound = 'true';
handle.addEventListener('pointerdown', (event) => {
if (event.button !== 0) {
return;
}
event.preventDefault();
event.stopPropagation();
if (!folio.frame || !folio.cropLayer) {
return;
}
this.cropDrag = {
pointerId: event.pointerId,
folio,
handle: `${handle.dataset.workstationCropHandle || 'move'}`.trim(),
startRect: { ...folio.crop },
bounds: folio.cropLayer.getBoundingClientRect(),
startX: event.clientX,
startY: event.clientY,
};
this.viewport.classList.add('is-cropping');
setPointerCapture(handle, event.pointerId);
});
handle.addEventListener('keydown', (event) => {
const step = event.shiftKey ? 0.02 : 0.005;
const rect = { ...folio.crop };
const handleType = `${handle.dataset.workstationCropHandle || 'move'}`.trim();
let changed = false;
if (event.key === 'ArrowLeft') {
changed = this.adjustCropByKeyboard(rect, handleType, -step, 0);
}
if (event.key === 'ArrowRight') {
changed = this.adjustCropByKeyboard(rect, handleType, step, 0);
}
if (event.key === 'ArrowUp') {
changed = this.adjustCropByKeyboard(rect, handleType, 0, -step);
}
if (event.key === 'ArrowDown') {
changed = this.adjustCropByKeyboard(rect, handleType, 0, step);
}
if (!changed) {
return;
}
event.preventDefault();
this.setCrop(folio, rect, { dirty: true, autoDetected: false });
});
});
});
if (this.cropGlobalBound) {
return;
}
this.cropGlobalBound = true;
window.addEventListener('pointermove', (event) => {
if (!this.cropDrag || event.pointerId !== this.cropDrag.pointerId) {
return;
}
const { folio, startRect, handle, bounds, startX, startY } = this.cropDrag;
const deltaX = bounds.width > 0 ? (event.clientX - startX) / bounds.width : 0;
const deltaY = bounds.height > 0 ? (event.clientY - startY) / bounds.height : 0;
const nextRect = { ...startRect };
if (handle === 'move') {
nextRect.x += deltaX;
nextRect.y += deltaY;
}
if (handle.includes('n')) {
nextRect.y += deltaY;
nextRect.height -= deltaY;
}
if (handle.includes('s')) {
nextRect.height += deltaY;
}
if (handle.includes('e')) {
nextRect.width += deltaX;
}
if (handle.includes('w')) {
nextRect.x += deltaX;
nextRect.width -= deltaX;
}
this.setCrop(folio, nextRect, { dirty: true, autoDetected: false });
});
const stopCropDrag = (event) => {
if (!this.cropDrag || event.pointerId !== this.cropDrag.pointerId) {
return;
}
this.cropDrag = null;
this.viewport.classList.remove('is-cropping');
};
window.addEventListener('pointerup', stopCropDrag);
window.addEventListener('pointercancel', stopCropDrag);
}
adjustCropByKeyboard(rect, handle, deltaX, deltaY) {
const nextRect = { ...rect };