diff --git a/CHANGELOG.md b/CHANGELOG.md index e429bd2a54..3311653ccb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,157 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [0.9.0](https://github.com/webern-unibas-ch/awg-app/compare/v0.8.5...v0.9.0) (2022-08-19) + + +### ⚠ BREAKING CHANGES + +* **edition:** The former workflow to display SVG files and overlays one-by-one is replaced by (re)drawing the SVGs with D3 library and creating the overlays automatically. + +### Features + +* **assets:** add classes in svg files of M 212 and M 317 Sk1 ([80721cc](https://github.com/webern-unibas-ch/awg-app/commit/80721ccf9521916227eb7d4f02df51bfa934f5cc); thanks to [@masthom](https://github.com/masthom)) +* **assets:** add classes to M 317 Sk5 ([11dd42f](https://github.com/webern-unibas-ch/awg-app/commit/11dd42f34669a2cfb3d57647515cab7988e9e7ac); thanks to [@masthom](https://github.com/masthom)) +* **assets:** add files for M 34 ([df94020](https://github.com/webern-unibas-ch/awg-app/commit/df94020abcc29c23ded489d3d519095261cae063), [411051f](https://github.com/webern-unibas-ch/awg-app/commit/411051faf30d9e8097af6fc1fa0ea8b441a45f1a), [bd0d342](https://github.com/webern-unibas-ch/awg-app/commit/bd0d342a089d463f34fc746cd983a2e4541566a3), [362d42b](https://github.com/webern-unibas-ch/awg-app/commit/362d42b3eae36f079fbe0f0ed571b5b5f07fcd02); thanks to [@chael-mi](https://github.com/chael-mi)) +* **assets:** add svgGroupId to textcritics files ([f983c61](https://github.com/webern-unibas-ch/awg-app/commit/f983c610cb0a74f0633780064a806317f0e64bf3)) +* **assets:** fix foliation in M 212 and add classes in some sketches of M 317 ([cd3cfd4](https://github.com/webern-unibas-ch/awg-app/commit/cd3cfd423ba20f8a2050044aaf744e5ae18b9c23); thanks to [@masthom](https://github.com/masthom)) +* **core:** add UtilityService for app-wide util functions ([6ef3654](https://github.com/webern-unibas-ch/awg-app/commit/6ef365467f487012d37f8bd01178ec5b8d4f7d77)) +* **edition:** activate M 34 in app ([404d780](https://github.com/webern-unibas-ch/awg-app/commit/404d78027f6736cbd7d42ae65a648bedb67a1e9a)) +* **edition:** add Clear button to SparqlEditor ([1b90f03](https://github.com/webern-unibas-ch/awg-app/commit/1b90f03f91df1e6ee357dda5f31090190cc9e428)) +* **edition:** add Clear button to TriplesEditor ([75a8dd9](https://github.com/webern-unibas-ch/awg-app/commit/75a8dd9f0b0d9f33dba76d619264a09031a65103)) +* **edition:** add ToastMessage class to ToastService ([ce101d4](https://github.com/webern-unibas-ch/awg-app/commit/ce101d41ef10a6a4f6f459006ab137dce59b89e0)) +* **edition:** extend overlay model with selection status ([49cbb2e](https://github.com/webern-unibas-ch/awg-app/commit/49cbb2e0ed35959e5c033f4d5f9b9ba97f349bec)) +* **edition:** generate source descriptions semi-automatically from JSON ([67ada7c](https://github.com/webern-unibas-ch/awg-app/commit/67ada7c5681702690082f93b08175522b910e703)) +* **edition:** use D3 library to render SVG sheets and overlays ([ba0676c](https://github.com/webern-unibas-ch/awg-app/commit/ba0676c00e7a774fd2ca0c871d32ff722db28693)) +* **shared:** add ViewHandleButtonGroup ([78a6c0c](https://github.com/webern-unibas-ch/awg-app/commit/78a6c0ce708bcac2f320f62f154a90a0fa1c68bf)) +* **shared:** create SliderConfig class ([b2f2ee3](https://github.com/webern-unibas-ch/awg-app/commit/b2f2ee3fa0339638df8aee905ad39f7213ac4165)) +* **shared:** integrate OrderByPipe lib as shared pipe (module issues) ([7ef2a2e](https://github.com/webern-unibas-ch/awg-app/commit/7ef2a2e51aaa3a8a5ad1e4d9bec073f22eee6a5c)) + + +### Bug Fixes + +* **app:** remove unused imports (SonarCloud) ([79fb6dd](https://github.com/webern-unibas-ch/awg-app/commit/79fb6dd433ace12e36a1406dd97ed229b801b57c)) +* **app:** remove unused variables ([376aae4](https://github.com/webern-unibas-ch/awg-app/commit/376aae4e24e4934c63c11d7800b4768a1081c472)) +* **assets:** add folio dimensions for M 34 ([8f168af](https://github.com/webern-unibas-ch/awg-app/commit/8f168af0dd8683e72a311f5ff253f520e93088f8)) +* **assets:** add g element to path242 in M 317 Sk2.1 ([2250314](https://github.com/webern-unibas-ch/awg-app/commit/2250314e3078daccc113cb6088fef0a5e2200b14)) +* **assets:** add links to svg files for M 317 in source-description.json ([0c186be](https://github.com/webern-unibas-ch/awg-app/commit/0c186bed27b100a5b7601c271b0e5c35b85be171); thanks to [@masthom](https://github.com/masthom)) +* **assets:** add links to svg files in textcritics.json ([1e5bbbd](https://github.com/webern-unibas-ch/awg-app/commit/1e5bbbd4195b3fec7c186daa1c22c1dadaeec6b2); thanks to [@masthom](https://github.com/masthom)) +* **assets:** add M number in source list for M 34 A ([7f601c1](https://github.com/webern-unibas-ch/awg-app/commit/7f601c10b67bc2169d8b2504fb2981dad625d198)) +* **assets:** add missing data files and placeholders for M 34 ([5b9427d](https://github.com/webern-unibas-ch/awg-app/commit/5b9427d559dcac38b89ff45d4f3e5517dfca0cb1)) +* **assets:** add missing svgGroupIds to op12 textcritics ([1b1b19d](https://github.com/webern-unibas-ch/awg-app/commit/1b1b19d2e1fb07f0602d4fb2d66fc4b3a2a2dd39)) +* **assets:** add missing svgGroupIds to op25 textcritics ([9f887ad](https://github.com/webern-unibas-ch/awg-app/commit/9f887ad64f43eda2926d266141fb3f2030017622)) +* **assets:** add svg ids to M 34 textcritics ([5fbbf4f](https://github.com/webern-unibas-ch/awg-app/commit/5fbbf4fcb8b3f9366a1edfacbfbeea522ba5766d)) +* **assets:** adjust source description for M 34 to new format ([f1946ec](https://github.com/webern-unibas-ch/awg-app/commit/f1946ec6dc6252e1f9d2eaa1c370ec210565d629)) +* **assets:** adjust svg-sheets.json and folio-convolute.json for new svg files ([3447c10](https://github.com/webern-unibas-ch/awg-app/commit/3447c10c204fb7912029072bd405eaa33f0abb98); thanks to [@masthom](https://github.com/masthom)) +* **assets:** adjust tabs in source descriptions ([7e4f66c](https://github.com/webern-unibas-ch/awg-app/commit/7e4f66cbe2c13328ae0b0a7023432d1b40bb50b6)) +* **assets:** fix typo in file name fpr M 34 Sk1b ([2fbe15b](https://github.com/webern-unibas-ch/awg-app/commit/2fbe15b57f3f45b9e1fc0531b20e7537a2cdc2f6)) +* **assets:** fix typos in textcritics of M 34 ([a4b6286](https://github.com/webern-unibas-ch/awg-app/commit/a4b62865fca524d43dfba42c4dd42abee2726417)) +* **assets:** link source descriptions of op19, 23 & 24 with svgs ([50e0b2f](https://github.com/webern-unibas-ch/awg-app/commit/50e0b2f407183b282cd75f87c1140155a81e2978)) +* **assets:** minor fixes (mainly tabs etc.) in source-decription and source-list of op12 and op25 ([54ee38c](https://github.com/webern-unibas-ch/awg-app/commit/54ee38c3d8604ac8d50259c67519cf42e6baf160); thanks to [@masthom](https://github.com/masthom)) +* **assets:** move inner svg group out of outer group ([82d603b](https://github.com/webern-unibas-ch/awg-app/commit/82d603b2d875b5f8ed10c099c856a139d6135883)) +* **assets:** remove deprecated SVGs ([104f951](https://github.com/webern-unibas-ch/awg-app/commit/104f951b3a2114d8d810578a4f8f3ea93485367d)) +* **assets:** update svg files for M 317 ([5272975](https://github.com/webern-unibas-ch/awg-app/commit/52729756f18c6f80851ce0240d594698ee063395); thanks to [@masthom](https://github.com/masthom)) +* **assets:** use RISM sigla for op19, 23 & 24 ([9a0d8de](https://github.com/webern-unibas-ch/awg-app/commit/9a0d8de1ca3b0b2794bc3b96350a5295db67126a)) +* **core:** adjust UtilityService to be stricter ([f6c9aaa](https://github.com/webern-unibas-ch/awg-app/commit/f6c9aaa3e0c5037be8026f8f0f27563c4b77cb4e)) +* **core:** highlight Edition link in navbar when active ([75867c6](https://github.com/webern-unibas-ch/awg-app/commit/75867c626127b74d407a79d4f6cee10d3c434a6d)) +* **edition:** add errorMessageRequest to SparqlEditor ([bd13db8](https://github.com/webern-unibas-ch/awg-app/commit/bd13db8e6a0059378cb97b6ffb22fa9324d88b88)) +* **edition:** add errorMessageRequest to TriplesEditor ([1625ae3](https://github.com/webern-unibas-ch/awg-app/commit/1625ae34dd94a909ab67a5a691beba4c7f179c83)) +* **edition:** compile html for all source description parts ([5dd3e8a](https://github.com/webern-unibas-ch/awg-app/commit/5dd3e8a62210f16a4b38c73a6cf3890df57998ee)) +* **edition:** do only prevent convolute selection if conv has modal link ([0ee0301](https://github.com/webern-unibas-ch/awg-app/commit/0ee0301e3a0cde94ab856660a56b063256f145a6)) +* **edition:** fix clearing order for svg ([c600bb1](https://github.com/webern-unibas-ch/awg-app/commit/c600bb1d4c981bc7ef9295776b9735493cb18113)) +* **edition:** fix container height of ForceGraph ([3d20803](https://github.com/webern-unibas-ch/awg-app/commit/3d208038ae1dd2abb8396cb16d5e8fbdf78a0973)) +* **edition:** iterate over tkkGroup nodes instead of artificial array ([915f056](https://github.com/webern-unibas-ch/awg-app/commit/915f0564e4234161cf2ea73752e38e34864d7b89)) +* **edition:** mute link boxes in svgs for now ([4007c42](https://github.com/webern-unibas-ch/awg-app/commit/4007c42f89fa6ad1d08cc7bb3cba38857a07cefb)) +* **edition:** remove EditionSvgSheetCmp from accolade module ([d46f24b](https://github.com/webern-unibas-ch/awg-app/commit/d46f24b8db2b0f47870d9a2a79c10468659a403f)) +* **edition:** remove unused import of EditionSvgOverlayTypes ([a10e6e8](https://github.com/webern-unibas-ch/awg-app/commit/a10e6e822b4553346120b8f96652ea52aa8044eb)) +* **edition:** use plural form for Edierte Notentexte ([30ab551](https://github.com/webern-unibas-ch/awg-app/commit/30ab551cb2d5ad183a5c5f33452094b7a4ad4702)) +* **edition:** use utility service in app ([f0c13d8](https://github.com/webern-unibas-ch/awg-app/commit/f0c13d841c17a5b1ac3960c210abfc689dde058a)) +* **edition:** use utility service to check for empty array ([d98a991](https://github.com/webern-unibas-ch/awg-app/commit/d98a991c15c2a5d72a338b03040cf8a0d2f1abd8)) +* **edition:** use ViewHandleButtonGroup in SearchResultList ([1a313f3](https://github.com/webern-unibas-ch/awg-app/commit/1a313f3234e6cda50da231808d5200a5d5227266)) +* **edition:** use ViewHandleButtonGroup in SparqlComponent ([47cfd44](https://github.com/webern-unibas-ch/awg-app/commit/47cfd449fa51afc3644cc49568b740caaeb1c3cf)) +* **shared:** add barrels for SliderConfig model class ([0255dab](https://github.com/webern-unibas-ch/awg-app/commit/0255dabb510c91b875720ab52d00f3b196d29b67)) +* **shared:** fix modal snippet for op12 ([87ec17c](https://github.com/webern-unibas-ch/awg-app/commit/87ec17c8e49749cbd743ffc5e6f57be7f4f56d1d)) + + +### Styles + +* **app:** adjust tab size ([dcc488f](https://github.com/webern-unibas-ch/awg-app/commit/dcc488fe506ec1833fd85701df61c162bd0081d3)) +* **app:** extend tab class ([2aa683a](https://github.com/webern-unibas-ch/awg-app/commit/2aa683a7007f90415f8f42a2a15aa69df0916a1f)) +* **app:** fix styles after bootstrap update ([94dcd83](https://github.com/webern-unibas-ch/awg-app/commit/94dcd83f0ffb7cfca8806981e1ee0bd261230f2e)) + + +### Documentation + +* **app:** fix typo in metadata ([43c87f3](https://github.com/webern-unibas-ch/awg-app/commit/43c87f30e722f0d9d2b07b0e0f38ec82c081db73)) +* **edition:** add docs for EditionSvgDrawingService ([0f0e94a](https://github.com/webern-unibas-ch/awg-app/commit/0f0e94a8eda440956d92a22ec93abb829037a2c8)) +* **shared:** fix incorrect line break in table docs ([fc77c3a](https://github.com/webern-unibas-ch/awg-app/commit/fc77c3a30ae25b275381718c7aca6e08655e4ebb)) +* **shared:** fix syntax for jsdocs in SliderConfig ([5fb19e7](https://github.com/webern-unibas-ch/awg-app/commit/5fb19e7c8986992b092c3d16aec2d32869f119a8)) + + +### Tests + +* **app:** fix file paths after changes ([b8b058d](https://github.com/webern-unibas-ch/awg-app/commit/b8b058dfe8e6d39bee4705029a55953572af6f39)) +* **core:** adjust tests for StorageService ([d634c96](https://github.com/webern-unibas-ch/awg-app/commit/d634c9625d0b2b7e2bcffb07efb460face65b4c5)) +* **core:** adjust tests for ToastService ([94d3a12](https://github.com/webern-unibas-ch/awg-app/commit/94d3a12fe052a17cb02b78ffd86d954b51a32045)) +* **edition:** add tests for SvgDrawingService (started) ([16caef1](https://github.com/webern-unibas-ch/awg-app/commit/16caef1ec85c7cd2601c3d00215593444128bc1b)) +* **edition:** add tests for TableComponent ([c425119](https://github.com/webern-unibas-ch/awg-app/commit/c4251192f2bac2461d9f6b76d3899727249f9c6c)) +* **edition:** add tests for TextCriticsListComponent ([8098c00](https://github.com/webern-unibas-ch/awg-app/commit/8098c005017b5ecd6f4d8fe39d57ec344ae4596b)) +* **edition:** adjust tests for GraphVisualizerComponent after changes ([1d701c0](https://github.com/webern-unibas-ch/awg-app/commit/1d701c091dbbe4cd4dd4c9df5cad1846e2ebe1b4)) +* **edition:** adjust tests for SourceDescriptionComp after changes ([5976390](https://github.com/webern-unibas-ch/awg-app/commit/5976390627285e616456c4403db8d66d5addd614)) +* **edition:** adjust tests for SparqlEditorComponent after changes ([fdbe9a0](https://github.com/webern-unibas-ch/awg-app/commit/fdbe9a0d554c28cc09be715e84c63b31f4c47c0b)) +* **edition:** adjust tests for SparqlEditorComponent after changes ([e5c4880](https://github.com/webern-unibas-ch/awg-app/commit/e5c4880e97e971bfcd07e5a4fdb9efba7cc68332)) +* **edition:** adjust tests for TriplesEditorComponent after changes ([456ddc5](https://github.com/webern-unibas-ch/awg-app/commit/456ddc523739adaae88e6b12cb04bce0edd6b0bc)) +* **edition:** fix accolade tests after changes ([4c230a7](https://github.com/webern-unibas-ch/awg-app/commit/4c230a7eae08d6f3cf247b6a845ec9f7cb946f92)) +* **edition:** fix tests after changes ([4d437cd](https://github.com/webern-unibas-ch/awg-app/commit/4d437cdccc576551a7c494698222d7c6434595f4)) +* **edition:** fix tests for GraphVisualizer after changes ([1402141](https://github.com/webern-unibas-ch/awg-app/commit/1402141e926ce43c07907fd3845fb83c0a2fce2d)) +* **edition:** fix typo in test for SparqlEditorCOmponent ([4cae2a1](https://github.com/webern-unibas-ch/awg-app/commit/4cae2a177acde0bb0dd2c46ff8664ecac2acc522)) +* **edition:** remove unused imports from test for EditionIntroComponent ([370930f](https://github.com/webern-unibas-ch/awg-app/commit/370930fc8be824bc6554e5e69cb847caee903096)) +* **search:** adjust tests for DataViewComponent ([d1f922a](https://github.com/webern-unibas-ch/awg-app/commit/d1f922a26412f18525ea703687fad33d6e70b658)) +* **search:** fix tests after changes in SearchParams ([8c8af2a](https://github.com/webern-unibas-ch/awg-app/commit/8c8af2adf9e3895adf39cf86d7d027604f735e2d)) +* **shared:** add tests for TablePaginationComponent ([a818bbb](https://github.com/webern-unibas-ch/awg-app/commit/a818bbb3d1313d17c0a9443b017adb4a147fe2a4)) +* **shared:** add tests for ViewHandleButtonGroup ([58349ac](https://github.com/webern-unibas-ch/awg-app/commit/58349acf33dcc966f846ca1730dd7e2d49c1c2ec)) +* **shared:** fix tests for OrderByPipe ([f7f2414](https://github.com/webern-unibas-ch/awg-app/commit/f7f2414693cd351fcdc18dd1b5b482537bcfff79)) + + +### Build System + +* **deps-dev:** bump @angular-devk/build-angular from 13.3.8 to 13.3.9 ([b5a35ea](https://github.com/webern-unibas-ch/awg-app/commit/b5a35ead4bc2e4989d33654d2409177b0653958e)) +* **deps-dev:** bump @angular/cli from 13.3.8 to 13.3.9 ([0771c25](https://github.com/webern-unibas-ch/awg-app/commit/0771c25d6200a3602a7454cb67b04586c32b1f7d)) +* **deps-dev:** bump @types/node from 16.11.41 to 16.11.49 ([5d8c662](https://github.com/webern-unibas-ch/awg-app/commit/5d8c662dd897fca2a6413e7733b0d13f4ac7a70b)) +* **deps-dev:** bump eslint related packages to latest version ([bbb3cd3](https://github.com/webern-unibas-ch/awg-app/commit/bbb3cd39b4de1b76529cb1923b3fc8b2db1f8287)) +* **deps-dev:** bump jasmine-core from 4.2.0 to 4.3.0 ([997634e](https://github.com/webern-unibas-ch/awg-app/commit/997634e0867c274a5aa28c102792bf724cf88653)) +* **deps:** add d3-fetch to dependencies ([635aa1f](https://github.com/webern-unibas-ch/awg-app/commit/635aa1f10c4fd25d69be95866288fbf9eff1e8e7)) +* **deps:** bump @fortawesome/fontawesome-svg-core from 6.1.1 to 6.1.2 ([b533058](https://github.com/webern-unibas-ch/awg-app/commit/b533058eed120cc647504f46cb59bc25b965f053)) +* **deps:** bump @fortawesome/free-solid-svg-icons from 6.1.1 to 6.1.2 ([d49d331](https://github.com/webern-unibas-ch/awg-app/commit/d49d331daa588ffeceed5eb66edf272d6b115b6c)) +* **deps:** bump bootstrap from 5.1.3 to 5.2.0 ([f667083](https://github.com/webern-unibas-ch/awg-app/commit/f667083ef07c52f4da44df371c856e5ec5c38394)) +* **deps:** bump codemirror from 5.65.6 to 5.65.7 ([6d9c17b](https://github.com/webern-unibas-ch/awg-app/commit/6d9c17b208a184513b477a98d6b08b3aaed75d06)) +* **deps:** bump custom rdfstore from 0.9.18-alpha.4 to -alpha.5 ([ba83f1f](https://github.com/webern-unibas-ch/awg-app/commit/ba83f1f16ede44f828f3a358f5e6dd43ef9a456f)) +* **deps:** bump rxjs from 7.5.5 to 7.5.6 ([df4132c](https://github.com/webern-unibas-ch/awg-app/commit/df4132c00c451ab213ece281821c5885536d1c9a)) +* **deps:** bump terser from 5.7.1 to 5.14.2 ([322d3b5](https://github.com/webern-unibas-ch/awg-app/commit/322d3b538d2ecea052300c46c9d917bb6bdab6cb)) +* **deps:** bump undici from 5.5.1 to 5.8.0 ([df097f9](https://github.com/webern-unibas-ch/awg-app/commit/df097f93e08d7710abfe29d343125e8bc8c27efb)) +* **deps:** bump undici from 5.8.0 to 5.9.1 ([8e893bf](https://github.com/webern-unibas-ch/awg-app/commit/8e893bf5c4b3837ab1ef7cea9fd940a5f8b0e9ea)) +* **deps:** bump zone.js from 0.11.6 to 0.11.7 ([e6007ff](https://github.com/webern-unibas-ch/awg-app/commit/e6007ff05b5ed08f0e57ffc3f0376084361bce10)) +* **deps:** bump zone.js from 0.11.7 to 0.11.8 ([ef553fe](https://github.com/webern-unibas-ch/awg-app/commit/ef553fea698da6e8f8d071dce8d47ddc3684951e)) + + +### Code Refactoring + +* **app:** use capital letters for prefix enum ([988bcce](https://github.com/webern-unibas-ch/awg-app/commit/988bcce95cfc06a1cc3e18448024ebcdd6c304c0)) +* **assets:** rename and reorganize files for M 34 ([f38b89b](https://github.com/webern-unibas-ch/awg-app/commit/f38b89bdd853ccf181b8516bf21ae90d333fe249)) +* **core:** move ToastService to shared ToastComponent ([b7e9e1f](https://github.com/webern-unibas-ch/awg-app/commit/b7e9e1f7a3f33bb8c01091d87d6354c8325d60eb)) +* **edition:** calculate container dimensions in separate method ([38b0bcd](https://github.com/webern-unibas-ch/awg-app/commit/38b0bcd90aab01b3065104868e93ae4aefb23ae6)) +* **edition:** move accolade and convolute into separate modules ([4a84c45](https://github.com/webern-unibas-ch/awg-app/commit/4a84c455b1aa13238272d8c9b32468d924ab02b9)) +* **edition:** move shared D3 models to general edition models ([638d308](https://github.com/webern-unibas-ch/awg-app/commit/638d3083c358fb7e7edf696513432736d21bd150)) +* **edition:** refactor TableComponent ([c1204af](https://github.com/webern-unibas-ch/awg-app/commit/c1204af55db728a9586d84b36019d17701bc6641)) +* **edition:** refactor ViewBox model to be used generic ([412b0fc](https://github.com/webern-unibas-ch/awg-app/commit/412b0fc5ecb0897e84c69f38359157ff54c6064b)) +* **edition:** rename createSVGOverlayGroup method ([582ef4d](https://github.com/webern-unibas-ch/awg-app/commit/582ef4d72966617d7e5601ceb806c8aaa57838fe)) +* **edition:** rename svgSheetRootSelection ([99ae616](https://github.com/webern-unibas-ch/awg-app/commit/99ae616a151b5ff4c7f5170741418b0a86509698)) +* **edition:** rename view -> viewType in SparqlEditor ([8e16652](https://github.com/webern-unibas-ch/awg-app/commit/8e166526f1baafc8efe3186aa17c6d6e886bd4aa)) +* **edition:** simplify retrieval of container coords in ForceGraph ([517ef78](https://github.com/webern-unibas-ch/awg-app/commit/517ef787b54401111ec3b8989a1dd7923ef6e7df)) +* **search:** use ViewHandleTypes for refactored SearchParams ([89bf082](https://github.com/webern-unibas-ch/awg-app/commit/89bf082fec64bb88822b4aab0e484935c0d8391d)) +* **shared:** rename formatInput method in TablePagination ([3179561](https://github.com/webern-unibas-ch/awg-app/commit/31795613fdc8cde4a145e515774d56544260f98a)) +* **shared:** rename OrderPipe -> OrderByPipe ([d836018](https://github.com/webern-unibas-ch/awg-app/commit/d836018a0ffdddf707544a8f6ffdfe39f4243fec)) + ### [0.8.5](https://github.com/webern-unibas-ch/awg-app/compare/v0.8.4...v0.8.5) (2022-07-01) diff --git a/package.json b/package.json index 8bfa309a63..0a3b975a44 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "awg-app", - "version": "0.8.5", + "version": "0.9.0", "license": "MIT", "author": { "name": "Stefan Münnich", @@ -90,14 +90,15 @@ "@angular/router": "~13.3.11", "@ctrl/ngx-codemirror": "^5.1.1", "@fortawesome/angular-fontawesome": "^0.10.2", - "@fortawesome/fontawesome-svg-core": "^6.1.1", - "@fortawesome/free-solid-svg-icons": "^6.1.1", + "@fortawesome/fontawesome-svg-core": "^6.1.2", + "@fortawesome/free-solid-svg-icons": "^6.1.2", "@kolkov/ngx-gallery": "2.0.1", "@ng-bootstrap/ng-bootstrap": "^12.1.2", "@popperjs/core": "^2.11.5", - "bootstrap": "^5.1.3", - "codemirror": "^5.65.6", + "bootstrap": "^5.2.0", + "codemirror": "^5.65.7", "d3-drag": "^3.0.0", + "d3-fetch": "^3.0.1", "d3-force": "^3.0.0", "d3-selection": "^3.0.0", "d3-zoom": "^3.0.0", @@ -105,42 +106,41 @@ "json2typescript": "^1.5.1", "n3": "^1.16.2", "ngx-json-viewer": "^3.1.0", - "ngx-order-pipe": "^2.2.0", - "rdfstore": "musicenfanthen/rdfstore-js#v0.9.18-alpha.4", - "rxjs": "~7.5.5", + "rdfstore": "musicenfanthen/rdfstore-js#v0.9.18-alpha.5", + "rxjs": "~7.5.6", "snapsvg": "^0.5.1", "tslib": "^2.4.0", - "zone.js": "~0.11.6" + "zone.js": "~0.11.8" }, "devDependencies": { - "@angular-devkit/build-angular": "~13.3.8", + "@angular-devkit/build-angular": "~13.3.9", "@angular-eslint/builder": "^13.5.0", "@angular-eslint/eslint-plugin": "^13.5.0", "@angular-eslint/eslint-plugin-template": "^13.5.0", "@angular-eslint/schematics": "^13.5.0", "@angular-eslint/template-parser": "^13.5.0", - "@angular/cli": "~13.3.8", + "@angular/cli": "~13.3.9", "@angular/compiler-cli": "~13.3.11", "@commitlint/cli": "^17.0.3", "@commitlint/config-angular": "^17.0.3", "@compodoc/compodoc": "^1.1.19", "@types/d3": "^7.4.0", "@types/jasmine": "~4.0.3", - "@types/node": "^16.11.41", - "@typescript-eslint/eslint-plugin": "^5.30.0", - "@typescript-eslint/parser": "^5.30.0", + "@types/node": "^16.11.49", + "@typescript-eslint/eslint-plugin": "^5.33.0", + "@typescript-eslint/parser": "^5.33.0", "angular-cli-ghpages": "^1.0.0", "conventional-recommended-bump": "^6.1.0", "cross-var": "^1.1.0", - "eslint": "^8.18.0", + "eslint": "^8.22.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-angular": "^4.1.0", "eslint-plugin-import": "^2.26.0", - "eslint-plugin-jsdoc": "^39.3.3", + "eslint-plugin-jsdoc": "^39.3.6", "eslint-plugin-prettier": "^4.2.1", "gzipper": "^7.1.0", "husky": "^8.0.1", - "jasmine-core": "~4.2.0", + "jasmine-core": "~4.3.0", "karma": "~6.4.0", "karma-chrome-launcher": "~3.1.1", "karma-coverage": "^2.2.0", diff --git a/src/app/app.globals.ts b/src/app/app.globals.ts index 8bea915da9..b9852ab4b1 100644 --- a/src/app/app.globals.ts +++ b/src/app/app.globals.ts @@ -1,15 +1,15 @@ // THIS IS AN AUTO-GENERATED FILE. DO NOT CHANGE IT MANUALLY! -// Generated last time on Fri Jul 1 11:18:25 2022 +// Generated last time on Fri Aug 19 15:39:14 2022 /** * The latest version of the AWG App */ -export const appVersion = '0.8.5'; +export const appVersion = '0.9.0'; /** * The release date of the latest version of the AWG App */ -export const appVersionReleaseDate = '01. Juli 2022'; +export const appVersionReleaseDate = '19. August 2022'; /** * The URL of the AWG App diff --git a/src/app/core/core-models/meta.model.ts b/src/app/core/core-models/meta.model.ts index bceaebc5aa..1ff1e0041f 100644 --- a/src/app/core/core-models/meta.model.ts +++ b/src/app/core/core-models/meta.model.ts @@ -1,7 +1,7 @@ /** * The MetaSectionTypes enumeration. * - * It stores the possible meta data section types. + * It stores the possible metadata section types. */ export enum MetaSectionTypes { page = 'page', @@ -12,7 +12,7 @@ export enum MetaSectionTypes { /** * The MetaPerson class. * - * It is used to store the meta data about a Person. + * It is used to store the metadata about a Person. */ export class MetaPerson { /** @@ -30,7 +30,7 @@ export class MetaPerson { * The MetaPage class. * * It is used in the context of the app framework - * to store the meta data for the main framework. + * to store the metadata for the main framework. */ export class MetaPage { /** @@ -83,7 +83,7 @@ export class MetaPage { * The MetaStructure class. * * It is used in the context of the structure view - * to store the meta data about the structure overview. + * to store the metadata about the structure overview. */ export class MetaStructure { /** @@ -101,7 +101,7 @@ export class MetaStructure { * The MetaContact class. * * It is used in the context of the contact view - * to store the meta data about contact information. + * to store the metadata about contact information. */ export class MetaContact { /** @@ -178,21 +178,21 @@ export class MetaContact { * The Meta class. * * It is used in the context of the app framework - * to store the meta data for different parts of the app. + * to store the metadata for different parts of the app. */ export class Meta { /** - * The meta data for the main app framework. + * The metadata for the main app framework. */ page: MetaPage; /** - * The meta data for the structure view. + * The metadata for the structure view. */ structure: MetaStructure; /** - * The meta data for the contact view. + * The metadata for the contact view. */ contact: MetaContact; } diff --git a/src/app/core/footer/footer-copyright/footer-copyright.component.ts b/src/app/core/footer/footer-copyright/footer-copyright.component.ts index 14e80ea7a7..d90347a8ee 100644 --- a/src/app/core/footer/footer-copyright/footer-copyright.component.ts +++ b/src/app/core/footer/footer-copyright/footer-copyright.component.ts @@ -17,7 +17,7 @@ export class FooterCopyrightComponent { /** * Input variable: pageMetaData. * - * It keeps the page meta data for the component. + * It keeps the page metadata for the component. */ @Input() pageMetaData: MetaPage; diff --git a/src/app/core/footer/footer-declaration/footer-declaration.component.ts b/src/app/core/footer/footer-declaration/footer-declaration.component.ts index 0bba97da8a..4d26445d85 100644 --- a/src/app/core/footer/footer-declaration/footer-declaration.component.ts +++ b/src/app/core/footer/footer-declaration/footer-declaration.component.ts @@ -18,7 +18,7 @@ export class FooterDeclarationComponent { /** * Input variable: pageMetaData. * - * It keeps the page meta data for the component. + * It keeps the page metadata for the component. */ @Input() pageMetaData: MetaPage; diff --git a/src/app/core/footer/footer.component.ts b/src/app/core/footer/footer.component.ts index f47cc7f3b5..295b4c146c 100644 --- a/src/app/core/footer/footer.component.ts +++ b/src/app/core/footer/footer.component.ts @@ -21,7 +21,7 @@ export class FooterComponent implements OnInit { /** * Public variable: pageMetaData. * - * It keeps the page meta data for the footer. + * It keeps the page metadata for the footer. */ pageMetaData: MetaPage; @@ -36,7 +36,7 @@ export class FooterComponent implements OnInit { * Constructor of the FooterComponent. * * It declares a private CoreService instance - * to get the meta data and logos. + * to get the metadata and logos. * * @param {CoreService} coreService Instance of the CoreService. */ @@ -56,7 +56,7 @@ export class FooterComponent implements OnInit { * Public method: provideMetaData. * * It calls the CoreService to provide - * the meta data and logos for the footer. + * the metadata and logos for the footer. * * @returns {void} Sets the pageMetaData and logos variables. */ diff --git a/src/app/core/navbar/navbar.component.html b/src/app/core/navbar/navbar.component.html index 35b2be50c3..1bfae8bba9 100644 --- a/src/app/core/navbar/navbar.component.html +++ b/src/app/core/navbar/navbar.component.html @@ -33,8 +33,8 @@ (current) -
Die Einleitungen, edierten Notentexte und Kritischen Berichte zu
erscheinen im Zusammenhang der vollständigen Edition der Vier Lieder op. 12 in AWG I/5.
', + 'Die Einleitungen, edierten Notentexte und Kritischen Berichte zu
erscheinen im Zusammenhang der vollständigen Edition der Vier Lieder op. 12 in AWG I/5.
', OP19_SOURCE_NOT_B: 'Die Beschreibung der Quellen A sowie C–D einschließlich der darin gegebenenfalls enthaltenen Korrekturen erfolgt im Zusammenhang der vollständigen Edition der Zwei Lieder für gemischten Chor und Ensemble op. 19 in AWG I/3.
', OP23_SOURCE_NOT_B: diff --git a/src/app/shared/order-by-pipe/order-by.pipe.spec.ts b/src/app/shared/order-by-pipe/order-by.pipe.spec.ts new file mode 100644 index 0000000000..4628fa5a0b --- /dev/null +++ b/src/app/shared/order-by-pipe/order-by.pipe.spec.ts @@ -0,0 +1,631 @@ +/** + * ********************************************** + * + * CREDITS + * + * This code is inspired, adapted or taken from: + * + * [**https://github.com/VadimDez/ngx-order-pipe**](https://github.com/VadimDez/ngx-order-pipe/blob/master/src/app/order-pipe/ngx-order.pipe.spec.ts) Build v2.2.0 on 12/11/2021, 14:45 MEZ + * + * + ************************************************/ + +import { OrderByPipe } from './order-by.pipe'; + +describe('OrderByPipe', () => { + let pipe: OrderByPipe; + + beforeEach(() => { + pipe = new OrderByPipe(); + }); + + it('create an instance', () => { + expect(pipe).toBeTruthy(); + }); + + it('should return empty array', () => { + expect(pipe.transform([], 'anything')).toEqual([]); + }); + + it('should work with not defined array as well', () => { + let array; + expect(pipe.transform(array, 'anything')).toEqual(array); + }); + + describe('without expression', () => { + it('should sort simple array', () => { + const array = [3, 2, 1]; + const sortedArray = [1, 2, 3]; + + expect(pipe.transform(array)).toEqual(sortedArray); + }); + + it('should sort simple chars array', () => { + const array = ['b', 'c', 'a']; + const sortedArray = ['a', 'b', 'c']; + + expect(pipe.transform(array)).toEqual(sortedArray); + }); + + it('should move `null` to the end', () => { + const array = [3, null, 1]; + const sortedArray = [1, 3, null]; + + expect(pipe.transform(array)).toEqual(sortedArray); + }); + + it('should move `undefind` to the end', () => { + const array = [3, undefined, 1]; + const sortedArray = [1, 3, undefined]; + + expect(pipe.transform(array)).toEqual(sortedArray); + }); + + it('should keep `NaN` as it is', () => { + const array = [3, NaN, 1]; + const sortedArray = [1, NaN, 3]; + + expect(pipe.transform(array)).toEqual(sortedArray); + }); + }); + + describe('if given expression is not found', () => { + it('should not sort simple array', () => { + const array = [3, 2, 1]; + const arraySorted = [3, 2, 1]; + + expect(pipe.transform(array, 'anything')).toEqual(arraySorted); + }); + + it('should not sort simple chars array', () => { + const array = ['c', 'b', 'a']; + const arraySorted = ['c', 'b', 'a']; + + expect(pipe.transform(array, 'anything')).toEqual(arraySorted); + }); + + it('should move empty string to the start', () => { + const array = ['c', '', 'a']; + const arraySorted = ['', 'c', 'a']; + + expect(pipe.transform(array, 'anything')).toEqual(arraySorted); + }); + + it('should keep `NaN` as it is', () => { + const array = [3, NaN, 1]; + const sortedArray = [1, NaN, 3]; + + expect(pipe.transform(array, 'anything')).toEqual(sortedArray); + }); + + it('should move `null` to the end', () => { + const array = [3, null, 1]; + const arraySorted = [3, 1, null]; + + expect(pipe.transform(array, 'anything')).toEqual(arraySorted); + }); + + it('should move `undefined` to the end', () => { + const array = [3, null, 1]; + const arraySorted = [3, 1, null]; + + expect(pipe.transform(array, 'anything')).toEqual(arraySorted); + }); + }); + + it('should return array with one element as it is', () => { + const array = [{ id: 1 }]; + expect(pipe.transform(array, 'id')).toEqual(array); + }); + + it('should return already sorted array as it is', () => { + const alreadySortedArray = [{ id: 1 }, { id: 2 }]; + expect(pipe.transform(alreadySortedArray, 'id')).toEqual(alreadySortedArray); + }); + + it('should order by id', () => { + const numbers = [{ id: 3 }, { id: 2 }, { id: 1 }]; + const sortedNumbers = [{ id: 1 }, { id: 2 }, { id: 3 }]; + + expect(pipe.transform(numbers, 'id')).toEqual(sortedNumbers); + }); + + it('should order by a', () => { + const arrayA = [{ a: 2 }, { a: null }, { a: 1 }, { a: 3 }]; + const arrayB = [{ a: 1 }, { a: 2 }, { a: 3 }, { a: null }]; + + expect(pipe.transform(arrayA, 'a')).toEqual(arrayB); + }); + + it('should sort strings too', () => { + const array = [{ string: 'abc' }, { string: 'aaa' }, { string: 'b' }]; + const arraySorted = [{ string: 'aaa' }, { string: 'abc' }, { string: 'b' }]; + + expect(pipe.transform(array, 'string')).toEqual(arraySorted); + }); + + it('should sort case-sensitive strings', () => { + const array = [{ string: 'Abc' }, { string: 'abc' }, { string: 'b' }, { string: 'B' }]; + const arraySorted = [{ string: 'Abc' }, { string: 'B' }, { string: 'abc' }, { string: 'b' }]; + + expect(pipe.transform(array, 'string', false, false)).toEqual(arraySorted); + }); + + it('should sort strings with case-sensitivity by default', () => { + const array = [{ string: 'Abc' }, { string: 'abc' }, { string: 'b' }, { string: 'B' }]; + const arraySorted = [{ string: 'Abc' }, { string: 'B' }, { string: 'abc' }, { string: 'b' }]; + + expect(pipe.transform(array, 'string', false)).toEqual(arraySorted); + }); + + it('should sort case-insensitive strings too', () => { + const array = [{ string: 'Abc' }, { string: 'aaa' }, { string: 'b' }]; + const arraySorted = [{ string: 'aaa' }, { string: 'Abc' }, { string: 'b' }]; + + expect(pipe.transform(array, 'string', false, true)).toEqual(arraySorted); + }); + + it('should not revert ordered array if `reverse=false`', () => { + const array = [{ value: 10 }, { value: 1 }, { value: 5 }]; + const arraySorted = [{ value: 1 }, { value: 5 }, { value: 10 }]; + + expect(pipe.transform(array, 'value', false)).toEqual(arraySorted); + }); + + it('should revert ordered array if `reverse=true`', () => { + const array = [{ value: 10 }, { value: 1 }, { value: 5 }]; + const arraySortedAndReverse = [{ value: 10 }, { value: 5 }, { value: 1 }]; + + expect(pipe.transform(array, 'value', true)).toEqual(arraySortedAndReverse); + }); + + it('should order arrays', () => { + const array = [{ values: [10, 0] }, { values: [1, 2] }, { values: [0, -1, 1] }]; + const arraySorted = [{ values: [0, -1, 1] }, { values: [1, 2] }, { values: [10, 0] }]; + + expect(pipe.transform(array, 'values')).toEqual(arraySorted); + }); + + it('should order nested elements', () => { + const object = { + b: { + c: [2, 1, 3], + d: ['h', 'ch'], + e: [{}, { f: 'g' }], + f: 'i', + }, + }; + const sortedObject = { + b: { + c: [1, 2, 3], + d: ['h', 'ch'], + e: [{}, { f: 'g' }], + f: 'i', + }, + }; + + expect(pipe.transform(object, 'b.c')).toEqual(sortedObject); + expect(pipe.transform(object, 'b.e[1].f')).toEqual(object); + expect(pipe.transform(object, 'b.e[2].f')).toEqual(object); + }); + + it('should not throw error on ordering "undefined" deep element', () => { + const object = { + b: { + e: [{}, { f: 'g' }], + }, + }; + + expect(pipe.transform(object, 'b.e[2].f')).toEqual(object); + }); + + it('should sort deep elements', () => { + const object = { + lists: { + users: [{ id: 3 }, { id: 2 }, { id: 1 }], + }, + }; + const objectSorted = { + lists: { + users: [{ id: 1 }, { id: 2 }, { id: 3 }], + }, + }; + + expect(pipe.transform(object, 'lists.users.id')).toEqual(objectSorted); + }); + + it('should sort array by deep prop', () => { + const arr = [ + { customer: { name: 'test' } }, + { customer: { name: 'b' } }, + { customer: { name: 'a' } }, + { customer: { name: 'c' } }, + ]; + + const res = [ + { customer: { name: 'a' } }, + { customer: { name: 'b' } }, + { customer: { name: 'c' } }, + { customer: { name: 'test' } }, + ]; + + expect(pipe.transform(arr, 'customer.name')).toEqual(res); + + const array = [ + { customer: { number: 25 } }, + { customer: { number: 5 } }, + { customer: { number: 0 } }, + { customer: { number: 15 } }, + { customer: { number: 1 } }, + ]; + + const result = [ + { customer: { number: 0 } }, + { customer: { number: 1 } }, + { customer: { number: 5 } }, + { customer: { number: 15 } }, + { customer: { number: 25 } }, + ]; + + expect(pipe.transform(array, 'customer.number')).toEqual(result); + }); + + it('should sort case-insensitive array by deep prop', () => { + const arr = [ + { customer: { name: 'test' } }, + { customer: { name: 'B' } }, + { customer: { name: 'a' } }, + { customer: { name: 'c' } }, + ]; + + const res = [ + { customer: { name: 'a' } }, + { customer: { name: 'B' } }, + { customer: { name: 'c' } }, + { customer: { name: 'test' } }, + ]; + + expect(pipe.transform(arr, 'customer.name', false, true)).toEqual(res); + }); + + it('should sort array with deep functions', () => { + function dupl(n: number): number { + return n * 2; + } + function dupl1(): number { + return dupl(1); + } + function dupl2(): number { + return dupl(2); + } + function dupl3(): number { + return dupl(3); + } + + const array = [{ customer: { fn: dupl3 } }, { customer: { fn: dupl2 } }, { customer: { fn: dupl1 } }]; + + const arraySorted = [{ customer: { fn: dupl1 } }, { customer: { fn: dupl2 } }, { customer: { fn: dupl3 } }]; + + expect(pipe.transform(array, 'customer.fn')).toEqual(arraySorted); + }); + + it('should keep same order', () => { + const collection = [ + { + name: 'John', + age: '25', + score: 12, + percent: '5.2%', + }, + { + name: 'Mark', + age: '19', + score: 12, + percent: 'No Value', + }, + { + name: 'Peter', + age: '21', + score: 12, + percent: '1.8%', + }, + ]; + + expect(pipe.transform(collection, 'score')).toEqual(collection); + }); + + it('should put `undefined` at the end', () => { + const collection = [{ a: { b: 3 } }, { a: { b: 1 } }, { a: undefined }, { a: { b: 2 } }]; + + const result = [{ a: { b: 1 } }, { a: { b: 2 } }, { a: { b: 3 } }, { a: undefined }]; + + expect(pipe.transform(collection, 'a.b')).toEqual(result); + }); + + describe('number-like strings', () => { + it('should compare two number-like strings', () => { + const el1 = '1'; + const el2 = '10'; + const arr = [el2, el1]; + const res = [el1, el2]; + + expect(pipe.transform(arr)).toEqual(res); + }); + + it('should compare two number-like strings with commas', () => { + const el1 = '$299,000'; + const el2 = '$1,100,000'; + const arr = [el2, el1]; + + expect(pipe.transform(arr)).toEqual(arr); + }); + }); + + describe('order with comparator', () => { + it('should return same order with "0"-comparator', () => { + const arr = [3, 2, 1]; + + expect(pipe.transform(arr, null, false, true, () => 0)).toEqual(arr); + }); + + it('should change the order with custom comparator', () => { + const arr = [3, 2, 1]; + const res = [1, 2, 3]; + + expect(pipe.transform(arr, null, false, true, (a, b) => (a > b ? 1 : -1))).toEqual(res); + }); + + it('should return change to order with custom comparator', () => { + const arr = ['$10,0', '$2,0', '$100,0']; + const res = ['$2,0', '$10,0', '$100,0']; + + const parse = value => parseInt(value.replace(/[^0-9]/g, ''), 10); + + expect( + pipe.transform(arr, null, false, true, (a, b) => { + const newA = parse(a); + const newB = parse(b); + return newA > newB ? 1 : -1; + }) + ).toEqual(res); + }); + + describe('test not valid values for comparator', () => { + const arr = [3, 2, 1]; + const res = [1, 2, 3]; + + it('should still work if comparator is null', () => { + expect(pipe.transform(arr, null, false, true, null)).toEqual(res); + }); + + it('should still work if comparator is undefined', () => { + expect(pipe.transform(arr, null, false, true, undefined)).toEqual(res); + expect(pipe.transform(arr, null, false, true, void 0)).toEqual(res); + }); + + it('should still work if comparator is not returning anything', () => { + expect(pipe.transform(arr, null, false, true, () => {})).toEqual(arr); + }); + }); + }); + + describe('multi field sort', () => { + it('should sort by multiple fields', () => { + const array = [ + { name: 'qwe', age: 1 }, + { name: 'asd', age: 3 }, + { name: 'asd', age: 2 }, + ]; + + const result = [ + { name: 'asd', age: 2 }, + { name: 'asd', age: 3 }, + { name: 'qwe', age: 1 }, + ]; + + expect(pipe.transform(array, ['name', 'age'])).toEqual(result); + }); + + it('should sort by multiple fields with case-insensitivity', () => { + const array = [ + { name: 'qwe', age: 1 }, + { name: 'asd', age: 3 }, + { name: 'Asd', age: 2 }, + ]; + + const result = [ + { name: 'asd', age: 3 }, + { name: 'Asd', age: 2 }, + { name: 'qwe', age: 1 }, + ]; + + expect(pipe.transform(array, ['name', 'age'], false, true)).toEqual(result); + }); + + it('should sort by multiple fields with case-sensitivity', () => { + const array = [ + { name: 'qwe', age: 1 }, + { name: 'asd', age: 3 }, + { name: 'Asd', age: 2 }, + ]; + + const result = [ + { name: 'Asd', age: 2 }, + { name: 'asd', age: 3 }, + { name: 'qwe', age: 1 }, + ]; + + expect(pipe.transform(array, ['name', 'age'], false, false)).toEqual(result); + }); + + it('should sort by multiple fields with case-sensitivity by default', () => { + const array = [ + { name: 'qwe', age: 1 }, + { name: 'asd', age: 3 }, + { name: 'Asd', age: 2 }, + ]; + + const result = [ + { name: 'Asd', age: 2 }, + { name: 'asd', age: 3 }, + { name: 'qwe', age: 1 }, + ]; + + expect(pipe.transform(array, ['name', 'age'], false, undefined)).toEqual(result); + }); + + describe('preserve sorting order', () => { + const array = [ + { group: 1, value: 2 }, + { group: 1, value: 1 }, + { group: 3, value: 1 }, + { group: 2, value: 2 }, + { group: 2, value: 1 }, + ]; + + it('should sort by multiple fields and preserve priority', () => { + const result = [ + { group: 1, value: 1 }, + { group: 1, value: 2 }, + { group: 2, value: 1 }, + { group: 2, value: 2 }, + { group: 3, value: 1 }, + ]; + + expect(pipe.transform(array, ['group', 'value'])).toEqual(result); + }); + + it('should sort by multiple fields and preserve priority (reversed)', () => { + const result = [ + { group: 1, value: 1 }, + { group: 2, value: 1 }, + { group: 3, value: 1 }, + { group: 1, value: 2 }, + { group: 2, value: 2 }, + ]; + + expect(pipe.transform(array, ['value', 'group'])).toEqual(result); + }); + }); + + it('should keep the same order if no sort fields are given', () => { + const array = [ + { name: 'q', age: 1 }, + { name: 'a', age: 3 }, + { name: 'a', age: 2 }, + ]; + + expect(pipe.transform(array, [])).toEqual(array); + }); + + it('should not modify original array', () => { + const array = [ + { key: 'a', value: 3 }, + { key: 'a', value: 1 }, + ]; + + const result = [ + { key: 'a', value: 1 }, + { key: 'a', value: 3 }, + ]; + + expect(pipe.transform(array, ['key', 'value'])).toEqual(result); + expect(array[0]).toEqual({ key: 'a', value: 3 }); + expect(array[1]).toEqual({ key: 'a', value: 1 }); + }); + }); + + describe('Booleans', () => { + it('should return same simple false array', () => { + const array = [{ value: false }]; + expect(pipe.transform(array, 'value')).toEqual(array); + }); + + it('should return same simple true array', () => { + const array = [{ value: true }]; + expect(pipe.transform(array, 'value')).toEqual(array); + }); + + it('should return sorted booleans as it is', () => { + const array = [{ value: false }, { value: true }]; + expect(pipe.transform(array, 'value')).toEqual(array); + }); + + it('should sort booleans', () => { + const array = [{ value: true }, { value: false }]; + const arraySorted = [{ value: false }, { value: true }]; + expect(pipe.transform(array, 'value')).toEqual(arraySorted); + }); + }); + + describe('Dates', () => { + it('should sort different dates', () => { + const a = { createdAt: new Date(1980, 11, 1, 0, 0, 0, 0) }; + const b = { createdAt: new Date(1980, 8, 2, 0, 0, 0, 0) }; + const c = { createdAt: new Date(1980, 10, 3, 0, 0, 0, 0) }; + const collection = [a, b, c]; + const result = [b, c, a]; + + expect(pipe.transform(collection, 'createdAt')).toEqual(result); + }); + + it('should sort dates', () => { + const a = { id: 1, info: { date: new Date(1980, 11, 31, 0, 0, 0, 0) } }; + const b = { id: 2, info: { date: new Date(1985, 8, 3, 0, 0, 0, 0) } }; + const c = { id: 3, info: { date: new Date(1978, 10, 12, 0, 0, 0, 0) } }; + const collection = [a, b, c]; + + const result = [c, a, b]; + + expect(pipe.transform(collection, 'info.date')).toEqual(result); + expect(pipe.transform(collection, 'info.date', true)).toEqual(result.reverse()); + }); + + it('should sort dates also including null as date', () => { + const a = { date: new Date(1980, 11, 31, 0, 0, 0, 0) }; + const b = { date: null }; + const c = { date: new Date(1978, 10, 12, 0, 0, 0, 0) }; + + const collection = [a, b, c]; + const result = [c, a, b]; + + expect(pipe.transform(collection, 'date')).toEqual(result); + expect(pipe.transform(collection, 'date', true)).toEqual(result.reverse()); + }); + + describe('multisort with dates', () => { + it('should sort dates equal dates', () => { + const a = { + info: { name: 'Adam', date: new Date(1978, 10, 12, 0, 0, 0, 0) }, + }; + const b = { + info: { name: 'Julie', date: new Date(1978, 10, 12, 0, 0, 0, 0) }, + }; + const collection = [b, a]; + + const result = [a, b]; + + expect(pipe.transform(collection, ['info.date', 'info.name'])).toEqual(result); + expect(pipe.transform(collection, ['info.date', 'info.name'], true)).toEqual(result); + }); + + it('should sort dates different dates', () => { + const a = { + info: { name: 'Adam', date: new Date(1970, 10, 12, 0, 0, 0, 0) }, + }; + const b = { + info: { name: 'Julie', date: new Date(1970, 11, 15, 0, 0, 0, 0) }, + }; + const c = { + info: { name: 'Julie', date: new Date(1970, 8, 15, 0, 0, 0, 0) }, + }; + const collection = [b, a, c]; + + const result = [c, a, b]; + + expect(pipe.transform(collection, ['info.date', 'info.name'])).toEqual(result); + expect(pipe.transform(collection, ['info.date', 'info.name'], true)).toEqual([b, a, c]); + }); + }); + }); +}); diff --git a/src/app/shared/order-by-pipe/order-by.pipe.ts b/src/app/shared/order-by-pipe/order-by.pipe.ts new file mode 100644 index 0000000000..70619ce4d2 --- /dev/null +++ b/src/app/shared/order-by-pipe/order-by.pipe.ts @@ -0,0 +1,270 @@ +/** + * ********************************************** + * + * CREDITS + * + * This code is inspired, adapted or taken from: + * + * [**https://github.com/VadimDez/ngx-order-pipe**](https://github.com/VadimDez/ngx-order-pipe/blob/master/src/app/order-pipe/ngx-order.pipe.ts) Build v2.2.0 on 12/11/2021, 14:45 MEZ + * + * + ************************************************/ + +import { Pipe, PipeTransform } from '@angular/core'; + +/** + * The OrderBy pipe. + * + * It declares a pipe that can be used in ngFor loops + * to order a given collection by a given field. + */ +@Pipe({ + name: 'orderBy', + pure: false, +}) +export class OrderByPipe implements PipeTransform { + /** + * Check if a value is a string + * + * @param value + */ + static isString(value: any): boolean { + return typeof value === 'string' || value instanceof String; + } + + /** + * Sorts values ignoring the case + * + * @param a + * @param b + */ + static caseInsensitiveSort(a: any, b: any) { + if (OrderByPipe.isString(a) && OrderByPipe.isString(b)) { + return a.localeCompare(b); + } + return OrderByPipe.defaultCompare(a, b); + } + + /** + * Default compare method + * + * @param a + * @param b + */ + static defaultCompare(a: any, b: any) { + if (a && a instanceof Date) { + a = a.getTime(); + } + if (b && b instanceof Date) { + b = b.getTime(); + } + + if (a === b) { + return 0; + } + if (a == null) { + return 1; + } + if (b == null) { + return -1; + } + return a > b ? 1 : -1; + } + + /** + * Parse expression, split into items + * @param expression + * @returns {string[]} + */ + static parseExpression(expression: string): string[] { + expression = expression.replace(/\[(\w+)\]/g, '.$1'); + expression = expression.replace(/^\./, ''); + return expression.split('.'); + } + + /** + * Get value by expression + * + * @param object + * @param expression + * @returns {any} + */ + static getValue(object: any, expression: string[]): any { + for (let i = 0, n = expression.length; i < n; ++i) { + if (!object) { + return; + } + const k = expression[i]; + if (!(k in object)) { + return; + } + if (typeof object[k] === 'function') { + object = object[k](); + } else { + object = object[k]; + } + } + + return object; + } + + /** + * Set value by expression + * + * @param object + * @param value + * @param expression + */ + static setValue(object: any, value: any, expression: string[]) { + let i; + for (i = 0; i < expression.length - 1; i++) { + object = object[expression[i]]; + } + + object[expression[i]] = value; + } + + transform( + value: any | any[], + expression?: any, + reverse?: boolean, + isCaseInsensitive: boolean = false, + comparator?: Function + ): any { + if (!value) { + return value; + } + + if (Array.isArray(expression)) { + return this._multiExpressionTransform(value, expression.slice(), reverse, isCaseInsensitive, comparator); + } + + if (Array.isArray(value)) { + return this._sortArray(value.slice(), expression, reverse, isCaseInsensitive, comparator); + } + + if (typeof value === 'object') { + return this._transformObject(Object.assign({}, value), expression, reverse, isCaseInsensitive, comparator); + } + + return value; + } + + /** + * Sort array, returns sorted array + * + * @param array + * @param expression + * @param reverse + * @param isCaseInsensitive + * @param comparator + * @returns {Type[]} + */ + private _sortArray