first commit
1
frontend/.npmrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
registry=https://repo.micord.ru/repository/npm-all/
|
||||
71
frontend/angular.json
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"webbpm-frontend": {
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"projectType": "application",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist",
|
||||
"index": "src/index.html",
|
||||
"main": "src/ts/main.ts",
|
||||
"tsConfig": "src/tsconfig.json",
|
||||
"polyfills": "src/ts/polyfills.ts",
|
||||
"assets": [
|
||||
"src/resources"
|
||||
],
|
||||
"styles": [
|
||||
],
|
||||
"scripts": [
|
||||
"node_modules/jquery/dist/jquery.min.js",
|
||||
"node_modules/moment/min/moment-with-locales.js",
|
||||
"node_modules/moment-timezone/builds/moment-timezone-with-data.min.js",
|
||||
"node_modules/eonasdan-bootstrap-datetimepicker/build/js/bootstrap-datetimepicker.min.js",
|
||||
"node_modules/selectize/dist/js/standalone/selectize.min.js",
|
||||
"node_modules/downloadjs/download.min.js"
|
||||
]
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"extractCss": true,
|
||||
"namedChunks": false,
|
||||
"aot": true,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "webbpm-frontend:build"
|
||||
},
|
||||
"configurations": {}
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "webbpm-frontend:build"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [],
|
||||
"exclude": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultProject": "ervu-dashboard"
|
||||
}
|
||||
10
frontend/bs-config.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"port": 8000,
|
||||
"open": false,
|
||||
"files": [
|
||||
"./**/*.{html,htm,css,js}"
|
||||
],
|
||||
"server": {
|
||||
"baseDir": "./"
|
||||
}
|
||||
}
|
||||
24
frontend/index.html
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Дашборд</title>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/png" href="src/resources/img/logo.png"/>
|
||||
<link rel="stylesheet" href="src/resources/css/style.css"/>
|
||||
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||
<script src="node_modules/reflect-metadata/Reflect.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
<script src="systemjs.config.js"></script>
|
||||
<script>
|
||||
System.import('webbpm').catch(function (err) {
|
||||
console.error(err);
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body webbpm class="webbpm ervu_dashboard">
|
||||
<div class="progress"></div>
|
||||
</body>
|
||||
</html>
|
||||
12
frontend/index.webpack.html
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Дашборд</title>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/png" href="src/resources/img/logo.png"/>
|
||||
</head>
|
||||
<body webbpm class="webbpm ervu_dashboard">
|
||||
<div class="progress"></div>
|
||||
</body>
|
||||
</html>
|
||||
10689
frontend/package-lock.json
generated
Normal file
101
frontend/package.json
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
{
|
||||
"name": "ervu-dashboard",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"lite": "node ./node_modules/lite-server/bin/lite-server",
|
||||
"cleanup": "npm run cleanup-ngc && node ./node_modules/rimraf/bin ./build ./dist",
|
||||
"cleanup-ngc": "node ./node_modules/rimraf/bin ./src/ts/**/*.js ./src/ts/**/*.json ./src/ts/page.routing.ts",
|
||||
"cleanup-and-ngc": "npm run cleanup && npm run ngc",
|
||||
"ngc": "node --max-old-space-size=14336 ./node_modules/@angular/compiler-cli/src/main -p tsconfig.aot.json",
|
||||
"build-webpack": "node --max-old-space-size=14336 ./node_modules/webpack/bin/webpack --config webpack.aot.config.js --progress --profile",
|
||||
"save-ts-metadata": "node save.ts.metadata.js",
|
||||
"tsc": "node ./node_modules/typescript/bin/tsc",
|
||||
"tsc-watch": "node ./node_modules/typescript/bin/tsc --watch",
|
||||
"ts-watch": "node node_modules/cross-env/dist/bin/cross-env.js TSC_NONPOLLING_WATCHER=true npm run tsc-watch",
|
||||
"ts": "npm install && npm run tsc",
|
||||
"compile": "npm run ts-watch",
|
||||
"install-compile": "npm install && npm run ts-watch"
|
||||
},
|
||||
"dependencies": {
|
||||
"@angular/animations": "7.2.15",
|
||||
"@angular/common": "7.2.15",
|
||||
"@angular/compiler": "7.2.15",
|
||||
"@angular/core": "7.2.15",
|
||||
"@angular/forms": "7.2.15",
|
||||
"@angular/http": "7.2.15",
|
||||
"@angular/platform-browser": "7.2.15",
|
||||
"@angular/platform-browser-dynamic": "7.2.15",
|
||||
"@angular/router": "7.2.15",
|
||||
"@ng-bootstrap/ng-bootstrap": "4.1.1",
|
||||
"@webbpm/base-package": "3.178.2",
|
||||
"ag-grid-angular": "29.0.0-micord.4",
|
||||
"ag-grid-community": "29.0.0-micord.4",
|
||||
"angular-calendar": "0.28.28",
|
||||
"autonumeric": "4.5.10-cg",
|
||||
"bootstrap": "4.3.1",
|
||||
"bootstrap-icons": "1.10.3",
|
||||
"cadesplugin_api": "2.0.4-micord.1",
|
||||
"chart.js": "3.8.0-cg.1",
|
||||
"chartjs-adapter-moment": "1.0.0",
|
||||
"core-js": "2.4.1",
|
||||
"date-fns": "2.29.3",
|
||||
"downloadjs": "1.4.8",
|
||||
"eonasdan-bootstrap-datetimepicker": "4.17.47-micord.4",
|
||||
"esmarttokenjs": "2.2.1-cg",
|
||||
"font-awesome": "4.7.0",
|
||||
"google-libphonenumber": "3.0.9",
|
||||
"inputmask": "5.0.5-cg.2",
|
||||
"jquery": "3.3.1",
|
||||
"js-year-calendar": "1.0.0-cg.2",
|
||||
"jsgantt-improved": "2.0.10-cg",
|
||||
"moment": "2.17.1",
|
||||
"moment-timezone": "0.5.11",
|
||||
"ngx-cookie": "3.0.1",
|
||||
"ngx-international-phone-number": "1.0.6",
|
||||
"ngx-toastr": "10.2.0-cg",
|
||||
"popper.js": "1.14.7",
|
||||
"reflect-metadata": "0.1.13",
|
||||
"rxjs": "6.4.0",
|
||||
"rxjs-compat": "6.4.0",
|
||||
"selectize": "0.12.4-cg.10",
|
||||
"systemjs": "0.21.4",
|
||||
"systemjs-plugin-babel": "0.0.25",
|
||||
"tslib": "1.9.3",
|
||||
"zone.js": "0.8.29"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-optimizer": "0.13.9",
|
||||
"@angular-devkit/core": "7.3.9",
|
||||
"@angular/cli": "7.3.9",
|
||||
"@angular/compiler-cli": "7.2.15",
|
||||
"@angular/platform-server": "7.2.15",
|
||||
"@babel/core": "7.9.6",
|
||||
"@babel/preset-env": "7.9.6",
|
||||
"@types/bootstrap": "3.3.39",
|
||||
"@types/jquery": "2.0.49",
|
||||
"@types/node": "7.0.5",
|
||||
"@types/selectize": "0.12.33",
|
||||
"angular-router-loader": "0.8.5",
|
||||
"angular2-template-loader": "0.6.2",
|
||||
"babel-loader": "8.1.0",
|
||||
"codelyzer": "5.2.1",
|
||||
"copy-webpack-plugin": "5.0.3",
|
||||
"cross-env": "5.2.1",
|
||||
"css-loader": "2.1.0",
|
||||
"del": "2.2.2",
|
||||
"file-loader": "3.0.1",
|
||||
"html-webpack-plugin": "4.5.2",
|
||||
"lite-server": "2.3.0",
|
||||
"mini-css-extract-plugin": "0.6.0",
|
||||
"mkdirp": "0.5.1",
|
||||
"raw-loader": "1.0.0",
|
||||
"style-loader": "0.23.1",
|
||||
"terser-webpack-plugin": "1.2.4",
|
||||
"tslint": "5.13.1",
|
||||
"typescript": "3.2.4",
|
||||
"typescript-parser": "2.6.1-cg-fork",
|
||||
"webpack": "4.32.2",
|
||||
"webpack-bundle-analyzer": "3.3.2",
|
||||
"webpack-cli": "3.3.2"
|
||||
}
|
||||
}
|
||||
113
frontend/pom.xml
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>ru.micord.ervu</groupId>
|
||||
<artifactId>dashboard</artifactId>
|
||||
<version>1.8.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<groupId>ru.micord.ervu.dashboard</groupId>
|
||||
<artifactId>frontend</artifactId>
|
||||
<packaging>war</packaging>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.google.code.maven-replacer-plugin</groupId>
|
||||
<artifactId>replacer</artifactId>
|
||||
<version>1.5.3</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>replace-version-in-url</id>
|
||||
<phase>process-resources</phase>
|
||||
<goals>
|
||||
<goal>replace</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<includes>
|
||||
<include>${basedir}/src/resources/app-config.json</include>
|
||||
<include>${basedir}/dist/src/resources/app-config.json</include>
|
||||
<include>${basedir}/src/resources/app.version</include>
|
||||
<include>${basedir}/dist/src/resources/app.version</include>
|
||||
</includes>
|
||||
<replacements>
|
||||
<replacement>
|
||||
<token>%project.version%</token>
|
||||
<value>${project.version}</value>
|
||||
</replacement>
|
||||
<replacement>
|
||||
<token>%enable.version.in.url%</token>
|
||||
<value>${enable.version.in.url}</value>
|
||||
</replacement>
|
||||
</replacements>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>dev</id>
|
||||
<activation>
|
||||
<activeByDefault>true</activeByDefault>
|
||||
</activation>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-war-plugin</artifactId>
|
||||
<configuration>
|
||||
<copyWebResources>false</copyWebResources>
|
||||
<webResources>
|
||||
<resource>
|
||||
<directory>${basedir}</directory>
|
||||
<includes>
|
||||
<include>src/resources/**/*</include>
|
||||
<include>build_dev/**/*</include>
|
||||
<include>node_modules/**/*</include>
|
||||
<include>index.html</include>
|
||||
<include>systemjs.config.js</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</webResources>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>compile-ts</id>
|
||||
<activation>
|
||||
<activeByDefault>false</activeByDefault>
|
||||
</activation>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>prod</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-war-plugin</artifactId>
|
||||
<configuration>
|
||||
<copyWebResources>false</copyWebResources>
|
||||
<webResources>
|
||||
<resource>
|
||||
<directory>${basedir}/dist</directory>
|
||||
</resource>
|
||||
</webResources>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
<profile>
|
||||
<id>enable-version-in-url</id>
|
||||
<properties>
|
||||
<enable.version.in.url>true</enable.version.in.url>
|
||||
</properties>
|
||||
</profile>
|
||||
</profiles>
|
||||
</project>
|
||||
24
frontend/preview.html
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Дашборд</title>
|
||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/png" href="src/resources/img/logo.png"/>
|
||||
<link rel="stylesheet" href="src/resources/css/style.css"/>
|
||||
|
||||
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||
<script src="node_modules/reflect-metadata/Reflect.js"></script>
|
||||
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||
<script src="systemjs.preview.config.js"></script>
|
||||
<script>
|
||||
System.import('preview').catch(function (err) {
|
||||
console.error(err);
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body preview-container id="webbpm-angular-application-container" class="webbpm ervu_dashboard">
|
||||
<div class="progress"></div>
|
||||
</body>
|
||||
</html>
|
||||
72
frontend/save.ts.metadata.js
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
#!/usr/bin/env node
|
||||
var tsp = require("typescript-parser");
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var ts = require("typescript");
|
||||
|
||||
var parser = new tsp.TypescriptParser();
|
||||
var excludedDirs = [
|
||||
'generated-sources'
|
||||
];
|
||||
|
||||
var walkFileTree = function (dir, action) {
|
||||
if (typeof action !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
fs.readdirSync(dir).forEach(function (file) {
|
||||
var path = dir + "/" + file;
|
||||
var stat = fs.statSync(path);
|
||||
var extension = ".ts";
|
||||
if (stat && stat.isDirectory() && excludedDirs.indexOf(file) === -1) {
|
||||
walkFileTree(path, action);
|
||||
}
|
||||
else if (path.indexOf(extension, path.length - extension.length) !== -1) {
|
||||
action(null, path);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var dateInLong = Date.now();
|
||||
var arr = [];
|
||||
|
||||
var basePath = path.resolve(__dirname, "src/ts/");
|
||||
walkFileTree(basePath, function (err, file) {
|
||||
var content = fs.readFileSync(file).toString();
|
||||
var jsonStructure = parser.parseTypescript(ts.createSourceFile(
|
||||
file,
|
||||
content,
|
||||
ts.ScriptTarget.Latest,
|
||||
true,
|
||||
ts.ScriptKind.TS
|
||||
),
|
||||
'/');
|
||||
jsonStructure['packageName'] = path.relative(path.resolve(__dirname, "src/ts/"),jsonStructure['filePath']);
|
||||
jsonStructure['imports'].forEach( function (val) {
|
||||
if (val.libraryName.startsWith(".")) {
|
||||
val['libraryName'] = path.resolve(path.dirname(jsonStructure['filePath']), val['libraryName']);
|
||||
val['libraryName'] = path.relative(path.resolve(__dirname, "src/ts/"), val['libraryName']);
|
||||
val['libraryName'] = path.dirname(val['libraryName']).split(path.sep).join(".");
|
||||
}
|
||||
});
|
||||
delete jsonStructure['filePath'];
|
||||
jsonStructure['packageName'] = path.dirname(jsonStructure['packageName']).split(path.sep).join( ".");
|
||||
arr.push(jsonStructure);
|
||||
});
|
||||
var cache = [];
|
||||
|
||||
fs.writeFileSync("./../.studio/typescript.metadata.json",
|
||||
JSON.stringify(arr, function (key, value) {
|
||||
if (typeof value === 'object' && value !== null) {
|
||||
if (cache.indexOf(value) !== -1) {
|
||||
// Circular reference found, discard key
|
||||
return;
|
||||
}
|
||||
// Store value in our collection
|
||||
cache.push(value);
|
||||
}
|
||||
return value;
|
||||
}));
|
||||
|
||||
cache = null;
|
||||
console.log("typescript parse time = " + (Date.now() - dateInLong));
|
||||
20
frontend/src/resources/app-config.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"electronic_sign.esmart_extension_url": "",
|
||||
"electronic_sign.tsp_address": "",
|
||||
"filter_cleanup_interval_hours": 720,
|
||||
"filter_cleanup_check_period_minutes": 30,
|
||||
"auth_method": "form",
|
||||
"enable.version.in.url": "false",
|
||||
"backend.context": "dashboard",
|
||||
"guard.confirm_exit": false,
|
||||
"message_service_error_timeout": "",
|
||||
"message_service_warning_timeout": "",
|
||||
"message_service_success_timeout": "",
|
||||
"message_service_info_timeout": "",
|
||||
"jivo_chat_widget_api_url": "https://code.jivo.ru/widget/{ID}",
|
||||
"jivo_chat_widget_enabled": false,
|
||||
"password_pattern": "^((?=(.*\\d){1,})(?=.*[a-zа-яё])(?=.*[A-ZА-ЯЁ]).{8,})$",
|
||||
"password_pattern_error": "Пароль должен содержать заглавные или прописные буквы и как минимум 1 цифру",
|
||||
"show.client.errors": false,
|
||||
"available_task.single_fetch": true
|
||||
}
|
||||
1
frontend/src/resources/app.version
Normal file
|
|
@ -0,0 +1 @@
|
|||
1.0.0-SNAPSHOT
|
||||
1584
frontend/src/resources/css/components-app.css
Normal file
1528
frontend/src/resources/css/components-dashboard.css
Normal file
9
frontend/src/resources/css/external/ngx-treeview/dropdown-treeview-select.component.scss
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
label {
|
||||
margin-bottom: 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.bi {
|
||||
cursor: pointer;
|
||||
margin-right: 0.3rem;
|
||||
}
|
||||
22
frontend/src/resources/css/external/ngx-treeview/dropdown-treeview.component.scss
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
.dropdown {
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
button {
|
||||
width: 100%;
|
||||
margin-right: 0.9rem;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
padding-right: 30px;
|
||||
text-overflow: ellipsis;
|
||||
&::after {
|
||||
position: absolute;
|
||||
right: 0.6rem;
|
||||
margin-top: 0.6rem;
|
||||
}
|
||||
}
|
||||
.dropdown-menu {
|
||||
.dropdown-container {
|
||||
padding: 0 0.6rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
9
frontend/src/resources/css/external/ngx-treeview/treeview-item.component.scss
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
:host {
|
||||
display: block;
|
||||
.treeview-item {
|
||||
white-space: nowrap;
|
||||
.treeview-item {
|
||||
margin-left: 2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
32
frontend/src/resources/css/external/ngx-treeview/treeview.component.scss
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
:host {
|
||||
.treeview-header {
|
||||
.row-filter {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.row-all {
|
||||
.bi {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
.treeview-container {
|
||||
.row-item {
|
||||
margin-bottom: 0.3rem;
|
||||
flex-wrap: nowrap;
|
||||
.bi {
|
||||
cursor: pointer;
|
||||
margin-right: 0.3rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.treeview-container {
|
||||
overflow-y: auto;
|
||||
padding-right: 0.3rem;
|
||||
}
|
||||
|
||||
.treeview-text {
|
||||
padding: 0.3rem 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
342
frontend/src/resources/css/inbox-app.css
Normal file
|
|
@ -0,0 +1,342 @@
|
|||
@font-face {
|
||||
font-family: 'Segoe';
|
||||
src: url('../fonts/Segoe.ttf');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'SegoeSL';
|
||||
src: url('../fonts/SegoeSL.ttf');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'SegoeSB';
|
||||
src: url('../fonts/SegoeSB.ttf');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'SegoeB';
|
||||
src: url('../fonts/SegoeB.ttf');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'SegoeBL';
|
||||
src: url('../fonts/SegoeBL.ttf');
|
||||
}
|
||||
|
||||
.webbpm a {
|
||||
color: var(--color-link);
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
color: var(--color-link-hover);
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
body.webbpm {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: var(--color-text-primary);
|
||||
font-family: 'Segoe';
|
||||
background-color: var(--white);
|
||||
}
|
||||
|
||||
.webbpm .container {
|
||||
padding: 70px 0 0;
|
||||
}
|
||||
|
||||
body.webbpm [id="page"],
|
||||
.webbpm .container .container-inside {
|
||||
font-family: 'Segoe';
|
||||
font-size: var(--size-text-primary);
|
||||
}
|
||||
|
||||
.webbpm .logo {
|
||||
height: auto;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.webbpm .header-logo {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-left: 40px;
|
||||
|
||||
.logo a {
|
||||
background: url('../../../src/resources/img/logo-full.png') no-repeat 0 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.webbpm .header-menu {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-left: auto;
|
||||
margin-right: 40px;
|
||||
& > * {
|
||||
margin-right: 20px;
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
.nav-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--white);
|
||||
font-size: var(--size-text-primary);
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
border-radius: 15px;
|
||||
background-color: var(--color-text-primary);
|
||||
outline: transparent;
|
||||
|
||||
&::after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
background-color: var(--color-link);
|
||||
}
|
||||
}
|
||||
.logout .user-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: var(--black);
|
||||
padding: 4px 20px;
|
||||
background: transparent;
|
||||
cursor: default;
|
||||
|
||||
& > * {
|
||||
display: flex;
|
||||
padding-bottom: 10px;
|
||||
margin: 0 0 10px 0;
|
||||
border-bottom: 1px solid #f1f5f9;
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.user-fio {
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 0;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.user-department {
|
||||
color: #a0b1bc;
|
||||
line-height: 1.2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.webbpm .header {
|
||||
display: flex;
|
||||
font-family: 'Segoe';
|
||||
width: 100%;
|
||||
height: auto;
|
||||
min-height: 70px;
|
||||
border-bottom: 1px solid var(--bg-light);
|
||||
background: var(--white);
|
||||
box-shadow: 0px 15px 20px 0px rgb(0 0 0 / 4%);
|
||||
|
||||
& > div > * {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dropdown-menu.show {
|
||||
top: 69px !important;
|
||||
right: 0px !important;
|
||||
left: auto !important;
|
||||
transform: none !important;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
border-radius: 0 0 10px 10px;
|
||||
background-color: var(--white);
|
||||
box-shadow: 0 8px 12px rgb(77 72 91 / 5%), 0 6px 10px rgb(77 72 91 / 0%);
|
||||
|
||||
.dropdown-menu-inner {
|
||||
max-height: calc(100vh - 140px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
:is(process, admin-menu) .dropdown-menu.show {
|
||||
top: 49px !important;
|
||||
}
|
||||
|
||||
.logout .dropdown-menu.show {
|
||||
width: 300px;
|
||||
}
|
||||
}
|
||||
|
||||
.webbpm .dropdown-menu-inner:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.webbpm .dropdown-item {
|
||||
padding: 4px 20px;
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
color: var(--color-link);
|
||||
background-color: transparent;
|
||||
outline: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.webbpm footer {
|
||||
color: var(--color-text-primary);
|
||||
font-size: var(--size-text-secondary);
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding: 15px 40px;
|
||||
border-top: 1px solid var(--border-light);
|
||||
a {
|
||||
color: var(--color-text-primary);
|
||||
&:hover {
|
||||
color: var(--color-link);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*-------------- Menu tasks -------------- */
|
||||
.webbpm .task-list-tree-panel {
|
||||
padding: 0 40px;
|
||||
.task-list-filter {
|
||||
font-family: 'Segoe';
|
||||
box-shadow: none;
|
||||
|
||||
li:first-of-type {
|
||||
font-family: 'SegoeSB';
|
||||
font-weight: normal;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
li.ontime label div {
|
||||
background-color: #31c980;
|
||||
}
|
||||
|
||||
li.overdue label div {
|
||||
background-color: var(--color-link);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.webbpm .task-list-workplace {
|
||||
padding: 20px 40px 0 40px;
|
||||
}
|
||||
|
||||
.webbpm .task-tbl :is(.tr.task-ontime, .tr.task-overdue) > .td.task::before {
|
||||
top: 24px;
|
||||
background-color: #31c980;
|
||||
}
|
||||
|
||||
.webbpm .task-tbl .tr.task-overdue > .td.task::before {
|
||||
background-color: var(--color-link);
|
||||
}
|
||||
|
||||
.webbpm .task-tbl .thead {
|
||||
display: table-header-group;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.webbpm .task-tbl .th {
|
||||
color: var(--color-text-primary);
|
||||
font-family: 'SegoeSB';
|
||||
font-size: var(--size-text-secondary);
|
||||
font-weight: normal;
|
||||
padding: 9px 12px;
|
||||
border: 0;
|
||||
background: var(--bg-light);
|
||||
}
|
||||
.webbpm .task-tbl .th:first-child {
|
||||
border-top-left-radius: 12px;
|
||||
border-bottom-left-radius: 12px;
|
||||
}
|
||||
.webbpm .task-tbl .th:last-child {
|
||||
border-top-right-radius: 12px;
|
||||
border-bottom-right-radius: 12px;
|
||||
}
|
||||
|
||||
.webbpm .task-tbl .td {
|
||||
color: var(--color-text-primary);
|
||||
font-size: var(--size-text-primary);
|
||||
padding: 16px 12px;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.webbpm .task-tbl .thead ~ .tr {
|
||||
border-color: var(--border-light);
|
||||
border-width: 1px 0 0 0;
|
||||
border-style: solid;
|
||||
}
|
||||
.webbpm .task-tbl .thead + .tr {
|
||||
border-top-color: transparent;
|
||||
}
|
||||
|
||||
.webbpm .task-tbl .thead ~ .tr:hover {
|
||||
border-top-color: transparent;
|
||||
border-bottom-color: transparent;
|
||||
}
|
||||
.webbpm .task-tbl .thead ~ .tr:hover +.tr {
|
||||
border-top-color: transparent;
|
||||
}
|
||||
|
||||
.webbpm .task-tbl .thead ~ .tr:hover .td {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
.webbpm .task-tbl .thead ~ .tr:hover .td:first-child {
|
||||
border-top-left-radius: 12px;
|
||||
border-bottom-left-radius: 12px;
|
||||
}
|
||||
.webbpm .task-tbl .thead ~ .tr:hover .td:last-child {
|
||||
border-top-right-radius: 12px;
|
||||
border-bottom-right-radius: 12px;
|
||||
}
|
||||
/*------------- end Menu tasks ----------- */
|
||||
|
||||
/*----------------- Login ---------------- */
|
||||
.webbpm :is(.form-signin, .form-signup, .confirm) {
|
||||
color: var(--color-text-primary);
|
||||
width: 560px;
|
||||
padding: 60px 80px;
|
||||
margin: 30px auto;
|
||||
border: 0;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0px 0px 30px 2px rgb(77 72 91 / 12%);
|
||||
background-color: var(--white);
|
||||
}
|
||||
|
||||
.webbpm :is(.form-signin, .form-signup) .row.title {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.webbpm .form-signin h1,
|
||||
.webbpm .form-signin h2,
|
||||
.webbpm .form-signup h2,
|
||||
.webbpm .confirm h2 {
|
||||
font-family: 'SegoeB';
|
||||
font-size: 32px;
|
||||
text-align: left;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.webbpm :is(.form-signin, .form-signup, .confirm) .logo {
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
right: 0;
|
||||
width: 145px;
|
||||
height: 40px;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.webbpm .form-signin .row.registration > * + *,
|
||||
.webbpm .form-signin .login-btn-box .password,
|
||||
.webbpm .form-signin .login-btn-box .btn + .btn {
|
||||
margin-left: 20px;
|
||||
}
|
||||
/*--------------- end Login -------------- */
|
||||
357
frontend/src/resources/css/inbox-dashboard.css
Normal file
|
|
@ -0,0 +1,357 @@
|
|||
@font-face {
|
||||
font-family: 'GilroyL';
|
||||
src: url('../fonts/gilroy-light.otf');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'Gilroy';
|
||||
src: url('../fonts/gilroy-regular.ttf');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'GilroyM';
|
||||
src: url('../fonts/gilroy-medium.ttf');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'GilroySB';
|
||||
src: url('../fonts/gilroy-semibold.ttf');
|
||||
}
|
||||
@font-face {
|
||||
font-family: 'GilroyEB';
|
||||
src: url('../fonts/gilroy-extrabold.otf');
|
||||
}
|
||||
|
||||
.webbpm.ervu_dashboard {
|
||||
--black: #070e1a;
|
||||
--color-text-primary: #f4fcff;
|
||||
--color-text-secondary: #b1daea;
|
||||
--color-text-mute: #6b7e9b;
|
||||
--color-primary-5: rgba(244, 252, 255, 0.05);
|
||||
--color-primary-20: rgba(244, 252, 255, 0.2);
|
||||
|
||||
--color-success: #00db5d;
|
||||
--color-error: #f91e11;
|
||||
--color-dark: #070e1a;
|
||||
--color-dark-20: rgba(7, 14, 26, 0.2);
|
||||
--color-dark-40: rgba(7, 14, 26, 0.4);
|
||||
--color-tooltip: rgba(8, 40, 59, 0.8);
|
||||
|
||||
--btn-border: #00f0ff;
|
||||
--btn-bg: linear-gradient(40deg, rgba(8, 131, 198, 1) 0%, rgba(32, 181, 219, 1) 100%);
|
||||
--btn-shadow: 0 20px 40px -20px rgba(11, 175, 218, 0.4);
|
||||
|
||||
--border-light: rgba(8, 131, 198, 0.6);
|
||||
--border-dark: rgba(1, 83, 111, 1);
|
||||
--bg-blur-40: blur(20px);
|
||||
--bg-blur-20: blur(10px);
|
||||
--bg-shadow: 4px 4px 2px 0 rgba(3, 20, 36, 0.4);
|
||||
--bg-block:
|
||||
linear-gradient(180deg, rgba(8, 131, 198, 0) 0%, rgba(8, 131, 198, 0.07) 33%, rgba(8, 131, 198, 0.2) 100%),
|
||||
linear-gradient(180deg, rgba(19, 46, 55, 0.16) 0%, rgba(19, 46, 55, 0.32) 100%),
|
||||
radial-gradient(ellipse at 50% 50%, rgba(0, 0, 0, 0.8) 0%, rgba(0, 0, 0, 0) 80%);
|
||||
--bg-group: rgba(8, 131, 198, 0.25);
|
||||
/*conic-gradient(from 90deg at 100% 0%, rgba(0, 240, 255, 0.6) 0%, rgba(0, 191, 255, 0.24) 34%, rgba(0, 191, 255, 1) 100%),*/
|
||||
/*
|
||||
linear-gradient(180deg, rgba(8, 131, 198, 0) 0%, rgba(8, 131, 198, 0.4) 100%),
|
||||
linear-gradient(0deg, rgba(19, 46, 55, 0.08) 0%, rgba(19, 46, 55, 0.16) 100%),
|
||||
linear-gradient(204deg, rgba(0, 191, 255, 0.5) 0%, rgba(0, 191, 255, 0) 50%);
|
||||
*/
|
||||
--bg-brick: rgba(9, 91, 120, 0.6);
|
||||
|
||||
--size-text-title: min(1.6vw, 2rem); /*32px*/
|
||||
--size-text-subtitle: min(1.2vw, 1.5rem); /*24px*/
|
||||
--size-text-addprimary: min(1vw, 1.25rem); /*20px*/
|
||||
--size-text-primary: min(0.9vw, 1.125rem); /*18px*/
|
||||
--size-text-secondary: min(0.7vw, 0.875rem); /*14px*/
|
||||
|
||||
--size-num-title: min(3vw, 3.75rem); /*60px*/
|
||||
--size-num-subtitle: min(2.4vw, 3rem); /*48px*/
|
||||
--size-num-addtitle: min(2vw, 2.5rem); /*40px*/
|
||||
--size-num-primary: min(1.6vw, 2rem); /*32px*/
|
||||
--size-num-secondary: min(1.2vw, 1.5rem); /*24px*/
|
||||
|
||||
--indent-huge: min(2.4vw, 3rem); /*48*/
|
||||
--indent-xlarge: min(2vw, 2.5rem); /*40*/
|
||||
--indent-large: min(1.6vw, 2rem); /*32*/
|
||||
--indent-medium: min(1.2vw, 1.5rem); /*24*/
|
||||
--indent-xbase: min(1vw, 1.25rem); /*20*/
|
||||
--indent-base: min(0.8vw, 1rem); /*16*/
|
||||
--indent-small: min(0.6vw, 0.75rem); /*12px*/
|
||||
--indent-mini: min(0.4vw, 0.5rem); /*8px*/
|
||||
--indent-xmini: min(0.2vw, 0.25rem); /*4px*/
|
||||
|
||||
--h-header: min(3.9vw, 4.875rem); /*78*/
|
||||
--w-screen: min(2vw, 2.5rem); /*40*/
|
||||
}
|
||||
|
||||
body.webbpm.ervu_dashboard {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: var(--color-text-primary);
|
||||
font-family: 'Gilroy';
|
||||
background: var(--color-dark) url('../img/bg_image.png') no-repeat center 0 fixed;
|
||||
-webkit-background-size: cover;
|
||||
-moz-background-size: cover;
|
||||
-o-background-size: cover;
|
||||
background-size: cover;
|
||||
}
|
||||
.webbpm.ervu_dashboard .wrapper {
|
||||
background:
|
||||
radial-gradient(circle at 0% 0%, rgba(16, 67, 77, 0.35) 0%, rgba(8, 37, 43, 0) 37%),
|
||||
radial-gradient(circle at 100% 0%, rgba(16, 67, 77, 0.35) 0%, rgba(8, 37, 43, 0) 37%),
|
||||
radial-gradient(circle at 50% 0%, rgba(8, 37, 43, 0.8) 0%, rgba(8, 37, 43, 0.6) 30%, rgba(8, 37, 43, 0) 80%),
|
||||
radial-gradient(circle at 50% 0%, rgba(8, 37, 43, 0.8) 0%, rgba(8, 37, 43, 0) 73%),
|
||||
radial-gradient(circle at 50% 50%, rgba(8, 37, 43, 0) 47%, rgba(3, 22, 26, 1) 100%);
|
||||
}
|
||||
|
||||
.webbpm.ervu_dashboard .container {
|
||||
padding: var(--h-header) 0 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.webbpm.ervu_dashboard .container-inside {
|
||||
position: relative;
|
||||
font-family: 'Gilroy';
|
||||
font-size: var(--size-text-primary);
|
||||
height: 100%;
|
||||
padding: 0 var(--w-screen) var(--indent-mini) var(--w-screen);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.webbpm.ervu_dashboard .header-logo {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin-left: var(--w-screen);
|
||||
}
|
||||
.webbpm.ervu_dashboard .header-logo .logo a {
|
||||
width: 100px;
|
||||
height: var(--h-header);
|
||||
background: url('../../../src/resources/img/logo-full.svg') no-repeat 0 50%;
|
||||
background-size: auto 100%;
|
||||
}
|
||||
.webbpm.ervu_dashboard .header-logo .logo-title {
|
||||
font-family: 'GilroySB';
|
||||
margin-left: min(6vw, 7.5rem); /*120*/
|
||||
}
|
||||
|
||||
.webbpm.ervu_dashboard .header-menu {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-left: auto;
|
||||
margin-right: var(--w-screen);
|
||||
}
|
||||
.webbpm.ervu_dashboard .header-menu process,
|
||||
.webbpm.ervu_dashboard .header-menu process + div,
|
||||
.webbpm.ervu_dashboard .header-menu admin-menu {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.webbpm.ervu_dashboard .header-menu .update-data {
|
||||
display: none;
|
||||
color: var(--color-text-primary);
|
||||
opacity: 0.4;
|
||||
margin-right: 16px;
|
||||
}
|
||||
.webbpm.ervu_dashboard .header-menu .logout {
|
||||
max-width: max-content;
|
||||
}
|
||||
.webbpm.ervu_dashboard .header-menu .logout > button {
|
||||
color: var(--color-text-primary);
|
||||
line-height: 1.3;
|
||||
padding: var(--indent-mini) var(--indent-xbase);
|
||||
border: 1px solid var(--color-success);
|
||||
background-color: var(--color-dark-20);
|
||||
outline: none;
|
||||
}
|
||||
.webbpm.ervu_dashboard .header-menu .logout .nav-link {
|
||||
padding-right: 0;
|
||||
border-radius: var(--indent-medium) 0 0 var(--indent-medium);
|
||||
border-right: 0;
|
||||
}
|
||||
.webbpm.ervu_dashboard .header-menu .logout .exit {
|
||||
border-radius: 0 var(--indent-medium) var(--indent-medium) 0;
|
||||
border-left: 0;
|
||||
}
|
||||
|
||||
.webbpm.ervu_dashboard .header {
|
||||
display: flex;
|
||||
font-family: 'Gilroy';
|
||||
font-size: var(--size-text-primary);
|
||||
width: 100%;
|
||||
height: auto;
|
||||
min-height: var(--h-header);
|
||||
background: transparent;
|
||||
}
|
||||
.webbpm.ervu_dashboard .header > div > * {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.webbpm.ervu_dashboard .header .dropdown-menu.show {
|
||||
top: var(--h-header) !important;
|
||||
right: 0px !important;
|
||||
left: auto !important;
|
||||
transform: none !important;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
border-radius: 0 0 10px 10px;
|
||||
background-color: var(--white);
|
||||
box-shadow: 0 8px 12px rgb(77 72 91 / 5%), 0 6px 10px rgb(77 72 91 / 0%);
|
||||
}
|
||||
.webbpm.ervu_dashboard .header .dropdown-menu.show .dropdown-menu-inner {
|
||||
max-height: calc(100vh - 140px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.webbpm.ervu_dashboard .header :is(process, admin-menu) .dropdown-menu.show {
|
||||
top: 49px !important;
|
||||
}
|
||||
|
||||
.webbpm.ervu_dashboard .header .logout .dropdown-menu.show {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.webbpm.ervu_dashboard .dropdown-menu-inner:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.webbpm.ervu_dashboard .dropdown-item {
|
||||
padding: 4px 20px;
|
||||
}
|
||||
.webbpm.ervu_dashboard .dropdown-item:hover,
|
||||
.webbpm.ervu_dashboard .dropdown-item:focus,
|
||||
.webbpm.ervu_dashboard .dropdown-item:active {
|
||||
color: var(--color-link);
|
||||
background-color: transparent;
|
||||
outline: transparent;
|
||||
}
|
||||
|
||||
/*----------------- Login ---------------- */
|
||||
.webbpm.ervu_dashboard :is(.form-signin, .form-signup, .confirm) {
|
||||
color: var(--color-text-primary);
|
||||
width: 560px;
|
||||
padding: 60px 80px;
|
||||
margin: 30px auto;
|
||||
border: 0;
|
||||
border-radius: 20px;
|
||||
border: 1px solid var(--color-text-secondary);
|
||||
background-color: var(--color-dark-20);
|
||||
box-shadow: var(--bg-shadow);
|
||||
backdrop-filter: var(--bg-blur-40);
|
||||
}
|
||||
|
||||
.webbpm.ervu_dashboard :is(.form-signin, .form-signup) .row.title {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.webbpm.ervu_dashboard .form-signin h1,
|
||||
.webbpm.ervu_dashboard .form-signin h2,
|
||||
.webbpm.ervu_dashboard .form-signup h2,
|
||||
.webbpm.ervu_dashboard .confirm h2 {
|
||||
font-family: 'GilroySB';
|
||||
font-size: 32px;
|
||||
text-align: left;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.webbpm.ervu_dashboard :is(.form-signin, .form-signup, .confirm) .logo {
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
right: 0;
|
||||
width: 145px;
|
||||
height: 40px;
|
||||
background: none;
|
||||
}
|
||||
|
||||
.webbpm.ervu_dashboard .form-signin .row.registration > * + *,
|
||||
.webbpm.ervu_dashboard .form-signin .login-btn-box .password,
|
||||
.webbpm.ervu_dashboard .form-signin .login-btn-box .btn + .btn {
|
||||
margin-left: 20px;
|
||||
}
|
||||
/*--------------- end Login -------------- */
|
||||
|
||||
@supports not selector(::-webkit-scrollbar) {
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--color-text-primary) transparent;
|
||||
}
|
||||
}
|
||||
::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
background-color: transparent;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 8px;
|
||||
background-color: var(--color-text-primary);
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
height: 4px;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
@media ((max-width: 780px) or (orientation: portrait)) {
|
||||
.webbpm.ervu_dashboard {
|
||||
--size-text-title: 2rem; /*32px*/
|
||||
--size-text-subtitle: 1.5rem; /*24px*/
|
||||
--size-text-addprimary: 1.25rem; /*20px*/
|
||||
--size-text-primary: 1.125rem; /*18px*/
|
||||
--size-text-secondary: 0.875rem; /*14px*/
|
||||
|
||||
--size-num-title: 3.75rem; /*60px*/
|
||||
--size-num-subtitle: 3rem; /*48px*/
|
||||
--size-num-addtitle: 2.5rem; /*40px*/
|
||||
--size-num-primary: 2rem; /*32px*/
|
||||
--size-num-secondary: 1.5rem; /*24px*/
|
||||
|
||||
--indent-huge: 3rem; /*48*/
|
||||
--indent-xlarge: 2.5rem; /*40*/
|
||||
--indent-large: 2rem; /*32*/
|
||||
--indent-medium: 1.5rem; /*24*/
|
||||
--indent-xbase: 1.25rem; /*20*/
|
||||
--indent-base: 1rem; /*16*/
|
||||
--indent-small: 0.75rem; /*12px*/
|
||||
--indent-mini: 0.5rem; /*8px*/
|
||||
--indent-xmini: 0.25rem; /*4px*/
|
||||
|
||||
--h-header: 4.875rem; /*78*/
|
||||
--w-screen: 1rem; /*40*/
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.webbpm.ervu_dashboard {
|
||||
--size-text-title: 1.75rem; /*28px*/
|
||||
--size-text-primary: 1rem; /*16px*/
|
||||
|
||||
--size-num-title: 3rem; /*48*/
|
||||
--size-num-subtitle: 2.5rem; /*40*/
|
||||
--size-num-addtitle: 2rem; /*32px*/
|
||||
--size-num-primary: 1.75rem; /*28px*/
|
||||
}
|
||||
}
|
||||
|
||||
.webbpm.ervu_dashboard .progress {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border: 5px dotted var(--color-text-primary);
|
||||
border-radius: 100%;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
background: transparent;
|
||||
box-sizing: border-box;
|
||||
animation: rotation 2.5s linear infinite;
|
||||
}
|
||||
.webbpm.ervu_dashboard .loader.modal .modal-content {
|
||||
border: 0;
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
@keyframes rotation {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
846
frontend/src/resources/css/structure.css
Normal file
|
|
@ -0,0 +1,846 @@
|
|||
:root {
|
||||
--white: #ffffff;
|
||||
--black: #000000;
|
||||
--color-text-primary: #404954;
|
||||
--color-link: #1c92ea;
|
||||
--color-link-hover: #1b84d2;
|
||||
--bg-light: #f5f7fa;
|
||||
--bg-secondary: #4c5969;
|
||||
--border-light: #e3e6ed;
|
||||
|
||||
--size-text-primary: 16px;
|
||||
--size-text-secondary: 14px;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
*, *:before, *:after {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body.webbpm .form-signin label {
|
||||
width: 160px;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.webbpm .progress {
|
||||
position: absolute;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
margin-bottom: 0 !important;
|
||||
background: url("../img/progress.gif") no-repeat 0 0;
|
||||
}
|
||||
|
||||
.webbpm > .progress {
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin-top: -30px;
|
||||
margin-left: -30px;
|
||||
}
|
||||
|
||||
.webbpm .search-task-progress-bar {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.webbpm .modal-body .progress,
|
||||
.webbpm .search-task-progress-bar .progress {
|
||||
position: relative;
|
||||
top: 0;
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/*-- common class --*/
|
||||
.webbpm .fl-left {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.webbpm .fl-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.webbpm .anchor {
|
||||
float: none;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.webbpm :is(ul, ol) li {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.webbpm :is(h1, h2, h3) {
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
}
|
||||
.webbpm h1 {
|
||||
font-size: 2.33em;
|
||||
}
|
||||
.webbpm h2 {
|
||||
font-size: 2em;
|
||||
}
|
||||
.webbpm h3 {
|
||||
font-size: 1.5em;
|
||||
}
|
||||
|
||||
.webbpm .table {
|
||||
display: table;
|
||||
}
|
||||
|
||||
.webbpm .tr {
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
.webbpm .td, .webbpm .th {
|
||||
display: table-cell;
|
||||
}
|
||||
|
||||
/*-- layout --*/
|
||||
html, body.webbpm {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
body.webbpm {
|
||||
background-color: #f9f9fa;
|
||||
font-family: Arial;
|
||||
font-size: var(--size-text-secondary);
|
||||
min-height: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.webbpm .wrapper {
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.webbpm .container {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
margin: 0;
|
||||
padding: 67px 0 0;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 50px;
|
||||
border: 0;
|
||||
overflow: hidden;
|
||||
|
||||
[ng-include="taskPageFile"] {
|
||||
position: relative;
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
.container-inside {
|
||||
font-family: Arial;
|
||||
font-size: var(--size-text-secondary);
|
||||
position: relative;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.webbpm footer {
|
||||
position: absolute;
|
||||
color: var(--black);
|
||||
font-size: 12px;
|
||||
bottom: 0;
|
||||
left: 15px;
|
||||
right: 15px;
|
||||
height: 50px;
|
||||
padding: 15px 0;
|
||||
border-top: 1px solid #c1c1c1;
|
||||
background: transparent;
|
||||
|
||||
span + span {
|
||||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
/*--------- TOP MENU ----------*/
|
||||
.webbpm .logo {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
float: left;
|
||||
|
||||
a {
|
||||
width: 200px;
|
||||
height: 67px;
|
||||
position: absolute;
|
||||
background: url("../img/logo.png") no-repeat 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.webbpm .header {
|
||||
position: absolute;
|
||||
color: var(--white);
|
||||
font-family: Corbel;
|
||||
font-size: var(--size-text-secondary);
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 67px;
|
||||
min-height: 67px;
|
||||
line-height: normal;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
background: #b9c0ca;
|
||||
z-index: 997;
|
||||
|
||||
.nav .nav-link {
|
||||
color: var(--white);
|
||||
float: none;
|
||||
display: block;
|
||||
line-height: 60px;
|
||||
padding: 0 15px 0 60px;
|
||||
text-shadow: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.nav .nav-link:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.webbpm .dropdown-menu {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.webbpm .nav .nav-item .dropdown-menu:after {
|
||||
border-bottom: 6px solid #eee;
|
||||
}
|
||||
|
||||
.webbpm .inner {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.webbpm .navbar .nav > * {
|
||||
float: left;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.webbpm .dropdown-menu > div > button {
|
||||
color: #d1dbe5;
|
||||
}
|
||||
}
|
||||
|
||||
/*--------- end - TOP MENU ----------*/
|
||||
.webbpm .user-department,
|
||||
.webbpm .user-info {
|
||||
color: #5a6473;
|
||||
}
|
||||
|
||||
.webbpm .user-info > * {
|
||||
display: inline-block;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.webbpm [log-out] {
|
||||
max-width: 40%;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.webbpm .content {
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.webbpm .inner {
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
overflow-y : scroll;
|
||||
}
|
||||
|
||||
/*--------------task-list------------------*/
|
||||
.task-list {
|
||||
font-size: 0;
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.task-list > div {
|
||||
display: block;
|
||||
float: left;
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
font-size: var(--size-text-secondary);
|
||||
}
|
||||
|
||||
.task-list-tree-panel {
|
||||
width: 20%;
|
||||
background: #e9edf2;
|
||||
border-right: 1px solid #b9c1ca;
|
||||
}
|
||||
|
||||
.task-list-tree-panel .task-list-filter {
|
||||
position: relative;
|
||||
margin-top: 15px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.task-list-tree-panel .task-list-filter li {
|
||||
position: relative;
|
||||
padding: 8px 10px;
|
||||
margin: 2px 10px;
|
||||
}
|
||||
|
||||
.task-list-tree-panel .task-list-filter li::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
bottom: 0px;
|
||||
right: 0;
|
||||
top: 0;
|
||||
border: 1px solid rgb(206, 212, 219);
|
||||
border-radius: 4px;
|
||||
background-color: var(--white);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.task-list-tree-panel .task-list-filter li label:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.task-list-tree-panel .task-list-filter li label {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
z-index: 11;
|
||||
}
|
||||
|
||||
.task-list-tree-panel .task-list-filter li label input[type="radio"] {
|
||||
float: left;
|
||||
margin-top: 2px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.task-list-tree-panel .task-list-filter li label span {
|
||||
float: right;
|
||||
background-color: #bbb;
|
||||
padding: 0px 4px;
|
||||
border-radius: 3px;
|
||||
min-width: 25px;
|
||||
text-align: center;
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
.task-list-tree-panel .task-list-filter li.ontime label span {
|
||||
background-color: #a0c367;
|
||||
}
|
||||
|
||||
.task-list-tree-panel .task-list-filter li.overdue label span {
|
||||
background-color: #fc2d2d;
|
||||
}
|
||||
|
||||
.task-list-header {
|
||||
border-bottom: 1px solid #b9c1ca;
|
||||
background-color: #ccd6e0;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
color: #565968;
|
||||
font-size: var(--size-text-secondary);
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.structure-box {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.task-list-workplace {
|
||||
width: 80%;
|
||||
padding: 15px 15px 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
/*--------------task-list end--------------*/
|
||||
|
||||
/*---------------table-list----------------*/
|
||||
.task-tbl {
|
||||
background: var(--white);
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.task-tbl .td, .task-tbl .th {
|
||||
border: 1px solid #b9c1ca;
|
||||
padding: 10px 15px;
|
||||
}
|
||||
|
||||
.task-tbl .th {
|
||||
color: #565968;
|
||||
}
|
||||
|
||||
.task-tbl .td {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.task-tbl .thead {
|
||||
background: #ccd6e0;
|
||||
}
|
||||
|
||||
.task-tbl > .tr:hover {
|
||||
cursor: pointer;
|
||||
background: #e9edf2;
|
||||
}
|
||||
|
||||
.task-tbl .tr.task-ontime > .td.task,
|
||||
.task-tbl .tr.task-overdue > .td.task {
|
||||
position: relative;
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.task-tbl .tr.task-ontime > .td.task::before,
|
||||
.task-tbl .tr.task-overdue > .td.task::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: 5px;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background-color: #a0c367;
|
||||
border-radius: 10px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.task-tbl .tr.task-overdue > .td.task::before {
|
||||
background-color: #ff0000;
|
||||
}
|
||||
/*----------------table-list end----------------*/
|
||||
|
||||
/*--------------Окно сообщения об ошибке--------------*/
|
||||
.webbpm #toast-container {
|
||||
font-size: 12px;
|
||||
bottom: auto;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
max-height: 95%;
|
||||
}
|
||||
|
||||
.webbpm #toast-container .toast:hover {
|
||||
box-shadow: 0 0 12px #999999;
|
||||
}
|
||||
|
||||
.webbpm #toast-container .ngx-toastr {
|
||||
min-width: 540px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.webbpm #toast-container .toast-error {
|
||||
background-color: #d9534f;
|
||||
}
|
||||
|
||||
.webbpm #toast-container .toast-close-button {
|
||||
text-shadow: none;
|
||||
}
|
||||
|
||||
.webbpm .toast-message > div {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.webbpm .toast-message > .active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.webbpm .toast-message > .active::after {
|
||||
display: block;
|
||||
content: "";
|
||||
float: none;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.webbpm .toast-message > .active a {
|
||||
float: right;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.webbpm .toast-message > .toast-msg-close.active ~ .toast-msg-text {
|
||||
display: block;
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.webbpm .toast-message a:not([href]):not([tabindex]) {
|
||||
text-decoration: underline;
|
||||
}
|
||||
/*------------Окно сообщения об ошибке end------------*/
|
||||
|
||||
/*----------------- Ошибка 404 -------------------*/
|
||||
.webbpm .container .task-not-found-page {
|
||||
position: relative;
|
||||
display: table;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.webbpm .container .task-not-found-container {
|
||||
display: table-cell;
|
||||
color: var(--black);
|
||||
font-size: 1.8em;
|
||||
background-color: #c9d4e0;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.webbpm .container .task-not-found-container > div {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.webbpm .container .task-not-found-container > div:first-child {
|
||||
color: var(--white);
|
||||
font-size: 7.8em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.webbpm .container .task-not-found-container > div:last-child {
|
||||
text-align: left;
|
||||
margin-left: 40px;
|
||||
}
|
||||
|
||||
.webbpm .container .task-not-found-container h2 {
|
||||
font-size: 2em;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
|
||||
.webbpm .container .task-not-found-container a {
|
||||
cursor: pointer;
|
||||
}
|
||||
/*--------------- end Ошибка 404 -----------------*/
|
||||
|
||||
/*-------------- MOBILE --------------*/
|
||||
.webbpm.mobile .task-list-tree-panel,
|
||||
.webbpm.mobile footer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.webbpm.mobile .task-list-workplace {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.webbpm.mobile .container {
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.webbpm.mobile form {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.webbpm.mobile .form-signin {
|
||||
width: auto;
|
||||
padding: 20px;
|
||||
margin: 40px 20px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.webbpm.mobile .form-signin h1,
|
||||
.webbpm.mobile .form-signin h2 {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.webbpm.mobile .form-signin label {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.webbpm.mobile .form-signin input[type="text"],
|
||||
.webbpm.mobile .form-signin input[type="password"] {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.webbpm.mobile .form-signin .login-btn-box {
|
||||
width: auto;
|
||||
margin-right: 0;
|
||||
}
|
||||
/*-------------- end MOBILE --------------*/
|
||||
|
||||
|
||||
/*-------------- НОВЫЙ ДИЗАЙН --------------*/
|
||||
/*------------------ Фильтры ------------------*/
|
||||
.webbpm .task-list {
|
||||
height: auto;
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.webbpm .task-list > .task-list-tree-panel {
|
||||
background: var(--white);
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.webbpm .task-list > .task-list-tree-panel,
|
||||
.webbpm .task-list > .task-list-workplace {
|
||||
font-size: var(--size-text-secondary);
|
||||
float: none;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
min-height: auto;
|
||||
}
|
||||
|
||||
.webbpm .task-list-tree-panel .task-list-filter {
|
||||
font-family: Corbel;
|
||||
margin: 0;
|
||||
box-shadow: 0px 4px 10px -5px rgba(40, 40, 40, 0.3);
|
||||
|
||||
ul {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
display: inline-block;
|
||||
padding: 12px 0px;
|
||||
margin: 0;
|
||||
|
||||
&:first-of-type {
|
||||
font-size: 1.4em;
|
||||
font-weight: bold;
|
||||
width: 197px;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
&::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
label {
|
||||
font-size: 1.3em;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
label div {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 10px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
&.ontime label div {
|
||||
background-color: #2da6a1;
|
||||
}
|
||||
|
||||
&.overdue label div {
|
||||
background-color: #9c5d7a;
|
||||
}
|
||||
|
||||
label input[type="radio"] {
|
||||
float: none;
|
||||
display: none;
|
||||
}
|
||||
|
||||
label span {
|
||||
float: none;
|
||||
color: var(--black);
|
||||
font-weight: normal;
|
||||
text-align: left;
|
||||
min-width: auto;
|
||||
padding: 5px 15px;
|
||||
border-radius: 0;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
label input[type="radio"].ng-valid-parse ~ span {
|
||||
background-color: #eaedf2;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
/*-------------- end НОВЫЙ ДИЗАЙН --------------*/
|
||||
|
||||
.webbpm .dialog-stack-trace {
|
||||
overflow-x: auto;
|
||||
overflow-y: auto;
|
||||
max-height: 300px;
|
||||
}
|
||||
.webbpm .dialog-show-button {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
.webbpm .dialog-error-number {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
.webbpm .dialog-error-title {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
/*-- login --*/
|
||||
.webbpm :is(.form-signin, .form-signup, .confirm) {
|
||||
color: #333;
|
||||
width: 580px;
|
||||
padding: 80px 100px;
|
||||
margin: 20px auto;
|
||||
border: 1px solid var(--bg-light);
|
||||
background: var(--white);
|
||||
}
|
||||
|
||||
.webbpm .form-signin.esia {
|
||||
width: 450px;
|
||||
text-align: center;
|
||||
padding: 45px 55px 35px 55px;
|
||||
}
|
||||
|
||||
.webbpm .form-signin h1,
|
||||
.webbpm .form-signin h2,
|
||||
.webbpm .form-signup h2,
|
||||
.webbpm .confirm h2 {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.webbpm .form-signin.esia :is(h1, h2) {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.webbpm .form-signin label,
|
||||
.webbpm .form-signup label {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.webbpm .form-signin input {
|
||||
width: 240px;
|
||||
font-size: var(--size-text-secondary);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.webbpm .form-signin.esia input {
|
||||
width: 160px;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.webbpm :is(.form-signin, .form-signup) .row {
|
||||
display: flex;
|
||||
margin: 0 0 20px;
|
||||
}
|
||||
|
||||
.webbpm .registration-link,
|
||||
.webbpm .login-link {
|
||||
margin-right: 20px;
|
||||
font-size: var(--size-text-secondary);
|
||||
}
|
||||
|
||||
.webbpm .form-signin .row.registration {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.webbpm .form-signin .login-btn-box {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.webbpm .form-signin .row.registration > * + *,
|
||||
.webbpm .form-signin .login-btn-box .password,
|
||||
.webbpm .form-signin .login-btn-box .btn + .btn {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.webbpm .input-group > .input-group-append > .input-group-text {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.webbpm .form-signin .register-btn-box,
|
||||
.webbpm .form-signup .register-btn-box,
|
||||
.webbpm .form-signup .reset-password-btn-box {
|
||||
width: 220px;
|
||||
}
|
||||
|
||||
.webbpm .form-signup .row international-phone-number .flagInput .btn {
|
||||
border-left: 1px solid #c6cdd3;
|
||||
}
|
||||
.webbpm .form-signup .row international-phone-number .flagInput ~ input {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
.webbpm .form-signin .has-error .help-block {
|
||||
padding-left: 125px;
|
||||
font-size: var(--size-text-secondary);
|
||||
}
|
||||
|
||||
.webbpm .form-signup .has-account a {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
/*------------------ Формы регистрации и подтверждения ------------------*/
|
||||
.form-signup .has-account {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.form-signup .has-account a span,
|
||||
.confirm a span {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.form-signup .dropbtn.btn {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form-signup .input-group-text {
|
||||
border-left-width: 0;
|
||||
}
|
||||
|
||||
.form-signup input.ng-invalid.ng-touched,
|
||||
.form-signup input.ng-invalid.ng-dirty {
|
||||
border-color: red !important;
|
||||
}
|
||||
|
||||
.form-signup .msg-alert {
|
||||
color: red;
|
||||
font-size: 11px;
|
||||
padding: 3px 0 0;
|
||||
}
|
||||
|
||||
.form-signup .consent {
|
||||
color: #929292;
|
||||
font-size: 13px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
/*------------------ End - Формы регистрации и подтверждения ------------------*/
|
||||
|
||||
/*------------------ Сообщения об ошибке ------------------*/
|
||||
.webbpm .error_message {
|
||||
width: 650px;
|
||||
margin: 0 auto;
|
||||
margin-top: 10%;
|
||||
}
|
||||
|
||||
.webbpm .error_title {
|
||||
position: relative;
|
||||
color: #9c5d7a;
|
||||
font-size: 5.5em;
|
||||
font-weight: bold;
|
||||
margin-left: 100px;
|
||||
line-height: 1;
|
||||
}
|
||||
.webbpm .error_title::before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
left: -100px;
|
||||
width: 75px;
|
||||
height: 75px;
|
||||
border-radius: 40px;
|
||||
background-color: #9c5d7a;
|
||||
background-image: url("../img/access_denied.png");
|
||||
background-repeat: no-repeat;
|
||||
background-position: 50% 50%;
|
||||
}
|
||||
|
||||
.webbpm .error_title_long {
|
||||
margin-bottom: 20px;
|
||||
font-size: 2.5em;
|
||||
}
|
||||
|
||||
.webbpm .error_body {
|
||||
font-size: 2em;
|
||||
line-height: 1.2;
|
||||
margin: 5px 0 0 100px;
|
||||
}
|
||||
/*---------------- end Сообщения об ошибке ---------------*/
|
||||
/*-------------- Поле телефона ------------ */
|
||||
.flag {
|
||||
background-image: url('./../img/country-flags.jpg') !important;
|
||||
}
|
||||
/*-------------- end Поле телефона ------------ */
|
||||
|
||||
14
frontend/src/resources/css/style.css
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
@import "../../../node_modules/angular-calendar/css/angular-calendar.css";
|
||||
@import "../../../node_modules/bootstrap/dist/css/bootstrap-grid.css";
|
||||
@import "../../../node_modules/bootstrap/dist/css/bootstrap-reboot.css";
|
||||
@import "../../../node_modules/bootstrap/dist/css/bootstrap.css";
|
||||
@import "../../../node_modules/bootstrap-icons/font/bootstrap-icons.css";
|
||||
@import "../../../node_modules/font-awesome/css/font-awesome.css";
|
||||
@import "../../../node_modules/@webbpm/base-package/css/style.css";
|
||||
@import "structure.css";
|
||||
@import "inbox-dashboard.css";
|
||||
@import "components-dashboard.css";
|
||||
@import "external/ngx-treeview/dropdown-treeview.component.scss";
|
||||
@import "external/ngx-treeview/dropdown-treeview-select.component.scss";
|
||||
@import "external/ngx-treeview/treeview.component.scss";
|
||||
@import "external/ngx-treeview/treeview-item.component.scss";
|
||||
BIN
frontend/src/resources/fonts/gilroy-extrabold.otf
Normal file
BIN
frontend/src/resources/fonts/gilroy-light.otf
Normal file
BIN
frontend/src/resources/fonts/gilroy-medium.ttf
Normal file
BIN
frontend/src/resources/fonts/gilroy-regular.ttf
Normal file
BIN
frontend/src/resources/fonts/gilroy-semibold.ttf
Normal file
BIN
frontend/src/resources/img/access_denied.png
Normal file
|
After Width: | Height: | Size: 855 B |
BIN
frontend/src/resources/img/admin.png
Normal file
|
After Width: | Height: | Size: 811 B |
BIN
frontend/src/resources/img/bg_image.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
frontend/src/resources/img/country-flags.jpg
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
frontend/src/resources/img/create.png
Normal file
|
After Width: | Height: | Size: 673 B |
10
frontend/src/resources/img/icons/dots-six-vertical.svg
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_340_5833)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.5 3.75C6.5 4.16421 6.16421 4.5 5.75 4.5C5.33579 4.5 5 4.16421 5 3.75C5 3.33579 5.33579 3 5.75 3C6.16421 3 6.5 3.33579 6.5 3.75ZM11 3.75C11 4.16421 10.6642 4.5 10.25 4.5C9.83579 4.5 9.5 4.16421 9.5 3.75C9.5 3.33579 9.83579 3 10.25 3C10.6642 3 11 3.33579 11 3.75ZM5.75 8.75C6.16421 8.75 6.5 8.41421 6.5 8C6.5 7.58579 6.16421 7.25 5.75 7.25C5.33579 7.25 5 7.58579 5 8C5 8.41421 5.33579 8.75 5.75 8.75ZM11 8C11 8.41421 10.6642 8.75 10.25 8.75C9.83579 8.75 9.5 8.41421 9.5 8C9.5 7.58579 9.83579 7.25 10.25 7.25C10.6642 7.25 11 7.58579 11 8ZM5.75 13C6.16421 13 6.5 12.6642 6.5 12.25C6.5 11.8358 6.16421 11.5 5.75 11.5C5.33579 11.5 5 11.8358 5 12.25C5 12.6642 5.33579 13 5.75 13ZM11 12.25C11 12.6642 10.6642 13 10.25 13C9.83579 13 9.5 12.6642 9.5 12.25C9.5 11.8358 9.83579 11.5 10.25 11.5C10.6642 11.5 11 11.8358 11 12.25Z" fill="#353535"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_340_5833">
|
||||
<rect width="16" height="16" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
3
frontend/src/resources/img/icons/settings-sm.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.4289 4.03093L14.5357 3.84641C14.7628 3.46915 15.2354 3.35853 15.5798 3.56346L15.593 3.57113L17.3235 4.56141C17.7982 4.83272 17.9624 5.4493 17.6908 5.91957C17.1401 6.8701 16.9763 7.94135 17.4902 8.83311C18.0042 9.72502 19.0132 10.12 20.11 10.12C20.656 10.12 21.11 10.5701 21.11 11.12V12.88C21.11 13.426 20.6598 13.88 20.11 13.88C19.0132 13.88 18.0042 14.275 17.4902 15.1669C16.9767 16.058 17.1399 17.1284 17.6896 18.0784C17.9642 18.5612 17.7961 19.1685 17.323 19.4389L15.593 20.4289L15.5798 20.4365C15.2354 20.6415 14.7628 20.5308 14.5357 20.1536L14.4308 19.9724L14.4297 19.9706C13.8837 19.0191 13.0383 18.3425 12.0087 18.3425C10.9799 18.3425 10.1319 19.0187 9.58109 19.9691L9.47424 20.1536C9.24711 20.5308 8.77457 20.6414 8.43014 20.4365L8.41699 20.4289L6.6865 19.4386C6.21174 19.1673 6.04759 18.5507 6.31911 18.0805C6.8698 17.1299 7.03367 16.0587 6.51976 15.1669C6.00578 14.275 4.99672 13.88 3.89998 13.88C3.35011 13.88 2.89998 13.426 2.89998 12.88V11.12C2.89998 10.574 3.35011 10.12 3.89998 10.12C4.99672 10.12 6.00578 9.72502 6.51976 8.83311C7.03367 7.94133 6.8698 6.87005 6.31911 5.9195C6.04759 5.44924 6.21222 4.83243 6.68699 4.56113L8.41699 3.57113L8.43014 3.56346C8.77458 3.35854 9.24714 3.46915 9.47426 3.8464L9.58016 4.02932C10.1262 4.98086 10.9716 5.65749 12.0012 5.65749C13.03 5.65749 13.878 4.9813 14.4289 4.03093ZM6.31911 5.9195C6.31895 5.91922 6.31879 5.91894 6.31863 5.91867L5.53998 6.36999L6.31925 5.91974C6.31921 5.91966 6.31916 5.91958 6.31911 5.9195ZM17.6896 18.0784C17.6902 18.0794 17.6908 18.0804 17.6913 18.0813L18.47 17.63L17.6886 18.0765C17.6889 18.0772 17.6893 18.0778 17.6896 18.0784ZM12.9865 2.92983C13.6989 1.73245 15.2596 1.28388 16.4929 2.01224L18.217 2.99885C19.5618 3.76765 20.0174 5.49069 19.2493 6.82024L19.2486 6.82132C18.8897 7.44056 18.9688 7.79392 19.0498 7.93437C19.1308 8.07497 19.3967 8.31999 20.11 8.31999C21.6439 8.31999 22.91 9.56986 22.91 11.12V12.88C22.91 14.414 21.6601 15.68 20.11 15.68C19.3967 15.68 19.1308 15.925 19.0498 16.0656C18.9688 16.2061 18.8897 16.5594 19.2486 17.1787L19.2514 17.1835C20.0153 18.5204 19.5627 20.2321 18.2165 21.0014L16.4929 21.9878C15.2596 22.7161 13.6989 22.2675 12.9865 21.0702L12.9811 21.0609L12.8711 20.8709L12.8692 20.8676C12.5152 20.25 12.171 20.1425 12.0087 20.1425C11.8451 20.1425 11.4977 20.252 11.1386 20.8713L11.0234 21.0702C10.3111 22.2675 8.75036 22.7161 7.51707 21.9878L5.79345 21.0014C4.4483 20.2328 3.99242 18.5095 4.7607 17.1797L4.76132 17.1787C5.12024 16.5594 5.04113 16.2061 4.96019 16.0656C4.87917 15.925 4.61323 15.68 3.89998 15.68C2.34984 15.68 1.09998 14.414 1.09998 12.88V11.12C1.09998 9.58602 2.34984 8.31999 3.89998 8.31999C4.61323 8.31999 4.87917 8.07497 4.96019 7.93437C5.04113 7.79392 5.12024 7.44056 4.76132 6.82132L4.7607 6.82024C3.99251 5.49069 4.44867 3.76737 5.79345 2.99857L7.51705 2.01224C8.75034 1.28388 10.3111 1.73245 11.0234 2.92983L11.0289 2.93906L11.1408 3.1324C11.4948 3.74995 11.8389 3.85749 12.0012 3.85749C12.1649 3.85749 12.5122 3.74829 12.8713 3.12867L12.9865 2.92983ZM9.89998 12C9.89998 10.8402 10.8402 9.89998 12 9.89998C13.1598 9.89998 14.1 10.8402 14.1 12C14.1 13.1598 13.1598 14.1 12 14.1C10.8402 14.1 9.89998 13.1598 9.89998 12ZM12 8.09998C9.84607 8.09998 8.09998 9.84607 8.09998 12C8.09998 14.1539 9.84607 15.9 12 15.9C14.1539 15.9 15.9 14.1539 15.9 12C15.9 9.84607 14.1539 8.09998 12 8.09998Z" fill="#353535"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.4 KiB |
BIN
frontend/src/resources/img/logo-full.png
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
28
frontend/src/resources/img/logo-full.svg
Normal file
|
After Width: | Height: | Size: 451 KiB |
BIN
frontend/src/resources/img/logo.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
frontend/src/resources/img/progress.gif
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
frontend/src/resources/img/project.png
Normal file
|
After Width: | Height: | Size: 712 B |
4
frontend/src/resources/img/svg/arrow-right-wt.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M10.7195 17.7046C10.3261 18.0985 9.68836 18.0985 9.29501 17.7046C8.90166 17.3108 8.90166 16.6723 9.29501 16.2785L14.2805 11.2869C14.6739 10.8931 15.3116 10.8931 15.705 11.2869C16.0983 11.6808 16.0983 12.3193 15.705 12.7131L10.7195 17.7046Z" fill="#F4FCFF"/>
|
||||
<path d="M9.29516 7.72152C8.90181 7.3277 8.90181 6.68919 9.29516 6.29537C9.68851 5.90154 10.3263 5.90154 10.7196 6.29537L15.705 11.2869C16.0983 11.6808 16.0983 12.3193 15.705 12.7131C15.3116 13.1069 14.674 13.1069 14.2807 12.7131L9.29516 7.72152Z" fill="#F4FCFF"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 635 B |
3
frontend/src/resources/img/svg/arrow-right.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.4201 12L8.27002 6.63853C7.90999 6.26373 7.90999 5.64991 8.27002 5.2751C8.62235 4.9083 9.18796 4.9083 9.5403 5.2751L16 12L9.5403 18.7249C9.18796 19.0917 8.62235 19.0917 8.27002 18.7249C7.90999 18.3501 7.90999 17.7363 8.27002 17.3615L13.4201 12Z" fill="#070E1A"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 418 B |
24
frontend/src/resources/img/svg/bg-diamond.svg
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<svg width="600" height="950" viewBox="0 0 600 950" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g opacity="0.82" filter="url(#filter0_b_1145_8)">
|
||||
<rect x="237" width="363" height="825" fill="url(#paint0_diamond_1145_8)" fill-opacity="0.6"/>
|
||||
<rect x="237" width="363" height="825" fill="url(#paint1_diamond_1145_8)" fill-opacity="0.6"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_b_1145_8" x="197" y="-40" width="443" height="905" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feGaussianBlur in="BackgroundImageFix" stdDeviation="20"/>
|
||||
<feComposite in2="SourceAlpha" operator="in" result="effect1_backgroundBlur_1145_8"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_backgroundBlur_1145_8" result="shape"/>
|
||||
</filter>
|
||||
<radialGradient id="paint0_diamond_1145_8" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(600 2.00138e-05) rotate(102.153) scale(786.119 149.292)">
|
||||
<stop stop-color="#00F0FF"/>
|
||||
<stop offset="0.288604" stop-color="#00BFFF" stop-opacity="0.4"/>
|
||||
<stop offset="1" stop-color="#00BFFF" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="paint1_diamond_1145_8" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(600 2.86284e-05) rotate(112.078) scale(826.075 220.976)">
|
||||
<stop stop-color="#00F0FF"/>
|
||||
<stop offset="0.338372" stop-color="#00BFFF" stop-opacity="0.4"/>
|
||||
<stop offset="0.982175" stop-color="#00BFFF" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
11
frontend/src/resources/img/svg/bg-people.svg
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<svg width="707" height="707" viewBox="0 0 707 707" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g opacity="0.6">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M238.297 240.476C268.437 240.476 293.22 215.731 293.22 185.635C293.22 155.54 268.437 130.795 238.297 130.795C208.156 130.795 183.374 155.54 183.374 185.635C183.374 215.731 208.156 240.476 238.297 240.476ZM190.071 395.633C190.071 425.729 165.289 450.474 135.148 450.474C105.008 450.474 80.2253 425.729 80.2253 395.633C80.2253 365.538 105.008 340.793 135.148 340.793C165.289 340.793 190.071 365.538 190.071 395.633ZM135.149 481.906C150.554 481.906 165.289 477.894 178.015 470.537C200.118 478.563 218.202 495.951 227.579 518.021C224.231 529.39 222.221 541.428 222.221 553.466V576.205H44.0572C39.3687 576.205 35.35 572.192 35.35 567.511V553.466C35.35 515.346 58.7926 483.244 92.2821 470.537C105.008 477.894 119.743 481.906 135.149 481.906ZM571.85 450.474C541.709 450.474 516.927 425.729 516.927 395.633C516.927 365.538 541.709 341.462 571.85 340.793C601.99 340.793 626.773 365.538 626.773 395.633C626.773 425.729 601.99 450.474 571.85 450.474ZM469.375 240.476C499.515 240.476 524.297 215.731 524.297 185.635C524.297 155.54 499.515 130.795 469.375 130.795C439.234 130.795 414.452 155.54 414.452 185.635C414.452 215.731 439.234 240.476 469.375 240.476ZM332.067 312.704C294.559 322.067 267.097 355.506 267.097 395.633C267.097 415.028 273.795 433.085 285.182 448.467C269.107 457.162 255.041 469.869 244.324 484.582C233.608 469.869 219.542 457.162 203.467 448.467C214.184 433.754 220.882 415.697 220.882 396.302C220.882 352.162 187.392 315.379 144.526 310.698C153.903 287.959 171.987 269.902 194.76 261.208C207.486 268.564 222.221 272.577 237.627 272.577C253.032 272.577 267.767 268.564 280.493 261.208C304.605 269.902 323.36 289.297 332.067 312.704ZM298.577 395.633C298.577 425.729 323.359 450.474 353.5 450.474C383.64 450.474 408.423 425.729 408.423 395.633C408.423 365.538 383.64 340.793 353.5 340.793C323.359 341.462 298.577 365.538 298.577 395.633ZM571.851 481.906C587.257 481.906 601.992 477.894 614.718 470.537C648.207 483.244 671.65 515.346 671.65 553.466V567.511C671.65 572.192 667.631 576.205 662.943 576.205H484.779V553.466C484.779 540.76 482.769 529.39 479.421 518.021C488.798 496.62 506.882 479.231 528.985 470.537C541.711 477.894 556.446 481.906 571.851 481.906ZM485.448 395.634C485.448 351.494 518.937 314.711 561.804 310.029C552.427 287.29 534.342 269.233 511.57 260.539C498.844 267.896 484.108 271.908 468.703 271.908C453.298 271.908 438.563 267.896 425.837 260.539C402.394 269.902 383.64 288.628 374.933 312.035C411.771 321.398 439.232 355.506 439.232 394.965C439.232 414.36 432.535 432.417 421.818 447.13C437.893 455.824 451.958 468.531 462.675 483.244C473.392 468.531 487.457 455.824 503.532 447.13C492.146 433.085 485.448 415.028 485.448 395.634ZM353.5 481.906C368.906 481.906 383.641 477.894 396.367 470.537C429.856 483.244 453.299 515.346 453.299 553.466V576.205H253.702V553.466C253.702 515.346 277.144 483.244 310.634 470.537C323.36 477.894 338.095 481.906 353.5 481.906Z" fill="url(#paint0_linear_1345_11863)" fill-opacity="0.4"/>
|
||||
</g>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_1345_11863" x1="291.818" y1="254.692" x2="291.818" y2="568.872" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#070A14"/>
|
||||
<stop offset="1" stop-color="#070A14" stop-opacity="0.2"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
34
frontend/src/resources/img/svg/bg-pers.svg
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
<svg width="440" height="392" viewBox="0 0 440 392" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M23.0504 232.015C30.2265 232.015 37.3878 231.069 44.318 229.186C48.6571 228.006 52.8936 226.461 56.9752 224.576C56.2232 219.469 55.8471 214.314 55.8471 209.152C55.8471 181.346 66.759 154.923 86.3534 135.264C90.5241 131.08 95.0425 127.256 99.8525 123.837C94.6866 108.839 85.2741 95.7064 72.7428 85.9915C58.4909 74.9429 41.057 69.0021 23.0504 69.0021C1.42682 69.0021 -19.1113 77.5374 -34.3975 92.8733C-49.6845 108.211 -58.1927 128.815 -58.1927 150.509C-58.1927 172.202 -49.6826 192.807 -34.3975 208.145C-19.1113 223.48 1.42682 232.015 23.0504 232.015Z" fill="url(#paint0_linear_1644_11725)" fill-opacity="0.4"/>
|
||||
<path d="M102.555 266.786C117.842 282.125 138.379 290.659 160.001 290.659C181.625 290.659 202.163 282.125 217.45 266.786C232.738 251.451 241.243 230.844 241.243 209.152C241.243 187.459 232.738 166.854 217.45 151.517C202.163 136.179 181.625 127.646 160.001 127.646C138.379 127.646 117.842 136.179 102.555 151.517C87.2671 166.854 78.7612 187.459 78.7612 209.152C78.7612 230.845 87.2668 251.451 102.555 266.786Z" fill="url(#paint1_linear_1644_11725)" fill-opacity="0.4"/>
|
||||
<path d="M264.157 209.152C264.157 214.313 263.783 219.47 263.03 224.576C267.111 226.461 271.347 228.005 275.687 229.185C282.615 231.068 289.777 232.013 296.954 232.013C318.578 232.013 339.115 223.478 354.402 208.142C369.689 192.805 378.196 172.2 378.196 150.507C378.196 128.813 369.688 108.209 354.402 92.8712C339.115 77.5341 318.578 69 296.954 69C278.946 69 261.513 74.9416 247.262 85.9894C234.73 95.7054 225.318 108.839 220.153 123.836C224.961 127.255 229.482 131.08 233.65 135.263C253.245 154.923 264.157 181.345 264.157 209.152Z" fill="url(#paint2_linear_1644_11725)" fill-opacity="0.4"/>
|
||||
<path d="M393.315 278.435C365.134 261.76 329.347 255.002 296.952 255.002C288.294 255.002 279.626 255.469 271.018 256.445C264.379 257.197 257.768 258.25 251.225 259.616C246.493 268.207 240.568 276.102 233.649 283.041C228.382 288.326 222.562 293.035 216.306 297.076C234.288 301.339 252.12 307.918 267.999 317.312C293.431 332.359 315.964 356.368 315.964 387.652V471.947C330.683 471.258 345.436 469.763 359.949 467.223C372.608 465.008 385.403 461.967 397.496 457.58C405.733 454.592 430 444.906 430 433.703V329.006C430.001 306.488 411.241 289.038 393.315 278.435Z" fill="url(#paint3_linear_1644_11725)" fill-opacity="0.4"/>
|
||||
<path d="M256.364 337.076C228.182 320.403 192.397 313.647 160.002 313.647C127.608 313.647 91.8212 320.403 63.6392 337.076C45.7151 347.682 26.9548 365.13 26.9548 387.651V492.347C26.9548 504.067 52.9504 513.995 61.5431 516.977C75.6904 521.888 90.7683 525.071 105.566 527.228C123.546 529.845 141.839 531 160.002 531C178.166 531 196.456 529.845 214.439 527.228C229.237 525.071 244.316 521.888 258.462 516.977C267.055 513.995 293.05 504.067 293.05 492.347V387.651C293.05 365.131 274.29 347.682 256.364 337.076Z" fill="url(#paint4_linear_1644_11725)" fill-opacity="0.4"/>
|
||||
<path d="M52.0021 317.313C67.8828 307.916 85.7152 301.338 103.697 297.075C97.4403 293.034 91.6191 288.325 86.3522 283.04C79.4367 276.101 73.5099 268.206 68.7785 259.615C62.2344 258.248 55.6237 257.195 48.982 256.444C40.3762 255.468 31.7092 255.002 23.0491 255.002C-9.34532 255.002 -45.1309 261.759 -73.3133 278.434C-91.239 289.037 -110 306.488 -110 329.006V433.702C-110 444.905 -85.7312 454.591 -77.494 457.579C-65.4002 461.966 -52.6052 465.007 -39.9495 467.222C-25.4351 469.762 -10.6809 471.257 4.03875 471.946V387.65C4.03914 356.367 26.5723 332.358 52.0021 317.313Z" fill="url(#paint5_linear_1644_11725)" fill-opacity="0.4"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_1644_11725" x1="107.653" y1="197.692" x2="107.653" y2="524.031" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#070A14"/>
|
||||
<stop offset="1" stop-color="#070A14" stop-opacity="0.2"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_1644_11725" x1="107.653" y1="197.692" x2="107.653" y2="524.031" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#070A14"/>
|
||||
<stop offset="1" stop-color="#070A14" stop-opacity="0.2"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_1644_11725" x1="107.653" y1="197.692" x2="107.653" y2="524.031" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#070A14"/>
|
||||
<stop offset="1" stop-color="#070A14" stop-opacity="0.2"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_1644_11725" x1="107.653" y1="197.692" x2="107.653" y2="524.031" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#070A14"/>
|
||||
<stop offset="1" stop-color="#070A14" stop-opacity="0.2"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint4_linear_1644_11725" x1="107.653" y1="197.692" x2="107.653" y2="524.031" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#070A14"/>
|
||||
<stop offset="1" stop-color="#070A14" stop-opacity="0.2"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint5_linear_1644_11725" x1="107.653" y1="197.692" x2="107.653" y2="524.031" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#070A14"/>
|
||||
<stop offset="1" stop-color="#070A14" stop-opacity="0.2"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.9 KiB |
3
frontend/src/resources/img/svg/close.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.29289 6.29289C6.68342 5.90237 7.31658 5.90237 7.70711 6.29289L12 10.5858L16.2929 6.29289C16.6834 5.90237 17.3166 5.90237 17.7071 6.29289C18.0976 6.68342 18.0976 7.31658 17.7071 7.70711L13.4142 12L17.7071 16.2929C18.0976 16.6834 18.0976 17.3166 17.7071 17.7071C17.3166 18.0976 16.6834 18.0976 16.2929 17.7071L12 13.4142L7.70711 17.7071C7.31658 18.0976 6.68342 18.0976 6.29289 17.7071C5.90237 17.3166 5.90237 16.6834 6.29289 16.2929L10.5858 12L6.29289 7.70711C5.90237 7.31658 5.90237 6.68342 6.29289 6.29289Z" fill="#070E1A"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 680 B |
3
frontend/src/resources/img/svg/mark.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14 4L6 12L2 8" stroke="#F4FCFF" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 213 B |
BIN
frontend/src/resources/img/tasks.png
Normal file
|
After Width: | Height: | Size: 351 B |
|
|
@ -0,0 +1,4 @@
|
|||
<div class="error_message">
|
||||
<div class="error_title">403</div>
|
||||
<div class="error_body">Доступ запрещен</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<!--
|
||||
<footer>
|
||||
<div id="webbpm-footer">
|
||||
</div>
|
||||
</footer>
|
||||
-->
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
<nav class="header" id="webbpm-header">
|
||||
<div class="header-logo">
|
||||
<div class="logo"><a routerLink="/"></a></div>
|
||||
<div class="logo-title">ЕДИНЫЙ РЕЕСТР<br />ВОИНСКОГО УЧЕТА</div>
|
||||
</div>
|
||||
<div class="header-menu">
|
||||
<div class="update-data">Данные на 14 августа 2024 г.</div>
|
||||
<div ngbDropdown class="logout" log-out></div>
|
||||
</div>
|
||||
</nav>
|
||||
|
|
@ -0,0 +1 @@
|
|||
<span id="version-footer">Версия: {{applicationVersion}}</span>
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<div class="modal-body">
|
||||
<div class="progress"></div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
<ng-template #itemTemplate let-item="item" let-onCollapseExpand="onCollapseExpand"
|
||||
let-onCheckedChange="onCheckedChange">
|
||||
<div class="text-nowrap row-item">
|
||||
<i *ngIf="item.children"
|
||||
(click)="onCollapseExpand()"
|
||||
aria-hidden="true"
|
||||
[ngSwitch]="item.collapsed">
|
||||
<i *ngSwitchCase="true" class="bi bi-caret-right-fill"></i>
|
||||
<i *ngSwitchCase="false" class="bi bi-caret-down-fill"></i>
|
||||
</i>
|
||||
<label class="form-check-label" (click)="select(item)">{{ item.text }}</label>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template #headerTemplate let-config="config" let-item="item"
|
||||
let-onCollapseExpand="onCollapseExpand" let-onCheckedChange="onCheckedChange"
|
||||
let-onFilterTextChange="onFilterTextChange">
|
||||
<div *ngIf="config.hasFilter" class="row row-filter">
|
||||
<div class="col-12">
|
||||
<input class="form-control" type="text"
|
||||
[placeholder]="i18n.getFilterPlaceholder()"
|
||||
[(ngModel)]="filterText"
|
||||
(ngModelChange)="onFilterTextChange($event)" />
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="config.hasAllCheckBox || config.hasCollapseExpand" class="row">
|
||||
<div class="col-12">
|
||||
<label *ngIf="config.hasAllCheckBox" (click)="select(item)">
|
||||
<strong>{{ i18n.getAllCheckboxText() }}</strong>
|
||||
</label>
|
||||
<label *ngIf="config.hasCollapseExpand" class="float-right" (click)="onCollapseExpand()">
|
||||
<i [title]="i18n.getTooltipCollapseExpandText(item.collapsed)" aria-hidden="true" [ngSwitch]="item.collapsed">
|
||||
<svg *ngSwitchCase="true" width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-arrows-angle-expand"
|
||||
fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd"
|
||||
d="M1.5 10.036a.5.5 0 0 1 .5.5v3.5h3.5a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5v-4a.5.5 0 0 1 .5-.5z" />
|
||||
<path fill-rule="evenodd"
|
||||
d="M6.354 9.646a.5.5 0 0 1 0 .708l-4.5 4.5a.5.5 0 0 1-.708-.708l4.5-4.5a.5.5 0 0 1 .708 0zm8.5-8.5a.5.5 0 0 1 0 .708l-4.5 4.5a.5.5 0 0 1-.708-.708l4.5-4.5a.5.5 0 0 1 .708 0z" />
|
||||
<path fill-rule="evenodd"
|
||||
d="M10.036 1.5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 .5.5v4a.5.5 0 1 1-1 0V2h-3.5a.5.5 0 0 1-.5-.5z" />
|
||||
</svg>
|
||||
<svg *ngSwitchCase="false" width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-arrows-angle-contract"
|
||||
fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd"
|
||||
d="M9.5 2.036a.5.5 0 0 1 .5.5v3.5h3.5a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5v-4a.5.5 0 0 1 .5-.5z" />
|
||||
<path fill-rule="evenodd"
|
||||
d="M14.354 1.646a.5.5 0 0 1 0 .708l-4.5 4.5a.5.5 0 1 1-.708-.708l4.5-4.5a.5.5 0 0 1 .708 0zm-7.5 7.5a.5.5 0 0 1 0 .708l-4.5 4.5a.5.5 0 0 1-.708-.708l4.5-4.5a.5.5 0 0 1 .708 0z" />
|
||||
<path fill-rule="evenodd"
|
||||
d="M2.036 9.5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-1 0V10h-3.5a.5.5 0 0 1-.5-.5z" />
|
||||
</svg>
|
||||
</i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="config.hasDivider" class="dropdown-divider"></div>
|
||||
</ng-template>
|
||||
<ngx-dropdown-treeview
|
||||
[config]="config"
|
||||
[headerTemplate]="headerTemplate"
|
||||
[items]="items"
|
||||
[itemTemplate]="itemTemplate">
|
||||
</ngx-dropdown-treeview>
|
||||
17
frontend/src/resources/template/component/external/ngx-treeview/dropdown-treeview.component.html
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<div class="dropdown" ngxDropdown>
|
||||
<button class="btn" [ngClass]="buttonClass" type="button" role="button" ngxDropdownToggle>
|
||||
{{buttonLabel}}
|
||||
</button>
|
||||
<div ngxDropdownMenu aria-labelledby="dropdownMenu" (click)="$event.stopPropagation()">
|
||||
<div class="dropdown-container">
|
||||
<ngx-treeview
|
||||
[config]="config"
|
||||
[headerTemplate]="headerTemplate"
|
||||
[items]="items"
|
||||
[itemTemplate]="itemTemplate"
|
||||
(selectedChange)="onSelectedChange($event)"
|
||||
(filterChange)="onFilterChange($event)">
|
||||
</ngx-treeview>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
16
frontend/src/resources/template/component/external/ngx-treeview/treeview-item.component.html
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
<div *ngIf="item" class="treeview-item">
|
||||
<ng-template
|
||||
[ngTemplateOutlet]="template"
|
||||
[ngTemplateOutletContext]="{item: item, onCollapseExpand: onCollapseExpand,
|
||||
onCheckedChange: onCheckedChange}">
|
||||
</ng-template>
|
||||
<div *ngIf="!item.collapsed">
|
||||
<ngx-treeview-item
|
||||
[config]="config"
|
||||
*ngFor="let child of item.children"
|
||||
[item]="child"
|
||||
[template]="template"
|
||||
(checkedChange)="onChildCheckedChange(child, $event)">
|
||||
</ngx-treeview-item>
|
||||
</div>
|
||||
</div>
|
||||
92
frontend/src/resources/template/component/external/ngx-treeview/treeview.component.html
vendored
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
<ng-template #defaultItemTemplate let-item="item" let-onCollapseExpand="onCollapseExpand"
|
||||
let-onCheckedChange="onCheckedChange">
|
||||
<div class="form-inline row-item">
|
||||
<i *ngIf="item.children" (click)="onCollapseExpand()" aria-hidden="true"
|
||||
[ngSwitch]="item.collapsed">
|
||||
<i *ngSwitchCase="true" class="bi bi-caret-right-fill"></i>
|
||||
<i *ngSwitchCase="false" class="bi bi-caret-down-fill"></i>
|
||||
</i>
|
||||
<div class="form-check">
|
||||
<input type="checkbox" class="form-check-input"
|
||||
[(ngModel)]="item.checked"
|
||||
(ngModelChange)="onCheckedChange()"
|
||||
[disabled]="item.disabled"
|
||||
[indeterminate]="item.indeterminate" />
|
||||
<label class="form-check-label" (click)="item.checked = !item.checked; onCheckedChange()">
|
||||
{{item.text}}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</ng-template>
|
||||
<ng-template #defaultHeaderTemplate let-config="config" let-item="item"
|
||||
let-onCollapseExpand="onCollapseExpand" let-onCheckedChange="onCheckedChange"
|
||||
let-onFilterTextChange="onFilterTextChange">
|
||||
<div *ngIf="config.hasFilter" class="row row-filter">
|
||||
<div class="col-12">
|
||||
<input class="form-control" type="text"
|
||||
[placeholder]="i18n.getFilterPlaceholder()"
|
||||
[(ngModel)]="filterText"
|
||||
(ngModelChange)="onFilterTextChange($event)" />
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="hasFilterItems">
|
||||
<div *ngIf="config.hasAllCheckBox || config.hasCollapseExpand" class="row row-all">
|
||||
<div class="col-12">
|
||||
<div class="form-check form-check-inline" *ngIf="config.hasAllCheckBox">
|
||||
<input type="checkbox" class="form-check-input"
|
||||
[(ngModel)]="item.checked"
|
||||
(ngModelChange)="onCheckedChange()"
|
||||
[indeterminate]="item.indeterminate" />
|
||||
<label class="form-check-label" (click)="item.checked = !item.checked; onCheckedChange()">
|
||||
{{i18n.getAllCheckboxText()}}
|
||||
</label>
|
||||
</div>
|
||||
<label *ngIf="config.hasCollapseExpand" class="float-right form-check-label"
|
||||
(click)="onCollapseExpand()">
|
||||
<i [title]="i18n.getTooltipCollapseExpandText(item.collapsed)" aria-hidden="true"
|
||||
[ngSwitch]="item.collapsed">
|
||||
<svg *ngSwitchCase="true" width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-arrows-angle-expand"
|
||||
fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd"
|
||||
d="M1.5 10.036a.5.5 0 0 1 .5.5v3.5h3.5a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5v-4a.5.5 0 0 1 .5-.5z" />
|
||||
<path fill-rule="evenodd"
|
||||
d="M6.354 9.646a.5.5 0 0 1 0 .708l-4.5 4.5a.5.5 0 0 1-.708-.708l4.5-4.5a.5.5 0 0 1 .708 0zm8.5-8.5a.5.5 0 0 1 0 .708l-4.5 4.5a.5.5 0 0 1-.708-.708l4.5-4.5a.5.5 0 0 1 .708 0z" />
|
||||
<path fill-rule="evenodd"
|
||||
d="M10.036 1.5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 .5.5v4a.5.5 0 1 1-1 0V2h-3.5a.5.5 0 0 1-.5-.5z" />
|
||||
</svg>
|
||||
<svg *ngSwitchCase="false" width="1em" height="1em" viewBox="0 0 16 16" class="bi bi-arrows-angle-contract"
|
||||
fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd"
|
||||
d="M9.5 2.036a.5.5 0 0 1 .5.5v3.5h3.5a.5.5 0 0 1 0 1h-4a.5.5 0 0 1-.5-.5v-4a.5.5 0 0 1 .5-.5z" />
|
||||
<path fill-rule="evenodd"
|
||||
d="M14.354 1.646a.5.5 0 0 1 0 .708l-4.5 4.5a.5.5 0 1 1-.708-.708l4.5-4.5a.5.5 0 0 1 .708 0zm-7.5 7.5a.5.5 0 0 1 0 .708l-4.5 4.5a.5.5 0 0 1-.708-.708l4.5-4.5a.5.5 0 0 1 .708 0z" />
|
||||
<path fill-rule="evenodd"
|
||||
d="M2.036 9.5a.5.5 0 0 1 .5-.5h4a.5.5 0 0 1 .5.5v4a.5.5 0 0 1-1 0V10h-3.5a.5.5 0 0 1-.5-.5z" />
|
||||
</svg>
|
||||
</i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="config.hasDivider" class="dropdown-divider"></div>
|
||||
</div>
|
||||
</ng-template>
|
||||
<div class="treeview-header">
|
||||
<ng-template
|
||||
[ngTemplateOutlet]="headerTemplate || defaultHeaderTemplate"
|
||||
[ngTemplateOutletContext]="headerTemplateContext">
|
||||
</ng-template>
|
||||
</div>
|
||||
<div [ngSwitch]="hasFilterItems">
|
||||
<div *ngSwitchCase="true" class="treeview-container" [style.max-height.px]="maxHeight">
|
||||
<ngx-treeview-item
|
||||
*ngFor="let item of filterItems"
|
||||
[config]="config"
|
||||
[item]="item"
|
||||
[template]="itemTemplate || defaultItemTemplate"
|
||||
(checkedChange)="onItemCheckedChange(item, $event)">
|
||||
</ngx-treeview-item>
|
||||
</div>
|
||||
<div *ngSwitchCase="false" class="treeview-text">
|
||||
{{i18n.getFilterNoItemsFoundText()}}
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="form-group">
|
||||
<div class="d-inline-block">
|
||||
<ngx-dropdown-treeview-select
|
||||
[items]="items"
|
||||
[maxHeight]="maxHeight"
|
||||
[(value)]="value"
|
||||
(valueChange)="onValueChange($event)">
|
||||
</ngx-dropdown-treeview-select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<div class="chart-content" [ngStyle]="style">
|
||||
<canvas id="canvas"></canvas>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
<div [id]="getObjectId()"
|
||||
[class.disabled]="!isEnabled()"
|
||||
class="filter-group"
|
||||
[ngbTooltip]="tooltip | emptyIfNull">
|
||||
<div>
|
||||
<ng-content></ng-content>
|
||||
</div>
|
||||
</div>
|
||||
3
frontend/src/resources/template/preview/preview.html
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<div id="page">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
|
|
@ -0,0 +1 @@
|
|||
<router-outlet></router-outlet>
|
||||
6
frontend/src/resources/template/webbpm/home.html
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<div id="home">
|
||||
<div class="inner">
|
||||
<div class="task-list">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
1
frontend/src/resources/template/webbpm/page.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
<div #pageContent></div>
|
||||
12
frontend/src/resources/template/webbpm/webbpm.html
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<div class="wrapper">
|
||||
<app-header *ngIf="headerVisible">
|
||||
</app-header>
|
||||
|
||||
<div class="container">
|
||||
<div class="container-inside" id="webbpm-angular-application-container">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
<app-footer *ngIf="footerVisible">
|
||||
</app-footer>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
import {Injectable} from "@angular/core";
|
||||
import {DefaultTreeviewI18n} from "../lib/treeview-i18n";
|
||||
import {TreeviewItem, TreeviewSelection} from "../lib/treeview-item";
|
||||
|
||||
@Injectable()
|
||||
export class DropdownTreeviewSelectI18n extends DefaultTreeviewI18n {
|
||||
private internalSelectedItem: TreeviewItem;
|
||||
|
||||
set selectedItem(value: TreeviewItem) {
|
||||
this.internalSelectedItem = value;
|
||||
}
|
||||
|
||||
get selectedItem(): TreeviewItem {
|
||||
return this.internalSelectedItem;
|
||||
}
|
||||
|
||||
getText(selection: TreeviewSelection): string {
|
||||
return this.internalSelectedItem ? this.internalSelectedItem.text : 'Элемент не выбран';
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnChanges,
|
||||
Output,
|
||||
ViewChild
|
||||
} from '@angular/core';
|
||||
import { DropdownTreeviewSelectI18n } from './dropdown-treeview-select-i18n';
|
||||
import { DropdownTreeviewComponent } from "../lib/dropdown-treeview.component";
|
||||
import { TreeviewConfig } from "../lib/treeview-config";
|
||||
import { TreeviewHelper } from "../lib/treeview-helper";
|
||||
import { TreeviewI18n } from "../lib/treeview-i18n";
|
||||
import { TreeviewItem } from "../lib/treeview-item";
|
||||
import { isNil } from '../lib/utils';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'ngx-dropdown-treeview-select',
|
||||
templateUrl: './../../../../../../src/resources/template/component/external/ngx-treeview/dropdown-treeview-select.component.html',
|
||||
providers: [{ provide: TreeviewI18n, useClass: DropdownTreeviewSelectI18n }],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class DropdownTreeviewSelectComponent implements OnChanges {
|
||||
@Input() config: TreeviewConfig;
|
||||
@Input() items: TreeviewItem[];
|
||||
@Input() maxHeight: number;
|
||||
@Input() value: any;
|
||||
@Output() valueChange = new EventEmitter<any>();
|
||||
@ViewChild(DropdownTreeviewComponent) dropdownTreeviewComponent: DropdownTreeviewComponent;
|
||||
filterText: string;
|
||||
private dropdownTreeviewSelectI18n: DropdownTreeviewSelectI18n;
|
||||
|
||||
constructor(public i18n: TreeviewI18n) {
|
||||
this.config = TreeviewConfig.create({
|
||||
hasAllCheckBox: false,
|
||||
hasCollapseExpand: false,
|
||||
hasFilter: true,
|
||||
maxHeight: 500
|
||||
});
|
||||
this.dropdownTreeviewSelectI18n = i18n as DropdownTreeviewSelectI18n;
|
||||
}
|
||||
|
||||
ngOnChanges(): void {
|
||||
if (this.maxHeight) {
|
||||
this.config.maxHeight = this.maxHeight;
|
||||
}
|
||||
this.updateSelectedItem();
|
||||
}
|
||||
|
||||
select(item: TreeviewItem): void {
|
||||
this.selectItem(item);
|
||||
}
|
||||
|
||||
private updateSelectedItem(): void {
|
||||
if (!isNil(this.items)) {
|
||||
const selectedItem = TreeviewHelper.findItemInList(this.items, this.value);
|
||||
this.selectItem(selectedItem);
|
||||
}
|
||||
}
|
||||
|
||||
private selectItem(item: TreeviewItem): void {
|
||||
if (this.dropdownTreeviewSelectI18n.selectedItem !== item) {
|
||||
this.dropdownTreeviewSelectI18n.selectedItem = item;
|
||||
if (this.dropdownTreeviewComponent) {
|
||||
this.dropdownTreeviewComponent.onSelectedChange([item]);
|
||||
}
|
||||
|
||||
if (item) {
|
||||
if (this.value !== item.value) {
|
||||
this.value = item.value;
|
||||
this.valueChange.emit(item.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
22
frontend/src/ts/component/external/ngx-treeview/lib/dropdown-menu.directive.ts
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { Directive, HostListener } from '@angular/core';
|
||||
import { DropdownDirective } from './dropdown.directive';
|
||||
|
||||
@Directive({
|
||||
selector: '[ngxDropdownMenu]',
|
||||
host: {
|
||||
'[class.dropdown-menu]': 'true',
|
||||
'[class.show]': 'dropdown.isOpen'
|
||||
}
|
||||
})
|
||||
export class DropdownMenuDirective {
|
||||
constructor(public dropdown: DropdownDirective) {
|
||||
}
|
||||
|
||||
@HostListener('click', ['$event'])
|
||||
onClick(event: MouseEvent): void {
|
||||
if (event.button !== 2 && event.srcElement.attributes[0]
|
||||
&& event.srcElement.attributes[0].nodeValue === 'form-check-label') {
|
||||
this.dropdown.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
17
frontend/src/ts/component/external/ngx-treeview/lib/dropdown-toggle.directive.ts
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { Directive, ElementRef } from '@angular/core';
|
||||
import { DropdownDirective } from './dropdown.directive';
|
||||
|
||||
@Directive({
|
||||
selector: '[ngxDropdownToggle]',
|
||||
host: {
|
||||
class: 'dropdown-toggle',
|
||||
'aria-haspopup': 'true',
|
||||
'[attr.aria-expanded]': 'dropdown.isOpen',
|
||||
'(click)': 'dropdown.toggle()'
|
||||
}
|
||||
})
|
||||
export class DropdownToggleDirective {
|
||||
constructor(public dropdown: DropdownDirective, elementRef: ElementRef) {
|
||||
dropdown.toggleElement = elementRef.nativeElement;
|
||||
}
|
||||
}
|
||||
49
frontend/src/ts/component/external/ngx-treeview/lib/dropdown-treeview.component.ts
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import {
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
Output,
|
||||
ViewChild,
|
||||
TemplateRef,
|
||||
ChangeDetectionStrategy
|
||||
} from '@angular/core';
|
||||
import { TreeviewI18n } from './treeview-i18n';
|
||||
import { TreeviewItem } from './treeview-item';
|
||||
import { TreeviewConfig } from './treeview-config';
|
||||
import { TreeviewComponent } from './treeview.component';
|
||||
import { TreeviewHeaderTemplateContext } from './treeview-header-template-context';
|
||||
import { TreeviewItemTemplateContext } from './treeview-item-template-context';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'ngx-dropdown-treeview',
|
||||
templateUrl: './../../../../../../src/resources/template/component/external/ngx-treeview/dropdown-treeview.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class DropdownTreeviewComponent {
|
||||
@Input() buttonClass = 'btn-outline-secondary';
|
||||
@Input() headerTemplate: TemplateRef<TreeviewHeaderTemplateContext>;
|
||||
@Input() itemTemplate: TemplateRef<TreeviewItemTemplateContext>;
|
||||
@Input() items: TreeviewItem[];
|
||||
@Input() config: TreeviewConfig;
|
||||
@Output() selectedChange = new EventEmitter<any[]>(true);
|
||||
@Output() filterChange = new EventEmitter<string>();
|
||||
@ViewChild(TreeviewComponent) treeviewComponent: TreeviewComponent;
|
||||
buttonLabel: string;
|
||||
|
||||
constructor(
|
||||
public i18n: TreeviewI18n,
|
||||
private defaultConfig: TreeviewConfig
|
||||
) {
|
||||
this.config = this.defaultConfig;
|
||||
}
|
||||
|
||||
onSelectedChange(values: any[]): void {
|
||||
this.buttonLabel = this.i18n.getText(this.treeviewComponent.selection);
|
||||
this.selectedChange.emit(values);
|
||||
}
|
||||
|
||||
onFilterChange(text: string): void {
|
||||
this.filterChange.emit(text);
|
||||
}
|
||||
}
|
||||
54
frontend/src/ts/component/external/ngx-treeview/lib/dropdown.directive.ts
vendored
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import { Directive, Input, Output, HostBinding, HostListener, EventEmitter } from '@angular/core';
|
||||
import { isNil } from './utils';
|
||||
|
||||
@Directive({
|
||||
selector: '[ngxDropdown]',
|
||||
exportAs: 'ngxDropdown'
|
||||
})
|
||||
export class DropdownDirective {
|
||||
toggleElement: any;
|
||||
@Input('open') internalOpen = false;
|
||||
@Output() openChange = new EventEmitter<boolean>();
|
||||
|
||||
@HostBinding('class.show') get isOpen(): boolean {
|
||||
return this.internalOpen;
|
||||
}
|
||||
|
||||
@HostListener('keyup.esc')
|
||||
onKeyupEsc(): void {
|
||||
this.close();
|
||||
}
|
||||
|
||||
@HostListener('document:click', ['$event'])
|
||||
onDocumentClick(event: MouseEvent): void {
|
||||
if (event.button !== 2 && !this.isEventFromToggle(event)) {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
||||
open(): void {
|
||||
if (!this.internalOpen) {
|
||||
this.internalOpen = true;
|
||||
this.openChange.emit(true);
|
||||
}
|
||||
}
|
||||
|
||||
close(): void {
|
||||
if (this.internalOpen) {
|
||||
this.internalOpen = false;
|
||||
this.openChange.emit(false);
|
||||
}
|
||||
}
|
||||
|
||||
toggle(): void {
|
||||
if (this.isOpen) {
|
||||
this.close();
|
||||
} else {
|
||||
this.open();
|
||||
}
|
||||
}
|
||||
|
||||
private isEventFromToggle(event: MouseEvent): boolean {
|
||||
return !isNil(this.toggleElement) && this.toggleElement.contains(event.target);
|
||||
}
|
||||
}
|
||||
26
frontend/src/ts/component/external/ngx-treeview/lib/treeview-config.ts
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable()
|
||||
export class TreeviewConfig {
|
||||
hasAllCheckBox = true;
|
||||
hasFilter = false;
|
||||
hasCollapseExpand = false;
|
||||
decoupleChildFromParent = false;
|
||||
maxHeight = 500;
|
||||
|
||||
get hasDivider(): boolean {
|
||||
return this.hasFilter || this.hasAllCheckBox || this.hasCollapseExpand;
|
||||
}
|
||||
|
||||
public static create(fields?: {
|
||||
hasAllCheckBox?: boolean,
|
||||
hasFilter?: boolean,
|
||||
hasCollapseExpand?: boolean,
|
||||
decoupleChildFromParent?: boolean
|
||||
maxHeight?: number,
|
||||
}): TreeviewConfig {
|
||||
const config = new TreeviewConfig();
|
||||
Object.assign(config, fields);
|
||||
return config;
|
||||
}
|
||||
}
|
||||
107
frontend/src/ts/component/external/ngx-treeview/lib/treeview-event-parser.ts
vendored
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { TreeviewItem } from './treeview-item';
|
||||
import {TreeviewComponent} from './treeview.component';
|
||||
import { isNil } from './utils';
|
||||
|
||||
@Injectable()
|
||||
export abstract class TreeviewEventParser {
|
||||
abstract getSelectedChange(component: TreeviewComponent): any[];
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class DefaultTreeviewEventParser extends TreeviewEventParser {
|
||||
getSelectedChange(component: TreeviewComponent): any[] {
|
||||
const checkedItems = component.selection.checkedItems;
|
||||
if (!isNil(checkedItems)) {
|
||||
return checkedItems.map(item => item.value);
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
export interface DownlineTreeviewItem {
|
||||
item: TreeviewItem;
|
||||
parent: DownlineTreeviewItem;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class DownlineTreeviewEventParser extends TreeviewEventParser {
|
||||
getSelectedChange(component: TreeviewComponent): any[] {
|
||||
const items = component.items;
|
||||
if (!isNil(items)) {
|
||||
let result: DownlineTreeviewItem[] = [];
|
||||
items.forEach(item => {
|
||||
const links = this.getLinks(item, null);
|
||||
if (!isNil(links)) {
|
||||
result = result.concat(links);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
private getLinks(item: TreeviewItem, parent: DownlineTreeviewItem): DownlineTreeviewItem[] {
|
||||
if (!isNil(item.children)) {
|
||||
const link = {
|
||||
item,
|
||||
parent
|
||||
};
|
||||
let result: DownlineTreeviewItem[] = [];
|
||||
item.children.forEach(child => {
|
||||
const links = this.getLinks(child, link);
|
||||
if (!isNil(links)) {
|
||||
result = result.concat(links);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (item.checked) {
|
||||
return [{
|
||||
item,
|
||||
parent
|
||||
}];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class OrderDownlineTreeviewEventParser extends TreeviewEventParser {
|
||||
private currentDownlines: DownlineTreeviewItem[] = [];
|
||||
private parser = new DownlineTreeviewEventParser();
|
||||
|
||||
getSelectedChange(component: TreeviewComponent): any[] {
|
||||
const newDownlines: DownlineTreeviewItem[] = this.parser.getSelectedChange(component);
|
||||
if (this.currentDownlines.length === 0) {
|
||||
this.currentDownlines = newDownlines;
|
||||
} else {
|
||||
const intersectDownlines: DownlineTreeviewItem[] = [];
|
||||
this.currentDownlines.forEach(downline => {
|
||||
let foundIndex = -1;
|
||||
const length = newDownlines.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
if (downline.item.value === newDownlines[i].item.value) {
|
||||
foundIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (foundIndex !== -1) {
|
||||
intersectDownlines.push(newDownlines[foundIndex]);
|
||||
newDownlines.splice(foundIndex, 1);
|
||||
}
|
||||
});
|
||||
|
||||
this.currentDownlines = intersectDownlines.concat(newDownlines);
|
||||
}
|
||||
|
||||
return this.currentDownlines;
|
||||
}
|
||||
}
|
||||
10
frontend/src/ts/component/external/ngx-treeview/lib/treeview-header-template-context.ts
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import { TreeviewItem } from './treeview-item';
|
||||
import { TreeviewConfig } from './treeview-config';
|
||||
|
||||
export interface TreeviewHeaderTemplateContext {
|
||||
config: TreeviewConfig;
|
||||
item: TreeviewItem;
|
||||
onCollapseExpand: () => void;
|
||||
onCheckedChange: (checked: boolean) => void;
|
||||
onFilterTextChange: (text: string) => void;
|
||||
}
|
||||
94
frontend/src/ts/component/external/ngx-treeview/lib/treeview-helper.ts
vendored
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
import { TreeviewItem } from './treeview-item';
|
||||
import { isNil, pull } from './utils';
|
||||
|
||||
export const TreeviewHelper = {
|
||||
findItem,
|
||||
findItemInList,
|
||||
findParent,
|
||||
removeItem,
|
||||
concatSelection
|
||||
};
|
||||
|
||||
function findItem(root: TreeviewItem, value: any): TreeviewItem {
|
||||
if (isNil(root)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (root.value === value) {
|
||||
return root;
|
||||
}
|
||||
|
||||
if (root.children) {
|
||||
for (const child of root.children) {
|
||||
const foundItem = findItem(child, value);
|
||||
if (foundItem) {
|
||||
return foundItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function findItemInList(list: TreeviewItem[], value: any): TreeviewItem {
|
||||
if (isNil(list)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
for (const item of list) {
|
||||
const foundItem = findItem(item, value);
|
||||
if (foundItem) {
|
||||
return foundItem;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function findParent(root: TreeviewItem, item: TreeviewItem): TreeviewItem {
|
||||
if (isNil(root) || isNil(root.children)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
for (const child of root.children) {
|
||||
if (child === item) {
|
||||
return root;
|
||||
} else {
|
||||
const parent = findParent(child, item);
|
||||
if (parent) {
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function removeItem(root: TreeviewItem, item: TreeviewItem): boolean {
|
||||
const parent = findParent(root, item);
|
||||
if (parent) {
|
||||
pull(parent.children, item);
|
||||
if (parent.children.length === 0) {
|
||||
parent.children = undefined;
|
||||
} else {
|
||||
parent.correctChecked();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function concatSelection(items: TreeviewItem[], checked: TreeviewItem[], unchecked: TreeviewItem[]): { [k: string]: TreeviewItem[] } {
|
||||
let checkedItems = [...checked];
|
||||
let uncheckedItems = [...unchecked];
|
||||
for (const item of items) {
|
||||
const selection = item.getSelection();
|
||||
checkedItems = checkedItems.concat(selection.checkedItems);
|
||||
uncheckedItems = uncheckedItems.concat(selection.uncheckedItems);
|
||||
}
|
||||
return {
|
||||
checked: checkedItems,
|
||||
unchecked: uncheckedItems
|
||||
};
|
||||
}
|
||||
49
frontend/src/ts/component/external/ngx-treeview/lib/treeview-i18n.ts
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { TreeviewSelection } from './treeview-item';
|
||||
|
||||
@Injectable()
|
||||
export abstract class TreeviewI18n {
|
||||
abstract getText(selection: TreeviewSelection): string;
|
||||
abstract getAllCheckboxText(): string;
|
||||
abstract getFilterPlaceholder(): string;
|
||||
abstract getFilterNoItemsFoundText(): string;
|
||||
abstract getTooltipCollapseExpandText(isCollapse: boolean): string;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class DefaultTreeviewI18n extends TreeviewI18n {
|
||||
getText(selection: TreeviewSelection): string {
|
||||
if (selection.uncheckedItems.length === 0) {
|
||||
if (selection.checkedItems.length > 0) {
|
||||
return this.getAllCheckboxText();
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
switch (selection.checkedItems.length) {
|
||||
case 0:
|
||||
return 'Select options';
|
||||
case 1:
|
||||
return selection.checkedItems[0].text;
|
||||
default:
|
||||
return `${selection.checkedItems.length} options selected`;
|
||||
}
|
||||
}
|
||||
|
||||
getAllCheckboxText(): string {
|
||||
return 'Все';
|
||||
}
|
||||
|
||||
getFilterPlaceholder(): string {
|
||||
return 'Поиск';
|
||||
}
|
||||
|
||||
getFilterNoItemsFoundText(): string {
|
||||
return 'Элементы не найдены';
|
||||
}
|
||||
|
||||
getTooltipCollapseExpandText(isCollapse: boolean): string {
|
||||
return isCollapse ? 'Развернуть' : 'Свернуть';
|
||||
}
|
||||
}
|
||||
7
frontend/src/ts/component/external/ngx-treeview/lib/treeview-item-template-context.ts
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import { TreeviewItem } from './treeview-item';
|
||||
|
||||
export interface TreeviewItemTemplateContext {
|
||||
item: TreeviewItem;
|
||||
onCollapseExpand: () => void;
|
||||
onCheckedChange: () => void;
|
||||
}
|
||||
68
frontend/src/ts/component/external/ngx-treeview/lib/treeview-item.component.ts
vendored
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
EventEmitter,
|
||||
Input,
|
||||
Output,
|
||||
TemplateRef
|
||||
} from '@angular/core';
|
||||
import { TreeviewConfig } from './treeview-config';
|
||||
import { TreeviewItem } from './treeview-item';
|
||||
import { TreeviewItemTemplateContext } from './treeview-item-template-context';
|
||||
import { isNil } from './utils';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'ngx-treeview-item',
|
||||
templateUrl: './../../../../../../src/resources/template/component/external/ngx-treeview/treeview-item.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class TreeviewItemComponent {
|
||||
@Input() config: TreeviewConfig;
|
||||
@Input() template: TemplateRef<TreeviewItemTemplateContext>;
|
||||
@Input() item: TreeviewItem;
|
||||
@Output() checkedChange = new EventEmitter<boolean>();
|
||||
|
||||
constructor(
|
||||
private defaultConfig: TreeviewConfig
|
||||
) {
|
||||
this.config = this.defaultConfig;
|
||||
}
|
||||
|
||||
onCollapseExpand = () => {
|
||||
this.item.collapsed = !this.item.collapsed;
|
||||
}
|
||||
|
||||
onCheckedChange = () => {
|
||||
const checked = this.item.checked;
|
||||
if (!isNil(this.item.children) && !this.config.decoupleChildFromParent) {
|
||||
this.item.children.forEach(child => child.setCheckedRecursive(checked));
|
||||
}
|
||||
this.checkedChange.emit(checked);
|
||||
}
|
||||
|
||||
onChildCheckedChange(child: TreeviewItem, checked: boolean): void {
|
||||
if (!this.config.decoupleChildFromParent) {
|
||||
let itemChecked: boolean = null;
|
||||
for (const childItem of this.item.children) {
|
||||
if (itemChecked === null) {
|
||||
itemChecked = childItem.checked;
|
||||
} else if (itemChecked !== childItem.checked) {
|
||||
itemChecked = undefined;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (itemChecked === null) {
|
||||
itemChecked = false;
|
||||
}
|
||||
|
||||
if (this.item.checked !== itemChecked) {
|
||||
this.item.checked = itemChecked;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this.checkedChange.emit(checked);
|
||||
}
|
||||
}
|
||||
185
frontend/src/ts/component/external/ngx-treeview/lib/treeview-item.ts
vendored
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
import { TreeviewHelper } from './treeview-helper';
|
||||
import { isBoolean, isNil, isString } from './utils';
|
||||
|
||||
export interface TreeviewSelection {
|
||||
checkedItems: TreeviewItem[];
|
||||
uncheckedItems: TreeviewItem[];
|
||||
}
|
||||
|
||||
export interface TreeItem {
|
||||
text: string;
|
||||
value: any;
|
||||
disabled?: boolean;
|
||||
checked?: boolean;
|
||||
collapsed?: boolean;
|
||||
children?: TreeItem[];
|
||||
}
|
||||
|
||||
export class TreeviewItem {
|
||||
private internalDisabled = false;
|
||||
private internalChecked = true;
|
||||
private internalCollapsed = false;
|
||||
private internalChildren: TreeviewItem[];
|
||||
text: string;
|
||||
value: any;
|
||||
|
||||
constructor(item: TreeItem, autoCorrectChecked = false) {
|
||||
if (isNil(item)) {
|
||||
throw new Error('Item must be defined');
|
||||
}
|
||||
if (isString(item.text)) {
|
||||
this.text = item.text;
|
||||
} else {
|
||||
throw new Error('A text of item must be string object');
|
||||
}
|
||||
this.value = item.value;
|
||||
if (isBoolean(item.checked)) {
|
||||
this.checked = item.checked;
|
||||
}
|
||||
if (isBoolean(item.collapsed)) {
|
||||
this.collapsed = item.collapsed;
|
||||
}
|
||||
if (isBoolean(item.disabled)) {
|
||||
this.disabled = item.disabled;
|
||||
}
|
||||
if (!isNil(item.children) && item.children.length > 0) {
|
||||
this.children = item.children.map(child => {
|
||||
if (this.disabled === true) {
|
||||
child.disabled = true;
|
||||
}
|
||||
|
||||
return new TreeviewItem(child);
|
||||
});
|
||||
}
|
||||
|
||||
if (autoCorrectChecked) {
|
||||
this.correctChecked();
|
||||
}
|
||||
}
|
||||
|
||||
get checked(): boolean {
|
||||
return this.internalChecked;
|
||||
}
|
||||
|
||||
set checked(value: boolean) {
|
||||
if (!this.internalDisabled) {
|
||||
if (this.internalChecked !== value) {
|
||||
this.internalChecked = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get indeterminate(): boolean {
|
||||
return this.checked === undefined;
|
||||
}
|
||||
|
||||
setCheckedRecursive(value: boolean): void {
|
||||
if (!this.internalDisabled) {
|
||||
this.internalChecked = value;
|
||||
if (!isNil(this.internalChildren)) {
|
||||
this.internalChildren.forEach(child => child.setCheckedRecursive(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get disabled(): boolean {
|
||||
return this.internalDisabled;
|
||||
}
|
||||
|
||||
set disabled(value: boolean) {
|
||||
if (this.internalDisabled !== value) {
|
||||
this.internalDisabled = value;
|
||||
if (!isNil(this.internalChildren)) {
|
||||
this.internalChildren.forEach(child => child.disabled = value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get collapsed(): boolean {
|
||||
return this.internalCollapsed;
|
||||
}
|
||||
|
||||
set collapsed(value: boolean) {
|
||||
if (this.internalCollapsed !== value) {
|
||||
this.internalCollapsed = value;
|
||||
}
|
||||
}
|
||||
|
||||
setCollapsedRecursive(value: boolean): void {
|
||||
this.internalCollapsed = value;
|
||||
if (!isNil(this.internalChildren)) {
|
||||
this.internalChildren.forEach(child => child.setCollapsedRecursive(value));
|
||||
}
|
||||
}
|
||||
|
||||
get children(): TreeviewItem[] {
|
||||
return this.internalChildren;
|
||||
}
|
||||
|
||||
set children(value: TreeviewItem[]) {
|
||||
if (this.internalChildren !== value) {
|
||||
if (!isNil(value) && value.length === 0) {
|
||||
throw new Error('Children must be not an empty array');
|
||||
}
|
||||
this.internalChildren = value;
|
||||
if (!isNil(this.internalChildren)) {
|
||||
let checked = null;
|
||||
this.internalChildren.forEach(child => {
|
||||
if (checked === null) {
|
||||
checked = child.checked;
|
||||
} else {
|
||||
if (child.checked !== checked) {
|
||||
checked = undefined;
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
this.internalChecked = checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getSelection(): TreeviewSelection {
|
||||
let checkedItems: TreeviewItem[] = [];
|
||||
let uncheckedItems: TreeviewItem[] = [];
|
||||
if (isNil(this.internalChildren)) {
|
||||
if (this.internalChecked) {
|
||||
checkedItems.push(this);
|
||||
} else {
|
||||
uncheckedItems.push(this);
|
||||
}
|
||||
} else {
|
||||
const selection = TreeviewHelper.concatSelection(this.internalChildren, checkedItems, uncheckedItems);
|
||||
checkedItems = selection.checked;
|
||||
uncheckedItems = selection.unchecked;
|
||||
}
|
||||
|
||||
return {
|
||||
checkedItems,
|
||||
uncheckedItems
|
||||
};
|
||||
}
|
||||
|
||||
correctChecked(): void {
|
||||
this.internalChecked = this.getCorrectChecked();
|
||||
}
|
||||
|
||||
private getCorrectChecked(): boolean {
|
||||
let checked: boolean = null;
|
||||
if (!isNil(this.internalChildren)) {
|
||||
for (const child of this.internalChildren) {
|
||||
child.internalChecked = child.getCorrectChecked();
|
||||
if (checked === null) {
|
||||
checked = child.internalChecked;
|
||||
} else if (checked !== child.internalChecked) {
|
||||
checked = undefined;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
checked = this.checked;
|
||||
}
|
||||
|
||||
return checked;
|
||||
}
|
||||
}
|
||||
242
frontend/src/ts/component/external/ngx-treeview/lib/treeview.component.ts
vendored
Normal file
|
|
@ -0,0 +1,242 @@
|
|||
import {
|
||||
Component,
|
||||
Input,
|
||||
Output,
|
||||
EventEmitter,
|
||||
OnChanges,
|
||||
OnInit,
|
||||
SimpleChanges,
|
||||
TemplateRef,
|
||||
ChangeDetectionStrategy
|
||||
} from '@angular/core';
|
||||
import { TreeviewI18n } from './treeview-i18n';
|
||||
import { TreeviewItem, TreeviewSelection } from './treeview-item';
|
||||
import { TreeviewConfig } from './treeview-config';
|
||||
import { TreeviewEventParser } from './treeview-event-parser';
|
||||
import { TreeviewHeaderTemplateContext } from './treeview-header-template-context';
|
||||
import { TreeviewItemTemplateContext } from './treeview-item-template-context';
|
||||
import { TreeviewHelper } from './treeview-helper';
|
||||
import { isNil } from './utils';
|
||||
|
||||
class FilterTreeviewItem extends TreeviewItem {
|
||||
private readonly refItem: TreeviewItem;
|
||||
constructor(item: TreeviewItem) {
|
||||
super({
|
||||
text: item.text,
|
||||
value: item.value,
|
||||
disabled: item.disabled,
|
||||
checked: item.checked,
|
||||
collapsed: item.collapsed,
|
||||
children: item.children
|
||||
});
|
||||
this.refItem = item;
|
||||
}
|
||||
|
||||
updateRefChecked(): void {
|
||||
this.children.forEach(child => {
|
||||
if (child instanceof FilterTreeviewItem) {
|
||||
child.updateRefChecked();
|
||||
}
|
||||
});
|
||||
|
||||
let refChecked = this.checked;
|
||||
if (refChecked) {
|
||||
for (const refChild of this.refItem.children) {
|
||||
if (!refChild.checked) {
|
||||
refChecked = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.refItem.checked = refChecked;
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'ngx-treeview',
|
||||
templateUrl: './../../../../../../src/resources/template/component/external/ngx-treeview/treeview.component.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class TreeviewComponent implements OnChanges, OnInit {
|
||||
@Input() headerTemplate: TemplateRef<TreeviewHeaderTemplateContext>;
|
||||
@Input() itemTemplate: TemplateRef<TreeviewItemTemplateContext>;
|
||||
@Input() items: TreeviewItem[];
|
||||
@Input() config: TreeviewConfig;
|
||||
@Output() selectedChange = new EventEmitter<any[]>();
|
||||
@Output() filterChange = new EventEmitter<string>();
|
||||
headerTemplateContext: TreeviewHeaderTemplateContext;
|
||||
allItem: TreeviewItem;
|
||||
filterText = '';
|
||||
filterItems: TreeviewItem[];
|
||||
selection: TreeviewSelection;
|
||||
|
||||
constructor(
|
||||
public i18n: TreeviewI18n,
|
||||
private defaultConfig: TreeviewConfig,
|
||||
private eventParser: TreeviewEventParser
|
||||
) {
|
||||
this.config = this.defaultConfig;
|
||||
this.allItem = new TreeviewItem({ text: 'All', value: undefined });
|
||||
}
|
||||
|
||||
get hasFilterItems(): boolean {
|
||||
return !isNil(this.filterItems) && this.filterItems.length > 0;
|
||||
}
|
||||
|
||||
get maxHeight(): string {
|
||||
return `${this.config.maxHeight}`;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.createHeaderTemplateContext();
|
||||
this.generateSelection();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
const itemsSimpleChange = changes.items;
|
||||
if (!isNil(itemsSimpleChange) && !isNil(this.items)) {
|
||||
this.updateFilterItems();
|
||||
this.updateCollapsedOfAll();
|
||||
this.raiseSelectedChange();
|
||||
}
|
||||
}
|
||||
|
||||
onAllCollapseExpand(): void {
|
||||
this.allItem.collapsed = !this.allItem.collapsed;
|
||||
this.filterItems.forEach(item => item.setCollapsedRecursive(this.allItem.collapsed));
|
||||
}
|
||||
|
||||
onFilterTextChange(text: string): void {
|
||||
this.filterText = text;
|
||||
this.filterChange.emit(text);
|
||||
this.updateFilterItems();
|
||||
}
|
||||
|
||||
onAllCheckedChange(): void {
|
||||
const checked = this.allItem.checked;
|
||||
this.filterItems.forEach(item => {
|
||||
item.setCheckedRecursive(checked);
|
||||
if (item instanceof FilterTreeviewItem) {
|
||||
item.updateRefChecked();
|
||||
}
|
||||
});
|
||||
|
||||
this.raiseSelectedChange();
|
||||
}
|
||||
|
||||
onItemCheckedChange(item: TreeviewItem, checked: boolean): void {
|
||||
if (item instanceof FilterTreeviewItem) {
|
||||
item.updateRefChecked();
|
||||
}
|
||||
|
||||
this.updateCheckedOfAll();
|
||||
this.raiseSelectedChange();
|
||||
}
|
||||
|
||||
raiseSelectedChange(): void {
|
||||
this.generateSelection();
|
||||
const values = this.eventParser.getSelectedChange(this);
|
||||
setTimeout(() => {
|
||||
this.selectedChange.emit(values);
|
||||
});
|
||||
}
|
||||
|
||||
private createHeaderTemplateContext(): void {
|
||||
this.headerTemplateContext = {
|
||||
config: this.config,
|
||||
item: this.allItem,
|
||||
onCheckedChange: () => this.onAllCheckedChange(),
|
||||
onCollapseExpand: () => this.onAllCollapseExpand(),
|
||||
onFilterTextChange: (text) => this.onFilterTextChange(text)
|
||||
};
|
||||
}
|
||||
|
||||
private generateSelection(): void {
|
||||
let checkedItems: TreeviewItem[] = [];
|
||||
let uncheckedItems: TreeviewItem[] = [];
|
||||
if (!isNil(this.items)) {
|
||||
const selection = TreeviewHelper.concatSelection(this.items, checkedItems, uncheckedItems);
|
||||
checkedItems = selection.checked;
|
||||
uncheckedItems = selection.unchecked;
|
||||
}
|
||||
|
||||
this.selection = {
|
||||
checkedItems,
|
||||
uncheckedItems
|
||||
};
|
||||
}
|
||||
|
||||
private updateFilterItems(): void {
|
||||
if (this.filterText !== '') {
|
||||
const filterItems: TreeviewItem[] = [];
|
||||
const filterText = this.filterText.toLowerCase();
|
||||
this.items.forEach(item => {
|
||||
const newItem = this.filterItem(item, filterText);
|
||||
if (!isNil(newItem)) {
|
||||
filterItems.push(newItem);
|
||||
}
|
||||
});
|
||||
this.filterItems = filterItems;
|
||||
} else {
|
||||
this.filterItems = this.items;
|
||||
}
|
||||
|
||||
this.updateCheckedOfAll();
|
||||
}
|
||||
|
||||
private filterItem(item: TreeviewItem, filterText: string): TreeviewItem {
|
||||
const isMatch = item.text.toLowerCase().includes(filterText);
|
||||
if (isMatch) {
|
||||
return item;
|
||||
} else {
|
||||
if (!isNil(item.children)) {
|
||||
const children: TreeviewItem[] = [];
|
||||
item.children.forEach(child => {
|
||||
const newChild = this.filterItem(child, filterText);
|
||||
if (!isNil(newChild)) {
|
||||
children.push(newChild);
|
||||
}
|
||||
});
|
||||
if (children.length > 0) {
|
||||
const newItem = new FilterTreeviewItem(item);
|
||||
newItem.collapsed = false;
|
||||
newItem.children = children;
|
||||
return newItem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private updateCheckedOfAll(): void {
|
||||
let itemChecked: boolean = null;
|
||||
for (const filterItem of this.filterItems) {
|
||||
if (itemChecked === null) {
|
||||
itemChecked = filterItem.checked;
|
||||
} else if (itemChecked !== filterItem.checked) {
|
||||
itemChecked = undefined;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (itemChecked === null) {
|
||||
itemChecked = false;
|
||||
}
|
||||
|
||||
this.allItem.checked = itemChecked;
|
||||
}
|
||||
|
||||
private updateCollapsedOfAll(): void {
|
||||
let hasItemExpanded = false;
|
||||
for (const filterItem of this.filterItems) {
|
||||
if (!filterItem.collapsed) {
|
||||
hasItemExpanded = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.allItem.collapsed = !hasItemExpanded;
|
||||
}
|
||||
}
|
||||
48
frontend/src/ts/component/external/ngx-treeview/lib/treeview.module.ts
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import { NgModule, ModuleWithProviders } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { DropdownDirective } from './dropdown.directive';
|
||||
import { DropdownMenuDirective } from './dropdown-menu.directive';
|
||||
import { DropdownToggleDirective } from './dropdown-toggle.directive';
|
||||
import { DropdownTreeviewComponent } from './dropdown-treeview.component';
|
||||
import { TreeviewComponent } from './treeview.component';
|
||||
import { TreeviewItemComponent } from './treeview-item.component';
|
||||
import { TreeviewPipe } from './treeview.pipe';
|
||||
import { TreeviewI18n, DefaultTreeviewI18n } from './treeview-i18n';
|
||||
import { TreeviewConfig } from './treeview-config';
|
||||
import { TreeviewEventParser, DefaultTreeviewEventParser } from './treeview-event-parser';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
FormsModule,
|
||||
CommonModule
|
||||
],
|
||||
declarations: [
|
||||
// TreeviewComponent,
|
||||
// TreeviewItemComponent,
|
||||
TreeviewPipe,
|
||||
DropdownDirective,
|
||||
DropdownMenuDirective,
|
||||
DropdownToggleDirective
|
||||
// DropdownTreeviewComponent
|
||||
], exports: [
|
||||
// TreeviewComponent,
|
||||
TreeviewPipe,
|
||||
DropdownDirective,
|
||||
DropdownMenuDirective,
|
||||
DropdownToggleDirective
|
||||
// DropdownTreeviewComponent
|
||||
]
|
||||
})
|
||||
export class TreeviewModule {
|
||||
static forRoot(): ModuleWithProviders {
|
||||
return {
|
||||
ngModule: TreeviewModule,
|
||||
providers: [
|
||||
TreeviewConfig,
|
||||
{ provide: TreeviewI18n, useClass: DefaultTreeviewI18n },
|
||||
{ provide: TreeviewEventParser, useClass: DefaultTreeviewEventParser }
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
16
frontend/src/ts/component/external/ngx-treeview/lib/treeview.pipe.ts
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
import { TreeviewItem } from './treeview-item';
|
||||
import { isNil } from './utils';
|
||||
|
||||
@Pipe({
|
||||
name: 'ngxTreeview'
|
||||
})
|
||||
export class TreeviewPipe implements PipeTransform {
|
||||
transform(objects: any[], textField: string): TreeviewItem[] {
|
||||
if (isNil(objects)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return objects.map(object => new TreeviewItem({ text: object[textField], value: object }));
|
||||
}
|
||||
}
|
||||
45
frontend/src/ts/component/external/ngx-treeview/lib/utils.ts
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
export default class Utils {
|
||||
static isBoolean(value): boolean { return isBoolean(value); }
|
||||
static isNil(value): boolean { return isNil(value); }
|
||||
static isString(value): boolean { return isString(value); }
|
||||
};
|
||||
|
||||
export const isBoolean = (value:any): boolean => {
|
||||
return value === true || value === false; // todo not completed; to complete from 'lodash'
|
||||
};
|
||||
|
||||
export const isNil = (value:any): boolean => {
|
||||
// from 'lodash'
|
||||
return value == null;
|
||||
};
|
||||
|
||||
export const isString = (value:any): boolean => {
|
||||
return typeof value == 'string'; // todo not completed; to complete from 'lodash'
|
||||
};
|
||||
|
||||
export const pull = (array:any[], item:any): any[] => {
|
||||
// modified 'remove' from 'lodash'
|
||||
// mutate array
|
||||
const result = [];
|
||||
if (!(array && array.length)) {
|
||||
return result;
|
||||
}
|
||||
const indexes = [];
|
||||
|
||||
array.forEach((value, index) => {
|
||||
if (value === item) {
|
||||
result.push(value);
|
||||
indexes.push(index);
|
||||
}
|
||||
});
|
||||
|
||||
for (const index of indexes.reverse()) {
|
||||
array.splice(index, 1);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
export const reverse = (array:any[]): any[] => {
|
||||
// from 'lodash'
|
||||
return array == null ? array : array.reverse();
|
||||
};
|
||||
107
frontend/src/ts/component/field/DropdownTreeViewComponent.ts
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef} from "@angular/core";
|
||||
import {Control, Event, UserService, Visible} from "@webbpm/base-package";
|
||||
import {TreeItem, TreeviewItem} from "../external/ngx-treeview/lib/treeview-item";
|
||||
import {
|
||||
DropdownTreeviewSelectI18n
|
||||
} from "../external/ngx-treeview/dropdown-treeview-select/dropdown-treeview-select-i18n";
|
||||
import {TreeItemDto} from "../../generated/component/model/TreeItemDto";
|
||||
import {TreeItemRpcService} from "../../generated/component/rpc/TreeItemRpcService";
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'dropdown-tree-view',
|
||||
templateUrl: './../../../../src/resources/template/component/field/DropdownTreeView.html',
|
||||
providers: [{
|
||||
provide: DropdownTreeviewSelectI18n, useClass: DropdownTreeviewSelectI18n
|
||||
}],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class DropdownTreeViewComponent extends Control {
|
||||
|
||||
public collapseLevel: number;
|
||||
public maxHeight: number;
|
||||
@Visible("false")
|
||||
public items: TreeviewItem[];
|
||||
@Visible("false")
|
||||
public value: any;
|
||||
|
||||
@Visible("false")
|
||||
public valueChangeEvent: Event<TreeItemDto> = new Event<TreeItemDto>();
|
||||
|
||||
private rpcService: TreeItemRpcService;
|
||||
|
||||
constructor(el: ElementRef, cd: ChangeDetectorRef,
|
||||
// todo replace UserService by another one from SUPPORT-8421 providing accountId or domainId
|
||||
private i18n: DropdownTreeviewSelectI18n, private userService: UserService) {
|
||||
super(el, cd);
|
||||
}
|
||||
|
||||
public initialize() {
|
||||
super.initialize();
|
||||
this.rpcService = this.getScript(TreeItemRpcService);
|
||||
this.loadTreeItems();
|
||||
}
|
||||
|
||||
@Visible()
|
||||
public loadTreeItems(): void {
|
||||
// todo replace the called method by
|
||||
// this.rpcService.loadTreeDataByDomainId(domainId)
|
||||
this.rpcService.loadTreeData()
|
||||
.then((res: TreeItemDto[]) => {
|
||||
this.items = res.map(value => new TreeviewItem(this.createTreeItem(value)));
|
||||
const rootItem = this.items[0];
|
||||
this.i18n.selectedItem = rootItem;
|
||||
this.value = rootItem.value;
|
||||
this.doCollapseLevel();
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
private createTreeItem(treeItemDto: TreeItemDto): TreeItem {
|
||||
let treeItem: TreeItem;
|
||||
if (treeItemDto) {
|
||||
treeItem = {
|
||||
text: treeItemDto.label,
|
||||
value: treeItemDto,
|
||||
children: this.createTreeItemArray(treeItemDto.children)
|
||||
};
|
||||
}
|
||||
return treeItem;
|
||||
}
|
||||
|
||||
private createTreeItemArray(treeItemDtoArray: TreeItemDto[]): TreeItem[] {
|
||||
if (treeItemDtoArray && treeItemDtoArray.length > 0) {
|
||||
return treeItemDtoArray.map(value => this.createTreeItem(value));
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
public onValueChange($event: any) {
|
||||
this.valueChangeEvent.trigger($event);
|
||||
}
|
||||
|
||||
@Visible()
|
||||
public getBusinessId(): any {
|
||||
return this.value ? this.value.businessId : this.value;
|
||||
}
|
||||
|
||||
@Visible()
|
||||
public setCollapseLevel(level: number): void {
|
||||
this.collapseLevel = level;
|
||||
}
|
||||
|
||||
private doCollapseLevel(): void {
|
||||
if (this.items != null && this.collapseLevel != null) {
|
||||
this.items.forEach((value) => this.checkCollapseLevelRecursive(value, 0));
|
||||
}
|
||||
}
|
||||
|
||||
private checkCollapseLevelRecursive(viewItem: TreeviewItem, level: number): void {
|
||||
if (level != null && this.collapseLevel != null && level >= this.collapseLevel) {
|
||||
viewItem.setCollapsedRecursive(true);
|
||||
}
|
||||
else if (viewItem.children != null) {
|
||||
viewItem.children.forEach((value) => this.checkCollapseLevelRecursive(value, level + 1))
|
||||
}
|
||||
}
|
||||
}
|
||||
71
frontend/src/ts/ervu-dashboard/BirthDateRageCalculator.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
import {
|
||||
AnalyticalScope,
|
||||
Behavior,
|
||||
DateTimePicker,
|
||||
NotNull,
|
||||
ObjectRef,
|
||||
ScriptLocationError,
|
||||
TextField
|
||||
} from "@webbpm/base-package";
|
||||
|
||||
@AnalyticalScope(TextField)
|
||||
export class BirthDateRageCalculator extends Behavior {
|
||||
|
||||
@ObjectRef()
|
||||
@NotNull()
|
||||
public startDate: DateTimePicker;
|
||||
|
||||
@ObjectRef()
|
||||
@NotNull()
|
||||
public endDate: DateTimePicker;
|
||||
|
||||
private ageField: TextField;
|
||||
private onUpdateFunction: Function;
|
||||
|
||||
public initialize() {
|
||||
super.initialize();
|
||||
this.ageField = this.getScript(TextField);
|
||||
if (!this.ageField) {
|
||||
throw new ScriptLocationError(TextField.name, this);
|
||||
}
|
||||
this.onUpdateFunction = () => {
|
||||
const age = this.ageField.getTextValue();
|
||||
this.startDate.setValueAsDate(this.getStartDate(age));
|
||||
this.endDate.setValueAsDate(this.getEndDate(age));
|
||||
}
|
||||
}
|
||||
|
||||
public bindEvents() {
|
||||
super.bindEvents();
|
||||
this.ageField.addUserChangeValueListener(this.onUpdateFunction);
|
||||
}
|
||||
|
||||
public unbindEvents() {
|
||||
super.unbindEvents();
|
||||
this.ageField.removeUserChangeValueListener(this.onUpdateFunction);
|
||||
}
|
||||
|
||||
protected getStartDate(age: string) : Date {
|
||||
if (age !== null && age !== "") {
|
||||
const date = new Date(
|
||||
new Date().getFullYear() - Number(age) - 1,
|
||||
new Date().getMonth(),
|
||||
new Date().getDay()
|
||||
);
|
||||
date.setDate(date.getDate() + 1);
|
||||
return date;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected getEndDate(age: string) : Date {
|
||||
if (age !== null && age !== "") {
|
||||
return new Date(
|
||||
new Date().getFullYear() - Number(age),
|
||||
new Date().getMonth(),
|
||||
new Date().getDay(),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
import {
|
||||
AnalyticalScope,
|
||||
Behavior,
|
||||
ControlWithValue,
|
||||
NotNull,
|
||||
ObjectRef,
|
||||
Text
|
||||
} from "@webbpm/base-package";
|
||||
|
||||
@AnalyticalScope(Text)
|
||||
export class UnionAgeAndBirthDateTextScript extends Behavior {
|
||||
|
||||
@ObjectRef()
|
||||
@NotNull()
|
||||
public inputBirthDate: ControlWithValue;
|
||||
|
||||
private text: Text;
|
||||
private readonly onChangeFunction = () => {
|
||||
if (this.text) {
|
||||
const birthDate = this.parseToIsoFormat(this.inputBirthDate.getTextValue());
|
||||
this.text.setValue(this.format(new Date(birthDate)));
|
||||
}
|
||||
}
|
||||
|
||||
initialize() {
|
||||
super.initialize();
|
||||
this.text = this.getScript(Text);
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
super.bindEvents();
|
||||
this.inputBirthDate.addChangeListener(this.onChangeFunction)
|
||||
}
|
||||
|
||||
unbindEvents() {
|
||||
super.unbindEvents();
|
||||
this.inputBirthDate.removeChangeListener(this.onChangeFunction);
|
||||
}
|
||||
|
||||
private format(birthdate: Date): string {
|
||||
const age = this.calculateAge(birthdate)
|
||||
const ageString = this.getTextAge(age);
|
||||
const strBirthDate = birthdate.getFullYear();
|
||||
return `${age} ${ageString}, ${strBirthDate}`;
|
||||
}
|
||||
|
||||
private getTextAge(age: number) {
|
||||
if (age % 10 === 1 && age % 100 !== 11) {
|
||||
return 'год';
|
||||
} else if (age % 10 >= 2 && age % 10 <= 4 &&
|
||||
(age % 100 < 10 || age % 100 >= 20)) {
|
||||
return 'года';
|
||||
} else {
|
||||
return 'лет';
|
||||
}
|
||||
}
|
||||
|
||||
private calculateAge(birthDate: Date) {
|
||||
const currentDate = new Date();
|
||||
let age = currentDate.getFullYear() - birthDate.getFullYear();
|
||||
|
||||
const difMonthValue = currentDate.getMonth() - birthDate.getMonth();
|
||||
if (difMonthValue < 0 || (difMonthValue === 0 && currentDate.getDate() < birthDate.getDate())) {
|
||||
age--;
|
||||
}
|
||||
|
||||
return age;
|
||||
}
|
||||
|
||||
private parseToIsoFormat(date: string): string {
|
||||
let strings = date.split('.');
|
||||
return `${strings[2]}-${1}-${strings[0]}`;
|
||||
}
|
||||
}
|
||||
30
frontend/src/ts/ervu-dashboard/component/chart/ChartUtils.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import {Font} from "../../../generated/ervu_dashboard/model/Font";
|
||||
import {Chart} from "chart.js";
|
||||
|
||||
export class ChartUtils {
|
||||
|
||||
public static isEmpty(chart: Chart) {
|
||||
return 0 == chart.data.datasets
|
||||
.map(value => value.data.length)
|
||||
.reduce((x, y) => x + y, 0);
|
||||
}
|
||||
|
||||
public static getTextMetrics(ctx: CanvasRenderingContext2D, text: string, font: Font) {
|
||||
ctx.font = this.getFont(font);
|
||||
let textMetrics = ctx.measureText(text);
|
||||
return {
|
||||
height: textMetrics.fontBoundingBoxAscent + textMetrics.fontBoundingBoxDescent,
|
||||
width: textMetrics.width
|
||||
}
|
||||
}
|
||||
|
||||
public static getFont(font: Font): string {
|
||||
return [font.weight, `${font.size}px`, font.family]
|
||||
.filter(elem => elem)
|
||||
.join(' ');
|
||||
}
|
||||
|
||||
public static clone<T>(source: T): T {
|
||||
return JSON.parse(JSON.stringify(source));
|
||||
}
|
||||
}
|
||||
312
frontend/src/ts/ervu-dashboard/component/chart/ErvuChartV2.ts
Normal file
|
|
@ -0,0 +1,312 @@
|
|||
import 'chartjs-adapter-moment';
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
ChangeDetectorRef,
|
||||
Component,
|
||||
ElementRef,
|
||||
Input
|
||||
} from "@angular/core";
|
||||
|
||||
import {Chart as ChartJs, ChartType, DefaultDataPoint} from "chart.js";
|
||||
import "chartjs-adapter-moment";
|
||||
import {
|
||||
AdvancedProperty, ChartConfigDto,
|
||||
ChartScaleSettings, ChartTitleSettings,
|
||||
ChartV2AdditionalElementProvider, ChartV2RpcService,
|
||||
Control, Filter,
|
||||
Filterable, FilterDelegate, NotNull, PointSettings, UnsupportedOperationError,
|
||||
Visible
|
||||
} from "@webbpm/base-package";
|
||||
import {ChartLegendSettings} from "./model/ChartLegendSettings";
|
||||
import {ChartBarSettings} from "./model/ChartBarSettings";
|
||||
import {ChartPlugin} from "./plugin/ChartPlugin";
|
||||
import {DoughnutCenterLabelsPlugin} from "./plugin/DoughnutCenterLabelsPlugin";
|
||||
import {ChartUtils} from "./ChartUtils";
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'ervu-chart-v2',
|
||||
templateUrl: './../../../../../src/resources/template/ervu-dashboard/ErvuChartV2.html',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class ErvuChartV2 extends Control implements Filterable {
|
||||
@Visible("false")
|
||||
public disabled: boolean;
|
||||
|
||||
@Input()
|
||||
public indexAxis: string = 'x';
|
||||
@Input()
|
||||
public title: ChartTitleSettings;
|
||||
@Input()
|
||||
public legend: ChartLegendSettings;
|
||||
@Input()
|
||||
public points: PointSettings;
|
||||
@Input()
|
||||
public scales: ChartScaleSettings[];
|
||||
|
||||
public bars: ChartBarSettings;
|
||||
|
||||
@NotNull()
|
||||
public loadOnStart: boolean = true;
|
||||
|
||||
@Visible("false")
|
||||
@Input()
|
||||
public chart: ChartJs<ChartType, DefaultDataPoint<ChartType>, unknown>;
|
||||
|
||||
@NotNull()
|
||||
@AdvancedProperty()
|
||||
public showTooltip: boolean = true;
|
||||
|
||||
@NotNull()
|
||||
@AdvancedProperty()
|
||||
public responsive: boolean = true;
|
||||
|
||||
@NotNull()
|
||||
@AdvancedProperty()
|
||||
public maintainAspectRatio: boolean = false;
|
||||
|
||||
@NotNull()
|
||||
@AdvancedProperty()
|
||||
public addDataSetsVisibilityToggleDataset: boolean = false;
|
||||
|
||||
@AdvancedProperty()
|
||||
@Visible("addDataSetsVisibilityToggleDataset==true")
|
||||
@NotNull("addDataSetsVisibilityToggleDataset==true")
|
||||
public hideAllDatasetsLabel: string = 'Cкрыть все';
|
||||
|
||||
@AdvancedProperty()
|
||||
@Visible("addDataSetsVisibilityToggleDataset==true")
|
||||
@NotNull("addDataSetsVisibilityToggleDataset==true")
|
||||
public showAllDatasetsLabel: string = 'Показать все';
|
||||
|
||||
@AdvancedProperty()
|
||||
public noDataText: string = "Нет данных для отображения";
|
||||
@AdvancedProperty()
|
||||
public noDataFont: string = "20px Arial";
|
||||
@AdvancedProperty()
|
||||
public noDataStyle: string = "rgb(100,100,100)";
|
||||
|
||||
private chartRpcService: ChartV2RpcService;
|
||||
private filterMap: { [key: string]: Filter } = {};
|
||||
private loadEnabled: boolean = true;
|
||||
private filterDelegate: FilterDelegate;
|
||||
private chartConfig;
|
||||
private plugins: ChartPlugin[] = [];
|
||||
|
||||
private noDataPlugin = {
|
||||
afterDraw: (chart) => {
|
||||
|
||||
if (ChartUtils.isEmpty(chart)) {
|
||||
let ctx = chart.ctx;
|
||||
let width = chart.width;
|
||||
let height = chart.height;
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.font = this.noDataFont;
|
||||
ctx.fillStyle = this.noDataStyle;
|
||||
ctx.fillText(this.noDataText, width / 2, height / 2);
|
||||
ctx.restore();
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
private addAdditionalElementsPlugin = {
|
||||
afterDraw: () => {
|
||||
this.getScripts(ChartV2AdditionalElementProvider)
|
||||
.forEach(additionalElementProvider =>
|
||||
additionalElementProvider.addAdditionalElement(this))
|
||||
},
|
||||
};
|
||||
|
||||
constructor(el: ElementRef, cd: ChangeDetectorRef) {
|
||||
super(el, cd);
|
||||
}
|
||||
|
||||
initialize(): void {
|
||||
super.initialize();
|
||||
this.chartRpcService = this.getScript(ChartV2RpcService);
|
||||
this.filterDelegate = this.getScript(FilterDelegate);
|
||||
this.plugins = this.getScripts('ervu-dashboard.component.chart.plugin.ChartPlugin');
|
||||
}
|
||||
|
||||
start(): void {
|
||||
super.start();
|
||||
|
||||
if (this.loadOnStart) {
|
||||
this.chartRpcService
|
||||
.getChartData(null, null, [])
|
||||
.then((config: ChartConfigDto) => {
|
||||
this.loadDataAndPaint(
|
||||
config
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
addFilter(filter: Filter): void {
|
||||
this.filterMap[filter.componentGuid] = filter;
|
||||
|
||||
if (this.filterDelegate) {
|
||||
this.filterDelegate.addFilter(filter);
|
||||
}
|
||||
}
|
||||
|
||||
disableInitialLoading() {
|
||||
this.loadOnStart = false;
|
||||
this.loadEnabled = false;
|
||||
|
||||
if (this.filterDelegate) {
|
||||
this.filterDelegate.disableInitialLoading();
|
||||
}
|
||||
}
|
||||
|
||||
reload(filters: Filter[]): void {
|
||||
filters.forEach((filter: Filter) => this.addFilter(filter));
|
||||
this.refresh();
|
||||
|
||||
if (this.filterDelegate) {
|
||||
this.filterDelegate.reload(filters);
|
||||
}
|
||||
}
|
||||
|
||||
removeFilter(guid: string): Filter {
|
||||
let ret = this.filterMap[guid];
|
||||
delete this.filterMap[guid];
|
||||
|
||||
if (this.filterDelegate) {
|
||||
this.filterDelegate.removeFilter(guid);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
getFilters(): Filter[] {
|
||||
return Object.values(this.filterMap);
|
||||
}
|
||||
|
||||
@Visible()
|
||||
public refresh() {
|
||||
this.chartRpcService
|
||||
.getChartData(null, null, this.getFilters())
|
||||
.then((configDto: ChartConfigDto) => this.loadDataAndPaint(configDto));
|
||||
}
|
||||
|
||||
public loadDataAndPaint(config: ChartConfigDto) {
|
||||
this.initAndSaveChartData(config);
|
||||
this.repaint(this.chartConfig);
|
||||
}
|
||||
|
||||
private initAndSaveChartData(chartConfig) {
|
||||
//To make visible empty chart in preview mode
|
||||
if (!chartConfig) {
|
||||
chartConfig = {};
|
||||
}
|
||||
let chartOptions: any = {};
|
||||
chartConfig.options = chartOptions;
|
||||
chartOptions.maintainAspectRatio = this.maintainAspectRatio;
|
||||
chartOptions.responsive = this.responsive;
|
||||
|
||||
if (this.indexAxis) {
|
||||
chartOptions.indexAxis = this.indexAxis;
|
||||
}
|
||||
|
||||
chartOptions.plugins = chartOptions.plugins ? chartOptions.plugins : {};
|
||||
|
||||
chartOptions.plugins.legend = this.legend ? this.legend : {};
|
||||
chartOptions.plugins.tooltip = {enabled: this.showTooltip};
|
||||
|
||||
if (this.title && this.title.text) {
|
||||
chartOptions.plugins.title = this.title;
|
||||
}
|
||||
|
||||
if (this.addDataSetsVisibilityToggleDataset && chartConfig.data.datasets &&
|
||||
chartConfig.data.datasets.length > 0) {
|
||||
let datasetLength = chartConfig.data.datasets.length;
|
||||
chartConfig.data.datasets.push({
|
||||
label: this.hideAllDatasetsLabel,
|
||||
xAxisID: {display: false},
|
||||
yAxisID: {display: false}
|
||||
});
|
||||
|
||||
chartOptions.plugins.legend.onClick = (e, legendItem, legend) => {
|
||||
const index = legendItem.datasetIndex;
|
||||
const chartObj = legend.chart;
|
||||
|
||||
let datasetMeta = chartObj.getDatasetMeta(index);
|
||||
|
||||
if (datasetLength == index) {
|
||||
let showAll = datasetMeta.label == this.showAllDatasetsLabel;
|
||||
chartObj.data.datasets[index].label =
|
||||
showAll ? this.hideAllDatasetsLabel : this.showAllDatasetsLabel;
|
||||
|
||||
for (let i = 0; i < datasetLength; i++) {
|
||||
chartObj.setDatasetVisibility(i, showAll);
|
||||
}
|
||||
chartObj.update();
|
||||
}
|
||||
else {
|
||||
if (chartObj.isDatasetVisible(index)) {
|
||||
chartObj.hide(index);
|
||||
legendItem.hidden = true;
|
||||
}
|
||||
else {
|
||||
chartObj.show(index);
|
||||
legendItem.hidden = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (chartConfig.data.datasets) { // remove it after change type of ChartDataSetDto.backgroundColor
|
||||
chartConfig.data.datasets.forEach(dataset => {
|
||||
if (dataset.backgroundColors) dataset.backgroundColor = dataset.backgroundColors;
|
||||
});
|
||||
}
|
||||
|
||||
if (this.points) {
|
||||
chartOptions.elements = chartOptions.elements ? chartOptions.elements : {};
|
||||
chartOptions.elements.point = this.points;
|
||||
}
|
||||
|
||||
if (this.scales) {
|
||||
chartOptions.scales = {};
|
||||
this.scales.forEach((scale: ChartScaleSettings) => {
|
||||
chartOptions.scales[scale.scaleId] = scale.toChartJsSettings();
|
||||
});
|
||||
}
|
||||
|
||||
if (this.bars) {
|
||||
chartOptions.scales = chartOptions.scales ? chartOptions.scales : {};
|
||||
chartOptions.scales.x = this.bars.x;
|
||||
chartOptions.scales.y = this.bars.y
|
||||
}
|
||||
|
||||
chartConfig.tooltips = {
|
||||
callbacks: {
|
||||
title: function (tooltipItem, data) {
|
||||
return data.labels[tooltipItem[0].index]
|
||||
},
|
||||
label: function (tooltipItem, data) {
|
||||
let dataset = data.datasets[tooltipItem.datasetIndex];
|
||||
return dataset.label + ": " + dataset.data[tooltipItem.index];
|
||||
}
|
||||
}
|
||||
};
|
||||
this.chartConfig = chartConfig;
|
||||
}
|
||||
|
||||
private repaint(chartData) {
|
||||
if (this.chart) {
|
||||
this.chart.destroy();
|
||||
}
|
||||
let canvasEl = this.el.nativeElement.querySelector('canvas');
|
||||
let chartConfig = {
|
||||
plugins: [this.noDataPlugin, this.addAdditionalElementsPlugin, ...this.plugins]
|
||||
};
|
||||
|
||||
this.chart = new ChartJs(canvasEl, jQuery.extend(true, chartConfig, chartData));
|
||||
}
|
||||
|
||||
clear() { //todo SUPPORT-4909
|
||||
throw new UnsupportedOperationError("Unsupported operation");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
export class AxisGridSettings {
|
||||
public display: boolean = true;
|
||||
public drawBorder: boolean = true;
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
import {AxisGridSettings} from "./AxisGridSettings";
|
||||
import {AxisTicksSettings} from "./AxisTicksSettings";
|
||||
|
||||
export class AxisSettings {
|
||||
public grid: AxisGridSettings;
|
||||
public ticks: AxisTicksSettings;
|
||||
public grace: string;
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
export class AxisTicksSettings {
|
||||
public display: boolean = true;
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
import {AxisSettings} from "./AxisSettings";
|
||||
|
||||
export class ChartBarSettings {
|
||||
public x: AxisSettings;
|
||||
public y: AxisSettings;
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import {
|
||||
ChartElementAlign,
|
||||
ChartElementPosition, ChartFontSettings,
|
||||
ChartLegendTitleSettings,
|
||||
ColorEditor
|
||||
} from "@webbpm/base-package";
|
||||
|
||||
export class ChartLegendSettings {
|
||||
public display: boolean = true;
|
||||
public fullSize: boolean;
|
||||
@ColorEditor()
|
||||
public color: string;
|
||||
|
||||
public align: ChartElementAlign = ChartElementAlign.CENTER;
|
||||
public position: ChartElementPosition = ChartElementPosition.TOP;
|
||||
|
||||
public font: ChartFontSettings;
|
||||
public title: ChartLegendTitleSettings;
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
import {Behavior, ColorEditor, Visible} from "@webbpm/base-package";
|
||||
import {ChartPlugin} from "./ChartPlugin";
|
||||
import {Chart, ChartMeta} from "chart.js";
|
||||
import {ArcDatasetBackgroundConfig} from "./model/ArcDatasetBackgroundConfig";
|
||||
import {ChartUtils} from "../ChartUtils";
|
||||
|
||||
export class ArcBackgroundChartPlugin extends Behavior implements ChartPlugin {
|
||||
@Visible('false')
|
||||
id: string = 'arcDatasetBackground';
|
||||
|
||||
public backgroundRadiusPercent: number = 100;
|
||||
@ColorEditor()
|
||||
public backgroundColor: string = '#FFFFFF1B';
|
||||
public datasetBackgroundConfig: ArcDatasetBackgroundConfig[];
|
||||
|
||||
beforeDatasetsDraw(chart: Chart): void {
|
||||
if (ChartUtils.isEmpty(chart)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let ctx = chart.ctx;
|
||||
let radius = Math.min(chart.height, chart.width) / 2;
|
||||
// Дуги рисуются чуть ниже, чем в центре, поэтому, чтобы при 100% круги не обрезались, умножаем на 0.97
|
||||
radius *= (this.backgroundRadiusPercent / 100 * 0.97);
|
||||
let centerX = chart.getDatasetMeta(0).data[0].x;
|
||||
let centerY = chart.getDatasetMeta(0).data[0].y;
|
||||
|
||||
ctx.fillStyle = this.backgroundColor;
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.arc(centerX, centerY, radius, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
public beforeDatasetDraw(chart: Chart, args: { index: number; meta: ChartMeta }) {
|
||||
if (!this.datasetBackgroundConfig || !this.datasetBackgroundConfig[args.index] || !args.meta.data[0]) {
|
||||
return;
|
||||
}
|
||||
|
||||
let ctx = chart.ctx;
|
||||
let config = this.datasetBackgroundConfig[args.index];
|
||||
let arc = args.meta.data[0] as any;
|
||||
|
||||
ctx.fillStyle = config.color;
|
||||
ctx.save();
|
||||
ctx.beginPath();
|
||||
ctx.arc(arc.x, arc.y, arc.outerRadius * (100 + config.outerBorderPercent) / 100, 0, Math.PI * 2, false);
|
||||
ctx.arc(arc.x, arc.y, arc.innerRadius * (100 - config.innerBorderPercent) / 100, 0, Math.PI * 2, true);
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
import {
|
||||
AnalyticalScope,
|
||||
Behavior,
|
||||
ChartFontFamily, ColorEditor, NotNull,
|
||||
Visible
|
||||
} from "@webbpm/base-package";
|
||||
import {ChartPlugin} from "./ChartPlugin";
|
||||
import {Chart, ChartMeta} from "chart.js";
|
||||
import {ErvuChartV2} from "../ErvuChartV2";
|
||||
import {ChartUtils} from "../ChartUtils";
|
||||
|
||||
@AnalyticalScope(ErvuChartV2)
|
||||
export class BarDataLabelChartPlugin extends Behavior implements ChartPlugin {
|
||||
@Visible('false')
|
||||
id: string = 'barDataLabel';
|
||||
|
||||
public labelAxis = 'y';
|
||||
|
||||
public weight: number = 500;
|
||||
public size: number = 12;
|
||||
public family: ChartFontFamily = ChartFontFamily.SANS_SERIF;
|
||||
|
||||
public useStaticColor: boolean = true;
|
||||
@Visible('useStaticColor == true')
|
||||
@NotNull('useStaticColor == true')
|
||||
@ColorEditor()
|
||||
public color: string;
|
||||
|
||||
afterDatasetDraw(chart: Chart, args: { index: number; meta: ChartMeta }): void {
|
||||
let ctx = chart.ctx;
|
||||
let data = chart.data.datasets[args.index];
|
||||
args.meta.data.forEach((datapoint: any, i) => {
|
||||
ctx.font = ChartUtils.getFont({
|
||||
family: this.family,
|
||||
weight: String(this.weight),
|
||||
size: this.size
|
||||
});
|
||||
ctx.textAlign = 'center';
|
||||
ctx.fillStyle = this.useStaticColor ? this.color : datapoint.options.backgroundColor;
|
||||
ctx.fillText(this.getLabel(data, i), datapoint.x, datapoint.y - 6);
|
||||
})
|
||||
}
|
||||
|
||||
private getLabel(data: any, index: number): string {
|
||||
let point = data.data[index];
|
||||
return typeof point == 'object' ? point[this.labelAxis] : point;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import {Chart, ChartMeta} from "chart.js";
|
||||
|
||||
export interface ChartPlugin {
|
||||
id: string;
|
||||
|
||||
beforeDraw?(chart: Chart): void;
|
||||
beforeDatasetsDraw?(chart: Chart, args?: { cancellable: true }, options?: any): void;
|
||||
beforeDatasetDraw?(chart: Chart, args: { index: number; meta: ChartMeta }): void;
|
||||
|
||||
afterDatasetDraw?(chart: Chart, args: { index: number; meta: ChartMeta }): void;
|
||||
afterDatasetsDraw?(chart: Chart, args?: unknown, options?: any): void;
|
||||
afterDraw?(chart: Chart): void;
|
||||
}
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
import {ChartPlugin} from "./ChartPlugin";
|
||||
import {Chart} from "chart.js";
|
||||
import {
|
||||
ChartLabelModel
|
||||
} from "../../../../generated/ervu_dashboard/model/chart/round/label/ChartLabelModel";
|
||||
import {ChartUtils} from "../ChartUtils";
|
||||
import {AnalyticalScope, Behavior, ChartFontFamily, Visible} from "@webbpm/base-package";
|
||||
import {ErvuChartV2} from "../ErvuChartV2";
|
||||
import {ChartLabelFormatter} from "./formatters/ChartLabelFormatter";
|
||||
|
||||
@AnalyticalScope(ErvuChartV2)
|
||||
export class DoughnutCenterLabelsPlugin extends Behavior implements ChartPlugin {
|
||||
public static readonly ID = 'doughnut-center-labels';
|
||||
private static readonly DEFAULTS = {
|
||||
color: 'rgb(255, 255, 255)',
|
||||
font: {
|
||||
family: ChartFontFamily.SANS_SERIF,
|
||||
weight: '500',
|
||||
size: 14
|
||||
}
|
||||
}
|
||||
|
||||
@Visible('false')
|
||||
public id: string = DoughnutCenterLabelsPlugin.ID;
|
||||
|
||||
public formatters: ChartLabelFormatter[] = [];
|
||||
|
||||
beforeDatasetsDraw(chart: Chart, args?: { cancellable: true }, options?: any): void {
|
||||
if (!chart.data || !chart.data.datasets || !(<any>chart.data).centerLabels || ChartUtils.isEmpty(chart)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let ctx = chart.ctx;
|
||||
let {top, bottom, left, right} = chart.chartArea;
|
||||
|
||||
let centerLabels = ChartUtils.clone((<any>chart.data).centerLabels as ChartLabelModel[]);
|
||||
this.processLabels(centerLabels);
|
||||
|
||||
let textAreaSize = this.getTextSize(chart, centerLabels);
|
||||
|
||||
const diagonal = Math.sqrt(
|
||||
Math.pow(textAreaSize.width, 2) + Math.pow(textAreaSize.height, 2)
|
||||
);
|
||||
const innerDiameter = this.getInnerRadius(chart) * 2;
|
||||
const fitRatio = innerDiameter / diagonal;
|
||||
|
||||
if (fitRatio < 1) {
|
||||
centerLabels.forEach(label => {
|
||||
label.font.size = Math.floor(label.font.size * fitRatio);
|
||||
});
|
||||
|
||||
textAreaSize = this.getTextSize(chart, centerLabels);
|
||||
}
|
||||
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "middle";
|
||||
|
||||
const centerX = (left + right) / 2;
|
||||
const centerY = (top + bottom) / 2;
|
||||
|
||||
const topY = centerY - textAreaSize.height / 2;
|
||||
|
||||
let currentHeight = 0;
|
||||
centerLabels.forEach(label => {
|
||||
let textMetrics = ChartUtils.getTextMetrics(ctx, label.text, label.font);
|
||||
|
||||
ctx.fillStyle = label.color
|
||||
ctx.font = ChartUtils.getFont(label.font);
|
||||
|
||||
const lineCenterY = topY + currentHeight + textMetrics.height / 2;
|
||||
currentHeight += textMetrics.height;
|
||||
|
||||
ctx.fillText(label.text, centerX, lineCenterY);
|
||||
})
|
||||
}
|
||||
|
||||
protected getTextSize(chart: Chart, centerLabels: ChartLabelModel[]) {
|
||||
const {ctx} = chart;
|
||||
const prev = ctx.font;
|
||||
let width = 0;
|
||||
let height = 0;
|
||||
|
||||
centerLabels.forEach(label => {
|
||||
let metrics = ChartUtils.getTextMetrics(ctx, label.text, label.font);
|
||||
width = Math.max(metrics.width, width);
|
||||
height += metrics.height;
|
||||
});
|
||||
|
||||
ctx.font = prev;
|
||||
|
||||
return {
|
||||
height: height,
|
||||
width: width
|
||||
};
|
||||
}
|
||||
|
||||
protected processLabels(labels: ChartLabelModel[]) {
|
||||
labels.forEach((label, index) => {
|
||||
let formatter = this.formatters[index];
|
||||
if (formatter) {
|
||||
formatter.format(label, labels, index);
|
||||
}
|
||||
|
||||
label.color = label.color ? label.color : DoughnutCenterLabelsPlugin.DEFAULTS.color;
|
||||
|
||||
if (label.font) {
|
||||
label.font.family =
|
||||
label.font.family ? label.font.family : DoughnutCenterLabelsPlugin.DEFAULTS.font.family;
|
||||
label.font.weight =
|
||||
label.font.weight ? label.font.weight : DoughnutCenterLabelsPlugin.DEFAULTS.font.weight;
|
||||
label.font.size =
|
||||
label.font.size ? label.font.size : DoughnutCenterLabelsPlugin.DEFAULTS.font.size;
|
||||
}
|
||||
else {
|
||||
label.font = DoughnutCenterLabelsPlugin.DEFAULTS.font
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected getInnerRadius(chart: Chart): number {
|
||||
return chart.getSortedVisibleDatasetMetas()
|
||||
.map(meta => meta.data[0])
|
||||
.filter(meta => meta)
|
||||
.map((arc: any) => arc.innerRadius as number)
|
||||
.reduce((prev, curr) => Math.min(prev, curr), chart.width / 2);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
import {AnalyticalScope, Behavior, Visible} from "@webbpm/base-package";
|
||||
import {ChartPlugin} from "./ChartPlugin";
|
||||
import {Chart, ChartMeta} from "chart.js";
|
||||
import {ErvuChartV2} from "../ErvuChartV2";
|
||||
|
||||
@AnalyticalScope(ErvuChartV2)
|
||||
export class RoundArcCornersChartPlugin extends Behavior implements ChartPlugin {
|
||||
@Visible('false')
|
||||
id: string = 'roundArcCorners';
|
||||
|
||||
/**
|
||||
* Основная идея: нарисовать полукруги на концах дуг, создавая эффект закругленности
|
||||
*/
|
||||
afterDatasetDraw(chart: Chart, args: { index: number; meta: ChartMeta }): void {
|
||||
const ctx = chart.ctx;
|
||||
const datasetMeta = args.meta;
|
||||
// Обходим бублик против часовой стрелки, так как полукруги наслаиваются и начинают друг друга перекрывать
|
||||
for (let i = datasetMeta.data.length - 1; i >= 0; i--) {
|
||||
if (!chart.getDataVisibility(i)) continue;
|
||||
|
||||
let data = datasetMeta.data[i] as any;
|
||||
let x = data.x;
|
||||
let y = data.y;
|
||||
let innerRadius = data.innerRadius;
|
||||
let outerRadius = data.outerRadius;
|
||||
// берем немного против часовой стрелки, чтобы не возникало стыков
|
||||
let endAngle = data.endAngle - Math.PI / 180 * 0.65;
|
||||
let radius = (outerRadius - innerRadius) / 2;
|
||||
let xCoor = Math.cos(endAngle) * (innerRadius + radius);
|
||||
let yCoor = Math.sin(endAngle) * (innerRadius + radius);
|
||||
ctx.save();
|
||||
ctx.fillStyle = data.options.backgroundColor;
|
||||
ctx.translate(x, y);
|
||||
ctx.beginPath();
|
||||
// рисуем полукруг немного большего радиуса, чтобы не возникало стыков
|
||||
ctx.arc(xCoor, yCoor, radius, endAngle, endAngle + Math.PI * 1.008);
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import {
|
||||
ChartLabelModel
|
||||
} from "../../../../../generated/ervu_dashboard/model/chart/round/label/ChartLabelModel";
|
||||
|
||||
export interface ChartLabelFormatter {
|
||||
format(label: ChartLabelModel, labels?: ChartLabelModel[], index?: number): void;
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
import {ChartLabelFormatter} from "./ChartLabelFormatter";
|
||||
import {
|
||||
ChartLabelModel
|
||||
} from "../../../../../generated/ervu_dashboard/model/chart/round/label/ChartLabelModel";
|
||||
import {NotNull, Visible} from "@webbpm/base-package";
|
||||
import {FormatterUtils} from "../../../../formatter/FormatterUtils";
|
||||
|
||||
export class UnitsLabelReplaceValueChartLabelFormatter implements ChartLabelFormatter {
|
||||
public valuesToReplace: string[];
|
||||
public replaceValue: string = '';
|
||||
public noDataValue: string = '';
|
||||
|
||||
@NotNull()
|
||||
public labelValueIndex: number;
|
||||
|
||||
public replaceDataLabel: string = '';
|
||||
|
||||
public unitsLabel: string = '';
|
||||
public thousandsLabel: string = '';
|
||||
public millionsLabel: string = '';
|
||||
public billionsLabel: string = '';
|
||||
|
||||
public decimalDelimiter: string;
|
||||
|
||||
@NotNull('decimalDelimiter != null')
|
||||
@Visible('decimalDelimiter != null')
|
||||
public decimalLength: number = 0;
|
||||
|
||||
format(numberValueModel: ChartLabelModel, labels?: ChartLabelModel[], index?: number): void {
|
||||
let labelValueModel = labels[this.labelValueIndex];
|
||||
|
||||
if (!numberValueModel.text) {
|
||||
numberValueModel.text = this.noDataValue;
|
||||
labelValueModel.text = this.replaceDataLabel;
|
||||
}
|
||||
|
||||
if (this.valuesToReplace && this.valuesToReplace.includes(numberValueModel.text)) {
|
||||
numberValueModel.text = this.replaceValue;
|
||||
labelValueModel.text = this.replaceDataLabel;
|
||||
}
|
||||
|
||||
let result = FormatterUtils.formatNumber({
|
||||
value: numberValueModel.text,
|
||||
|
||||
unitsLabel: this.unitsLabel,
|
||||
thousandsLabel: this.thousandsLabel,
|
||||
millionsLabel: this.millionsLabel,
|
||||
billionsLabel: this.billionsLabel,
|
||||
|
||||
decimalDelimiter: this.decimalDelimiter,
|
||||
decimalLength: this.decimalLength
|
||||
});
|
||||
|
||||
numberValueModel.text = result.formattedNumber;
|
||||
labelValueModel.text = result.label;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
import {ColorEditor} from "@webbpm/base-package";
|
||||
|
||||
export class ArcDatasetBackgroundConfig {
|
||||
@ColorEditor()
|
||||
public color: string;
|
||||
public outerBorderPercent: number = 0;
|
||||
public innerBorderPercent: number = 0;
|
||||
}
|
||||