mirror of
https://github.com/wanderer-industries/wanderer
synced 2025-12-10 09:45:43 +00:00
fix(Map): First prototype of windows
This commit is contained in:
@@ -19,7 +19,7 @@ import classes from './Map.module.scss';
|
|||||||
import './styles/neon-theme.scss';
|
import './styles/neon-theme.scss';
|
||||||
import './styles/eve-common.scss';
|
import './styles/eve-common.scss';
|
||||||
import { MapProvider, useMapState } from './MapProvider';
|
import { MapProvider, useMapState } from './MapProvider';
|
||||||
import { useNodesState, useEdgesState, useMapHandlers, useUpdateNodes } from './hooks';
|
import { useEdgesState, useMapHandlers, useNodesState, useUpdateNodes } from './hooks';
|
||||||
import { MapHandlers, OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts';
|
import { MapHandlers, OutCommand, OutCommandHandler } from '@/hooks/Mapper/types/mapHandlers.ts';
|
||||||
import {
|
import {
|
||||||
ContextMenuConnection,
|
ContextMenuConnection,
|
||||||
@@ -243,6 +243,10 @@ const MapComp = ({
|
|||||||
onConnectStart={() => update({ isConnecting: true })}
|
onConnectStart={() => update({ isConnecting: true })}
|
||||||
onConnectEnd={() => update({ isConnecting: false })}
|
onConnectEnd={() => update({ isConnecting: false })}
|
||||||
onNodeMouseEnter={(_, node) => update({ hoverNodeId: node.id })}
|
onNodeMouseEnter={(_, node) => update({ hoverNodeId: node.id })}
|
||||||
|
onPaneClick={event => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}}
|
||||||
// onKeyUp=
|
// onKeyUp=
|
||||||
onNodeMouseLeave={() => update({ hoverNodeId: null })}
|
onNodeMouseLeave={() => update({ hoverNodeId: null })}
|
||||||
onEdgeClick={(_, t) => {
|
onEdgeClick={(_, t) => {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
} from '@/hooks/Mapper/components/mapInterface/widgets';
|
} from '@/hooks/Mapper/components/mapInterface/widgets';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { SESSION_KEY } from '@/hooks/Mapper/constants.ts';
|
import { SESSION_KEY } from '@/hooks/Mapper/constants.ts';
|
||||||
|
import { WindowManager, WindowProps } from '@/hooks/Mapper/components/ui-kit/WindowManager';
|
||||||
// import { debounce } from 'lodash/debounce';
|
// import { debounce } from 'lodash/debounce';
|
||||||
|
|
||||||
const DEFAULT_WINDOWS = [
|
const DEFAULT_WINDOWS = [
|
||||||
@@ -63,16 +64,49 @@ const restoreWindowsFromLS = (): WidgetGridItem[] => {
|
|||||||
return out;
|
return out;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MapInterface = () => {
|
const DEFAULT: WindowProps[] = [
|
||||||
const [items, setItems] = useState<WidgetGridItem[]>(restoreWindowsFromLS);
|
{
|
||||||
|
id: 'info',
|
||||||
|
position: { x: 10, y: 10 },
|
||||||
|
size: { width: 250, height: 200 },
|
||||||
|
zIndex: 0,
|
||||||
|
content: () => <SystemInfo />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'signatures',
|
||||||
|
position: { x: 10, y: 220 },
|
||||||
|
size: { width: 250, height: 300 },
|
||||||
|
zIndex: 0,
|
||||||
|
content: () => <SystemSignatures />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'local',
|
||||||
|
position: { x: 270, y: 10 },
|
||||||
|
size: { width: 250, height: 510 },
|
||||||
|
zIndex: 0,
|
||||||
|
content: () => <LocalCharacters />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'routes',
|
||||||
|
position: { x: 10, y: 530 },
|
||||||
|
size: { width: 510, height: 200 },
|
||||||
|
zIndex: 0,
|
||||||
|
content: () => <RoutesWidget />,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
export const MapInterface = () => {
|
||||||
<WidgetsGrid
|
return <WindowManager windows={DEFAULT} dragSelector=".react-grid-dragHandleExample" />;
|
||||||
items={items}
|
|
||||||
onChange={x => {
|
// const [items, setItems] = useState<WidgetGridItem[]>(restoreWindowsFromLS);
|
||||||
saveWindowsToLS(x);
|
//
|
||||||
setItems(x);
|
// return (
|
||||||
}}
|
// <WidgetsGrid
|
||||||
/>
|
// items={items}
|
||||||
);
|
// onChange={x => {
|
||||||
|
// saveWindowsToLS(x);
|
||||||
|
// setItems(x);
|
||||||
|
// }}
|
||||||
|
// />
|
||||||
|
// );
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
.windowContainer {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.window {
|
||||||
|
position: absolute;
|
||||||
|
//background: #fff;
|
||||||
|
//border: 1px solid #000;
|
||||||
|
//box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
pointer-events: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resizeHandle {
|
||||||
|
position: absolute;
|
||||||
|
//background: rgba(0, 0, 0, 0.2);
|
||||||
|
width: 15px;
|
||||||
|
height: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topRight,
|
||||||
|
.bottomLeft {
|
||||||
|
cursor: nesw-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topLeft,
|
||||||
|
.bottomRight {
|
||||||
|
cursor: nwse-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topLeft {
|
||||||
|
top: -7.5px;
|
||||||
|
left: -7.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topRight {
|
||||||
|
top: -7.5px;
|
||||||
|
right: -7.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottomLeft {
|
||||||
|
bottom: -7.5px;
|
||||||
|
left: -7.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottomRight {
|
||||||
|
bottom: -7.5px;
|
||||||
|
right: -7.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top {
|
||||||
|
top: -5px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 10px;
|
||||||
|
cursor: ns-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom {
|
||||||
|
bottom: -5px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 10px;
|
||||||
|
cursor: ns-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: -5px;
|
||||||
|
width: 10px;
|
||||||
|
cursor: ew-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: -5px;
|
||||||
|
width: 10px;
|
||||||
|
cursor: ew-resize;
|
||||||
|
}
|
||||||
@@ -0,0 +1,401 @@
|
|||||||
|
import React, { useState, useRef, useEffect, useMemo } from 'react';
|
||||||
|
import styles from './WindowManager.module.scss';
|
||||||
|
|
||||||
|
const MIN_WINDOW_SIZE = 100;
|
||||||
|
const SNAP_THRESHOLD = 10;
|
||||||
|
const SNAP_GAP = 10;
|
||||||
|
|
||||||
|
export enum ActionType {
|
||||||
|
Drag = 'drag',
|
||||||
|
Resize = 'resize',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DefaultWindowState = {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 0,
|
||||||
|
height: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type WindowProps = {
|
||||||
|
id: number | string;
|
||||||
|
content: (w: WindowProps) => React.ReactNode;
|
||||||
|
position: { x: number; y: number };
|
||||||
|
size: { width: number; height: number };
|
||||||
|
zIndex: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
function getWindowsBySides(windows: WindowProps[], containerWidth: number, containerHeight: number) {
|
||||||
|
const centerX = containerWidth / 2;
|
||||||
|
const centerY = containerHeight / 2;
|
||||||
|
|
||||||
|
const top = windows.filter(window => window.position.y + window.size.height / 2 < centerY);
|
||||||
|
const bottom = windows.filter(window => window.position.y + window.size.height / 2 >= centerY);
|
||||||
|
const left = windows.filter(window => window.position.x + window.size.width / 2 < centerX);
|
||||||
|
const right = windows.filter(window => window.position.x + window.size.width / 2 >= centerX);
|
||||||
|
|
||||||
|
return { top, bottom, left, right };
|
||||||
|
}
|
||||||
|
|
||||||
|
export type WindowWrapperProps = {
|
||||||
|
onDrag: (e: React.MouseEvent, windowId: string | number) => void;
|
||||||
|
onResize: (e: React.MouseEvent, windowId: string | number, resizeDirection: string) => void;
|
||||||
|
} & WindowProps;
|
||||||
|
|
||||||
|
export const WindowWrapper = ({ onResize, onDrag, ...window }: WindowWrapperProps) => {
|
||||||
|
const handleMouseDownRoot = (e: React.MouseEvent) => {
|
||||||
|
onDrag(e, window.id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const { handleResizeTL, handleResizeTR, handleResizeBL, handleResizeBR } = useMemo(() => {
|
||||||
|
const handleResizeTL = (e: React.MouseEvent) => onResize(e, window.id, 'top left');
|
||||||
|
const handleResizeTR = (e: React.MouseEvent) => onResize(e, window.id, 'top right');
|
||||||
|
const handleResizeBL = (e: React.MouseEvent) => onResize(e, window.id, 'bottom left');
|
||||||
|
const handleResizeBR = (e: React.MouseEvent) => onResize(e, window.id, 'bottom right');
|
||||||
|
|
||||||
|
return {
|
||||||
|
handleResizeTL,
|
||||||
|
handleResizeTR,
|
||||||
|
handleResizeBL,
|
||||||
|
handleResizeBR,
|
||||||
|
};
|
||||||
|
}, [window]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={window.id}
|
||||||
|
className={`drag-handle ${styles.window}`}
|
||||||
|
style={{
|
||||||
|
width: window.size.width,
|
||||||
|
height: window.size.height,
|
||||||
|
top: window.position.y,
|
||||||
|
left: window.position.x,
|
||||||
|
zIndex: window.zIndex,
|
||||||
|
}}
|
||||||
|
onMouseDown={handleMouseDownRoot}
|
||||||
|
>
|
||||||
|
{window.content(window)}
|
||||||
|
<div className={styles.resizeHandle + ' ' + styles.topLeft} onMouseDown={handleResizeTL} />
|
||||||
|
<div className={styles.resizeHandle + ' ' + styles.topRight} onMouseDown={handleResizeTR} />
|
||||||
|
<div className={styles.resizeHandle + ' ' + styles.bottomLeft} onMouseDown={handleResizeBL} />
|
||||||
|
<div className={styles.resizeHandle + ' ' + styles.bottomRight} onMouseDown={handleResizeBR} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type WindowManagerProps = {
|
||||||
|
windows: WindowProps[];
|
||||||
|
dragSelector?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WindowManager: React.FC<WindowManagerProps> = ({ windows: initialWindows, dragSelector }) => {
|
||||||
|
const [windows, setWindows] = useState(
|
||||||
|
initialWindows.map((window, index) => ({
|
||||||
|
...window,
|
||||||
|
zIndex: index + 1,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const activeWindowIdRef = useRef<string | number | null>(null);
|
||||||
|
const actionTypeRef = useRef<ActionType | null>(null);
|
||||||
|
const resizeDirectionRef = useRef<string | null>(null);
|
||||||
|
const startMousePositionRef = useRef<{ x: number; y: number }>({ x: 0, y: 0 });
|
||||||
|
const startWindowStateRef = useRef<{ x: number; y: number; width: number; height: number }>(DefaultWindowState);
|
||||||
|
|
||||||
|
const ref = useRef({ windows });
|
||||||
|
ref.current = { windows };
|
||||||
|
|
||||||
|
const refPrevSize = useRef({ w: 0, h: 0 });
|
||||||
|
|
||||||
|
const handleMouseDown = (
|
||||||
|
e: React.MouseEvent,
|
||||||
|
windowId: string | number,
|
||||||
|
actionType: ActionType,
|
||||||
|
resizeDirection?: string,
|
||||||
|
) => {
|
||||||
|
if (dragSelector && actionType === ActionType.Drag && !(e.target as HTMLElement).closest(dragSelector)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
e.stopPropagation();
|
||||||
|
activeWindowIdRef.current = windowId;
|
||||||
|
actionTypeRef.current = actionType;
|
||||||
|
resizeDirectionRef.current = resizeDirection || null;
|
||||||
|
startMousePositionRef.current = { x: e.clientX, y: e.clientY };
|
||||||
|
const targetWindow = windows.find(win => win.id === windowId);
|
||||||
|
if (targetWindow) {
|
||||||
|
startWindowStateRef.current = {
|
||||||
|
x: targetWindow.position.x,
|
||||||
|
y: targetWindow.position.y,
|
||||||
|
width: targetWindow.size.width,
|
||||||
|
height: targetWindow.size.height,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bring window to front by updating zIndex
|
||||||
|
setWindows(prevWindows => {
|
||||||
|
const maxZIndex = Math.max(...prevWindows.map(w => w.zIndex));
|
||||||
|
return prevWindows.map(window => (window.id === windowId ? { ...window, zIndex: maxZIndex + 1 } : window));
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('mousemove', handleMouseMove);
|
||||||
|
window.addEventListener('mouseup', handleMouseUp);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseMove = (e: MouseEvent) => {
|
||||||
|
if (activeWindowIdRef.current !== null && actionTypeRef.current) {
|
||||||
|
const deltaX = e.clientX - startMousePositionRef.current.x;
|
||||||
|
const deltaY = e.clientY - startMousePositionRef.current.y;
|
||||||
|
const container = containerRef.current;
|
||||||
|
|
||||||
|
setWindows(prevWindows =>
|
||||||
|
prevWindows.map(window => {
|
||||||
|
if (window.id === activeWindowIdRef.current) {
|
||||||
|
let newX = startWindowStateRef.current.x;
|
||||||
|
let newY = startWindowStateRef.current.y;
|
||||||
|
let newWidth = startWindowStateRef.current.width;
|
||||||
|
let newHeight = startWindowStateRef.current.height;
|
||||||
|
|
||||||
|
if (actionTypeRef.current === ActionType.Drag) {
|
||||||
|
newX += deltaX;
|
||||||
|
newY += deltaY;
|
||||||
|
|
||||||
|
// Ensure the window stays within the container boundaries
|
||||||
|
if (container) {
|
||||||
|
newX = Math.max(SNAP_GAP, Math.min(container.clientWidth - window.size.width - SNAP_GAP, newX));
|
||||||
|
newY = Math.max(SNAP_GAP, Math.min(container.clientHeight - window.size.height - SNAP_GAP, newY));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Snap to other windows with or without gap
|
||||||
|
prevWindows.forEach(otherWindow => {
|
||||||
|
if (otherWindow.id === window.id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Snap vertically (top and bottom)
|
||||||
|
if (Math.abs(newY - otherWindow.position.y) < SNAP_THRESHOLD) {
|
||||||
|
newY = otherWindow.position.y; // Align top without gap
|
||||||
|
} else if (Math.abs(newY + window.size.height - otherWindow.position.y) < SNAP_THRESHOLD) {
|
||||||
|
newY = otherWindow.position.y - window.size.height - SNAP_GAP; // Bottom aligns to top
|
||||||
|
} else if (Math.abs(newY - (otherWindow.position.y + otherWindow.size.height)) < SNAP_THRESHOLD) {
|
||||||
|
newY = otherWindow.position.y + otherWindow.size.height + SNAP_GAP; // Align bottom without gap
|
||||||
|
} else if (
|
||||||
|
Math.abs(newY + window.size.height - (otherWindow.position.y + otherWindow.size.height)) <
|
||||||
|
SNAP_THRESHOLD
|
||||||
|
) {
|
||||||
|
newY = otherWindow.position.y + otherWindow.size.height - window.size.height; // Bottom aligns bottom
|
||||||
|
}
|
||||||
|
|
||||||
|
// Snap horizontally (left and right)
|
||||||
|
if (Math.abs(newX - otherWindow.position.x) < SNAP_THRESHOLD) {
|
||||||
|
newX = otherWindow.position.x; // Align left without gap
|
||||||
|
} else if (Math.abs(newX + window.size.width - otherWindow.position.x) < SNAP_THRESHOLD) {
|
||||||
|
newX = otherWindow.position.x - window.size.width - SNAP_GAP; // Right aligns to left
|
||||||
|
} else if (Math.abs(newX - (otherWindow.position.x + otherWindow.size.width)) < SNAP_THRESHOLD) {
|
||||||
|
newX = otherWindow.position.x + otherWindow.size.width + SNAP_GAP; // Align right without gap
|
||||||
|
} else if (
|
||||||
|
Math.abs(newX + window.size.width - (otherWindow.position.x + otherWindow.size.width)) <
|
||||||
|
SNAP_THRESHOLD
|
||||||
|
) {
|
||||||
|
newX = otherWindow.position.x + otherWindow.size.width - window.size.width; // Right aligns right
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (actionTypeRef.current === ActionType.Resize && resizeDirectionRef.current) {
|
||||||
|
if (resizeDirectionRef.current.includes('right')) {
|
||||||
|
newWidth = Math.max(MIN_WINDOW_SIZE, startWindowStateRef.current.width + deltaX);
|
||||||
|
|
||||||
|
// Снап для правой границы с отступом SNAP_THRESHOLD
|
||||||
|
prevWindows.forEach(otherWindow => {
|
||||||
|
if (otherWindow.id !== window.id) {
|
||||||
|
// Правая граница текущего окна к левой границе другого окна
|
||||||
|
const snapRightToLeft =
|
||||||
|
otherWindow.position.x - (startWindowStateRef.current.x + newWidth) - SNAP_THRESHOLD;
|
||||||
|
if (Math.abs(snapRightToLeft) < SNAP_THRESHOLD) {
|
||||||
|
newWidth = otherWindow.position.x - startWindowStateRef.current.x - SNAP_THRESHOLD;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Правая граница текущего окна к правой границе другого окна
|
||||||
|
const snapRightToRight =
|
||||||
|
otherWindow.position.x + otherWindow.size.width - (startWindowStateRef.current.x + newWidth);
|
||||||
|
if (Math.abs(snapRightToRight) < SNAP_THRESHOLD) {
|
||||||
|
newWidth = otherWindow.position.x + otherWindow.size.width - startWindowStateRef.current.x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resizeDirectionRef.current.includes('left')) {
|
||||||
|
newWidth = Math.max(MIN_WINDOW_SIZE, startWindowStateRef.current.width - deltaX);
|
||||||
|
newX = startWindowStateRef.current.x + (startWindowStateRef.current.width - newWidth);
|
||||||
|
|
||||||
|
// Снап для левой границы с отступом SNAP_THRESHOLD
|
||||||
|
prevWindows.forEach(otherWindow => {
|
||||||
|
if (otherWindow.id !== window.id) {
|
||||||
|
// Левая граница текущего окна к правой границе другого окна
|
||||||
|
const snapLeftToRight = newX - (otherWindow.position.x + otherWindow.size.width + SNAP_THRESHOLD);
|
||||||
|
if (Math.abs(snapLeftToRight) < SNAP_THRESHOLD) {
|
||||||
|
newX = otherWindow.position.x + otherWindow.size.width + SNAP_THRESHOLD;
|
||||||
|
newWidth = startWindowStateRef.current.width + startWindowStateRef.current.x - newX;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Левая граница текущего окна к левой границе другого окна
|
||||||
|
const snapLeftToLeft = newX - otherWindow.position.x;
|
||||||
|
if (Math.abs(snapLeftToLeft) < SNAP_THRESHOLD) {
|
||||||
|
newX = otherWindow.position.x;
|
||||||
|
newWidth = startWindowStateRef.current.width + startWindowStateRef.current.x - newX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resizeDirectionRef.current.includes('bottom')) {
|
||||||
|
newHeight = Math.max(MIN_WINDOW_SIZE, startWindowStateRef.current.height + deltaY);
|
||||||
|
|
||||||
|
// Снап для нижней границы с отступом SNAP_THRESHOLD
|
||||||
|
prevWindows.forEach(otherWindow => {
|
||||||
|
if (otherWindow.id !== window.id) {
|
||||||
|
// Нижняя граница текущего окна к верхней границе другого окна
|
||||||
|
const snapBottomToTop =
|
||||||
|
otherWindow.position.y - (startWindowStateRef.current.y + newHeight) - SNAP_THRESHOLD;
|
||||||
|
if (Math.abs(snapBottomToTop) < SNAP_THRESHOLD) {
|
||||||
|
newHeight = otherWindow.position.y - startWindowStateRef.current.y - SNAP_THRESHOLD;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Нижняя граница текущего окна к нижней границе другого окна
|
||||||
|
const snapBottomToBottom =
|
||||||
|
otherWindow.position.y + otherWindow.size.height - (startWindowStateRef.current.y + newHeight);
|
||||||
|
if (Math.abs(snapBottomToBottom) < SNAP_THRESHOLD) {
|
||||||
|
newHeight = otherWindow.position.y + otherWindow.size.height - startWindowStateRef.current.y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resizeDirectionRef.current.includes('top')) {
|
||||||
|
newHeight = Math.max(MIN_WINDOW_SIZE, startWindowStateRef.current.height - deltaY);
|
||||||
|
newY = startWindowStateRef.current.y + (startWindowStateRef.current.height - newHeight);
|
||||||
|
|
||||||
|
// Снап для верхней границы с отступом SNAP_THRESHOLD
|
||||||
|
prevWindows.forEach(otherWindow => {
|
||||||
|
if (otherWindow.id !== window.id) {
|
||||||
|
// Верхняя граница текущего окна к нижней границе другого окна
|
||||||
|
const snapTopToBottom = newY - (otherWindow.position.y + otherWindow.size.height + SNAP_THRESHOLD);
|
||||||
|
if (Math.abs(snapTopToBottom) < SNAP_THRESHOLD) {
|
||||||
|
newY = otherWindow.position.y + otherWindow.size.height + SNAP_THRESHOLD;
|
||||||
|
newHeight = startWindowStateRef.current.height + startWindowStateRef.current.y - newY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Верхняя граница текущего окна к верхней границе другого окна
|
||||||
|
const snapTopToTop = newY - otherWindow.position.y;
|
||||||
|
if (Math.abs(snapTopToTop) < SNAP_THRESHOLD) {
|
||||||
|
newY = otherWindow.position.y;
|
||||||
|
newHeight = startWindowStateRef.current.height + startWindowStateRef.current.y - newY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure the window stays within the container boundaries
|
||||||
|
if (container) {
|
||||||
|
newX = Math.max(0 + SNAP_GAP, Math.min(container.clientWidth - newWidth - SNAP_GAP, newX));
|
||||||
|
newY = Math.max(0 + SNAP_GAP, Math.min(container.clientHeight - newHeight - SNAP_GAP, newY));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...window,
|
||||||
|
position: { x: newX, y: newY },
|
||||||
|
size: { width: newWidth, height: newHeight },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return window;
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMouseUp = () => {
|
||||||
|
activeWindowIdRef.current = null;
|
||||||
|
actionTypeRef.current = null;
|
||||||
|
resizeDirectionRef.current = null;
|
||||||
|
|
||||||
|
window.removeEventListener('mousemove', handleMouseMove);
|
||||||
|
window.removeEventListener('mouseup', handleMouseUp);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Handle resize of the container and reposition windows
|
||||||
|
useEffect(() => {
|
||||||
|
if (containerRef.current) {
|
||||||
|
refPrevSize.current = { w: containerRef.current.clientWidth, h: containerRef.current.clientHeight };
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
const container = containerRef.current;
|
||||||
|
const { windows } = ref.current;
|
||||||
|
if (!container) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const deltaX = container.clientWidth - refPrevSize.current.w;
|
||||||
|
const deltaY = container.clientHeight - refPrevSize.current.h;
|
||||||
|
|
||||||
|
const { bottom, right } = getWindowsBySides(windows, refPrevSize.current.w, refPrevSize.current.h);
|
||||||
|
|
||||||
|
setWindows(w => {
|
||||||
|
return w.map(x => {
|
||||||
|
if (right.some(r => r.id === x.id)) {
|
||||||
|
return {
|
||||||
|
...x,
|
||||||
|
position: {
|
||||||
|
...x.position,
|
||||||
|
x: x.position.x + deltaX,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return x;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
setWindows(w => {
|
||||||
|
return w.map(x => {
|
||||||
|
if (bottom.some(r => r.id === x.id)) {
|
||||||
|
return {
|
||||||
|
...x,
|
||||||
|
position: {
|
||||||
|
...x.position,
|
||||||
|
y: x.position.y + deltaY,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return x;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
refPrevSize.current = { w: container.clientWidth, h: container.clientHeight };
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleDrag = (e: React.MouseEvent, windowId: string | number) => {
|
||||||
|
handleMouseDown(e, windowId, ActionType.Drag);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResize = (e: React.MouseEvent, windowId: string | number, resizeDirection: string) => {
|
||||||
|
handleMouseDown(e, windowId, ActionType.Resize, resizeDirection);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={containerRef} className={styles.windowContainer}>
|
||||||
|
{windows.map(window => (
|
||||||
|
<WindowWrapper key={window.id} onDrag={handleDrag} onResize={handleResize} {...window} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './WindowManager';
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export type WindowProps = {
|
||||||
|
id: number;
|
||||||
|
content: (w: WindowProps) => React.ReactNode;
|
||||||
|
position: { x: number; y: number };
|
||||||
|
size: { width: number; height: number };
|
||||||
|
zIndex: number;
|
||||||
|
};
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { WindowProps } from '@/hooks/Mapper/components/ui-kit/WindowManager/WindowManager.tsx';
|
||||||
|
|
||||||
|
export function getWindowsBySides(windows: WindowProps[], containerWidth: number, containerHeight: number) {
|
||||||
|
const centerX = containerWidth / 2;
|
||||||
|
const centerY = containerHeight / 2;
|
||||||
|
|
||||||
|
const top = windows.filter(window => window.position.y + window.size.height / 2 < centerY);
|
||||||
|
const bottom = windows.filter(window => window.position.y + window.size.height / 2 >= centerY);
|
||||||
|
const left = windows.filter(window => window.position.x + window.size.width / 2 < centerX);
|
||||||
|
const right = windows.filter(window => window.position.x + window.size.width / 2 >= centerX);
|
||||||
|
|
||||||
|
return { top, bottom, left, right };
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ContextStoreDataUpdate, useContextStore } from '@/hooks/Mapper/utils';
|
import { ContextStoreDataUpdate, useContextStore } from '@/hooks/Mapper/utils';
|
||||||
import { createContext, Dispatch, ForwardedRef, forwardRef, SetStateAction, useContext } from 'react';
|
import { createContext, Dispatch, ForwardedRef, forwardRef, SetStateAction, useContext, useEffect } from 'react';
|
||||||
import { MapUnionTypes, OutCommandHandler, SolarSystemConnection } from '@/hooks/Mapper/types';
|
import { MapUnionTypes, OutCommandHandler, SolarSystemConnection } from '@/hooks/Mapper/types';
|
||||||
import { useMapRootHandlers } from '@/hooks/Mapper/mapRootProvider/hooks';
|
import { useMapRootHandlers } from '@/hooks/Mapper/mapRootProvider/hooks';
|
||||||
import { WithChildren } from '@/hooks/Mapper/types/common.ts';
|
import { WithChildren } from '@/hooks/Mapper/types/common.ts';
|
||||||
@@ -99,6 +99,24 @@ export const MapRootProvider = ({ children, fwdRef, outCommand }: MapRootProvide
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let foundNew = false;
|
||||||
|
const newVals = Object.keys(STORED_INTERFACE_DEFAULT_VALUES).reduce((acc, x) => {
|
||||||
|
if (Object.keys(acc).includes(x)) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
|
||||||
|
foundNew = true;
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
return { ...acc, [x]: STORED_INTERFACE_DEFAULT_VALUES[x] };
|
||||||
|
}, interfaceSettings);
|
||||||
|
|
||||||
|
if (foundNew) {
|
||||||
|
setInterfaceSettings(newVals);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MapRootContext.Provider
|
<MapRootContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
|||||||
Reference in New Issue
Block a user