diff --git a/docs/release-notes-editor.txt b/docs/release-notes-editor.txt index 7e9ad46831d6f93fe0f82fba0519cd795e580ba8..ece0d660e6d090a1d6cf96ec578149fbf67f2d58 100644 --- a/docs/release-notes-editor.txt +++ b/docs/release-notes-editor.txt @@ -1,5 +1,71 @@ Editor ====== +1.32.0 +- Fix likert row generation +- Overhaul new element panel + Some new icons and element reordering. Button is now called + navigation button, because that is it's only purpose. +- Fix dropList element to read and register existing value IDs +- Disable autocomplete in TextEditor + This 'fixes' issues with wrong formatted lists. +- Add more quoting characters to TextEditor special characters menu +- Fix spaces around sub and sup elements in cloze + +1.31.1 +- Fix drag and drop with images +- Fix drag and drop out of cloze children + +1.31.0 +- Add section copy&paste functionality + Add 2 new menu buttons to the section menu. + One simply copies the selected section to the clipboard. + The other opens a dialog where a copied section can be pasted. The + section element's IDs are checked for availability and a warning may be + shown. + This feature has one caveat: When pasting a section into one that already + has elements in it, it will warn about duplicate IDs even though the IDs + would be free again when the old section is replaced. +- Implement 'copy on drop' for dropLists + With this setting elements are copied when being dropped to another list. + On drop, it is now also checked if the item ID is already present in the + target list. If it is, the drop event is silently discarded. This allows + putting items back in the list without creating duplicate IDs. + Lists with this setting: + - do show a placeholder of the items being dragged. This way it is + conveyed that the item will remain there after being dropped. + - don't show a placeholder when foreign items are hovered over them, to + avoid confusion with duplicate items. +- Add dynamic selection menu for connected drop lists + This shows all available drop lists (including cloze children). + This is pure QOL feature. Everything works the same as before, you just + do not have to enter the target drop list ID manually. +- Rework new element panel + No longer uses tabs but but a single column of grouped elements. + The grouping is supposed to reflect logical meaning of elements for + creating units instead of the system (ascending by technical + complexity) used before. + (This classification has been criticized heavily already and will probably + change again in the future. It still is an improvement and will be used + until better ideas are submitted.) +- Add tooltips to section menu + This is supposed to help finding the right one, as there are quite a lot + of buttons on there already. Also some icons are sub optimal. + This whole panel needs a rework. +- Improve properties panel style and layout + For example, add property groups (field sets) for some elements. + The image control of buttons is has a fixed size, scales properly + and show buttons only on hover. + Addes tooltip to action parameter of dropdown: When having selected + pageNav it shows a tooltip to get across that only valid pages can show up. + Increase the panel width slightly (by 20px). +- Move dynamic width parameter of toggle buttons to dimension properties panel +- Fix Frame element not saving changed properties +- Fix line height property of cloze element +- Improve alignment of all cloze child elements +- Fix cloze element and section containing cloze elements duplication + (IDs of children) +- Fix unclickable radio button label + 1.30.0 - Improve aligmnent of elements within cloze - Re-Add line-height property to radio button group diff --git a/docs/release-notes-player.txt b/docs/release-notes-player.txt index ef4462f534c6bcf33e95022f8a31331effe7370e..2aaca5fbf4b2dff9706cf0aa42dc001c112dd0d6 100644 --- a/docs/release-notes-player.txt +++ b/docs/release-notes-player.txt @@ -1,5 +1,24 @@ Player ====== +1.25.2 +- Fix the scroll position of input elements when + opening the virtual keyboard in small browser views + In small browser views elements are now scrolled + to the top of the view instead of the middle of the view + +1.25.1 +- Fix sending unit state values to the backend + +1.25.0 +- Scroll page when opening the virtual keyboard +- Scroll to section after it is unlocked +- Fix initialisation problems of elements in hidden sections +- Resolve response status issues after re-entering a unit +- Fix problems with restoring drop-list, radio, radio-group-images, + dropdown, toggle-button, likert-row values when re-entering the unit +- Reset the shift key after entering a capital letter +- Add software tests + 1.24.0 - Fix display of empty dynamic drop lists on iOS 14 diff --git a/package-lock.json b/package-lock.json index 482665bc0bd3cb043b73bfa4d3448e32d942c271..b7e325721b0cfe0e22a140792806184c38122c24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,18 +6,18 @@ "": { "name": "verona-modules-aspect", "dependencies": { - "@angular/animations": "~13.3.9", - "@angular/cdk": "~13.3.7", - "@angular/common": "~13.3.9", - "@angular/compiler": "~13.3.9", - "@angular/core": "~13.3.9", - "@angular/elements": "^13.3.9", - "@angular/flex-layout": "~13.0.0-beta.38", - "@angular/forms": "~13.3.9", - "@angular/material": "~13.3.6", - "@angular/platform-browser": "~13.3.9", - "@angular/platform-browser-dynamic": "~13.3.9", - "@angular/router": "~13.3.9", + "@angular/animations": "~13.3.0", + "@angular/cdk": "^12.2.13", + "@angular/common": "~13.3.0", + "@angular/compiler": "~13.3.0", + "@angular/core": "~13.3.0", + "@angular/elements": "^13.2.7", + "@angular/flex-layout": "^12.0.0-beta.35", + "@angular/forms": "~13.3.0", + "@angular/material": "^12.2.13", + "@angular/platform-browser": "~13.3.0", + "@angular/platform-browser-dynamic": "~13.3.0", + "@angular/router": "~13.3.0", "@ngx-translate/core": "^14.0.0", "@ngx-translate/http-loader": "^6.0.0", "@tiptap/core": "^2.0.0-beta.175", @@ -48,9 +48,9 @@ "zone.js": "~0.11.5" }, "devDependencies": { - "@angular-devkit/build-angular": "~13.3.6", - "@angular/cli": "~13.3.6", - "@angular/compiler-cli": "~13.3.9", + "@angular-devkit/build-angular": "~13.3.0", + "@angular/cli": "~13.3.0", + "@angular/compiler-cli": "~13.3.0", "@iqb/eslint-config": "^1.1.1", "@types/jasmine": "~3.6.0", "@types/node": "^12.11.7", @@ -80,12 +80,12 @@ } }, "node_modules/@angular-devkit/architect": { - "version": "0.1303.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.6.tgz", - "integrity": "sha512-Quh8KzO17PZH38mrDlBihrT6TioTnD8I+nSXuTZIqHwDpyFCTB9wBm9wC1J6HzMzEJ1GoYWEH/ukfbVpmj8ghw==", + "version": "0.1303.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.5.tgz", + "integrity": "sha512-ZF5Vul8UqwDSwYPxJ4YvdG7lmciJZ1nncyt9Dbk0swxw4MGdy0ZIf+91o318qUn/5JrttQ7ZCYoCZJCjYOSBtw==", "dev": true, "dependencies": { - "@angular-devkit/core": "13.3.6", + "@angular-devkit/core": "13.3.5", "rxjs": "6.6.7" }, "engines": { @@ -113,15 +113,15 @@ "dev": true }, "node_modules/@angular-devkit/build-angular": { - "version": "13.3.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.3.6.tgz", - "integrity": "sha512-VrEw9AhS0BBPRA59aZZYDXelXX8SdTfo7mW1gSeuvyeqk8RpMdjHWJPUmuvNJro6s5zOe4lc9dVrGxdevCbQFQ==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.3.5.tgz", + "integrity": "sha512-6ZQ788U0vT7KqMZeOsNQxP01IhOpxlbKonxK2fZNju8e+Ha2K77yV9A9XMbmcUGWRRHCOFvUEaJhvxDFsunESg==", "dev": true, "dependencies": { "@ampproject/remapping": "2.2.0", - "@angular-devkit/architect": "0.1303.6", - "@angular-devkit/build-webpack": "0.1303.6", - "@angular-devkit/core": "13.3.6", + "@angular-devkit/architect": "0.1303.5", + "@angular-devkit/build-webpack": "0.1303.5", + "@angular-devkit/core": "13.3.5", "@babel/core": "7.16.12", "@babel/generator": "7.16.8", "@babel/helper-annotate-as-pure": "7.16.7", @@ -132,9 +132,9 @@ "@babel/runtime": "7.16.7", "@babel/template": "7.16.7", "@discoveryjs/json-ext": "0.5.6", - "@ngtools/webpack": "13.3.6", + "@ngtools/webpack": "13.3.5", "ansi-colors": "4.1.1", - "babel-loader": "8.2.5", + "babel-loader": "8.2.3", "babel-plugin-istanbul": "6.1.1", "browserslist": "^4.9.1", "cacache": "15.3.0", @@ -247,12 +247,12 @@ "dev": true }, "node_modules/@angular-devkit/build-webpack": { - "version": "0.1303.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1303.6.tgz", - "integrity": "sha512-T2Uwt57RGPNH5OOblWcWd4Q4NaOK0f2jNTH29L6kSFVlYQ7kKCe9xo3YdzVdiY5b4w/hfnDlpGR+J3aSkkmBRw==", + "version": "0.1303.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1303.5.tgz", + "integrity": "sha512-EI7scRGKPw9Rg4LypUSTf7JM3lE1imTVxY8mY6gqNkRWnvsb5+kptJQ+gK+VZSom/URcPFbN40lJYwgmZBNPeA==", "dev": true, "dependencies": { - "@angular-devkit/architect": "0.1303.6", + "@angular-devkit/architect": "0.1303.5", "rxjs": "6.6.7" }, "engines": { @@ -284,9 +284,9 @@ "dev": true }, "node_modules/@angular-devkit/core": { - "version": "13.3.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.6.tgz", - "integrity": "sha512-ZmD586B+RnM2CG5+jbXh2NVfIydTc/yKSjppYDDOv4I530YBm6vpfZMwClpiNk6XLbMv7KqX4Tlr4wfxlPYYbA==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.5.tgz", + "integrity": "sha512-w7vzK4VoYP9rLgxJ2SwEfrkpKybdD+QgQZlsDBzT0C6Ebp7b4gkNcNVFo8EiZvfDl6Yplw2IAP7g7fs3STn0hQ==", "dev": true, "dependencies": { "ajv": "8.9.0", @@ -329,12 +329,12 @@ "dev": true }, "node_modules/@angular-devkit/schematics": { - "version": "13.3.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.6.tgz", - "integrity": "sha512-yLh5xc92C/FiaAp27coPiKWpSUmwoXF7vMxbJYJTyOXlt0mUITAEAwtrZQNr4yAxW/yvgTdyg7PhXaveQNTUuQ==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.5.tgz", + "integrity": "sha512-0N/kL/Vfx0yVAEwa3HYxNx9wYb+G9r1JrLjJQQzDp+z9LtcojNf7j3oey6NXrDUs1WjVZOa/AIdRl3/DuaoG5w==", "dev": true, "dependencies": { - "@angular-devkit/core": "13.3.6", + "@angular-devkit/core": "13.3.5", "jsonc-parser": "3.0.0", "magic-string": "0.25.7", "ora": "5.4.1", @@ -365,9 +365,9 @@ "dev": true }, "node_modules/@angular/animations": { - "version": "13.3.9", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-13.3.9.tgz", - "integrity": "sha512-PIspkNm1r7Uq1F3/c24mTQjCbKvl84Iy5kRmKOtjxp9uBGg1Dy+akw29hJt2FAqKa5yKFustBFCgWKypqgSoUQ==", + "version": "13.3.8", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-13.3.8.tgz", + "integrity": "sha512-zHQPFy2iW8Eqqm4vDTcri89zBg3UbSlOzREaUk1j6+ulFcNK50vmzqkUvcW/hy8x31+6VXkNvNDn2+Q2BWBxzA==", "dependencies": { "tslib": "^2.3.0" }, @@ -375,36 +375,36 @@ "node": "^12.20.0 || ^14.15.0 || >=16.10.0" }, "peerDependencies": { - "@angular/core": "13.3.9" + "@angular/core": "13.3.8" } }, "node_modules/@angular/cdk": { - "version": "13.3.7", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.7.tgz", - "integrity": "sha512-HtGqlrt4+ikbpzooF0LT/uMW6fgRJxLRUoOwkTY1oHhfNXhQaE2p8XEUH2qshl28aCIF8r8zrb6jpd4VqC+tyg==", + "version": "12.2.13", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-12.2.13.tgz", + "integrity": "sha512-zSKRhECyFqhingIeyRInIyTvYErt4gWo+x5DQr0b7YLUbU8DZSwWnG4w76Ke2s4U8T7ry1jpJBHoX/e8YBpGMg==", "dependencies": { - "tslib": "^2.3.0" + "tslib": "^2.2.0" }, "optionalDependencies": { "parse5": "^5.0.0" }, "peerDependencies": { - "@angular/common": "^13.0.0 || ^14.0.0-0", - "@angular/core": "^13.0.0 || ^14.0.0-0", - "rxjs": "^6.5.3 || ^7.4.0" + "@angular/common": "^12.0.0 || ^13.0.0-0", + "@angular/core": "^12.0.0 || ^13.0.0-0", + "rxjs": "^6.5.3 || ^7.0.0" } }, "node_modules/@angular/cli": { - "version": "13.3.6", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.3.6.tgz", - "integrity": "sha512-+OC7uspa8oDGQzcpml3DI8XyLvYurhSFhcmLPsyY/naHAV78NKSNf3dIWMPNozAioDzkZXPZXH0dwSdb+cOeQA==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.3.5.tgz", + "integrity": "sha512-FrPg86cfmm0arWZInt55muCTpcQSNlvoViVrIVkyqSN06GoyCAQ2zn6/OYJnx/XAg/XvXTbygL+58c0WXuOaiA==", "dev": true, "hasInstallScript": true, "dependencies": { - "@angular-devkit/architect": "0.1303.6", - "@angular-devkit/core": "13.3.6", - "@angular-devkit/schematics": "13.3.6", - "@schematics/angular": "13.3.6", + "@angular-devkit/architect": "0.1303.5", + "@angular-devkit/core": "13.3.5", + "@angular-devkit/schematics": "13.3.5", + "@schematics/angular": "13.3.5", "@yarnpkg/lockfile": "1.1.0", "ansi-colors": "4.1.1", "debug": "4.3.3", @@ -431,9 +431,9 @@ } }, "node_modules/@angular/common": { - "version": "13.3.9", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-13.3.9.tgz", - "integrity": "sha512-+bTleNL1XGlzuxLbVbsol82/33IW2pJasQN8ViraxKIElT2F8ooBJevIBMCSIcdvxvMBGAjn4ayQJr6tkPqPaw==", + "version": "13.3.8", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-13.3.8.tgz", + "integrity": "sha512-8GYa11PYC7Vj/bkNmzDqmpUyszAaJ565isKRDEQGcKWOB6k5OjnLT+nvMrKap7f347K2z+2qf+pXkgY8xXiDNA==", "dependencies": { "tslib": "^2.3.0" }, @@ -441,14 +441,14 @@ "node": "^12.20.0 || ^14.15.0 || >=16.10.0" }, "peerDependencies": { - "@angular/core": "13.3.9", + "@angular/core": "13.3.8", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/compiler": { - "version": "13.3.9", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-13.3.9.tgz", - "integrity": "sha512-fXmcN9PIUTJ9Vw2QyQE9vtW95K5ML9bVI7409Zf3DAQJEo4IhX2eUjgiGF3RtSn9Kdjj3tZY0xSbGGwp1hTBIA==", + "version": "13.3.8", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-13.3.8.tgz", + "integrity": "sha512-9g6prh4q6jbROskV+ZKDs0+gsllu8A3wYme/b54MHRc8Xdz9IeWAqwYFqUwuu2PZneR1ba0HmFtptTuzKnWGSQ==", "dependencies": { "tslib": "^2.3.0" }, @@ -457,9 +457,9 @@ } }, "node_modules/@angular/compiler-cli": { - "version": "13.3.9", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-13.3.9.tgz", - "integrity": "sha512-jf4iG7uqE1ZW2mJgKJyDyFby/sVQDlGOtUl68TMf8r06koWeny5nk/LhL7jp3Q6rgN2ZdYALtPTRSj7HVQD8pA==", + "version": "13.3.8", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-13.3.8.tgz", + "integrity": "sha512-DT3ux6OvWy4oL9kJPduQhFktKYmJKYWifPaEx6mCS3NOmWcMZLnpU03yC8mn+JFM2/ZHZs3m1Xn00hVzUmEPvg==", "dev": true, "dependencies": { "@babel/core": "^7.17.2", @@ -482,26 +482,26 @@ "node": "^12.20.0 || ^14.15.0 || >=16.10.0" }, "peerDependencies": { - "@angular/compiler": "13.3.9", + "@angular/compiler": "13.3.8", "typescript": ">=4.4.2 <4.7" } }, "node_modules/@angular/compiler-cli/node_modules/@babel/core": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.0.tgz", - "integrity": "sha512-Xyw74OlJwDijToNi0+6BBI5mLLR5+5R3bcSH80LXzjzEGEUlvNzujEE71BaD/ApEZHAvFI/Mlmp4M5lIkdeeWw==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.10.tgz", + "integrity": "sha512-liKoppandF3ZcBnIYFjfSDHZLKdLHGJRkoWtG8zQyGJBQfIYobpnVGI5+pLBNtS6psFLDzyq8+h5HiVljW9PNA==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.18.0", + "@babel/generator": "^7.17.10", "@babel/helper-compilation-targets": "^7.17.10", - "@babel/helper-module-transforms": "^7.18.0", - "@babel/helpers": "^7.18.0", - "@babel/parser": "^7.18.0", + "@babel/helper-module-transforms": "^7.17.7", + "@babel/helpers": "^7.17.9", + "@babel/parser": "^7.17.10", "@babel/template": "^7.16.7", - "@babel/traverse": "^7.18.0", - "@babel/types": "^7.18.0", + "@babel/traverse": "^7.17.10", + "@babel/types": "^7.17.10", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -526,33 +526,19 @@ } }, "node_modules/@angular/compiler-cli/node_modules/@babel/generator": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.0.tgz", - "integrity": "sha512-81YO9gGx6voPXlvYdZBliFXAZU8vZ9AZ6z+CjlmcnaeOcYSFbMTpdeDUO9xD9dh/68Vq03I8ZspfUTPfitcDHg==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.10.tgz", + "integrity": "sha512-46MJZZo9y3o4kmhBVc7zW7i8dtR1oIK/sdO5NcfcZRhTGYi+KKJRtHNgsU6c4VUcJmUNV/LQdebD/9Dlv4K+Tg==", "dev": true, "dependencies": { - "@babel/types": "^7.18.0", - "@jridgewell/gen-mapping": "^0.3.0", + "@babel/types": "^7.17.10", + "@jridgewell/gen-mapping": "^0.1.0", "jsesc": "^2.5.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@angular/compiler-cli/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz", - "integrity": "sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg==", - "dev": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@angular/compiler-cli/node_modules/magic-string": { "version": "0.26.2", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.2.tgz", @@ -566,9 +552,9 @@ } }, "node_modules/@angular/core": { - "version": "13.3.9", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-13.3.9.tgz", - "integrity": "sha512-LaY3yBDgN/efG11x/cwVeuC4gUG3YUMmk/sgAP3L1VGawYOiKoJ76decFpy6y4UgYPShQQRzpZQEVXF405jrLg==", + "version": "13.3.8", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-13.3.8.tgz", + "integrity": "sha512-FGQ+fvTm0QnNBKBamTJSzfBnxn+/gVBHT+2gpQtpax4LFP/6Z05N3AsJ2ZwM9mJwT4MH1HsEVJcXZCy7xXUXhg==", "dependencies": { "tslib": "^2.3.0" }, @@ -581,9 +567,9 @@ } }, "node_modules/@angular/elements": { - "version": "13.3.9", - "resolved": "https://registry.npmjs.org/@angular/elements/-/elements-13.3.9.tgz", - "integrity": "sha512-jD194V5BLSvls/+1SH0Qv2Btq8I5b6JVJ5DOPKVFzem52vY0Nuj2EPqHF4Cb5lzHxgvFKz58yHmtnuCInkrQtw==", + "version": "13.3.8", + "resolved": "https://registry.npmjs.org/@angular/elements/-/elements-13.3.8.tgz", + "integrity": "sha512-PftTRmESisMWIHNbZ/SbxxEjr36paYI1g76BhFGI8inijXrcPalU5rFTHzKPuJxVofdmB4+zcy8fEugMMN0pTA==", "dependencies": { "tslib": "^2.3.0" }, @@ -591,29 +577,29 @@ "node": "^12.20.0 || ^14.15.0 || >=16.10.0" }, "peerDependencies": { - "@angular/core": "13.3.9", + "@angular/core": "13.3.8", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/flex-layout": { - "version": "13.0.0-beta.38", - "resolved": "https://registry.npmjs.org/@angular/flex-layout/-/flex-layout-13.0.0-beta.38.tgz", - "integrity": "sha512-kcWb7CcoHbvw7fjo/knizWVmSSmvaTnr8v1ML6zOdxu1PK9UPPOcOS8RTm6fy61zoC2LABivP1/6Z2jF5XfpdQ==", + "version": "12.0.0-beta.35", + "resolved": "https://registry.npmjs.org/@angular/flex-layout/-/flex-layout-12.0.0-beta.35.tgz", + "integrity": "sha512-nPi2MGDFuCacwWHqxF/G7lUJd2X99HbLjjUvKXnyLwyCIVgH1sfS52su2wYbVYWJRqAVAB2/VMlrtW8Khr8hDA==", "dependencies": { - "tslib": "^2.3.0" + "tslib": "^2.1.0" }, "peerDependencies": { - "@angular/cdk": "^13.0.0", - "@angular/common": "^13.0.0", - "@angular/core": "^13.0.0", - "@angular/platform-browser": "^13.0.0", - "rxjs": "^6.5.3 || ^7.4.0" + "@angular/cdk": "^12.0.0", + "@angular/common": ">=12.0.0", + "@angular/core": ">=12.0.0", + "@angular/platform-browser": ">=12.0.0", + "rxjs": "^6.0.0 || ^7.0.0" } }, "node_modules/@angular/forms": { - "version": "13.3.9", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-13.3.9.tgz", - "integrity": "sha512-eNOsqMVrMsBceoAJ9pS+2qQDWsgwt62q7abqfYdzSdkjWbnLrtaIPP6iYMGQke1pIcdUSyoun29VsdQTSXXksg==", + "version": "13.3.8", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-13.3.8.tgz", + "integrity": "sha512-XG1uHUr7LfFKsE7cHfhOBj7f3xmuHWndBrHyqqNShpZBng35wiSktIWKHFL13IHeT+5J8WntR7/HpKwzKV84Vw==", "dependencies": { "tslib": "^2.3.0" }, @@ -621,33 +607,32 @@ "node": "^12.20.0 || ^14.15.0 || >=16.10.0" }, "peerDependencies": { - "@angular/common": "13.3.9", - "@angular/core": "13.3.9", - "@angular/platform-browser": "13.3.9", + "@angular/common": "13.3.8", + "@angular/core": "13.3.8", + "@angular/platform-browser": "13.3.8", "rxjs": "^6.5.3 || ^7.4.0" } }, "node_modules/@angular/material": { - "version": "13.3.7", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.7.tgz", - "integrity": "sha512-CXdLvohaxl3Nii6I70pEJX2FZRRkBPNiocUNP39hFHXf2PW/eNYCN8TS5DG2uOAaR2wyPwrXrU95mDjgvlqXqQ==", + "version": "12.2.13", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-12.2.13.tgz", + "integrity": "sha512-6g2GyN4qp2D+DqY2AwrQuPB3cd9gybvQVXvNRbTPXEulHr+LgGei00ySdFHFp6RvdGSMZ4i3LM1Fq3VkFxhCfQ==", "dependencies": { - "tslib": "^2.3.0" + "tslib": "^2.2.0" }, "peerDependencies": { - "@angular/animations": "^13.0.0 || ^14.0.0-0", - "@angular/cdk": "13.3.7", - "@angular/common": "^13.0.0 || ^14.0.0-0", - "@angular/core": "^13.0.0 || ^14.0.0-0", - "@angular/forms": "^13.0.0 || ^14.0.0-0", - "@angular/platform-browser": "^13.0.0 || ^14.0.0-0", - "rxjs": "^6.5.3 || ^7.4.0" + "@angular/animations": "^12.0.0 || ^13.0.0-0", + "@angular/cdk": "12.2.13", + "@angular/common": "^12.0.0 || ^13.0.0-0", + "@angular/core": "^12.0.0 || ^13.0.0-0", + "@angular/forms": "^12.0.0 || ^13.0.0-0", + "rxjs": "^6.5.3 || ^7.0.0" } }, "node_modules/@angular/platform-browser": { - "version": "13.3.9", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-13.3.9.tgz", - "integrity": "sha512-1l0IVYFbKCEfACR60bfLjH35BYP69CerIW9Ok58pedp0MIUsvPttBzUjM5HhW+3jhvNyO0cCMaK4r5kKTRMo4w==", + "version": "13.3.8", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-13.3.8.tgz", + "integrity": "sha512-QRkly2LL6aJJaO6Fw6VwuigtYxefukCJ9SS/jZI0rND94bUX9g4exHv87sQsN8LOUdFIVXXqXSY1Oy1eW0X+IQ==", "dependencies": { "tslib": "^2.3.0" }, @@ -655,9 +640,9 @@ "node": "^12.20.0 || ^14.15.0 || >=16.10.0" }, "peerDependencies": { - "@angular/animations": "13.3.9", - "@angular/common": "13.3.9", - "@angular/core": "13.3.9" + "@angular/animations": "13.3.8", + "@angular/common": "13.3.8", + "@angular/core": "13.3.8" }, "peerDependenciesMeta": { "@angular/animations": { @@ -666,9 +651,9 @@ } }, "node_modules/@angular/platform-browser-dynamic": { - "version": "13.3.9", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-13.3.9.tgz", - "integrity": "sha512-flyfoJG9vBSj3rmH/jUNaOPGfGlGHSj4v34OL16Qjk3M5bxbQKxBYNrDAUwk0Ve4S4qUfXF7ZDE0v1vUvn+3MA==", + "version": "13.3.8", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-13.3.8.tgz", + "integrity": "sha512-KWAfceZePkFZF9uePwIkoToM0LF4WPM/Ps1ijbeX0XaCFN7SFImoKfjiFEiRrRXjiCgKhPOJs69I0tTxiQA0VA==", "dependencies": { "tslib": "^2.3.0" }, @@ -676,16 +661,16 @@ "node": "^12.20.0 || ^14.15.0 || >=16.10.0" }, "peerDependencies": { - "@angular/common": "13.3.9", - "@angular/compiler": "13.3.9", - "@angular/core": "13.3.9", - "@angular/platform-browser": "13.3.9" + "@angular/common": "13.3.8", + "@angular/compiler": "13.3.8", + "@angular/core": "13.3.8", + "@angular/platform-browser": "13.3.8" } }, "node_modules/@angular/router": { - "version": "13.3.9", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-13.3.9.tgz", - "integrity": "sha512-M9j3ZscdRLsTnOLware1Yyw87JlRw/axoeZcN8JnTs3ltCc0+UMEJ22q0s8ud6j9GmYiA0+kmaM35OsEoHX72g==", + "version": "13.3.8", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-13.3.8.tgz", + "integrity": "sha512-iYiWezg+XCaBWgP/GCe8IOEtR4hcn0oGcnBZVNySD+gHBWH9o7QYDvDItCEpVKBLTwNaB0ftSIkkPzcW/eR+kA==", "dependencies": { "tslib": "^2.3.0" }, @@ -693,9 +678,9 @@ "node": "^12.20.0 || ^14.15.0 || >=16.10.0" }, "peerDependencies": { - "@angular/common": "13.3.9", - "@angular/core": "13.3.9", - "@angular/platform-browser": "13.3.9", + "@angular/common": "13.3.8", + "@angular/core": "13.3.8", + "@angular/platform-browser": "13.3.8", "rxjs": "^6.5.3 || ^7.4.0" } }, @@ -967,9 +952,9 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.0.tgz", - "integrity": "sha512-kclUYSUBIjlvnzN2++K9f2qzYKFgjmnmjwL4zlmU5f8ZtzgWe8s0rUPSTGy2HmK4P8T52MQsS+HTQAgZd3dMEA==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz", + "integrity": "sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==", "dependencies": { "@babel/helper-environment-visitor": "^7.16.7", "@babel/helper-module-imports": "^7.16.7", @@ -977,8 +962,8 @@ "@babel/helper-split-export-declaration": "^7.16.7", "@babel/helper-validator-identifier": "^7.16.7", "@babel/template": "^7.16.7", - "@babel/traverse": "^7.18.0", - "@babel/types": "^7.18.0" + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0" }, "engines": { "node": ">=6.9.0" @@ -1095,13 +1080,13 @@ } }, "node_modules/@babel/helpers": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.0.tgz", - "integrity": "sha512-AE+HMYhmlMIbho9nbvicHyxFwhrO+xhKB6AhRxzl8w46Yj0VXTZjEsAoBVC7rB2I0jzX+yWyVybnO08qkfx6kg==", + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.9.tgz", + "integrity": "sha512-cPCt915ShDWUEzEp3+UNRktO2n6v49l5RSnG9M5pS24hA+2FAc5si+Pn1i4VVbQQ+jh+bIZhPFQOJOzbrOYY1Q==", "dependencies": { "@babel/template": "^7.16.7", - "@babel/traverse": "^7.18.0", - "@babel/types": "^7.18.0" + "@babel/traverse": "^7.17.9", + "@babel/types": "^7.17.0" }, "engines": { "node": ">=6.9.0" @@ -1121,9 +1106,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.0.tgz", - "integrity": "sha512-AqDccGC+m5O/iUStSJy3DGRIUFu7WbY/CppZYwrEUB4N0tZlnI8CSTsgL7v5fHVFmUbRv2sd+yy27o8Ydt4MGg==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.10.tgz", + "integrity": "sha512-n2Q6i+fnJqzOaq2VkdXxy2TCPCWQZHiCo0XqmrCvDWcZQKRyZzYi4Z0yxlBuN0w+r2ZHmre+Q087DSrw3pbJDQ==", "bin": { "parser": "bin/babel-parser.js" }, @@ -2376,18 +2361,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.0.tgz", - "integrity": "sha512-oNOO4vaoIQoGjDQ84LgtF/IAlxlyqL4TUuoQ7xLkQETFaHkY1F7yazhB4Kt3VcZGL0ZF/jhrEpnXqUb0M7V3sw==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.10.tgz", + "integrity": "sha512-VmbrTHQteIdUUQNTb+zE12SHS/xQVIShmBPhlNP12hD5poF2pbITW1Z4172d03HegaQWhLffdkRJYtAzp0AGcw==", "dependencies": { "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.18.0", + "@babel/generator": "^7.17.10", "@babel/helper-environment-visitor": "^7.16.7", "@babel/helper-function-name": "^7.17.9", "@babel/helper-hoist-variables": "^7.16.7", "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.18.0", - "@babel/types": "^7.18.0", + "@babel/parser": "^7.17.10", + "@babel/types": "^7.17.10", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -2396,35 +2381,22 @@ } }, "node_modules/@babel/traverse/node_modules/@babel/generator": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.0.tgz", - "integrity": "sha512-81YO9gGx6voPXlvYdZBliFXAZU8vZ9AZ6z+CjlmcnaeOcYSFbMTpdeDUO9xD9dh/68Vq03I8ZspfUTPfitcDHg==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.10.tgz", + "integrity": "sha512-46MJZZo9y3o4kmhBVc7zW7i8dtR1oIK/sdO5NcfcZRhTGYi+KKJRtHNgsU6c4VUcJmUNV/LQdebD/9Dlv4K+Tg==", "dependencies": { - "@babel/types": "^7.18.0", - "@jridgewell/gen-mapping": "^0.3.0", + "@babel/types": "^7.17.10", + "@jridgewell/gen-mapping": "^0.1.0", "jsesc": "^2.5.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/traverse/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz", - "integrity": "sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg==", - "dependencies": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@babel/types": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.0.tgz", - "integrity": "sha512-vhAmLPAiC8j9K2GnsnLPCIH5wCrPpYIVBCWRBFDCB7Y/BXLqi/O+1RSTTM2bsmg6U/551+FCf9PNPxjABmxHTw==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.10.tgz", + "integrity": "sha512-9O26jG0mBYfGkUYCYZRnBwbVLd1UZOICEr2Em6InB6jVfsAv1GKgwXHmrSg+WFWDmeKTA6vyTZiN8tCSM5Oo3A==", "dependencies": { "@babel/helper-validator-identifier": "^7.16.7", "to-fast-properties": "^2.0.0" @@ -2658,7 +2630,6 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dev": true, "dependencies": { "@jridgewell/set-array": "^1.0.0", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -2671,6 +2642,7 @@ "version": "3.0.7", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz", "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==", + "dev": true, "engines": { "node": ">=6.0.0" } @@ -2692,6 +2664,7 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz", "integrity": "sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w==", + "dev": true, "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -2706,9 +2679,9 @@ } }, "node_modules/@ngtools/webpack": { - "version": "13.3.6", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.3.6.tgz", - "integrity": "sha512-QSdFtQIUgnDvM0EXFOpVRp5HTN0U4B9z60fHHfKKzxpNuU3z9EFzJoDUMZ8WabLXtWboUZWCSx0x3tdBt/sVQw==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.3.5.tgz", + "integrity": "sha512-OaMZR0rO0ljBHamLwzddfZX03ijtpheUpjH5dNzMNyNrrpKgS4/3jTQ1wvs2j3zzKfKjOS12WG0905QFJYWG6g==", "dev": true, "engines": { "node": "^12.20.0 || ^14.15.0 || >=16.10.0", @@ -2870,13 +2843,13 @@ } }, "node_modules/@schematics/angular": { - "version": "13.3.6", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-13.3.6.tgz", - "integrity": "sha512-BGBmIasjipBxQhV+UdN8B5P73SBXgBPkc7rcOK3Py+xpqMcoTWn290nqxIxLxRVmSeHLabE7+n1m3WnCumlm9Q==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-13.3.5.tgz", + "integrity": "sha512-1Ovx0cq72ZaNCyTyRD8ebIwUzpqhEH9ypWF05bfBLq3J0LlZgewIMhPJSxKmwRC3NQB5DZIYEvD0uhzBIuHCCA==", "dev": true, "dependencies": { - "@angular-devkit/core": "13.3.6", - "@angular-devkit/schematics": "13.3.6", + "@angular-devkit/core": "13.3.5", + "@angular-devkit/schematics": "13.3.5", "jsonc-parser": "3.0.0" }, "engines": { @@ -4455,13 +4428,13 @@ } }, "node_modules/babel-loader": { - "version": "8.2.5", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.5.tgz", - "integrity": "sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==", + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.3.tgz", + "integrity": "sha512-n4Zeta8NC3QAsuyiizu0GkmRcQ6clkV9WFUnUf1iXP//IeSKbWjofW3UHyZVwlOB4y039YQKefawyTn64Zwbuw==", "dev": true, "dependencies": { "find-cache-dir": "^3.3.1", - "loader-utils": "^2.0.0", + "loader-utils": "^1.4.0", "make-dir": "^3.1.0", "schema-utils": "^2.6.5" }, @@ -4473,18 +4446,30 @@ "webpack": ">=2" } }, + "node_modules/babel-loader/node_modules/json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, "node_modules/babel-loader/node_modules/loader-utils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", - "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", "dev": true, "dependencies": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", - "json5": "^2.1.2" + "json5": "^1.0.1" }, "engines": { - "node": ">=8.9.0" + "node": ">=4.0.0" } }, "node_modules/babel-plugin-dynamic-import-node": { @@ -6634,6 +6619,102 @@ "esbuild-windows-arm64": "0.14.22" } }, + "node_modules/esbuild-android-arm64": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.22.tgz", + "integrity": "sha512-k1Uu4uC4UOFgrnTj2zuj75EswFSEBK+H6lT70/DdS4mTAOfs2ECv2I9ZYvr3w0WL0T4YItzJdK7fPNxcPw6YmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-64": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.22.tgz", + "integrity": "sha512-d8Ceuo6Vw6HM3fW218FB6jTY6O3r2WNcTAU0SGsBkXZ3k8SDoRLd3Nrc//EqzdgYnzDNMNtrWegK2Qsss4THhw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-arm64": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.22.tgz", + "integrity": "sha512-YAt9Tj3SkIUkswuzHxkaNlT9+sg0xvzDvE75LlBo4DI++ogSgSmKNR6B4eUhU5EUUepVXcXdRIdqMq9ppeRqfw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-64": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.22.tgz", + "integrity": "sha512-ek1HUv7fkXMy87Qm2G4IRohN+Qux4IcnrDBPZGXNN33KAL0pEJJzdTv0hB/42+DCYWylSrSKxk3KUXfqXOoH4A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-arm64": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.22.tgz", + "integrity": "sha512-zPh9SzjRvr9FwsouNYTqgqFlsMIW07O8mNXulGeQx6O5ApgGUBZBgtzSlBQXkHi18WjrosYfsvp5nzOKiWzkjQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-32": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.22.tgz", + "integrity": "sha512-SnpveoE4nzjb9t2hqCIzzTWBM0RzcCINDMBB67H6OXIuDa4KqFqaIgmTchNA9pJKOVLVIKd5FYxNiJStli21qg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/esbuild-linux-64": { "version": "0.14.22", "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.22.tgz", @@ -6650,6 +6731,150 @@ "node": ">=12" } }, + "node_modules/esbuild-linux-arm": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.22.tgz", + "integrity": "sha512-soPDdbpt/C0XvOOK45p4EFt8HbH5g+0uHs5nUKjHVExfgR7du734kEkXR/mE5zmjrlymk5AA79I0VIvj90WZ4g==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm64": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.22.tgz", + "integrity": "sha512-8q/FRBJtV5IHnQChO3LHh/Jf7KLrxJ/RCTGdBvlVZhBde+dk3/qS9fFsUy+rs3dEi49aAsyVitTwlKw1SUFm+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-mips64le": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.22.tgz", + "integrity": "sha512-SiNDfuRXhGh1JQLLA9JPprBgPVFOsGuQ0yDfSPTNxztmVJd8W2mX++c4FfLpAwxuJe183mLuKf7qKCHQs5ZnBQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-ppc64le": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.22.tgz", + "integrity": "sha512-6t/GI9I+3o1EFm2AyN9+TsjdgWCpg2nwniEhjm2qJWtJyJ5VzTXGUU3alCO3evopu8G0hN2Bu1Jhz2YmZD0kng==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-riscv64": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.22.tgz", + "integrity": "sha512-AyJHipZKe88sc+tp5layovquw5cvz45QXw5SaDgAq2M911wLHiCvDtf/07oDx8eweCyzYzG5Y39Ih568amMTCQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-s390x": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.22.tgz", + "integrity": "sha512-Sz1NjZewTIXSblQDZWEFZYjOK6p8tV6hrshYdXZ0NHTjWE+lwxpOpWeElUGtEmiPcMT71FiuA9ODplqzzSxkzw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-netbsd-64": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.22.tgz", + "integrity": "sha512-TBbCtx+k32xydImsHxvFgsOCuFqCTGIxhzRNbgSL1Z2CKhzxwT92kQMhxort9N/fZM2CkRCPPs5wzQSamtzEHA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-openbsd-64": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.22.tgz", + "integrity": "sha512-vK912As725haT313ANZZZN+0EysEEQXWC/+YE4rQvOQzLuxAQc2tjbzlAFREx3C8+uMuZj/q7E5gyVB7TzpcTA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-sunos-64": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.22.tgz", + "integrity": "sha512-/mbJdXTW7MTcsPhtfDsDyPEOju9EOABvCjeUU2OJ7fWpX/Em/H3WYDa86tzLUbcVg++BScQDzqV/7RYw5XNY0g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/esbuild-wasm": { "version": "0.14.22", "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.14.22.tgz", @@ -6662,6 +6887,54 @@ "node": ">=12" } }, + "node_modules/esbuild-windows-32": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.22.tgz", + "integrity": "sha512-1vRIkuvPTjeSVK3diVrnMLSbkuE36jxA+8zGLUOrT4bb7E/JZvDRhvtbWXWaveUc/7LbhaNFhHNvfPuSw2QOQg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-64": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.22.tgz", + "integrity": "sha512-AxjIDcOmx17vr31C5hp20HIwz1MymtMjKqX4qL6whPj0dT9lwxPexmLj6G1CpR3vFhui6m75EnBEe4QL82SYqw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-arm64": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.22.tgz", + "integrity": "sha512-5wvQ+39tHmRhNpu2Fx04l7QfeK3mQ9tKzDqqGR8n/4WUxsFxnVLfDRBGirIfk4AfWlxk60kqirlODPoT5LqMUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -7787,6 +8060,20 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -10032,9 +10319,9 @@ } }, "node_modules/memfs": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.3.tgz", - "integrity": "sha512-eivjfi7Ahr6eQTn44nvTnR60e4a1Fs1Via2kCR5lHo/kyNoiMWaXCNJ/GpSd0ilXas2JSOl9B5FTIhflXu0hlg==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.1.tgz", + "integrity": "sha512-1c9VPVvW5P7I85c35zAdEr1TD5+F11IToIHIlrVIcflfnzPkJa0ZoYEoEdYDP8KgPFoSZ/opDrUsAoZWym3mtw==", "dev": true, "dependencies": { "fs-monkey": "1.0.3" @@ -15094,9 +15381,9 @@ } }, "node_modules/webpack-dev-server/node_modules/del": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/del/-/del-6.1.0.tgz", - "integrity": "sha512-OpcRktOt7G7HBfyxP0srBH4Djg4824EQORX8E1qvIhIzthNNArxxhrB/Mm7dRMiLi1nvFyUpDhzD2cTtbBhV8A==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz", + "integrity": "sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==", "dev": true, "dependencies": { "globby": "^11.0.1", @@ -15549,12 +15836,12 @@ } }, "@angular-devkit/architect": { - "version": "0.1303.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.6.tgz", - "integrity": "sha512-Quh8KzO17PZH38mrDlBihrT6TioTnD8I+nSXuTZIqHwDpyFCTB9wBm9wC1J6HzMzEJ1GoYWEH/ukfbVpmj8ghw==", + "version": "0.1303.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1303.5.tgz", + "integrity": "sha512-ZF5Vul8UqwDSwYPxJ4YvdG7lmciJZ1nncyt9Dbk0swxw4MGdy0ZIf+91o318qUn/5JrttQ7ZCYoCZJCjYOSBtw==", "dev": true, "requires": { - "@angular-devkit/core": "13.3.6", + "@angular-devkit/core": "13.3.5", "rxjs": "6.6.7" }, "dependencies": { @@ -15576,15 +15863,15 @@ } }, "@angular-devkit/build-angular": { - "version": "13.3.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.3.6.tgz", - "integrity": "sha512-VrEw9AhS0BBPRA59aZZYDXelXX8SdTfo7mW1gSeuvyeqk8RpMdjHWJPUmuvNJro6s5zOe4lc9dVrGxdevCbQFQ==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-13.3.5.tgz", + "integrity": "sha512-6ZQ788U0vT7KqMZeOsNQxP01IhOpxlbKonxK2fZNju8e+Ha2K77yV9A9XMbmcUGWRRHCOFvUEaJhvxDFsunESg==", "dev": true, "requires": { "@ampproject/remapping": "2.2.0", - "@angular-devkit/architect": "0.1303.6", - "@angular-devkit/build-webpack": "0.1303.6", - "@angular-devkit/core": "13.3.6", + "@angular-devkit/architect": "0.1303.5", + "@angular-devkit/build-webpack": "0.1303.5", + "@angular-devkit/core": "13.3.5", "@babel/core": "7.16.12", "@babel/generator": "7.16.8", "@babel/helper-annotate-as-pure": "7.16.7", @@ -15595,9 +15882,9 @@ "@babel/runtime": "7.16.7", "@babel/template": "7.16.7", "@discoveryjs/json-ext": "0.5.6", - "@ngtools/webpack": "13.3.6", + "@ngtools/webpack": "13.3.5", "ansi-colors": "4.1.1", - "babel-loader": "8.2.5", + "babel-loader": "8.2.3", "babel-plugin-istanbul": "6.1.1", "browserslist": "^4.9.1", "cacache": "15.3.0", @@ -15674,12 +15961,12 @@ } }, "@angular-devkit/build-webpack": { - "version": "0.1303.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1303.6.tgz", - "integrity": "sha512-T2Uwt57RGPNH5OOblWcWd4Q4NaOK0f2jNTH29L6kSFVlYQ7kKCe9xo3YdzVdiY5b4w/hfnDlpGR+J3aSkkmBRw==", + "version": "0.1303.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.1303.5.tgz", + "integrity": "sha512-EI7scRGKPw9Rg4LypUSTf7JM3lE1imTVxY8mY6gqNkRWnvsb5+kptJQ+gK+VZSom/URcPFbN40lJYwgmZBNPeA==", "dev": true, "requires": { - "@angular-devkit/architect": "0.1303.6", + "@angular-devkit/architect": "0.1303.5", "rxjs": "6.6.7" }, "dependencies": { @@ -15701,9 +15988,9 @@ } }, "@angular-devkit/core": { - "version": "13.3.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.6.tgz", - "integrity": "sha512-ZmD586B+RnM2CG5+jbXh2NVfIydTc/yKSjppYDDOv4I530YBm6vpfZMwClpiNk6XLbMv7KqX4Tlr4wfxlPYYbA==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-13.3.5.tgz", + "integrity": "sha512-w7vzK4VoYP9rLgxJ2SwEfrkpKybdD+QgQZlsDBzT0C6Ebp7b4gkNcNVFo8EiZvfDl6Yplw2IAP7g7fs3STn0hQ==", "dev": true, "requires": { "ajv": "8.9.0", @@ -15732,12 +16019,12 @@ } }, "@angular-devkit/schematics": { - "version": "13.3.6", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.6.tgz", - "integrity": "sha512-yLh5xc92C/FiaAp27coPiKWpSUmwoXF7vMxbJYJTyOXlt0mUITAEAwtrZQNr4yAxW/yvgTdyg7PhXaveQNTUuQ==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-13.3.5.tgz", + "integrity": "sha512-0N/kL/Vfx0yVAEwa3HYxNx9wYb+G9r1JrLjJQQzDp+z9LtcojNf7j3oey6NXrDUs1WjVZOa/AIdRl3/DuaoG5w==", "dev": true, "requires": { - "@angular-devkit/core": "13.3.6", + "@angular-devkit/core": "13.3.5", "jsonc-parser": "3.0.0", "magic-string": "0.25.7", "ora": "5.4.1", @@ -15762,32 +16049,32 @@ } }, "@angular/animations": { - "version": "13.3.9", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-13.3.9.tgz", - "integrity": "sha512-PIspkNm1r7Uq1F3/c24mTQjCbKvl84Iy5kRmKOtjxp9uBGg1Dy+akw29hJt2FAqKa5yKFustBFCgWKypqgSoUQ==", + "version": "13.3.8", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-13.3.8.tgz", + "integrity": "sha512-zHQPFy2iW8Eqqm4vDTcri89zBg3UbSlOzREaUk1j6+ulFcNK50vmzqkUvcW/hy8x31+6VXkNvNDn2+Q2BWBxzA==", "requires": { "tslib": "^2.3.0" } }, "@angular/cdk": { - "version": "13.3.7", - "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-13.3.7.tgz", - "integrity": "sha512-HtGqlrt4+ikbpzooF0LT/uMW6fgRJxLRUoOwkTY1oHhfNXhQaE2p8XEUH2qshl28aCIF8r8zrb6jpd4VqC+tyg==", + "version": "12.2.13", + "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-12.2.13.tgz", + "integrity": "sha512-zSKRhECyFqhingIeyRInIyTvYErt4gWo+x5DQr0b7YLUbU8DZSwWnG4w76Ke2s4U8T7ry1jpJBHoX/e8YBpGMg==", "requires": { "parse5": "^5.0.0", - "tslib": "^2.3.0" + "tslib": "^2.2.0" } }, "@angular/cli": { - "version": "13.3.6", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.3.6.tgz", - "integrity": "sha512-+OC7uspa8oDGQzcpml3DI8XyLvYurhSFhcmLPsyY/naHAV78NKSNf3dIWMPNozAioDzkZXPZXH0dwSdb+cOeQA==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-13.3.5.tgz", + "integrity": "sha512-FrPg86cfmm0arWZInt55muCTpcQSNlvoViVrIVkyqSN06GoyCAQ2zn6/OYJnx/XAg/XvXTbygL+58c0WXuOaiA==", "dev": true, "requires": { - "@angular-devkit/architect": "0.1303.6", - "@angular-devkit/core": "13.3.6", - "@angular-devkit/schematics": "13.3.6", - "@schematics/angular": "13.3.6", + "@angular-devkit/architect": "0.1303.5", + "@angular-devkit/core": "13.3.5", + "@angular-devkit/schematics": "13.3.5", + "@schematics/angular": "13.3.5", "@yarnpkg/lockfile": "1.1.0", "ansi-colors": "4.1.1", "debug": "4.3.3", @@ -15806,25 +16093,25 @@ } }, "@angular/common": { - "version": "13.3.9", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-13.3.9.tgz", - "integrity": "sha512-+bTleNL1XGlzuxLbVbsol82/33IW2pJasQN8ViraxKIElT2F8ooBJevIBMCSIcdvxvMBGAjn4ayQJr6tkPqPaw==", + "version": "13.3.8", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-13.3.8.tgz", + "integrity": "sha512-8GYa11PYC7Vj/bkNmzDqmpUyszAaJ565isKRDEQGcKWOB6k5OjnLT+nvMrKap7f347K2z+2qf+pXkgY8xXiDNA==", "requires": { "tslib": "^2.3.0" } }, "@angular/compiler": { - "version": "13.3.9", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-13.3.9.tgz", - "integrity": "sha512-fXmcN9PIUTJ9Vw2QyQE9vtW95K5ML9bVI7409Zf3DAQJEo4IhX2eUjgiGF3RtSn9Kdjj3tZY0xSbGGwp1hTBIA==", + "version": "13.3.8", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-13.3.8.tgz", + "integrity": "sha512-9g6prh4q6jbROskV+ZKDs0+gsllu8A3wYme/b54MHRc8Xdz9IeWAqwYFqUwuu2PZneR1ba0HmFtptTuzKnWGSQ==", "requires": { "tslib": "^2.3.0" } }, "@angular/compiler-cli": { - "version": "13.3.9", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-13.3.9.tgz", - "integrity": "sha512-jf4iG7uqE1ZW2mJgKJyDyFby/sVQDlGOtUl68TMf8r06koWeny5nk/LhL7jp3Q6rgN2ZdYALtPTRSj7HVQD8pA==", + "version": "13.3.8", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-13.3.8.tgz", + "integrity": "sha512-DT3ux6OvWy4oL9kJPduQhFktKYmJKYWifPaEx6mCS3NOmWcMZLnpU03yC8mn+JFM2/ZHZs3m1Xn00hVzUmEPvg==", "dev": true, "requires": { "@babel/core": "^7.17.2", @@ -15840,21 +16127,21 @@ }, "dependencies": { "@babel/core": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.0.tgz", - "integrity": "sha512-Xyw74OlJwDijToNi0+6BBI5mLLR5+5R3bcSH80LXzjzEGEUlvNzujEE71BaD/ApEZHAvFI/Mlmp4M5lIkdeeWw==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.17.10.tgz", + "integrity": "sha512-liKoppandF3ZcBnIYFjfSDHZLKdLHGJRkoWtG8zQyGJBQfIYobpnVGI5+pLBNtS6psFLDzyq8+h5HiVljW9PNA==", "dev": true, "requires": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.18.0", + "@babel/generator": "^7.17.10", "@babel/helper-compilation-targets": "^7.17.10", - "@babel/helper-module-transforms": "^7.18.0", - "@babel/helpers": "^7.18.0", - "@babel/parser": "^7.18.0", + "@babel/helper-module-transforms": "^7.17.7", + "@babel/helpers": "^7.17.9", + "@babel/parser": "^7.17.10", "@babel/template": "^7.16.7", - "@babel/traverse": "^7.18.0", - "@babel/types": "^7.18.0", + "@babel/traverse": "^7.17.10", + "@babel/types": "^7.17.10", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -15871,27 +16158,16 @@ } }, "@babel/generator": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.0.tgz", - "integrity": "sha512-81YO9gGx6voPXlvYdZBliFXAZU8vZ9AZ6z+CjlmcnaeOcYSFbMTpdeDUO9xD9dh/68Vq03I8ZspfUTPfitcDHg==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.10.tgz", + "integrity": "sha512-46MJZZo9y3o4kmhBVc7zW7i8dtR1oIK/sdO5NcfcZRhTGYi+KKJRtHNgsU6c4VUcJmUNV/LQdebD/9Dlv4K+Tg==", "dev": true, "requires": { - "@babel/types": "^7.18.0", - "@jridgewell/gen-mapping": "^0.3.0", + "@babel/types": "^7.17.10", + "@jridgewell/gen-mapping": "^0.1.0", "jsesc": "^2.5.1" } }, - "@jridgewell/gen-mapping": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz", - "integrity": "sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, "magic-string": { "version": "0.26.2", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.2.tgz", @@ -15904,65 +16180,65 @@ } }, "@angular/core": { - "version": "13.3.9", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-13.3.9.tgz", - "integrity": "sha512-LaY3yBDgN/efG11x/cwVeuC4gUG3YUMmk/sgAP3L1VGawYOiKoJ76decFpy6y4UgYPShQQRzpZQEVXF405jrLg==", + "version": "13.3.8", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-13.3.8.tgz", + "integrity": "sha512-FGQ+fvTm0QnNBKBamTJSzfBnxn+/gVBHT+2gpQtpax4LFP/6Z05N3AsJ2ZwM9mJwT4MH1HsEVJcXZCy7xXUXhg==", "requires": { "tslib": "^2.3.0" } }, "@angular/elements": { - "version": "13.3.9", - "resolved": "https://registry.npmjs.org/@angular/elements/-/elements-13.3.9.tgz", - "integrity": "sha512-jD194V5BLSvls/+1SH0Qv2Btq8I5b6JVJ5DOPKVFzem52vY0Nuj2EPqHF4Cb5lzHxgvFKz58yHmtnuCInkrQtw==", + "version": "13.3.8", + "resolved": "https://registry.npmjs.org/@angular/elements/-/elements-13.3.8.tgz", + "integrity": "sha512-PftTRmESisMWIHNbZ/SbxxEjr36paYI1g76BhFGI8inijXrcPalU5rFTHzKPuJxVofdmB4+zcy8fEugMMN0pTA==", "requires": { "tslib": "^2.3.0" } }, "@angular/flex-layout": { - "version": "13.0.0-beta.38", - "resolved": "https://registry.npmjs.org/@angular/flex-layout/-/flex-layout-13.0.0-beta.38.tgz", - "integrity": "sha512-kcWb7CcoHbvw7fjo/knizWVmSSmvaTnr8v1ML6zOdxu1PK9UPPOcOS8RTm6fy61zoC2LABivP1/6Z2jF5XfpdQ==", + "version": "12.0.0-beta.35", + "resolved": "https://registry.npmjs.org/@angular/flex-layout/-/flex-layout-12.0.0-beta.35.tgz", + "integrity": "sha512-nPi2MGDFuCacwWHqxF/G7lUJd2X99HbLjjUvKXnyLwyCIVgH1sfS52su2wYbVYWJRqAVAB2/VMlrtW8Khr8hDA==", "requires": { - "tslib": "^2.3.0" + "tslib": "^2.1.0" } }, "@angular/forms": { - "version": "13.3.9", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-13.3.9.tgz", - "integrity": "sha512-eNOsqMVrMsBceoAJ9pS+2qQDWsgwt62q7abqfYdzSdkjWbnLrtaIPP6iYMGQke1pIcdUSyoun29VsdQTSXXksg==", + "version": "13.3.8", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-13.3.8.tgz", + "integrity": "sha512-XG1uHUr7LfFKsE7cHfhOBj7f3xmuHWndBrHyqqNShpZBng35wiSktIWKHFL13IHeT+5J8WntR7/HpKwzKV84Vw==", "requires": { "tslib": "^2.3.0" } }, "@angular/material": { - "version": "13.3.7", - "resolved": "https://registry.npmjs.org/@angular/material/-/material-13.3.7.tgz", - "integrity": "sha512-CXdLvohaxl3Nii6I70pEJX2FZRRkBPNiocUNP39hFHXf2PW/eNYCN8TS5DG2uOAaR2wyPwrXrU95mDjgvlqXqQ==", + "version": "12.2.13", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-12.2.13.tgz", + "integrity": "sha512-6g2GyN4qp2D+DqY2AwrQuPB3cd9gybvQVXvNRbTPXEulHr+LgGei00ySdFHFp6RvdGSMZ4i3LM1Fq3VkFxhCfQ==", "requires": { - "tslib": "^2.3.0" + "tslib": "^2.2.0" } }, "@angular/platform-browser": { - "version": "13.3.9", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-13.3.9.tgz", - "integrity": "sha512-1l0IVYFbKCEfACR60bfLjH35BYP69CerIW9Ok58pedp0MIUsvPttBzUjM5HhW+3jhvNyO0cCMaK4r5kKTRMo4w==", + "version": "13.3.8", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-13.3.8.tgz", + "integrity": "sha512-QRkly2LL6aJJaO6Fw6VwuigtYxefukCJ9SS/jZI0rND94bUX9g4exHv87sQsN8LOUdFIVXXqXSY1Oy1eW0X+IQ==", "requires": { "tslib": "^2.3.0" } }, "@angular/platform-browser-dynamic": { - "version": "13.3.9", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-13.3.9.tgz", - "integrity": "sha512-flyfoJG9vBSj3rmH/jUNaOPGfGlGHSj4v34OL16Qjk3M5bxbQKxBYNrDAUwk0Ve4S4qUfXF7ZDE0v1vUvn+3MA==", + "version": "13.3.8", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-13.3.8.tgz", + "integrity": "sha512-KWAfceZePkFZF9uePwIkoToM0LF4WPM/Ps1ijbeX0XaCFN7SFImoKfjiFEiRrRXjiCgKhPOJs69I0tTxiQA0VA==", "requires": { "tslib": "^2.3.0" } }, "@angular/router": { - "version": "13.3.9", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-13.3.9.tgz", - "integrity": "sha512-M9j3ZscdRLsTnOLware1Yyw87JlRw/axoeZcN8JnTs3ltCc0+UMEJ22q0s8ud6j9GmYiA0+kmaM35OsEoHX72g==", + "version": "13.3.8", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-13.3.8.tgz", + "integrity": "sha512-iYiWezg+XCaBWgP/GCe8IOEtR4hcn0oGcnBZVNySD+gHBWH9o7QYDvDItCEpVKBLTwNaB0ftSIkkPzcW/eR+kA==", "requires": { "tslib": "^2.3.0" } @@ -16167,9 +16443,9 @@ } }, "@babel/helper-module-transforms": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.18.0.tgz", - "integrity": "sha512-kclUYSUBIjlvnzN2++K9f2qzYKFgjmnmjwL4zlmU5f8ZtzgWe8s0rUPSTGy2HmK4P8T52MQsS+HTQAgZd3dMEA==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz", + "integrity": "sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==", "requires": { "@babel/helper-environment-visitor": "^7.16.7", "@babel/helper-module-imports": "^7.16.7", @@ -16177,8 +16453,8 @@ "@babel/helper-split-export-declaration": "^7.16.7", "@babel/helper-validator-identifier": "^7.16.7", "@babel/template": "^7.16.7", - "@babel/traverse": "^7.18.0", - "@babel/types": "^7.18.0" + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0" } }, "@babel/helper-optimise-call-expression": { @@ -16262,13 +16538,13 @@ } }, "@babel/helpers": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.0.tgz", - "integrity": "sha512-AE+HMYhmlMIbho9nbvicHyxFwhrO+xhKB6AhRxzl8w46Yj0VXTZjEsAoBVC7rB2I0jzX+yWyVybnO08qkfx6kg==", + "version": "7.17.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.9.tgz", + "integrity": "sha512-cPCt915ShDWUEzEp3+UNRktO2n6v49l5RSnG9M5pS24hA+2FAc5si+Pn1i4VVbQQ+jh+bIZhPFQOJOzbrOYY1Q==", "requires": { "@babel/template": "^7.16.7", - "@babel/traverse": "^7.18.0", - "@babel/types": "^7.18.0" + "@babel/traverse": "^7.17.9", + "@babel/types": "^7.17.0" } }, "@babel/highlight": { @@ -16282,9 +16558,9 @@ } }, "@babel/parser": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.18.0.tgz", - "integrity": "sha512-AqDccGC+m5O/iUStSJy3DGRIUFu7WbY/CppZYwrEUB4N0tZlnI8CSTsgL7v5fHVFmUbRv2sd+yy27o8Ydt4MGg==" + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.10.tgz", + "integrity": "sha512-n2Q6i+fnJqzOaq2VkdXxy2TCPCWQZHiCo0XqmrCvDWcZQKRyZzYi4Z0yxlBuN0w+r2ZHmre+Q087DSrw3pbJDQ==" }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { "version": "7.16.7", @@ -17094,48 +17370,38 @@ } }, "@babel/traverse": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.18.0.tgz", - "integrity": "sha512-oNOO4vaoIQoGjDQ84LgtF/IAlxlyqL4TUuoQ7xLkQETFaHkY1F7yazhB4Kt3VcZGL0ZF/jhrEpnXqUb0M7V3sw==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.10.tgz", + "integrity": "sha512-VmbrTHQteIdUUQNTb+zE12SHS/xQVIShmBPhlNP12hD5poF2pbITW1Z4172d03HegaQWhLffdkRJYtAzp0AGcw==", "requires": { "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.18.0", + "@babel/generator": "^7.17.10", "@babel/helper-environment-visitor": "^7.16.7", "@babel/helper-function-name": "^7.17.9", "@babel/helper-hoist-variables": "^7.16.7", "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.18.0", - "@babel/types": "^7.18.0", + "@babel/parser": "^7.17.10", + "@babel/types": "^7.17.10", "debug": "^4.1.0", "globals": "^11.1.0" }, "dependencies": { "@babel/generator": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.18.0.tgz", - "integrity": "sha512-81YO9gGx6voPXlvYdZBliFXAZU8vZ9AZ6z+CjlmcnaeOcYSFbMTpdeDUO9xD9dh/68Vq03I8ZspfUTPfitcDHg==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.10.tgz", + "integrity": "sha512-46MJZZo9y3o4kmhBVc7zW7i8dtR1oIK/sdO5NcfcZRhTGYi+KKJRtHNgsU6c4VUcJmUNV/LQdebD/9Dlv4K+Tg==", "requires": { - "@babel/types": "^7.18.0", - "@jridgewell/gen-mapping": "^0.3.0", + "@babel/types": "^7.17.10", + "@jridgewell/gen-mapping": "^0.1.0", "jsesc": "^2.5.1" } - }, - "@jridgewell/gen-mapping": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.1.tgz", - "integrity": "sha512-GcHwniMlA2z+WFPWuY8lp3fsza0I8xPFMWL5+n8LYyP6PSvPrXf4+n8stDHZY2DM0zy9sVkRDy1jDI4XGzYVqg==", - "requires": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } } } }, "@babel/types": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.18.0.tgz", - "integrity": "sha512-vhAmLPAiC8j9K2GnsnLPCIH5wCrPpYIVBCWRBFDCB7Y/BXLqi/O+1RSTTM2bsmg6U/551+FCf9PNPxjABmxHTw==", + "version": "7.17.10", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.10.tgz", + "integrity": "sha512-9O26jG0mBYfGkUYCYZRnBwbVLd1UZOICEr2Em6InB6jVfsAv1GKgwXHmrSg+WFWDmeKTA6vyTZiN8tCSM5Oo3A==", "requires": { "@babel/helper-validator-identifier": "^7.16.7", "to-fast-properties": "^2.0.0" @@ -17312,7 +17578,6 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "dev": true, "requires": { "@jridgewell/set-array": "^1.0.0", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -17321,7 +17586,8 @@ "@jridgewell/resolve-uri": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz", - "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==" + "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==", + "dev": true }, "@jridgewell/set-array": { "version": "1.1.1", @@ -17337,6 +17603,7 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.13.tgz", "integrity": "sha512-o1xbKhp9qnIAoHJSWd6KlCZfqslL4valSF81H8ImioOAxluWYWOpWkpyktY2vnt4tbrX9XYaxovq6cgowaJp2w==", + "dev": true, "requires": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" @@ -17348,9 +17615,9 @@ "integrity": "sha512-TaW4jTGVE1/ln2VGFChnheMh589QCAZy1MVnLvjjSzZ4pEAa4WYAWPwFkDVZbSdPQdLfZy7LuTyZjWRkhX9/Gg==" }, "@ngtools/webpack": { - "version": "13.3.6", - "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.3.6.tgz", - "integrity": "sha512-QSdFtQIUgnDvM0EXFOpVRp5HTN0U4B9z60fHHfKKzxpNuU3z9EFzJoDUMZ8WabLXtWboUZWCSx0x3tdBt/sVQw==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-13.3.5.tgz", + "integrity": "sha512-OaMZR0rO0ljBHamLwzddfZX03ijtpheUpjH5dNzMNyNrrpKgS4/3jTQ1wvs2j3zzKfKjOS12WG0905QFJYWG6g==", "dev": true, "requires": {} }, @@ -17472,13 +17739,13 @@ "integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==" }, "@schematics/angular": { - "version": "13.3.6", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-13.3.6.tgz", - "integrity": "sha512-BGBmIasjipBxQhV+UdN8B5P73SBXgBPkc7rcOK3Py+xpqMcoTWn290nqxIxLxRVmSeHLabE7+n1m3WnCumlm9Q==", + "version": "13.3.5", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-13.3.5.tgz", + "integrity": "sha512-1Ovx0cq72ZaNCyTyRD8ebIwUzpqhEH9ypWF05bfBLq3J0LlZgewIMhPJSxKmwRC3NQB5DZIYEvD0uhzBIuHCCA==", "dev": true, "requires": { - "@angular-devkit/core": "13.3.6", - "@angular-devkit/schematics": "13.3.6", + "@angular-devkit/core": "13.3.5", + "@angular-devkit/schematics": "13.3.5", "jsonc-parser": "3.0.0" } }, @@ -18638,26 +18905,35 @@ } }, "babel-loader": { - "version": "8.2.5", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.5.tgz", - "integrity": "sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==", + "version": "8.2.3", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.3.tgz", + "integrity": "sha512-n4Zeta8NC3QAsuyiizu0GkmRcQ6clkV9WFUnUf1iXP//IeSKbWjofW3UHyZVwlOB4y039YQKefawyTn64Zwbuw==", "dev": true, "requires": { "find-cache-dir": "^3.3.1", - "loader-utils": "^2.0.0", + "loader-utils": "^1.4.0", "make-dir": "^3.1.0", "schema-utils": "^2.6.5" }, "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + }, "loader-utils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.2.tgz", - "integrity": "sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", "dev": true, "requires": { "big.js": "^5.2.2", "emojis-list": "^3.0.0", - "json5": "^2.1.2" + "json5": "^1.0.1" } } } @@ -20292,6 +20568,48 @@ "esbuild-windows-arm64": "0.14.22" } }, + "esbuild-android-arm64": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.22.tgz", + "integrity": "sha512-k1Uu4uC4UOFgrnTj2zuj75EswFSEBK+H6lT70/DdS4mTAOfs2ECv2I9ZYvr3w0WL0T4YItzJdK7fPNxcPw6YmQ==", + "dev": true, + "optional": true + }, + "esbuild-darwin-64": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.22.tgz", + "integrity": "sha512-d8Ceuo6Vw6HM3fW218FB6jTY6O3r2WNcTAU0SGsBkXZ3k8SDoRLd3Nrc//EqzdgYnzDNMNtrWegK2Qsss4THhw==", + "dev": true, + "optional": true + }, + "esbuild-darwin-arm64": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.22.tgz", + "integrity": "sha512-YAt9Tj3SkIUkswuzHxkaNlT9+sg0xvzDvE75LlBo4DI++ogSgSmKNR6B4eUhU5EUUepVXcXdRIdqMq9ppeRqfw==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-64": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.22.tgz", + "integrity": "sha512-ek1HUv7fkXMy87Qm2G4IRohN+Qux4IcnrDBPZGXNN33KAL0pEJJzdTv0hB/42+DCYWylSrSKxk3KUXfqXOoH4A==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-arm64": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.22.tgz", + "integrity": "sha512-zPh9SzjRvr9FwsouNYTqgqFlsMIW07O8mNXulGeQx6O5ApgGUBZBgtzSlBQXkHi18WjrosYfsvp5nzOKiWzkjQ==", + "dev": true, + "optional": true + }, + "esbuild-linux-32": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.22.tgz", + "integrity": "sha512-SnpveoE4nzjb9t2hqCIzzTWBM0RzcCINDMBB67H6OXIuDa4KqFqaIgmTchNA9pJKOVLVIKd5FYxNiJStli21qg==", + "dev": true, + "optional": true + }, "esbuild-linux-64": { "version": "0.14.22", "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.22.tgz", @@ -20299,12 +20617,96 @@ "dev": true, "optional": true }, + "esbuild-linux-arm": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.22.tgz", + "integrity": "sha512-soPDdbpt/C0XvOOK45p4EFt8HbH5g+0uHs5nUKjHVExfgR7du734kEkXR/mE5zmjrlymk5AA79I0VIvj90WZ4g==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm64": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.22.tgz", + "integrity": "sha512-8q/FRBJtV5IHnQChO3LHh/Jf7KLrxJ/RCTGdBvlVZhBde+dk3/qS9fFsUy+rs3dEi49aAsyVitTwlKw1SUFm+A==", + "dev": true, + "optional": true + }, + "esbuild-linux-mips64le": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.22.tgz", + "integrity": "sha512-SiNDfuRXhGh1JQLLA9JPprBgPVFOsGuQ0yDfSPTNxztmVJd8W2mX++c4FfLpAwxuJe183mLuKf7qKCHQs5ZnBQ==", + "dev": true, + "optional": true + }, + "esbuild-linux-ppc64le": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.22.tgz", + "integrity": "sha512-6t/GI9I+3o1EFm2AyN9+TsjdgWCpg2nwniEhjm2qJWtJyJ5VzTXGUU3alCO3evopu8G0hN2Bu1Jhz2YmZD0kng==", + "dev": true, + "optional": true + }, + "esbuild-linux-riscv64": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.22.tgz", + "integrity": "sha512-AyJHipZKe88sc+tp5layovquw5cvz45QXw5SaDgAq2M911wLHiCvDtf/07oDx8eweCyzYzG5Y39Ih568amMTCQ==", + "dev": true, + "optional": true + }, + "esbuild-linux-s390x": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.22.tgz", + "integrity": "sha512-Sz1NjZewTIXSblQDZWEFZYjOK6p8tV6hrshYdXZ0NHTjWE+lwxpOpWeElUGtEmiPcMT71FiuA9ODplqzzSxkzw==", + "dev": true, + "optional": true + }, + "esbuild-netbsd-64": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.22.tgz", + "integrity": "sha512-TBbCtx+k32xydImsHxvFgsOCuFqCTGIxhzRNbgSL1Z2CKhzxwT92kQMhxort9N/fZM2CkRCPPs5wzQSamtzEHA==", + "dev": true, + "optional": true + }, + "esbuild-openbsd-64": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.22.tgz", + "integrity": "sha512-vK912As725haT313ANZZZN+0EysEEQXWC/+YE4rQvOQzLuxAQc2tjbzlAFREx3C8+uMuZj/q7E5gyVB7TzpcTA==", + "dev": true, + "optional": true + }, + "esbuild-sunos-64": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.22.tgz", + "integrity": "sha512-/mbJdXTW7MTcsPhtfDsDyPEOju9EOABvCjeUU2OJ7fWpX/Em/H3WYDa86tzLUbcVg++BScQDzqV/7RYw5XNY0g==", + "dev": true, + "optional": true + }, "esbuild-wasm": { "version": "0.14.22", "resolved": "https://registry.npmjs.org/esbuild-wasm/-/esbuild-wasm-0.14.22.tgz", "integrity": "sha512-FOSAM29GN1fWusw0oLMv6JYhoheDIh5+atC72TkJKfIUMID6yISlicoQSd9gsNSFsNBvABvtE2jR4JB1j4FkFw==", "dev": true }, + "esbuild-windows-32": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.22.tgz", + "integrity": "sha512-1vRIkuvPTjeSVK3diVrnMLSbkuE36jxA+8zGLUOrT4bb7E/JZvDRhvtbWXWaveUc/7LbhaNFhHNvfPuSw2QOQg==", + "dev": true, + "optional": true + }, + "esbuild-windows-64": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.22.tgz", + "integrity": "sha512-AxjIDcOmx17vr31C5hp20HIwz1MymtMjKqX4qL6whPj0dT9lwxPexmLj6G1CpR3vFhui6m75EnBEe4QL82SYqw==", + "dev": true, + "optional": true + }, + "esbuild-windows-arm64": { + "version": "0.14.22", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.22.tgz", + "integrity": "sha512-5wvQ+39tHmRhNpu2Fx04l7QfeK3mQ9tKzDqqGR8n/4WUxsFxnVLfDRBGirIfk4AfWlxk60kqirlODPoT5LqMUg==", + "dev": true, + "optional": true + }, "escalade": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", @@ -21165,6 +21567,13 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -22854,9 +23263,9 @@ "dev": true }, "memfs": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.3.tgz", - "integrity": "sha512-eivjfi7Ahr6eQTn44nvTnR60e4a1Fs1Via2kCR5lHo/kyNoiMWaXCNJ/GpSd0ilXas2JSOl9B5FTIhflXu0hlg==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.1.tgz", + "integrity": "sha512-1c9VPVvW5P7I85c35zAdEr1TD5+F11IToIHIlrVIcflfnzPkJa0ZoYEoEdYDP8KgPFoSZ/opDrUsAoZWym3mtw==", "dev": true, "requires": { "fs-monkey": "1.0.3" @@ -26711,9 +27120,9 @@ "dev": true }, "del": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/del/-/del-6.1.0.tgz", - "integrity": "sha512-OpcRktOt7G7HBfyxP0srBH4Djg4824EQORX8E1qvIhIzthNNArxxhrB/Mm7dRMiLi1nvFyUpDhzD2cTtbBhV8A==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz", + "integrity": "sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==", "dev": true, "requires": { "globby": "^11.0.1", diff --git a/package.json b/package.json index 73bb5922e10c7c9854c4c4264312dae61c271f04..70a51689f1d5b82a122144801c1983bad85e9169 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "verona-modules-aspect", "config": { - "player_version": "1.25.0", - "editor_version": "1.31.0", + "player_version": "1.25.2", + "editor_version": "1.32.0", "unit_definition_version": "3.6.0" }, "scripts": { @@ -42,18 +42,18 @@ ] }, "dependencies": { - "@angular/animations": "~13.3.9", - "@angular/cdk": "~13.3.7", - "@angular/common": "~13.3.9", - "@angular/compiler": "~13.3.9", - "@angular/core": "~13.3.9", - "@angular/elements": "^13.3.9", - "@angular/flex-layout": "~13.0.0-beta.38", - "@angular/forms": "~13.3.9", - "@angular/material": "~13.3.6", - "@angular/platform-browser": "~13.3.9", - "@angular/platform-browser-dynamic": "~13.3.9", - "@angular/router": "~13.3.9", + "@angular/animations": "~13.3.0", + "@angular/cdk": "^12.2.13", + "@angular/common": "~13.3.0", + "@angular/compiler": "~13.3.0", + "@angular/core": "~13.3.0", + "@angular/elements": "^13.2.7", + "@angular/flex-layout": "^12.0.0-beta.35", + "@angular/forms": "~13.3.0", + "@angular/material": "^12.2.13", + "@angular/platform-browser": "~13.3.0", + "@angular/platform-browser-dynamic": "~13.3.0", + "@angular/router": "~13.3.0", "@ngx-translate/core": "^14.0.0", "@ngx-translate/http-loader": "^6.0.0", "@tiptap/core": "^2.0.0-beta.175", @@ -84,9 +84,9 @@ "zone.js": "~0.11.5" }, "devDependencies": { - "@angular-devkit/build-angular": "~13.3.6", - "@angular/cli": "~13.3.6", - "@angular/compiler-cli": "~13.3.9", + "@angular-devkit/build-angular": "~13.3.0", + "@angular/cli": "~13.3.0", + "@angular/compiler-cli": "~13.3.0", "@iqb/eslint-config": "^1.1.1", "@types/jasmine": "~3.6.0", "@types/node": "^12.11.7", diff --git a/projects/common/components/compound-elements/cloze/cloze-child-elements/drop-list-simple.component.ts b/projects/common/components/compound-elements/cloze/cloze-child-elements/drop-list-simple.component.ts index b11c815d570ef526eaedc34b78013688144e2929..71773d295bea7dedbf66142e2dbd9dae51733589 100644 --- a/projects/common/components/compound-elements/cloze/cloze-child-elements/drop-list-simple.component.ts +++ b/projects/common/components/compound-elements/cloze/cloze-child-elements/drop-list-simple.component.ts @@ -3,8 +3,9 @@ import { CdkDragDrop } from '@angular/cdk/drag-drop/drag-events'; import { CdkDrag, CdkDropList, moveItemInArray, transferArrayItem } from '@angular/cdk/drag-drop'; -import { FormElementComponent } from '../../../../directives/form-element-component.directive'; -import { DropListSimpleElement } from 'common/models/elements/compound-elements/cloze/cloze-child-elements/drop-list-simple'; +import { FormElementComponent } from 'common/directives/form-element-component.directive'; +import { DropListSimpleElement } from + 'common/models/elements/compound-elements/cloze/cloze-child-elements/drop-list-simple'; import { DragNDropValueObject } from 'common/models/elements/element'; @Component({ @@ -30,8 +31,9 @@ import { DragNDropValueObject } from 'common/models/elements/element'; [cdkDropListEnterPredicate]="onlyOneItemPredicate" (cdkDropListDropped)="drop($event)"> <ng-container *ngIf="!parentForm"> - <ng-container *ngFor="let value of $any(elementModel.value)"> - <ng-container [ngTemplateOutlet]="dropObject" [ngTemplateOutletContext]="{ $implicit: value }"> + <ng-container *ngFor="let dropListValueElement of $any(elementModel.value)"> + <ng-container [ngTemplateOutlet]="dropObject" + [ngTemplateOutletContext]="{ $implicit: dropListValueElement }"> </ng-container> </ng-container> </ng-container> @@ -46,7 +48,8 @@ import { DragNDropValueObject } from 'common/models/elements/element'; <ng-template #dropObject let-value> <div class="item" [style.background-color]="elementModel.styling.itemBackgroundColor" - cdkDrag (cdkDragStarted)=dragStart() (cdkDragEnded)="dragEnd()"> + cdkDrag [cdkDragData]="{ element: value }" + (cdkDragStarted)=dragStart() (cdkDragEnded)="dragEnd()"> <div *cdkDragPreview [style.font-size.px]="elementModel.styling.fontSize" [style.background-color]="elementModel.styling.itemBackgroundColor"> @@ -100,6 +103,9 @@ export class DropListSimpleComponent extends FormElementComponent { if (event.previousContainer === event.container) { moveItemInArray(event.container.data.elementFormControl.value as unknown as DragNDropValueObject[], event.previousIndex, event.currentIndex); + this.elementFormControl.setValue( + (event.container.data.elementFormControl.value as DragNDropValueObject[]) + ); } else { transferArrayItem( event.previousContainer.data.elementFormControl.value as unknown as DragNDropValueObject[], diff --git a/projects/common/components/compound-elements/cloze/cloze-child-elements/text-field-simple.component.ts b/projects/common/components/compound-elements/cloze/cloze-child-elements/text-field-simple.component.ts index 6a6185313416ae56e148e5c2de70bcaf92a8a028..8783a49eb176425915eab58489440c4970b643b5 100644 --- a/projects/common/components/compound-elements/cloze/cloze-child-elements/text-field-simple.component.ts +++ b/projects/common/components/compound-elements/cloze/cloze-child-elements/text-field-simple.component.ts @@ -27,8 +27,8 @@ import { [formControl]="elementFormControl" [value]="elementModel.value" (keydown)="elementModel.showSoftwareKeyboard ? hardwareKeyDetected.emit() : null" - (focus)="textInputExpected.emit({ inputElement: input, focused: true })" - (blur)="textInputExpected.emit({ inputElement: input, focused: false })"> + (focus)="focusChanged.emit({ inputElement: input, focused: true })" + (blur)="focusChanged.emit({ inputElement: input, focused: false })"> `, styles: [ '.clozeChild {border: 1px solid rgba(0,0,0,.12); border-radius: 5px}', @@ -38,5 +38,5 @@ import { export class TextFieldSimpleComponent extends FormElementComponent { @Input() elementModel!: TextFieldSimpleElement; @Output() hardwareKeyDetected = new EventEmitter(); - @Output() textInputExpected = new EventEmitter<{ inputElement: HTMLElement; focused: boolean }>(); + @Output() focusChanged = new EventEmitter<{ inputElement: HTMLElement; focused: boolean }>(); } diff --git a/projects/common/components/compound-elements/cloze/cloze.component.ts b/projects/common/components/compound-elements/cloze/cloze.component.ts index 50851a2e5b140585bcc5adac17359d159e62151e..c556fb5d9d951d7ea23a277c810cee9c49a79cfa 100644 --- a/projects/common/components/compound-elements/cloze/cloze.component.ts +++ b/projects/common/components/compound-elements/cloze/cloze.component.ts @@ -12,7 +12,7 @@ import { ClozeElement } from 'common/models/elements/compound-elements/cloze/clo selector: 'aspect-cloze', template: ` <ng-container *ngIf="elementModel.document.content.length < 1"> - Kein Dokument vorhanden + <i>Kein Dokument vorhanden</i> </ng-container> <div [style.width.%]="100" [style.height]="'auto'" @@ -144,22 +144,14 @@ import { ClozeElement } from 'common/models/elements/compound-elements/cloze/clo <ng-container *ngIf="$any(subPart).type === 'text' && (!(subPart.marks | markList).includes('superscript')) && (!(subPart.marks | markList).includes('subscript'))"> - <span [ngStyle]="subPart.marks | styleMarks"> - {{subPart.text}} - </span> + <span [ngStyle]="subPart.marks | styleMarks">{{subPart.text}}</span> </ng-container> - <ng-container *ngIf="$any(subPart).type === 'text' && (subPart.marks | markList).includes('superscript')"> - <sup [ngStyle]="subPart.marks | styleMarks"> - {{subPart.text}} - </sup> + <sup [ngStyle]="subPart.marks | styleMarks">{{subPart.text}}</sup> </ng-container> <ng-container *ngIf="$any(subPart).type === 'text' && (subPart.marks | markList).includes('subscript')"> - <sub [ngStyle]="subPart.marks | styleMarks"> - {{subPart.text}} - </sub> + <sub [ngStyle]="subPart.marks | styleMarks">{{subPart.text}}</sub> </ng-container> - <ng-container *ngIf="$any(subPart).type === 'image'"> <img [src]="subPart.attrs.src" [alt]="subPart.attrs.alt" [style.display]="'inline-block'" diff --git a/projects/common/components/input-elements/drop-list.component.ts b/projects/common/components/input-elements/drop-list.component.ts index 038a0e08c0928b90c857140508d11ec60b62ffe3..99a012af1bd983764478a32e2c3bccab41b4a115 100644 --- a/projects/common/components/input-elements/drop-list.component.ts +++ b/projects/common/components/input-elements/drop-list.component.ts @@ -87,7 +87,8 @@ import { DragNDropValueObject } from 'common/models/elements/element'; class="item" [ngClass]="{ 'vertical-orientation' : elementModel.orientation === 'vertical', 'horizontal-orientation' : elementModel.orientation === 'horizontal'}" - cdkDrag (cdkDragStarted)=dragStart(index) (cdkDragEnded)="dragEnd()" + cdkDrag [cdkDragData]="{ element: dropListValueElement, index: index }" + (cdkDragStarted)=dragStart(index) (cdkDragEnded)="dragEnd()" [style.object-fit]="'scale-down'"> <img *ngIf="elementModel.copyOnDrop && draggedItemIndex === index && dropListValueElement.imgSrcValue" [src]="dropListValueElement.imgSrcValue | safeResourceUrl" alt="Image Placeholder" @@ -95,7 +96,6 @@ import { DragNDropValueObject } from 'common/models/elements/element'; class="item" [ngClass]="{ 'vertical-orientation' : elementModel.orientation === 'vertical', 'horizontal-orientation' : elementModel.orientation === 'horizontal'}" - cdkDrag (cdkDragStarted)=dragStart(index) (cdkDragEnded)="dragEnd()" [style.object-fit]="'scale-down'"> </ng-template> </div> diff --git a/projects/common/components/input-elements/spell-correct.component.ts b/projects/common/components/input-elements/spell-correct.component.ts index d467c5b6a8baa0fca6050d4da40dc7dba0adbbf5..87301ec38d8532af2c92e591ba79216c3628b74d 100644 --- a/projects/common/components/input-elements/spell-correct.component.ts +++ b/projects/common/components/input-elements/spell-correct.component.ts @@ -32,8 +32,8 @@ import { SpellCorrectElement } from 'common/models/elements/input-elements/spell [style.text-decoration]="elementModel.styling.underline ? 'underline' : ''" [formControl]="elementFormControl" (keydown)="elementModel.showSoftwareKeyboard ? hardwareKeyDetected.emit() : null" - (focus)="textInputExpected.emit({ inputElement: input, focused: true })" - (blur)="textInputExpected.emit({ inputElement: input, focused: false })"> + (focus)="focusChanged.emit({ inputElement: input, focused: true })" + (blur)="focusChanged.emit({ inputElement: input, focused: false })"> </mat-form-field> <button #buttonElement mat-button @@ -67,7 +67,7 @@ import { SpellCorrectElement } from 'common/models/elements/input-elements/spell }) export class SpellCorrectComponent extends FormElementComponent { @Input() elementModel!: SpellCorrectElement; - @Output() textInputExpected = new EventEmitter<{ inputElement: HTMLElement; focused: boolean }>(); + @Output() focusChanged = new EventEmitter<{ inputElement: HTMLElement; focused: boolean }>(); @Output() hardwareKeyDetected = new EventEmitter(); @ViewChild(MatInput) inputElement!: MatInput; } diff --git a/projects/common/components/input-elements/text-area.component.ts b/projects/common/components/input-elements/text-area.component.ts index 2f8c708bd864283ac42367b793e8b41293790f7a..257ae1c6655fdb113f2afba04f83ac5b6c0a0328 100644 --- a/projects/common/components/input-elements/text-area.component.ts +++ b/projects/common/components/input-elements/text-area.component.ts @@ -35,8 +35,8 @@ import { TextAreaElement } from 'common/models/elements/input-elements/text-area [style.line-height.%]="elementModel.styling.lineHeight" [style.resize]="elementModel.resizeEnabled ? 'both' : 'none'" (keydown)="elementModel.showSoftwareKeyboard ? hardwareKeyDetected.emit() : null" - (focus)="textInputExpected.emit({ inputElement: input, focused: true })" - (blur)="textInputExpected.emit({ inputElement: input, focused: false })"> + (focus)="focusChanged.emit({ inputElement: input, focused: true })" + (blur)="focusChanged.emit({ inputElement: input, focused: false })"> </textarea> <mat-error *ngIf="elementFormControl.errors"> {{elementFormControl.errors | errorTransform: elementModel}} @@ -50,6 +50,6 @@ import { TextAreaElement } from 'common/models/elements/input-elements/text-area }) export class TextAreaComponent extends FormElementComponent { @Input() elementModel!: TextAreaElement; - @Output() textInputExpected = new EventEmitter<{ inputElement: HTMLElement; focused: boolean }>(); + @Output() focusChanged = new EventEmitter<{ inputElement: HTMLElement; focused: boolean }>(); @Output() hardwareKeyDetected = new EventEmitter(); } diff --git a/projects/common/components/input-elements/text-field.component.ts b/projects/common/components/input-elements/text-field.component.ts index 3374fb2405c8471bca6fd9290337407bfae17f51..27cc28db3db6d9c59d3f33addd6235ab4f32bea0 100644 --- a/projects/common/components/input-elements/text-field.component.ts +++ b/projects/common/components/input-elements/text-field.component.ts @@ -31,8 +31,8 @@ import { TextFieldElement } from 'common/models/elements/input-elements/text-fie [pattern]="$any(elementModel.pattern)" [readonly]="elementModel.readOnly" (keydown)="elementModel.showSoftwareKeyboard ? hardwareKeyDetected.emit() : null" - (focus)="textInputExpected.emit({ inputElement: input, focused: true })" - (blur)="textInputExpected.emit({ inputElement: input, focused: false })"> + (focus)="focusChanged.emit({ inputElement: input, focused: true })" + (blur)="focusChanged.emit({ inputElement: input, focused: false })"> <button *ngIf="elementModel.clearable" type="button" matSuffix mat-icon-button aria-label="Clear" @@ -52,5 +52,5 @@ import { TextFieldElement } from 'common/models/elements/input-elements/text-fie export class TextFieldComponent extends FormElementComponent { @Input() elementModel!: TextFieldElement; @Output() hardwareKeyDetected = new EventEmitter(); - @Output() textInputExpected = new EventEmitter<{ inputElement: HTMLElement; focused: boolean }>(); + @Output() focusChanged = new EventEmitter<{ inputElement: HTMLElement; focused: boolean }>(); } diff --git a/projects/common/models/elements/button/button.ts b/projects/common/models/elements/button/button.ts index e30588dd20d4bc677b1760fa99bece9785f55369..a97786f59a6f6764e8891a7eaa7e5b59517293ff 100644 --- a/projects/common/models/elements/button/button.ts +++ b/projects/common/models/elements/button/button.ts @@ -1,11 +1,11 @@ +import { Type } from '@angular/core'; import { ElementFactory } from 'common/util/element.factory'; import { BasicStyles, PositionedUIElement, PositionProperties, UIElement } from 'common/models/elements/element'; import { ButtonComponent } from 'common/components/button/button.component'; -import { Type } from '@angular/core'; import { ElementComponent } from 'common/directives/element-component.directive'; export class ButtonElement extends UIElement implements PositionedUIElement { - label: string = 'Knopf'; + label: string = 'Navigationsknopf'; imageSrc: string | null = null; asLink: boolean = false; action: null | 'unitNav' | 'pageNav' = null; @@ -15,9 +15,13 @@ export class ButtonElement extends UIElement implements PositionedUIElement { borderRadius: number; }; - constructor(element: Partial<ButtonElement>) { - super(element); - Object.assign(this, element); + constructor(element: Partial<ButtonElement>, ...args: unknown[]) { + super(element, ...args); + if (element.label !== undefined) this.label = element.label; + if (element.imageSrc) this.imageSrc = element.imageSrc; + if (element.asLink) this.asLink = element.asLink; + if (element.action) this.action = element.action; + if (element.actionParam) this.actionParam = element.actionParam; this.position = ElementFactory.initPositionProps(element.position); this.styling = { ...ElementFactory.initStylingProps<{ borderRadius: number; }>({ borderRadius: 0, ...element.styling }) diff --git a/projects/common/models/elements/compound-elements/cloze/cloze-child-elements/drop-list-simple.ts b/projects/common/models/elements/compound-elements/cloze/cloze-child-elements/drop-list-simple.ts index a46a04197294ed8ded0c57e40c8e617e38ab4510..3cfe30c975de23340ced9bde2d30da2600e20ef1 100644 --- a/projects/common/models/elements/compound-elements/cloze/cloze-child-elements/drop-list-simple.ts +++ b/projects/common/models/elements/compound-elements/cloze/cloze-child-elements/drop-list-simple.ts @@ -22,10 +22,13 @@ export class DropListSimpleElement extends InputElement { itemBackgroundColor: string; }; - constructor(element: Partial<DropListSimpleElement>) { - super({ width: 150, height: 30, ...element }); - Object.assign(this, element); + constructor(element: Partial<DropListSimpleElement>, ...args: unknown[]) { + super({ width: 150, height: 30, ...element }, ...args); this.value = element.value || []; + if (element.connectedTo) this.connectedTo = element.connectedTo; + if (element.copyOnDrop) this.copyOnDrop = element.copyOnDrop; + if (element.highlightReceivingDropList) this.highlightReceivingDropList = element.highlightReceivingDropList; + if (element.highlightReceivingDropListColor) this.highlightReceivingDropListColor = element.highlightReceivingDropListColor; this.styling = { ...ElementFactory.initStylingProps({ backgroundColor: '#f4f4f2', diff --git a/projects/common/models/elements/compound-elements/cloze/cloze-child-elements/text-field-simple.ts b/projects/common/models/elements/compound-elements/cloze/cloze-child-elements/text-field-simple.ts index 34ce168d1b36d250068c9131312aca3f3dcb4112..eeb0c61f05e1914ae958341056020d7d6f1c4135 100644 --- a/projects/common/models/elements/compound-elements/cloze/cloze-child-elements/text-field-simple.ts +++ b/projects/common/models/elements/compound-elements/cloze/cloze-child-elements/text-field-simple.ts @@ -8,19 +8,16 @@ import { } from 'common/models/elements/element'; import { Type } from '@angular/core'; import { ElementComponent } from 'common/directives/element-component.directive'; -import { - DropListSimpleComponent -} from 'common/components/compound-elements/cloze/cloze-child-elements/drop-list-simple.component'; import { TextFieldSimpleComponent } from 'common/components/compound-elements/cloze/cloze-child-elements/text-field-simple.component'; export class TextFieldSimpleElement extends InputElement { - minLength: number | undefined; + minLength: number | null = null; minLengthWarnMessage: string = 'Eingabe zu kurz'; - maxLength: number | undefined; + maxLength: number | null = null; maxLengthWarnMessage: string = 'Eingabe zu lang'; - pattern: string | undefined; + pattern: string | null = null; patternWarnMessage: string = 'Eingabe entspricht nicht der Vorgabe'; inputAssistancePreset: InputAssistancePreset = null; inputAssistancePosition: 'floating' | 'right' = 'floating'; @@ -32,9 +29,22 @@ export class TextFieldSimpleElement extends InputElement { lineHeight: number; }; - constructor(element: Partial<TextFieldSimpleElement>) { - super({ width: 150, height: 30, ...element }); - Object.assign(this, element); + constructor(element: Partial<TextFieldSimpleElement>, ...args: unknown[]) { + super({ width: 150, height: 30, ...element }, ...args); + if (element.minLength) this.minLength = element.minLength; + if (element.minLengthWarnMessage !== undefined) this.minLengthWarnMessage = element.minLengthWarnMessage; + if (element.maxLength) this.maxLength = element.maxLength; + if (element.maxLengthWarnMessage !== undefined) this.maxLengthWarnMessage = element.maxLengthWarnMessage; + if (element.pattern) this.pattern = element.pattern; + if (element.patternWarnMessage !== undefined) this.patternWarnMessage = element.patternWarnMessage; + if (element.inputAssistancePreset) this.inputAssistancePreset = element.inputAssistancePreset; + if (element.inputAssistancePosition) this.inputAssistancePosition = element.inputAssistancePosition; + if (element.restrictedToInputAssistanceChars !== undefined) { + this.restrictedToInputAssistanceChars = element.restrictedToInputAssistanceChars; + } + if (element.showSoftwareKeyboard) this.showSoftwareKeyboard = element.showSoftwareKeyboard; + if (element.softwareKeyboardShowFrench) this.softwareKeyboardShowFrench = element.softwareKeyboardShowFrench; + if (element.clearable) this.clearable = element.clearable; this.styling = { ...ElementFactory.initStylingProps({ lineHeight: 135, backgroundColor: 'transparent', ...element.styling }) }; diff --git a/projects/common/models/elements/compound-elements/cloze/cloze-child-elements/toggle-button.ts b/projects/common/models/elements/compound-elements/cloze/cloze-child-elements/toggle-button.ts index 4cb77834bc1aaadda6a352b712b80d0b3332b641..55a28e0f51f5ae91cae244f587d79d7906b4398a 100644 --- a/projects/common/models/elements/compound-elements/cloze/cloze-child-elements/toggle-button.ts +++ b/projects/common/models/elements/compound-elements/cloze/cloze-child-elements/toggle-button.ts @@ -16,9 +16,12 @@ export class ToggleButtonElement extends InputElement { selectionColor: string; }; - constructor(element: Partial<ToggleButtonElement>) { - super({ height: 25, ...element }); - Object.assign(this, element); + constructor(element: Partial<ToggleButtonElement>, ...args: unknown[]) { + super({ height: 25, ...element }, ...args); + if (element.richTextOptions) this.richTextOptions = element.richTextOptions; + if (element.strikeOtherOptions) this.strikeOtherOptions = element.strikeOtherOptions; + if (element.verticalOrientation) this.verticalOrientation = element.verticalOrientation; + if (element.dynamicWidth !== undefined) this.dynamicWidth = element.dynamicWidth; this.styling = { ...ElementFactory.initStylingProps({ lineHeight: 135, diff --git a/projects/common/models/elements/compound-elements/cloze/cloze.ts b/projects/common/models/elements/compound-elements/cloze/cloze.ts index 6947c4f2b92e302cfe32ba9adab4e127e8575ca8..a90b6dc5b5b6f3146208bbea07b4230f32131655 100644 --- a/projects/common/models/elements/compound-elements/cloze/cloze.ts +++ b/projects/common/models/elements/compound-elements/cloze/cloze.ts @@ -1,15 +1,23 @@ -import { ElementFactory } from 'common/util/element.factory'; import { BasicStyles, CompoundElement, InputElement, PositionedUIElement, PositionProperties, - UIElement + UIElement, UIElementValue } from 'common/models/elements/element'; import { Type } from '@angular/core'; import { ElementComponent } from 'common/directives/element-component.directive'; import { ClozeComponent } from 'common/components/compound-elements/cloze/cloze.component'; +import { ElementFactory } from 'common/util/element.factory'; +import { + TextFieldSimpleElement +} from 'common/models/elements/compound-elements/cloze/cloze-child-elements/text-field-simple'; +import { + DropListSimpleElement +} from 'common/models/elements/compound-elements/cloze/cloze-child-elements/drop-list-simple'; +import { ToggleButtonElement } from 'common/models/elements/compound-elements/cloze/cloze-child-elements/toggle-button'; +import { IDManager } from 'common/util/id-manager'; export class ClozeElement extends CompoundElement implements PositionedUIElement { document: ClozeDocument = { type: 'doc', content: [] }; @@ -19,17 +27,53 @@ export class ClozeElement extends CompoundElement implements PositionedUIElement lineHeight: number; }; - constructor(element: Partial<ClozeElement>) { - super({ height: 200, ...element }); - Object.assign(this, element); - this.document = this.initDocument(element); + constructor(element: Partial<ClozeElement>, idManager?: IDManager) { + super({ height: 200, ...element }, idManager); + if (element.columnCount) this.columnCount = element.columnCount; + this.document = this.initDocument(element, idManager); this.position = ElementFactory.initPositionProps(element.position); this.styling = { ...ElementFactory.initStylingProps({ lineHeight: 150, ...element.styling }) }; } - private initDocument(element: Partial<ClozeElement>): ClozeDocument { + setProperty(property: string, value: UIElementValue): void { + if (property === 'document') { + this.document = value as ClozeDocument; + + this.document.content.forEach((node: any) => { + if (node.type === 'paragraph' || node.type === 'heading') { + ClozeElement.createSubNodeElements(node); + } else if (node.type === 'bulletList' || node.type === 'orderedList') { + node.content.forEach((listItem: any) => { + listItem.content.forEach((listItemParagraph: any) => { + ClozeElement.createSubNodeElements(listItemParagraph); + }); + }); + } else if (node.type === 'blockquote') { + node.content.forEach((blockQuoteItem: any) => { + ClozeElement.createSubNodeElements(blockQuoteItem); + }); + } + }); + + } else { + super.setProperty(property, value); + } + } + + private static createSubNodeElements(node: any) { + node.content?.forEach((subNode: any) => { + if (['ToggleButton', 'DropList', 'TextField'].includes(subNode.type) && + subNode.attrs.model.id === 'cloze-child-id-placeholder') { + const newID = IDManager.getInstance().getNewID(subNode.attrs.model.type); + subNode.attrs.model = + ClozeElement.createChildElement({ ...subNode.attrs.model, id: newID }, IDManager.getInstance()); + } + }); + } + + private initDocument(element: Partial<ClozeElement>, idManager?: IDManager): ClozeDocument { return { ...element.document, content: element.document?.content ? element.document.content @@ -42,9 +86,7 @@ export class ClozeElement extends CompoundElement implements PositionedUIElement ...paraPart, attrs: { ...paraPart.attrs, - model: ElementFactory.createElement( - (paraPart.attrs?.model as InputElement).type, paraPart.attrs?.model as InputElement - ) + model: ClozeElement.createChildElement(paraPart.attrs?.model as InputElement, idManager) } } : { @@ -60,8 +102,12 @@ export class ClozeElement extends CompoundElement implements PositionedUIElement } getChildElements(): UIElement[] { - if (!this.document) return []; - const clozeDocument: ClozeDocument = this.document; + return ClozeElement.getDocumentChildElements(this.document); + } + + static getDocumentChildElements(document: ClozeDocument): UIElement[] { + if (!document) return []; + const clozeDocument: ClozeDocument = document; const elementList: InputElement[] = []; clozeDocument.content.forEach((documentPart: ClozeDocumentParagraph) => { if (documentPart.type === 'paragraph' || documentPart.type === 'heading') { @@ -81,6 +127,25 @@ export class ClozeElement extends CompoundElement implements PositionedUIElement return elementList; } + private static createChildElement(elementModel: Partial<UIElement>, idManager?: IDManager): InputElement { + let newElement: InputElement; + switch (elementModel.type) { + case 'text-field-simple': + newElement = new TextFieldSimpleElement(elementModel as TextFieldSimpleElement, idManager); + break; + case 'drop-list-simple': + newElement = new DropListSimpleElement(elementModel as DropListSimpleElement, idManager); + break; + case 'toggle-button': + newElement = new ToggleButtonElement(elementModel as ToggleButtonElement, idManager); + break; + default: + throw new Error(`ElementType ${elementModel.type} not found!`); + } + // console.log('newElement', newElement); + return newElement; + } + private static getParagraphCustomElements(documentPart: any): InputElement[] { if (!documentPart.content) { return []; diff --git a/projects/common/models/elements/compound-elements/cloze/tiptap-editor-extensions/drop-list.ts b/projects/common/models/elements/compound-elements/cloze/tiptap-editor-extensions/drop-list.ts index 01e7090c91a1f1e90fced0b2ee531bc041d0f236..2ca23e9bb0debdd39851d8d329434b1b8f0b6976 100644 --- a/projects/common/models/elements/compound-elements/cloze/tiptap-editor-extensions/drop-list.ts +++ b/projects/common/models/elements/compound-elements/cloze/tiptap-editor-extensions/drop-list.ts @@ -1,5 +1,7 @@ import { Node, mergeAttributes } from '@tiptap/core'; -import { ElementFactory } from '../../../../../util/element.factory'; +import { + DropListSimpleElement +} from 'common/models/elements/compound-elements/cloze/cloze-child-elements/drop-list-simple'; const DropListExtension = Node.create({ @@ -10,7 +12,7 @@ const DropListExtension = addAttributes() { return { model: { - default: ElementFactory.createElement('drop-list-simple') + default: new DropListSimpleElement({ type: 'drop-list-simple' }) } }; }, diff --git a/projects/common/models/elements/compound-elements/cloze/tiptap-editor-extensions/text-field.ts b/projects/common/models/elements/compound-elements/cloze/tiptap-editor-extensions/text-field.ts index b2830e7d9723d88c13fbde6f5fbfc422d786a5d0..896be9cfb5a6805bf58133fe9423d90fff1bc7a4 100644 --- a/projects/common/models/elements/compound-elements/cloze/tiptap-editor-extensions/text-field.ts +++ b/projects/common/models/elements/compound-elements/cloze/tiptap-editor-extensions/text-field.ts @@ -1,5 +1,8 @@ import { Node, mergeAttributes } from '@tiptap/core'; import { ElementFactory } from '../../../../../util/element.factory'; +import { + TextFieldSimpleElement +} from 'common/models/elements/compound-elements/cloze/cloze-child-elements/text-field-simple'; const TextFieldExtension = Node.create({ @@ -10,7 +13,7 @@ const TextFieldExtension = addAttributes() { return { model: { - default: ElementFactory.createElement('text-field-simple') + default: new TextFieldSimpleElement({ type: 'text-field-simple' }) } }; }, diff --git a/projects/common/models/elements/compound-elements/cloze/tiptap-editor-extensions/toggle-button.ts b/projects/common/models/elements/compound-elements/cloze/tiptap-editor-extensions/toggle-button.ts index 926a4effe9d39ab8df518c1bd7c9c8296a24a390..272e746778410be05b614c8ba3df0cdd5ef5f95d 100644 --- a/projects/common/models/elements/compound-elements/cloze/tiptap-editor-extensions/toggle-button.ts +++ b/projects/common/models/elements/compound-elements/cloze/tiptap-editor-extensions/toggle-button.ts @@ -1,5 +1,5 @@ import { Node, mergeAttributes } from '@tiptap/core'; -import { ElementFactory } from '../../../../../util/element.factory'; +import { ToggleButtonElement } from 'common/models/elements/compound-elements/cloze/cloze-child-elements/toggle-button'; const ToggleButtonExtension = Node.create({ @@ -10,7 +10,7 @@ const ToggleButtonExtension = addAttributes() { return { model: { - default: ElementFactory.createElement('toggle-button') + default: new ToggleButtonElement({ type: 'toggle-button' }) } }; }, diff --git a/projects/common/models/elements/compound-elements/likert/likert-row.ts b/projects/common/models/elements/compound-elements/likert/likert-row.ts index 1a3319e1c612ead226fa87f962af5dbfaa706b48..6c51af8056965bb426090addfd61b03e8c0bd069 100644 --- a/projects/common/models/elements/compound-elements/likert/likert-row.ts +++ b/projects/common/models/elements/compound-elements/likert/likert-row.ts @@ -1,5 +1,5 @@ -import { InputElement, TextImageLabel } from 'common/models/elements/element'; import { Type } from '@angular/core'; +import { InputElement, SchemerData, SchemerValue, TextImageLabel } from 'common/models/elements/element'; import { ElementComponent } from 'common/directives/element-component.directive'; import { LikertRadioButtonGroupComponent @@ -11,9 +11,28 @@ export class LikertRowElement extends InputElement { firstColumnSizeRatio: number = 5; verticalButtonAlignment: 'auto' | 'center' = 'center'; - constructor(element: Partial<LikertRowElement>) { - super(element); - Object.assign(this, element); + constructor(element: Partial<LikertRowElement>, ...args: unknown[]) { + super(element, ...args); + if (element.rowLabel) this.rowLabel = element.rowLabel; + if (element.columnCount) this.columnCount = element.columnCount; + if (element.firstColumnSizeRatio) this.firstColumnSizeRatio = element.firstColumnSizeRatio; + if (element.verticalButtonAlignment) this.verticalButtonAlignment = element.verticalButtonAlignment; + } + + getSchemerData(): SchemerData { + return { + id: this.id, + type: 'integer', + format: '', + multiple: false, + nullable: !this.value && this.value === 0, + values: this.getSchemerValues(), + valuesComplete: true + }; + } + + private getSchemerValues(): SchemerValue[] { + return []; } getComponentFactory(): Type<ElementComponent> { diff --git a/projects/common/models/elements/compound-elements/likert/likert.ts b/projects/common/models/elements/compound-elements/likert/likert.ts index 9c453fded15d070944fb678f4dd1b2485b61a4e6..076ad6d8c76cad39d8d4ccafada6d971944196f7 100644 --- a/projects/common/models/elements/compound-elements/likert/likert.ts +++ b/projects/common/models/elements/compound-elements/likert/likert.ts @@ -1,13 +1,10 @@ +import { Type } from '@angular/core'; import { ElementFactory } from 'common/util/element.factory'; import { - BasicStyles, CompoundElement, - PositionedUIElement, - PositionProperties, - TextImageLabel, - UIElement + BasicStyles, CompoundElement, UIElement, + PositionedUIElement, PositionProperties, TextImageLabel } from 'common/models/elements/element'; import { LikertRowElement } from 'common/models/elements/compound-elements/likert/likert-row'; -import { Type } from '@angular/core'; import { ElementComponent } from 'common/directives/element-component.directive'; import { LikertComponent } from 'common/components/compound-elements/likert/likert.component'; @@ -22,10 +19,11 @@ export class LikertElement extends CompoundElement implements PositionedUIElemen lineColoringColor: string; }; - constructor(element: Partial<LikertElement>) { - super({ width: 250, height: 200, ...element }); - Object.assign(this, element); - this.rows = element.rows !== undefined ? element.rows?.map(row => new LikertRowElement(row)) : []; + constructor(element: Partial<LikertElement>, ...args: unknown[]) { + super({ width: 250, height: 200, ...element }, ...args); + if (element.columns) this.columns = element.columns; + if (element.firstColumnSizeRatio) this.firstColumnSizeRatio = element.firstColumnSizeRatio; + this.rows = element.rows !== undefined ? element.rows?.map(row => new LikertRowElement(row, ...args)) : []; this.position = ElementFactory.initPositionProps(element.position); this.styling = { ...ElementFactory.initStylingProps({ diff --git a/projects/common/models/elements/element.ts b/projects/common/models/elements/element.ts index c9b233636b0aec3fe292f5c594d2a862fafbd066..989b354fba5a84f38004f58dc0df39799fcf7350 100644 --- a/projects/common/models/elements/element.ts +++ b/projects/common/models/elements/element.ts @@ -1,7 +1,8 @@ -import { ElementFactory } from 'common/util/element.factory'; import { ElementComponent } from 'common/directives/element-component.directive'; import { Type } from '@angular/core'; import { ClozeDocument } from 'common/models/elements/compound-elements/cloze/cloze'; +import { ElementFactory } from 'common/util/element.factory'; +import { IDManager } from 'common/util/id-manager'; export type UIElementType = 'text' | 'button' | 'text-field' | 'text-field-simple' | 'text-area' | 'checkbox' | 'dropdown' | 'radio' | 'image' | 'audio' | 'video' | 'likert' | 'likert-row' | 'radio-group-images' @@ -15,7 +16,7 @@ export type InputAssistancePreset = null | 'french' | 'numbers' | 'numbersAndOpe | 'comparisonOperators' | 'squareDashDot' | 'placeValue'; export abstract class UIElement { - [index: string]: any; + [index: string]: unknown; id: string = 'id_placeholder'; type: UIElementType; width: number = 180; @@ -24,10 +25,29 @@ export abstract class UIElement { styling?: BasicStyles & ExtendedStyles; player?: PlayerProperties; - constructor(element: Partial<UIElement>) { - Object.assign(this, element); + constructor(element: Partial<UIElement>, ...args: unknown[]) { if (!element.type) throw Error('Element has no type!'); this.type = element.type; + + // IDManager is an optional parameter. When given, check/repair and register the ID. + if (args[0]) { + const idManager: IDManager = args[0] as IDManager; + if (!element.id) { + this.id = idManager.getNewID(element.type as string); + } else if (!IDManager.getInstance().isIdAvailable(element.id)) { + this.id = idManager.getNewID(element.type as string); + } else { + this.id = element.id; + } + idManager.addID(this.id); + } else if (element.id) { + this.id = element.id; + } else { + throw Error('No ID for element!'); + } + + if (element.width !== undefined) this.width = element.width; + if (element.height !== undefined) this.height = element.height; } setProperty(property: string, value: UIElementValue): void { @@ -35,15 +55,19 @@ export abstract class UIElement { } setStyleProperty(property: string, value: UIElementValue): void { - (this.styling as { [key: string]: any })[property] = value; + (this.styling as BasicStyles & ExtendedStyles)[property] = value; } setPositionProperty(property: string, value: UIElementValue): void { - (this.position as { [key: string]: any })[property] = value; + (this.position as PositionProperties)[property] = value; } setPlayerProperty(property: string, value: UIElementValue): void { - (this.player as { [key: string]: any })[property] = value; + (this.player as PlayerProperties)[property] = value; + } + + getChildElements(): UIElement[] { + return []; } abstract getComponentFactory(): Type<ElementComponent>; @@ -58,9 +82,13 @@ export abstract class InputElement extends UIElement { requiredWarnMessage: string = 'Eingabe erforderlich'; readOnly: boolean = false; - protected constructor(element: Partial<InputElement>) { - super(element); - Object.assign(this, element); + protected constructor(element: Partial<InputElement>, ...args: unknown[]) { + super(element, ...args); + if (element.label !== undefined) this.label = element.label; + if (element.value !== undefined) this.value = element.value; + if (element.required) this.required = element.required; + if (element.requiredWarnMessage !== undefined) this.requiredWarnMessage = element.requiredWarnMessage; + if (element.readOnly) this.readOnly = element.readOnly; } abstract getSchemerData(options?: unknown): SchemerData; @@ -74,8 +102,8 @@ export abstract class CompoundElement extends UIElement { export abstract class PlayerElement extends UIElement { player: PlayerProperties; - protected constructor(element: Partial<PlayerElement>) { - super(element); + protected constructor(element: Partial<PlayerElement>, ...args: unknown[]) { + super(element, ...args); this.player = ElementFactory.initPlayerProps(element.player); } @@ -102,7 +130,7 @@ export interface PositionedUIElement extends UIElement { } export interface PositionProperties { - [index: string]: string | number | boolean | null; + [index: string]: unknown; fixedSize: boolean; dynamicPositioning: boolean; xPosition: number; @@ -120,6 +148,7 @@ export interface PositionProperties { } export interface BasicStyles { + [index: string]: unknown; fontColor: string; font: string; fontSize: number; @@ -130,6 +159,7 @@ export interface BasicStyles { } export interface ExtendedStyles { + [index: string]: unknown; lineHeight?: number; borderRadius?: number; itemBackgroundColor?: string; @@ -146,7 +176,7 @@ export interface PlayerElement { } export interface PlayerProperties { - [index: string]: string | number | boolean | null; + [index: string]: unknown; autostart: boolean; autostartDelay: number; loop: boolean; diff --git a/projects/common/models/elements/frame/frame.ts b/projects/common/models/elements/frame/frame.ts index a0b89d7935999091e666460e5b653959060131ce..5a63eafeff14fbb031b76753d97b77cfdb196190 100644 --- a/projects/common/models/elements/frame/frame.ts +++ b/projects/common/models/elements/frame/frame.ts @@ -1,8 +1,8 @@ +import { Type } from '@angular/core'; import { ElementFactory } from 'common/util/element.factory'; import { BasicStyles, PositionedUIElement, PositionProperties, UIElement } from 'common/models/elements/element'; import { FrameComponent } from 'common/components/frame/frame.component'; import { ElementComponent } from 'common/directives/element-component.directive'; -import { Type } from '@angular/core'; export class FrameElement extends UIElement implements PositionedUIElement { position: PositionProperties; @@ -13,9 +13,8 @@ export class FrameElement extends UIElement implements PositionedUIElement { borderRadius: number; }; - constructor(element: Partial<FrameElement>) { - super(element); - Object.assign(this, element); + constructor(element: Partial<FrameElement>, ...args: unknown[]) { + super(element, ...args); this.position = ElementFactory.initPositionProps({ zIndex: -1, ...element.position }); this.styling = { ...ElementFactory.initStylingProps({ diff --git a/projects/common/models/elements/input-elements/checkbox.ts b/projects/common/models/elements/input-elements/checkbox.ts index 2f55078936e10cf4d0af4bf23be39aa794220521..3eb64b673b911944a50c1327649182d253343190 100644 --- a/projects/common/models/elements/input-elements/checkbox.ts +++ b/projects/common/models/elements/input-elements/checkbox.ts @@ -1,3 +1,4 @@ +import { Type } from '@angular/core'; import { ElementFactory } from 'common/util/element.factory'; import { BasicStyles, @@ -6,7 +7,6 @@ import { PositionProperties, SchemerData, SchemerValue } from 'common/models/elements/element'; -import { Type } from '@angular/core'; import { ElementComponent } from 'common/directives/element-component.directive'; import { CheckboxComponent } from 'common/components/input-elements/checkbox.component'; @@ -14,8 +14,8 @@ export class CheckboxElement extends InputElement implements PositionedUIElement position: PositionProperties; styling: BasicStyles; - constructor(element: Partial<CheckboxElement>) { - super({ width: 215, ...element }); + constructor(element: Partial<CheckboxElement>, ...args: unknown[]) { + super({ width: 215, ...element }, ...args); this.position = ElementFactory.initPositionProps(element.position); this.styling = ElementFactory.initStylingProps(element.styling); } diff --git a/projects/common/models/elements/input-elements/drop-list.ts b/projects/common/models/elements/input-elements/drop-list.ts index 2731c770869a685155c5baa62fb4e96871a0abfe..2f7555a2c55a3bc6248df6ff47df84152ff7ff01 100644 --- a/projects/common/models/elements/input-elements/drop-list.ts +++ b/projects/common/models/elements/input-elements/drop-list.ts @@ -1,14 +1,13 @@ +import { Type } from '@angular/core'; import { ElementFactory } from 'common/util/element.factory'; import { - BasicStyles, DragNDropValueObject, - InputElement, - PositionedUIElement, - PositionProperties, - SchemerData, SchemerValue + InputElement, PositionedUIElement, + DragNDropValueObject, + BasicStyles, PositionProperties, SchemerData, SchemerValue } from 'common/models/elements/element'; -import { Type } from '@angular/core'; import { ElementComponent } from 'common/directives/element-component.directive'; import { DropListComponent } from 'common/components/input-elements/drop-list.component'; +import { IDManager } from 'common/util/id-manager'; import { DropListSimpleElement } from 'common/models/elements/compound-elements/cloze/cloze-child-elements/drop-list-simple'; @@ -25,10 +24,19 @@ export class DropListElement extends InputElement implements PositionedUIElement itemBackgroundColor: string; }; - constructor(element: Partial<DropListElement>) { - super({ height: 100, ...element }); - Object.assign(this, element); + constructor(element: Partial<DropListElement>, idManager?: IDManager) { + super({ height: 100, ...element }, idManager); this.value = element.value || []; + if (idManager) { + (this.value as DragNDropValueObject[]).forEach(valueElement => idManager.addID(valueElement.id)); + } + if (element.onlyOneItem) this.onlyOneItem = element.onlyOneItem; + if (element.connectedTo) this.connectedTo = element.connectedTo; + if (element.copyOnDrop) this.copyOnDrop = element.copyOnDrop; + if (element.orientation) this.orientation = element.orientation; + if (element.highlightReceivingDropList) this.highlightReceivingDropList = element.highlightReceivingDropList; + if (element.highlightReceivingDropListColor) this.highlightReceivingDropListColor = + element.highlightReceivingDropListColor; this.position = ElementFactory.initPositionProps({ useMinHeight: true, ...element.position }); this.styling = { ...ElementFactory.initStylingProps({ diff --git a/projects/common/models/elements/input-elements/dropdown.ts b/projects/common/models/elements/input-elements/dropdown.ts index 7c15e9567742d975a90e6b21440f06afd1649579..867d979b48ef963d5d753af5f0c4e48ef1d6fc68 100644 --- a/projects/common/models/elements/input-elements/dropdown.ts +++ b/projects/common/models/elements/input-elements/dropdown.ts @@ -1,3 +1,4 @@ +import { Type } from '@angular/core'; import { ElementFactory } from 'common/util/element.factory'; import { BasicStyles, @@ -6,7 +7,6 @@ import { PositionProperties, SchemerData, SchemerValue } from 'common/models/elements/element'; -import { Type } from '@angular/core'; import { ElementComponent } from 'common/directives/element-component.directive'; import { DropdownComponent } from 'common/components/input-elements/dropdown.component'; @@ -16,16 +16,17 @@ export class DropdownElement extends InputElement implements PositionedUIElement position: PositionProperties; styling: BasicStyles; - constructor(element: Partial<DropdownElement>) { - super({ width: 240, height: 83, ...element }); - Object.assign(this, element); + constructor(element: Partial<DropdownElement>, ...args: unknown[]) { + super({ width: 240, height: 83, ...element }, ...args); + if (element.options) this.options = element.options; + if (element.allowUnset) this.allowUnset = element.allowUnset; this.position = ElementFactory.initPositionProps(element.position); this.styling = { ...ElementFactory.initStylingProps(element.styling) }; } - getSchemerData(options: never): SchemerData { + getSchemerData(): SchemerData { return { id: this.id, type: 'integer', diff --git a/projects/common/models/elements/input-elements/radio-button-group-complex.ts b/projects/common/models/elements/input-elements/radio-button-group-complex.ts index 8d49a152a73b5e30a8f43c2078ea8b29a2a54c43..8001df65db36ac6bbe5e7f40dc88f5b67b5caeab 100644 --- a/projects/common/models/elements/input-elements/radio-button-group-complex.ts +++ b/projects/common/models/elements/input-elements/radio-button-group-complex.ts @@ -1,3 +1,4 @@ +import { Type } from '@angular/core'; import { ElementFactory } from 'common/util/element.factory'; import { BasicStyles, @@ -6,7 +7,6 @@ import { PositionProperties, SchemerData, SchemerValue, TextImageLabel } from 'common/models/elements/element'; -import { Type } from '@angular/core'; import { ElementComponent } from 'common/directives/element-component.directive'; import { RadioGroupImagesComponent } from 'common/components/input-elements/radio-group-images.component'; @@ -15,9 +15,9 @@ export class RadioButtonGroupComplexElement extends InputElement implements Posi position: PositionProperties; styling: BasicStyles; - constructor(element: Partial<RadioButtonGroupComplexElement>) { - super({ height: 100, ...element }); - Object.assign(this, element); + constructor(element: Partial<RadioButtonGroupComplexElement>, ...args: unknown[]) { + super({ height: 100, ...element }, ...args); + if (element.columns) this.columns = element.columns; this.position = ElementFactory.initPositionProps({ marginBottom: 40, ...element.position }); this.styling = { ...ElementFactory.initStylingProps({ backgroundColor: 'transparent', ...element.styling }) diff --git a/projects/common/models/elements/input-elements/radio-button-group.ts b/projects/common/models/elements/input-elements/radio-button-group.ts index e0577a0f41ca161bb5022afc9983317122eea458..1bb9615b6ad399651834f7a5d13e2c55b3cf3229 100644 --- a/projects/common/models/elements/input-elements/radio-button-group.ts +++ b/projects/common/models/elements/input-elements/radio-button-group.ts @@ -1,3 +1,4 @@ +import { Type } from '@angular/core'; import { ElementFactory } from 'common/util/element.factory'; import { BasicStyles, @@ -6,7 +7,6 @@ import { PositionProperties, SchemerData, SchemerValue } from 'common/models/elements/element'; -import { Type } from '@angular/core'; import { ElementComponent } from 'common/directives/element-component.directive'; import { RadioButtonGroupComponent } from 'common/components/input-elements/radio-button-group.component'; @@ -19,9 +19,11 @@ export class RadioButtonGroupElement extends InputElement implements PositionedU lineHeight: number; }; - constructor(element: Partial<RadioButtonGroupElement>) { - super({ height: 100, ...element }); - Object.assign(this, element); + constructor(element: Partial<RadioButtonGroupElement>, ...args: unknown[]) { + super({ height: 100, ...element }, ...args); + if (element.richTextOptions) this.richTextOptions = element.richTextOptions; + if (element.alignment) this.alignment = element.alignment; + if (element.strikeOtherOptions) this.strikeOtherOptions = element.strikeOtherOptions; this.position = ElementFactory.initPositionProps({ marginBottom: 30, ...element.position }); this.styling = { ...ElementFactory.initStylingProps({ diff --git a/projects/common/models/elements/input-elements/slider.ts b/projects/common/models/elements/input-elements/slider.ts index 10281f8a73176164ccefb2311cf4123b4c156ba6..820ecf0ecd9772534fc1ff0d573b952b667d5f53 100644 --- a/projects/common/models/elements/input-elements/slider.ts +++ b/projects/common/models/elements/input-elements/slider.ts @@ -1,3 +1,4 @@ +import { Type } from '@angular/core'; import { ElementFactory } from 'common/util/element.factory'; import { BasicStyles, @@ -6,7 +7,6 @@ import { PositionProperties, SchemerData, SchemerValue } from 'common/models/elements/element'; -import { Type } from '@angular/core'; import { ElementComponent } from 'common/directives/element-component.directive'; import { SliderComponent } from 'common/components/input-elements/slider.component'; @@ -21,9 +21,13 @@ export class SliderElement extends InputElement implements PositionedUIElement { lineHeight: number; }; - constructor(element: Partial<SliderElement>) { - super(element); - Object.assign(this, element); + constructor(element: Partial<SliderElement>, ...args: unknown[]) { + super(element, ...args); + if (element.minValue) this.minValue = element.minValue; + if (element.maxValue !== undefined) this.maxValue = element.maxValue; + if (element.showValues !== undefined) this.showValues = element.showValues; + if (element.barStyle) this.barStyle = element.barStyle; + if (element.thumbLabel) this.thumbLabel = element.thumbLabel; this.position = ElementFactory.initPositionProps(element.position); this.styling = { ...ElementFactory.initStylingProps({ diff --git a/projects/common/models/elements/input-elements/spell-correct.ts b/projects/common/models/elements/input-elements/spell-correct.ts index d73b84c7ca0a93fc54c10a77a87aeea627727ebc..2b95efbbef23add022a5ac195218aa846c29b472 100644 --- a/projects/common/models/elements/input-elements/spell-correct.ts +++ b/projects/common/models/elements/input-elements/spell-correct.ts @@ -1,3 +1,4 @@ +import { Type } from '@angular/core'; import { ElementFactory } from 'common/util/element.factory'; import { BasicStyles, @@ -6,7 +7,6 @@ import { PositionedUIElement, PositionProperties, SchemerData, SchemerValue } from 'common/models/elements/element'; -import { Type } from '@angular/core'; import { ElementComponent } from 'common/directives/element-component.directive'; import { SpellCorrectComponent } from 'common/components/input-elements/spell-correct.component'; @@ -19,9 +19,15 @@ export class SpellCorrectElement extends InputElement implements PositionedUIEle position: PositionProperties; styling: BasicStyles; - constructor(element: Partial<SpellCorrectElement>) { - super({ width: 230, height: 80, ...element }); - Object.assign(this, element); + constructor(element: Partial<SpellCorrectElement>, ...args: unknown[]) { + super({ width: 230, height: 80, ...element }, ...args); + if (element.inputAssistancePreset) this.inputAssistancePreset = element.inputAssistancePreset; + if (element.inputAssistancePosition) this.inputAssistancePosition = element.inputAssistancePosition; + if (element.restrictedToInputAssistanceChars !== undefined) { + this.restrictedToInputAssistanceChars = element.restrictedToInputAssistanceChars; + } + if (element.showSoftwareKeyboard) this.showSoftwareKeyboard = element.showSoftwareKeyboard; + if (element.softwareKeyboardShowFrench) this.softwareKeyboardShowFrench = element.softwareKeyboardShowFrench; this.position = ElementFactory.initPositionProps(element.position); this.styling = { ...ElementFactory.initStylingProps({ backgroundColor: 'transparent', ...element.styling }) diff --git a/projects/common/models/elements/input-elements/text-area.ts b/projects/common/models/elements/input-elements/text-area.ts index dc95087ec4f137d11004e4de2c219d0f1c0d5972..70906e9ee38708c73ce68bb6976108624de34cf4 100644 --- a/projects/common/models/elements/input-elements/text-area.ts +++ b/projects/common/models/elements/input-elements/text-area.ts @@ -1,3 +1,4 @@ +import { Type } from '@angular/core'; import { ElementFactory } from 'common/util/element.factory'; import { BasicStyles, @@ -6,7 +7,6 @@ import { PositionedUIElement, PositionProperties, SchemerData, SchemerValue } from 'common/models/elements/element'; -import { Type } from '@angular/core'; import { ElementComponent } from 'common/directives/element-component.directive'; import { TextAreaComponent } from 'common/components/input-elements/text-area.component'; @@ -24,9 +24,18 @@ export class TextAreaElement extends InputElement implements PositionedUIElement lineHeight: number; }; - constructor(element: Partial<TextAreaElement>) { - super({ width: 230, height: 132, ...element }); - Object.assign(this, element); + constructor(element: Partial<TextAreaElement>, ...args: unknown[]) { + super({ width: 230, height: 132, ...element }, ...args); + if (element.appearance) this.appearance = element.appearance; + if (element.resizeEnabled) this.resizeEnabled = element.resizeEnabled; + if (element.rowCount) this.rowCount = element.rowCount; + if (element.inputAssistancePreset) this.inputAssistancePreset = element.inputAssistancePreset; + if (element.inputAssistancePosition) this.inputAssistancePosition = element.inputAssistancePosition; + if (element.restrictedToInputAssistanceChars !== undefined) { + this.restrictedToInputAssistanceChars = element.restrictedToInputAssistanceChars; + } + if (element.showSoftwareKeyboard) this.showSoftwareKeyboard = element.showSoftwareKeyboard; + if (element.softwareKeyboardShowFrench) this.softwareKeyboardShowFrench = element.softwareKeyboardShowFrench; this.position = ElementFactory.initPositionProps(element.position); this.styling = { ...ElementFactory.initStylingProps({ diff --git a/projects/common/models/elements/input-elements/text-field.ts b/projects/common/models/elements/input-elements/text-field.ts index 8937a2b0d90214e726457977e8a8efa033e35702..bccfa40c261bb5cc3346867c3336744c966d0a05 100644 --- a/projects/common/models/elements/input-elements/text-field.ts +++ b/projects/common/models/elements/input-elements/text-field.ts @@ -1,9 +1,9 @@ +import { Type } from '@angular/core'; import { ElementFactory } from 'common/util/element.factory'; import { BasicStyles, InputAssistancePreset, InputElement, PositionedUIElement, PositionProperties, SchemerData, SchemerValue } from 'common/models/elements/element'; -import { Type } from '@angular/core'; import { ElementComponent } from 'common/directives/element-component.directive'; import { TextFieldComponent } from 'common/components/input-elements/text-field.component'; @@ -26,9 +26,23 @@ export class TextFieldElement extends InputElement implements PositionedUIElemen lineHeight: number; }; - constructor(element: Partial<TextFieldElement>) { - super({ width: 180, height: 120, ...element }); - Object.assign(this, element); + constructor(element: Partial<TextFieldElement>, ...args: unknown[]) { + super({ width: 180, height: 120, ...element }, ...args); + if (element.appearance) this.appearance = element.appearance; + if (element.minLength) this.minLength = element.minLength; + if (element.minLengthWarnMessage) this.minLengthWarnMessage = element.minLengthWarnMessage; + if (element.maxLength) this.maxLength = element.maxLength; + if (element.maxLengthWarnMessage) this.maxLengthWarnMessage = element.maxLengthWarnMessage; + if (element.pattern) this.pattern = element.pattern; + if (element.patternWarnMessage) this.patternWarnMessage = element.patternWarnMessage; + if (element.inputAssistancePreset) this.inputAssistancePreset = element.inputAssistancePreset; + if (element.inputAssistancePosition) this.inputAssistancePosition = element.inputAssistancePosition; + if (element.restrictedToInputAssistanceChars !== undefined) { + this.restrictedToInputAssistanceChars = element.restrictedToInputAssistanceChars; + } + if (element.showSoftwareKeyboard) this.showSoftwareKeyboard = element.showSoftwareKeyboard; + if (element.softwareKeyboardShowFrench) this.softwareKeyboardShowFrench = element.softwareKeyboardShowFrench; + if (element.clearable) this.clearable = element.clearable; this.position = ElementFactory.initPositionProps(element.position); this.styling = { ...ElementFactory.initStylingProps({ diff --git a/projects/common/models/elements/media-elements/audio.ts b/projects/common/models/elements/media-elements/audio.ts index 74258545e979e57128443ad88cd326fde31d73d3..388ac28b0c37f72d709b77e63f04cd14fb9e284b 100644 --- a/projects/common/models/elements/media-elements/audio.ts +++ b/projects/common/models/elements/media-elements/audio.ts @@ -1,16 +1,16 @@ +import { Type } from '@angular/core'; import { ElementFactory } from 'common/util/element.factory'; import { PlayerElement, PositionedUIElement, PositionProperties } from 'common/models/elements/element'; -import { Type } from '@angular/core'; import { ElementComponent } from 'common/directives/element-component.directive'; import { AudioComponent } from 'common/components/media-elements/audio.component'; export class AudioElement extends PlayerElement implements PositionedUIElement { - src: string | undefined; + src: string | null = null; position: PositionProperties; - constructor(element: Partial<AudioElement>) { - super({ width: 250, height: 90, ...element }); - Object.assign(this, element); + constructor(element: Partial<AudioElement>, ...args: unknown[]) { + super({ width: 250, height: 90, ...element }, ...args); + if (element.src) this.src = element.src; this.position = ElementFactory.initPositionProps(element.position); } diff --git a/projects/common/models/elements/media-elements/image.ts b/projects/common/models/elements/media-elements/image.ts index efc64c08779e56b1347308e5956b71786fb1cb30..f189af3e1b76ef119e16098a283e923b55c675e2 100644 --- a/projects/common/models/elements/media-elements/image.ts +++ b/projects/common/models/elements/media-elements/image.ts @@ -1,11 +1,11 @@ +import { Type } from '@angular/core'; import { ElementFactory } from 'common/util/element.factory'; import { PositionedUIElement, PositionProperties, UIElement } from 'common/models/elements/element'; -import { Type } from '@angular/core'; import { ElementComponent } from 'common/directives/element-component.directive'; import { ImageComponent } from 'common/components/media-elements/image.component'; export class ImageElement extends UIElement implements PositionedUIElement { - src: string | undefined; + src: string | null = null; scale: boolean = false; magnifier: boolean = false; magnifierSize: number = 100; @@ -13,9 +13,14 @@ export class ImageElement extends UIElement implements PositionedUIElement { magnifierUsed: boolean = false; position: PositionProperties; - constructor(element: Partial<ImageElement>) { - super({ height: 100, ...element }); - Object.assign(this, element); + constructor(element: Partial<ImageElement>, ...args: unknown[]) { + super({ height: 100, ...element }, ...args); + if (element.src) this.src = element.src; + if (element.scale) this.scale = element.scale; + if (element.magnifier) this.magnifier = element.magnifier; + if (element.magnifierSize) this.magnifierSize = element.magnifierSize; + if (element.magnifierZoom) this.magnifierZoom = element.magnifierZoom; + if (element.magnifierUsed) this.magnifierUsed = element.magnifierUsed; this.position = ElementFactory.initPositionProps(element.position); } diff --git a/projects/common/models/elements/media-elements/video.ts b/projects/common/models/elements/media-elements/video.ts index b3a77f1ba5d4047a34a1ed250dc8dffa6e83f7c4..89ff862e1664ea3f8bf9793b51aafbfd3eaad7d8 100644 --- a/projects/common/models/elements/media-elements/video.ts +++ b/projects/common/models/elements/media-elements/video.ts @@ -1,6 +1,6 @@ +import { Type } from '@angular/core'; import { ElementFactory } from 'common/util/element.factory'; import { PlayerElement, PositionedUIElement, PositionProperties } from 'common/models/elements/element'; -import { Type } from '@angular/core'; import { ElementComponent } from 'common/directives/element-component.directive'; import { VideoComponent } from 'common/components/media-elements/video.component'; @@ -9,9 +9,10 @@ export class VideoElement extends PlayerElement implements PositionedUIElement { scale: boolean = false; position: PositionProperties; - constructor(element: Partial<VideoElement>) { - super({ width: 280, height: 230, ...element }); - Object.assign(this, element); + constructor(element: Partial<VideoElement>, ...args: unknown[]) { + super({ width: 280, height: 230, ...element }, ...args); + if (element.src) this.src = element.src; + if (element.scale) this.scale = element.scale; this.position = ElementFactory.initPositionProps(element.position); } diff --git a/projects/common/models/elements/text/text.ts b/projects/common/models/elements/text/text.ts index 404a75f9224c750e6fcd21040918c006bfa08ba9..e86291c9404736c634547ca0e1770f1d963fde4a 100644 --- a/projects/common/models/elements/text/text.ts +++ b/projects/common/models/elements/text/text.ts @@ -1,11 +1,9 @@ +import { Type } from '@angular/core'; import { ElementFactory } from 'common/util/element.factory'; import { - BasicStyles, - PositionedUIElement, - PositionProperties, SchemerData, SchemerValue, - UIElement + BasicStyles, PositionedUIElement, + PositionProperties, SchemerData, UIElement } from 'common/models/elements/element'; -import { Type } from '@angular/core'; import { ElementComponent } from 'common/directives/element-component.directive'; import { TextComponent } from 'common/components/text/text.component'; @@ -20,9 +18,13 @@ export class TextElement extends UIElement implements PositionedUIElement { lineHeight: number; }; - constructor(element: Partial<TextElement>) { - super({ height: 98, ...element }); - Object.assign(this, element); + constructor(element: Partial<TextElement>, ...args: unknown[]) { + super({ height: 98, ...element }, ...args); + if (element.text) this.text = element.text; + if (element.highlightableOrange) this.highlightableOrange = element.highlightableOrange; + if (element.highlightableTurquoise) this.highlightableTurquoise = element.highlightableTurquoise; + if (element.highlightableYellow) this.highlightableYellow = element.highlightableYellow; + if (element.columnCount) this.columnCount = element.columnCount; this.position = ElementFactory.initPositionProps(element.position); this.styling = { ...ElementFactory.initStylingProps({ @@ -33,7 +35,7 @@ export class TextElement extends UIElement implements PositionedUIElement { }; } - isHighlightable(): boolean { + private isHighlightable(): boolean { return this.highlightableYellow || this.highlightableTurquoise || this.highlightableOrange; diff --git a/projects/common/models/page.ts b/projects/common/models/page.ts index 06752164466eba1805cfd3a25b1df420225d5fa0..df8fcda487911a3c9cc9a4709684d1d8d8e874f9 100644 --- a/projects/common/models/page.ts +++ b/projects/common/models/page.ts @@ -1,7 +1,9 @@ import { Section } from 'common/models/section'; +import { IDManager } from 'common/util/id-manager'; +import { UIElement } from 'common/models/elements/element'; export class Page { - [index: string]: any; + [index: string]: unknown; sections: Section[] = []; hasMaxWidth: boolean = false; maxWidth: number = 900; @@ -11,8 +13,18 @@ export class Page { alwaysVisiblePagePosition: 'left' | 'right' | 'top' | 'bottom' = 'left'; alwaysVisibleAspectRatio: number = 50; - constructor(page?: Page) { - Object.assign(this, page); - this.sections = page?.sections.map(section => new Section(section)) || [new Section()]; + constructor(page?: Partial<Page>, idManager?: IDManager) { + if (page?.hasMaxWidth) this.hasMaxWidth = page.hasMaxWidth; + if (page?.maxWidth) this.maxWidth = page.maxWidth; + if (page?.margin !== undefined) this.margin = page.margin; + if (page?.backgroundColor) this.backgroundColor = page.backgroundColor; + if (page?.alwaysVisible) this.alwaysVisible = page.alwaysVisible; + if (page?.alwaysVisiblePagePosition) this.alwaysVisiblePagePosition = page.alwaysVisiblePagePosition; + if (page?.alwaysVisibleAspectRatio) this.alwaysVisibleAspectRatio = page.alwaysVisibleAspectRatio; + this.sections = page?.sections?.map(section => new Section(section, idManager)) || [new Section()]; + } + + getAllElements(elementType?: string): UIElement[] { + return this.sections.map(section => section.getAllElements(elementType)).flat(); } } diff --git a/projects/common/models/section.ts b/projects/common/models/section.ts index 19bd845115f2c71f0af2c725dda7cc35b800e688..c7612b91f712c575e6ed55c12b20465f1e0f1796 100644 --- a/projects/common/models/section.ts +++ b/projects/common/models/section.ts @@ -1,9 +1,33 @@ -import { PositionedUIElement, UIElement } from 'common/models/elements/element'; -import { ElementFactory } from 'common/util/element.factory'; +import { Type } from '@angular/core'; +import { IDManager } from 'common/util/id-manager'; +import { PositionedUIElement, UIElement, UIElementValue } from 'common/models/elements/element'; +import { ButtonElement } from 'common/models/elements/button/button'; +import { TextElement } from 'common/models/elements/text/text'; +import { TextFieldElement } from 'common/models/elements/input-elements/text-field'; +import { + TextFieldSimpleElement +} from 'common/models/elements/compound-elements/cloze/cloze-child-elements/text-field-simple'; +import { TextAreaElement } from 'common/models/elements/input-elements/text-area'; +import { CheckboxElement } from 'common/models/elements/input-elements/checkbox'; +import { DropdownElement } from 'common/models/elements/input-elements/dropdown'; +import { RadioButtonGroupElement } from 'common/models/elements/input-elements/radio-button-group'; +import { ImageElement } from 'common/models/elements/media-elements/image'; +import { AudioElement } from 'common/models/elements/media-elements/audio'; +import { VideoElement } from 'common/models/elements/media-elements/video'; +import { LikertElement } from 'common/models/elements/compound-elements/likert/likert'; +import { RadioButtonGroupComplexElement } from 'common/models/elements/input-elements/radio-button-group-complex'; +import { DropListElement } from 'common/models/elements/input-elements/drop-list'; +import { + DropListSimpleElement +} from 'common/models/elements/compound-elements/cloze/cloze-child-elements/drop-list-simple'; +import { ClozeElement } from 'common/models/elements/compound-elements/cloze/cloze'; +import { SliderElement } from 'common/models/elements/input-elements/slider'; +import { SpellCorrectElement } from 'common/models/elements/input-elements/spell-correct'; +import { FrameElement } from 'common/models/elements/frame/frame'; +import { ToggleButtonElement } from 'common/models/elements/compound-elements/cloze/cloze-child-elements/toggle-button'; export class Section { - [index: string]: any; - + [index: string]: unknown; elements: PositionedUIElement[] = []; height: number = 400; backgroundColor: string = '#ffffff'; @@ -14,18 +38,63 @@ export class Section { gridRowSizes: string = '1fr'; activeAfterID: string | null = null; - constructor(section?: Partial<Section>) { - Object.assign(this, section); + static ELEMENT_CLASSES: Record<string, Type<UIElement>> = { + 'text': TextElement, + 'button': ButtonElement, + 'text-field': TextFieldElement, + 'text-field-simple': TextFieldSimpleElement, + 'text-area': TextAreaElement, + 'checkbox': CheckboxElement, + 'dropdown': DropdownElement, + 'radio': RadioButtonGroupElement, + 'image': ImageElement, + 'audio': AudioElement, + 'video': VideoElement, + 'likert': LikertElement, + 'radio-group-images': RadioButtonGroupComplexElement, + 'drop-list': DropListElement, + 'drop-list-simple': DropListSimpleElement, + 'cloze': ClozeElement, + 'slider': SliderElement, + 'spell-correct': SpellCorrectElement, + 'frame': FrameElement, + 'toggle-button': ToggleButtonElement + }; + + constructor(section?: Partial<Section>, idManager?: IDManager) { + if (section?.height) this.height = section.height; + if (section?.backgroundColor) this.backgroundColor = section.backgroundColor; + if (section?.dynamicPositioning !== undefined) this.dynamicPositioning = section.dynamicPositioning; + if (section?.autoColumnSize !== undefined) this.autoColumnSize = section.autoColumnSize; + if (section?.autoRowSize !== undefined) this.autoRowSize = section.autoRowSize; + if (section?.gridColumnSizes !== undefined) this.gridColumnSizes = section.gridColumnSizes; + if (section?.gridRowSizes !== undefined) this.gridRowSizes = section.gridRowSizes; + if (section?.activeAfterID) this.activeAfterID = section.activeAfterID; this.elements = - section?.elements?.map(element => ElementFactory.createElement(element.type, element) as PositionedUIElement) || + section?.elements?.map(element => Section.createElement(element, idManager)) || []; } - setProperty(property: string, value: any): void { + static createElement(element: { type: string } & Partial<UIElement>, idManager?: IDManager): PositionedUIElement { + return new Section.ELEMENT_CLASSES[element.type](element, idManager) as PositionedUIElement; + } + + setProperty(property: string, value: UIElementValue): void { this[property] = value; } - getElements(): UIElement[] { - return this.elements; + addElement(element: PositionedUIElement): void { + this.elements.push(element); + } + + /* Includes children of children, i.e. compound children. */ + getAllElements(elementType?: string): UIElement[] { + let allElements: UIElement[] = + this.elements.map(element => [element, ...element.getChildElements()]) + .flat(); + if (elementType) { + allElements = allElements.filter(element => element.type === elementType); + } + return allElements; } } diff --git a/projects/common/models/unit.ts b/projects/common/models/unit.ts index cb457121da1984cb2010b129945444ff1d0977b3..f23bcd99d4f7b2e9d2423a4e916a8769c4e6a765 100644 --- a/projects/common/models/unit.ts +++ b/projects/common/models/unit.ts @@ -1,13 +1,19 @@ import packageJSON from '../../../package.json'; import { Page } from 'common/models/page'; +import { IDManager } from 'common/util/id-manager'; +import { UIElement } from 'common/models/elements/element'; export class Unit { type = 'aspect-unit-definition'; version: string; pages: Page[] = []; - constructor(unit?: Unit) { + constructor(unit?: Partial<Unit>, idManager?: IDManager) { this.version = packageJSON.config.unit_definition_version; - this.pages = unit?.pages.map(page => new Page(page)) || [new Page()]; + this.pages = unit?.pages?.map(page => new Page(page, idManager)) || [new Page()]; + } + + getAllElements(elementType?: string): UIElement[] { + return this.pages.map(page => page.getAllElements(elementType)).flat(); } } diff --git a/projects/common/services/id.service.spec.ts b/projects/common/services/id.service.spec.ts index 11906bf47d22d149072d2da9c91ef5b1f03fac5e..efa25193236da5c700a42cf861206e3501e24cc1 100644 --- a/projects/common/services/id.service.spec.ts +++ b/projects/common/services/id.service.spec.ts @@ -1,12 +1,11 @@ -import { TestBed } from '@angular/core/testing'; -import { IDService } from 'common/services/id.service'; +import { IDManager } from 'common/util/id-manager'; describe('IDService', () => { - let service: IDService; + let service: IDManager; beforeEach(() => { - TestBed.configureTestingModule({ providers: [IDService] }); - service = TestBed.inject(IDService); + service = IDManager.getInstance(); + service.reset(); }); it('getNewID should fail on empty string param', () => { diff --git a/projects/common/services/sanitization.service.spec.ts b/projects/common/services/sanitization.service.spec.ts index 168e2969ce7feb47eeec8bcb3078a45f2a282deb..913a18f2e54efd4c15496029554bfa7580578d36 100644 --- a/projects/common/services/sanitization.service.spec.ts +++ b/projects/common/services/sanitization.service.spec.ts @@ -25,7 +25,7 @@ describe('SanitizationService', () => { }); it('isUnitDefinitionOutdated should return false on current version', () => { - const basicUnitDefinition: Unit = { + const basicUnitDefinition: Partial<Unit> = { 'type': 'aspect-unit-definition', 'version': packageJSON.config.unit_definition_version, 'pages': [] @@ -34,7 +34,7 @@ describe('SanitizationService', () => { }); it('isUnitDefinitionOutdated should return true on older version', () => { - const unitDefinition: Unit = { + const unitDefinition: Partial<Unit> = { 'type': 'aspect-unit-definition', 'version': '3.3.0', 'pages': [] @@ -43,7 +43,7 @@ describe('SanitizationService', () => { }); it('sanitizeUnitDefinition should return basic unit definition', () => { - const basicUnitDefinition: Unit = { + const basicUnitDefinition: Partial<Unit> = { 'type': 'aspect-unit-definition', 'version': packageJSON.config.unit_definition_version, 'pages': [] @@ -71,7 +71,7 @@ describe('SanitizationService', () => { 'zIndex': 0 } }; - expect(service.sanitizeUnitDefinition(testData130).pages[0].sections[0].elements[0]) + expect(service.sanitizeUnitDefinition(testData130).pages![0].sections[0].elements[0]) .toEqual(jasmine.objectContaining(expectedPositionProps)); }); @@ -85,9 +85,9 @@ describe('SanitizationService', () => { 'gridRowRange': 1 }; const sanitizedUnit126 = service.sanitizeUnitDefinition(JSON.parse(JSON.stringify(sampleUnit126))); - expect(Object.keys(sanitizedUnit126.pages[0].sections[0].elements[0])) + expect(Object.keys(sanitizedUnit126.pages![0].sections[0].elements[0])) .toContain('position'); - expect(sanitizedUnit126.pages[0].sections[0].elements[0].position) + expect(sanitizedUnit126.pages![0].sections[0].elements[0].position) .toEqual(jasmine.objectContaining(expectedPositionProps)); }); @@ -102,9 +102,9 @@ describe('SanitizationService', () => { 'gridRowRange': 1 }; const sanitizedUnit100 = service.sanitizeUnitDefinition(JSON.parse(JSON.stringify(sampleUnit100))); - expect(Object.keys(sanitizedUnit100.pages[0].sections[0].elements[0])) + expect(Object.keys(sanitizedUnit100.pages![0].sections[0].elements[0])) .toContain('position'); - expect(sanitizedUnit100.pages[0].sections[0].elements[0].position) + expect(sanitizedUnit100.pages![0].sections[0].elements[0].position) .toEqual(jasmine.objectContaining(expectedPositionProps)); }); @@ -113,14 +113,14 @@ describe('SanitizationService', () => { const unit = sampleUnit126; unit.pages[0].sections[0].dynamicPositioning = true; const sanitizedUnit = service.sanitizeUnitDefinition(JSON.parse(JSON.stringify(unit))); - expect(sanitizedUnit.pages[0].sections[0].elements[0].position) + expect(sanitizedUnit.pages![0].sections[0].elements[0].position) .toEqual(jasmine.objectContaining({ 'dynamicPositioning': true })); // no change necessary const sanitizedUnit129 = service.sanitizeUnitDefinition(JSON.parse(JSON.stringify(sampleUnit129))); - expect(sanitizedUnit129.pages[0].sections[0].elements[0].position) + expect(sanitizedUnit129.pages![0].sections[0].elements[0].position) .toEqual(jasmine.objectContaining({ 'dynamicPositioning': false })); @@ -138,9 +138,9 @@ describe('SanitizationService', () => { 'lineHeight': 135 }; const sanitizedUnit130 = service.sanitizeUnitDefinition(JSON.parse(JSON.stringify(sampleUnit130))); - expect(Object.keys(sanitizedUnit130.pages[0].sections[0].elements[0])) + expect(Object.keys(sanitizedUnit130.pages![0].sections[0].elements[0])) .toContain('styling'); - expect(sanitizedUnit130.pages[0].sections[0].elements[0].styling) + expect(sanitizedUnit130.pages![0].sections[0].elements[0].styling) .toEqual(jasmine.objectContaining(expectedStylingProps)); }); @@ -156,9 +156,9 @@ describe('SanitizationService', () => { 'backgroundColor': 'red' }; const sanitizedUnit126 = service.sanitizeUnitDefinition(JSON.parse(JSON.stringify(sampleUnit126))); - expect(Object.keys(sanitizedUnit126.pages[0].sections[0].elements[0])) + expect(Object.keys(sanitizedUnit126.pages![0].sections[0].elements[0])) .toContain('styling'); - expect(sanitizedUnit126.pages[0].sections[0].elements[0].styling) + expect(sanitizedUnit126.pages![0].sections[0].elements[0].styling) .toEqual(jasmine.objectContaining(expectedStylingProps)); }); @@ -175,9 +175,9 @@ describe('SanitizationService', () => { 'interactiveProgressbar': false, 'volumeControl': true }; - expect(Object.keys(sanitizedUnit130Audio.pages[0].sections[0].elements[0])) + expect(Object.keys(sanitizedUnit130Audio.pages![0].sections[0].elements[0])) .toContain('player'); - expect(sanitizedUnit130Audio.pages[0].sections[0].elements[0].player) + expect(sanitizedUnit130Audio.pages![0].sections[0].elements[0].player) .toEqual(jasmine.objectContaining(expectedPlayerProps)); }); @@ -197,9 +197,9 @@ describe('SanitizationService', () => { 'minVolume': 0, 'muteControl': true }; - expect(Object.keys(sanitizedUnit126Audio.pages[0].sections[0].elements[0])) + expect(Object.keys(sanitizedUnit126Audio.pages![0].sections[0].elements[0])) .toContain('player'); - expect(sanitizedUnit126Audio.pages[0].sections[0].elements[0].player) + expect(sanitizedUnit126Audio.pages![0].sections[0].elements[0].player) .toEqual(jasmine.objectContaining(expectedPlayerProps)); }); @@ -211,7 +211,7 @@ describe('SanitizationService', () => { highlightableTurquoise: false, highlightableOrange: false }; - expect(sanitizedUnit112Texts.pages[0].sections[0].elements[1]) + expect(sanitizedUnit112Texts.pages![0].sections[0].elements[1]) .not.toEqual(jasmine.objectContaining(expectedTextProps)); }); @@ -223,7 +223,7 @@ describe('SanitizationService', () => { highlightableTurquoise: true, highlightableOrange: true }; - expect(sanitizedUnit112Texts.pages[0].sections[0].elements[0]) + expect(sanitizedUnit112Texts.pages![0].sections[0].elements[0]) .toEqual(jasmine.objectContaining(expectedTextProps)); }); @@ -235,26 +235,26 @@ describe('SanitizationService', () => { highlightableTurquoise: false, highlightableOrange: false }; - expect(sanitizedUnit112Texts.pages[0].sections[0].elements[2]) + expect(sanitizedUnit112Texts.pages![0].sections[0].elements[2]) .not.toEqual(jasmine.objectContaining(expectedTextProps)); }); it('sanitizeUnitDefinition should return text field element', () => { const sanitizedUnit130TextFields = service.sanitizeUnitDefinition(JSON.parse(JSON.stringify(sampleUnit130TextFields))); - expect(sanitizedUnit130TextFields.pages[0].sections[0].elements[0]) + expect(sanitizedUnit130TextFields.pages![0].sections[0].elements[0]) .toEqual(jasmine.objectContaining({ 'inputAssistancePreset': null, 'inputAssistancePosition': 'floating', 'restrictedToInputAssistanceChars': false })); - expect(sanitizedUnit130TextFields.pages[0].sections[0].elements[1]) + expect(sanitizedUnit130TextFields.pages![0].sections[0].elements[1]) .toEqual(jasmine.objectContaining({ 'inputAssistancePreset': 'french', 'inputAssistancePosition': 'floating', 'restrictedToInputAssistanceChars': true })); - expect(sanitizedUnit130TextFields.pages[0].sections[0].elements[2]) + expect(sanitizedUnit130TextFields.pages![0].sections[0].elements[2]) .toEqual(jasmine.objectContaining({ 'inputAssistancePreset': 'numbers', 'inputAssistancePosition': 'floating', @@ -266,41 +266,41 @@ describe('SanitizationService', () => { it('sanitizeUnitDefinition should repair text field and area elements', () => { const sanitizedUnit112TextFields = service.sanitizeUnitDefinition(JSON.parse(JSON.stringify(sampleUnit112TextFields))); - expect(sanitizedUnit112TextFields.pages[0].sections[0].elements[0]) + expect(sanitizedUnit112TextFields.pages![0].sections[0].elements[0]) .toEqual(jasmine.objectContaining({ 'inputAssistancePreset': null, 'inputAssistancePosition': 'floating' })); - expect(sanitizedUnit112TextFields.pages[0].sections[0].elements[0]) + expect(sanitizedUnit112TextFields.pages![0].sections[0].elements[0]) .not.toEqual(jasmine.objectContaining({ 'restrictedToInputAssistanceChars': false })); - expect(sanitizedUnit112TextFields.pages[0].sections[0].elements[1]) + expect(sanitizedUnit112TextFields.pages![0].sections[0].elements[1]) .toEqual(jasmine.objectContaining({ 'inputAssistancePreset': 'french', 'inputAssistancePosition': 'right', 'restrictedToInputAssistanceChars': false })); - expect(sanitizedUnit112TextFields.pages[0].sections[0].elements[2]) + expect(sanitizedUnit112TextFields.pages![0].sections[0].elements[2]) .toEqual(jasmine.objectContaining({ 'inputAssistancePreset': 'numbers', 'inputAssistancePosition': 'floating' })); - expect(sanitizedUnit112TextFields.pages[0].sections[0].elements[2]) + expect(sanitizedUnit112TextFields.pages![0].sections[0].elements[2]) .not.toEqual(jasmine.objectContaining({ 'restrictedToInputAssistanceChars': false })); // text areas - expect(sanitizedUnit112TextFields.pages[0].sections[0].elements[3]) + expect(sanitizedUnit112TextFields.pages![0].sections[0].elements[3]) .toEqual(jasmine.objectContaining({ 'inputAssistancePreset': null, 'inputAssistancePosition': 'floating' })); - expect(sanitizedUnit112TextFields.pages[0].sections[0].elements[3]) + expect(sanitizedUnit112TextFields.pages![0].sections[0].elements[3]) .not.toEqual(jasmine.objectContaining({ 'restrictedToInputAssistanceChars': false })); - expect(sanitizedUnit112TextFields.pages[0].sections[0].elements[4]) + expect(sanitizedUnit112TextFields.pages![0].sections[0].elements[4]) .toEqual(jasmine.objectContaining({ 'inputAssistancePreset': 'french', 'inputAssistancePosition': 'floating', @@ -356,13 +356,13 @@ describe('SanitizationService', () => { }; const sanitizedUnitDefinition = service.sanitizeUnitDefinition(unitDef as unknown as Unit); - expect(sanitizedUnitDefinition.pages[0].sections[0].elements[0]) + expect(sanitizedUnitDefinition.pages![0].sections[0].elements[0]) .toEqual(jasmine.objectContaining({ 'id': 'cloze_1', 'type': 'cloze' })); - const sanatizedClozeChild1 = (sanitizedUnitDefinition.pages[0].sections[0].elements[0] as ClozeElement) + const sanatizedClozeChild1 = (sanitizedUnitDefinition.pages![0].sections[0].elements[0] as ClozeElement) .document.content[0].content[0]?.attrs?.model; expect(sanatizedClozeChild1) .toEqual(jasmine.objectContaining({ @@ -370,7 +370,7 @@ describe('SanitizationService', () => { 'type': 'text-field-simple' })); - const sanatizedClozeChild2 = (sanitizedUnitDefinition.pages[0].sections[0].elements[0] as ClozeElement) + const sanatizedClozeChild2 = (sanitizedUnitDefinition.pages![0].sections[0].elements[0] as ClozeElement) .document.content[0].content[1]?.attrs?.model; expect(sanatizedClozeChild2) .toEqual(jasmine.objectContaining({ @@ -378,7 +378,7 @@ describe('SanitizationService', () => { 'type': 'drop-list-simple' })); - const sanatizedClozeChild3 = (sanitizedUnitDefinition.pages[0].sections[0].elements[0] as ClozeElement) + const sanatizedClozeChild3 = (sanitizedUnitDefinition.pages![0].sections[0].elements[0] as ClozeElement) .document.content[0].content[2]?.attrs?.model; expect(sanatizedClozeChild3) .toEqual(jasmine.objectContaining({ diff --git a/projects/common/services/sanitization.service.ts b/projects/common/services/sanitization.service.ts index b807bd021604016a19d59ecdc7c0b539097924af..bf57a0c950602eedee9021dc8a17ef396a98d676 100644 --- a/projects/common/services/sanitization.service.ts +++ b/projects/common/services/sanitization.service.ts @@ -2,15 +2,15 @@ import { Injectable } from '@angular/core'; import packageJSON from '../../../package.json'; import { Editor } from '@tiptap/core'; import StarterKit from '@tiptap/starter-kit'; -import ToggleButtonExtension from 'common/models/elements/compound-elements/cloze/tiptap-editor-extensions/toggle-button'; +import ToggleButtonExtension from + 'common/models/elements/compound-elements/cloze/tiptap-editor-extensions/toggle-button'; import DropListExtension from 'common/models/elements/compound-elements/cloze/tiptap-editor-extensions/drop-list'; import TextFieldExtension from 'common/models/elements/compound-elements/cloze/tiptap-editor-extensions/text-field'; -import { IDService } from './id.service'; import { Unit } from 'common/models/unit'; import { BasicStyles, DragNDropValueObject, ExtendedStyles, InputElement, PlayerProperties, - PositionedUIElement, PositionProperties, + PositionedUIElement, PositionProperties, TextImageLabel, UIElement, UIElementValue } from 'common/models/elements/element'; import { LikertElement } from 'common/models/elements/compound-elements/likert/likert'; @@ -27,30 +27,29 @@ import { import { DropListElement } from 'common/models/elements/input-elements/drop-list'; import { Page } from 'common/models/page'; import { Section } from 'common/models/section'; +import { IDManager } from 'common/util/id-manager'; @Injectable({ providedIn: 'root' }) export class SanitizationService { - constructor(private iDService: IDService) { } - private static expectedUnitVersion: [number, number, number] = packageJSON.config.unit_definition_version.split('.') as unknown as [number, number, number]; private static unitDefinitionVersion: [number, number, number] | undefined; // TODO: isUnitDefinitionOutdated must not set the unitDefinitionVersion - static isUnitDefinitionOutdated(unitDefinition: Unit): boolean { + static isUnitDefinitionOutdated(unitDefinition: Partial<Unit>): boolean { SanitizationService.unitDefinitionVersion = SanitizationService.readUnitDefinitionVersion(unitDefinition as unknown as Record<string, string>); return SanitizationService.isVersionOlderThanCurrent(SanitizationService.unitDefinitionVersion); } - sanitizeUnitDefinition(unitDefinition: Unit): Unit { + sanitizeUnitDefinition(unitDefinition: Partial<Unit>): Partial<Unit> { return { ...unitDefinition, - pages: unitDefinition.pages.map((page: Page) => this.sanitizePage(page)) + pages: unitDefinition.pages?.map((page: Page) => this.sanitizePage(page)) as Page[] }; } @@ -73,7 +72,7 @@ export class SanitizationService { return version[2] < SanitizationService.expectedUnitVersion[2]; } - private sanitizePage(page: Page): Page { + private sanitizePage(page: Page): Partial<Page> { return { ...page, sections: page.sections.map((section: Section) => this.sanitizeSection(section)) @@ -84,7 +83,10 @@ export class SanitizationService { return { ...section, elements: section.elements.map((element: UIElement) => ( - this.sanitizeElement(element, section.dynamicPositioning))) as PositionedUIElement[] + this.sanitizeElement( + element as Record<string, UIElementValue>, + section.dynamicPositioning + ))) as PositionedUIElement[] } as Section; } @@ -97,11 +99,11 @@ export class SanitizationService { player: SanitizationService.getPlayerProps(element) }; if (newElement.type === 'text') { - newElement = SanitizationService.handleTextElement(newElement); + newElement = SanitizationService.handleTextElement(newElement as Record<string, UIElementValue>); } if (['text-field', 'text-area', 'text-field-simple', 'spell-correct'] .includes(newElement.type as string)) { - newElement = SanitizationService.sanitizeTextInputElement(newElement); + newElement = SanitizationService.sanitizeTextInputElement(newElement as Record<string, UIElementValue>); } if (newElement.type === 'cloze') { newElement = this.handleClozeElement(newElement as Record<string, UIElementValue>); @@ -109,8 +111,7 @@ export class SanitizationService { if (newElement.type === 'toggle-button') { newElement = SanitizationService.handleToggleButtonElement(newElement as ToggleButtonElement); } - // TODO: drop-list-simple? - if (newElement.type === 'drop-list') { + if (['drop-list', 'drop-list-simple'].includes(newElement.type as string)) { newElement = this.handleDropListElement(newElement as Record<string, UIElementValue>); } if (['dropdown', 'radio', 'likert-row', 'radio-group-images', 'toggle-button'] @@ -124,7 +125,7 @@ export class SanitizationService { newElement = this.handleLikertElement(newElement as LikertElement); } if (['likert-row', 'likert_row'].includes(newElement.type as string)) { - newElement = SanitizationService.handleLikertRowElement(newElement as LikertRowElement); + newElement = SanitizationService.handleLikertRowElement(newElement as Record<string, UIElementValue>); } return newElement as unknown as UIElement; @@ -247,8 +248,8 @@ export class SanitizationService { // TODO: create a sub method if (element.document) { - childElements = new ClozeElement(element).getChildElements(); doc = element.document as ClozeDocument; + childElements = ClozeElement.getDocumentChildElements(doc); } else { childElements = (element.parts as any[]) .map((el: any) => el @@ -280,7 +281,7 @@ export class SanitizationService { ...paraPart, attrs: { ...paraPart.attrs, - model: this.sanitizeElement(childElements.shift()!) + model: this.sanitizeElement(childElements.shift() as Record<string, UIElementValue>) } } : { @@ -322,7 +323,7 @@ export class SanitizationService { newElement.value = []; (newElement.options as string[]).forEach(option => { (newElement.value as DragNDropValueObject[]).push({ - id: this.iDService.getNewID('value'), + id: IDManager.getInstance().getNewID('value'), stringValue: option }); }); @@ -331,7 +332,7 @@ export class SanitizationService { const newValues: DragNDropValueObject[] = []; (newElement.value as string[]).forEach(value => { newValues.push({ - id: this.iDService.getNewID('value'), + id: IDManager.getInstance().getNewID('value'), stringValue: value }); }); @@ -343,11 +344,12 @@ export class SanitizationService { private handleLikertElement(element: LikertElement): LikertElement { return new LikertElement({ ...element, - rows: element.rows.map((row: LikertRowElement) => this.sanitizeElement(row) as LikertRowElement) + rows: element.rows + .map((row: LikertRowElement) => this.sanitizeElement(row as Record<string, UIElementValue>) as LikertRowElement) }); } - private static handleLikertRowElement(element: LikertRowElement): LikertRowElement { + private static handleLikertRowElement(element: Record<string, UIElementValue>): Partial<LikertRowElement> { if (element.rowLabel) { return element; } @@ -357,7 +359,7 @@ export class SanitizationService { text: element.text, imgSrc: null, position: 'above' - } + } as TextImageLabel }); } diff --git a/projects/common/util/element.factory.ts b/projects/common/util/element.factory.ts index 3c87e4dddf88a52819067bdff6fb4aebf23ec3d5..7573c6f82d674b400bea45fdb2b718171d48b3ef 100644 --- a/projects/common/util/element.factory.ts +++ b/projects/common/util/element.factory.ts @@ -1,80 +1,9 @@ import { - BasicStyles, PlayerProperties, PositionProperties, TextImageLabel, - UIElement, UIElementValue + BasicStyles, PlayerProperties, PositionProperties, TextImageLabel } from 'common/models/elements/element'; -import { TextElement } from 'common/models/elements/text/text'; -import { ButtonElement } from 'common/models/elements/button/button'; -import { TextFieldElement } from 'common/models/elements/input-elements/text-field'; -import { TextFieldSimpleElement } from - 'common/models/elements/compound-elements/cloze/cloze-child-elements/text-field-simple'; -import { CheckboxElement } from 'common/models/elements/input-elements/checkbox'; -import { TextAreaElement } from 'common/models/elements/input-elements/text-area'; -import { DropdownElement } from 'common/models/elements/input-elements/dropdown'; -import { RadioButtonGroupElement } from 'common/models/elements/input-elements/radio-button-group'; -import { VideoElement } from 'common/models/elements/media-elements/video'; -import { ImageElement } from 'common/models/elements/media-elements/image'; -import { AudioElement } from 'common/models/elements/media-elements/audio'; -import { LikertElement } from 'common/models/elements/compound-elements/likert/likert'; -import { RadioButtonGroupComplexElement } from 'common/models/elements/input-elements/radio-button-group-complex'; -import { DropListElement } from 'common/models/elements/input-elements/drop-list'; -import { DropListSimpleElement } from - 'common/models/elements/compound-elements/cloze/cloze-child-elements/drop-list-simple'; -import { ClozeElement } from 'common/models/elements/compound-elements/cloze/cloze'; -import { SliderElement } from 'common/models/elements/input-elements/slider'; -import { SpellCorrectElement } from 'common/models/elements/input-elements/spell-correct'; -import { FrameElement } from 'common/models/elements/frame/frame'; -import { ToggleButtonElement } from 'common/models/elements/compound-elements/cloze/cloze-child-elements/toggle-button'; export abstract class ElementFactory { - static createElement(elementType: string, defaultValues: Partial<UIElement> = {}): UIElement { - switch (elementType) { - case 'text': - return new TextElement({ type: elementType, ...defaultValues } as TextElement); - case 'button': - return new ButtonElement({ type: elementType, ...defaultValues } as ButtonElement); - case 'text-field': - return new TextFieldElement({ type: elementType, ...defaultValues } as TextFieldElement); - case 'text-field-simple': - return new TextFieldSimpleElement({ type: elementType, ...defaultValues } as TextFieldSimpleElement); - case 'text-area': - return new TextAreaElement({ type: elementType, ...defaultValues } as TextAreaElement); - case 'checkbox': - return new CheckboxElement({ type: elementType, ...defaultValues } as CheckboxElement); - case 'dropdown': - return new DropdownElement({ type: elementType, ...defaultValues } as DropdownElement); - case 'radio': - return new RadioButtonGroupElement({ type: elementType, ...defaultValues } as RadioButtonGroupElement); - case 'image': - return new ImageElement({ type: elementType, ...defaultValues } as ImageElement); - case 'audio': - return new AudioElement({ type: elementType, ...defaultValues } as AudioElement); - case 'video': - return new VideoElement({ type: elementType, ...defaultValues } as VideoElement); - case 'likert': - return new LikertElement({ type: elementType, ...defaultValues } as LikertElement); - case 'radio-group-images': - return new RadioButtonGroupComplexElement({ - type: elementType, ...defaultValues } as RadioButtonGroupComplexElement); - case 'drop-list': - return new DropListElement({ type: elementType, ...defaultValues } as DropListElement); - case 'drop-list-simple': - return new DropListSimpleElement({ type: elementType, ...defaultValues } as DropListSimpleElement); - case 'cloze': - return new ClozeElement({ type: elementType, ...defaultValues } as ClozeElement); - case 'slider': - return new SliderElement({ type: elementType, ...defaultValues } as SliderElement); - case 'spell-correct': - return new SpellCorrectElement({ type: elementType, ...defaultValues } as SpellCorrectElement); - case 'frame': - return new FrameElement({ type: elementType, ...defaultValues } as FrameElement); - case 'toggle-button': - return new ToggleButtonElement({ type: elementType, ...defaultValues } as ToggleButtonElement); - default: - throw new Error(`ElementType ${elementType} not found!`); - } - } - - static initPositionProps(defaults: Record<string, UIElementValue> = {}): PositionProperties { + static initPositionProps(defaults: Partial<PositionProperties> = {}): PositionProperties { return { fixedSize: defaults.fixedSize !== undefined ? defaults.fixedSize as boolean : false, dynamicPositioning: defaults.dynamicPositioning !== undefined ? defaults.dynamicPositioning as boolean : true, @@ -114,7 +43,7 @@ export abstract class ElementFactory { }; } - static initPlayerProps(defaults: Record<string, UIElementValue> = {}): PlayerProperties { + static initPlayerProps(defaults: Partial<PlayerProperties> = {}): PlayerProperties { return { autostart: defaults.autostart !== undefined ? defaults.autostart as boolean : false, autostartDelay: defaults.autostartDelay !== undefined ? defaults.autostartDelay as number : 0, diff --git a/projects/common/services/id.service.ts b/projects/common/util/id-manager.ts similarity index 79% rename from projects/common/services/id.service.ts rename to projects/common/util/id-manager.ts index 1e39e4829cbb20e081c24848e4294ba23b6aa7f5..09bc3eb92a4458c4a671b5d1d9601ab585a36745 100644 --- a/projects/common/services/id.service.ts +++ b/projects/common/util/id-manager.ts @@ -1,10 +1,7 @@ -import { Injectable } from '@angular/core'; +export class IDManager { + private static instance: IDManager; -@Injectable({ - providedIn: 'root' -}) -export class IDService { - private givenIDs: string[] = []; + givenIDs: string[] = []; private idCounter: Record<string, number> = { text: 0, button: 0, @@ -19,7 +16,7 @@ export class IDService { audio: 0, video: 0, likert: 0, - likert_row: 0, + 'likert-row': 0, slider: 0, 'spell-correct': 0, 'radio-group-images': 0, @@ -31,15 +28,17 @@ export class IDService { value: 0 }; + static getInstance() { + return this.instance || (this.instance = new this()); + } + getNewID(type: string): string { if (!type) { throw Error('ID-Service: No type given!'); } do { this.idCounter[type] += 1; - } - while (!this.isIdAvailable(`${type}_${this.idCounter[type]}`)); - this.givenIDs.push(`${type}_${this.idCounter[type]}`); + } while (!this.isIdAvailable(`${type}_${this.idCounter[type]}`)); return `${type}_${this.idCounter[type]}`; } diff --git a/projects/common/util/unit-utils.ts b/projects/common/util/unit-utils.ts index b242c9c2eaafcfdb9f12dd8ac401d1957052a81e..c5989685a249600d2d3a5951d344f7b7c0070531 100644 --- a/projects/common/util/unit-utils.ts +++ b/projects/common/util/unit-utils.ts @@ -1,6 +1,6 @@ import { UIElement, UIElementType } from 'common/models/elements/element'; -export abstract class UnitUtils { +export abstract class UnitUtils { // TODO delete this. replce by unit method static findUIElements(value: any | unknown[], type?: UIElementType): UIElement[] { const elements: UIElement[] = []; if (value && typeof value === 'object') { diff --git a/projects/editor/src/app/app.module.ts b/projects/editor/src/app/app.module.ts index 58cc15c18317fff70e6bad8215789525d0a40b44..605bbb69eaa6b5a75852a5986f79959678306e6e 100644 --- a/projects/editor/src/app/app.module.ts +++ b/projects/editor/src/app/app.module.ts @@ -70,6 +70,7 @@ import { ImagePropertiesComponent } from './components/properties-panel/model-pr import { DropListPropertiesComponent } from './components/properties-panel/model-properties-tab/input-groups/drop-list-properties.component'; import { RichTextEditorSimpleComponent } from './text-editor-simple/rich-text-editor-simple.component'; import { RichTextSimpleEditDialogComponent } from './components/dialogs/rich-text-simple-edit-dialog.component'; +import { SectionInsertDialogComponent } from 'editor/src/app/components/dialogs/section-insert-dialog.component'; @NgModule({ declarations: [ @@ -111,7 +112,8 @@ import { RichTextSimpleEditDialogComponent } from './components/dialogs/rich-tex ImagePropertiesComponent, DropListPropertiesComponent, RichTextEditorSimpleComponent, - RichTextSimpleEditDialogComponent + RichTextSimpleEditDialogComponent, + SectionInsertDialogComponent ], imports: [ BrowserModule, diff --git a/projects/editor/src/app/components/canvas/canvas.component.html b/projects/editor/src/app/components/canvas/canvas.component.html index d99ef49a9a7c8149f9280bae6a9ab4617a471fbc..8f130d24db7e979cbbdf3673e53e1141b395668f 100644 --- a/projects/editor/src/app/components/canvas/canvas.component.html +++ b/projects/editor/src/app/components/canvas/canvas.component.html @@ -14,7 +14,7 @@ [allowDelete]="page.sections.length > 1" (moveSection)="unitService.moveSection(section, page, $event)" (duplicateSection)="unitService.duplicateSection(section, page, i)" - (selectElementComponent)="selectElementComponent($event)"> + (selectElementComponent)="selectElementOverlay($event)"> </aspect-section-menu> <aspect-section-static *ngIf="!section.dynamicPositioning" #sectionComponent diff --git a/projects/editor/src/app/components/canvas/canvas.component.ts b/projects/editor/src/app/components/canvas/canvas.component.ts index b7346e505cf4671a765f421c259e6f4d9ec76d3a..4fc948d6be6d257dbd081aa5b354b9941f5442b1 100644 --- a/projects/editor/src/app/components/canvas/canvas.component.ts +++ b/projects/editor/src/app/components/canvas/canvas.component.ts @@ -25,7 +25,7 @@ import { Section } from 'common/models/section'; export class CanvasComponent { @Input() page!: Page; @ViewChildren('sectionComponent') - childSectionComponents!: QueryList<SectionStaticComponent | SectionDynamicComponent>; + sectionComponents!: QueryList<SectionStaticComponent | SectionDynamicComponent>; constructor(public selectionService: SelectionService, public unitService: UnitService) { } @@ -75,8 +75,8 @@ export class CanvasComponent { this.selectionService.selectedPageSectionIndex = this.page.sections.length - 1; } - selectElementComponent(element: UIElement): void { - const elementComponent = this.getElementComponent(element); + selectElementOverlay(element: UIElement): void { + const elementComponent = this.getElementOverlay(element); if (elementComponent) { this.selectionService.selectElement({ elementComponent: elementComponent, multiSelect: false }); } else { @@ -84,8 +84,8 @@ export class CanvasComponent { } } - private getElementComponent(element: UIElement): CanvasElementOverlay | null { - for (const sectionComponent of this.childSectionComponents.toArray()) { + private getElementOverlay(element: UIElement): CanvasElementOverlay | null { + for (const sectionComponent of this.sectionComponents.toArray()) { for (const elementComponent of sectionComponent.childElementComponents.toArray()) { if (elementComponent.element.id === element.id) { return elementComponent; diff --git a/projects/editor/src/app/components/canvas/dynamic-section-helper-grid.component.ts b/projects/editor/src/app/components/canvas/dynamic-section-helper-grid.component.ts index a4fb2e4452d4967b48f9d89b870d86e5eb490a0b..f8c898252a7e5a65c19df8988a3519727aba2084 100644 --- a/projects/editor/src/app/components/canvas/dynamic-section-helper-grid.component.ts +++ b/projects/editor/src/app/components/canvas/dynamic-section-helper-grid.component.ts @@ -17,7 +17,7 @@ import { Section } from 'common/models/section'; [style.grid-row-start]="y + 1" [style.grid-row-end]="y + 1" cdkDropList [cdkDropListData]="{ sectionIndex: sectionIndex, gridCoordinates: [x + 1, y + 1] }" - (cdkDropListDropped)="drop($any($event))" + (cdkDropListDropped)="drop($event)" id="list-{{sectionIndex}}-{{x+1}}-{{y+1}}" (dragover)="$event.preventDefault()" (drop)="newElementDropped( $event, x + 1, y + 1)"> @@ -97,7 +97,7 @@ export class DynamicSectionHelperGridComponent implements OnInit, OnChanges { this.rowCountArray = Array(Math.max(numberOfRows, 1)); } - drop(event: CdkDragDrop<{ sectionIndex: number; gridCoordinates?: number[]; }>): void { + drop(event: CdkDragDrop<{ sectionIndex: number; gridCoordinates: number[]; }>): void { const dragItemData: { dragType: string; element: UIElement; } = event.item.data; // Move element to other section - handled by parent (page-canvas). @@ -108,26 +108,26 @@ export class DynamicSectionHelperGridComponent implements OnInit, OnChanges { }); } if (dragItemData.dragType === 'move') { - this.unitService.updateElementsProperty( + this.unitService.updateElementsPositionProperty( [event.item.data.element], 'gridColumn', - event.container.data.gridCoordinates![0] + event.container.data.gridCoordinates[0] ); - this.unitService.updateElementsProperty( + this.unitService.updateElementsPositionProperty( [dragItemData.element], 'gridRow', - event.container.data.gridCoordinates![1] + event.container.data.gridCoordinates[1] ); } else if (event.item.data.dragType === 'resize') { - this.unitService.updateElementsProperty( + this.unitService.updateElementsPositionProperty( [dragItemData.element], 'gridColumnEnd', - event.container.data.gridCoordinates![0] + 1 + event.container.data.gridCoordinates[0] + 1 ); - this.unitService.updateElementsProperty( + this.unitService.updateElementsPositionProperty( [dragItemData.element], 'gridRowEnd', - event.container.data.gridCoordinates![1] + 1 + event.container.data.gridCoordinates[1] + 1 ); } else { throw new Error('Unknown drop event'); diff --git a/projects/editor/src/app/components/canvas/overlays/canvas-element-overlay.ts b/projects/editor/src/app/components/canvas/overlays/canvas-element-overlay.ts index 73ff5e32d38a9813d753dc579521db11d28805ff..1388d7d083fd83c859ec15bce599e5434caac81e 100644 --- a/projects/editor/src/app/components/canvas/overlays/canvas-element-overlay.ts +++ b/projects/editor/src/app/components/canvas/overlays/canvas-element-overlay.ts @@ -10,7 +10,7 @@ import { SelectionService } from '../../../services/selection.service'; import { CompoundElementComponent } from 'common/directives/compound-element.directive'; import { ClozeComponent } from 'common/components/compound-elements/cloze/cloze.component'; import { CompoundChildOverlayComponent } from - 'common/components/compound-elements/cloze/compound-child-overlay.component'; + 'common/components/compound-elements/cloze/compound-child-overlay.component'; import { UIElement } from 'common/models/elements/element'; @Directive() @@ -70,7 +70,7 @@ export abstract class CanvasElementOverlay implements OnInit, OnDestroy { } } - elementClicked(event: MouseEvent): void { + elementClicked(event: MouseEvent): void { //TODO method name if (!this.isSelected) { this.selectElement(event.shiftKey); } diff --git a/projects/editor/src/app/components/canvas/section-menu.component.ts b/projects/editor/src/app/components/canvas/section-menu.component.ts index 638b05422369b5acd27cd37b00bebe30673471f1..b3ce0f25425cdfaa65f000d18d65ec4176607c73 100644 --- a/projects/editor/src/app/components/canvas/section-menu.component.ts +++ b/projects/editor/src/app/components/canvas/section-menu.component.ts @@ -23,7 +23,7 @@ import { Section } from 'common/models/section'; <mat-menu #elementListMenu="matMenu" class="layoutMenu" xPosition="before"> <mat-action-list> <ng-container *ngIf="section.elements.length === 0"> - Keine Elemente im Abschnitt + Keine Elemente im Abschnitt </ng-container> <mat-list-item *ngFor="let element of section.elements" (click)="selectElement(element)"> @@ -68,7 +68,7 @@ import { Section } from 'common/models/section'; <ng-container *ngIf="!section.dynamicPositioning"> <mat-form-field appearance="fill"> <mat-label>{{'section-menu.height' | translate }}</mat-label> - <input matInput type="number" + <input matInput type="number" [value]="$any(section.height)" (click)="$any($event).stopPropagation()" (change)="updateModel('height', $any($event.target).value)"> @@ -163,22 +163,15 @@ import { Section } from 'common/models/section'; </div> </mat-menu> - <button mat-mini-fab + <button mat-mini-fab [matTooltip]="'Abschnitt kopieren'" [matTooltipPosition]="'left'" (click)="copySectionToClipboard()"> <mat-icon>content_copy</mat-icon> </button> - - <button mat-mini-fab [matMenuTriggerFor]="pasteSectionMenu"> + <button mat-mini-fab + [matTooltip]="'Abschnitt einfügen'" [matTooltipPosition]="'left'" + (click)="showSectionInsertDialog()"> <mat-icon>content_paste</mat-icon> </button> - <mat-menu #pasteSectionMenu="matMenu" xPosition="before"> - <mat-form-field appearance="fill" (click)="$any($event).stopPropagation()"> -<!-- <mat-label>{{'section-menu.activeAfterID' | translate }}</mat-label>--> - <input matInput (click)="$any($event).stopPropagation()" - (paste)="pasteSectionFromClipboard($event);"> - </mat-form-field> - </mat-menu> - <button *ngIf="allowMoveUp" mat-mini-fab [matTooltip]="'Nach oben verschieben'" [matTooltipPosition]="'left'" (click)="this.moveSection.emit('up')"> @@ -311,23 +304,17 @@ export class SectionMenuComponent implements OnInit, OnDestroy { copySectionToClipboard() { this.clipboard.copy(JSON.stringify(this.section)); - console.log('bla', navigator.clipboard); + this.messageService.showSuccess('Abschnitt in Zwischenablage kopiert'); } - pasteSectionFromClipboard(event: ClipboardEvent) { - console.log('paste', event); - console.log('paste2', event.clipboardData?.getData('Text')); - const pastedText = event.clipboardData?.getData('Text'); - // TODO try catch - if (!pastedText) return; - try { - const newSection = new Section(JSON.parse(pastedText) as Section); - this.unitService.replaceSection(this.selectionService.selectedPageIndex, this.sectionIndex, newSection); - } catch (e) { - this.messageService.showError('Fehler beim Lesen der Sektion'); - } - // console.log('evvv', event.target); - // (event.target as HTMLInputElement).value = 'abc'; - event.stopPropagation(); + showSectionInsertDialog(): void { + this.dialogService.showSectionInsertDialog(this.section) + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe((newSection: Section) => { + if (newSection) { + this.unitService.replaceSection(this.selectionService.selectedPageIndex, this.sectionIndex, newSection); + } + }); } + } diff --git a/projects/editor/src/app/components/dialogs/section-insert-dialog.component.ts b/projects/editor/src/app/components/dialogs/section-insert-dialog.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..3fb0c7cda6ca8a77f0056186287bfa0283391638 --- /dev/null +++ b/projects/editor/src/app/components/dialogs/section-insert-dialog.component.ts @@ -0,0 +1,83 @@ +import { Component, Inject } from '@angular/core'; +import { Section } from 'common/models/section'; +import { MessageService } from 'common/services/message.service'; +import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { Subject } from 'rxjs'; +import { UIElement } from 'common/models/elements/element'; +import { IDManager } from 'common/util/id-manager'; +import { TranslateService } from '@ngx-translate/core'; + +@Component({ + selector: 'app-section-insert-dialog', + template: ` + <mat-dialog-content> + <div (mouseenter)="hovered = true;" (mouseleave)="hovered = false;"> + <div class="paste-area" contenteditable="true" *ngIf="isPastedTextPresent === false" + (paste)="pasteSectionFromClipboard($event)"> + </div> + <div *ngIf="!hovered || isPastedTextPresent" class="message-area" [style.background-color]="operationStatus"> + {{operationStatusText}} + </div> + </div> + </mat-dialog-content> + <mat-dialog-actions> + <button mat-button *ngIf="operationStatus === 'green' || operationStatus === 'yellow'" + [mat-dialog-close]="newSection"> + {{'confirm' | translate }} + </button> + <button mat-button mat-dialog-close>{{'cancel' | translate }}</button> + </mat-dialog-actions> + `, + styles: [ + '.mat-dialog-content {width: 400px; height: 200px;}', + '.paste-area {position: absolute; width: 400px; height: 200px; border: 1px solid; overflow: hidden}', + '.message-area {position: absolute; width: 400px; height: 200px; text-align: center; font-size: large;}' + ] +}) +export class SectionInsertDialogComponent { + private ngUnsubscribe = new Subject<void>(); + + operationStatus: 'red' | 'yellow' | 'green' | 'none' = 'none'; + operationStatusText: string = this.translateService.instant('Bitte kopierten Abschnitt einfügen'); + hovered = false; + isPastedTextPresent = false; + newSection: Section | null = null; + + constructor(@Inject(MAT_DIALOG_DATA) public data: { existingSection: Section }, + private messageService: MessageService, + private translateService: TranslateService) { } + + + pasteSectionFromClipboard(event: ClipboardEvent) { + this.isPastedTextPresent = true; + const pastedText = event.clipboardData?.getData('Text'); + if (!pastedText) { + this.operationStatusText = this.translateService.instant('Fehler beim Lesen des Abschnitts!'); + return; + } + try { + this.newSection = new Section(JSON.parse(pastedText)); + + if (this.findElementsWithDuplicateID(this.newSection.getAllElements()).length > 0) { + this.operationStatusText = + this.translateService.instant('Doppelte IDs festgestellt. Weiter mit neu generierten IDs?'); + this.operationStatus = 'yellow'; + } else { + this.operationStatusText = this.translateService.instant('Abschnitt wurde erfolgreich gelesen.'); + this.operationStatus = 'green'; + } + } catch (e) { + this.operationStatusText = this.translateService.instant('Fehler beim Lesen des Abschnitts!'); + this.operationStatus = 'red'; + } + } + + private findElementsWithDuplicateID(elements: UIElement[]): UIElement[] { + return elements.filter(element => !IDManager.getInstance().isIdAvailable(element.id)); + } + + ngOnDestroy(): void { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); + } +} diff --git a/projects/editor/src/app/components/new-ui-element-panel/ui-element-toolbox.component.html b/projects/editor/src/app/components/new-ui-element-panel/ui-element-toolbox.component.html index 79a74dc77cd62581fcc669f8348b0f4eebda4fc1..b6a6ff9f855e9f1479fb2e8a636020a912cececb 100644 --- a/projects/editor/src/app/components/new-ui-element-panel/ui-element-toolbox.component.html +++ b/projects/editor/src/app/components/new-ui-element-panel/ui-element-toolbox.component.html @@ -2,7 +2,7 @@ <mat-expansion-panel expanded> <mat-expansion-panel-header> <mat-panel-description> - Stimulus + Medium </mat-panel-description> </mat-expansion-panel-header> <button mat-stroked-button (click)="addUIElement('text')" @@ -34,14 +34,24 @@ </mat-expansion-panel-header> <button mat-stroked-button (click)="addUIElement('text-field')" draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','text-field')"> - <mat-icon>text_fields</mat-icon> + <mat-icon>edit</mat-icon> {{'toolbox.text-field' | translate }} </button> <button mat-stroked-button (click)="addUIElement('text-area')" draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','text-area')"> - <mat-icon>notes</mat-icon> + <mat-icon>edit_note</mat-icon> {{'toolbox.text-area' | translate }} </button> + <button mat-stroked-button (click)="addUIElement('cloze')" + draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','cloze')"> + <mat-icon>vertical_split</mat-icon> + {{'toolbox.cloze' | translate }} + </button> + <button mat-stroked-button (click)="addUIElement('spell-correct')" + draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','spell-correct')"> + <mat-icon>format_strikethrough</mat-icon> + {{'toolbox.spell-correct' | translate }} + </button> </mat-expansion-panel> <mat-expansion-panel expanded> <mat-expansion-panel-header> @@ -49,16 +59,6 @@ Auswahl </mat-panel-description> </mat-expansion-panel-header> - <button mat-stroked-button (click)="addUIElement('checkbox')" - draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','checkbox')"> - <mat-icon>check_box</mat-icon> - {{'toolbox.checkbox' | translate }} - </button> - <button mat-stroked-button (click)="addUIElement('dropdown')" - draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','dropdown')"> - <mat-icon>menu_open</mat-icon> - {{'toolbox.dropdown' | translate }} - </button> <div (mouseover)="hoverRadioButton = true" (mouseleave)="hoverRadioButton = false" fxLayout="row"> <button *ngIf="!hoverRadioButton" class="radio-button-placeholder" mat-stroked-button> <mat-icon>radio_button_checked</mat-icon> @@ -75,9 +75,19 @@ </div> <button mat-stroked-button (click)="addUIElement('likert')" draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','likert')"> - <mat-icon>toc</mat-icon> + <mat-icon>margin</mat-icon> {{'toolbox.likert' | translate }} </button> + <button mat-stroked-button (click)="addUIElement('dropdown')" + draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','dropdown')"> + <mat-icon>menu_open</mat-icon> + {{'toolbox.dropdown' | translate }} + </button> + <button mat-stroked-button (click)="addUIElement('checkbox')" + draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','checkbox')"> + <mat-icon>check_box</mat-icon> + {{'toolbox.checkbox' | translate }} + </button> <button mat-stroked-button (click)="addUIElement('slider')" draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','slider')"> <mat-icon>linear_scale</mat-icon> @@ -96,23 +106,6 @@ {{'toolbox.drop-list' | translate }} </button> </mat-expansion-panel> - <mat-expansion-panel> - <mat-expansion-panel-header> - <mat-panel-description> - Verbund - </mat-panel-description> - </mat-expansion-panel-header> - <button mat-stroked-button (click)="addUIElement('cloze')" - draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','cloze')"> - <mat-icon>vertical_split</mat-icon> - {{'toolbox.cloze' | translate }} - </button> - <button mat-stroked-button (click)="addUIElement('spell-correct')" - draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','spell-correct')"> - <mat-icon>format_strikethrough</mat-icon> - {{'toolbox.spell-correct' | translate }} - </button> - </mat-expansion-panel> <mat-expansion-panel> <mat-expansion-panel-header> <mat-panel-description> @@ -121,7 +114,7 @@ </mat-expansion-panel-header> <button mat-stroked-button (click)="addUIElement('button')" draggable="true" (dragstart)="$event.dataTransfer?.setData('elementType','button')"> - <mat-icon>smart_button</mat-icon> + <mat-icon>navigation</mat-icon> {{'toolbox.button' | translate }} </button> <button mat-stroked-button (click)="addUIElement('frame')" diff --git a/projects/editor/src/app/components/properties-panel/element-properties-panel.component.ts b/projects/editor/src/app/components/properties-panel/element-properties-panel.component.ts index b01cb4f93801aadec708a4a1a156bad1565fc360..03c9afae47427e9e1cd3889f020204a79b05c908 100644 --- a/projects/editor/src/app/components/properties-panel/element-properties-panel.component.ts +++ b/projects/editor/src/app/components/properties-panel/element-properties-panel.component.ts @@ -8,7 +8,7 @@ import { TranslateService } from '@ngx-translate/core'; import { UnitService } from '../../services/unit.service'; import { SelectionService } from '../../services/selection.service'; import { MessageService } from 'common/services/message.service'; -import { DragNDropValueObject, TextImageLabel, UIElement, UIElementValue } from 'common/models/elements/element'; +import { DragNDropValueObject, TextImageLabel, UIElement } from 'common/models/elements/element'; @Component({ selector: 'aspect-element-properties', @@ -53,35 +53,37 @@ export class ElementPropertiesPanelComponent implements OnInit, OnDestroy { ); } - static createCombinedProperties(elements: Record<string, UIElementValue>[]): Record<string, UIElementValue> { - const combinedProperties: Record<string, UIElementValue> = { ...elements[0], id: [elements[0]?.id as string] }; + static createCombinedProperties(elements: UIElement[]): Partial<UIElement> { + if (elements.length > 0) { + const combinedProperties: Partial<UIElement> & { id: string | string[] } = { ...elements[0], id: elements[0].id }; - for (let elementCounter = 1; elementCounter < elements.length; elementCounter++) { - const elementToMerge = elements[elementCounter]; - Object.keys(combinedProperties).forEach((property: keyof UIElement) => { - if (Object.prototype.hasOwnProperty.call(elementToMerge, property)) { - if (typeof combinedProperties[property] === 'object' && - !Array.isArray(combinedProperties[property]) && - combinedProperties[property] !== null) { - (combinedProperties[property] as Record<string, UIElementValue>) = - ElementPropertiesPanelComponent.createCombinedProperties( - [(combinedProperties[property] as Record<string, UIElementValue>), - (elementToMerge[property] as Record<string, UIElementValue>)] - ); - } else if (JSON.stringify(combinedProperties[property]) !== JSON.stringify(elementToMerge[property])) { - if (property === 'id') { - (combinedProperties.id as string[]).push(elementToMerge.id as string); - } else { - combinedProperties[property] = null; + for (let elementCounter = 1; elementCounter < elements.length; elementCounter++) { + const elementToMerge = elements[elementCounter]; + Object.keys(combinedProperties).forEach((property: keyof UIElement) => { + if (Object.prototype.hasOwnProperty.call(elementToMerge, property)) { + if (typeof combinedProperties[property] === 'object' && + !Array.isArray(combinedProperties[property]) && + combinedProperties[property] !== null) { + (combinedProperties[property] as UIElement) = + ElementPropertiesPanelComponent.createCombinedProperties( + [(combinedProperties[property] as UIElement), + (elementToMerge[property] as UIElement)] + ) as UIElement; + } else if (JSON.stringify(combinedProperties[property]) !== JSON.stringify(elementToMerge[property])) { + if (property === 'id') { + (combinedProperties.id as string[]).push(elementToMerge.id as string); + } else { + combinedProperties[property] = null; + } } + } else { + delete combinedProperties[property]; } - } else { - delete combinedProperties[property]; - } - }); + }); + } + return combinedProperties; } - // console.log('combined', combinedProperties); - return combinedProperties; + return {}; } updateModel(property: string, diff --git a/projects/editor/src/app/components/properties-panel/model-properties-tab/element-model-properties.component.ts b/projects/editor/src/app/components/properties-panel/model-properties-tab/element-model-properties.component.ts index 152f4b85cec284c69e5d06c64b3c360fad38f848..e176e705013d2438636f64bc98d772c40b1ba16b 100644 --- a/projects/editor/src/app/components/properties-panel/model-properties-tab/element-model-properties.component.ts +++ b/projects/editor/src/app/components/properties-panel/model-properties-tab/element-model-properties.component.ts @@ -11,6 +11,7 @@ import { DomSanitizer } from '@angular/platform-browser'; import { DragNDropValueObject, InputElementValue, TextImageLabel, UIElement } from 'common/models/elements/element'; import { LikertRowElement } from 'common/models/elements/compound-elements/likert/likert-row'; import { LikertElement } from 'common/models/elements/compound-elements/likert/likert'; +import { IDManager } from 'common/util/id-manager'; @Component({ selector: 'aspect-element-model-properties-component', @@ -84,10 +85,15 @@ export class ElementModelPropertiesComponent { } addLikertRow(rowLabelText: string): void { - const newRow = this.unitService.createLikertRowElement( - rowLabelText, - (this.combinedProperties.columns as TextImageLabel[]).length - ); + const newRow = new LikertRowElement({ + type: 'likert-row', + rowLabel: { + text: rowLabelText, + imgSrc: null, + position: 'above' + }, + columnCount: (this.combinedProperties.columns as TextImageLabel[]).length + }, IDManager.getInstance()); (this.combinedProperties.rows as LikertRowElement[]).push(newRow); this.updateModel.emit({ property: 'rows', value: this.combinedProperties.rows as LikertRowElement[] }); } diff --git a/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/button-properties.component.ts b/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/button-properties.component.ts index 1649639e3047d66885bb9066efdc5e18e9374e7d..8db4f5d8a51aa0b2db10488b30e95a7b7826aa3d 100644 --- a/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/button-properties.component.ts +++ b/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/button-properties.component.ts @@ -1,24 +1,27 @@ import { - Component, EventEmitter, Input, Output + Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { UnitService } from '../../../../services/unit.service'; import { SelectionService } from '../../../../services/selection.service'; import { FileService } from 'common/services/file.service'; +import { UIElement } from 'common/models/elements/element'; +import { ButtonElement } from 'common/models/elements/button/button'; @Component({ selector: 'aspect-button-properties', template: ` <fieldset *ngIf="combinedProperties.asLink !== undefined"> <legend>Knopf</legend> + <mat-checkbox *ngIf="combinedProperties.asLink !== undefined" - [checked]="$any(combinedProperties.asLink)" + [checked]="combinedButtonElement.asLink" (change)="updateModel.emit({ property: 'asLink', value: $event.checked })"> {{'propertiesPanel.asLink' | translate }} </mat-checkbox> <mat-form-field *ngIf="combinedProperties.action !== undefined" appearance="fill"> <mat-label>{{'propertiesPanel.action' | translate }}</mat-label> - <mat-select [value]="combinedProperties.action" + <mat-select [value]="combinedButtonElement.action" (selectionChange)="updateModel.emit({ property: 'action', value: $event.value })"> <mat-option [value]="null"> {{ 'propertiesPanel.none' | translate }} @@ -32,13 +35,13 @@ import { FileService } from 'common/services/file.service'; <mat-form-field appearance="fill"> <mat-label>{{'propertiesPanel.actionParam' | translate }}</mat-label> - <mat-select [disabled]="combinedProperties.action === null" - [value]="combinedProperties.actionParam" - [matTooltipDisabled]="combinedProperties.action !== 'pageNav'" + <mat-select [disabled]="combinedButtonElement.action === null" + [value]="combinedButtonElement.actionParam" + [matTooltipDisabled]="combinedButtonElement.action !== 'pageNav'" [matTooltip]="'propertiesPanel.pageNavSelectionHint' | translate" (selectionChange)="updateModel.emit({ property: 'actionParam', value: $event.value })"> - <ng-container *ngIf="combinedProperties.action === 'pageNav'"> + <ng-container *ngIf="combinedButtonElement.action === 'pageNav'"> <ng-container *ngFor="let page of unitService.unit.pages; index as i"> <mat-option *ngIf="!page.alwaysVisible && selectionService.selectedPageIndex !== i" [value]="i"> @@ -47,7 +50,7 @@ import { FileService } from 'common/services/file.service'; </ng-container> </ng-container> - <ng-container *ngIf="combinedProperties.action === 'unitNav'"> + <ng-container *ngIf="combinedButtonElement.action === 'unitNav'"> <mat-option *ngFor="let option of [undefined, 'previous', 'next', 'first', 'last', 'end']" [value]="option"> {{ 'propertiesPanel.' + option | translate }} @@ -57,14 +60,14 @@ import { FileService } from 'common/services/file.service'; </mat-form-field> <div class="image-panel" (mouseenter)="hoveringImage = true" (mouseleave)="hoveringImage = false"> - <button *ngIf="combinedProperties.imageSrc === null || hoveringImage" + <button *ngIf="combinedButtonElement.imageSrc === null || hoveringImage" class="add-image-button" mat-raised-button (click)="loadImage()">{{'loadImage' | translate }}</button> - <button *ngIf="combinedProperties.imageSrc !== null && hoveringImage" + <button *ngIf="combinedButtonElement.imageSrc !== null && hoveringImage" class="remove-image-button" mat-raised-button (click)="removeImage()">{{'removeImage' | translate }}</button> - <img *ngIf="combinedProperties.imageSrc" - [src]="combinedProperties.imageSrc"> + <img *ngIf="combinedButtonElement.imageSrc" + [src]="combinedButtonElement.imageSrc"> </div> </fieldset> `, @@ -77,15 +80,20 @@ import { FileService } from 'common/services/file.service'; '.image-panel img {width:100%; height:100%;}' ] }) -export class ButtonPropertiesComponent { - @Input() combinedProperties!: any; +export class ButtonPropertiesComponent implements OnInit { + @Input() combinedProperties!: UIElement; @Output() updateModel = - new EventEmitter<{ property: string; value: string | number | boolean | null, isInputValid?: boolean | null }>(); + new EventEmitter<{ property: string; value: string | number | boolean | null, isInputValid?: boolean | null }>(); + combinedButtonElement: ButtonElement = {} as ButtonElement; hoveringImage = false; constructor(public unitService: UnitService, public selectionService: SelectionService) { } + ngOnInit(): void { + this.combinedButtonElement = this.combinedProperties as ButtonElement; + } + async loadImage(): Promise<void> { this.updateModel.emit({ property: 'imageSrc', value: await FileService.loadImage() }); } diff --git a/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/drop-list-properties.component.ts b/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/drop-list-properties.component.ts index 8aea1b19ae2ee49cc4f892c4841b307a1e6bdadd..dcf7b37f87d41e71e3e2d40237c3dcc0ffe9ff3e 100644 --- a/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/drop-list-properties.component.ts +++ b/projects/editor/src/app/components/properties-panel/model-properties-tab/input-groups/drop-list-properties.component.ts @@ -1,63 +1,61 @@ import { - Component, ElementRef, EventEmitter, Input, Output, ViewChild + Component, EventEmitter, Input, Output } from '@angular/core'; import { UnitService } from '../../../../services/unit.service'; import { SelectionService } from '../../../../services/selection.service'; import { DialogService } from '../../../../services/dialog.service'; -import { IDService } from 'common/services/id.service'; import { MessageService } from 'common/services/message.service'; import { TranslateService } from '@ngx-translate/core'; import { CdkDragDrop } from '@angular/cdk/drag-drop/drag-events'; import { moveItemInArray } from '@angular/cdk/drag-drop'; import { DragNDropValueObject } from 'common/models/elements/element'; +import { IDManager } from 'common/util/id-manager'; @Component({ selector: 'aspect-drop-list-properties', template: ` <fieldset *ngIf="combinedProperties.type === 'drop-list' || - combinedProperties.type === 'drop-list-sorting'"> + combinedProperties.type === 'drop-list-simple'"> <legend>Ablegeliste</legend> - <mat-form-field disabled="true" *ngIf="combinedProperties.type === 'drop-list' || - combinedProperties.type === 'drop-list-sorting'"> - <ng-container> - <mat-label>{{'preset' | translate }}</mat-label> - <div class="drop-list" cdkDropList [cdkDropListData]="combinedProperties.value" - (cdkDropListDropped)="moveListValue($any($event))"> - <div *ngFor="let value of $any(combinedProperties.value); let i = index" cdkDrag - class="list-items" fxLayout="row" fxLayoutAlign="end center"> - <div fxFlex="70" class="draggable-element-label"> - {{value.stringValue}} ({{value.id}}) - </div> - <img [src]="value.imgSrcValue" - [style.object-fit]="'scale-down'" - [style.height.px]="40"> - <button mat-icon-button color="primary" - (click)="editDropListOption(i)"> - <mat-icon>build</mat-icon> - </button> - <button mat-icon-button color="primary" - (click)="removeListValue('value', i)"> - <mat-icon>clear</mat-icon> - </button> + <div class="value-list-container"> + <mat-label>{{'preset' | translate }}</mat-label> + <div class="drop-list" cdkDropList [cdkDropListData]="combinedProperties.value" + (cdkDropListDropped)="moveListValue($any($event))"> + <div *ngFor="let value of $any(combinedProperties.value); let i = index" cdkDrag + class="list-items" fxLayout="row" fxLayoutAlign="end center"> + <div fxFlex="70" class="draggable-element-label"> + {{value.stringValue}} ({{value.id}}) </div> + <img [src]="value.imgSrcValue" + [style.object-fit]="'scale-down'" + [style.height.px]="40"> + <button mat-icon-button color="primary" + (click)="editDropListOption(i)"> + <mat-icon>build</mat-icon> + </button> + <button mat-icon-button color="primary" + (click)="removeListValue('value', i)"> + <mat-icon>clear</mat-icon> + </button> </div> - </ng-container> - <div fxLayout="row" fxLayoutAlign="center center"> - <textarea matInput type="text" #newValue rows="2" + </div> + <div fxLayout="row" fxLayoutAlign="center center" class="text-area-container"> + <textarea matInput type="text" + #newValue rows="2" (keyup.enter)="addDropListOption(newValue.value); newValue.select()"></textarea> <button mat-icon-button (click)="addDropListOption(newValue.value); newValue.select()"> <mat-icon>add</mat-icon> </button> </div> - </mat-form-field> + </div> - <mat-form-field appearance="fill" *ngIf="combinedProperties.connectedTo !== null"> + <mat-form-field appearance="fill" *ngIf="combinedProperties.connectedTo !== null" + (click)="generateValidDropLists()"> <mat-label>{{'propertiesPanel.connectedDropLists' | translate }}</mat-label> <mat-select multiple [ngModel]="combinedProperties.connectedTo" - (ngModelChange)="toggleConnectedDropList($event)" - (click)="generateValidDropLists()"> + (ngModelChange)="toggleConnectedDropList($event)"> <mat-select-trigger> {{'propertiesPanel.connectedDropLists' | translate }} ({{combinedProperties.connectedTo.length}}) </mat-select-trigger> @@ -109,7 +107,11 @@ import { DragNDropValueObject } from 'common/models/elements/element'; styles: [ 'mat-form-field {width: 100%;}', '.draggable-element-label {overflow-wrap: anywhere;}', - 'mat-select {height: 100%;}' + 'mat-select {height: 100%;}', + '.text-area-container {background-color: lightgray; margin-bottom: 15px;}', + '.value-list-container {background-color: rgba(0,0,0,.04);}', + '.text-area-container button {border: 1px solid gray;}', + '.value-list-container mat-label {font-size: large;}' ] }) export class DropListPropertiesComponent { @@ -125,7 +127,6 @@ export class DropListPropertiesComponent { constructor(public unitService: UnitService, private selectionService: SelectionService, private dialogService: DialogService, - private idService: IDService, private messageService: MessageService, private translateService: TranslateService) { } @@ -145,7 +146,7 @@ export class DropListPropertiesComponent { await this.dialogService.showDropListOptionEditDialog(oldOptions[optionIndex]) .subscribe((result: DragNDropValueObject) => { if (result) { - if (result.id !== oldOptions[optionIndex].id && !this.idService.isIdAvailable(result.id)) { + if (result.id !== oldOptions[optionIndex].id && !IDManager.getInstance().isIdAvailable(result.id)) { this.messageService.showError(this.translateService.instant('idTaken')); return; } diff --git a/projects/editor/src/app/components/properties-panel/position-properties-tab/element-position-properties.component.ts b/projects/editor/src/app/components/properties-panel/position-properties-tab/element-position-properties.component.ts index 7046989c6504980198f6e30f33fce9bca86a617e..88aedbbdd7eacbc07dd678ab91c8b13cd8d5fca1 100644 --- a/projects/editor/src/app/components/properties-panel/position-properties-tab/element-position-properties.component.ts +++ b/projects/editor/src/app/components/properties-panel/position-properties-tab/element-position-properties.component.ts @@ -17,9 +17,7 @@ import { PositionedUIElement, PositionProperties } from 'common/models/elements/ <aspect-dimension-field-set [positionProperties]="positionProperties" - [dimensions]="dimensions" - (updateModel)="unitService.updateElementsProperty( - this.selectionService.getSelectedElements(), $event.property, $event.value)"> + [dimensions]="dimensions"> </aspect-dimension-field-set> <ng-container *ngIf="(selectionService.selectedElements | async)!.length > 1"> diff --git a/projects/editor/src/app/components/properties-panel/position-properties-tab/input-groups/dimension-field-set.component.ts b/projects/editor/src/app/components/properties-panel/position-properties-tab/input-groups/dimension-field-set.component.ts index e971473baa107e7caff992df71ef30933d8914d1..de37bc75777aa0ce60f9eefdedd497090a129d11 100644 --- a/projects/editor/src/app/components/properties-panel/position-properties-tab/input-groups/dimension-field-set.component.ts +++ b/projects/editor/src/app/components/properties-panel/position-properties-tab/input-groups/dimension-field-set.component.ts @@ -2,6 +2,8 @@ import { Component, EventEmitter, Input, Output } from '@angular/core'; import { PositionProperties } from 'common/models/elements/element'; +import { UnitService } from 'editor/src/app/services/unit.service'; +import { SelectionService } from 'editor/src/app/services/selection.service'; @Component({ selector: 'aspect-dimension-field-set', @@ -10,14 +12,14 @@ import { PositionProperties } from 'common/models/elements/element'; <legend>Dimensionen</legend> <mat-checkbox *ngIf="dimensions.dynamicWidth !== undefined" [checked]="$any(dimensions?.dynamicWidth)" - (change)="updateModel.emit({ property: 'dynamicWidth', value: $event.checked })"> + (change)="updateDimensionProperty('dynamicWidth', $event.checked)"> {{'propertiesPanel.dynamicWidth' | translate }} </mat-checkbox> <mat-checkbox *ngIf="positionProperties?.dynamicPositioning" matTooltip="Element ist nicht mehr dynamisch. Die eingestellte Größe wird benutzt." [checked]="$any(positionProperties?.fixedSize)" - (change)="updateModel.emit({ property: 'fixedSize', value: $event.checked })"> + (change)="updatePositionProperty('fixedSize', $event.checked)"> {{'propertiesPanel.fixedSize' | translate }} </mat-checkbox> @@ -32,14 +34,12 @@ import { PositionProperties } from 'common/models/elements/element'; <input matInput type="number" #width="ngModel" min="0" [disabled]="$any(dimensions.dynamicWidth)" [ngModel]="dimensions.width" - (ngModelChange)="updateModel.emit({ property: 'width', - value: $event, - isInputValid: width.valid && $event !== null })"> + (ngModelChange)="updateDimensionProperty('width', $event)"> </mat-form-field> <mat-checkbox *ngIf="positionProperties?.dynamicPositioning && !positionProperties?.fixedSize" [checked]="$any(positionProperties?.useMinHeight)" - (change)="updateModel.emit({ property: 'useMinHeight', value: $event.checked })"> + (change)="updatePositionProperty('useMinHeight', $event.checked)"> {{'propertiesPanel.useMinHeight' | translate }} </mat-checkbox> <mat-form-field *ngIf="!positionProperties?.dynamicPositioning || @@ -56,9 +56,7 @@ import { PositionProperties } from 'common/models/elements/element'; </mat-label> <input matInput type="number" #height="ngModel" min="0" [ngModel]="dimensions.height" - (ngModelChange)="updateModel.emit({ property: 'height', - value: $event, - isInputValid: height.valid && $event !== null })"> + (ngModelChange)="updateDimensionProperty('height', $event)"> </mat-form-field> </fieldset> `, @@ -69,6 +67,14 @@ import { PositionProperties } from 'common/models/elements/element'; export class DimensionFieldSetComponent { @Input() positionProperties: PositionProperties | undefined; @Input() dimensions!: { width: number; height: number; dynamicWidth?: boolean }; - @Output() updateModel = - new EventEmitter<{ property: string; value: string | boolean, isInputValid?: boolean | null }>(); + + constructor(public unitService: UnitService, public selectionService: SelectionService) { } + + updateDimensionProperty(property: string, value: any): void { + this.unitService.updateElementsProperty(this.selectionService.getSelectedElements(), property, value); + } + + updatePositionProperty(property: string, value: any): void { + this.unitService.updateSelectedElementsPositionProperty(property, value); + } } diff --git a/projects/editor/src/app/components/unit-view/unit-view.component.html b/projects/editor/src/app/components/unit-view/unit-view.component.html index 2bdeef3e2f780641a5a740d09fc4d6577ed54fb2..d093f740cc7967839d56586f15d551cd195686cc 100644 --- a/projects/editor/src/app/components/unit-view/unit-view.component.html +++ b/projects/editor/src/app/components/unit-view/unit-view.component.html @@ -48,8 +48,8 @@ </button> <mat-divider></mat-divider> <mat-checkbox class="menuItem" [checked]="page.hasMaxWidth" - (click)="$any($event).stopPropagation()" - (change)="updateModel(page, 'hasMaxWidth', $any($event.source).checked)"> + (click)="$event.stopPropagation()" + (change)="updateModel(page, 'hasMaxWidth', $event.source.checked)"> {{'pageProperties.maxWidth' | translate }} </mat-checkbox> <div *ngIf="page.hasMaxWidth" class="menuItem"> @@ -59,33 +59,33 @@ <mat-label>{{'pageProperties.sectionWidth' | translate }}</mat-label> <input matInput type="number" min="0" #maxWidth="ngModel" [ngModel]="page.maxWidth" - (click)="$any($event).stopPropagation()" + (click)="$event.stopPropagation()" (ngModelChange)="updateModel(page,'maxWidth', $event, maxWidth.valid)"> </mat-form-field> <mat-form-field class="menuItem" appearance="fill"> <mat-label>{{'pageProperties.marginWidth' | translate }}</mat-label> <input matInput type="number" min="0" #margin="ngModel" [ngModel]="page.margin" - (click)="$any($event).stopPropagation()" + (click)="$event.stopPropagation()" (ngModelChange)="updateModel(page,'margin', $event, margin.valid)"> </mat-form-field> <mat-form-field class="menuItem" appearance="fill"> <mat-label>{{'pageProperties.backgroundColor' | translate }}</mat-label> <input matInput type="color" - [value]="page.backgroundColor" - (change)="updateModel(page,'backgroundColor', $any($event.target).value)"> + [ngModel]="page.backgroundColor" + (ngModelChange)="updateModel(page,'backgroundColor', $event.target.value)"> </mat-form-field> <mat-checkbox class="menuItem" [disabled]="unitService.unit.pages.length < 2 || unitService.unit.pages[0].alwaysVisible && i != 0" [ngModel]="page.alwaysVisible" - (click)="$any($event).stopPropagation()" - (change)="updateModel(page, 'alwaysVisible', $any($event.source).checked)"> + (click)="$event.stopPropagation()" + (change)="updateModel(page, 'alwaysVisible', $event.source.checked)"> {{'pageProperties.alwaysVisible' | translate }} </mat-checkbox> <mat-form-field *ngIf="page.alwaysVisible" class="menuItem" appearance="fill"> <mat-label>{{'pageProperties.position' | translate }}</mat-label> <mat-select [value]="page.alwaysVisiblePagePosition" - (click)="$any($event).stopPropagation()" + (click)="$event.stopPropagation()" (selectionChange)="updateModel(page, 'alwaysVisiblePagePosition', $event.value)"> <mat-option *ngFor="let option of ['left', 'right', 'top', 'bottom']" [value]="option"> @@ -98,7 +98,7 @@ <mat-label>{{'pageProperties.alwaysVisibleAspectRatio' | translate }}</mat-label> <input matInput type="number" min="0" max="100" [ngModel]="page.alwaysVisibleAspectRatio" - (click)="$any($event).stopPropagation()" + (click)="$event.stopPropagation()" (ngModelChange)="updateModel(page, 'alwaysVisibleAspectRatio', $event)"> </mat-form-field> </div> diff --git a/projects/editor/src/app/services/dialog.service.ts b/projects/editor/src/app/services/dialog.service.ts index 8ae192880047efc0e200186bdd7832442028c708..86f6863805a82d8287a9e5e7224da278ae976c6d 100644 --- a/projects/editor/src/app/services/dialog.service.ts +++ b/projects/editor/src/app/services/dialog.service.ts @@ -13,6 +13,8 @@ import { RichTextSimpleEditDialogComponent } from '../components/dialogs/rich-te import { DragNDropValueObject, PlayerProperties, TextImageLabel } from 'common/models/elements/element'; import { ClozeDocument } from 'common/models/elements/compound-elements/cloze/cloze'; import { LikertRowElement } from 'common/models/elements/compound-elements/likert/likert-row'; +import { SectionInsertDialogComponent } from 'editor/src/app/components/dialogs/section-insert-dialog.component'; +import { Section } from 'common/models/section'; @Injectable({ providedIn: 'root' @@ -99,4 +101,11 @@ export class DialogService { }); return dialogRef.afterClosed(); } + + showSectionInsertDialog(section: Section): Observable<Section> { + const dialogRef = this.dialog.open(SectionInsertDialogComponent, { + data: { section } + }); + return dialogRef.afterClosed(); + } } diff --git a/projects/editor/src/app/services/unit.service.ts b/projects/editor/src/app/services/unit.service.ts index 27d89fc98b7d62c165be53011da10edf063f7cc9..0545daf1028fe77324ce126dd52ac6855a03eade 100644 --- a/projects/editor/src/app/services/unit.service.ts +++ b/projects/editor/src/app/services/unit.service.ts @@ -4,19 +4,14 @@ import { Subject } from 'rxjs'; import { TranslateService } from '@ngx-translate/core'; import { FileService } from 'common/services/file.service'; import { MessageService } from 'common/services/message.service'; -import { IDService } from 'common/services/id.service'; import { DialogService } from './dialog.service'; import { VeronaAPIService } from './verona-api.service'; import { SelectionService } from './selection.service'; -import { ElementFactory } from 'common/util/element.factory'; -import { ClozeParser } from '../util/cloze-parser'; -import { UnitUtils } from 'common/util/unit-utils'; import { ArrayUtils } from 'common/util/array'; import { SanitizationService } from 'common/services/sanitization.service'; import { Unit } from 'common/models/unit'; import { - DragNDropValueObject, - InputElement, + DragNDropValueObject, InputElement, InputElementValue, PlayerElement, PlayerProperties, PositionedUIElement, TextImageLabel, UIElement, UIElementType } from 'common/models/elements/element'; @@ -27,6 +22,8 @@ import { TextElement } from 'common/models/elements/text/text'; import { DropListElement } from 'common/models/elements/input-elements/drop-list'; import { Page } from 'common/models/page'; import { Section } from 'common/models/section'; +import { ElementFactory } from 'common/util/element.factory'; +import { IDManager } from 'common/util/id-manager'; @Injectable({ providedIn: 'root' @@ -37,7 +34,6 @@ export class UnitService { elementPropertyUpdated: Subject<void> = new Subject<void>(); constructor(private selectionService: SelectionService, - private idService: IDService, private veronaApiService: VeronaAPIService, private messageService: MessageService, private dialogService: DialogService, @@ -48,38 +44,23 @@ export class UnitService { } loadUnitDefinition(unitDefinition: string): void { - this.idService.reset(); + IDManager.getInstance().reset(); const unitDef = JSON.parse(unitDefinition); if (SanitizationService.isUnitDefinitionOutdated(unitDef)) { - // this.unit = UnitFactory.createUnit(this.sanitizationService.sanitizeUnitDefinition(unitDef)); this.unit = new Unit(this.sanitizationService.sanitizeUnitDefinition(unitDef)); this.messageService.showMessage(this.translateService.instant('outdatedUnit')); } else { - this.unit = new Unit(unitDef); + this.unit = new Unit(unitDef, IDManager.getInstance()); } - this.readIDs(this.unit); - } - - private readIDs(unit: Unit): void { - UnitUtils.findUIElements(unit).forEach(element => { - if (element.type === 'likert') { - (element as LikertElement).getChildElements().forEach(row => this.idService.addID(row.id)); - } - if (element.type === 'cloze') { - (element as ClozeElement).getChildElements() - .forEach(child => this.idService.addID(child.id)); - } - this.idService.addID(element.id); - }); } unitUpdated(): void { - this.sendChangedNotifications(); + this.veronaApiService.sendVoeDefinitionChangedNotification(this.unit); } - addSection(page: Page): void { - page.sections.push(new Section()); - this.sendChangedNotifications(); + addSection(page: Page, newSection?: Partial<Section>): void { + page.sections.push(new Section(newSection, IDManager.getInstance())); + this.veronaApiService.sendVoeDefinitionChangedNotification(this.unit); } deleteSection(section: Section): void { @@ -87,7 +68,7 @@ export class UnitService { this.unit.pages[this.selectionService.selectedPageIndex].sections.indexOf(section), 1 ); - this.sendChangedNotifications(); + this.veronaApiService.sendVoeDefinitionChangedNotification(this.unit); } duplicateSection(section: Section, page: Page, sectionIndex: number): void { @@ -96,7 +77,7 @@ export class UnitService { elements: section.elements.map(element => this.duplicateElement(element) as PositionedUIElement) }); page.sections.splice(sectionIndex + 1, 0, newSection); - this.sendChangedNotifications(); + this.veronaApiService.sendVoeDefinitionChangedNotification(this.unit); } moveSection(section: Section, page: Page, direction: 'up' | 'down'): void { @@ -106,7 +87,7 @@ export class UnitService { } else if (direction === 'down') { this.selectionService.selectedPageSectionIndex += 1; } - this.sendChangedNotifications(); + this.veronaApiService.sendVoeDefinitionChangedNotification(this.unit); } addElementToSectionByIndex(elementType: UIElementType, @@ -118,11 +99,10 @@ export class UnitService { async addElementToSection(elementType: UIElementType, section: Section, coordinates?: { x: number, y: number }): Promise<void> { - console.log('addElementToSection', elementType); - let newElement: PositionedUIElement; - // TODO: Remove switch use parameter for loadFile + const newElement: { type: string } & Partial<PositionedUIElement> = { + type: elementType + }; if (['audio', 'video', 'image'].includes(elementType)) { - // TODO: loadFile before addElementToSection let mediaSrc = ''; switch (elementType) { case 'image': @@ -136,35 +116,19 @@ export class UnitService { break; // no default } - // TODO: ElementFactory.createElement is used 2 times - newElement = ElementFactory.createElement( - elementType, { - id: this.idService.getNewID(elementType), - src: mediaSrc, - position: { - dynamicPositioning: section.dynamicPositioning - } - } as unknown as UIElement) as PositionedUIElement; - } else { - newElement = ElementFactory.createElement( - elementType, { - id: this.idService.getNewID(elementType), - position: { - dynamicPositioning: section.dynamicPositioning - } - } as unknown as UIElement) as PositionedUIElement; + newElement.src = mediaSrc; } - if (coordinates && section.dynamicPositioning) { - newElement.position.gridColumn = coordinates.x; - newElement.position.gridColumnRange = 1; - newElement.position.gridRow = coordinates.y; - newElement.position.gridRowRange = 1; - } else if (coordinates && !section.dynamicPositioning) { - newElement.position.xPosition = coordinates.x; - newElement.position.yPosition = coordinates.y; + + if (coordinates) { + newElement.position = ElementFactory.initPositionProps({ + ...(section.dynamicPositioning && { gridColumn: coordinates.x }), + ...(section.dynamicPositioning && { gridRow: coordinates.y }), + ...(!section.dynamicPositioning && { yPosition: coordinates.y }), + ...(!section.dynamicPositioning && { yPosition: coordinates.y }) + }); } - section.elements.push(newElement); - this.sendChangedNotifications(); + section.addElement(Section.createElement(newElement, IDManager.getInstance())); + this.veronaApiService.sendVoeDefinitionChangedNotification(this.unit); } deleteElements(elements: UIElement[]): void { @@ -172,17 +136,17 @@ export class UnitService { this.unit.pages[this.selectionService.selectedPageIndex].sections.forEach(section => { section.elements = section.elements.filter(element => !elements.includes(element)); }); - this.sendChangedNotifications(); + this.veronaApiService.sendVoeDefinitionChangedNotification(this.unit); } - private freeUpIds(elements: UIElement[]): void { + private freeUpIds(elements: UIElement[]): void { // TODO free up child and value IDs elements.forEach(element => { if (element.type === 'drop-list') { ((element as DropListElement).value as DragNDropValueObject[]).forEach((value: DragNDropValueObject) => { - this.idService.removeId(value.id); + IDManager.getInstance().removeId(value.id); }); } - this.idService.removeId(element.id); + IDManager.getInstance().removeId(element.id); }); } @@ -193,7 +157,7 @@ export class UnitService { newSection.elements.push(element as PositionedUIElement); (element as PositionedUIElement).position.dynamicPositioning = newSection.dynamicPositioning; }); - this.sendChangedNotifications(); + this.veronaApiService.sendVoeDefinitionChangedNotification(this.unit); } duplicateElementsInSection(elements: UIElement[], pageIndex: number, sectionIndex: number): void { @@ -201,35 +165,35 @@ export class UnitService { elements.forEach((element: UIElement) => { section.elements.push(this.duplicateElement(element) as PositionedUIElement); }); - this.sendChangedNotifications(); + this.veronaApiService.sendVoeDefinitionChangedNotification(this.unit); } private duplicateElement(element: UIElement): UIElement { - const newElement = ElementFactory - .createElement(element.type, { ...element, id: this.idService.getNewID(element.type) }); + const newElement = Section.createElement(element, IDManager.getInstance()); if (newElement.position) { newElement.position.xPosition += 10; newElement.position.yPosition += 10; } if ('value' in newElement && newElement.value instanceof Object) { // replace value Ids with fresh ones (dropList) - newElement.value.forEach((valueObject: { id: string }) => { - valueObject.id = this.idService.getNewID('value'); + (newElement.value as DragNDropValueObject[]).forEach((valueObject: { id: string }) => { + valueObject.id = IDManager.getInstance().getNewID('value'); }); } if ('row' in newElement && newElement.rows instanceof Object) { // replace row Ids with fresh ones (likert) - newElement.rows.forEach((rowObject: { id: string }) => { - rowObject.id = this.idService.getNewID('likert_row'); + (newElement.rows as LikertRowElement[]).forEach((rowObject: { id: string }) => { + rowObject.id = IDManager.getInstance().getNewID('likert_row'); }); } - if (newElement.type === 'cloze') { - element.getChildElements().forEach((childElement: InputElement) => { - childElement.id = this.idService.getNewID(childElement.type); + + if (newElement instanceof ClozeElement) { + element.getChildElements().forEach((childElement: UIElement) => { + childElement.id = IDManager.getInstance().getNewID(childElement.type); if (childElement.type === 'drop-list-simple') { // replace value Ids with fresh ones (dropList) (childElement.value as DragNDropValueObject[]).forEach((valueObject: DragNDropValueObject) => { - valueObject.id = this.idService.getNewID('value'); + valueObject.id = IDManager.getInstance().getNewID('value'); }); } }); @@ -244,11 +208,10 @@ export class UnitService { (element as PositionedUIElement).position.dynamicPositioning = value as boolean; }); } else { - // section[property] = value; section.setProperty(property, value); } this.elementPropertyUpdated.next(); - this.sendChangedNotifications(); + this.veronaApiService.sendVoeDefinitionChangedNotification(this.unit); } updateElementsProperty(elements: UIElement[], @@ -258,15 +221,13 @@ export class UnitService { console.log('updateElementProperty', elements, property, value); elements.forEach(element => { if (property === 'id') { - if (!this.idService.isIdAvailable((value as string))) { // prohibit existing IDs + if (!IDManager.getInstance().isIdAvailable((value as string))) { // prohibit existing IDs this.messageService.showError(this.translateService.instant('idTaken')); } else { - this.idService.removeId(element.id); - this.idService.addID(value as string); + IDManager.getInstance().removeId(element.id); + IDManager.getInstance().addID(value as string); element.id = value as string; } - } else if (property === 'document') { - element[property] = ClozeParser.setMissingIDs(value as ClozeDocument, this.idService); } else if (element.type === 'likert' && property === 'columns') { (element as LikertElement).rows.forEach(row => { row.columnCount = (element as LikertElement).columns.length; @@ -280,7 +241,7 @@ export class UnitService { } }); this.elementPropertyUpdated.next(); - this.sendChangedNotifications(); + this.veronaApiService.sendVoeDefinitionChangedNotification(this.unit); } updateSelectedElementsPositionProperty(property: string, value: any): void { @@ -292,7 +253,7 @@ export class UnitService { element.setPositionProperty(property, value); }); this.elementPropertyUpdated.next(); - this.sendChangedNotifications(); + this.veronaApiService.sendVoeDefinitionChangedNotification(this.unit); } updateSelectedElementsStyleProperty(property: string, value: any): void { @@ -301,7 +262,7 @@ export class UnitService { element.setStyleProperty(property, value); }); this.elementPropertyUpdated.next(); - this.sendChangedNotifications(); + this.veronaApiService.sendVoeDefinitionChangedNotification(this.unit); } updateElementsPlayerProperty(elements: UIElement[], property: string, value: any): void { @@ -309,19 +270,7 @@ export class UnitService { element.setPlayerProperty(property, value); }); this.elementPropertyUpdated.next(); - this.sendChangedNotifications(); - } - - createLikertRowElement(rowLabelText: string, columnCount: number): LikertRowElement { - return new LikertRowElement({ - id: this.idService.getNewID('likert_row'), - rowLabel: { - text: rowLabelText, - imgSrc: null, - position: 'above' - }, - columnCount: columnCount - } as Partial<LikertRowElement>); + this.veronaApiService.sendVoeDefinitionChangedNotification(this.unit); } alignElements(elements: PositionedUIElement[], alignmentDirection: 'left' | 'right' | 'top' | 'bottom'): void { @@ -357,35 +306,9 @@ export class UnitService { // no default } this.elementPropertyUpdated.next(); - this.sendChangedNotifications(); - } - - sendChangedNotifications(): void { - // stattdessen event emitten? this.veronaApiService.sendVoeDefinitionChangedNotification(this.unit); - - // relevante schemer Data ermitteln - const schemerData = UnitUtils.findUIElements(this.unit.pages) - .filter(element => element.getSchemerData) - .map(element => this.getSchemerOption(element.type) ? - element.getSchemerData(this.getSchemerOption(element.type)) : - element.getSchemerData() - ) - .filter(data => data.values.length || !data.valuesComplete); // schemerData mit leeren values sind nicht von interesse - console.log(schemerData); } - // Values für Schemer elemente setzen? Sind nur Droplists dynamisch? - private getSchemerOption(type: UIElementType): any { - if (type === 'drop-list-simple' || type === 'drop-list') { - return UnitUtils - .findUIElements(this.unit.pages, 'drop-list') - .concat(UnitUtils.findUIElements(this.unit.pages, 'drop-list-simple')); - } - return null; - } - - saveUnit(): void { FileService.saveUnitToFile(JSON.stringify(this.unit)); } @@ -394,7 +317,6 @@ export class UnitService { this.loadUnitDefinition(await FileService.loadFile(['.json'])); } - // TODO: showDefaultEditDialog is method in unitService? showDefaultEditDialog(element: UIElement): void { switch (element.type) { case 'button': @@ -465,23 +387,19 @@ export class UnitService { } getNewValueID(): string { - return this.idService.getNewID('value'); + return IDManager.getInstance().getNewID('value'); } /* Used by props panel to show available dropLists to connect */ getDropListElementIDs(): string[] { - // TODO: DropListSinple? - return this.unit.pages - .map(page => page.sections - .map(section => section.elements - .reduce((accumulator: any[], currentValue: any) => ( - currentValue.type === 'drop-list' ? accumulator.concat(currentValue.id) : accumulator), []) - .flat() - ) - .flat()).flat(); + const allDropLists = [ + ...this.unit.getAllElements('drop-list'), + ...this.unit.getAllElements('drop-list-simple')]; + return allDropLists.map(dropList => dropList.id); } replaceSection(pageIndex: number, sectionIndex: number, newSection: Section): void { - this.unit.pages[pageIndex].sections[sectionIndex] = newSection; + this.deleteSection(this.unit.pages[pageIndex].sections[sectionIndex]); + this.addSection(this.unit.pages[pageIndex], newSection); } } diff --git a/projects/editor/src/app/text-editor/angular-node-views/drop-list-component-extension.ts b/projects/editor/src/app/text-editor/angular-node-views/drop-list-component-extension.ts index ad7dfdedffff4fbcb31d092b686870c24d7a8f70..cf7df19a095ef33d5c97186f0012bccbbde50176 100644 --- a/projects/editor/src/app/text-editor/angular-node-views/drop-list-component-extension.ts +++ b/projects/editor/src/app/text-editor/angular-node-views/drop-list-component-extension.ts @@ -2,8 +2,8 @@ import { Injector } from '@angular/core'; import { Node, mergeAttributes } from '@tiptap/core'; import { AngularNodeViewRenderer } from 'ngx-tiptap'; import { DropListNodeviewComponent } from './drop-list-nodeview.component'; - -import { ElementFactory } from 'common/util/element.factory'; +import { DropListSimpleElement } + from 'common/models/elements/compound-elements/cloze/cloze-child-elements/drop-list-simple'; const DropListComponentExtension = (injector: Injector): Node => { return Node.create({ @@ -14,7 +14,7 @@ const DropListComponentExtension = (injector: Injector): Node => { addAttributes() { return { model: { - default: ElementFactory.createElement('drop-list-simple') + default: new DropListSimpleElement({ type: 'drop-list-simple', id: 'cloze-child-id-placeholder' }) } }; }, diff --git a/projects/editor/src/app/text-editor/angular-node-views/text-field-component-extension.ts b/projects/editor/src/app/text-editor/angular-node-views/text-field-component-extension.ts index ff70a6662cd541a5318b31e3b9ee11736bbc87ea..9e57277b8633d5545eb1ec04ab2f55969e4da5b4 100644 --- a/projects/editor/src/app/text-editor/angular-node-views/text-field-component-extension.ts +++ b/projects/editor/src/app/text-editor/angular-node-views/text-field-component-extension.ts @@ -2,7 +2,9 @@ import { Injector } from '@angular/core'; import { Node, mergeAttributes } from '@tiptap/core'; import { AngularNodeViewRenderer } from 'ngx-tiptap'; import { TextFieldNodeviewComponent } from './text-field-nodeview.component'; -import { ElementFactory } from 'common/util/element.factory'; +import { + TextFieldSimpleElement +} from 'common/models/elements/compound-elements/cloze/cloze-child-elements/text-field-simple'; const TextFieldComponentExtension = (injector: Injector): Node => { return Node.create({ @@ -13,7 +15,7 @@ const TextFieldComponentExtension = (injector: Injector): Node => { addAttributes() { return { model: { - default: ElementFactory.createElement('text-field-simple') + default: new TextFieldSimpleElement({ type: 'text-field-simple', id: 'cloze-child-id-placeholder' }) } }; }, diff --git a/projects/editor/src/app/text-editor/angular-node-views/toggle-button-component-extension.ts b/projects/editor/src/app/text-editor/angular-node-views/toggle-button-component-extension.ts index 00b64cbab83a3b8333d4191ff35957cb98ad2b42..ca5dc0d9e292c62201f4dad62226e7ed98fa0dbf 100644 --- a/projects/editor/src/app/text-editor/angular-node-views/toggle-button-component-extension.ts +++ b/projects/editor/src/app/text-editor/angular-node-views/toggle-button-component-extension.ts @@ -3,7 +3,7 @@ import { Node, mergeAttributes } from '@tiptap/core'; import { AngularNodeViewRenderer } from 'ngx-tiptap'; import { ToggleButtonNodeviewComponent } from './toggle-button-nodeview.component'; -import { ElementFactory } from 'common/util/element.factory'; +import { ToggleButtonElement } from 'common/models/elements/compound-elements/cloze/cloze-child-elements/toggle-button'; const ToggleButtonComponentExtension = (injector: Injector): Node => { return Node.create({ @@ -14,7 +14,12 @@ const ToggleButtonComponentExtension = (injector: Injector): Node => { addAttributes() { return { model: { - default: ElementFactory.createElement('toggle-button', { height: 25, width: 100 }) + default: new ToggleButtonElement({ + type: 'toggle-button', + height: 25, + width: 100, + id: 'cloze-child-id-placeholder' + }) } }; }, diff --git a/projects/editor/src/app/text-editor/rich-text-editor.component.html b/projects/editor/src/app/text-editor/rich-text-editor.component.html index fa909c0771ddd558ab9907ec6d67db6391b7c755..4991d39c035fae146d57bc84d5e6e36f35ab6d5a 100644 --- a/projects/editor/src/app/text-editor/rich-text-editor.component.html +++ b/projects/editor/src/app/text-editor/rich-text-editor.component.html @@ -212,7 +212,9 @@ <button mat-button (click)="insertSpecialChar('♀')">♀</button> <button mat-button (click)="insertSpecialChar('♂')">♂</button> <br> + <button mat-button (click)="insertSpecialChar('‘')">‘</button> <button mat-button (click)="insertSpecialChar('’')">’</button> + <button mat-button (click)="insertSpecialChar('‚')">‚</button> <button mat-button (click)="insertSpecialChar('„')">„</button> <button mat-button (click)="insertSpecialChar('“')">“</button> <button mat-button (click)="insertSpecialChar('”')">”</button> @@ -249,11 +251,11 @@ <button mat-button (click)="insertSpecialChar('♥')">♥</button> <button mat-button (click)="insertSpecialChar('♦')">♦</button> </mat-menu> - <button mat-icon-button matTooltip="Bild" [matTooltipPosition]="'above'" [matTooltipShowDelay]="300" + <button mat-icon-button matTooltip="Bild" [matTooltipShowDelay]="300" (click)="addImage()"> <mat-icon>image</mat-icon> </button> - <button mat-icon-button matTooltip="Zitat" [matTooltipPosition]="'above'" [matTooltipShowDelay]="300" + <button mat-icon-button matTooltip="Zitat" [matTooltipShowDelay]="300" [class.active]="editor.isActive('blockquote')" (click)="toggleBlockquote()"> <mat-icon>format_quote</mat-icon> @@ -263,7 +265,7 @@ <div *ngIf="clozeMode" fxLayout="row" fxLayoutAlign="space-around center" > <button mat-icon-button matTooltip="Eingabefeld" [matTooltipShowDelay]="300" (click)="insertTextField()"> - <mat-icon>text_fields</mat-icon> + <mat-icon>edit</mat-icon> </button> <button mat-icon-button matTooltip="Ablegeliste" [matTooltipShowDelay]="300" (click)="insertDropList()"> diff --git a/projects/editor/src/app/text-editor/rich-text-editor.component.ts b/projects/editor/src/app/text-editor/rich-text-editor.component.ts index 8460daa70c348b5bcf555e7992cc8d4876869afe..c5f4162f03ca467773a32603a05e8a041c675f55 100644 --- a/projects/editor/src/app/text-editor/rich-text-editor.component.ts +++ b/projects/editor/src/app/text-editor/rich-text-editor.component.ts @@ -20,9 +20,7 @@ import { ParagraphExtension } from './extensions/paragraph-extension'; import { FontSize } from './extensions/font-size'; import { BulletListExtension } from './extensions/bullet-list'; import { OrderedListExtension } from './extensions/ordered-list'; - import { FileService } from 'common/services/file.service'; - import ToggleButtonComponentExtension from './angular-node-views/toggle-button-component-extension'; import DropListComponentExtension from './angular-node-views/drop-list-component-extension'; import TextFieldComponentExtension from './angular-node-views/text-field-component-extension'; @@ -75,7 +73,11 @@ export class RichTextEditorComponent implements OnInit, AfterViewInit { Blockquote ]; - editor: Editor = new Editor({ extensions: this.defaultExtensions }); + editor: Editor = new Editor({ + extensions: this.defaultExtensions, + enablePasteRules: false, + enableInputRules: false + }); constructor(private injector: Injector) { } @@ -86,7 +88,11 @@ export class RichTextEditorComponent implements OnInit, AfterViewInit { activeExtensions.push(DropListComponentExtension(this.injector)); activeExtensions.push(TextFieldComponentExtension(this.injector)); } - this.editor = new Editor({ extensions: activeExtensions }); + this.editor = new Editor({ + extensions: activeExtensions, + enablePasteRules: false, + enableInputRules: false + }); } ngAfterViewInit(): void { diff --git a/projects/editor/src/app/util/cloze-parser.ts b/projects/editor/src/app/util/cloze-parser.ts deleted file mode 100644 index 00edefc1b16aeae065c8342e715646b8c1fbb6e0..0000000000000000000000000000000000000000 --- a/projects/editor/src/app/util/cloze-parser.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { IDService } from 'common/services/id.service'; -import { ElementFactory } from 'common/util/element.factory'; -import { InputElement, UIElement } from 'common/models/elements/element'; -import { ClozeDocument } from 'common/models/elements/compound-elements/cloze/cloze'; - -export abstract class ClozeParser { - static setMissingIDs(clozeJSON: ClozeDocument, idService: IDService): ClozeDocument { - clozeJSON.content.forEach((node: any) => { - if (node.type === 'paragraph' || node.type === 'heading') { - ClozeParser.createSubNodeElements(node, idService); - } else if (node.type === 'bulletList' || node.type === 'orderedList') { - node.content.forEach((listItem: any) => { - listItem.content.forEach((listItemParagraph: any) => { - ClozeParser.createSubNodeElements(listItemParagraph, idService); - }); - }); - } else if (node.type === 'blockquote') { - node.content.forEach((blockQuoteItem: any) => { - ClozeParser.createSubNodeElements(blockQuoteItem, idService); - }); - } - }); - return clozeJSON; - } - - // create element anew because the TextEditor can't create multiple element instances - private static createSubNodeElements(node: any, idService: IDService) { - node.content?.forEach((subNode: any) => { - if (['ToggleButton', 'DropList', 'TextField'].includes(subNode.type) && - subNode.attrs.model.id === 'id_placeholder') { - subNode.attrs.model = ClozeParser.createElement(subNode.attrs.model); - subNode.attrs.model.id = idService.getNewID(subNode.attrs.model.type); - } - }); - } - - private static createElement(elementModel: Partial<UIElement>): InputElement { - let newElement: InputElement; - switch (elementModel.type) { - case 'text-field-simple': - newElement = ElementFactory.createElement(elementModel.type, elementModel as UIElement) as InputElement; - break; - case 'drop-list-simple': - newElement = ElementFactory.createElement(elementModel.type, elementModel as UIElement) as InputElement; - break; - case 'toggle-button': - newElement = ElementFactory.createElement(elementModel.type, elementModel as UIElement) as InputElement; - break; - default: - throw new Error(`ElementType ${elementModel.type} not found!`); - } - return newElement; - } -} diff --git a/projects/editor/src/assets/i18n/de.json b/projects/editor/src/assets/i18n/de.json index a84f5751780bee5847474ebba1d275dd819f73ea..a11c716c717a6be55ba8acdd171a28708db712e8 100644 --- a/projects/editor/src/assets/i18n/de.json +++ b/projects/editor/src/assets/i18n/de.json @@ -184,15 +184,15 @@ }, "toolbox": { "text": "Text", - "button": "Knopf", + "button": "Navigationsknopf", "frame": "Rahmen", "text-field": "Eingabefeld", "text-area": "Eingabebereich", "checkbox": "Kontrollkästchen", "dropdown": "Klappliste", - "radio": "Optionsfeld", - "simple": "einfach", - "complex": "komplex", + "radio": "Optionsfelder", + "simple": "mit Text", + "complex": "mit Bild", "drop-list": "Ablegeliste", "image": "Bild", "audio": "Audio", @@ -215,5 +215,9 @@ "fraction": "Anteile", "pixel": "Bildpunkte", "activeAfterID": "Sichtbar nach A/V-ID" - } + }, + "Bitte kopierten Abschnitt einfügen": "Bitte kopierten Abschnitt einfügen", + "Doppelte IDs festgestellt. Weiter mit neu generierten IDs?": "Doppelte IDs festgestellt. Weiter mit neu generierten IDs?", + "Abschnitt wurde erfolgreich gelesen.": "Abschnitt wurde erfolgreich gelesen.", + "Fehler beim Lesen des Abschnitts!": "Fehler beim Lesen des Abschnitts!" } diff --git a/projects/player/src/app/components/elements/compound-group-element/compound-group-element.component.ts b/projects/player/src/app/components/elements/compound-group-element/compound-group-element.component.ts index af4d9657d4eaa870e74b17b29ffb6cefafebada3..536880f5f0dc3a443cdf49f31881a217c752f4fd 100644 --- a/projects/player/src/app/components/elements/compound-group-element/compound-group-element.component.ts +++ b/projects/player/src/app/components/elements/compound-group-element/compound-group-element.component.ts @@ -74,7 +74,7 @@ export class CompoundGroupElementComponent extends ElementFormGroupDirective imp private manageKeyInputToggling(textFieldSimpleComponent: TextFieldSimpleComponent, elementModel: InputElement): void { (textFieldSimpleComponent) - .textInputExpected + .focusChanged .pipe(takeUntil(this.ngUnsubscribe)) .subscribe(focusedTextInput => { this.toggleKeyInput(focusedTextInput, textFieldSimpleComponent, elementModel); diff --git a/projects/player/src/app/components/elements/text-input-group-element/text-input-group-element.component.html b/projects/player/src/app/components/elements/text-input-group-element/text-input-group-element.component.html index fb75e4335bca43dac2ae06631c8335024c07cc40..4d4e116eab76ee7bde9aa50da601db7b82ff27d0 100644 --- a/projects/player/src/app/components/elements/text-input-group-element/text-input-group-element.component.html +++ b/projects/player/src/app/components/elements/text-input-group-element/text-input-group-element.component.html @@ -6,7 +6,7 @@ [parentForm]="form" [elementModel]="elementModel | cast: TextAreaElement" (hardwareKeyDetected)="detectHardwareKeyboard()" - (textInputExpected)="toggleKeyInput($event, elementComponent)"> + (focusChanged)="toggleKeyInput($event, elementComponent)"> </aspect-text-area> <aspect-text-field *ngIf="elementModel.type === 'text-field'" @@ -14,7 +14,7 @@ [parentForm]="form" [elementModel]="elementModel | cast: TextFieldElement" (hardwareKeyDetected)="detectHardwareKeyboard()" - (textInputExpected)="toggleKeyInput($event, elementComponent)"> + (focusChanged)="toggleKeyInput($event, elementComponent)"> </aspect-text-field> <aspect-spell-correct *ngIf="elementModel.type === 'spell-correct'" @@ -22,7 +22,7 @@ [parentForm]="form" [elementModel]="elementModel | cast: SpellCorrectElement" (hardwareKeyDetected)="detectHardwareKeyboard()" - (textInputExpected)="toggleKeyInput($event, elementComponent)"> + (focusChanged)="toggleKeyInput($event, elementComponent)"> </aspect-spell-correct> </form> diff --git a/projects/player/src/app/components/elements/text-input-group-element/text-input-group-element.component.ts b/projects/player/src/app/components/elements/text-input-group-element/text-input-group-element.component.ts index f155265cf618acfbae24027cbd537528fe6ddbb4..1c2b98ab759aa318d6fdbe90a8b0275e07861e36 100644 --- a/projects/player/src/app/components/elements/text-input-group-element/text-input-group-element.component.ts +++ b/projects/player/src/app/components/elements/text-input-group-element/text-input-group-element.component.ts @@ -63,7 +63,6 @@ export class TextInputGroupElementComponent extends ElementFormGroupDirective im toggleKeyInput(focusedTextInput: { inputElement: HTMLElement; focused: boolean }, elementComponent: TextAreaComponent | TextFieldComponent | SpellCorrectComponent): void { - console.log('open---------------------', this.elementModel.inputAssistance); if (this.elementModel.inputAssistancePreset) { this.keypadService.toggle(focusedTextInput, elementComponent); this.isKeypadOpen = this.keypadService.isOpen; diff --git a/projects/player/src/app/components/layouts/player-layout/player-layout.component.html b/projects/player/src/app/components/layouts/player-layout/player-layout.component.html index 05e95ebb03bb5b15d1f7f61e5ccc55819f4d273c..a71c6ff49e18dc909dcbaeaf5dcf0451292befc6 100644 --- a/projects/player/src/app/components/layouts/player-layout/player-layout.component.html +++ b/projects/player/src/app/components/layouts/player-layout/player-layout.component.html @@ -20,6 +20,7 @@ <aspect-keyboard *ngIf="keyboardService.isOpen" @keyboardSlideInOut + (@keyboardSlideInOut.done)="keyboardService.scrollElement()" [showFrenchCharacters]="keyboardService.elementComponent.elementModel.softwareKeyboardShowFrench" (keyClicked)="keyboardService.enterKey($event)" (backspaceClicked)="keyboardService.deleteCharacters(true)"> diff --git a/projects/player/src/app/components/page/page.component.spec.ts b/projects/player/src/app/components/page/page.component.spec.ts index 8c1a3fa598a31bde15c78d63a2b29a1b1cb05a01..7c13e562939a4a704cd6e3af17ce39372304588a 100644 --- a/projects/player/src/app/components/page/page.component.spec.ts +++ b/projects/player/src/app/components/page/page.component.spec.ts @@ -1,5 +1,6 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { PageComponent } from './page.component'; +import { Page } from 'common/models/page'; describe('PageComponent', () => { let component: PageComponent; @@ -15,16 +16,7 @@ describe('PageComponent', () => { beforeEach(() => { fixture = TestBed.createComponent(PageComponent); component = fixture.componentInstance; - component.page = { - sections: [], - hasMaxWidth: false, - maxWidth: 0, - margin: 0, - backgroundColor: 'white', - alwaysVisible: false, - alwaysVisiblePagePosition: 'left', - alwaysVisibleAspectRatio: 50 - }; + component.page = new Page(); fixture.detectChanges(); }); diff --git a/projects/player/src/app/pipes/always-visible-page.pipe.spec.ts b/projects/player/src/app/pipes/always-visible-page.pipe.spec.ts index bab9c940d3b78c82e433bc42c03a21b97e1bf76e..52565511c841bdaaec2023a814c231f64b4d478e 100644 --- a/projects/player/src/app/pipes/always-visible-page.pipe.spec.ts +++ b/projects/player/src/app/pipes/always-visible-page.pipe.spec.ts @@ -4,7 +4,7 @@ import { Page } from 'common/models/page'; describe('AlwaysVisiblePagePipe', () => { - const page: Page = { + const page: Page = new Page({ hasMaxWidth: false, maxWidth: 0, margin: 0, @@ -13,13 +13,13 @@ describe('AlwaysVisiblePagePipe', () => { alwaysVisiblePagePosition: 'left', alwaysVisibleAspectRatio: 50, sections: [] - }; + }); const pipe = new AlwaysVisiblePagePipe(); it('should transform an array of pages to the always visible page of the array', () => { - const pages = [page, page, { ...page, alwaysVisible: true }]; - expect(pipe.transform(pages)).toEqual({ ...page, alwaysVisible: true }); + const pages = [page, page, { ...page, alwaysVisible: true } as Page]; + expect(pipe.transform(pages)).toEqual({ ...page, alwaysVisible: true } as Page); }); it('should transform an array of pages without any always visible page to null', () => { diff --git a/projects/player/src/app/pipes/page-index.pipe.spec.ts b/projects/player/src/app/pipes/page-index.pipe.spec.ts index 07218091671bb6e9126df25d296370fc66e9e0c5..3acb646a6edee8aa234d06665eeabede75d25a0a 100644 --- a/projects/player/src/app/pipes/page-index.pipe.spec.ts +++ b/projects/player/src/app/pipes/page-index.pipe.spec.ts @@ -3,7 +3,7 @@ import { Page } from 'common/models/page'; describe('PageIndexPipe', () => { - const page: Page = { + const page: Page = new Page({ hasMaxWidth: false, maxWidth: 0, margin: 0, @@ -12,9 +12,9 @@ describe('PageIndexPipe', () => { alwaysVisiblePagePosition: 'left', alwaysVisibleAspectRatio: 50, sections: [] - }; + }); - const page2: Page = { + const page2: Page = new Page({ hasMaxWidth: false, maxWidth: 0, margin: 0, @@ -23,33 +23,33 @@ describe('PageIndexPipe', () => { alwaysVisiblePagePosition: 'left', alwaysVisibleAspectRatio: 50, sections: [] - }; + }); const pipe = new PageIndexPipe(); it('should transform pages to the index of given page (0)', () => { - const pages = [page, page2, { ...page2, alwaysVisible: true }]; + const pages = [page, page2, { ...page2, alwaysVisible: true } as Page]; expect(pipe.transform(pages, page)).toEqual(0); }); it('should transform pages to the index of given page (not 1)', () => { - const pages = [page, page2, { ...page2, alwaysVisible: true }]; + const pages = [page, page2, { ...page2, alwaysVisible: true } as Page]; expect(pipe.transform(pages, page)).not.toEqual(1); }); it('should transform pages to the index of given page2 (1)', () => { - const pages = [page, page2, { ...page2, alwaysVisible: true }]; + const pages = [page, page2, { ...page2, alwaysVisible: true } as Page]; expect(pipe.transform(pages, page2)).toEqual(1); }); it('should transform pages to the index of unknown page (-1)', () => { - const pages = [page, { ...page2, alwaysVisible: true }]; + const pages = [page, { ...page2, alwaysVisible: true } as Page]; expect(pipe.transform(pages, page2)).toEqual(-1); }); it('should transform pages to the index of unknown page (-1)', () => { - const pages = [page, { ...page2, alwaysVisible: true }]; - expect(pipe.transform(pages, { ...page2, alwaysVisible: true })).toEqual(-1); + const pages = [page, { ...page2, alwaysVisible: true } as Page]; + expect(pipe.transform(pages, { ...page2, alwaysVisible: true } as Page)).toEqual(-1); }); }); diff --git a/projects/player/src/app/pipes/scroll-pages.pipe.spec.ts b/projects/player/src/app/pipes/scroll-pages.pipe.spec.ts index e0620054a66bb0c0dce5d2e64187c04f32236e66..5c28680960dc52bfca8030bae8fc90d59fe159a9 100644 --- a/projects/player/src/app/pipes/scroll-pages.pipe.spec.ts +++ b/projects/player/src/app/pipes/scroll-pages.pipe.spec.ts @@ -3,7 +3,7 @@ import { Page } from 'common/models/page'; describe('ScrollPagesPipe', () => { - const page: Page = { + const page: Page = new Page({ hasMaxWidth: false, maxWidth: 0, margin: 0, @@ -12,12 +12,12 @@ describe('ScrollPagesPipe', () => { alwaysVisiblePagePosition: 'left', alwaysVisibleAspectRatio: 50, sections: [] - }; + }); const pipe = new ScrollPagesPipe(); it('should transform 3 pages to 2 scroll pages', () => { - const pages = [page, page, { ...page, alwaysVisible: true }]; + const pages = [page, page, { ...page, alwaysVisible: true } as Page]; expect(pipe.transform(pages).length).toBe(2); }); diff --git a/projects/player/src/app/pipes/valid-pages.pipe.spec.ts b/projects/player/src/app/pipes/valid-pages.pipe.spec.ts index 88eebb0e3727daaf486f3a6e693cd6a4a06b577c..ff72271707565bf0f34dae535b89bc5c33651e86 100644 --- a/projects/player/src/app/pipes/valid-pages.pipe.spec.ts +++ b/projects/player/src/app/pipes/valid-pages.pipe.spec.ts @@ -13,7 +13,7 @@ describe('ValidPagesPipe', () => { } }; - const page: Page = { + const page = new Page({ hasMaxWidth: false, maxWidth: 0, margin: 0, @@ -22,7 +22,7 @@ describe('ValidPagesPipe', () => { alwaysVisiblePagePosition: 'left', alwaysVisibleAspectRatio: 50, sections: [] - }; + }); beforeEach(() => { TestBed diff --git a/projects/player/src/app/services/keyboard.service.ts b/projects/player/src/app/services/keyboard.service.ts index 43eb9407da8dc2791875e7d1d409ad7b708e9992..9b48608ce3c9f42abd6910dd49d473357711ecfd 100644 --- a/projects/player/src/app/services/keyboard.service.ts +++ b/projects/player/src/app/services/keyboard.service.ts @@ -27,18 +27,26 @@ export class KeyboardService extends InputService { elementComponent: TextAreaComponent | TextFieldComponent | TextFieldSimpleComponent | SpellCorrectComponent): void { this.alternativeKeyboardShowFrench = elementComponent.elementModel.softwareKeyboardShowFrench; - this.scrollElement(inputElement); this.setCurrentKeyInputElement(inputElement, elementComponent); this.isOpen = true; } - private scrollElement = (element: HTMLElement): void => { - if (this.isHiddenByKeyboard(element)) { - element.scrollIntoView({ behavior: 'smooth', block: 'center' }); + scrollElement(): void { + if (this.isOpen && this.isElementHiddenByKeyboard()) { + const scrollPositionTarget = this.isViewHighEnoughToCenterElement() ? 'start' : 'center'; + this.elementComponent.domElement.scrollIntoView({ block: scrollPositionTarget }); } - }; + } + + private isViewHighEnoughToCenterElement(): boolean { + return window.innerHeight < this.getKeyboardHeight() * 2; + } - private isHiddenByKeyboard = (element: HTMLElement): boolean => ( - window.innerHeight - element.getBoundingClientRect().top < 300 - ); + private isElementHiddenByKeyboard(): boolean { + return window.innerHeight - this.elementComponent.domElement.getBoundingClientRect().top < this.getKeyboardHeight(); + } + + private getKeyboardHeight(): number { + return this.alternativeKeyboardShowFrench ? 400 : 350; + } } diff --git a/projects/player/src/app/services/unit-state.service.ts b/projects/player/src/app/services/unit-state.service.ts index fc05fb6d6e307e48b63a93a00ccec6d65b507ca3..dd22bd12dbbf27cbde3b4b00bb820931fce701bd 100644 --- a/projects/player/src/app/services/unit-state.service.ts +++ b/projects/player/src/app/services/unit-state.service.ts @@ -105,21 +105,22 @@ export class UnitStateService { private setElementCodeStatus(id: string, status: ElementCodeStatus): void { const unitStateElementCode = this.getElementCodeById(id); if (unitStateElementCode) { - if (ElementCodeStatusValue[status] > ElementCodeStatusValue[unitStateElementCode.status]) { - unitStateElementCode.status = status; - this._elementCodeChanged.next(unitStateElementCode); - this.checkPresentedPageStatus(this.elementIdPageIndexMap[id], true); + const actualStatus = unitStateElementCode.status; + unitStateElementCode.status = status; + this._elementCodeChanged.next(unitStateElementCode); + if (ElementCodeStatusValue[status] > ElementCodeStatusValue[actualStatus]) { + this.checkPresentedPageStatus(this.elementIdPageIndexMap[id]); } } } private buildPresentedPages(): void { const uniqPages = [...new Set( Object.values(this.elementIdPageIndexMap))]; - uniqPages.forEach((pageIndex, index) => this - .checkPresentedPageStatus(pageIndex, index === uniqPages.length - 1)); + uniqPages.forEach(pageIndex => this + .checkPresentedPageStatus(pageIndex)); } - private checkPresentedPageStatus(pageIndex: number, emitEvent: boolean): void { + private checkPresentedPageStatus(pageIndex: number): void { if (this.presentedPages.indexOf(pageIndex) === -1) { const notDisplayedElements = Object.entries(this.elementIdPageIndexMap) .filter((map: [string, number]): boolean => map[1] === pageIndex) @@ -129,9 +130,7 @@ export class UnitStateService { ElementCodeStatusValue.DISPLAYED); if (notDisplayedElements.length === 0) { this.presentedPages.push(pageIndex); - if (emitEvent) { - this._presentedPageAdded.next(pageIndex); - } + this._presentedPageAdded.next(pageIndex); } } else { LogService.warn(`player: page ${pageIndex} is already presented`);