Merge branch 'dev' into feature-global-search

This commit is contained in:
shamoon 2024-05-01 22:02:11 -07:00
commit 4877a8e263
11 changed files with 800 additions and 864 deletions

File diff suppressed because it is too large Load Diff

258
src-ui/package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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">&nbsp;<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>&nbsp;<ng-container i18n>Create New Field</ng-container>
</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>&nbsp;<ng-container i18n>Add to document</ng-container>
</button>
<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>
</li>
</ul>
</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>
}
@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>&nbsp;<ng-container i18n>Create new field</ng-container>
</small>
</button>
}
</div>
</div>
</div>

View File

@ -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;
}

View File

@ -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()
}))
})

View File

@ -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
}
}
}

View File

@ -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(

View File

@ -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

View File

@ -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"

View File

@ -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')
})
})