diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index da4005ea4701c0cce6f1f64c25a93c3bdaa98af1..88620b3228b28ec5d5def6fa3a40f326987f8a67 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,6 +1,6 @@ cache: paths: - - node_modules/ + - node_modules/ stages: - build @@ -29,4 +29,4 @@ publish package to npm repository: - echo "//registry.npmjs.org/:_authToken=${NPM_REGISTRY_TOKEN}" > .npmrc - npm install - if [[ "$CI_COMMIT_TAG" == v* ]]; then npm run ci-publish $CI_COMMIT_TAG; fi - - if [[ "$CI_COMMIT_TAG" == *@* ]]; then npm run ci-publish $CI_COMMIT_TAG; fi \ No newline at end of file + - if [[ "$CI_COMMIT_TAG" == *@* ]]; then npm run ci-publish $CI_COMMIT_TAG; fi diff --git a/.npmrc b/.npmrc index 95b9bdc55e1fe67f19fd6f2ba741ab54c25cde15..69bb280739e850d4b892c0dd922c40e143691a48 100644 --- a/.npmrc +++ b/.npmrc @@ -1,4 +1,4 @@ -@piveau:registry=https://paca.fokus.fraunhofer.de/repository/npm-hosted/ +#@piveau:registry=https://paca.fokus.fraunhofer.de/repository/npm-hosted/ @deu:registry=https://paca.fokus.fraunhofer.de/repository/npm-group/ ########### Verdaccio: ########### diff --git a/apps/vanilla-piveau-hub-ui b/apps/vanilla-piveau-hub-ui index 574d0ea043a8a88e53af8638ed96ea0e9378720e..213dd60b390a07693073693d9c4900ad5a3121a1 160000 --- a/apps/vanilla-piveau-hub-ui +++ b/apps/vanilla-piveau-hub-ui @@ -1 +1 @@ -Subproject commit 574d0ea043a8a88e53af8638ed96ea0e9378720e +Subproject commit 213dd60b390a07693073693d9c4900ad5a3121a1 diff --git a/packages/piveau-hub-ui-modules/3rd-party-licenses.csv b/packages/piveau-hub-ui-modules/3rd-party-licenses.csv new file mode 100644 index 0000000000000000000000000000000000000000..4c3c01b8c9ce2040e8345af9e6f5c33e1e3de912 --- /dev/null +++ b/packages/piveau-hub-ui-modules/3rd-party-licenses.csv @@ -0,0 +1,2 @@ +"module name","license","repository" +"@piveau/piveau-hub-ui-modules@4.4.8","Apache-2.0","" \ No newline at end of file diff --git a/packages/piveau-hub-ui-modules/CHANGELOG.md b/packages/piveau-hub-ui-modules/CHANGELOG.md index 65e4acd41e0890433041f8e516c7474e21656589..23b307c6a48774c124933d5a767b9195cf65bf65 100644 --- a/packages/piveau-hub-ui-modules/CHANGELOG.md +++ b/packages/piveau-hub-ui-modules/CHANGELOG.md @@ -23,6 +23,61 @@ # [80.0.0-alpha.39](https://gitlab.com/piveau/ui/piveau-ui/compare/v80.0.0-alpha.38...v80.0.0-alpha.39) (2025-01-29) +## [4.6.13](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.6.12...v4.6.13) (2025-02-18) + + +### Bug Fixes + +* **dpi:** disable catalog selection on edit ([94210e2](https://gitlab.com/piveau/ui/piveau-ui/commit/94210e282885a913d105089029e2414e4b9fe62a)) +* **dpi:** licence -> licenses ([799d4b8](https://gitlab.com/piveau/ui/piveau-ui/commit/799d4b8b9c29126631c01b7d10d328260296af8c)) + + + +## [4.6.12](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.6.11...v4.6.12) (2025-02-17) + + + +## [4.6.11](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.6.10...v4.6.11) (2025-02-13) + + +### Bug Fixes + +* handling of downloadprogress modal with activated cookie ([9a973a5](https://gitlab.com/piveau/ui/piveau-ui/commit/9a973a537054f7bf79f93aff1fda928ed094cc6f)) +* modal cookie conditional ([6f017c0](https://gitlab.com/piveau/ui/piveau-ui/commit/6f017c0f9d0945273f67db4f549ee6ef9de56055)) +* prevent close option by outside click or close button ([f02cd91](https://gitlab.com/piveau/ui/piveau-ui/commit/f02cd91094111a51ff36b60a4f14d0ad37ff6b4a)) +* text on downloadall button during download ([44cbf15](https://gitlab.com/piveau/ui/piveau-ui/commit/44cbf15f6c02cd5e6ae5210adf09694dc5f42080)) +* variable assignment in dpi ([9ceb086](https://gitlab.com/piveau/ui/piveau-ui/commit/9ceb0867c6722a87e525e69043c53d7784791025)) + + + +## [4.6.10](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.6.9...v4.6.10) (2025-02-13) + + +### Bug Fixes + +* **SimpleSelect:** fix bug that caused catalog select to break when catalog contains no title ([9c8ca53](https://gitlab.com/piveau/ui/piveau-ui/commit/9c8ca5307ad678731325b291e43dcde3334b903d)) + + + +## [4.6.9](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.6.8...v4.6.9) (2025-02-12) + + +### Bug Fixes + +* **SimpleSelect:** use more convenient template structuring to not break accordion logic ([f9e6123](https://gitlab.com/piveau/ui/piveau-ui/commit/f9e612348247a64b61b92a6c27303efd116cdaa3)) + + + +## [4.6.8](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.6.7...v4.6.8) (2025-02-12) + + +### Bug Fixes + +* **SimpleSelect:** rework SimpleSelect to make catalogue selection more robust ([9445a6e](https://gitlab.com/piveau/ui/piveau-ui/commit/9445a6e31120f323bca3c54da37306a8f0d98be3)) + + + +## [4.6.7](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.6.6...v4.6.7) (2025-02-06) ### Bug Fixes @@ -32,6 +87,11 @@ # [80.0.0-alpha.38](https://gitlab.com/piveau/ui/piveau-ui/compare/v80.0.0-alpha.37...v80.0.0-alpha.38) (2025-01-28) +* fix preview button not working for geo distributions ([414d26d](https://gitlab.com/piveau/ui/piveau-ui/commit/414d26de5f538fa276549565c2f41ae89f660215)) + + + +## [4.6.6](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.6.5...v4.6.6) (2025-01-30) ### Bug Fixes @@ -65,6 +125,12 @@ # [80.0.0-alpha.31](https://gitlab.com/piveau/ui/piveau-ui/compare/v80.0.0-alpha.30...v80.0.0-alpha.31) (2025-01-15) +* fix minor syntax ([55923f2](https://gitlab.com/piveau/ui/piveau-ui/commit/55923f2b5fc04c7d567db6bfd7e814cf92f60f15)) +* rename visualize -> preview + rearrange ([0086bef](https://gitlab.com/piveau/ui/piveau-ui/commit/0086bef61e406914e2faa871c25ed80a0ea10bf6)) + + + +## [4.6.5](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.6.4...v4.6.5) (2025-01-28) ### Bug Fixes @@ -84,6 +150,15 @@ # [80.0.0-alpha.28](https://gitlab.com/piveau/ui/piveau-ui/compare/v80.0.0-alpha.27...v80.0.0-alpha.28) (2025-01-13) +* minor csv class namings to allow csv overwrite ([77f0cde](https://gitlab.com/piveau/ui/piveau-ui/commit/77f0cde0936bdbca1e0bed082fdfff703e4644da)) + + + +## [4.6.4](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.6.3...v4.6.4) (2025-01-27) + + + +## [4.6.3](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.6.2...v4.6.3) (2025-01-23) ### Bug Fixes @@ -100,6 +175,11 @@ # [80.0.0-alpha.26](https://gitlab.com/piveau/ui/piveau-ui/compare/v80.0.0-alpha.25...v80.0.0-alpha.26) (2025-01-08) +* remove preview button when visualise is active ([401ddac](https://gitlab.com/piveau/ui/piveau-ui/commit/401ddac0077d4806cebd36e8275cbacd3ca7b27e)) + + + +## [4.6.2](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.6.1...v4.6.2) (2025-01-22) ### Bug Fixes @@ -127,6 +207,15 @@ # [80.0.0-alpha.22](https://gitlab.com/piveau/ui/piveau-ui/compare/v80.0.0-alpha.21...v80.0.0-alpha.22) (2025-01-07) +* fix switching between visualisation of different distributions + implement auto scroll on click ([861c307](https://gitlab.com/piveau/ui/piveau-ui/commit/861c307e42e2cc8f2a98fa132cff679ba0c330a6)) + + + +## [4.6.1](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.6.0...v4.6.1) (2025-01-22) + + + +# [4.6.0](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.5.13...v4.6.0) (2025-01-20) ### Features @@ -144,6 +233,23 @@ # [80.0.0-alpha.19](https://gitlab.com/piveau/ui/piveau-ui/compare/v80.0.0-alpha.18...v80.0.0-alpha.19) (2024-12-18) +* **DataProviderInterface:** enables standalone DPI operation ([ec9e235](https://gitlab.com/piveau/ui/piveau-ui/commit/ec9e2359cd2767fcd19860537702a18cc3d54ca7)) + + + +## [4.5.13](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.5.12...v4.5.13) (2025-01-16) + + + +## [4.5.12](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.5.11...v4.5.12) (2025-01-15) + + + +## [4.5.11](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.5.10...v4.5.11) (2025-01-14) + + + +## [4.5.10](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.5.9...v4.5.10) (2025-01-10) ### Bug Fixes @@ -190,6 +296,11 @@ # [80.0.0-alpha.9](https://gitlab.com/piveau/ui/piveau-ui/compare/v80.0.0-alpha.8...v80.0.0-alpha.9) (2024-12-04) +* use established version of datasetsfacets ([3d16bbc](https://gitlab.com/piveau/ui/piveau-ui/commit/3d16bbc364fb8806ef2aec878aa717884262c290)) + + + +## [4.5.9](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.5.8...v4.5.9) (2024-12-19) ### Bug Fixes @@ -199,6 +310,12 @@ # [80.0.0-alpha.8](https://gitlab.com/piveau/ui/piveau-ui/compare/v80.0.0-alpha.7...v80.0.0-alpha.8) (2024-12-03) +* hide table for PPE ([4a034ec](https://gitlab.com/piveau/ui/piveau-ui/commit/4a034eca021f63f0a669c00a540846f1334f8784)) +* PPE UI ([b8e3ef9](https://gitlab.com/piveau/ui/piveau-ui/commit/b8e3ef9c6903ca5ef736bf41fdf6963e8680425f)) + + + +## [4.5.8](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.5.7...v4.5.8) (2024-12-19) ### Bug Fixes @@ -244,6 +361,401 @@ +# [100.0.0-alpha.0](https://gitlab.com/piveau/ui/piveau-ui/compare/v99.99.99...v100.0.0-alpha.0) (2024-10-10) + + + +## [99.99.99](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.3.4...v99.99.99) (2024-10-10) +* better typing and no store use for facet injection ([82d71b3](https://gitlab.com/piveau/ui/piveau-ui/commit/82d71b38340256328c271cc50b041b71e58ce8a9)) +* correct usefacets composable ([2f5bf2a](https://gitlab.com/piveau/ui/piveau-ui/commit/2f5bf2ae93fabf8573901906bdba386ac97ab7a1)) +* extract query param handling ([fafb7ea](https://gitlab.com/piveau/ui/piveau-ui/commit/fafb7ea4ce1165fd934aab3af5b6f789be08de65)) +* getFacet.ts only taking id arg ([643d640](https://gitlab.com/piveau/ui/piveau-ui/commit/643d64027b0d7248d186c187e2e25f8544976315)) +* implement facetclicked emit ([ace90c8](https://gitlab.com/piveau/ui/piveau-ui/commit/ace90c854ce4fd1be9042e5ee5ae69b4565d4b5e)) +* improve facet clickhandling and display ([a8ef6ad](https://gitlab.com/piveau/ui/piveau-ui/commit/a8ef6ad322583fd4283b3dd42c1a1f8e1019e410)) +* introduce composable for facet ordering and filtering ([d691f16](https://gitlab.com/piveau/ui/piveau-ui/commit/d691f16d41236f78e51f148d2a439796d79cf151)) +* introduce facet stat ([29459d1](https://gitlab.com/piveau/ui/piveau-ui/commit/29459d1668d108687e1fc97ec1a034b3243c594e)) +* introduce search component ([cdcb803](https://gitlab.com/piveau/ui/piveau-ui/commit/cdcb803803a12f8912a8e8b1bf156f921a141553)) +* use env composable ([fe7bbb3](https://gitlab.com/piveau/ui/piveau-ui/commit/fe7bbb3ae93d5ce1b230068d6b77d8dee04e7e77)) + + +### Features + +* integrate ExpandableSelectFacet.vue into new facet handling ([2a5af46](https://gitlab.com/piveau/ui/piveau-ui/commit/2a5af460eb62e7e9b596f74294437e857c0a70c4)) + + + +## [4.5.7](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.5.6...v4.5.7) (2024-12-18) + + +### Bug Fixes + +* Ui formating ([20d01fc](https://gitlab.com/piveau/ui/piveau-ui/commit/20d01fc75908ddca38487832aab58ec0d3d1fcae)) +* Ui_Formatting_fix ([06853be](https://gitlab.com/piveau/ui/piveau-ui/commit/06853befc95ee0169d082e9159a6956008bcddc0)) + + + +## [4.5.6](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.5.5...v4.5.6) (2024-12-11) + + +### Bug Fixes + +* **dpi:** improve label clarity for spatialinput ([b05a046](https://gitlab.com/piveau/ui/piveau-ui/commit/b05a04656c908bdc31790c2a3250c46e6c7df819)) + + + +## [4.5.5](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.5.4...v4.5.5) (2024-12-09) + + +### Bug Fixes + +* eurovoc clean ([627f9f8](https://gitlab.com/piveau/ui/piveau-ui/commit/627f9f82c57ebdc466c398f20fe82c720d349c7a)) +* uifix ([53d04f6](https://gitlab.com/piveau/ui/piveau-ui/commit/53d04f6649466841dbb65676be22d406f79d9ae6)) + + + +## [4.5.4](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.5.3...v4.5.4) (2024-12-05) + + +### Bug Fixes + +* only show visualize button on distributions with supported formats ([fddaae7](https://gitlab.com/piveau/ui/piveau-ui/commit/fddaae72f5d359bdd96edebdd04aeac369417eec)) + + + +## [4.5.3](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.5.2...v4.5.3) (2024-12-05) + + +### Bug Fixes + +* fix distribution format for visualisation tool ([0f97c41](https://gitlab.com/piveau/ui/piveau-ui/commit/0f97c41cb26e0770db5f6288715eda3bf493bad0)) + + + +## [4.5.2](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.5.1...v4.5.2) (2024-12-04) + + +### Bug Fixes + +* formating ([632dec0](https://gitlab.com/piveau/ui/piveau-ui/commit/632dec0d3d62bc710e6693523b09c17fb007c994)) +* userprofilefix ([0c0760b](https://gitlab.com/piveau/ui/piveau-ui/commit/0c0760baf6f562f67b114ace013e5401bdbbb64c)) +* userprofilepage ([b36c158](https://gitlab.com/piveau/ui/piveau-ui/commit/b36c15898268c9761c0326911db499aeb041c78e)) + + + +## [4.5.1](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.5.0...v4.5.1) (2024-11-25) + + +### Bug Fixes + +* **dpi:** remove debug information ([8b9bc2d](https://gitlab.com/piveau/ui/piveau-ui/commit/8b9bc2d1702d2f160009ff2e6563f81bc51eae6a)) + + + +# [4.5.0](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.4.29...v4.5.0) (2024-11-25) + + +### Bug Fixes + +* **dpi:** Fix Repeatable input elements not being displayed; address repeatable problems specific for SpatialInputs ([5ef443b](https://gitlab.com/piveau/ui/piveau-ui/commit/5ef443b61a42eae139e359376a895b3ad52bc20f)) + + +### Features + +* **DatasetDetails:** expose DatasetDetailsDataset through configureComponent; add slots to DatasetDetailsProperties ([7d5ad2b](https://gitlab.com/piveau/ui/piveau-ui/commit/7d5ad2b9fae1fc4f8dcc64065cedf886fbd0ff46)) +* **dpi:** make autocomplete implementation customizable; fix issue preventing license field not being resolved properly when editing ([b23c2b0](https://gitlab.com/piveau/ui/piveau-ui/commit/b23c2b0f6ab152d4f8964b13e17bc1986426f68b)) + + + +## [4.4.29](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.4.28...v4.4.29) (2024-11-22) + + +### Bug Fixes + +* quickfix for the legal notice ([ddd161b](https://gitlab.com/piveau/ui/piveau-ui/commit/ddd161b264299eb1a5332eff76db264f99bd545e)) + + + +## [4.4.28](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.4.27...v4.4.28) (2024-11-20) + + +### Bug Fixes + +* removed the details element ([b29e559](https://gitlab.com/piveau/ui/piveau-ui/commit/b29e5596beb8699bdcda66a94c2d5428f2dbdb7c)) + + + +## [4.4.27](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.4.26...v4.4.27) (2024-11-19) + + +### Bug Fixes + +* fixed the repeatable (addition and deletion of repeatable fields) ([81236d4](https://gitlab.com/piveau/ui/piveau-ui/commit/81236d49c49872e8503fc4d4a9b5cef9d1ef6482)) +* quickfix for the catalogue selection in harvested datasets ([00497b8](https://gitlab.com/piveau/ui/piveau-ui/commit/00497b8b230a4b876719a9f6980d77dd4d2cfad4)) + + + +## [4.4.26](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.4.25...v4.4.26) (2024-11-18) + + +### Bug Fixes + +* addedlegalnotice to overview tab ([2d1b6d1](https://gitlab.com/piveau/ui/piveau-ui/commit/2d1b6d18f886f47188269b4475a077a441f9d95a)) + + + +## [4.4.25](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.4.24...v4.4.25) (2024-11-14) + + +### Bug Fixes + +* **dpi:** fix dcatde prefix for dcat-ap.de spec ([9e123ce](https://gitlab.com/piveau/ui/piveau-ui/commit/9e123cebe63cf444a4a1f39b9463813036cf94b7)) + + + +## [4.4.24](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.4.23...v4.4.24) (2024-11-12) + + +### Bug Fixes + +* **dpi:** fix edit mode not working on direct navigation ([050118f](https://gitlab.com/piveau/ui/piveau-ui/commit/050118f796aad8b134be61b072e4ce96a323e4c5)) +* fix bug where visualisation is activated by clicking on wrong buttons ([2683786](https://gitlab.com/piveau/ui/piveau-ui/commit/268378624129d2bcbb9dea99786f5b04776e70a2)) + + + +## [4.4.23](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.4.21...v4.4.23) (2024-11-08) + + +### Bug Fixes + +* **dpi:** fix edge case temporal resolution causing edit mode to break ([f7e4cfc](https://gitlab.com/piveau/ui/piveau-ui/commit/f7e4cfc1c4dfcbcf07203159f39a6185f1c8dffe)) +* **dpi:** fix spatialinput to show vocabulary only ([43b6e89](https://gitlab.com/piveau/ui/piveau-ui/commit/43b6e89d86692f1be3b1ec85b1e28a180b6c3147)) +* **dpi:** fix UniqueIdentifierInput being sometimes disabled when creating new dataset ([bc58d60](https://gitlab.com/piveau/ui/piveau-ui/commit/bc58d60e3420149d77c62fa62e33c82c55d61b4d)) +* several fixes in the DPI (DKSR Feedback table) ([6868e0c](https://gitlab.com/piveau/ui/piveau-ui/commit/6868e0c21459d7855f7cddc6042f6f31578055e3)) + + + +## [4.4.21](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.4.20...v4.4.21) (2024-11-06) + + +### Bug Fixes + +* fix bug that visualisation tool appears on a click on 'Access' button ([a812c04](https://gitlab.com/piveau/ui/piveau-ui/commit/a812c047489a4303d15fb325d455874d2d99dcd4)) + + + +## [4.4.20](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.4.19...v4.4.20) (2024-11-05) + + +### Bug Fixes + +* fixed the AutocompleteInput and removed catalog restrictions for operators ([abceaef](https://gitlab.com/piveau/ui/piveau-ui/commit/abceaef9c9e0af24eb3f2fb8cb8a97539a350d1c)) + + + +## [4.4.19](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.4.18...v4.4.19) (2024-10-31) + + + +## [4.4.18](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.4.17...v4.4.18) (2024-10-31) + + +### Bug Fixes + +* **DatasetDetailsHeaderTitle:** fix a case where title is appended by dataset ID in separate h1 ([16776d8](https://gitlab.com/piveau/ui/piveau-ui/commit/16776d89280d44656085838b5ebb47cd67925f3f)) + + + +## [4.4.17](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.4.16...v4.4.17) (2024-10-30) + + +### Bug Fixes + +* **dpi:** use "-" instead of "Invalid Date" ([d5e3312](https://gitlab.com/piveau/ui/piveau-ui/commit/d5e33122d551c55dd1d8d5b25b4f90a1ae23e51e)) + + + +## [4.4.16](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.4.15...v4.4.16) (2024-10-30) + + +### Bug Fixes + +* **dpi:** properly apply localized strings for politicalGeocoding and spatial ([ef8f0f4](https://gitlab.com/piveau/ui/piveau-ui/commit/ef8f0f4083734f3870a0eb933bde186f96f5ed1d)) + + + +## [4.4.15](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.4.14...v4.4.15) (2024-10-30) + + + +## [4.4.14](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.4.13...v4.4.14) (2024-10-29) + + + +## [4.4.13](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.4.12...v4.4.13) (2024-10-28) + + + +## [4.4.12](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.4.11...v4.4.12) (2024-10-24) + + +### Bug Fixes + +* fixed the dcatapde specification (downloadURL was buggy) ([1b6f526](https://gitlab.com/piveau/ui/piveau-ui/commit/1b6f526befe24b94cb62e24292a34e123be8dcd9)) +* fixed the overview and the distribution input field of the DPI ([f1f70d9](https://gitlab.com/piveau/ui/piveau-ui/commit/f1f70d9814137c27b08b11f121b833a8740a70c1)) +* fixed the overview page of the dpi and added components to handle dcatapde properties ([1147d77](https://gitlab.com/piveau/ui/piveau-ui/commit/1147d7731c9ea1fa3330a3c112de6e510c5ec4c5)) +* quickfix for the distribution table ([bb672b2](https://gitlab.com/piveau/ui/piveau-ui/commit/bb672b2df415193471beb18cff933b5d94293f88)) + + + +## [4.4.11](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.4.10...v4.4.11) (2024-10-24) + + +### Bug Fixes + +* activate visualisation tool through button ([01b2b8a](https://gitlab.com/piveau/ui/piveau-ui/commit/01b2b8a527f0b17f4340e7e432c747abec9b2921)) + + + +## [4.4.10](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.4.9...v4.4.10) (2024-10-21) + + +### Bug Fixes + +* fixed the autocomplete and the conditionalinput ([f526d23](https://gitlab.com/piveau/ui/piveau-ui/commit/f526d2368ad421026a05e6c4d83c3e69e11922fd)) + + + +## [4.4.9](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.4.7...v4.4.9) (2024-10-17) + + + +## [4.4.7](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.4.6...v4.4.7) (2024-10-16) + + +### Bug Fixes + +* **dpi:** fix confirmation button not showing on form reset ([92b886f](https://gitlab.com/piveau/ui/piveau-ui/commit/92b886ffd67355fd9a0450db16525fb217949b9a)) + + + +## [4.4.6](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.4.5...v4.4.6) (2024-10-16) + + +### Bug Fixes + +* **dpi:** fix validation messages not showing properly for UniqueIdentifierInput ([298f5ae](https://gitlab.com/piveau/ui/piveau-ui/commit/298f5ae5805f286667c72badb21437ded4150f3d)) + + + +## [4.4.5](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.4.4...v4.4.5) (2024-10-16) + + +### Bug Fixes + +* **dpi:** fix language selector not responding ([d5beb02](https://gitlab.com/piveau/ui/piveau-ui/commit/d5beb02f2ade876e6446d789bacb04b9ae510f5c)) +* **dpi:** fix overview page not properly showing distribution properties on load ([8387753](https://gitlab.com/piveau/ui/piveau-ui/commit/8387753961ab3ba13703b1764fd28e5c034fe6aa)) +* updatedlicensetext ([d77e870](https://gitlab.com/piveau/ui/piveau-ui/commit/d77e870a07490ae6814eaa8d4425cd8efc701f20)) + + + +## [4.4.4](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.4.3...v4.4.4) (2024-10-15) + + +### Bug Fixes + +* edited the results of the autocompletes and excluded OP_DATPRO ([8541db9](https://gitlab.com/piveau/ui/piveau-ui/commit/8541db909a09c95ddf536e5632cb3078804edfa7)) +* fixed the spatial input (added translation paths) ([d721227](https://gitlab.com/piveau/ui/piveau-ui/commit/d721227a8b17b5e2a06cb849fca8eb65c334813b)) +* small fix in the license description field ([b764e55](https://gitlab.com/piveau/ui/piveau-ui/commit/b764e55ff0a57f8bde5a85eaddbf8b57b990efbb)) +* Updated the DPI License Text ([a1e0c0a](https://gitlab.com/piveau/ui/piveau-ui/commit/a1e0c0a1a4ff43dcaa8c1c1fc2a79cc925499532)) + + + +## [4.4.3](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.4.2...v4.4.3) (2024-10-11) + + +### Bug Fixes + +* **LanguageSelector:** use vue3 convention for v-model ([402252e](https://gitlab.com/piveau/ui/piveau-ui/commit/402252ef8307d26c5f6891ad6fd6ed1a1c9fbca4)) +* **OverviewPage:** properly localize OverviewPage ([efb4fae](https://gitlab.com/piveau/ui/piveau-ui/commit/efb4faea4b3b0ace7835eb0954e0f1a390208dae)) + + + +## [4.4.2](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.4.1...v4.4.2) (2024-10-11) + + + +## [4.4.1](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.4.0...v4.4.1) (2024-10-11) + + +### Bug Fixes + +* **SpatialInput:** localize hardcoded messages ([daae1fb](https://gitlab.com/piveau/ui/piveau-ui/commit/daae1fbbe50a05244e06b8adfb661a1b5503c097)) + + + +# [4.4.0](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.3.14...v4.4.0) (2024-10-11) + + +### Bug Fixes + +* **dpi:** fix query param conflict in edit mode ([e04d654](https://gitlab.com/piveau/ui/piveau-ui/commit/e04d6542e2fc237a3f935636713b1565728c0567)) + + + +## [4.3.14](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.3.13...v4.3.14) (2024-10-10) + + + +## [4.3.13](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.3.12...v4.3.13) (2024-10-10) + + +### Features + +* **dpi:** implement dpiContext prop for custom specifications ([0f83ef6](https://gitlab.com/piveau/ui/piveau-ui/commit/0f83ef63a591fd32c0d21ee21c4d4fdcdada7565)) + + + +## [4.3.12](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.3.11...v4.3.12) (2024-10-10) + + + +## [4.3.11](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.3.10...v4.3.11) (2024-10-09) + + + +## [4.3.10](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.3.8...v4.3.10) (2024-10-02) + + + +## [4.3.8](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.3.7...v4.3.8) (2024-10-01) + + +### Bug Fixes + +* hvd meta tag ([e5c7ec9](https://gitlab.com/piveau/ui/piveau-ui/commit/e5c7ec96f78eb3508c5b9cd004bd61f9aa35a553)) + + + +## [4.3.7](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.3.6...v4.3.7) (2024-10-01) + + +### Bug Fixes + +* hvd meta tag ([761c691](https://gitlab.com/piveau/ui/piveau-ui/commit/761c69128a9c2b62e0968950bab26cd40789c674)) + + + +## [4.3.6](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.3.5...v4.3.6) (2024-09-26) + + +### Bug Fixes + +* adding exclude uris field to knn_request ([27c8e6c](https://gitlab.com/piveau/ui/piveau-ui/commit/27c8e6c4dea5e070c96befb5e8a48835894d1692)) +* correct exclude uris field to knn_request ([46eaed9](https://gitlab.com/piveau/ui/piveau-ui/commit/46eaed92c1d799cbd43d061231670cb816f4b7f8)) + + + +## [4.3.5](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.3.4...v4.3.5) (2024-09-26) # [100.0.0-alpha.0](https://gitlab.com/piveau/ui/piveau-ui/compare/v4.3.4...v100.0.0-alpha.0) (2024-10-10) diff --git a/packages/piveau-hub-ui-modules/lib/catalogues/CatalogPage.vue b/packages/piveau-hub-ui-modules/lib/catalogues/CatalogPage.vue index 19cc585b564ddf4a5e48b709a4eb39fa8055a961..9b77e1533c78b33f1d45a913aefece41beca790d 100644 --- a/packages/piveau-hub-ui-modules/lib/catalogues/CatalogPage.vue +++ b/packages/piveau-hub-ui-modules/lib/catalogues/CatalogPage.vue @@ -290,6 +290,7 @@ export default { this.$env.api.hubUrl, this.$env.api.qualityBaseUrl, this.$env.api.similarityBaseUrl, + this.$env.api.similarityServiceName, this.$env.content.datasets.facets.scoringFacets.defaultScoringFacets, ); @@ -314,7 +315,7 @@ export default { // We should refactor this code to use the store instead of making // a dedicated HTTP request for dataset search. // This will ensure that the most relevant datasets for the catalog are always displayed, - // even if the search page has filtered datasets. + // even if the search page has filtered datasets. Axios.get(`${this.$env.api.baseUrl}search`, { params: { q: '', @@ -539,4 +540,4 @@ export default { margin-left: 1.5rem; } } -</style> \ No newline at end of file +</style> diff --git a/packages/piveau-hub-ui-modules/lib/composables/head/useDatasetDetailsDatasetHead.ts b/packages/piveau-hub-ui-modules/lib/composables/head/useDatasetDetailsDatasetHead.ts index d2f569d3d62e3a9ae9009f1d3c13def02c888bcf..e4bf6f374bbacec3a4e73fe126585863b63b766b 100644 --- a/packages/piveau-hub-ui-modules/lib/composables/head/useDatasetDetailsDatasetHead.ts +++ b/packages/piveau-hub-ui-modules/lib/composables/head/useDatasetDetailsDatasetHead.ts @@ -22,7 +22,7 @@ function showKeyword(keyword: any) { && !isNil(keyword.title); } -/** +/** * Returns a schema.org license object by providing dcat-ap license * See https://schema.org/license * See https://confluencesrv.fokus.fraunhofer.de/pages/viewpage.action?spaceKey=PIV&title=DCAT-AP+Guide @@ -47,7 +47,7 @@ function getSchemaOfLicense(license: any) { return licenseObject; } -/** +/** * Returns a json-ld object of the license of the first distribution where a license exists in this dataset * or null, if first distribution or license does not exist */ @@ -277,15 +277,22 @@ export function useDatasetDetailsDatasetHead() { name: 'robots', content: 'index', }, + { + name: 'dataset_type', + content: () => { + // return store.getters['datasetDetails/getIsHvd'] ? 'High-Value Dataset' : 'Dataset'; + return computed(() => (store.getters['datasetDetails/getIsHvd'] ? 'High-Value Dataset' : 'Dataset')).value; + } + } ], script: schemaOrg, link: [ { rel: 'canonical', href: window.location.href }, ], }) - + return { head, schemaOrg, } -} \ No newline at end of file +} diff --git a/packages/piveau-hub-ui-modules/lib/composables/useDataInfoBox.js b/packages/piveau-hub-ui-modules/lib/composables/useDataInfoBox.js index 3aedd8d923a32ec4c36124d54b3467c859ecd8b0..640d7443abd17e09d2efa8d18657e2ea4bbb67ca 100644 --- a/packages/piveau-hub-ui-modules/lib/composables/useDataInfoBox.js +++ b/packages/piveau-hub-ui-modules/lib/composables/useDataInfoBox.js @@ -1,11 +1,13 @@ -import { computed, getCurrentInstance, unref } from "vue"; -import { getTranslationFor, truncate, getImg } from "../utils/helpers"; -import { toPairs, uniqBy, groupBy, has } from "lodash-es"; +import {computed, unref} from "vue"; +import {getImg, getTranslationFor, truncate} from "../utils/helpers"; +import {groupBy, has, toPairs, uniqBy} from "lodash-es"; +import {useRuntimeEnv} from "./useRuntimeEnv.ts"; export function useDataInfoBox({ datasets, locale }) { - const vm = getCurrentInstance(); - const $env = vm.proxy.$env; + // const vm = getCurrentInstance(); + // const $env = vm.proxy.$env; + const $env = useRuntimeEnv(); function getCatalogImage(catalog) { return $env.content.catalogs.useCatalogCountries @@ -41,10 +43,9 @@ export function useDataInfoBox({ datasets, locale }) { const onlyFormatObjectsArray = sortedArray.map(arr => arr[1][0]); // lodash uniqBy funtion removes duplicate id´s from array of objects const uniqById = uniqBy(onlyFormatObjectsArray, "id"); - const uniqByIdAndLabel = uniqBy(uniqById, "label"); - return uniqByIdAndLabel; + return uniqBy(uniqById, "label"); } - + return computed(() => { const dsList = unref(datasets); const l = unref(locale); @@ -68,4 +69,4 @@ export function useDataInfoBox({ datasets, locale }) { } }) }) -} \ No newline at end of file +} diff --git a/packages/piveau-hub-ui-modules/lib/composables/useTed.ts b/packages/piveau-hub-ui-modules/lib/composables/useTed.ts new file mode 100644 index 0000000000000000000000000000000000000000..07e7314ebb2e62d31beffeced0b648974c541327 --- /dev/null +++ b/packages/piveau-hub-ui-modules/lib/composables/useTed.ts @@ -0,0 +1,22 @@ +import { computed, MaybeRefOrGetter, toValue } from 'vue'; +import { useI18n } from 'vue-i18n' + +/** + * A shorthand for `i18n.te(key) ? i18n.t(key) : def`. + * + * @example + * const ted = useTed() + * const label = ted('my-key', 'My Label') + * // label is computed and returns the translation for 'my-key' if it exists, otherwise 'My Label' + * + * @param {MaybeRefOrGetter<string>} key The key to translate + * @param {MaybeRefOrGetter<string>} def The default value if the key does not exist + * @returns {ComputedRef<string>} The translated value + */ +export function useTed() { + const { t, te } = useI18n(); + + const ted = (key: MaybeRefOrGetter<string>, def: MaybeRefOrGetter<string>) => computed(() => te(toValue(key)) ? t(toValue(key)) : toValue(def)) + + return ted +} \ No newline at end of file diff --git a/packages/piveau-hub-ui-modules/lib/configurations/configureComponents.ts b/packages/piveau-hub-ui-modules/lib/configurations/configureComponents.ts index 8e99360240fdf2ccad0e8f3ee944ddcfb71a94f8..b1849ff383c61fd48bdc5171ef86db988105b35e 100644 --- a/packages/piveau-hub-ui-modules/lib/configurations/configureComponents.ts +++ b/packages/piveau-hub-ui-modules/lib/configurations/configureComponents.ts @@ -12,17 +12,20 @@ import SelectedFacetsOverview from "../facets/SelectedFacetsOverview.vue" import SubNavigation from "../navigation/SubNavigation.vue"; import DatasetDetailsHeader from "../datasetDetails/header/DatasetDetailsHeader.vue"; import DatasetDetailsDescription from "../datasetDetails/DatasetDetailsDescription.vue"; +import DatasetDetailsDataset from "../datasetDetails/DatasetDetailsDataset.vue"; import Distribution from "../datasetDetails/distributions/Distribution.vue"; import DistributionActions from "../datasetDetails/distributions/distributionActions/DistributionActions.vue"; import DatasetDetailsProperties from "../datasetDetails/properties/DatasetDetailsProperties.vue"; import DatasetDetailsFeatures from "../datasetDetails/features/DatasetDetailsFeatures.vue"; import DatasetDetailsFeatureHeader from "../datasetDetails/features/DatasetDetailsFeatureHeader.vue" import DistributionVisualisationSlot from "../datasetDetails/distributions/distributionPreview/DistributionVisualisationSlot.vue"; +import DownloadAllDistributions from "../datasetDetails/distributions/DownloadAllDistributions"; export type ComponentMap = { [key: string]: any }; const defaultComponents: ComponentMap = { SelectFacet: ExpandableSelectFacet, + DownloadAllDistributions, RadioFacet, PvShowMore, PvButton, @@ -36,6 +39,7 @@ const defaultComponents: ComponentMap = { SubNavigation, DatasetDetailsHeader, DatasetDetailsDescription, + DatasetDetailsDataset, Distribution, DistributionActions, DatasetDetailsProperties, @@ -47,7 +51,7 @@ const defaultComponents: ComponentMap = { export function configureComponents(app: any, components?: ComponentMap) { const allComponents: ComponentMap = { ...defaultComponents, ...components }; const componentKeys: string[] = Object.keys(allComponents); - componentKeys.forEach((key: string) => { + componentKeys.forEach((key: string) => { app.component(key, allComponents[key]); }); } diff --git a/packages/piveau-hub-ui-modules/lib/configurations/configureModules.ts b/packages/piveau-hub-ui-modules/lib/configurations/configureModules.ts index 62d861f78741d8dea50283fd39db6cb3c920e650..9d0f640197a98ef5fc53a55cba0c72f96dcd6b3c 100644 --- a/packages/piveau-hub-ui-modules/lib/configurations/configureModules.ts +++ b/packages/piveau-hub-ui-modules/lib/configurations/configureModules.ts @@ -4,12 +4,16 @@ import { ComponentMap, configureComponents } from "./configureComponents"; type ModulesConfig = { components: ComponentMap, serviceParams: {[key: string]: unknown}, + customFacets: any }; +export const extras: {[key:string]: any} = {}; + export function configureModules(app: any, store: any, config?: ModulesConfig) { store.$app = app; if (config) { - configureServices(store, config.serviceParams); - configureComponents(app, config.components); + configureServices(store, config?.serviceParams); + configureComponents(app, config?.components); } + extras.customFacets = config?.customFacets } diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/CatalogueMQA.vue b/packages/piveau-hub-ui-modules/lib/data-provider-interface/CatalogueMQA.vue new file mode 100644 index 0000000000000000000000000000000000000000..6eb9057b387e301ee8462c4e5b9674315625fe0f --- /dev/null +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/CatalogueMQA.vue @@ -0,0 +1,655 @@ +<template> + <div class="container mb-3"> + <div> + <div> + <div class="debug"> + <strong> API object:</strong> <br><br> + <!-- Disabled for PPE release (no backend on PPE yet)--> + {{ notficationInfo }} + + <br> --- <br> + + {{ mailList }} + + <br>---<br> + <strong>Fequency:</strong> <br> + weekly: {{ weekly }}<br> + monthly: {{ monthly }} <br> + <p></p> + unit: {{ frequencyUnit }} <br> + value: {{ frequencyValue }} + + <br>---<br> + treshold: {{ treshold }} + + </div> + <h1>MQA Report Settings</h1> + <p>Configuration for + + <strong>{{ catalogId }}</strong> + <!-- <strong>Test Catalogue</strong> --> + </p> + <p>For this Catalogue the <b>MQA Rating Checks </b>are currently <b> + <span v-if="!active">deactivated</span> + <span v-if="active">activated</span></b>.</p> + </div> + + <button type="button" :class="{ 'btn btn-primary mb-5': !active, 'btn btn-secondary': active }" @click="handleActivate()"> + {{ activatedString }} + </button> + </div> + + <div v-if="active"> + <div class="mqaWrapper" > + <h3>Recipients Mail</h3> + <span>Add and edit mail addresses for recieving the MQA report</span> + <table class="mt-4" ref="mailButtonWrap" v-if="mailList.length != 0"> + <tr> + <th>Mail</th> + </tr> + <tr v-for="(item, index) in mailList" class="mailItems" :key="index"> + <td> + <span v-if="!editMode">{{ item }}</span> + <input type="text" v-model="mailList[index]" class="mail-input" v-if="editMode" + @input="editErrors[index] = ''" + :class="{ 'invalidNewMail': editErrors[index] }" > + + <div> + <button type="button" class="btn btn-simple" + @click="editMode ? saveMail(index) : editMail(index)"> + {{ editMode ? 'Save' : 'Edit' }} + </button> + <button type="button" class="btn btn-simple" + @click="deleteMail(index)">Delete</button> + </div> + </td> + <span class="errormsg" v-if="editErrors[index]">{{ editErrors[index] }}</span> + </tr> + </table> + <div class="d-flex mt-3"> + <input type="text" v-model="newMail.mail" @input="newMail.isValid = true" + :class="{ 'invalidNewMail': !newMail.isValid }" placeholder="Enter email address" + class="mail"> + <button type="button" class="btn btn-simple mx-3" @click="addNewMail()">+ Add Mail</button> + </div> + <span class="errormsg" v-if="!newMail.isValid">*Invalid email format</span> + </div> + <div class="mqaWrapper"> + <h3>Frequency of Rating Checks</h3> + <span>Configure the frequency of the MQA rating checks.</span> + + <div class="d-flex mt-3"> + <div class="mr-3 my-3"> + <button type="button" class="btn btn-simple" @click="setWeekly()" + :class="{ 'activeChoiceButton': weekly }">Weekly</button> + <div class="weekdays" :class="{ 'blur': !weekly }"> + <span v-for="(day, index) in week" :key="index"> + <button :disabled="!weekly" class="dayButtons" + :class="{ 'activeItem': selectedDay === index }" @click="selectDay(index)"> + {{ day }} + </button> + </span> + </div> + </div> + <div class="my-3"> + <button type="button" :class="{ 'activeChoiceButton': monthly }" class="btn btn-simple" + @click="setMonthly()">Monthly</button> + <div class="d-flex daypicker my-3" :class="{ 'blur': !monthly }"> + <input v-model="daysInMonth" @input="selectDay(index)" :class="{ dynamicWidth: inputWidth }" + :disabled="!monthly"> + <div class="caretWrap"> + <button @click="editDate('up')" class="caretButtons ml-1"><svg + xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" + class="bi bi-caret-up-fill" viewBox="0 0 16 16"> + <path + d="m7.247 4.86-4.796 5.481c-.566.647-.106 1.659.753 1.659h9.592a1 1 0 0 0 .753-1.659l-4.796-5.48a1 1 0 0 0-1.506 0z" /> + </svg></button> + <button @click="editDate()" class="caretButtons mr-1"><svg + xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" + class="bi bi-caret-down-fill" viewBox="0 0 16 16"> + <path + d="M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z" /> + </svg></button> + </div> + <span>day of the month</span> + </div> + </div> + </div> + </div> + <div class="mqaWrapper"> + <h3>Notification Treshold</h3> + <span>Set the threshold for triggering the report.</span> + <div class="w-50 mt-3"> + <div class="d-flex justify-content-between"> + <span><b>0</b></span> + <span><b>400</b></span> + </div> + <input type="range" v-model="treshold" min="0" max="400" /> + <p>Lower than <b>{{ treshold }}</b> Points</p> + </div> + </div> + <div class="actionWrapper"> + <button type="button" class="btn btn-primary" @click="postNotificationSettings">Save</button> + <button type="button" class="btn btn-cancel" @click="back()">Cancel</button> + </div> + </div> + </div> + +</template> +<script setup> + +import { ref } from 'vue'; +import { useRoute } from 'vue-router' +import { getCurrentInstance } from "vue"; +import { useStore } from 'vuex'; +import { useRouter } from 'vue-router'; + + +import axios from 'axios' + +const store = useStore(); +const router = useRouter(); + +const showSnackbar = (payload) => { + store.dispatch('snackbar/showSnackbar', payload); +}; + + +const triggerSnackbar = () => { + showSnackbar({ + message: 'Saved Successfully', + variant: 'success', + }); + }; + +// Map the showSnackbar action from the snackbar module + +const route = useRoute() + +let monthly = ref(false) +let weekly = ref(false) +let mailButtonWrap = ref(null) +let activatedString = ref('Activate') +let inputWidth = ref(false) +let active = ref(false) +let mailList = ref() +let week = ['Mo', 'Tue', 'We', 'Th', 'Fr', 'Sa', 'Su'] +let daysInMonth = ref(1) +let editMode = ref(false); +let treshold = ref(0); +let frequencyUnit = ref(''); +let frequencyValue = ref(''); +let selectedDay = ref(null); +const catalogId = route.params.id +const app = getCurrentInstance() +const notificationBaseUrl = app.appContext.app.config.globalProperties.$env.api.notificationBaseUrl +const apiKey = app.appContext.app.config.globalProperties.$env.api.apiKey + +const selectDay = (index) => { + if (weekly.value) { + selectedDay.value = index; + frequencyValue.value = selectedDay; // Update frequencyValue when a day is selected + } +}; + +const setWeekly = () => { + weekly.value = true; + monthly.value = false; + selectedDay.value = frequencyValue.value; // Set the selected day based on frequencyValue +}; + +const setMonthly = () => { + weekly.value = false; + monthly.value = true; + selectedDay.value = frequencyValue.value; // Set the day of the month based on frequencyValue +}; + +const notficationInfo = ref({}) + +const isValidEmail = (email) => { + const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + return regex.test(email); +}; + +let newMail = ref({ mail: '', isValid: true }); +let editErrors = ref({}); + +// enabeld for PPE release (dummy data) +// mailList.value = [{ 'mail': "mail@mail2.com" }, { 'mail': "mail@mail1.com" }] + +const fetchNotificationInfo = async () => { + const config = { + method: 'get', + url: `${notificationBaseUrl}/catalogue/${catalogId}/setting`, + headers: { + 'Authorization': apiKey, + 'Accept': 'application/json', + }, + withCredentials: true + } + + try { + const response = await axios.request(config) + notficationInfo.value = response.data + + if (notficationInfo.value.activeStatus === true) { + active.value = true + activatedString.value = 'Deactivate' + } if (notficationInfo.value.activeStatus === false) { + active.value = false + activatedString.value = 'Activate' + } + + mailList.value = notficationInfo.value.receiverEmailList + frequencyUnit.value = notficationInfo.value.frequency.unit + frequencyValue.value = notficationInfo.value.frequency.value + treshold.value = notficationInfo.value.threshold + + if (frequencyUnit.value === 'week') { + weekly.value = true + monthly.value = false + selectedDay.value = frequencyValue.value; // Set the selected day based on frequencyValue + } else if (frequencyUnit.value === 'month') { + weekly.value = false + monthly.value = true + daysInMonth.value = frequencyValue.value + } + + console.log('Response:', response) + } catch (error) { + console.log('Full error:', error) + } +} + +const postNotificationSettings = async () => { + + if (weekly.value === true) { + frequencyUnit.value = "week" + frequencyValue.value = selectedDay.value + } else if (monthly.value === true) { + frequencyUnit.value = "month" + frequencyValue.value = daysInMonth.value + } + + const config = { + method: 'post', + url: `https://piveau-metrics-notifications-piveau.apps.osc.fokus.fraunhofer.de/catalogue/${catalogId}/setting`, + headers: { + 'Authorization': apiKey, + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', + 'Access-Control-Allow-Headers': 'Authorization' + }, + withCredentials: true, + data: JSON.stringify({ + receiverEmailList: mailList.value, + threshold: Number(treshold.value), + frequency: { + unit: frequencyUnit.value, + value: frequencyValue.value + }, + activeStatus: true + }) + } + + try { + const response = await axios.request(config) + console.log('Settings updated:', response.data) + triggerSnackbar(); + router.push({ name: 'DataProviderInterface-UserCatalogues' }); + return response.data + + + } catch (error) { + triggerSnackbar({ + message: 'Error updating settings.', + variant: 'error', + }); + + console.log('Error updating settings:', error) + + throw error + + } + + +} + +fetchNotificationInfo() + +const postDeactive = async () => { + + const config = { + method: 'post', + url: `https://piveau-metrics-notifications-piveau.apps.osc.fokus.fraunhofer.de/catalogue/${catalogId}/setting`, + headers: { + 'Authorization': apiKey, + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', + 'Access-Control-Allow-Headers': 'Authorization' + }, + withCredentials: true, + data: JSON.stringify({ + activeStatus: active.value + }) + } + + try { + const response = await axios.request(config) + console.log('Settings updated:', response.data) + triggerSnackbar(); + if(!active.value) {router.push({ name: 'DataProviderInterface-UserCatalogues' });} + return response.data + + + } catch (error) { + triggerSnackbar({ + message: 'Error updating settings.', + variant: 'error', + }); + + console.log('Error updating settings:', error) + + throw error + + } +} + +const editMail = () => { + editMode.value = true + showSnackbar({ + message: 'Email added successfully.', + variant: 'success', + }); +} + +const saveMail = (index) => { + const email = mailList.value[index].trim(); + if (isValidEmail(email)) { + editMode.value = false; + delete editErrors.value[index]; + } else { + editErrors.value[index] = '*Invalid email format'; + } +}; + +const deleteMail = (index) => { + mailList.value.splice(index, 1); +} + +const editDate = (count) => { + if (count === "up" && daysInMonth.value < 28) { + if (daysInMonth.value > 8) { + inputWidth.value = true + } + daysInMonth.value++ + } + else if (daysInMonth.value > 1) { + if (daysInMonth.value < 11) { + inputWidth.value = false + } + daysInMonth.value-- + } +} + +const addNewMail = () => { + if (newMail.value.mail.trim() === '') { + newMail.value.isValid = false; + return; + } + if (isValidEmail(newMail.value.mail)) { + mailList.value.push(newMail.value.mail); + newMail.value.mail = ''; // Clear input after successful addition + newMail.value.isValid = true; + } else { + newMail.value.isValid = false; + } +} +const handleActivate = () => { + if (!active.value) { + active.value = true + activatedString.value = 'Deactivate' + postDeactive() + } else { + active.value = false + activatedString.value = 'Activate' + postDeactive() + } +} + +const back = () => { + router.push({ name: 'DataProviderInterface-UserCatalogues' }); +} + +</script> +<style scoped> +.debug { + position: fixed; + right: 20px; + top: 20px; + border-radius: 20px; + width: 400px; + z-index: 999999; + padding: 20px; + background: rgba(255, 255, 255, 0.2); + border-radius: 16px; + box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1); + backdrop-filter: blur(5px); + -webkit-backdrop-filter: blur(5px); + border: 1px solid rgba(255, 255, 255, 0.3); + display: none; +} + +.btn-simple { + border-color: rgb(115, 115, 115); +} + +.btn-cancel { + border-color: transparent; +} + +.mail { + width: 270px; + padding: 5px 5px 5px 10px; +} + + +.errormsg { + color: red; + font-size: 10px; +} + +.activeChoiceButton { + background-color: var(--primary); + color: white; + border-color: var(--primary); + + &:active { + background-color: #3E6CD5 !important; + } + + &:focus { + outline: none; + background-color: #3E6CD5; + } + + &:focus-visible { + outline: none; + background-color: #3E6CD5; + } +} + + +.invalid { + border-bottom: 1px solid red !important; +} + +.blur { + opacity: 0.3; +} + +.invalidNewMail { + + border-radius: 2px; + border: 2px solid rgba(255, 0, 0, 0.336); + + &:focus { + border-radius: 2px; + box-shadow: 0 0 0 0.1rem rgba(255, 0, 0, 0.774); + border: 1px solid rgba(255, 0, 0, 0.336); + } + + &:focus-visible { + outline: 0; + border-radius: 2px; + box-shadow: 0 0 0 0.1rem rgba(255, 0, 0, 0.774); + border: 1px solid rgba(255, 0, 0, 0.336); + } +} + +.editable { + transition: all 200ms ease-in-out; + padding-left: 0.5rem; + border-bottom: 2px solid var(--primary) !important; +} + +.invalid { + border-bottom: 1px solid red !important; +} + +.caretButtons { + all: unset; + cursor: pointer; +} + +.caretWrap { + display: contents; + +} + +.actionWrapper { + display: flex; + flex-direction: row-reverse; + margin-bottom: 100px; + + button { + margin-left: 1rem; + } +} + +input[type="range"] { + width: 100%; +} + +.mqaWrapper { + margin: 3rem 0; +} + +table { + margin-top: 1rem; + min-width: 50%; +} + +th { + border-bottom: 1px solid lightgray; +} + +td { + display: flex; + justify-content: space-between; + padding: 15px 0 0 0cap; +} + +.daypicker { + padding: 1rem; + border: 1px solid lightgray; + border-radius: 15px; + + input { + width: 25px; + border: none; + background-color: unset; + font-weight: 700; + } + +} + +.dynamicWidth { + width: 22px !important; +} + +.activeItem { + background-color: var(--primary); + color: white; +} + +.weekdays { + border: 1px solid lightgray; + border-radius: 15px; + margin: 1rem 0; + overflow: hidden; + + .dayButtons { + border: none; + display: inline-block; + text-align: center; + flex-direction: row; + min-width: 60px; + padding: 1rem; + border-right: 1px solid lightgray; + cursor: pointer; + + &:focus-visible { + outline: unset; + background-color: #3E6CD5; + color: white; + } + + &:hover { + background-color: #3E6CD5; + color: white; + } + } + + span:last-child button { + border: none; + } +} + +.mqaWrapper { + padding: 1rem; + background-color: #f3f6fc; + border-radius: 3px; +} + +button { + background-color: unset; + border: 1px solid var(--primary); + color: black; + + &:hover { + background-color: #3E6CD5; + color: white; + border: 1px solid #3E6CD5; + } +} + +.btn-primary { + background-color: var(--primary); + color: white; + +} + +.btn-secondary { + color: #0e47cb; +} + +button, +span { + transition: all 100ms ease-in-out; +} +</style> \ No newline at end of file diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/DPIMenu.vue b/packages/piveau-hub-ui-modules/lib/data-provider-interface/DPIMenu.vue index 9dc2fa1557ed8bab25fd760d003c65455cd233dc..694b66bf341946e742748f452bedccfbc63e5e18 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/DPIMenu.vue +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/DPIMenu.vue @@ -11,7 +11,7 @@ <router-link :to="{ name: 'DPI-Comp-Lib' }">Component Library</router-link> </button> <div style="margin-top:1%;"> - <dropup v-for="(group, index) in menuGroups" :key="`Group${index}`" :groupName="group.group" + <dropup v-for="(group, index) in menuGroups" :key="`Group${index}`" :groupName="group.group" :groupItems="group.items" :show="$env.content.dataProviderInterface.buttons[group.group]" :isOperator="getUserData.roles.includes('operator')" :isCatalog="group.group === 'Catalogue' ? true : false"> </dropup> @@ -37,7 +37,7 @@ <slot name="right" :get-user-data="getUserData"> <small class="text-white">{{ $t('message.dataupload.menu.loggedInAs') }} {{ getUserData.userName }}</small> - <br> + <br> <button type="button" class="btn btn-default logout"> <router-link :to="{ name: 'Logout' }">{{ $t('message.dataupload.menu.logout') }}</router-link> </button> @@ -147,7 +147,7 @@ export default { }, }, }, - + { key: 'draft-dataset', name: 'setToDraft', @@ -275,9 +275,9 @@ export default { if (!onCatalogPage) return false; const catalogId = onCatalogPage && this.$route.params.ctlg_id; - const permissions = this.getUserData && this.getUserData.permissions; - const hasPermission = permissions.find(permission => permission.rsname === catalogId); - + // const permissions = this.getUserData && this.getUserData.permissions; + // const hasPermission = permissions.find(permission => permission.rsname === catalogId); + const hasPermission = true // does user have permission on current catalogue return hasPermission && onCatalogPage; } diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/DataProviderInterface.vue b/packages/piveau-hub-ui-modules/lib/data-provider-interface/DataProviderInterface.vue index c7e71f6365b0c85bb8ce7daae6bf6becf6e373ae..e24e111101cdc30d61f742521f420d1c0d79466a 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/DataProviderInterface.vue +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/DataProviderInterface.vue @@ -11,23 +11,44 @@ <div class="dpi dpiV3_dpi" :key="property"> <!-- CONTENT --> - <router-view ref="view" :key="$route.query.edit"> + <router-view v-if="isReady" ref="view" :key="key"> </router-view> </div> </template> -<script> +<script lang="ts"> /* eslint-disable no-nested-ternary, no-lonely-if, no-param-reassign */ -import { defineAsyncComponent } from 'vue'; -import { mapActions, mapGetters } from 'vuex'; +import { DpiContext, setupDpiContext } from './composables/useDpiContext'; +import { computed, defineAsyncComponent, defineComponent, PropType , provide, ref, toRef, toRefs, toValue, watch, watchEffect} from 'vue'; +import { mapActions, mapGetters, useStore } from 'vuex'; +import dpiSpecs from './config/dpi-spec-config'; +import { useRuntimeEnv } from '../composables/useRuntimeEnv'; +import { useRoute } from 'vue-router'; +import { useAsyncState, watchOnce } from '@vueuse/core'; +import { useDpiEditMode } from './composables/useDpiEditMode'; +import { AutocompleteInstance, autocompleteKey, defaultAutocompleteAdapter, useAutocomplete } from './composables/aucotomplete'; -export default { + +export default defineComponent({ name: 'DataProviderInterface', components: { - InputPage: defineAsyncComponent(() => import('./views/InputPage')), + InputPage: defineAsyncComponent(() => import('./views/InputPage.vue')), + }, + props: { + name: { + type: String, + default: '', + }, + dpiContext: { + type: Object as PropType<DpiContext>, + default: () => undefined, + }, + autocomplete: { + type: Object as PropType<AutocompleteInstance>, + default: () => undefined, + } }, - props: ['name'], metaInfo() { return { title: `${this.$t('message.metadata.upload')} | ${this.$t('message.header.navigation.data.datasets')}`, @@ -72,11 +93,11 @@ export default { }, handleScroll() { try { - if (document.getElementById("stepperAnchor").offsetTop >= 35) { - document.getElementById("stepperAnchor").classList.add("border-bottom-lightgray"); + if (document.getElementById("stepperAnchor")?.offsetTop || 0 >= 35) { + document.getElementById("stepperAnchor")?.classList.add("border-bottom-lightgray"); } else { - document.getElementById("stepperAnchor").classList.remove("border-bottom-lightgray"); + document.getElementById("stepperAnchor")?.classList.remove("border-bottom-lightgray"); } } catch (error) { @@ -96,7 +117,63 @@ export default { window.removeEventListener('scroll', this.handleScroll); }, -}; + setup(props) { + const route = useRoute() + const store = useStore() + const env = useRuntimeEnv() + const userSpec = env.content.dataProviderInterface.specification as 'dcatap' | 'dcatapde' | 'dcatapdeODB' + const fallbackSpec = dpiSpecs[userSpec] + const dpiContext = toRef(props, 'dpiContext') + + const resolvedDpiContext = computed<DpiContext>(() => { + const _dpiContext = toValue(dpiContext) + + return { + specification: fallbackSpec, + specificationName: userSpec, + edit: { + enabled: route.query.edit === 'true', + id: route.query.id as string || undefined, + fromDraft: route.query.fromDraft === 'true', + }, + ..._dpiContext + } + }) + + const specification = computed(() => { + return resolvedDpiContext.value.specification + }) + + const specificationName = computed(() => { + return resolvedDpiContext.value.specificationName + }) + + setupDpiContext(resolvedDpiContext) + const defaultAutocompleteInstance = defaultAutocompleteAdapter({ + envs: env, + dpiContext: resolvedDpiContext + }); + provide(autocompleteKey, props.autocomplete || defaultAutocompleteInstance.adapter); + + watchEffect(() => { + store.dispatch('dpiStore/setSpecification', specification.value) + store.dispatch('dpiStore/setSpecificationname', specificationName.value) + }) + + const key = computed(() => { + return `${route.query.key}@${specificationName.value}` + }) + + const { isReady } = useDpiEditMode(resolvedDpiContext) + + return { + resolvedDpiContext, + key, + isReady, + } + + } +}); </script> <style lang="scss"> diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/AutocompleteInput.vue b/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/AutocompleteInput.vue index 1b6854e267539bfd2fc1c12f7117f40576e44257..c7ce83c8167579e597767387af496b1c6e015454 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/AutocompleteInput.vue +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/AutocompleteInput.vue @@ -6,8 +6,8 @@ <h4 class="formkitHeader" v-if="inDis"> {{ $t('message.dataupload.distributions.' + props.context.attrs.identifier + '.label') }} </h4> - <h4 v-if="props.context.attrs.identifier != 'licence' && !inDis">{{ $t('message.dataupload.datasets.' + - props.context.attrs.identifier + '.label') }}</h4> + <h4 v-if="props.context.attrs.identifier != 'licence' && !inDis" v-html="$t('message.dataupload.datasets.' + props.context.attrs.identifier + '.label')"> + </h4> <div class="formkitCmpWrap"> @@ -24,9 +24,11 @@ <!-- <div class="infoI"> <div class="tooltipFormkit">{{ props.context.attrs.info }}</div> </div> --> - <input class="autocompleteInputfield" :placeholder="props.context.attrs.placeholder" v-model="inputText" + + <input ref="acInput" class="autocompleteInputfield" :placeholder="props.context.attrs.placeholder" v-model="inputText" type="text" v-on:keyup="getAutocompleteSuggestions($event)" @click="activeList = !activeList"> </div> + <ul ref="dropdownList" v-show="activeList" class="autocompleteResultList"> <li v-for="match in matches" :key="match" @click="setValue(match); activeList = !activeList" class="p-2 border-b border-gray-200 data-[selected=true]:bg-blue-100 choosableItemsAC">{{ match.name }} @@ -44,7 +46,7 @@ </div> <div class="w-100 mt-4"> <div class="d-flex justify-content-between align-items-center flex-wrap"> - <h3>{{ props.context.label }} - {{ $t('message.dataupload.info.suggestions') }}</h3> + <h3>{{ $t('message.dataupload.info.suggestions') }}</h3> <span>{{ $t('message.dataupload.info.suggestionText') }}</span> <button class="navlikeButton" type="button" @click="fillAnnifsuggestions(); annifTrigger.value = true">{{ $t('message.dataupload.info.tryIt') @@ -70,9 +72,14 @@ <div class="removeX" @click="removeMultipleProperty(item)"></div> </div> </div> - <div class="formkit-wrapper"> - <div class="formkit-help">{{ props.context.help }}</div> - </div> + + + <!-- <div class="formkit-wrapper mb-3"> + <div v-html="$t('message.dataupload.distributions.licence.vocabulary.help')" class="formkit-help"> + </div> + </div> --> + + <div v-html="props.context.attrs.info" class="formkit-help"></div> </div> </div> </div> @@ -89,14 +96,17 @@ import { useI18n } from 'vue-i18n'; import qs from 'qs'; import axios from 'axios'; +import { useAutocomplete } from '../composables/aucotomplete'; let instance = getCurrentInstance().appContext.app.config.globalProperties.$env +const { requestAutocompleteSuggestions, requestResourceName } = useAutocomplete() + const props = defineProps({ context: Object }) const store = useStore(); -const { t,locale } = useI18n(); +const { t, locale } = useI18n(); let listOfValues = computed(() => { return props.context.value; @@ -116,6 +126,7 @@ let matches = ref({ }) let activeList = ref() let dropdownList = ref(null) +let acInput = ref(null) let inputText = ref({}) let cacheList = []; let annifList = []; @@ -151,7 +162,7 @@ const requestURIname = async (res) => { let name; - await store.dispatch('dpiStore/requestResourceName', { voc: voc, uri: res, envs: instance }).then( + await requestResourceName({ voc: voc, uri: res, envs: instance }).then( (response) => { if (props.context.attrs.property === 'dcatde:politicalGeocodingURI') { if (response != undefined) { @@ -159,7 +170,7 @@ const requestURIname = async (res) => { ? response.data.result.results .filter((dataset) => dataset.resource === res) .map((dataset) => dataset.alt_label)[0].en - : getTranslationFor(response.data.result.alt_label, 'en', []); + : getTranslationFor(response.data.result.alt_label, locale.value, []); name = result; } } else { @@ -169,7 +180,7 @@ const requestURIname = async (res) => { ? response.data.result.results .filter((dataset) => dataset.resource === res) .map((dataset) => dataset.pref_label)[0].en - : getTranslationFor(response.data.result.pref_label, 'en', []); + : getTranslationFor(response.data.result.pref_label, locale.value, []); name = result; } @@ -240,6 +251,8 @@ function findPropertyToUpdate(trigger) { window.removeEventListener("click", onClickOutside); } onClickOutside(dropdownList, event => activeList.value = false) +onClickOutside(acInput, event => inputText.value = "") + let annifHandlerTheme = async (input, limit) => { let finalLimit = 10; @@ -269,10 +282,10 @@ let annifHandlerTheme = async (input, limit) => { axios(config) .then(async (response) => { - + for (let i = 0; i < response.data.results.length; i++) { - - + + annifList[i] = { "name": response.data.results[i].label, "resource": response.data.results[i].uri, "activeValue": false } } let annifCacheList = [] @@ -338,6 +351,7 @@ const fillAnnifsuggestions = async (limitChange) => { props.context.classes.outer += ' autocompleteInput ' + props.context.attrs.identifier const setValue = async (e) => { + inputText.value = "" if (Object.keys(e).length === 1) { return } @@ -375,11 +389,11 @@ const setValue = async (e) => { const getAutocompleteSuggestions = async (e) => { let innerText = e.target.value -console.log(locale); - + await requestAutocompleteSuggestions({ voc, text: innerText, base: instance.api.baseUrl }).then((response) => { + // filter OP_DATPRO + const updatedArray = response.data.result.results.filter(obj => obj.id !== 'OP_DATPRO'); - await store.dispatch('dpiStore/requestAutocompleteSuggestions', { voc: voc, text: innerText, base: instance.api.baseUrl }).then((response) => { - const results = response.data.result.results.map((r) => ({ + const results = updatedArray.map((r) => ({ name: getTranslationFor(r.pref_label, locale.value, []) + " (" + r.id + ")", resource: r.resource, })); @@ -408,4 +422,4 @@ function removeMultipleProperty(e) { props.context.node.input(selection); findPropertyToUpdate(); } -</script> \ No newline at end of file +</script> diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/ConditionalInput.vue b/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/ConditionalInput.vue index 610793c47140dae36cf1ff849f421d6eeb09ef6d..b4bd09608738ebb6fde2f6fed5ca1ded0d7dceda 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/ConditionalInput.vue +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/ConditionalInput.vue @@ -18,30 +18,29 @@ d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0" /> </svg> <div class="w-80"> - <p>For <strong>European</strong> <strong>Commission's datasets</strong>, bear in mind that <a - class="external-link" href="https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32011D0833" - target="_blank" rel="nofollow noopener"><ins>Decision 2011/833/EU</ins></a>] allows for their commercial - reuse without prior authorisation, except for the material subject to the third party intellectual property - rights. This Decision has been implemented under the <a class="external-link" - href="https://ec.europa.eu/transparency/documents-register/detail?ref=C(2019)1655&lang=en" - target="_blank" rel="nofollow noopener"><ins>Decision C(2019) 1655 final</ins></a>] by which Creative - Commons Attribution 4.0 International Public License (CC BY 4.0) is adopted as an open licence for the - Commission's reuse policy. Additionally, raw data, metadata or other documents of comparable nature may - alternatively be distributed under the provisions of the Creative Commons Universal Public Domain Dedication - deed (CC0 1.0).</p> + <p>For <strong>European</strong> <strong>Commission's datasets</strong>, bear in mind + that <a href="https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32011D0833]" + target="_blank" rel="nofollow noopener"><ins>Decision 2011/833/EU</ins></a> allows for their commercial + reuse without + prior authorisation, except for the material subject to the third party intellectual property rights. This + Decision has been implemented under the <a + href="https://ec.europa.eu/transparency/documents-register/detail?ref=C(2019)1655&lang=en]" + target="_blank" rel="nofollow noopener"><ins>Decision C(2019) 1655 final</ins></a> by which Creative + Commons + Attribution 4.0 International Public License (CC BY 4.0) is adopted as an open licence for the Commission's + reuse policy. Additionally, raw data, metadata or other documents of comparable nature may alternatively be + distributed under the provisions of the Creative Commons Universal Public Domain Dedication deed (CC0 + 1.0).</p> <p>The <strong>Council</strong> and the <strong>European Court of Auditors</strong> have approved similar decisions on reuse. It is advisable that you check <strong>the reuse policy of your organisation</strong> before publishing or submitting your dataset.</p> - <p>If you need further information regarding copyright issues, please contact us at <span class="nobr"><a - class="external-link" href="mailto:op-copyright@publications.europa.eu" target="_blank" - rel="nofollow noopener">op-copyright@publications.europa.eu<sup><img class="rendericon" - src="https://citnet.tech.ec.europa.eu/CITnet/jira/images/icons/mail_small.gif" alt="" width="13" - height="12" align="absmiddle" border="0" /></sup></a></span></p> - + <p>If you need further information regarding copyright issues, please contact us at <a + href="mailto:op-copyright@publications.europa.eu" target="_blank" + rel="nofollow noopener">op-copyright@publications.europa.eu</a></p> </div> - </div> + <div v-if="props.context.attrs.identifier === 'rights' && env.content.dataProviderInterface.annifIntegration" class="d-flex infoLicense py-5"> <svg xmlns="http://www.w3.org/2000/svg" width="30px" height="30px" fill="currentColor" @@ -93,6 +92,7 @@ <div class="conditionalSelectDiv"> <input ref="I1" type="text" class="conditionalSelect formkit-input formkit-inner" @click="openSelect($event)" :placeholder=props.context.attrs.placeholder v-model="selectModeVal"> + <div v-if="showSelect"> <ul class="selectListConditional"> <li v-for="el, index in props.context.attrs.selection" class="p-2 border-b border-gray-200 " @@ -100,6 +100,10 @@ $t('message.dataupload.datasets.conditional.' + el) }}</li> </ul> </div> + <div v-if="selectedItem === 'vocabulary'" + v-html="$t('message.dataupload.distributions.licence.vocabulary.help')" + class="formkit-help position-absolute"> + </div> </div> <div class="conditionalManual"> <div class="d-flex" v-if="selectedItem === 'manually' || @@ -113,13 +117,14 @@ <div v-if="selectedItem === 'vocabulary' && !props.context.value['name']" class=""> <AutocompleteInput :context="props.context"></AutocompleteInput> </div> + <div v-if="props.context.value['name']" class="conditionalVocabulary d-flex"> <a class="autocompleteInputSingleValue">{{ - props.context.value['name'] }}</a> + resolvedUriName }}</a> <div class="removeX" @click="removeProperty"></div> </div> - + <div v-if="selectedItem === 'manually'" v-html="props.context.attrs.info" class="formkit-help"></div> </div> @@ -128,25 +133,54 @@ </template> <script setup> -import { ref, watchEffect } from 'vue'; + import AutocompleteInput from './AutocompleteInput.vue'; import { onClickOutside } from '@vueuse/core' import { useRuntimeEnv } from "../../composables/useRuntimeEnv.ts"; +import { ref, watch, computed, onMounted, watchEffect, toRef } from 'vue'; +import { useStore } from 'vuex'; +import { getTranslationFor } from "../../utils/helpers"; +import { getCurrentInstance } from "vue"; +import { getNode } from '@formkit/core' import { useI18n } from 'vue-i18n'; +import { useRequestUriName } from '../composables/useRequestUriName'; let env = useRuntimeEnv() const props = defineProps({ context: Object, }) -const { t } = useI18n(); +let instance = getCurrentInstance().appContext.app.config.globalProperties.$env +const { t, locale } = useI18n(); let selectModeVal = ref() let selectedItem = ref(false) let showSelect = ref(false) - - +let voc = props.context.attrs.voc; +const store = useStore(); +let resolvedUriName = ref(); + +const { execute: requestURIname, error: requestURInameError, isLoading: requestURInameLoading } = useRequestUriName({ + res: computed(() => props.context.value.name), + voc: computed(() => props.context.attrs.voc), + property: computed(() => props.context.attrs.property), + locale, +}) watchEffect(() => { if (props.context.value['name']) { + if (resolvedUriName.value != props.context.value.name) { + + if (props.context.value.name === props.context.value.resource) { + requestURIname().then(result => { + resolvedUriName.value = result; + }).catch(error => { + console.error(error); + }) + } + else { + resolvedUriName.value = props.context.value.name + } + } + selectModeVal.value = t('message.dataupload.datasets.conditional.' + 'vocabulary'); } if (props.context.value['foaf:name']) { @@ -211,4 +245,4 @@ onClickOutside(I1, event => showSelect.value = false) .textInfoI { width: 95%; } -</style> \ No newline at end of file +</style> diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/DataFetchingComponent.vue b/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/DataFetchingComponent.vue index 7d1c9e8a82c4d426eae9cdd405f1d7a884256280..51d17dadbd660340a9d5cd6c59394e64ace7ba7c 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/DataFetchingComponent.vue +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/DataFetchingComponent.vue @@ -33,6 +33,22 @@ export default { 'convertToInput', ]), async setupEditPage() { + this.$router.push({ + name: 'DataProviderInterface-Input', + params: { property: this.property }, + query: { + locale: this.$route.query.locale, + edit: true, + id: !this.getIsDraft && this.property === 'catalogues' + ? this.catalog + : this.id, + catalog: this.catalog, + fromDraft: this.getIsDraft ? 'true' : undefined, + }, + }) + + return; + let endpoint; this.setIsEditMode(true); const specification = this.$env.content.dataProviderInterface.specification; diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/DistributionStepper.vue b/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/DistributionStepper.vue index 667376a33e284f83fe08d81aca2e54cace50a254..62e02bf196f1ec1172480cc17a6f030b5849a153 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/DistributionStepper.vue +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/DistributionStepper.vue @@ -6,12 +6,13 @@ <li v-for="(step, stepName, index) in steps" :key="index" class="step" :data-step-active="activeStep === stepName" :data-step-valid="step.valid && step.errorCount === 0" :class="{ activeItem: activeStep === stepName, inactiveStep: stepName != activeStep, 'has-errors': checkStepValidity(stepName) - }" @click="activeStep = stepName"> + }" @click="activeStep = stepName; indexOfDis = index + 1"> + <div class="stepBubbleWrap"> - <div class="circle stepCircle">{{ index + 1 }}</div> + <div class="circle stepCircle">{{ convertToRoman(index + 1) }}</div> <span v-if="checkStepValidity(stepName)" class="step--errors" v-text="step.errorCount + step.blockingCount" /> - {{ $t('message.dataupload.steps.' + stepName+'Step') }} + {{ $t('message.dataupload.steps.' + stepName + 'Step') }} </div> <div v-if="index + 1 != Object.keys(getNavSteps($env.content.dataProviderInterface.specification).distributions).length" @@ -30,18 +31,24 @@ <!-- <PropertyChooser></PropertyChooser> --> <FormKitSchema :schema="schema[stepName]" :library="library" /> <p v-if="stepName === 'Mandatory'" class="p-1"> <b>*</b> {{ $t('message.dataupload.steps.MandatoryStep') - }}</p> + }}</p> </InputPageStep> </div> </div> </div> </div> </FormKit> + <div class="m-3 d-flex justify-content-end"> - <button type="button" class="btn btn-secondary" @click="goToPreviousStep(); scrollToTop();">{{ $t('message.dataupload.steps.previousDisStep') - }}</button> - <button type="button" class="btn btn-secondary ml-3" @click="goToNextStep(); scrollToTop();">{{ $t('message.dataupload.steps.nextDisStep') - }}</button> + + <button v-if="indexOfDis > 1" type="button" class="btn btn-secondary" + @click="goToPreviousStep(); scrollToTop(); indexOfDis = indexOfDis - 1">{{ + $t('message.dataupload.steps.previousDisStep') + }}</button> + <button v-if="indexOfDis < 4" type="button" class="btn btn-secondary ml-3" + @click="goToNextStep(); scrollToTop(); indexOfDis = indexOfDis + 1">{{ + $t('message.dataupload.steps.nextDisStep') + }}</button> </div> </template> @@ -91,7 +98,8 @@ export default defineComponent({ .replace(/([A-Z])/g, (match) => ` ${match}`) .replace(/^./, (match) => match.toUpperCase()) .trim(), - isActive: false + isActive: false, + indexOfDis: 1 } }, methods: { @@ -103,6 +111,20 @@ export default defineComponent({ let { x, y } = useWindowScroll({ behavior: 'smooth' }) y.value = 0 + }, + convertToRoman(element) { + if (element === 1) { + return "A" + } + if (element === 2) { + return "B" + } + if (element === 3) { + return "C" + } + if (element === 4) { + return "D" + } } }, computed: { diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/LanguageSelector.vue b/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/LanguageSelector.vue index 542f10cdd1cd90b4f72fe225b51ddf338502546c..0c24c16ff46f57e5857ca4b422df6916eb491648 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/LanguageSelector.vue +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/LanguageSelector.vue @@ -8,7 +8,10 @@ </template> <script> - export default { + import { useVModel } from '@vueuse/core'; +import { defineComponent } from 'vue'; + + export default defineComponent({ name: 'LanguageSelector', data() { return { @@ -41,23 +44,18 @@ }; }, props: { - value: { + modelValue: { type: String, default: 'en', }, }, - computed: { - locale: { - get() { - return this.value; - }, - set(newLocale) { - this.$emit('input', newLocale); - } - } - }, created() {}, - }; + setup(props, { emit }) { + const locale = useVModel(props, 'modelValue', emit, { passive: true }); + + return { locale } + } + }); </script> <style lang="scss" scoped> diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/Navigation.vue b/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/Navigation.vue index 54dea90c129143d73c9c1d34c1b853125b7e73d3..0597db1eb728de427d75ae3a6a5a58466369ffe7 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/Navigation.vue +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/Navigation.vue @@ -123,7 +123,7 @@ export default { } }, created() { - this.$i18n.locale = this.$route.query.locale + // this.$i18n.locale = this.$route.query.locale this.modals.clear.message = this.$t('message.dataupload.modal.resetForm') this.modals.clear.confirm = this.$t('message.dataupload.modal.confirmReset') @@ -160,7 +160,7 @@ export default { }, async submit(mode) { this.uploading[mode] = true; - this.$Progress.start(); + this.$Progress?.start(); const specification = this.$env.content.dataProviderInterface.specification; const RDFdata = await this.convertToRDF({ property: this.property, specification: specification }).then((response) => { return response; }); @@ -221,8 +221,8 @@ export default { await this.$store.dispatch(actionName, actionParams); // await new Promise(resolve => setTimeout(resolve, 250)); - this.$Progress.finish(); - this.uploading = false; + this.$Progress?.finish(); + this.uploading[mode] = false; if (mode === 'createcatalogue') this.createCatalogue(datasetId); if (mode === 'dataset') this.createDataset(datasetId); @@ -233,12 +233,13 @@ export default { } else { this.uploading[mode] = false; - this.$Progress.fail(); + this.$Progress?.fail(); this.handleIDError(); } } catch (err) { + console.error(err) this.uploading[mode] = false; - this.$Progress.fail(); + this.$Progress?.fail(); this.showSnackbar({ message: 'Network Error', variant: 'error' }); } }, diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/SimpleSelect.vue b/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/SimpleSelect.vue index 2783e57760d52bd49941a9aacd6a2de832d1b0dd..f780f739cfbab609669b73d6dc2ad0939ea5f8f8 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/SimpleSelect.vue +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/SimpleSelect.vue @@ -1,126 +1,171 @@ <template> - <div class="formkitProperty"> - <h4>{{ props.context.label }}</h4> - <div class="position-relative formkitCmpWrap"> - <FormKit v-if="isEditMode.value && !isDuplicate" class="autocompleteInputfield" type="text" readonly - :placeholder="getNode('dcat:catalog').value" :name="props.context.node.name" /> - <FormKit v-else class="autocompleteInputfield" v-model="catVal" - :placeholder="props.context.attrs.placeholder" type="text" @click="showList = !showList" - validation="required" mandatory="true" readonly :validation-messages="{ - required: props.context.attrs.placeholder, - }" :name="props.context.node.name" /> - - <ul ref="dropdownList" v-show="showList" class="autocompleteResultList selectListFK catSelectList"> - - <li v-for="match in filteredCatalogs" :key="match" @click="setvalue(match)" - class="p-2 border-b border-gray-200 data-[selected=true]:bg-blue-100 choosableItemsAC">{{ - match.name }} - </li> - <li v-if="filteredCatalogs.length === 0" v-for="idMatch in userCats" :key="idMatch" - @click="setvalue(idMatch)" - class="p-2 border-b border-gray-200 data-[selected=true]:bg-blue-100 choosableItemsAC">{{ - idMatch }} - </li> - </ul> - </div> + <div class="formkitProperty"> + <h4>{{ props.context.label }}</h4> + <div class="position-relative formkitCmpWrap"> + + <div v-if="isReady"> + + <!-- Hidden input that contains the actual catalog id. We rely on that to pass the catalog id to backend later --> + <FormKit v-show="false" v-model="selectedCatalogId" :name="`${props.context.node.name}`" type="text" /> + + <!-- User-facing input that displays the name of the selected catalog --> + <FormKit + class="autocompleteInputfield" + v-model="selectedCatalogTitle" + :placeholder="props.context.attrs.placeholder" + type="text" + validation="required" + mandatory="true" + :validation-messages="{ + required: props.context.attrs.placeholder, + }" :name="`${props.context.node.name}__displayedValue`" + :disabled="isDisabled" + @click="showList = !showList" + /> + <ul ref="dropdownList" v-show="showList" class="autocompleteResultList selectListFK catSelectList"> + <li v-for="match in authorizedCatalogs" :key="match" @click="setvalue(match)" + class="p-2 border-b border-gray-200 data-[selected=true]:bg-blue-100 choosableItemsAC">{{ + match.name }} + </li> + <li v-if="authorizedCatalogs.length === 0" v-for="idMatch in userCats" :key="idMatch" @click="setvalue(idMatch)" + class="p-2 border-b border-gray-200 data-[selected=true]:bg-blue-100 choosableItemsAC">{{ + idMatch }} + </li> + </ul> + </div> </div> + </div> </template> <script setup> -import { ref, computed, onMounted } from 'vue'; +import { ref, computed, onMounted, watch, nextTick } from 'vue'; import { useStore } from 'vuex'; import { getNode } from '@formkit/core' import { onClickOutside } from '@vueuse/core' import axios from 'axios' import { useRuntimeEnv } from "../../composables/useRuntimeEnv.ts"; -import { - has, - isNil, -} from 'lodash-es'; +import { useI18n } from 'vue-i18n'; +import { useAsyncState } from '@vueuse/core'; import { FormKit } from '@formkit/vue'; +import { useDpiContext } from '../composables/useDpiContext'; const props = defineProps({ - context: Object + context: Object }) +const dpiContext = useDpiContext() +const { locale, fallbackLocale } = useI18n({ useScope: 'global' }) - -const isDuplicate = computed(()=> localStorage.getItem('dpi_duplicate')) const userCats = computed(() => store.getters['auth/getUserCatalogIds']); -let showList = ref() +const showList = ref(false) const store = useStore() const dropdownList = ref(null) -const isEditMode = ref() -let validationTrigger = ref(true) -isEditMode.value = computed(() => store.getters['auth/getIsEditMode']); -let filteredCatalogs = ref([]) -let env = useRuntimeEnv() -let catVal = ref() - -catVal = getNode('dcat:catalog').value +const env = useRuntimeEnv() +const selectedCatalogTitle = ref('') +const selectedCatalogId = ref('') +const hasMounted = ref(false) onClickOutside(dropdownList, event => showList.value = false) const setvalue = async (e) => { - - validationTrigger = false - if (e.id) { - props.context.node.input(e.id) - showList.value = !showList.value; - getNode('dcat:catalog').value = e.id - catVal = e.name - } else { - props.context.node.input(e) - showList.value = !showList.value; - getNode('dcat:catalog').value = e - catVal = e - } - -} -let filterCatList = async () => { - let cache; - await axios - .get(env.api.baseUrl + 'search?filter=catalogue&limit=1000') - .then(response => (cache = response)) - .catch((err) => { - reject(err); - }); - - - cache.data.result.results.forEach((e) => { - // console.log(cache); - - if (has(e, 'title') && !isNil(e.title) && has(e, 'id') && !isNil(e.id)) filteredCatalogs.value.push({ title: Object.values(e.title)[0], id: e.id }) - }); - - filteredCatalogs.value = await filteredCatalogs.value - .filter(item => userCats.value.includes(item.id)) - .map(item => ({ id: item.id, name: item.title })); - - // Check if theres only one catalog and set it directly - - let clone = JSON.parse(JSON.stringify(filteredCatalogs.value)) - if (clone.length === 1) { - catVal = clone[0].id - } + if (e.id) { + selectedCatalogId.value = e.id + selectedCatalogTitle.value = e?.name || e.id + } else { + selectedCatalogId.value = e + selectedCatalogTitle.value = e + } + + showList.value = false } +const { execute: filterCatList, state: catalogListData, isReady: isQueryReady, error } = useAsyncState(async () => { + const catalogListData = await axios.get(env.api.baseUrl + 'search?filter=catalogue&limit=1000') + return catalogListData +}, { data: { result: { results: [] } } }, { immediate: false }) + +// Wait until everything mounted and loaded tu ensure that the catalog list is available and selected catalog from edit mode is available +const isReady = computed(() => hasMounted.value && !!catalogListData.value && isQueryReady.value) + +watch(error, () => { + console.error(error.value) +}) + +const hasResults = computed(() => { + return catalogListData?.value?.data?.result?.results?.length > 0 +}) + +const hasOneResultOnly = computed(() => { + return hasResults && catalogListData?.value?.data?.result?.results?.length === 1 +}) + +/** + * Computes list of users' authorized catalogs in { id: string; name: string } format + */ +const authorizedCatalogs = computed(() => { + if (!hasResults.value || !isQueryReady.value) return [] + + const allCatalogs = catalogListData.value?.data?.result?.results || [] + const authorizedCatalogs = allCatalogs.filter(catalog => userCats.value?.includes(catalog.id)) + + // map to { id: string; name: string } pairs + const authorizedCatalogsDataModel = authorizedCatalogs.map((catalog) => { + const id = catalog?.id || '' + let title = '' + + if (!catalog?.title) title = id + else if (typeof catalog?.title === 'string') title = catalog?.title + else if (typeof catalog?.title === 'object') title = + catalog?.title[locale.value] + || catalog?.title[fallbackLocale.value] + || Object.values(catalog?.title)?.[0] + || id + + return { id, name: title || id } + }); + return authorizedCatalogsDataModel +}) + +watch(hasOneResultOnly, (yes) => { + if (yes) { + const result = catalogListData.value.data.result.results[0] + setvalue({ id: result.id, name: result.name }) + } +}, { immediate: true }) onMounted(async () => { - filterCatList(); + // When editing, we can restore the selected catalog id immediately, but we rely on fetching from hub-search for its catalog title. + // So we need to wait until everything is fetched before proceeding further. + await filterCatList() + await nextTick() + // todo: can this be made more robust by using dpiContext.value.edit?.id? + const catalogIdToLoadForEdit = getNode?.('dcat:catalog')?.value || undefined + const maybeFoundCatalogFromQuery = authorizedCatalogs.value?.find(item => item.id === catalogIdToLoadForEdit) + if (maybeFoundCatalogFromQuery) { + setvalue({ id: maybeFoundCatalogFromQuery.id, name: maybeFoundCatalogFromQuery.name }) + } + await nextTick() + hasMounted.value = true }); +// Disable select option if in edit mode +// Note: Decision is made due to a backend limitation that causes dataset duplicates to occur when changing a catalog +const isDisabled = computed(() => { + const isInEditMode = dpiContext.value.edit?.enabled + return isInEditMode +}) + </script> <style> .catSelectList { - width: 97.3% !important; - margin: 0 1rem; + width: 97.3% !important; + margin: 0 1rem; } .selectListFK { - max-height: 20rem; - overflow: overlay; - overflow-x: hidden; + max-height: 20rem; + overflow: overlay; + overflow-x: hidden; } -</style> \ No newline at end of file +</style> diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/SpatialInput.vue b/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/SpatialInput.vue index 57d081574697d26de65b87afaf03e690e4a1d0c3..b44170283b1c5714dd90b852082edbb074cef4d5 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/SpatialInput.vue +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/SpatialInput.vue @@ -1,45 +1,148 @@ <script setup> -import { ref, reactive, watch, computed, onBeforeMount, onMounted, nextTick } from 'vue'; +import { ref, reactive, watch, computed, onBeforeMount, onMounted, nextTick, watchEffect } from 'vue'; import { useStore } from 'vuex'; import { getTranslationFor } from "../../utils/helpers"; -import { onClickOutside } from '@vueuse/core' +import { onClickOutside, whenever } from '@vueuse/core' import { getCurrentInstance } from "vue"; import { useRoute } from 'vue-router'; +import { useI18n } from 'vue-i18n'; +import { useTed } from '../../composables/useTed'; +import { useAutocomplete } from '../composables/aucotomplete'; +import { useRequestUriName } from '../composables/useRequestUriName'; +import { useDpiContext } from '../composables'; + let instance = getCurrentInstance().appContext.app.config.globalProperties.$env let route = useRoute(); +const dpiContext = useDpiContext(); const props = defineProps({ context: Object, }) + +const { requestAutocompleteSuggestions } = useAutocomplete() + // let listOfVoc: [{ item: 'Country', active: false }, { item: 'Place', active: false }, { item: 'Continent', active: false }], let listOfVoc = ref([]) +const typeText = ref('') let inputText = ref({}) let voc = ref({}) let matches = ref({}) let manURL = ref({}) const store = useStore(); +const { t, locale } = useI18n({ inheritLocale: true, useScope: 'global' }); +const ted = useTed(); + +const man = ref(false) +const vocSearch = ref(false) + +const isEdit = computed(() => !!dpiContext.value.edit?.enabled) +const resourceNameWhenEditing = computed(() => !!isEdit.value && props.context.value.resource) +// e.g. extract "municipalityKey" (second to last) out of resourceName +const keyFromResourceName = computed(() => { + if (resourceNameWhenEditing.value) { + return resourceNameWhenEditing.value.split('/')[resourceNameWhenEditing.value.split('/').length - 2] + } + return '' +}) +const spatialVocabName = computed(() => { + if (!keyFromResourceName.value) { + return '' + } + + const maybePrefix = props.context.attrs.identifier === 'politicalGeocodingURI' + ? 'political-geocoding-' + // todo: extend this for other vocabularies + : '' + + // transform key from camelCase to kebab-case + const vocabKey = keyFromResourceName.value.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase() + return `${maybePrefix}${vocabKey}` +}) + +const translatedResourceName = computed(() => { + // transform key 'districtKey' to 'District Key' + const vocabKey = keyFromResourceName.value.replace(/([a-z])([A-Z])/g, '$1 $2').toLowerCase() + // make first letters of each word uppercase + const vocabKeyUpper = vocabKey.split(' ').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ') + return t(`message.dataupload.datasets.conditional.${vocabKeyUpper}`) +}) + +const { execute, state, isLoading } = useRequestUriName({ + voc: spatialVocabName, + res: computed(() => props.context.value.resource), + // property: 'dcatde:politicalGeocodingURI', + property: computed(() => props.context.attrs.identifier === 'politicalGeocodingURI' ? 'dcatde:politicalGeocodingURI' : 'dct:spatial'), + locale, +}) + +const restoredValueFromEditMode = computed(() => { + if (isEdit.value && !isLoading.value && resourceNameWhenEditing.value && state.value) { + return `${translatedResourceName.value}: ${state.value} (${resourceNameWhenEditing.value.split('/').pop()})` + } + + return '' +}) + +const once = ref(false) +watchEffect(() => { + if (once.value) return; + + if (!!resourceNameWhenEditing.value) { + once.value = true + execute() + } +}, { immediate: true }) + +whenever(restoredValueFromEditMode, () => { + const v = { name: restoredValueFromEditMode.value, resource: resourceNameWhenEditing.value } + props.context.node.input(v) +}) + +// If true, then: +// - hides the select input for manual and vocabulary +// - pre-selects the vocabulary option +// When using formkit schema, enable this option by setting vocabularyOnly to true +// { +// $formkit: 'spatialinput', +// name: 'dcatde:politicalGeocodingURI', +// identifier: 'politicalGeocodingURI', +// vocabularyOnly: true, +// } +const vocabularyOnly = computed(() => !!props.context?.attrs?.vocabularyOnly) if (props.context.attrs.identifier === 'politicalGeocodingURI') { - listOfVoc.value.push({ item: 'Municipality Key', active: false }, { item: 'Regional Key', active: false }, { item: 'Municipal Association Key', active: false }, { item: 'District Key', active: false }, { item: 'Government District Key', active: false }, { item: 'State Key', active: false }) + listOfVoc.value.push( + { item: 'Municipality Key', active: false, placeholder: ted('message.dataupload.datasets.conditional.Municipality Key', 'Municipality Key') }, + { item: 'Regional Key', active: false, placeholder: ted('message.dataupload.datasets.conditional.Regional Key', 'Regional Key') }, + { item: 'Municipal Association Key', active: false, placeholder: ted('message.dataupload.datasets.conditional.Municipal Association Key', 'Municipal Association Key') }, + { item: 'District Key', active: false, placeholder: ted('message.dataupload.datasets.conditional.District Key', 'District Key') }, + { item: 'Government District Key', active: false, placeholder: ted('message.dataupload.datasets.conditional.Government District Key', 'Government District Key') }, + { item: 'State Key', active: false, placeholder: ted('message.dataupload.datasets.conditional.State Key', 'State Key') }, + ) } if (props.context.attrs.identifier === 'spatial') { - listOfVoc.value.push({ item: 'Country', active: false }, { item: 'Place', active: false }, { item: 'Continent', active: false }) + listOfVoc.value.push( + { item: 'Country', active: false, placeholder: ted('message.dataupload.datasets.conditional.Country', 'Country') }, + { item: 'Place', active: false, placeholder: ted('message.dataupload.datasets.conditional.Place', 'Place') }, + { item: 'Continent', active: false, placeholder: ted('message.dataupload.datasets.conditional.Continent', 'Continent') }, + ) } watch(inputText, async () => { getAutocompleteSuggestions(); }) -watch(voc, async () => { +watch(voc, async (newValue, oldValue) => { + if (newValue === oldValue) { return } voc.value = voc.value.toLowerCase(); }) watch(manURL, async () => { props.context.node.input({ 'name': manURL, 'resource': manURL }) }) onMounted(async () => { - matches = [{ name: '--- Type in anything for a live search of the vocabulary ---', resource: 'invalid' }] + matches = [{ name: ted('message.dataupload.info.searchVocabulary', '--- Type in anything for a live search of the vocabulary ---').value, resource: 'invalid' }] await nextTick() // DOM loaded @@ -48,6 +151,10 @@ onMounted(async () => { } else showTable.activeValue = true // console.log(showTable.activeValue); + if (vocabularyOnly.value) { + activeInput('showTable'); vocSearch.value = true; if (man.value) { man.value = false } + } + }); function closeAll() { @@ -83,7 +190,8 @@ function saveToLocal(el) { localStorage.setItem(`dpi_${route.params.property}`, JSON.stringify(pathToLocalStorage)) } const getAutocompleteSuggestions = async () => { - let vocCache = voc.value + + let vocCache = voc.value?.toLowerCase() if (props.context.attrs.identifier === 'politicalGeocodingURI') { @@ -91,9 +199,9 @@ const getAutocompleteSuggestions = async () => { try { let text = inputText.value; - await store.dispatch('dpiStore/requestAutocompleteSuggestions', { voc: vocCache, text: text, base: instance.api.baseUrl }).then((response) => { + await requestAutocompleteSuggestions({ voc: vocCache, text: text, base: instance.api.baseUrl }).then((response) => { const results = response.data.result.results.map((r) => ({ - name: getTranslationFor(r.alt_label, 'en', []) + " (" + r.id + ")", + name: getTranslationFor(r.alt_label, locale.value, []) + " (" + r.id + ")", resource: r.resource, })); matches = results; @@ -104,9 +212,9 @@ const getAutocompleteSuggestions = async () => { else { try { let text = inputText.value; - await store.dispatch('dpiStore/requestAutocompleteSuggestions', { voc: vocCache, text: text, base: instance.api.baseUrl }).then((response) => { + await requestAutocompleteSuggestions({ voc: vocCache, text: text, base: instance.api.baseUrl }).then((response) => { const results = response.data.result.results.map((r) => ({ - name: getTranslationFor(r.pref_label, 'en', []) + " (" + r.id + ")", + name: getTranslationFor(r.pref_label, locale.value, []) + " (" + r.id + ")", resource: r.resource, })); matches = results; @@ -145,6 +253,19 @@ function activeInput(e) { function manURLInput(e) { props.context.node.input({ 'name': e.target.value, 'resource': e.target.value }) } + +function handleSpatielListClick(el) { + props.context.node.input(el); + inputText.value = el.name; + activeInput('showVocEntries'); + showTable.third = false; + saveToLocal(el) +} + +function submitType() { + activeInput('showVocTable'); + +} // console.log(voc); </script> @@ -154,15 +275,18 @@ function manURLInput(e) { <!-- <div class="infoI"> <div class="tooltipFormkit">{{ props.context.attrs.info }}</div> </div> --> - <a class="autocompleteInputSingleValue ">{{ props.context.value.name }}</a> + <a class="autocompleteInputSingleValue ">{{ restoredValueFromEditMode }}</a> <div class="removeX" @click="removeProperty"></div> </div> <div v-else> <div class=" w-100 d-flex"> - <div class="d-flex position-relative m-3 w-100"> - <input id="I1" type="text" class="selectInputField formkit-inner" readonly="readonly" - :placeholder="$t('message.dataupload.info.preferredInput')" - @click="activeInput('showTable')" /> + <div v-if="!vocabularyOnly" class="d-flex position-relative m-3 w-100"> + <label class="w-100"> {{ $t('message.dataupload.info.preferredInput') }} + <input id="I1" type="text" class="selectInputField formkit-inner" readonly="readonly" + :placeholder="$t('message.dataupload.info.preferredInput')" + @click="activeInput('showTable')" /> + </label> + <ul ref="I1" v-show="showTable.first" class="spatialListUpload"> <li @click="activeInput('showTable'); man = true; if (vocSearch) { vocSearch = false; };" class="p-2 border-b border-gray-200 choosableItemsAC"> @@ -175,32 +299,40 @@ function manURLInput(e) { </ul> </div> <div v-if="man" class="d-flex position-relative m-3 w-100"> - <label class="w-100"> Provide an URL + <label class="w-100"> + <!-- todo: I borrowed this from another input. Maybe refactor? --> + {{ $te('message.dataupload.datasets.isReferencedByUrl.placeholder') ? $t('message.dataupload.datasets.isReferencedByUrl.placeholder') : 'Provide an URL' }} <input type="URL" class="selectInputField formkit-inner" placeholder="URL" @input="manURLInput($event)"> </label> </div> <div v-if="vocSearch" class="d-flex position-relative m-3 w-100"> - <label class="w-100"> Choose type of vocabulary - <input id="I2" type="text" class="selectInputField formkit-inner" readonly="readonly" - placeholder="Search the vocabulary" @click="activeInput('showVocTable');"> + <!-- todo: I borrowed this from another input. Maybe refactor? --> + <label class="w-100"> {{ $te('message.dataupload.datasets.contributorType.placeholder') ? $t('message.dataupload.datasets.contributorType.placeholder') : 'Choose type of vocabulary' }} + <input id="I2" type="text" class="selectInputField formkit-inner" readonly="readonly" v-model="typeText" + :placeholder="$te('message.dataupload.datasets.accessRights.placeholder') ? $t('message.dataupload.datasets.accessRights.placeholder') : 'Choose type of vocabulary'" @click="activeInput('showVocTable');"> </label> <ul ref="I2" v-if="showTable.second" class="spatialListUpload"> <li v-for="el in listOfVoc" :key="el" class="p-2 border-b border-gray-200 choosableItemsAC" - @click=" closeAll(); el.active = !el.active; activeInput('showVocTable'); inputText = ''; voc = el.item"> - {{ el.item }}</li> + @click=" closeAll(); el.active = !el.active; activeInput('showVocTable'); inputText = ''; voc = el.item; typeText = el.placeholder"> + {{ $t('message.dataupload.datasets.conditional.' + el.item) }}</li> </ul> </div> </div> <div class="m-3" v-if="vocSearch"> <div v-for="el in listOfVoc" :key="el" class="position-relative"> - <label class="w-100" v-if="el.active"> Search the vocabulary <input id="I3" type="text" - v-model="inputText" class="selectInputField formkit-inner" :placeholder="el.item" - @click="activeInput('showVocEntries'); inputText = ''"> + <label class="w-100" v-if="el.active"> + <!-- todo: I borrowed this from another input. Maybe refactor? --> + <!-- {{ $te('message.dataupload.datasets.accessRights.placeholder') ? $t('message.dataupload.datasets.accessRights.placeholder') : 'Choose type of vocabulary' }} --> + {{ el.placeholder ? el.placeholder : $t('message.dataupload.datasets.conditional.' + el.item) }} + <input id="I3" type="text" + v-model="inputText" class="selectInputField formkit-inner" :placeholder="$te('message.dataupload.datasets.accessRights.placeholder') ? $t('message.dataupload.datasets.accessRights.placeholder') : 'Choose type of vocabulary'" + @click="activeInput('showVocEntries'); inputText = ''" + > </label> <ul ref="I3" v-if="showTable.third && el.active" class="spatialListUpload"> <li v-for="el in matches" :key="el" class="p-2 border-b border-gray-200 choosableItemsAC" - @click="props.context.node.input(el); inputText = el.name; activeInput('showVocEntries'); showTable.third = false; saveToLocal(el)"> + @click="handleSpatielListClick(el)"> {{ el.name }}</li> </ul> </div> @@ -213,15 +345,6 @@ function manURLInput(e) { <script> -export default { - data() { - return { - man: false, - vocSearch: false, - } - }, - -} </script> <style lang="scss" scoped> diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/UniqueIdentifierInput.vue b/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/UniqueIdentifierInput.vue index b44b8f24e3f0b964a1db5351ea854edc430b04ac..4f4fe096399d65a2c21398a955a6084eaff0f6ab 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/UniqueIdentifierInput.vue +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/components/UniqueIdentifierInput.vue @@ -1,34 +1,34 @@ <script setup> -import { ref, onMounted, watchEffect } from 'vue' +import { ref, onMounted, watchEffect, computed } from 'vue' +import { useRoute } from 'vue-router'; import { isNil } from 'lodash'; import axios from 'axios'; import { useStore } from 'vuex'; import { getCurrentInstance } from "vue"; import { useI18n } from 'vue-i18n'; +import { useDpiContext } from '../composables'; let env = getCurrentInstance().appContext.app.config.globalProperties.$env; const store = useStore(); -const isEditMode = ref(); +const dpiContext = useDpiContext(); const isDuplicate = ref(); +const isEditMode = computed(() => !!dpiContext.value.edit?.enabled) isDuplicate.value = localStorage.getItem('dpi_duplicate'); -isEditMode.value = store.getters['auth/getIsEditMode']; -const { t, locale } = useI18n(); -let validationMessages = { +const { t, locale } = useI18n({ useScope: 'global' }); + +const validationMessages = ref({ idformatvalid: "", idunique: "", required: "" -}; +}); onMounted(() => { // This is kind of buggy, its taking the strings from the wrong json (de and en is switched) - validationMessages = { - idformatvalid: t('message.dataupload.datasets.datasetID.invalidFormat'), - idunique: t('message.dataupload.datasets.datasetID.duplicate'), - required: t('message.dataupload.datasets.datasetID.required') - }; - + validationMessages.value.idformatvalid = t('message.dataupload.datasets.datasetID.invalidFormat'); + validationMessages.value.idunique = t('message.dataupload.datasets.datasetID.duplicate'); + validationMessages.value.required = t('message.dataupload.datasets.datasetID.required'); }); diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/composables/aucotomplete.ts b/packages/piveau-hub-ui-modules/lib/data-provider-interface/composables/aucotomplete.ts new file mode 100644 index 0000000000000000000000000000000000000000..9c2629d77e99972ad7bfe5736abbcd905b8bcc67 --- /dev/null +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/composables/aucotomplete.ts @@ -0,0 +1,91 @@ +import { computed, inject, InjectionKey, toValue } from "vue"; +import axios from "axios"; +import { ComputedDpiContext, useDpiContext } from "./useDpiContext"; +import { useRuntimeEnv } from "../../composables/useRuntimeEnv"; +import { ResolvedConfig } from "../../configurations/config-schema"; + +export interface AutocompleteInstance { + requestFirstEntrySuggestions(voc: string, base: string): Promise<any>; + requestAutocompleteSuggestions(options: { + voc: string; + text: string; + base: string; + }): Promise<any>; + requestResourceName(options: { + voc: string; + uri: string; + envs: any; + }): Promise<any>; +} + +export interface AutocompleteOptions { + name: string; + adapter: AutocompleteInstance; +} + +export const autocompleteKey = Symbol( + "autocomplete" +) as InjectionKey<AutocompleteInstance>; + +export function defaultAutocompleteAdapter(options: { + envs: ResolvedConfig + dpiContext: ComputedDpiContext +}): AutocompleteOptions { + const { envs, dpiContext } = options + + return { + name: "default", + adapter: { + requestFirstEntrySuggestions: async (voc, base) => { + return axios.get(`${base}search?filter=vocabulary&vocabulary=${voc}`); + }, + requestAutocompleteSuggestions: async (options) => { + return axios.get( + `${options.base}search?filter=vocabulary&vocabulary=${options.voc}&q=${options.text}&limit=14` + ); + }, + requestResourceName: async (options) => { + const { voc, uri } = options; + const specification = computed( + () => toValue(dpiContext)?.specification + ); + + // // Catching invalid URIs + if (voc === undefined) return; + if (voc === "application") return; + + let req = ""; + + // vocabularies for spdx checksum and inana-media-types are structured differently in the backend then other vocabularies + if (voc === "iana-media-types" || voc === "spdx-checksum-algorithm") { + req = `${envs.api.baseUrl}vocabularies/${voc}`; + } else { + const value = uri.replace( + (specification.value.vocabPrefixes as any)[voc], + "" + ); + const valueEncoded = encodeURIComponent(value); + const requestByUri = value !== valueEncoded; + req = !requestByUri + ? `${envs.api.baseUrl}vocabularies/${voc}/${valueEncoded}` + : `${ + envs.api.baseUrl + }vocabularies/${voc}/vocable?resource=${encodeURIComponent(uri)}`; + } + return axios.get(req); + }, + }, + }; +} + +export function useAutocomplete() { + const autocomplete = inject(autocompleteKey); + + if (!autocomplete) { + throw new Error( + "[useAutocomplete] Autocomplete not found. Did you forget to inject it?" + ); + } + + return autocomplete; +} diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/composables/index.ts b/packages/piveau-hub-ui-modules/lib/data-provider-interface/composables/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..198eb0c4d251d8430f9d62ef5abd6ff770b1a694 --- /dev/null +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/composables/index.ts @@ -0,0 +1,7 @@ +export * from './useDpiContext' +export * from './useDpiStepper' +export * from './useDpiContext' +export * from './useDpiEditMode' +export * from './useDpiStepper' +export * from './useRequestUriName' +export * from './useFormSchema' \ No newline at end of file diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/composables/useDpiContext.ts b/packages/piveau-hub-ui-modules/lib/data-provider-interface/composables/useDpiContext.ts new file mode 100644 index 0000000000000000000000000000000000000000..7af596bfe5f1fe2117b7097a31a6a13086c59781 --- /dev/null +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/composables/useDpiContext.ts @@ -0,0 +1,38 @@ +import { FormKitLibrary } from "@formkit/core" +import { computed, ComputedRef, getCurrentInstance, inject, InjectionKey, MaybeRefOrGetter, provide, toValue } from "vue" + +export interface DpiSpecification { + pageConent?: object + inputDefinition: object + prefixes: object + formatTypes: object + vocabPrefixes: object +} + +export interface DpiContext { + specification: DpiSpecification + specificationName: string + edit?: { + enabled?: boolean + id?: string + fromDraft?: boolean + } +} + +export type ComputedDpiContext = ComputedRef<DpiContext> + +export const dpiContextKey: InjectionKey<ComputedDpiContext> = Symbol('dpiContext') + +export function useDpiContext(): ComputedDpiContext { + const dpiContext = inject(dpiContextKey) + + if (!dpiContext) { + throw new Error('[useDpiContext] DPI Context not found. Did you forget to inject it?') + } + + return dpiContext +} + +export function setupDpiContext(context: MaybeRefOrGetter<DpiContext>) { + provide(dpiContextKey, computed(() => toValue(context))) +} \ No newline at end of file diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/composables/useDpiEditMode.ts b/packages/piveau-hub-ui-modules/lib/data-provider-interface/composables/useDpiEditMode.ts new file mode 100644 index 0000000000000000000000000000000000000000..4b1557a43c28b4d4b79a54431153bcdec8cfb9f6 --- /dev/null +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/composables/useDpiEditMode.ts @@ -0,0 +1,86 @@ +import { useRoute } from "vue-router"; +import { type DpiContext } from "./useDpiContext"; +import { computed, MaybeRefOrGetter, toValue, watch } from "vue"; +import { useStore } from "vuex"; +import { useRuntimeEnv } from "../../composables/useRuntimeEnv"; +import { useAsyncState, watchOnce } from "@vueuse/core"; + + /** + * Use this composable in the DataProviderInterface to fetch a dataset from the Hub API and + * convert it to a form input via localStorage. This composable is used when the user navigates to a dataset + * by clicking on the "Edit" on the DatasetDetails page or in DraftsPage + * + * + * @param dpiContext - The DPI context as returned by `useDpiContext`. + * @returns The computed properties described above. + */ +export function useDpiEditMode(dpiContext: MaybeRefOrGetter<DpiContext>) { + const route = useRoute(); + const store = useStore(); + const env = useRuntimeEnv(); + + const editQuery = computed(() => { + return toValue(dpiContext)?.edit?.enabled ?? route.query.edit === 'true'; + }); + + const editIdQuery = computed(() => { + return toValue(dpiContext)?.edit?.id ?? route.query.id; + }); + + const editFromDraft = computed(() => { + return toValue(dpiContext)?.edit?.fromDraft ?? route.query.fromDraft ?? store.getters["auth/getIsDraft"]; + }); + + // For legacy purposes, set editmode to false if editQuery is false + if (!!editQuery.value) { + localStorage.setItem('dpi_editmode', 'false'); + store.dispatch("auth/setIsEditMode", false); + } + + const requestParams = computed(() => { + const isDraft = editFromDraft.value; + const token = store.getters["auth/getUserData"]?.rtpToken; + const property = route.params.property; + const id = route.params.id; + + const endpoint = isDraft + ? `${env.api.hubUrl}drafts/datasets/${editIdQuery.value}.nt?catalogue=${route.query.catalog}` + : route.params.property === "catalogues" + ? `${env.api.hubUrl}catalogues/${route.query.catalog}.nt` + : `${env.api.hubUrl}datasets/${editIdQuery.value}.nt?useNormalizedId=true`; + return { endpoint, token, property, id }; + }); + + const { execute, isLoading, error } = useAsyncState( + async () => { + return await store.dispatch( + "dpiStore/convertToInput", + { ...requestParams.value } + ); + }, + undefined, + { + immediate: false, + } + ); + + const inEditModeAndRptAvailable = computed(() => !!editIdQuery.value && !!requestParams.value.token) + watch(inEditModeAndRptAvailable, () => { + if (!inEditModeAndRptAvailable.value) return; + const isDraft = editFromDraft.value; + store.dispatch("auth/setIsEditMode", true); + store.dispatch("auth/setIsDraft", isDraft); + execute(); + }, { immediate: true }); + + // Ensure dpiStore contains a specification before rendering the input page. + // Maybe it's not needed but better safe than sorry. + const isReady = computed(() => { + return !!store.getters["dpiStore/getSpecificationName"] && !isLoading.value; + }); + + return { + isReady, + error, + } +} diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/composables/useFormSchema.ts b/packages/piveau-hub-ui-modules/lib/data-provider-interface/composables/useFormSchema.ts new file mode 100644 index 0000000000000000000000000000000000000000..df8f099cbfa82d97d88a9e9b25203e8b03552b37 --- /dev/null +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/composables/useFormSchema.ts @@ -0,0 +1,103 @@ +import { createSharedComposable } from '@vueuse/core' +import { computed, MaybeRefOrGetter, ref, toValue, watch } from 'vue' +import { useDpiContext } from './useDpiContext' +import { useI18n } from 'vue-i18n' +import translate from '../utils/translation-helper' + +const useFormSchemaGlobal = createSharedComposable(() => { + const state = ref({ + schema: { + datasets: {}, + distributions: {}, + catalogues: {} + }, + usersCatalogs: {} + }) + + /** + * Retrieves the schema for a given property. + * + * @param property - The property whose schema should be retrieved. + * Can be a string or a ref to a string. + * @returns A computed ref to the schema of the given property. + */ + const getSchema = (property: MaybeRefOrGetter<string>) => computed(() => { + const _property = toValue(property) + + if (property === 'catalogues' || property === 'datasets' || property === 'distributions') { + return state.value.schema[_property as keyof typeof state.value.schema] + } + + return undefined + }) + + return { state, getSchema } +}) + +export interface UseFormSchemaOptions { + t?: (key: string) => string + te?: (key: string) => boolean +} + +export function useFormSchema(options?: UseFormSchemaOptions) { + const dpiContext = useDpiContext() + const { t, te } = options || useI18n({ useScope: 'global' }) + const { state, getSchema } = useFormSchemaGlobal() + + const dpiConfig = computed(() => dpiContext.value.specification) + + function createSchema({ property, page }: { property: string, page: string }) { + const pageProperties = dpiConfig.value.pageConent?.[property as keyof typeof dpiConfig.value.pageConent][page] as unknown as string[] + const propertyDefinitions = dpiConfig.value.inputDefinition?.[property as keyof typeof dpiConfig.value.inputDefinition] + + // Create copy of state because extractSchema does in-place modifications. + // Just to be safe. + const stateCopy = JSON.parse(JSON.stringify(state.value)) + extractSchema({ state: stateCopy, pageProperties, propertyDefinitions, property, page }) + + state.value = stateCopy + } + + function extractSchema({ + state, + pageProperties, + propertyDefinitions, + property, + page, + }: { + state: any + pageProperties: string[] + propertyDefinitions: any + property: string + page: string + }) { + // important: create new empty schema each time so already existing schema will be overwritten on route/view-change + const newSchema = []; + + for (let index = 0; index < pageProperties.length; index += 1) { + const propertyKey = pageProperties[index]; + try { + newSchema.push(propertyDefinitions[propertyKey]); + } catch (err) { + console.warn( + `DCATAP doens't include a property called: ${propertyKey}` + ); + } + } + + state.schema[property][page] = newSchema; + } + + function translateSchema({ property, page }: { property: string, page: string }) { + const schemaCopy = { ...state.value.schema } + // @ts-ignore + translate(schemaCopy[property][page], property, t, te); + + // Update the global state with the new schema + // @ts-ignore + state.value.schema[property][page] = schemaCopy[property][page] + } + + return { createSchema, translateSchema, getSchema } + +} \ No newline at end of file diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/composables/useRequestUriName.ts b/packages/piveau-hub-ui-modules/lib/data-provider-interface/composables/useRequestUriName.ts new file mode 100644 index 0000000000000000000000000000000000000000..6c8c75c53144c8aaab537b0b49b03ff795616142 --- /dev/null +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/composables/useRequestUriName.ts @@ -0,0 +1,98 @@ +import { useStore } from "vuex"; +import { useRuntimeEnv } from "../../composables/useRuntimeEnv"; +import { getTranslationFor } from "../../utils/helpers"; +import { MaybeRefOrGetter, toValue } from "vue"; +import { useAsyncState } from "@vueuse/core"; +import { useAutocomplete } from "./aucotomplete"; + +/** + * Fetches the name of a resource based on its URI, vocabulary, and property. + */ +export function useRequestUriName(options: { + res: MaybeRefOrGetter<string>; + voc: MaybeRefOrGetter<string>; + property: MaybeRefOrGetter<string>; + locale: MaybeRefOrGetter<string>; +}) { + const { + res: _res, + voc: _voc, + property: _property, + locale: _locale, + } = options; + + const envs = useRuntimeEnv(); + const store = useStore(); + const { requestResourceName } = useAutocomplete() + + const request = useAsyncState( + async () => { + const res = toValue(_res); + const voc = toValue(_voc); + const property = toValue(_property); + const locale = _locale; + + if (res != undefined) { + let vocMatch = + voc === "iana-media-types" || voc === "spdx-checksum-algorithm"; + + let name; + + await requestResourceName({ + voc: voc, + uri: res, + envs, + }) + .then((response) => { + if (property === "dcatde:politicalGeocodingURI") { + if (response != undefined) { + let result = vocMatch + ? response.data.result.results + .filter((dataset: any) => dataset.resource === res) + .map((dataset: any) => + getTranslationFor( + dataset.alt_label, + toValue(locale), + [] + ) + )[0] + : getTranslationFor( + response.data.result.alt_label, + toValue(locale), + [] + ); + name = result; + } + } else { + if (response != undefined) { + let result = vocMatch + ? response.data.result.results + .filter((dataset: any) => dataset.resource === res) + .map((dataset: any) => + getTranslationFor( + dataset.pref_label, + toValue(locale), + [] + ) + )[0] + : getTranslationFor( + response.data.result.pref_label, + toValue(locale), + [] + ); + name = result; + } + } + }); + + return name; + } + }, + undefined, + { immediate: false } + ); + + return { + ...request, + }; +} diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/config/dcatap/input-definition.ts b/packages/piveau-hub-ui-modules/lib/data-provider-interface/config/dcatap/input-definition.ts index 95a1dcf028ef34e48df5ed7ad6c4ca2509233821..189a004232432f368bac942dac43bbb30d75fea3 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/config/dcatap/input-definition.ts +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/config/dcatap/input-definition.ts @@ -1818,7 +1818,7 @@ const dcatapProperties: InputDefinition = { identifier: 'licence', voc: 'licence', options: { text: 'dct:title', textarea: 'skos:prefLabel', url: 'skos:exactMatch' }, - selection: { 1: 'Vocabulary', 2: 'Manually' } + selection: { 1: 'vocabulary', 2: 'manually' } }, spatial: { @@ -1836,7 +1836,7 @@ const dcatapProperties: InputDefinition = { identifier: 'homepage', $formkit: 'simpleInput', name: 'foaf:homepage', - validation: 'optional|url', + validationType: 'url', }, hasPart: { identifier: 'hasPart', diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/config/dcatapde/input-definition.ts b/packages/piveau-hub-ui-modules/lib/data-provider-interface/config/dcatapde/input-definition.ts index 2d7ff8ff9ec74ad3684e86360a5b48e3fb618d41..81adb7fe35b93cc568fee1338829472599c8759c 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/config/dcatapde/input-definition.ts +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/config/dcatapde/input-definition.ts @@ -1420,7 +1420,6 @@ const dcatapProperties: InputDefinition = { identifier: 'accessUrl', name: 'dcat:accessURL', $formkit: 'fileupload', - } ] }, @@ -1483,7 +1482,8 @@ const dcatapProperties: InputDefinition = { $formkit: 'simpleConditional', name: 'dct:license', identifier: 'licence', - voc: 'licence', + // 👇 Intentionally set to 'licenses' instead of 'licence'. License submission and fetching will break otherwise for dcat-ap.de + voc: 'licenses', class: 'property inDistribution', options: { text: 'dct:title', textarea: 'skos:prefLabel', url: 'skos:exactMatch' }, selection: { 1: 'vocabulary', 2: 'manually' }, @@ -1540,13 +1540,21 @@ const dcatapProperties: InputDefinition = { class: 'property inDistribution', children: [ { - identifier: 'downloadUrl', - $formkit: 'url', - name: '@id', - validation: 'optional|url', - classes: { - outer: 'w100-textfield' - } + $formkit: 'group', + identifier: 'relation', + name: 'dct:relation', + class: 'property', + children: [ + { + identifier: 'downloadUrl', + $formkit: 'url', + name: '@id', + validation: 'optional|url', + classes: { + outer: 'w100-textfield' + } + }, + ], }, ], }, @@ -2118,7 +2126,7 @@ const dcatapProperties: InputDefinition = { identifier: 'homepage', $formkit: 'simpleInput', name: 'foaf:homepage', - validation: 'optional|url', + validationType: 'url', class: '' }, hasPart: { diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/config/dcatapde/page-content-config.js b/packages/piveau-hub-ui-modules/lib/data-provider-interface/config/dcatapde/page-content-config.js index 52ba41cd0a53f81cdf857cf8be03737c1cd6f09f..48058b7d015e5602a53794d19a7e46c86f56d779 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/config/dcatapde/page-content-config.js +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/config/dcatapde/page-content-config.js @@ -1,7 +1,7 @@ const config = { datasets: { Mandatory: [ 'title', 'datasetID', 'description', 'catalog', 'publisher', 'theme', 'issued', 'modified' ], - Advised: [ 'politicalGeocodingLevelURI', 'politicalGeocodingURI', 'availabilityDE', 'contributorID', 'geocodingDescription', 'legalBasis', 'qualityProcessURI', 'references', 'contributor', 'originator', 'maintainer', 'keyword', 'subject', 'contactPoint', 'landingPage', 'accrualPeriodicity', 'language', 'spatial', 'temporal', 'creator', 'identifier', 'admsIdentifier', 'page', 'accessRights' ], + Advised: [ 'politicalGeocodingLevelURI', 'politicalGeocodingURI', 'availabilityDE', 'contributorID', 'geocodingDescription', 'legalBasis', 'qualityProcessURI', 'references', 'contributor', 'originator', 'maintainer', 'keyword', 'contactPoint', 'landingPage', 'accrualPeriodicity', 'language', 'spatial', 'temporal', 'creator', 'identifier', 'admsIdentifier', 'page', 'accessRights' ], Recommended: [ 'type', 'isUsedBy', 'conformsTo', 'versionInfo', 'versionNotes', 'temporalResolution', 'spatialResolutionInMeters', 'relation', 'qualifiedRelation', 'isReferencedBy', 'hasVersion', 'isVersionOf', 'source', 'provenance', 'qualifiedAttribution', 'wasGeneratedBy' ], Distributions: [], Overview: [ 'overview' ] diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/config/dcatapde/prefixes.js b/packages/piveau-hub-ui-modules/lib/data-provider-interface/config/dcatapde/prefixes.js index f76df2b136d25bdae969a4b475ba9d0774dd1d03..dcf67d9c1fa979d39175a8b635b89f59bc94008b 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/config/dcatapde/prefixes.js +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/config/dcatapde/prefixes.js @@ -19,7 +19,7 @@ const prefixes = { vcard: "http://www.w3.org/2006/vcard/ns#", time: "http://www.w3.org/2006/time#", dext: "https://data.europa.eu/ns/ext#", - dcatde:"http://www.dcat-ap.de/def/dcatde/" + dcatde: "http://dcat-ap.de/def/dcatde/" }; export default prefixes; \ No newline at end of file diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/config/dcatapde/vocab-prefixes.js b/packages/piveau-hub-ui-modules/lib/data-provider-interface/config/dcatapde/vocab-prefixes.js index 3e13196fbbd88f7a9de83d61094abe4bf265cd9d..614a35d52ca0edbfe5fa378020c633a897b313cf 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/config/dcatapde/vocab-prefixes.js +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/config/dcatapde/vocab-prefixes.js @@ -1,5 +1,5 @@ const vocabPrefixesDCATAPDE = { - "licenses": "http://dcat-ap.de/def/licenses/", + "licence": "http://dcat-ap.de/def/licenses/", "contributors": "http://dcat-ap.de/def/contributors/", "dataset-type": "http://publications.europa.eu/resource/authority/dataset-type/", "dataset-types": "http://dcat-ap.de/def/datasetTypes/", diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/config/dpi-spec-config.js b/packages/piveau-hub-ui-modules/lib/data-provider-interface/config/dpi-spec-config.js index 0e00848c28e26e01ac76e1e3f1655fef77436286..40c2a24c82b405c8c17fed5abf4057811529d1db 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/config/dpi-spec-config.js +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/config/dpi-spec-config.js @@ -31,7 +31,7 @@ import vocabPrefixesDCATAPDEHAPPYFLOW from './dcatapdeHappyFlow/vocab-prefixes'; -const config = { +export const config = { dcatap: { pageConent: pageContent, inputDefinition: inputDefinition, diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/index.ts b/packages/piveau-hub-ui-modules/lib/data-provider-interface/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..fe55e12d7792db663ffad266be1491e69b68e1d4 --- /dev/null +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/index.ts @@ -0,0 +1,82 @@ +import { App } from 'vue'; + +export * from './composables/index' +export { config as dpiSpecConfig } from './config/dpi-spec-config' + +// Import data-provider-interface components +import AutocompleteInput from "./components/AutocompleteInput.vue"; +import ConditionalInput from "./components/ConditionalInput.vue"; +import DataFetchingComponent from "./components/DataFetchingComponent.vue"; +import Dropup from "./components/Dropup.vue"; +import FileUpload from "./components/FileUpload.vue"; +import InfoSlot from "./components/InfoSlot.vue"; +import LanguageSelector from "./components/LanguageSelector.vue"; +import Navigation from "./components/Navigation.vue"; +import UniqueIdentifierInput from "./components/UniqueIdentifierInput.vue"; +import inputDefinitions from "../form/inputDefinitions"; + +// import data provider-interface views OverviewPage +import CatalogueOverview from "./views/OverviewPage/CatalogueOverview.vue"; +import DatasetOverview from "./views/OverviewPage/DatasetOverview.vue"; +import DistributionOverview from "./views/OverviewPage/DistributionOverview.vue"; +import PropertyEntry from "./views/OverviewPage/PropertyEntry.vue"; + +// import data provider-interface views +import DistOverview from "./views/DistributionOverview.vue"; +import DraftsPage from "./views/DraftsPage.vue"; +import InputPage from "./views/InputPage.vue"; +import LinkedDataViewer from "./views/LinkedDataViewer.vue"; +import OverviewPage from "./views/OverviewPage.vue"; +import UserCataloguesPage from "./views/UserCataloguesPage.vue"; +import UserProfilePage from "./views/UserProfilePage.vue"; + +// import data provider-interface +import DataProviderInterface from "./DataProviderInterface.vue"; +import DpiMenu from "./DPIMenu.vue"; + +import store from "./store/dpiStore"; + +export function registerComponents(app: App) { + app.component('AutocompleteInput', AutocompleteInput); + app.component('ConditionalInput', ConditionalInput); + app.component('DataFetchingComponent', DataFetchingComponent); + app.component('Dropup', Dropup); + app.component('FileUpload', FileUpload); + app.component('InfoSlot', InfoSlot); + app.component('LanguageSelector', LanguageSelector); + app.component('Navigation', Navigation); + app.component('UniqueIdentifierInput', UniqueIdentifierInput); + + app.component('CatalogueOverview', CatalogueOverview); + app.component('DatasetOverview', DatasetOverview); + app.component('DistributionOverview', DistributionOverview); + app.component('PropertyEntry', PropertyEntry); + + app.component('DistOverview', DistOverview); + app.component('DraftsPage', DraftsPage); + app.component('InputPage', InputPage); + app.component('LinkedDataViewer', LinkedDataViewer); + app.component('OverviewPage', OverviewPage); + app.component('UserCataloguesPage', UserCataloguesPage); + app.component('UserProfilePage', UserProfilePage); + + app.component('DataProviderInterface', DataProviderInterface); + app.component('DpiMenu', DpiMenu); +} + +// export components +export { AutocompleteInput, ConditionalInput, DataFetchingComponent, Dropup, FileUpload, InfoSlot, LanguageSelector, Navigation, UniqueIdentifierInput } + +// export views +export { CatalogueOverview, DatasetOverview, DistributionOverview, PropertyEntry } +export { DistOverview, DraftsPage, InputPage, LinkedDataViewer, OverviewPage, UserCataloguesPage, UserProfilePage } + +// export data provider-interface +export { DataProviderInterface, DpiMenu } + +export { + inputDefinitions, + store +} + +export { injectionKey as injectionKeyRuntimeConfig } from '../services/runtimeConfigurationService'; \ No newline at end of file diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/store/dpiStore.ts b/packages/piveau-hub-ui-modules/lib/data-provider-interface/store/dpiStore.ts index ff08d20f6ecf7bb5451ba2cc3d5b7ca60f29545a..98c25284ae2b45cc92914a980721eada14532c90 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/store/dpiStore.ts +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/store/dpiStore.ts @@ -1,14 +1,30 @@ // @ts-nocheck /* eslint-disable no-param-reassign, no-shadow, no-console */ import formModule from './modules/formSchemaStore'; -import autocompleteModule from './modules/autocompleteStore'; import conversionModule from './modules/conversionStore'; import navigationModule from './modules/navigationStore'; -const state = {}; -const getters = {}; -const actions = {}; -const mutations = {}; +const state = { + // DPI specification as root state to be shared between all modules + specification: {}, + specificationName: '', +}; +const getters = { + getSpecification: state => state.specification, + getSpecificationName: state => state.specificationName +}; +const actions = { + setSpecification: (context, specification) => { + context.commit('setSpecification', specification); + }, + setSpecificationname: (context, specificationName) => { + context.commit('setSpecificationName', specificationName); + } +}; +const mutations = { + setSpecification: (state, specification) => (state.specification = specification), + setSpecificationName: (state, specificationName) => (state.specificationName = specificationName) +}; const module = { namespaced: true, @@ -18,7 +34,6 @@ const module = { getters, modules: { formModule, - autocompleteModule, conversionModule, navigationModule } diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/store/modules/autocompleteStore.ts b/packages/piveau-hub-ui-modules/lib/data-provider-interface/store/modules/autocompleteStore.ts deleted file mode 100644 index 1dcafb4aa3cc5958324bd796c67e309ed7f7e38d..0000000000000000000000000000000000000000 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/store/modules/autocompleteStore.ts +++ /dev/null @@ -1,87 +0,0 @@ -// @ts-nocheck -/* eslint-disable no-param-reassign, no-shadow, no-console */ -import axios from 'axios'; -import generalDpiConfig from '../../config/dpi-spec-config.js'; - -const state = {}; -const getters = {}; - -const actions = { - requestFirstEntrySuggestions({ commit }, voc, base) { - return new Promise((resolve, reject) => { - const req = `${base}search?filter=vocabulary&vocabulary=${voc}`; - axios.get(req) - .then((res) => { - resolve(res); - }) - .catch((err) => { - reject(err); - }); - }); - }, - requestAutocompleteSuggestions({ commit }, { voc, text, base }) { - // console.log(voc,text,base); - return new Promise((resolve, reject) => { - const req = `${base}search?filter=vocabulary&vocabulary=${voc}&q=${text}`; - axios.get(req) - .then((res) => { - // console.log(res); - - resolve(res); - }) - .catch((err) => { - reject(err); - }); - }); - }, - async requestResourceName({ commit }, { voc, uri, envs }) { - try { - - - const specification = envs.content.dataProviderInterface.specification; - - // Catching invalid URI's - if (voc === undefined) return - if (voc === "application") return - - let req; - - // vocabularies for spdx checksum and inana-media-types are structured differently in the backend then other vocabularies - if (voc === 'iana-media-types' || voc === 'spdx-checksum-algorithm') { - req = `${envs.api.baseUrl}vocabularies/${voc}`; - - } else { - const value = encodeURIComponent(uri.replace(generalDpiConfig[specification].vocabPrefixes[voc], "")); - - - req = `${envs.api.baseUrl}vocabularies/${voc}/${value}`; - - } - return new Promise((resolve, reject) => { - axios.get(req) - .then((res) => { - resolve(res); - }) - .catch((err) => { - reject(err); - - }); - }); - } catch (error) { - // console.log(error); - - } - - }, -}; - -const mutations = {}; - -const autocompleteModule = { - state, - getters, - actions, - mutations -}; - -export default autocompleteModule; \ No newline at end of file diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/store/modules/conversionStore.ts b/packages/piveau-hub-ui-modules/lib/data-provider-interface/store/modules/conversionStore.ts index b4ca329d6a973639eb162992cdb6079cebb5278a..726471f40425f406a68f2af9fa00d12985af75fa 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/store/modules/conversionStore.ts +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/store/modules/conversionStore.ts @@ -9,8 +9,6 @@ import generalHelper from '../../utils/general-helper'; import toRDF from '../../utils/RDFconverter'; import toInput from '../../utils/inputConverter'; -import generalDpiConfig from '../../config/dpi-spec-config.js'; - const state = { datasets: {}, distributions: [], @@ -76,8 +74,8 @@ const actions = { * @param param0 * @param param1 Object containing endpoint and token for data fetching as well as property */ - async convertToInput({ commit }, { endpoint, token, property, specification }) { - + async convertToInput({ commit, rootGetters }, { endpoint, token, property }) { + const specification = rootGetters['dpiStore/getSpecification']; const fetchedData = await generalHelper.fetchLinkedData(endpoint, token).then((response) => { return response; }); @@ -98,7 +96,8 @@ const actions = { * @param property Object containing all values within nested objects for each page of the frontend * @returns Data values in RDF format */ - convertToRDF({ state }, { property, specification }) { + convertToRDF({ state, rootGetters }, { property }) { + const specification = rootGetters['dpiStore/getSpecification']; // merging objects with nested objects containing the values of each page into one main object containing all values for the given property const data = { @@ -168,7 +167,7 @@ const mutations = { */ saveLinkedDataToStore(state, { property, data, specification }) { - const dpiConfig = generalDpiConfig[specification]; + const dpiConfig = specification; toInput.convertToInput(state, property, data, dpiConfig ); localStorage.setItem(`dpi_${property}`, JSON.stringify(state[property])); diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/store/modules/formSchemaStore.ts b/packages/piveau-hub-ui-modules/lib/data-provider-interface/store/modules/formSchemaStore.ts index 2861f1bd1d5d8c922d6127bd4b2ef424c1e465be..d2e486d8f16a84bb1cf001a35139f5c453960d69 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/store/modules/formSchemaStore.ts +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/store/modules/formSchemaStore.ts @@ -1,7 +1,6 @@ // @ts-nocheck /* eslint-disable no-param-reassign, no-shadow, no-console */ import { isEmpty } from 'lodash-es'; -import generalDpiConfig from '../../config/dpi-spec-config.js'; // external translation method import translate from '../../utils/translation-helper'; @@ -11,7 +10,8 @@ const state = { datasets: {}, distributions: {}, catalogues: {} - } + }, + usersCatalogs: {} }; const getters = { @@ -26,9 +26,9 @@ const actions = { * @param {Object} param0 * @param {Object} param1 Object containing property (datasets/catalogues), page (step1/step2/step3) and subpage (distribution1/distribution2/distribution3) of current view */ - createSchema({ commit }, { property, page, specification }) { + createSchema({ commit, state, rootGetters }, { property, page }) { - const dpiConfig = generalDpiConfig[specification]; + const dpiConfig = rootGetters['dpiStore/getSpecification']; const pageProperties = dpiConfig.pageConent[property][page] const propertyDefinitions = dpiConfig.inputDefinition[property] commit('extractSchema', { pageProperties, propertyDefinitions, property, page }); diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/store/modules/navigationStore.ts b/packages/piveau-hub-ui-modules/lib/data-provider-interface/store/modules/navigationStore.ts index b1c30cc88b6cc71f57a2e6c4be5c21c1fbd90529..d554515e00e3c9afbcb8e3e2c85f6b093f3a7281 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/store/modules/navigationStore.ts +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/store/modules/navigationStore.ts @@ -1,9 +1,6 @@ // @ts-nocheck /* eslint-disable no-param-reassign, no-shadow, no-console */ -// config defining which properties are displayed on which page -import generalDpiConfig from '../../config/dpi-spec-config.js'; - const state = { navigation: { datasets: [], @@ -13,8 +10,8 @@ const state = { }; const getters = { - getNavSteps: (state) => (specification) => { - const dpiConfig = generalDpiConfig[specification]; + getNavSteps: (state, _getters, _rootState, rootGetters) => (specification) => { + const dpiConfig = rootGetters['dpiStore/getSpecification']; setConfig(dpiConfig); return state.navigation; diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/utils/RDFconverter.js b/packages/piveau-hub-ui-modules/lib/data-provider-interface/utils/RDFconverter.js index 1219d577d6f3beb00fb9e0fb1639e4a0cc45bcaf..452a1ccb10efa63994102883d4f7bb036489c235 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/utils/RDFconverter.js +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/utils/RDFconverter.js @@ -16,10 +16,7 @@ function convertToRDF(data, property, specification) { let finishedRDFdata; - let dpiConfig; - if (specification === undefined) { - dpiConfig = generalDpiConfig["dcatap"] - } else dpiConfig = generalDpiConfig[specification]; + let dpiConfig = specification; // writer for adding data as quads const RDFdata = new N3.Writer({ prefixes: dpiConfig.prefixes, format: 'N-Triples' }); diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/utils/inputConverter.js b/packages/piveau-hub-ui-modules/lib/data-provider-interface/utils/inputConverter.js index b675fe4708fcfa7910581e15baa10ee09db60407..c4005d65b1b011caa7aa82a93146017975155d79 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/utils/inputConverter.js +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/utils/inputConverter.js @@ -172,7 +172,7 @@ function convertProperties(property, state, id, data, propertyKeys, dpiConfig) { for (let tempIndex = 0; tempIndex < shorts.length; tempIndex += 1) { const position = resolutionValue.indexOf(shorts[tempIndex]); // position of duration letter const subDuration = resolutionValue.substring(0, position); // substring until position of duration letter - const value = subDuration.match(/\d+/g)[0]; // extract number + const value = subDuration?.match(/\d+/g)?.[0] ?? ''; // extract number resolutionValue = resolutionValue.substring(position); // overwrite resolution string with shortened version (missing the extracted part) state[key][forms[tempIndex]] = value; // write to result object } diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/utils/translation-helper.js b/packages/piveau-hub-ui-modules/lib/data-provider-interface/utils/translation-helper.js index 85f8d4a46424ef8b95b9f0be6af689ccc6b250a4..4b0aefaf26dd147afc0b4e2efe98d8fd73dfe782 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/utils/translation-helper.js +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/utils/translation-helper.js @@ -1,5 +1,5 @@ import { has, isObject } from 'lodash-es'; -import store from '../../store'; +import { useI18n } from 'vue-i18n'; /** * Translation of each translatable parameter within the given structure if a translation is available @@ -7,9 +7,8 @@ import store from '../../store'; * @param {String} property String defining which property translation should be used */ -function translateProperty(propertyDefinition, property) { +function translateProperty(propertyDefinition, property, t, te) { - const i18n = store.$app.config.globalProperties.i18n.global; if (has(propertyDefinition, 'identifier')) { // hidden fields don't need a label and have no identifier const translatableParameters = ['label', 'info', 'help', 'placeholder', 'add-label']; const propertyName = propertyDefinition.identifier; @@ -18,15 +17,15 @@ function translateProperty(propertyDefinition, property) { let translation = propertyName; const parameter = translatableParameters[valueIndex]; - const translationExsists = i18n.te(`message.dataupload.${property}.${propertyName}.${parameter}`); - const translationExsistsEN = i18n.te(`message.dataupload.${property}.${propertyName}.${parameter}`, 'en'); + const translationExsists = te(`message.dataupload.${property}.${propertyName}.${parameter}`); + const translationExsistsEN = te(`message.dataupload.${property}.${propertyName}.${parameter}`, 'en'); // Check if translation exists if (!has(property, parameter)) { if (translationExsists) { - translation = i18n.t(`message.dataupload.${property}.${propertyName}.${parameter}`); + translation = t(`message.dataupload.${property}.${propertyName}.${parameter}`); } else if (translationExsistsEN) { - translation = i18n.t(`message.dataupload.${property}.${propertyName}.${parameter}`, 'en'); + translation = t(`message.dataupload.${property}.${propertyName}.${parameter}`, 'en'); } else { translation = parameter; } @@ -61,29 +60,29 @@ function translateProperty(propertyDefinition, property) { * @param {Object} schema Object containing the forms schema * @param {String} property String defining which property translation should be used (datasets/ distribution/ catalogues) */ -function translate(schema, property) { +function translate(schema, property, t, te) { for (let index = 0; index < schema.length; index += 1) { const schemaPropertyValues = schema[index]; // translation of group forms and their nested properties if (has(schemaPropertyValues, 'children')) { // group attributes should be translated too - translateProperty(schemaPropertyValues, property); + translateProperty(schemaPropertyValues, property, t, te); // translated nested properties - translate(schemaPropertyValues.children, property); + translate(schemaPropertyValues.children, property, t, te); // translation of conditional forms and their nested properties } else if (has(schemaPropertyValues, 'data')) { // group attributes should be translated too - translateProperty(schemaPropertyValues, property); + translateProperty(schemaPropertyValues, property, t, te); // translate nested data const dataKeys = Object.keys(schemaPropertyValues.data); for (let keyIndex = 0; keyIndex < dataKeys.length; keyIndex += 1) { const currentKey = dataKeys[keyIndex]; - translate(schemaPropertyValues.data[currentKey], property); + translate(schemaPropertyValues.data[currentKey], property, t, te); } // translation of 'normal' singular form properties } else { - translateProperty(schemaPropertyValues, property); + translateProperty(schemaPropertyValues, property, t, te); } } } diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/DraftsPage.vue b/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/DraftsPage.vue index c9b553df1a59c6efb04ebf2996730c6846503467..da2cd46f76e5948059ac7fe800d3020fb5fa2d43 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/DraftsPage.vue +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/DraftsPage.vue @@ -298,10 +298,8 @@ export default { } .dropDownWrap { - width: 175px; + min-width: 175px; position: relative; - - } .dropdown-toggle { diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/InputPage.vue b/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/InputPage.vue index 48996b5e028c838ef098527cd9fd62c9f97f9409..b9b65554d8e850f56ba6c2b1451de3796689b8f8 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/InputPage.vue +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/InputPage.vue @@ -6,7 +6,7 @@ <FormKit type="form" v-model="formValues" :actions="false" :plugins="[stepPlugin]" id="dpiForm" @change="saveFormValues({ property: property, page: page, distid: id, values: formValues })" @click="saveFormValues({ property: property, page: page, distid: id, values: formValues })" @submit.prevent="" - class="d-flex"> + class="d-flex" > <!-- DpiV3 Stepper - make it a component! --> @@ -125,10 +125,10 @@ In this new approach, every property out of the page-content-config.js gets loaded separately. This is for the substepper to work correctly --> - <div style="width: 100%" v-for="(item, innerIndex) in Object.keys(getSchema(property)[stepName]).length" - v-show="Object.values(getSchema(property)[stepName][innerIndex])[0] === activeSubStep.replace(/_\d+$/, '') || stepName === 'Landing'"> + <div style="width: 100%" v-for="(item, innerIndex) in Object.keys(getSchema(property).value[stepName]).length" + v-show="Object.values(getSchema(property).value[stepName][innerIndex])[0] === activeSubStep.replace(/_\d+$/, '') || stepName === 'Landing'"> <FormKitSchema v-if="stepName !== 'Distributions'" @handleNav="handleNavInput" - :schema="getSchema(property)[stepName][innerIndex]" :library="library"> + :schema="getSchema(property).value[stepName][innerIndex]" :library="library"> </FormKitSchema> </div> <div v-if="activeStep === 'Landing'" class="dpiV3_CTALanding"> @@ -257,7 +257,7 @@ <script> /* eslint-disable no-alert,arrow-parens,no-param-reassign,no-lonely-if */ -import { defineComponent, markRaw } from "vue"; +import { defineComponent, markRaw, computed } from "vue"; import { mapActions, mapGetters } from "vuex"; import $ from "jquery"; import PropertyChooser from "./PropertyChooser.vue"; @@ -288,6 +288,8 @@ import { useI18n } from "vue-i18n"; import "../config/styles/variables.css"; import "../config/styles/typography.css"; import { PhCheckCircle, PhNumberCircleFive } from "@phosphor-icons/vue"; +import { plugin, defaultConfig } from '@formkit/vue' +import { useDpiContext, useFormSchema } from '../composables'; export default defineComponent({ props: { @@ -344,11 +346,13 @@ export default defineComponent({ PhNumberCircleFive }, computed: { - ...mapGetters("auth", ["getIsEditMode", "getUserCatalogIds"]), - ...mapGetters("dpiStore", [ - "getSchema", - "getNavSteps", - "getDeleteDistributionInline", + ...mapGetters('auth', [ + 'getIsEditMode', + 'getUserCatalogIds', + ]), + ...mapGetters('dpiStore', [ + 'getNavSteps', + 'getDeleteDistributionInline', ]), getTitleStep() { return Object.keys(this.formValues).filter((key) => @@ -381,14 +385,14 @@ export default defineComponent({ }, }, methods: { - ...mapActions("auth", ["setIsEditMode", "setIsDraft"]), - ...mapActions("dpiStore", [ - "createSchema", - "translateSchema", - "saveFormValues", - "saveLocalstorageValues", - "addCatalogOptions", - "clearAll", + ...mapActions('auth', [ + 'setIsEditMode', + 'setIsDraft', + ]), + ...mapActions('dpiStore', [ + 'saveFormValues', + 'saveLocalstorageValues', + 'clearAll', ]), update() { this.$forceUpdate(); @@ -472,7 +476,6 @@ export default defineComponent({ this.activeStep = steplist[this.stepCounter]["step"]; } } - }, dropdownCLick() { const h4Elements = document.querySelectorAll(".formkitProperty h4"); @@ -503,17 +506,21 @@ export default defineComponent({ // no validation in edit mode // get step name where issued and modified are included - const initialSchema = this.getSchema(this.property); + const initialSchema = this.getSchema(this.property).value; const stepWithDates = Object.keys(initialSchema).find( (key) => initialSchema[key].map((el) => el.name).includes("dct:issued") || initialSchema[key].map((el) => el.name).includes("dct:modified") ); - if ( - localStorage.getItem("dpi_editmode") === "true" && - stepWithDates != undefined - ) { + + + this.$formkit.setLocale('de'); + // console.log(this.$formkit); + + + // this.$formkit.getValidationMessages() + if (localStorage.getItem('dpi_editmode') === 'true' && stepWithDates != undefined) { initialSchema[stepWithDates].forEach((el) => { if ( el["identifier"] === "issued" || @@ -639,18 +646,16 @@ export default defineComponent({ } } }; - // MutationObserver - const observer = new MutationObserver(attributeChangedCallback); - const config = { attributes: true }; - let allMatchingElements = []; - - elements.forEach((element, index) => { - const matchingChildren = element.querySelectorAll(".formkit-outer"); - allMatchingElements = allMatchingElements.concat( - Array.from(matchingChildren) - ); - observer.observe(allMatchingElements[index], config); - }); + // MutationObserver + // const observer = new MutationObserver(attributeChangedCallback); + // const config = { attributes: true }; + // let allMatchingElements = []; + + // elements.forEach((element, index) => { + // const matchingChildren = element.querySelectorAll('.formkit-outer'); + // allMatchingElements = allMatchingElements.concat(Array.from(matchingChildren)); + // observer.observe(allMatchingElements[index], config); + // }); }); }); }, @@ -711,6 +716,7 @@ export default defineComponent({ }, mounted() { this.initInputPage(); + }, watch: { activeStep: { @@ -719,13 +725,14 @@ export default defineComponent({ }, }, getFirstTitleFromForm: { - handler() { - if (localStorage.getItem("dpi_editmode") === "false") { - this.setIsDraft(false); - this.setIsEditMode(false); + handler(newVal, oldVal) { + if (newVal === oldVal) return + if (localStorage.getItem('dpi_editmode') === 'false') { + this.setIsDraft(false) + this.setIsEditMode(false) } // only create id from title if the user is not editing an existing dataset with an existing datasetID - if (!this.getIsEditMode) { + if (!this.isEditMode) { this.createDatasetID(); } }, @@ -748,7 +755,7 @@ export default defineComponent({ }); }, setup() { - const { t } = useI18n(); + const dpiContext = useDpiContext(); const { steps, activeStep, @@ -760,6 +767,9 @@ export default defineComponent({ goToNextStep, goToPreviousStep, } = useDpiStepper(); + const { t, te } = useI18n() + + const { translateSchema, createSchema, getSchema } = useFormSchema({ t, te }); const scrollToTop = () => { let { x, y } = useWindowScroll({ behavior: "smooth" }); @@ -775,6 +785,11 @@ export default defineComponent({ const library = markRaw({ OverviewPage }); + const isEditMode = computed(() => { + return !!dpiContext.value.edit?.enabled + }) + + return { steps, visitedSteps, @@ -788,9 +803,12 @@ export default defineComponent({ goToPreviousStep, scrollToTop, library, - t, - }; - }, + isEditMode, + translateSchema, + createSchema, + getSchema, + } + } }); </script> diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage.vue b/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage.vue index 18e9fd03ac1cf1c232de1bb4ec9c41a0cc92c03c..ac4fc348417333b84afbdbfb7c7e6d6738f26c61 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage.vue +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage.vue @@ -1,5 +1,5 @@ <template> - <div class="col-12"> + <div ref="overview-page" class="col-12"> <!-- LANGUAGE SELECTOR --> <div class="mt-5 mb-0"> <div class="row"> @@ -9,13 +9,67 @@ </div> </div> </div> - <div class="mb-3" v-if="showDatasetsOverview"> - <DatasetOverview :dpiLocale="dpiLocale" /> + <div class="mb-3" v-if="showDatasetsOverview && overviewPageIsVisible"> + <DatasetOverview :dpiLocale="dpiLocale" :key="dpiLocale" /> </div> - <div class="mb-3" v-if="showCatalogsOverview"> - <CatalogueOverview :dpiLocale="dpiLocale" /> + <div class="mb-3" v-if="showCatalogsOverview && overviewPageIsVisible"> + <CatalogueOverview :dpiLocale="dpiLocale" :key="dpiLocale" /> </div> </div> +<!-- Legal notice *** Checks for ANNIF to determine that this UI is DEU --> + <div v-if="instance.content.dataProviderInterface.annifIntegration" class="legalnotice py-5" style="width: 90%; margin: 0 auto;"> + <div class="d-flex align-items-start"> + <svg xmlns="http://www.w3.org/2000/svg" width="30px" height="30px" fill="currentColor" + class="bi bi-info-circle mx-3 mb-3 mt-1 infoboxI" viewBox="0 0 16 16"> + <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16" /> + <path + d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0" /> + </svg> + <div class="w-80"> + <p>For <strong>European</strong> <strong>Commission's datasets</strong>, bear in mind that <a + class="external-link" href="https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX:32011D0833" + target="_blank" rel="nofollow noopener"><ins>Decision 2011/833/EU</ins></a> allows for their commercial + reuse + without prior authorisation, except for the material subject to the third party intellectual property rights. + This + Decision has been implemented under the <a class="external-link" + href="https://ec.europa.eu/transparency/documents-register/detail?ref=C(2019)1655&lang=en" + target="_blank" rel="nofollow noopener"><ins>Decision C(2019) 1655 final</ins></a> by which Creative Commons + Attribution 4.0 + International Public License (CC BY 4.0) is adopted as an open licence for the Commission's reuse policy. + Additionally, raw data, metadata or other documents of comparable nature may alternatively be distributed + under + the provisions of the Creative Commons Universal Public Domain Dedication deed (CC0 1.0).</p> + <p>The <strong>Council</strong> and the <strong>European Court of Auditors</strong> have + approved similar decisions on reuse. It is advisable that you check <strong>the reuse policy of your + organisation</strong> before publishing or submitting your dataset.</p> + </div> + </div> + <!-- <p> </p> + <hr /> + <p> </p> --> + + <div class="d-flex align-items-start mt-4"> + <svg xmlns="http://www.w3.org/2000/svg" width="30px" height="30px" fill="currentColor" + class="bi bi-info-circle mx-3 mb-3 mt-1 infoboxI " viewBox="0 0 16 16"> + <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16" /> + <path + d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0" /> + </svg> + <div class="w-80"> + <p>As owner of your dataset, you guarantee that it does not violate the copyright, other intellectual property + or + privacy rights of any third party. In particular, if third party material is included in the dataset, you must + ensure that all necessary permissions have been obtained and appropriate acknowledgment is given, if + necessary. + </p> + <p>If you need further information regarding <strong>licenses or copyright</strong> issues, please contact us + at <span class="nobr"><a class="external-link" href="mailto:op-copyright@publications.europa.eu" + target="_blank" rel="nofollow noopener">op-copyright@publications.europa.eu</a></span></p> + </div> + </div> + </div> + </template> <script> @@ -27,6 +81,10 @@ import LanguageSelector from '../components/LanguageSelector'; import DatasetOverview from './OverviewPage/DatasetOverview.vue'; import CatalogueOverview from './OverviewPage/CatalogueOverview.vue'; +import { useIntersectionObserver } from '@vueuse/core' +import { ref, useTemplateRef } from 'vue' +import { getCurrentInstance } from "vue"; + export default { components: { LanguageSelector, @@ -43,8 +101,9 @@ export default { }, data() { return { - dpiLocale: 'en', - }; + instance : getCurrentInstance().appContext.app.config.globalProperties.$env, + dpiLocale: this.$route.query?.locale || this.$i18n.locale || this.$i18n.fallbackLocale || 'en', + } }, computed: { ...mapGetters('auth', [ @@ -58,7 +117,7 @@ export default { }, showCatalogsOverview() { return this.property === 'catalogues'; - }, + } }, methods: { ...mapActions('dpiStore', [ @@ -166,6 +225,28 @@ export default { } }); }, + setup() { + const target = useTemplateRef('overview-page') + const overviewPageIsVisible = ref(false) + + // Workaround. + // Ensure the individual overview pages are only mounted whenever this overview page is visible. + // For some reason, the form values are not reactive so on initial load, the distribution values are not displayed properly. + // This workaround delays the initial mount of the overview page until the overview page is visible. + // We use the IntersectionObserver to check if the overview page is visible, but we could also use more proper datamodels to determine this. + // todo: figure out why the form values are not reactive on initial load + useIntersectionObserver( + target, + ([{ isIntersecting }]) => { + overviewPageIsVisible.value = isIntersecting + }, + { + rootMargin: '999999px 999999px 999999px 999999px' + } + ) + + return {overviewPageIsVisible} + } }; </script> @@ -188,4 +269,23 @@ export default { } } } -}</style> +} + +.legalnotice { + a { + color: blue; + } + + padding: 1rem; + background-color: rgb(171, 225, 165) +} + +.infoboxI { + width: 5%; +} + +.w-80 { + width: 80%; +} + +</style> diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/DatasetOverview.vue b/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/DatasetOverview.vue index b6186bd11670a1e079ab942dd5f783d083818dda..56e30fa4d0f8ea77f28e1b01119f012d0475db76 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/DatasetOverview.vue +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/DatasetOverview.vue @@ -3,12 +3,15 @@ <div class="overviewHeader p-3"> <div class="firstRow d-flex "> - <div class="datasetNotation dsd-title-tag d-flex align-items-center"><span>Dataset</span></div> - <h1 class="dsTitle"> {{ getTitle() }}</h1> + <div class="datasetNotation dsd-title-tag d-flex align-items-center"><span> + {{ $te('message.metadata.dataset') ? $t('message.metadata.dataset') : 'Dataset' }} + </span></div> + <h1 class="dsTitle"> {{ getTitle }}</h1> </div> <div class="secondRow d-flex justify-content-between"> <div class="dsCatalogue "> - <span><b>Catalog:</b></span> + <span><b>{{ $te('message.metadata.catalog') ? $t('message.metadata.catalog') : 'Catalogue' + }}:</b></span> <a href=""> {{ checkIfPropertySet(getDatasets, 'dcat:catalog') }} </a> @@ -19,17 +22,18 @@ :dpiLocale="dpiLocale"></PropertyEntry> </div> <div class="dsIssued "> - <span><b>Issued:</b></span> + <span><b> + {{ $te('message.dataupload.datasets.issued.label') ? + $t('message.dataupload.datasets.issued.label') : 'Issued' }}:</b></span> <a> - {{ new Date(checkIfPropertyValueSet(getDatasets, 'dct:issued', '@value')).toDateString() }} - <!-- {{ new Date(getDatasets['dct:modified']).toISOString().split('T')[0] }} --> + {{ getDate(new Date(checkIfPropertyValueSet(getDatasets, 'dct:issued', '@value'))) }} </a> </div> <div class="dsUpdated "> - <span><b>Updated:</b></span> + <span><b>{{ $te('message.dataupload.datasets.modified.label') ? + $t('message.dataupload.datasets.modified.label') : 'Updated' }}:</b></span> <a> - {{ new Date(checkIfPropertyValueSet(getDatasets, 'dct:modified', '@value')).toDateString() }} - <!-- {{ new Date(getDatasets['dct:modified']).toISOString().split('T')[0] }} --> + {{ getDate(new Date(checkIfPropertyValueSet(getDatasets, 'dct:modified', '@value')))}} </a> </div> </div> @@ -37,7 +41,7 @@ <div class="dsMainWrap d-flex flex-column mt-3"> <div class=""> <p class="dsDesc px-3"> - {{ getDescription() }} + {{ getDescription }} </p> </div> <div class=""> @@ -154,31 +158,44 @@ export default { }, storeData() { return this.getData('datasets') - } + }, + getTitle() { + return this.getDatasets['dct:title'] && this.getDatasets['dct:title'].filter(el => el['@language'] === this.dpiLocale).map(el => el['@value'])[0]; + }, + getDescription() { + return this.getDatasets['dct:description'] && this.getDatasets['dct:description'].filter(el => el['@language'] === this.dpiLocale).map(el => el['@value'])[0]; + }, }, methods: { - ...mapActions("dpiStore", [ - "requestFirstEntrySuggestions", - "requestAutocompleteSuggestions", - ]), checkIfPropertySet(data, property) { if (data[property] != undefined) return data[property] else { - return "No data available" + return '-' + // return "No data available" + } + }, + getDate(date) { + if (!(date instanceof Date) || isNaN(date.getTime())) return '-'; + + let options = {}; + if (this.dpiLocale === 'de') { + options = { year: 'numeric', month: 'long', day: 'numeric' }; + return date.toLocaleDateString('de-DE', options); + } + else { + options = { year: 'numeric', month: 'long', day: 'numeric' }; + return date.toLocaleDateString('en-EN', options); } + + }, checkIfPropertyValueSet(data, property, value) { if (data[property] != undefined && data[property][value] != undefined) return data[property][value] else { - return "No data available" + return '-' + // return "No data available" } }, - getTitle() { - return this.getDatasets['dct:title'] && this.getDatasets['dct:title'].filter(el => el['@language'] === this.dpiLocale).map(el => el['@value'])[0]; - }, - getDescription() { - return this.getDatasets['dct:description'] && this.getDatasets['dct:description'].filter(el => el['@language'] === this.dpiLocale).map(el => el['@value'])[0]; - }, async reqName(URI) { let nameOfProperty = URI.split('/') let req = `${this.$env.api.baseUrl}vocabularies/${nameOfProperty[nameOfProperty.length - 2]}/${nameOfProperty[nameOfProperty.length - 1]}` diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/DistributionOverview.vue b/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/DistributionOverview.vue index 6d3863509523dc5d23bf4e5383fde5b6199afabb..7bc15795457e5664b4b7d72061a2967758f7e374 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/DistributionOverview.vue +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/DistributionOverview.vue @@ -3,14 +3,13 @@ <!-- DISTRIBUTIONS --> <div class="w-100 disDetailsWrap"> <div class="tHeadWrap"> - <!-- ToDo integrate Weblate --> - <p class="">Name of the Distribution</p> - <p class="">Format</p> - <p class="">Updated</p> - <p class="">Actions</p> + <p class="">{{ $t('message.dataupload.datasets.distribution.overview.name') }}</p> + <p class="">{{ $t('message.metadata.format') }}</p> + <p class="">{{ $t('message.metadata.updated') }}</p> + <p class="">{{ $t('message.metadata.issued') }}</p> + <p class="">{{ $t('message.dataupload.info.actions') }}</p> </div> <div v-for="( distribution, id) in distributionList" :key="'distribution' + id"> - <div class="tdWrap" v-if="id % 2 == 0"> <p v-if="distribution['dct:title'] != undefined && distribution['dct:title'].filter(el => el['@language'] === dpiLocale).map(el => el['@value'])[0]"> @@ -18,7 +17,7 @@ el['@value'])[0] }} </p> <p v-else> - No title in this language + {{ $t('message.dataupload.datasets.distribution.overview.notitleinthislanguage') }} </p> <p v-if="distribution['dct:format'] != '' || Object.keys(distribution['dct:format']).length != 0"> <PropertyEntry profile="distributions" :data="distributionList[id]" property='dct:format' @@ -26,10 +25,17 @@ </PropertyEntry> </p> <p v-else> - No format provided + {{ $t('message.dataupload.datasets.distribution.overview.noformatprovided') }} + </p> + <p v-if="new Date(distribution['dct:modified']['@value']).toLocaleDateString(dpiLocale) != 'Invalid Date'"> + + {{ new Date(distribution['dct:modified']['@value']).toLocaleDateString(dpiLocale) }} + </p> + <p v-else> + - </p> - <p v-if="distribution['dct:modified'] != undefined && distribution['dct:modified']"> - {{ new Date(distribution['dct:modified']['@value']).toDateString() }} + <p v-if="new Date(distribution['dct:issued']['@value']).toLocaleDateString(dpiLocale) != 'Invalid Date'"> + {{ new Date(distribution['dct:issued']['@value']).toLocaleDateString(dpiLocale) }} </p> <p v-else> - @@ -52,7 +58,7 @@ el['@value'])[0] }} </p> <p v-else> - No title in this language + {{ $t('message.dataupload.datasets.distribution.overview.notitleinthislanguage') }} </p> <p v-if="distribution['dct:format'] != undefined"> <PropertyEntry profile="distributions" :data="distributionList[id]" property='dct:format' @@ -60,14 +66,22 @@ </PropertyEntry> </p> <p v-else> - No format provided - </p> - <p v-if="distribution['dct:modified'] != undefined && distribution['dct:modified']"> - {{ new Date(distribution['dct:modified']['@value']).toDateString() }} - </p> - <p v-else> - - + {{ $t('message.dataupload.datasets.distribution.overview.noformatprovided') }} </p> + + <p v-if="new Date(distribution['dct:modified']['@value']).toLocaleDateString(dpiLocale) != 'Invalid Date'"> + + {{ new Date(distribution['dct:modified']['@value']).toLocaleDateString(dpiLocale) }} + </p> + <p v-else> + - + </p> + <p v-if="new Date(distribution['dct:issued']['@value']).toLocaleDateString(dpiLocale) != 'Invalid Date'"> + {{ new Date(distribution['dct:issued']['@value']).toLocaleDateString(dpiLocale) }} + </p> + <p v-else> + - + </p> <p> <a class="moreDisInfoBtn" @click="unfoldDisDetails(id)"> More information @@ -93,7 +107,7 @@ <!-- DISTRIBUTIONS ACCESS URL --> <table class="table table-borderless table-responsive pl-3 bg-light mb-0" v-if="showValue(distribution, 'dcat:accessURL')"> - <tr v-for="( elem, index ) in distribution['dcat:accessURL'] " :key="index"> + <tr v-for="( elem, index ) in distribution['dcat:accessURL'] " :key="index"> <td class="font-weight-bold w-25"> {{ $t('message.metadata.accessUrl') }}: </td> @@ -105,7 +119,7 @@ </tr> </table> <table class="table table-borderless table-responsive pl-3 bg-light"> - <div v-for="( value, name, index ) in tableProperties " :key="index"> + <div v-for="( value, name, index ) in tableProperties " :key="index"> <PropertyEntry profile="distributions" :data="distributionList[id]" :property="name" :value="value" :dpiLocale="dpiLocale" :distId="id"> </PropertyEntry> @@ -280,4 +294,4 @@ export default { display: flex; align-items: center; } -</style> \ No newline at end of file +</style> diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/Properties/MultilingualProp.vue b/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/Properties/MultilingualProp.vue new file mode 100644 index 0000000000000000000000000000000000000000..eba5de5ac93cc0a3ea8fdeeb205017c5836e2970 --- /dev/null +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/Properties/MultilingualProp.vue @@ -0,0 +1,22 @@ +<template> + <tr class="w-100"> + <td class=" font-weight-bold">{{ $t(`${value.label}`) }}:</td> + + <td class="d-flex flex-column"> + {{ data[property].filter(el => el['@language']).length === 0 ? + data[property].map(el => el['@value'])[0] : + data[property].filter(el => el['@language'] === dpiLocale).map(el => el['@value'])[0] || + $t('message.catalogsAndDatasets.noDescriptionAvailable') }} + </td> + </tr> +</template> +<script setup> +const props = defineProps({ + property: Object, + value: Object, + data: Object, + dpiLocale: Object +}) + +</script> +<style scoped></style> \ No newline at end of file diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/Properties/SpecialProp.vue b/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/Properties/SpecialProp.vue index 04118610f807eed2274e368ae0e50ea9ec9e18d8..1f15a01c8da0f99300204d3f30be9b81399ab176 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/Properties/SpecialProp.vue +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/Properties/SpecialProp.vue @@ -6,7 +6,7 @@ <td class=" font-weight-bold">{{ $t(`${value.label}`) }}:</td> <td> <div v-if="showValue(data, 'rdf:type')">{{ $t('message.metadata.type') }}: {{ data['rdf:type'].split(':')[1] - }} + }} </div> <div v-if="showValue(data, 'foaf:name')">{{ $t('message.metadata.name') }}: {{ data['foaf:name'] }}</div> <div v-if="showValue(data, 'foaf:mbox')">{{ $t('message.metadata.email') }}: <app-link @@ -23,12 +23,12 @@ <td class=" font-weight-bold">{{ $t(`${value.label}`) }}:</td> <td class=""> <div v-if="showValue(data, 'rdf:type')">{{ $t('message.metadata.type') }}: {{ data['rdf:type'].split(':')[1] - }} + }} </div> <div v-if="showValue(data, 'vcard:fn')">{{ $t('message.metadata.name') }}: {{ data['vcard:fn'] }}</div> <div v-if="showValue(data, 'vcard:hasEmail')">{{ $t('message.metadata.email') }}: <app-link :to="`mailto:${data['vcard:hasEmail']}`">{{ data['vcard:hasEmail'] }}</app-link></div> - <div v-if="showValue(data, 'vcard:hasOrganizationName')">{{ $t('message.metadata.organizationName') }}: {{ + <div v-if="showValue(data, 'vcard:hasOrganizationName')">{{ data['vcard:hasOrganizationName'] }}</div> <div v-if="showValue(data, 'vcard:hasTelephone')">{{ $t('message.metadata.telephone') }}: {{ data['vcard:hasTelephone'] }}</div> @@ -51,7 +51,8 @@ <tr v-if="property === 'dct:contributor' || property === 'dcatde:maintainer' || property === 'dcatde:originator'"> <td class=" font-weight-bold">{{ $t(`${value.label}`) }}:</td> <td> - <div v-if="showValue(data, 'rdf:type')">{{ $t('message.metadata.type') }}: {{ data['rdf:type'].split(':')[1] }} + <div v-if="showValue(data, 'rdf:type')">{{ $t('message.metadata.type') }}: {{ data['rdf:type'].split(':')[1] + }} </div> <div v-if="showValue(data, 'foaf:name')">{{ $t('message.metadata.name') }}: {{ data['foaf:name'] }}</div> <div v-if="showValue(data, 'foaf:mbox')">{{ $t('message.metadata.email') }}: <app-link @@ -102,23 +103,28 @@ <div v-if="property === 'foaf:page'" class="w-100 d-flex"> <td class=" font-weight-bold">{{ $t(`${value.label}`) }}:</td> <td> - <div v-if="showMultilingualValue(data, 'dct:title')">{{ $t('message.metadata.title') }}: {{ - data['dct:title'].filter(el => el['@language']).length === 0 ? - data['dct:title'].map(el => el['@value'])[0] : - data['dct:title'].filter(el => el['@language'] === dpiLocale).map(el => el['@value'])[0] }}</div> + <div v-if="showMultilingualValue(data, 'dct:title')">{{ $t('message.metadata.title') }}: {{ + data['dct:title'].filter(el => el['@language']).length === 0 ? + data['dct:title'].map(el => el['@value'])[0] : + data['dct:title'].filter(el => el['@language'] === dpiLocale).map(el => el['@value'])[0] || + $t('message.dataupload.datasets.distribution.overview.notitleinthislanguage') + }} + </div> <!-- <div v-if="showMultilingualValue(data, 'dct:title')" class="multilang">This property is available in: <span --> <!-- v-for="(el, index) in data['dct:title']" :key="index">({{ el['@language'] }}) </span></div> --> - <div v-if="showMultilingualValue(data, 'dct:description')">{{ $t('message.metadata.description') }}: {{ - data['dct:description'].filter(el => el['@language']).length === 0 ? - data['dct:description'].map(el => el['@value'])[0] : - data['dct:description'].filter(el => el['@language'] === dpiLocale).map(el => el['@value'])[0] }}</div> + <div v-if="showMultilingualValue(data, 'dct:description')">{{ $t('message.metadata.description') }}: {{ + data['dct:description'].filter(el => el['@language']).length === 0 ? + data['dct:description'].map(el => el['@value'])[0] : + data['dct:description'].filter(el => el['@language'] === dpiLocale).map(el => el['@value'])[0] || + $t('message.catalogsAndDatasets.noDescriptionAvailable') }}</div> <!-- <div v-if="showMultilingualValue(data, 'dct:description')" class="multilang">This property is available in: --> <!-- <span v-for="(el, index) in data['dct:description']" :key="index">({{ el['@language'] }}) </span></div> --> - <div v-if="showValue(data, 'dct:format')">{{ $t('message.metadata.format') }}: {{ data['dct:format']['name'] }} + <div v-if="showValue(data, 'dct:format')">{{ $t('message.metadata.format') }}: {{ data['dct:format']['name'] + }} </div> <div v-if="showValue(data, '@id')">{{ $t('message.metadata.url') }}: <app-link :to="data['@id']">{{ data['@id'] - }}</app-link></div> + }}</app-link></div> </td> </div> @@ -138,25 +144,31 @@ <div>{{ convertTemporalResolution(data) }}</div> </td> </tr> - <!-- DATA SERVICE --> - <tr v-if="showDataService()"> - + <tr v-if="data['rdf:type'] === 'dcat:DataService'"> + <td class=" font-weight-bold">{{ $t(`${value.label}`) }}:</td> <td class=""> <div v-if="showValue(data, 'dct:title')"> <span class="">{{ $t('message.dataupload.distributions.accessServiceTitle.label') - }}:</span> + }}:</span> {{ data['dct:title'].filter(el => el['@language'] === dpiLocale).map(el => el['@value'])[0] }} + <span + v-if="data['dct:title'].filter(el => el['@language'] === dpiLocale).map(el => el['@value'])[0] === undefined"><b>{{ + $t('message.dataupload.datasets.distribution.overview.notitleinthislanguage') }}</b></span> </div> <div v-if="showValue(data, 'dct:description')"> <span class="">{{ $t('message.dataupload.distributions.accessServiceDescription.label') - }}:</span> + }}:</span> {{ data['dct:description'].filter(el => el['@language'] === dpiLocale).map(el => el['@value'])[0] }} + <span + v-if="data['dct:description'].filter(el => el['@language'] === dpiLocale).map(el => el['@value'])[0] === undefined"><b>{{ + $t('message.dataupload.datasets.distribution.overview.nodescriptioninthislanguage') + }}</b></span> </div> <div v-if="showValue(data, 'dcat:endpointURL')" class="pr-1"> <span class="">{{ $t('message.dataupload.distributions.accessServiceEndpointURL.label') - }}:</span> + }}:</span> <app-link class="w-100" :to="data['dcat:endpointURL']">{{ data['dcat:endpointURL'] }}</app-link> </div> </td> @@ -199,12 +211,13 @@ </URIProp> </tr> <!-- License --> - + <tr v-if="manualSwitch(data) === 'liMan'"> <td class=" font-weight-bold">{{ $t(`${value.label}`) }}:</td> <td> <div v-for="item, index in Object.keys(data['dct:license']) "> - <div v-if="data['dct:license'][item] != null && data['dct:license'][item] != '' && item === 'dct:title'"> + <div + v-if="data['dct:license'][item] != null && data['dct:license'][item] != '' && item === 'dct:title'"> <span class="">{{ $t('message.dataupload.distributions.licenceTitle.label') }}:</span> <span>{{ data['dct:license'][item] }}</span> @@ -249,13 +262,17 @@ export default { AppLink, URIProp, }, + methods: { - showDataService() { - try { - return this.property === 'dcat:accessService' && Object.keys(this.data[Object.keys(this.data)[0]][0]).length > 1 && Object.keys(this.data[Object.keys(this.data)[1]][0]).length > 1; - } catch (error) { - } - }, + // showDataService() { + + + // try { + // return this.property === 'dcat:accessService' && Object.keys(this.data[Object.keys(this.data)[0]][0]).length > 1 && Object.keys(this.data[Object.keys(this.data)[1]][0]).length > 1; + + // } catch (error) { + // } + // }, manualSwitch(propData, head) { if (propData != undefined) { diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/Properties/StringProp.vue b/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/Properties/StringProp.vue index 99087ef155cad936278bbb04c0ef7995145092b7..03bc6534fe8fd50fc91bdf2fcc9c4cff3e6fcdbf 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/Properties/StringProp.vue +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/Properties/StringProp.vue @@ -1,5 +1,5 @@ <template> - <td class="font-weight-bold">{{ $t(`${value.label}`) }}</td> + <td class="font-weight-bold">{{ $t(`${value.label}`) }}:</td> <div> <!-- MULTISTRING --> <td v-if="value.type === 'multiString'"> @@ -9,8 +9,9 @@ <!-- SINGULAR STRING --> <td v-if="value.type === 'singularString'"> + - <span v-if="data[property]['@type'] != '' && property === 'dct:rights'">{{ data[property][Object.keys(data[property])[1]] }}</span> + <span v-if="data[property]['@type'] != '' && property === 'dct:rights'"> {{ data[property]['rdfs:label'] }}</span> <span v-if="property != 'dct:rights'"> {{ data[property] }} <span v-if="property === 'dcat:spatialResolutionInMeters'">Meters</span> </span> diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/Properties/URIProp.vue b/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/Properties/URIProp.vue index 78d9bfbcb1289d3fc2ed863a9f1492030e9565ef..c04f2e4729e36712062165350a1e7b211a0ed42e 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/Properties/URIProp.vue +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/Properties/URIProp.vue @@ -33,6 +33,8 @@ import { mapActions } from 'vuex'; import { getTranslationFor } from "../../../../utils/helpers"; import generalHelper from '../../../utils/general-helper'; import dpiConfig from '../../../config/dpi-spec-config'; +import { useDpiContext } from '../../../composables/useDpiContext'; +import { useAutocomplete } from '../../../composables/aucotomplete'; export default { data() { @@ -48,9 +50,6 @@ export default { inHeader: String }, methods: { - ...mapActions("dpiStore", [ - "requestResourceName", - ]), trimString(str, maxLength) { if (str.length > maxLength) { return str.slice(0, maxLength) + '...'; @@ -96,17 +95,6 @@ export default { return name } }, - async getURILabel(value) { - // only request name if there is no name already given - // console.log('########',value); - if (generalHelper.isUrl(value.name)) { - - const prefixes = dpiConfig[this.$env.content.dataProviderInterface.specification].vocabPrefixes; - const vocabulary = Object.keys(prefixes).find(key => value.name.includes(key)); - return await this.requestURILabel(vocabulary, value.name); - } - else return value.name; - } }, async created() { @@ -132,6 +120,25 @@ export default { console.warn(e); } }, + setup() { + const { requestResourceName } = useAutocomplete() + const dpiConfig = useDpiContext(); + + async function getURILabel(value) { + if (generalHelper.isUrl(value.name)) { + + const prefixes = dpiConfig.value.specification.vocabPrefixes; + const vocabulary = Object.keys(prefixes).find(key => value.name.includes(key)); + return await this.requestURILabel(vocabulary, value.name); + } + else return value.name; + } + + return { + requestResourceName, + getURILabel, + } + } } </script> diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/Properties/URLProp.vue b/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/Properties/URLProp.vue index 1cd73804374bdb123988edc81ba1244e0dcee9ed..51876a86fca9ce4b7e82ddbeda89df2200f127c0 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/Properties/URLProp.vue +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/Properties/URLProp.vue @@ -1,6 +1,6 @@ <template> - <td class="font-weight-bold">{{ $t(`${value.label}`) }}</td> + <td class="font-weight-bold">{{ $t(`${value.label}`) }}:</td> <!-- SINGULAR URL --> <td v-if="value.type === 'singularURL'"> @@ -19,23 +19,31 @@ <div v-else> <div v-for="(el, index) in data[property]" :key="index"> - <!-- regular multiple URLs --> + + <!-- regular multiple URLs wit ID Notation--> <app-link v-if="showValue(el, '@id')" :to="el['@id']"> {{ el['@id'] }} </app-link> + <!-- IS USED BY --> <app-link v-if="showValue(el, 'dext:isUsedBy')" :to="el['dext:isUsedBy']"> {{ el['dext:isUsedBy'] }} </app-link> + <!-- regular multiple URLs as an array --> + <app-link v-if="typeof el === 'string'" :to="el" > + {{ el }} + </app-link> </div> </div> <!-- <div class="infoI" @click="editProp(property)"></div> --> </td> + </template> <script> +import { string } from "zod"; import AppLink from "../../../../widgets/AppLink.vue"; import { has, isNil, isEmpty } from 'lodash-es'; diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/PropertyEntry.vue b/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/PropertyEntry.vue index 63abf0a4888bc18d5eb7d88cc06d525679d5602d..03d5a55adc119f3698129e4ff401b94720e383f2 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/PropertyEntry.vue +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/OverviewPage/PropertyEntry.vue @@ -1,16 +1,17 @@ <template> <div> - + <tr class="align-items-center" v-if="isSet"> <!-- <td class=" font-weight-bold" v-if="value.type !== 'special'">{{ $t(`${value.label}`) }}:</td> --> <URIProp v-if="value.type === 'singularURI' || value.type === 'multiURI' || value.type === 'singularURI'" :property="property" :value="value" :data="data" :inHeader="inHeader"> </URIProp> - <URLProp v-if="value.type === 'singularURL' || value.type === 'multiURL'" :property="property" :value="value" - :data="data"></URLProp> + <URLProp v-if="value.type === 'singularURL' || value.type === 'multiURL'" :property="property" + :value="value" :data="data"></URLProp> <StringProp v-if="value.type === 'singularString' || value.type === 'multiString'" :property="property" :value="value" :data="data" :dpiLocale="dpiLocale"></StringProp> - + <MultilingualProp v-if="value.type === 'multiLingual' " :property="property" + :value="value" :data="data" :dpiLocale="dpiLocale"></MultilingualProp> <!-- SPECIAL --> <div class="w-100" v-if="value.type === 'special'"> @@ -20,7 +21,8 @@ <div v-else-if="property != 'dct:creator' && property != 'dcat:temporalResolution' && property != 'spdx:checksum'"> <div v-for="(elem, index) in data[property]" :key="index"> - <SpecialProp :property="property" :value="value" :data="elem" :dpiLocale="dpiLocale"></SpecialProp> + <SpecialProp :property="property" :value="value" :data="elem" :dpiLocale="dpiLocale"> + </SpecialProp> </div> </div> <div v-else> @@ -39,6 +41,7 @@ import URIProp from './Properties/URIProp.vue'; import URLProp from './Properties/URLProp.vue'; import StringProp from './Properties/StringProp.vue'; import SpecialProp from './Properties/SpecialProp.vue'; +import MultilingualProp from './Properties/MultilingualProp.vue'; import generalHelper from '../../utils/general-helper'; import { has, isNil, isEmpty } from 'lodash'; @@ -49,6 +52,7 @@ export default { URLProp, StringProp, SpecialProp, + MultilingualProp }, props: { profile: String, @@ -58,7 +62,7 @@ export default { dpiLocale: String, distId: Number, type: String, - inHeader:String + inHeader: String }, computed: { isSet() { diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/UserCataloguesPage.vue b/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/UserCataloguesPage.vue index b019246d5e4fd8930ef2d8984319fc7e753225c6..a7d9a5d896b6817891391d82f82434b20184f81c 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/UserCataloguesPage.vue +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/UserCataloguesPage.vue @@ -3,12 +3,17 @@ import { useStore } from 'vuex'; import { ref, computed, onMounted, } from 'vue'; import AppLink from "../../widgets/AppLink.vue"; import axios from 'axios' +import { useRouter, useRoute } from 'vue-router'; import { getCurrentInstance } from "vue"; + import { has, isNil, } from 'lodash-es'; +const router = useRouter(); +const route = useRoute(); + let env = getCurrentInstance().appContext.app.config.globalProperties.$env; const store = useStore(); let filteredCatalogs = ref([]) @@ -30,6 +35,17 @@ let filterCatList = async () => { filteredCatalogs.value = filteredCatalogs.value .filter(item => userCatIDList.value.includes(item.id)) .map(item => ({ id: item.id, name: item.title })); +} +const handleMQA = (cat) => { + + router.push({ + name: 'DataProviderInterface-MQASettings', + params: { id: cat.id }, + query: { locale: route.query.locale } + }).catch(() => { }); +} +const handleEdit = () => { + } onMounted(async () => { filterCatList() @@ -40,27 +56,88 @@ onMounted(async () => { <div class="d-flex flex-column bg-transparent container-fluid justify-content-between content "> <h1 class="small-headline">{{ $t('message.dataupload.info.userCatalogues') }}</h1> <p class="m-0 ">{{ $t('message.dataupload.info.userCatDescription') }}</p> - <div class="catWrap"> + <!-- <div class="catWrap"> <div v-for="(catalog, index) in filteredCatalogs" :key="index" class="annifItems "> <app-link :to="{ name: 'CatalogueDetails', query: { locale: $route.query.locale }, params: { ctlg_id: catalog.id } }">{{ catalog.name }}</app-link> </div> - <div v-if="filteredCatalogs.length === 0" v-for="(catalog, index) in userCatIDList" :key="index" class="annifItems "> + <div v-if="filteredCatalogs.length === 0" v-for="(catalog, index) in userCatIDList" :key="index" + class="annifItems "> <app-link :to="{ name: 'CatalogueDetails', query: { locale: $route.query.locale }, params: { ctlg_id: catalog } }">{{ catalog }}</app-link> </div> - </div> - </div> + </div> --> + + + + + + + + <table> + <thead> + <tr> + <th>{{ $t('message.metadata.catalog') }} - ID</th> + <th>{{ $t('message.metadata.description') }}</th> + <th>{{ $t('message.dataupload.menu.actions') }}</th> + </tr> + </thead> + + <tr v-for="(catalog, index) in filteredCatalogs" :key="index"> + <td> + + <app-link + :to="{ name: 'CatalogueDetails', query: { locale: $route.query.locale }, params: { ctlg_id: catalog.id } }">{{ + catalog.id }}</app-link> + + </td> + <td> + <span>{{ catalog.name }}</span> + + </td> + <td> + <!-- <button type="button" class="btn btn-secondary" @click="handleEdit(id, catalog)">{{ + $t('message.metadata.linkedData') }}</button> + <button type="button" class="btn btn-secondary" @click="handleEdit(id, catalog)">{{ + $t('message.dataupload.menu.edit') }}</button> --> + + <button type="button" class="btn btn-action" @click="handleMQA(catalog)">Configure MQA report + + </button> + + </td> + </tr> + + + + </table> + +</div> </div> </template> -<style> +<style scoped> + +table { +margin-top: 50px !important; +margin-left: 0px !important; +} .catOverview { min-height: 60vh; } +.btn-action { + border: solid 1px #3f3f3f; +} + +.btn-action:hover { + border: solid 1px #3f3f3f; + background-color: #3f3f3f; + color: #fff; +} + .catWrap { display: flex; flex-wrap: wrap; @@ -81,4 +158,20 @@ onMounted(async () => { color: lightgray; } + + +th, +td { + padding: 1rem; + +} + +tr { + padding: 1rem; + border-bottom: 1px solid lightgray; +} + +thead { + border-bottom: 1px solid lightgray; +} </style> \ No newline at end of file diff --git a/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/UserProfilePage.vue b/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/UserProfilePage.vue index a5d8408294598aa258864ba44fe0faa6f0b0dcf7..25ed382c908deafde642cee70466916b99a3c5ac 100644 --- a/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/UserProfilePage.vue +++ b/packages/piveau-hub-ui-modules/lib/data-provider-interface/views/UserProfilePage.vue @@ -1,26 +1,50 @@ <template> <div class="d-flex flex-column bg-transparent container-fluid justify-content-between content"> - <h1 class="small-headline">My Profile</h1> + <h1 class="small-headline dataset-details-title">My Profile</h1> <div class="panel-body inf-content"> <div class="row"> <div class="col-md-12"> - <strong class="table-header">User Information</strong><br> + <!-- <strong class="table-header">User Information</strong><br> --> <div class="table-responsive"> - <table class="table table-user-information"> - <tbody> - <tr> - <td> - <strong> - <span class="glyphicon glyphicon-user text-primary"></span> - Name: - </strong> - </td> - <td class="text-primary"> - {{ getUserName }} - </td> - </tr> - </tbody> - </table> + <table class="table table-user-information"> + <tbody> + <tr> + <td class="label-column"> + <strong> + <span class="glyphicon glyphicon-user text-primary"></span> + User ID: + </strong> + </td> + <td class="text-primary value-column"> + {{ getUserName }} + </td> + </tr> + <tr> + <td class="label-column"> + <strong> + <span class="glyphicon glyphicon-user text-primary"></span> + Roles: + </strong> + </td> + <td class="text-primary value-column"> + <p v-for="i in getUserData['roles'].filter(role => !role.startsWith('default-roles') && !['offline_access', 'uma_authorization'].includes(role))" :key="i"> + {{ i }} + </p> + </td> + </tr> + <tr v-if="userProfileDashboardUrl"> + <td class="label-column"> + <strong> + <span class="glyphicon glyphicon-user text-primary"></span> + User Profile: + </strong> + </td> + <td class="text-primary value-column"> + <a :href="userProfileDashboardUrl" target="_blank" class="btn btn-link">Edit Profile</a> + </td> + </tr> + </tbody> + </table> </div> </div> </div> @@ -30,35 +54,126 @@ <script> import { mapGetters } from 'vuex'; +import { getCurrentInstance } from "vue"; + +// let instance = getCurrentInstance().appContext.app.config.globalProperties.$env + + export default { name: 'DataProviderInterface-UserProfile', - props: [], + // props: [], + // data() { + // return { + // values: {}, + // }; + // }, + data() { return { - values: {}, + // Need to assign the URL for userProfileDashboardUrl in the config files. + userProfileDashboardUrl: getCurrentInstance().appContext.app.config.globalProperties.$env.content.dataProviderInterface.userProfileDashboardUrl || '', }; }, computed: { ...mapGetters('auth', [ 'getUserName', + 'getUserData' ]), }, - methods: {}, - created() {}, + // methods: {}, + // created() { }, }; </script> <style scoped> -.inf-content{ - border:1px solid #DDDDDD; - -webkit-border-radius:10px; - -moz-border-radius:10px; - border-radius:10px; - box-shadow: 7px 7px 7px rgba(0, 0, 0, 0.3); +.inf-content { + /* border: 1px solid #DDDDDD; + -webkit-border-radius: 10px; + -moz-border-radius: 10px; + border-radius: 10px; + box-shadow: 7px 7px 7px rgba(0, 0, 0, 0.3); */ + padding: 10px; } -.table-header { +/* .table-header { padding: 0.75rem; +} */ + +.table td { + padding: 15px; + vertical-align: middle; + border-bottom: 1px solid #DDDDDD; /* Ensures the horizontal lines span full width */ +} + +.table th { + text-align: left; +} + +h1.small-headline { + margin-bottom: 20px; +} + +p { + margin: 0; /* Ensure proper line breaks */ +} +/* Added space at the bottom of the page */ +.content { + padding-bottom: 60px; +} + +.table { + width: 100%; + border-collapse: collapse; /* Ensures the borders are merged properly */ } + +.label-column { + width: 10%; /* Adjust as needed */ + text-align: left; + padding-right: 10px; + min-width: 200px; +} + +.value-column { + width: 100%; /* Adjust as needed */ +} + +.value-column .btn { + padding: 0; + /* text-decoration: none; + color: #5bc0de; */ +} + +.table tr:last-child td { + border-bottom: none; +} + +/* .dataset-details-title { + font-size: 1.4rem; + margin-bottom: 0.5rem; + font-family: inherit; + font-weight: 500; + line-height: 1.2; + color: inherit; +} */ + +.dataset-details-title { + display: flex; + align-items: center; /* Aligns the text and line in the center */ + font-size: 1.4rem; + margin-bottom: 0.5rem; + font-family: inherit; + font-weight: 500; + line-height: 1.2; + color: inherit; +} + +.dataset-details-title::after { + content: ""; /* Creates the line after the text */ + flex-grow: 1; /* Makes the line span the remaining width */ + height: 1px; /* Thickness of the line */ + background-color: #ccc; /* Line color */ + margin-left: 10px; /* Space between the text and the line */ +} + </style> diff --git a/packages/piveau-hub-ui-modules/lib/datasetDetails/DatasetDetails.vue b/packages/piveau-hub-ui-modules/lib/datasetDetails/DatasetDetails.vue index 8f65d7761c0cd17fa623936a5c89036c6636af39..ea4bc5a7bcc6d2c1ec8f1f38f6780c92e2820f41 100644 --- a/packages/piveau-hub-ui-modules/lib/datasetDetails/DatasetDetails.vue +++ b/packages/piveau-hub-ui-modules/lib/datasetDetails/DatasetDetails.vue @@ -3,13 +3,14 @@ class="d-flex flex-column p-0 bg-transparent" :data-cy="getRepresentativeLocaleOf(getTitle, $route.query.locale, getLanguages) && `dataset@${getRepresentativeLocaleOf(getTitle, $route.query.locale, getLanguages)}`" - > + > + <router-view name="test" ></router-view> <dataset-details-navigation v-if="topTitle" :dataset-id="getID"/> <div class="container-fluid mb-5 pt-1 content dsd-content"> <dataset-details-header /> <dataset-details-navigation v-if="!topTitle" :dataset-id="getID"/> <hr v-if="topTitle" /> - <router-view name="datasetDetailsSubpages"></router-view> + <router-view name="datasetDetailsSubpages" ></router-view> </div> </div> </template> diff --git a/packages/piveau-hub-ui-modules/lib/datasetDetails/DatasetDetailsDataset.vue b/packages/piveau-hub-ui-modules/lib/datasetDetails/DatasetDetailsDataset.vue index b4a0b5707d6e4f336b1b3dbeb154481d8dc75dc7..328f87ddc7266a354c56ff0788f0b0b328021056 100644 --- a/packages/piveau-hub-ui-modules/lib/datasetDetails/DatasetDetailsDataset.vue +++ b/packages/piveau-hub-ui-modules/lib/datasetDetails/DatasetDetailsDataset.vue @@ -58,7 +58,6 @@ :showPublisher="showPublisher" :embed="embed" /> - <dataset-details-features :getKeywords="getKeywords" :pages="pages" @@ -70,10 +69,12 @@ :showArray="showArray" :showObject="showObject" /> - - <dataset-details-properties - v-if="showDatasetProperties" - /> + <dataset-details-properties v-if="showDatasetProperties"> + <template #property-header><slot name="property-header"></slot></template> + <template #property-table-before><slot name="property-table-before"></slot></template> + <template #property-table><slot name="property-table"></slot></template> + <template #property-table-after><slot name="property-table-after"></slot></template> + </dataset-details-properties> </div> </div> </template> @@ -207,7 +208,7 @@ 'getReleaseDate', 'getSpatial', 'getTranslationMetaData', - 'getTitle', + 'getTitle' ]), dateIncorrect() { return this.getDateIncorrect; diff --git a/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/Distribution.vue b/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/Distribution.vue index f59d445b6eb29ba7dd76c75d7cbd8e3311248c08..1b8f3974443bbce5ce48108aeebfcffd07715e4b 100644 --- a/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/Distribution.vue +++ b/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/Distribution.vue @@ -1,60 +1,36 @@ <template> - <div class="position-relative" @click="selectForPreview"> + <div class="position-relative"> <div class="mb-3 d-flex flex-row flex-wrap flex-md-nowrap distributions__item"> - <distribution-format - :distribution="distribution" - :getDistributionFormat="getDistributionFormat" - :distributionFormatTruncated="distributionFormatTruncated" - :embed="embed" - /> - <distribution-details - :getDistributionTitle="getDistributionTitle" - :distribution="distribution" - :distributions="distributions" - :distributionDescriptionIsExpanded="distributionDescriptionIsExpanded" + <distribution-format :distribution="distribution" :getDistributionFormat="getDistributionFormat" + :distributionFormatTruncated="distributionFormatTruncated" :embed="embed" /> + <distribution-details :getDistributionTitle="getDistributionTitle" :distribution="distribution" + :distributions="distributions" :distributionDescriptionIsExpanded="distributionDescriptionIsExpanded" :getDistributionDescription="getDistributionDescription" :toggleDistributionDescription="toggleDistributionDescription" :distributionDescriptionIsExpandable="distributionDescriptionIsExpandable" :distributionVisibleContent="distributionVisibleContent" - :distributionExpandedContent="distributionExpandedContent" - :distributionIsExpanded="distributionIsExpanded" - :showLicensingAssistant="showLicensingAssistant" - :showLicence="showLicence" - :filterDateFormatEU="filterDateFormatEU" - :showArray="showArray" - :showNumber="showNumber" - :showObject="showObject" - :showObjectArray="showObjectArray" - :appendCurrentLocaleToURL="appendCurrentLocaleToURL" - :toggleDistribution="toggleDistribution" - /> - <distribution-added - :date="updatedDate" - /> - <distribution-actions - :distribution="distribution" - :distributions="distributions" - :isUrlInvalid="isUrlInvalid" - :getVisualisationLink="getVisualisationLink" - :showTooltipVisualiseButton="showTooltipVisualiseButton" - :previewLinkCallback="previewLinkCallback" - :openIfValidUrl="openIfValidUrl" - :showDownloadDropdown="showDownloadDropdown" - :getDownloadUrl="getDownloadUrl" - :showAccessUrls="showAccessUrls" - :isOnlyOneUrl="isOnlyOneUrl" - :trackGoto="trackGoto" - :getDistributionFormat="getDistributionFormat" - :replaceHttp="replaceHttp" - /> + :distributionExpandedContent="distributionExpandedContent" :distributionIsExpanded="distributionIsExpanded" + :showLicensingAssistant="showLicensingAssistant" :showLicence="showLicence" + :filterDateFormatEU="filterDateFormatEU" :showArray="showArray" :showNumber="showNumber" + :showObject="showObject" :showObjectArray="showObjectArray" :appendCurrentLocaleToURL="appendCurrentLocaleToURL" + :toggleDistribution="toggleDistribution" /> + <distribution-added :date="updatedDate" /> + + <div class="actions-container"> + <distribution-actions @display-visualisation="selectForPreview" :distribution="distribution" :distributions="distributions" :isUrlInvalid="isUrlInvalid" + :getVisualisationLink="getVisualisationLink" :showTooltipVisualiseButton="showTooltipVisualiseButton" + :previewLinkCallback="previewLinkCallback" :openIfValidUrl="openIfValidUrl" + :showDownloadDropdown="showDownloadDropdown" :getDownloadUrl="getDownloadUrl" :showAccessUrls="showAccessUrls" + :isOnlyOneUrl="isOnlyOneUrl" :trackGoto="trackGoto" :getDistributionFormat="getDistributionFormat" + :replaceHttp="replaceHttp" /> + <slot name="additional-actions"></slot> + </div> + </div> - <fading-distribution-overlay - v-if="fading" - :distributions="distributions" + <fading-distribution-overlay v-if="fading" :distributions="distributions" :setDistributionsDisplayCount="setDistributionsDisplayCount" :increaseNumDisplayedDistributions="increaseNumDisplayedDistributions" - :nonOverflowingIncrementsForDistributions="nonOverflowingIncrementsForDistributions" - /> + :nonOverflowingIncrementsForDistributions="nonOverflowingIncrementsForDistributions" /> </div> </template> @@ -131,7 +107,7 @@ export default { }, computed: { updatedDate() { - if (this.has(this.distribution, 'modificationDate') && !this.isNil(this.distribution.modificationDate)) { + if (this.has(this.distribution, 'modificationDate') && !this.isNil(this.distribution.modificationDate)) { return this.filterDateFormatEU(this.distribution.modificationDate); } else { return this.filterDateFormatEU(this.distribution.releaseDate); @@ -142,8 +118,9 @@ export default { has, isNil, truncate, - selectForPreview(){ - this.$emit('selectForPreview', this.getDistributionTitle(this.distribution)) + selectForPreview() { + const accessUrl = this.distribution.downloadUrls && this.distribution.downloadUrls.length ? this.distribution.downloadUrls[0] : this.distribution.accessUrl[0] + this.$emit('selectForPreview', {'title': this.getDistributionTitle(this.distribution),'url': accessUrl, 'format': this.distribution.format.id.toLowerCase()}) } } }; @@ -151,7 +128,6 @@ export default { <style lang="scss" scoped> - .text-break { word-break: break-word; } @@ -164,9 +140,11 @@ td { /*** BOOTSTRAP ***/ button:focus { - outline:0; + outline: 0; } -.options, .download { + +.options, +.download { .dropdown-menu { .dropdown-item { &:hover { @@ -185,6 +163,4 @@ button:focus { //position: relative; border-bottom: 1px solid rgba(0, 0, 0, 0.1); } - - </style> diff --git a/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/Distributions.vue b/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/Distributions.vue index 6be52cf823a9e00b4571648f42ff6fbe1afe1880..9dc9a7ca5884abb4814e5c57683cebaa8f183a97 100644 --- a/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/Distributions.vue +++ b/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/Distributions.vue @@ -69,27 +69,29 @@ </div> <download-as-modal /> <!-- <distribution-visualization v-if="showDistibutionVisualisation" class="mb-4"></distribution-visualization> --> - <distribution-visualisation-slot v-if="showDistibutionVisualisation" class="mb-4"></distribution-visualisation-slot> + <distribution-visualisation-slot + v-if="showDistibutionVisualisation && showVisualisation" + ref="visualisationComponent" + :key="componentKey" + class="mb-4" + :title="previewedDistTitle" + :downloadUrl="previewedDistDownloadUrl" + :format="previewedDistFormat" + ></distribution-visualisation-slot> </div> </template> <script> -// import Distribution from './Distribution.vue'; -import DownloadAllDistributions from "../../datasetDetails/distributions/DownloadAllDistributions"; import {has, isNil} from "lodash"; import {getTranslationFor} from "../../utils/helpers"; import { mapGetters } from "vuex"; import DownloadAsModal from "../../datasetDetails/distributions/DistributionDownloadAsModal"; -// import DistributionVisualisation from "./distributionPreview/DistributionVisualisation.vue"; export default { name: 'Distributions', components: { - DownloadAllDistributions, - // Distribution, - DownloadAsModal, - // DistributionVisualisation, + DownloadAsModal }, props: { openModal: Function, @@ -137,9 +139,16 @@ export default { }, data() { return { + componentKey: 0, downloadAllTop: this.$env.content.datasetDetails.bulkDownload.buttonPosition === "top", // previewedDistributionTitle: this.getDistributionTitle(this.displayedDistributions[0]), - showDistibutionVisualisation: this.$env.content.datasetDetails.distributions.showVisualisation, + showDistibutionVisualisation: false, + previewedDistributionTitle: "", + previewedDistDownloadUrl: "", + previewedDistFormat: "", + selectedDistribution: null, + showVisualisation: this.$env.content.datasetDetails.distributions.showVisualisation, + }; }, computed: { @@ -152,13 +161,22 @@ export default { getDistributionDescription(distribution) { return (has(distribution, 'description') && !isNil(distribution.description)) ? getTranslationFor(distribution.description, this.$route.query.locale, this.getLanguages) : '-'; }, - selectDistribution(distribution) { - this.previewedDistributionTitle = distribution; - } + selectDistribution(dist) { + this.previewedDistTitle = dist.title; + this.previewedDistDownloadUrl = dist.url; + this.previewedDistFormat = dist.format; + this.showDistibutionVisualisation = true; + this.componentKey += 1; // kill and rerender with new values + + // scroll to visualisation component + this.$nextTick(() => { + const visualisationComponent = this.$refs.visualisationComponent; + if (visualisationComponent) { + visualisationComponent.$el.scrollIntoView({ behavior: 'smooth' }); + } + }); + }, }, - // created() { - // this.showDistibutionVisualisation = this.$env.content.datasetDetails.distributions.showVisualisation - // } }; </script> diff --git a/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/DistributionsHeader.vue b/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/DistributionsHeader.vue index 1792a94e1fe2c4aef42d53080b236778322cad7f..69b48cbc979f3ae584d52486cf88f67571c774ed 100644 --- a/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/DistributionsHeader.vue +++ b/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/DistributionsHeader.vue @@ -1,6 +1,7 @@ <template> <div class="distributions-header-container w-100"> - <div class="distributions-header-title-container dsd-item d-flex justify-content-between align-items-center"> + <!-- Adjusting the container for correct alignment --> + <div class="distributions-header-title-container dsd-item d-flex flex-column align-items-start" style="position: relative; left: -15px;"> <div class="distributions-header-title"> <h2 :title="$t('message.tooltip.datasetDetails.distribution')" data-toggle="tooltip" diff --git a/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/DownloadAllDistributions.vue b/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/DownloadAllDistributions.vue index 1efe6346332acfcf754fd4ec67d8ba188971c25f..f25120e9d2c7dc39a86b7b1e70a35a0aff01ea8b 100644 --- a/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/DownloadAllDistributions.vue +++ b/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/DownloadAllDistributions.vue @@ -2,21 +2,21 @@ <div v-if="getDistributions.length > 1 && getCatalog.is_part_of !== 'erpd'" class="dsd-download-all-distributions-button"> <pv-button v-if="isLoadingAllDistributionFiles" :small="small" :rounded="true" :primary="primary" :download="true" class="download-all-btn" - data-toggle="modal" data-target="#downloadAllModal" > <div class="loading-spinner"></div> + {{ $t('message.datasetDetails.datasets.downloadAll') }} </pv-button> - <pv-button v-else class="download-all-btn" :small="small" :rounded="true" :primary="primary" :download="true" :action="() => openModal(trackAndDownloadAllDistributions, true)"> + <pv-button v-else class="download-all-btn" :small="small" :rounded="true" :primary="primary" :download="true" :action="openRessourceAccessPopup"> {{ $t('message.datasetDetails.datasets.downloadAll') }} </pv-button> - <div class="modal fade" id="downloadAllModal" tabindex="-1" role="dialog" aria-labelledby="download progress" aria-hidden="true"> + <div class="modal fade" data-backdrop="static" data-keyboard="false" id="downloadAllModal" ref="downloadAllModal" tabindex="-1" role="dialog" aria-labelledby="download progress" aria-hidden="true"> <div class="modal-dialog" role="document"> <div class="modal-content"> <div class="modal-header"> <h3 class="modal-title">{{ $t('message.datasetDetails.datasets.modal.downloadProgress') }}</h3> - <button type="button" class="close" data-dismiss="modal" aria-label="Close"> - <span aria-hidden="true">×</span> - </button> +<!-- <button type="button" class="close" data-dismiss="modal" aria-label="Close">--> +<!-- <span aria-hidden="true">×</span>--> +<!-- </button>--> </div> <div class="modal-body"> <div class="progress"> @@ -44,11 +44,11 @@ </div> </div> <div class="modal-footer"> - <button type="button" class="btn btn-secondary" data-dismiss="modal">{{ $t('message.datasetDetails.datasets.modal.close') }}</button> +<!-- <button type="button" class="btn btn-secondary" data-dismiss="modal">{{ $t('message.datasetDetails.datasets.modal.close') }}</button>--> <button v-if="!downloadAllSuccess && !downloadAllError" type="button" class="btn btn-danger" data-dismiss="modal" @click="cancelDownloadAll(cancelDownloadAllAxiosRequestSource)"> {{ $t('message.datasetDetails.datasets.modal.cancel') }} </button> - <button v-else-if="downloadAllError" type="button" class="btn btn-danger" @click="trackAndDownloadAllDistributions()">{{ $t('message.datasetDetails.datasets.modal.error') }}</button> + <button v-if="downloadAllError" type="button" class="btn btn-danger" @click="trackAndDownloadAllDistributions()">{{ $t('message.datasetDetails.datasets.modal.error') }}</button> <button v-else-if="downloadAllSuccess" type="button" class="btn btn-success" data-dismiss="modal">{{ $t('message.datasetDetails.datasets.modal.okay') }}</button> </div> </div> @@ -273,6 +273,7 @@ export default { return await this.downloadAllDistributions(); }, async downloadAllDistributions() { + $('#downloadAllModal').modal('show'); this.downloadedFilesCounter = 0; this.downloadProgress = 0; this.failedDownloads = 0; @@ -303,17 +304,39 @@ export default { // Chrome requires returnValue to be set e.returnValue = ''; // eslint-disable-line } + }, + openRessourceAccessPopup() { + // const modal = this.$refs.downloadAllModal; + // const modal = document.getElementById('externalAccess'); + // console.log("openRessourceAccessPopup", modal) + // modal.addEventListener('shown.bs.modal', function() { + // console.log("CLOSING"); + // }); + this.openModal(this.trackAndDownloadAllDistributions, true); + }, + onClose(e) { + console.log("CLOSING"); } }, created() { window.addEventListener('beforeunload', this.beforeWindowUnload); }, + // mounted() { + // setTimeout(() => { + // const modal = document.getElementById('externalAccess'); + // modal.addEventListener('click', function() { + // console.log("CLOSING"); + // }); + // }) + // }, beforeUnmount() { window.removeEventListener('beforeunload', this.beforeWindowUnload); + // const modal = this.$refs.downloadAllModal.$el; + // modal.removeEventListener('hide.bs.modal', this.onClose); }, setup() { const router = useRouter(); - + onUnmounted(() => { router.beforeEach((to, from, next) => { if (this.isLoadingAllDistributionFiles && !this.isDownloadAllDistributionsCanceled) { diff --git a/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/distributionActions/DistributionActions.vue b/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/distributionActions/DistributionActions.vue index 025378304515f7c66e2837fb545923a339c68ad4..2ae9ea39a6a254cabb2127c6811b8e6020bb61e1 100644 --- a/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/distributionActions/DistributionActions.vue +++ b/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/distributionActions/DistributionActions.vue @@ -1,13 +1,17 @@ <template> <div class="d-flex flex-sm-row flex-md-column flex-lg-row justify-content-start justify-content-lg-end mt-2 text-md-right col text-left distribution-actions"> - <distribution-preview v-if="!hidePreviewButton" :isUrlInvalid="isUrlInvalid" + <!-- button of new visualization tool --> + <distribution-visualize-button v-if="showVisualisationButton" class="distribution-action btn btn-sm visualise-btn pt-0" @displayVisualisation="$emit('displayVisualisation', getDownloadUrl)" :isUrlInvalid="isUrlInvalid" + :getVisualisationLink="getVisualisationLink" :distribution="distribution" :openIfValidUrl="openIfValidUrl">{{ $t('message.datasetDetails.preview') }}</distribution-visualize-button> + <!-- TODO: remove distribution-preview button once the new visualisation service is online --> + <distribution-preview v-if="!hidePreviewButton && getCatalog.is_part_of !== 'erpd' && !showVisualisationButton" :isUrlInvalid="isUrlInvalid" :getVisualisationLink="getVisualisationLink" :distribution="distribution" :openIfValidUrl="openIfValidUrl" :previewLinkCallback="previewLinkCallback" class="distribution-action" /> <distribution-download v-if="showDownloadDropdown(distribution)" :getDownloadUrl="getDownloadUrl" :showAccessUrls="showAccessUrls" :isOnlyOneUrl="isOnlyOneUrl" :trackGoto="trackGoto" :getDistributionFormat="getDistributionFormat" :replaceHttp="replaceHttp" :distribution="distribution" - class="distribution-action" /> + class="distribution-action distribution-download" /> <linked-data-buttons-dropdown :distributions="distributions" :distribution="distribution" class="distribution-action" /> <div> @@ -21,13 +25,17 @@ <script> import AppLink from "../../../widgets/AppLink"; +import DistributionVisualizeButton from "../../../datasetDetails/distributions/distributionActions/DistributionVisualizeButton"; import DistributionPreview from "../../../datasetDetails/distributions/distributionActions/DistributionPreview"; import DistributionDownload from "../../../datasetDetails/distributions/distributionActions/DistributionDownload"; import LinkedDataButtonsDropdown from "../../../datasetDetails/distributions/distributionActions/LinkedDataButtonsDropdown"; +import {mapGetters} from "vuex"; +import {has, isNil} from "lodash"; + export default { name: "DistributionActions", - components: { AppLink, LinkedDataButtonsDropdown, DistributionDownload, DistributionPreview }, + components: { AppLink, LinkedDataButtonsDropdown, DistributionDownload, DistributionPreview, DistributionVisualizeButton }, props: { distribution: Object, distributions: Object, @@ -44,7 +52,22 @@ export default { getDistributionFormat: Function, replaceHttp: Function, }, + data() { + return { + showVisualisationButton: this.$env.content.datasetDetails.distributions.showVisualisation, + visualisationLinkFormats: [ + 'csv', + 'tsv', + 'ods', + 'xlsx', + 'xls', + ], + } + }, computed: { + ...mapGetters('datasetDetails', [ + 'getCatalog', + ]), showValidateButton() { return this.$env?.content?.datasetDetails?.distributions?.showValidationButton; }, @@ -52,6 +75,14 @@ export default { // only returns true if the config variable hidePreviewButton exists and is set to true return this.$env?.content?.datasetDetails?.distributions?.hidePreviewButton; } + }, + methods: { + showVisualisationLink(distribution) { + if (!has(distribution, 'format.label') || isNil(distribution?.format?.label) + || (isNil(distribution?.downloadUrls[0]) && isNil(distribution?.accessUrl[0]))) return false; + const f = distribution?.format?.id?.toLowerCase(); + return f && this.visualisationLinkFormats.includes(f); + } } } </script> diff --git a/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/distributionActions/DistributionDownload.vue b/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/distributionActions/DistributionDownload.vue index d00877d2268325c947259409c049c1f3d7c7497b..b6ddd69acae4242d2baeeca08f4a2e3542ee130d 100644 --- a/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/distributionActions/DistributionDownload.vue +++ b/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/distributionActions/DistributionDownload.vue @@ -1,12 +1,14 @@ <template> <distribution-dropdown-download :distribution="distribution" - :title="$t('message.tooltip.datasetDetails.distributions.download')" :message="$t('message.datasetDetails.download')" - :isOnlyOneUrl="isOnlyOneUrl(distribution)" :getDownloadUrl="getDownloadUrl" @trackGoto="trackGoto" bgLight="true"> + :title="$t('message.tooltip.datasetDetails.distributions.download')" + :message="$t('message.datasetDetails.download')" + :isOnlyOneUrl="isOnlyOneUrl(distribution)" :getDownloadUrl="getDownloadUrl" + @trackGoto="trackGoto" bgLight="true"> <span class="dropdown-item px-3 d-flex align-items-center d-block" v-if="showAccessUrls(distribution)"> <app-link class="text-decoration-none d-flex justify-content-between access-uri-link" - :to="replaceHttp(distribution.accessUrl[0])" target="_blank" rel="dcat:distribution noopener" - :matomo-track-download="{ format: distribution?.format?.id }" @after-click="$emit('trackGoto')"> + :to="replaceHttp(distribution.accessUrl[0])" target="_blank" rel="dcat:distribution noopener" + :matomo-track-download="{ format: distribution?.format?.id }" @after-click="$emit('trackGoto')"> <span> <i class="material-icons align-bottom">open_in_new</i> <!-- <i class=" copy-text material-icons align-bottom" @click="setClipboard(distribution.accessUrl[0])">file_copy</i> copy --> @@ -22,11 +24,12 @@ </span> <app-link class="text-decoration-none d-flex justify-content-between access-uri-link" - :to="replaceHttp(distribution.accessUrl[0])" target="_blank" rel="dcat:distribution noopener" - :matomo-track-download="{ format: distribution?.format?.id }" @after-click="$emit('trackGoto')"> + :to="replaceHttp(distribution.accessUrl[0])" target="_blank" rel="dcat:distribution noopener" + :matomo-track-download="{ format: distribution?.format?.id }" @after-click="$emit('trackGoto')"> <span> <small class="px-2" property="dcat:mediaType" :content="getDistributionFormat">{{ - $t('message.metadata.accessUrl') }}</small> + $t('message.metadata.accessUrl') + }}</small> </span> </app-link> <!-- <i class="material-icons help-icon ml-3" data-toggle="tooltip" data-placement="bottom" :title="$t('message.datasetDetails.accessURLTooltip')">help_outline</i> --> @@ -37,11 +40,12 @@ </span> <span class="dropdown-item d-block px-3 d-flex align-items-center" - v-for="(downloadURL, i) in distribution.downloadUrls" :key="i"> - <app-link class="text-decoration-none d-flex justify-content-between" :to="replaceHttp(downloadURL)" target="_blank" - :matomo-track-download="{ format: distribution?.format?.id }" @after-click="$emit('trackGoto')"> + v-for="(downloadURL, i) in distribution.downloadUrls" :key="i"> + <app-link class="text-decoration-none d-flex justify-content-between" :to="replaceHttp(downloadURL)" + target="_blank" + :matomo-track-download="{ format: distribution?.format?.id }" @after-click="$emit('trackGoto')"> <span> - <i class="material-icons align-bottom">open_in_new</i> + <i class="open_in_new material-icons align-bottom" >open_in_new</i> <!-- <i class=" copy-text material-icons align-bottom" @click="setClipboard(downloadURL)">file_copy</i> --> </span> <!-- <span> @@ -52,18 +56,23 @@ <!-- <i class="material-icons align-bottom">open_in_new</i> --> <i class="copy-text material-icons align-bottom" @click="setClipboard(downloadURL)">file_copy</i> </span> + <app-link class="text-decoration-none d-flex justify-content-between" :to="replaceHttp(downloadURL)" + target="_blank" + :matomo-track-download="{ format: distribution?.format?.id }" @after-click="$emit('trackGoto')"> <span> <small class="px-2" property="dcat:mediaType">{{ $t('message.metadata.downloadUrl') }}</small> </span> + </app-link> <!-- <i class="material-icons help-icon ml-3" data-toggle="tooltip" data-placement="bottom" :title="$t('message.datasetDetails.downloadURLTooltip')">help_outline</i> --> </span> - <distribution-download-as v-if="this.$env.content.datasetDetails.downloadAs.enable" :distribution="distribution" /> + <distribution-download-as v-if="this.$env.content.datasetDetails.downloadAs.enable" :distribution="distribution"/> </distribution-dropdown-download> </template> <script> -import DistributionDropdownDownload from "../../../datasetDetails/distributions/distributionActions/DistributionDropdownDownload"; +import DistributionDropdownDownload + from "../../../datasetDetails/distributions/distributionActions/DistributionDropdownDownload"; import DistributionDownloadAs from "../../../datasetDetails/distributions/distributionActions/DistributionDownloadAs"; import AppLink from "../../../widgets/AppLink"; @@ -119,6 +128,9 @@ export default { .dropdown-item { .access-uri-link, + .open_in_new { + color: #0e47cb; + } .copy-text { color: #0e47cb; } @@ -148,4 +160,6 @@ export default { .copy-url-popup.hide { display: none; } + + </style> diff --git a/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/distributionActions/DistributionDropdownDownload.vue b/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/distributionActions/DistributionDropdownDownload.vue index 2ac90d1a612750fab29f14ba8d7d2993c146e4f9..00b791e2dd71bf120af7e826b077370677dbe332 100644 --- a/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/distributionActions/DistributionDropdownDownload.vue +++ b/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/distributionActions/DistributionDropdownDownload.vue @@ -1,6 +1,6 @@ <template> <div class="position-relative d-inline-block ml-1 mb-1"> - <button @click="openDropdown" class="dsd-action-button btn btn-sm btn-primary p-0 pl-2 w-100 rounded-lg btn-color dropdown-button d-flex justify-content-between" + <button @click="openDropdown" class="distribution-download dsd-action-button btn btn-sm btn-primary p-0 pl-2 w-100 rounded-lg btn-color dropdown-button d-flex justify-content-between" type="button" aria-haspopup="true" aria-expanded="false"> diff --git a/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/distributionActions/DistributionVisualizeButton.vue b/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/distributionActions/DistributionVisualizeButton.vue new file mode 100644 index 0000000000000000000000000000000000000000..4711ea9fa406be0c63105a78c43e0faa1dd3177b --- /dev/null +++ b/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/distributionActions/DistributionVisualizeButton.vue @@ -0,0 +1,115 @@ +<template> + <div class="position-relative d-inline-block ml-1 mb-1"> + <div v-if="showVisualisationButton && showVisualisationLink(distribution)" class="distribution-action btn btn-sm visualise-btn pt-0" @click="$emit('displayVisualisation', getDownloadUrl)">{{ $t('message.datasetDetails.preview') }}</div> + <a @click="$emit('track-link', getGeoLink, 'link')" + v-else-if="showGeoLink(distribution)" + :href="getGeoLink" + target="_blank" + class="d-flex justify-content-between btn btn-sm btn-primary p-0 pl-2 pr-2 w-100 rounded-lg btn-color dropdown-button"> + {{ $t('message.datasetDetails.preview') }} + </a> + <span v-else class="d-flex justify-content-between p-0 pl-2 pr-2 w-100 rounded-lg btn-color dropdown-button text-opacity-0">{{ $t('message.datasetDetails.preview') }}</span> + </div> + </template> + <script> + import AppLink from "../../../widgets/AppLink"; + import {has, isNil} from "lodash"; + import {mapGetters} from "vuex"; + + export default { + name: "DistributionVisualizeButton", + components: { + AppLink, + }, + props: [ + 'showTooltipVisualiseButton', + 'isUrlInvalid', + 'getVisualisationLink', + 'distribution', + 'openIfValidUrl', + 'previewLinkCallback' + ], + data() { + return { + showVisualisationButton: this.$env.content.datasetDetails.distributions?.showVisualisation, + visualisationLinkFormats: [ + 'csv', + 'tsv', + 'ods', + 'xlsx', + 'xls', + ], + geoLinkFormats: { + wms: 'WMS', + geojson: 'GeoJSON', + fiware_cb: 'fiware_cb', + 'fiware-cb': 'fiware_cb', + }, + geoLink: this.$env?.datasetDetails?.distributions?.geoLink, + }; + }, + computed: { + ...mapGetters('datasetDetails', [ + 'getCatalog', + 'getID' + ]), + getGeoLink() { + const format = this.distribution.format.label; + let f = format.toLowerCase(); + // Use correct Case Sensitive strings + f = this.geoLinkFormats[f]; + if (this.geoLink) { + const geoLinkVariables = { + catalog: this.getCatalog.id, + dataset: this.getID, + distribution: this.distribution.id, + type: f, + lang: this.$route.query.locale, + accessUrl: this.distribution?.accessUrl[0], + } + // Inject variables into geo link + for (let linkVariable in geoLinkVariables) { + this.geoLink = this.geoLink.replace(`{${linkVariable}}`, geoLinkVariables[linkVariable]); + } + // Return Geo Visualisation Link + return this.geoLink; + // return `/geo-viewer/?dataset=${distributionID}&type=${f}&lang=${this.$route.query.locale}`; + } + // Return default Geo Visualisation Link if no link in user-config provided + return `/geo-viewer/?catalog=${this.getCatalog.id}&dataset=${this.getID}&distribution=${this.distribution.id}&type=${f}&lang=${this.$route.query.locale}`; + } + }, + methods: { + showGeoLink(distribution) { + if (!has(distribution, 'format.label') || isNil(distribution.format.label) || !has(distribution, 'id') || isNil(distribution.id) || !has(distribution, 'accessUrl[0]')) return false; + const f = distribution.format.label.toLowerCase(); + return Object.keys(this.geoLinkFormats).includes(f); + }, + showVisualisationLink(distribution) { + if (!has(distribution, 'format.label') || isNil(distribution?.format?.label) + || (isNil(distribution?.downloadUrls[0]) && isNil(distribution?.accessUrl[0]))) return false; + const f = distribution?.format?.id?.toLowerCase(); + return f && this.visualisationLinkFormats.includes(f); + } + } + } + </script> + <style scoped lang="scss"> + .disabled { + cursor: not-allowed; + pointer-events: none; + text-decoration: none; + color: gray; + } + .text-opacity-0 { + opacity: 0; + cursor: default!important; + } + + .visualise-btn { + color: #0e47cb; + vertical-align: text-bottom; + text-decoration: none; + } + </style> + \ No newline at end of file diff --git a/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/distributionDetails/DistributionContent.vue b/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/distributionDetails/DistributionContent.vue index da0601b03554bc631c84a959b8eb81bd7ca76fc3..e42a9822cf070ec87e023bb730219f45e0b5a662 100644 --- a/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/distributionDetails/DistributionContent.vue +++ b/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/distributionDetails/DistributionContent.vue @@ -1,5 +1,5 @@ <template> - <table class="col-12 table table-borderless table-responsive small pl-0 mt-2 mb-1 distribution-details-table"> + <table class="col-12 table table-borderless table-responsive small pl-0 mt-2 mb-1 distribution-details-table" style="margin-left: -10px;"> <template v-for="item in contentList"> <tr class="distribution-license-tr distribution-content-tr" v-if="item === 'license'"> @@ -263,15 +263,15 @@ </td> </tr> - <tr class="distribution-spatialResolutionInMeters-tr distribution-content-tr" v-if="item === 'spatialResolutionInMeters' && has(distribution, 'spatialResolutionInMeters') && showArray(distribution.spatialResolutionInMeters)"> + <tr class="distribution-spatialResolutionInMeters-tr distribution-content-tr" v-if="item === 'spatialResolutionInMeters' && has(distribution, 'spatialResolutionInMeters') "> <td class="w-25 font-weight-bold"> <tooltip :title="$t('message.tooltip.datasetDetails.spatialResolutionInMeters')"> {{ $t('message.metadata.spatialResolutionInMeters.label') }} </tooltip> </td> <td> - <div v-if="showNumber(distribution.spatialResolutionInMeters[0])"> - {{ $t('message.metadata.spatialResolutionInMeters.value', {number: distribution.spatialResolutionInMeters[0]}) }} + <div v-if="showNumber(distribution.spatialResolutionInMeters)"> + {{ $t('message.metadata.spatialResolutionInMeters.value', {number: distribution.spatialResolutionInMeters}) }} </div> </td> </tr> diff --git a/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/distributionDetails/DistributionDetails.vue b/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/distributionDetails/DistributionDetails.vue index 1dc18e302b263651d1ffce7481bd0a69bbae026e..d3a8fc0f68e4880a72bd2afa0171c55b3af8cc60 100644 --- a/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/distributionDetails/DistributionDetails.vue +++ b/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/distributionDetails/DistributionDetails.vue @@ -122,7 +122,8 @@ export default { case 'packageFormat': return accu || (has(distribution, 'packageFormat') && this.showObject(distribution.packageFormat)) case 'hasPolicy': return accu || (has(distribution, 'hasPolicy') && !isNil(distribution.hasPolicy)) case 'conformsTo': return accu || (has(distribution, 'conformsTo') && this.showObjectArray(distribution.conformsTo)) - case 'spatialResolutionInMeters': return accu || (has(distribution, 'spatialResolutionInMeters') && this.showArray(distribution.spatialResolutionInMeters)) + //case 'spatialResolutionInMeters': return accu || (has(distribution, 'spatialResolutionInMeters') && this.showArray(distribution.spatialResolutionInMeters)) + case 'spatialResolutionInMeters': return accu || (has(distribution, 'spatialResolutionInMeters') && !isNil(distribution.spatialResolutionInMeters)) case 'temporalResolution': return accu || (has(distribution, 'temporalResolution') && this.showArray(distribution.temporalResolution)) default: return accu } diff --git a/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/distributionPreview/DistributionVisualisationSlot.vue b/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/distributionPreview/DistributionVisualisationSlot.vue index 1c080303583c67d8d9b11624006ba8823a7bf7fc..3badfaacccde0c6b9dc7894dfc72cdc28f4a1aee 100644 --- a/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/distributionPreview/DistributionVisualisationSlot.vue +++ b/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/distributionPreview/DistributionVisualisationSlot.vue @@ -4,11 +4,17 @@ Visualisation tool there --> <template> - <div></div> + <div> + </div> </template> <script> export default { name: "DistributionVisualisationSlot", + props: { + title: String, + downloadUrl: String, + format: String, + } } </script> diff --git a/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/distributionPreview/DistributionVisualization.vue b/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/distributionPreview/DistributionVisualization.vue deleted file mode 100644 index a8115150d99e1f086486f9a0daa257d3bfa9f268..0000000000000000000000000000000000000000 --- a/packages/piveau-hub-ui-modules/lib/datasetDetails/distributions/distributionPreview/DistributionVisualization.vue +++ /dev/null @@ -1,650 +0,0 @@ -<template> - <div class="dsd-item preview-container"> - <h2 class=" mb-lg-4 mt-lg-4" - data-toggle="tooltip" - data-placement="top" - data-cy="dataset-distribution-preview" - > - <!-- TODO: translate! --> - Preview - </h2> - <!-- Card --> - <div v-if="this.jsonData" class="dv-card card text-center"> - <div class="dv-card-header card-header"> - <ul class="dv-nav-tabs nav nav-tabs card-header-tabs"> - <li class="nav-item" role="tab" :aria-controls="'numerical-chart'" - :aria-selected="activeTab === 'numerical'" aria-label="Numerical chart tab"> - <a class="nav-link" :class="{active: activeTab === 'numerical'}" href="#" @click.prevent="showTab('numerical')">Numerical Data</a> - </li> - <li class="nav-item" role="tab" :aria-controls="'categorical-chart'" - :aria-selected="activeTab === 'categorical'" aria-label="Categorical chart tab"> - <a class="nav-link" :class="{active: activeTab === 'categorical'}" href="#" @click.prevent="showTab('categorical')">Categorical Data</a> - </li> - <li class="nav-item" role="tab" :aria-controls="'table-view'" - :aria-selected="activeTab === 'table-view'" aria-label="Table view tab"> - <a class="nav-link" :class="{active: activeTab === 'table-view'}" href="#" @click.prevent="showTab('table-view')">Table View</a> - </li> - </ul> - </div> - <div class="card-body"> - {{ addData() }} - <!-- Tab for Numerical Charts --> - <div v-if="activeTab == 'numerical'" role="tabpanel" id="numerical-chart"> - <div v-if="showNumTabContent"> - <div> - <div class="d-flex justify-content-between"> - <div class="mb-2 d-flex"> - - <div class="mr-3 d-flex flex-column align-items-start"> - <distribution-preview-select-header title="Labels" :tooltip-text="' '" :labels="categoricalLabels" :defaultLabel="xPicked" @updatePicked="(label) => this.xPicked = label"></distribution-preview-select-header> - </div> - <div class="d-flex flex-column align-items-start"> - <distribution-preview-select-header title="Values" :tooltip-text="' '" :labels="numericalLabels" :multiSelect="true" :defaultLabel="yPicked" @updatePicked="updateYPicked"></distribution-preview-select-header> - </div> - <!-- <button class="btn dv-add-btn" type="button" @click="toggleDropdown">+ add multiple values</button> --> - - </div> - <div class="d-flex flex-column align-items-start"> - <distribution-preview-select-header title="Chart type" :tooltip-text="' '" :labels="viewOptions.map(e => e.label)" :defaultLabel="viewType" @updatePicked="(label) => {this.viewType = label}"></distribution-preview-select-header> - </div> - </div> - <div class="d-flex justify-content-start"> - <!-- <span> - <small>Add chart: </small> - <select v-model="yNewPicked" ref="yNewPicked" class="form-select" aria-label="select Y coerdinator"> - <option disabled value="">select chart to add</option> - <option v-for="(value, index) in numericalLabels" :key="index" :value="value">{{ value }}</option> - </select> - </span> - <button :disabled="!yNewPicked" - :class="{disabled: !yNewPicked}" - class="btn btn-light btn-sm" - @click="addSelectedChart" - > - add chart - </button> --> - <!-- <button v-if="this.datasets.length > 1" - class="btn btn-light btn-sm" - @click="removeLastChart" - > - remove chart - </button> --> - <!-- <button - class="btn btn-light btn-sm" - @click="hideXLabels = !hideXLabels" - > - {{ hideXLabels ? 'show labels' : 'hide labels' }} - </button> --> - </div> - </div> - <!-- <bar-chart v-if="viewType.toLowerCase() === 'bar'" :chartData="this.datacollection" :chartOptions="this.options"></bar-chart> - <line-chart v-else-if="viewType.toLowerCase() === 'line'" :chartData="this.datacollection" :chartOptions="this.options" @remove-chart="removeChart"></line-chart> - <pie-chart v-else-if="viewType.toLowerCase() === 'pie'" :chartData="this.datacollection" :chartOptions="this.options"></pie-chart> - <doughnut-chart v-else-if="viewType.toLowerCase() === 'doughnut'" :chartData="this.datacollection" :chartOptions="this.options"></doughnut-chart> --> - </div> - <div v-else class="m-5">No numerical data to show.</div> - </div> - <!-- Tab for Categorical Charts --> - <div v-else-if="activeTab == 'categorical'" role="tabpanel" id="categorical-chart"> - <div v-if="showCatTabContent"> - <div > - <div class="d-flex justify-content-between mb-2"> - <distribution-preview-select-header title="Labels" :tooltip-text="' '" :labels="categoricalLabels" :defaultLabel="catYPicked" @updatePicked="(label) => this.catYPicked = label"></distribution-preview-select-header> - <distribution-preview-select-header title="Chart type" :tooltip-text="' '" :labels="catViewOptions.map(e => e.label)" :defaultLabel="catViewType" @updatePicked="(label) => {this.catViewType = label}"></distribution-preview-select-header> - </div> - - <!-- <span>Chart type: </span> - <select v-model="catViewType" class="form-select" aria-label="Default select example"> - <option v-for="viewOption in catViewOptions" :value="viewOption.value" :key="viewOption.value">{{ viewOption.label }} </option> - </select> - <br> - <small>Labels: </small> - <select v-model="catYPicked" class="" aria-label="select a view option"> - <option v-for="(value, index) in categoricalLabels" :key="index" :value="value">{{ value }}</option> - </select> --> - - </div> - <!-- <bar-chart v-if="catViewType.toLowerCase() === 'bar'" :chartData="this.catDatacollection" :chartOptions="this.options"></bar-chart> - <line-chart v-else-if="catViewType.toLowerCase() === 'line'" :chartData="this.catDatacollection" :chartOptions="this.options" @remove-chart="removeChart"></line-chart> - <pie-chart v-else-if="catViewType.toLowerCase() === 'pie'" :chartData="this.catDatacollection" :chartOptions="this.options"></pie-chart> - <doughnut-chart v-else-if="catViewType.toLowerCase() === 'doughnut'" :chartData="this.catDatacollection" :chartOptions="this.options"></doughnut-chart> --> - </div> - <div v-else> - No categorical data to show. - </div> - </div> - <!-- Tab for Table View --> - <div v-show="activeTab == 'table-view'" role="tabpanel" id="table-view"> - <!-- <div class="table-wrapper"> - <v-table - class="dist-table" - :data="dataRows" - :currentPage.sync="currentPage" - :pageSize="11" - @totalPagesChanged="totalPages = $event"> - <thead slot="head"> - <v-th v-for="(label, index) in allLabels" :key="index" :sortKey="label">{{ label }}</v-th> - </thead> - <tbody slot="body" slot-scope="{displayData}"> - <tr v-for="row in displayData" :key="row.id"> - <td v-for="(rowValue, index) in Object.values(row)" :key="row.id + '-' + index" > {{ rowValue }} </td> - </tr> - </tbody> - </v-table> - </div> - <smart-pagination - :currentPage.sync="currentPage" - :totalPages="totalPages" - :maxPageLinks="10" - :boundaryLinks="true" - /> --> - </div> - </div> - </div> - - <!-- Feedback Tool --> - <br> - <div class="d-flex justify-content-between align-items-center"> - <small class="text-muted"> - Don't like the default view? suggest a better one <span @click="showFeedbackTool = !showFeedbackTool" class="text-primary pointer" type="button">here</span> - </small> - - <!-- Records range bar --> - <div class="dv-records-range-container text-center"> - <small class="dv-records-range-container-record-number mr-1">{{ this.dataRows.length }} Records</small> - <small v-if="activeTab === 'numerical'"> - <label for="from">show records from</label> - <input @change="updateRange" type="number" id="from" v-model.number="fromIndex" min="1"> - <label for="to">-</label> - <input @change="updateRange" type="number" id="to" v-model.number="toIndex" :min="fromIndex" :max="this.dataRows.length"> - </small> - </div> - </div> - - <div v-if="showFeedbackTool" class="feedback-tool-wrapper"> - <form v-if="showFeedbackForm" action="" class="feedback-tool-form" @submit.prevent> - <span>Suggest a Chart type and/or Labels and Values to be set as default: </span> - - <div class="d-flex dv-fb-options py-3"> - <distribution-preview-select-header title="Chart type" :labels="allChartOptions" :updatePicked="() => {console.log('todo')}"></distribution-preview-select-header> - <distribution-preview-select-header title="Labels" :labels="allLabels" :updatePicked="() => {console.log('todo')}"></distribution-preview-select-header> - <distribution-preview-select-header title="Values" :labels="allLabels" :updatePicked="() => {console.log('todo')}"></distribution-preview-select-header> - </div> - <!-- <br><br> - <label for="view-type">View type:</label> - <select id="view-type" class="form-select" v-model="suggestedViewType" aria-label="select a view option" required> - <option disabled value="">Please select type</option> - <option value="num-view">Numerical View</option> - <option value="cat-view">Categorical View</option> - <option value="tabular-view">Tabular View</option> - </select> - <br> - <div v-if="suggestedViewType == 'num-view'"> - <label for="x-label">Label for X-axis:</label> - <select id="x-label" v-model="suggestedX" aria-label="select X coerdinator"> - <option disabled value="">select default X-axis</option> - <option v-for="(value, index) in allLabels" :key="index" :value="value">{{ value }}</option> - </select> - <br> - <label for="y-label">Label for Y-axis:</label> - <select id="y-label" v-model="suggestedY" aria-label="select Y coerdinator"> - <option disabled value="">select default Y-axis</option> - <option v-for="(value, index) in allLabels" :key="index" :value="value">{{ value }}</option> - </select> - </div> - <div v-else-if="suggestedViewType == 'cat-view'"> - <label for="cat-label">Label for Categorical View:</label> - <select id="cat-label" v-model="suggestedX" aria-label="select X coerdinator"> - <option disabled value="">select default categorical label</option> - <option v-for="(value, index) in categoricalLabels" :key="index" :value="value">{{ value }}</option> - </select> - </div> --> - <br> - <input @click="showFeedbackTool = false" class="btn btn-outline-dark mr-2 mb-1" type="button" value="Cancel"> - <input @click="submitFeedbackInput" class="btn btn-primary mb-1" type="button" value="Submit"> - </form> - <div v-else class="text-center m-5"> - Thank you for your feedback! - </div> - </div> - </div> -</template> - -<script> - - // import BarChart from "../../../charts/BarChart"; - // import LineChart from '../../../charts/LineChart.vue'; - // import DoughnutChart from '../../../charts/DoughnutChart.vue'; - // import PieChart from '../../../charts/PieChart.vue'; - import DistributionPreviewSelectHeader from './DistributionPreviewSelectHeader.vue'; - - export default { - name: "DistributionVisualisation", - components: { - // BarChart, - // LineChart, - // DoughnutChart, - // PieChart, - DistributionPreviewSelectHeader, - }, - data() { - return { - // print: false, - jsonData: null, - activeTab: '', - xLabel: null, - allLabels: [], - categoricalLabels: [], - numericalLabels: [], - xList: [], - yList: [], - fromIndex: 1, - toIndex: null, - viewOptions: [ - { value: "Bar", label:"Bar" }, - { value: "Line", label:"Line" }, - ], - showNumTabContent: true, - chartValues: [], - chartLabels: [], - viewType: '', - xPicked: '', - yPicked: '', - yNewPicked: '', - yNewValues: [], - xLabels: [], - yValues: [], - datacollection: {}, - datasets: [], // dataRows, label, backgroundColor - dataRows: [], // only the data arrays - hideXLabels: false, - - catViewOptions: [ - { value: "Bar", label:"Bar" }, - // { value: "Pie", label:"Pie" }, - // { value: "Line", label:"Line" }, - { value: "Doughnut", label:"Doughnut" } - ], - showCatTabContent: true, - nCatToShow: 100, // - catChartValues: [], - catYValues: [], - catViewType: '', - catYPicked: '', // only Y, because X will be automatically generated from the categories of Y - catDatacollection: {}, - catDatasets: [], // dataRows, label, backgroundColor - - categorizedData: [], - skipFactor: 1, - currentPage: 1, - totalPages: 0, - showFeedbackTool: false, - showFeedbackForm: true, - showAlert: false, - options: { - responsive: true, - maintainAspectRatio: true, - scales: { - yAxes: { - beginAtZero: false - }, - x: { - ticks: { - maxTicksLimit: 20, - } - } - }, - plugins: { - legend: { - onClick: (e, legendItem) => { - // this.removeChart(legendItem.text) - }, - display: true, - labels: { - borderRadius: 10, - useBorderRadius: true, - pointStyle: 'circle', - usePointStyle: true, - padding: 20, - } - } - } - }, - // Feedback tool - allChartOptions: ['Line', 'Bar', 'Pie', 'Doughnut'], - suggestedViewType: '', - suggestedX: '', - suggestedY: '', - } - }, - // props: { - // distributionTitle: String - // }, - mounted() { - // Einlesen - fetch("/test_json_6.json") - .then(response => { - const reader = response.body.getReader(); - return new ReadableStream({ - start(controller) { - function push() { - reader.read().then(({ done, value }) => { - if (done) { - controller.close(); - return; - } - controller.enqueue(value); - push(); - }); - } - push(); - } - }); - }) - .then(stream => new Response(stream)) - .then(res => res.json()) - .then(data => (this.jsonData = data)) - .catch(err => console.log(err.message)); - }, - methods: { - showTab(tabName) { - this.activeTab = tabName; - }, - // Once the JSON record is fetched, this method is called to fill all the data() fields with the necessary data - addData() { - this.activeTab = this.activeTab ? this.activeTab : this.jsonData.default_view_options.default_view; - this.allLabels = this.jsonData.labels.all_labels; - this.numericalLabels = this.jsonData.labels.numerical_labels; - if (this.numericalLabels.length == 0) this.showNumTabContent = false; - this.categoricalLabels = this.jsonData.labels.categorical_labels; - if (this.categoricalLabels.length == 0) { - this.showCatTabContent = false; - // this.showXLabels = false; - } - this.xList = this.jsonData.labels.categorical_labels; - this.yList = this.jsonData.labels.numerical_labels; - // this.xPicked = this.xPicked ? this.xPicked : ( localStorage.xPicked ? localStorage.xPicked : this.jsonData.default_axes.x ); - // this.yPicked = this.yPicked ? this.yPicked : ( localStorage.yPicked ? localStorage.yPicked : this.jsonData.default_axes.y ); - // this.viewType = this.viewType ? this.viewType : ( localStorage.viewType ? localStorage.viewType : this.jsonData.default_chart_type ); - this.viewType = this.viewType ? this.viewType : this.jsonData.default_view_options.default_numerical_view; - this.xPicked = this.xPicked ? this.xPicked : this.jsonData.default_view_options.default_axes.numerical_chart.x; - this.yPicked = this.yPicked ? this.yPicked : this.jsonData.default_view_options.default_axes.numerical_chart.y; - - this.catViewType = this.catViewType ? this.catViewType : this.jsonData.default_view_options.default_categorical_view; - this.catYPicked = this.catYPicked ? this.catYPicked : this.jsonData.default_view_options.default_axes.categorical_chart; - this.dataRows = this.jsonData.data; // this line can take time - if (!this.toIndex) this.toIndex = this.dataRows.length < 100 ? this.dataRows.length : 100; - this.categorizedData = this.jsonData.categorized; - this.skipFactor = this.calculateSkipFactor(this.dataRows.length, this.desiredPoints); - }, - fillNumPlotData() { - // In case there are no assigned lables, create empty lables (chart.js needs an array of lables, otherwise empty chart will display) - if (this.xLabels.length === 0 || this.hideXLabels) { - let emptyValues = Array(this.datasets[0].data.length).fill(''); - - this.datacollection = { - labels: emptyValues, - datasets: structuredClone(this.datasets), - }; - } - - else { - this.datacollection = { - labels: this.xLabels, - datasets: structuredClone(this.datasets), - }; - } - - }, - fillCatPlotData() { - this.catChartValues = Object.keys(this.categorizedData.find(e => e.column === this.catYPicked).categories).slice(0, this.nCatToShow) // get array of all category names of yPicked - this.catYValues = Object.values(this.categorizedData.find(e => e.column === this.catYPicked).categories).slice(0, this.nCatToShow) // get array of all category counts of yPicked - this.catDatasets[0] = - { - label: this.catYPicked, - data: this.catYValues, - backgroundColor: this.catYValues.map(() => this.randomColor()), - }, - this.catDatacollection = { - labels: this.catChartValues, - datasets: structuredClone(this.catDatasets).slice(0, this.nCatToShow), - }; - }, - updateRange() { - // console.log('firing updateRange()') - this.datasets[0].data = this.yValues.slice(this.fromIndex-1, this.toIndex); - // for (const dataset of this.datasets) { - // dataset.data = this.yValues.slice(this.fromIndex-1, this.toIndex); - // } - this.xLabels = this.chartLabels.slice(this.fromIndex-1, this.toIndex); - // console.log(this.chartLabels) - this.fillNumPlotData(); - }, - updateYPicked(label){ - // this.yPicked = label - console.log(this.datasets.map(e => e.label)) - if (this.datasets.map(e => e.label).includes(label)) { - console.log('label exists already') - this.removeChart(label) - } - else { - console.log('label doesnt exist') - this.yNewPicked = label - this.addSelectedChart() - } - }, - // TODO: fix bug: changing range after adding a new chart doesn't update data (.slice() will not be rendered) - addSelectedChart() { - // this.chartValues = this.dataRows.map(e => e[this.yNewPicked]) - this.chartValues = this.dataRows.map(e => e[this.yNewPicked]) - this.yNewValues = this.chartValues; - const randomColor = this.randomColor(); - this.datasets.push( - { - label: this.yNewPicked, - data: this.yNewValues.slice(this.fromIndex-1, this.toIndex), - backgroundColor: randomColor, - borderColor: randomColor, - borderWidth: 1, - }, - ) - this.fillNumPlotData(); - }, - removeChart(label){ - console.log(label) - this.datasets = this.datasets.filter(o => o.label !== label) - console.log('datasets after', this.datasets) - this.fillNumPlotData(); - }, - randomColor() { - const letters = '0123456789ABCDEF'; - let color = '#'; - for (let i = 0; i < 6; i++) { - color += letters[Math.floor(Math.random() * 16)]; - } - return color; - }, - submitFeedbackInput() { - // TODO implement feedback-tool logic - // if(this.suggestedViewType) localStorage.viewType = this.suggestedViewType; - // if(this.suggestedX) localStorage.xPicked = this.suggestedX; - // if(this.suggestedY) localStorage.yPicked = this.suggestedY; - localStorage.viewType = ''; - localStorage.xPicked = ''; - localStorage.yPicked = ''; - console.log('Selected View Type: ', this.suggestedViewType); - this.showFeedbackForm = false; - - // TODO: only if successful - // this.showAlert = true; - }, - calculateSkipFactor(totalPoints) { - const desiredPoints = 50; - if (totalPoints <= desiredPoints) { - return 1; // No downsampling needed - } - - const skipFactor = Math.ceil(totalPoints / desiredPoints); - return skipFactor; - }, - // downsampleData(data, skipFactor) { - // return data.filter((point, index) => index % skipFactor === 0); - // }, - // testLogger(smth) { - // console.log("something: ", smth) - // } - }, - watch: { - chartData() { - this.$data._chart.update(); - }, - xPicked() { - this.$emit("input", this.xPicked); - this.chartLabels = this.dataRows.map(e => e[this.xPicked]).slice(this.fromIndex-1, this.toIndex) - this.xLabels = this.chartLabels; - this.fillNumPlotData(); - }, - yPicked() { - this.$emit("input", this.yPicked); - this.chartValues = this.dataRows.map(e => e[this.yPicked]).slice(this.fromIndex-1, this.toIndex) - this.yValues = this.chartValues; - const randomColor = this.randomColor(); - this.datasets[0] = - { - label: this.yPicked, - data: this.yValues, - backgroundColor: randomColor, - borderColor: randomColor, - borderWidth: 1, - }, - - this.fillNumPlotData(); - }, - hideXLabels() { - this.fillNumPlotData(); - }, - catViewType() { - if (this.catViewType === 'Pie') this.nCatToShow = 20; - else if (this.catViewType === 'Bar') this.nCatToShow = 50; - else if (this.catViewType === 'Line') this.nCatToShow = 100; - // this.$emit("input", this.catYPicked); - this.fillCatPlotData(); - }, - catYPicked() { - this.$emit("input", this.catYPicked); - this.fillCatPlotData(); - }, - } - } - -</script> - -<style lang="scss" scoped> - - .preview-container { - /* padding: 3px; */ - /* border-bottom: 1px solid gray; - border-top: 1px solid gray; */ - max-width: 100%; - overflow: auto; - } - - .preview-container .card-header { - background-color: transparent; - } - - .preview-container .card { - border: none; - } - - .dv-nav-tabs li .nav-link{ - color: inherit; - &.active { - // background-color: oldlace; - font-weight: bold; - color: var(--primary-light); - border-top: 4px solid var(--primary-light); - } - &:hover:not(.active) { - border-color: transparent; - } - } - - .dv-add-btn { - height: 48px; - align-self: end; - background-color: var(--primary-light); - color: white; - - &:hover { - color: white; - } - } - - .dv-records-range-container input { - text-align: center; - width: 50px; - } - - .dv-records-range-container-record-number { - background-color: #f0efed; - border-radius: 5px; - padding: 10px 15px; - font-weight: 650; - } - - .dv-records-range-container small label { - padding: 0 5px; - } - - select { - margin: 5px 2px; - width: 200px - } - - .table-wrapper { - overflow-x: auto; - } - - .dist-table { - margin: 15px 0; - /* margin-right: 15px; */ - } - - td, th { - border: 1px solid #dfe2e5; - padding: 0.6em 1em; - } - - tr:nth-child(2n) { - background-color: #f6f8fa; - } - - .vt-sort:before{ - font-family: FontAwesome; - padding-right: 0.5em; - width: 1.28571429em; - display: inline-block; - text-align: center; - } - - .vt-sortable:before{ - content: "\f0dc"; - } - - .vt-asc:before{ - content: "\f160"; - } - - .vt-desc:before{ - content: "\f161"; - } - - .feedback-tool-wrapper { - padding: 5px 15px; - margin: 15px 0; - background-color: #f6f8fa; - border: 1px solid #f6f8fa; - border-radius: 5px; - } -</style> \ No newline at end of file diff --git a/packages/piveau-hub-ui-modules/lib/datasetDetails/features/DatasetDetailsSubject.vue b/packages/piveau-hub-ui-modules/lib/datasetDetails/features/DatasetDetailsSubject.vue index 70ae3fd8c2cd91bada730b391035a574b98415c8..0df1073896c54fba26fcfb8ed297e8098176c61b 100644 --- a/packages/piveau-hub-ui-modules/lib/datasetDetails/features/DatasetDetailsSubject.vue +++ b/packages/piveau-hub-ui-modules/lib/datasetDetails/features/DatasetDetailsSubject.vue @@ -1,172 +1,153 @@ <template> - <div class="row mt-4 flex-column dsd-item dsd-keywords"> - <span v-if="showTitle" class="mb-4 h-4 font-weight-bold">Subjects ({{ displayedSubjects.length }})</span> - <div class="keywords__item row"> - - + <div class="mt-2 flex-column dsd-feature"> + <div> + <dataset-details-feature-header + :title="`EuroVoc Descriptors (${ + getSubject ? getSubject.length.toLocaleString('fi') : 0 + })`" + :arrowDown="!expanded" + tag="keywords-toggle" + :onClick="toggleExpanded" + class="ml-0" + /> + </div> + <div v-if="expanded" class="keywords__item row mt-4"> <span - v-for='(subject, i) in displayedSubjects' + v-for="(subject, i) in displayedSubjects" :key="i" class="col-6 col-sm-3 col-md-2 mt-md-0 mt-3 mb-2 mx-0 px-1" > - - - - <app-link :to="getSubjectLink(subject)"> - <small v-if="typeof subject.title === 'object'" class="d-inline-block w-100 py-2 rounded-pill text-center text-white subject"> - <span v-for="(value, key) in subject.title" :key="key"> - - <tooltip :title="value" data-placement="top"> - {{ truncate(value, maxSubjectLength, false) }} - </tooltip> - - </span> - </small> - - <small v-else-if="typeof subject.title === 'string'" class="d-inline-block w-100 py-2 rounded-pill text-center text-white subject"> - <span> - <tooltip :title="subject.title" data-placement="top"> - {{ truncate(subject.title, maxSubjectLength, false) }} - </tooltip> - </span> - </small> - </app-link> + <app-link :to="getSubjectLink(subject)"> + <small + v-if="typeof subject.title === 'object'" + class="d-inline-block w-100 py-2 rounded-pill text-center text-white subject" + > + <span v-for="(value, key) in subject.title" :key="key"> + <tooltip :title="value" data-placement="top"> + {{ truncate(value, maxSubjectLength, false) }} + </tooltip> + </span> + </small> + <small + v-else-if="typeof subject.title === 'string'" + class="d-inline-block w-100 py-2 rounded-pill text-center text-white subject" + > + <span> + <tooltip :title="subject.title" data-placement="top"> + {{ truncate(subject.title, maxSubjectLength, false) }} + </tooltip> + </span> + </small> + </app-link> </span> </div> - <div> + <div v-if="expanded && displayedSubjects.length > defaultDisplayCount"> <pv-show-more - v-if="displayedSubjects.length > defaultDisplayCount" - :label="isSubjectAllDisplayed? $t('message.metadata.showLess') : $t('message.metadata.showMore')" + :label="isSubjectAllDisplayed ? $t('message.metadata.showLess') : $t('message.metadata.showMore')" :upArrow="isSubjectAllDisplayed" :action="toggleDisplayCount" class="row text-primary" /> </div> -<!-- <div>--> -<!-- <div--> -<!-- v-if="!subject.displayAll && !isSubjectAllDisplayed"--> -<!-- class="keywords__item"--> -<!-- >--> -<!-- <div--> -<!-- class="keywords__actions pb-md-3"--> -<!-- >--> -<!-- <button--> -<!-- v-for="increment in subject.incrementSteps.filter(nonOverflowingIncrementsForSubject)"--> -<!-- :key="increment"--> -<!-- class="btn btn-sm btn-secondary mr-1"--> -<!-- @click="increaseNumDisplayedSubject(increment)"--> -<!-- >--> -<!-- <i class="fas fa-chevron-down"/> {{ $t('message.metadata.showXMore', { increment }) }}--> -<!-- </button>--> -<!-- <button--> -<!-- class="btn btn-sm btn-primary"--> -<!-- @click="subject.displayCount = getSubject.length"--> -<!-- >--> -<!-- <i class="fas fa-eye"/> {{ $t('message.metadata.showAll') }} {{ getSubject.length.toLocaleString('fi') }}--> -<!-- </button>--> -<!-- </div>--> -<!-- </div>--> -<!-- </div>--> </div> </template> <script> -import {truncate} from "../../utils/helpers"; -import {has} from "lodash"; -import {mapGetters} from "vuex"; +import { truncate } from "../../utils/helpers"; +import { has } from "lodash"; +import { mapGetters } from "vuex"; import Tooltip from "../../widgets/Tooltip"; import AppLink from "../../widgets/AppLink"; -import {sortAlphabetically} from "./utils/sortAlphabetically"; +import { sortAlphabetically } from "./utils/sortAlphabetically"; +import $ from "jquery"; export default { name: "DatasetDetailsSubject", - components: {Tooltip, AppLink}, + components: { Tooltip, AppLink }, data() { return { showTitle: this.$env.content.datasetDetails.keywords.showTitle, maxSubjectLength: this.$env.content.datasets.maxKeywordLength, defaultDisplayCount: 24, followSubjectLinks: this.$env.content.datasets.followSubjectLinks, + expanded: false, // Toggle state subject: { displayAll: false, - displayCount: 24, // Should never exceed number of subjects + displayCount: 24, incrementSteps: [12, 60], - } + }, }; }, computed: { - ...mapGetters('datasetDetails', [ - 'getSubject' - ]), + ...mapGetters("datasetDetails", ["getSubject"]), displayedSubjects() { const subjects = this.subject.displayAll ? Object.freeze(this.sortedSubject) : Object.freeze(this.sortedSubject.slice(0, this.subject.displayCount)); - return subjects.filter(subject => subject.title); + return subjects.filter((subject) => subject.title); }, isSubjectAllDisplayed() { return this.subject.displayCount >= this.displayedSubjects.length; }, sortedSubject() { - let selected = [], fallback = [], other = {}, without = []; // eslint-disable-line + let selected = []; const selectedLanguage = this.$route.query.locale; - // Sort by language this.getSubject.forEach((element) => { - const e = element; - if (has(element, 'title') && element.title !== null) { - if (typeof element.title === 'object') { + if (has(element, "title") && element.title !== null) { + if (typeof element.title === "object") { Object.keys(element.title).forEach((key) => { if (key !== selectedLanguage) { - delete e.title[key]; + delete element.title[key]; } }); } selected.push(element); } }); - this.sortAlphabetically(selected, 'title', selectedLanguage); + this.sortAlphabetically(selected, "title", selectedLanguage); return selected; - } + }, }, methods: { truncate, sortAlphabetically, - increaseNumDisplayedSubject(increment) { - this.subject.displayCount = this.clamp(this.subject.displayCount - + increment, 0, this.getSubject.length); - }, - nonOverflowingIncrementsForSubject(incrementStep) { - return this.subject.displayCount + incrementStep <= this.getSubject.length; + toggleDisplayCount() { + $('body').tooltip({ selector: '[data-toggle="tooltip"]' }); + if (this.subject.displayCount < this.getSubject.length) { + this.subject.displayCount = this.getSubject.length; + } else { + this.subject.displayCount = this.defaultDisplayCount; + } + this.subject.displayAll = !this.subject.displayAll; }, - clamp(n, min, max) { - return Math.min(Math.max(n, min), max); + toggleExpanded() { + this.expanded = !this.expanded; // Toggle visibility }, getSubjectLink(subject) { - const urlResource = subject.id - - - return { + const urlResource = subject.id; + return { path: `/datasets`, - query: Object.assign({}, { locale: this.$route.query.locale, subject:urlResource }), + query: { + locale: this.$route.query.locale, + subject: urlResource, + }, }; - - - }, - toggleDisplayCount() { - if (this.keywords.displayCount < this.displayedSubjects.length) { - this.keywords.displayCount = this.displayedSubjects.length; - } else { - this.keywords.displayCount = this.defaultDisplayCount; - } - } - } -} + }, +}; </script> <style scoped lang="scss"> .subject { background-color: #196fd2; } + +.keywords-header { + justify-content: flex-start; /* Aligned header to the left */ +} + +.keywords-header .keywords-toggle { + margin-left: 0; /* The toggle header is on the left */ +} </style> diff --git a/packages/piveau-hub-ui-modules/lib/datasetDetails/header/DatasetDetailsHeaderTitle.vue b/packages/piveau-hub-ui-modules/lib/datasetDetails/header/DatasetDetailsHeaderTitle.vue index 5cf085ebd6f9c515290c817cd16ea68fcda3dc35..006ac736ee805dda03f0cca0e4bc835bcf80ba54 100644 --- a/packages/piveau-hub-ui-modules/lib/datasetDetails/header/DatasetDetailsHeaderTitle.vue +++ b/packages/piveau-hub-ui-modules/lib/datasetDetails/header/DatasetDetailsHeaderTitle.vue @@ -9,7 +9,7 @@ </span> </div> <h1 v-if="getTitle && !embed" class="d-none d-lg-block dataset-details-title" data-cy="dataset-title">{{ getTranslationFor(getTitle, $route.query.locale, getLanguages) }}</h1> - <h1 v-if="getTitle && embed" class="d-lg-block dataset-details-title" data-cy="dataset-title">{{ getTranslationFor(getTitle, $route.query.locale, getLanguages) }} </h1> + <h1 v-else-if="getTitle && embed" class="d-lg-block dataset-details-title" data-cy="dataset-title">{{ getTranslationFor(getTitle, $route.query.locale, getLanguages) }} </h1> <h1 v-else class="d-none d-lg-block dataset-details-title" data-cy="dataset-title">{{ getID }}</h1> </div> </template> diff --git a/packages/piveau-hub-ui-modules/lib/datasetDetails/navigation/DatasetDetailsNavigationLinks.vue b/packages/piveau-hub-ui-modules/lib/datasetDetails/navigation/DatasetDetailsNavigationLinks.vue index 67508ca64c80b19a5700de33350a07f0bea38329..347f5aa1e7b36f5f720059a9e6d9da9e64e11747 100644 --- a/packages/piveau-hub-ui-modules/lib/datasetDetails/navigation/DatasetDetailsNavigationLinks.vue +++ b/packages/piveau-hub-ui-modules/lib/datasetDetails/navigation/DatasetDetailsNavigationLinks.vue @@ -16,52 +16,35 @@ <dataset-details-share-button class="dropdown-item" :to="`https://www.linkedin.com/shareArticle?mini=true&url=${url}`" :icon="{ prefix: 'fab', iconName: 'linkedin-in' }"></dataset-details-share-button> </div> </div> - <div class="d-inline dsd-link-feed"> - <app-link class="nav-item nav-link text-nowrap" - :to="getFeedLink()" - target="_blank" - matomo-track-page-view - role="button"> - <span :title="$t('message.tooltip.datasetDetails.datasetFeed')" data-toggle="tooltip" data-placement="top"> {{ $t('message.datasetDetails.datasetFeed') }} + + <!-- API link with True/False--> + <div v-if="this.$env.content.datasetDetails.embed.enable" class="d-inline dropdown dsd-link-cite"> + <button + @click="openApiCall()" + class="nav-item nav-link text-nowrap api-button" + :class="{ + 'disabled': getLoading, + }" + fragment="#" + role="button" + id="citationDropdown" + data-toggle="dropdown" + aria-haspopup="true" + aria-expanded="false" + data-cy="citation-dropdown-expand"> + + <span class="api-link" title="Open API request of this Dataset." data-toggle="tooltip" data-placement="top"> + API + <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" fill="currentColor" class="bi bi-box-arrow-up-right" viewBox="0 0 16 16"> + <path fill-rule="evenodd" d="M8.636 3.5a.5.5 0 0 0-.5-.5H1.5A1.5 1.5 0 0 0 0 4.5v10A1.5 1.5 0 0 0 1.5 16h10a1.5 1.5 0 0 0 1.5-1.5V7.864a.5.5 0 0 0-1 0V14.5a.5.5 0 0 1-.5.5h-10a.5.5 0 0 1-.5-.5v-10a.5.5 0 0 1 .5-.5h6.636a.5.5 0 0 0 .5-.5"/> + <path fill-rule="evenodd" d="M16 .5a.5.5 0 0 0-.5-.5h-5a.5.5 0 0 0 0 1h3.793L6.146 9.146a.5.5 0 1 0 .708.708L15 1.707V5.5a.5.5 0 0 0 1 0z"/> + </svg> </span> - </app-link> - </div> - <div class="d-inline dropdown dsd-link-linked-data"> - <app-link class="nav-item nav-link dropdown-toggle text-nowrap" fragment="#" role="button" id="linkedDataDropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> - <!--<i class="material-icons small-icon align-bottom text-dark">***FIND A LINKED DATA ICON***</i>--> - <span :title="$t('message.tooltip.datasetDetails.linkedData')" - data-toggle="tooltip" - data-placement="top"> - {{ $t('message.metadata.linkedData') }} - </span> - </app-link> - <div class="dropdown-menu" aria-labelledby="linkedDataDropdownMenuLink"> - <resource-details-linked-data-button class="dropdown-item" format="rdf" text="RDF/XML" resources="datasets" v-bind:resources-id="datasetId"></resource-details-linked-data-button> - <resource-details-linked-data-button class="dropdown-item" format="ttl" text="Turtle" resources="datasets" v-bind:resources-id="datasetId"></resource-details-linked-data-button> - <resource-details-linked-data-button class="dropdown-item" format="n3" text="Notation3" resources="datasets" v-bind:resources-id="datasetId"></resource-details-linked-data-button> - <resource-details-linked-data-button class="dropdown-item" format="nt" text="N-Triples" resources="datasets" v-bind:resources-id="datasetId"></resource-details-linked-data-button> - <resource-details-linked-data-button class="dropdown-item" format="jsonld" text="JSON-LD" resources="datasets" v-bind:resources-id="datasetId"></resource-details-linked-data-button> - </div> - </div> - <div class="d-inline dropdown dsd-link-dqv" v-if="showDQV"> - <app-link class="nav-item nav-link dropdown-toggle text-nowrap" - :class="{'disabled': !(getIsDQVDataRDFAvailable || getIsDQVDataTTLAvailable || getIsDQVDataN3Available || getIsDQVDataNTAvailable || getIsDQVDataJSONLDAvailable)}" - fragment="#" role="button" id="metaDataDropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> - <!--<i class="material-icons small-icon align-bottom text-dark">***FIND A LINKED DATA ICON***</i>--> - <span :title="$t('message.tooltip.datasetDetails.dqvData')" - data-toggle="tooltip" - data-placement="top"> - DQV Data - </span> - </app-link> - <div class="dropdown-menu dropdown-menu-right" aria-labelledby="metaDataDropdownMenuLink"> - <dataset-details-linked-metrics-button class="dropdown-item" :class="{'disabled': !getIsDQVDataRDFAvailable}" format="rdf" text="RDF/XML" v-bind:dataset-id="datasetId"></dataset-details-linked-metrics-button> - <dataset-details-linked-metrics-button class="dropdown-item" :class="{'disabled': !getIsDQVDataTTLAvailable}" format="ttl" text="Turtle" v-bind:dataset-id="datasetId"></dataset-details-linked-metrics-button> - <dataset-details-linked-metrics-button class="dropdown-item" :class="{'disabled': !getIsDQVDataN3Available}" format="n3" text="Notation3" v-bind:dataset-id="datasetId"></dataset-details-linked-metrics-button> - <dataset-details-linked-metrics-button class="dropdown-item" :class="{'disabled': !getIsDQVDataNTAvailable}" format="nt" text="N-Triples" v-bind:dataset-id="datasetId"></dataset-details-linked-metrics-button> - <dataset-details-linked-metrics-button class="dropdown-item" :class="{'disabled': !getIsDQVDataJSONLDAvailable}" format="jsonld" text="JSON-LD" v-bind:dataset-id="datasetId"></dataset-details-linked-metrics-button> - </div> + </button> + </div> + + <!-- Cite link --> <div class="d-inline dropdown dsd-link-cite"> <app-link class="nav-item nav-link dropdown-toggle text-nowrap" @@ -96,9 +79,30 @@ </button> </div> </div> + + <!-- Linked Data link--> + <div class="d-inline dropdown dsd-link-linked-data"> + <app-link class="nav-item nav-link dropdown-toggle text-nowrap" fragment="#" role="button" id="linkedDataDropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> + <!--<i class="material-icons small-icon align-bottom text-dark">***FIND A LINKED DATA ICON***</i>--> + <span :title="$t('message.tooltip.datasetDetails.linkedData')" + data-toggle="tooltip" + data-placement="top"> + {{ $t('message.metadata.linkedData') }} + </span> + </app-link> + <div class="dropdown-menu" aria-labelledby="linkedDataDropdownMenuLink"> + <resource-details-linked-data-button class="dropdown-item" format="rdf" text="RDF/XML" resources="datasets" v-bind:resources-id="datasetId"></resource-details-linked-data-button> + <resource-details-linked-data-button class="dropdown-item" format="ttl" text="Turtle" resources="datasets" v-bind:resources-id="datasetId"></resource-details-linked-data-button> + <resource-details-linked-data-button class="dropdown-item" format="n3" text="Notation3" resources="datasets" v-bind:resources-id="datasetId"></resource-details-linked-data-button> + <resource-details-linked-data-button class="dropdown-item" format="nt" text="N-Triples" resources="datasets" v-bind:resources-id="datasetId"></resource-details-linked-data-button> + <resource-details-linked-data-button class="dropdown-item" format="jsonld" text="JSON-LD" resources="datasets" v-bind:resources-id="datasetId"></resource-details-linked-data-button> + </div> + </div> + + <!-- Embed link with True/False--> <div v-if="this.$env.content.datasetDetails.embed.enable" class="d-inline dropdown dsd-link-cite"> <button type="button" data-toggle="modal" data-target="#embedModal" class="nav-item nav-link text-nowrap bg-none border-0" fragment="#" role="button" id="" style="background:none;"> - <span title="Embed" + <span title="Copy link to embed the dataset in other pages" data-toggle="tooltip" data-placement="top" > @@ -106,6 +110,39 @@ </span> </button> </div> + + <!-- DQV link with True/False--> + <div class="d-inline dropdown dsd-link-dqv" v-if="showDQV"> + <app-link class="nav-item nav-link dropdown-toggle text-nowrap" + :class="{'disabled': !(getIsDQVDataRDFAvailable || getIsDQVDataTTLAvailable || getIsDQVDataN3Available || getIsDQVDataNTAvailable || getIsDQVDataJSONLDAvailable)}" + fragment="#" role="button" id="metaDataDropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> + <!--<i class="material-icons small-icon align-bottom text-dark">***FIND A LINKED DATA ICON***</i>--> + <span :title="$t('message.tooltip.datasetDetails.dqvData')" + data-toggle="tooltip" + data-placement="top"> + DQV Data + </span> + </app-link> + <div class="dropdown-menu dropdown-menu-right" aria-labelledby="metaDataDropdownMenuLink"> + <dataset-details-linked-metrics-button class="dropdown-item" :class="{'disabled': !getIsDQVDataRDFAvailable}" format="rdf" text="RDF/XML" v-bind:dataset-id="datasetId"></dataset-details-linked-metrics-button> + <dataset-details-linked-metrics-button class="dropdown-item" :class="{'disabled': !getIsDQVDataTTLAvailable}" format="ttl" text="Turtle" v-bind:dataset-id="datasetId"></dataset-details-linked-metrics-button> + <dataset-details-linked-metrics-button class="dropdown-item" :class="{'disabled': !getIsDQVDataN3Available}" format="n3" text="Notation3" v-bind:dataset-id="datasetId"></dataset-details-linked-metrics-button> + <dataset-details-linked-metrics-button class="dropdown-item" :class="{'disabled': !getIsDQVDataNTAvailable}" format="nt" text="N-Triples" v-bind:dataset-id="datasetId"></dataset-details-linked-metrics-button> + <dataset-details-linked-metrics-button class="dropdown-item" :class="{'disabled': !getIsDQVDataJSONLDAvailable}" format="jsonld" text="JSON-LD" v-bind:dataset-id="datasetId"></dataset-details-linked-metrics-button> + </div> + </div> + + <!-- Dataset Feed link --> + <div class="d-inline dsd-link-feed"> + <app-link class="nav-item nav-link text-nowrap" + :to="getFeedLink()" + target="_blank" + matomo-track-page-view + role="button"> + <span :title="$t('message.tooltip.datasetDetails.datasetFeed')" data-toggle="tooltip" data-placement="top"> {{ $t('message.datasetDetails.datasetFeed') }} + </span> + </app-link> + </div> <hr /> </ul> <dataset-citation-modal @@ -191,6 +228,9 @@ export default { modal() { $('#citationModal').modal({ show: true }); }, + openApiCall() { + window.open(this.baseUrl + "datasets/" + this.getID, "_blank"); + } }, mounted() {}, setup() { @@ -209,4 +249,19 @@ export default { .nav-link { text-decoration: underline; } + +.api-link svg { + margin-bottom: 4px; +} + +.api-button { + all: unset; + cursor: pointer; + padding: 0px; + margin-top: 8px; +} + +.menu .nav-link:focus { + border: none !important; +} </style> diff --git a/packages/piveau-hub-ui-modules/lib/datasetDetails/properties/DatasetDetailsProperties.vue b/packages/piveau-hub-ui-modules/lib/datasetDetails/properties/DatasetDetailsProperties.vue index 994ef3614e2ebf2cfa8a5a6a7a556852f2a0fc4f..6deeee4ef995650036b0b63c4505eccb8502ee29 100644 --- a/packages/piveau-hub-ui-modules/lib/datasetDetails/properties/DatasetDetailsProperties.vue +++ b/packages/piveau-hub-ui-modules/lib/datasetDetails/properties/DatasetDetailsProperties.vue @@ -1,28 +1,34 @@ <template> <div class="mt-5 dsd-properties"> <div class="col-12 mb-2 p-0 dsd-properties-list"> - <dataset-details-feature-header - :title="$t('message.datasetDetails.additionalInfo')" - :arrowDown="!infoVisible" - tag="additional-information-toggle" - :onClick="toggleInfo" - /> + <slot name="property-header"> + <dataset-details-feature-header + :title="$t('message.datasetDetails.additionalInfo')" + :arrowDown="!infoVisible" + tag="additional-information-toggle" + :onClick="toggleInfo" + /> + </slot> <div class="position-relative dsd-item additional-information" data-cy="additional-information" v-show="infoVisible"> - <table class="table table-borderless table-responsive" ref="dsdProperties" role="tablist" id="myTab"> - <dataset-details-property v-for='(name, index) in fieldsArray' - :name="name" - :translate="fieldSchema[name]?.translate" - :type="fieldSchema[name]?.type" - :propertyFields="fieldSchema[name]?.fields" - :track="fieldSchema[name]?.track" - :itemstyles="fieldSchema[name]?.itemstyles" - :preTransform="fieldSchema[name]?.preTransform" - :transform="fieldSchema[name]?.transform" - :key="index"/> - </table> + <slot name="property-table-before"></slot> + <slot name="property-table"> + <table class="table table-borderless table-responsive" ref="dsdProperties" role="tablist" id="myTab"> + <dataset-details-property v-for='(name, index) in fieldsArray' + :name="name" + :translate="fieldSchema[name]?.translate" + :type="fieldSchema[name]?.type" + :propertyFields="fieldSchema[name]?.fields" + :track="fieldSchema[name]?.track" + :itemstyles="fieldSchema[name]?.itemstyles" + :preTransform="fieldSchema[name]?.preTransform" + :transform="fieldSchema[name]?.transform" + :key="index"/> + </table> + </slot> <div class="additional-information-overlay" ref="overlay" v-show="showMoreVisible && !expanded"></div> + <slot name="property-table-after"></slot> </div> </div> <pv-show-more diff --git a/packages/piveau-hub-ui-modules/lib/datasetDetails/properties/DatasetDetailsProperty.vue b/packages/piveau-hub-ui-modules/lib/datasetDetails/properties/DatasetDetailsProperty.vue index 26cff6a90fb9a3f791593769832020b7855ffc29..19f632134f9d08e8e080c34cf85a0b2d3a1d6c4d 100644 --- a/packages/piveau-hub-ui-modules/lib/datasetDetails/properties/DatasetDetailsProperty.vue +++ b/packages/piveau-hub-ui-modules/lib/datasetDetails/properties/DatasetDetailsProperty.vue @@ -5,7 +5,7 @@ {{ labelDisplay[1] }} </tooltip> </td> - <td v-if="['string', 'date', 'first:number', 'translation'].includes(type)">{{ value }}</td> + <td v-if="['string', 'date', 'number', 'first:number', 'translation'].includes(type)">{{ value }}</td> <td v-if="type==='uri'"><a :href='value'>{{ value }}</a></td> <td v-if="type==='links'"> <div v-for="(v, i) of value" :key="i"> diff --git a/packages/piveau-hub-ui-modules/lib/datasetDetails/properties/specification.ts b/packages/piveau-hub-ui-modules/lib/datasetDetails/properties/specification.ts index 06d572bf4f76793fcf8a511b787d42b7684320cd..9a3634db5d2cbad8a16638c0e4262cf0fbebe97a 100644 --- a/packages/piveau-hub-ui-modules/lib/datasetDetails/properties/specification.ts +++ b/packages/piveau-hub-ui-modules/lib/datasetDetails/properties/specification.ts @@ -66,7 +66,7 @@ export const dcatSchema = (t) => ({ sources: {type: 'links', translate:',sources'}, languages: {type: 'links', translate: 'language,languages'}, publisher: {type: 'object', fields: 'name,email,homepage'}, - contactPoints: {type: 'objects', translate:"/message.tooltip.contactPoints,contactPoints", fields: "name::/message.datasetDetails.contactPoints.organizationName,email,telephone,address,url::'URL'"}, + contactPoints: {type: 'objects', translate:"/message.tooltip.contactPoints,contactPoints", fields: "name::/message.metadata.name,organisation_name::/message.datasetDetails.contactPoints.organizationName, email,telephone,address,url:link"}, catalogRecord: {type: 'object', translate:"/message.tooltip.catalogRecord,catalogRecord", fields: "issued:date:addedToDataEuropaEU,modified:date:updatedOnDataEuropaEU,homepage"}, spatial: {type: 'objects', fields: "coordinates,type"}, spatialResource: {type: 'objects', translate:",spatialResource", fields: "resource:link"}, @@ -75,7 +75,7 @@ export const dcatSchema = (t) => ({ // relatedResources: {type: 'links', translate: 'relatedResource,relatedResources'}, identifiers: {type: 'links', translate: 'identifier,identifiers'}, otherIdentifiers: {type: 'objects', translate: 'otherIdentifier,otherIdentifiers', fields:"identifier:link,resource:link:identifier,scheme:link"}, - resource: {type: 'uri', translate: ",'uriRef'"}, + resource: {type: 'uri', translate: ",'uriRef:'"}, frequency: {type: 'object', fields:"title,resource:link"}, accessRights: {type: 'object', translate:"/message.tooltip.datasetDetails.distributions.rights,accessRights", fields:"label::"}, accrualPeriodicity: {type: 'object', translate:"frequency,accrualPeriodicity", fields:"label::"}, @@ -96,8 +96,8 @@ export const dcatSchema = (t) => ({ qualifiedRelations: {type: 'objects', translate: "qualifiedRelation", fields: "relation:link,had_role:link:role"}, sample: {type: 'links'}, spatialResolutionInMeters: { - type: 'first:number', translate: "spatialResolutionInMeters,spatialResolutionInMeters.label", - transform: value => t('message.metadata.spatialResolutionInMeters.value', {number: value[0]}) + type: 'number', translate: "spatialResolutionInMeters,spatialResolutionInMeters.label", + transform: value => t('message.metadata.spatialResolutionInMeters.value', {number: value}) }, type: {type: 'object', fields: "label,resource:link"}, temporalResolution: {type: 'string'} diff --git a/packages/piveau-hub-ui-modules/lib/datasets/Datasets.vue b/packages/piveau-hub-ui-modules/lib/datasets/Datasets.vue index 2ab32a7709391d18c0cb92bbbf2819f40631d362..80e86e3f40e7e2c782412a63220bec7ebe012423 100644 --- a/packages/piveau-hub-ui-modules/lib/datasets/Datasets.vue +++ b/packages/piveau-hub-ui-modules/lib/datasets/Datasets.vue @@ -27,7 +27,6 @@ <button class="btn btn-primary mb-3 text-right text-white" data-toggle="collapse" data-target="#datasetFacets" data-cy="btn-filter-toggle" @click="filterCollapsed = !filterCollapsed"> - {{ $t('message.datasetFacets.title') }} <i class="material-icons small-icon align-bottom" v-if="filterCollapsed">arrow_drop_up</i> <i class="material-icons small-icon align-bottom" v-else>arrow_drop_down</i> </button> @@ -38,6 +37,12 @@ :dataScope="dataScope" :available-facets="getAllAvailableFacets" ></datasets-facets> +<!-- <facets--> +<!-- :facetsConfig="getAllAvailableFacets"--> +<!-- :facetsIds="$env.content.datasets.facets.defaultFacetOrder"--> +<!-- class="col-md-3 col-12 mb-3 mb-md-0 px-0 collapse"--> +<!-- id="datasetFacets">--> +<!-- </facets>--> <section class="col-md-9 col-12"> <slot name="datasets-filters"> <datasets-filters/> @@ -117,7 +122,6 @@ } from 'lodash-es'; import $ from 'jquery'; import fileTypes from '../utils/fileTypes'; - import DatasetsFacets from './datasetsFacets/DatasetsFacets.vue'; import Pagination from '../widgets/Pagination.vue'; import SelectedFacetsOverview from '../facets/SelectedFacetsOverview'; import AppLink from '../widgets/AppLink.vue'; @@ -126,7 +130,8 @@ import DatasetsFilters from "../datasets/DatasetsFilters.vue"; import DatasetList from './DatasetList.vue' import { useDatasetsHead } from '../composables/head' - + // import Facets from "../facets_2.0/Facets.vue" + import DatasetsFacets from './datasetsFacets/DatasetsFacets.vue'; export default { name: 'Datasets', components: { @@ -137,6 +142,7 @@ export default { datasetsFacets: DatasetsFacets, pagination: Pagination, DatasetList, + // Facets }, props: { infiniteScrolling: { @@ -223,7 +229,6 @@ export default { return facetObjWithMaybeDefaultSuperCatalog; }, - getAllAvailableFacets() { return this.showCatalogDetails ? this.getAllAvailableFacetsOriginal.filter(facet => facet.id !== 'catalog') diff --git a/packages/piveau-hub-ui-modules/lib/embed/DatasetEmbedModal.vue b/packages/piveau-hub-ui-modules/lib/embed/DatasetEmbedModal.vue index 827759fbc823a65b0b667ffd99bb71cab4aded8f..e2772ca1e50ddbd17d349e87f62257d11d6e9375 100644 --- a/packages/piveau-hub-ui-modules/lib/embed/DatasetEmbedModal.vue +++ b/packages/piveau-hub-ui-modules/lib/embed/DatasetEmbedModal.vue @@ -19,7 +19,8 @@ <div class="input-wrapper"> <label for="iframeWidth">{{ $t('message.datasetDetails.datasets.modal.width') }}</label><br> <div class="input-with-px"> - <input + <input v-if="!isResponsive" + type="number" id="iframeWidth" v-model="iframeWidth" @@ -27,6 +28,16 @@ :min="minRange" :max="maxRange" /> + + <input v-if="isResponsive" + class="disable-width" + type="text" + id="iframeWidth" + v-model="iframeWidth" + @input="updateEmbedCode" + :min="minRange" + :max="maxRange" + /> </div> </div> <div class="input-wrapper height" style="padding-left: 3.3rem"> @@ -44,10 +55,18 @@ </div> </div> </div> + + <div class="form-check mb-4"> + <input class="form-check-input" type="checkbox" v-model="isResponsive" @change="updateEmbedCode"> + <label class="form-check-label" for="responsive"> + Responsive + </label> + </div> + <div class="ecl-form-group"> <label class="ecl-form-label" for="ebmedTextarea" style="font-size:18px; font-weight: 400; color: #000000;">{{ $t('message.datasetDetails.datasets.modal.code')}}</label> <textarea id="ebmedTextarea" style="width: 100%; resize: none; padding: 20px 25px 0px 20px; overflow: hidden; font-size:18px; line-height: 1.1; color:#888888; margin-top: 1.375rem; word-break: break-all;float: left!important;" - class="ecl-text-area ecl-text-area--m" rows="4" :value="embedCode" readonly></textarea> + class="ecl-text-area ecl-text-area--m" rows="4" :value="embedCode" @click="selectAll" ></textarea> </div> </div> @@ -62,10 +81,11 @@ <label class="form-check-label" for="exampleRadios2"> Snippet Dataset </label> - </div> - <div class="modal-footer justify-content-start"> + </div> + <div class="modal-footer justify-content-start"> <button @click="copy" class="ecl-button ecl-button--secondary px-4 py-3" style="float: left!important;" type="button">{{ $t('message.datasetDetails.datasets.modal.copy')}}</button> <span v-show="copied" class="copied"> + <label class="form-check-label"> <svg width="20" height="23" viewBox="0 0 20 23" fill="none" xmlns="http://www.w3.org/2000/svg"> <g clip-path="url(#clip0_13_474)"> <path d="M14.2857 20.125V21.9219C14.2857 22.5173 13.806 23 13.2143 23H1.07143C0.479688 23 0 22.5173 0 21.9219V5.39062C0 4.79519 0.479688 4.3125 1.07143 4.3125H4.28571V17.6094C4.28571 18.9965 5.40719 20.125 6.78571 20.125H14.2857ZM14.2857 4.67188V0H6.78571C6.19397 0 5.71429 0.482686 5.71429 1.07812V17.6094C5.71429 18.2048 6.19397 18.6875 6.78571 18.6875H18.9286C19.5203 18.6875 20 18.2048 20 17.6094V5.75H15.3571C14.7679 5.75 14.2857 5.26484 14.2857 4.67188ZM19.6862 3.27799L16.7424 0.315756C16.5414 0.113582 16.2689 1.49498e-06 15.9848 0L15.7143 0V4.3125H20V4.04032C20 3.75439 19.8871 3.48018 19.6862 3.27799Z" fill="#464646"/> @@ -76,7 +96,10 @@ </clipPath> </defs> </svg> - {{ $t('message.datasetDetails.datasets.modal.copied') }} + + {{ $t('message.datasetDetails.datasets.modal.copied') }} + </label> + </span> </div> </div> @@ -96,13 +119,22 @@ export default { minRange: this.$env.content.datasetDetails.embed.minRange, maxRange: this.$env.content.datasetDetails.embed.maxRange, embedType: 'embed', // default to 'embed' + isResponsive: false }; }, methods: { + selectAll(event) { + event.target.select() + }, updateEmbedCode() { // Ensure the input values are within the specified range + if(this.isResponsive){ + this.iframeWidth = '100%'; + } else { + this.iframeWidth = 900; this.iframeWidth = Math.min(Math.max(this.iframeWidth, this.minRange), this.maxRange); this.iframeHeight = Math.min(Math.max(this.iframeHeight, this.minRange), this.maxRange); + } // Generate the embed code const currentURL = window.location.href.replace(/\?.*$/, '') + '/' + this.embedType; @@ -144,6 +176,13 @@ export default { // line-height: 24px; color: #000000; } + +.disable-width { + background: #d8d8d8; + border-radius: 10px; + border: 1px solid #cecece !important; + width: 140px; +} .modal-head { border-bottom: none; padding: 0; @@ -161,6 +200,7 @@ export default { padding: 2.438rem 0 0 0; font-size: 16px; } + .close { font-size: 14px; color: #1C3D66; @@ -183,7 +223,7 @@ export default { color: #000000!important; } .iframeSettings { - padding: 1.875rem 0 2.438rem 0; + padding: 1.875rem 0 1.438rem 0; font-size: 18px; input { @@ -197,13 +237,13 @@ export default { } .input-with-px input { - padding: 15px 50px 15px 50px; - border: 1px solid #A5A5A5; + padding: 15px 30px 15px 50px; + border: 1px solid #cbcbcb; } /* Style the "px" text */ .input-with-px::after { - content: "px"; +// content: "px"; position: absolute; right: 30%; top: 54%; diff --git a/packages/piveau-hub-ui-modules/lib/embed/EmbedDataset.vue b/packages/piveau-hub-ui-modules/lib/embed/EmbedDataset.vue index 8cc5965360af6fd003ef1c293bc1ca42920e831d..41a4808924fb5b3cc333c1f2ad8316f4130c35c0 100644 --- a/packages/piveau-hub-ui-modules/lib/embed/EmbedDataset.vue +++ b/packages/piveau-hub-ui-modules/lib/embed/EmbedDataset.vue @@ -12,10 +12,11 @@ <p class="px-5 py-3 m-0">data.europa.eu - The official portal for European data</p> </div> <div class="p-5"> - <dataset-details-header-title + <button @click="openUrl" class="btn-title"> + <dataset-details-header-title titleFontSize="0.75rem" :embed="true" - /> + /> </button> <div class="mb-5"> <dataset-details-header-catalogue :disableLink="true" @@ -69,8 +70,16 @@ export default { background-color: white; /* Semi-transparent background */ display: flex; color: black; - z-index: 99; /* Ensure it's above other content */ + z-index: 999999; /* Ensure it's above other content */ } + +.btn-title { + border: none; + background: none; + text-align: left; + width: 100%; +} + .logo { width: 290px; height: 70px; diff --git a/packages/piveau-hub-ui-modules/lib/embed/EmbedDatasetSnippet.vue b/packages/piveau-hub-ui-modules/lib/embed/EmbedDatasetSnippet.vue index 4ac2bbed8c7352cfbc8a713ab67c85f1136a98ef..48ed177978db0f5826a5dec125e2811993cdc804 100644 --- a/packages/piveau-hub-ui-modules/lib/embed/EmbedDatasetSnippet.vue +++ b/packages/piveau-hub-ui-modules/lib/embed/EmbedDatasetSnippet.vue @@ -4,7 +4,7 @@ :embed-snippet="true" class="d-none" /> <div class="p-5 content-snipp"> - <dataset-details-header-title :embed="true" class="mr-2" /> + <button @click="openUrl" class="btn-title"> <dataset-details-header-title :embed="true" class="mr-2" /></button> <dataset-details-description class="snip-desc"></dataset-details-description> @@ -64,13 +64,14 @@ export default { </script> <style scoped> + .embed-dataset-snipp { position: fixed; top: 0; left: 0; width: 100%; height: 100%; - background-color: white; + background-color: #fff; /* Semi-transparent background */ display: flex; color: black; @@ -80,6 +81,13 @@ export default { /* This hides the scrollbar */ overflow-y: scroll; /* This allows vertical scrolling but hides the scrollbar */ + z-index: 99999; +} +.btn-title { + border: none; + background: none; + text-align: left; + width: 100%; } .embed-dataset-snipp::-webkit-scrollbar { diff --git a/packages/piveau-hub-ui-modules/lib/facets_2.0/Facets.vue b/packages/piveau-hub-ui-modules/lib/facets_2.0/Facets.vue new file mode 100644 index 0000000000000000000000000000000000000000..3b77a8b5984196bba100fc074644108a4dc188e9 --- /dev/null +++ b/packages/piveau-hub-ui-modules/lib/facets_2.0/Facets.vue @@ -0,0 +1,36 @@ +<template> + <div style="width:25%"> + <h1>{{ nonZeroConfig.length }}</h1> + <div v-for="item in nonZeroConfig" class="mb-3"> + <component + :is="getFacet(item.id)" + :config="item" + @facet-clicked="facetClicked" + /> + </div> + </div> +</template> + +<script setup lang="ts"> + import { getFacet } from "./getFacet" + import {FacetsConfig} from "./types"; + import {useFacets} from "./useFacets"; + import {computed, toRef} from "vue"; + import {useRoute, useRouter} from "vue-router"; + import {toggleQueryParam} from "./toggleQueryParam"; + + const props = defineProps<{ + facetsConfig: FacetsConfig + facetsIds: [String] + }>(); + + const route = useRoute(); + const router = useRouter(); + const facetsConfig = toRef(props, 'facetsConfig'); + const config = useFacets(facetsConfig, props.facetsIds, route); + const nonZeroConfig = computed(() => + config.value.filter(element => element.items.length)); + const facetClicked = (facetId, item) => { + toggleQueryParam(facetId, item.id, router, route); + } +</script> diff --git a/packages/piveau-hub-ui-modules/lib/facets_2.0/facets/CatalogFacet.vue b/packages/piveau-hub-ui-modules/lib/facets_2.0/facets/CatalogFacet.vue new file mode 100644 index 0000000000000000000000000000000000000000..99bd3362792291f87f72150f6013d9e321b1465e --- /dev/null +++ b/packages/piveau-hub-ui-modules/lib/facets_2.0/facets/CatalogFacet.vue @@ -0,0 +1,17 @@ +<template> + <div> + <h2>HI I AM SPECIAL!!!!</h2> + </div> +</template> + +<script setup> + +</script> + +<style scoped> + h2 { + border-radius: .75rem; + background-color: cornflowerblue; + padding: 0.75rem; + } +</style> diff --git a/packages/piveau-hub-ui-modules/lib/facets_2.0/facets/DataServicesFacet.vue b/packages/piveau-hub-ui-modules/lib/facets_2.0/facets/DataServicesFacet.vue new file mode 100644 index 0000000000000000000000000000000000000000..2215a6b53cf773adde9993cd42bd9d2fe03b12aa --- /dev/null +++ b/packages/piveau-hub-ui-modules/lib/facets_2.0/facets/DataServicesFacet.vue @@ -0,0 +1,52 @@ +<template> + <RadioFacet + :config="config" + :property="property" + :optionIds="['true', 'false']" + :optionLabels="[yes, no]" + :change="changeDataServices" + :initialOption="getDataServices" + /> +</template> + +<script> +import {mapActions, mapGetters} from "vuex"; +import RadioFacet from "./RadioFacet.vue"; + +export default { + name: "DataServicesFacet", + props: { + config: Object + }, + components: { + RadioFacet + }, + data() { + return { + yes: this.i18n.global.t('message.metadata.yes'), + no: this.i18n.global.t('message.metadata.no'), + property: this.i18n.global.t('message.datasetFacets.facets.dataServices.dataServicesOnly'), + title: this.i18n.global.t('message.metadata.dataServices'), + toolTipTitle: this.i18n.global.t('message.helpIcon.dataServices') + }; + }, + computed: { + ...mapGetters('datasets', ['getDataServices']) + }, + methods: { + ...mapActions('datasets', ['setDataServices']), + changeDataServices(dataServices) { + this.setDataServices(dataServices); + const query = Object.assign({}, this.$route.query, { dataServices, page: 1 }); + if (dataServices === 'false') { + delete query.dataServices; + } + this.$router.replace({ query }); + } + } +} +</script> + +<style scoped> + +</style> diff --git a/packages/piveau-hub-ui-modules/lib/facets_2.0/facets/ExpandableSelectFacet.vue b/packages/piveau-hub-ui-modules/lib/facets_2.0/facets/ExpandableSelectFacet.vue new file mode 100644 index 0000000000000000000000000000000000000000..f7e86082ef81db67a0c521db7bda83d12f054bd1 --- /dev/null +++ b/packages/piveau-hub-ui-modules/lib/facets_2.0/facets/ExpandableSelectFacet.vue @@ -0,0 +1,183 @@ +<template> + <!-- Component for a collapsible facet --> + <div class="list-group col pr-0" role="group" :aria-labelledby="myTitleId" > + <template v-if="header"> + <facet-title + :title="header" + :tooltip="toolTipTitle" + :title-id="myTitleId" + /> + <a + class="facet-title-mobile d-flex d-md-none list-group-item justify-content-between align-items-baseline" + data-toggle="collapse" + :data-target="`#${myListId}`" + @click="isExpanded = !isExpanded" + > + <h2 class="h5 mb-0">{{ header }}</h2> + <button class="btn"> + <i class="material-icons small-icon expand-more animated" v-if="!isExpanded">expand_more</i> + <i class="material-icons small-icon expand-less animated" v-else>expand_less</i> + </button> + </a> + </template> + + <div + :id="myListId" + class="collapse dont-collapse-sm"> + <template v-if="items && items.length > 0"> + <div + class="list-item-container" + v-for="(item, index) in items.slice(0, numItemsAllowed)" + :key="`field@${index}`" + > + <select-facets-item + class="d-flex facet list-group-item list-group-item-action justify-content-between align-items-center" + :title="getFacetTranslation(fieldId, item.id, $route.query.locale, item.title)" + :count="getFacetCount(item)" + :hide-count="fieldId === 'dataScope'" + :class="{active: facetIsSelected(fieldId, item)}" + @click.native="$emit('facet-clicked', fieldId, item)" + /> + </div> + <button + v-if="items.length > minItems" + class="d-block btn btn-primary btn-color w-100 facet-expand-button" + @click="handleGrowToggle" + > + <i class="material-icons align-bottom expand-more animated">{{ isGrown ? 'expand_less' : 'expand_more' }}</i> + </button> + </template> + </div> + </div> +</template> + +<script> +import { defineComponent } from 'vue'; +import SelectFacetsItem from "./SelectFacetsItem.vue"; +import FacetTitle from "./FacetTitle.vue"; +import { getFacetTranslation } from '../../utils/helpers'; + +export default defineComponent({ + name: 'ExpandableSelectFacet', + inheritAttrs: false, + components: {FacetTitle, SelectFacetsItem}, + props: { + config: { + type: Object + // type: {title: String, id: String, items: [], query: Object} + }, + multiSelect: { + type: Boolean, + default: true + }, + // facetIsSelected: { + // type: Function, + // default: (fieldId, item) => { + // // console.log("facetIsSelected", fieldId, item); + // const queryParams = this.$route.query[fieldId]; + // return queryParams === item || queryParams.includes(item) + // } + // }, + // facetClicked: { + // type: Function, + // default: (fieldId, item) => { + // console.log("facetClicked", fieldId, item); + // } + // } + }, + emits: ['facet-clicked'], + data() { + return { + isExpanded: false, + isGrown: false, + numItemsAllowed: this.$env.content.datasets.facets.MIN_FACET_LIMIT || 5, + minItems: this.$env.content.datasets.facets.MIN_FACET_LIMIT || 5, + maxItems: this.$env.content.datasets.facets.MAX_FACET_LIMIT || 50, + header: this.i18n.global.t(`message.datasetFacets.facets.${this.config.id.toLowerCase()}`), + fieldId: this.config.id, + toolTipTitle: this.i18n.global.t(`message.helpIcon.${this.config.id.toLowerCase()}`), + }; + }, + computed: { + items() { // sorting by count + return this.config.items.slice().sort((a, b) => { + const n = b.count - a.count; + if (n !== 0) return b.count - a.count; + if (a.name < b.name) return -1; + return 1; + }); + }, + myId() { + // Use Vue generated uid to set give each facet a unique id + return `facet-${this.fieldId}`; + }, + myListId() { + // Use Vue generated uid to set give each facet a unique id + return `facet-list-${this.fieldId}`; + }, + myTitleId() { + // Use Vue generated uid to set give each facet a unique id + return `facet-title-${this.fieldId}`; + }, + }, + methods: { + getFacetTranslation, + handleGrowToggle() { + this.isGrown = !this.isGrown; + this.numItemsAllowed = this.isGrown + ? this.maxItems + : this.minItems; + }, + getFacetCount(facet) { + if (this.fieldId === 'scoring') return ''; + return facet.count; + }, + facetIsSelected(fieldId, item) { + const queryParams = this.$route.query[fieldId]; + return queryParams && (queryParams === item.id || queryParams.includes(item.id)); + } + } +}); + +</script> + +<style lang="scss" scoped> +.facet-header { + // background-color: #fffade; + // background-color: rgba(255, 204, 0, 0.1); + background-color: rgba(0, 29, 133,0.05); + // background-color: #cddbe8; + + // background-color: #001d85; + // color: white !important; +} +.tooltip-icon { + font-size: 15px; +} + +@media (min-width: 768px) { + .collapse.dont-collapse-sm { + display: block; + height: auto !important; + visibility: visible; + } +} + +.list-item-container { + margin-bottom: -1px; +} + +.btn-color { + background-color: var(--primary); + border-color: var(--primary); + + &:hover { + background-color: #196fd2; + border-color: #196fd2; + } +} +.active { + background-color: var(--primary); + border-color: var(--primary); +} +</style> diff --git a/packages/piveau-hub-ui-modules/lib/facets_2.0/facets/FacetTitle.vue b/packages/piveau-hub-ui-modules/lib/facets_2.0/facets/FacetTitle.vue new file mode 100644 index 0000000000000000000000000000000000000000..f8b12e9e359f1acf328fecd3404edb5a4a3d9814 --- /dev/null +++ b/packages/piveau-hub-ui-modules/lib/facets_2.0/facets/FacetTitle.vue @@ -0,0 +1,39 @@ +<template> + <div class="d-none d-md-block list-group-item facet-title"> + <h2 class="h5 mb-0 float-left" :id="titleId">{{ title }}</h2> + <i v-if="tooltip" + class="tooltip-icon material-icons small-icon align-right text-dark pl-1" + ref="tooltip-icon" + data-toggle="tooltip" + data-placement="right" + :title="tooltip"> + help_outline + </i> + </div> +</template> + +<script> + + +export default { + name: "FacetTitle", + props: { + title: String, + tooltip: String, + titleId: { + type: String, + default: null, + }, + } +} +</script> + +<style scoped lang="scss"> +.facet-title { + background-color: rgba(0, 29, 133,0.05); +} + +.tooltip-icon { + font-size: 15px; +} +</style> diff --git a/packages/piveau-hub-ui-modules/lib/facets_2.0/facets/InfoFacet.vue b/packages/piveau-hub-ui-modules/lib/facets_2.0/facets/InfoFacet.vue new file mode 100644 index 0000000000000000000000000000000000000000..946476b225ada210e3fcd16ba1503e000aeae337 --- /dev/null +++ b/packages/piveau-hub-ui-modules/lib/facets_2.0/facets/InfoFacet.vue @@ -0,0 +1,21 @@ +<template> + <div> + <h2>{{ config.title }}</h2> + </div> +</template> + +<script setup lang="ts"> + import {FacetsConfigItem} from "../types"; + defineProps<{ + config: FacetsConfigItem + }>(); +</script> + +<style scoped> + h2 { + border-radius: .5rem; + background-color: lightsalmon; + padding: .75rem; + margin: .5rem 0; + } +</style> diff --git a/packages/piveau-hub-ui-modules/lib/facets_2.0/facets/PublisherFacet.vue b/packages/piveau-hub-ui-modules/lib/facets_2.0/facets/PublisherFacet.vue new file mode 100644 index 0000000000000000000000000000000000000000..9cf0670d48f6e3565b6ca4997e949538093be790 --- /dev/null +++ b/packages/piveau-hub-ui-modules/lib/facets_2.0/facets/PublisherFacet.vue @@ -0,0 +1,17 @@ +<template> + <div> + <h2>Publisher!!!!</h2> + </div> +</template> + +<script setup> + +</script> + +<style scoped> + h2 { + border-radius: .75rem; + background-color: gold; + padding: 0.75rem; + } +</style> diff --git a/packages/piveau-hub-ui-modules/lib/facets_2.0/facets/RadioFacet.vue b/packages/piveau-hub-ui-modules/lib/facets_2.0/facets/RadioFacet.vue new file mode 100644 index 0000000000000000000000000000000000000000..ba7dfbea029945f7770794cd92a1a3a89c9a4942 --- /dev/null +++ b/packages/piveau-hub-ui-modules/lib/facets_2.0/facets/RadioFacet.vue @@ -0,0 +1,75 @@ +<template> + <div class="list-group w-100 radio-facet" role="group" :aria-labelledby="myTitleId"> + <facet-title + :title="header" + :tooltip="toolTipTitle" + :title-id="myTitleId" + /> + <div class="list-group-item list-group-item-action d-flex justify-content-between align-items-center"> + {{ property }} + <span class="ml-2 d-flex flex-wrap"> + <div class="custom-control custom-radio" v-for="(id, index) in optionIds"> + <input type="radio" :id="`${header}_${id}`" :value="id" :name="header" class="custom-control-input" @click="onChange(id)" :checked="option === id"> + <label class="custom-control-label" :for="`${header}_${id}`">{{ optionLabels[index] }}</label> + </div> + </span> + </div> + </div> +</template> + +<script> +import FacetTitle from "../facets/FacetTitle.vue"; +export default { + name: "RadioFacet", + components: {FacetTitle}, + props: { + config: Object, + property: String, + optionIds: { + type: Array, + default: ['true', 'false'] + }, + optionLabels: { + type: Array + }, + initialOption: String, + change: Function + }, + data() { + return { + id: '', + option: this.initialOption, + header: this.i18n.global.t(`message.datasetFacets.facets.${this.config.id.toLowerCase()}`), + toolTipTitle: this.i18n.global.t(`message.helpIcon.${this.config.id.toLowerCase()}`), + yes: this.i18n.global.t('message.metadata.yes'), + no: this.i18n.global.t('message.metadata.no') + }; + }, + computed: { + myId() { + // Use Vue generated uid to give each facet a unique id + return `facet-${this.id}`; + }, + myTitleId() { + // Use Vue generated uid to give each facet a unique id + return `facet-title-${this.id}`; + } + }, + methods: { + onChange(id) { + this.option = id; + console.log("onChange", id) + this.change(id); + } + }, + watch: { + initialOption(value) { + this.option = value; + } + }, + mounted() { + this.id = this._uid; // eslint-disable-line + }, +} +</script> + diff --git a/packages/piveau-hub-ui-modules/lib/facets_2.0/facets/SelectFacet.vue b/packages/piveau-hub-ui-modules/lib/facets_2.0/facets/SelectFacet.vue new file mode 100644 index 0000000000000000000000000000000000000000..fcb979055a5d1d55f36ebc9f457403f4c78def51 --- /dev/null +++ b/packages/piveau-hub-ui-modules/lib/facets_2.0/facets/SelectFacet.vue @@ -0,0 +1,21 @@ +<template> + <div> + <h2>{{ config.title }}</h2> + </div> +</template> + +<script setup lang="ts"> + import {FacetsConfigItem} from "../types"; + + defineProps<{ + config: FacetsConfigItem + }>(); +</script> + +<style scoped> + h2 { + border-radius: .75rem; + background-color: lightsalmon; + padding: 0.75rem; + } +</style> diff --git a/packages/piveau-hub-ui-modules/lib/facets_2.0/facets/SelectFacetsItem.vue b/packages/piveau-hub-ui-modules/lib/facets_2.0/facets/SelectFacetsItem.vue new file mode 100644 index 0000000000000000000000000000000000000000..fba6dd03da4a7943b73a4112ddd44ab3b1a83ac0 --- /dev/null +++ b/packages/piveau-hub-ui-modules/lib/facets_2.0/facets/SelectFacetsItem.vue @@ -0,0 +1,40 @@ +<template> + <button + :title="tooltip" + > + <span ref="facetBtn" class="text-truncate">{{ title }}</span> + <span v-if="!hideCount" class="facet-count badge">{{ count.toLocaleString('fi') }}</span> + </button> +</template> + +<script> + +export default { + name: 'DatasetsFacetsItem', + data() { + return { + tooltip: '', + }; + }, + props: { + title: { + type: String, + default: '', + }, + count: { + type: [Number, String], + default: 0, + }, + hideCount: { + type: Boolean, + default: false, + }, + }, + mounted() { + // Sets tooltip text to title when title itself is rendered truncated + this.tooltip = this.$refs.facetBtn.offsetWidth < this.$refs.facetBtn.scrollWidth + ? this.title + : ''; + }, +}; +</script> diff --git a/packages/piveau-hub-ui-modules/lib/facets_2.0/getFacet.ts b/packages/piveau-hub-ui-modules/lib/facets_2.0/getFacet.ts new file mode 100644 index 0000000000000000000000000000000000000000..b042f4de079a036947808ee872c00c386e0489a5 --- /dev/null +++ b/packages/piveau-hub-ui-modules/lib/facets_2.0/getFacet.ts @@ -0,0 +1,19 @@ +import {Component} from "vue"; +import {extras} from "../configurations/configureModules"; +import ExpandableSelectFacet from "./facets/ExpandableSelectFacet.vue"; +import DataServicesFacet from "./facets/DataServicesFacet.vue"; +import RadioFacet from "./facets/RadioFacet.vue"; + +export const getFacet = (id: string): Component => { + const custom = extras.customFacets; + if (custom && custom[id]) { // custom + return custom[id]; + } else { // defaults + switch (id) { + case "dataServices": return DataServicesFacet; + case "superCatalog": return RadioFacet; + // case "dataServices":case "superCatalog": return InfoFacet; + } + return ExpandableSelectFacet; + } +} diff --git a/packages/piveau-hub-ui-modules/lib/facets_2.0/toggleQueryParam.ts b/packages/piveau-hub-ui-modules/lib/facets_2.0/toggleQueryParam.ts new file mode 100644 index 0000000000000000000000000000000000000000..1dbdd01d5f3dab0cc656a02195b10403b769f9e0 --- /dev/null +++ b/packages/piveau-hub-ui-modules/lib/facets_2.0/toggleQueryParam.ts @@ -0,0 +1,31 @@ +/** + * Uses the vue router to perform the action of selecting a query param key-value pair. + * If it already exists in the route it is removed, else it is added. + * @param key + * @param value + * @param router + * @param route + */ +export function toggleQueryParam(key, value, router, route) { + let query: any = route.query[key]; + console.log("QUERY", query, typeof query) + if ( ! query) { + query = value; + } else if (typeof query === 'string') { + if (query === value) { + query = undefined; + } else { + query = [query, value]; + } + } else if (Array.isArray(query)) { + if (query.includes(value)) { + query = query.filter(element => element !== value); + } else { + query = [...query, value]; + } + } + router.push({ + path: route.path, + query: {...route.query, [key]: query} + }); +} diff --git a/packages/piveau-hub-ui-modules/lib/facets_2.0/types.d.ts b/packages/piveau-hub-ui-modules/lib/facets_2.0/types.d.ts new file mode 100644 index 0000000000000000000000000000000000000000..934d27ec005ca98be34b733fd60010a76a8d3034 --- /dev/null +++ b/packages/piveau-hub-ui-modules/lib/facets_2.0/types.d.ts @@ -0,0 +1,8 @@ +import {Ref} from "vue"; +import {LocationQueryValue} from "vue-router"; + +export type FacetsConfigItem = {title: String, id: String, items: [], selected: string | null | LocationQueryValue[]}; +export type FacetsConfig = [FacetsConfigItem]; + +export type FacetState = {config:FacetsConfigItem, selection:unknown}; +export type FacetsState = [FacetState]; diff --git a/packages/piveau-hub-ui-modules/lib/facets_2.0/useFacets.ts b/packages/piveau-hub-ui-modules/lib/facets_2.0/useFacets.ts new file mode 100644 index 0000000000000000000000000000000000000000..f2c1471c604cb563641a9dc4302257438114b1ef --- /dev/null +++ b/packages/piveau-hub-ui-modules/lib/facets_2.0/useFacets.ts @@ -0,0 +1,19 @@ +import {FacetsConfig, FacetsConfigItem} from "./types"; +import {Ref, ref, toValue, watchEffect} from "vue"; +import {RouteLocationNormalizedLoadedGeneric} from "vue-router"; + +export function useFacets(facetsConfig: Ref<FacetsConfig>, facetsIds, route: RouteLocationNormalizedLoadedGeneric) { + const facets = ref(null); + const currentFacets = () => { + facets.value = (facetsIds || []).reduce((acc, curr) => { + const configItem: FacetsConfigItem = toValue(facetsConfig).find(c => c.id === curr); + if (configItem) { + // configItem.selected = route.query[configItem.id]; + acc.push(configItem); + } + return acc; + }, []); + }; + watchEffect(currentFacets); + return facets; +} diff --git a/packages/piveau-hub-ui-modules/lib/form/FormKitGroup.vue b/packages/piveau-hub-ui-modules/lib/form/FormKitGroup.vue index 4edbd325a9796e25c77fa051e5e4ca47b26d102c..790ad6b65c3c59704891ed04b912768b5381943a 100644 --- a/packages/piveau-hub-ui-modules/lib/form/FormKitGroup.vue +++ b/packages/piveau-hub-ui-modules/lib/form/FormKitGroup.vue @@ -132,7 +132,7 @@ } } - button { + .dpi button { align-self: flex-start; } </style> diff --git a/packages/piveau-hub-ui-modules/lib/form/Repeatable.vue b/packages/piveau-hub-ui-modules/lib/form/Repeatable.vue index 439d53b26b0580f26b624f1df12641bf7d7fc9be..eff9c9c1a374655e081d6452c55d559c4d0995e8 100644 --- a/packages/piveau-hub-ui-modules/lib/form/Repeatable.vue +++ b/packages/piveau-hub-ui-modules/lib/form/Repeatable.vue @@ -1,9 +1,9 @@ <template> - <div class="repeatable formkitProperty" :class="[props.context.attrs.identifier]" - v-for="key, repeatableIndex in counter" :key="key"> - <h4 v-if="props.context.attrs.class != undefined && props.context.attrs.class.includes('inDistribution')">{{ - $t('message.dataupload.distributions.' + props.context.attrs.identifier + '.label') }}</h4> - <h4 v-else>{{ $t('message.dataupload.datasets.' + props.context.attrs.identifier + '.label') }}</h4> + <div class="repeatable formkitProperty" :class="[props.context?.attrs.identifier]" + v-for="({ item: key, nonce }, repeatableIndex) in counter" :key="`${nonce}`"> + <h4 v-if="!!props.context?.attrs.class && props.context.attrs.class.includes('inDistribution')"> + {{ $t('message.dataupload.distributions.' + props.context.attrs.identifier + '.label') }}</h4> + <h4 v-else>{{ $t('message.dataupload.datasets.' + props.context?.attrs.identifier + '.label') }}</h4> <div class="horizontal-wrapper"> <div class="repeatableWrap"> <div class="interactionHeaderRepeatable my-1"> @@ -13,7 +13,7 @@ </template> <template v-slot:remove> - <a class="remove" :class="{ disabledRemove: props.context.value.length === 1 }" + <a class="remove" :class="{ disabledRemove: props.context?.value.length === 1 }" @click="removeItem(repeatableIndex, counter.length)" :data-key="key">- {{ $t('message.dataupload.info.remove') }}</a> </template> @@ -28,36 +28,40 @@ </div> </div> </template> -<script setup> -import { ref } from 'vue'; +<script setup lang="ts"> +import { whenever } from '@vueuse/core'; +import { computed, ref } from 'vue'; const props = defineProps({ context: Object }) -const counter = ref([]) +const counter = ref<any[]>([{ value: 'init', nonce: 0 }]) +const nonce = ref(0) -// Need to handle the data like this. The values seem to take their time while loading into the DOM. -setTimeout(() => { - if (props.context.value.length === 0) { - counter.value.push('init') +whenever(() => props.context?.value, (newValue, oldValue) => { + // Workaround to prevent infinite recursive loop + if (JSON.stringify(newValue) === JSON.stringify(oldValue)) { + return } - else { - for (let index = 0; index < props.context.value.length; index++) { - if (props.context.value[index] != null) { - counter.value.push(props.context.value[index]['@value']) - } - } + if (!newValue || newValue.length === 0) { + counter.value = [{ value: 'init', nonce: 0 }]; + } else { + counter.value = newValue.filter(Boolean).map((item: any, idx: number) => ({ value: item['@value'] ?? item.name ?? item, nonce: idx })); } + + nonce.value = counter.value.length +}, { + immediate: false, }); // Pushing a blank to the context object and refreshing the counter -const addItem = (index) => { - counter.value.push(props.context.value[index]['@value']) - +const addItem = (index: number) => { + counter.value.push({ item: props.context?.value[index]['@value'], nonce: nonce.value }) + nonce.value += 1 } // remove Item - ToDo need to make sure the localhost notices the splice -const removeItem = (index, counterLength) => { +const removeItem = (index: number, counterLength: number) => { if (counterLength != 1) { counter.value.splice(index, 1) diff --git a/packages/piveau-hub-ui-modules/lib/index.ts b/packages/piveau-hub-ui-modules/lib/index.ts index aaccf9784ef84d7ccc1156c3cf58fae461640c54..300f4dd99ffdfe6b6c971c440c577e3b78650ebe 100644 --- a/packages/piveau-hub-ui-modules/lib/index.ts +++ b/packages/piveau-hub-ui-modules/lib/index.ts @@ -11,6 +11,7 @@ import runtimeConfigurationService from "./services/runtimeConfigurationService" import datasetService from "./services/datasetService"; import catalogService from "./services/catalogService"; import gazetteerService from "./services/gazetteerService"; +import { getResponseData } from "./services/datasetService"; // Import Stores @@ -57,6 +58,7 @@ import DatasetCitationTable from "./citation/DatasetCitationTable"; import AutocompleteInput from "./data-provider-interface/components/AutocompleteInput"; import ConditionalInput from "./data-provider-interface/components/ConditionalInput"; import DataFetchingComponent from "./data-provider-interface/components/DataFetchingComponent"; +import CatalogueMQA from "./data-provider-interface/CatalogueMQA"; import Dropup from "./data-provider-interface/components/Dropup"; import FileUpload from "./data-provider-interface/components/FileUpload"; import InfoSlot from "./data-provider-interface/components/InfoSlot"; @@ -164,6 +166,8 @@ import DatasetsFilters from "./datasets/DatasetsFilters"; import DatasetsFiltersTabs from "./datasets/DatasetsFiltersTabs"; import DatasetsTopControls from "./datasets/DatasetsTopControls"; +import Search from "./search/Search.vue"; + // Import datasetsFacets import CatalogDetailsFacet from "./facets/CatalogDetailsFacet"; import DatasetsFacets from "./datasets/datasetsFacets/DatasetsFacets"; @@ -222,6 +226,8 @@ import Tooltip from "./widgets/Tooltip"; import {configSchema} from "./configurations/config-schema"; export { + Search, + inputDefinitions, configSchema, vueKeycloak, @@ -233,6 +239,8 @@ export { catalogService, gazetteerService, + getResponseData, + store, decode, @@ -260,7 +268,7 @@ export { DatasetCitationTable, AutocompleteInput, - + ConditionalInput, DataFetchingComponent, Dropup, @@ -281,6 +289,7 @@ export { LinkedDataViewer, OverviewPage, UserCataloguesPage, + CatalogueMQA, UserProfilePage, DataProviderInterface, DpiMenu, @@ -399,4 +408,6 @@ export * as helpers from "./utils/helpers"; export { defineUserConfig, type Config, type ResolvedConfig } from "./configurations/config-schema"; export { useRuntimeEnv } from './composables/useRuntimeEnv'; -export * as head from './composables/head'; \ No newline at end of file +export * as head from './composables/head'; + +export * as dpi from './data-provider-interface'; diff --git a/packages/piveau-hub-ui-modules/lib/modal/AppConfirmationDialog.vue b/packages/piveau-hub-ui-modules/lib/modal/AppConfirmationDialog.vue index 606825b9802b6944e7210ea616288a00f700cc51..e02e4cd42ff29b1b9e836554e2581587a2bd1b88 100644 --- a/packages/piveau-hub-ui-modules/lib/modal/AppConfirmationDialog.vue +++ b/packages/piveau-hub-ui-modules/lib/modal/AppConfirmationDialog.vue @@ -35,7 +35,8 @@ </template> <script> -import { defineComponent } from 'vue' +import { computed, defineComponent } from 'vue' +import { useI18n } from 'vue-i18n'; import $ from 'jquery'; export default defineComponent({ @@ -51,21 +52,17 @@ export default defineComponent({ default: false, }, }, - data() { - return { - resolvedConfirm: this.confirm, - }; - }, methods:{ handleHeader(){ $('#navbar-toggle').css("z-index", "99") } - }, created() { - if (this.confirm === 'Confirm') { // Überprüfe den Default-Wert - this.$i18n.locale = this.$route.query.locale - this.resolvedConfirm = this.$t('message.dataupload.modal.confirmModal'); - } }, + setup(props) { + const { t, te } = useI18n(); + const resolvedConfirm = computed(() => props.confirm === 'Confirm' && te('message.dataupload.modal.confirmModal') ? t('message.dataupload.modal.confirmModal') : props.confirm); + + return { resolvedConfirm }; + } }); </script> diff --git a/packages/piveau-hub-ui-modules/lib/scss/_dpi-style.scss b/packages/piveau-hub-ui-modules/lib/scss/_dpi-style.scss index c61d9bb7a538f0e5146c27aab1e5ab7594e052e8..c8e222e7f1ea6c7b9d89f9217930e068d298eaea 100644 --- a/packages/piveau-hub-ui-modules/lib/scss/_dpi-style.scss +++ b/packages/piveau-hub-ui-modules/lib/scss/_dpi-style.scss @@ -711,7 +711,7 @@ transition: all ease-in-out 200ms; &:hover { - background-color: #bababa; + background-color: lightcoral; } &:hover:before { @@ -807,10 +807,11 @@ border-radius: 15px; transition: all 100ms ease-in-out; display: flex; + color: green; - &:hover { - color: #196fd2; - } + // &:hover { + // color: lightcoral; + // } .removeX { margin-right: 0; diff --git a/packages/piveau-hub-ui-modules/lib/search/Datasets.vue b/packages/piveau-hub-ui-modules/lib/search/Datasets.vue new file mode 100644 index 0000000000000000000000000000000000000000..2dd905f2ac067879eaf0851c76cda3604ff9b90d --- /dev/null +++ b/packages/piveau-hub-ui-modules/lib/search/Datasets.vue @@ -0,0 +1,11 @@ +<template> + +</template> + +<script setup> + +</script> + +<style scoped> + +</style> diff --git a/packages/piveau-hub-ui-modules/lib/search/Search.vue b/packages/piveau-hub-ui-modules/lib/search/Search.vue new file mode 100644 index 0000000000000000000000000000000000000000..91505fe8f38315765af1bef50174d86cf46b92da --- /dev/null +++ b/packages/piveau-hub-ui-modules/lib/search/Search.vue @@ -0,0 +1,25 @@ +<template> + <div id="search-container"> + <datasets-top-controls + :getPage="getPage" + :getLimit="getLimit" + class="datasets-top-controls" + /> + <h1>New Search Page</h1> +<!-- <facets--> +<!-- :facetsConfig="getAllAvailableFacets"--> +<!-- :facetsIds="$env.content.datasets.facets.defaultFacetOrder"--> +<!-- class="col-md-3 col-12 mb-3 mb-md-0 px-0 collapse"--> +<!-- id="datasetFacets">--> + +<!-- </facets>--> + </div> +</template> + +<script setup> + import DatasetsTopControls from "../datasets/DatasetsTopControls.vue"; +</script> + +<style scoped> + +</style> diff --git a/packages/piveau-hub-ui-modules/lib/search/TopControls.vue b/packages/piveau-hub-ui-modules/lib/search/TopControls.vue new file mode 100644 index 0000000000000000000000000000000000000000..e83f5195b4703c1eb7b109e8046cd3c843b27a42 --- /dev/null +++ b/packages/piveau-hub-ui-modules/lib/search/TopControls.vue @@ -0,0 +1,79 @@ +<template> + <sub-navigation> + <div class="container-fluid justify-content-between"> + <div class="navbar-datasets-feed navbar-nav align-items-center justify-content-end"> + <div class="nav-item dropdown"> + <div v-if="useFeed" class="nav-link dropdown-toggle cursor-pointer" + id="dropdown-feeds" data-toggle="dropdown" + aria-haspopup="true" aria-expanded="false"> + <ins>{{ $t('message.datasets.datasetsFeed') }}</ins> + </div> + <div class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdown-feeds"> + <app-link class="dropdown-item text-decoration-none" + :path="getFeedLink('rss')" + :query="getFeedQuery()" + target="_blank" + matomo-track-page-view> + RSS Feed</app-link> + <app-link class="dropdown-item text-decoration-none" + :path="getFeedLink('atom')" + :query="getFeedQuery()" + target="_blank" + matomo-track-page-view> + ATOM Feed</app-link> + </div> + </div> + </div> + </div> + </sub-navigation> +</template> + +<script> +import AppLink from "../widgets/AppLink.vue"; +import {mapGetters} from "vuex"; + +export default { + name: "TopControls", + components: { + AppLink + }, + props: [ + "facets", + "getPage", + "getLimit" + ], + data() { + return { + useFeed: this.$env.content.datasets.useFeed, + baseUrl: this.$env.api.baseUrl, + } + }, + computed: { + ...mapGetters('datasets', [ + 'getSort' + ]) + }, + methods: { + getFeedLink(format) { + return `${this.baseUrl}${this.$route.query.locale}/feeds/datasets.${format}`; + }, + getFeedQuery() { + const feedQuery = {}; + const query = this.$route?.query + if (query?.query) feedQuery.q = query.query; + if (this.facetsNotEmpty() && JSON.stringify(this.facets)) feedQuery.facets = JSON.stringify(this.facets); + if (this.getPage) feedQuery.page = Math.max(this.getPage - 1, 0); + if (this.getLimit) feedQuery.limit = this.getLimit; + feedQuery.facetOperator = query?.facetOperator || 'AND'; + feedQuery.facetGroupOperator = query?.facetOperator || 'AND'; + feedQuery.dataServices = query?.dataServices || 'false'; + if (this.getSort) feedQuery.sort = this.getSort; + return feedQuery; + }, + facetsNotEmpty() { + return Object.values(this.facets).some(facet => facet.length > 0); + } + } +} +</script> + diff --git a/packages/piveau-hub-ui-modules/lib/services/datasetService.ts b/packages/piveau-hub-ui-modules/lib/services/datasetService.ts index a8ece8c3c57d0553fdf9e63fd4668e8f3d0ca2e5..0a7d7ed32179d9ff4ee1f929d40e979b993b1e0a 100644 --- a/packages/piveau-hub-ui-modules/lib/services/datasetService.ts +++ b/packages/piveau-hub-ui-modules/lib/services/datasetService.ts @@ -5,7 +5,7 @@ import dataGetters from './getters/data-getters'; - const getResponseData = (dataset: { is_hvd: boolean; }) => { + export const getResponseData = (dataset: { is_hvd: boolean; }) => { const ds: {[key: string]: unknown} = {}; // New fields from DCAT-AP.de // Dataset @@ -30,7 +30,7 @@ ds.catalogRecord = dataGetters.getObject(dataset, 'catalog_record', ['issued', 'modified']); ds.categories = dataGetters.getArrayOfObjects(dataset, 'categories', ['id', 'label']); ds.conformsTo = dataGetters.getArrayOfObjects(dataset, 'conforms_to', ['label', 'resource']); - ds.contactPoints = dataGetters.getArrayOfObjects(dataset, 'contact_point', ['name', 'type', 'resource', 'email', 'telephone', 'address', 'url']); + ds.contactPoints = dataGetters.getArrayOfObjects(dataset, 'contact_point', ['name','organisation_name', 'type', 'resource', 'email', 'telephone', 'address', 'url']); ds.country = dataGetters.getObject(dataset, 'country', [{ key: 'id', default: 'eu' }, { key: 'title', default: 'European Union' }]); ds.creator = dataGetters.getObject(dataset, 'creator', ['name', 'type', 'email', 'resource', 'homepage']); ds.deadline = dataGetters.getString(dataset, 'deadline'); @@ -70,7 +70,7 @@ ds.sample = dataGetters.getArrayOfStrings(dataset, 'sample'); ds.sources = dataGetters.getArrayOfStrings(dataset, 'source'); ds.spatial = dataGetters.getArrayOfObjects(dataset, 'spatial', ['type', 'coordinates']); - ds.spatialResolutionInMeters = dataGetters.getArrayOfNumbers(dataset, 'spatial_resolution_in_meters'); + ds.spatialResolutionInMeters = dataGetters.getNumber(dataset, 'spatial_resolution_in_meters'); ds.spatialResource = dataGetters.getArrayOfObjects(dataset, 'spatial_resource', ['label', 'resource']); ds.statUnitMeasures = dataGetters.getArrayOfStrings(dataset, 'stat_unit_measure'); ds.subject = dataGetters.getArrayOfObjects(dataset, 'subject', ['resource', 'label', 'id']); @@ -126,7 +126,8 @@ distribution.pages = dataGetters.getArrayOfObjects(dist, 'page', ['format', 'title', 'description', 'resource']); distribution.releaseDate = dataGetters.getString(dist, 'issued'); distribution.rights = dataGetters.getObject(dist, 'rights', ['label', 'resource']); - distribution.spatialResolutionInMeters = dataGetters.getArrayOfNumbers(dist, 'spatial_resolution_in_meters'); + //distribution.spatialResolutionInMeters = dataGetters.getArrayOfNumbers(dist, 'spatial_resolution_in_meters'); + distribution.spatialResolutionInMeters = dataGetters.getNumber(dist, 'spatial_resolution_in_meters'); distribution.status = dataGetters.getObject(dist, 'status', ['label', 'resource']); distribution.temporalResolution = dataGetters.getArrayOfStrings(dist, 'temporal_resolution'); distribution.title = dataGetters.getObjectLanguage(dist, 'title', ''); @@ -357,7 +358,7 @@ /** * @description Get similar datasets to the dataset represented by the provided id. * @param id {string} The dataset id to get similar datasets for. - * @param payload + * @param properties additional parameters, in particular for the knn-request * @param query {SimilarDatasetsQuery} query params */ getSimilarDatasets(id: any, properties: any, query?: SimilarDatasetsQuery) { @@ -368,6 +369,8 @@ } const similarityServiceName = this.similarityServiceName; if (similarityServiceName === 'knn_request') { + properties.exclude_uris = [`http://data.europa.eu/88u/dataset/${id}`]; + // properties.exclude_uris = [`https://data.europa.eu/api/hub/search/datasets/${id}`]; axios.post(`${url}knn_request/`, { ...properties, k: 10}) .then((response) => { resolve(response); diff --git a/packages/piveau-hub-ui-modules/lib/store/modules/cataloguesStore.ts b/packages/piveau-hub-ui-modules/lib/store/modules/cataloguesStore.ts index d46fac495c9c77e2474ba752b209ec33a059f485..d2ff82ca9f0dee8475f3213823df201d605000e9 100644 --- a/packages/piveau-hub-ui-modules/lib/store/modules/cataloguesStore.ts +++ b/packages/piveau-hub-ui-modules/lib/store/modules/cataloguesStore.ts @@ -212,6 +212,12 @@ const actions = { setLoading({ commit }, isLoading) { commit('SET_LOADING', isLoading); }, + setCatalogs({ commit }, catalogs) { + commit('SET_catalogS', catalogs); + }, + setCatalogsCount({ commit }, count) { + commit('SET_catalogS_COUNT', count); + } }; const mutations = { diff --git a/packages/piveau-hub-ui-modules/lib/store/modules/datasetDetailsStore.ts b/packages/piveau-hub-ui-modules/lib/store/modules/datasetDetailsStore.ts index ac6e1f8adf11148c020dbe685028b335e7f111fb..d308b4ba0f8461de7e5adecc3ebc5f55766041ef 100755 --- a/packages/piveau-hub-ui-modules/lib/store/modules/datasetDetailsStore.ts +++ b/packages/piveau-hub-ui-modules/lib/store/modules/datasetDetailsStore.ts @@ -90,7 +90,7 @@ const state = { sources: [], spatial: [], spatialResource: [], - spatialResolutionInMeters: [], + spatialResolutionInMeters: 0, statUnitMeasures: [], subject: [], temporal: [], diff --git a/packages/piveau-hub-ui-modules/lib/store/modules/datasetsStore.ts b/packages/piveau-hub-ui-modules/lib/store/modules/datasetsStore.ts index 79248d470dd77c18b517105c6314cff95f0e0b65..e4c2d950a4d4f8735d28779ea6053abe0de09c7a 100644 --- a/packages/piveau-hub-ui-modules/lib/store/modules/datasetsStore.ts +++ b/packages/piveau-hub-ui-modules/lib/store/modules/datasetsStore.ts @@ -172,7 +172,6 @@ const actions = { reject(error); }); }); - }, /** @@ -318,6 +317,18 @@ const actions = { setDataScope({ commit }, dataScope) { commit('SET_DATA_SCOPE', dataScope); }, + setAvailableFacets({ commit }, facets) { + commit('SET_AVAILABLE_FACETS', facets); + }, + setScoringCount({ commit }, count) { + commit('SET_SCORING_COUNT', count); + }, + setDatasetsCount({ commit }, count) { + commit('SET_DATASETS_COUNT', count); + }, + setDatasets({ commit }, datasets) { + commit('SET_DATASETS', datasets); + }, }; const mutations = { diff --git a/packages/piveau-hub-ui-modules/lib/widgets/ResourceAccessPopup.vue b/packages/piveau-hub-ui-modules/lib/widgets/ResourceAccessPopup.vue index 9423442948bbe19acb3a404e29107de5966609d1..f0b12a0d9c1598c5634117e5b4a4b6bae5c78590 100644 --- a/packages/piveau-hub-ui-modules/lib/widgets/ResourceAccessPopup.vue +++ b/packages/piveau-hub-ui-modules/lib/widgets/ResourceAccessPopup.vue @@ -47,9 +47,9 @@ export default { this.callback = ''; this.callback = callbackFunction; this.toggleDownloadPopup = toggleDownloadPopup; - if (cookie === 'false') { + if (cookie === 'false' || !cookie) { $('#externalAccess').appendTo("body").modal('show'); - $('#myModal').appendTo("body").modal('show'); + $('#myModal').appendTo("body").modal('show'); // what is this? } else { this.callback(); } diff --git a/packages/piveau-hub-ui-modules/package.json b/packages/piveau-hub-ui-modules/package.json index d44853316a7548f186417adbc27c3492636636bb..edaf3276992d29c8b61581d3f701ffdf76d59d4f 100644 --- a/packages/piveau-hub-ui-modules/package.json +++ b/packages/piveau-hub-ui-modules/package.json @@ -7,15 +7,31 @@ "exports": { ".": { "types": "./dist/index.d.ts", + "import": "./dist/index", "default": "./dist/index" }, "./configSchema": { "types": "./dist/configSchema.d.ts", + "import": "./dist/configSchema.mjs", "default": "./dist/configSchema.mjs" }, + "./dataProviderInterface": { + "types": "./dist/data-provider-interface/index.d.ts", + "import": "./dist/dataProviderInterface.mjs", + "default": "./dist/dataProviderInterface.mjs" + }, "./styles": "./dist/piveau-hub-ui-modules.css" }, "types": "./dist/index.d.ts", + "typesVersions": { + "*": { + "*": [ + "./dist/index.d.ts", + "./dist/configSchema.d.ts", + "./dist/data-provider-interface/index.d.ts" + ] + } + }, "files": [ "dist" ], @@ -52,7 +68,7 @@ "@unhead/vue": "^1.8.8", "leaflet": "^1.9.4", "leaflet-easybutton": "^2.4.0", - "vue": "^3.3.8", + "vue": "^3.5.12", "vue-demi": "^0.13.1", "vue-router": "^4.1.6", "vuex": "^4.0.2", diff --git a/packages/piveau-hub-ui-modules/vite.config.ts b/packages/piveau-hub-ui-modules/vite.config.ts index 9314bcf619def5fd32a5c4a93bf9bec21cae970d..ff71a3daa93b95c28861eaf53190ab8aa66d2981 100644 --- a/packages/piveau-hub-ui-modules/vite.config.ts +++ b/packages/piveau-hub-ui-modules/vite.config.ts @@ -24,6 +24,7 @@ const peerDependencies = { "@triply/yasgui": "x.x.x", "@triply/yasqe": "x.x.x", "@triply/yasr": "x.x.x", + "@vueuse/router": "", "animejs": "x.x.x", "axios": "x.x.x", "bootstrap": "x.x.x", @@ -105,7 +106,7 @@ export default defineConfig({ ), copy({ targets: [ - { src: 'lib/scss', dest: 'dist' } + { src: 'lib/scss', dest: 'dist' }, ], hook: 'writeBundle', verbose: true, @@ -168,6 +169,7 @@ export default defineConfig({ lib: { entry: { 'index': path.resolve(__dirname, 'lib/index'), + 'dataProviderInterface': path.resolve(__dirname, 'lib/data-provider-interface/index'), // We build config schema as a separate module due to side effects in the main modules // for those who want to use consume the config schema in isolation 'configSchema': path.resolve(__dirname, 'lib/configurations/config-schema/index'), @@ -187,6 +189,8 @@ export default defineConfig({ vue: 'Vue' }, + chunkFileNames: 'piveau-hub-ui-modules.[ext]', + entryFileNames: (chunkInfo) => { // if chunkInfo.name starts with node_modules, replace with external if (chunkInfo.name.startsWith('node_modules')) {