initial commit

This commit is contained in:
Gregory Bednov 2025-01-13 15:00:11 +03:00
commit 3751b8ddb0
23 changed files with 2048 additions and 0 deletions

16
.fleet/run.json Normal file
View file

@ -0,0 +1,16 @@
{
"configurations": [
{
"type": "npm",
"name": "Diagram run dev",
"command": "run",
"scripts": "dev",
},
{
"type": "npm",
"name": "Diagram run preview",
"command": "run",
"scripts": "preview",
}
]
}

24
.gitignore vendored Normal file
View file

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

14
index.html Normal file
View file

@ -0,0 +1,14 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<link rel="stylesheet" href="./src/style.css" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>FSA Editor</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="./src/main.ts"></script>
</body>
</html>

1453
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

20
package.json Normal file
View file

@ -0,0 +1,20 @@
{
"name": "diagram-js-svelte",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview"
},
"devDependencies": {
"@sveltejs/vite-plugin-svelte": "^5.0.3",
"svelte-preprocess": "^6.0.3",
"typescript": "~5.6.2",
"vite": "^6.0.5"
},
"dependencies": {
"diagram-js": "^15.2.4"
}
}

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="AccentColor" stroke-width="1.5" width="46" height="46"><rect x="10" y="13" width="26" height="20"/></svg>

After

Width:  |  Height:  |  Size: 165 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" stroke="AccentColor" fill="AccentColor" width="46" height="46" viewBox="0 0 485 485"><path d="M382.5 69.429c-7.441 0-14.5 1.646-20.852 4.573-4.309-23.218-24.7-40.859-49.148-40.859a49.685 49.685 0 0 0-21.467 4.852C285.641 16.205 265.932 0 242.5 0c-23.432 0-43.141 16.206-48.533 37.995a49.696 49.696 0 0 0-21.467-4.852c-27.57 0-50 22.43-50 50v122.222a49.702 49.702 0 0 0-20-4.187c-27.57 0-50 22.43-50 50V354c0 72.233 58.766 131 131 131h118c72.233 0 131-58.767 131-131V119.429c0-27.571-22.43-50-50-50zM402.5 354c0 55.691-45.309 101-101 101h-118c-55.691 0-101-45.309-101-101V251.178c0-11.028 8.972-20 20-20s20 8.972 20 20v80h30V83.143c0-11.028 8.972-20 20-20s20 8.972 20 20v158.035h30V50c0-11.028 8.972-20 20-20s20 8.972 20 20v191.178h30V83.143c0-11.028 8.972-20 20-20s20 8.972 20 20v158.035h30v-121.75c0-11.028 8.972-20 20-20s20 8.972 20 20V354z"/></svg>

After

Width:  |  Height:  |  Size: 891 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="AccentColor" stroke-width="1.5" width="46" height="46"><rect x="10" y="10" width="16" height="16" stroke-dasharray="5, 5"/><line x1="16" y1="26" x2="36" y2="26" stroke="AccentColor"/><line x1="26" y1="16" x2="26" y2="36" stroke="AccentColor"/></svg>

After

Width:  |  Height:  |  Size: 309 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="black" stroke-width="1.5" width="46" height="46"><rect x="10" y="13" width="26" height="20"/></svg>

After

Width:  |  Height:  |  Size: 159 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="46" height="46" viewBox="0 0 485 485"><path d="M382.5 69.429c-7.441 0-14.5 1.646-20.852 4.573-4.309-23.218-24.7-40.859-49.148-40.859a49.685 49.685 0 0 0-21.467 4.852C285.641 16.205 265.932 0 242.5 0c-23.432 0-43.141 16.206-48.533 37.995a49.696 49.696 0 0 0-21.467-4.852c-27.57 0-50 22.43-50 50v122.222a49.702 49.702 0 0 0-20-4.187c-27.57 0-50 22.43-50 50V354c0 72.233 58.766 131 131 131h118c72.233 0 131-58.767 131-131V119.429c0-27.571-22.43-50-50-50zM402.5 354c0 55.691-45.309 101-101 101h-118c-55.691 0-101-45.309-101-101V251.178c0-11.028 8.972-20 20-20s20 8.972 20 20v80h30V83.143c0-11.028 8.972-20 20-20s20 8.972 20 20v158.035h30V50c0-11.028 8.972-20 20-20s20 8.972 20 20v191.178h30V83.143c0-11.028 8.972-20 20-20s20 8.972 20 20v158.035h30v-121.75c0-11.028 8.972-20 20-20s20 8.972 20 20V354z"/></svg>

After

Width:  |  Height:  |  Size: 851 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="#000" stroke-width="1.5" width="46" height="46"><rect x="10" y="10" width="16" height="16" stroke-dasharray="5, 5"/><line x1="16" y1="26" x2="36" y2="26" stroke="black"/><line x1="26" y1="16" x2="26" y2="36" stroke="black"/></svg>

After

Width:  |  Height:  |  Size: 290 B

15
src/App.svelte Normal file
View file

@ -0,0 +1,15 @@
<script>
import Diagram from './Diagram.svelte'; // Подключаем компонент с diagram-js
</script>
<main>
<h1>FSA Editor</h1>
<Diagram />
</main>
<style>
main {
font-family: Arial, sans-serif;
padding: 1rem;
}
</style>

View file

@ -0,0 +1,61 @@
function PalettePlugin(palette, lassoTool, create, elementFactory, globalConnect) {
palette.registerProvider({
getPaletteEntries: () => ({
'hand-tool': {
group: 'tools',
className: 'icon-hand-tool',
title: 'Hand Tool',
action: {
click: function(event) {
console.log("Hello");
}
}
},
'lasso-tool': {
group: 'tools',
className: 'icon-lasso-tool',
title: 'Lasso Tool',
action: {
click: function(event) {
lassoTool.activateSelection(event);
}
}
},
'tool-separator': {
group: 'tools',
separator: true
},
'create-shape': {
group: 'create',
className: 'icon-create-shape',
title: 'Create Shape',
action: {
click: function() {
var shape = elementFactory.createShape({
width: 100,
height: 80,
canStartConnection:true
});
console.log(shape.canStartConnection);
create.start(event, shape);
}
}
},
'create-connection': {
group: 'create',
className: 'connection-create-shape',
title: 'Create Connection',
action: {
click: (event) => {
globalConnect.start(event);
}
}
}
})
});
}
export default {
__init__: [ 'palettePlugin' ],
palettePlugin: [ 'type', PalettePlugin ]
};

102
src/Diagram.svelte Normal file
View file

@ -0,0 +1,102 @@
<script lang="ts">
import { connectPoints } from 'diagram-js/lib/layout/ManhattanLayout';
import { onMount } from "svelte";
import Diagram from "diagram-js";
import Canvas from "diagram-js/lib/core/Canvas";
import LassoTool from "diagram-js/lib/features/lasso-tool"
import Editor from './editor.ts'
import 'diagram-js/assets/diagram-js.css';
let container: HTMLDivElement | null = null;
function createAction(type, group, className, title, options) {
function createListener(event) {
var shape = elementFactory.createShape(assign({ type: type }, options));
create.start(event, shape);
}
return {
group: group,
className: className,
title: title,
action: {
dragstart: createListener,
click: createListener
}
};
}
onMount(() => {
if (container === null) return;
const diagram = new Editor({ container });
const canvas = diagram.get<Canvas>("canvas");
const elementFactory = diagram.get('elementFactory');
var root = elementFactory.createRoot();
canvas.setRootElement(root);
var shape1 = elementFactory.createShape({
x: 150,
y: 100,
width: 100,
height: 80
});
canvas.addShape(shape1, root);
var shape2 = elementFactory.createShape({
x: 290,
y: 220,
width: 100,
height: 80
});
canvas.addShape(shape2, root);
var shape3 = elementFactory.createShape({
x: 450,
y: 80,
width: 100,
height: 80
});
canvas.addShape(shape3, root);
var shape4 = elementFactory.createShape({
x: 425,
y: 50,
width: 300,
height: 200,
isFrame: true
});
canvas.addShape(shape4, root);
const manhattanLayout = connectPoints(
{ x: shape1.x + shape1.width / 2, y: shape1.y + shape1.height / 2 }, // Начальная точка
{ x: shape2.x + shape2.width / 2, y: shape2.y + shape2.height / 2 } // Конечная точка
);
var connection1 = elementFactory.createConnection({
waypoints: manhattanLayout,
source: shape1,
target: shape2
});
canvas.addConnection(connection1, root);
});
</script>
<style>
.container {
width: 100%;
height: 500px;
border: 1px solid #aaa;
}
</style>
<div bind:this={container} class="container"></div>

View file

@ -0,0 +1,32 @@
import { connectPoints } from 'diagram-js/lib/layout/ManhattanLayout';
function ManhattanLayoutPlugin(eventBus, modeling) {
eventBus.on('connection.changed', (event) => {
var connection = event.element;
if (connection.source && connection.target) {
const x0 = connection.source.x + connection.source.width / 2;
const x1 = connection.target.x + connection.target.width / 2;
const y0 = connection.source.y + connection.source.height / 2;
const y1 = connection.target.y + connection.target.height / 2;
const x2 = (Math.abs(x0-x1) < 5) ? x0 : x1;
const y2 = (Math.abs(y0-y1) < 5) ? y0 : y1;
const waypoints = connectPoints(
{ x: x0, y: y0 },
{ x: x2, y: y2 });
const hasChanged = JSON.stringify(connection.waypoints) != JSON.stringify(waypoints);
if (hasChanged) {
modeling.updateWaypoints(connection, waypoints)
}
}
});
}
// export as module
export default {
__init__: [ 'manhattanLayoutPlugin' ],
manhattanLayoutPlugin: [ 'type', ManhattanLayoutPlugin ]
};

11
src/StyleModule.ts Normal file
View file

@ -0,0 +1,11 @@
export default {
__init__: ['customRenderer'],
customRenderer: [
'type',
function (defaultRenderer: any) {
defaultRenderer.CONNECTION_STYLE = { fill: 'none', strokeWidth: 5, stroke: 'CanvasText' };
defaultRenderer.SHAPE_STYLE = { fill: 'Canvas', stroke: 'CanvasText', strokeWidth: 2 };
defaultRenderer.FRAME_STYLE = { fill: 'none', stroke: 'CanvasText', strokeDasharray: 4, strokeWidth: 2 };
}
]
};

View file

@ -0,0 +1,55 @@
const CustomConnectionModule = {
__init__: [
[
'eventBus',
'modeling',
function (eventBus: any, modeling: any) {
console.log('CustomConnectionModule initialized');
// Регистрируем обработчик событий
eventBus.on('connection.changed', (event: any) => {
const connection = event.element;
if (connection.source && connection.target) {
const waypoints = calculateWaypoints(connection);
// Проверяем изменения
if (JSON.stringify(connection.waypoints) != JSON.stringify(waypoints)) {
modeling.updateWaypoints(connection, waypoints);
console.log('Waypoints updated', waypoints);
}
}
});
}
]
]
};
/**
* Функция для вычисления новых точек маршрута соединений.
*/
function calculateWaypoints(connection: any): { x: number; y: number }[] {
const x0 = connection.source.x + connection.source.width / 2;
const y0 = connection.source.y + connection.source.height / 2;
const x1 = connection.target.x + connection.target.width / 2;
const y1 = connection.target.y + connection.target.height / 2;
const x2 = Math.abs(x0 - x1) < 5 ? x0 : x1;
const y2 = Math.abs(y0 - y1) < 5 ? y0 : y1;
return connectPoints({ x: x0, y: y0 }, { x: x2, y: y2 });
}
/**
* Функция для соединения двух точек.
*/
function connectPoints(
start: { x: number; y: number },
end: { x: number; y: number }
): { x: number; y: number }[] {
return [start, end];
}
export default CustomConnectionModule;

58
src/editor.ts Normal file
View file

@ -0,0 +1,58 @@
import Diagram from 'diagram-js';
import ConnectModule from 'diagram-js/lib/features/connect';
import ContextPadModule from 'diagram-js/lib/features/context-pad';
import CreateModule from 'diagram-js/lib/features/create';
import LassoToolModule from 'diagram-js/lib/features/lasso-tool';
import ModelingModule from 'diagram-js/lib/features/modeling';
import MoveCanvasModule from 'diagram-js/lib/navigation/movecanvas';
import MoveModule from 'diagram-js/lib/features/move';
import OutlineModule from 'diagram-js/lib/features/outline';
import PaletteModule from 'diagram-js/lib/features/palette';
import ResizeModule from 'diagram-js/lib/features/resize';
import RulesModule from 'diagram-js/lib/features/rules';
import AlignElementsModule from 'diagram-js/lib/features/align-elements';
import SelectionModule from 'diagram-js/lib/features/selection';
import GlobalConnectModule from 'diagram-js/lib/features/global-connect';
import ZoomScrollModule from 'diagram-js/lib/navigation/zoomscroll';
import BendpointMoveModule from 'diagram-js/lib/features/bendpoints';
import CustomConnectionModule from './CustomConnectionModule';
import StyleModule from './StyleModule.ts';
import ManhattanConnectionModule from './ManhattanConnectionModule.ts';
import CustomPaletteModule from './CustomPaletteModule.ts';
interface EditorOptions {
container: HTMLElement;
additionalModules?: Array<any>;
}
export default function Editor(options: EditorOptions): Diagram {
const { container, additionalModules = [] } = options;
const modules = [
BendpointMoveModule
, ConnectModule
, ContextPadModule
, CreateModule
, GlobalConnectModule
, LassoToolModule
, ModelingModule
, MoveCanvasModule
, MoveModule
, OutlineModule
, PaletteModule
, ResizeModule
, RulesModule
, SelectionModule
, ZoomScrollModule
, StyleModule
, ManhattanConnectionModule
, CustomPaletteModule
];
return new Diagram({
canvas: {
container
},
modules: modules
});
}

14
src/main.ts Normal file
View file

@ -0,0 +1,14 @@
import { mount } from 'svelte'
import App from './App.svelte';
const target = document.getElementById('app');
if (!target) {
throw new Error("Target element with ID 'app' not found in DOM.");
}
// Создаём экземпляр приложения
const app = mount(App, {
target: document.getElementById('app')!,
})
export default app;

135
src/style.css Normal file
View file

@ -0,0 +1,135 @@
:root {
color: CanvasText; /* Цвет текста по умолчанию */
background-color: Canvas; /* Фоновый цвет, адаптируемый системой */
}
button {
background-color: ButtonFace;
color: ButtonText;
border-color: ButtonBorder;
}
button:hover {
border-color: AccentColor;
}
button:focus {
outline: 4px auto Highlight;
}
.anchor {
background-color: AccentColor;
color: AccentColorText;
visibility: hidden;
transition: visibility 0.2s ease-in-out;
}
@media (prefers-color-scheme: dark) {
:root {
color: CanvasText;
background-color: Canvas;
}
}
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
--primary-color: CanvasText;
--hover-color: #747bff;
--background-color: #242424;
--background-light: #ffffff;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: var(--background-color);
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
margin: 0;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: var(--background-color);
color: inherit;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: var(--primary-color);
}
button:focus {
outline: 4px auto -webkit-focus-ring-color;
}
.svgContainer {
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
}
@media (prefers-color-scheme: light) {
:root {
color: var(--background-color);
background-color: var(--background-light);
}
button {
background-color: #f9f9f9;
}
button:hover {
border-color: var(--hover-color);
}
}
.svgContainer {
border: solid CanvasText 1px;
}
.icon-hand-tool {
background: url('../resources/icon-hand-tool.svg');
}
.icon-lasso-tool {
background: url('../resources/icon-lasso-tool.svg');
}
.icon-hand-tool:hover {
background: url('../resources/hovered/icon-hand-tool.svg');
}
.icon-lasso-tool:hover {
background: url('../resources/hovered/icon-lasso-tool.svg');
}
.pad-icon-remove {
background: url('data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%221.5%22%20width%3D%2246%22%20height%3D%2246%22%3E%3Cline%20x1%3D%225%22%20y1%3D%225%22%20x2%3D%2215%22%20y2%3D%2215%22%2F%3E%3Cline%20x1%3D%2215%22%20y1%3D%225%22%20x2%3D%225%22%20y2%3D%2215%22%2F%3E%3C%2Fsvg%3E') !important;
}
.pad-icon-connect {
background: url('data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20fill%3D%22none%22%20stroke%3D%22%23000%22%20stroke-width%3D%221.5%22%20width%3D%2246%22%20height%3D%2246%22%3E%3Cline%20x1%3D%2215%22%20y1%3D%225%22%20x2%3D%225%22%20y2%3D%2215%22%2F%3E%3C%2Fsvg%3E') !important;
}
.icon-create-shape {
background: url('../resources/icon-create-rect.svg');
}
.icon-create-shape:hover {
background: url('../resources/hovered/icon-create-rect.svg');
}

1
src/vite-env.d.ts vendored Normal file
View file

@ -0,0 +1 @@
/// <reference types="vite/client" />

24
tsconfig.json Normal file
View file

@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
}

7
vite.config.ts Normal file
View file

@ -0,0 +1,7 @@
import { defineConfig } from 'vite'
import { svelte } from '@sveltejs/vite-plugin-svelte'
// https://vite.dev/config/
export default defineConfig({
plugins: [svelte()],
})