Улучшил работу, мелкие фиксы
This commit is contained in:
+33
-29
@@ -77,9 +77,9 @@ interface Box {
|
|||||||
const SNAP_THRESHOLD = 6;
|
const SNAP_THRESHOLD = 6;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compares the dragged box's edges/center against every other box on the same
|
* Compares the dragged box's center against every other box's center on the
|
||||||
* level and, for the first close-enough match on each axis, snaps to it and
|
* same level and, for the first close-enough match on each axis, snaps to it
|
||||||
* records a guide line to draw across the canvas.
|
* and records a guide line to draw across the canvas.
|
||||||
*/
|
*/
|
||||||
function computeSnapAndGuides(dragged: Box, others: Box[]): { x: number; y: number; guides: AlignmentGuide[] } {
|
function computeSnapAndGuides(dragged: Box, others: Box[]): { x: number; y: number; guides: AlignmentGuide[] } {
|
||||||
const guides: AlignmentGuide[] = [];
|
const guides: AlignmentGuide[] = [];
|
||||||
@@ -87,36 +87,24 @@ function computeSnapAndGuides(dragged: Box, others: Box[]): { x: number; y: numb
|
|||||||
let snappedY = dragged.y;
|
let snappedY = dragged.y;
|
||||||
let matchedX = false;
|
let matchedX = false;
|
||||||
let matchedY = false;
|
let matchedY = false;
|
||||||
|
const draggedCenterX = dragged.x + dragged.width / 2;
|
||||||
|
const draggedCenterY = dragged.y + dragged.height / 2;
|
||||||
|
|
||||||
for (const other of others) {
|
for (const other of others) {
|
||||||
if (!matchedX) {
|
if (!matchedX) {
|
||||||
const xCandidates: [number, number][] = [
|
const otherCenterX = other.x + other.width / 2;
|
||||||
[dragged.x, other.x],
|
if (Math.abs(draggedCenterX - otherCenterX) <= SNAP_THRESHOLD) {
|
||||||
[dragged.x + dragged.width, other.x + other.width],
|
snappedX = dragged.x + (otherCenterX - draggedCenterX);
|
||||||
[dragged.x + dragged.width / 2, other.x + other.width / 2],
|
guides.push({ axis: 'x', position: otherCenterX });
|
||||||
];
|
|
||||||
for (const [dValue, oValue] of xCandidates) {
|
|
||||||
if (Math.abs(dValue - oValue) <= SNAP_THRESHOLD) {
|
|
||||||
snappedX = dragged.x + (oValue - dValue);
|
|
||||||
guides.push({ axis: 'x', position: oValue });
|
|
||||||
matchedX = true;
|
matchedX = true;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!matchedY) {
|
if (!matchedY) {
|
||||||
const yCandidates: [number, number][] = [
|
const otherCenterY = other.y + other.height / 2;
|
||||||
[dragged.y, other.y],
|
if (Math.abs(draggedCenterY - otherCenterY) <= SNAP_THRESHOLD) {
|
||||||
[dragged.y + dragged.height, other.y + other.height],
|
snappedY = dragged.y + (otherCenterY - draggedCenterY);
|
||||||
[dragged.y + dragged.height / 2, other.y + other.height / 2],
|
guides.push({ axis: 'y', position: otherCenterY });
|
||||||
];
|
|
||||||
for (const [dValue, oValue] of yCandidates) {
|
|
||||||
if (Math.abs(dValue - oValue) <= SNAP_THRESHOLD) {
|
|
||||||
snappedY = dragged.y + (oValue - dValue);
|
|
||||||
guides.push({ axis: 'y', position: oValue });
|
|
||||||
matchedY = true;
|
matchedY = true;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -307,8 +295,21 @@ function DiagramCanvas({ initialValue, onChange }: DiagramEditorProps) {
|
|||||||
);
|
);
|
||||||
if (isHistoryWorthy) pushHistory(nodes, edges);
|
if (isHistoryWorthy) pushHistory(nodes, edges);
|
||||||
const next = applyNodeChanges(adjustedChanges, nodes);
|
const next = applyNodeChanges(adjustedChanges, nodes);
|
||||||
|
|
||||||
|
// React Flow's own keyboard delete dispatches node and edge removal as
|
||||||
|
// separate change events that race through the same debounced
|
||||||
|
// emitChange — filtering edges here too (redundantly with onEdgesChange)
|
||||||
|
// guarantees a removed node never leaves a dangling edge behind.
|
||||||
|
const removedIds = new Set(
|
||||||
|
filteredChanges.filter((change) => change.type === 'remove').map((change) => change.id),
|
||||||
|
);
|
||||||
|
const nextEdges = removedIds.size
|
||||||
|
? edges.filter((edge) => !removedIds.has(edge.source) && !removedIds.has(edge.target))
|
||||||
|
: edges;
|
||||||
|
|
||||||
setNodes(next);
|
setNodes(next);
|
||||||
emitChange(next, edges);
|
if (nextEdges !== edges) setEdges(nextEdges);
|
||||||
|
emitChange(next, nextEdges);
|
||||||
},
|
},
|
||||||
[nodes, edges, emitChange, pushHistory, alignmentGuides],
|
[nodes, edges, emitChange, pushHistory, alignmentGuides],
|
||||||
);
|
);
|
||||||
@@ -327,7 +328,10 @@ function DiagramCanvas({ initialValue, onChange }: DiagramEditorProps) {
|
|||||||
const onConnect: OnConnect = useCallback(
|
const onConnect: OnConnect = useCallback(
|
||||||
(connection) => {
|
(connection) => {
|
||||||
pushHistory(nodes, edges);
|
pushHistory(nodes, edges);
|
||||||
const next = addEdge({ ...connection, type: DEFAULT_EDGE_TYPE, markerEnd: DEFAULT_MARKER_END }, edges);
|
const next = addEdge(
|
||||||
|
{ ...connection, id: crypto.randomUUID(), type: DEFAULT_EDGE_TYPE, markerEnd: DEFAULT_MARKER_END },
|
||||||
|
edges,
|
||||||
|
);
|
||||||
setEdges(next);
|
setEdges(next);
|
||||||
emitChange(nodes, next);
|
emitChange(nodes, next);
|
||||||
},
|
},
|
||||||
@@ -397,7 +401,7 @@ function DiagramCanvas({ initialValue, onChange }: DiagramEditorProps) {
|
|||||||
const addNode = useCallback(
|
const addNode = useCallback(
|
||||||
(kind: NodeKind) => {
|
(kind: NodeKind) => {
|
||||||
nodeCounter.current += 1;
|
nodeCounter.current += 1;
|
||||||
const id = `${kind}-${nodeCounter.current}`;
|
const id = crypto.randomUUID();
|
||||||
const center = getViewportCenter();
|
const center = getViewportCenter();
|
||||||
const fanOut = nodeCounter.current;
|
const fanOut = nodeCounter.current;
|
||||||
const size = DEFAULT_NODE_SIZE[kind];
|
const size = DEFAULT_NODE_SIZE[kind];
|
||||||
@@ -431,7 +435,7 @@ function DiagramCanvas({ initialValue, onChange }: DiagramEditorProps) {
|
|||||||
const source = clipboardRef.current;
|
const source = clipboardRef.current;
|
||||||
if (!source) return;
|
if (!source) return;
|
||||||
nodeCounter.current += 1;
|
nodeCounter.current += 1;
|
||||||
const newId = `${source.type}-${nodeCounter.current}`;
|
const newId = crypto.randomUUID();
|
||||||
const { parentId: _sourceParentId, selected: _sourceSelected, ...restSource } = source;
|
const { parentId: _sourceParentId, selected: _sourceSelected, ...restSource } = source;
|
||||||
const newNode: Node = {
|
const newNode: Node = {
|
||||||
...restSource,
|
...restSource,
|
||||||
|
|||||||
+10
-3
@@ -1,11 +1,18 @@
|
|||||||
import { defineConfig } from 'vite'
|
import { defineConfig, loadEnv } from 'vite'
|
||||||
import react from '@vitejs/plugin-react'
|
import react from '@vitejs/plugin-react'
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig(({ mode }) => {
|
||||||
|
// VITE_BASE_PATH is read here (config/Node context), so it needs an explicit
|
||||||
|
// loadEnv call — unlike VITE_BITRIX_ORIGIN, which Vite injects into
|
||||||
|
// import.meta.env automatically because it's only read from app code.
|
||||||
|
const env = loadEnv(mode, process.cwd(), '')
|
||||||
|
|
||||||
|
return {
|
||||||
// Needed when the build is served from a subpath (e.g. local/diagram-app/
|
// Needed when the build is served from a subpath (e.g. local/diagram-app/
|
||||||
// inside the Bitrix docroot) instead of domain root — see
|
// inside the Bitrix docroot) instead of domain root — see
|
||||||
// docs/bitrix-integration.md §7.
|
// docs/bitrix-integration.md §7.
|
||||||
base: process.env.VITE_BASE_PATH || '/',
|
base: env.VITE_BASE_PATH || '/',
|
||||||
plugins: [react()],
|
plugins: [react()],
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user