diff --git a/docs/presentation.md b/docs/presentation.md index 85c64bc9..7dee1eb3 100644 --- a/docs/presentation.md +++ b/docs/presentation.md @@ -50,9 +50,9 @@ This project uses a singleton instance of the application state, making it avail The decision to not use third-party state management libraries like [`vuex`](https://web.archive.org/web/20230801191617/https://vuex.vuejs.org/) or [`pinia`](https://web.archive.org/web/20230801191743/https://pinia.vuejs.org/) was made to promote code independence and enhance portability. -Stateful components can mutate and/or react to state changes (e.g., user selection, search queries) in the [ApplicationContext](./../src/application/Context/ApplicationContext.ts). Vue components import [`CollectionState.ts`](./../src/presentation/components/Shared/CollectionState.ts) to access both the application context and the state. +Stateful components can mutate and/or react to state changes (e.g., user selection, search queries) in the [ApplicationContext](./../src/application/Context/ApplicationContext.ts). Vue components import [`CollectionState.ts`](./../src/presentation/components/Shared/Hooks/UseCollectionState.ts) to access both the application context and the state. -[`CollectionState.ts`](./../src/presentation/components/Shared/CollectionState.ts) provides several functionalities including: +[`UseCollectionState.ts`](./../src/presentation/components/Shared/Hooks/UseCollectionState.ts) provides several functionalities including: - **Singleton State Instance**: It creates a singleton instance of the state, which is shared across the presentation layer. The singleton instance ensures that there's a single source of truth for the application's state. - **State Change Callback and Lifecycle Management**: It offers a mechanism to register callbacks, which will be invoked when the state initializes or mutates. It ensures that components unsubscribe from state events when they are no longer in use or when [ApplicationContext](./../src/application/Context/ApplicationContext.ts) switches the active [collection](./collection-files.md). @@ -63,16 +63,7 @@ Stateful components can mutate and/or react to state changes (e.g., user selecti ## Modals -[ModalDialog.vue](./../src/presentation/components/Shared/ModalDialog.vue) is a shared component utilized for rendering modal windows. - -Use the component by wrapping the desired content within its slot and calling the .show() function on its reference, as shown below: - -```html - -
Hello world
-
-
Show dialog
-``` +- [ModalDialog.vue](./../src/presentation/components/Shared/Modal/ModalDialog.vue) is a shared component utilized for rendering modal windows. ## Sass naming convention diff --git a/package-lock.json b/package-lock.json index 851bb873..d6ef7d8c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,8 +25,7 @@ "markdown-it": "^13.0.1", "npm": "^9.8.1", "v-tooltip": "2.1.3", - "vue": "^2.7.14", - "vue-js-modal": "^2.0.1" + "vue": "^2.7.14" }, "devDependencies": { "@rushstack/eslint-patch": "^1.3.2", @@ -44,6 +43,7 @@ "@vue/cli-service": "~5.0.8", "@vue/eslint-config-airbnb-with-typescript": "^7.0.0", "@vue/eslint-config-typescript": "^11.0.3", + "@vue/test-utils": "^1.3.6", "chai": "^4.3.7", "cypress": "^12.17.2", "electron": "^25.3.2", @@ -3311,6 +3311,12 @@ "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, + "node_modules/@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", + "dev": true + }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -4826,6 +4832,21 @@ } } }, + "node_modules/@vue/test-utils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-1.3.6.tgz", + "integrity": "sha512-udMmmF1ts3zwxUJEIAj5ziioR900reDrt6C9H3XpWPsLBx2lpHKoA4BTdd9HNIYbkGltWw+JjWJ+5O6QBwiyEw==", + "dev": true, + "dependencies": { + "dom-event-types": "^1.0.0", + "lodash": "^4.17.15", + "pretty": "^2.0.0" + }, + "peerDependencies": { + "vue": "2.x", + "vue-template-compiler": "^2.x" + } + }, "node_modules/@vue/vue-loader-v15": { "name": "vue-loader", "version": "15.10.0", @@ -5043,6 +5064,12 @@ "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", "dev": true }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -7053,6 +7080,36 @@ "typedarray": "^0.0.6" } }, + "node_modules/condense-newlines": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/condense-newlines/-/condense-newlines-0.2.1.tgz", + "integrity": "sha512-P7X+QL9Hb9B/c8HI5BFFKmjgBu2XpQuF98WZ9XkO+dBGgk5XgwiQz7o1SmpglNWId3581UcS0SFAWfoIhMHPfg==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "is-whitespace": "^0.3.0", + "kind-of": "^3.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "dependencies": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "node_modules/config-chain/node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, "node_modules/config-file-ts": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/config-file-ts/-/config-file-ts-0.2.4.tgz", @@ -7834,6 +7891,13 @@ "integrity": "sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA==", "dev": true }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true, + "peer": true + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -8419,6 +8483,12 @@ "utila": "~0.4" } }, + "node_modules/dom-event-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/dom-event-types/-/dom-event-types-1.1.0.tgz", + "integrity": "sha512-jNCX+uNJ3v38BKvPbpki6j5ItVlnSqVV6vDWGS6rExzCMjsc39frLjm1n91o6YaKK6AZl0wLloItW6C6mr61BQ==", + "dev": true + }, "node_modules/dom-serializer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", @@ -8554,6 +8624,57 @@ "safer-buffer": "^2.1.0" } }, + "node_modules/editorconfig": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", + "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", + "dev": true, + "dependencies": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "9.0.1", + "semver": "^7.5.3" + }, + "bin": { + "editorconfig": "bin/editorconfig" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/editorconfig/node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/editorconfig/node_modules/minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -10374,6 +10495,18 @@ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/extract-zip": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", @@ -12156,6 +12289,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, "node_modules/is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -12228,6 +12367,15 @@ "integrity": "sha512-F2FnH/otLNJv0J6wc73A5Xo7oHLNnqplYqZhUu01tD54DIPvxIRSTSLkrUB/M0nHO4vo1O9PDfN4KoTxCzLh/w==", "dev": true }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -12575,6 +12723,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-whitespace": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-whitespace/-/is-whitespace-0.3.0.tgz", + "integrity": "sha512-RydPhl4S6JwAyj0JJjshWJEFG6hNye3pZFBRZaTUfZFwGHxzppNaNOVgQuS/E/SlhrApuMXrpnK1EEIXfdo3Dg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -12808,6 +12965,66 @@ "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", "dev": true }, + "node_modules/js-beautify": { + "version": "1.14.9", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.9.tgz", + "integrity": "sha512-coM7xq1syLcMyuVGyToxcj2AlzhkDjmfklL8r0JgJ7A76wyGMpJ1oA35mr4APdYNO/o/4YY8H54NQIJzhMbhBg==", + "dev": true, + "dependencies": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.3", + "glob": "^8.1.0", + "nopt": "^6.0.0" + }, + "bin": { + "css-beautify": "js/bin/css-beautify.js", + "html-beautify": "js/bin/html-beautify.js", + "js-beautify": "js/bin/js-beautify.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/js-beautify/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/js-beautify/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/js-beautify/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/js-message": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/js-message/-/js-message-1.0.7.tgz", @@ -13158,6 +13375,18 @@ "json-buffer": "3.0.0" } }, + "node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "dependencies": { + "is-buffer": "^1.1.5" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/klaw": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", @@ -15637,6 +15866,21 @@ "dev": true, "hasInstallScript": true }, + "node_modules/nopt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", + "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "dev": true, + "dependencies": { + "abbrev": "^1.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, "node_modules/normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -20360,6 +20604,20 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/pretty": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pretty/-/pretty-2.0.0.tgz", + "integrity": "sha512-G9xUchgTEiNpormdYBl+Pha50gOUovT18IvAe7EYMZ1/f9W/WWMPRn+xI68yXNMUk3QXHDwo/1wV/4NejVNe1w==", + "dev": true, + "dependencies": { + "condense-newlines": "^0.2.1", + "extend-shallow": "^2.0.1", + "js-beautify": "^1.6.12" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -20580,6 +20838,12 @@ "levenshtein-edit-distance": "^1.0.0" } }, + "node_modules/proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -22259,11 +22523,6 @@ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", "dev": true }, - "node_modules/resize-observer-polyfill": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", - "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" - }, "node_modules/resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -25767,17 +26026,6 @@ "integrity": "sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==", "dev": true }, - "node_modules/vue-js-modal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/vue-js-modal/-/vue-js-modal-2.0.1.tgz", - "integrity": "sha512-5FUwsH2zoxRKX4a7wkFAqX0eITCcIMunJDEfIxzHs2bHw9o20+Iqm+uQvBcg1jkzyo1+tVgThR/7NGU8djbD8Q==", - "dependencies": { - "resize-observer-polyfill": "^1.5.1" - }, - "peerDependencies": { - "vue": "^2.6.11" - } - }, "node_modules/vue-loader": { "version": "17.0.0", "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-17.0.0.tgz", @@ -25903,6 +26151,17 @@ "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==", "dev": true }, + "node_modules/vue-template-compiler": { + "version": "2.7.14", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz", + "integrity": "sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==", + "dev": true, + "peer": true, + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, "node_modules/vue-template-es2015-compiler": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz", @@ -29399,6 +29658,12 @@ "integrity": "sha512-pwK+BfEBZJbKdNYpHHRTNBwBoqrN/iIMO0AiGvYsp3Hoaq0WbgGSWQR6SCldZovoDpY3yje5lkFUe6gsDgJ2vg==", "dev": true }, + "@one-ini/wasm": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", + "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", + "dev": true + }, "@pkgjs/parseargs": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", @@ -30605,6 +30870,17 @@ "vue-eslint-parser": "^9.1.1" } }, + "@vue/test-utils": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-1.3.6.tgz", + "integrity": "sha512-udMmmF1ts3zwxUJEIAj5ziioR900reDrt6C9H3XpWPsLBx2lpHKoA4BTdd9HNIYbkGltWw+JjWJ+5O6QBwiyEw==", + "dev": true, + "requires": { + "dom-event-types": "^1.0.0", + "lodash": "^4.17.15", + "pretty": "^2.0.0" + } + }, "@vue/vue-loader-v15": { "version": "npm:vue-loader@15.10.0", "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.10.0.tgz", @@ -30808,6 +31084,12 @@ "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", "dev": true }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, "accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -32322,6 +32604,35 @@ "typedarray": "^0.0.6" } }, + "condense-newlines": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/condense-newlines/-/condense-newlines-0.2.1.tgz", + "integrity": "sha512-P7X+QL9Hb9B/c8HI5BFFKmjgBu2XpQuF98WZ9XkO+dBGgk5XgwiQz7o1SmpglNWId3581UcS0SFAWfoIhMHPfg==", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-whitespace": "^0.3.0", + "kind-of": "^3.0.2" + } + }, + "config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "dev": true, + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + }, + "dependencies": { + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + } + } + }, "config-file-ts": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/config-file-ts/-/config-file-ts-0.2.4.tgz", @@ -32894,6 +33205,13 @@ "integrity": "sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA==", "dev": true }, + "de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true, + "peer": true + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -33305,6 +33623,12 @@ "utila": "~0.4" } }, + "dom-event-types": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/dom-event-types/-/dom-event-types-1.1.0.tgz", + "integrity": "sha512-jNCX+uNJ3v38BKvPbpki6j5ItVlnSqVV6vDWGS6rExzCMjsc39frLjm1n91o6YaKK6AZl0wLloItW6C6mr61BQ==", + "dev": true + }, "dom-serializer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", @@ -33413,6 +33737,44 @@ "safer-buffer": "^2.1.0" } }, + "editorconfig": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", + "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", + "dev": true, + "requires": { + "@one-ini/wasm": "0.1.1", + "commander": "^10.0.0", + "minimatch": "9.0.1", + "semver": "^7.5.3" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true + }, + "minimatch": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", + "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -34795,6 +35157,15 @@ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, "extract-zip": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", @@ -36104,6 +36475,12 @@ "has-tostringtag": "^1.0.0" } }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + }, "is-callable": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", @@ -36149,6 +36526,12 @@ "integrity": "sha512-F2FnH/otLNJv0J6wc73A5Xo7oHLNnqplYqZhUu01tD54DIPvxIRSTSLkrUB/M0nHO4vo1O9PDfN4KoTxCzLh/w==", "dev": true }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -36381,6 +36764,12 @@ "call-bind": "^1.0.2" } }, + "is-whitespace": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/is-whitespace/-/is-whitespace-0.3.0.tgz", + "integrity": "sha512-RydPhl4S6JwAyj0JJjshWJEFG6hNye3pZFBRZaTUfZFwGHxzppNaNOVgQuS/E/SlhrApuMXrpnK1EEIXfdo3Dg==", + "dev": true + }, "is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -36559,6 +36948,51 @@ "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==", "dev": true }, + "js-beautify": { + "version": "1.14.9", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.9.tgz", + "integrity": "sha512-coM7xq1syLcMyuVGyToxcj2AlzhkDjmfklL8r0JgJ7A76wyGMpJ1oA35mr4APdYNO/o/4YY8H54NQIJzhMbhBg==", + "dev": true, + "requires": { + "config-chain": "^1.1.13", + "editorconfig": "^1.0.3", + "glob": "^8.1.0", + "nopt": "^6.0.0" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + } + }, + "minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, "js-message": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/js-message/-/js-message-1.0.7.tgz", @@ -36863,6 +37297,15 @@ "json-buffer": "3.0.0" } }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, "klaw": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", @@ -38685,6 +39128,15 @@ "integrity": "sha512-7Ws63oC+215smeKJQCxzrK21VFVlCFBkwl0MOObt0HOpVQXs3u483sAmtkF33nNqZ5rSOQjB76fgyPBmAUrtCA==", "dev": true }, + "nopt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", + "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", + "dev": true, + "requires": { + "abbrev": "^1.0.0" + } + }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -41856,6 +42308,17 @@ "dev": true, "optional": true }, + "pretty": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pretty/-/pretty-2.0.0.tgz", + "integrity": "sha512-G9xUchgTEiNpormdYBl+Pha50gOUovT18IvAe7EYMZ1/f9W/WWMPRn+xI68yXNMUk3QXHDwo/1wV/4NejVNe1w==", + "dev": true, + "requires": { + "condense-newlines": "^0.2.1", + "extend-shallow": "^2.0.1", + "js-beautify": "^1.6.12" + } + }, "pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -42024,6 +42487,12 @@ "levenshtein-edit-distance": "^1.0.0" } }, + "proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true + }, "proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -43324,11 +43793,6 @@ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", "dev": true }, - "resize-observer-polyfill": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz", - "integrity": "sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==" - }, "resolve": { "version": "1.22.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", @@ -45949,14 +46413,6 @@ "integrity": "sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==", "dev": true }, - "vue-js-modal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/vue-js-modal/-/vue-js-modal-2.0.1.tgz", - "integrity": "sha512-5FUwsH2zoxRKX4a7wkFAqX0eITCcIMunJDEfIxzHs2bHw9o20+Iqm+uQvBcg1jkzyo1+tVgThR/7NGU8djbD8Q==", - "requires": { - "resize-observer-polyfill": "^1.5.1" - } - }, "vue-loader": { "version": "17.0.0", "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-17.0.0.tgz", @@ -46056,6 +46512,17 @@ } } }, + "vue-template-compiler": { + "version": "2.7.14", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz", + "integrity": "sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==", + "dev": true, + "peer": true, + "requires": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, "vue-template-es2015-compiler": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.9.1.tgz", diff --git a/package.json b/package.json index c800b137..c86c2e04 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build", - "test:unit": "vue-cli-service test:unit", + "test:unit": "vue-cli-service test:unit --include ./tests/bootstrap/setup.ts", "test:e2e": "vue-cli-service test:e2e", "lint": "npm run lint:md && npm run lint:md:consistency && npm run lint:md:relative-urls && npm run lint:eslint && npm run lint:yaml", "create-icons": "node img/logo-update.mjs", @@ -21,7 +21,7 @@ "lint:yaml": "yamllint **/*.yaml --ignore=node_modules/**/*.yaml", "postinstall": "electron-builder install-app-deps", "postuninstall": "electron-builder install-app-deps", - "test:integration": "vue-cli-service test:unit \"tests/integration/**/*.spec.ts\"" + "test:integration": "vue-cli-service test:unit \"tests/integration/**/*.spec.ts\" --include ./tests/bootstrap/setup.ts" }, "main": "index.js", "dependencies": { @@ -41,8 +41,7 @@ "markdown-it": "^13.0.1", "npm": "^9.8.1", "v-tooltip": "2.1.3", - "vue": "^2.7.14", - "vue-js-modal": "^2.0.1" + "vue": "^2.7.14" }, "devDependencies": { "@rushstack/eslint-patch": "^1.3.2", @@ -60,6 +59,7 @@ "@vue/cli-service": "~5.0.8", "@vue/eslint-config-airbnb-with-typescript": "^7.0.0", "@vue/eslint-config-typescript": "^11.0.3", + "@vue/test-utils": "^1.3.6", "chai": "^4.3.7", "cypress": "^12.17.2", "electron": "^25.3.2", diff --git a/src/presentation/assets/styles/_mixins.scss b/src/presentation/assets/styles/_mixins.scss index ffb0e8b4..c6d2d3fd 100644 --- a/src/presentation/assets/styles/_mixins.scss +++ b/src/presentation/assets/styles/_mixins.scss @@ -27,3 +27,21 @@ */ -webkit-tap-highlight-color: transparent; } + +@mixin fade-slide-transition($name, $duration, $offset-upward: null) { + .#{$name}-enter-active, + .#{$name}-leave-active { + transition: all $duration; + } + + .#{$name}-leave-active, + .#{$name}-enter, // Vue 2.X compatibility + .#{$name}-enter-from // Vue 3.X compatibility + { + opacity: 0; + + @if $offset-upward { + transform: translateY($offset-upward); + } + } +} \ No newline at end of file diff --git a/src/presentation/bootstrapping/ApplicationBootstrapper.ts b/src/presentation/bootstrapping/ApplicationBootstrapper.ts index 3c2830aa..357350ed 100644 --- a/src/presentation/bootstrapping/ApplicationBootstrapper.ts +++ b/src/presentation/bootstrapping/ApplicationBootstrapper.ts @@ -1,4 +1,3 @@ -import { VModalBootstrapper } from './Modules/VModalBootstrapper'; import { TreeBootstrapper } from './Modules/TreeBootstrapper'; import { IconBootstrapper } from './Modules/IconBootstrapper'; import { VueConstructor, IVueBootstrapper } from './IVueBootstrapper'; @@ -19,7 +18,6 @@ export class ApplicationBootstrapper implements IVueBootstrapper { new TreeBootstrapper(), new VueBootstrapper(), new TooltipBootstrapper(), - new VModalBootstrapper(), ]; } } diff --git a/src/presentation/bootstrapping/Modules/VModalBootstrapper.ts b/src/presentation/bootstrapping/Modules/VModalBootstrapper.ts deleted file mode 100644 index 2c59a995..00000000 --- a/src/presentation/bootstrapping/Modules/VModalBootstrapper.ts +++ /dev/null @@ -1,8 +0,0 @@ -import VModal from 'vue-js-modal'; -import { VueConstructor, IVueBootstrapper } from '../IVueBootstrapper'; - -export class VModalBootstrapper implements IVueBootstrapper { - public bootstrap(vue: VueConstructor): void { - vue.use(VModal, { dynamic: true, injectModalsContainer: true }); - } -} diff --git a/src/presentation/components/Code/CodeButtons/TheCodeButtons.vue b/src/presentation/components/Code/CodeButtons/TheCodeButtons.vue index 682e95cd..1b8e22e1 100644 --- a/src/presentation/components/Code/CodeButtons/TheCodeButtons.vue +++ b/src/presentation/components/Code/CodeButtons/TheCodeButtons.vue @@ -19,7 +19,7 @@ icon-prefix="fas" icon-name="copy" /> - + @@ -30,7 +30,7 @@ import { defineComponent, ref, computed } from 'vue'; import { useCollectionState } from '@/presentation/components/Shared/Hooks/UseCollectionState'; import { SaveFileDialog, FileType } from '@/infrastructure/SaveFileDialog'; import { Clipboard } from '@/infrastructure/Clipboard'; -import ModalDialog from '@/presentation/components/Shared/ModalDialog.vue'; +import ModalDialog from '@/presentation/components/Shared/Modal/ModalDialog.vue'; import { Environment } from '@/application/Environment/Environment'; import { IReadOnlyCategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState'; import { ScriptingLanguage } from '@/domain/ScriptingLanguage'; @@ -57,7 +57,7 @@ export default defineComponent({ currentState, currentContext, onStateChange, events, } = useCollectionState(); - const instructionsDialog = ref(); + const areInstructionsVisible = ref(false); const canRun = computed(() => getCanRunState(currentState.value.os)); const fileName = computed(() => buildFileName(currentState.value.collection.scripting)); const hasCode = ref(false); @@ -73,7 +73,7 @@ export default defineComponent({ function saveCode() { saveCodeToDisk(fileName.value, currentState.value); - instructionsDialog.value?.show(); + areInstructionsVisible.value = true; } async function executeCode() { @@ -103,7 +103,7 @@ export default defineComponent({ hasCode, instructions, fileName, - instructionsDialog, + areInstructionsVisible, copyCode, saveCode, executeCode, diff --git a/src/presentation/components/Shared/Modal/Hooks/UseAllTrueWatcher.ts b/src/presentation/components/Shared/Modal/Hooks/UseAllTrueWatcher.ts new file mode 100644 index 00000000..75da19e1 --- /dev/null +++ b/src/presentation/components/Shared/Modal/Hooks/UseAllTrueWatcher.ts @@ -0,0 +1,37 @@ +import { Ref, computed, watch } from 'vue'; + +/** + * This function monitors a set of conditions (represented as refs) and + * maintains a composite status based on all conditions. + */ +export function useAllTrueWatcher( + ...conditions: Ref[] +) { + const allMetCallbacks = new Array<() => void>(); + + const areAllConditionsMet = computed(() => conditions.every((condition) => condition.value)); + + watch(areAllConditionsMet, (areMet) => { + if (areMet) { + allMetCallbacks.forEach((action) => action()); + } + }); + + function resetAllConditions() { + conditions.forEach((condition) => { + condition.value = false; + }); + } + + function onAllConditionsMet(callback: () => void) { + allMetCallbacks.push(callback); + if (areAllConditionsMet.value) { + callback(); + } + } + + return { + resetAllConditions, + onAllConditionsMet, + }; +} diff --git a/src/presentation/components/Shared/Modal/Hooks/UseCurrentFocusToggle.ts b/src/presentation/components/Shared/Modal/Hooks/UseCurrentFocusToggle.ts new file mode 100644 index 00000000..357f1d55 --- /dev/null +++ b/src/presentation/components/Shared/Modal/Hooks/UseCurrentFocusToggle.ts @@ -0,0 +1,23 @@ +import { Ref, watchEffect } from 'vue'; + +/** + * Manages focus transitions, ensuring good usability and accessibility. + */ +export function useCurrentFocusToggle(shouldDisableFocus: Ref) { + let previouslyFocusedElement: HTMLElement | undefined; + + watchEffect(() => { + if (shouldDisableFocus.value) { + previouslyFocusedElement = document.activeElement as HTMLElement | null; + previouslyFocusedElement?.blur(); + } else { + if (!previouslyFocusedElement || previouslyFocusedElement.tagName === 'BODY') { + // It doesn't make sense to return focus to the body after the modal is + // closed because the body itself doesn't offer meaningful interactivity + return; + } + previouslyFocusedElement.focus(); + previouslyFocusedElement = undefined; + } + }); +} diff --git a/src/presentation/components/Shared/Modal/Hooks/UseEscapeKeyListener.ts b/src/presentation/components/Shared/Modal/Hooks/UseEscapeKeyListener.ts new file mode 100644 index 00000000..8fc920ab --- /dev/null +++ b/src/presentation/components/Shared/Modal/Hooks/UseEscapeKeyListener.ts @@ -0,0 +1,17 @@ +import { onBeforeMount, onBeforeUnmount } from 'vue'; + +export function useEscapeKeyListener(callback: () => void) { + const onKeyUp = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + callback(); + } + }; + + onBeforeMount(() => { + window.addEventListener('keyup', onKeyUp); + }); + + onBeforeUnmount(() => { + window.removeEventListener('keyup', onKeyUp); + }); +} diff --git a/src/presentation/components/Shared/Modal/Hooks/UseLockBodyBackgroundScroll.ts b/src/presentation/components/Shared/Modal/Hooks/UseLockBodyBackgroundScroll.ts new file mode 100644 index 00000000..6fcd4fe9 --- /dev/null +++ b/src/presentation/components/Shared/Modal/Hooks/UseLockBodyBackgroundScroll.ts @@ -0,0 +1,37 @@ +import { Ref, watch, onBeforeUnmount } from 'vue'; + +/* + It blocks background scrolling. + Designed to be used by modals, overlays etc. +*/ +export function useLockBodyBackgroundScroll(isActive: Ref) { + const originalStyles = { + overflow: document.body.style.overflow, + width: document.body.style.width, + }; + + const block = () => { + originalStyles.overflow = document.body.style.overflow; + originalStyles.width = document.body.style.width; + + document.body.style.overflow = 'hidden'; + document.body.style.width = '100vw'; + }; + + const unblock = () => { + document.body.style.overflow = originalStyles.overflow; + document.body.style.width = originalStyles.width; + }; + + watch(isActive, (shouldBlock) => { + if (shouldBlock) { + block(); + } else { + unblock(); + } + }, { immediate: true }); + + onBeforeUnmount(() => { + unblock(); + }); +} diff --git a/src/presentation/components/Shared/Modal/ModalContainer.vue b/src/presentation/components/Shared/Modal/ModalContainer.vue new file mode 100644 index 00000000..8d231da5 --- /dev/null +++ b/src/presentation/components/Shared/Modal/ModalContainer.vue @@ -0,0 +1,150 @@ + + + + + diff --git a/src/presentation/components/Shared/Modal/ModalContent.vue b/src/presentation/components/Shared/Modal/ModalContent.vue new file mode 100644 index 00000000..6b65be99 --- /dev/null +++ b/src/presentation/components/Shared/Modal/ModalContent.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/src/presentation/components/Shared/Modal/ModalDialog.vue b/src/presentation/components/Shared/Modal/ModalDialog.vue new file mode 100644 index 00000000..2f9bf380 --- /dev/null +++ b/src/presentation/components/Shared/Modal/ModalDialog.vue @@ -0,0 +1,87 @@ + + + + + diff --git a/src/presentation/components/Shared/Modal/ModalOverlay.vue b/src/presentation/components/Shared/Modal/ModalOverlay.vue new file mode 100644 index 00000000..e37dc9cf --- /dev/null +++ b/src/presentation/components/Shared/Modal/ModalOverlay.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/src/presentation/components/Shared/ModalDialog.vue b/src/presentation/components/Shared/ModalDialog.vue deleted file mode 100644 index c208676c..00000000 --- a/src/presentation/components/Shared/ModalDialog.vue +++ /dev/null @@ -1,92 +0,0 @@ - - - - - diff --git a/src/presentation/components/TheFooter/TheFooter.vue b/src/presentation/components/TheFooter/TheFooter.vue index 6474ab0a..d0f1d4c7 100644 --- a/src/presentation/components/TheFooter/TheFooter.vue +++ b/src/presentation/components/TheFooter/TheFooter.vue @@ -33,11 +33,11 @@ - + @@ -46,7 +46,7 @@