diff --git a/.gitignore b/.gitignore index 01131591d9..641fdbfc19 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ yarn-error.log /public/img /public/fonts /public/mix-manifest.json +/public/stats.json tests/js/coverage tests/coverage-report tests/coverage-report/* diff --git a/ProcessMaker/Http/Controllers/CasesController.php b/ProcessMaker/Http/Controllers/CasesController.php index 88d48593ad..5ed1f0a89c 100644 --- a/ProcessMaker/Http/Controllers/CasesController.php +++ b/ProcessMaker/Http/Controllers/CasesController.php @@ -25,10 +25,12 @@ class CasesController extends Controller */ public function index() { + $manager = app(ScreenBuilderManager::class); + event(new ScreenBuilderStarting($manager, 'FORM')); $currentUser = Auth::user()->only(['id', 'username', 'fullname', 'firstname', 'lastname', 'avatar']); // This is a temporary API the engine team will provide the new - return view('cases.casesMain', compact('currentUser')); + return view('cases.casesMain', compact('currentUser', 'manager')); } /** diff --git a/package-lock.json b/package-lock.json index 97775d2196..11b77b90cc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -105,7 +105,8 @@ "sass-loader": "^12.6.0", "tailwindcss": "^3.4.10", "vue-loader": "^15.10.0", - "vue-template-compiler": "^2.7.16" + "vue-template-compiler": "^2.7.16", + "webpack-bundle-analyzer": "^4.10.2" }, "engines": { "node": ">=16", @@ -3748,6 +3749,13 @@ "node": ">=14" } }, + "node_modules/@polka/url": { + "version": "1.0.0-next.28", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz", + "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==", + "dev": true, + "license": "MIT" + }, "node_modules/@processmaker/modeler": { "version": "1.64.0", "resolved": "https://registry.npmjs.org/@processmaker/modeler/-/modeler-1.64.0.tgz", @@ -6898,6 +6906,19 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/adjust-sourcemap-loader": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-3.0.0.tgz", @@ -10014,6 +10035,13 @@ "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", "dev": true }, + "node_modules/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", + "dev": true, + "license": "MIT" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -10516,6 +10544,13 @@ "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==" }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true, + "license": "MIT" + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -12513,6 +12548,22 @@ "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", "integrity": "sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==" }, + "node_modules/gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -12758,6 +12809,13 @@ "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==" }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, "node_modules/html-loader": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/html-loader/-/html-loader-1.3.2.tgz", @@ -13957,7 +14015,7 @@ "resolved": "https://registry.npmjs.org/isomorphic.js/-/isomorphic.js-0.2.5.tgz", "integrity": "sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==", "funding": { - "type": "GitHub Sponsors \u2764", + "type": "GitHub Sponsors ❤", "url": "https://github.com/sponsors/dmonad" } }, @@ -15088,7 +15146,7 @@ "node": ">=16" }, "funding": { - "type": "GitHub Sponsors \u2764", + "type": "GitHub Sponsors ❤", "url": "https://github.com/sponsors/dmonad" } }, @@ -16215,6 +16273,16 @@ "node": ">=4" } }, + "node_modules/mrmime": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", + "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -19680,6 +19748,21 @@ "resolved": "https://registry.npmjs.org/simple-uploader.js/-/simple-uploader.js-0.5.6.tgz", "integrity": "sha512-ukjL0wZhK1dNMaQa6sd+UpCSmnUjblaUGbAd/B8f5IFrChMzDsC/7eFSK4bs4BS5NPJFSZVLI+l6Ri7THTkQtw==" }, + "node_modules/sirv": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", + "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -21221,6 +21304,16 @@ "popper.js": "^1.0.2" } }, + "node_modules/totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", @@ -22378,6 +22471,56 @@ } } }, + "node_modules/webpack-bundle-analyzer": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz", + "integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@discoveryjs/json-ext": "0.5.7", + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "commander": "^7.2.0", + "debounce": "^1.2.1", + "escape-string-regexp": "^4.0.0", + "gzip-size": "^6.0.0", + "html-escaper": "^2.0.2", + "opener": "^1.5.2", + "picocolors": "^1.0.0", + "sirv": "^2.0.3", + "ws": "^7.3.1" + }, + "bin": { + "webpack-bundle-analyzer": "lib/bin/analyzer.js" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/webpack-cli": { "version": "4.10.0", "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", @@ -23187,7 +23330,7 @@ "lib0": "^0.2.31" }, "funding": { - "type": "GitHub Sponsors \u2764", + "type": "GitHub Sponsors ❤", "url": "https://github.com/sponsors/dmonad" }, "peerDependencies": { @@ -23206,7 +23349,7 @@ "npm": ">=8.0.0" }, "funding": { - "type": "GitHub Sponsors \u2764", + "type": "GitHub Sponsors ❤", "url": "https://github.com/sponsors/dmonad" }, "peerDependencies": { @@ -23231,7 +23374,7 @@ "npm": ">=8.0.0" }, "funding": { - "type": "GitHub Sponsors \u2764", + "type": "GitHub Sponsors ❤", "url": "https://github.com/sponsors/dmonad" }, "optionalDependencies": { @@ -23469,7 +23612,7 @@ "npm": ">=8.0.0" }, "funding": { - "type": "GitHub Sponsors \u2764", + "type": "GitHub Sponsors ❤", "url": "https://github.com/sponsors/dmonad" } }, @@ -25855,6 +25998,12 @@ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", "optional": true }, + "@polka/url": { + "version": "1.0.0-next.28", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.28.tgz", + "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==", + "dev": true + }, "@processmaker/modeler": { "version": "1.64.0", "resolved": "https://registry.npmjs.org/@processmaker/modeler/-/modeler-1.64.0.tgz", @@ -27986,6 +28135,15 @@ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "requires": {} }, + "acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "requires": { + "acorn": "^8.11.0" + } + }, "adjust-sourcemap-loader": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/adjust-sourcemap-loader/-/adjust-sourcemap-loader-3.0.0.tgz", @@ -30371,6 +30529,12 @@ "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", "dev": true }, + "debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", + "dev": true + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -30733,6 +30897,12 @@ "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==" }, + "duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, "eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", @@ -32270,6 +32440,15 @@ "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", "integrity": "sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==" }, + "gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "dev": true, + "requires": { + "duplexer": "^0.1.2" + } + }, "handle-thing": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.1.tgz", @@ -32459,6 +32638,12 @@ "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.3.3.tgz", "integrity": "sha512-DV5Ln36z34NNTDgnz0EWGBLZENelNAtkiFA4kyNOG2tDI6Mz1uSWiq1wAKdyjnJwyDiDO7Fa2SO1CTxPXL8VxA==" }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, "html-loader": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/html-loader/-/html-loader-1.3.2.tgz", @@ -35027,6 +35212,12 @@ "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==" }, + "mrmime": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", + "integrity": "sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==", + "dev": true + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -37525,6 +37716,17 @@ "resolved": "https://registry.npmjs.org/simple-uploader.js/-/simple-uploader.js-0.5.6.tgz", "integrity": "sha512-ukjL0wZhK1dNMaQa6sd+UpCSmnUjblaUGbAd/B8f5IFrChMzDsC/7eFSK4bs4BS5NPJFSZVLI+l6Ri7THTkQtw==" }, + "sirv": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", + "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==", + "dev": true, + "requires": { + "@polka/url": "^1.0.0-next.24", + "mrmime": "^2.0.0", + "totalist": "^3.0.0" + } + }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -38695,6 +38897,12 @@ "popper.js": "^1.0.2" } }, + "totalist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-3.0.1.tgz", + "integrity": "sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==", + "dev": true + }, "tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", @@ -39601,6 +39809,40 @@ } } }, + "webpack-bundle-analyzer": { + "version": "4.10.2", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.10.2.tgz", + "integrity": "sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==", + "dev": true, + "requires": { + "@discoveryjs/json-ext": "0.5.7", + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "commander": "^7.2.0", + "debounce": "^1.2.1", + "escape-string-regexp": "^4.0.0", + "gzip-size": "^6.0.0", + "html-escaper": "^2.0.2", + "opener": "^1.5.2", + "picocolors": "^1.0.0", + "sirv": "^2.0.3", + "ws": "^7.3.1" + }, + "dependencies": { + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + } + } + }, "webpack-cli": { "version": "4.10.0", "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz", diff --git a/package.json b/package.json index 074d7c1179..9176d9f55f 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "test-watch": "npm run test -- --watch --notify", "lint": "eslint --fix --ignore-path .eslintignore resources/", "font": "node ./devhub/pm-font/svgtofont.js && cp -f ./devhub/pm-font/dist/*.* ./resources/fonts/pm-font", - "dev-font": "node ./devhub/pm-font/svgtofont.js && http-server ./devhub/pm-font/dist -p 7771 && cp -f ./devhub/pm-font/dist/*.* ./resources/fonts/pm-font " + "dev-font": "node ./devhub/pm-font/svgtofont.js && http-server ./devhub/pm-font/dist -p 7771 && cp -f ./devhub/pm-font/dist/*.* ./resources/fonts/pm-font ", + "stats": "STATS=1 NODE_OPTIONS=\"--max-old-space-size=8000\" mix" }, "devDependencies": { "@babel/eslint-parser": "^7.15.8", @@ -45,7 +46,8 @@ "sass-loader": "^12.6.0", "tailwindcss": "^3.4.10", "vue-loader": "^15.10.0", - "vue-template-compiler": "^2.7.16" + "vue-template-compiler": "^2.7.16", + "webpack-bundle-analyzer": "^4.10.2" }, "dependencies": { "@babel/runtime": "^7.23.8", diff --git a/resources/js/components/TreeView.vue b/resources/js/components/TreeView.vue index e60a779c4e..459fdcdcb6 100644 --- a/resources/js/components/TreeView.vue +++ b/resources/js/components/TreeView.vue @@ -37,7 +37,7 @@ export default { }, }, mounted() { - const jsonCrackEmbed = this.$refs.jsonCrackEmbed; + const { jsonCrackEmbed } = this.$refs; const json = this.jsonData === "" ? "{}" : this.jsonData; const options = { theme: "light", diff --git a/resources/js/initialLoad.js b/resources/js/initialLoad.js new file mode 100644 index 0000000000..696f23034d --- /dev/null +++ b/resources/js/initialLoad.js @@ -0,0 +1,494 @@ +/* eslint-disable func-names */ +// Our initial node types to register with our modeler +import { + association, + endEvent, + terminateEndEvent, + exclusiveGateway, + inclusiveGateway, + parallelGateway, + sequenceFlow, + startEvent, + task, + scriptTask, + manualTask, + pool, + poolLane, + textAnnotation, + messageFlow, + serviceTask, + callActivity, + eventBasedGateway, + intermediateMessageCatchEvent, + intermediateSignalThrowEvent, + signalEndEvent, + loopCharacteristicsInspector, + loopCharacteristicsHandler, + loopCharacteristicsData, + NodeIdentifierInput, +} from "@processmaker/modeler"; +import { registerNodes } from "@processmaker/modeler"; +import i18next from "i18next"; +import ModelerScreenSelect from "./processes/modeler/components/inspector/ScreenSelect.vue"; +import UserSelect from "./processes/modeler/components/inspector/UserSelect.vue"; +import GroupSelect from "./processes/modeler/components/inspector/GroupSelect.vue"; +import UserById from "./processes/modeler/components/inspector/UserById.vue"; +import TaskNotifications from "./processes/modeler/components/inspector/TaskNotifications.vue"; +import ExpressionEditor from "./processes/modeler/components/inspector/ExpressionEditor.vue"; +import TaskAssignment from "./processes/modeler/components/inspector/TaskAssignment.vue"; +import TaskDueIn from "./processes/modeler/components/inspector/TaskDueIn.vue"; +import GatewayFlowVariable from "./processes/modeler/components/inspector/GatewayFlowVariable.vue"; +import ConfigEditor from "./processes/modeler/components/inspector/ConfigEditor.vue"; +import SignalPayload from "./processes/modeler/components/inspector/SignalPayload.vue"; +import ScriptSelect from "./processes/modeler/components/inspector/ScriptSelect.vue"; +import StartPermission from "./processes/modeler/components/inspector/StartPermission.vue"; +import Interstitial from "./processes/modeler/components/inspector/Interstitial.vue"; +import SelectUserGroup from "./components/SelectUserGroup.vue"; +import validateScreenRef from "./processes/modeler/validateScreenRef"; +import validateFlowGenieRef from "./processes/modeler/validateFlowGenieRef"; +import ErrorHandlingTimeout from "./processes/modeler/components/inspector/ErrorHandlingTimeout.vue"; +import ErrorHandlingRetryAttempts from "./processes/modeler/components/inspector/ErrorHandlingRetryAttempts.vue"; +import ErrorHandlingRetryWaitTime from "./processes/modeler/components/inspector/ErrorHandlingRetryWaitTime.vue"; +import NotifyProcessManager from "./processes/modeler/components/inspector/NotifyProcessManager.vue"; + +Vue.component("UserSelect", UserSelect); +Vue.component("UserById", UserById); +Vue.component("GroupSelect", GroupSelect); +Vue.component("ModelerScreenSelect", ModelerScreenSelect); +Vue.component("TaskNotifications", TaskNotifications); +Vue.component("ExpressionEditor", ExpressionEditor); +Vue.component("TaskAssignment", TaskAssignment); +Vue.component("TaskDueIn", TaskDueIn); +Vue.component("GatewayFlowVariable", GatewayFlowVariable); +Vue.component("ConfigEditor", ConfigEditor); +Vue.component("SignalPayload", SignalPayload); +Vue.component("ScriptSelect", ScriptSelect); +Vue.component("StartPermission", StartPermission); +Vue.component("Interstitial", Interstitial); +Vue.component("SelectUserGroup", SelectUserGroup); +Vue.component("NodeIdentifierInput", NodeIdentifierInput); +Vue.component("ErrorHandlingTimeout", ErrorHandlingTimeout); +Vue.component("ErrorHandlingRetryAttempts", ErrorHandlingRetryAttempts); +Vue.component("ErrorHandlingRetryWaitTime", ErrorHandlingRetryWaitTime); +Vue.component("NotifyProcessManager", NotifyProcessManager); + +const nodeTypes = [ + endEvent, + task, + scriptTask, + manualTask, + callActivity, + exclusiveGateway, + inclusiveGateway, + eventBasedGateway, + parallelGateway, + sequenceFlow, + association, + pool, + poolLane, + messageFlow, + serviceTask, + textAnnotation, + intermediateMessageCatchEvent, + intermediateSignalThrowEvent, + signalEndEvent, + eventBasedGateway, +]; + +ProcessMaker.nodeTypes.push(startEvent); +ProcessMaker.nodeTypes.push(...nodeTypes); +ProcessMaker.modelerExtensions = { + loopCharacteristicsInspector, + loopCharacteristicsHandler, + loopCharacteristicsData, + NodeIdentifierInput, +}; + +ProcessMaker.EventBus.$on("modeler-init", registerNodes); + +ProcessMaker.EventBus.$on( + "modeler-init", + ({ registerInspectorExtension }) => { + /* Register extension for start permission */ + registerInspectorExtension(startEvent, { + component: "FormAccordion", + container: true, + config: { + initiallyOpen: false, + label: i18next.t("Start Permissions"), + icon: "user-shield", + name: "permissions-accordion", + }, + items: [ + { + component: "StartPermission", + config: { + label: "Permission To Start", + helper: "Select who may start a Request of this Process", + userHelper: "Select who may start a Request", + groupHelper: "Select the group from which any user may start a Request", + name: "startPermission", + }, + }, + ], + }); + registerInspectorExtension(startEvent, { + component: "Interstitial", + config: { + label: "Display Next Assigned Task to Task Assignee", + helper: "Directs Task assignee to the next assigned Task", + name: "interstitial", + enabledByDefault: true, + }, + }); + + /* Register the inspector extensions for tasks */ + registerInspectorExtension(task, { + component: "ModelerScreenSelect", + config: { + label: "Screen for Input", + helper: "Select Screen to display this Task", + name: "screenRef", + required: true, + params: { + type: "FORM,CONVERSATIONAL", + interactive: true, + }, + }, + }); + + registerInspectorExtension(task, { + component: "TaskDueIn", + config: { + label: "Due In", + helper: "Time when the task will be due", + name: "taskDueIn", + }, + }); + registerInspectorExtension(task, { + component: "FormAccordion", + container: true, + config: { + initiallyOpen: false, + label: i18next.t("Assignment Rules"), + icon: "users", + name: "assignments-accordion", + }, + items: [ + { + component: "TaskAssignment", + config: { + label: "Assignment Type", + helper: "", + name: "taskAssignment", + }, + }, + ], + }); + registerInspectorExtension(task, { + component: "FormAccordion", + container: true, + config: { + initiallyOpen: false, + label: i18next.t("Notifications"), + icon: "bell", + name: "notifications-accordion", + }, + items: [ + { + component: "TaskNotifications", + config: { + helper: "Users that should be notified about task events", + }, + }, + ], + }); + + registerInspectorExtension(task, { + component: "Interstitial", + config: { + label: "Display Next Assigned Task to Task Assignee", + helper: "Directs Task assignee to the next assigned Task", + name: "interstitial", + }, + }); + + /* Register the inspector extensions for script tasks */ + registerInspectorExtension(scriptTask, { + component: "ScriptSelect", + config: { + label: "Script", + helper: "Select the Script this element runs", + name: "scriptRef", + required: true, + }, + }); + + registerInspectorExtension(scriptTask, { + component: "FormAccordion", + container: true, + config: { + initiallyOpen: false, + label: i18next.t("Error Handling"), + icon: "exclamation-triangle", + name: "error-handling-accordion", + }, + items: [ + { + component: "ErrorHandlingTimeout", + config: { + type: "script", + }, + }, + { + component: "ErrorHandlingRetryAttempts", + config: { + type: "script", + }, + }, + { + component: "ErrorHandlingRetryWaitTime", + config: { + type: "script", + }, + }, + { + component: "NotifyProcessManager", + config: { + type: "script", + }, + }, + ], + }); + + registerInspectorExtension(scriptTask, { + component: "ConfigEditor", + config: { + label: "Script Configuration", + helper: "Enter the JSON to configure the Script", + name: "config", + }, + }); + registerInspectorExtension(endEvent, { + component: "ModelerScreenSelect", + config: { + label: "Summary Screen", + helper: + "Select Display-type Screen to show the summary of this Request when it completes", + name: "screenRef", + params: { type: "DISPLAY" }, + }, + }); + registerInspectorExtension(signalEndEvent, { + component: "ModelerScreenSelect", + config: { + label: "Summary Screen", + helper: + "Select Display-type Screen to show the summary of this Request when it completes", + name: "screenRef", + params: { type: "DISPLAY" }, + }, + }); + registerInspectorExtension(terminateEndEvent, { + component: "ModelerScreenSelect", + config: { + label: "Summary Screen", + helper: + "Select Display-type Screen to show the summary of this Request when it completes", + name: "screenRef", + params: { type: "DISPLAY" }, + }, + }); + registerInspectorExtension(manualTask, { + component: "ModelerScreenSelect", + config: { + label: "Screen for Input", + helper: + "Select Screen to display this Task", + name: "screenRef", + params: { type: "DISPLAY" }, + required: true, + }, + }); + registerInspectorExtension(manualTask, { + component: "TaskDueIn", + config: { + label: "Due In", + helper: "Enter the hours until this Task is overdue", + name: "taskDueIn", + }, + }); + registerInspectorExtension(manualTask, { + component: "FormAccordion", + container: true, + config: { + initiallyOpen: false, + label: i18next.t("Assignment Rules"), + icon: "users", + name: "assignments-accordion", + }, + items: [ + { + component: "TaskAssignment", + config: { + label: "Task Assignment", + helper: "", + name: "taskAssignment", + }, + }, + ], + }); + registerInspectorExtension(manualTask, { + component: "FormAccordion", + container: true, + config: { + initiallyOpen: false, + label: i18next.t("Notifications"), + icon: "bell", + name: "notifications-accordion", + }, + items: [ + { + component: "TaskNotifications", + config: { + helper: "Users that should be notified about task events", + }, + }, + ], + }); + registerInspectorExtension(manualTask, { + component: "Interstitial", + config: { + label: "Enable Interstitial", + helper: "redirected to my next assigned task", + name: "interstitial", + }, + }); + + /* Register extension for intermediate message catch event */ + registerInspectorExtension(intermediateMessageCatchEvent, { + component: "UserSelect", + config: { + label: "Allowed User", + helper: "Select allowed user", + name: "allowedUsers", + }, + }); + + registerInspectorExtension(intermediateMessageCatchEvent, { + component: "GroupSelect", + config: { + label: "Allowed Group", + helper: "Select allowed group", + name: "allowedGroups", + }, + }); + + registerInspectorExtension(intermediateMessageCatchEvent, { + component: "FormInput", + config: { + label: i18next.t("Whitelist"), + helper: i18next.t("IP/Domain whitelist"), + name: "whitelist", + }, + }); + + registerInspectorExtension(sequenceFlow, { + component: "GatewayFlowVariable", + config: { + label: "Screen for Input", + helper: "Select Screen to display this Task", + name: "FlowVariable", + }, + }); + + registerInspectorExtension(callActivity, { + component: "FormAccordion", + container: true, + config: { + initiallyOpen: false, + label: i18next.t("Assignment Rules"), + icon: "users", + name: "assignments-accordion", + }, + items: [ + { + component: "TaskAssignment", + config: { + label: "Start Sub Process As", + helper: "", + name: "taskAssignment", + configurables: [], + assignmentTypes: [ + { + value: "", + label: "Anonymous", + }, + { + value: "requester", + label: "Requester", + }, + { + value: "user_group", + label: "Users / Groups", + }, + { + value: "previous_task_assignee", + label: "Previous Task Assignee", + }, + { + value: "process_variable", + label: "Process Variable", + }, + ], + }, + }, + ], + }); + + registerInspectorExtension(intermediateSignalThrowEvent, { + component: "SignalPayload", + config: { + label: "Payload Type", + helper: "data that will be sent as payload", + name: "interstitial", + }, + }); + + registerInspectorExtension(signalEndEvent, { + component: "SignalPayload", + config: { + label: "Payload Type", + helper: "data that will be sent as payload", + name: "interstitial", + }, + }); + }, +); + +ProcessMaker.EventBus.$on( + "modeler-init", + (event) => { + event.registerPreview({ + url: "/designer/screens/preview", + assetUrl: (nodeData) => (nodeData.screenRef ? `/designer/screen-builder/${nodeData.screenRef}/edit` : null), + receivingParams: ["screenRef"], + matcher: (nodeData) => nodeData?.$type === "bpmn:Task", + }); + event.registerPreview({ + url: "/designer/screens/preview", + assetUrl: (nodeData) => (nodeData.screenRef ? `/designer/screen-builder/${nodeData.screenRef}/edit` : null), + receivingParams: ["screenRef"], + matcher: (nodeData) => nodeData?.$type === "bpmn:ManualTask", + }); + event.registerPreview({ + url: "/designer/scripts/preview", + assetUrl: (nodeData) => (nodeData.scriptRef ? `/designer/scripts/${nodeData.scriptRef}/builder` : null), + receivingParams: ["scriptRef"], + matcher: (nodeData) => nodeData?.$type === "bpmn:ScriptTask", + }); + }, +); + +validateScreenRef(); +validateFlowGenieRef(); diff --git a/resources/js/next/components/index.js b/resources/js/next/components/index.js new file mode 100644 index 0000000000..564026864c --- /dev/null +++ b/resources/js/next/components/index.js @@ -0,0 +1,115 @@ +import { getGlobalVariable } from "../globalVariables"; + +// Multiselect + +const Vue = getGlobalVariable("Vue"); + +const pmComponents = { + // Components folder + AvatarImage: () => import("../../components/AvatarImage.vue"), + Breadcrumbs: () => import("../../components/Breadcrumbs.vue"), + Confirm: () => import("../../components/Confirm.vue"), + CustomActions: () => import("../../components/CustomActions.vue"), + DetailRow: () => import("../../components/DetailRow.vue"), + FilterBar: () => import("../../components/FilterBar.vue"), + Menu: () => import("../../components/Menu.vue"), + Message: () => import("../../components/Message.vue"), + NavbarProfile: () => import("../../components/NavbarProfile.vue"), + PMBadgesFilters: () => import("../../components/PMBadgesFilters.vue"), + PMDatetimePicker: () => import("../../components/PMDatetimePicker.vue"), + PMDropdownSuggest: () => import("../../components/PMDropdownSuggest.vue"), + PMFloatingButtons: () => import("../../components/PMFloatingButtons.vue"), + PMFormSelectSuggest: () => import("../../components/PMFormSelectSuggest.vue"), + PMMessageResults: () => import("../../components/PMMessageResults.vue"), + PMMessageScreen: () => import("../../components/PMMessageScreen.vue"), + PMPanelWithCustomHeader: () => import("../../components/PMPanelWithCustomHeader.vue"), + PMPopoverConfirmation: () => import("../../components/PMPopoverConfirmation.vue"), + PMSearchBar: () => import("../../components/PMSearchBar.vue"), + PMTable: () => import("../../components/PMTable.vue"), + PMTabs: () => import("../../components/PMTabs.vue"), + Recommendations: () => import("../../components/Recommendations.vue"), + SelectFromApi: () => import("../../components/SelectFromApi.vue"), + SelectLanguage: () => import("../../components/SelectLanguage.vue"), + SelectScreen: () => import("../../components/SelectScreen.vue"), + SelectStatus: () => import("../../components/SelectStatus.vue"), + SelectUser: () => import("../../components/SelectUser.vue"), + SelectUserGroup: () => import("../../components/SelectUserGroup.vue"), + Session: () => import("../../components/Session.vue"), + Sidebaricon: () => import("../../components/Sidebaricon.vue"), + Timeline: () => import("../../components/Timeline.vue"), + TimelineItem: () => import("../../components/TimelineItem.vue"), + TreeView: () => import("../../components/TreeView.vue"), + // Shared components folder + AddToBundle: () => import("../../components/shared/AddToBundle"), + AddToProjectModal: () => import("../../components/shared/AddToProjectModal"), + AssetDependentTreeModal: () => import("../../components/shared/AssetDependentTreeModal.vue"), + AssetTreeModal: () => import("../../components/shared/AssetTreeModal.vue"), + BackendSelect: () => import("../../components/shared/BackendSelect.vue"), + BasicSearch: () => import("../../components/shared/BasicSearch.vue"), + CategorySelect: () => import("../../components/shared/CategorySelect.vue"), + ChangeLog: () => import("../../components/shared/ChangeLog.vue"), + ColorSchemeSelector: () => import("../../components/shared/ColorSchemeSelector.vue"), + Column: () => import("../../components/shared/Column.vue"), + ColumnChooser: () => import("../../components/shared/ColumnChooser.vue"), + ColumnConfig: () => import("../../components/shared/ColumnConfig.vue"), + DataCard: () => import("../../components/shared/DataCard.vue"), + DataFormatSelector: () => import("../../components/shared/DataFormatSelector.vue"), + DataMaskSelector: () => import("../../components/shared/DataMaskSelector.vue"), + DataNode: () => import("../../components/shared/DataNode.vue"), + DataTree: () => import("../../components/shared/DataTree.vue"), + DownloadSvgButton: () => import("../../components/shared/DownloadSvgButton.vue"), + DraggableFileUpload: () => import("../../components/shared/DraggableFileUpload.vue"), + EllipsisMenu: () => import("../../components/shared/EllipsisMenu.vue"), + FileUploadButton: () => import("../../components/shared/FileUploadButton.vue"), + FilterTable: () => import("../../components/shared/FilterTable.vue"), + IconDropdown: () => import("../../components/shared/IconDropdown.vue"), + IconSelector: () => import("../../components/shared/IconSelector.vue"), + InputImageCarousel: () => import("../../components/shared/InputImageCarousel.vue"), + LaunchpadSettingsModal: () => import("../../components/shared/LaunchpadSettingsModal.vue"), + Modal: () => import("../../components/shared/Modal.vue"), + PmModal: () => import("../../components/shared/Modal.vue"), + ModalSaveVersion: () => import("../../components/shared/ModalSaveVersion.vue"), + MultiThumbnailFileUploader: () => import("../../components/shared/MultiThumbnailFileUploader.vue"), + PaginationTable: () => import("../../components/shared/PaginationTable.vue"), + PmqlInput: () => import("../../components/shared/PmqlInput.vue"), + PmqlInputFilters: () => import("../../components/shared/PmqlInputFilters.vue"), + ProjectSelect: () => import("../../components/shared/ProjectSelect.vue"), + PTab: () => import("../../components/shared/PTab.vue"), + PTabs: () => import("../../components/shared/PTabs.vue"), + Required: () => import("../../components/shared/Required.vue"), + SidebarButton: () => import("../../components/shared/SidebarButton.vue"), + SidebarNav: () => import("../../components/shared/SidebarNav.vue"), + SliderWithInput: () => import("../../components/shared/SliderWithInput.vue"), + // Common components folder + DataTreeToggle: () => import("../../components/common/data-tree-toggle.vue"), + // Tasks components folder + MobileTasks: () => import("../../tasks/components/MobileTasks.vue"), + NavbarTaskMobile: () => import("../../tasks/components/NavbarTaskMobile.vue"), + QuickFillPreview: () => import("../../tasks/components/QuickFillPreview.vue"), + ReassignMobileModal: () => import("../../tasks/components/ReassignMobileModal.vue"), + SplitpaneContainer: () => import("../../tasks/components/SplitpaneContainer.vue"), + TaskDetailsMobile: () => import("../../tasks/components/TaskDetailsMobile.vue"), + TaskListRowButtons: () => import("../../tasks/components/TaskListRowButtons.vue"), + TaskLoading: () => import("../../tasks/components/TaskLoading.vue"), + TaskSaveNotification: () => import("../../tasks/components/TaskSaveNotification.vue"), + TaskSavePanel: () => import("../../tasks/components/TaskSavePanel.vue"), + TasksHome: () => import("../../tasks/components/TasksHome.vue"), + TasksList: () => import("../../tasks/components/TasksList.vue"), + TasksListCounter: () => import("../../tasks/components/TasksListCounter.vue"), + TasksPreview: () => import("../../tasks/components/TasksPreview.vue"), + TaskTooltip: () => import("../../tasks/components/TaskTooltip.vue"), + TaskView: () => import("../../tasks/components/TaskView.vue"), + // Modeler components folder + ModelerAssetQuickCreate: () => import("../../processes/modeler/components/inspector/ModelerAssetQuickCreate.vue"), // Decision engine +}; + +Object.entries(pmComponents).forEach(([key, component]) => { + Vue.component(key, component); +}); + +// Multiselect +Vue.component("Multiselect", (resolve, reject) => { + import("@processmaker/vue-multiselect").then((Multiselect) => { + resolve(Multiselect.Multiselect); + }).catch(reject); +}); diff --git a/resources/js/next/config/accesibility.js b/resources/js/next/config/accesibility.js new file mode 100644 index 0000000000..b6abaad3e1 --- /dev/null +++ b/resources/js/next/config/accesibility.js @@ -0,0 +1,6 @@ +import AccessibilityMixin from "../../components/common/mixins/accessibility"; +import { getGlobalVariable } from "../globalVariables"; + +const Vue = getGlobalVariable("Vue"); + +Vue.mixin(AccessibilityMixin); diff --git a/resources/js/next/config/i18n.js b/resources/js/next/config/i18n.js new file mode 100644 index 0000000000..55c7eadd36 --- /dev/null +++ b/resources/js/next/config/i18n.js @@ -0,0 +1,62 @@ +import i18next from "i18next"; +import Backend from "i18next-chained-backend"; +import LocalStorageBackend from "i18next-localstorage-backend"; +import XHR from "i18next-xhr-backend"; +import VueI18Next from "@panter/vue-i18next"; +import { setGlobalPMVariables, setUses, getGlobalVariable } from "../globalVariables"; + +export default () => { + const Vue = getGlobalVariable("Vue"); + + const isProd = document.head.querySelector("meta[name=\"is-prod\"]")?.content === "true"; + let translationsLoaded = false; + + const mdates = JSON.parse( + document.head.querySelector("meta[name=\"i18n-mdate\"]")?.content, + ); + + const missingTranslations = new Set(); + const missingTranslation = function (value) { + if (missingTranslations.has(value)) { return; } + missingTranslations.add(value); + if (!isProd) { + console.warn("Missing Translation:", value); + } + }; + + const i18nPromise = i18next.use(Backend).init({ + lng: document.documentElement.lang, + fallbackLng: "en", // default language when no translations + returnEmptyString: false, // When a translation is an empty string, return the default language, not empty + nsSeparator: false, + keySeparator: false, + parseMissingKeyHandler(value) { + if (!translationsLoaded) { return value; } + // Report that a translation is missing + missingTranslation(value); + // Fallback to showing the english version + return value; + }, + backend: { + backends: [ + LocalStorageBackend, // Try cache first + XHR, + ], + backendOptions: [ + { versions: mdates }, + { loadPath: "/i18next/fetch/{{lng}}/_default" }, + ], + }, + }); + + i18nPromise.then(() => { translationsLoaded = true; }); + + setUses(Vue, { VueI18Next }); + Vue.mixin({ i18n: new VueI18Next(i18next) }); + setGlobalPMVariables({ + i18n: i18next, + i18nPromise, + missingTranslations, + missingTranslation, + }); +}; diff --git a/resources/js/next/config/notifications.js b/resources/js/next/config/notifications.js new file mode 100644 index 0000000000..50627949db --- /dev/null +++ b/resources/js/next/config/notifications.js @@ -0,0 +1,40 @@ +import { setGlobalPMVariables, getGlobalPMVariable } from "../globalVariables"; + +export default () => { + const apiClient = getGlobalPMVariable("apiClient"); + + const notifications = []; + + const pushNotification = (notification) => { + if (notifications.filter((x) => x.id === notification).length === 0) { + notifications.push(notification); + } + }; + + const removeNotifications = (messageIds = [], urls = []) => apiClient.put("/read_notifications", { message_ids: messageIds, routes: urls }).then(() => { + messageIds.forEach((messageId) => { + notifications.splice(notifications.findIndex((x) => x.id === messageId), 1); + }); + + urls.forEach((url) => { + const messageIndex = notifications.findIndex((x) => x.url === url); + if (messageIndex >= 0) { + removeNotifications(notifications[messageIndex].id); + } + }); + }); + + const unreadNotifications = (messageIds = [], urls = []) => apiClient.put("/unread_notifications", { message_ids: messageIds, routes: urls }); + + const $notifications = { + icons: {}, + }; + + setGlobalPMVariables({ + notifications, + pushNotification, + removeNotifications, + unreadNotifications, + $notifications, + }); +}; diff --git a/resources/js/next/config/openAI.js b/resources/js/next/config/openAI.js new file mode 100644 index 0000000000..617e58a88f --- /dev/null +++ b/resources/js/next/config/openAI.js @@ -0,0 +1,13 @@ +import { setGlobalPMVariables } from "../globalVariables"; + +export default () => { + const openAi = document.head.querySelector("meta[name=\"open-ai-nlq-to-pmql\"]"); + + setGlobalPMVariables({ + openAi: openAi ? { + enabled: openAi.content, + } : { + enabled: false, + }, + }); +}; diff --git a/resources/js/next/config/processmaker.js b/resources/js/next/config/processmaker.js new file mode 100644 index 0000000000..348227533a --- /dev/null +++ b/resources/js/next/config/processmaker.js @@ -0,0 +1,141 @@ +import axios from "axios"; +import { setGlobalPMVariables, getGlobalPMVariable } from "../globalVariables"; + +export default () => { + const token = document.head.querySelector("meta[name=\"csrf-token\"]"); + const EventBus = getGlobalPMVariable("EventBus"); + + // Setup api versions + const apiVersionConfig = [ + { version: "1.0", baseURL: "/api/1.0/" }, + { version: "1.1", baseURL: "/api/1.1/" }, + ]; + + // Set the default API timeout + let apiTimeout = 5000; + + /** + * Create a axios instance which any vue component can bring in to call + * REST api endpoints through oauth authentication + */ + + const apiClient = axios; + + apiClient.defaults.headers.common["X-Requested-With"] = "XMLHttpRequest"; + + apiClient.defaults.baseURL = apiVersionConfig[0].baseURL; + + apiClient.interceptors.request.use((config) => { + if (typeof config.url !== "string" || !config.url) { + throw new Error("Invalid URL in the request configuration"); + } + + apiVersionConfig.forEach(({ version, baseURL }) => { + const versionPrefix = `/api/${version}/`; + if (config.url.startsWith(versionPrefix)) { + // eslint-disable-next-line no-param-reassign + config.baseURL = baseURL; + // eslint-disable-next-line no-param-reassign + config.url = config.url.replace(versionPrefix, ""); + } + }); + + return config; + }); + + // flags print forms + apiClient.requestCount = 0; + apiClient.requestCountFlag = false; + apiClient.interceptors.request.use((request) => { + // flags print forms + if (apiClient.requestCountFlag) { + apiClient.requestCount += 1; + } + + EventBus.$emit("api-client-loading", request); + return request; + }); + + apiClient.interceptors.response.use((response) => { + // TODO: this could be used to show a default "created/upated/deleted resource" alert + // response.config.method (PUT, POST, DELETE) + // response.config.url (extract resource name) + EventBus.$emit("api-client-done", response); + + if (apiClient.requestCountFlag && apiClient.requestCount > 0) { + apiClient.requestCount -= 1; + } + return response; + }, (error) => { + // Set in your .catch to false to not show the alert inside window.ProcessMaker.apiClient + if (!error?.response?.showAlert) { + return Promise.reject(error); + } + + if (error.code && error.code === "ERR_CANCELED") { + return Promise.reject(error); + } + EventBus.$emit("api-client-error", error); + if (error.response && error.response.status && error.response.status === 401) { + // stop 401 error consuming endpoints with data-sources + const { url } = error.config; + if (url.includes("/data_sources/")) { + if (url.includes("requests/") || url.includes("/test")) { + throw error; + } + } + window.location = "/login"; + } else { + if (_.has(error, "config.url") && !error.config.url.match("/debug")) { + apiClient.post("/debug", { + name: "Javascript ProcessMaker.apiClient Error", + message: JSON.stringify({ + message: error.message, + code: error.code, + config: error.config, + }), + }); + } + return Promise.reject(error); + } + }); + + /** + * Next we will register the CSRF Token as a common header with Axios so that + * all outgoing HTTP requests automatically have it attached. This is just + * a simple convenience so we don't have to attach every token manually. + */ + + if (token) { + apiClient.defaults.headers.common["X-CSRF-TOKEN"] = token.content; + } else { + console.error("CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token"); + } + + if (window.Processmaker && window.Processmaker.apiTimeout !== undefined) { + apiTimeout = window.Processmaker.apiTimeout; + } + + apiClient.defaults.timeout = apiTimeout; + + // Display any uncaught promise rejections from axios in the Process Maker alert box + window.addEventListener("unhandledrejection", (event) => { + const error = event.reason; + if (error.config && error.config._defaultErrorShown) { + // Already handeled + event.preventDefault(); // stops the unhandled rejection error + } else if (error.response && error.response.data && error.response.data.message) { + if (!(error.code && error.code === "ECONNABORTED")) { + window.ProcessMaker.alert(error.response.data.message, "danger"); + } + } else if (error.message) { + if (!(error.code && error.code === "ECONNABORTED")) { + window.ProcessMaker.alert(error.message, "danger"); + } + } + }); + + setGlobalPMVariables({ + apiClient, + }); +}; diff --git a/resources/js/next/config/session.js b/resources/js/next/config/session.js new file mode 100644 index 0000000000..6390c538e8 --- /dev/null +++ b/resources/js/next/config/session.js @@ -0,0 +1,101 @@ +import { getGlobalPMVariable, setGlobalPMVariables, getGlobalVariable } from "../globalVariables"; + +export default () => { + const timeoutScript = document.head.querySelector("meta[name=\"timeout-worker\"]")?.content; + + const Vue = getGlobalVariable("Vue"); + const Echo = getGlobalVariable("Echo"); + + const pushNotification = getGlobalPMVariable("pushNotification"); + const closeSessionModal = getGlobalPMVariable("closeSessionModal"); + const alert = getGlobalPMVariable("alert"); + const user = getGlobalPMVariable("user"); + const sessionModal = getGlobalPMVariable("sessionModal"); + + const isSameDevice = (e) => { + const localDeviceId = Vue.$cookies.get(e.device_variable); + const remoteDeviceId = e.device_id; + return localDeviceId && localDeviceId === remoteDeviceId; + }; + + if (user) { + // Session timeout + const AccountTimeoutLength = parseInt(eval(document.head.querySelector("meta[name=\"timeout-length\"]")?.content)); + const AccountTimeoutWarnSeconds = parseInt(document.head.querySelector("meta[name=\"timeout-warn-seconds\"]")?.content); + const AccountTimeoutEnabled = document.head.querySelector("meta[name=\"timeout-enabled\"]") ? parseInt(document.head.querySelector("meta[name=\"timeout-enabled\"]")?.content) : 1; + const AccountTimeoutWorker = new Worker(timeoutScript); + + AccountTimeoutWorker.addEventListener("message", (e) => { + if (e.data.method === "countdown") { + sessionModal( + "Session Warning", + "
Your user session is expiring. If your session expires, all of your unsaved data will be lost.
Would you like to stay connected?
", + e.data.data.time, + AccountTimeoutWarnSeconds, + ); + } + if (e.data.method === "timedOut") { + window.location = "/logout?timeout=true"; + } + }); + + // in some cases it's necessary to start manually + AccountTimeoutWorker.postMessage({ + method: "start", + data: { + timeout: AccountTimeoutLength, + warnSeconds: AccountTimeoutWarnSeconds, + enabled: AccountTimeoutEnabled, + }, + }); + + Echo.private(`ProcessMaker.Models.User.${user.id}`) + .notification((token) => { + pushNotification(token); + }) + .listen(".SessionStarted", (e) => { + const lifetime = parseInt(eval(e.lifetime)); + if (isSameDevice(e)) { + AccountTimeoutWorker.postMessage({ + method: "start", + data: { + timeout: lifetime, + warnSeconds: AccountTimeoutWarnSeconds, + enabled: AccountTimeoutEnabled, + }, + }); + if (closeSessionModal) { + closeSessionModal(); + } + } + }) + .listen(".Logout", (e) => { + if (isSameDevice(e) && window.location.pathname.indexOf("/logout") === -1) { + const localDeviceId = Vue.$cookies.get(e.device_variable); + const redirectLogoutinterval = setInterval(() => { + const newDeviceId = Vue.$cookies.get(e.device_variable); + if (localDeviceId !== newDeviceId) { + clearInterval(redirectLogoutinterval); + window.location.href = "/logout"; + } + }, 100); + } + }) + .listen(".SecurityLogDownloadJobCompleted", (e) => { + if (e.success) { + const { link } = e; + const { message } = e; + alert(message, "success", 0, false, false, link); + } else { + alert(e.message, "warning"); + } + }); + + setGlobalPMVariables({ + AccountTimeoutLength, + AccountTimeoutWarnSeconds, + AccountTimeoutEnabled, + AccountTimeoutWorker, + }); + } +}; diff --git a/resources/js/next/config/user.js b/resources/js/next/config/user.js new file mode 100644 index 0000000000..85e26880c2 --- /dev/null +++ b/resources/js/next/config/user.js @@ -0,0 +1,51 @@ +import { getGlobalVariable, setGlobalPMVariables } from "../globalVariables"; +import datetime_format from "../../data/datetime_formats.json"; + +export default () => { + const moment = getGlobalVariable("moment"); + const userID = document.head.querySelector("meta[name=\"user-id\"]"); + const userFullName = document.head.querySelector("meta[name=\"user-full-name\"]"); + const userAvatar = document.head.querySelector("meta[name=\"user-avatar\"]"); + const formatDate = document.head.querySelector("meta[name=\"datetime-format\"]"); + const timezone = document.head.querySelector("meta[name=\"timezone\"]"); + const appUrl = document.head.querySelector("meta[name=\"app-url\"]"); + + const app = appUrl ? { + url: appUrl.content, + } : null; + + let user; + if (userID) { + user = { + id: userID.content, + datetime_format: formatDate?.content, + calendar_format: formatDate?.content, + timezone: timezone?.content, + fullName: userFullName?.content, + avatar: userAvatar?.content, + }; + + datetime_format.forEach((value) => { + if (formatDate.content === value.format) { + user.datetime_format = value.momentFormat; + user.calendar_format = value.calendarFormat; + } + }); + + if (user) { + moment.tz.setDefault(user.timezone); + moment.defaultFormat = user.datetime_format; + moment.defaultFormatUtc = user.datetime_format; + } + + if (document.documentElement.lang) { + moment.locale(document.documentElement.lang); + user.lang = document.documentElement.lang; + } + } + + setGlobalPMVariables({ + user, + app, + }); +}; diff --git a/resources/js/next/globalVariables.js b/resources/js/next/globalVariables.js new file mode 100644 index 0000000000..395c34cfcf --- /dev/null +++ b/resources/js/next/globalVariables.js @@ -0,0 +1,76 @@ +export default {}; + +export const setGlobalVariable = (key, value) => { + window[key] = value; +}; + +export const setGlobalVariables = (variables) => { + if (typeof variables === "object") { + Object.entries(variables).forEach(([key, value]) => { + window[key] = value; + }); + } +}; + +export const getGlobalVariable = (key) => window[key]; + +export const setGlobalPMVariable = (key, value) => { + if (!window.ProcessMaker) { + window.ProcessMaker = {}; + } + + window.ProcessMaker[key] = value; +}; + +export const setGlobalPMVariables = (variables) => { + if (!window.ProcessMaker) { + window.ProcessMaker = {}; + } + + Object.assign(window.ProcessMaker, variables); +}; + +export const getGlobalPMVariable = (key) => window.ProcessMaker[key]; + +export const setUses = (Vue, uses) => { + if (typeof uses === "object") { + Object.values(uses).forEach((use) => { + if (use) { + Vue.use(use); + } + }); + } +}; + +export const setMixins = (Vue, mixins) => { + if (typeof mixins === "object") { + Object.values(mixins).forEach((mixin) => { + if (mixin) { + Vue.mixin(mixin); + } + }); + } +}; + +export const setGlobalComponents = (Vue, components) => { + Object.entries(components).forEach(([key, component]) => { + if (component) { + Vue.component(key, component); + } + }); +}; + +export const loadModulesSequentially = async (modules) => { + const loadedModules = []; + + for (const modulePath of modules) { + try { + const module = await modulePath; + loadedModules.push(module); + } catch (error) { + console.error(`Error module: ${modulePath}`, error); + } + } + + return loadedModules; +}; diff --git a/resources/js/next/layout/navbar.js b/resources/js/next/layout/navbar.js new file mode 100644 index 0000000000..21edd620a7 --- /dev/null +++ b/resources/js/next/layout/navbar.js @@ -0,0 +1,259 @@ +import newRequestModal from "../../components/requests/requestModal.vue"; +import requestModal from "../../components/requests/modal.vue"; +import requestModalMobile from "../../components/requests/modalMobile.vue"; +import WelcomeModal from "../../Mobile/WelcomeModal.vue"; +import notifications from "../../notifications/components/notifications.vue"; +import sessionModalComponent from "../../components/Session.vue"; +import ConfirmationModal from "../../components/Confirm.vue"; +import MessageModal from "../../components/Message.vue"; +import NavbarProfile from "../../components/NavbarProfile.vue"; +import Menu from "../../components/Menu.vue"; +import { getGlobalVariable, getGlobalPMVariable, setGlobalPMVariable } from "../globalVariables"; + +const Vue = getGlobalVariable("Vue"); +const $ = getGlobalVariable("$"); +const events = getGlobalPMVariable("events"); + +Vue.component("LanguageSelectorButton", (resolve) => { + if (window.ProcessMaker.languageSelectorButtonComponent) { + resolve(window.ProcessMaker.languageSelectorButtonComponent); + } else { + window.ProcessMaker.languageSelectorButtonComponentResolve = resolve; + } +}); + +// Variables +const browser = navigator.userAgent; +const isMobileDevice = /Android|webOS|iPhone|iPad|iPod|BlackBerry|Windows Phone/i.test(browser); + +const mobileApp = !!isMobileDevice; +const isMobileNavbar = events.$cookies.get("isMobile"); // Verify is in mobile mode + +// Set our own specific alert function at the ProcessMaker global object that could +// potentially be overwritten by some custom theme support +const alert = function (msg, variant, showValue = 5, stayNextScreen = false, showLoader = false, msgLink = "", msgTitle = "") { + if (showValue === 0) { + // Just show it indefinitely, no countdown + showValue = true; + } + // amount of items allowed in array + if (ProcessMaker.navbar.alerts.length > 5) { + ProcessMaker.navbar.alerts.shift(); + } + ProcessMaker.navbar.alerts.push({ + alertText: msg, + alertLink: msgLink, + alertTitle: msgTitle, + alertShow: showValue, + alertVariant: String(variant), + showLoader, + stayNextScreen, + timestamp: Date.now(), + }); +}; + +// Setup our login modal +const sessionModal = function (title, message, time, warnSeconds) { + ProcessMaker.navbar.sessionTitle = title || __("Session Warning"); + ProcessMaker.navbar.sessionMessage = message || __("Your session is about to expire."); + ProcessMaker.navbar.sessionTime = time; + ProcessMaker.navbar.sessionWarnSeconds = warnSeconds; + ProcessMaker.navbar.sessionShow = true; +}; + +const closeSessionModal = function () { + ProcessMaker.navbar.sessionShow = false; +}; + +// Set out own specific confirm modal. +const confirmModal = function ( + title, + message, + variant, + callback, + size = "md", + dataTestClose = "confirm-btn-close", + dataTestOk = "confirm-btn-ok", +) { + ProcessMaker.navbar.confirmTitle = title || __("Confirm"); + ProcessMaker.navbar.confirmMessage = message || __("Are you sure you want to delete?"); + ProcessMaker.navbar.confirmVariant = variant; + ProcessMaker.navbar.confirmCallback = callback; + ProcessMaker.navbar.confirmShow = true; + ProcessMaker.navbar.confirmSize = size; + ProcessMaker.navbar.confirmDataTestClose = dataTestClose; + ProcessMaker.navbar.confirmDataTestOk = dataTestOk; +}; + +// Set out own specific message modal. +const messageModal = function (title, message, variant, callback) { + ProcessMaker.navbar.messageTitle = title || __("Message"); + ProcessMaker.navbar.messageMessage = message || __(""); + ProcessMaker.navbar.messageVariant = variant; + ProcessMaker.navbar.messageCallback = callback; + ProcessMaker.navbar.messageShow = true; +}; + +// Assign our navbar component to our global ProcessMaker object +const navbar = new Vue({ + el: "#navbar", + components: { + TopMenu: Menu, + requestModal, + notifications, + sessionModal: sessionModalComponent, + ConfirmationModal, + MessageModal, + NavbarProfile, + newRequestModal, + GlobalSearch: (resolve) => { + if (window.ProcessMaker.globalSearchComponent) { + resolve(window.ProcessMaker.globalSearchComponent); + } else { + window.ProcessMaker.globalSearchComponentResolve = resolve; + } + }, + }, + data() { + return { + screenBuilder: null, + messages: ProcessMaker.notifications, + alerts: this.loadLocalAlerts(), + confirmTitle: "", + confirmMessage: "", + confirmVariant: "", + confirmCallback: "", + confirmSize: "md", + confirmDataTestClose: "confirm-btn-close", + confirmDataTestOk: "confirm-btn-ok", + messageTitle: "", + messageMessage: "", + messageVariant: "", + messageCallback: "", + confirmShow: false, + sessionShow: false, + messageShow: false, + sessionTitle: "", + sessionMessage: "", + sessionTime: "", + sessionWarnSeconds: "", + taskTitle: "", + isMobile: false, + isMobileDevice: mobileApp, + }; + }, + watch: { + alerts(array) { + this.saveLocalAlerts(array); + }, + }, + mounted() { + Vue.nextTick() // This is needed to override the default alert method. + .then(() => { + this.onResize(); + window.addEventListener("resize", this.onResize, { passive: true }); + + if (document.querySelector("meta[name='alert']")) { + ProcessMaker.alert( + document.querySelector("meta[name='alertMessage']").getAttribute("content"), + document.querySelector("meta[name='alertVariant']").getAttribute("content"), + ); + } + const findSB = setInterval(() => { + this.screenBuilder = window.ProcessMaker.ScreenBuilder; // window.ProcessMaker.ScreenBuilder is not defined in the global scope + if (this.screenBuilder) { + clearInterval(findSB); + } + }, 80); + }); + }, + methods: { + alertDownChanged(dismissCountDown, item) { + item.alertShow = dismissCountDown; + this.saveLocalAlerts(this.alerts); + }, + alertDismissed(alert) { + alert.alertShow = 0; + const index = this.alerts.indexOf(alert); + let copy = _.cloneDeep(this.alerts); + index > -1 ? copy.splice(index, 1) : null; + // remove old alerts + copy = copy.filter((item) => ((Date.now() - item.timestamp) / 1000) < item.alertShow); + this.saveLocalAlerts(copy); + }, + loadLocalAlerts() { + try { + return window.localStorage.processmakerAlerts + && window.localStorage.processmakerAlerts.substr(0, 1) === "[" + ? JSON.parse(window.localStorage.processmakerAlerts) : []; + } catch (e) { + return []; + } + }, + saveLocalAlerts(array) { + const nextScreenAlerts = array.filter((alert) => alert.stayNextScreen); + window.localStorage.processmakerAlerts = JSON.stringify(nextScreenAlerts); + }, + switchToMobile() { + this.$cookies.set("isMobile", true); + window.open("/requests", "_self"); + }, + getRoutes() { + if (this.$refs.breadcrumbs) { + return this.$refs.breadcrumbs.list; + } + return []; + }, + setRoutes(routes) { + if (this.$refs.breadcrumbs) { + return this.$refs.breadcrumbs.updateRoutes(routes); + } + return false; + }, + onResize() { + this.isMobile = window.innerWidth < 992; + }, + }, +}); + +setGlobalPMVariable("mobileApp", mobileApp); +setGlobalPMVariable("alert", alert); +setGlobalPMVariable("sessionModal", sessionModal); +setGlobalPMVariable("closeSessionModal", closeSessionModal); +setGlobalPMVariable("confirmModal", confirmModal); +setGlobalPMVariable("messageModal", messageModal); +setGlobalPMVariable("navbar", navbar); +// Breadcrumbs are now part of the navbar component. Alias it here. +setGlobalPMVariable("breadcrumbs", navbar); + +// Assign our navbar component to our global ProcessMaker object +if (isMobileNavbar === "true") { + const navbarMobile = new Vue({ + el: "#navbarMobile", + components: { + requestModalMobile, + WelcomeModal, + }, + data() { + return { + display: true, + }; + }, + mounted() { + if (this.$cookies.get("firstMounted") === "true") { + $("#welcomeModal").modal("show"); + } + }, + methods: { + switchToDesktop() { + this.$cookies.set("isMobile", false); + window.location.reload(); + }, + onResize() { + this.isMobile = window.innerWidth < 992; + }, + }, + }); + + setGlobalPMVariable("navbarMobile", navbarMobile); +} diff --git a/resources/js/next/layout/sidebar.js b/resources/js/next/layout/sidebar.js new file mode 100644 index 0000000000..b3854f94de --- /dev/null +++ b/resources/js/next/layout/sidebar.js @@ -0,0 +1,21 @@ +import { sanitizeUrl } from "@braintree/sanitize-url"; +// import VueHtml2Canvas from "vue-html2canvas"; +import Sidebaricon from "../../components/Sidebaricon.vue"; +import { getGlobalVariable } from "../globalVariables"; + +const Vue = getGlobalVariable("Vue"); + +// Vue.use(VueHtml2Canvas); +Vue.prototype.$sanitize = sanitizeUrl; + +const sidebar = new Vue({ + el: "#sidebar", + components: { + Sidebaricon, + }, + data() { + return { + expanded: false, + }; + }, +}); diff --git a/resources/js/next/libraries/bootstrap.js b/resources/js/next/libraries/bootstrap.js new file mode 100644 index 0000000000..bc71e0f73d --- /dev/null +++ b/resources/js/next/libraries/bootstrap.js @@ -0,0 +1,9 @@ +import * as bootstrap from "bootstrap"; + +import("bootstrap-vue/dist/bootstrap-vue.css"); + +export default { + global: { + bootstrap, + }, +}; diff --git a/resources/js/next/libraries/broadcast.js b/resources/js/next/libraries/broadcast.js new file mode 100644 index 0000000000..0cb4ace0c2 --- /dev/null +++ b/resources/js/next/libraries/broadcast.js @@ -0,0 +1,20 @@ +import Echo from "laravel-echo"; +import { setGlobalVariables } from "../globalVariables"; + +export default () => { + // Verify if the broadcasting is enabled + if (Processmaker && Processmaker.broadcasting) { + const config = Processmaker.broadcasting; + let Pusher; + + if (config.broadcaster === "pusher") { + Pusher = require("pusher-js"); + Pusher.logToConsole = config.debug; + } + + setGlobalVariables({ + Echo: new Echo(config), + Pusher, + }); + } +}; diff --git a/resources/js/next/libraries/jquery.js b/resources/js/next/libraries/jquery.js new file mode 100644 index 0000000000..a11f9d8fa7 --- /dev/null +++ b/resources/js/next/libraries/jquery.js @@ -0,0 +1,8 @@ +import * as jQuery from "jquery"; + +export default { + global: { + jQuery, + $: jQuery, + }, +}; diff --git a/resources/js/next/libraries/lodash.js b/resources/js/next/libraries/lodash.js new file mode 100644 index 0000000000..f6fe0f4ac6 --- /dev/null +++ b/resources/js/next/libraries/lodash.js @@ -0,0 +1,6 @@ +import lodash from "lodash"; + +window._ = lodash; +window.debounce = lodash.debounce; + +export default {}; diff --git a/resources/js/next/libraries/modelerInspector.js b/resources/js/next/libraries/modelerInspector.js new file mode 100644 index 0000000000..fc5c03e009 --- /dev/null +++ b/resources/js/next/libraries/modelerInspector.js @@ -0,0 +1,6 @@ +// import VueFormElements from "@processmaker/vue-form-elements"; +import * as ModelerInspector from "../../processes/modeler/components/inspector/index"; + +window.ModelerInspector = ModelerInspector; + +export default {}; diff --git a/resources/js/next/libraries/popper.js b/resources/js/next/libraries/popper.js new file mode 100644 index 0000000000..519a53b597 --- /dev/null +++ b/resources/js/next/libraries/popper.js @@ -0,0 +1 @@ +window.Popper = require("popper.js").default; diff --git a/resources/js/next/libraries/sharedComponents.js b/resources/js/next/libraries/sharedComponents.js new file mode 100644 index 0000000000..63d1fcee22 --- /dev/null +++ b/resources/js/next/libraries/sharedComponents.js @@ -0,0 +1,5 @@ +import * as SharedComponents from "../../components/shared"; + +window.SharedComponents = SharedComponents; + +export default {}; diff --git a/resources/js/next/libraries/vueCookies.js b/resources/js/next/libraries/vueCookies.js new file mode 100644 index 0000000000..f4829ef252 --- /dev/null +++ b/resources/js/next/libraries/vueCookies.js @@ -0,0 +1,7 @@ +import VueCookies from "vue-cookies"; + +export default { + use: { + VueCookies, + }, +}; diff --git a/resources/js/next/libraries/vueDeepset.js b/resources/js/next/libraries/vueDeepset.js new file mode 100644 index 0000000000..931acd4b9e --- /dev/null +++ b/resources/js/next/libraries/vueDeepset.js @@ -0,0 +1,3 @@ +import * as VueDeepSet from "vue-deepset"; + +window.Vue.use(VueDeepSet); diff --git a/resources/js/next/libraries/vueFormElements.js b/resources/js/next/libraries/vueFormElements.js new file mode 100644 index 0000000000..ddd4b0c563 --- /dev/null +++ b/resources/js/next/libraries/vueFormElements.js @@ -0,0 +1,5 @@ +import VueFormElements from "@processmaker/vue-form-elements"; + +window.VueFormElements = VueFormElements; + +export default {}; diff --git a/resources/js/next/libraries/vueRouter.js b/resources/js/next/libraries/vueRouter.js new file mode 100644 index 0000000000..c59d5d2884 --- /dev/null +++ b/resources/js/next/libraries/vueRouter.js @@ -0,0 +1,15 @@ +import Router from "vue-router"; + +export default { + global: { + VueRouter: Router, + }, + pm: { + Router: new Router({ + mode: "history", + }), + }, + use: { + Router, + }, +}; diff --git a/resources/js/next/libraries/vueTable.js b/resources/js/next/libraries/vueTable.js new file mode 100644 index 0000000000..a88c2fb50b --- /dev/null +++ b/resources/js/next/libraries/vueTable.js @@ -0,0 +1,3 @@ +import { install as VuetableInstall } from "vuetable-2"; + +VuetableInstall(window.Vue); diff --git a/resources/js/next/libraries/vuex.js b/resources/js/next/libraries/vuex.js new file mode 100644 index 0000000000..e71f405bd5 --- /dev/null +++ b/resources/js/next/libraries/vuex.js @@ -0,0 +1,9 @@ +import Vuex from "vuex"; +import GlobalStore from "../../globalStore"; + +export default { + use: { + Vuex, + GlobalStore, + }, +}; diff --git a/resources/js/next/modeler.js b/resources/js/next/modeler.js new file mode 100644 index 0000000000..d16da21b52 --- /dev/null +++ b/resources/js/next/modeler.js @@ -0,0 +1,12 @@ +import { setGlobalPMVariables } from "./globalVariables"; + +export default () => { + const nodeTypes = []; + nodeTypes.get = function (id) { + return this.find((node) => node.id === id); + }; + + setGlobalPMVariables({ + nodeTypes, + }); +}; diff --git a/resources/js/next/monaco.js b/resources/js/next/monaco.js new file mode 100644 index 0000000000..bf37bc0e09 --- /dev/null +++ b/resources/js/next/monaco.js @@ -0,0 +1,9 @@ +import MonacoEditor from "vue-monaco"; +import { getGlobalVariable, setGlobalVariable } from "./globalVariables"; + +export default () => { + const Vue = getGlobalVariable("Vue"); + Vue.component("MonacoEditor", MonacoEditor); + setGlobalVariable("VueMonaco", MonacoEditor); + setGlobalVariable("monaco", MonacoEditor); +}; diff --git a/resources/js/next/screenBuilder.js b/resources/js/next/screenBuilder.js new file mode 100644 index 0000000000..5ad5b1c470 --- /dev/null +++ b/resources/js/next/screenBuilder.js @@ -0,0 +1,52 @@ +import { + getGlobalVariable, setGlobalVariable, getGlobalPMVariable, setGlobalPMVariable, +} from "./globalVariables"; + +const addScriptsToDOM = async function (scripts) { + for (const script of scripts) { + await new Promise((resolve, reject) => { + const scriptElement = document.createElement("script"); + scriptElement.src = script; + scriptElement.async = false; + scriptElement.onload = resolve; + scriptElement.onerror = reject; + document.head.appendChild(scriptElement); + }); + } +}; + +export default () => { + const Vue = getGlobalVariable("Vue"); + + const componentsScreenBuilder = ["VueFormRenderer", "Task"]; + + componentsScreenBuilder.forEach((component) => { + Vue.component(component, (resolve, reject) => { + import("@processmaker/screen-builder/dist/vue-form-builder.css"); + import("@processmaker/screen-builder").then((ScreenBuilder) => { + const apiClient = getGlobalPMVariable("apiClient"); + Vue.use(ScreenBuilder.default); + + const { initializeScreenCache } = ScreenBuilder; + // Configuration Global object used by ScreenBuilder + // @link https://processmaker.atlassian.net/browse/FOUR-6833 Cache configuration + const screenCacheEnabled = document.head.querySelector("meta[name=\"screen-cache-enabled\"]")?.content ?? "false"; + const screenCacheTimeout = document.head.querySelector("meta[name=\"screen-cache-timeout\"]")?.content ?? "5000"; + const screen = { + cacheEnabled: screenCacheEnabled === "true", + cacheTimeout: Number(screenCacheTimeout), + }; + + setGlobalVariable("ScreenBuilder", ScreenBuilder); + setGlobalPMVariable("screen", screen); + // Initialize screen-builder cache + initializeScreenCache(apiClient, screen);// TODO: Its a bad practice to use the apiClient here + if (screenBuilderScripts) { + addScriptsToDOM(screenBuilderScripts).then(() => { + resolve(ScreenBuilder[component]); + }); + } + }).catch(reject); + }); + }); +}; diff --git a/resources/js/next/setupMain.js b/resources/js/next/setupMain.js new file mode 100644 index 0000000000..d9192ecac8 --- /dev/null +++ b/resources/js/next/setupMain.js @@ -0,0 +1,74 @@ +import Vue from "vue"; +import * as vue from "vue"; +import { BootstrapVue, BootstrapVueIcons } from "bootstrap-vue"; +import moment from "moment-timezone"; +import { + setUses, setGlobalVariables, setGlobalPMVariables, +} from "./globalVariables"; + +import vuex from "./libraries/vuex"; +import bootstrap from "./libraries/bootstrap"; +import jquery from "./libraries/jquery"; +import lodash from "./libraries/lodash"; +import sharedComponents from "./libraries/sharedComponents"; + +import vueRouter from "./libraries/vueRouter"; +import vueCookies from "./libraries/vueCookies"; +import processmaker from "./config/processmaker"; +import broadcast from "./libraries/broadcast"; +import i18n from "./config/i18n"; +import notifications from "./config/notifications"; +import user from "./config/user"; +import session from "./config/session"; +import openAI from "./config/openAI"; + +export const setupMain = () => { + window.Vue = Vue; + window.vue = vue; + window.moment = moment; + + window.Vue.prototype.moment = moment; + window.Vue.use(BootstrapVue); + window.Vue.use(BootstrapVueIcons); + + window.ProcessMaker = { + EventBus: new Vue(), + events: new Vue(), + packages: window.packages, + }; + + // Vuex + setUses(Vue, vuex.use); + + // Bootstrap + setGlobalVariables(bootstrap.global); + + // Jquery + setGlobalVariables(jquery.global); + + // VueRouter + setGlobalVariables(vueRouter.global); + setGlobalPMVariables(vueRouter.pm); + setUses(Vue, vueRouter.use); + + // VueCookies + setUses(Vue, vueCookies.use); + + // INIT CONFIGURATION + processmaker(); + broadcast(); + i18n(); + notifications(); + user(); + session(); + openAI(); + + // Initialize components asyncronously + import("./components/index"); + import("./config/accesibility"); + + import("./layout/sidebar"); + import("./layout/navbar"); +}; + +export default { }; diff --git a/resources/js/processes/screen-builder/typeDisplay.js b/resources/js/processes/screen-builder/typeDisplay.js index 8b6e412e08..81100912ae 100644 --- a/resources/js/processes/screen-builder/typeDisplay.js +++ b/resources/js/processes/screen-builder/typeDisplay.js @@ -1,42 +1,39 @@ -import { renderer, FormBuilderControls, globalProperties } from "@processmaker/screen-builder"; import formTypes from "./formTypes"; -const { - FormText, -} = renderer; +ProcessMaker.EventBus.$on("screen-builder-init", (manager) => { + const { FormBuilderControls, globalProperties } = window.ScreenBuilder; -const TableControl = FormBuilderControls.find((control) => control.rendererBinding === "FormMultiColumn"); -const RichTextControl = FormBuilderControls.find((control) => control.rendererBinding === "FormHtmlEditor"); -const FormRecordList = FormBuilderControls.find((control) => control.rendererBinding === "FormRecordList"); -const FormImage = FormBuilderControls.find((control) => control.rendererBinding === "FormImage"); -const FormAvatar = FormBuilderControls.find((control) => control.rendererBinding === "FormAvatar"); -const FormLoop = FormBuilderControls.find((control) => control.rendererBinding === "FormLoop"); -const FormNestedScreen = FormBuilderControls.find((control) => control.rendererBinding === "FormNestedScreen"); -const FileDownloadControl = FormBuilderControls.find((control) => control.builderBinding === "FileDownload"); -const FormListTable = FormBuilderControls.find((control) => control.rendererBinding === "FormListTable"); -const FormAnalyticsChart = FormBuilderControls.find((control) => control.rendererBinding === "FormAnalyticsChart"); -const FormCollectionViewControl = FormBuilderControls.find((control) => control.rendererBinding === "FormCollectionViewControl"); -// Remove editable inspector props -FormRecordList.control.inspector = FormRecordList.control.inspector.filter((prop) => prop.field !== "editable" && prop.field !== "form"); + const TableControl = FormBuilderControls.find((control) => control.rendererBinding === "FormMultiColumn"); + const RichTextControl = FormBuilderControls.find((control) => control.rendererBinding === "FormHtmlEditor"); + const FormRecordList = FormBuilderControls.find((control) => control.rendererBinding === "FormRecordList"); + const FormImage = FormBuilderControls.find((control) => control.rendererBinding === "FormImage"); + const FormAvatar = FormBuilderControls.find((control) => control.rendererBinding === "FormAvatar"); + const FormLoop = FormBuilderControls.find((control) => control.rendererBinding === "FormLoop"); + const FormNestedScreen = FormBuilderControls.find((control) => control.rendererBinding === "FormNestedScreen"); + const FileDownloadControl = FormBuilderControls.find((control) => control.builderBinding === "FileDownload"); + const FormListTable = FormBuilderControls.find((control) => control.rendererBinding === "FormListTable"); + const FormAnalyticsChart = FormBuilderControls.find((control) => control.rendererBinding === "FormAnalyticsChart"); + const FormCollectionViewControl = FormBuilderControls.find((control) => control.rendererBinding === "FormCollectionViewControl"); + // Remove editable inspector props + FormRecordList.control.inspector = FormRecordList.control.inspector.filter((prop) => prop.field !== "editable" && prop.field !== "form"); -// Modify record list description when used in a display screen -FormRecordList.control.popoverContent = "Format content in a table structure"; + // Modify record list description when used in a display screen + FormRecordList.control.popoverContent = "Format content in a table structure"; -const controlsDisplay = [ - RichTextControl, - TableControl, - FormRecordList, - FormImage, - FormAvatar, - FormLoop, - FormNestedScreen, - FileDownloadControl, - FormListTable, - FormAnalyticsChart, - FormCollectionViewControl -]; + const controlsDisplay = [ + RichTextControl, + TableControl, + FormRecordList, + FormImage, + FormAvatar, + FormLoop, + FormNestedScreen, + FileDownloadControl, + FormListTable, + FormAnalyticsChart, + FormCollectionViewControl, + ]; -ProcessMaker.EventBus.$on("screen-builder-init", (manager) => { controlsDisplay.forEach((item) => { item.control.inspector.push(...globalProperties[0].inspector); manager.type = formTypes.display; diff --git a/resources/js/processes/screen-builder/typeForm.js b/resources/js/processes/screen-builder/typeForm.js index 5df0c692ae..beaea38034 100644 --- a/resources/js/processes/screen-builder/typeForm.js +++ b/resources/js/processes/screen-builder/typeForm.js @@ -1,18 +1,12 @@ -import Vue from "vue"; -import VueFormElements from "@processmaker/vue-form-elements"; -import { FormBuilderControls as initialControls, globalProperties } from "@processmaker/screen-builder"; -import { Multiselect } from "@processmaker/vue-multiselect"; - -Vue.use(VueFormElements); -Vue.component("Multiselect", Multiselect); - -// The submit button has by default the 'submit' value -const submitButton = initialControls.find((x) => x.control.label === "Submit"); -if (submitButton) { - submitButton.control.config.fieldValue = "submit"; -} - ProcessMaker.EventBus.$on("screen-builder-init", (manager) => { + const { FormBuilderControls, globalProperties } = window.ScreenBuilder; + const initialControls = FormBuilderControls; + // The submit button has by default the 'submit' value + const submitButton = initialControls.find((x) => x.control.label === "Submit"); + if (submitButton) { + submitButton.control.config.fieldValue = "submit"; + } + initialControls.forEach((config) => { config.control.inspector.push(...globalProperties[0].inspector); diff --git a/resources/js/tasks/components/NavbarTaskMobile.vue b/resources/js/tasks/components/NavbarTaskMobile.vue index 063c89b604..f510b36f10 100644 --- a/resources/js/tasks/components/NavbarTaskMobile.vue +++ b/resources/js/tasks/components/NavbarTaskMobile.vue @@ -74,9 +74,10 @@ diff --git a/resources/js/tasks/components/TasksList.vue b/resources/js/tasks/components/TasksList.vue index 5f8e47d8f4..aca5440893 100644 --- a/resources/js/tasks/components/TasksList.vue +++ b/resources/js/tasks/components/TasksList.vue @@ -23,11 +23,11 @@@{{ __(labelDate) }}:
+@{{ $t(labelDate) }}:
@{{ moment(statusDate).format() }}{{ __('STARTED BY') }}:
+@{{ $t('STARTED BY') }}:
{{ __('LAUNCHPAD') }}
+@{{ $t('LAUNCHPAD') }}
{{ $request->name }} @@ -86,7 +86,7 @@ class="d-inline-flex pull-left align-items-center"
{{ __('PARTICIPANTS') }}:
+@{{ $t('PARTICIPANTS') }}:
@{{__('Something went wrong. Try refreshing the application')}}
+