v0.1
3
.browserslistrc
Normal file
@@ -0,0 +1,3 @@
|
||||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
||||
22
.eslintrc.js
Normal file
@@ -0,0 +1,22 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
env: {
|
||||
node: true,
|
||||
},
|
||||
extends: ["plugin:vue/vue3-essential", "eslint:recommended", "@vue/prettier"],
|
||||
parserOptions: {
|
||||
parser: "babel-eslint",
|
||||
},
|
||||
rules: {
|
||||
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
files: ["*.js"],
|
||||
rules: {
|
||||
"no-dupe-class-members": "off",
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
23
.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
24
README.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# euler-cycle
|
||||
|
||||
## Project setup
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
||||
3
babel.config.js
Normal file
@@ -0,0 +1,3 @@
|
||||
module.exports = {
|
||||
presets: ["@vue/cli-plugin-babel/preset"],
|
||||
};
|
||||
28569
package-lock.json
generated
Normal file
29
package.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "euler-cycle",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build",
|
||||
"lint": "vue-cli-service lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"core-js": "^3.6.5",
|
||||
"d3": "^7.1.1",
|
||||
"vue": "^3.0.0",
|
||||
"vue-router": "^4.0.0-0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.5.0",
|
||||
"@vue/cli-plugin-eslint": "~4.5.0",
|
||||
"@vue/cli-plugin-router": "~4.5.0",
|
||||
"@vue/cli-service": "~4.5.0",
|
||||
"@vue/compiler-sfc": "^3.0.0",
|
||||
"@vue/eslint-config-prettier": "^6.0.0",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
"eslint-plugin-vue": "^7.0.0",
|
||||
"prettier": "^2.2.1"
|
||||
}
|
||||
}
|
||||
6
public/favicon.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="256px" height="288px" viewBox="0 0 256 288" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||
<g fill-rule="evenodd">
|
||||
<path d="M152.575995,32.9634453 L211.722058,67.1124427 C213.387503,65.348329 215.332859,63.7907081 217.539734,62.5088708 C229.806457,55.4573413 245.452038,59.6441869 252.577021,71.9109101 C259.62855,84.1776333 255.441705,99.8232143 243.174982,106.948197 C240.984974,108.207124 238.684929,109.10784 236.343812,109.66832 L236.343812,177.99302 C238.659582,178.554683 240.934429,179.449686 243.101528,180.695443 C255.441705,187.820426 259.62855,203.466007 252.503568,215.73273 C245.452038,227.999453 229.733004,232.186299 217.46628,225.13477 C215.024105,223.730884 212.902189,221.983449 211.125456,219.988103 L152.340162,253.928803 C153.180519,256.46808 153.635248,259.18431 153.635248,262.008393 C153.635248,276.111452 142.176512,287.64364 128,287.64364 C113.823488,287.64364 102.364752,276.184905 102.364752,262.008393 C102.364752,259.488481 102.726802,257.054441 103.40181,254.755362 L44.2714887,220.615454 C42.6238257,222.34493 40.705394,223.873378 38.5337196,225.13477 C26.193543,232.186299 10.547962,227.999453 3.49643248,215.73273 C-3.55509701,203.466007 0.631748621,187.820426 12.8984718,180.695443 C15.0673397,179.448669 17.3418356,178.553222 19.6561876,177.991646 L19.6561876,109.66832 C17.3150714,109.10784 15.0150257,108.207124 12.8250184,106.948197 C0.558295189,99.8966677 -3.62855044,84.1776333 3.42297904,71.9109101 C10.4745085,59.6441869 26.193543,55.4573413 38.4602662,62.5088708 C40.6551374,63.7837361 42.5913269,65.3313931 44.2507413,67.0836756 L103.41276,32.9254664 C102.730718,30.6154532 102.364752,28.1687503 102.364752,25.6352478 C102.364752,11.4587354 113.823488,0 128,0 C142.176512,0 153.635248,11.4587354 153.635248,25.6352478 C153.635248,28.1826393 153.265258,30.6422768 152.575995,32.9634453 Z M146.413638,43.4848713 L205.700555,77.715193 C203.867899,84.1516888 204.540515,91.2885638 208.137694,97.5461579 C211.746601,103.824151 217.625783,107.985785 224.150543,109.607654 L224.150543,178.017842 C223.818032,178.099463 223.48718,178.187674 223.158201,178.282419 L145.72529,44.1686182 C145.959017,43.9450469 146.18851,43.7170876 146.413638,43.4848713 Z M110.29093,44.1841203 L32.8593279,178.295656 C32.524559,178.19844 32.1878611,178.108015 31.8494573,178.024438 L31.8494573,109.607654 C38.3742168,107.985785 44.2533992,103.824151 47.8623055,97.5461579 C51.466599,91.276189 52.1347497,84.1234583 50.2885426,77.6770157 L109.558747,43.4563434 C109.797913,43.7038019 110.042027,43.9464469 110.29093,44.1841203 Z M135.162749,50.259763 L212.576817,184.340928 C210.844241,185.99279 209.317376,187.91755 208.064241,190.097482 C206.818484,192.264581 205.923481,194.539429 205.361818,196.855198 L50.6395564,196.855198 C50.0779798,194.540846 49.1825333,192.26635 47.9357589,190.097482 C46.6776713,187.931497 45.1539248,186.017428 43.4300297,184.372667 L120.858821,50.2659993 C123.124941,50.9200822 125.520915,51.2704957 128,51.2704957 C130.486952,51.2704957 132.890265,50.9178547 135.162749,50.259763 Z M146.958084,244.737995 L205.860107,210.729899 C205.683398,210.174924 205.525483,209.614096 205.38664,209.048468 L50.6067643,209.048468 C50.5246888,209.380792 50.4360084,209.711472 50.3407792,210.040295 L109.531782,244.215239 C114.192298,239.378545 120.739739,236.373145 128,236.373145 C135.518379,236.373145 142.272352,239.596041 146.958084,244.737995 Z" fill="#E535AB"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
19
public/index.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
||||
<link rel="icon" href="<%= BASE_URL %>favicon.svg" />
|
||||
<title>Эйлеров цикл</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>
|
||||
Для работы приложения необходимо включить JavaScript в браузере.
|
||||
</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
</html>
|
||||
7
src/App.vue
Normal file
@@ -0,0 +1,7 @@
|
||||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
|
||||
<style>
|
||||
@import url("~@/assets/css/formular.css");
|
||||
</style>
|
||||
224
src/assets/css/creation.css
Normal file
@@ -0,0 +1,224 @@
|
||||
@charset "UTF-8";
|
||||
|
||||
:root {
|
||||
--main-color: rgb(97 196 189);
|
||||
--complement-color: rgb(46 105 120);
|
||||
--hover-main-color: rgb(224, 241, 240);
|
||||
--hover-complement-color: rgb(214 231 234);
|
||||
--body-color: #f3f3f3;
|
||||
--text-color: #3d3d3d;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background-color: var(--body-color);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 2px;
|
||||
background-color: #3e132e;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background-color: var(--body-color);
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: "Formular";
|
||||
/* line-height: 1.6; */
|
||||
height: 100%;
|
||||
background-color: var(--body-color);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
padding: 48px 64px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.matrix-cont {
|
||||
max-width: 100%;
|
||||
width: max-content;
|
||||
display: flex;
|
||||
margin: 44px 0 56px;
|
||||
}
|
||||
|
||||
.matrix {
|
||||
display: flex;
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.matrix-cell,
|
||||
.auto-matrix-cell {
|
||||
height: 40px;
|
||||
background-color: #e8e8e8;
|
||||
cursor: pointer;
|
||||
border-radius: 2px;
|
||||
margin-bottom: 4px;
|
||||
background-image: url("~@/assets/icons/zero.svg");
|
||||
background-size: 16px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.matrix-cell:last-child,
|
||||
.auto-matrix-cell:last-child {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.matrix-column {
|
||||
margin-right: 4px;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.matrix-column:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.value-row-matrix {
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
color: #c1c1c1;
|
||||
font-weight: 700;
|
||||
width: 40px;
|
||||
justify-content: center;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.value-row-matrix:last-child {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.value-matrix-column {
|
||||
text-align: center;
|
||||
margin-bottom: 8px;
|
||||
font-size: 12px;
|
||||
height: 32px;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: #c1c1c1;
|
||||
}
|
||||
|
||||
.value-rows-matrix-cont {
|
||||
padding-top: 40px;
|
||||
margin-right: 8px;
|
||||
background-color: var(--body-color);
|
||||
}
|
||||
|
||||
.active-cell {
|
||||
background-color: #feafc7;
|
||||
background-image: url("~@/assets/icons/one.svg");
|
||||
}
|
||||
|
||||
.auto-matrix-cell {
|
||||
background-color: transparent;
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.active-auto-cell {
|
||||
background-image: url("~@/assets/icons/one.svg");
|
||||
}
|
||||
|
||||
.body-matrix {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.number {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.number input[type="number"] {
|
||||
display: block;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
font-family: "Formular";
|
||||
}
|
||||
.number input[type="number"]::-webkit-outer-spin-button,
|
||||
.number input[type="number"]::-webkit-inner-spin-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.number-minus,
|
||||
.number-plus {
|
||||
position: absolute;
|
||||
background-color: #e8e8e8;
|
||||
cursor: pointer;
|
||||
top: 6px;
|
||||
left: 1px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
padding: 0;
|
||||
display: block;
|
||||
border: none;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
font-family: "Formular";
|
||||
border-radius: 100%;
|
||||
}
|
||||
|
||||
.number-plus {
|
||||
position: absolute;
|
||||
left: auto;
|
||||
right: 1px;
|
||||
}
|
||||
|
||||
.input-cont {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.text-input-cont {
|
||||
font-size: 14px;
|
||||
margin: 0px 20px 1px 20px;
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.matrix-cell:hover {
|
||||
background-color: #c1c1c1;
|
||||
}
|
||||
|
||||
.creation-button {
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
padding: 12px 24px;
|
||||
border-radius: 6px;
|
||||
color: #4f2841;
|
||||
border: 1px solid #4f2841;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
font-family: "Formular";
|
||||
transition: background-color 0.2s, opacity 0.2s;
|
||||
letter-spacing: 0.5px;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.disabled-button {
|
||||
opacity: 0.2;
|
||||
cursor: auto;
|
||||
}
|
||||
125
src/assets/css/formular.css
Normal file
@@ -0,0 +1,125 @@
|
||||
@font-face {
|
||||
font-family: "Formular";
|
||||
src: url("~@/assets/fonts/Formular-BlackItalic.eot");
|
||||
src: local("Formular Black Italic"), local("Formular-BlackItalic"),
|
||||
url("~@/assets/fonts/Formular-BlackItalic.eot?#iefix")
|
||||
format("embedded-opentype"),
|
||||
url("~@/assets/fonts/Formular-BlackItalic.woff2") format("woff2"),
|
||||
url("~@/assets/fonts/Formular-BlackItalic.woff") format("woff"),
|
||||
url("~@/assets/fonts/Formular-BlackItalic.ttf") format("truetype");
|
||||
font-weight: 900;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Formular";
|
||||
src: url("~@/assets/fonts/Formular-MediumItalic.eot");
|
||||
src: local("Formular Medium Italic"), local("Formular-MediumItalic"),
|
||||
url("~@/assets/fonts/Formular-MediumItalic.eot?#iefix")
|
||||
format("embedded-opentype"),
|
||||
url("~@/assets/fonts/Formular-MediumItalic.woff2") format("woff2"),
|
||||
url("~@/assets/fonts/Formular-MediumItalic.woff") format("woff"),
|
||||
url("~@/assets/fonts/Formular-MediumItalic.ttf") format("truetype");
|
||||
font-weight: 500;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Formular";
|
||||
src: url("~@/assets/fonts/Formular-Italic.eot");
|
||||
src: local("Formular Italic"), local("Formular-Italic"),
|
||||
url("~@/assets/fonts/Formular-Italic.eot?#iefix")
|
||||
format("embedded-opentype"),
|
||||
url("~@/assets/fonts/Formular-Italic.woff2") format("woff2"),
|
||||
url("~@/assets/fonts/Formular-Italic.woff") format("woff"),
|
||||
url("~@/assets/fonts/Formular-Italic.ttf") format("truetype");
|
||||
font-weight: normal;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Formular";
|
||||
src: url("~@/assets/fonts/Formular-LightItalic.eot");
|
||||
src: local("Formular Light Italic"), local("Formular-LightItalic"),
|
||||
url("~@/assets/fonts/Formular-LightItalic.eot?#iefix")
|
||||
format("embedded-opentype"),
|
||||
url("~@/assets/fonts/Formular-LightItalic.woff2") format("woff2"),
|
||||
url("~@/assets/fonts/Formular-LightItalic.woff") format("woff"),
|
||||
url("~@/assets/fonts/Formular-LightItalic.ttf") format("truetype");
|
||||
font-weight: 300;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Formular";
|
||||
src: url("~@/assets/fonts/Formular-Black.eot");
|
||||
src: local("Formular Black"), local("Formular-Black"),
|
||||
url("~@/assets/fonts/Formular-Black.eot?#iefix") format("embedded-opentype"),
|
||||
url("~@/assets/fonts/Formular-Black.woff2") format("woff2"),
|
||||
url("~@/assets/fonts/Formular-Black.woff") format("woff"),
|
||||
url("~@/assets/fonts/Formular-Black.ttf") format("truetype");
|
||||
font-weight: 900;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Formular";
|
||||
src: url("~@/assets/fonts/Formular-Light.eot");
|
||||
src: local("Formular Light"), local("Formular-Light"),
|
||||
url("~@/assets/fonts/Formular-Light.eot?#iefix") format("embedded-opentype"),
|
||||
url("~@/assets/fonts/Formular-Light.woff2") format("woff2"),
|
||||
url("~@/assets/fonts/Formular-Light.woff") format("woff"),
|
||||
url("~@/assets/fonts/Formular-Light.ttf") format("truetype");
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Formular";
|
||||
src: url("~@/assets/fonts/Formular.eot");
|
||||
src: local("Formular"),
|
||||
url("~@/assets/fonts/Formular.eot?#iefix") format("embedded-opentype"),
|
||||
url("~@/assets/fonts/Formular.woff2") format("woff2"),
|
||||
url("~@/assets/fonts/Formular.woff") format("woff"),
|
||||
url("~@/assets/fonts/Formular.ttf") format("truetype");
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Formular";
|
||||
src: url("~@/assets/fonts/Formular-Medium.eot");
|
||||
src: local("Formular Medium"), local("Formular-Medium"),
|
||||
url("~@/assets/fonts/Formular-Medium.eot?#iefix")
|
||||
format("embedded-opentype"),
|
||||
url("~@/assets/fonts/Formular-Medium.woff2") format("woff2"),
|
||||
url("~@/assets/fonts/Formular-Medium.woff") format("woff"),
|
||||
url("~@/assets/fonts/Formular-Medium.ttf") format("truetype");
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Formular";
|
||||
src: url("~@/assets/fonts/Formular-BoldItalic.eot");
|
||||
src: local("Formular Bold Italic"), local("Formular-BoldItalic"),
|
||||
url("~@/assets/fonts/Formular-BoldItalic.eot?#iefix")
|
||||
format("embedded-opentype"),
|
||||
url("~@/assets/fonts/Formular-BoldItalic.woff2") format("woff2"),
|
||||
url("~@/assets/fonts/Formular-BoldItalic.woff") format("woff"),
|
||||
url("~@/assets/fonts/Formular-BoldItalic.ttf") format("truetype");
|
||||
font-weight: bold;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Formular";
|
||||
src: url("~@/assets/fonts/Formular-Bold.eot");
|
||||
src: local("Formular Bold"), local("Formular-Bold"),
|
||||
url("~@/assets/fonts/Formular-Bold.eot?#iefix") format("embedded-opentype"),
|
||||
url("~@/assets/fonts/Formular-Bold.woff2") format("woff2"),
|
||||
url("~@/assets/fonts/Formular-Bold.woff") format("woff"),
|
||||
url("~@/assets/fonts/Formular-Bold.ttf") format("truetype");
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
227
src/assets/css/graph.css
Normal file
@@ -0,0 +1,227 @@
|
||||
@charset "UTF-8";
|
||||
|
||||
:root {
|
||||
--main-color: rgb(97 196 189);
|
||||
--complement-color: rgb(46 105 120);
|
||||
--hover-main-color: rgb(224, 241, 240);
|
||||
--hover-complement-color: rgb(214 231 234);
|
||||
--body-color: #f3f3f3;
|
||||
--text-color: #3d3d3d;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 0px;
|
||||
height: 0px;
|
||||
background-color: var(--body-color);
|
||||
}
|
||||
|
||||
body > #app {
|
||||
margin: 0;
|
||||
font-family: "Formular";
|
||||
height: 100%;
|
||||
background-color: var(--body-color);
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
padding-right: 32px;
|
||||
padding-left: 32px;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.graph-cont {
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.graph-cont,
|
||||
.graph {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
header {
|
||||
width: 100%;
|
||||
padding: 48px 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.header-step-cont {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-left: 48px;
|
||||
}
|
||||
|
||||
.header-step-description {
|
||||
font-size: 14px;
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.header-step-text {
|
||||
display: flex;
|
||||
font-size: 28px;
|
||||
margin-bottom: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-arrow {
|
||||
width: 48px;
|
||||
height: 20px;
|
||||
margin: 0 12px;
|
||||
background-image: url("~@/assets/icons/vector.svg");
|
||||
}
|
||||
|
||||
.control-cont {
|
||||
padding: 48px 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.menu-cont {
|
||||
padding: 48px 64px;
|
||||
width: max-content;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.menu-cont__left {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.menu-cont__right {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.step-cont,
|
||||
.btn-cont {
|
||||
height: max-content;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.step {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 12px;
|
||||
border: 1px solid #c1c1c1;
|
||||
border-radius: 100%;
|
||||
cursor: pointer;
|
||||
margin-bottom: 16px;
|
||||
position: relative;
|
||||
transition: 0.2s;
|
||||
}
|
||||
|
||||
.step:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.header-close-button {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.2s;
|
||||
opacity: 0.4;
|
||||
background-image: url("~@/assets/icons/close.svg");
|
||||
}
|
||||
|
||||
.step::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
background: #c1c1c1;
|
||||
width: 1.5px;
|
||||
height: 16px;
|
||||
bottom: -17px;
|
||||
}
|
||||
|
||||
.step:last-child::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.active-step {
|
||||
border-color: #ff5c8e;
|
||||
background: #f8e8ed;
|
||||
color: #ec407a;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.active-step::after {
|
||||
background: #ff5c8e;
|
||||
}
|
||||
|
||||
.last-active-step::after {
|
||||
background: #c1c1c1;
|
||||
}
|
||||
|
||||
.dynamic-active-step::after {
|
||||
animation-name: next;
|
||||
animation-duration: 4s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
@keyframes next {
|
||||
from {
|
||||
background: #c1c1c1;
|
||||
}
|
||||
to {
|
||||
background: #ff5c8e;
|
||||
}
|
||||
}
|
||||
|
||||
.header-close-button:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.control-button {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background-position: center;
|
||||
margin: 0 12px;
|
||||
cursor: pointer;
|
||||
transition: 0.2s;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.main-control-button {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
#previous-step {
|
||||
background-image: url("~@/assets/icons/previous-step.svg");
|
||||
}
|
||||
|
||||
#next-step {
|
||||
background-image: url("~@/assets/icons/next-step.svg");
|
||||
}
|
||||
|
||||
#play {
|
||||
background-image: url("~@/assets/icons/play.svg");
|
||||
}
|
||||
|
||||
#pause {
|
||||
background-image: url("~@/assets/icons/pause.svg");
|
||||
}
|
||||
|
||||
.control-button:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.boundary-step {
|
||||
opacity: 0.1;
|
||||
cursor: auto;
|
||||
}
|
||||
BIN
src/assets/fonts/Formular-Black.eot
Normal file
BIN
src/assets/fonts/Formular-Black.ttf
Normal file
BIN
src/assets/fonts/Formular-Black.woff
Normal file
BIN
src/assets/fonts/Formular-Black.woff2
Normal file
BIN
src/assets/fonts/Formular-BlackItalic.eot
Normal file
BIN
src/assets/fonts/Formular-BlackItalic.ttf
Normal file
BIN
src/assets/fonts/Formular-BlackItalic.woff
Normal file
BIN
src/assets/fonts/Formular-BlackItalic.woff2
Normal file
BIN
src/assets/fonts/Formular-Bold.eot
Normal file
BIN
src/assets/fonts/Formular-Bold.ttf
Normal file
BIN
src/assets/fonts/Formular-Bold.woff
Normal file
BIN
src/assets/fonts/Formular-Bold.woff2
Normal file
BIN
src/assets/fonts/Formular-BoldItalic.eot
Normal file
BIN
src/assets/fonts/Formular-BoldItalic.ttf
Normal file
BIN
src/assets/fonts/Formular-BoldItalic.woff
Normal file
BIN
src/assets/fonts/Formular-BoldItalic.woff2
Normal file
BIN
src/assets/fonts/Formular-Italic.eot
Normal file
BIN
src/assets/fonts/Formular-Italic.ttf
Normal file
BIN
src/assets/fonts/Formular-Italic.woff
Normal file
BIN
src/assets/fonts/Formular-Italic.woff2
Normal file
BIN
src/assets/fonts/Formular-Light.eot
Normal file
BIN
src/assets/fonts/Formular-Light.ttf
Normal file
BIN
src/assets/fonts/Formular-Light.woff
Normal file
BIN
src/assets/fonts/Formular-Light.woff2
Normal file
BIN
src/assets/fonts/Formular-LightItalic.eot
Normal file
BIN
src/assets/fonts/Formular-LightItalic.ttf
Normal file
BIN
src/assets/fonts/Formular-LightItalic.woff
Normal file
BIN
src/assets/fonts/Formular-LightItalic.woff2
Normal file
BIN
src/assets/fonts/Formular-Medium.eot
Normal file
BIN
src/assets/fonts/Formular-Medium.ttf
Normal file
BIN
src/assets/fonts/Formular-Medium.woff
Normal file
BIN
src/assets/fonts/Formular-Medium.woff2
Normal file
BIN
src/assets/fonts/Formular-MediumItalic.eot
Normal file
BIN
src/assets/fonts/Formular-MediumItalic.ttf
Normal file
BIN
src/assets/fonts/Formular-MediumItalic.woff
Normal file
BIN
src/assets/fonts/Formular-MediumItalic.woff2
Normal file
BIN
src/assets/fonts/Formular.eot
Normal file
BIN
src/assets/fonts/Formular.ttf
Normal file
BIN
src/assets/fonts/Formular.woff
Normal file
BIN
src/assets/fonts/Formular.woff2
Normal file
1
src/assets/icons/close.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="Слой_1" data-name="Слой 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><style>.cls-1{fill:none;stroke:#330522;stroke-miterlimit:10;stroke-width:10px;}</style></defs><line class="cls-1" x1="21" y1="21" x2="79" y2="79"/><line class="cls-1" x1="21" y1="79" x2="79" y2="21"/></svg>
|
||||
|
After Width: | Height: | Size: 313 B |
1443
src/assets/icons/graph-icons.ai
Normal file
6
src/assets/icons/graphql.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="256px" height="288px" viewBox="0 0 256 288" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||
<g fill-rule="evenodd">
|
||||
<path d="M152.575995,32.9634453 L211.722058,67.1124427 C213.387503,65.348329 215.332859,63.7907081 217.539734,62.5088708 C229.806457,55.4573413 245.452038,59.6441869 252.577021,71.9109101 C259.62855,84.1776333 255.441705,99.8232143 243.174982,106.948197 C240.984974,108.207124 238.684929,109.10784 236.343812,109.66832 L236.343812,177.99302 C238.659582,178.554683 240.934429,179.449686 243.101528,180.695443 C255.441705,187.820426 259.62855,203.466007 252.503568,215.73273 C245.452038,227.999453 229.733004,232.186299 217.46628,225.13477 C215.024105,223.730884 212.902189,221.983449 211.125456,219.988103 L152.340162,253.928803 C153.180519,256.46808 153.635248,259.18431 153.635248,262.008393 C153.635248,276.111452 142.176512,287.64364 128,287.64364 C113.823488,287.64364 102.364752,276.184905 102.364752,262.008393 C102.364752,259.488481 102.726802,257.054441 103.40181,254.755362 L44.2714887,220.615454 C42.6238257,222.34493 40.705394,223.873378 38.5337196,225.13477 C26.193543,232.186299 10.547962,227.999453 3.49643248,215.73273 C-3.55509701,203.466007 0.631748621,187.820426 12.8984718,180.695443 C15.0673397,179.448669 17.3418356,178.553222 19.6561876,177.991646 L19.6561876,109.66832 C17.3150714,109.10784 15.0150257,108.207124 12.8250184,106.948197 C0.558295189,99.8966677 -3.62855044,84.1776333 3.42297904,71.9109101 C10.4745085,59.6441869 26.193543,55.4573413 38.4602662,62.5088708 C40.6551374,63.7837361 42.5913269,65.3313931 44.2507413,67.0836756 L103.41276,32.9254664 C102.730718,30.6154532 102.364752,28.1687503 102.364752,25.6352478 C102.364752,11.4587354 113.823488,0 128,0 C142.176512,0 153.635248,11.4587354 153.635248,25.6352478 C153.635248,28.1826393 153.265258,30.6422768 152.575995,32.9634453 Z M146.413638,43.4848713 L205.700555,77.715193 C203.867899,84.1516888 204.540515,91.2885638 208.137694,97.5461579 C211.746601,103.824151 217.625783,107.985785 224.150543,109.607654 L224.150543,178.017842 C223.818032,178.099463 223.48718,178.187674 223.158201,178.282419 L145.72529,44.1686182 C145.959017,43.9450469 146.18851,43.7170876 146.413638,43.4848713 Z M110.29093,44.1841203 L32.8593279,178.295656 C32.524559,178.19844 32.1878611,178.108015 31.8494573,178.024438 L31.8494573,109.607654 C38.3742168,107.985785 44.2533992,103.824151 47.8623055,97.5461579 C51.466599,91.276189 52.1347497,84.1234583 50.2885426,77.6770157 L109.558747,43.4563434 C109.797913,43.7038019 110.042027,43.9464469 110.29093,44.1841203 Z M135.162749,50.259763 L212.576817,184.340928 C210.844241,185.99279 209.317376,187.91755 208.064241,190.097482 C206.818484,192.264581 205.923481,194.539429 205.361818,196.855198 L50.6395564,196.855198 C50.0779798,194.540846 49.1825333,192.26635 47.9357589,190.097482 C46.6776713,187.931497 45.1539248,186.017428 43.4300297,184.372667 L120.858821,50.2659993 C123.124941,50.9200822 125.520915,51.2704957 128,51.2704957 C130.486952,51.2704957 132.890265,50.9178547 135.162749,50.259763 Z M146.958084,244.737995 L205.860107,210.729899 C205.683398,210.174924 205.525483,209.614096 205.38664,209.048468 L50.6067643,209.048468 C50.5246888,209.380792 50.4360084,209.711472 50.3407792,210.040295 L109.531782,244.215239 C114.192298,239.378545 120.739739,236.373145 128,236.373145 C135.518379,236.373145 142.272352,239.596041 146.958084,244.737995 Z" fill="#E535AB"></path>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
1
src/assets/icons/next-step.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="Слой_1" data-name="Слой 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><style>.cls-1{fill:none;stroke:#3d3d3d;stroke-miterlimit:10;stroke-width:8px;}</style></defs><path class="cls-1" d="M19.12,16.64V83.36a1.51,1.51,0,0,0,2.26,1.3L78.89,51.3a1.51,1.51,0,0,0,0-2.6L21.38,15.34A1.51,1.51,0,0,0,19.12,16.64Z"/><rect class="cls-1" x="77.88" y="25" width="3" height="50" rx="1.5" transform="translate(158.75 100) rotate(180)"/></svg>
|
||||
|
After Width: | Height: | Size: 464 B |
1
src/assets/icons/one.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="Слой_1" data-name="Слой 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><style>.cls-1{fill:none;stroke:#330522;stroke-linecap:round;stroke-miterlimit:10;stroke-width:10px;}</style></defs><path class="cls-1" d="M57,74V26.92c0-.3-.49-.44-.76-.22L39,40.2"/></svg>
|
||||
|
After Width: | Height: | Size: 295 B |
1
src/assets/icons/pause.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="Слой_1" data-name="Слой 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><style>.cls-1{fill:none;stroke:#3d3d3d;stroke-miterlimit:10;stroke-width:8px;}</style></defs><rect class="cls-1" x="15" y="8" width="21" height="84" rx="1.5"/><rect class="cls-1" x="64" y="8" width="21" height="84" rx="1.5"/></svg>
|
||||
|
After Width: | Height: | Size: 338 B |
1
src/assets/icons/play.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="Слой_1" data-name="Слой 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><style>.cls-1{fill:none;stroke:#3d3d3d;stroke-miterlimit:10;stroke-width:8px;}</style></defs><path class="cls-1" d="M22.06,9.81V90.19a1.8,1.8,0,0,0,2.71,1.56L94.05,51.56a1.8,1.8,0,0,0,0-3.12L24.77,8.25A1.8,1.8,0,0,0,22.06,9.81Z"/></svg>
|
||||
|
After Width: | Height: | Size: 343 B |
1
src/assets/icons/previous-step.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="Слой_1" data-name="Слой 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><style>.cls-1{fill:none;stroke:#3d3d3d;stroke-miterlimit:10;stroke-width:8px;}</style></defs><path class="cls-1" d="M80.88,83.36V16.64a1.51,1.51,0,0,0-2.26-1.3L21.11,48.7a1.51,1.51,0,0,0,0,2.6L78.62,84.66A1.51,1.51,0,0,0,80.88,83.36Z"/><rect class="cls-1" x="19.12" y="25" width="3" height="50" rx="1.5"/></svg>
|
||||
|
After Width: | Height: | Size: 418 B |
1
src/assets/icons/vector.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="Слой_1" data-name="Слой 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 100"><defs><style>.cls-1{fill:none;stroke:#3d3d3d;stroke-miterlimit:10;stroke-width:10px;}</style></defs><line class="cls-1" x1="226" y1="50" x2="14" y2="50"/><path class="cls-1" d="M158.24,20.56a106.34,106.34,0,0,0,29.44,20A106.47,106.47,0,0,0,225.36,50"/><path class="cls-1" d="M158.24,79.44A106.23,106.23,0,0,1,225.36,50"/></svg>
|
||||
|
After Width: | Height: | Size: 428 B |
1
src/assets/icons/zero.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="Слой_1" data-name="Слой 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><style>.cls-1{fill:#330522;}</style></defs><path class="cls-1" d="M50,32c6.82,0,12.58,8.24,12.58,18S56.82,68,50,68,37.42,59.76,37.42,50,43.18,32,50,32m0-10C37.53,22,27.42,34.54,27.42,50S37.53,78,50,78,72.58,65.46,72.58,50,62.47,22,50,22Z"/></svg>
|
||||
|
After Width: | Height: | Size: 353 B |
254
src/components/Graph.vue
Normal file
@@ -0,0 +1,254 @@
|
||||
<template>
|
||||
<svg
|
||||
class="render-area"
|
||||
ref="renderer"
|
||||
@dblclick="createNode"
|
||||
@click="deactivateAllNodes"
|
||||
>
|
||||
<g>
|
||||
<line
|
||||
class="line-default"
|
||||
v-for="(link, idx) in links"
|
||||
:key="idx"
|
||||
:x1="nodes[link.source].x"
|
||||
:y1="nodes[link.source].y"
|
||||
:x2="nodes[link.target].x"
|
||||
:y2="nodes[link.target].y"
|
||||
:class="{ 'line-active': link.selected }"
|
||||
/>
|
||||
</g>
|
||||
<g>
|
||||
<circle
|
||||
class="node-default"
|
||||
r="25"
|
||||
v-for="(node, idx) in nodes"
|
||||
:key="idx"
|
||||
:cx="node.x"
|
||||
:cy="node.y"
|
||||
:class="{ 'node-active': node.selected }"
|
||||
@dblclick.prevent="createNode"
|
||||
@click.right.prevent="removeNode(idx)"
|
||||
@click.left.stop="selectNode(idx)"
|
||||
@mousedown="dragNode($event, idx)"
|
||||
@mouseup="dropNode()"
|
||||
/>
|
||||
</g>
|
||||
<g>
|
||||
<text
|
||||
class="node-text"
|
||||
v-for="(node, idx) in nodes"
|
||||
:key="idx"
|
||||
:x="node.x - 4"
|
||||
:y="node.y + 5.5"
|
||||
>
|
||||
{{ idx }}
|
||||
</text>
|
||||
</g>
|
||||
</svg>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref, watch } from "vue";
|
||||
import Linker from "./core/linker";
|
||||
import EulerCycle from "./core/euler";
|
||||
|
||||
export default {
|
||||
name: "Graph",
|
||||
props: {
|
||||
stepData: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
// Const
|
||||
const RADIUS = 25;
|
||||
|
||||
// Reactive
|
||||
const renderer = ref(null);
|
||||
|
||||
const nodes = ref([]);
|
||||
const links = ref([]);
|
||||
|
||||
const drag = ref({
|
||||
id: null,
|
||||
offsetX: null,
|
||||
offsetY: null,
|
||||
});
|
||||
|
||||
const linker = new Linker();
|
||||
const euler = new EulerCycle();
|
||||
|
||||
// Watchers
|
||||
watch(
|
||||
() => props.stepData,
|
||||
(sd) => {
|
||||
nodes.value.forEach((e) => {
|
||||
e.selected = false;
|
||||
});
|
||||
|
||||
links.value.forEach((e) => {
|
||||
e.selected = false;
|
||||
});
|
||||
|
||||
if (Object.keys(sd).length !== 0) {
|
||||
nodes.value[sd.source].selected = true;
|
||||
nodes.value[sd.target].selected = true;
|
||||
links.value.forEach((e) => {
|
||||
if (e.source === sd.source && e.target === sd.target)
|
||||
e.selected = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// Methods
|
||||
const hasntIntersections = (node) => {
|
||||
return nodes.value.every((current) => {
|
||||
return (
|
||||
(RADIUS * 2) ** 2 <
|
||||
(node.x - current.x) ** 2 + (node.y - current.y) ** 2
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const deactivateAllNodes = () =>
|
||||
nodes.value.forEach((e) => {
|
||||
e.selected = false;
|
||||
linker.reset();
|
||||
});
|
||||
|
||||
const activateNodes = (ids) =>
|
||||
ids.forEach((e) => (nodes.value[e].selected = true));
|
||||
|
||||
const deactivateNodes = (ids) =>
|
||||
ids.forEach((e) => (nodes.value[e].selected = false));
|
||||
|
||||
// Nodes
|
||||
const createNode = ({ offsetX, offsetY }) => {
|
||||
const newNode = {
|
||||
x: offsetX,
|
||||
y: offsetY,
|
||||
selected: false,
|
||||
};
|
||||
|
||||
if (hasntIntersections(newNode)) {
|
||||
nodes.value.push(newNode);
|
||||
}
|
||||
};
|
||||
|
||||
const removeNode = (id) => {
|
||||
links.value = links.value.filter(
|
||||
(e) => e.source !== id && e.target !== id
|
||||
);
|
||||
nodes.value = nodes.value.filter((el, idx) => idx !== id);
|
||||
emit("isEuler", []);
|
||||
};
|
||||
|
||||
const selectNode = (id) => {
|
||||
if (linker.sourceEmpty()) {
|
||||
linker.source = id;
|
||||
activateNodes([id]);
|
||||
} else {
|
||||
linker.target = id;
|
||||
deactivateNodes([linker.source, linker.target]);
|
||||
createLink(linker.load());
|
||||
linker.reset();
|
||||
}
|
||||
};
|
||||
|
||||
// Node drag and drop
|
||||
const dragNode = ({ offsetX, offsetY }, id) => {
|
||||
drag.value = {
|
||||
id,
|
||||
offsetX: offsetX - nodes.value[id].x,
|
||||
offsetY: offsetY - nodes.value[id].y,
|
||||
};
|
||||
|
||||
renderer.value.addEventListener("mousemove", nodeMove);
|
||||
};
|
||||
|
||||
const dropNode = () => {
|
||||
drag.value = {
|
||||
id: null,
|
||||
offsetX: null,
|
||||
offsetY: null,
|
||||
};
|
||||
|
||||
renderer.value.removeEventListener("mousemove", nodeMove);
|
||||
};
|
||||
|
||||
const nodeMove = ({ offsetX, offsetY }) => {
|
||||
const d = drag.value;
|
||||
|
||||
nodes.value[d.id].x = offsetX - d.offsetX;
|
||||
nodes.value[d.id].y = offsetY - d.offsetY;
|
||||
};
|
||||
|
||||
// Links
|
||||
const createLink = (link) => {
|
||||
links.value.push(Object.assign({ selected: false }, link));
|
||||
|
||||
euler.loadLinks([...links.value]);
|
||||
|
||||
if (euler.check()) emit("isEuler", euler.find());
|
||||
else emit("isEuler", []);
|
||||
};
|
||||
|
||||
return {
|
||||
nodes,
|
||||
links,
|
||||
renderer,
|
||||
createNode,
|
||||
selectNode,
|
||||
removeNode,
|
||||
deactivateAllNodes,
|
||||
dragNode,
|
||||
dropNode,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
circle,
|
||||
line {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
line {
|
||||
stroke-width: 4px;
|
||||
}
|
||||
|
||||
.node-text {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
font-family: "Formular";
|
||||
user-select: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.node-default {
|
||||
fill: #3d3d3d;
|
||||
}
|
||||
|
||||
.node-active {
|
||||
fill: #ec407a;
|
||||
}
|
||||
|
||||
.line-default {
|
||||
stroke: #3d3d3d;
|
||||
}
|
||||
|
||||
.line-active {
|
||||
stroke: #a81043;
|
||||
}
|
||||
</style>
|
||||
92
src/components/Matrix.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<div class="matrix-cont">
|
||||
<div class="value-rows-matrix-cont">
|
||||
<div class="value-row-matrix" v-for="ni in size" :key="ni">
|
||||
{{ ni }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="body-matrix">
|
||||
<div class="matrix">
|
||||
<div class="matrix-column" v-for="nj in size" :key="nj">
|
||||
<div class="value-matrix-column">{{ nj }}</div>
|
||||
<div
|
||||
class="matrix-cell"
|
||||
v-for="ni in size"
|
||||
:key="ni"
|
||||
:class="{
|
||||
'active-cell': isActive(ni - 1, nj - 1),
|
||||
'auto-matrix-cell': isAbove(ni - 1, nj - 1),
|
||||
}"
|
||||
@click="change(ni - 1, nj - 1)"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// active-cell - one with background
|
||||
// matrix-cell - default
|
||||
// auto-matrix-cell - without background
|
||||
// active-auto-cell - one
|
||||
|
||||
const newArray = (size) =>
|
||||
Array(size)
|
||||
.fill(0)
|
||||
.map(() => Array(size).fill(0));
|
||||
|
||||
export default {
|
||||
name: "Matrix",
|
||||
props: {
|
||||
size: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
matrix: newArray(this.size),
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
size(val) {
|
||||
this.matrix = newArray(val);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
isActive(i, j) {
|
||||
return this.matrix[i][j] === 1;
|
||||
},
|
||||
isAbove(i, j) {
|
||||
return i > j;
|
||||
},
|
||||
change(i, j) {
|
||||
const val = this.matrix[i][j] === 1 ? 0 : 1;
|
||||
|
||||
this.matrix[i][j] = val;
|
||||
|
||||
if (i != j) this.matrix[j][i] = val;
|
||||
|
||||
this.checkEuler();
|
||||
},
|
||||
checkEuler() {
|
||||
const isValid = this.matrix.reduce(
|
||||
(res, current) => {
|
||||
const relations = current.reduce((sum, i) => sum + i);
|
||||
|
||||
res.sum += relations;
|
||||
res.even &= !(relations % 2);
|
||||
|
||||
return res;
|
||||
},
|
||||
{ sum: 0, even: 1 }
|
||||
);
|
||||
|
||||
const result = isValid.sum && isValid.even;
|
||||
|
||||
this.$emit("valid", result);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
91
src/components/core/euler.js
Normal file
@@ -0,0 +1,91 @@
|
||||
class EulerCycle {
|
||||
#graph = [];
|
||||
|
||||
constructor(graph) {
|
||||
this.#graph = graph;
|
||||
}
|
||||
|
||||
loadLinks(graph) {
|
||||
this.#graph = graph;
|
||||
}
|
||||
|
||||
find() {
|
||||
const stack = [];
|
||||
const tour = [];
|
||||
|
||||
stack.push(this.#graph[0].source);
|
||||
|
||||
while (stack.length > 0) {
|
||||
const v = stack[stack.length - 1];
|
||||
const degree = this.#getDegree(v);
|
||||
|
||||
if (degree === 0) {
|
||||
stack.pop();
|
||||
tour.push(v);
|
||||
} else {
|
||||
const { index, edge } = this.#getEdgeAndIndex(v);
|
||||
this.#graph.splice(index, 1);
|
||||
stack.push(v === edge.source ? edge.target : edge.source);
|
||||
}
|
||||
}
|
||||
|
||||
return this.#toVector(tour);
|
||||
}
|
||||
|
||||
check() {
|
||||
let counter = new Map();
|
||||
|
||||
for (const vertex of this.#graph) {
|
||||
if (counter.has(vertex.source))
|
||||
counter.set(vertex.source, counter.get(vertex.source) + 1);
|
||||
else counter.set(vertex.source, 1);
|
||||
|
||||
if (counter.has(vertex.target))
|
||||
counter.set(vertex.target, counter.get(vertex.target) + 1);
|
||||
else counter.set(vertex.target, 1);
|
||||
}
|
||||
|
||||
for (const degree of counter.values()) if (degree % 2 !== 0) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#getDegree(v) {
|
||||
let degree = 0;
|
||||
|
||||
for (const { source, target } of this.#graph) {
|
||||
if (v === source || v === target) degree++;
|
||||
}
|
||||
|
||||
return degree;
|
||||
}
|
||||
|
||||
#getEdgeAndIndex(v) {
|
||||
let edge = {};
|
||||
let index = -1;
|
||||
|
||||
for (let i = 0; i < this.#graph.length; i++) {
|
||||
if (v === this.#graph[i].source || v === this.#graph[i].target) {
|
||||
edge = this.#graph[i];
|
||||
index = i;
|
||||
}
|
||||
}
|
||||
|
||||
return { index, edge };
|
||||
}
|
||||
|
||||
#toVector(arr) {
|
||||
const result = [];
|
||||
|
||||
for (let i = 1; i < arr.length; i++) {
|
||||
result.push({
|
||||
source: arr[i - 1],
|
||||
target: arr[i],
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export default EulerCycle;
|
||||
45
src/components/core/linker.js
Normal file
@@ -0,0 +1,45 @@
|
||||
class Linker {
|
||||
#source = null;
|
||||
#target = null;
|
||||
|
||||
constructor() {}
|
||||
|
||||
get source() {
|
||||
return this.#source;
|
||||
}
|
||||
|
||||
set source(src) {
|
||||
if (this.#source === null) this.#source = src;
|
||||
}
|
||||
|
||||
get target() {
|
||||
return this.#target;
|
||||
}
|
||||
|
||||
set target(trg) {
|
||||
if (trg === this.#source) return;
|
||||
if (this.#target === null) this.#target = trg;
|
||||
}
|
||||
|
||||
sourceEmpty() {
|
||||
return this.#source === null;
|
||||
}
|
||||
|
||||
targetEmpty() {
|
||||
return this.#target === null;
|
||||
}
|
||||
|
||||
load() {
|
||||
return {
|
||||
source: this.#source,
|
||||
target: this.#target,
|
||||
};
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.#source = null;
|
||||
this.#target = null;
|
||||
}
|
||||
}
|
||||
|
||||
export default Linker;
|
||||
5
src/main.js
Normal file
@@ -0,0 +1,5 @@
|
||||
import { createApp } from "vue";
|
||||
import router from "./router";
|
||||
import App from "./App.vue";
|
||||
|
||||
createApp(App).use(router).mount("#app");
|
||||
23
src/router/index.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import { createRouter, createWebHistory } from "vue-router";
|
||||
|
||||
import Home from "../views/Home.vue";
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: "/",
|
||||
name: "Home",
|
||||
component: Home,
|
||||
},
|
||||
{
|
||||
path: "/view",
|
||||
name: "view",
|
||||
component: () => import(/* webpackChunkName: "view" */ "../views/View.vue"),
|
||||
},
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHistory(process.env.BASE_URL),
|
||||
routes,
|
||||
});
|
||||
|
||||
export default router;
|
||||
64
src/views/Home.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div class="wrapper">
|
||||
<div class="input-cont">
|
||||
<div class="text-input-cont">размер матрицы:</div>
|
||||
<div class="number">
|
||||
<button class="number-minus" type="button" @click="sub">-</button>
|
||||
<input type="number" v-model="size" />
|
||||
<button class="number-plus" type="button" @click="add">+</button>
|
||||
</div>
|
||||
</div>
|
||||
<matrix
|
||||
:size="size"
|
||||
@valid="isValid = $event"
|
||||
@save="matrix = $event"
|
||||
></matrix>
|
||||
<router-link
|
||||
to="/view"
|
||||
class="creation-button"
|
||||
:class="{ 'disabled-button': !isValid }"
|
||||
>
|
||||
СОЗДАТЬ ГРАФ
|
||||
</router-link>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Matrix from "../components/Matrix.vue";
|
||||
|
||||
export default {
|
||||
name: "Home",
|
||||
components: {
|
||||
Matrix,
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener("keydown", (evt) => {
|
||||
if (evt.keyCode === 38) this.add();
|
||||
if (evt.keyCode === 40) this.sub();
|
||||
});
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
size: 5,
|
||||
isValid: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
add() {
|
||||
if (this.size < 30) this.size++;
|
||||
},
|
||||
sub() {
|
||||
if (this.size > 1) this.size--;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
size(val) {
|
||||
this.size = Math.min(Math.max(1, val), 30);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import "~@/assets/css/creation.css";
|
||||
</style>
|
||||
145
src/views/View.vue
Normal file
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<div class="menu-cont">
|
||||
<div class="step-cont">
|
||||
<div
|
||||
class="step"
|
||||
v-for="cs in stepsCount"
|
||||
:key="cs"
|
||||
:class="{ 'active-step last-active-step': cs - 1 === currentStep }"
|
||||
>
|
||||
{{ cs }}
|
||||
</div>
|
||||
<!-- <div class="step active-step">2</div>
|
||||
<div class="step active-step last-active-step">3</div>
|
||||
<div class="step">1</div>
|
||||
<div class="step">4</div> -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="wrapper">
|
||||
<header>
|
||||
<div></div>
|
||||
<div class="header-step-cont">
|
||||
<div class="header-step-text">
|
||||
<div class="header-vertex">
|
||||
{{ stepExists ? steps[currentStep].source : "-" }}
|
||||
</div>
|
||||
<div class="header-arrow"></div>
|
||||
<div class="header-vertex">
|
||||
{{ stepExists ? steps[currentStep].target : "-" }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-step-description">
|
||||
шаг {{ stepExists ? currentStep + 1 : "-" }} /
|
||||
{{ stepExists ? stepsCount : "-" }}
|
||||
</div>
|
||||
</div>
|
||||
<router-link to="/">
|
||||
<div class="header-close-button"></div>
|
||||
</router-link>
|
||||
</header>
|
||||
<div class="graph-cont">
|
||||
<graph @isEuler="getSteps" :stepData="currentStepData"></graph>
|
||||
</div>
|
||||
<div class="control-cont">
|
||||
<div class="control-button" id="previous-step" @click="prevStep"></div>
|
||||
<div
|
||||
class="main-control-button control-button"
|
||||
id="pause"
|
||||
v-if="played"
|
||||
@click="stop"
|
||||
></div>
|
||||
<div
|
||||
class="main-control-button control-button"
|
||||
id="play"
|
||||
v-else
|
||||
:class="{ 'boundary-step': !stepExists }"
|
||||
@click="play"
|
||||
></div>
|
||||
|
||||
<div class="control-button" id="next-step" @click="nextStep"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed, ref } from "vue";
|
||||
import Graph from "../components/Graph.vue";
|
||||
|
||||
export default {
|
||||
name: "View",
|
||||
components: {
|
||||
Graph,
|
||||
},
|
||||
setup() {
|
||||
// Const
|
||||
let timer = null;
|
||||
|
||||
// Reactive
|
||||
const steps = ref([]);
|
||||
const currentStep = ref(0);
|
||||
const currentStepData = ref({});
|
||||
const played = ref(false);
|
||||
|
||||
// Computed
|
||||
const stepExists = computed(() => {
|
||||
return steps.value.length > 0;
|
||||
});
|
||||
|
||||
const stepsCount = computed(() => {
|
||||
return steps.value.length;
|
||||
});
|
||||
|
||||
// Methods
|
||||
const getSteps = (data) => {
|
||||
steps.value = data;
|
||||
currentStep.value = 0;
|
||||
currentStepData.value = steps.value[currentStep.value];
|
||||
};
|
||||
|
||||
const nextStep = () => {
|
||||
if (currentStep.value >= stepsCount.value - 1) currentStep.value = 0;
|
||||
else currentStep.value++;
|
||||
|
||||
currentStepData.value = steps.value[currentStep.value];
|
||||
};
|
||||
|
||||
const prevStep = () => {
|
||||
if (currentStep.value <= 0) currentStep.value = stepsCount.value - 1;
|
||||
else currentStep.value--;
|
||||
|
||||
currentStepData.value = steps.value[currentStep.value];
|
||||
};
|
||||
|
||||
const play = () => {
|
||||
if (!stepExists.value) return;
|
||||
|
||||
played.value = true;
|
||||
timer = setInterval(nextStep, 1000);
|
||||
};
|
||||
|
||||
const stop = () => {
|
||||
played.value = false;
|
||||
|
||||
clearInterval(timer);
|
||||
};
|
||||
|
||||
return {
|
||||
steps,
|
||||
stepsCount,
|
||||
stepExists,
|
||||
currentStep,
|
||||
currentStepData,
|
||||
played,
|
||||
getSteps,
|
||||
nextStep,
|
||||
prevStep,
|
||||
play,
|
||||
stop,
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import "~@/assets/css/graph.css";
|
||||
</style>
|
||||