Merge branch 'dev' into feature-global-search
This commit is contained in:
commit
4877a8e263
1016
src-ui/messages.xlf
1016
src-ui/messages.xlf
File diff suppressed because it is too large
Load Diff
258
src-ui/package-lock.json
generated
258
src-ui/package-lock.json
generated
@ -9,15 +9,15 @@
|
||||
"version": "0.0.0",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@angular/cdk": "^17.3.2",
|
||||
"@angular/common": "~17.3.2",
|
||||
"@angular/compiler": "~17.3.2",
|
||||
"@angular/core": "~17.3.2",
|
||||
"@angular/forms": "~17.3.2",
|
||||
"@angular/localize": "~17.3.2",
|
||||
"@angular/platform-browser": "~17.3.2",
|
||||
"@angular/platform-browser-dynamic": "~17.3.2",
|
||||
"@angular/router": "~17.3.2",
|
||||
"@angular/cdk": "^17.3.6",
|
||||
"@angular/common": "~17.3.7",
|
||||
"@angular/compiler": "~17.3.7",
|
||||
"@angular/core": "~17.3.7",
|
||||
"@angular/forms": "~17.3.7",
|
||||
"@angular/localize": "~17.3.7",
|
||||
"@angular/platform-browser": "~17.3.7",
|
||||
"@angular/platform-browser-dynamic": "~17.3.7",
|
||||
"@angular/router": "~17.3.7",
|
||||
"@ng-bootstrap/ng-bootstrap": "^16.0.0",
|
||||
"@ng-select/ng-select": "^12.0.7",
|
||||
"@ngneat/dirty-check-forms": "^3.0.3",
|
||||
@ -38,14 +38,14 @@
|
||||
"zone.js": "^0.14.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-builders/jest": "17.0.2",
|
||||
"@angular-devkit/build-angular": "~17.3.2",
|
||||
"@angular-builders/jest": "17.0.3",
|
||||
"@angular-devkit/build-angular": "~17.3.6",
|
||||
"@angular-eslint/builder": "17.3.0",
|
||||
"@angular-eslint/eslint-plugin": "17.3.0",
|
||||
"@angular-eslint/eslint-plugin-template": "17.3.0",
|
||||
"@angular-eslint/schematics": "17.3.0",
|
||||
"@angular-eslint/template-parser": "17.3.0",
|
||||
"@angular/cli": "~17.3.2",
|
||||
"@angular/cli": "~17.3.6",
|
||||
"@angular/compiler-cli": "~17.3.2",
|
||||
"@playwright/test": "^1.42.1",
|
||||
"@types/jest": "^29.5.12",
|
||||
@ -86,9 +86,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-builders/common": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@angular-builders/common/-/common-1.0.1.tgz",
|
||||
"integrity": "sha512-qPgTjz3ISdGIY+vOIiUzpZRXwchdL/HEhCRzM2QKdqz/c5AB06X9wKhvXezabtzpYSq4lN9fliPYCntqimefFw==",
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular-builders/common/-/common-1.0.2.tgz",
|
||||
"integrity": "sha512-lUusRq6jN1It5LcUTLS6Q+AYAYGTo/EEN8hV0M6Ek9qXzweAouJaSEnwv7p04/pD7yJTl0YOCbN79u+wGm3x4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@angular-devkit/core": "^17.1.0",
|
||||
@ -100,12 +100,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-builders/jest": {
|
||||
"version": "17.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular-builders/jest/-/jest-17.0.2.tgz",
|
||||
"integrity": "sha512-IoEDqudGTpPBhUv0R7TU0eewASVDMIj+pFZTyHCSb2Y17DkE98mo5wp/GXRBl09LO6VK63bHNBB56oXbKG93fA==",
|
||||
"version": "17.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@angular-builders/jest/-/jest-17.0.3.tgz",
|
||||
"integrity": "sha512-LW4s8t+NLnWR7Aud+EZup8dOBfQF8rfOIncsarDtP/48rz/Ucnzvum7xEt/NYAlZ6y/Dpk7wO6SlqAsaOPf8mA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@angular-builders/common": "1.0.1",
|
||||
"@angular-builders/common": "1.0.2",
|
||||
"@angular-devkit/architect": ">=0.1700.0 < 0.1800.0",
|
||||
"@angular-devkit/core": "^17.0.0",
|
||||
"jest-preset-angular": "14.0.3",
|
||||
@ -123,12 +123,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/architect": {
|
||||
"version": "0.1703.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1703.2.tgz",
|
||||
"integrity": "sha512-fT5gSzwDHOyGv8zF97t8rjeoYSGSxXjWWstl3rN1nXdO0qgJ5m6Sv0fupON+HltdXDCBLRH+2khNpqx/Fh0Qww==",
|
||||
"version": "0.1703.6",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1703.6.tgz",
|
||||
"integrity": "sha512-Ck501FD/QuOjeKVFs7hU92w8+Ffetv0d5Sq09XY2/uygo5c/thMzp9nkevaIWBxUSeU5RqYZizDrhFVgYzbbOw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@angular-devkit/core": "17.3.2",
|
||||
"@angular-devkit/core": "17.3.6",
|
||||
"rxjs": "7.8.1"
|
||||
},
|
||||
"engines": {
|
||||
@ -138,15 +138,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/build-angular": {
|
||||
"version": "17.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-17.3.2.tgz",
|
||||
"integrity": "sha512-muPCUyL0uHvRkLH4NLWiccER6P2vCm/Q5DDvqyN4XOzzY3tAHHLrKrpvY87sgd2oNJ6Ci8x7GPNcfzR5KELCnw==",
|
||||
"version": "17.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-17.3.6.tgz",
|
||||
"integrity": "sha512-K4CEZvhQZUUOpmXPVoI1YBM8BARbIlqE6FZRxakmnr+YOtVTYE5s+Dr1wgja8hZIohNz6L7j167G9Aut7oPU/w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "2.3.0",
|
||||
"@angular-devkit/architect": "0.1703.2",
|
||||
"@angular-devkit/build-webpack": "0.1703.2",
|
||||
"@angular-devkit/core": "17.3.2",
|
||||
"@angular-devkit/architect": "0.1703.6",
|
||||
"@angular-devkit/build-webpack": "0.1703.6",
|
||||
"@angular-devkit/core": "17.3.6",
|
||||
"@babel/core": "7.24.0",
|
||||
"@babel/generator": "7.23.6",
|
||||
"@babel/helper-annotate-as-pure": "7.22.5",
|
||||
@ -157,7 +157,7 @@
|
||||
"@babel/preset-env": "7.24.0",
|
||||
"@babel/runtime": "7.24.0",
|
||||
"@discoveryjs/json-ext": "0.5.7",
|
||||
"@ngtools/webpack": "17.3.2",
|
||||
"@ngtools/webpack": "17.3.6",
|
||||
"@vitejs/plugin-basic-ssl": "1.1.0",
|
||||
"ansi-colors": "4.1.3",
|
||||
"autoprefixer": "10.4.18",
|
||||
@ -198,8 +198,8 @@
|
||||
"terser": "5.29.1",
|
||||
"tree-kill": "1.2.2",
|
||||
"tslib": "2.6.2",
|
||||
"undici": "6.7.1",
|
||||
"vite": "5.1.5",
|
||||
"undici": "6.11.1",
|
||||
"vite": "5.1.7",
|
||||
"watchpack": "2.4.0",
|
||||
"webpack": "5.90.3",
|
||||
"webpack-dev-middleware": "6.1.2",
|
||||
@ -725,12 +725,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@angular-devkit/build-webpack": {
|
||||
"version": "0.1703.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1703.2.tgz",
|
||||
"integrity": "sha512-w7rVFQcZK4iTCd/MLfQWIkDkwBOfAs++txNQyS9qYID8KvLs1V+oWYd2qDBRelRv1u3YtaCIS1pQx3GFKBC3OA==",
|
||||
"version": "0.1703.6",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1703.6.tgz",
|
||||
"integrity": "sha512-pJu0et2SiF0kfXenHSTtAART0omzbWpLgBfeUo4hBh4uwX5IaT+mRpYpr8gCXMq+qsjoQp3HobSU3lPDeBn+bg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@angular-devkit/architect": "0.1703.2",
|
||||
"@angular-devkit/architect": "0.1703.6",
|
||||
"rxjs": "7.8.1"
|
||||
},
|
||||
"engines": {
|
||||
@ -744,9 +744,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular-devkit/core": {
|
||||
"version": "17.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.2.tgz",
|
||||
"integrity": "sha512-1vxKo9+pdSwTOwqPDSYQh84gZYmCJo6OgR5+AZoGLGMZSeqvi9RG5RiUcOMLQYOnuYv0arlhlWxz0ZjyR8ApKw==",
|
||||
"version": "17.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.6.tgz",
|
||||
"integrity": "sha512-FVbkT9dEwHEvjnxr4mvMNSMg2bCFoGoP4X68xXU9dhLEUpC05opLvfbaR3Qh543eCJ5AstosBFVzB/krfIkOvA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ajv": "8.12.0",
|
||||
@ -777,12 +777,12 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@angular-devkit/schematics": {
|
||||
"version": "17.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.3.2.tgz",
|
||||
"integrity": "sha512-AYO6oc6QpFGigc1KiDzEVT1CeLnwvnIedU5Q/U3JDZ/Yqmvgc09D64g9XXER2kg6tV7iEgLxiYnonIAQOHq7eA==",
|
||||
"version": "17.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.3.6.tgz",
|
||||
"integrity": "sha512-2G1YuPInd8znG7uUgKOS7z72Aku50lTzB/2csWkWPJLAFkh7vKC8QZ40x8S1nC9npVYPhI5CRLX/HVpBh9CyxA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@angular-devkit/core": "17.3.2",
|
||||
"@angular-devkit/core": "17.3.6",
|
||||
"jsonc-parser": "3.2.1",
|
||||
"magic-string": "0.30.8",
|
||||
"ora": "5.4.1",
|
||||
@ -1287,9 +1287,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/cdk": {
|
||||
"version": "17.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-17.3.2.tgz",
|
||||
"integrity": "sha512-mC2U7aoIf7RSpGgIwVyfQEbaPDDX59plQt88KeTz15wjF8vosLt2DG0rZEoV8Mq14YS47J+jI76q/LJfd6/GCw==",
|
||||
"version": "17.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-17.3.6.tgz",
|
||||
"integrity": "sha512-7eKrC61/6pmMAxllU/vYKadZRF7x7GxUYpA5G70fNaQsIUUiZvxx/SJN9AuZEoPGAtF6atKlJD8QVmFoDzv/Lw==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
@ -1303,15 +1303,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/cli": {
|
||||
"version": "17.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-17.3.2.tgz",
|
||||
"integrity": "sha512-g6r4XZyGnh9P6GmWgaFh8RmR4L6UdQ408e3SpG3rjncuPRD57Ur8806GfCLPt6HIA9TARiKmaJ0EJ3RsIjag0g==",
|
||||
"version": "17.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-17.3.6.tgz",
|
||||
"integrity": "sha512-poKaRPeI+hFqX+AxIaEriaIggFVcC3XqlT9E1/uBC2rfHirE1n5F9Z7xqEDtMHduKwLbNXhQIPoKIKya8+Hnew==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@angular-devkit/architect": "0.1703.2",
|
||||
"@angular-devkit/core": "17.3.2",
|
||||
"@angular-devkit/schematics": "17.3.2",
|
||||
"@schematics/angular": "17.3.2",
|
||||
"@angular-devkit/architect": "0.1703.6",
|
||||
"@angular-devkit/core": "17.3.6",
|
||||
"@angular-devkit/schematics": "17.3.6",
|
||||
"@schematics/angular": "17.3.6",
|
||||
"@yarnpkg/lockfile": "1.1.0",
|
||||
"ansi-colors": "4.1.3",
|
||||
"ini": "4.1.2",
|
||||
@ -1343,9 +1343,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@angular/common": {
|
||||
"version": "17.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular/common/-/common-17.3.2.tgz",
|
||||
"integrity": "sha512-7fo+hrQEzo+VX0fJAKK+P4YNeiEnpdMOAkyIdwweyAeUZYeFIs6TKtax3CiJAubnkIkhQ/52uxiusDhK3Wg/WQ==",
|
||||
"version": "17.3.7",
|
||||
"resolved": "https://registry.npmjs.org/@angular/common/-/common-17.3.7.tgz",
|
||||
"integrity": "sha512-A7LRJu1vVCGGgrfZXjU+njz50SiU4weheKCar5PIUprcdIofS1IrHAJDqYh+kwXxkjXbZMOr/ijQY0+AESLEsw==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
@ -1353,14 +1353,14 @@
|
||||
"node": "^18.13.0 || >=20.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/core": "17.3.2",
|
||||
"@angular/core": "17.3.7",
|
||||
"rxjs": "^6.5.3 || ^7.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/compiler": {
|
||||
"version": "17.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-17.3.2.tgz",
|
||||
"integrity": "sha512-+/l/FQpVsOPbxZzSKyqEra+yxoI/r8LlTRqshVACv10+DKMWJMHnDkVUrNxvWHutfn4RszpGMtbtHp3yM9rxcA==",
|
||||
"version": "17.3.7",
|
||||
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-17.3.7.tgz",
|
||||
"integrity": "sha512-AlKiqPoxnrpQ0hn13fIaQPSVodaVAIjBW4vpFyuKFqs2LBKg6iolwZ21s8rEI0KR2gXl+8ugj0/UZ6YADiVM5w==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
@ -1368,7 +1368,7 @@
|
||||
"node": "^18.13.0 || >=20.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/core": "17.3.2"
|
||||
"@angular/core": "17.3.7"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@angular/core": {
|
||||
@ -1377,9 +1377,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/compiler-cli": {
|
||||
"version": "17.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-17.3.2.tgz",
|
||||
"integrity": "sha512-PG81BrJjeF679tkafjt+t9VEBE1rPq39cdLoBTnPY7Q+E/thVoem5JTRG6hmnLmwEc0xxY6sfYpvx2BB5ywUSA==",
|
||||
"version": "17.3.7",
|
||||
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-17.3.7.tgz",
|
||||
"integrity": "sha512-vSg5IQZ9jGmvYjpbfH8KbH4Sl1IVeE+Mr1ogcxkGEsURSRvKo7EWc0K7LSEI9+gg0VLamMiP9EyCJdPxiJeLJQ==",
|
||||
"dependencies": {
|
||||
"@babel/core": "7.23.9",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14",
|
||||
@ -1399,14 +1399,14 @@
|
||||
"node": "^18.13.0 || >=20.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/compiler": "17.3.2",
|
||||
"@angular/compiler": "17.3.7",
|
||||
"typescript": ">=5.2 <5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/core": {
|
||||
"version": "17.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular/core/-/core-17.3.2.tgz",
|
||||
"integrity": "sha512-eylatBGaN8uihKomEcXkaSHmAea5bEqu1OXifEoVOJiJpJA9Dbt/VcLXkIRFnRGH2NWUT5W79vSoU9GRvPMk5w==",
|
||||
"version": "17.3.7",
|
||||
"resolved": "https://registry.npmjs.org/@angular/core/-/core-17.3.7.tgz",
|
||||
"integrity": "sha512-HWcrbxqnvIMSxFuQdN0KPt08bc87hqr0LKm89yuRTUwx/2sNJlNQUobk6aJj4trswGBttcRDT+GOS4DQP2Nr4g==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
@ -1419,9 +1419,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/forms": {
|
||||
"version": "17.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-17.3.2.tgz",
|
||||
"integrity": "sha512-sbHYjAEeEWW+02YDEKuuuTEUukm6AayQuHiAu37vACj/2q/2RWQar49IoRcSJfAwP2ckqRSK4mmLoDX4IG/KSg==",
|
||||
"version": "17.3.7",
|
||||
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-17.3.7.tgz",
|
||||
"integrity": "sha512-FEhXh/VmT++XCoO8i7bBtzxG7Am/cE1zrr9aF+fWW+4jpWvJvVN1IaSiJxgBB+iPsOJ9lTBRwfRW3onlcDkhrw==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
@ -1429,16 +1429,16 @@
|
||||
"node": "^18.13.0 || >=20.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": "17.3.2",
|
||||
"@angular/core": "17.3.2",
|
||||
"@angular/platform-browser": "17.3.2",
|
||||
"@angular/common": "17.3.7",
|
||||
"@angular/core": "17.3.7",
|
||||
"@angular/platform-browser": "17.3.7",
|
||||
"rxjs": "^6.5.3 || ^7.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/localize": {
|
||||
"version": "17.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular/localize/-/localize-17.3.2.tgz",
|
||||
"integrity": "sha512-8DMdpWqBZwj367jdT2fSnD406wyNP6WD9wmZr1gzDyViGsM6xUM4udbIJHQ+EABkriSKj3usHqZw6LAzO9kepw==",
|
||||
"version": "17.3.7",
|
||||
"resolved": "https://registry.npmjs.org/@angular/localize/-/localize-17.3.7.tgz",
|
||||
"integrity": "sha512-GidwcxquawJBZXNQs6cJ3GvmyowupW9JFkG5sVsS6KG4yu9SIt4FZC+EbrVtYDhXI3U2wxGkm+9vDKvkSGzG0g==",
|
||||
"dependencies": {
|
||||
"@babel/core": "7.23.9",
|
||||
"@types/babel__core": "7.20.5",
|
||||
@ -1454,14 +1454,14 @@
|
||||
"node": "^18.13.0 || >=20.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/compiler": "17.3.2",
|
||||
"@angular/compiler-cli": "17.3.2"
|
||||
"@angular/compiler": "17.3.7",
|
||||
"@angular/compiler-cli": "17.3.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/platform-browser": {
|
||||
"version": "17.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.3.2.tgz",
|
||||
"integrity": "sha512-rBVmpJ/uh+CTjYef3Nib1K+31GFbM4mZaw2R2PowKZLgWOT3MWXKy41i44NEyM8qY1dxESmzMzy4NuGfZol42Q==",
|
||||
"version": "17.3.7",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.3.7.tgz",
|
||||
"integrity": "sha512-Nn8ZMaftAvO9dEwribWdNv+QBHhYIBrRkv85G6et80AXfXoYAr/xcfnQECRFtZgPmANqHC5auv/xrmExQG+Yeg==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
@ -1469,9 +1469,9 @@
|
||||
"node": "^18.13.0 || >=20.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/animations": "17.3.2",
|
||||
"@angular/common": "17.3.2",
|
||||
"@angular/core": "17.3.2"
|
||||
"@angular/animations": "17.3.7",
|
||||
"@angular/common": "17.3.7",
|
||||
"@angular/core": "17.3.7"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@angular/animations": {
|
||||
@ -1480,9 +1480,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/platform-browser-dynamic": {
|
||||
"version": "17.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-17.3.2.tgz",
|
||||
"integrity": "sha512-fcGo9yQ+t9VaG9zPgjQW5HIizbYOKj+9kVk9FPru+uJbYyvJUwEDgpD3aI0DUrQy/OvSf4NMzY/Ucgw1AUknQw==",
|
||||
"version": "17.3.7",
|
||||
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-17.3.7.tgz",
|
||||
"integrity": "sha512-9c2I4u0L1p2v1/lW8qy+WaNHisUWbyy6wqsv2v9FfCaSM49Lxymgo9LPFPC4qEG5ei5nE+eIQ2ocRiXXsf5QkQ==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
@ -1490,16 +1490,16 @@
|
||||
"node": "^18.13.0 || >=20.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": "17.3.2",
|
||||
"@angular/compiler": "17.3.2",
|
||||
"@angular/core": "17.3.2",
|
||||
"@angular/platform-browser": "17.3.2"
|
||||
"@angular/common": "17.3.7",
|
||||
"@angular/compiler": "17.3.7",
|
||||
"@angular/core": "17.3.7",
|
||||
"@angular/platform-browser": "17.3.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/router": {
|
||||
"version": "17.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@angular/router/-/router-17.3.2.tgz",
|
||||
"integrity": "sha512-BJiaG7zldhe8FPsg3Xv1o2xsmWNMIuntubRiSt2NlSceAr/NEgHoARpZfAGKTaFSngl6jc407wHOmBBPPALECw==",
|
||||
"version": "17.3.7",
|
||||
"resolved": "https://registry.npmjs.org/@angular/router/-/router-17.3.7.tgz",
|
||||
"integrity": "sha512-lMkuRrc1ZjP5JPDxNHqoAhB0uAnfPQ/q6mJrw1s8IZoVV6VyM+FxR5r13ajNcXWC38xy/YhBjpXPF1vBdxuLXg==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
},
|
||||
@ -1507,9 +1507,9 @@
|
||||
"node": "^18.13.0 || >=20.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": "17.3.2",
|
||||
"@angular/core": "17.3.2",
|
||||
"@angular/platform-browser": "17.3.2",
|
||||
"@angular/common": "17.3.7",
|
||||
"@angular/core": "17.3.7",
|
||||
"@angular/platform-browser": "17.3.7",
|
||||
"rxjs": "^6.5.3 || ^7.4.0"
|
||||
}
|
||||
},
|
||||
@ -4844,9 +4844,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@ngtools/webpack": {
|
||||
"version": "17.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-17.3.2.tgz",
|
||||
"integrity": "sha512-E8zejFF4aJ8l2XcF+GgnE/1IqsZepnPT1xzulLB4LXtjVuXLFLoF9xkHQwxs7cJWWZsxd/SlNsCIcn/ezrYBcQ==",
|
||||
"version": "17.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-17.3.6.tgz",
|
||||
"integrity": "sha512-equxbgh2DKzZtiFMoVf1KD4yJcH1q8lpqQ/GSPPQUvONcmHrr+yqdRUdaJ7oZCyCYmXF/nByBxtMKtJr6nKZVg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "^18.13.0 || >=20.9.0",
|
||||
@ -5604,13 +5604,13 @@
|
||||
]
|
||||
},
|
||||
"node_modules/@schematics/angular": {
|
||||
"version": "17.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-17.3.2.tgz",
|
||||
"integrity": "sha512-zPINvow0Qo6ionnDl25ZzSSLDyDxBjqRPEJWGHU70expbjXK4A2caQT9P/8ImhapbJAXJCfxg4GF9z1d/sWe4w==",
|
||||
"version": "17.3.6",
|
||||
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-17.3.6.tgz",
|
||||
"integrity": "sha512-jCNZdjHSVrI8TrrCnCoXC8GYvQRj7zh+SDdmm91Ve8dbikYNmBOKYLuPaCTsmojWx7ytv962yLlgKzpaa2bbfw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@angular-devkit/core": "17.3.2",
|
||||
"@angular-devkit/schematics": "17.3.2",
|
||||
"@angular-devkit/core": "17.3.6",
|
||||
"@angular-devkit/schematics": "17.3.6",
|
||||
"jsonc-parser": "3.2.1"
|
||||
},
|
||||
"engines": {
|
||||
@ -5935,9 +5935,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/express-serve-static-core": {
|
||||
"version": "4.17.43",
|
||||
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.43.tgz",
|
||||
"integrity": "sha512-oaYtiBirUOPQGSWNGPWnzyAFJ0BP3cwvN4oWZQY+zUBwpVIGsKUkpBpSztp74drYcjavs7SKFZ4DX1V2QeN8rg==",
|
||||
"version": "4.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.0.tgz",
|
||||
"integrity": "sha512-bGyep3JqPCRry1wq+O5n7oiBgGWmeIJXPjXXCo8EK0u8duZGSYar7cGqd3ML2JUsLGeB7fmc06KYo9fLGWqPvQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
@ -6046,9 +6046,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/qs": {
|
||||
"version": "6.9.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.14.tgz",
|
||||
"integrity": "sha512-5khscbd3SwWMhFqylJBLQ0zIu7c1K6Vz0uBIt915BI3zV0q1nfjRQD3RqSBcPaO6PHEF4ov/t9y89fSiyThlPA==",
|
||||
"version": "6.9.15",
|
||||
"resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz",
|
||||
"integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/range-parser": {
|
||||
@ -6089,14 +6089,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/serve-static": {
|
||||
"version": "1.15.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.5.tgz",
|
||||
"integrity": "sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==",
|
||||
"version": "1.15.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz",
|
||||
"integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/http-errors": "*",
|
||||
"@types/mime": "*",
|
||||
"@types/node": "*"
|
||||
"@types/node": "*",
|
||||
"@types/send": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/sockjs": {
|
||||
@ -9100,9 +9100,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ejs": {
|
||||
"version": "3.1.9",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz",
|
||||
"integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==",
|
||||
"version": "3.1.10",
|
||||
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
|
||||
"integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"jake": "^10.8.5"
|
||||
@ -11056,9 +11056,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ipaddr.js": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.1.0.tgz",
|
||||
"integrity": "sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==",
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz",
|
||||
"integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
@ -18066,9 +18066,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/undici": {
|
||||
"version": "6.7.1",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.7.1.tgz",
|
||||
"integrity": "sha512-+Wtb9bAQw6HYWzCnxrPTMVEV3Q1QjYanI0E4q02ehReMuquQdLTEFEYbfs7hcImVYKcQkWSwT6buEmSVIiDDtQ==",
|
||||
"version": "6.11.1",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.11.1.tgz",
|
||||
"integrity": "sha512-KyhzaLJnV1qa3BSHdj4AZ2ndqI0QWPxYzaIOio0WzcEJB9gvuysprJSLtpvc2D9mhR9jPDUk7xlJlZbH2KR5iw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=18.0"
|
||||
@ -18295,9 +18295,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "5.1.5",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.1.5.tgz",
|
||||
"integrity": "sha512-BdN1xh0Of/oQafhU+FvopafUp6WaYenLU/NFoL5WyJL++GxkNfieKzBhM24H3HVsPQrlAqB7iJYTHabzaRed5Q==",
|
||||
"version": "5.1.7",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.1.7.tgz",
|
||||
"integrity": "sha512-sgnEEFTZYMui/sTlH1/XEnVNHMujOahPLGMxn1+5sIT45Xjng1Ec1K78jRP15dSmVgg5WBin9yO81j3o9OxofA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.19.3",
|
||||
|
@ -11,15 +11,15 @@
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/cdk": "^17.3.2",
|
||||
"@angular/common": "~17.3.2",
|
||||
"@angular/compiler": "~17.3.2",
|
||||
"@angular/core": "~17.3.2",
|
||||
"@angular/forms": "~17.3.2",
|
||||
"@angular/localize": "~17.3.2",
|
||||
"@angular/platform-browser": "~17.3.2",
|
||||
"@angular/platform-browser-dynamic": "~17.3.2",
|
||||
"@angular/router": "~17.3.2",
|
||||
"@angular/cdk": "^17.3.6",
|
||||
"@angular/common": "~17.3.7",
|
||||
"@angular/compiler": "~17.3.7",
|
||||
"@angular/core": "~17.3.7",
|
||||
"@angular/forms": "~17.3.7",
|
||||
"@angular/localize": "~17.3.7",
|
||||
"@angular/platform-browser": "~17.3.7",
|
||||
"@angular/platform-browser-dynamic": "~17.3.7",
|
||||
"@angular/router": "~17.3.7",
|
||||
"@ng-bootstrap/ng-bootstrap": "^16.0.0",
|
||||
"@ng-select/ng-select": "^12.0.7",
|
||||
"@ngneat/dirty-check-forms": "^3.0.3",
|
||||
@ -40,14 +40,14 @@
|
||||
"zone.js": "^0.14.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-builders/jest": "17.0.2",
|
||||
"@angular-devkit/build-angular": "~17.3.2",
|
||||
"@angular-builders/jest": "17.0.3",
|
||||
"@angular-devkit/build-angular": "~17.3.6",
|
||||
"@angular-eslint/builder": "17.3.0",
|
||||
"@angular-eslint/eslint-plugin": "17.3.0",
|
||||
"@angular-eslint/eslint-plugin-template": "17.3.0",
|
||||
"@angular-eslint/schematics": "17.3.0",
|
||||
"@angular-eslint/template-parser": "17.3.0",
|
||||
"@angular/cli": "~17.3.2",
|
||||
"@angular/cli": "~17.3.6",
|
||||
"@angular/compiler-cli": "~17.3.2",
|
||||
"@playwright/test": "^1.42.1",
|
||||
"@types/jest": "^29.5.12",
|
||||
|
@ -1,31 +1,27 @@
|
||||
<div ngbDropdown #fieldDropdown="ngbDropdown" (openChange)="onOpenClose()">
|
||||
<div ngbDropdown #fieldDropdown="ngbDropdown" (openChange)="onOpenClose($event)">
|
||||
<button class="btn btn-sm btn-outline-primary" id="customFieldsDropdown" [disabled]="disabled" ngbDropdownToggle>
|
||||
<i-bs name="ui-radios"></i-bs>
|
||||
<div class="d-none d-sm-inline"> <ng-container i18n>Custom Fields</ng-container></div>
|
||||
</button>
|
||||
<div ngbDropdownMenu aria-labelledby="customFieldsDropdown" class="shadow custom-fields-dropdown">
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item">
|
||||
<pngx-input-select
|
||||
[items]="unusedFields"
|
||||
bindLabel="name"
|
||||
[(ngModel)]="field"
|
||||
[placeholder]="placeholderText"
|
||||
[notFoundText]="notFoundText"
|
||||
[disableCreateNew]="!canCreateFields"
|
||||
(createNew)="createField($event)"
|
||||
[hideAddButton]="true"
|
||||
bindValue="id">
|
||||
</pngx-input-select>
|
||||
<div class="btn-toolbar" role="toolbar">
|
||||
<button class="btn btn-sm btn-outline-secondary me-auto" type="button" (click)="createField()" [disabled]="!canCreateFields">
|
||||
<i-bs width="1em" height="1em" name="asterisk"></i-bs> <ng-container i18n>Create New Field</ng-container>
|
||||
<div class="list-group list-group-flush" (keydown)="listKeyDown($event)">
|
||||
<div class="list-group-item">
|
||||
<div class="input-group input-group-sm">
|
||||
<input class="form-control" type="text" [(ngModel)]="filterText" placeholder="Search fields" i18n-placeholder (keyup.enter)="listFilterEnter()" #listFilterTextInput>
|
||||
</div>
|
||||
</div>
|
||||
@for (field of filteredFields; track field.id) {
|
||||
<button class="list-group-item list-group-item-action bg-light" (click)="addField(field)" #button>
|
||||
<small class="d-flex">{{field.name}} <small class="ms-auto text-muted">{{getDataTypeLabel(field.data_type)}}</small></small>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-primary" type="button" (click)="addField(); fieldDropdown.close()" [disabled]="field === undefined">
|
||||
<i-bs width="1.2em" height="1.2em" name="plus-circle"></i-bs> <ng-container i18n>Add to document</ng-container>
|
||||
}
|
||||
@if (!filterText?.length || filteredFields.length === 0) {
|
||||
<button class="list-group-item list-group-item-action bg-light" (click)="createField(filterText)" [disabled]="!canCreateFields" #button>
|
||||
<small>
|
||||
<i-bs width=".9em" height=".9em" name="asterisk"></i-bs> <ng-container i18n>Create new field</ng-container>
|
||||
</small>
|
||||
</button>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
.custom-fields-dropdown {
|
||||
min-width: 380px;
|
||||
min-width: 300px;
|
||||
|
||||
// correct position on mobile
|
||||
@media (max-width: 575.98px) {
|
||||
@ -8,13 +8,3 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
::ng-deep .custom-fields-dropdown .ng-select .ng-select-container .ng-value-container .ng-placeholder,
|
||||
::ng-deep .custom-fields-dropdown .ng-select .ng-option,
|
||||
::ng-deep .custom-fields-dropdown .ng-select .ng-select-container .ng-value-container .ng-value {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
::ng-deep .custom-fields-dropdown .paperless-input-select .ng-select .ng-select-container .ng-value-container .ng-input {
|
||||
top: 4px;
|
||||
}
|
||||
|
@ -1,5 +1,9 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing'
|
||||
|
||||
import {
|
||||
ComponentFixture,
|
||||
TestBed,
|
||||
fakeAsync,
|
||||
tick,
|
||||
} from '@angular/core/testing'
|
||||
import { CustomFieldsDropdownComponent } from './custom-fields-dropdown.component'
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
@ -71,28 +75,33 @@ describe('CustomFieldsDropdownComponent', () => {
|
||||
let addedField
|
||||
component.added.subscribe((f) => (addedField = f))
|
||||
component.documentId = 11
|
||||
component.field = fields[0].id
|
||||
component.addField()
|
||||
component.addField({ field: fields[0].id } as any)
|
||||
expect(addedField).not.toBeUndefined()
|
||||
})
|
||||
|
||||
it('should clear field on open / close, updated unused fields', () => {
|
||||
component.field = fields[1].id
|
||||
component.onOpenClose()
|
||||
expect(component.field).toBeUndefined()
|
||||
|
||||
expect(component.unusedFields).toEqual(fields)
|
||||
const updateSpy = jest.spyOn(
|
||||
CustomFieldsDropdownComponent.prototype as any,
|
||||
'updateUnusedFields'
|
||||
)
|
||||
component.existingFields = [{ field: fields[1].id } as any]
|
||||
component.onOpenClose()
|
||||
expect(updateSpy).toHaveBeenCalled()
|
||||
expect(component.unusedFields).toEqual([fields[0]])
|
||||
it('should support filtering fields', () => {
|
||||
const input = fixture.debugElement.query(By.css('input'))
|
||||
input.nativeElement.value = 'Field 1'
|
||||
input.triggerEventHandler('input', { target: input.nativeElement })
|
||||
fixture.detectChanges()
|
||||
expect(component.filteredFields.length).toEqual(1)
|
||||
expect(component.filteredFields[0].name).toEqual('Field 1')
|
||||
})
|
||||
|
||||
it('should support creating field, show error if necessary', () => {
|
||||
it('should support update unused fields', () => {
|
||||
component.existingFields = [{ field: fields[0].id } as any]
|
||||
component['updateUnusedFields']()
|
||||
expect(component['unusedFields'].length).toEqual(1)
|
||||
expect(component['unusedFields'][0].name).toEqual('Field 2')
|
||||
})
|
||||
|
||||
it('should support getting data type label', () => {
|
||||
expect(component.getDataTypeLabel(CustomFieldDataType.Integer)).toEqual(
|
||||
'Integer'
|
||||
)
|
||||
})
|
||||
|
||||
it('should support creating field, show error if necessary, then add', fakeAsync(() => {
|
||||
let modal: NgbModalRef
|
||||
modalService.activeInstances.subscribe((m) => (modal = m[m.length - 1]))
|
||||
const toastErrorSpy = jest.spyOn(toastService, 'showError')
|
||||
@ -101,8 +110,9 @@ describe('CustomFieldsDropdownComponent', () => {
|
||||
CustomFieldsDropdownComponent.prototype as any,
|
||||
'getFields'
|
||||
)
|
||||
const addFieldSpy = jest.spyOn(component, 'addField')
|
||||
|
||||
const createButton = fixture.debugElement.queryAll(By.css('button'))[1]
|
||||
const createButton = fixture.debugElement.queryAll(By.css('button'))[3]
|
||||
createButton.triggerEventHandler('click')
|
||||
|
||||
expect(modal).not.toBeUndefined()
|
||||
@ -115,9 +125,11 @@ describe('CustomFieldsDropdownComponent', () => {
|
||||
|
||||
// succeed
|
||||
editDialog.succeeded.emit(fields[0])
|
||||
tick(100)
|
||||
expect(toastInfoSpy).toHaveBeenCalled()
|
||||
expect(getFieldsSpy).toHaveBeenCalled()
|
||||
})
|
||||
expect(addFieldSpy).toHaveBeenCalled()
|
||||
}))
|
||||
|
||||
it('should support creating field with name', () => {
|
||||
let modal: NgbModalRef
|
||||
@ -128,4 +140,106 @@ describe('CustomFieldsDropdownComponent', () => {
|
||||
const editDialog = modal.componentInstance as CustomFieldEditDialogComponent
|
||||
expect(editDialog.object.name).toEqual('Foo bar')
|
||||
})
|
||||
|
||||
it('should support arrow keyboard navigation', fakeAsync(() => {
|
||||
fixture.nativeElement
|
||||
.querySelector('button')
|
||||
.dispatchEvent(new MouseEvent('click')) // open
|
||||
fixture.detectChanges()
|
||||
tick(100)
|
||||
const filterInputEl: HTMLInputElement =
|
||||
component.listFilterTextInput.nativeElement
|
||||
expect(document.activeElement).toEqual(filterInputEl)
|
||||
const itemButtons = Array.from(
|
||||
(fixture.nativeElement as HTMLDivElement).querySelectorAll(
|
||||
'.custom-fields-dropdown button'
|
||||
)
|
||||
).filter((b) => b.textContent.includes('Field'))
|
||||
filterInputEl.dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true })
|
||||
)
|
||||
expect(document.activeElement).toEqual(itemButtons[0])
|
||||
itemButtons[0].dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true })
|
||||
)
|
||||
expect(document.activeElement).toEqual(itemButtons[1])
|
||||
itemButtons[1].dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'ArrowUp', bubbles: true })
|
||||
)
|
||||
expect(document.activeElement).toEqual(itemButtons[0])
|
||||
itemButtons[0].dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'ArrowUp', bubbles: true })
|
||||
)
|
||||
expect(document.activeElement).toEqual(filterInputEl)
|
||||
filterInputEl.value = 'foo'
|
||||
component.filterText = 'foo'
|
||||
|
||||
// dont move focus if we're traversing the field
|
||||
filterInputEl.selectionStart = 1
|
||||
expect(document.activeElement).toEqual(filterInputEl)
|
||||
|
||||
// now we're at end, so move focus
|
||||
filterInputEl.selectionStart = 3
|
||||
filterInputEl.dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true })
|
||||
)
|
||||
expect(document.activeElement).toEqual(itemButtons[0])
|
||||
}))
|
||||
|
||||
it('should support arrow keyboard navigation after tab keyboard navigation', fakeAsync(() => {
|
||||
fixture.nativeElement
|
||||
.querySelector('button')
|
||||
.dispatchEvent(new MouseEvent('click')) // open
|
||||
fixture.detectChanges()
|
||||
tick(100)
|
||||
const filterInputEl: HTMLInputElement =
|
||||
component.listFilterTextInput.nativeElement
|
||||
expect(document.activeElement).toEqual(filterInputEl)
|
||||
const itemButtons = Array.from(
|
||||
(fixture.nativeElement as HTMLDivElement).querySelectorAll(
|
||||
'.custom-fields-dropdown button'
|
||||
)
|
||||
).filter((b) => b.textContent.includes('Field'))
|
||||
filterInputEl.dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'Tab', bubbles: true })
|
||||
)
|
||||
itemButtons[0]['focus']() // normally handled by browser
|
||||
itemButtons[0].dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'Tab', bubbles: true })
|
||||
)
|
||||
itemButtons[1]['focus']() // normally handled by browser
|
||||
itemButtons[1].dispatchEvent(
|
||||
new KeyboardEvent('keydown', {
|
||||
key: 'Tab',
|
||||
shiftKey: true,
|
||||
bubbles: true,
|
||||
})
|
||||
)
|
||||
itemButtons[0]['focus']() // normally handled by browser
|
||||
itemButtons[0].dispatchEvent(
|
||||
new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true })
|
||||
)
|
||||
expect(document.activeElement).toEqual(itemButtons[1])
|
||||
}))
|
||||
|
||||
it('should support enter keyboard navigation', fakeAsync(() => {
|
||||
jest.spyOn(component, 'canCreateFields', 'get').mockReturnValue(true)
|
||||
const addFieldSpy = jest.spyOn(component, 'addField')
|
||||
const createFieldSpy = jest.spyOn(component, 'createField')
|
||||
component.filterText = 'Field 1'
|
||||
component.listFilterEnter()
|
||||
expect(addFieldSpy).toHaveBeenCalled()
|
||||
|
||||
component.filterText = 'Field 3'
|
||||
component.listFilterEnter()
|
||||
expect(createFieldSpy).toHaveBeenCalledWith('Field 3')
|
||||
|
||||
addFieldSpy.mockClear()
|
||||
createFieldSpy.mockClear()
|
||||
|
||||
component.filterText = undefined
|
||||
component.listFilterEnter()
|
||||
expect(createFieldSpy).not.toHaveBeenCalled()
|
||||
expect(addFieldSpy).not.toHaveBeenCalled()
|
||||
}))
|
||||
})
|
||||
|
@ -1,13 +1,17 @@
|
||||
import {
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
Input,
|
||||
OnDestroy,
|
||||
Output,
|
||||
QueryList,
|
||||
ViewChild,
|
||||
ViewChildren,
|
||||
} from '@angular/core'
|
||||
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'
|
||||
import { Subject, first, takeUntil } from 'rxjs'
|
||||
import { CustomField } from 'src/app/data/custom-field'
|
||||
import { CustomField, DATA_TYPE_LABELS } from 'src/app/data/custom-field'
|
||||
import { CustomFieldInstance } from 'src/app/data/custom-field-instance'
|
||||
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
@ -39,23 +43,25 @@ export class CustomFieldsDropdownComponent implements OnDestroy {
|
||||
@Output()
|
||||
created: EventEmitter<CustomField> = new EventEmitter()
|
||||
|
||||
@ViewChild('listFilterTextInput') listFilterTextInput: ElementRef
|
||||
@ViewChildren('button') buttons: QueryList<ElementRef>
|
||||
|
||||
private customFields: CustomField[] = []
|
||||
public unusedFields: CustomField[]
|
||||
private unusedFields: CustomField[] = []
|
||||
private keyboardIndex: number
|
||||
|
||||
public name: string
|
||||
public get filteredFields(): CustomField[] {
|
||||
return this.unusedFields.filter(
|
||||
(f) =>
|
||||
!this.filterText ||
|
||||
f.name.toLowerCase().includes(this.filterText.toLowerCase())
|
||||
)
|
||||
}
|
||||
|
||||
public field: number
|
||||
public filterText: string
|
||||
|
||||
private unsubscribeNotifier: Subject<any> = new Subject()
|
||||
|
||||
get placeholderText(): string {
|
||||
return $localize`Choose field`
|
||||
}
|
||||
|
||||
get notFoundText(): string {
|
||||
return $localize`No unused fields found`
|
||||
}
|
||||
|
||||
get canCreateFields(): boolean {
|
||||
return this.permissionsService.currentUserCan(
|
||||
PermissionAction.Add,
|
||||
@ -87,28 +93,26 @@ export class CustomFieldsDropdownComponent implements OnDestroy {
|
||||
})
|
||||
}
|
||||
|
||||
public getCustomFieldFromInstance(
|
||||
instance: CustomFieldInstance
|
||||
): CustomField {
|
||||
return this.customFields.find((f) => f.id === instance.field)
|
||||
}
|
||||
|
||||
private updateUnusedFields() {
|
||||
this.unusedFields = this.customFields.filter(
|
||||
(f) =>
|
||||
!this.existingFields?.find(
|
||||
(e) => this.getCustomFieldFromInstance(e)?.id === f.id
|
||||
)
|
||||
(f) => !this.existingFields?.find((e) => e.field === f.id)
|
||||
)
|
||||
}
|
||||
|
||||
onOpenClose() {
|
||||
this.field = undefined
|
||||
onOpenClose(open: boolean) {
|
||||
if (open) {
|
||||
setTimeout(() => {
|
||||
this.listFilterTextInput.nativeElement.focus()
|
||||
}, 100)
|
||||
} else {
|
||||
this.filterText = undefined
|
||||
}
|
||||
this.updateUnusedFields()
|
||||
}
|
||||
|
||||
addField() {
|
||||
this.added.emit(this.customFields.find((f) => f.id === this.field))
|
||||
addField(field: CustomField) {
|
||||
this.added.emit(field)
|
||||
this.updateUnusedFields()
|
||||
}
|
||||
|
||||
createField(newName: string = null) {
|
||||
@ -121,6 +125,7 @@ export class CustomFieldsDropdownComponent implements OnDestroy {
|
||||
this.customFieldsService.clearCache()
|
||||
this.getFields()
|
||||
this.created.emit(newField)
|
||||
setTimeout(() => this.addField(newField), 100)
|
||||
})
|
||||
modal.componentInstance.failed
|
||||
.pipe(takeUntil(this.unsubscribeNotifier))
|
||||
@ -128,4 +133,82 @@ export class CustomFieldsDropdownComponent implements OnDestroy {
|
||||
this.toastService.showError($localize`Error saving field.`, e)
|
||||
})
|
||||
}
|
||||
|
||||
getDataTypeLabel(dataType: string) {
|
||||
return DATA_TYPE_LABELS.find((l) => l.id === dataType)?.name
|
||||
}
|
||||
|
||||
public listFilterEnter() {
|
||||
if (this.filteredFields.length === 1) {
|
||||
this.addField(this.filteredFields[0])
|
||||
} else if (
|
||||
this.filterText &&
|
||||
this.filteredFields.length === 0 &&
|
||||
this.canCreateFields
|
||||
) {
|
||||
this.createField(this.filterText)
|
||||
}
|
||||
}
|
||||
|
||||
private focusNextButtonItem(setFocus: boolean = true) {
|
||||
this.keyboardIndex = Math.min(
|
||||
this.buttons.length - 1,
|
||||
this.keyboardIndex + 1
|
||||
)
|
||||
if (setFocus) this.setButtonItemFocus()
|
||||
}
|
||||
|
||||
focusPreviousButtonItem(setFocus: boolean = true) {
|
||||
this.keyboardIndex = Math.max(0, this.keyboardIndex - 1)
|
||||
if (setFocus) this.setButtonItemFocus()
|
||||
}
|
||||
|
||||
setButtonItemFocus() {
|
||||
this.buttons.get(this.keyboardIndex)?.nativeElement.focus()
|
||||
}
|
||||
|
||||
public listKeyDown(event: KeyboardEvent) {
|
||||
switch (event.key) {
|
||||
case 'ArrowDown':
|
||||
if (event.target instanceof HTMLInputElement) {
|
||||
if (
|
||||
!this.filterText ||
|
||||
event.target.selectionStart === this.filterText.length
|
||||
) {
|
||||
this.keyboardIndex = -1
|
||||
this.focusNextButtonItem()
|
||||
event.preventDefault()
|
||||
}
|
||||
} else if (event.target instanceof HTMLButtonElement) {
|
||||
this.focusNextButtonItem()
|
||||
event.preventDefault()
|
||||
}
|
||||
break
|
||||
case 'ArrowUp':
|
||||
if (event.target instanceof HTMLButtonElement) {
|
||||
if (this.keyboardIndex === 0) {
|
||||
this.listFilterTextInput.nativeElement.focus()
|
||||
} else {
|
||||
this.focusPreviousButtonItem()
|
||||
}
|
||||
event.preventDefault()
|
||||
}
|
||||
break
|
||||
case 'Tab':
|
||||
// just track the index in case user uses arrows
|
||||
if (event.target instanceof HTMLInputElement) {
|
||||
this.keyboardIndex = 0
|
||||
} else if (event.target instanceof HTMLButtonElement) {
|
||||
if (event.shiftKey) {
|
||||
if (this.keyboardIndex > 0) {
|
||||
this.focusPreviousButtonItem(false)
|
||||
}
|
||||
} else {
|
||||
this.focusNextButtonItem(false)
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -949,9 +949,9 @@ describe('DocumentDetailComponent', () => {
|
||||
fixture.detectChanges()
|
||||
expect(component.document.custom_fields).toHaveLength(initialLength - 1)
|
||||
expect(component.customFieldFormFields).toHaveLength(initialLength - 1)
|
||||
expect(fixture.debugElement.nativeElement.textContent).not.toContain(
|
||||
'Field 1'
|
||||
)
|
||||
expect(
|
||||
fixture.debugElement.query(By.css('form')).nativeElement.textContent
|
||||
).not.toContain('Field 1')
|
||||
const updateSpy = jest.spyOn(documentService, 'update')
|
||||
component.save(true)
|
||||
expect(updateSpy.mock.lastCall[0].custom_fields).toHaveLength(
|
||||
|
@ -674,13 +674,17 @@ export class DocumentDetailComponent
|
||||
this.documentForm.patchValue(docValues)
|
||||
this.store.next(this.documentForm.value)
|
||||
this.openDocumentService.setDirty(this.document, false)
|
||||
this.openDocumentService.save()
|
||||
this.toastService.showInfo($localize`Document saved successfully.`)
|
||||
this.networkActive = false
|
||||
this.error = null
|
||||
close &&
|
||||
if (close) {
|
||||
this.close(() =>
|
||||
this.openDocumentService.refreshDocument(this.documentId)
|
||||
)
|
||||
} else {
|
||||
this.openDocumentService.refreshDocument(this.documentId)
|
||||
}
|
||||
},
|
||||
error: (error) => {
|
||||
this.networkActive = false
|
||||
|
@ -72,7 +72,7 @@
|
||||
[disabled]="disabled"
|
||||
shortcutKey="u"></pngx-filterable-dropdown>
|
||||
}
|
||||
@if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.StoragePath)) {
|
||||
@if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.StoragePath) && storagePaths.length > 0) {
|
||||
<pngx-filterable-dropdown class="flex-fill" title="Storage path" icon="folder-fill" i18n-title
|
||||
filterPlaceholder="Filter storage paths" i18n-filterPlaceholder
|
||||
[items]="storagePaths"
|
||||
@ -85,7 +85,7 @@
|
||||
shortcutKey="i"></pngx-filterable-dropdown>
|
||||
}
|
||||
|
||||
@if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.CustomField)) {
|
||||
@if (permissionsService.currentUserCan(PermissionAction.View, PermissionType.CustomField) && customFields.length > 0) {
|
||||
<pngx-filterable-dropdown class="flex-fill" title="Custom fields" icon="ui-radios" i18n-title
|
||||
filterPlaceholder="Filter custom fields" i18n-filterPlaceholder
|
||||
[items]="customFields"
|
||||
|
@ -33,8 +33,9 @@ describe('CustomDatePipe', () => {
|
||||
const notNow = new Date(now)
|
||||
notNow.setDate(now.getDate() - 1)
|
||||
expect(datePipe.transform(notNow, 'relative')).toEqual('1 day ago')
|
||||
notNow.setDate(now.getDate() - 2)
|
||||
expect(datePipe.transform(notNow, 'relative')).toEqual('2 days ago')
|
||||
notNow.setDate(now.getDate())
|
||||
notNow.setMonth(now.getMonth() - 1)
|
||||
expect(datePipe.transform(notNow, 'relative')).toEqual('1 month ago')
|
||||
expect(datePipe.transform(now, 'relative')).toEqual('Just now')
|
||||
})
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user