Chantier F - Typage Python - ruff S101 déplacé en per-file-ignores (pyproject.toml) - pyrightconfig.json : mode standard confirmé Chantier G - Assertions & bare-except - 16 blocs except documentés dans config.py - --cov-branch activé, seuil coverage 60→68% Chantier H - Cache settings - Stratégie documentée dans settings.py - _resolve_index_dir() logguée dans config.py Chantier I - Split gros fichiers JS - client.js 1509L → client.js 406L + clientAuth.js + clientOps.js + fetchUtils.js - toolbar.js 927L → toolbar.js 458L + toolbarActions.js + toolbarControls.js - FloatingViewer.js → FloatingViewer.impl.js + floatingViewer*(8 modules) - Viewer_impl.js → ViewerRuntime.js + viewerShell.js + viewerThemeStyles.js + viewerOverlayDismiss.js - model3dRenderer.js → model3dRenderer.impl.js + model3dCore.js + model3dLoaders.js + model3dSupport.js - dialogs.js → dialogs.js + DialogTemplates.js Chantier J - Tests frontend (Phases 1-2) - use_drag_drop.vitest.mjs : 5 tests - use_grid_state.vitest.mjs : 18 tests - security_client_headers, security_deep_merge, security_runtime_state, runtime_store, dialog_templates - Total: 80 fichiers test / 350 tests vitest Chantier K - Pinia migration - runtimeState.js entièrement migré vers Pinia - getOptionalRuntimeStore.js + runtimeEnrichmentState.js ajoutés Chantier L - Durcissement sécurité - tests/security/test_rate_limit.py : 8 tests rate-limit (state machine complète) - clientAuth.js : docstring sessionStorage security boundary - three.js 0.183.2 audité : 0 CVE (Snyk) Chantier M - Simplification config - deps.py, tool_detect.py, utils.py nettoyés docs: PLAN_REFACTO_V2_LONG_TERME.md ajouté et clôturé .gitignore: coverage/ ajouté
231 lines
8.2 KiB
JavaScript
231 lines
8.2 KiB
JavaScript
import { MFV_RESIZE_EDGE_HIT_PX } from "./floatingViewerConstants.js";
|
|
|
|
export function getFloatingViewerResizeCursor(dir) {
|
|
const map = {
|
|
n: "ns-resize",
|
|
s: "ns-resize",
|
|
e: "ew-resize",
|
|
w: "ew-resize",
|
|
ne: "nesw-resize",
|
|
nw: "nwse-resize",
|
|
se: "nwse-resize",
|
|
sw: "nesw-resize",
|
|
};
|
|
return map[dir] || "";
|
|
}
|
|
|
|
export function getFloatingViewerResizeDirectionFromPoint(clientX, clientY, rect) {
|
|
if (!rect) return "";
|
|
const nearLeft = clientX <= rect.left + MFV_RESIZE_EDGE_HIT_PX;
|
|
const nearRight = clientX >= rect.right - MFV_RESIZE_EDGE_HIT_PX;
|
|
const nearTop = clientY <= rect.top + MFV_RESIZE_EDGE_HIT_PX;
|
|
const nearBottom = clientY >= rect.bottom - MFV_RESIZE_EDGE_HIT_PX;
|
|
if (nearTop && nearLeft) return "nw";
|
|
if (nearTop && nearRight) return "ne";
|
|
if (nearBottom && nearLeft) return "sw";
|
|
if (nearBottom && nearRight) return "se";
|
|
if (nearTop) return "n";
|
|
if (nearBottom) return "s";
|
|
if (nearLeft) return "w";
|
|
if (nearRight) return "e";
|
|
return "";
|
|
}
|
|
|
|
export function stopFloatingViewerEdgeResize(viewer) {
|
|
if (!viewer.element) return;
|
|
if (viewer._resizeState?.pointerId != null) {
|
|
try {
|
|
viewer.element.releasePointerCapture(viewer._resizeState.pointerId);
|
|
} catch (e) {
|
|
console.debug?.(e);
|
|
}
|
|
}
|
|
viewer._resizeState = null;
|
|
viewer.element.classList.remove("mjr-mfv--resizing");
|
|
viewer.element.style.cursor = "";
|
|
}
|
|
|
|
export function bindFloatingViewerPanelInteractions(viewer) {
|
|
if (!viewer.element) return;
|
|
viewer._stopEdgeResize();
|
|
try {
|
|
viewer._panelAC?.abort();
|
|
} catch (e) {
|
|
console.debug?.(e);
|
|
}
|
|
viewer._panelAC = new AbortController();
|
|
viewer._initEdgeResize(viewer.element);
|
|
viewer._initDrag(viewer.element.querySelector(".mjr-mfv-header"));
|
|
}
|
|
|
|
export function initFloatingViewerEdgeResize(viewer, el) {
|
|
if (!el) return;
|
|
const resolveDir = (e) => {
|
|
if (!viewer.element || viewer._isPopped) return "";
|
|
const rect = viewer.element.getBoundingClientRect();
|
|
return viewer._getResizeDirectionFromPoint(e.clientX, e.clientY, rect);
|
|
};
|
|
const signal = viewer._panelAC?.signal;
|
|
|
|
const onPointerDown = (e) => {
|
|
if (e.button !== 0 || !viewer.element || viewer._isPopped) return;
|
|
const dir = resolveDir(e);
|
|
if (!dir) return;
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
const rect = viewer.element.getBoundingClientRect();
|
|
const style = window.getComputedStyle(viewer.element);
|
|
const minWidth = Math.max(120, Number.parseFloat(style.minWidth) || 0);
|
|
const minHeight = Math.max(100, Number.parseFloat(style.minHeight) || 0);
|
|
viewer._resizeState = {
|
|
pointerId: e.pointerId,
|
|
dir,
|
|
startX: e.clientX,
|
|
startY: e.clientY,
|
|
startLeft: rect.left,
|
|
startTop: rect.top,
|
|
startWidth: rect.width,
|
|
startHeight: rect.height,
|
|
minWidth,
|
|
minHeight,
|
|
};
|
|
viewer.element.style.left = `${Math.round(rect.left)}px`;
|
|
viewer.element.style.top = `${Math.round(rect.top)}px`;
|
|
viewer.element.style.right = "auto";
|
|
viewer.element.style.bottom = "auto";
|
|
viewer.element.classList.add("mjr-mfv--resizing");
|
|
viewer.element.style.cursor = viewer._resizeCursorForDirection(dir);
|
|
try {
|
|
viewer.element.setPointerCapture(e.pointerId);
|
|
} catch (err) {
|
|
console.debug?.(err);
|
|
}
|
|
};
|
|
|
|
const onPointerMove = (e) => {
|
|
if (!viewer.element || viewer._isPopped) return;
|
|
const state = viewer._resizeState;
|
|
if (!state) {
|
|
const dir = resolveDir(e);
|
|
viewer.element.style.cursor = dir ? viewer._resizeCursorForDirection(dir) : "";
|
|
return;
|
|
}
|
|
if (state.pointerId !== e.pointerId) return;
|
|
|
|
const dx = e.clientX - state.startX;
|
|
const dy = e.clientY - state.startY;
|
|
let width = state.startWidth;
|
|
let height = state.startHeight;
|
|
let left = state.startLeft;
|
|
let top = state.startTop;
|
|
|
|
if (state.dir.includes("e")) width = state.startWidth + dx;
|
|
if (state.dir.includes("s")) height = state.startHeight + dy;
|
|
if (state.dir.includes("w")) {
|
|
width = state.startWidth - dx;
|
|
left = state.startLeft + dx;
|
|
}
|
|
if (state.dir.includes("n")) {
|
|
height = state.startHeight - dy;
|
|
top = state.startTop + dy;
|
|
}
|
|
|
|
if (width < state.minWidth) {
|
|
if (state.dir.includes("w")) left -= state.minWidth - width;
|
|
width = state.minWidth;
|
|
}
|
|
if (height < state.minHeight) {
|
|
if (state.dir.includes("n")) top -= state.minHeight - height;
|
|
height = state.minHeight;
|
|
}
|
|
|
|
width = Math.min(width, Math.max(state.minWidth, window.innerWidth));
|
|
height = Math.min(height, Math.max(state.minHeight, window.innerHeight));
|
|
left = Math.min(Math.max(0, left), Math.max(0, window.innerWidth - width));
|
|
top = Math.min(Math.max(0, top), Math.max(0, window.innerHeight - height));
|
|
|
|
viewer.element.style.width = `${Math.round(width)}px`;
|
|
viewer.element.style.height = `${Math.round(height)}px`;
|
|
viewer.element.style.left = `${Math.round(left)}px`;
|
|
viewer.element.style.top = `${Math.round(top)}px`;
|
|
viewer.element.style.right = "auto";
|
|
viewer.element.style.bottom = "auto";
|
|
};
|
|
|
|
const onPointerEnd = (e) => {
|
|
if (!viewer.element || !viewer._resizeState) return;
|
|
if (viewer._resizeState.pointerId !== e.pointerId) return;
|
|
const dir = resolveDir(e);
|
|
viewer._stopEdgeResize();
|
|
if (dir) viewer.element.style.cursor = viewer._resizeCursorForDirection(dir);
|
|
};
|
|
|
|
el.addEventListener("pointerdown", onPointerDown, { capture: true, signal });
|
|
el.addEventListener("pointermove", onPointerMove, { signal });
|
|
el.addEventListener("pointerup", onPointerEnd, { signal });
|
|
el.addEventListener("pointercancel", onPointerEnd, { signal });
|
|
el.addEventListener(
|
|
"pointerleave",
|
|
() => {
|
|
if (!viewer._resizeState && viewer.element) viewer.element.style.cursor = "";
|
|
},
|
|
{ signal },
|
|
);
|
|
}
|
|
|
|
export function initFloatingViewerDrag(viewer, handle) {
|
|
if (!handle) return;
|
|
const signal = viewer._panelAC?.signal;
|
|
let dragAC = null;
|
|
handle.addEventListener(
|
|
"pointerdown",
|
|
(e) => {
|
|
if (e.button !== 0) return;
|
|
if (e.target.closest("button")) return;
|
|
if (e.target.closest("select")) return;
|
|
if (viewer._isPopped || !viewer.element) return;
|
|
const edgeDir = viewer._getResizeDirectionFromPoint(
|
|
e.clientX,
|
|
e.clientY,
|
|
viewer.element.getBoundingClientRect(),
|
|
);
|
|
if (edgeDir) return;
|
|
e.preventDefault();
|
|
handle.setPointerCapture(e.pointerId);
|
|
try {
|
|
dragAC?.abort();
|
|
} catch {}
|
|
dragAC = new AbortController();
|
|
const dragSig = dragAC.signal;
|
|
|
|
const el = viewer.element;
|
|
const rect = el.getBoundingClientRect();
|
|
const offX = e.clientX - rect.left;
|
|
const offY = e.clientY - rect.top;
|
|
|
|
const onMove = (me) => {
|
|
const x = Math.min(
|
|
window.innerWidth - el.offsetWidth,
|
|
Math.max(0, me.clientX - offX),
|
|
);
|
|
const y = Math.min(
|
|
window.innerHeight - el.offsetHeight,
|
|
Math.max(0, me.clientY - offY),
|
|
);
|
|
el.style.left = `${x}px`;
|
|
el.style.top = `${y}px`;
|
|
el.style.right = "auto";
|
|
el.style.bottom = "auto";
|
|
};
|
|
const onUp = () => {
|
|
try {
|
|
dragAC?.abort();
|
|
} catch {}
|
|
};
|
|
handle.addEventListener("pointermove", onMove, { signal: dragSig });
|
|
handle.addEventListener("pointerup", onUp, { signal: dragSig });
|
|
},
|
|
signal ? { signal } : undefined,
|
|
);
|
|
}
|