snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s

This commit is contained in:
upstream-mirror
2026-09-18 01:22:23 +05:00
commit 7e385fc126
8391 changed files with 1766236 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
> 1%
last 2 versions
not dead

View File

@@ -0,0 +1,10 @@
module.exports = {
extends: ['corteza-client'],
parser: 'vue-eslint-parser',
parserOptions: {
parser: '@babel/eslint-parser',
requireConfigFile: false,
ecmaVersion: 2020,
sourceType: 'module',
},
}

20
client/web/workflow/.gitignore vendored Normal file
View File

@@ -0,0 +1,20 @@
.DS_Store
node_modules
dist
*.log
# Editor directories and files
.idea
.vscode
*.iml
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw*
.nyc_output
coverage
/.act*
/workflow

View File

@@ -0,0 +1,42 @@
.PHONY: dep test build release upload
YARN_FLAGS ?= --non-interactive --no-progress --silent --emoji false
YARN = yarn $(YARN_FLAGS)
REPO_NAME ?= corteza-webapp-workflow
BUILD_FLAVOUR ?= corteza
BUILD_FLAGS ?= --production
BUILD_DEST_DIR = dist
BUILD_TIME ?= $(shell date +%FT%T%z)
BUILD_VERSION ?= $(shell git describe --tags --abbrev=0)
BUILD_NAME = $(REPO_NAME)-$(BUILD_VERSION)
RELEASE_NAME = $(BUILD_NAME).tar.gz
RELEASE_EXTRA_FILES ?= README.md LICENSE CONTRIBUTING.md DCO
RELEASE_PKEY ?= .upload-rsa
dep:
$(YARN) install
test:
$(YARN) lint
$(YARN) test:unit
build:
export BUILD_VERSION=${BUILD_VERSION} && $(YARN) build $(BUILD_FLAGS)
release:
@ echo $(RELEASE_NAME)
@ cp $(RELEASE_EXTRA_FILES) $(BUILD_DEST_DIR)
@ tar -C $(BUILD_DEST_DIR) -czf $(RELEASE_NAME) $(dir $(BUILD_DEST_DIR))
upload: $(RELEASE_PKEY)
@ echo "put *.tar.gz" | sftp -q -o "StrictHostKeyChecking no" -i $(RELEASE_PKEY) $(RELEASE_SFTP_URI)
@ rm -f $(RELEASE_PKEY)
$(RELEASE_PKEY):
@ echo $(RELEASE_SFTP_KEY) | base64 -d > $(RELEASE_PKEY)
@ chmod 0400 $@

View File

@@ -0,0 +1,5 @@
module.exports = {
presets: [
['@vue/app', { useBuiltIns: 'entry' }],
],
}

View File

@@ -0,0 +1,36 @@
/** @type {import('jest').Config} */
module.exports = {
preset: '@vue/cli-plugin-unit-jest',
testMatch: [
'**/tests/unit/**/*.spec.js',
],
moduleNameMapper: {
'^corteza-webapp-workflow/(.*)$': '<rootDir>/$1',
},
moduleFileExtensions: [
'js',
'jsx',
'json',
'vue',
],
transform: {
'^.+\\.vue$': '@vue/vue2-jest',
'.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub',
'^.+\\.jsx?$': 'babel-jest',
},
transformIgnorePatterns: [
'/node_modules/(?!(chai|sinon)/)',
],
snapshotSerializers: [
'jest-serializer-vue',
],
testEnvironment: 'jsdom',
testURL: 'http://localhost/',
watchPlugins: [
'jest-watch-typeahead/filename',
'jest-watch-typeahead/testname',
],
setupFiles: [
'<rootDir>/tests/setup.js',
],
}

View File

@@ -0,0 +1,82 @@
{
"name": "corteza-webapp-workflow",
"description": "Corteza Workflow WebApp",
"version": "2024.9.10",
"license": "Apache-2.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"dev-env": "vue-cli-service serve src/dev-env.js",
"build": "vue-cli-service build --mode production",
"lint": "vue-cli-service lint --fix",
"cdeps": "yarn link @cortezaproject/corteza-js @cortezaproject/corteza-vue",
"test:unit": "vue-cli-service test:unit",
"test": "yarn test:unit"
},
"gitHooks": {
"pre-commit": "yarn lint"
},
"stylelint": {
"plugins": [
"stylelint-scss"
],
"extends": "stylelint-config-standard",
"rules": {
"color-hex-length": null,
"no-empty-source": null
}
},
"resolutions": {
"cross-spawn": "^7.0.6",
"postcss": "^8.5.3"
},
"dependencies": {
"@cortezaproject/corteza-js": "^2024.9.10",
"@cortezaproject/corteza-vue": "2024.9.10",
"@fortawesome/fontawesome-svg-core": "^6.7.2",
"@fortawesome/free-regular-svg-icons": "^6.7.2",
"@fortawesome/free-solid-svg-icons": "^6.7.2",
"@fortawesome/vue-fontawesome": "^2.0.10",
"bootstrap-vue": "2.23.1",
"file-saver": "^2.0.5",
"mxgraph": "^4.2.0",
"portal-vue": "^2.1.7",
"v-jsoneditor": "^1.4.2",
"vue": "2.7.16",
"vue-native-websocket": "^2.0.15",
"vue-router": "3.1.5",
"vuedraggable": "^2.23.0",
"vuex": "^3.6.2"
},
"devDependencies": {
"@babel/core": "^7.26.7",
"@babel/eslint-parser": "^7.26.5",
"@vue/cli-plugin-babel": "5.0.8",
"@vue/cli-plugin-eslint": "5.0.8",
"@vue/cli-plugin-unit-jest": "^5.0.8",
"@vue/cli-service": "5.0.8",
"@vue/eslint-config-standard": "^8.0.1",
"@vue/test-utils": "^1.3.6",
"@vue/vue2-jest": "^27.0.0",
"babel-jest": "^27.1.0",
"chai": "^4.3.7",
"chai-as-promised": "^7.1.1",
"eslint": "^8.7.2",
"eslint-config-corteza-client": "file:../../../lib/eslint-client",
"eslint-plugin-vue": "^9.32.0",
"jest": "^27.1.0",
"null-loader": "^4.0.1",
"resolve-url-loader": "^5.0.0",
"rtlcss": "^4.3.0",
"sass": "^1.85.1",
"sass-loader": "^10",
"sinon": "^9.2.4",
"stylelint": "16.14.1",
"stylelint-config-standard": "^37.0.0",
"stylelint-scss": "^6.11.0",
"stylelint-webpack-plugin": "^5.0.1",
"vue-eslint-parser": "^9.4.3",
"vue-template-compiler": "2.7.16",
"webpack": "^5.97.1"
}
}

View File

@@ -0,0 +1,6 @@
module.exports = {
plugins: {
autoprefixer: {},
rtlcss: {},
},
}

1
client/web/workflow/public/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
config.js

View File

@@ -0,0 +1,12 @@
// Corteza API location
window.CortezaAPI = 'https://api.cortezaproject.your-domain.tld';
// CortezaAuth can be autoconfigured by replacing /api with /auth in CortezaAPI
// or by appending /auth to the end of CortezaAPI string
// When this is not possible and your configuration is more exotic you can set it
// explicitly:
// window.CortezaAuth = 'https://api.cortezaproject.your-domain.tld/auth';
// Configure CortezaWebapp when your web applications are not placed on the root.
// This is autoconfigured from the value of <base> tag href attribute in most cases.
// window.CortezaWebapp = 'https://cortezaproject.your-domain.tld';

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.0075 -0.0058 36.9075 38.5121" width="32px" height="32px">
<defs>
<style type="text/css">
.st0{fill:#95B4AF;}
</style>
</defs>
<path class="st0" d="M2.5,9.4c1.6-2.9,4-5.3,6.9-6.9c3-1.7,6.3-2.5,9.8-2.5c3.9-0.1,7.8,1.1,11.1,3.2c3.2,2.2,5.5,5.3,6.6,9h-8.7 c-0.8-1.7-2-3.1-3.6-4.1c-1.7-0.9-3.5-1.4-5.4-1.3c-2.1,0-4.1,0.5-5.9,1.5c-1.7,1-3.1,2.6-4,4.4c-1,2.1-1.5,4.3-1.4,6.6 c0,2.3,0.4,4.5,1.4,6.6c0.9,1.8,2.3,3.3,4,4.4c1.8,1,3.8,1.6,5.9,1.5c1.9,0,3.8-0.4,5.4-1.4c1.6-1,2.9-2.4,3.6-4.1h8.7 c-1.1,3.7-3.5,6.8-6.6,9c-3.3,2.2-7.2,3.3-11.1,3.2c-3.4,0-6.8-0.8-9.8-2.5c-2.9-1.6-5.2-4-6.9-6.8c-1.7-3-2.6-6.5-2.5-10 C-0.1,15.9,0.8,12.4,2.5,9.4z" id="object-0" style="fill: rgb(255, 150, 97);"/>
</svg>

After

Width:  |  Height:  |  Size: 792 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" width="32px" height="32px" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);" preserveAspectRatio="xMidYMid meet" viewBox="0 0 2048 2048"><path d="M1022.25 99.354c-450.277-9.22-870.449 360.942-917.287 809.188c-49.497 379.099 158.63 778.965 505.847 944.176c327.865 164.75 753.014 115.134 1024.927-134.893c274.593-240.828 389.319-650.62 262.28-995.771c-116.8-340.195-443.475-599.282-804.796-620.01a950.215 950.215 0 0 0-70.971-2.69zm-10.482 99.76c412.893-15.55 800.952 328.748 833.306 741.03c39.736 363.606-189.04 740.293-537.188 860.127c-338.432 125.141-753.857 3.445-958.257-297.536c-209.788-293.857-201.456-727.62 36.58-1003.308c152.352-183.514 386.115-299.42 625.559-300.314zm16.396 89.923c-381.953-12.147-733.35 323.18-740.25 705.154c-16.837 345.168 238.98 683.903 580.72 749.641c316.644 69.318 670.444-93.359 811.717-387.88c146.107-287.645 80.764-670.441-168.526-880.558c-132.088-117.004-306.804-186.158-483.66-186.357zm-4.494 98.049c336.708-8.916 643.367 294.108 637.967 631.146c5.952 311.574-244.821 607.697-557.659 639.11c-293.715 38.565-603.394-149.77-687.837-437.104c-88.514-273.766 27.776-603.496 282.864-744.453c97.803-57.623 211.099-88.88 324.665-88.7zM794.723 615.498L615.498 794.723l230.63 230.633l-226.464 226.466l176.38 176.381l226.468-226.467l230.765 230.766l179.225-179.225l-230.766-230.765l226.473-226.475l-176.38-176.38c-75.492 75.49-150.983 150.981-226.473 226.472L794.723 615.498zm-.127 70.68l230.765 230.765l226.467-226.464l105.695 105.695l-226.466 226.465l230.633 230.632l-108.41 108.41l-230.632-230.632l-226.466 226.467l-105.696-105.696l226.467-226.467l-230.765-230.765l108.408-108.41z" fill="#0B344E"/><rect x="0" y="0" width="2048" height="2048" fill="rgba(0, 0, 0, 0)" /></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Free 5.15.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path fill="#E54122" d="M256,8C119,8,8,119,8,256S119,504,256,504,504,393,504,256,393,8,256,8Zm92.49,313h0l-20,25a16,16,0,0,1-22.49,2.5h0l-67-49.72a40,40,0,0,1-15-31.23V112a16,16,0,0,1,16-16h32a16,16,0,0,1,16,16V256l58,42.5A16,16,0,0,1,348.49,321Z"></path></svg>

After

Width:  |  Height:  |  Size: 504 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Free 5.15.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path fill="#43AA8B" d="M256,8C119,8,8,119,8,256S119,504,256,504,504,393,504,256,393,8,256,8Zm92.49,313h0l-20,25a16,16,0,0,1-22.49,2.5h0l-67-49.72a40,40,0,0,1-15-31.23V112a16,16,0,0,1,16-16h32a16,16,0,0,1,16,16V256l58,42.5A16,16,0,0,1,348.49,321Z"></path></svg>

After

Width:  |  Height:  |  Size: 504 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Free 5.15.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path d="M487.4 315.7l-42.6-24.6c4.3-23.2 4.3-47 0-70.2l42.6-24.6c4.9-2.8 7.1-8.6 5.5-14-11.1-35.6-30-67.8-54.7-94.6-3.8-4.1-10-5.1-14.8-2.3L380.8 110c-17.9-15.4-38.5-27.3-60.8-35.1V25.8c0-5.6-3.9-10.5-9.4-11.7-36.7-8.2-74.3-7.8-109.2 0-5.5 1.2-9.4 6.1-9.4 11.7V75c-22.2 7.9-42.8 19.8-60.8 35.1L88.7 85.5c-4.9-2.8-11-1.9-14.8 2.3-24.7 26.7-43.6 58.9-54.7 94.6-1.7 5.4.6 11.2 5.5 14L67.3 221c-4.3 23.2-4.3 47 0 70.2l-42.6 24.6c-4.9 2.8-7.1 8.6-5.5 14 11.1 35.6 30 67.8 54.7 94.6 3.8 4.1 10 5.1 14.8 2.3l42.6-24.6c17.9 15.4 38.5 27.3 60.8 35.1v49.2c0 5.6 3.9 10.5 9.4 11.7 36.7 8.2 74.3 7.8 109.2 0 5.5-1.2 9.4-6.1 9.4-11.7v-49.2c22.2-7.9 42.8-19.8 60.8-35.1l42.6 24.6c4.9 2.8 11 1.9 14.8-2.3 24.7-26.7 43.6-58.9 54.7-94.6 1.5-5.5-.7-11.3-5.6-14.1zM256 336c-44.1 0-80-35.9-80-80s35.9-80 80-80 80 35.9 80 80-35.9 80-80 80z" fill="#4D7281"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,8 @@
<svg
xmlns="http://www.w3.org/2000/svg"
width="100"
height="100"
version="1.1"
>
<circle cx="50" cy="50" r="25" fill="#A7D0E3"/>
</svg>

After

Width:  |  Height:  |  Size: 145 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="32px" height="32px"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path fill="#0B344E" d="M432 416h-23.4L277.9 53.7A32 32 0 0 0 247.6 32h-47.2a32 32 0 0 0 -30.3 21.7L39.4 416H16a16 16 0 0 0 -16 16v32a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16v-32a16 16 0 0 0 -16-16h-19.6l23.3-64h152.6l23.3 64H304a16 16 0 0 0 -16 16v32a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16v-32a16 16 0 0 0 -16-16zM176.9 272L224 142.5 271.2 272z"/></svg>

After

Width:  |  Height:  |  Size: 594 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" width="32px" height="32px" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);" preserveAspectRatio="xMidYMid meet" viewBox="0 0 2048 2048"><path d="M1022.25 99.36C571.973 90.17 151.801 460.306 104.963 908.553c-49.497 379.097 158.63 778.958 505.847 944.173c327.865 164.75 753.014 115.136 1024.928-134.893c274.592-240.828 389.318-650.625 262.28-995.771c-116.8-340.193-443.475-599.288-804.797-620.012c-23.612-1.742-47.291-2.71-70.97-2.71zm-10.482 99.762c412.892-15.548 800.952 328.747 833.307 741.03c39.736 363.608-189.04 740.296-537.189 860.124c-338.432 125.14-753.857 3.483-958.257-297.534c-209.788-293.858-201.456-727.62 36.58-1003.309c152.352-183.51 386.115-299.42 625.559-300.31zm16.396 89.922c-381.953-12.152-733.35 323.184-740.25 705.155c-16.837 345.166 238.98 683.897 580.72 749.641c316.644 69.314 670.444-93.357 811.717-387.882c146.108-287.647 80.764-670.44-168.525-880.558c-132.09-117.003-306.805-186.162-483.662-186.356zm-4.494 98.05c336.708-8.902 643.367 294.109 637.967 631.148c5.952 311.573-244.82 607.695-557.658 639.11c-293.716 38.566-603.396-149.773-687.84-437.11c-88.514-273.762 27.778-603.496 282.866-744.455c97.803-57.616 211.099-88.877 324.665-88.693zm102.98 236.909v205.745H664v388.53h462.65v205.716c112.45-133.325 224.9-266.65 337.35-399.986l-337.35-400.005zm56.356 149.56l208.967 248.52l-208.967 248.52v-111.876H726.938V887.45h456.068V773.563z" fill="#0B344E"/><rect x="0" y="0" width="2048" height="2048" fill="rgba(0, 0, 0, 0)" /></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" width="32px" height="32px" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);" preserveAspectRatio="xMidYMid meet" viewBox="0 0 2048 2048"><path d="M1022.25 99.354c-450.277-9.22-870.449 360.942-917.287 809.188c-49.497 379.099 158.63 778.965 505.847 944.176c327.865 164.75 753.014 115.134 1024.927-134.893c274.593-240.828 389.319-650.62 262.28-995.771c-116.8-340.195-443.475-599.282-804.796-620.01a950.215 950.215 0 0 0-70.971-2.69zm-10.482 99.76c412.893-15.55 800.952 328.748 833.306 741.03c39.736 363.606-189.04 740.293-537.188 860.127c-338.432 125.141-753.857 3.445-958.257-297.536c-209.788-293.857-201.456-727.62 36.58-1003.308c152.352-183.514 386.115-299.42 625.559-300.314zm16.396 89.923c-381.953-12.147-733.35 323.18-740.25 705.154c-16.837 345.168 238.98 683.903 580.72 749.641c316.644 69.318 670.444-93.359 811.717-387.88c146.107-287.645 80.764-670.441-168.526-880.558c-132.088-117.004-306.804-186.158-483.66-186.357zm-4.494 98.049c336.708-8.916 643.367 294.108 637.967 631.146c5.952 311.574-244.821 607.697-557.659 639.11c-293.715 38.565-603.394-149.77-687.837-437.104c-88.514-273.766 27.776-603.496 282.864-744.453c97.803-57.623 211.099-88.88 324.665-88.7zM794.723 615.498L615.498 794.723l230.63 230.633l-226.464 226.466l176.38 176.381l226.468-226.467l230.765 230.766l179.225-179.225l-230.766-230.765l226.473-226.475l-176.38-176.38c-75.492 75.49-150.983 150.981-226.473 226.472L794.723 615.498zm-.127 70.68l230.765 230.765l226.467-226.464l105.695 105.695l-226.466 226.465l230.633 230.632l-108.41 108.41l-230.632-230.632l-226.466 226.467l-105.696-105.696l226.467-226.467l-230.765-230.765l108.408-108.41z" fill="#FBF7F4"/><rect x="0" y="0" width="2048" height="2048" fill="rgba(0, 0, 0, 0)" /></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Free 5.15.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path fill="#E54122" d="M256,8C119,8,8,119,8,256S119,504,256,504,504,393,504,256,393,8,256,8Zm92.49,313h0l-20,25a16,16,0,0,1-22.49,2.5h0l-67-49.72a40,40,0,0,1-15-31.23V112a16,16,0,0,1,16-16h32a16,16,0,0,1,16,16V256l58,42.5A16,16,0,0,1,348.49,321Z"></path></svg>

After

Width:  |  Height:  |  Size: 504 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Free 5.15.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path fill="#43AA8B" d="M256,8C119,8,8,119,8,256S119,504,256,504,504,393,504,256,393,8,256,8Zm92.49,313h0l-20,25a16,16,0,0,1-22.49,2.5h0l-67-49.72a40,40,0,0,1-15-31.23V112a16,16,0,0,1,16-16h32a16,16,0,0,1,16,16V256l58,42.5A16,16,0,0,1,348.49,321Z"></path></svg>

After

Width:  |  Height:  |  Size: 504 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="32px" height="32px"><!--!Font Awesome Free 6.5.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path fill="#FBF7F4" d="M432 416h-23.4L277.9 53.7A32 32 0 0 0 247.6 32h-47.2a32 32 0 0 0 -30.3 21.7L39.4 416H16a16 16 0 0 0 -16 16v32a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16v-32a16 16 0 0 0 -16-16h-19.6l23.3-64h152.6l23.3 64H304a16 16 0 0 0 -16 16v32a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16v-32a16 16 0 0 0 -16-16zM176.9 272L224 142.5 271.2 272z"/></svg>

After

Width:  |  Height:  |  Size: 594 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" width="32px" height="32px" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);" preserveAspectRatio="xMidYMid meet" viewBox="0 0 2048 2048"><path d="M1022.25 99.36C571.973 90.17 151.801 460.306 104.963 908.553c-49.497 379.097 158.63 778.958 505.847 944.173c327.865 164.75 753.014 115.136 1024.928-134.893c274.592-240.828 389.318-650.625 262.28-995.771c-116.8-340.193-443.475-599.288-804.797-620.012c-23.612-1.742-47.291-2.71-70.97-2.71zm-10.482 99.762c412.892-15.548 800.952 328.747 833.307 741.03c39.736 363.608-189.04 740.296-537.189 860.124c-338.432 125.14-753.857 3.483-958.257-297.534c-209.788-293.858-201.456-727.62 36.58-1003.309c152.352-183.51 386.115-299.42 625.559-300.31zm16.396 89.922c-381.953-12.152-733.35 323.184-740.25 705.155c-16.837 345.166 238.98 683.897 580.72 749.641c316.644 69.314 670.444-93.357 811.717-387.882c146.108-287.647 80.764-670.44-168.525-880.558c-132.09-117.003-306.805-186.162-483.662-186.356zm-4.494 98.05c336.708-8.902 643.367 294.109 637.967 631.148c5.952 311.573-244.82 607.695-557.658 639.11c-293.716 38.566-603.396-149.773-687.84-437.11c-88.514-273.762 27.778-603.496 282.866-744.455c97.803-57.616 211.099-88.877 324.665-88.693zm102.98 236.909v205.745H664v388.53h462.65v205.716c112.45-133.325 224.9-266.65 337.35-399.986l-337.35-400.005zm56.356 149.56l208.967 248.52l-208.967 248.52v-111.876H726.938V887.45h456.068V773.563z" fill="#FBF7F4"/><rect x="0" y="0" width="2048" height="2048" fill="rgba(0, 0, 0, 0)" /></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="bug" class="svg-inline--fa fa-bug fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" width="28px" height="28px" viewBox="0 0 512 512"><path fill="#FBF7F4" d="M511.988 288.9c-.478 17.43-15.217 31.1-32.653 31.1H424v16c0 21.864-4.882 42.584-13.6 61.145l60.228 60.228c12.496 12.497 12.496 32.758 0 45.255-12.498 12.497-32.759 12.496-45.256 0l-54.736-54.736C345.886 467.965 314.351 480 280 480V236c0-6.627-5.373-12-12-12h-24c-6.627 0-12 5.373-12 12v244c-34.351 0-65.886-12.035-90.636-32.108l-54.736 54.736c-12.498 12.497-32.759 12.496-45.256 0-12.496-12.497-12.496-32.758 0-45.255l60.228-60.228C92.882 378.584 88 357.864 88 336v-16H32.666C15.23 320 .491 306.33.013 288.9-.484 270.816 14.028 256 32 256h56v-58.745l-46.628-46.628c-12.496-12.497-12.496-32.758 0-45.255 12.498-12.497 32.758-12.497 45.256 0L141.255 160h229.489l54.627-54.627c12.498-12.497 32.758-12.497 45.256 0 12.496 12.497 12.496 32.758 0 45.255L424 197.255V256h56c17.972 0 32.484 14.816 31.988 32.9zM257 0c-61.856 0-112 50.144-112 112h224C369 50.144 318.856 0 257 0z"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" width="32px" height="32px" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);" preserveAspectRatio="xMidYMid meet" viewBox="0 0 2048 2048"><path d="M1024 99.004c-510.27 0-925 414.72-925 924.994s414.729 925.004 925 925.004s925-414.73 925-925.004c0-510.273-414.73-924.994-925-924.994zm0 100c456.228 0 825 368.764 825 824.994s-368.773 825.004-825 825.004s-825-368.774-825-825.004s368.772-824.994 825-824.994zm-8.29 385.945c-171.528 1.212-335.1 113.885-398.317 273.285c-65.79 155.801-29.42 347.57 88.884 468.442c115.027 123.83 304.555 169.464 463.307 111.428c166.36-56.675 288.573-221.231 292.976-397.19c8.863-170.945-93.874-339.97-248.558-412.566c-59.027-28.58-124.545-43.458-190.117-43.371a394.838 394.838 0 0 0-8.174-.028zm15.643 79.973c156.707.7 303.715 116.52 340.258 269.176c40.7 149.018-27.87 318.936-161.361 397.015c-136.3 85.544-328.47 62.745-440.395-53.11c-118.113-113.943-139.147-311.084-48.53-447.733c64.178-101.947 182.05-166.467 302.56-165.293c2.492-.048 4.981-.066 7.468-.055zm105.952 102.072l-119.903 217.527c-37.032 3.938-45.83 60.122-11.662 75.106c22.57 15.666 40.247-10.392 60.793-9.494h183.293v-50H1065.89l115.205-209.004l-43.79-24.135z" fill="#FBF7F4"/><rect x="0" y="0" width="2048" height="2048" fill="rgba(0, 0, 0, 0)" /></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" width="32px" height="32px" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);" preserveAspectRatio="xMidYMid meet" viewBox="0 0 2048 2048"><path d="M1024 99c-363.757-4.934-714.249 229.17-852.226 565.192c-144.118 330.153-69.236 740.283 182.862 997.771c242.464 260.992 641.95 356.786 976.52 234.67c346.74-118.073 602.724-458.592 616.639-825.09c23.34-360.131-188.536-719.422-512.954-876.293C1307.807 131.856 1165.95 98.797 1024 99zm0 100c359.283-6.249 700.984 251.836 793.747 598.93c97.03 326.908-34.215 705.405-315.907 899.024C1209.227 1912.03 775.3 1896.47 498.977 1660.8c-278.414-221.98-378.073-632.576-235.228-958.182C387.557 401.605 698.338 195.179 1024 199zm354.53 430.836l-210.237 426.18l-261.406-340.014l-237.416 702.162l266.172-342.129l274.562 314.17l168.324-760.37zm-448.397 224.86l248.558 315.126l62.078-138.195l-59.431 239.111l-253.695-298.441l-102.926 159.117l105.416-276.719z" fill="#FBF7F4"/><rect x="0" y="0" width="2048" height="2048" fill="rgba(0, 0, 0, 0)" /></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" width="32px" height="32px" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);" preserveAspectRatio="xMidYMid meet" viewBox="0 0 2048 2048"><path d="M1005.303 98.988c-447.59.377-859.063 373.342-901.291 819.483c-45.314 379.987 168.535 777.154 517.567 938.558c324.137 158.423 740.176 108.288 1009.01-135.647c273.526-236.69 393.041-640.13 272.893-984.32c-114.258-351.15-451.46-619.981-822.842-636.252c-25.079-1.64-50.213-1.947-75.337-1.822zm18.334 289.733c329.628-9.588 631.825 281.137 635.693 610.898c14.255 301.673-212.385 597.907-513.018 648.484c-290.913 58.275-613.563-109.854-715.39-392.282c-109.622-279.29 2.217-632.478 269.045-779.065c97.56-57.284 210.508-88.176 323.67-88.035zm354.892 241.117l-210.236 426.178L906.887 716L669.47 1418.162l266.174-342.127l274.562 314.172l168.322-760.37z" fill="#FBF7F4"/><rect x="0" y="0" width="2048" height="2048" fill="rgba(0, 0, 0, 0)" /></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32px" height="32px" preserveAspectRatio="xMidYMid meet" viewBox="0 0 2048 2048" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);"><g transform="translate(0 995.638)"><rect width="1699.302" height="1400.778" x="174.349" y="-672.027" rx="266.951" fill="none" stroke="#FBF7F4" stroke-width="100" stroke-linecap="round" stroke-linejoin="round"/></g><rect x="0" y="0" width="2048" height="2048" fill="rgba(0, 0, 0, 0)" /></svg>

After

Width:  |  Height:  |  Size: 563 B

View File

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<g id="Group_6221" data-name="Group 6221" transform="translate(-378 -5834)">
<rect id="Rectangle_261" data-name="Rectangle 261" width="32" height="32" transform="translate(378 5834)" fill="rgba(255,255,255,0)"/>
<g id="Group_6219" data-name="Group 6219" transform="translate(381 5845)">
<path id="Path_5690" data-name="Path 5690" d="M6.877,0,4.709,3.643,2.382,0H0L3.424,5.353,0,10.613H2.312L4.461,6.991l2.311,3.623H9.148L5.746,5.3,9.17,0Z" fill="#FBF7F4"/>
<path id="Path_5691" data-name="Path 5691" d="M10.855,0V4H6.908V5.882h3.947v4h1.973v-4h3.949V4H12.828V0Z" transform="translate(3.815)" fill="#FBF7F4"/>
<path id="Path_5692" data-name="Path 5692" d="M14.266,0V1.9h1.527v8.717h2.044V0Z" transform="translate(7.877)" fill="#FBF7F4"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 872 B

View File

@@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
<defs>
<clipPath id="clip-path">
<rect id="Rectangle_262" data-name="Rectangle 262" width="26.102" height="16.706" fill="#FBF7F4"/>
</clipPath>
</defs>
<g id="Group_6220" data-name="Group 6220" transform="translate(-333 -5834)">
<rect id="Rectangle_260" data-name="Rectangle 260" width="32" height="32" transform="translate(333 5834)" fill="rgba(255,255,255,0)"/>
<g id="Group_6218" data-name="Group 6218" transform="translate(336 5842)">
<g id="Group_6217" data-name="Group 6217" transform="translate(0 0)" clip-path="url(#clip-path)">
<path id="Path_5686" data-name="Path 5686" d="M5.664,5.292v-1.6H3.719v-.6a1.556,1.556,0,0,1,.344-1.149A1.882,1.882,0,0,1,5.353,1.6h.176V0H5.353A4.067,4.067,0,0,0,2.684.736a2.941,2.941,0,0,0-.874,2.35v.6H.609v1.6h1.2V13.62a1.565,1.565,0,0,1-.343,1.149A1.9,1.9,0,0,1,.176,15.1H0v1.606H.176a4.064,4.064,0,0,0,2.669-.736,2.937,2.937,0,0,0,.874-2.35V5.292Z" transform="translate(0 0.001)" fill="#FBF7F4"/>
<path id="Path_5687" data-name="Path 5687" d="M8.146,12.7a10.534,10.534,0,0,1,0-8.562A9.09,9.09,0,0,1,10.9.658L10.971.6V.22H9.411L9.365.254A8.615,8.615,0,0,0,6.681,3.667a11.287,11.287,0,0,0-1,4.745,11.322,11.322,0,0,0,1,4.751,8.6,8.6,0,0,0,2.684,3.42l.045.034h1.561v-.385l-.068-.054A9,9,0,0,1,8.146,12.7" transform="translate(2.328 0.091)" fill="#FBF7F4"/>
<path id="Path_5688" data-name="Path 5688" d="M14.359,7.428l3.108-4.813H15.386L13.417,5.924,11.3,2.616H9.143l3.106,4.862-3.11,4.777h2.1l1.952-3.29,2.1,3.29h2.16Z" transform="translate(3.744 1.072)" fill="#FBF7F4"/>
<path id="Path_5689" data-name="Path 5689" d="M19.054,3.667A8.612,8.612,0,0,0,16.371.254L16.325.22H14.764V.6l.066.054A9.213,9.213,0,0,1,17.583,4.14a10.489,10.489,0,0,1,0,8.562,9.082,9.082,0,0,1-2.753,3.475l-.066.054v.385h1.561l.047-.034a8.6,8.6,0,0,0,2.683-3.42,11.31,11.31,0,0,0,1-4.751,11.275,11.275,0,0,0-1-4.745" transform="translate(6.048 0.091)" fill="#FBF7F4"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" width="32px" height="32px" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);" preserveAspectRatio="xMidYMid meet" viewBox="0 0 2048 2048"><path d="M1024.022 99.36c-19.324-.017-38.646 7.15-52.98 21.55L120.937 971.023c-28.67 28.668-28.537 77.295.132 105.963l849.971 849.965c28.67 28.678 77.294 28.804 105.963 0l850.106-850.1c28.669-28.667 28.536-77.296-.135-105.964L1077.002 120.91c-14.334-14.334-33.657-21.534-52.98-21.55zm-.065 126.045l798.66 798.666l-798.66 798.657l-798.66-798.657l798.66-798.666zM725.686 669.792c-.014 0-9.612 1.838-9.62 1.838c-.01 0-8.144 5.513-8.15 5.513l-30.732 30.739c-.01 0-5.61 8.225-5.614 8.322c0 .01-1.737 9.48-1.736 9.48c0 .01 1.868 9.385 1.871 9.385c0 .01 5.338 8.322 5.344 8.322l280.707 280.7l-280.572 280.574v-.088c0 .01-5.61 8.32-5.614 8.32c0 .01-1.736 9.483-1.736 9.483c0 .02 1.868 9.385 1.871 9.385c0 0 5.339 8.223 5.344 8.32l30.734 30.728c.01.01 8.411 5.516 8.418 5.516c.01 0 9.346 1.838 9.354 1.838c.01 0 9.479-1.74 9.486-1.74c.01 0 8.28-5.614 8.285-5.614l280.576-280.582l280.637 280.641c.01.01 8.412 5.516 8.418 5.516c.01 0 9.346 1.838 9.354 1.838c.01 0 9.48-1.743 9.488-1.743c.01 0 8.276-5.61 8.281-5.61l30.735-30.73c.01-.01 5.475-8.126 5.48-8.126c0-.01 1.871-9.58 1.871-9.676c0-.01-1.869-9.385-1.873-9.385c0 0-5.472-8.418-5.478-8.418l-280.606-280.611l280.608-280.604c.01 0 5.473-8.127 5.478-8.127c0-.01 1.871-9.578 1.871-9.578c0-.02-1.868-9.385-1.87-9.385c0-.01-5.606-8.322-5.612-8.322l-30.735-30.738c-.01 0-8.143-5.514-8.15-5.514c-.01 0-9.345-1.84-9.353-1.84c-.01 0-9.613 1.84-9.62 1.84c-.01 0-8.145 5.514-8.15 5.514l-280.613 280.613l-280.739-280.748v-.088c-.01 0-8.278-5.32-8.285-5.32c-.01 0-9.34-1.837-9.351-1.838h-.002z" fill="#FBF7F4"/><rect x="0" y="0" width="2048" height="2048" fill="rgba(0, 0, 0, 0)" /></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32px" height="32px" preserveAspectRatio="xMidYMid meet" viewBox="0 0 2048 2048" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);"><path d="M1024.022 99.36c-19.324-.017-38.646 7.15-52.98 21.55L120.937 971.023c-28.67 28.668-28.537 77.295.132 105.963l849.971 849.965c28.67 28.678 77.294 28.804 105.963 0l850.106-850.1c28.669-28.667 28.536-77.296-.135-105.964L1077.002 120.91c-14.334-14.334-33.657-21.534-52.98-21.55zm-.065 126.045l798.66 798.666l-798.66 798.657l-798.66-798.657l798.66-798.666zm.043 368.6c-237.232 0-430 192.78-430 430.008c0 237.228 192.768 430 430 430s430-192.772 430-430c0-237.229-192.768-430.008-430-430.008zm0 47.69c211.408 0 382.323 170.912 382.323 382.318c0 211.405-170.915 382.33-382.323 382.33c-211.407 0-382.322-170.925-382.322-382.33c0-211.406 170.915-382.319 382.322-382.319z" fill="#FBF7F4"/><rect x="0" y="0" width="2048" height="2048" fill="rgba(0, 0, 0, 0)" /></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32px" height="32px" preserveAspectRatio="xMidYMid meet" viewBox="0 0 2048 2048" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);"><path d="M1024.022 99.36c-19.324-.017-38.646 7.15-52.98 21.55L120.937 971.023c-28.67 28.668-28.537 77.295.132 105.963l849.971 849.965c28.67 28.678 77.294 28.804 105.963 0l850.106-850.1c28.669-28.667 28.536-77.296-.135-105.964L1077.002 120.91c-14.334-14.334-33.657-21.534-52.98-21.55zm-.065 126.045l798.66 798.666l-798.66 798.657l-798.66-798.657l798.66-798.666zm-21.803 329.82c0 .01-9.657 1.838-9.662 1.838c-.01 0-7.908 5.323-7.914 5.323c-.01.01-5.497 8.127-5.502 8.127c0 .01-1.86 9.675-1.863 9.675V977.04H580.188l-.067-.078c0 .01-9.618 2.129-9.623 2.129c-.01.01-7.907 5.322-7.912 5.322l.008-.098c-.01.01-5.497 8.127-5.502 8.127c-.01 0-1.861 9.676-1.865 9.676v43.47s1.848 9.783 1.914 9.85c0 .01 5.478 7.934 5.478 7.934c.01.01 7.957 5.322 7.96 5.322c.01.01 9.656 2.127 9.66 2.127h396.978v396.785l-.063-.058c0 .01 1.916 9.85 1.915 9.85c0 .01 5.476 7.933 5.476 7.933c.01.01 7.96 5.32 7.961 5.32c0 0 9.59 2.032 9.662 2.13l43.461-.01c.011 0 9.846-2.032 9.852-2.032c0 0 7.908-5.32 7.914-5.32c.01-.01 5.47-7.936 5.476-7.936c0 0 1.887-9.82 1.89-9.82V1070.86h396.88c.011.01 9.847-2.031 9.851-2.031c.01 0 7.91-5.322 7.914-5.322c.01-.01 5.471-7.934 5.477-7.934c0 0 1.886-9.82 1.89-9.82v-43.461c0-.01-1.877-9.58-1.874-9.676c-.01-.01-5.45-8.127-5.518-8.127c-.01-.01-7.958-5.32-7.96-5.32c0 .01-9.82-2.13-9.825-2.033h-396.838V580.294c0-.01-1.878-9.577-1.875-9.674c-.01-.01-5.45-8.129-5.45-8.129c-.01-.01-7.956-5.32-7.958-5.32c-.01 0-9.848-1.936-9.852-1.936l-43.469-.01z" fill="#FBF7F4"/><rect x="0" y="0" width="2048" height="2048" fill="rgba(0, 0, 0, 0)" /></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,13 @@
<svg width="8192" height="8192" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="smallGrid" width="8" height="8" patternUnits="userSpaceOnUse">
<path d="M 8 0 L 0 0 0 8" fill="none" stroke="#3E5A6F" stroke-width="0.5"/>
</pattern>
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
<rect width="80" height="80" fill="url(#smallGrid)"/>
<path d="M 80 0 L 0 0 0 80" fill="none" stroke="#3E5A6F" stroke-width="1"/>
</pattern>
</defs>
<rect width="8192" height="8192" fill="url(#grid)" />
</svg>

After

Width:  |  Height:  |  Size: 561 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" width="32px" height="32px" style="-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg);transform:rotate(360deg)" preserveAspectRatio="xMidYMid meet" viewBox="0 0 2000 2000">
<path d="M429.65 210C214.934 210 40 384.196 40 598.5v803c0 214.304 174.934 388.5 389.65 388.5h1140.7c214.716 0 389.65-174.196 389.65-388.5v-803c0-214.304-174.934-388.5-389.65-388.5H429.65zm0 120h1140.7C1720.887 330 1840 448.826 1840 598.5v803c0 149.674-119.113 268.5-269.65 268.5H429.65C279.113 1670 160 1551.174 160 1401.5v-803C160 448.826 279.113 330 429.65 330z" fill="#FBF7F4"/>
<path d="M1057.07 410.836C805.11 407.3 563.447 583.065 491.134 824.983c-55.584 173.977-23.105 373.061 85.522 520.027l-269.086-52.09l-22.804 117.814l483.865 93.664l93.687-479.986l-117.779-22.988l-56.861 291.316c-138.733-165.6-136.73-427.773 4.367-591.379c137.417-171.716 399.203-221.007 590.733-114c183.232 94.568 284.888 318.597 234.896 518.746c-44.77 208.91-247.404 367.34-460.604 363.053c-55.485-3.935-83.374 76.196-37.436 107.561c40.104 24.986 90.846 7.364 134.808 4.475c248.181-37.748 457.52-249.452 489.52-498.91c36.994-238.025-91.384-488.935-304.803-600.241c-86.168-46.769-184.068-71.364-282.089-71.21z" fill="#FBF7F4"/>
<rect x="0" y="0" width="2000" height="2000" fill="rgba(0, 0, 0, 0)"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" width="32px" height="32px" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);" preserveAspectRatio="xMidYMid meet" viewBox="0 0 2000 2000"><g transform="translate(0 947.638)"><rect width="1800" height="1460" x="100" y="-677.638" rx="329.651" ry="328.5" fill="transparent" stroke="#FBF7F4" stroke-width="120" stroke-linecap="round"/><path d="M782.99 94.728h320.662c31.894 0 46.426-18.387 52.19-46.737c8.96-43.893-13.184-85.435-52.814-85.533c-97.378-.489-320.04-.196-320.04-.196v.223s340.368.268 379.564 0c39.195-.27 53.287-33.884 53.405-71.521c.12-37.54-18.907-70.744-53.287-70.793c-117.153-.098-379.68-.635-379.68-.635v-.194s199.412.465 285.185.465c35.051-.098 53.445-30.314 53.327-68.293c-.237-37.197-19.42-63.45-53.722-63.45c-137.086-.244-428.878 1.895-437.468-.146c-2.274-.54-4.22-4.971-2.723-6.794c14.054-17.122 80.562-79.478 95.325-98.59c18.512-23.803 20.486-56.943 6.394-78.401c-16.341-24.977-38.406-24.44-58.418-11.536c-57.669 37.344-260.516 178.067-305.237 208.763c-35.525 24.39-59.84 61.245-74.287 107.045c-14.604 46.435-13.105 100.51-12.947 147.875c.158 35.095.949 63.478 9.554 98.573c24.827 102.01 90.39 154.164 186.464 154.457c182.282.684 364.643.929 546.964 0c29.801-.195 46.221-19.702 46.655-55.482c.474-36.854-16.696-58.591-46.695-58.787c-70.181-.489-238.37 0-238.37 0z" fill="none" stroke="#FBF7F4" stroke-width="43.663900000000005" stroke-linecap="round"/></g><rect x="0" y="0" width="2000" height="2000" fill="rgba(0, 0, 0, 0)" /></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32px" height="32px" preserveAspectRatio="xMidYMid meet" viewBox="0 0 2048 2048" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);"><path d="M96 402v1280h1856V402zm1756 97.7v1091.542H480V499.702l1372-.003zM192 499.7h192v1091.541H192z" fill="#FBF7F4"/><rect x="0" y="0" width="2048" height="2048" fill="rgba(0, 0, 0, 0)" /></svg>

After

Width:  |  Height:  |  Size: 467 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" width="32px" height="32px" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);" preserveAspectRatio="xMidYMid meet" viewBox="0 0 2048 2048"><path d="M1005.303 98.988c-447.59.377-859.063 373.342-901.291 819.483c-45.314 379.987 168.535 777.154 517.567 938.558c324.137 158.423 740.176 108.288 1009.01-135.647c273.526-236.69 393.041-640.13 272.893-984.32c-114.258-351.15-451.46-619.981-822.842-636.252c-25.079-1.64-50.213-1.947-75.337-1.822zm18.334 289.733c329.628-9.588 631.825 281.137 635.693 610.898c14.255 301.673-212.385 597.907-513.018 648.484c-290.913 58.275-613.563-109.854-715.39-392.282c-109.622-279.29 2.217-632.478 269.045-779.065c97.56-57.284 210.508-88.176 323.67-88.035zM1024 554.17c-304.682-11.568-549.567 321.3-448.846 608.674c79.046 294.298 469.266 430.4 714.132 248.923c257.896-162.49 272.52-575.472 26.765-755.798c-82.202-65.575-186.913-102.06-292.051-101.8z" fill="#FBF7F4"/><rect x="0" y="0" width="2048" height="2048" fill="rgba(0, 0, 0, 0)" /></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32px" height="32px" preserveAspectRatio="xMidYMid meet" viewBox="0 0 2048 2048" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);"><path d="M1899 1023.999c0-483.252-391.75-874.995-875-874.995S149 540.747 149 1024c0 483.251 391.75 875.004 875 875.004s875-391.753 875-875.004z" fill="none" stroke="#FBF7F4" stroke-width="100.04248826"/><rect x="0" y="0" width="2048" height="2048" fill="rgba(0, 0, 0, 0)" /></svg>

After

Width:  |  Height:  |  Size: 551 B

View File

@@ -0,0 +1 @@
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="bug" class="svg-inline--fa fa-bug fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" width="28px" height="28px" viewBox="0 0 512 512"><path fill="#0B344E" d="M511.988 288.9c-.478 17.43-15.217 31.1-32.653 31.1H424v16c0 21.864-4.882 42.584-13.6 61.145l60.228 60.228c12.496 12.497 12.496 32.758 0 45.255-12.498 12.497-32.759 12.496-45.256 0l-54.736-54.736C345.886 467.965 314.351 480 280 480V236c0-6.627-5.373-12-12-12h-24c-6.627 0-12 5.373-12 12v244c-34.351 0-65.886-12.035-90.636-32.108l-54.736 54.736c-12.498 12.497-32.759 12.496-45.256 0-12.496-12.497-12.496-32.758 0-45.255l60.228-60.228C92.882 378.584 88 357.864 88 336v-16H32.666C15.23 320 .491 306.33.013 288.9-.484 270.816 14.028 256 32 256h56v-58.745l-46.628-46.628c-12.496-12.497-12.496-32.758 0-45.255 12.498-12.497 32.758-12.497 45.256 0L141.255 160h229.489l54.627-54.627c12.498-12.497 32.758-12.497 45.256 0 12.496 12.497 12.496 32.758 0 45.255L424 197.255V256h56c17.972 0 32.484 14.816 31.988 32.9zM257 0c-61.856 0-112 50.144-112 112h224C369 50.144 318.856 0 257 0z"></path></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" width="32px" height="32px" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);" preserveAspectRatio="xMidYMid meet" viewBox="0 0 2048 2048"><path d="M1024 99.004c-510.27 0-925 414.72-925 924.994s414.729 925.004 925 925.004s925-414.73 925-925.004c0-510.273-414.73-924.994-925-924.994zm0 100c456.228 0 825 368.764 825 824.994s-368.773 825.004-825 825.004s-825-368.774-825-825.004s368.772-824.994 825-824.994zm-8.29 385.945c-171.528 1.212-335.1 113.885-398.317 273.285c-65.79 155.801-29.42 347.57 88.884 468.442c115.027 123.83 304.555 169.464 463.307 111.428c166.36-56.675 288.573-221.231 292.976-397.19c8.863-170.945-93.874-339.97-248.558-412.566c-59.027-28.58-124.545-43.458-190.117-43.371a394.838 394.838 0 0 0-8.174-.028zm15.643 79.973c156.707.7 303.715 116.52 340.258 269.176c40.7 149.018-27.87 318.936-161.361 397.015c-136.3 85.544-328.47 62.745-440.395-53.11c-118.113-113.943-139.147-311.084-48.53-447.733c64.178-101.947 182.05-166.467 302.56-165.293c2.492-.048 4.981-.066 7.468-.055zm105.952 102.072l-119.903 217.527c-37.032 3.938-45.83 60.122-11.662 75.106c22.57 15.666 40.247-10.392 60.793-9.494h183.293v-50H1065.89l115.205-209.004l-43.79-24.135z" fill="#0B344E"/><rect x="0" y="0" width="2048" height="2048" fill="rgba(0, 0, 0, 0)" /></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" width="32px" height="32px" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);" preserveAspectRatio="xMidYMid meet" viewBox="0 0 2048 2048"><path d="M1024 99c-363.757-4.934-714.249 229.17-852.226 565.192c-144.118 330.153-69.236 740.283 182.862 997.771c242.464 260.992 641.95 356.786 976.52 234.67c346.74-118.073 602.724-458.592 616.639-825.09c23.34-360.131-188.536-719.422-512.954-876.293C1307.807 131.856 1165.95 98.797 1024 99zm0 100c359.283-6.249 700.984 251.836 793.747 598.93c97.03 326.908-34.215 705.405-315.907 899.024C1209.227 1912.03 775.3 1896.47 498.977 1660.8c-278.414-221.98-378.073-632.576-235.228-958.182C387.557 401.605 698.338 195.179 1024 199zm354.53 430.836l-210.237 426.18l-261.406-340.014l-237.416 702.162l266.172-342.129l274.562 314.17l168.324-760.37zm-448.397 224.86l248.558 315.126l62.078-138.195l-59.431 239.111l-253.695-298.441l-102.926 159.117l105.416-276.719z" fill="#0B344E"/><rect x="0" y="0" width="2048" height="2048" fill="rgba(0, 0, 0, 0)" /></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" width="32px" height="32px" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);" preserveAspectRatio="xMidYMid meet" viewBox="0 0 2048 2048"><path d="M1005.303 98.988c-447.59.377-859.063 373.342-901.291 819.483c-45.314 379.987 168.535 777.154 517.567 938.558c324.137 158.423 740.176 108.288 1009.01-135.647c273.526-236.69 393.041-640.13 272.893-984.32c-114.258-351.15-451.46-619.981-822.842-636.252c-25.079-1.64-50.213-1.947-75.337-1.822zm18.334 289.733c329.628-9.588 631.825 281.137 635.693 610.898c14.255 301.673-212.385 597.907-513.018 648.484c-290.913 58.275-613.563-109.854-715.39-392.282c-109.622-279.29 2.217-632.478 269.045-779.065c97.56-57.284 210.508-88.176 323.67-88.035zm354.892 241.117l-210.236 426.178L906.887 716L669.47 1418.162l266.174-342.127l274.562 314.172l168.322-760.37z" fill="#0B344E"/><rect x="0" y="0" width="2048" height="2048" fill="rgba(0, 0, 0, 0)" /></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32px" height="32px" preserveAspectRatio="xMidYMid meet" viewBox="0 0 2048 2048" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);"><g transform="translate(0 995.638)"><rect width="1699.302" height="1400.778" x="174.349" y="-672.027" rx="266.951" fill="none" stroke="#0B344E" stroke-width="100" stroke-linecap="round" stroke-linejoin="round"/></g><rect x="0" y="0" width="2048" height="2048" fill="rgba(0, 0, 0, 0)" /></svg>

After

Width:  |  Height:  |  Size: 563 B

View File

@@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<g id="Group_6221" data-name="Group 6221" transform="translate(-378 -5834)">
<rect id="Rectangle_261" data-name="Rectangle 261" width="32" height="32" transform="translate(378 5834)" fill="rgba(255,255,255,0)"/>
<g id="Group_6219" data-name="Group 6219" transform="translate(381 5845)">
<path id="Path_5690" data-name="Path 5690" d="M6.877,0,4.709,3.643,2.382,0H0L3.424,5.353,0,10.613H2.312L4.461,6.991l2.311,3.623H9.148L5.746,5.3,9.17,0Z" fill="#0B344E"/>
<path id="Path_5691" data-name="Path 5691" d="M10.855,0V4H6.908V5.882h3.947v4h1.973v-4h3.949V4H12.828V0Z" transform="translate(3.815)" fill="#0B344E"/>
<path id="Path_5692" data-name="Path 5692" d="M14.266,0V1.9h1.527v8.717h2.044V0Z" transform="translate(7.877)" fill="#0B344E"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 872 B

View File

@@ -0,0 +1,18 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32" height="32" viewBox="0 0 32 32">
<defs>
<clipPath id="clip-path">
<rect id="Rectangle_262" data-name="Rectangle 262" width="26.102" height="16.706" fill="#0B344E"/>
</clipPath>
</defs>
<g id="Group_6220" data-name="Group 6220" transform="translate(-333 -5834)">
<rect id="Rectangle_260" data-name="Rectangle 260" width="32" height="32" transform="translate(333 5834)" fill="rgba(255,255,255,0)"/>
<g id="Group_6218" data-name="Group 6218" transform="translate(336 5842)">
<g id="Group_6217" data-name="Group 6217" transform="translate(0 0)" clip-path="url(#clip-path)">
<path id="Path_5686" data-name="Path 5686" d="M5.664,5.292v-1.6H3.719v-.6a1.556,1.556,0,0,1,.344-1.149A1.882,1.882,0,0,1,5.353,1.6h.176V0H5.353A4.067,4.067,0,0,0,2.684.736a2.941,2.941,0,0,0-.874,2.35v.6H.609v1.6h1.2V13.62a1.565,1.565,0,0,1-.343,1.149A1.9,1.9,0,0,1,.176,15.1H0v1.606H.176a4.064,4.064,0,0,0,2.669-.736,2.937,2.937,0,0,0,.874-2.35V5.292Z" transform="translate(0 0.001)" fill="#0B344E"/>
<path id="Path_5687" data-name="Path 5687" d="M8.146,12.7a10.534,10.534,0,0,1,0-8.562A9.09,9.09,0,0,1,10.9.658L10.971.6V.22H9.411L9.365.254A8.615,8.615,0,0,0,6.681,3.667a11.287,11.287,0,0,0-1,4.745,11.322,11.322,0,0,0,1,4.751,8.6,8.6,0,0,0,2.684,3.42l.045.034h1.561v-.385l-.068-.054A9,9,0,0,1,8.146,12.7" transform="translate(2.328 0.091)" fill="#0B344E"/>
<path id="Path_5688" data-name="Path 5688" d="M14.359,7.428l3.108-4.813H15.386L13.417,5.924,11.3,2.616H9.143l3.106,4.862-3.11,4.777h2.1l1.952-3.29,2.1,3.29h2.16Z" transform="translate(3.744 1.072)" fill="#0B344E"/>
<path id="Path_5689" data-name="Path 5689" d="M19.054,3.667A8.612,8.612,0,0,0,16.371.254L16.325.22H14.764V.6l.066.054A9.213,9.213,0,0,1,17.583,4.14a10.489,10.489,0,0,1,0,8.562,9.082,9.082,0,0,1-2.753,3.475l-.066.054v.385h1.561l.047-.034a8.6,8.6,0,0,0,2.683-3.42,11.31,11.31,0,0,0,1-4.751,11.275,11.275,0,0,0-1-4.745" transform="translate(6.048 0.091)" fill="#0B344E"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" width="32px" height="32px" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);" preserveAspectRatio="xMidYMid meet" viewBox="0 0 2048 2048"><path d="M1024.022 99.36c-19.324-.017-38.646 7.15-52.98 21.55L120.937 971.023c-28.67 28.668-28.537 77.295.132 105.963l849.971 849.965c28.67 28.678 77.294 28.804 105.963 0l850.106-850.1c28.669-28.667 28.536-77.296-.135-105.964L1077.002 120.91c-14.334-14.334-33.657-21.534-52.98-21.55zm-.065 126.045l798.66 798.666l-798.66 798.657l-798.66-798.657l798.66-798.666zM725.686 669.792c-.014 0-9.612 1.838-9.62 1.838c-.01 0-8.144 5.513-8.15 5.513l-30.732 30.739c-.01 0-5.61 8.225-5.614 8.322c0 .01-1.737 9.48-1.736 9.48c0 .01 1.868 9.385 1.871 9.385c0 .01 5.338 8.322 5.344 8.322l280.707 280.7l-280.572 280.574v-.088c0 .01-5.61 8.32-5.614 8.32c0 .01-1.736 9.483-1.736 9.483c0 .02 1.868 9.385 1.871 9.385c0 0 5.339 8.223 5.344 8.32l30.734 30.728c.01.01 8.411 5.516 8.418 5.516c.01 0 9.346 1.838 9.354 1.838c.01 0 9.479-1.74 9.486-1.74c.01 0 8.28-5.614 8.285-5.614l280.576-280.582l280.637 280.641c.01.01 8.412 5.516 8.418 5.516c.01 0 9.346 1.838 9.354 1.838c.01 0 9.48-1.743 9.488-1.743c.01 0 8.276-5.61 8.281-5.61l30.735-30.73c.01-.01 5.475-8.126 5.48-8.126c0-.01 1.871-9.58 1.871-9.676c0-.01-1.869-9.385-1.873-9.385c0 0-5.472-8.418-5.478-8.418l-280.606-280.611l280.608-280.604c.01 0 5.473-8.127 5.478-8.127c0-.01 1.871-9.578 1.871-9.578c0-.02-1.868-9.385-1.87-9.385c0-.01-5.606-8.322-5.612-8.322l-30.735-30.738c-.01 0-8.143-5.514-8.15-5.514c-.01 0-9.345-1.84-9.353-1.84c-.01 0-9.613 1.84-9.62 1.84c-.01 0-8.145 5.514-8.15 5.514l-280.613 280.613l-280.739-280.748v-.088c-.01 0-8.278-5.32-8.285-5.32c-.01 0-9.34-1.837-9.351-1.838h-.002z" fill="#0B344E"/><rect x="0" y="0" width="2048" height="2048" fill="rgba(0, 0, 0, 0)" /></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32px" height="32px" preserveAspectRatio="xMidYMid meet" viewBox="0 0 2048 2048" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);"><path d="M1024.022 99.36c-19.324-.017-38.646 7.15-52.98 21.55L120.937 971.023c-28.67 28.668-28.537 77.295.132 105.963l849.971 849.965c28.67 28.678 77.294 28.804 105.963 0l850.106-850.1c28.669-28.667 28.536-77.296-.135-105.964L1077.002 120.91c-14.334-14.334-33.657-21.534-52.98-21.55zm-.065 126.045l798.66 798.666l-798.66 798.657l-798.66-798.657l798.66-798.666zm.043 368.6c-237.232 0-430 192.78-430 430.008c0 237.228 192.768 430 430 430s430-192.772 430-430c0-237.229-192.768-430.008-430-430.008zm0 47.69c211.408 0 382.323 170.912 382.323 382.318c0 211.405-170.915 382.33-382.323 382.33c-211.407 0-382.322-170.925-382.322-382.33c0-211.406 170.915-382.319 382.322-382.319z" fill="#0B344E"/><rect x="0" y="0" width="2048" height="2048" fill="rgba(0, 0, 0, 0)" /></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32px" height="32px" preserveAspectRatio="xMidYMid meet" viewBox="0 0 2048 2048" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);"><path d="M1024.022 99.36c-19.324-.017-38.646 7.15-52.98 21.55L120.937 971.023c-28.67 28.668-28.537 77.295.132 105.963l849.971 849.965c28.67 28.678 77.294 28.804 105.963 0l850.106-850.1c28.669-28.667 28.536-77.296-.135-105.964L1077.002 120.91c-14.334-14.334-33.657-21.534-52.98-21.55zm-.065 126.045l798.66 798.666l-798.66 798.657l-798.66-798.657l798.66-798.666zm-21.803 329.82c0 .01-9.657 1.838-9.662 1.838c-.01 0-7.908 5.323-7.914 5.323c-.01.01-5.497 8.127-5.502 8.127c0 .01-1.86 9.675-1.863 9.675V977.04H580.188l-.067-.078c0 .01-9.618 2.129-9.623 2.129c-.01.01-7.907 5.322-7.912 5.322l.008-.098c-.01.01-5.497 8.127-5.502 8.127c-.01 0-1.861 9.676-1.865 9.676v43.47s1.848 9.783 1.914 9.85c0 .01 5.478 7.934 5.478 7.934c.01.01 7.957 5.322 7.96 5.322c.01.01 9.656 2.127 9.66 2.127h396.978v396.785l-.063-.058c0 .01 1.916 9.85 1.915 9.85c0 .01 5.476 7.933 5.476 7.933c.01.01 7.96 5.32 7.961 5.32c0 0 9.59 2.032 9.662 2.13l43.461-.01c.011 0 9.846-2.032 9.852-2.032c0 0 7.908-5.32 7.914-5.32c.01-.01 5.47-7.936 5.476-7.936c0 0 1.887-9.82 1.89-9.82V1070.86h396.88c.011.01 9.847-2.031 9.851-2.031c.01 0 7.91-5.322 7.914-5.322c.01-.01 5.471-7.934 5.477-7.934c0 0 1.886-9.82 1.89-9.82v-43.461c0-.01-1.877-9.58-1.874-9.676c-.01-.01-5.45-8.127-5.518-8.127c-.01-.01-7.958-5.32-7.96-5.32c0 .01-9.82-2.13-9.825-2.033h-396.838V580.294c0-.01-1.878-9.577-1.875-9.674c-.01-.01-5.45-8.129-5.45-8.129c-.01-.01-7.956-5.32-7.958-5.32c-.01 0-9.848-1.936-9.852-1.936l-43.469-.01z" fill="#0B344E"/><rect x="0" y="0" width="2048" height="2048" fill="rgba(0, 0, 0, 0)" /></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,13 @@
<svg width="8192" height="8192" xmlns="http://www.w3.org/2000/svg">
<defs>
<pattern id="smallGrid" width="8" height="8" patternUnits="userSpaceOnUse">
<path d="M 8 0 L 0 0 0 8" fill="none" stroke="#E4E9EF" stroke-width="0.5"/>
</pattern>
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
<rect width="80" height="80" fill="url(#smallGrid)"/>
<path d="M 80 0 L 0 0 0 80" fill="none" stroke="#E4E9EF" stroke-width="1"/>
</pattern>
</defs>
<rect width="8192" height="8192" fill="url(#grid)" />
</svg>

After

Width:  |  Height:  |  Size: 561 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512">
<!-- Font Awesome Free 5.15.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) -->
<path fill="#E54122" d="M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"></path>
</svg>

After

Width:  |  Height:  |  Size: 736 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" width="32px" height="32px" style="-ms-transform:rotate(360deg);-webkit-transform:rotate(360deg);transform:rotate(360deg)" preserveAspectRatio="xMidYMid meet" viewBox="0 0 2000 2000">
<path d="M429.65 210C214.934 210 40 384.196 40 598.5v803c0 214.304 174.934 388.5 389.65 388.5h1140.7c214.716 0 389.65-174.196 389.65-388.5v-803c0-214.304-174.934-388.5-389.65-388.5H429.65zm0 120h1140.7C1720.887 330 1840 448.826 1840 598.5v803c0 149.674-119.113 268.5-269.65 268.5H429.65C279.113 1670 160 1551.174 160 1401.5v-803C160 448.826 279.113 330 429.65 330z" fill="#0B344E"/>
<path d="M1057.07 410.836C805.11 407.3 563.447 583.065 491.134 824.983c-55.584 173.977-23.105 373.061 85.522 520.027l-269.086-52.09l-22.804 117.814l483.865 93.664l93.687-479.986l-117.779-22.988l-56.861 291.316c-138.733-165.6-136.73-427.773 4.367-591.379c137.417-171.716 399.203-221.007 590.733-114c183.232 94.568 284.888 318.597 234.896 518.746c-44.77 208.91-247.404 367.34-460.604 363.053c-55.485-3.935-83.374 76.196-37.436 107.561c40.104 24.986 90.846 7.364 134.808 4.475c248.181-37.748 457.52-249.452 489.52-498.91c36.994-238.025-91.384-488.935-304.803-600.241c-86.168-46.769-184.068-71.364-282.089-71.21z" fill="#0B344E"/>
<rect x="0" y="0" width="2000" height="2000" fill="rgba(0, 0, 0, 0)"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!-- Font Awesome Free 5.15.2 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --><path fill="#43AA8B" d="M424.4 214.7L72.4 6.6C43.8-10.3 0 6.1 0 47.9V464c0 37.5 40.7 60.1 72.4 41.3l352-208c31.4-18.5 31.5-64.1 0-82.6z"></path></svg>

After

Width:  |  Height:  |  Size: 392 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" width="32px" height="32px" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);" preserveAspectRatio="xMidYMid meet" viewBox="0 0 2000 2000"><g transform="translate(0 947.638)"><rect width="1800" height="1460" x="100" y="-677.638" rx="329.651" ry="328.5" fill="transparent" stroke="#0B344E" stroke-width="120" stroke-linecap="round"/><path d="M782.99 94.728h320.662c31.894 0 46.426-18.387 52.19-46.737c8.96-43.893-13.184-85.435-52.814-85.533c-97.378-.489-320.04-.196-320.04-.196v.223s340.368.268 379.564 0c39.195-.27 53.287-33.884 53.405-71.521c.12-37.54-18.907-70.744-53.287-70.793c-117.153-.098-379.68-.635-379.68-.635v-.194s199.412.465 285.185.465c35.051-.098 53.445-30.314 53.327-68.293c-.237-37.197-19.42-63.45-53.722-63.45c-137.086-.244-428.878 1.895-437.468-.146c-2.274-.54-4.22-4.971-2.723-6.794c14.054-17.122 80.562-79.478 95.325-98.59c18.512-23.803 20.486-56.943 6.394-78.401c-16.341-24.977-38.406-24.44-58.418-11.536c-57.669 37.344-260.516 178.067-305.237 208.763c-35.525 24.39-59.84 61.245-74.287 107.045c-14.604 46.435-13.105 100.51-12.947 147.875c.158 35.095.949 63.478 9.554 98.573c24.827 102.01 90.39 154.164 186.464 154.457c182.282.684 364.643.929 546.964 0c29.801-.195 46.221-19.702 46.655-55.482c.474-36.854-16.696-58.591-46.695-58.787c-70.181-.489-238.37 0-238.37 0z" fill="none" stroke="#0B344E" stroke-width="43.663900000000005" stroke-linecap="round"/></g><rect x="0" y="0" width="2000" height="2000" fill="rgba(0, 0, 0, 0)" /></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!-- Font Awesome Pro 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) --><path fill="#E54122" d="M400 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48z"/></svg>

After

Width:  |  Height:  |  Size: 337 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32px" height="32px" preserveAspectRatio="xMidYMid meet" viewBox="0 0 2048 2048" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);"><path d="M96 402v1280h1856V402zm1756 97.7v1091.542H480V499.702l1372-.003zM192 499.7h192v1091.541H192z" fill="#0B344E"/><rect x="0" y="0" width="2048" height="2048" fill="rgba(0, 0, 0, 0)" /></svg>

After

Width:  |  Height:  |  Size: 467 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" focusable="false" width="32px" height="32px" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);" preserveAspectRatio="xMidYMid meet" viewBox="0 0 2048 2048"><path d="M1005.303 98.988c-447.59.377-859.063 373.342-901.291 819.483c-45.314 379.987 168.535 777.154 517.567 938.558c324.137 158.423 740.176 108.288 1009.01-135.647c273.526-236.69 393.041-640.13 272.893-984.32c-114.258-351.15-451.46-619.981-822.842-636.252c-25.079-1.64-50.213-1.947-75.337-1.822zm18.334 289.733c329.628-9.588 631.825 281.137 635.693 610.898c14.255 301.673-212.385 597.907-513.018 648.484c-290.913 58.275-613.563-109.854-715.39-392.282c-109.622-279.29 2.217-632.478 269.045-779.065c97.56-57.284 210.508-88.176 323.67-88.035zM1024 554.17c-304.682-11.568-549.567 321.3-448.846 608.674c79.046 294.298 469.266 430.4 714.132 248.923c257.896-162.49 272.52-575.472 26.765-755.798c-82.202-65.575-186.913-102.06-292.051-101.8z" fill="#0B344E"/><rect x="0" y="0" width="2048" height="2048" fill="rgba(0, 0, 0, 0)" /></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="32px" height="32px" preserveAspectRatio="xMidYMid meet" viewBox="0 0 2048 2048" style="-ms-transform: rotate(360deg); -webkit-transform: rotate(360deg); transform: rotate(360deg);"><path d="M1899 1023.999c0-483.252-391.75-874.995-875-874.995S149 540.747 149 1024c0 483.251 391.75 875.004 875 875.004s875-391.753 875-875.004z" fill="none" stroke="#0B344E" stroke-width="100.04248826"/><rect x="0" y="0" width="2048" height="2048" fill="rgba(0, 0, 0, 0)" /></svg>

After

Width:  |  Height:  |  Size: 551 B

View File

@@ -0,0 +1,44 @@
<!DOCTYPE html>
<html data-color-mode="light" lang="en">
<head>
<!--
base location is root by default for all webapps
href value should be modified when app is placed under subdir
-->
<base href="/" />
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" id="favicon" href="">
<link rel="stylesheet" type="text/css" href="<%= BASE_URL %>custom.css">
<link rel="preload" href="<%= BASE_URL %>config.js" as="script" />
<title><%= FLAVOUR %></title>
<style type="text/css">
html {
height: 100vh;
}
body {
height: 100%;
}
</style>
</head>
<body id="workflow">
<noscript>
<strong>
JavaScript is disabled in your browser!
</strong>
<p>
We're sorry but Corteza doesn't work properly without JavaScript enabled.
Please enable it to continue.
</p>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
<script src="<%= BASE_URL %>config.js" type="text/javascript"></script>
<!-- contains cdns that will be injected at the head of the page -->
<script src="<%= BASE_URL %>code-snippets.js" type="text/javascript"></script>
</body>
</html>

View File

@@ -0,0 +1,148 @@
import Vue from 'vue'
import './config-check'
import './console-splash'
import './filters'
import './plugins'
import './mixins'
import './components'
import store from './store'
import router from './router'
import { i18n, websocket } from '@cortezaproject/corteza-vue'
export default (options = {}) => {
options = {
el: '#app',
name: 'workflow',
template: '<router-view v-if="loaded && i18nLoaded" />',
data: () => ({
loaded: false,
i18nLoaded: false,
}),
async created () {
this.$i18n.i18next.on('initialized', () => {
this.i18nLoaded = true
})
this.websocket()
return this.$auth.vue(this).handle().then(async ({ user }) => {
// switch the favicon based on the settings
await this.$Settings.init({ api: this.$SystemAPI }).then(() => {
const icon = this.$Settings.attachment('ui.iconLogo') || '/icon.svg'
const favicon = document.getElementById('favicon')
if (favicon) {
favicon.href = icon
}
})
if (user.meta.preferredLanguage) {
// After user is authenticated, get his preferred language
// and instruct i18next to change it
this.$i18n.i18next.changeLanguage(user.meta.preferredLanguage)
}
// switch the webapp theme based on user preference
// disabled for now as it's not working properly
if (user.meta.theme) {
document.getElementsByTagName('html')[0].setAttribute('data-color-mode', user.meta.theme)
}
// Load effective permissions
this.$store.dispatch('rbac/load')
// Initialize notifications
this.$store.dispatch('notifications/fetchNotifications')
this.loaded = true
}).catch((err) => {
if (err instanceof Error && err.message === 'Unauthenticated') {
// user not logged-in,
// start with authentication flow
this.$auth.startAuthenticationFlow()
return
}
throw err
})
},
methods: {
/**
* Registers event listener for websocket messages and
* routes them depending on their type
*/
websocket () {
// cross-link auth & websocket so that ws can use the right access token
websocket.init(this)
// register event listener for messages
this.$on('websocket-message', ({ data }) => {
const msg = JSON.parse(data)
switch (msg['@type']) {
case 'notification':
this.$store.dispatch('notifications/addNotification', msg['@value'])
break
case 'notification.read':
this.$store.dispatch('notifications/updateReadNotification', msg['@value'])
break
case 'notification.unread':
this.$store.dispatch('notifications/updateUnreadNotification', msg['@value'])
break
case 'notification.read.all':
this.$store.dispatch('notifications/updateAllReadNotifications', msg['@value'])
break
case 'notification.unread.all':
this.$store.dispatch('notifications/updateAllUnreadNotifications', msg['@value'])
break
case 'notification.delete':
this.$store.dispatch('notifications/removeNotification', msg['@value'])
break
case 'error':
this.toastDanger('Websocket message with error', msg['@value'])
}
})
},
},
router,
store,
i18n: i18n(Vue,
{ app: 'corteza-webapp-workflow' },
'configurator',
'editor',
'help',
'general',
'navigation',
'notification',
'notifications',
'permissions',
'configurator',
'steps',
),
// Any additional options we want to merge
...options,
}
const app = new Vue(options)
// Simple HMR acceptance
if (module.hot) {
module.hot.accept()
}
return app
}

View File

@@ -0,0 +1,5 @@
import { default as WorkflowEditor } from './WorkflowEditor.c3'
export default {
WorkflowEditor,
}

View File

@@ -0,0 +1,37 @@
<template>
<c-rich-text-input
v-model="label"
:labels="{
urlPlaceholder: $t('steps:content.configurator.urlPlaceholder'),
ok: $t('steps:content.configurator.ok'),
}"
class="m-3"
@input="$emit('update-value', $event)"
/>
</template>
<script>
import base from './base'
import { components } from '@cortezaproject/corteza-vue'
const { CRichTextInput } = components
export default {
components: {
CRichTextInput,
},
extends: base,
computed: {
label: {
get () {
return this.item.node.value
},
set (label) {
this.item.node.value = label
},
},
},
}
</script>

View File

@@ -0,0 +1,85 @@
<template>
<div>
<b-card
class="flex-grow-1 border-bottom border-light rounded-0"
>
<b-card-header
header-tag="header"
class="p-0 mb-3"
>
<h5
class="mb-0"
>
{{ $t('configurator:configuration') }}
</h5>
</b-card-header>
<b-card-body
class="p-0"
>
<b-form-group
:label="$t('configurator:delay.duration.label')"
:description="$t('configurator:delay.duration.description')"
label-class="text-primary"
class="mb-0"
>
<expression-editor
v-model="item.config.arguments[0].expr"
font-size="18px"
show-line-numbers
:show-popout="false"
@input="valueChanged"
/>
</b-form-group>
</b-card-body>
</b-card>
</div>
</template>
<script>
import base from './base'
import ExpressionEditor from '../ExpressionEditor'
export default {
components: {
ExpressionEditor,
},
extends: base,
watch: {
'item.config.stepID': {
immediate: true,
handler () {
let args = [{
target: 'offset',
type: 'Duration',
expr: '',
}]
if (this.item.config.arguments && this.item.config.arguments.length) {
args = this.item.config.arguments.map(({ target, type, value, expr }) => {
return {
target,
type,
expr: expr || (value ? `"${value}"` : ''),
}
})
}
this.$set(this.item.config, 'arguments', args)
},
},
},
methods: {
valueChanged (value) {
this.$emit('update-default-value', {
value: `Delay workflow execution for ${value}`,
force: !this.item.node.value,
})
this.$root.$emit('change-detected')
},
},
}
</script>

View File

@@ -0,0 +1,13 @@
<template>
<div>
{{ $t('configurator:edge') }}
</div>
</template>
<script>
import base from './base'
export default {
extends: base,
}
</script>

View File

@@ -0,0 +1,124 @@
<template>
<b-card
class="flex-grow-1 border-bottom border-light rounded-0"
>
<b-card-header
header-tag="header"
class="p-0 mb-3"
>
<h5
class="mb-0"
>
{{ $t('configurator:configuration') }}
</h5>
</b-card-header>
<b-card-body
class="p-0"
>
<b-form-group
:label="$t('general:error-expression')"
label-class="text-primary"
class="mb-0"
>
<expression-editor
v-model="item.config.arguments[0].expr"
font-size="18px"
show-line-numbers
@open="openInEditor"
@input="valueChanged"
/>
</b-form-group>
</b-card-body>
<b-modal
id="expression-editor"
:visible="!!expressionEditor.currentExpression"
:title="$t('editor:editor')"
size="xl"
scrollable
:ok-title="$t('general:save')"
:cancel-title="$t('general:cancel')"
cancel-variant="light"
body-class="p-0"
no-fade
@ok="saveExpression"
@hidden="resetExpression"
>
<expression-editor
v-model="expressionEditor.currentExpression"
min-height="80vh"
font-size="18px"
show-line-numbers
:border="false"
:show-popout="false"
/>
</b-modal>
</b-card>
</template>
<script>
import base from './base'
import ExpressionEditor from '../ExpressionEditor.vue'
export default {
components: {
ExpressionEditor,
},
extends: base,
data () {
return {
expressionEditor: {
currentExpression: undefined,
},
}
},
created () {
let args = [{
target: 'message',
type: 'String',
expr: '',
}]
if (this.item.config.arguments && this.item.config.arguments.length) {
args = this.item.config.arguments.map(({ target, type, value, expr }) => {
return {
target,
type,
expr: expr || (value ? `"${value}"` : ''),
}
})
}
this.$set(this.item.config, 'arguments', args)
},
methods: {
valueChanged (value) {
this.$emit('update-default-value', {
value: `Stop workflow with error: ${value}`,
force: !this.item.node.value,
})
this.$root.$emit('change-detected')
},
openInEditor () {
this.expressionEditor.currentExpression = this.item.config.arguments[0].expr
},
saveExpression () {
const { currentExpression } = this.expressionEditor
this.$set(this.item.config.arguments[0], 'expr', currentExpression)
this.$root.$emit('change-detected')
this.resetExpression()
},
resetExpression () {
this.expressionEditor.currentExpression = undefined
},
},
}
</script>

View File

@@ -0,0 +1,49 @@
<script>
import Function from './Function'
export default {
extends: Function,
data () {
return {
showFunctionList: false,
functionRef: 'error-handler',
}
},
methods: {
async getFunctionTypes () {
this.functions = [
{
ref: 'error-handler',
kind: 'function',
meta: {
short: 'Handle error',
},
parameters: [],
results: [
{
name: 'error',
types: [
'Any',
],
},
{
name: 'errorMessage',
types: [
'String',
],
},
{
name: 'errorStepID',
types: [
'Integer',
],
},
],
},
]
},
},
}
</script>

View File

@@ -0,0 +1,75 @@
<script>
import Function from './Function'
export default {
extends: Function,
data () {
return {
showFunctionList: false,
expressionResults: true,
functionRef: 'exec-workflow',
workflowOptions: [],
}
},
mounted () {
if (!this.workflowOptions.length) {
this.searchWorkflows('', () => {})
}
},
methods: {
async getFunctionTypes () {
this.functions = [
{
ref: 'exec-workflow',
kind: 'function',
meta: {
short: 'Execute a workflow',
},
parameters: [
{
name: 'workflow',
types: [
'ID',
'Handle',
],
required: true,
},
{
name: 'scope',
types: [
'Vars',
],
required: false,
},
],
results: [],
},
]
},
searchWorkflows (query = '', loading) {
loading(true)
this.$AutomationAPI.workflowList({ query, subWorkflow: 2 })
.then(({ set }) => {
this.workflowOptions = set.map(m => Object.freeze(m))
})
.finally(() => {
loading(false)
})
},
getWorkflowLabel ({ workflowID, handle, meta = {} }) {
return meta.name || handle || workflowID
},
getWorkflowKey ({ workflowID, handle }) {
return handle || workflowID
},
},
}
</script>

View File

@@ -0,0 +1,216 @@
<template>
<div>
<b-card
class="flex-grow-1 rounded-0"
body-class="p-0"
>
<b-card-header
header-tag="header"
class="d-flex align-items-center py-4"
>
<h5
class="d-flex align-items-center mb-0"
>
{{ $t('steps:expressions.label') }}
<a
:href="documentationURL"
target="_blank"
class="d-flex align-items-center h6 mb-0 ml-1"
>
<font-awesome-icon
:icon="['far', 'question-circle']"
/>
</a>
</h5>
<portal to="sidebar-footer">
<b-button
variant="primary"
class="align-top border-0 ml-auto"
@click="addArgument()"
>
{{ $t('steps:expressions.configurator.add-expression') }}
</b-button>
</portal>
</b-card-header>
<b-card-body
v-if="hasArguments"
class="p-0"
>
<expression-table
value-field="expr"
:items="item.config.arguments"
:fields="argumentFields"
:types="fieldTypes"
@remove="removeArgument"
@open-editor="openInEditor"
/>
</b-card-body>
</b-card>
<b-modal
id="expression-editor"
:visible="!!expressionEditor.currentExpression"
:title="$t('editor:editor')"
size="xl"
scrollable
:ok-title="$t('general:save')"
:cancel-title="$t('general:cancel')"
cancel-variant="light"
body-class="p-0"
no-fade
@ok="saveExpression"
@hidden="resetExpression"
>
<expression-editor
v-model="currentExpressionValue"
min-height="80vh"
font-size="18px"
show-line-numbers
:border="false"
:show-popout="false"
/>
</b-modal>
</div>
</template>
<script>
import base from './base'
import ExpressionTable from '../ExpressionTable.vue'
import ExpressionEditor from '../ExpressionEditor.vue'
import { getDocumentationURL } from '../../lib/version'
export default {
components: {
ExpressionEditor,
ExpressionTable,
},
extends: base,
data () {
return {
fieldTypes: [],
expressionEditor: {
currentIndex: undefined,
currentExpression: undefined,
},
}
},
computed: {
currentExpressionValue: {
get () {
return this.expressionEditor.currentExpression ? this.expressionEditor.currentExpression.expr : ''
},
set (value) {
if (this.expressionEditor.currentExpression) {
this.expressionEditor.currentExpression.expr = value
}
},
},
argumentFields () {
return [
{
key: 'target',
label: this.$t('steps:expressions.configurator.target'),
thClass: 'pl-4 ml-1',
formatter: (item) => {
return `${item.target}(${item.type})`
},
},
{
key: 'expr',
label: this.$t('steps:expressions.configurator.expression'),
thClass: 'pl-1 mr-3',
},
]
},
hasArguments () {
const { config } = this.item || {}
return (config && (config.arguments || []).length) || []
},
documentationURL () {
return getDocumentationURL('integrator-guide/expr/index.html')
},
},
watch: {
'item.config.stepID': {
immediate: true,
handler () {
this.$set(this.item.config, 'arguments', this.item.config.arguments || [])
},
},
},
created () {
this.getTypes()
},
methods: {
addArgument () {
this.item.config.arguments.push({
target: '',
expr: '',
type: 'Any',
_showDetails: true,
})
this.$root.$emit('change-detected')
},
removeArgument (index) {
this.item.config.arguments.splice(index, 1)
this.$root.$emit('change-detected')
},
openInEditor (index = -1) {
this.expressionEditor = {
currentIndex: index >= -1 ? index : undefined,
currentExpression: index >= 0 ? { ...this.item.config.arguments[index] } : undefined,
}
},
saveExpression () {
if (this.expressionEditor.currentIndex >= 0) {
const args = [...this.item.config.arguments]
args[this.expressionEditor.currentIndex] = this.expressionEditor.currentExpression
this.$set(this.item.config, 'arguments', args)
this.$root.$emit('change-detected')
}
this.resetExpression()
},
resetExpression () {
this.expressionEditor = {
currentIndex: undefined,
currentExpression: undefined,
}
},
async getTypes () {
return this.$AutomationAPI.typeList()
.then(({ set }) => {
this.fieldTypes = set
})
.catch(this.toastErrorHandler(this.$t('notification:fetch-types-failed')))
},
getTypeDescription (type) {
// This will be moved to backend field type information
const typeDescriptions = {
ID: 'Make sure to provide the ID in double quotes if you\'re using a literal value. Example "123"',
}
return typeDescriptions[type]
},
},
}
</script>

View File

@@ -0,0 +1,709 @@
<template>
<div
v-if="!processing"
>
<b-card
v-if="showFunctionList"
class="flex-grow-1 border-bottom border-light rounded-0"
>
<b-card-header
header-tag="header"
class="p-0 mb-3"
>
<h5
class="mb-0"
>
{{ $t('configurator:configuration') }}
</h5>
</b-card-header>
<b-card-body
v-if="functionTypes.length"
class="p-0"
>
<b-form-group
:label="$t('steps:function.configurator.type*')"
label-class="text-primary"
class="mb-0"
>
<c-input-select
v-model="functionRef"
:options="functionTypes"
:get-option-key="getOptionTypeKey"
label="text"
:selectable="f => !f.disabled"
:reduce="f => f.value"
:filter="functionFilter"
:placeholder="$t('steps:function.configurator.select-function')"
@input="functionChanged"
/>
</b-form-group>
<p
v-if="functionDescription"
class="mt-3 mb-0"
>
{{ functionDescription }}
</p>
</b-card-body>
</b-card>
<b-card
v-if="args.length"
class="flex-grow-1 border-bottom border-light rounded-0"
body-class="p-0"
>
<b-card-header
header-tag="header"
class="d-flex align-items-center"
>
<h5
class="mb-0"
>
{{ $t('steps:function.configurator.arguments') }}
</h5>
</b-card-header>
<b-card-body
class="p-0"
>
<b-table
id="arguments"
fixed
borderless
hover
head-variant="light"
details-td-class="bg-white"
:items="args"
:fields="argumentFields"
:tbody-tr-class="rowClass"
class="mb-4"
@row-clicked="item=>$set(item, '_showDetails', !item._showDetails)"
>
<template #cell(target)="{ item: a }">
<strong>{{ a.label || a.target }}<span
v-if="a.required"
class="text-danger"
>*</span></strong>
<samp v-if="!isWhileIterator"> ({{ a.type }})</samp>
</template>
<template #cell(type)="{ item: a }">
<var>{{ a.type }}</var>
</template>
<template #cell(value)="{ item: a }">
<samp>{{ a[a.valueType] }}</samp>
</template>
<template #cell(expression)="{ item: a }">
<span
v-if="a.valueType === 'expr'"
v-b-tooltip
:title="$t('steps:function.configurator.expression')"
class="circle-badge"
>
e
</span>
</template>
<template #row-details="{ item: a, index }">
<div class="arrow-up" />
<b-card
class="bg-light"
body-class="px-4 pb-3"
>
<b-form-group
v-if="(paramTypes[functionRef][a.target] || []).length > 1"
label-class="text-primary"
>
<c-input-select
v-model="a.type"
:options="(paramTypes[functionRef][a.target] || [])"
:get-option-key="getOptionParamKey"
:filter="argTypeFilter"
:clearable="false"
@input="$root.$emit('change-detected')"
/>
</b-form-group>
<b-form-group
label-class="d-flex align-items-center text-primary"
class="mb-0"
>
<div
v-if="a.valueType === 'value'"
>
<c-input-select
v-if="a.target === 'workflow'"
v-model="a.value"
:options="workflowOptions"
:get-option-label="getWorkflowLabel"
:get-option-key="getWorkflowKey"
:reduce="wf => a.type === 'ID' ? wf.workflowID : wf.handle"
:placeholder="$t('steps:function.configurator.search-workflow')"
:filterable="false"
@input="$root.$emit('change-detected')"
@search="searchWorkflows"
/>
<c-input-select
v-else-if="a.input.type === 'select'"
v-model="a.value"
:options="a.input.properties.options"
:get-option-key="getOptionTypeKey"
label="text"
:filter="varFilter"
:reduce="a => a.value"
:placeholder="$t('steps:function.configurator.option-select')"
:clearable="false"
@input="$root.$emit('change-detected')"
/>
<c-rich-text-input
v-else-if="a.input.type === 'richtext'"
v-model="a.value"
:labels="{
urlPlaceholder: $t('steps:function.configurator.urlPlaceholder'),
ok: $t('steps:function.configurator.ok'),
}"
min-body-height="6rem"
@input="$root.$emit('change-detected')"
/>
<b-form-checkbox
v-else-if="a.type === 'Boolean'"
v-model="a.value"
value="true"
size="md"
unchecked-value="false"
@input="$root.$emit('change-detected')"
>
{{ a.target }}
</b-form-checkbox>
<expression-editor
v-else
v-model="a.value"
:auto-complete="false"
@open="openInEditor(index)"
@input="$root.$emit('change-detected')"
/>
</div>
<expression-editor
v-else-if="a.valueType === 'expr'"
v-model="a.expr"
show-line-numbers
@open="openInEditor(index)"
@input="$root.$emit('change-detected')"
/>
</b-form-group>
<b-form-checkbox
v-if="!isWhileIterator"
v-model="a.valueType"
value="expr"
unchecked-value="value"
switch
size="sm"
class="float-right mr-2 mt-2"
@change="valueTypeChanged($event, index)"
>
<div
class="d-flex"
>
{{ $t('steps:function.configurator.expression') }}
<a
:href="documentationURL"
target="_blank"
class="d-flex align-items-center h6 mb-0 ml-1"
>
<font-awesome-icon
:icon="['far', 'question-circle']"
class="ml-1"
/>
</a>
</div>
</b-form-checkbox>
</b-card>
</template>
</b-table>
</b-card-body>
</b-card>
<b-card
v-if="expressionResults || results.length"
class="flex-grow-1 border-bottom border-light rounded-0"
body-class="p-0"
>
<b-card-header
header-tag="header"
class="d-flex align-items-center"
>
<h5
class="mb-0"
>
{{ $t('steps:function.configurator.results') }}
</h5>
</b-card-header>
<b-card-body
v-if="results.length"
class="p-0"
>
<expression-table
v-if="expressionResults"
value-field="expr"
:items="results"
:fields="resultFields"
:types="fieldTypes"
@remove="removeResult"
@open-editor="openInEditor"
/>
<b-table
v-else
id="results"
fixed
borderless
hover
head-variant="light"
details-td-class="bg-white"
class="mb-4"
:items="results"
:fields="resultFields"
:tbody-tr-class="rowClass"
@row-clicked="item=>$set(item, '_showDetails', !item._showDetails)"
>
<template #cell(type)="{ item: a }">
<var>{{ a.type }}</var>
</template>
<template #cell(value)="{ item: a }">
<samp>{{ a.expr }}</samp>
</template>
<template #row-details="{ item: a }">
<div class="arrow-up" />
<b-card
class="bg-light"
body-class="px-4 pb-3"
>
<b-form-group
class="mb-0"
>
<b-form-input
v-model="a.target"
:placeholder="$t('configurator:target')"
@input="$root.$emit('change-detected')"
/>
</b-form-group>
</b-card>
</template>
</b-table>
</b-card-body>
</b-card>
<portal to="sidebar-footer">
<b-button
v-if="expressionResults"
variant="primary"
class="align-top border-0 ml-auto"
@click="addResult()"
>
{{ $t('steps:function.configurator.add-result') }}
</b-button>
</portal>
<b-modal
id="expression-editor"
:visible="!!expressionEditor.currentExpression"
:title="$t('editor:editor')"
size="xl"
scrollable
:ok-title="$t('general:save')"
:cancel-title="$t('general:cancel')"
cancel-variant="light"
body-class="p-0"
no-fade
@ok="saveExpression"
@hidden="resetExpression"
>
<expression-editor
v-model="currentExpressionValue"
:lang="expressionEditor.lang"
min-height="80vh"
font-size="18px"
show-line-numbers
:border="false"
:show-popout="false"
/>
</b-modal>
</div>
</template>
<script>
import base from './base'
import ExpressionTable from '../ExpressionTable.vue'
import ExpressionEditor from '../ExpressionEditor.vue'
import { objectSearchMaker, stringSearchMaker } from '../../lib/filter'
import { getDocumentationURL } from '../../lib/version'
import { components } from '@cortezaproject/corteza-vue'
const { CRichTextInput } = components
export default {
components: {
CRichTextInput,
ExpressionEditor,
ExpressionTable,
},
extends: base,
data () {
return {
processing: true,
showFunctionList: true,
expressionResults: false,
functionRef: undefined,
functions: [],
args: [],
results: [],
fieldTypes: [],
paramTypes: {},
resultTypes: {},
expressionEditor: {
currentIndex: undefined,
currentExpression: undefined,
lang: 'javascript',
},
}
},
computed: {
// Used for expression editor modal
currentExpressionValue: {
get () {
const { currentExpression } = this.expressionEditor
return currentExpression ? currentExpression[currentExpression.valueType] : ''
},
set (value) {
const { currentExpression } = this.expressionEditor
if (currentExpression) {
currentExpression[currentExpression.valueType] = value
}
},
},
functionTypes () {
return this.functions.map(({ ref, meta, disabled = false }) => ({ value: ref, text: meta.short, disabled }))
},
argumentFields () {
return [
{
key: 'target',
label: this.$t('steps:function.configurator.name'),
thClass: 'pl-3 py-2',
tdClass: 'text-truncate pointer',
},
{
key: 'value',
thClass: 'pr-3 py-2',
tdClass: 'text-truncate pointer',
},
{
key: 'expression',
label: '',
thClass: 'text-center',
tdClass: 'text-center pointer',
thStyle: {
width: '3rem',
},
},
]
},
resultFields () {
return [
{
key: 'target',
label: this.$t('steps:function.configurator.target'),
thClass: 'pl-3',
tdClass: 'text-truncate pointer',
},
{
key: 'type',
label: this.$t('steps:function.configurator.type'),
tdClass: 'text-truncate pointer',
},
{
key: 'expr',
label: this.$t('steps:function.configurator.result'),
thClass: 'mr-3',
tdClass: 'position-relative pointer',
},
]
},
valueTypes () {
return [
{ text: this.$t('steps:function.configurator.expression'), value: 'expr' },
{ text: this.$t('steps:function.configurator.constant'), value: 'value' },
]
},
defaultOptions () {
return [{ value: null, text: this.$t('steps:function.configurator.option-select'), disabled: true }]
},
functionDescription () {
return (this.functions.find(({ ref }) => ref === this.functionRef) || { meta: {} }).meta.description
},
isWhileIterator () {
if (this.item.config) {
return this.item.config.kind === 'iterator' && this.functionRef === 'loopDo'
}
return false
},
documentationURL () {
return getDocumentationURL('integrator-guide/expr/index.html')
},
},
watch: {
'item.config.stepID': {
immediate: true,
async handler () {
this.processing = true
this.$set(this.item.config, 'arguments', this.item.config.arguments || [])
this.$set(this.item.config, 'results', this.item.config.results || [])
await this.getFunctionTypes()
await this.getTypes()
this.functionRef = this.item.config.ref || this.functionRef
this.setParams(this.functionRef, true)
this.processing = false
},
},
args: {
deep: true,
handler (args) {
this.item.config.arguments = args.filter(({ value, source, expr }) => value || source || expr)
.map(arg => {
const argMapped = {
target: arg.target,
type: arg.type,
}
argMapped[arg.valueType] = arg[arg.valueType]
return argMapped
})
},
},
results: {
deep: true,
handler (res) {
this.item.config.results = res.filter(({ target }) => target).map(({ target, expr, type }) => ({ target, type, expr }))
},
},
},
methods: {
functionFilter: objectSearchMaker('text'),
argTypeFilter: stringSearchMaker(),
varFilter: objectSearchMaker('text'),
setParams (fName, immediate = false) {
this.args = []
this.results = []
if (!immediate) {
this.$root.$emit('change-detected')
}
if (fName) {
const func = this.functions.find(({ ref }) => ref === fName)
// Set parameters
if (!this.paramTypes[func.ref] && func.parameters) {
this.paramTypes[func.ref] = {}
func.parameters.forEach(({ name, types }) => {
this.paramTypes[func.ref][name] = types || []
})
}
this.args = func.parameters?.map(param => {
const arg = this.item.config.arguments.find(({ target }) => target === param.name) || {}
const { input = {} } = (param.meta || {}).visual || {}
return {
name: param.name,
target: param.name,
label: (param.meta || {}).label,
type: arg.type || this.paramTypes[func.ref][param.name][0],
valueType: arg.expr !== undefined ? 'expr' : 'value',
value: arg.value || input.default || null,
expr: arg.expr || arg.source || null,
required: param.required || false,
input,
}
}) || []
// Set results
if (!this.expressionResults) {
if (!this.resultTypes[func.ref] && func.results) {
this.resultTypes[func.ref] = {}
func.results.forEach(({ name, types }) => {
this.resultTypes[func.ref][name] = types || []
})
}
this.results = func.results?.map(result => {
const res = this.item.config.results.find(({ expr }) => expr === result.name) || {}
return {
name: result.name,
valueType: 'expr',
target: res.target || undefined,
type: this.resultTypes[func.ref][result.name][0],
expr: res.expr || result.name,
}
}) || []
} else {
this.results = this.item.config.results.map(({ target, type, expr }) => {
return {
valueType: 'expr',
target,
type,
expr,
}
}) || []
}
}
},
openInEditor (index = -1) {
this.expressionEditor = {
currentIndex: index >= -1 ? index : undefined,
currentExpression: index >= 0 ? { ...this.args[index] } : undefined,
}
this.expressionEditor.lang = this.expressionEditor.currentExpression.valueType === 'expr' ? 'javascript' : 'text'
},
saveExpression () {
const { currentIndex = -1, currentExpression } = this.expressionEditor
if (currentIndex >= 0) {
this.args[currentIndex] = currentExpression
this.$set(this.args, currentIndex, currentExpression)
this.$root.$emit('change-detected')
}
this.resetExpression()
},
resetExpression () {
this.expressionEditor = {
currentIndex: undefined,
currentExpression: undefined,
lang: 'javascript',
}
},
async getFunctionTypes () {
return this.$AutomationAPI.functionList()
.then(({ set }) => {
this.functions = set.filter(({ kind = '' }) => kind !== 'iterator').sort((a, b) => a.meta.short.localeCompare(b.meta.short))
})
.catch(this.toastErrorHandler(this.$t('notification:failed-fetch-functions')))
},
async getTypes () {
return this.$AutomationAPI.typeList()
.then(({ set }) => {
this.fieldTypes = set
})
.catch(this.toastErrorHandler(this.$t('notification:fetch-types-failed')))
},
functionChanged (functionRef) {
this.item.config.ref = functionRef
this.setParams(functionRef)
this.$emit('update-default-value', {
value: (this.functionTypes.find(({ value }) => value === functionRef) || { meta: {} }).text,
force: !this.item.node.value,
})
},
valueTypeChanged (valueType, index) {
const oldType = valueType === 'value' ? 'expr' : 'value'
this.args[index][valueType] = this.args[index][oldType]
if (!this.args[index].value && this.args[index].type === 'Boolean' && valueType === 'value') {
this.args[index].value = 'false'
}
this.$root.$emit('change-detected')
},
rowClass (item, type) {
return item._showDetails && type === 'row' ? 'border-thick' : 'border-thick-transparent'
},
addResult () {
this.results.push({
target: '',
expr: '',
type: 'Any',
_showDetails: true,
})
this.$root.$emit('change-detected')
},
removeResult (index) {
this.results.splice(index, 1)
this.$root.$emit('change-detected')
},
getTypeDescription (type) {
// This will be moved to backend field type information
const typeDescriptions = {
ID: 'Make sure to provide the ID in double quotes if you\'re using a literal value. Example "123"',
}
return typeDescriptions[type]
},
getOptionTypeKey ({ value }) {
return value
},
getOptionEWorkflowLabelKey ({ workflowID }) {
return workflowID
},
getOptionParamKey (type) {
return type
},
},
}
</script>

View File

@@ -0,0 +1,93 @@
<template>
<div>
<b-card
v-if="['incl', 'excl'].includes(gatewayKind)"
class="flex-grow-1 border-bottom border-light rounded-0"
>
<b-card-header
header-tag="header"
class="p-0 mb-3"
>
<h5
class="mb-0"
>
{{ $t('configurator:configuration') }}
</h5>
</b-card-header>
<b-card-body
class="p-0"
>
<var
v-if="outEdges < 2"
>
{{ $t('steps:gateway.configurator.two-paths') }}
</var>
<div
v-else
>
<b-form-group
v-for="edge in gatewayEdges"
:key="edge.id"
:label="edge.value"
label-class="text-primary"
>
<expression-editor
v-model="edge.expr"
show-line-numbers
:show-popout="false"
@input="updateEdge(edge.id, $event)"
/>
</b-form-group>
</div>
</b-card-body>
</b-card>
</div>
</template>
<script>
import base from './base'
import ExpressionEditor from '../ExpressionEditor.vue'
export default {
components: {
ExpressionEditor,
},
extends: base,
computed: {
gatewayKind () {
return this.item.config.ref
},
gatewayEdges () {
const edges = []
if (['incl', 'excl'].includes(this.gatewayKind)) {
if (this.outEdges && this.item.node.edges) {
this.item.node.edges.forEach(({ id, source, target, value = '' }) => {
if (source.id === this.item.node.id) {
edges.push({
id,
source: source.id,
target: target.id,
value,
expr: this.edges[id].config.expr || '',
})
}
})
}
}
return edges
},
},
methods: {
updateEdge (id, expr) {
this.edges[id].config.expr = expr
this.$root.$emit('change-detected')
},
},
}
</script>

View File

@@ -0,0 +1,17 @@
<script>
import Function from './Function'
export default {
extends: Function,
methods: {
async getFunctionTypes () {
return this.$AutomationAPI.functionList()
.then(({ set }) => {
this.functions = set.filter(({ kind = '' }) => kind === 'iterator').sort((a, b) => a.meta.short.localeCompare(b.meta.short))
})
.catch(this.toastErrorHandler(this.$t('notification:failed-fetch-functions')))
},
},
}
</script>

View File

@@ -0,0 +1,14 @@
<script>
import Function from './Function'
import { components } from '@cortezaproject/corteza-vue'
export default {
extends: Function,
methods: {
async getFunctionTypes () {
this.functions = components.promptDefinitions || []
},
},
}
</script>

View File

@@ -0,0 +1,534 @@
<template>
<div>
<b-card
class="flex-grow-1 border-bottom border-light rounded-0"
>
<b-card-header
header-tag="header"
class="p-0 mb-3"
>
<h5
class="mb-0"
>
{{ $t('configurator:configuration') }}
</h5>
</b-card-header>
<b-card-body
class="p-0"
>
<b-form-group
:label="$t('steps:trigger.configurator.resource*')"
label-class="text-primary"
>
<c-input-select
v-model="item.triggers.resourceType"
:options="resourceTypeOptions"
:get-option-key="getOptionTypeKey"
label="text"
:reduce="r => r.value"
:filter="resTypeFilter"
:placeholder="$t('steps:trigger.configurator.select-resource-type')"
:clearable="false"
@input="resourceChanged"
/>
</b-form-group>
<b-form-group
v-if="item.triggers.resourceType"
:label="$t('steps:trigger.configurator.event*')"
label-class="text-primary"
>
<c-input-select
v-model="item.triggers.eventType"
:options="eventTypeOptions"
:get-option-key="getOptionEventTypeKey"
:get-option-label="getEventTypeLabel"
:reduce="e => e.eventType"
:filter="evtTypeFilter"
:placeholder="$t('steps:trigger.configurator.select-event-type')"
:clearable="false"
@input="eventChanged"
/>
</b-form-group>
<b-form-group
class="mb-0"
>
<b-form-checkbox
v-model="item.triggers.enabled"
:disabled="isSubworkflow && !item.triggers.enabled"
class="text-primary"
@change="enabledChanged()"
>
{{ $t('general:enabled') }}
</b-form-checkbox>
</b-form-group>
</b-card-body>
</b-card>
<b-card
v-if="showConstraints"
class="flex-grow-1 border-bottom border-light rounded-0"
body-class="p-0"
>
<b-card-header
header-tag="header"
class="d-flex align-items-center"
>
<h5
class="mb-0"
>
{{ $t('steps:trigger.configurator.constraints') }}
</h5>
<b-button
v-if="constraintNameTypes.length"
variant="primary"
class="ml-3"
@click="addConstraint()"
>
{{ $t('steps:trigger.configurator.add-constraints') }}
</b-button>
</b-card-header>
<b-card-body
class="p-0"
>
<b-table
v-if="constraintNameTypes.length"
id="constraints"
fixed
borderless
hover
head-variant="light"
details-td-class="bg-white"
:items="item.triggers.constraints"
:fields="constraintFields"
:tbody-tr-class="rowClass"
show-empty
class="mb-0"
@row-clicked="item=>$set(item, '_showDetails', !item._showDetails)"
>
<template #empty>
<p class="text-center text-muted m-4">
{{ $t('steps:trigger.configurator.no-constraints') }}
</p>
</template>
<template #cell(name)="{ item: c }">
{{ getConstraintNameLabel(c.name) }}
</template>
<template #cell(op)="{ item: c }">
<p style="margin-bottom: 0.4rem;">
{{ getConstraintOperatorLabel(c.op) }}
</p>
</template>
<template #cell(values)="{ item: c, index }">
{{ c.values.join(' or ') }}
<c-input-confirm
show-icon
class="delete-btn ml-auto"
@confirmed="removeConstraint(index)"
/>
</template>
<template #row-details="{ item: c }">
<div class="arrow-up" />
<b-card class="bg-light">
<b-form-group
:label="$t('steps:trigger.configurator.resource')"
label-class="text-primary"
>
<c-input-select
v-model="c.name"
:options="constraintNameTypes"
:get-option-key="getOptionTypeKey"
label="text"
:reduce="c => c.value"
:filter="constrFilter"
:placeholder="$t('steps:trigger.configurator.select-constraint-type')"
:clearable="false"
@input="$root.$emit('change-detected')"
/>
</b-form-group>
<b-form-group
:label="$t('steps:trigger.configurator.operator')"
label-class="text-primary"
>
<c-input-select
v-model="c.op"
:options="constraintOperatorTypes"
:get-option-key="getOptionTypeKey"
label="text"
:reduce="c => c.value"
:placeholder="$t('steps:trigger.configurator.select-operator')"
:clearable="false"
@input="$root.$emit('change-detected')"
/>
</b-form-group>
<b-form-group
label="Values"
label-class="text-primary"
>
<div
v-for="(value, index) in c.values"
:key="index"
class="mb-2"
>
<p
v-if="index > 0"
class="text-center text-uppercase text-muted mb-2"
>
{{ $t('general:label.or') }}
</p>
<div class="d-flex align-items-center gap-1">
<b-form-input
v-model="c.values[index]"
@input="$root.$emit('change-detected')"
/>
<c-input-confirm
show-icon
@confirmed="c.values.splice(index, 1)"
/>
</div>
</div>
<b-button
variant="primary"
size="sm"
class="mr-auto"
@click="c.values.push('')"
>
{{ $t('steps:trigger.configurator.add') }}
</b-button>
</b-form-group>
</b-card>
</template>
</b-table>
<b-form-group
v-else-if="item.triggers.constraints[0]"
label-class="d-flex align-items-center text-primary"
class="mt-0 mb-4 mx-4"
>
<template #label>
{{ item.triggers.eventType.replace('on', '') }}
<a
:href="intervalDocumentationURL"
target="_blank"
class="d-flex align-items-center h6 mb-0 ml-1 pointer"
>
<font-awesome-icon
:icon="['far', 'question-circle']"
/>
</a>
</template>
<c-input-date-time
v-if="item.triggers.eventType === 'onTimestamp'"
v-model="item.triggers.constraints[0].values[0]"
:labels="{
clear: $t('general:clear'),
none: $t('general:none'),
now: $t('general:now'),
today: $t('general:today'),
}"
@input="$root.$emit('change-detected')"
/>
<b-form-input
v-else
v-model="item.triggers.constraints[0].values[0]"
@input="$root.$emit('change-detected')"
/>
</b-form-group>
</b-card-body>
</b-card>
<b-card
v-if="(eventType.properties || []).length"
class="flex-grow-1 rounded-0"
body-class="p-0"
>
<b-card-header
header-tag="header"
>
<h5
class="mb-0"
>
{{ $t('steps:trigger.configurator.initial-scope') }}
</h5>
</b-card-header>
<b-card-body
class="p-0"
>
<b-table
id="variable"
fixed
borderless
head-variant="light"
class="mb-4"
:items="eventType.properties || []"
:fields="scopeFields"
>
<template #cell(type)="{ item: v }">
<var>{{ v.type || $t('general:label.any') }}</var>
</template>
</b-table>
</b-card-body>
</b-card>
</div>
</template>
<script>
import base from './base'
import { components } from '@cortezaproject/corteza-vue'
import { objectSearchMaker } from '../../lib/filter'
import { getConstraintNameLabel } from '../../lib/constraint'
import { getDocumentationURL } from '../../lib/version'
import { camelToTitle } from '../../lib/string'
const { CInputDateTime } = components
export default {
components: {
CInputDateTime,
},
extends: base,
data () {
return {
modules: [],
eventTypes: [],
resourceTypes: [],
}
},
computed: {
resourceTypeOptions () {
return this.resourceTypes
},
eventTypeOptions () {
return this.eventTypes.filter(({ resourceType }) => resourceType === this.item.triggers.resourceType)
},
eventType () {
return this.eventTypes.find(({ resourceType, eventType }) => resourceType === this.item.triggers.resourceType && eventType === this.item.triggers.eventType) || {}
},
showConstraints () {
if (this.item.triggers.resourceType && this.item.triggers.eventType) {
return this.constraintNameTypes.length ? true : this.item.triggers.eventType !== 'onManual'
}
return false
},
constraintFields () {
return [
{
key: 'name',
tdClass: 'pointer',
},
{
key: 'op',
label: this.$t('steps:trigger.configurator.operator'),
thClass: 'text-center',
tdClass: 'text-truncate text-center pointer',
},
{
key: 'values',
tdClass: 'd-flex align-items-start gap-2 pointer pr-2',
},
]
},
scopeFields () {
return [
{
key: 'name',
thClass: 'pl-3 py-2',
tdClass: 'text-truncate',
},
{
key: 'type',
thClass: 'pr-3 py-2',
tdClass: 'text-truncate',
},
]
},
constraintNameTypes () {
const constraints = this.eventType.constraints || []
return constraints.reduce((cons, { name }) => {
if (!name.includes('*')) {
cons.push({
value: name,
text: this.getConstraintNameLabel(name),
})
}
return cons
}, [])
},
constraintOperatorTypes () {
return [
{ value: '=', text: this.$t('steps:trigger.configurator.equal') },
{ value: '!=', text: this.$t('steps:trigger.configurator.not-equal') },
{ value: 'like', text: this.$t('steps:trigger.configurator.like') },
{ value: 'not like', text: this.$t('steps:trigger.configurator.not-like') },
]
},
intervalDocumentationURL () {
return getDocumentationURL('integrator-guide/automation/workflows/index.html#deferred-interval')
},
},
async created () {
if (!this.item.triggers) {
this.$set(this.item, 'triggers', {
resourceType: null,
eventType: null,
constraints: [],
enabled: true,
})
}
await this.getEventTypes()
},
methods: {
resTypeFilter: objectSearchMaker('text'),
evtTypeFilter: objectSearchMaker('eventType'),
constrFilter: objectSearchMaker('text'),
getConstraintNameLabel,
async getEventTypes () {
return this.$AutomationAPI.eventTypesList()
.then(({ set }) => {
this.eventTypes = set
const resourceTypes = new Set(set.map(({ resourceType }) => resourceType))
this.resourceTypes = [...resourceTypes].map(resourceType => {
return {
value: resourceType,
text: this.getResourceTypeLabel(resourceType),
}
})
})
.catch(this.toastErrorHandler(this.$t('steps:trigger.configurator.failed-fetch-event-types')))
},
addConstraint () {
this.item.triggers.constraints.push({
name: '',
op: '=',
values: [''],
_showDetails: true,
})
this.$root.$emit('change-detected')
},
removeConstraint (index) {
this.item.triggers.constraints.splice(index, 1)
this.$root.$emit('change-detected')
},
resourceChanged () {
this.item.triggers.eventType = null
this.item.triggers.constraints = []
this.$root.$emit('change-detected')
this.updateDefaultName()
},
eventChanged () {
if (['onTimestamp', 'onInterval'].includes(this.item.triggers.eventType)) {
this.item.triggers.constraints = []
this.addConstraint()
}
this.$root.$emit('change-detected')
this.updateDefaultName()
},
enabledChanged () {
this.$root.$emit('trigger-updated', this.item.node)
this.$root.$emit('change-detected')
},
rowClass (item, type) {
if (type === 'row') {
return item._showDetails ? 'border-thick' : 'border-thick-transparent'
} else if (type === 'row-details') {
return ''
}
},
updateDefaultName () {
const { resourceType, eventType } = this.item.triggers
if (resourceType) {
let value = [this.getResourceTypeLabel(resourceType), this.getEventTypeLabel({ eventType })].filter(v => v).join(' - ')
value = value.charAt(0).toUpperCase() + value.slice(1)
this.$emit('update-default-value', { value, force: !this.item.node.value })
}
},
getOptionTypeKey ({ value }) {
return value
},
getOptionEventTypeKey ({ eventType }) {
return eventType
},
getResourceTypeLabel (resourceType) {
if (!resourceType) return ''
return resourceType
.split(':')
.map(part => part
.split('-')
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(' '),
)
.join(' - ')
},
getEventTypeLabel ({ eventType = '' } = {}) {
if (!eventType) return ''
return camelToTitle(eventType.replace('on', ''))
},
getConstraintOperatorLabel (op) {
const operator = this.constraintOperatorTypes.find(type => type.value === op)
return operator ? operator.text : op
},
},
}
</script>
<style lang="scss" scoped>
#constraints {
tbody tr .delete-btn {
display: none !important;
}
tbody tr:hover .delete-btn {
display: inline-flex !important;
}
}
</style>

View File

@@ -0,0 +1,319 @@
<template>
<b-modal
id="workflow"
:title="$t('editor:workflow-configuration')"
size="lg"
:hide-header-close="workflow.workflowID === '0'"
:no-close-on-backdrop="workflow.workflowID === '0'"
:no-close-on-esc="workflow.workflowID === '0'"
no-fade
body-class="p-0"
>
<template #modal-title>
{{ $t('editor:workflow-configuration') }}
</template>
<div
v-if="workflow.workflowID && workflow.workflowID !== '0'"
class="d-flex p-3"
>
<import
data-test-id="button-import-workflow"
:disabled="importProcessing"
@import="$emit('import', $event)"
/>
<export
data-test-id="button-export-workflow"
:workflows="[workflow.workflowID]"
:file-name="workflow.meta.name || workflow.handle"
size="lg"
class="ml-1"
/>
<c-permissions-button
v-if="workflow.canGrant"
:title="workflow.meta.name || workflow.handle || workflow.workflowID"
:target="workflow.meta.name || workflow.handle || workflow.workflowID"
:resource="`corteza::automation:workflow/${workflow.workflowID}`"
:button-label="$t('general:permissions')"
class="btn-lg ml-1"
/>
</div>
<div v-if="localWorkflow">
<b-tabs card>
<b-tab
:title="$t('general.label')"
active
>
<b-form-group
:label="$t('name.label')"
label-class="text-primary"
>
<b-form-input
v-model="localWorkflow.meta.name"
data-test-id="input-label"
:placeholder="$t('name.placeholder')"
:state="nameState"
/>
</b-form-group>
<b-form-group
:label="$t('handle.label')"
label-class="text-primary"
>
<b-form-input
v-model="localWorkflow.handle"
data-test-id="input-handle"
:state="handleState"
:placeholder="$t('handle.placeholder')"
/>
<b-form-invalid-feedback
data-test-id="input-handle-invalid-state"
:state="handleState"
>
{{ $t('handle.invalid-handle-characters') }}
</b-form-invalid-feedback>
</b-form-group>
<b-form-group
:label="$t('description.label')"
label-class="text-primary"
>
<b-form-textarea
v-model="localWorkflow.meta.description"
data-test-id="input-description"
:placeholder="$t('description.placeholder')"
/>
</b-form-group>
<b-form-group
:label="$t('run-as.label')"
:description="$t('run-as.description')"
label-class="text-primary"
>
<c-input-user
v-model="localWorkflow.runAs"
data-test-id="select-run-as"
:placeholder="$t('run-as.placeholder')"
clearable
/>
</b-form-group>
<b-form-group>
<b-form-checkbox
v-model="localWorkflow.enabled"
data-test-id="checkbox-enable-workflow"
>
{{ $t('general:enabled') }}
</b-form-checkbox>
</b-form-group>
<b-form-group
:description="$t('sub-workflow.description')"
>
<b-form-checkbox
v-model="localWorkflow.meta.subWorkflow"
data-test-id="checkbox-sub-workflow"
>
{{ $t('sub-workflow.label') }}
</b-form-checkbox>
</b-form-group>
</b-tab>
<b-tab :title="$t('labels.label')">
<namespace-module-selector
:namespace-labels="localWorkflow?.labels?.ref_namespace || []"
:module-labels="localWorkflow?.labels?.ref_module || []"
@change="handleLabelsChange"
/>
</b-tab>
</b-tabs>
</div>
<template #modal-footer>
<div class="d-flex w-100">
<c-input-confirm
v-if="workflow.canDeleteWorkflow && !isDeleted"
size="md"
size-confirm="md"
variant="danger"
:processing="processingDelete"
:text="$t('editor:delete')"
:borderless="false"
@confirmed="$emit('delete')"
/>
<c-input-confirm
v-else-if="isDeleted"
size="md"
size-confirm="md"
variant="light"
:processing="processingDelete"
:text="$t('editor:undelete')"
:borderless="false"
@confirmed="$emit('undelete')"
/>
<b-button
v-if="workflow.workflowID === '0'"
variant="light"
@click="$router.back()"
>
{{ $t('editor:back') }}
</b-button>
<div class="d-flex ml-auto">
<b-button
v-if="workflow.workflowID !== '0'"
variant="light"
class="ml-auto"
@click="handleCancel"
>
{{ $t('general:cancel') }}
</b-button>
<c-button-submit
data-test-id="button-save-workflow"
:disabled="isSaveDisabled"
:processing="processingSave"
:text="$t('editor:save')"
class="ml-1"
@submit="handleSave"
/>
</div>
</div>
</template>
</b-modal>
</template>
<script>
import { handle, components } from '@cortezaproject/corteza-vue'
import { automation } from '@cortezaproject/corteza-js'
import Import from '../Import'
import Export from '../Export'
import NamespaceModuleSelector from '../NamespaceModuleSelector'
const { CInputUser } = components
export default {
i18nOptions: {
namespaces: 'configurator',
},
components: {
CInputUser,
Import,
Export,
NamespaceModuleSelector,
},
props: {
workflow: {
type: Object,
default: () => {},
},
canCreate: {
type: Boolean,
default: false,
},
processingSave: {
type: Boolean,
default: false,
},
processingDelete: {
type: Boolean,
default: false,
},
importProcessing: {
type: Boolean,
default: false,
},
},
data () {
return {
localWorkflow: null,
}
},
computed: {
nameState () {
return this.localWorkflow?.meta?.name ? null : false
},
handleState () {
return this.localWorkflow ? handle.handleState(this.localWorkflow.handle) : null
},
canUpdateWorkflow () {
return this.workflow.workflowID === '0' ? this.canCreate : this.workflow.canUpdateWorkflow
},
isSaveDisabled () {
return !this.canUpdateWorkflow || [this.nameState, this.handleState].includes(false)
},
isDeleted () {
return this.workflow.deletedAt
},
},
watch: {
workflow: {
handler (newWorkflow) {
if (!newWorkflow) {
this.localWorkflow = null
return
}
// Create a new Workflow instance from the existing workflow
// This properly clones the workflow and avoids circular references
this.localWorkflow = new automation.Workflow(newWorkflow)
},
immediate: true,
},
},
methods: {
handleLabelsChange ({ namespaceLabels, moduleLabels }) {
if (!this.localWorkflow.labels) {
this.localWorkflow.labels = {}
}
// Store labels directly - no transformation needed
if (namespaceLabels.length > 0) {
this.localWorkflow.labels.ref_namespace = namespaceLabels
} else {
delete this.localWorkflow.labels.ref_namespace
}
if (moduleLabels.length > 0) {
this.localWorkflow.labels.ref_module = moduleLabels
} else {
delete this.localWorkflow.labels.ref_module
}
},
handleSave () {
// Emit save event with the local workflow copy
this.$emit('save', this.localWorkflow)
// Close the modal after save
this.$bvModal.hide('workflow')
},
handleCancel () {
// Reset local workflow to original workflow data
this.localWorkflow = new automation.Workflow(this.workflow)
// Close the modal
this.$bvModal.hide('workflow')
},
},
}
</script>

View File

@@ -0,0 +1,25 @@
<script>
export default {
props: {
item: {
type: Object,
default: () => {},
},
edges: {
type: Object,
default: () => {},
},
outEdges: {
type: Number,
default: 0,
},
isSubworkflow: {
type: Boolean,
default: false,
},
},
}
</script>

View File

@@ -0,0 +1,72 @@
<template>
<div>
<b-form-group
:label="$t('general:label.label')"
label-class="text-primary"
>
<b-form-input
v-model="label"
@input="$emit('update-value', $event)"
/>
</b-form-group>
<!-- <b-form-group
v-if="getSelectedItemConfigJSON"
label="Config"
>
<b-form-textarea
v-model="getSelectedItemConfigJSON"
rows="10"
/>
</b-form-group> -->
</div>
</template>
<script>
import base from './base'
export default {
extends: base,
computed: {
// Ignores exclusiveGateway indexes (#n)
label: {
get () {
if (this.getSourceType) {
if (this.getSourceType === 'gatewayExclusive') {
/* eslint-disable no-unused-vars */
const [edgeID, ...rest] = this.item.node.value.split(' - ')
return rest.join(' - ')
}
}
return this.item.node.value
},
set (label) {
if (this.getSourceType) {
if (this.getSourceType === 'gatewayExclusive') {
/* eslint-disable no-unused-vars */
const [edgeID, ...rest] = this.item.node.value.split(' - ')
const newLabel = [edgeID]
if (label) {
newLabel.push(label)
}
label = newLabel.join(' - ')
}
}
this.item.node.value = label
},
},
getSourceType () {
const { source } = this.item.node
if (source && source.style) {
return source.style
}
return undefined
},
},
}
</script>

View File

@@ -0,0 +1,99 @@
<template>
<div
class="d-flex flex-column"
>
<b-card
v-if="kind !== 'Content'"
class="flex-grow-1 border-bottom border-light rounded-0"
>
<b-card-header
header-tag="header"
class="p-0 mb-3"
>
<h5
class="mb-0"
>
{{ $t('general:general') }}
</h5>
</b-card-header>
<b-card-body
class="p-0"
>
<basic
:item="item"
@update-value="$emit('update-value', $event)"
/>
</b-card-body>
</b-card>
<component
:is="stepComponent"
v-if="stepComponent"
:item.sync="item"
:edges.sync="edges"
:out-edges="outEdges"
:is-subworkflow="isSubworkflow"
@update-value="$emit('update-value', $event)"
@update-default-value="updateDefaultName"
/>
</div>
</template>
<script>
import base from './base'
import basic from './basic'
import * as Configurators from './loader'
export default {
components: {
...Configurators,
basic,
},
extends: base,
data () {
return {
collapse: {
basic: true,
configurator: true,
},
}
},
computed: {
stepComponent () {
return Configurators[this.kind]
},
kind () {
const { kind, ref } = this.item.config
if (kind === 'exec-workflow') {
return 'ExecWorkflow'
}
if (kind === 'error-handler') {
return 'ErrorHandler'
}
if (kind === 'visual' && ref === 'content') {
return 'Content'
}
if (kind) {
return kind.charAt(0).toUpperCase() + kind.slice(1)
}
return undefined
},
},
methods: {
updateDefaultName ({ value, force = false }) {
if (force || this.item.config.defaultName || this.item.config.defaultName === undefined) {
this.$emit('update-default-value', value)
}
},
},
}
</script>

View File

@@ -0,0 +1,13 @@
export { default as Workflow } from './Workflow'
export { default as Expressions } from './Expressions'
export { default as Function } from './Function'
export { default as Iterator } from './Iterator'
export { default as Trigger } from './Trigger'
export { default as Error } from './Error'
export { default as Gateway } from './Gateway'
export { default as Edge } from './Edge'
export { default as Prompt } from './Prompt'
export { default as Delay } from './Delay'
export { default as ExecWorkflow } from './ExecWorkflow'
export { default as ErrorHandler } from './ErrorHandler'
export { default as Content } from './Content'

View File

@@ -0,0 +1,87 @@
<template>
<b-button
:variant="variant"
:size="size"
@click="jsonExport(workflows)"
>
<slot />
{{ $t('general:export') }}
</b-button>
</template>
<script>
import { saveAs } from 'file-saver'
export default {
props: {
workflows: {
type: Array,
default: () => ([]),
},
fileName: {
type: String,
default: 'workflows-export',
},
size: {
type: String,
default: 'md',
},
variant: {
type: String,
default: 'light',
},
},
methods: {
async jsonExport (workflowID = []) {
const triggers = {}
let workflows = []
// Get workflow triggers
await this.$AutomationAPI.triggerList({ workflowID, disabled: 1 })
.then(({ set = [] }) => {
set.forEach(({ workflowID, resourceType, eventType, constraints, enabled, stepID, meta }) => {
if (!triggers[workflowID]) {
triggers[workflowID] = []
}
triggers[workflowID].push({
resourceType,
eventType,
constraints,
enabled,
stepID,
meta,
})
})
})
.catch(this.toastErrorHandler(this.$t('notification:failed-fetch-triggers')))
// Get workflows, add related triggers
await this.$AutomationAPI.workflowList({ workflowID, disabled: 1, subWorkflow: 1 })
.then(({ set = [] }) => {
workflows = set.map(({ workflowID, handle, enabled, keepSessions, steps, paths, meta }) => {
return {
handle,
enabled,
meta,
keepSessions,
steps,
paths,
triggers: triggers[workflowID],
}
})
})
.catch(this.toastErrorHandler(this.$t('notification:failed-fetch-workflows')))
// Save file
const blob = new Blob([JSON.stringify({ workflows }, null, 2)], { type: 'application/json' })
const filename = this.fileName.replace(/[/\\?%*:|"<>]/g, '')
saveAs(blob, `${filename}.json`)
},
},
}
</script>

View File

@@ -0,0 +1,100 @@
<template>
<div
class="position-relative"
>
<c-ace-editor
v-model="expressionValue"
:lang="lang"
:min-height="minHeight"
:show-line-numbers="showLineNumbers"
:font-size="fontSize"
:show-popout="showPopout"
:auto-complete="autoComplete"
:border="border"
:auto-complete-suggestions="expressionAutoCompleteValues"
resizable
v-on="$listeners"
/>
</div>
</template>
<script>
import { components } from '@cortezaproject/corteza-vue'
import { EXPRESSION_EDITOR_AUTO_COMPLETE_VALUES } from '../lib/editor-auto-complete.js'
const { CAceEditor } = components
export default {
components: {
CAceEditor,
},
props: {
value: {
type: String,
default: '',
},
lang: {
type: String,
default: 'text',
},
minHeight: {
type: String,
default: '6rem',
},
showLineNumbers: {
type: Boolean,
default: false,
},
fontSize: {
type: String,
default: '14px',
},
border: {
type: Boolean,
default: true,
},
showPopout: {
type: Boolean,
default: true,
},
autoComplete: {
type: Boolean,
default: true,
},
},
data () {
return {
expressionAutoCompleteValues: EXPRESSION_EDITOR_AUTO_COMPLETE_VALUES,
}
},
computed: {
expressionValue: {
get () {
return this.value
},
set (value = '') {
this.$emit('update:value', value)
},
},
},
}
</script>
<style lang="scss" scoped>
.popout {
z-index: 7;
bottom: 0;
right: 0;
}
</style>

View File

@@ -0,0 +1,187 @@
<template>
<div class="table-responsive">
<b-row class="header pl-4">
<b-col
v-for="(field, index) in fields"
:key="index"
:class="`py-2 ${field.thClass}`"
>
<label>{{ field.label }}</label>
</b-col>
</b-row>
<draggable
:list="items"
handle=".grab"
@end="$root.$emit('change-detected')"
>
<div
v-for="(item, index) in items"
:key="index"
>
<b-row
class="d-flex justify-content-between align-items-center pointer expr-item"
no-gutters
@click="$set(item, '_showDetails', !item._showDetails)"
>
<div class="p-2 grab">
<font-awesome-icon
:icon="['fas', 'bars']"
class="text-secondary"
/>
</div>
<b-col
v-for="(field, i) in fields"
:key="i"
class="text-truncate p-2"
>
<div
v-if="field.key === 'expr'"
class="d-flex justify-content-between align-items-center"
>
<samp class="text-truncate">{{ item[field.key] }}</samp>
<c-input-confirm
v-if="item._showDetails"
show-icon
@confirmed="$emit('remove', index)"
/>
</div>
<var
v-else
class=""
>
{{ field.formatter ? field.formatter(item) : item[field.key] }}
</var>
</b-col>
</b-row>
<transition name="fade">
<div
v-if="item._showDetails"
class="mb-3 px-3"
>
<div class="arrow-up" />
<b-card
class="bg-light"
body-class="px-4 pb-3"
>
<b-form-group
label-class="text-primary"
>
<b-form-input
v-model="item.target"
placeholder="Target"
@input="$root.$emit('change-detected')"
/>
</b-form-group>
<b-form-group
label-class="text-primary"
:description="getTypeDescription(item.type)"
>
<c-input-select
v-model="item.type"
:options="types"
:get-option-key="getOptionKey"
:clearable="false"
:filter="varFilter"
append-to-body
@input="$root.$emit('change-detected')"
/>
</b-form-group>
<b-form-group
class="mb-0"
>
<expression-editor
v-model="item[valueField]"
show-line-numbers
@open="$emit('open-editor', index)"
@input="$root.$emit('change-detected')"
/>
</b-form-group>
</b-card>
</div>
</transition>
</div>
</draggable>
</div>
</template>
<script>
import ExpressionEditor from './ExpressionEditor.vue'
import { objectSearchMaker } from '../lib/filter'
import draggable from 'vuedraggable'
export default {
components: {
ExpressionEditor,
draggable,
},
props: {
valueField: {
type: String,
required: true,
},
items: {
type: Array,
required: true,
},
fields: {
type: Array,
required: true,
},
types: {
type: Array,
required: true,
},
},
methods: {
varFilter: objectSearchMaker('text'),
getTypeDescription (type) {
// This will be moved to backend field type information
const typeDescriptions = {
ID: 'Make sure to provide the ID in double quotes if you\'re using a literal value. Example "123"',
}
return typeDescriptions[type]
},
getOptionKey (type) {
return type
},
},
}
</script>
<style lang="scss" scoped>
.header {
background-color: var(--light);
label {
margin: 0;
}
}
.table-responsive {
overflow: hidden;
}
.expr-item:hover {
background-color: var(--light);
.grab > * {
color: var(--secondary) !important;
}
}
</style>

View File

@@ -0,0 +1,87 @@
<template>
<div>
<b-form-group
label-class="sticky-top bg-white pb-0 text-primary"
class="p-2 mb-0"
>
<template #label>
<h5
class="d-flex align-items-center p-2 mb-0"
>
{{ $t('help:basic-controls-label') }}
<font-awesome-icon
:icon="['fas', 'mouse']"
class="ml-2"
/>
</h5>
</template>
<b-table
:items="basicControls"
fixed
thead-class="d-none"
/>
</b-form-group>
<b-form-group
label-class="sticky-top bg-white pb-0 text-primary"
class="p-2 mb-0"
>
<template #label>
<h5
class="d-flex align-items-center pt-2 px-2 mb-0"
>
{{ $t('help:keyboard-shortcuts-label') }}
<font-awesome-icon
:icon="['fas', 'keyboard']"
class="ml-2"
/>
</h5>
</template>
<small
class="ml-2"
>
{{ $t('help:mac-users') }}
</small>
<b-table
:items="keyboardShortcuts"
fixed
thead-class="d-none"
/>
</b-form-group>
</div>
</template>
<script>
export default {
data () {
return {
basicControls: [
{ action: this.$t('help:basic-controls.action.select'), shortcut: this.$t('help:basic-controls.shortcut.left-click.label') },
{ action: this.$t('help:basic-controls.action.select-multiple'), shortcut: this.$t('help:basic-controls.shortcut.left-click.drag') },
{ action: this.$t('help:basic-controls.action.deselect'), shortcut: this.$t('help:basic-controls.shortcut.left-click.background') },
{ action: this.$t('help:basic-controls.action.pan'), shortcut: this.$t('help:basic-controls.shortcut.pan') },
{ action: this.$t('help:basic-controls.action.zoom-in/out'), shortcut: this.$t('help:basic-controls.shortcut.zoom') },
],
keyboardShortcuts: [
{ action: this.$t('help:keyboard-shortcuts.action.select-all'), shortcut: 'Ctrl + A' },
{ action: this.$t('help:keyboard-shortcuts.action.add-selection'), shortcut: `Ctrl + ${this.$t('help:keyboard-shortcuts.shortcut.left-mouse-button')}` },
{ action: this.$t('help:keyboard-shortcuts.action.delete-selected-elements'), shortcut: 'Delete or Backspace' },
{ action: this.$t('help:keyboard-shortcuts.action.reset-view-to-start-point'), shortcut: 'Ctrl + Space' },
{ action: this.$t('help:keyboard-shortcuts.action.undo'), shortcut: 'Ctrl + Z' },
{ action: this.$t('help:keyboard-shortcuts.action.redo'), shortcut: 'Ctrl + Shift + Z' },
{ action: this.$t('help:keyboard-shortcuts.action.cut'), shortcut: 'Ctrl + X' },
{ action: this.$t('help:keyboard-shortcuts.action.copy'), shortcut: 'Ctrl + C' },
{ action: this.$t('help:keyboard-shortcuts.action.paste'), shortcut: 'Ctrl + V' },
{ action: this.$t('help:keyboard-shortcuts.action.save'), shortcut: 'Ctrl + S' },
{ action: this.$t('help:keyboard-shortcuts.action.nudge'), shortcut: `${this.$t('help:keyboard-shortcuts.shortcut.arrow-keys')} Shift ${this.$t('help:keyboard-shortcuts.shortcut.adjust-distance')}` },
{ action: this.$t('help:keyboard-shortcuts.action.hide-guides'), shortcut: `${this.$t('help:keyboard-shortcuts.shortcut.hold')} Alt` },
{ action: this.$t('help:keyboard-shortcuts.action.show-help'), shortcut: 'Shift + ?' },
],
}
},
}
</script>
<style>
</style>

View File

@@ -0,0 +1,108 @@
<template>
<div class="d-flex">
<b-button
variant="light"
size="lg"
class="flex-fill"
@click="show = true"
>
{{ $t('general:import.label') }}
</b-button>
<b-modal
id="import"
v-model="show"
size="lg"
:title="$t('general:import.json')"
ok-only
no-fade
class="d-none"
@ok="$emit('import', workflows)"
>
<b-form-group
:description="$t('general:import.reassign-run-as')"
class="mb-0"
>
<b-form-file
:placeholder="$t('general:import.upload-files')"
@change="fileUpload"
/>
</b-form-group>
<template #modal-footer>
<b-button
variant="primary"
size="lg"
:disabled="!workflows.length || processing"
class="d-flex justify-content-center align-items-center"
@click="$emit('import', workflows)"
>
<b-spinner
v-if="processing"
small
type="grow"
/>
<span
v-else
>
{{ $t('general:import.label') }}
</span>
</b-button>
</template>
</b-modal>
</div>
</template>
<script>
export default {
props: {
disabled: {
type: Boolean,
default: false,
},
},
data () {
return {
show: false,
workflows: [],
processing: false,
}
},
methods: {
fileUpload (e = {}) {
const { files = [] } = (e.type === 'drop' ? e.dataTransfer : e.target) || {}
if (files[0]) {
this.processing = true
const reader = new FileReader()
reader.readAsText(files[0])
reader.onload = (evt) => {
try {
const { workflows = [] } = JSON.parse(evt.target.result)
this.workflows = workflows
} catch (err) {
err.message = this.$t('notification:failed-load-file')
this.toastErrorHandler(this.$t('notification:general.warning'))(err)
} finally {
this.processing = false
}
}
reader.onerror = () => {
this.toastErrorHandler(this.$t('notification:failed-load-file'))
this.processing = false
}
}
},
},
}
</script>
<style>
</style>

View File

@@ -0,0 +1,360 @@
<template>
<div>
<b-form-group
:label="$t('filter.namespace.label')"
label-class="text-primary"
>
<c-input-select
class="namespace-selector"
:options="namespace.options"
:value="namespace.values"
:get-option-label="getNamespaceOptionLabel"
:get-option-key="n => `corteza::compose:namespace/${n.namespaceID}`"
:reduce="n => `corteza::compose:namespace/${n.namespaceID}`"
:placeholder="$t('filter.namespace.placeholder')"
:loading="namespace.processing"
:filterable="false"
multiple
@search="searchNamespaces"
@input="updateNamespaces"
/>
</b-form-group>
<b-form-group
v-for="ns in namespace.values"
:key="ns"
:label="getModuleLabel(ns)"
label-class="text-primary"
>
<c-input-select
class="module-selector"
:options="getModulesForNamespace(ns.split('/')[1])"
:value="getModuleValuesForNamespace(ns.split('/')[1])"
:get-option-key="m => `corteza::compose:module/${m.namespaceID}/${m.moduleID}`"
:get-option-label="getModuleOptionLabel"
:reduce="m => `corteza::compose:module/${m.namespaceID}/${m.moduleID}`"
:placeholder="$t('filter.module.placeholder')"
:loading="module.processing"
:filterable="false"
multiple
@search="query => searchModulesForNamespace(query, ns.split('/')[1])"
@input="modules => updateModulesForNamespace(modules, ns.split('/')[1])"
/>
</b-form-group>
</div>
</template>
<script>
import { debounce } from 'lodash'
import { components } from '@cortezaproject/corteza-vue'
const { CInputSelect } = components
export default {
name: 'NamespaceModuleSelector',
components: {
CInputSelect,
},
i18nOptions: {
namespaces: 'general',
},
props: {
/**
* Initial namespace labels in format: ["corteza::compose:namespace/id1", ...]
*/
namespaceLabels: {
type: Array,
default: () => [],
},
/**
* Initial module labels in format: ["corteza::compose:module/nsID/modID", ...]
*/
moduleLabels: {
type: Array,
default: () => [],
},
},
data () {
return {
namespace: {
processing: false,
values: [],
options: [],
filter: {
query: null,
limit: 20,
sort: 'name DESC',
},
},
module: {
processing: false,
values: [],
options: [],
filter: {
query: null,
limit: 20,
sort: 'name DESC',
},
},
}
},
computed: {
modulesByNamespace () {
const grouped = {}
this.module.options.forEach(module => {
if (!grouped[module.namespaceID]) {
grouped[module.namespaceID] = []
}
grouped[module.namespaceID].push(module)
})
return grouped
},
},
watch: {
namespaceLabels: {
handler (newVal) {
if (newVal && newVal.length > 0 && this.namespace.options.length > 0) {
this.initializeFromProps()
}
},
immediate: false,
},
},
created () {
this.fetchNamespaces().then(() => {
this.initializeFromProps()
})
},
methods: {
initializeFromProps () {
// Set namespace values directly from labels, filtering out malformed entries
this.namespace.values = (this.namespaceLabels || []).filter(label => this.isValidLabel(label, 2))
// Set module values directly from labels
if (this.namespace.values.length > 0) {
this.fetchModules().then(() => {
this.module.values = (this.moduleLabels || []).filter(label => this.isValidLabel(label, 3))
})
}
},
fetchNamespaces () {
this.namespace.processing = true
return this.$ComposeAPI.namespaceList(this.namespace.filter).then(({ set = [] } = {}) => {
const namespacePromises = []
// Fetch any missing namespaces from initial labels
if (this.namespaceLabels && this.namespaceLabels.length > 0 && !this.namespace.filter.query) {
const namespaceIDs = this.namespaceLabels.map(label => label.split('/')[1]).filter(Boolean)
namespaceIDs.forEach(namespaceID => {
if (!set.some(n => n.namespaceID === namespaceID)) {
namespacePromises.push(
this.$ComposeAPI.namespaceRead({ namespaceID })
.then(n => [n])
.catch(() => [{ namespaceID }]),
)
}
})
}
return Promise.all(namespacePromises).then(results => {
this.namespace.options = [...set, ...results.flat()].sort((a, b) =>
(a.name || '').localeCompare(b.name || ''),
)
}).catch(() => {
this.namespace.options = []
})
}).finally(() => {
this.namespace.processing = false
})
},
fetchModules () {
if (!this.namespace.values || this.namespace.values.length === 0) {
this.module.options = []
return Promise.resolve()
}
this.module.processing = true
// Extract namespace IDs from label strings
const namespaceIDs = this.namespace.values.map(label => label.split('/')[1])
const promises = namespaceIDs.map(namespaceID =>
this.$ComposeAPI.moduleList({
namespaceID,
...this.module.filter,
}).then(({ set }) => set),
)
return Promise.all(promises).then(results => {
const set = results.flat()
// Fetch any selected modules missing from the listed page (e.g. when there are more modules in a namespace than the page limit).
const missingPromises = []
if (this.moduleLabels && this.moduleLabels.length > 0 && !this.module.filter.query) {
this.moduleLabels.forEach(label => {
const parts = label.split('/')
const namespaceID = parts[1]
const moduleID = parts[2]
if (!namespaceID || !moduleID) return
if (set.some(m => m.moduleID === moduleID)) return
missingPromises.push(
this.$ComposeAPI.moduleRead({ namespaceID, moduleID })
.then(m => [m])
.catch(() => [{ namespaceID, moduleID }]),
)
})
}
return Promise.all(missingPromises).then(extra => {
this.module.options = [...set, ...extra.flat()]
})
}).catch(() => {
this.module.options = []
}).finally(() => {
this.module.processing = false
})
},
searchNamespaces: debounce(function (query) {
if (query !== this.namespace.filter.query) {
this.namespace.filter.query = query
}
this.fetchNamespaces()
}, 300),
searchModulesForNamespace: debounce(function (query, namespaceID) {
if (query !== this.module.filter.query) {
this.module.filter.query = query
}
this.fetchModules()
}, 300),
updateNamespaces (namespaceLabels) {
this.namespace.values = (namespaceLabels || []).filter(label => this.isValidLabel(label, 2))
if (this.namespace.values.length > 0) {
// Filter out modules from namespaces that are no longer selected,
// and drop any malformed module labels at the same time.
const selectedNsIDs = new Set(this.namespace.values.map(label => label.split('/')[1]))
this.module.values = this.module.values.filter(moduleLabel => {
if (!this.isValidLabel(moduleLabel, 3)) return false
const nsID = moduleLabel.split('/')[1]
return selectedNsIDs.has(nsID)
})
this.fetchModules()
} else {
this.module.options = []
this.module.values = []
}
this.emitChange()
},
updateModulesForNamespace (moduleLabels, namespaceID) {
// Remove old modules for this namespace
this.module.values = this.module.values.filter(label => {
const nsID = label.split('/')[1]
return nsID !== namespaceID
})
// Add new module labels for this namespace
if (moduleLabels && moduleLabels.length > 0) {
const valid = moduleLabels.filter(label => this.isValidLabel(label, 3))
this.module.values = [...this.module.values, ...valid]
}
this.emitChange()
},
isValidLabel (label, expectedParts) {
if (typeof label !== 'string' || !label) return false
const parts = label.split('/')
if (parts.length < expectedParts) return false
for (let i = 1; i < expectedParts; i++) {
if (!parts[i] || parts[i] === 'undefined') return false
}
return true
},
emitChange () {
// Values are already in label format - emit directly
this.$emit('change', {
namespaceLabels: this.namespace.values,
moduleLabels: this.module.values,
})
},
getNamespaceOptionLabel ({ name, slug, namespaceID } = {}) {
return name || slug || namespaceID
},
getModuleOptionLabel ({ name, handle, moduleID } = {}) {
return name || handle || moduleID
},
getModuleLabel (namespaceLabel) {
const namespaceID = namespaceLabel.split('/')[1]
const namespace = this.namespace.options.find(n => n.namespaceID === namespaceID)
const nsLabel = namespace ? this.getNamespaceOptionLabel(namespace) : namespaceID
return this.$t('filter.module.template', { namespace: nsLabel })
},
getModulesForNamespace (namespaceID) {
return this.modulesByNamespace[namespaceID] || []
},
getModuleValuesForNamespace (namespaceID) {
return this.module.values.filter(label => {
const nsID = label.split('/')[1]
return nsID === namespaceID
})
},
/**
* Reset the selector to empty state
*/
reset () {
this.namespace.values = []
this.module.values = []
this.module.options = []
this.emitChange()
},
},
}
</script>
<style scoped>
/* Style namespace select tags (primary blue) */
::v-deep .namespace-selector .vs__selected {
background-color: var(--primary) !important;
color: white !important;
}
::v-deep .namespace-selector .vs__deselect {
fill: white !important;
}
/* Style module select tags (extra-light with dark text) */
::v-deep .module-selector .vs__selected {
background-color: var(--extra-light) !important;
color: var(--dark) !important;
}
</style>

View File

@@ -0,0 +1,85 @@
<template>
<b-tooltip
:id="kind"
:target="kind"
triggers="hover"
placement="right"
variant="light"
custom-class="ml-2"
boundary="window"
noninteractive
@hide="show = false"
@show="show = true"
>
<transition name="fade">
<b-card
v-if="show"
:img-src="img"
:img-alt="kind"
img-height="100px"
img-top
:title="title"
class="text-left text-dark"
>
<b-card-text>
{{ text }}
</b-card-text>
</b-card>
</transition>
</b-tooltip>
</template>
<script>
export default {
props: {
title: {
type: String,
required: true,
},
kind: {
type: String,
required: true,
},
img: {
type: String,
required: true,
},
text: {
type: String,
required: true,
},
},
data () {
return {
show: false,
}
},
}
</script>
<style lang="scss">
.tooltip.b-tooltip-light > .tooltip-inner {
padding: 0;
max-width: 20rem;
min-width: 20rem;
background-color: transparent !important;
opacity: 1;
}
.tooltip.b-tooltip-light.bs-tooltip-right .arrow::before {
border-right-color: var(--dark) !important;
}
.tooltip.b-tooltip {
opacity: 1;
}
.fade-enter-active, .fade-leave-active {
transition: opacity 0.3s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
</style>

View File

@@ -0,0 +1,48 @@
import { default as component } from './WorkflowEditor.vue'
import { components } from '@cortezaproject/corteza-vue'
const { checkbox } = components.C3.controls
const props = {
workflowObject: {
workflowID: '',
handle: 'handle',
meta: {
name: 'Name',
description: 'desc',
},
enabled: true,
},
workflowTriggers: [],
changeDetected: false,
canCreate: true,
}
export default {
name: 'Workflow editor',
group: ['Root components'],
component,
props,
controls: [
checkbox('CanCreate', 'canCreate'),
],
scenarios: [
{
label: 'Full form',
props,
},
{
label: 'Empty form',
props: {
...props,
workflowObject: {
...props.workflowObject,
enabled: false,
name: '',
},
canCreate: false,
},
},
],
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,114 @@
<template>
<b-modal
id="workflow-filter"
:title="$t('general:filter.title')"
size="lg"
no-fade
body-class="p-3"
@show="onShow"
>
<namespace-module-selector
ref="selector"
:namespace-labels="localNamespaceLabels"
:module-labels="localModuleLabels"
@change="handleChange"
/>
<template #modal-footer>
<div class="d-flex gap-1 w-100">
<b-button
variant="light"
@click="handleReset"
>
{{ $t('general:reset') }}
</b-button>
<div class="d-flex ml-auto gap-1">
<b-button
variant="light"
@click="handleCancel"
>
{{ $t('general:cancel') }}
</b-button>
<b-button
variant="primary"
@click="handleApply"
>
{{ $t('general:filter.apply') }}
</b-button>
</div>
</div>
</template>
</b-modal>
</template>
<script>
import NamespaceModuleSelector from './NamespaceModuleSelector'
export default {
name: 'WorkflowFilterModal',
components: {
NamespaceModuleSelector,
},
props: {
/**
* Current namespace labels filter
*/
namespaceLabels: {
type: Array,
default: () => [],
},
/**
* Current module labels filter
*/
moduleLabels: {
type: Array,
default: () => [],
},
},
data () {
return {
localNamespaceLabels: [],
localModuleLabels: [],
}
},
methods: {
onShow () {
// Reset local state to current props when modal opens
this.localNamespaceLabels = [...this.namespaceLabels]
this.localModuleLabels = [...this.moduleLabels]
},
handleChange ({ namespaceLabels, moduleLabels }) {
this.localNamespaceLabels = namespaceLabels
this.localModuleLabels = moduleLabels
},
handleApply () {
this.$emit('apply', {
namespaceLabels: this.localNamespaceLabels,
moduleLabels: this.localModuleLabels,
})
this.$bvModal.hide('workflow-filter')
},
handleReset () {
this.localNamespaceLabels = []
this.localModuleLabels = []
if (this.$refs.selector) {
this.$refs.selector.reset()
}
},
handleCancel () {
this.$bvModal.hide('workflow-filter')
},
},
}
</script>

View File

@@ -0,0 +1,127 @@
import { library } from '@fortawesome/fontawesome-svg-core'
import {
faCheck,
faCheckDouble,
faTimes,
faHome,
faSave,
faKeyboard,
faMouse,
faCog,
faChevronLeft,
faChevronRight,
faChevronDown,
faExclamationTriangle,
faSearchPlus,
faSearchMinus,
faThumbtack,
faGripHorizontal,
faSearch,
faExpandAlt,
faPlus,
faQuestion,
faAngleDoubleLeft,
faAngleUp,
faAngleDown,
faAngleRight,
faAngleLeft,
faEllipsisV,
faBars,
faFileExport,
faToggleOn,
faToggleOff,
faBold,
faItalic,
faUnderline,
faStrikethrough,
faQuoteRight,
faCode,
faListUl,
faListOl,
faOutdent,
faIndent,
faAlignLeft,
faAlignCenter,
faAlignRight,
faAlignJustify,
faLink,
faRemoveFormat,
faParagraph,
faTasks,
faTable,
faBellSlash,
faBell as faBellSolid,
faFilter,
} from '@fortawesome/free-solid-svg-icons'
import {
faEdit,
faTrashAlt,
faQuestionCircle,
faUser,
faBell,
faEnvelope,
faEnvelopeOpen,
} from '@fortawesome/free-regular-svg-icons'
library.add(
faEdit,
faTrashAlt,
faCheck,
faCheckDouble,
faTimes,
faHome,
faSave,
faKeyboard,
faMouse,
faCog,
faChevronLeft,
faChevronRight,
faChevronDown,
faExclamationTriangle,
faQuestionCircle,
faSearchPlus,
faSearchMinus,
faUser,
faThumbtack,
faSearch,
faGripHorizontal,
faExpandAlt,
faPlus,
faQuestion,
faAngleDoubleLeft,
faAngleRight,
faAngleLeft,
faEllipsisV,
faBars,
faFileExport,
faToggleOn,
faToggleOff,
faAngleUp,
faAngleDown,
faBold,
faItalic,
faUnderline,
faStrikethrough,
faQuoteRight,
faCode,
faListUl,
faListOl,
faOutdent,
faIndent,
faAlignLeft,
faAlignCenter,
faAlignRight,
faAlignJustify,
faLink,
faRemoveFormat,
faParagraph,
faTasks,
faTable,
faBell,
faBellSolid,
faBellSlash,
faEnvelope,
faEnvelopeOpen,
faFilter,
)

View File

@@ -0,0 +1,13 @@
import Vue from 'vue'
import { FontAwesomeIcon, FontAwesomeLayers } from '@fortawesome/vue-fontawesome'
import './faIcons'
import PortalVue from 'portal-vue'
import { components } from '@cortezaproject/corteza-vue'
Vue.use(PortalVue)
Vue.component('font-awesome-icon', FontAwesomeIcon)
Vue.component('font-awesome-layers', FontAwesomeLayers)
Vue.component('c-permissions-button', components.CPermissionsButton)
Vue.component('c-input-confirm', components.CInputConfirm)
Vue.component('c-button-submit', components.CButtonSubmit)
Vue.component('c-input-select', components.CInputSelect)

View File

@@ -0,0 +1,8 @@
[
'CortezaAPI',
].forEach((cfg) => {
if (window[cfg] === undefined) {
throw new Error(`Missing or invalid configuration.
Make sure there is a public/config.js configuration file with window.${cfg} entry.`)
}
})

View File

@@ -0,0 +1,6 @@
/* eslint-disable no-undef */
const text = `%c${WEBAPP || 'Corteza Webapp'}, version: ${VERSION}, build time: ${BUILD_TIME}`
const style = 'background-color: #1397CB; color: white; padding: 3px 10px; border: 1px solid black; font: Courier'
/* eslint-disable no-console */
console.log(text, style)

View File

@@ -0,0 +1,42 @@
import Vue from 'vue'
import Router from 'vue-router'
import BootstrapVue from 'bootstrap-vue'
import c3catalogue from './components/C3'
import { components, i18n } from '@cortezaproject/corteza-vue'
import './components'
import './themes'
const routes = [
{
path: '/c3',
name: 'c3',
component: components.C3.View,
props: { catalogue: c3catalogue },
},
{ path: '*', redirect: { name: 'c3' } },
]
Vue.use(Router)
Vue.use(BootstrapVue)
export default new Vue({
el: '#app',
name: 'DevEnv',
template: '<router-view/>',
router: new Router({
mode: 'history',
routes,
}),
i18n: i18n(Vue,
{ app: 'corteza-webapp-workflow' },
'configurator',
'editor',
'help',
'general',
'navigation',
'notification',
'permissions',
'configurator',
'steps',
),
})

View File

@@ -0,0 +1,6 @@
import Vue from 'vue'
import { filters } from '@cortezaproject/corteza-vue'
for (const n in filters) {
Vue.filter(n, filters[n])
}

View File

@@ -0,0 +1,77 @@
export function encodeGraph (model, vertices, edges) {
const steps = []
const paths = {}
const triggers = []
Object.values(model.cells)
.filter(cell => {
return !!cell.vertex
}).forEach(cell => {
const triggerEdges = []
const defaultName = vertices[cell.id].config.defaultName || false
cell = {
id: cell.id,
value: cell.value,
defaultName,
xywh: [
cell.geometry.x || 0,
cell.geometry.y || 0,
cell.geometry.width || 0,
cell.geometry.height || 0,
],
parent: cell.parent.id,
edges: (cell.edges || []).forEach(({ id, value, parent, source, target, geometry, style }) => {
const edge = {
...((edges[id] || {}).config || {}),
parentID: source.id,
childID: target.id,
meta: {
label: value || '',
description: '',
visual: {
id,
value,
parent: parent.id,
points: geometry.points,
style,
},
},
}
if (vertices[source.id].triggers || vertices[target.id].triggers) {
triggerEdges.push(edge)
} else if (!paths[id]) {
paths[id] = edge
}
}),
}
if (vertices[cell.id].triggers) {
cell.edges = triggerEdges
triggers.push({
...vertices[cell.id].triggers,
stepID: (triggerEdges[0] || { childID: '0' }).childID,
enabled: vertices[cell.id].triggers.enabled,
constraints: vertices[cell.id].triggers.constraints,
meta: {
name: cell.value || '',
description: '',
visual: cell,
},
})
} else {
steps.push({
...vertices[cell.id].config,
meta: {
label: cell.value || '',
description: '',
visual: cell,
},
})
}
})
return { steps, paths: Object.values(paths), triggers }
}

View File

@@ -0,0 +1,21 @@
/**
* Constraint utility functions
*/
/**
* Converts a constraint name to a human-readable label
* @param {string} name - The constraint name (e.g., "user.email")
* @returns {string} The formatted label (e.g., "User Email")
*/
export function getConstraintNameLabel (name) {
if (!name) return ''
return name
.split('.')
.map(s => {
const first = s[0] || ''
const rest = s.slice(1) || ''
return first.toUpperCase() + rest.toLowerCase()
})
.join(' ')
}

View File

@@ -0,0 +1,146 @@
import { automation } from '@cortezaproject/corteza-js'
export async function encodeInput (initialScope, ComposeAPI, SystemAPI) {
const ev = { args: {} }
// Types that can and must be fetched
const {
namespace,
oldNamespace,
module,
oldModule,
page,
oldPage,
record,
oldRecord,
user,
oldUser,
role,
oldRole,
application,
oldApplication,
} = initialScope
if (namespace && namespace.value) {
await ComposeAPI.namespaceList({ slug: namespace.value })
.then(({ set = [] }) => {
const [ns] = set
if (ns) {
ev.args.namespace = { ...ns, resourceType: 'compose:namespace' }
}
})
.catch(() => {})
}
if (oldNamespace && oldNamespace.value) {
await ComposeAPI.namespaceList({ slug: oldNamespace.value })
.then(({ set = [] }) => {
const [ns] = set
if (ns) {
ev.args.oldNamespace = { ...ns, resourceType: 'compose:namespace' }
}
})
.catch(() => {})
}
if (module && module.value && ev.args.namespace) {
await ComposeAPI.moduleList({ namespaceID: ev.args.namespace.namespaceID, handle: module.value })
.then(({ set = [] }) => {
const [m] = set
if (m) {
ev.args.module = { ...m, resourceType: 'compose:module' }
}
})
.catch(() => {})
}
if (oldModule && oldModule.value && ev.args.namespace) {
await ComposeAPI.moduleList({ namespaceID: ev.args.namespace.namespaceID, handle: oldModule.value })
.then(({ set = [] }) => {
const [m] = set
if (m) {
ev.args.oldModule = { ...m, resourceType: 'compose:module' }
}
})
.catch(() => {})
}
if (page && page.value && ev.args.namespace) {
await ComposeAPI.pageRead({ pageID: page.value, namespaceID: ev.args.namespace.namespaceID })
.then(p => {
ev.args.page = { ...p }
})
}
if (oldPage && oldPage.value && ev.args.namespace) {
await ComposeAPI.pageRead({ pageID: page.value, namespaceID: ev.args.namespace.namespaceID })
.then(p => {
ev.args.oldPage = { ...p }
})
}
if (record && record.value && ev.args.module && ev.args.namespace) {
await ComposeAPI.recordRead({ recordID: record.value, moduleID: ev.args.module.moduleID, namespaceID: ev.args.namespace.namespaceID })
.then(r => {
ev.args.record = { ...r, resourceType: 'compose:record' }
})
}
if (oldRecord && oldRecord.value && ev.args.module && ev.args.namespace) {
await ComposeAPI.recordRead({ recordID: oldRecord.value, moduleID: ev.args.module.moduleID, namespaceID: ev.args.namespace.namespaceID })
.then(r => {
ev.args.oldRecord = { ...r, resourceType: 'compose:record' }
})
}
if (user && user.value) {
await SystemAPI.userRead({ userID: user.value })
.then(u => {
ev.args.user = { ...u, resourceType: 'User' }
})
}
if (oldUser && oldUser.value) {
await SystemAPI.oldUserRead({ userID: oldUser.value })
.then(u => {
ev.args.oldUser = { ...u, resourceType: 'User' }
})
}
if (role && role.value) {
await SystemAPI.roleRead({ roleID: role.value })
.then(r => {
ev.args.role = { ...r, resourceType: 'Role' }
})
}
if (oldRole && oldRole.value) {
await SystemAPI.roleRead({ roleID: oldRole.value })
.then(r => {
ev.args.oldRole = { ...r, resourceType: 'Role' }
})
}
if (application && application.value) {
await SystemAPI.applicationRead({ applicationID: application.value })
.then(a => {
ev.args.application = { ...a }
})
}
if (oldApplication && oldApplication.value) {
await SystemAPI.applicationRead({ applicationID: oldApplication.value })
.then(a => {
ev.args.oldApplication = { ...a }
})
}
// Add rest to args
Object.entries(initialScope).forEach(([key, value]) => {
if (!ev.args[key]) {
ev.args[key] = {}
}
})
return automation.Encode(ev.args)
}

View File

@@ -0,0 +1,73 @@
export const EXPRESSION_EDITOR_AUTO_COMPLETE_VALUES = [
{ value: 'push(', caption: 'push(array, ...elements)' },
{ value: 'pop(', caption: 'pop(array)' },
{ value: 'shift(', caption: 'shift(array)' },
{ value: 'count(', caption: 'count(array, ...elements)' },
{ value: 'has(', caption: 'has(arr, ...elements)' },
{ value: 'hasAll(', caption: 'hasAll(arr, ...elements)' },
{ value: 'find(', caption: 'find(arr, elements)' },
{ value: 'sort(', caption: 'sort(array, descending)' },
{ value: 'splice(', caption: 'splice(array, start, end)' },
{ value: 'min(', caption: 'min(...number)' },
{ value: 'max(', caption: 'max(...number)' },
{ value: 'round(', caption: 'round(number, places)' },
{ value: 'floor(', caption: 'floor(number)' },
{ value: 'ceil(', caption: 'ceil(number)' },
{ value: 'abs(', caption: 'abs(number)' },
{ value: 'log(', caption: 'log(number)' },
{ value: 'pow(', caption: 'pow(number, exp)' },
{ value: 'sqrt(', caption: 'sqrt(number)' },
{ value: 'sum(', caption: 'sum(...number)' },
{ value: 'average(', caption: 'average(...number)' },
{ value: 'random(', caption: 'random(a, b?)' },
{ value: 'int(', caption: 'int(Any)' },
{ value: 'float(', caption: 'float(Any)' },
{ value: 'toJSON(', caption: 'toJSON(string)' },
{ value: 'trim(', caption: 'trim(string)' },
{ value: 'trimLeft(', caption: 'trimLeft(string, remove)' },
{ value: 'trimRight(', caption: 'trimRight(string, remove)' },
{ value: 'toLower(', caption: 'toLower(string)' },
{ value: 'toUpper(', caption: 'toUpper(string)' },
{ value: 'shortest(', caption: 'shortest(string1, ...strings)' },
{ value: 'longest(', caption: 'longest(arg1, arg2, ...arguments)' },
{ value: 'format(', caption: 'format(format, ...arguments)' },
{ value: 'title(', caption: 'title(string)' },
{ value: 'untitle(', caption: 'untitle(string)' },
{ value: 'repeat(', caption: 'repeat(string, count)' },
{ value: 'replace(', caption: 'replace(string, old, new, count)' },
{ value: 'isUrl(', caption: 'isUrl(string)' },
{ value: 'isEmail(', caption: 'isEmail(string)' },
{ value: 'split(', caption: 'split(string, separator)' },
{ value: 'join(', caption: 'join(strings, separator)' },
{ value: 'hasSubstring(', caption: 'hasSubstring(string, substring, case)' },
{ value: 'substring(', caption: 'substring(string, start, end)' },
{ value: 'hasPrefix(', caption: 'hasPrefix(string, prefix)' },
{ value: 'hasSuffix(', caption: 'hasSuffix(string, prefix)' },
{ value: 'shorten(', caption: 'shorten(string, type, count)' },
{ value: 'camelize(', caption: 'camelize(string)' },
{ value: 'snakify(', caption: 'snakify(string)' },
{ value: 'match(', caption: 'match(string, regex)' },
{ value: 'base64encode(', caption: 'base64encode(string)' },
{ value: 'set(', caption: 'set(kv, k, v)' },
{ value: 'merge(', caption: 'merge(kv, ...kv)' },
{ value: 'filter(', caption: 'filter(kv, ...include)' },
{ value: 'omit(', caption: 'omit(kv, ...exclude)' },
{ value: 'coalesce(', caption: 'coalesce(...Any)' },
{ value: 'isEmpty(', caption: 'isEmpty(Any)' },
{ value: 'isNil(', caption: 'isNil(Any)' },
{ value: 'length(', caption: 'length(array)' },
{ value: 'earliest(', caption: 'earliest(DateTime, ...DateTime)' },
{ value: 'latest(', caption: 'latest(DateTime, ...DateTime)' },
{ value: 'parseISOTime(', caption: 'parseISOTime(string)' },
{ value: 'modTime(', caption: 'modTime(datetime, duration)' },
{ value: 'modDate(', caption: 'modDate(datetime, days)' },
{ value: 'modWeek(', caption: 'modWeek(datetime, weeks)' },
{ value: 'modMonth(', caption: 'modMonth(datetime, months)' },
{ value: 'modYear(', caption: 'modYear(datetime, years)' },
{ value: 'parseDuration(', caption: 'parseDuration(string)' },
{ value: 'strftime(', caption: 'strftime(datetime, pattern)' },
{ value: 'isLeapYear(', caption: 'isLeapYear(datetime)' },
{ value: 'now(', caption: 'now()' },
{ value: 'isWeekDay(', caption: 'isWeekDay(datetime)' },
{ value: 'sub(', caption: 'sub(from, to)' },
]

View File

@@ -0,0 +1,13 @@
import { filter } from '@cortezaproject/corteza-vue'
export function objectSearchMaker (field, ...fields) {
return function (opts, search) {
return opts.filter(o => filter.Assert(o, search, field, ...fields))
}
}
export function stringSearchMaker () {
return function (opts, search) {
return opts.filter(o => filter.Assert(o, search))
}
}

View File

@@ -0,0 +1,17 @@
/**
* String utility functions
*/
/**
* Converts a camelCase string to Title Case with spaces
* @param {string} str - The camelCase string to convert
* @returns {string} The converted title case string
*/
export function camelToTitle (str) {
if (!str) return ''
return str
.split(/(?=[A-Z])/)
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(' ')
}

Some files were not shown because too many files have changed in this diff Show More