Skip to content
This repository was archived by the owner on Apr 24, 2025. It is now read-only.
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
14 changes: 11 additions & 3 deletions apps/xi.front/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ const withPWA = require('next-pwa')({
});

const nextConfig = {
webpack: (config) => {
// eslint-disable-next-line no-param-reassign
config.externals = [...config.externals, { canvas: 'canvas' }];
return config;
},
outputFileTracingRoot: path.join(__dirname, '../../'),
experimental: {
esmExternals: true,
Expand Down Expand Up @@ -65,9 +70,12 @@ const nextConfig = {
'@xipkg/routerurl',
'@xipkg/badge',
],
compiler: process.env.NODE_ENV === 'production' ? {
removeConsole: true,
} : {},
compiler:
process.env.NODE_ENV === 'production'
? {
removeConsole: true,
}
: {},
reactStrictMode: true,
images: {
remotePatterns: [
Expand Down
2 changes: 1 addition & 1 deletion apps/xi.front/public/sw.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion apps/xi.front/public/sw.js.map

Large diffs are not rendered by default.

2,671 changes: 1,889 additions & 782 deletions package-lock.json

Large diffs are not rendered by default.

31 changes: 23 additions & 8 deletions packages/pkg.module.board/Board.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
// components/Whiteboard.tsx
import React, { useState, useRef, useEffect } from 'react';
import { Stage } from 'react-konva';
import Konva from 'konva';
import { useDebouncedFunction } from '@xipkg/utils';
import CanvasLayer from './CanvasLayer';
import { ToolType } from './types';
import { useBoardStore, useUIStore } from './store';
Expand All @@ -12,10 +14,10 @@ export const Board: React.FC = () => {
// Выбранный инструмент
const [selectedTool, setSelectedTool] = useState<ToolType>('pen');
const { boardElements } = useBoardStore();
const stageRef = useRef<any>(null);
const stageRef = useRef<Konva.Stage>(null);

// Получаем scale, setScale, zoomIn и zoomOut из UI‑стора
const { scale } = useUIStore();
const { scale, setStagePosition } = useUIStore();

// Пример хоткеев: Escape – переключиться в режим выделения,
// Delete – удалить выделенные элементы (реализовать логику выбора)
Expand All @@ -34,23 +36,36 @@ export const Board: React.FC = () => {

const handleWheel = useWheelZoom(stageRef);

const debouncedSetStagePos = useDebouncedFunction((x, y) => {
setStagePosition({ x, y });
}, 100);

const handleDragMove = (e: Konva.KonvaEventObject<DragEvent>) => {
debouncedSetStagePos(e.target.x(), e.target.y());
};

const handleOnWheel = (e: Konva.KonvaEventObject<WheelEvent>) => {
setStagePosition({ x: e.target.x(), y: e.target.y() });
handleWheel(e);
};

// Получаем размеры доски (для примера используем window.innerWidth и window.innerHeight - 50)
const boardWidth = window.innerWidth;
const boardHeight = window.innerHeight;

return (
<div className="flex h-full w-full flex-col">
<div className="relative flex-1">
<div className="relative flex-1 overflow-hidden">
<Stage
width={boardWidth}
height={boardHeight}
ref={stageRef}
className="bg-white"
onWheel={handleWheel}
scaleX={scale}
scaleY={scale}
className="bg-gray-0"
onWheel={handleOnWheel}
onDragMove={handleDragMove}
draggable
>
<BackgroundLayer width={boardWidth} height={boardHeight} stageScale={scale} />
<BackgroundLayer scaleValue={scale} />
<CanvasLayer boardElements={boardElements} selectedTool={selectedTool} />
</Stage>
</div>
Expand Down
104 changes: 57 additions & 47 deletions packages/pkg.module.board/components/BackgroundLayer.tsx
Original file line number Diff line number Diff line change
@@ -1,49 +1,59 @@
// components/BackgroundLayer.tsx
import React, { useMemo } from 'react';
import { Layer, Rect } from 'react-konva';

interface BackgroundLayerProps {
width: number;
height: number;
stageScale: number; // текущий масштаб доски (из UI-стора или компонента)
}

export const BackgroundLayer: React.FC<BackgroundLayerProps> = ({ width, height, stageScale }) => {
// Создаем offscreen canvas для паттерна один раз при монтировании компонента
const patternCanvas = useMemo(() => {
const size = 20; // размер "тайла" паттерна (можно настроить)
const canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
const context = canvas.getContext('2d');
if (context) {
// Заливаем фон тайла (например, светло-серым)
context.fillStyle = '#f5f5f5';
context.fillRect(0, 0, size, size);
// Рисуем точку в центре (цвет можно изменить)
context.fillStyle = '#d1d1d1';
context.beginPath();
context.arc(size / 2, size / 2, 2, 0, Math.PI * 2);
context.fill();
}
return canvas;
}, []);

return (
<Layer>
<Rect
x={0}
y={0}
width={width}
height={height}
// Приводим canvas к типу HTMLImageElement для TypeScript
fillPatternImage={patternCanvas as unknown as HTMLImageElement}
fillPatternRepeat="repeat"
// Обратное масштабирование паттерна:
// Если Stage масштабирован с коэффициентом stageScale,
// то паттерн масштабируется в обратном направлении, чтобы сохранить свой размер на экране.
fillPatternScale={{ x: 1 / stageScale, y: 1 / stageScale }}
'use client';

import React, { useEffect, useMemo } from 'react';
import { Layer, Shape } from 'react-konva';
import { useUIStore } from '../store';
import { boardGridStep } from '../const';

type BackgroundLayerPropsT = {
scaleValue: number;
};

export const BackgroundLayer = ({ scaleValue }: BackgroundLayerPropsT) => {
const { viewport, setViewport, stagePosition } = useUIStore();

useEffect(() => {
const updateSize = () => {
setViewport({ width: window.innerWidth, height: window.innerHeight });
};

updateSize();
window.addEventListener('resize', updateSize);
return () => window.removeEventListener('resize', updateSize);
}, [setViewport]);

const dots = useMemo(() => {
const visibleWidth = viewport.width / scaleValue;
const visibleHeight = viewport.height / scaleValue;

const buffer = Math.max(visibleWidth, visibleHeight) * 2;

const startX =
Math.floor((-stagePosition.x / scaleValue - buffer) / boardGridStep) * boardGridStep;
const endX =
Math.ceil((-stagePosition.x / scaleValue + visibleWidth + buffer) / boardGridStep) *
boardGridStep;
const startY =
Math.floor((-stagePosition.y / scaleValue - buffer) / boardGridStep) * boardGridStep;
const endY =
Math.ceil((-stagePosition.y / scaleValue + visibleHeight + buffer) / boardGridStep) *
boardGridStep;

return (
<Shape
sceneFunc={(context) => {
context.fillStyle = '#e8e8e8';
for (let x = startX; x <= endX; x += boardGridStep) {
for (let y = startY; y <= endY; y += boardGridStep) {
context.beginPath();
context.arc(x, y, 4 / scaleValue, 0, Math.PI * 2);
context.fill();
}
}
}}
/>
</Layer>
);
);
}, [viewport.width, viewport.height, scaleValue, stagePosition]);

return <Layer listening={false}>{dots}</Layer>;
};
1 change: 1 addition & 0 deletions packages/pkg.module.board/const.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const boardGridStep = 40;
22 changes: 12 additions & 10 deletions packages/pkg.module.board/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,25 @@
"lint": "eslint \"**/*.{ts,tsx}\""
},
"dependencies": {
"@hocuspocus/provider": "2.15.0",
"@tldraw/editor": "3.6.1",
"@xipkg/button": "2.2.0",
"@xipkg/dropdown": "^2.5.0",
"@xipkg/icons": "1.6.0",
"@xipkg/tooltip": "1.3.0",
"@xipkg/slider": "1.2.0",
"@xipkg/tooltip": "1.3.0",
"@xipkg/utils": "^1.6.3",
"konva": "^9.3.18",
"pkg.logo": "*",
"pkg.stores": "*",
"pkg.utils": "*",
"pkg.utils.client": "*",
"react-konva": "^19.0.2",
"sonner": "^1.5.0",
"pkg.utils": "*",
"pkg.stores": "*",
"@hocuspocus/provider": "2.15.0",
"yjs": "13.6.21",
"use-image": "1.1.1",
"y-utility": "0.1.4",
"react-konva": "19.0.2",
"zustand": "5.0.3",
"use-image": "1.1.1"
"yjs": "13.6.21",
"zustand": "5.0.3"
},
"devDependencies": {
"@types/node": "^20.3.1",
Expand All @@ -37,8 +39,8 @@
"typescript": "^5.4.2"
},
"peerDependencies": {
"react": "19",
"next": "15"
"next": "15",
"react": "19"
},
"description": "",
"author": "xi.effect"
Expand Down
8 changes: 8 additions & 0 deletions packages/pkg.module.board/store/useUIStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ interface UIState {
zoomIn: () => void;
zoomOut: () => void;
setPan: (pan: { x: number; y: number }) => void;
viewport: { width: number; height: number };
setViewport: (viewport: { width: number; height: number }) => void;
stagePosition: { x: number; y: number };
setStagePosition: (stagePosition: { x: number; y: number }) => void;
}

export const useUIStore = create<UIState>((set, get) => ({
Expand All @@ -25,4 +29,8 @@ export const useUIStore = create<UIState>((set, get) => ({
set({ scale: newScale });
},
setPan: (pan) => set({ pan }),
viewport: { width: 0, height: 0 },
setViewport: (viewport) => set({ viewport }),
stagePosition: { x: 0, y: 0 },
setStagePosition: (stagePosition) => set({ stagePosition }),
}));
Loading