Initial commit

This commit is contained in:
Круглицкий Никита Витальевич 2025-09-24 18:40:33 +06:00
commit b06ca2d99e
31 changed files with 11361 additions and 0 deletions

31
.gitignore vendored Normal file
View File

@ -0,0 +1,31 @@
# 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?
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions

3
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}

942
.yarn/releases/yarn-4.10.2.cjs vendored Executable file

File diff suppressed because one or more lines are too long

22
.yarnrc.yml Normal file
View File

@ -0,0 +1,22 @@
yarnPath: .yarn/releases/yarn-4.10.2.cjs
enableStrictSsl: false
nodeLinker: node-modules
npmRegistryServer: 'https://nexus.services.mts.ru/repository/npm-all/'
unsafeHttpWhitelist:
- '*.mts.ru'
npmScopes:
mts-ds:
npmRegistryServer: 'https://artifactory.mts.ru/artifactory/api/npm/web-designsystem-npm-local/'
servicedesk:
npmRegistryServer: 'https://artifactory.mts.ru/artifactory/api/npm/mts-servicedesk-npm-local/'
stash:
npmRegistryServer: 'https://artifactory.mts.ru/artifactory/api/npm/mts-abp-mts-ui-npm-local/'
mts-ui-next:
npmRegistryServer: 'https://artifactory.mts.ru/artifactory/api/npm/mts-abp-mts-ui-npm-local/'
docflow:
npmRegistryServer: 'https://artifactory.mts.ru/artifactory/api/npm/npm-plf-df-plf-df-demo-autotest-inside-0000s1-npm-local/'

5
README.md Normal file
View File

@ -0,0 +1,5 @@
# Vue 3 + TypeScript + Vite
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
Learn more about the recommended Project Setup and IDE Support in the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).

23
eslint.config.js Normal file
View File

@ -0,0 +1,23 @@
import antfu from '@antfu/eslint-config'
export default antfu({
ignores: [
'src/__generated__',
'scripts',
],
vue: {
vueVersion: 3,
sfcBlocks: false,
},
typescript: true,
formatters: {
css: true,
},
overrides: {
vue: {
'vue/block-order': ['error', {
order: ['template', 'script', 'style'],
}],
},
},
})

13
index.html Normal file
View File

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

41
package.json Normal file
View File

@ -0,0 +1,41 @@
{
"name": "bpmn-test",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vue-tsc -b && vite build",
"preview": "vite preview",
"pack-lint-config": "bpmnlint-pack-config -c ./src/bpmn/.bpmnlintrc -o ./src/bpmn/packed-lint-config.js -t es"
},
"dependencies": {
"@mts-ds/base": "^3.0.0",
"@mts-ds/icons": "^3.0.0",
"@servicedesk/ui-kit": "^2.0.4",
"@stash/hubify": "^0.45.6",
"@stash/icons": "^1.0.0",
"bpmn-js": "^18.6.4",
"bpmn-js-bpmnlint": "^0.23.0",
"bpmn-js-properties-panel": "^5.42.1",
"camunda-bpmn-js": "^5.13.0",
"vue": "^3.5.21"
},
"devDependencies": {
"@antfu/eslint-config": "^5.4.1",
"@types/node": "^24.5.2",
"@vitejs/plugin-vue": "^6.0.1",
"@vue/tsconfig": "^0.8.1",
"bpmnlint-pack-config": "^0.8.0",
"eslint": "^9.36.0",
"eslint-plugin-format": "^1.0.2",
"sass-embedded": "^1.93.0",
"typescript": "~5.8.3",
"vite": "^7.1.6",
"vue-tsc": "^3.0.7"
},
"packageManager": "yarn@4.10.2",
"resolutions": {
"bpmnlint-plugin-service-desk": "portal:/Users/nvkrug/Work/forms/bpmnlint-plugin-service-desk"
}
}

1
public/vite.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

8
src/App.vue Normal file
View File

@ -0,0 +1,8 @@
<template>
<BpmnModeler :diagram-xml="DiagramExample" />
</template>
<script setup lang="ts">
import DiagramExample from './assets/diagrams/approvement_test_1.bpmn?raw'
import BpmnModeler from './components/BpmnModeler.vue'
</script>

View File

@ -0,0 +1,114 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:camunda="http://camunda.org/schema/1.0/bpmn" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:bioc="http://bpmn.io/schema/bpmn/biocolor/1.0" xmlns:color="http://www.omg.org/spec/BPMN/non-normative/color/1.0" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_16l7d92" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.25.0" modeler:executionPlatform="Camunda Platform" modeler:executionPlatformVersion="7.21.0">
<bpmn:process id="approvement_test_1" name="approvement-test" isExecutable="true" camunda:historyTimeToLive="100">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_1ai9etl</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:userTask id="Activity_1bne0zs" name="Согласование руководителем" camunda:assignee="assavinc10">
<bpmn:extensionElements>
<camunda:properties>
<camunda:property name="tags" value="approvement" />
<camunda:property name="serviceKey" value="sd-adapter" />
</camunda:properties>
</bpmn:extensionElements>
<bpmn:incoming>Flow_15bt283</bpmn:incoming>
<bpmn:outgoing>Flow_1e4gjlx</bpmn:outgoing>
</bpmn:userTask>
<bpmn:sequenceFlow id="Flow_1ai9etl" sourceRef="StartEvent_1" targetRef="Activity_05wz618" />
<bpmn:endEvent id="Event_0e1s3ol">
<bpmn:incoming>Flow_1kfmx5m</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1e4gjlx" sourceRef="Activity_1bne0zs" targetRef="Activity_10de6ie" />
<bpmn:userTask id="Activity_10de6ie" name="Согласование HR" camunda:assignee="sa0000jocasta">
<bpmn:extensionElements>
<camunda:properties>
<camunda:property name="tags" value="approvement" />
<camunda:property name="serviceKey" value="sd-adapter" />
</camunda:properties>
<camunda:inputOutput>
<camunda:outputParameter name="lastDecision">${comment}</camunda:outputParameter>
</camunda:inputOutput>
</bpmn:extensionElements>
<bpmn:incoming>Flow_1e4gjlx</bpmn:incoming>
<bpmn:outgoing>Flow_1yxc8vk</bpmn:outgoing>
</bpmn:userTask>
<bpmn:sequenceFlow id="Flow_1yxc8vk" sourceRef="Activity_10de6ie" targetRef="Activity_0i59amn" />
<bpmn:serviceTask id="Activity_0i59amn" name="Смена статуса на &#34;Завершено&#34;" camunda:asyncAfter="true">
<bpmn:extensionElements>
<camunda:inputOutput>
<camunda:inputParameter name="type">save</camunda:inputParameter>
<camunda:inputParameter name="statusDescription">Заявка завершена</camunda:inputParameter>
<camunda:inputParameter name="statusID">solved</camunda:inputParameter>
<camunda:inputParameter name="statusComment">${lastDecision}</camunda:inputParameter>
</camunda:inputOutput>
<camunda:connector>
<camunda:inputOutput>
<camunda:inputParameter name="signalKey">update_status</camunda:inputParameter>
<camunda:inputParameter name="serviceKey">sd-adapter</camunda:inputParameter>
</camunda:inputOutput>
<camunda:connectorId>kafka-connector</camunda:connectorId>
</camunda:connector>
</bpmn:extensionElements>
<bpmn:incoming>Flow_1yxc8vk</bpmn:incoming>
<bpmn:outgoing>Flow_1kfmx5m</bpmn:outgoing>
</bpmn:serviceTask>
<bpmn:sequenceFlow id="Flow_1kfmx5m" sourceRef="Activity_0i59amn" targetRef="Event_0e1s3ol" />
<bpmn:userTask id="Activity_05wz618" name="Согласование группой" camunda:candidateUsers="approvers" camunda:candidateGroups="6d78cf93-a9f6-4b51-95ed-c85ecbb1dc40">
<bpmn:extensionElements>
<camunda:properties>
<camunda:property name="tags" value="approvement" />
<camunda:property name="serviceKey" value="sd-adapter" />
</camunda:properties>
</bpmn:extensionElements>
<bpmn:incoming>Flow_1ai9etl</bpmn:incoming>
<bpmn:outgoing>Flow_15bt283</bpmn:outgoing>
</bpmn:userTask>
<bpmn:sequenceFlow id="Flow_15bt283" sourceRef="Activity_05wz618" targetRef="Activity_1bne0zs" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="approvement_test_1">
<bpmndi:BPMNShape id="Event_0e1s3ol_di" bpmnElement="Event_0e1s3ol">
<dc:Bounds x="882" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_10in7yl" bpmnElement="Activity_0i59amn" bioc:stroke="#0d4372" bioc:fill="#bbdefb" color:background-color="#bbdefb" color:border-color="#0d4372">
<dc:Bounds x="720" y="77" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1bne0zs_di" bpmnElement="Activity_1bne0zs">
<dc:Bounds x="400" y="77" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_05m0nz6" bpmnElement="Activity_10de6ie">
<dc:Bounds x="560" y="77" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="152" y="99" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BPMNShape_10f3ez1" bpmnElement="Activity_05wz618">
<dc:Bounds x="240" y="77" width="100" height="80" />
<bpmndi:BPMNLabel />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_1ai9etl_di" bpmnElement="Flow_1ai9etl">
<di:waypoint x="188" y="117" />
<di:waypoint x="240" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1e4gjlx_di" bpmnElement="Flow_1e4gjlx">
<di:waypoint x="500" y="117" />
<di:waypoint x="560" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1yxc8vk_di" bpmnElement="Flow_1yxc8vk">
<di:waypoint x="660" y="117" />
<di:waypoint x="720" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1kfmx5m_di" bpmnElement="Flow_1kfmx5m">
<di:waypoint x="820" y="117" />
<di:waypoint x="882" y="117" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_15bt283_di" bpmnElement="Flow_15bt283">
<di:waypoint x="340" y="117" />
<di:waypoint x="400" y="117" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>

1
src/assets/vue.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>

After

Width:  |  Height:  |  Size: 496 B

9
src/bpmn/.bpmnlintrc Normal file
View File

@ -0,0 +1,9 @@
{
"extends": [
"bpmnlint:recommended",
"plugin:service-desk/recommended"
],
"rules": {
"label-required": "warn"
}
}

View File

@ -0,0 +1,11 @@
import translations from './ru.json'
export default function Translate(template, replacements) {
replacements = replacements || {}
template = translations[template] || template
return template.replace(/\{([^}]+)\}/g, (_, key) => {
return replacements[key] || `{${key}}`
})
}

View File

@ -0,0 +1,5 @@
import Translate from './Translate.js'
export default {
translate: ['value', Translate],
}

View File

@ -0,0 +1,154 @@
{
"Activate create/remove space tool": "Включить инструмент «Управление пространством»",
"Activate global connect tool": "Включить инструмент «Глобальное соединение»",
"Activate hand tool": "Включить инструмент «Рука»",
"Activate lasso tool": "Включить инструмент «Лассо»",
"Ad-hoc": "Для этого случая",
"Ad-hoc sub-process (collapsed)": "Специальный подпроцесс (свернутый)",
"Ad-hoc sub-process (expanded)": "Специальный подпроцесс (развернутый)",
"Add lane above": "Добавить дорожку сверху",
"Add lane below": "Добавить дорожку снизу",
"Add text annotation": "Добавить текстовую аннотацию",
"Align elements": "Выровнять элементы",
"Align elements bottom": "Выровнять элементы по нижней границе",
"Align elements center": "Выровнять элементы по центру",
"Align elements left": "Выровнять элементы по левому краю",
"Align elements middle": "Выровнять элементы посередине",
"Align elements right": "Выровнять элементы по правому краю",
"Align elements top": "Выровнять элементы по верхней границе",
"Append compensation activity": "Добавить компенсирующее действие",
"Append conditional intermediate catch event": "Добавить обработчик промежуточного события-условия",
"Append end event": "Добавить конечное событие",
"Append gateway": "Добавить шлюз",
"Append intermediate/boundary event": "Добавить промежуточное/граничное событие",
"Append message intermediate catch event": "Добавить обработчик промежуточного события-сообщения",
"Append receive task": "Добавить задачу получения сообщения",
"Append signal intermediate catch event": "Добавить обработчик промежуточного события-сигнала",
"Append task": "Добавить задачу",
"Append text annotation": "Добавить комментарий",
"Append timer intermediate catch event": "Добавить обработчик промежуточного события-таймера",
"Business rule task": "Задача выполнения бизнес-правила",
"Call activity": "Действие «Вызов»",
"Cancel boundary event": "Граничное событие-отмена",
"Cancel end event": "Конечное событие-отмена",
"Change element": "Изменить элемент",
"Change type": "Изменить тип",
"Collection": "Коллекция",
"Compensation boundary event": "Граничное событие-компенсация",
"Compensation end event": "Конечное событие-компенсация",
"Compensation intermediate throw event": "Инициатор промежуточного события-компенсации",
"Compensation start event": "Начальное событие-компенсация",
"Complex gateway": "Комплексный шлюз",
"Conditional boundary event": "Граничное событие-условие",
"Conditional boundary event (non-interrupting)": "Граничное событие-условие (без прерываний)",
"Conditional flow": "Граничный поток",
"Conditional intermediate catch event": "Обработчик промежуточного события-условия",
"Conditional start event": "Начальное событие-условие",
"Conditional start event (non-interrupting)": "Начальное событие-условие (без прерываний)",
"Connect to other element": "Соединить с другим элементом",
"Connect using association": "Соединить с использованием ассоциации",
"Connect using data input association": "Соединить с использованием ассоциации ввода данных",
"Connect using sequence/message flow or association": "Соединить с использованием потока управления/сообщений или ассоциации",
"Create data object reference": "Создать объект данных",
"Create data store reference": "Создать хранилище данных",
"Create end event": "Создать конечное событие",
"Create expanded sub-process": "Создать развернутый подпроцесс",
"Create gateway": "Создать шлюз",
"Create group": "Создать группу",
"Create intermediate/boundary event": "Создать промежуточное/граничное событие",
"Create pool/participant": "Создать пул/участника",
"Create start event": "Создать начальное событие",
"Create task": "Создать задачу",
"Data object reference": "Объект данных",
"Data store reference": "Хранилище данных",
"Default flow": "Поток по умолчанию",
"Delete": "Удалить",
"Distribute elements horizontally": "Расположить элементы по горизонтали",
"Distribute elements vertically": "Расположить элементы по вертикали",
"Divide into three lanes": "Разделить на три дорожки",
"Divide into two lanes": "Разделить на две дорожки",
"Empty pool/participant": "Пустой пул",
"Empty pool/participant (removes content)": "Пустой пул (удаляет контент)",
"End event": "Конечное событие",
"Error boundary event": "Граничное событие-ошибка",
"Error end event": "Конечное событие-ошибка",
"Error start event": "Начальное событие-ошибка",
"Escalation boundary event": "Граничное событие-эскалация",
"Escalation boundary event (non-interrupting)": "Граничное событие-эскалация (без прерываний)",
"Escalation end event": "Конечное событие-эскалация",
"Escalation intermediate throw event": "Инициатор промежуточного события-эскалации",
"Escalation start event": "Начальное событие-эскалация",
"Escalation start event (non-interrupting)": "Начальное событие-эскалация (без прерываний)",
"Event sub-process": "Подпроцесс по событию",
"Event-based gateway": "Шлюз по событиям",
"Exclusive gateway": "Шлюз «исключающее или»",
"Inclusive gateway": "Шлюз «или»",
"Intermediate throw event": "Промежуточное событие-инициатор",
"Link intermediate catch event": "Обработчик промежуточного события-ссылки",
"Link intermediate throw event": "Инициатор промежуточного события-ссылки",
"Loop": "Цикл",
"Manual task": "Задача, выполняемая вручную",
"Message boundary event": "Граничное событие-сообщение",
"Message boundary event (non-interrupting)": "Граничное событие-сообщение (без прерываний)",
"Message end event": "Конечное событие-сообщение",
"Message intermediate catch event": "Обработчик промежуточного события-сообщения",
"Message intermediate throw event": "Инициатор промежуточного события-сообщения",
"Message start event": "Начальное событие-сообщение",
"Message start event (non-interrupting)": "Начальное событие-сообщение (без прерываний)",
"Open {element}": "Открыть {element}",
"Parallel gateway": "Шлюз «и»",
"Parallel multi-instance": "Параллельное выполнение",
"Participant multiplicity": "Множество участников",
"Receive task": "Задача получения сообщения",
"Remove": "Удалить",
"Script task": "Задача-сценарий",
"Search in diagram": "Поиск на диаграмме",
"Send task": "Задача отправки сообщения",
"Sequence flow": "Поток управления",
"Sequential multi-instance": "Последовательное выполнение",
"Service task": "Задача-сервис",
"Signal boundary event": "Граничное событие-сигнал",
"Signal boundary event (non-interrupting)": "Граничное событие-сигнал (без прерываний)",
"Signal end event": "Конечное событие-сигнал",
"Signal intermediate catch event": "Обработчик промежуточного события-сигнала",
"Signal intermediate throw event": "Инициатор промежуточного события-сигнала",
"Signal start event": "Начальное событие-сигнал",
"Signal start event (non-interrupting)": "Начальное событие-сигнал (без прерываний)",
"Start event": "Начальное событие",
"Sub-process": "Подпроцесс",
"Sub-process (collapsed)": "Свернутый подпроцесс",
"Sub-process (expanded)": "Развернутый подпроцесс",
"Task": "Задача",
"Terminate end event": "Конечное событие-остановка",
"Timer boundary event": "Граничное событие-таймер",
"Timer boundary event (non-interrupting)": "Граничное событие-таймер (без прерываний)",
"Timer intermediate catch event": "Обработчик промежуточного события-таймера",
"Timer start event": "Начальное событие-таймер",
"Timer start event (non-interrupting)": "Начальное событие-таймер (без прерываний)",
"Transaction": "Транзакция",
"User task": "Задача, выполняемая пользователем",
"User Task": "Задача, выполняемая пользователем",
"correcting missing bpmnElement on {plane} to {rootElement}": "Исправление отсутствующего bpmn-элемента на {plane} у {rootElement}",
"element {element} referenced by {referenced}#{property} not yet drawn": "Элемент {element}, на который ссылается {referenced}#{property}, еще не отрисован",
"failed to import {element}": "Не удалось импортировать {element}",
"flow elements must be children of pools/participants": "Элементы потока должны быть дочерними элементами пула/участников",
"missing {semantic}#attachedToRef": "Отсутствует {semantic}#attachedToRef",
"multiple DI elements defined for {element}": "Определено множество DI элементов для {element}",
"no bpmnElement referenced in {element}": "нет ни одного bpmn-элемента, ссылающегося на {element}",
"no diagram to display": "Нет диаграммы для отображения",
"no process or collaboration to display": "Нет процесса или взаимодействия для отображения",
"Approvement": "Согласование",
"Create element": "Создать элемент",
"Append element": "Добавить элемент",
"Open minimap": "Карта",
"Close minimap": "Закрыть",
"Tasks": "Задачи",
"Sub-processes": "Подпроцессы",
"Gateways": "Шлюзы",
"Events": "События",
"Data": "Данные",
"Process": "Процесс",
"Function name": "Название функции",
"Loading...": "Загрузка..."
}

View File

@ -0,0 +1,76 @@
export default function PaletteProvider(bpmnFactory, create, elementFactory, palette, translate) {
this.bpmnFactory = bpmnFactory
this.create = create
this.elementFactory = elementFactory
this.translate = translate
palette.registerProvider(this)
}
PaletteProvider.$inject = [
'bpmnFactory',
'create',
'elementFactory',
'palette',
'translate',
]
PaletteProvider.prototype.getPaletteEntries = function () {
const {
bpmnFactory,
create,
elementFactory,
translate,
} = this
function createApprovementUserTask(event) {
const extensionElements = bpmnFactory.create('bpmn:ExtensionElements', {
values: [
bpmnFactory.create('camunda:Properties', {
values: [
bpmnFactory.create('camunda:Property', {
name: 'tags',
value: 'approvement',
}),
bpmnFactory.create('camunda:Property', {
name: 'serviceKey',
value: 'sd-adapter',
}),
],
}),
],
})
const businessObject = bpmnFactory.create('bpmn:UserTask', {
name: translate('Approvement'),
extensionElements,
})
const shape = elementFactory.createShape({
type: 'bpmn:UserTask',
businessObject,
})
create.start(event, shape)
}
return {
'sd-separator-top': {
group: 'sd',
separator: true,
},
'create.approvement': {
group: 'sd',
className: 'bpmn-icon-user sd-badge',
title: translate('Approvement'),
action: {
dragstart: createApprovementUserTask,
click: createApprovementUserTask,
},
},
'sd-separator-bottom': {
group: 'sd',
separator: true,
},
}
}

View File

@ -0,0 +1,68 @@
import { is } from 'bpmn-js/lib/util/ModelUtil.js'
import FunctionSelect from '@/bpmn/modules/service-desk/PropertiesProvider/FunctionSelect.js'
import { ensureExtensionProperty } from '@/bpmn/utils/extesion-properties.js'
export default function PropertiesProvider(propertiesPanel, translate, commandStack, bpmnFactory) {
this.translate = translate
this.commandStack = commandStack
this.bpmnFactory = bpmnFactory
propertiesPanel.registerProvider(500, this)
}
PropertiesProvider.$inject = [
'propertiesPanel',
'translate',
'commandStack',
'bpmnFactory',
]
PropertiesProvider.prototype.getGroups = function (element) {
const { translate, commandStack, bpmnFactory } = this
return (groups) => {
if (!is(element, 'bpmn:ScriptTask')) {
return groups
}
return groups.map((group) => {
if (group.id === 'CamundaPlatform__ExtensionProperties') {
const propertyName = 'functionName'
const id = `${element.id}-extensionProperty-${propertyName}`
const property = ensureExtensionProperty(bpmnFactory, commandStack, element, propertyName)
const groupItem = group.items.find((item) => {
return !!item.entries.find(entry => entry.property.name === propertyName)
})
const entry = {
id,
component: FunctionSelect,
property,
}
if (groupItem) {
groupItem.id = id
groupItem.label = translate('Function name')
groupItem.entries = [entry]
}
else {
group.items.unshift({
id,
label: translate('Function name'),
entries: [
{
id,
component: FunctionSelect,
property,
},
],
autoFocusEntry: `${id}-name`,
})
}
}
return group
})
}
}

View File

@ -0,0 +1,61 @@
import { SelectEntry } from '@bpmn-io/properties-panel'
import { useEffect, useState } from '@bpmn-io/properties-panel/preact/hooks'
import { useService } from 'bpmn-js-properties-panel'
import { html } from 'htm/preact'
export default function FunctionSelect(props) {
const { element, id, property } = props
const commandStack = useService('commandStack')
const translate = useService('translate')
const getValue = () => {
return property?.value
}
const setValue = (value) => {
return commandStack.execute('element.updateModdleProperties', {
element,
moddleElement: property,
properties: {
value,
},
})
}
const [functionList, setFunctionList] = useState([])
const [fetching, setFetching] = useState(false)
useEffect(() => {
function fetch() {
setFetching(true)
new Promise((resolve) => {
setTimeout(resolve, 5000)
}).then(() => {
setFunctionList(Array.from({ length: 10 }, (v, k) => `Function ${k}`))
}).finally(() => {
setFetching(false)
})
}
fetch()
}, [setFunctionList])
const getOptions = () => {
return functionList.map(functionName => ({
label: functionName,
value: functionName.toLowerCase().replace(/\s/, '_'),
}))
}
return html`<${SelectEntry}
id=${id}
element=${element}
label=${translate('Function name')}
description=${fetching ? translate('Loading...') : undefined}
disabled=${fetching}
getValue=${getValue}
setValue=${setValue}
getOptions=${getOptions}
/>`
}

View File

@ -0,0 +1,8 @@
import Palette from './PaletteProvider.js'
import PropertiesProvider from './PropertiesProvider.js'
export default {
__init__: ['sdPaletteProvider', 'sdPropertiesProvider'],
sdPaletteProvider: ['type', Palette],
sdPropertiesProvider: ['type', PropertiesProvider],
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,68 @@
export function ensureExtensionProperty(bpmnFactory, commandStack, element, propertyName) {
const commands = []
const businessObject = element.businessObject
let extensionElements = businessObject.get('extensionElements')
if (!extensionElements) {
extensionElements = bpmnFactory.create(
'bpmn:ExtensionElements',
{ values: [] },
)
extensionElements.parent = businessObject
commands.push({
cmd: 'element.updateModdleProperties',
context: {
element,
moddleElement: businessObject,
properties: { extensionElements },
},
})
}
let properties = (extensionElements?.values ?? [])[0]
if (!properties) {
const parent = extensionElements
properties = bpmnFactory.create('camunda:Properties', {
values: [],
})
properties.parent = parent
commands.push({
cmd: 'element.updateModdleProperties',
context: {
element,
moddleElement: extensionElements,
properties: {
values: [...extensionElements.get('values'), properties],
},
},
})
}
let property = properties?.values?.find(property => property.name === propertyName)
if (!property) {
property = bpmnFactory.create('camunda:Property', { name: propertyName })
property.parent = properties
commands.push({
cmd: 'element.updateModdleProperties',
context: {
element,
moddleElement: properties,
properties: {
values: [...properties.get('values'), property],
},
},
})
}
commandStack.execute('properties-panel.multi-command-executor', commands)
return property
}

View File

@ -0,0 +1,125 @@
<template>
<div class="bpmn-modeler">
<div ref="container" class="bpmn-modeler__viewer" />
<div ref="properties-panel" class="bpmn-modeler__properties-panel" />
<div class="bpmn-modeler__toolbar">
<button @click="openXmlDialog">
XML
</button>
</div>
<dialog ref="xmlDialog" class="bpmn-modeler__xml-dialog">
<pre>{{ currentXml }}</pre>
</dialog>
</div>
</template>
<script lang="ts" setup>
import LintModule from 'bpmn-js-bpmnlint'
import BpmnModeler from 'camunda-bpmn-js/lib/camunda-platform/Modeler'
import { ref, shallowRef, useTemplateRef, watchEffect } from 'vue'
import I18nModule from '@/bpmn/modules/i18n'
import SdElementsModule from '@/bpmn/modules/service-desk'
import bpmnlintConfig from '@/bpmn/packed-lint-config'
const props = defineProps<{
diagramXml: string
}>()
const containerRef = useTemplateRef('container')
const propertiesPanelRef = useTemplateRef('properties-panel')
const xmlDialogRef = useTemplateRef('xmlDialog')
const modeler = shallowRef<BpmnModeler>()
const currentXml = ref(props.diagramXml)
watchEffect(() => {
if (modeler.value) {
modeler.value.destroy()
modeler.value = undefined
}
if (!containerRef.value || !propertiesPanelRef.value)
return
modeler.value = new BpmnModeler({
container: containerRef.value,
propertiesPanel: {
parent: propertiesPanelRef.value,
},
additionalModules: [
SdElementsModule,
I18nModule,
LintModule,
],
linting: {
bpmnlint: bpmnlintConfig,
},
})
modeler.value.importXML(props.diagramXml)
})
async function openXmlDialog() {
try {
const { xml } = await modeler.value?.saveXML({ format: true })
currentXml.value = xml
xmlDialogRef.value?.showModal()
}
catch {}
}
</script>
<style lang="scss">
.bpmn-modeler {
height: 100%;
flex: 1;
display: grid;
grid-template-columns: 1fr 350px;
grid-template-rows: 1fr 30px;
grid-template-areas: 'viewer properties' 'toolbar toolbar';
&__viewer {
grid-area: viewer;
svg {
outline: none;
}
}
&__properties-panel {
overflow-x: auto;
grid-area: properties;
}
&__toolbar {
grid-area: toolbar;
background-color: lightgray;
> button {
height: 100%;
border: none;
outline: none;
cursor: pointer;
}
}
&__xml-dialog {
width: 80%;
max-height: 80%;
outline: none;
&::backdrop {
backdrop-filter: blur(10px);
}
pre {
white-space: pre-wrap;
word-wrap: break-word;
}
}
}
</style>

11
src/main.ts Normal file
View File

@ -0,0 +1,11 @@
import { createApp } from 'vue'
import App from './App.vue'
// import 'bpmn-js/dist/assets/diagram-js.css';
// import 'bpmn-js/dist/assets/bpmn-js.css';
// import 'bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css';
import 'bpmn-js-bpmnlint/dist/assets/css/bpmn-js-bpmnlint.css'
import 'camunda-bpmn-js/dist/assets/camunda-platform-modeler.css'
import './style.css'
createApp(App).mount('#app')

65
src/style.css Normal file
View File

@ -0,0 +1,65 @@
:root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
html,
body {
height: 100%;
}
#app {
height: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
.entry.sd-badge {
position: relative;
&::after {
content: 'SD';
position: absolute;
display: block;
top: 0;
right: 0;
font-size: 11px;
font-weight: 700;
border-radius: 9999px;
padding: 0 3px;
line-height: 14px;
background-color: #ff0032;
color: white;
pointer-events: none;
}
}
.bjs-powered-by {
display: none !important;
}

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

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

15
tsconfig.app.json Normal file
View File

@ -0,0 +1,15 @@
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
}

7
tsconfig.json Normal file
View File

@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}

25
tsconfig.node.json Normal file
View File

@ -0,0 +1,25 @@
{
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
"target": "ES2023",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"erasableSyntaxOnly": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["vite.config.ts"]
}

21
vite.config.ts Normal file
View File

@ -0,0 +1,21 @@
import * as fs from 'node:fs'
import { resolve } from 'node:path'
import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vite'
// https://vite.dev/config/
export default defineConfig({
plugins: [vue()],
server: {
https: {
cert: fs.readFileSync('/Users/nvkrug/Work/localhost.pem'),
key: fs.readFileSync('/Users/nvkrug/Work/localhost-key.pem'),
},
},
resolve: {
alias: [
{ find: '@', replacement: resolve(__dirname, './src') },
{ find: '@mts-icons', replacement: resolve(__dirname, './node_modules/@mts-ds/icons/web2') },
],
},
})

6688
yarn.lock Normal file

File diff suppressed because it is too large Load Diff