Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
575636e6b7 | ||
|
|
daa997b21b | ||
|
|
5934b17283 | ||
|
|
d7de420d5c | ||
|
|
df273f7f63 | ||
|
|
67b2d1c11c | ||
|
|
15353d0e25 | ||
|
|
f1e21babbf | ||
|
|
34b8822ac8 | ||
|
|
73e0520de7 | ||
|
|
fbc3b109b9 |
36
.github/ISSUE_TEMPLATE/1-bug-report-scripts.md
vendored
Normal file
36
.github/ISSUE_TEMPLATE/1-bug-report-scripts.md
vendored
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
---
|
||||||
|
name: Bug report (script bug or unexpected script behavior)
|
||||||
|
about: Create a bug report for generated scripts to help privacy.sexy improve
|
||||||
|
labels: bug
|
||||||
|
title: '[BUG]: '
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Thank you for reporting an issue with generated script(s).
|
||||||
|
Please fill in as much of the template below as you're able.
|
||||||
|
As a small open source project with small community, it can sometimes take a long time for issues to be addressed so please be patient.
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Describe the bug
|
||||||
|
|
||||||
|
<!-- A clear and concise description of what the bug is. -->
|
||||||
|
|
||||||
|
### OS
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Which OS are you using? What version of OS you were using?
|
||||||
|
On Windows you can find it using "Start button" > "Settings" > "System" > "About".
|
||||||
|
On macOS you can find it using "Apple menu (top left corner)" > "About This Mac".
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Screenshots
|
||||||
|
|
||||||
|
<!-- If applicable, add screenshots to help explain your problem. -->
|
||||||
|
|
||||||
|
### Scripts
|
||||||
|
|
||||||
|
<!-- Which scripts did you execute? If applicable, please paste the executed scripts or attach the generated privacy.sexy file . -->
|
||||||
|
|
||||||
|
### Additional information
|
||||||
|
|
||||||
|
<!-- Add any other context about the problem here. -->
|
||||||
52
.github/ISSUE_TEMPLATE/2-bug-report-generic.md
vendored
Normal file
52
.github/ISSUE_TEMPLATE/2-bug-report-generic.md
vendored
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
---
|
||||||
|
name: Bug report (unrelated to generated scripts)
|
||||||
|
about: Create a bug report that's not related to generated scripts to help privacy.sexy improve
|
||||||
|
labels: bug
|
||||||
|
title: '[BUG]: '
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Thank you for reporting an issue.
|
||||||
|
Please fill in as much of the template below as you're able.
|
||||||
|
As a small open source project with small community, it can sometimes take a long time for issues to be addressed so please be patient.
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Describe the bug
|
||||||
|
|
||||||
|
<!-- A clear and concise description of what the bug is. -->
|
||||||
|
|
||||||
|
### To Reproduce
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Expected behavior
|
||||||
|
|
||||||
|
<!--
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Screenshots
|
||||||
|
|
||||||
|
<!--
|
||||||
|
If applicable, add screenshots to help explain your problem.
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Distribution
|
||||||
|
|
||||||
|
<!--
|
||||||
|
If applicable, mention how you were using privacy.sexy when the bug was encountered:
|
||||||
|
- Web (on Desktop or mobile?)
|
||||||
|
- Or desktop (Windows, macOS or Linux?)
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Additional context
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Add any other context about the problem here.
|
||||||
|
-->
|
||||||
27
.github/ISSUE_TEMPLATE/3-feature_request.md
vendored
Normal file
27
.github/ISSUE_TEMPLATE/3-feature_request.md
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for privacy.sexy
|
||||||
|
labels: enhancement
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Thank you for suggesting an idea to make privacy better. 🤗
|
||||||
|
|
||||||
|
Please fill in as much of the template below as you're able.
|
||||||
|
-->
|
||||||
|
|
||||||
|
### Problem Description
|
||||||
|
|
||||||
|
<!-- Please add a clear and concise description of the problem you are seeking to solve with this feature request. Ex. I'm always frustrated when [...] -->
|
||||||
|
|
||||||
|
### Proposed solution
|
||||||
|
|
||||||
|
<!-- Describe the solution you'd like in a clear and concise manner. -->
|
||||||
|
|
||||||
|
### Alternatives considered
|
||||||
|
|
||||||
|
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||||
|
|
||||||
|
### Additional information
|
||||||
|
|
||||||
|
<!-- Add any other context or screenshots about the feature request here. -->
|
||||||
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
blank_issues_enabled: true
|
||||||
12
CHANGELOG.md
12
CHANGELOG.md
@@ -1,5 +1,17 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 0.9.1 (2021-01-23)
|
||||||
|
|
||||||
|
* in CI/CD, allow publishing to github if release is more than 2 hours old electron-userland/electron-builder#2074 | [cf907d0](https://github.com/undergroundwires/privacy.sexy/commit/cf907d029a6d80682ba78ec887a9c4fab639db51)
|
||||||
|
* in CI/CD, publish packages for other OSes if single one fails | [4015e2c](https://github.com/undergroundwires/privacy.sexy/commit/4015e2ccd8492e0693365b70fbfe3bd0ac7a6ea2)
|
||||||
|
* specify desktop publish targets as defaults (may) change | [2316e3f](https://github.com/undergroundwires/privacy.sexy/commit/2316e3fb6867e5d765eafcf675b77f88bd2a0f52)
|
||||||
|
* fix selection state indicator on cards not showing up | [8b0e47d](https://github.com/undergroundwires/privacy.sexy/commit/8b0e47da38c49cfe2645d7d25970c448ecd200f8)
|
||||||
|
* transpile using babel for legacy browser support | [7930bef](https://github.com/undergroundwires/privacy.sexy/commit/7930bef48c4e9a4fe0823673958ed8377f5ee533)
|
||||||
|
* fix node APIs no longer working on desktop nklayman/vue-cli-plugin-electron-builder#610, nklayman/vue-cli-plugin-electron-builder#742 | [d7f9ef1](https://github.com/undergroundwires/privacy.sexy/commit/d7f9ef1cbebe911aa19f29be8c5fa9360550793e)
|
||||||
|
* improve explanation for selections | [229c13a](https://github.com/undergroundwires/privacy.sexy/commit/229c13a195dee92e4a31731b7b41c319273a16f1)
|
||||||
|
|
||||||
|
[compare](https://github.com/undergroundwires/privacy.sexy/compare/0.9.0...0.9.1)
|
||||||
|
|
||||||
## 0.9.0 (2021-01-15)
|
## 0.9.0 (2021-01-15)
|
||||||
|
|
||||||
* refactor application.yaml to become an os definition #40 | [f7557bc](https://github.com/undergroundwires/privacy.sexy/commit/f7557bcc0faf44e8395b68c7eb14c5f715f07b92)
|
* refactor application.yaml to become an os definition #40 | [f7557bc](https://github.com/undergroundwires/privacy.sexy/commit/f7557bcc0faf44e8395b68c7eb14c5f715f07b92)
|
||||||
|
|||||||
@@ -23,17 +23,6 @@
|
|||||||
- ❗ DON'T
|
- ❗ DON'T
|
||||||
- Do not update the versions, current version is only [set by the maintainer](./img/architecture/gitops.png) and updated automatically by [bump-everywhere](https://github.com/undergroundwires/bump-everywhere)
|
- Do not update the versions, current version is only [set by the maintainer](./img/architecture/gitops.png) and updated automatically by [bump-everywhere](https://github.com/undergroundwires/bump-everywhere)
|
||||||
|
|
||||||
## Guidelines
|
|
||||||
|
|
||||||
### Handle the state in presentation layer
|
|
||||||
|
|
||||||
- There are two types of components:
|
|
||||||
- **Stateless**, extends `Vue`
|
|
||||||
- **Stateful**, extends [`StatefulVue`](./src/presentation/StatefulVue.ts)
|
|
||||||
- The source of truth for the state lies in application layer ([`./src/application/`](src/application/)) and must be updated from the views if they're mutating the state
|
|
||||||
- They mutate or/and react to state changes in [ApplicationContext](src/application/Context/ApplicationContext.ts).
|
|
||||||
- You can react by getting the state and listening to it and update the view accordingly in [`mounted()`](https://vuejs.org/v2/api/#mounted) method.
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
By contributing, you agree that your contributions will be licensed under its GNU General Public License v3.0.
|
By contributing, you agree that your contributions will be licensed under its GNU General Public License v3.0.
|
||||||
|
|||||||
22
README.md
22
README.md
@@ -15,7 +15,7 @@
|
|||||||
## Get started
|
## Get started
|
||||||
|
|
||||||
- Online version: [https://privacy.sexy](https://privacy.sexy)
|
- Online version: [https://privacy.sexy](https://privacy.sexy)
|
||||||
- or download latest desktop version for [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.9.0/privacy.sexy-Setup-0.9.0.exe), [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.9.0/privacy.sexy-0.9.0.AppImage), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.9.0/privacy.sexy-0.9.0.dmg)
|
- or download latest desktop version for [Windows](https://github.com/undergroundwires/privacy.sexy/releases/download/0.9.1/privacy.sexy-Setup-0.9.1.exe), [Linux](https://github.com/undergroundwires/privacy.sexy/releases/download/0.9.1/privacy.sexy-0.9.1.AppImage), [macOS](https://github.com/undergroundwires/privacy.sexy/releases/download/0.9.1/privacy.sexy-0.9.1.dmg)
|
||||||
- 💡 Come back regularly to apply latest version for stronger privacy and security.
|
- 💡 Come back regularly to apply latest version for stronger privacy and security.
|
||||||
|
|
||||||
[](https://privacy.sexy)
|
[](https://privacy.sexy)
|
||||||
@@ -51,29 +51,17 @@
|
|||||||
- Development: `npm run serve` to compile & hot-reload for development.
|
- Development: `npm run serve` to compile & hot-reload for development.
|
||||||
- Production: `npm run build` to prepare files for distribution.
|
- Production: `npm run build` to prepare files for distribution.
|
||||||
- Or run using Docker:
|
- Or run using Docker:
|
||||||
1. Build: `docker build -t undergroundwires/privacy.sexy:0.9.0 .`
|
1. Build: `docker build -t undergroundwires/privacy.sexy:0.9.1 .`
|
||||||
2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy-0.9.0 undergroundwires/privacy.sexy:0.9.0`
|
2. Run: `docker run -it -p 8080:80 --rm --name privacy.sexy-0.9.1 undergroundwires/privacy.sexy:0.9.1`
|
||||||
|
|
||||||
## Architecture
|
## Architecture overview
|
||||||
|
|
||||||
### Application
|
### Application
|
||||||
|
|
||||||
- Powered by **TypeScript**, **Vue.js** and **Electron** 💪
|
- Powered by **TypeScript**, **Vue.js** and **Electron** 💪
|
||||||
- and driven by **Domain-driven design**, **Event-driven architecture**, **Data-driven programming** concepts.
|
- and driven by **Domain-driven design**, **Event-driven architecture**, **Data-driven programming** concepts.
|
||||||
- Application uses highly decoupled models & services in different DDD layers.
|
- Application uses highly decoupled models & services in different DDD layers.
|
||||||
- **Domain layer** is where the application is modelled with validation logic.
|
- 📖 Read more on • [Presentation](./docs/presentation.md) • [Application](./docs/application.md)
|
||||||
- **Presentation Layer**
|
|
||||||
- Consists of Vue.js components and other UI-related code.
|
|
||||||
- Desktop application is created using [Electron](https://www.electronjs.org/).
|
|
||||||
- Event driven as in components simply listens to events from the state and act accordingly.
|
|
||||||
- **Application Layer**
|
|
||||||
- Keeps the application state using [state pattern](https://en.wikipedia.org/wiki/State_pattern)
|
|
||||||
- [ApplicationContext](src/application/Context/ApplicationContext.ts)
|
|
||||||
- Holds the [CategoryCollectionState](src/application/Context/State/CategoryCollectionState.ts)] for each OS
|
|
||||||
- Same instance is shared throughout the application
|
|
||||||
- The scripts are defined and controlled in [yaml files](src/application/collections/) per OS
|
|
||||||
- Uses [data-driven programming](https://en.wikipedia.org/wiki/Data-driven_programming)
|
|
||||||
- 📖 See [extend scripts](#extend-scripts) to read about how to extend them.
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|||||||
22
docs/application.md
Normal file
22
docs/application.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Application
|
||||||
|
|
||||||
|
- It's mainly responsible for
|
||||||
|
- creating and event based [application state](#application-state)
|
||||||
|
- parsing and compiling [application data](#application-data)
|
||||||
|
|
||||||
|
## Application state
|
||||||
|
|
||||||
|
- [ApplicationContext.ts](./../src/application/Context/ApplicationContext.ts) holds the [CategoryCollectionState](./../src/application/Context/State/CategoryCollectionState.ts) for each OS
|
||||||
|
- Uses [state pattern](https://en.wikipedia.org/wiki/State_pattern)
|
||||||
|
- Same instance is shared throughout the application to ensure consistent state
|
||||||
|
- 📖 See [Application State | Presentation layer](./presentation.md#application-state) to read more about how the state should be managed by the presentation layer.
|
||||||
|
- 📖 See [ApplicationContext.ts](./../src/application/Context/ApplicationContext.ts) to start diving into the state code.
|
||||||
|
|
||||||
|
## Application data
|
||||||
|
|
||||||
|
- Compiled to `Application` domain object.
|
||||||
|
- The scripts are defined and controlled in different data files per OS
|
||||||
|
- Enables [data-driven programming](https://en.wikipedia.org/wiki/Data-driven_programming) and easier contributions
|
||||||
|
- Application data is defined in collection files and
|
||||||
|
- 📖 See [Application data | Presentation layer](./presentation.md#application-data) to read how the application data is read by the presentation layer.
|
||||||
|
- 📖 See [collection files documentation](./collection-files.md) to read more about how the data files are structured/defined and see [collection yaml files](./../src/application/collections/) to directly check the code.
|
||||||
24
docs/presentation.md
Normal file
24
docs/presentation.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
# Presentation layer
|
||||||
|
|
||||||
|
- Consists of Vue.js components and other UI-related code.
|
||||||
|
- Desktop application is created using [Electron](https://www.electronjs.org/).
|
||||||
|
- Event driven as in components simply listens to events from the state and act accordingly.
|
||||||
|
|
||||||
|
## Application data
|
||||||
|
|
||||||
|
- Components and should use [ApplicationFactory](./../src/application/ApplicationFactory.ts) singleton to reach the application domain.
|
||||||
|
- [Application.ts](../src/domain/Application.ts) domain model is the stateless application representation including
|
||||||
|
- available scripts, collections as defined in [collection files](./collection-files.md)
|
||||||
|
- package information as defined in [`package.json`](./../package.json)
|
||||||
|
- 📖 See [Application data | Application layer](./presentation.md#application-data) where application data is parsed and compiled.
|
||||||
|
|
||||||
|
## Application state
|
||||||
|
|
||||||
|
- Stateful components mutate or/and react to state changes in [ApplicationContext](./../src/application/Context/ApplicationContext.ts).
|
||||||
|
- Stateless components that does not handle state extends `Vue`
|
||||||
|
- Stateful components that depends on the collection state such as user selection, search queries and more extends [`StatefulVue`](./../src/presentation/StatefulVue.ts)
|
||||||
|
- The single source of truth is a singleton of the state created and made available to presentation layer by [`StatefulVue`](./../src/presentation/StatefulVue.ts)
|
||||||
|
- `StatefulVue` includes abstract `handleCollectionState` that is fired once the component is loaded and also each time [collection](./collection-files.md) is changed.
|
||||||
|
- Do not forget to subscribe from events when component is destroyed or if needed [collection](./collection-files.md) is changed.
|
||||||
|
- 💡 `events` in base class [`StatefulVue`](./../src/presentation/StatefulVue.ts) makes lifecycling easier
|
||||||
|
- 📖 See [Application state | Application layer](./presentation.md#application-state) where the state is implemented using using state pattern.
|
||||||
@@ -1 +1 @@
|
|||||||
<mxfile host="www.draw.io" modified="2019-12-27T03:04:27.829Z" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36" etag="O-1eaon4mqUmgvki0auB" version="12.4.3" pages="1"><diagram id="rhL8jzEM8kVVyiS98U7u" name="Page-1">3Zpdk6I4FIZ/jZdakPDlpa3tzlTNbnVVV83OzE1XhACZAUKF2Or8+k0wCBhau1f8GrpayUkC5HlzTg7IAE7T9V8M5fHfNMDJABjBegBnAwA8YIpPadhsDS50t4aIkWBrMmvDM/mNldFQ1iUJcNFqyClNOMnbRp9mGfZ5y4YYo6t2s5Am7bPmKMKa4dlHiW79lwQ8VsOyjdr+CZMors5sGqomRVVjZShiFNBVwwQfB3DKKOXbvXQ9xYlkV3HZ9pu/Ubu7MIYz/p4OD0/efJxMXmZfs6kd/vj2Txp+HlrVxfFNNWIcCACqSBmPaUQzlDzW1gdGl1mA5WENUarbfKE0F0ZTGH9izjdKTbTkVJhiniaqFq8J/ya7j2xV+t6oma3VkcvCpipknG0anWTxe7Ou7laWqn4FZ/QXntKEsnJ8cFxuomY7cjncN4kqU0GXzMcHMFYzE7EI8wPtwE534S+YplhcqejHcII4eW1fB1IzN9q1q8UVO0rfj2gNrqL1TreWarWI96AbPFG3suuEMbRpNMgpyXjROPKTNIgGKnZC5Zsqctp77n24NTDtvQmzPX09fXbjOGFGwatGj0bsMEbAfmf4uONpaF0zfKjjvqJkqc70xHAhxirOTTNtIrRlXsWE4+cclRhWIlNoSxqSJGkwDmzsBVYXfQ8soOPIHjTj1TyrrgwzjteHxdDhqQ6O23Yf5U2res2H1coeN9Z7xzgTbehc07XMlmu907PMtmeBm3AtcCHX+n8RftwO2iZsBe2j7YHrnj/IQ+9eUsSbmlCnpgwnRQ+gxepJnifE7yNUtwKvFrpDW/51ieGUmzpCw77ddiKdFMXtdhTf3Yw1wzjoCOPu2cK4fS/Oc9P3V/AeEiSoOd2Mpoj0mxotzCAIO7mbhgvH+Ayp0XjPqca6UwGrw6nsczmVpYH+nIUMCSJLny8Z7hV46PnY97uALzzbso0zAHf3clGnA7h3ySjmaMDxK1aZzRugzY+DDrHTDTpwxwvD6GeF8PbYujpbtwMtOBdaV0PLcE4Lwqk6+F3jhR0L8EXxenqoeMw44ffIdj8uXB3uWIPLY4ZRQLLoD8B79dBg6ll8Kn9e6TfsLpDvBbALLoCWZQf9wBXJSRuuYelw7ZHZgRecC6+er4ldEhy7R7pVxMA2R+Obg2xrkAuO+KEU7R18e6C15+xm9ZTjiqjOk2X1MbOc47DMrqej52Olp02o+YDDSGhE/NsDZ+pLymXBVWPoBjfaiNFfG9rtMdMXCg1SEaNc7pK0fJ+giUTCEHyTSUKiTNi4fIizs35BC5w8yYRfTlw4W1DOaSoaJLLiAfm/olKAVjokN9GkPNmkyLfvPcg0CFWFkKylZA/qemYx5/KFiYkkAeZ+kFkj4tMsJEJaNvLFGcFcrHxIfEm7iDlz4UK0GKIsGC6Y+JQmW6Ykc+i4L1+X+GfxIpuINMUb5VXG1+t9r60Jb1/yMQPQ16771x1+WPchCugC79QHnvfyjIqiR91NcGPC6yvx/QtvHhV+ka3Ep5B1Lf7FnlgYpFk+3Sp6Uhq4+zkqBCNdbQh1tStb/2rrucT9q20cVTukLMLZsMBU/rI/d8Stwjwn5fPTYkhzTlLyu0wKenR0WN0O7MTXpbc6pLc+LL0o1m/WbX8JrV9PhI//AQ==</diagram></mxfile>
|
<mxfile host="Electron" modified="2021-01-31T12:32:01.751Z" agent="5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/14.1.8 Chrome/87.0.4280.88 Electron/11.1.1 Safari/537.36" etag="OTbSPW1ZOLwiPL6mt-j9" version="14.1.8" type="device"><diagram id="rhL8jzEM8kVVyiS98U7u" name="Page-1">3VtZd6JKF/01/dhZjA6PREjCXRbGFpM2L70QCYIoLsUI/PpvnwKcMD3cazrJl6wEqfHU3vvsKoz5Infm6e3KWU5ZPPGiL5IwSb/I+hdJUsUmflNBVhQoDaEo8FfBpCgS9wWDIPfKwqrZJph466OGSRxHSbA8LnTjxcJzk6MyZ7WKt8fNnuPoeNal43u1goHrRPXSx2CSTIvSlirsy++8wJ9WM4tCWTN3qsZlwXrqTOLtQZFsfJE7qzhOilfztONFhF2FS9Hv5pXaXWArb5H8Tofr+9ZNO9J+6A+Ljvr89N2aP5tflSq4JKtW7E0AQHkbr5Jp7McLJzL2pdereLOYeDSsgLt9m24cL1EoojD0kiQr2XQ2SYyiaTKPylovDZLv1P1KLe9GBzV6Wo7Mb7LqZpGssoNOdDs6rNt343dVv3WyimdeJ47iFV+f3OZfqKkDWGK6jjcr1/sJapUQnZXvJT9pJxXtCNGDCUp6br147iFSNFh5kZMEL8eSc0rl+rt2e3LxouT3T7iW3oXrHW9HrO1J/IC8yZfmjXfVVisnO2iwjINFsj4Y+Z4K0KD0TrnMzdI51ZP0/nlrSVRPBFNMv5fPbh3/QVHyu7rHgXcIV5L6m/bxeWSofCj7KMd9caJNOdP9yltj8Zg7XtSEcEzzdhok3mDpcFy2OCkcU/ocRNEBxhPVa02Uc+i3pLHcaFCPeJFUOjuzFVbBeqvES38KXlnbaB6nT5lN2/2eL1c7+/Rgv69OMxdHW268Z2qJR6n1m5klHmeW9B6pJb1Xav07h28fm7YoH5n2L9tLzebbm7zc+ixHxPcU1MWPDP/JPaSaV2vLZRS4l7DqI+OtWfezSt/nyGjwr3KEg/Li62ck/b6Lq8cuvnsYO7Rx6YyNN9/MxtXPkjwf6flK/pQHJLmWdHo8d4LLHo3G4mTyfBZ3UWjKbe8Njkbtk6Rq15NKUs4klfpWSaXUgDYXzysHiGzcZLPyLgr4c8v1XPcc4OOWqqjCGwDePDmLNs4A3vqbLtaoAe69eOXJ5hWgxT8H+tlrnAd60myPBeEyO0TrBNtmHdvmGWilt4K2WYN25S3jdZDE5eCfGl75zAb8V+Ft1a3CWCRB8hmxPfWFdwe3XQM3ma48ZxIs/P8DeN/dGsT6KX5Of165rO2OBU/2GufAFbyW0GpdBlwcTo7BFZQ6uOqVeAZe6a3grZ/X8DKY/OoZ6aNCLKniVfvDgazWQF4nTvKzI9qf42vo9H0ZFE9MQKze/XhHCP/K6euSGJ6+mXoORPHcu6lvh2H9mOUcviEiRLEfuJ8HULG+Nf1dQHdr2AOazamXG0eR5xKqH1efHw/N+lZUA289dZb0MpjzTywcQkUrh5QjLQr8BcoSeptoV9p1xl50T48UJHVZH8dJEs/RIKKKa8ed+ZyYowMXfaEJn0xbL4tPVtBBy6lunoOUqLwu49GnSUIfydAICenGnSyUq8CNF88BKF9duZhRusHe6uBC5VDHDZIuXn91FpOv4xV+U5FKh54budH88bDxwvUPaoKDUOtqWZ0pL/pk3aa/UpxyryhXap38fenl6a9vkp+ffvmP6f/qTOKxtxOB1Gr9GDjr9QXpl9TWVVP9PQlI5zUgvZkI6tv85xeB+EsRjBdb/AbFKX7wCtsyFdNbausLsS7LyhHlcuOqVT+QSO0zht9+K7Lr55HPT7bwS7Kf45XvLb6uvZg+TXDTAC83y4C/Z7v+Gi+TYB7k/ER2wZzfvUtSsS/XqK/0cUh9VfZfqH/Y3Hrj5vegr7fv2awhLwLh8cyf5n7J/CWIPS+Z8+Qe8V/RzamUteJWulm/gJzrlM58nfs7S3rKrpXxY7pxcyFw7r4Jrh6/dOWJPMlUmWXqizt3X1iobVmnnU/mbmDeTZZPd9/i+4GZWfYoMG+nkfM4iSe6ELBwKJnBteQ8Psj9eVtBm62paz4v103RCkzMfX/r+k/zaD1Gj/G8vXkamMV9R8wmj2l0P/gnmswfNmPp28wMldZIirKRlEbm7dNyfLttmwHL+uE/N0wwMLtFs6Qs9Om1unt9Z7bN0Mh6HfPlPkwXB33Vb7OH62/hLCjLEnfxsH6yi1i8+UM2zniUd9fTya3vPyFK2zawfkVkuulbubGxwijs2qbUBS7WQBC6oZtbgSBYgSJaobthoZl27SGVSyzQUqujCMweJqhPcfWZrW0se5Z17f6G5SMZ2KDtKLcGGq6mYOpMxb1gzY0MV8nKtOoKPA2fdajdKMOcVC4yiSVWpsiWbSbMZhjbxNgzxDFCHKZIYzEepylYmZAjnhx1PuJRmO6jD9aUz1LiqBsaQq8jpKjLMJZv6RjH9uWubaTd0M+tx3MxaRTTpmcPJawb8/Zlby4kWBvKZqqpu2jLtpbEUo4jxwBz2kOxWLshW4GWsUDJe3bfZ/kM8ZBmGOZkSq+j5bQ+FjJoiWL1wYO76el+jv4y4tiygaaijYR+GBu42po0sk1wxfEtr4hzoG27wIjpmkj4WzraZVgvjU9zow/TXQXrABY+8Qeeh4SJivi2TGcJHz830MYgvMCvoIBrzkcP2Fq8DeFPPDDU9WX0zxnVvcqpkHfDmQAN4DpUvYArdzv6/i02b/ttcyakPYo3NGl9W8xHvFPcWyg+QY5izQblGcadIXcFgc+n+xwP8CsWmhymLBBIu+grEB8CcEW8Ptqwck0zWgv0CDz1IepGGLt/Hs8O8BwoWyuc+Wifs9wKGdeARtio4C4j3Vvh0IfuVHALfg1gPEOsBtbaV0x9RPFluE8pZ4ABrn3oxkQe+FuKE30zWgt+gP1MfY1bU+8Tf9Ay8kDvS6RDi17nlAsjhWvtkdY7pHFIY4h1poInmluxOhrmdIUe8gXYiozj7UNrTCy4nAms0KqAssQKSQt96Jj0MiIfkICpwmwXGoWOwdPrsRJOrgz8ZCt/CLkubZYjZ0n3AmLJMJZq6W5i0Rj5CJgMEYNJmMiESY+0w/EeIUcpTkMs8sYAf8CNuAk06Arj5CbnGXy/ih/4Jx3JRR4b4hkdKtxrMi3t6S7laT7KMa+tCUUu+6RB4lXqwc8QP7h3eU739D7mIe0apPuUUUz6kDREeKo9e5T0qL3OuEYseFGXvIzy5/bVvCFNy/C3jPN2W3ohjYs1kkd0aT2hKUKLaW+gKD3ySfJpvQ//MEhjyG3kAvyD8XgE0qxI8XA/DYeUx7S2DJiCY7STXo0HOsB6uIZd1ZKXCTiBRkzu9z19BLwoj42kyEsT+qdYfLHUYIo4Ea+m0N6CeAXue1inRdjkM65dC+OAb8xtSow8PSM9ukmP+1c/ezVXqd4eCZQnaC+McoPvNSzTuIfhWuwPpF/eZkQ5grWT12jEPfj0aayUv87dpPBt4piwYrTPUR5R/iiv40T+oEHPtA/McuSpCK1k2DtoPInZ/4RYM+FAuSgSLvBZeBe45X7F84G4zfg+m1VtuYbLMu20n1/1IzzIO7G/kW/tvL+o2/Xf9ava0rqrGE777eYF5zx/4Iamfj0lbjAW4uvnZZ7Bi4wyV3gcx/WYAzGU9dcN2+7THqLynON7Vz8d5aSZ4UE8/dN4hAMcgAnXu0px4QwjHuC2679fB28LXWrJHhPe73DMPaaFXgn7Au+BdgbvPTZFjgAdySjXDB/RWbVu0jr5vQLfOq2vtFPUL+Kgmyst9/ZGcDrXM5wcLcxH5wHSqkjnLkv/Fv4Rd3WshDpW7BQr9V9j1flbWGEs7rczn2so9zOce6CxmVLstxo8XeBeWJwrNRWemvL9Jx8mHMP8aQo/ynCu86trNU9RT35J50BXIo/r2eRptNe78B/yf9pvyZ/orGls6ZxSxK3RtYxBK2OqYtCKmIr85vtOEdNTwwxadFLvtBf34fblCUroyniiyRV6hrrMnz8l+Uo9fuNJlurPoY0zz6GNP34Oxe3+v8qKTwHv/zVPNv4H</diagram></mxfile>
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 26 KiB After Width: | Height: | Size: 29 KiB |
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "privacy.sexy",
|
"name": "privacy.sexy",
|
||||||
"version": "0.9.0",
|
"version": "0.9.1",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "privacy.sexy",
|
"name": "privacy.sexy",
|
||||||
"version": "0.9.0",
|
"version": "0.9.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Enforce privacy & security best-practices on Windows and macOS, because privacy is sexy 🍑🍆",
|
"description": "Enforce privacy & security best-practices on Windows and macOS, because privacy is sexy 🍑🍆",
|
||||||
"author": "undergroundwires",
|
"author": "undergroundwires",
|
||||||
|
|||||||
21
src/application/ApplicationFactory.ts
Normal file
21
src/application/ApplicationFactory.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { IApplication } from '@/domain/IApplication';
|
||||||
|
import { AsyncLazy } from '@/infrastructure/Threading/AsyncLazy';
|
||||||
|
import { IApplicationFactory } from './IApplicationFactory';
|
||||||
|
import { parseApplication } from './Parser/ApplicationParser';
|
||||||
|
|
||||||
|
export type ApplicationGetter = () => IApplication;
|
||||||
|
const ApplicationGetter: ApplicationGetter = parseApplication;
|
||||||
|
|
||||||
|
export class ApplicationFactory implements IApplicationFactory {
|
||||||
|
public static readonly Current: IApplicationFactory = new ApplicationFactory(ApplicationGetter);
|
||||||
|
private readonly getter: AsyncLazy<IApplication>;
|
||||||
|
protected constructor(costlyGetter: ApplicationGetter) {
|
||||||
|
if (!costlyGetter) {
|
||||||
|
throw new Error('undefined getter');
|
||||||
|
}
|
||||||
|
this.getter = new AsyncLazy<IApplication>(() => Promise.resolve(costlyGetter()));
|
||||||
|
}
|
||||||
|
public getAppAsync(): Promise<IApplication> {
|
||||||
|
return this.getter.getValueAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,12 +4,12 @@ import { CategoryCollectionState } from './State/CategoryCollectionState';
|
|||||||
import { IApplication } from '@/domain/IApplication';
|
import { IApplication } from '@/domain/IApplication';
|
||||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
import { ICategoryCollection } from '@/domain/ICategoryCollection';
|
import { ICategoryCollection } from '@/domain/ICategoryCollection';
|
||||||
import { Signal } from '@/infrastructure/Events/Signal';
|
import { EventSource } from '@/infrastructure/Events/EventSource';
|
||||||
|
|
||||||
type StateMachine = Map<OperatingSystem, ICategoryCollectionState>;
|
type StateMachine = Map<OperatingSystem, ICategoryCollectionState>;
|
||||||
|
|
||||||
export class ApplicationContext implements IApplicationContext {
|
export class ApplicationContext implements IApplicationContext {
|
||||||
public readonly contextChanged = new Signal<IApplicationContextChangedEvent>();
|
public readonly contextChanged = new EventSource<IApplicationContextChangedEvent>();
|
||||||
public collection: ICategoryCollection;
|
public collection: ICategoryCollection;
|
||||||
public currentOs: OperatingSystem;
|
public currentOs: OperatingSystem;
|
||||||
|
|
||||||
|
|||||||
@@ -4,15 +4,15 @@ import { OperatingSystem } from '@/domain/OperatingSystem';
|
|||||||
import { Environment } from '../Environment/Environment';
|
import { Environment } from '../Environment/Environment';
|
||||||
import { IApplication } from '@/domain/IApplication';
|
import { IApplication } from '@/domain/IApplication';
|
||||||
import { IEnvironment } from '../Environment/IEnvironment';
|
import { IEnvironment } from '../Environment/IEnvironment';
|
||||||
import { parseApplication } from '../Parser/ApplicationParser';
|
import { IApplicationFactory } from '../IApplicationFactory';
|
||||||
|
import { ApplicationFactory } from '../ApplicationFactory';
|
||||||
|
|
||||||
export type ApplicationParserType = () => IApplication;
|
export async function buildContextAsync(
|
||||||
const ApplicationParser: ApplicationParserType = parseApplication;
|
factory: IApplicationFactory = ApplicationFactory.Current,
|
||||||
|
environment = Environment.CurrentEnvironment): Promise<IApplicationContext> {
|
||||||
export function buildContext(
|
if (!factory) { throw new Error('undefined factory'); }
|
||||||
parser = ApplicationParser,
|
if (!environment) { throw new Error('undefined environment'); }
|
||||||
environment = Environment.CurrentEnvironment): IApplicationContext {
|
const app = await factory.getAppAsync();
|
||||||
const app = parser();
|
|
||||||
const os = getInitialOs(app, environment);
|
const os = getInitialOs(app, environment);
|
||||||
return new ApplicationContext(app, os);
|
return new ApplicationContext(app, os);
|
||||||
}
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import { ICategoryCollectionState } from './State/ICategoryCollectionState';
|
import { ICategoryCollectionState } from './State/ICategoryCollectionState';
|
||||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
import { ISignal } from '@/infrastructure/Events/ISignal';
|
import { IEventSource } from '@/infrastructure/Events/IEventSource';
|
||||||
import { IApplication } from '@/domain/IApplication';
|
import { IApplication } from '@/domain/IApplication';
|
||||||
|
|
||||||
export interface IApplicationContext {
|
export interface IApplicationContext {
|
||||||
readonly app: IApplication;
|
readonly app: IApplication;
|
||||||
readonly state: ICategoryCollectionState;
|
readonly state: ICategoryCollectionState;
|
||||||
readonly contextChanged: ISignal<IApplicationContextChangedEvent>;
|
readonly contextChanged: IEventSource<IApplicationContextChangedEvent>;
|
||||||
changeContext(os: OperatingSystem): void;
|
changeContext(os: OperatingSystem): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ import { ICodeChangedEvent } from './Event/ICodeChangedEvent';
|
|||||||
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
|
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
|
||||||
import { IUserSelection } from '@/application/Context/State/Selection/IUserSelection';
|
import { IUserSelection } from '@/application/Context/State/Selection/IUserSelection';
|
||||||
import { UserScriptGenerator } from './Generation/UserScriptGenerator';
|
import { UserScriptGenerator } from './Generation/UserScriptGenerator';
|
||||||
import { Signal } from '@/infrastructure/Events/Signal';
|
import { EventSource } from '@/infrastructure/Events/EventSource';
|
||||||
import { IApplicationCode } from './IApplicationCode';
|
import { IApplicationCode } from './IApplicationCode';
|
||||||
import { IUserScriptGenerator } from './Generation/IUserScriptGenerator';
|
import { IUserScriptGenerator } from './Generation/IUserScriptGenerator';
|
||||||
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
|
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
|
||||||
|
|
||||||
export class ApplicationCode implements IApplicationCode {
|
export class ApplicationCode implements IApplicationCode {
|
||||||
public readonly changed = new Signal<ICodeChangedEvent>();
|
public readonly changed = new EventSource<ICodeChangedEvent>();
|
||||||
public current: string;
|
public current: string;
|
||||||
|
|
||||||
private scriptPositions = new Map<SelectedScript, CodePosition>();
|
private scriptPositions = new Map<SelectedScript, CodePosition>();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ICodeChangedEvent } from './Event/ICodeChangedEvent';
|
import { ICodeChangedEvent } from './Event/ICodeChangedEvent';
|
||||||
import { ISignal } from '@/infrastructure/Events/ISignal';
|
import { IEventSource } from '@/infrastructure/Events/IEventSource';
|
||||||
|
|
||||||
export interface IApplicationCode {
|
export interface IApplicationCode {
|
||||||
readonly changed: ISignal<ICodeChangedEvent>;
|
readonly changed: IEventSource<ICodeChangedEvent>;
|
||||||
readonly current: string;
|
readonly current: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { ISignal } from '@/infrastructure/Events/ISignal';
|
import { IEventSource } from '@/infrastructure/Events/IEventSource';
|
||||||
import { IFilterResult } from './IFilterResult';
|
import { IFilterResult } from './IFilterResult';
|
||||||
|
|
||||||
export interface IUserFilter {
|
export interface IUserFilter {
|
||||||
readonly currentFilter: IFilterResult | undefined;
|
readonly currentFilter: IFilterResult | undefined;
|
||||||
readonly filtered: ISignal<IFilterResult>;
|
readonly filtered: IEventSource<IFilterResult>;
|
||||||
readonly filterRemoved: ISignal<void>;
|
readonly filterRemoved: IEventSource<void>;
|
||||||
setFilter(filter: string): void;
|
setFilter(filter: string): void;
|
||||||
removeFilter(): void;
|
removeFilter(): void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ import { IScript } from '@/domain/IScript';
|
|||||||
import { FilterResult } from './FilterResult';
|
import { FilterResult } from './FilterResult';
|
||||||
import { IFilterResult } from './IFilterResult';
|
import { IFilterResult } from './IFilterResult';
|
||||||
import { IUserFilter } from './IUserFilter';
|
import { IUserFilter } from './IUserFilter';
|
||||||
import { Signal } from '@/infrastructure/Events/Signal';
|
import { EventSource } from '@/infrastructure/Events/EventSource';
|
||||||
import { ICategoryCollection } from '@/domain/ICategoryCollection';
|
import { ICategoryCollection } from '@/domain/ICategoryCollection';
|
||||||
|
|
||||||
export class UserFilter implements IUserFilter {
|
export class UserFilter implements IUserFilter {
|
||||||
public readonly filtered = new Signal<IFilterResult>();
|
public readonly filtered = new EventSource<IFilterResult>();
|
||||||
public readonly filterRemoved = new Signal<void>();
|
public readonly filterRemoved = new EventSource<void>();
|
||||||
public currentFilter: IFilterResult | undefined;
|
public currentFilter: IFilterResult | undefined;
|
||||||
|
|
||||||
constructor(private collection: ICategoryCollection) {
|
constructor(private collection: ICategoryCollection) {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { SelectedScript } from './SelectedScript';
|
import { SelectedScript } from './SelectedScript';
|
||||||
import { IScript } from '@/domain/IScript';
|
import { IScript } from '@/domain/IScript';
|
||||||
import { ICategory } from '@/domain/ICategory';
|
import { ICategory } from '@/domain/ICategory';
|
||||||
import { ISignal } from '@/infrastructure/Events/ISignal';
|
import { IEventSource } from '@/infrastructure/Events/IEventSource';
|
||||||
|
|
||||||
export interface IUserSelection {
|
export interface IUserSelection {
|
||||||
readonly changed: ISignal<ReadonlyArray<SelectedScript>>;
|
readonly changed: IEventSource<ReadonlyArray<SelectedScript>>;
|
||||||
readonly selectedScripts: ReadonlyArray<SelectedScript>;
|
readonly selectedScripts: ReadonlyArray<SelectedScript>;
|
||||||
readonly totalSelected: number;
|
readonly totalSelected: number;
|
||||||
areAllSelected(category: ICategory): boolean;
|
areAllSelected(category: ICategory): boolean;
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import { SelectedScript } from './SelectedScript';
|
|||||||
import { IUserSelection } from './IUserSelection';
|
import { IUserSelection } from './IUserSelection';
|
||||||
import { InMemoryRepository } from '@/infrastructure/Repository/InMemoryRepository';
|
import { InMemoryRepository } from '@/infrastructure/Repository/InMemoryRepository';
|
||||||
import { IScript } from '@/domain/IScript';
|
import { IScript } from '@/domain/IScript';
|
||||||
import { Signal } from '@/infrastructure/Events/Signal';
|
import { EventSource } from '@/infrastructure/Events/EventSource';
|
||||||
import { IRepository } from '@/infrastructure/Repository/IRepository';
|
import { IRepository } from '@/infrastructure/Repository/IRepository';
|
||||||
import { ICategory } from '@/domain/ICategory';
|
import { ICategory } from '@/domain/ICategory';
|
||||||
import { ICategoryCollection } from '@/domain/ICategoryCollection';
|
import { ICategoryCollection } from '@/domain/ICategoryCollection';
|
||||||
|
|
||||||
export class UserSelection implements IUserSelection {
|
export class UserSelection implements IUserSelection {
|
||||||
public readonly changed = new Signal<ReadonlyArray<SelectedScript>>();
|
public readonly changed = new EventSource<ReadonlyArray<SelectedScript>>();
|
||||||
private readonly scripts: IRepository<string, SelectedScript>;
|
private readonly scripts: IRepository<string, SelectedScript>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
|||||||
5
src/application/IApplicationFactory.ts
Normal file
5
src/application/IApplicationFactory.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { IApplication } from '@/domain/IApplication';
|
||||||
|
|
||||||
|
export interface IApplicationFactory {
|
||||||
|
getAppAsync(): Promise<IApplication>;
|
||||||
|
}
|
||||||
@@ -30,6 +30,7 @@ export class ScriptCompiler implements IScriptCompiler {
|
|||||||
calls.forEach((currentCall, currentCallIndex) => {
|
calls.forEach((currentCall, currentCallIndex) => {
|
||||||
ensureValidCall(currentCall, script.name);
|
ensureValidCall(currentCall, script.name);
|
||||||
const commonFunction = this.getFunctionByName(currentCall.function);
|
const commonFunction = this.getFunctionByName(currentCall.function);
|
||||||
|
ensureExpectedParameters(commonFunction, currentCall);
|
||||||
let functionCode = compileCode(commonFunction, currentCall.parameters);
|
let functionCode = compileCode(commonFunction, currentCall.parameters);
|
||||||
if (currentCallIndex !== calls.length - 1) {
|
if (currentCallIndex !== calls.length - 1) {
|
||||||
functionCode = appendLine(functionCode);
|
functionCode = appendLine(functionCode);
|
||||||
@@ -57,6 +58,18 @@ export class ScriptCompiler implements IScriptCompiler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ensureExpectedParameters(func: FunctionData, call: FunctionCallData) {
|
||||||
|
if (!func.parameters && !call.parameters) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const unexpectedParameters = Object.keys(call.parameters || {})
|
||||||
|
.filter((callParam) => !func.parameters.includes(callParam));
|
||||||
|
if (unexpectedParameters.length) {
|
||||||
|
throw new Error(
|
||||||
|
`function "${func.name}" has unexpected parameter(s) provided: "${unexpectedParameters.join('", "')}"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getDuplicates(texts: readonly string[]): string[] {
|
function getDuplicates(texts: readonly string[]): string[] {
|
||||||
return texts.filter((item, index) => texts.indexOf(item) !== index);
|
return texts.filter((item, index) => texts.indexOf(item) !== index);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1467,7 +1467,7 @@ actions:
|
|||||||
name: Delete NVIDIA residual telemetry files
|
name: Delete NVIDIA residual telemetry files
|
||||||
recommend: standard
|
recommend: standard
|
||||||
code: |-
|
code: |-
|
||||||
del /s %systemdrive%\System32\DriverStore\FileRepository\NvTelemetry*.dll
|
del /s %SystemRoot%\System32\DriverStore\FileRepository\NvTelemetry*.dll
|
||||||
rmdir /s /q "%ProgramFiles(x86)%\NVIDIA Corporation\NvTelemetry" 2>nul
|
rmdir /s /q "%ProgramFiles(x86)%\NVIDIA Corporation\NvTelemetry" 2>nul
|
||||||
rmdir /s /q "%ProgramFiles%\NVIDIA Corporation\NvTelemetry" 2>nul
|
rmdir /s /q "%ProgramFiles%\NVIDIA Corporation\NvTelemetry" 2>nul
|
||||||
-
|
-
|
||||||
@@ -1508,45 +1508,72 @@ actions:
|
|||||||
name: Disable Visual Studio Code telemetry
|
name: Disable Visual Studio Code telemetry
|
||||||
docs: https://code.visualstudio.com/docs/getstarted/telemetry
|
docs: https://code.visualstudio.com/docs/getstarted/telemetry
|
||||||
recommend: standard
|
recommend: standard
|
||||||
code: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | Out-String | ConvertFrom-Json; $json | Add-Member -Type NoteProperty -Name 'telemetry.enableTelemetry' -Value $false -Force; $json | ConvertTo-Json | Set-Content $jsonfile;"
|
call:
|
||||||
revertCode: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | ConvertFrom-Json; $json.PSObject.Properties.Remove('telemetry.enableTelemetry'); $json | ConvertTo-Json | Set-Content $jsonfile;"
|
function: SetVsCodeSetting
|
||||||
|
parameters:
|
||||||
|
setting: telemetry.enableTelemetry
|
||||||
|
powerShellValue: $false
|
||||||
-
|
-
|
||||||
name: Disable Visual Studio Code crash reporting
|
name: Disable Visual Studio Code crash reporting
|
||||||
docs: https://code.visualstudio.com/docs/getstarted/telemetry
|
docs: https://code.visualstudio.com/docs/getstarted/telemetry
|
||||||
recommend: standard
|
recommend: standard
|
||||||
code: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | Out-String | ConvertFrom-Json; $json | Add-Member -Type NoteProperty -Name 'telemetry.enableCrashReporter' -Value $false -Force; $json | ConvertTo-Json | Set-Content $jsonfile;"
|
call:
|
||||||
revertCode: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | ConvertFrom-Json;$json.PSObject.Properties.Remove('telemetry.enableCrashReporter'); $json | ConvertTo-Json | Set-Content $jsonfile;"
|
function: SetVsCodeSetting
|
||||||
|
parameters:
|
||||||
|
setting: telemetry.enableCrashReporter
|
||||||
|
powerShellValue: $false
|
||||||
-
|
-
|
||||||
name: Do not run Microsoft online experiments
|
name: Do not run Microsoft online experiments
|
||||||
docs: https://github.com/Microsoft/vscode/blob/1aee0c194cff72d179b9f8ef324e47f34555a07d/src/vs/workbench/contrib/experiments/node/experimentService.ts#L173
|
docs: https://github.com/Microsoft/vscode/blob/1aee0c194cff72d179b9f8ef324e47f34555a07d/src/vs/workbench/contrib/experiments/node/experimentService.ts#L173
|
||||||
recommend: standard
|
recommend: standard
|
||||||
code: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | Out-String | ConvertFrom-Json; $json | Add-Member -Type NoteProperty -Name 'workbench.enableExperiments' -Value $false -Force; $json | ConvertTo-Json | Set-Content $jsonfile;"
|
call:
|
||||||
revertCode: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | ConvertFrom-Json;$json.PSObject.Properties.Remove('workbench.enableExperiments'); $json | ConvertTo-Json | Set-Content $jsonfile;"
|
function: SetVsCodeSetting
|
||||||
|
parameters:
|
||||||
|
setting: workbench.enableExperiments
|
||||||
|
powerShellValue: $false
|
||||||
-
|
-
|
||||||
name: Choose manual updates over automatic updates
|
name: Choose manual updates over automatic updates
|
||||||
docs: https://github.com/Microsoft/vscode/blob/1aee0c194cff72d179b9f8ef324e47f34555a07d/src/vs/workbench/contrib/experiments/node/experimentService.ts#L173
|
call:
|
||||||
code: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | Out-String | ConvertFrom-Json; $json | Add-Member -Type NoteProperty -Name 'update.mode' -Value \"manual\" -Force; $json | ConvertTo-Json | Set-Content $jsonfile;"
|
function: SetVsCodeSetting
|
||||||
revertCode: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | ConvertFrom-Json;$json.PSObject.Properties.Remove('update.mode'); $json | ConvertTo-Json | Set-Content $jsonfile;"
|
parameters:
|
||||||
|
setting: update.mode
|
||||||
|
powerShellValue: >-
|
||||||
|
'manual'
|
||||||
-
|
-
|
||||||
name: Show Release Notes from Microsoft online service after an update
|
name: Show Release Notes from Microsoft online service after an update
|
||||||
code: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | Out-String | ConvertFrom-Json; $json | Add-Member -Type NoteProperty -Name 'update.showReleaseNotes' -Value $false -Force; $json | ConvertTo-Json | Set-Content $jsonfile;"
|
call:
|
||||||
revertCode: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | ConvertFrom-Json;$json.PSObject.Properties.Remove('update.showReleaseNotes'); $json | ConvertTo-Json | Set-Content $jsonfile;"
|
function: SetVsCodeSetting
|
||||||
|
parameters:
|
||||||
|
setting: update.showReleaseNotes
|
||||||
|
powerShellValue: $false
|
||||||
-
|
-
|
||||||
name: Automatically check extensions from Microsoft online service
|
name: Automatically check extensions from Microsoft online service
|
||||||
code: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | Out-String | ConvertFrom-Json; $json | Add-Member -Type NoteProperty -Name 'extensions.autoCheckUpdates' -Value $false -Force; $json | ConvertTo-Json | Set-Content $jsonfile;"
|
call:
|
||||||
revertCode: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | ConvertFrom-Json;$json.PSObject.Properties.Remove('extensions.autoCheckUpdates'); $json | ConvertTo-Json | Set-Content $jsonfile;"
|
function: SetVsCodeSetting
|
||||||
|
parameters:
|
||||||
|
setting: extensions.autoCheckUpdates
|
||||||
|
powerShellValue: $false
|
||||||
-
|
-
|
||||||
name: Fetch recommendations from a Microsoft online service
|
name: Fetch recommendations from Microsoft only on demand
|
||||||
code: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | Out-String | ConvertFrom-Json; $json | Add-Member -Type NoteProperty -Name 'extensions.showRecommendationsOnlyOnDemand' -Value $true -Force; $json | ConvertTo-Json | Set-Content $jsonfile;"
|
call:
|
||||||
revertCode: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | ConvertFrom-Json;$json.PSObject.Properties.Remove('extensions.showRecommendationsOnlyOnDemand'); $json | ConvertTo-Json | Set-Content $jsonfile;"
|
function: SetVsCodeSetting
|
||||||
|
parameters:
|
||||||
|
setting: extensions.showRecommendationsOnlyOnDemand
|
||||||
|
powerShellValue: $true
|
||||||
-
|
-
|
||||||
name: Automatically fetch git commits from remote repository
|
name: Automatically fetch git commits from remote repository
|
||||||
code: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | Out-String | ConvertFrom-Json; $json | Add-Member -Type NoteProperty -Name 'git.autofetch' -Value $false -Force; $json | ConvertTo-Json | Set-Content $jsonfile;"
|
call:
|
||||||
revertCode: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | ConvertFrom-Json;$json.PSObject.Properties.Remove('git.autofetch'); $json | ConvertTo-Json | Set-Content $jsonfile;"
|
function: SetVsCodeSetting
|
||||||
|
parameters:
|
||||||
|
setting: git.autofetch
|
||||||
|
powerShellValue: $false
|
||||||
-
|
-
|
||||||
name: Fetch package information from NPM and Bower
|
name: Fetch package information from NPM and Bower
|
||||||
code: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | Out-String | ConvertFrom-Json; $json | Add-Member -Type NoteProperty -Name 'npm.fetchOnlinePackageInfo' -Value $false -Force; $json | ConvertTo-Json | Set-Content $jsonfile;"
|
call:
|
||||||
revertCode: Powershell -Command "$jsonfile = \"$env:APPDATA\Code\User\settings.json\"; $json = Get-Content $jsonfile | ConvertFrom-Json;$json.PSObject.Properties.Remove('npm.fetchOnlinePackageInfo'); $json | ConvertTo-Json | Set-Content $jsonfile;"
|
function: SetVsCodeSetting
|
||||||
|
parameters:
|
||||||
|
setting: npm.fetchOnlinePackageInfo
|
||||||
|
powerShellValue: $false
|
||||||
-
|
-
|
||||||
category: Disable Microsoft Office telemetry
|
category: Disable Microsoft Office telemetry
|
||||||
docs: https://docs.microsoft.com/en-us/deployoffice/compat/manage-the-privacy-of-data-monitored-by-telemetry-in-office
|
docs: https://docs.microsoft.com/en-us/deployoffice/compat/manage-the-privacy-of-data-monitored-by-telemetry-in-office
|
||||||
@@ -4220,3 +4247,18 @@ functions:
|
|||||||
) else (
|
) else (
|
||||||
echo Could not find backup file "{{ $filePath }}.OLD" 1>&2
|
echo Could not find backup file "{{ $filePath }}.OLD" 1>&2
|
||||||
)
|
)
|
||||||
|
-
|
||||||
|
name: SetVsCodeSetting
|
||||||
|
parameters: [ setting, powerShellValue ]
|
||||||
|
code:
|
||||||
|
Powershell -Command "
|
||||||
|
$jsonfile = \"$env:APPDATA\Code\User\settings.json\";
|
||||||
|
$json = Get-Content $jsonfile | Out-String | ConvertFrom-Json;
|
||||||
|
$json | Add-Member -Type NoteProperty -Name '{{ $setting }}' -Value {{ $powerShellValue }} -Force;
|
||||||
|
$json | ConvertTo-Json | Set-Content $jsonfile;"
|
||||||
|
revertCode:
|
||||||
|
Powershell -Command "
|
||||||
|
$jsonfile = \"$env:APPDATA\Code\User\settings.json\";
|
||||||
|
$json = Get-Content $jsonfile | ConvertFrom-Json;
|
||||||
|
$json.PSObject.Properties.Remove('{{ $setting }}');
|
||||||
|
$json | ConvertTo-Json | Set-Content $jsonfile;"
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import { EventHandler, ISignal } from './ISignal';
|
import { EventHandler, IEventSource, IEventSubscription } from './IEventSource';
|
||||||
import { IEventSubscription } from './ISubscription';
|
|
||||||
|
|
||||||
export class Signal<T> implements ISignal<T> {
|
export class EventSource<T> implements IEventSource<T> {
|
||||||
private handlers = new Map<number, EventHandler<T>>();
|
private handlers = new Map<number, EventHandler<T>>();
|
||||||
|
|
||||||
public on(handler: EventHandler<T>): IEventSubscription {
|
public on(handler: EventHandler<T>): IEventSubscription {
|
||||||
12
src/infrastructure/Events/EventSubscriptionCollection.ts
Normal file
12
src/infrastructure/Events/EventSubscriptionCollection.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { IEventSubscription } from './IEventSource';
|
||||||
|
|
||||||
|
export class EventSubscriptionCollection {
|
||||||
|
private readonly subscriptions = new Array<IEventSubscription>();
|
||||||
|
public register(...subscriptions: IEventSubscription[]) {
|
||||||
|
this.subscriptions.push(...subscriptions);
|
||||||
|
}
|
||||||
|
public unsubscribeAll() {
|
||||||
|
this.subscriptions.forEach((listener) => listener.unsubscribe());
|
||||||
|
this.subscriptions.splice(0, this.subscriptions.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
import { IEventSubscription } from './ISubscription';
|
export interface IEventSource<T> {
|
||||||
export interface ISignal<T> {
|
|
||||||
on(handler: EventHandler<T>): IEventSubscription;
|
on(handler: EventHandler<T>): IEventSubscription;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IEventSubscription {
|
||||||
|
unsubscribe(): void;
|
||||||
|
}
|
||||||
|
|
||||||
export type EventHandler<T> = (data: T) => void;
|
export type EventHandler<T> = (data: T) => void;
|
||||||
|
|
||||||
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export interface IEventSubscription {
|
|
||||||
unsubscribe(): void;
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Signal } from '../Events/Signal';
|
import { EventSource } from '../Events/EventSource';
|
||||||
|
|
||||||
export class AsyncLazy<T> {
|
export class AsyncLazy<T> {
|
||||||
private valueCreated = new Signal();
|
private valueCreated = new EventSource();
|
||||||
private isValueCreated = false;
|
private isValueCreated = false;
|
||||||
private isCreatingValue = false;
|
private isCreatingValue = false;
|
||||||
private value: T | undefined;
|
private value: T | undefined;
|
||||||
@@ -15,7 +15,7 @@ export class AsyncLazy<T> {
|
|||||||
public async getValueAsync(): Promise<T> {
|
public async getValueAsync(): Promise<T> {
|
||||||
// If value is already created, return the value directly
|
// If value is already created, return the value directly
|
||||||
if (this.isValueCreated) {
|
if (this.isValueCreated) {
|
||||||
return Promise.resolve(this.value as T);
|
return Promise.resolve(this.value);
|
||||||
}
|
}
|
||||||
// If value is being created, wait until the value is created and then return it.
|
// If value is being created, wait until the value is created and then return it.
|
||||||
if (this.isCreatingValue) {
|
if (this.isCreatingValue) {
|
||||||
|
|||||||
@@ -80,29 +80,26 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Prop } from 'vue-property-decorator';
|
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||||
import { StatefulVue } from '@/presentation/StatefulVue';
|
|
||||||
import Code from './Code.vue';
|
import Code from './Code.vue';
|
||||||
import { IApplication } from '@/domain/IApplication';
|
|
||||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
|
import { ApplicationFactory } from '@/application/ApplicationFactory';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
Code,
|
Code,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class MacOsInstructions extends StatefulVue {
|
export default class MacOsInstructions extends Vue {
|
||||||
@Prop() public fileName: string;
|
@Prop() public fileName: string;
|
||||||
public appName = '';
|
public appName = '';
|
||||||
public macOsDownloadUrl = '';
|
public macOsDownloadUrl = '';
|
||||||
|
|
||||||
protected initialize(app: IApplication): void {
|
public async created() {
|
||||||
|
const app = await ApplicationFactory.Current.getAppAsync();
|
||||||
this.appName = app.info.name;
|
this.appName = app.info.name;
|
||||||
this.macOsDownloadUrl = app.info.getDownloadUrl(OperatingSystem.macOS);
|
this.macOsDownloadUrl = app.info.getDownloadUrl(OperatingSystem.macOS);
|
||||||
}
|
}
|
||||||
protected handleCollectionState(): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -35,9 +35,7 @@ import MacOsInstructions from './MacOsInstructions.vue';
|
|||||||
import { Environment } from '@/application/Environment/Environment';
|
import { Environment } from '@/application/Environment/Environment';
|
||||||
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
||||||
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||||
import { IApplication } from '@/domain/IApplication';
|
|
||||||
import { IApplicationCode } from '@/application/Context/State/Code/IApplicationCode';
|
import { IApplicationCode } from '@/application/Context/State/Code/IApplicationCode';
|
||||||
import { IEventSubscription } from '@/infrastructure/Events/ISubscription';
|
|
||||||
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
|
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
|
||||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
|
|
||||||
@@ -55,8 +53,6 @@ export default class TheCodeButtons extends StatefulVue {
|
|||||||
public isMacOsCollection = false;
|
public isMacOsCollection = false;
|
||||||
public fileName = '';
|
public fileName = '';
|
||||||
|
|
||||||
private codeListener: IEventSubscription;
|
|
||||||
|
|
||||||
public async copyCodeAsync() {
|
public async copyCodeAsync() {
|
||||||
const code = await this.getCurrentCodeAsync();
|
const code = await this.getCurrentCodeAsync();
|
||||||
Clipboard.copyText(code.current);
|
Clipboard.copyText(code.current);
|
||||||
@@ -68,15 +64,7 @@ export default class TheCodeButtons extends StatefulVue {
|
|||||||
this.$modal.show(this.macOsModalName);
|
this.$modal.show(this.macOsModalName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public destroyed() {
|
|
||||||
if (this.codeListener) {
|
|
||||||
this.codeListener.unsubscribe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected initialize(app: IApplication): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
protected handleCollectionState(newState: ICategoryCollectionState): void {
|
protected handleCollectionState(newState: ICategoryCollectionState): void {
|
||||||
this.isMacOsCollection = newState.collection.os === OperatingSystem.macOS;
|
this.isMacOsCollection = newState.collection.os === OperatingSystem.macOS;
|
||||||
this.fileName = buildFileName(newState.collection.scripting);
|
this.fileName = buildFileName(newState.collection.scripting);
|
||||||
@@ -90,12 +78,10 @@ export default class TheCodeButtons extends StatefulVue {
|
|||||||
}
|
}
|
||||||
private async react(code: IApplicationCode) {
|
private async react(code: IApplicationCode) {
|
||||||
this.hasCode = code.current && code.current.length > 0;
|
this.hasCode = code.current && code.current.length > 0;
|
||||||
if (this.codeListener) {
|
this.events.unsubscribeAll();
|
||||||
this.codeListener.unsubscribe();
|
this.events.register(code.changed.on((newCode) => {
|
||||||
}
|
|
||||||
this.codeListener = code.changed.on((newCode) => {
|
|
||||||
this.hasCode = newCode && newCode.code.length > 0;
|
this.hasCode = newCode && newCode.code.length > 0;
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import { StatefulVue } from '@/presentation/StatefulVue';
|
|||||||
import { ICategory } from '@/domain/ICategory';
|
import { ICategory } from '@/domain/ICategory';
|
||||||
import { hasDirective } from './NonCollapsingDirective';
|
import { hasDirective } from './NonCollapsingDirective';
|
||||||
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
||||||
import { IApplication } from '@/domain/IApplication';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
@@ -45,9 +44,6 @@ export default class CardList extends StatefulVue {
|
|||||||
this.activeCategoryId = isExpanded ? categoryId : undefined;
|
this.activeCategoryId = isExpanded ? categoryId : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected initialize(app: IApplication): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
|
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
|
||||||
this.setCategories(newState.collection.actions);
|
this.setCategories(newState.collection.actions);
|
||||||
this.activeCategoryId = undefined;
|
this.activeCategoryId = undefined;
|
||||||
|
|||||||
@@ -35,7 +35,6 @@
|
|||||||
import { Component, Prop, Watch, Emit } from 'vue-property-decorator';
|
import { Component, Prop, Watch, Emit } from 'vue-property-decorator';
|
||||||
import ScriptsTree from '@/presentation/Scripts/ScriptsTree/ScriptsTree.vue';
|
import ScriptsTree from '@/presentation/Scripts/ScriptsTree/ScriptsTree.vue';
|
||||||
import { StatefulVue } from '@/presentation/StatefulVue';
|
import { StatefulVue } from '@/presentation/StatefulVue';
|
||||||
import { IEventSubscription } from '@/infrastructure/Events/ISubscription';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
@@ -50,17 +49,11 @@ export default class CardListItem extends StatefulVue {
|
|||||||
public isAnyChildSelected = false;
|
public isAnyChildSelected = false;
|
||||||
public areAllChildrenSelected = false;
|
public areAllChildrenSelected = false;
|
||||||
|
|
||||||
private selectionChangedListener: IEventSubscription;
|
|
||||||
|
|
||||||
public async mounted() {
|
public async mounted() {
|
||||||
this.updateStateAsync(this.categoryId);
|
|
||||||
const context = await this.getCurrentContextAsync();
|
const context = await this.getCurrentContextAsync();
|
||||||
this.selectionChangedListener = context.state.selection.changed.on(() => this.updateStateAsync(this.categoryId));
|
this.events.register(context.state.selection.changed.on(
|
||||||
}
|
() => this.updateSelectionIndicatorsAsync(this.categoryId)));
|
||||||
public destroyed() {
|
await this.updateStateAsync(this.categoryId);
|
||||||
if (this.selectionChangedListener) {
|
|
||||||
this.selectionChangedListener.unsubscribe();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@Emit('selected')
|
@Emit('selected')
|
||||||
public onSelected(isExpanded: boolean) {
|
public onSelected(isExpanded: boolean) {
|
||||||
@@ -81,19 +74,22 @@ export default class CardListItem extends StatefulVue {
|
|||||||
@Watch('categoryId')
|
@Watch('categoryId')
|
||||||
public async updateStateAsync(value: |number) {
|
public async updateStateAsync(value: |number) {
|
||||||
const context = await this.getCurrentContextAsync();
|
const context = await this.getCurrentContextAsync();
|
||||||
const category = !value ? undefined : context.state.collection.findCategory(this.categoryId);
|
const category = !value ? undefined : context.state.collection.findCategory(value);
|
||||||
this.cardTitle = category ? category.name : undefined;
|
this.cardTitle = category ? category.name : undefined;
|
||||||
const currentSelection = context.state.selection;
|
await this.updateSelectionIndicatorsAsync(value);
|
||||||
this.isAnyChildSelected = category ? currentSelection.isAnySelected(category) : false;
|
|
||||||
this.areAllChildrenSelected = category ? currentSelection.areAllSelected(category) : false;
|
|
||||||
}
|
|
||||||
protected initialize(): void {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected handleCollectionState(): void {
|
protected handleCollectionState(): void {
|
||||||
// No need, as categoryId will be updated instead
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async updateSelectionIndicatorsAsync(categoryId: number) {
|
||||||
|
const context = await this.getCurrentContextAsync();
|
||||||
|
const selection = context.state.selection;
|
||||||
|
const category = context.state.collection.findCategory(categoryId);
|
||||||
|
this.isAnyChildSelected = category ? selection.isAnySelected(category) : false;
|
||||||
|
this.areAllChildrenSelected = category ? selection.areAllSelected(category) : false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { DirectiveOptions } from 'vue';
|
import { DirectiveOptions } from 'vue';
|
||||||
|
|
||||||
const attributeName = 'data-interactionDoesNotCollapse';
|
const attributeName = 'data-interaction-does-not-collapse';
|
||||||
|
|
||||||
export function hasDirective(el: Element): boolean {
|
export function hasDirective(el: Element): boolean {
|
||||||
if (el.hasAttribute(attributeName)) {
|
if (el.hasAttribute(attributeName)) {
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ import SelectableTree from './SelectableTree/SelectableTree.vue';
|
|||||||
import { INode, NodeType } from './SelectableTree/Node/INode';
|
import { INode, NodeType } from './SelectableTree/Node/INode';
|
||||||
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
|
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
|
||||||
import { INodeSelectedEvent } from './SelectableTree/INodeSelectedEvent';
|
import { INodeSelectedEvent } from './SelectableTree/INodeSelectedEvent';
|
||||||
import { IApplication } from '@/domain/IApplication';
|
|
||||||
import { IEventSubscription } from '@/infrastructure/Events/ISubscription';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
@@ -43,7 +41,6 @@ export default class ScriptsTree extends StatefulVue {
|
|||||||
public filterText?: string = null;
|
public filterText?: string = null;
|
||||||
|
|
||||||
private filtered?: IFilterResult;
|
private filtered?: IFilterResult;
|
||||||
private listeners = new Array<IEventSubscription>();
|
|
||||||
|
|
||||||
public async toggleNodeSelectionAsync(event: INodeSelectedEvent) {
|
public async toggleNodeSelectionAsync(event: INodeSelectedEvent) {
|
||||||
const context = await this.getCurrentContextAsync();
|
const context = await this.getCurrentContextAsync();
|
||||||
@@ -75,31 +72,24 @@ export default class ScriptsTree extends StatefulVue {
|
|||||||
|| this.filtered.categoryMatches.some(
|
|| this.filtered.categoryMatches.some(
|
||||||
(category: ICategory) => node.id === getCategoryNodeId(category));
|
(category: ICategory) => node.id === getCategoryNodeId(category));
|
||||||
}
|
}
|
||||||
public destroyed() {
|
|
||||||
this.unsubscribeAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected initialize(app: IApplication): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
protected async handleCollectionState(newState: ICategoryCollectionState) {
|
protected async handleCollectionState(newState: ICategoryCollectionState) {
|
||||||
this.setCurrentFilter(newState.filter.currentFilter);
|
this.setCurrentFilter(newState.filter.currentFilter);
|
||||||
if (!this.categoryId) {
|
if (!this.categoryId) {
|
||||||
this.nodes = parseAllCategories(newState.collection);
|
this.nodes = parseAllCategories(newState.collection);
|
||||||
}
|
}
|
||||||
this.unsubscribeAll();
|
this.events.unsubscribeAll();
|
||||||
this.subscribe(newState);
|
this.subscribeState(newState);
|
||||||
}
|
}
|
||||||
|
|
||||||
private subscribe(state: ICategoryCollectionState) {
|
private subscribeState(state: ICategoryCollectionState) {
|
||||||
this.listeners.push(state.selection.changed.on(this.handleSelectionChanged));
|
this.events.register(
|
||||||
this.listeners.push(state.filter.filterRemoved.on(this.handleFilterRemoved));
|
state.selection.changed.on(this.handleSelectionChanged),
|
||||||
this.listeners.push(state.filter.filtered.on(this.handleFiltered));
|
state.filter.filterRemoved.on(this.handleFilterRemoved),
|
||||||
}
|
state.filter.filtered.on(this.handleFiltered),
|
||||||
private unsubscribeAll() {
|
);
|
||||||
this.listeners.forEach((listener) => listener.unsubscribe());
|
|
||||||
this.listeners.splice(0, this.listeners.length);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private setCurrentFilter(currentFilter: IFilterResult | undefined) {
|
private setCurrentFilter(currentFilter: IFilterResult | undefined) {
|
||||||
if (!currentFilter) {
|
if (!currentFilter) {
|
||||||
this.handleFilterRemoved();
|
this.handleFilterRemoved();
|
||||||
|
|||||||
@@ -13,49 +13,40 @@
|
|||||||
|
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Prop, Watch } from 'vue-property-decorator';
|
import { Component, Prop, Watch } from 'vue-property-decorator';
|
||||||
import { IReverter } from './Reverter/IReverter';
|
import { IReverter } from './Reverter/IReverter';
|
||||||
import { StatefulVue } from '@/presentation/StatefulVue';
|
import { StatefulVue } from '@/presentation/StatefulVue';
|
||||||
import { INode } from './INode';
|
import { INode } from './INode';
|
||||||
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
|
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
|
||||||
import { getReverter } from './Reverter/ReverterFactory';
|
import { getReverter } from './Reverter/ReverterFactory';
|
||||||
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
||||||
import { IApplication } from '@/domain/IApplication';
|
|
||||||
import { IEventSubscription } from '@/infrastructure/Events/ISubscription';
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
export default class RevertToggle extends StatefulVue {
|
export default class RevertToggle extends StatefulVue {
|
||||||
@Prop() public node: INode;
|
@Prop() public node: INode;
|
||||||
public isReverted = false;
|
public isReverted = false;
|
||||||
|
|
||||||
private handler: IReverter;
|
private handler: IReverter;
|
||||||
private selectionChangeListener: IEventSubscription;
|
|
||||||
|
|
||||||
@Watch('node', {immediate: true}) public async onNodeChangedAsync(node: INode) {
|
@Watch('node', {immediate: true}) public async onNodeChangedAsync(node: INode) {
|
||||||
const context = await this.getCurrentContextAsync();
|
const context = await this.getCurrentContextAsync();
|
||||||
this.handler = getReverter(node, context.state.collection);
|
this.handler = getReverter(node, context.state.collection);
|
||||||
}
|
|
||||||
public async onRevertToggledAsync() {
|
|
||||||
const context = await this.getCurrentContextAsync();
|
|
||||||
this.handler.selectWithRevertState(this.isReverted, context.state.selection);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected initialize(app: IApplication): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
|
|
||||||
this.updateStatus(newState.selection.selectedScripts);
|
|
||||||
if (this.selectionChangeListener) {
|
|
||||||
this.selectionChangeListener.unsubscribe();
|
|
||||||
}
|
|
||||||
this.selectionChangeListener = newState.selection.changed.on(
|
|
||||||
(scripts) => this.updateStatus(scripts));
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateStatus(scripts: ReadonlyArray<SelectedScript>) {
|
|
||||||
this.isReverted = this.handler.getState(scripts);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
public async onRevertToggledAsync() {
|
||||||
|
const context = await this.getCurrentContextAsync();
|
||||||
|
this.handler.selectWithRevertState(this.isReverted, context.state.selection);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected handleCollectionState(newState: ICategoryCollectionState): void {
|
||||||
|
this.updateStatus(newState.selection.selectedScripts);
|
||||||
|
this.events.unsubscribeAll();
|
||||||
|
this.events.register(newState.selection.changed.on((scripts) => this.updateStatus(scripts)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateStatus(scripts: ReadonlyArray<SelectedScript>) {
|
||||||
|
this.isReverted = this.handler.getState(scripts);
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ import { IScript } from '@/domain/IScript';
|
|||||||
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
|
import { SelectedScript } from '@/application/Context/State/Selection/SelectedScript';
|
||||||
import { RecommendationLevel } from '@/domain/RecommendationLevel';
|
import { RecommendationLevel } from '@/domain/RecommendationLevel';
|
||||||
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
||||||
import { IApplication } from '@/domain/IApplication';
|
|
||||||
|
|
||||||
enum SelectionState {
|
enum SelectionState {
|
||||||
Standard,
|
Standard,
|
||||||
@@ -82,9 +81,6 @@ export default class TheSelector extends StatefulVue {
|
|||||||
selectType(context.state, type);
|
selectType(context.state, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected initialize(app: IApplication): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
|
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
|
||||||
this.updateSelections(newState);
|
this.updateSelections(newState);
|
||||||
newState.selection.changed.on(() => this.updateSelections(newState));
|
newState.selection.changed.on(() => this.updateSelections(newState));
|
||||||
|
|||||||
@@ -16,23 +16,24 @@ import { Component } from 'vue-property-decorator';
|
|||||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
import { StatefulVue } from '@/presentation/StatefulVue';
|
import { StatefulVue } from '@/presentation/StatefulVue';
|
||||||
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
||||||
import { IApplication } from '@/domain/IApplication';
|
import { ApplicationFactory } from '@/application/ApplicationFactory';
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
export default class TheOsChanger extends StatefulVue {
|
export default class TheOsChanger extends StatefulVue {
|
||||||
public allOses: Array<{ name: string, os: OperatingSystem }> = [];
|
public allOses: Array<{ name: string, os: OperatingSystem }> = [];
|
||||||
public currentOs: OperatingSystem = undefined;
|
public currentOs: OperatingSystem = OperatingSystem.Unknown;
|
||||||
|
|
||||||
|
public async created() {
|
||||||
|
const app = await ApplicationFactory.Current.getAppAsync();
|
||||||
|
this.allOses = app.getSupportedOsList()
|
||||||
|
.map((os) => ({ os, name: renderOsName(os) }));
|
||||||
|
}
|
||||||
public async changeOsAsync(newOs: OperatingSystem) {
|
public async changeOsAsync(newOs: OperatingSystem) {
|
||||||
const context = await this.getCurrentContextAsync();
|
const context = await this.getCurrentContextAsync();
|
||||||
context.changeContext(newOs);
|
context.changeContext(newOs);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected initialize(app: IApplication): void {
|
protected handleCollectionState(newState: ICategoryCollectionState): void {
|
||||||
this.allOses = app.getSupportedOsList()
|
|
||||||
.map((os) => ({ os, name: renderOsName(os) }));
|
|
||||||
}
|
|
||||||
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
|
|
||||||
this.currentOs = newState.os;
|
this.currentOs = newState.os;
|
||||||
this.$forceUpdate(); // v-bind:class is not updated otherwise
|
this.$forceUpdate(); // v-bind:class is not updated otherwise
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,8 +49,7 @@ import { StatefulVue } from '@/presentation/StatefulVue';
|
|||||||
import { Grouping } from './Grouping/Grouping';
|
import { Grouping } from './Grouping/Grouping';
|
||||||
import { IFilterResult } from '@/application/Context/State/Filter/IFilterResult';
|
import { IFilterResult } from '@/application/Context/State/Filter/IFilterResult';
|
||||||
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
||||||
import { IApplication } from '@/domain/IApplication';
|
import { ApplicationFactory } from '@/application/ApplicationFactory';
|
||||||
import { IEventSubscription } from '@/infrastructure/Events/ISubscription';
|
|
||||||
|
|
||||||
/** Shows content of single category or many categories */
|
/** Shows content of single category or many categories */
|
||||||
@Component({
|
@Component({
|
||||||
@@ -79,10 +78,9 @@ export default class TheScripts extends StatefulVue {
|
|||||||
public isSearching = false;
|
public isSearching = false;
|
||||||
public searchHasMatches = false;
|
public searchHasMatches = false;
|
||||||
|
|
||||||
private listeners = new Array<IEventSubscription>();
|
public async created() {
|
||||||
|
const app = await ApplicationFactory.Current.getAppAsync();
|
||||||
public destroyed() {
|
this.repositoryUrl = app.info.repositoryWebUrl;
|
||||||
this.unsubscribeAll();
|
|
||||||
}
|
}
|
||||||
public async clearSearchQueryAsync() {
|
public async clearSearchQueryAsync() {
|
||||||
const context = await this.getCurrentContextAsync();
|
const context = await this.getCurrentContextAsync();
|
||||||
@@ -93,27 +91,22 @@ export default class TheScripts extends StatefulVue {
|
|||||||
this.currentGrouping = group;
|
this.currentGrouping = group;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected initialize(app: IApplication): void {
|
|
||||||
this.repositoryUrl = app.info.repositoryWebUrl;
|
|
||||||
}
|
|
||||||
protected handleCollectionState(newState: ICategoryCollectionState): void {
|
protected handleCollectionState(newState: ICategoryCollectionState): void {
|
||||||
this.unsubscribeAll();
|
this.events.unsubscribeAll();
|
||||||
this.subscribe(newState);
|
this.subscribeState(newState);
|
||||||
}
|
}
|
||||||
|
|
||||||
private subscribe(state: ICategoryCollectionState) {
|
private subscribeState(state: ICategoryCollectionState) {
|
||||||
this.listeners.push(state.filter.filterRemoved.on(() => {
|
this.events.register(
|
||||||
this.isSearching = false;
|
state.filter.filterRemoved.on(() => {
|
||||||
}));
|
this.isSearching = false;
|
||||||
state.filter.filtered.on((result: IFilterResult) => {
|
}),
|
||||||
this.searchQuery = result.query;
|
state.filter.filtered.on((result: IFilterResult) => {
|
||||||
this.isSearching = true;
|
this.searchQuery = result.query;
|
||||||
this.searchHasMatches = result.hasAnyMatches();
|
this.isSearching = true;
|
||||||
});
|
this.searchHasMatches = result.hasAnyMatches();
|
||||||
}
|
}),
|
||||||
private unsubscribeAll() {
|
);
|
||||||
this.listeners.forEach((listener) => listener.unsubscribe());
|
|
||||||
this.listeners.splice(0, this.listeners.length);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,33 +1,30 @@
|
|||||||
import { Component, Vue } from 'vue-property-decorator';
|
import { Component, Vue } from 'vue-property-decorator';
|
||||||
import { AsyncLazy } from '@/infrastructure/Threading/AsyncLazy';
|
import { AsyncLazy } from '@/infrastructure/Threading/AsyncLazy';
|
||||||
import { IApplicationContext } from '@/application/Context/IApplicationContext';
|
import { IApplicationContext } from '@/application/Context/IApplicationContext';
|
||||||
import { buildContext } from '@/application/Context/ApplicationContextProvider';
|
import { buildContextAsync } from '@/application/Context/ApplicationContextFactory';
|
||||||
import { IApplicationContextChangedEvent } from '../application/Context/IApplicationContext';
|
import { IApplicationContextChangedEvent } from '@/application/Context/IApplicationContext';
|
||||||
import { IApplication } from '@/domain/IApplication';
|
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
||||||
import { ICategoryCollectionState } from '../application/Context/State/ICategoryCollectionState';
|
import { EventSubscriptionCollection } from '../infrastructure/Events/EventSubscriptionCollection';
|
||||||
import { IEventSubscription } from '../infrastructure/Events/ISubscription';
|
|
||||||
|
|
||||||
// @ts-ignore because https://github.com/vuejs/vue-class-component/issues/91
|
// @ts-ignore because https://github.com/vuejs/vue-class-component/issues/91
|
||||||
@Component
|
@Component
|
||||||
export abstract class StatefulVue extends Vue {
|
export abstract class StatefulVue extends Vue {
|
||||||
public static instance = new AsyncLazy<IApplicationContext>(
|
private static readonly instance = new AsyncLazy<IApplicationContext>(() => buildContextAsync());
|
||||||
() => Promise.resolve(buildContext()));
|
|
||||||
|
|
||||||
private listener: IEventSubscription;
|
protected readonly events = new EventSubscriptionCollection();
|
||||||
|
|
||||||
|
private readonly ownEvents = new EventSubscriptionCollection();
|
||||||
|
|
||||||
public async mounted() {
|
public async mounted() {
|
||||||
const context = await this.getCurrentContextAsync();
|
const context = await this.getCurrentContextAsync();
|
||||||
this.listener = context.contextChanged.on((event) => this.handleStateChangedEvent(event));
|
this.ownEvents.register(context.contextChanged.on((event) => this.handleStateChangedEvent(event)));
|
||||||
this.initialize(context.app);
|
|
||||||
this.handleCollectionState(context.state, undefined);
|
this.handleCollectionState(context.state, undefined);
|
||||||
}
|
}
|
||||||
public destroyed() {
|
public destroyed() {
|
||||||
if (this.listener) {
|
this.ownEvents.unsubscribeAll();
|
||||||
this.listener.unsubscribe();
|
this.events.unsubscribeAll();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract initialize(app: IApplication): void;
|
|
||||||
protected abstract handleCollectionState(
|
protected abstract handleCollectionState(
|
||||||
newState: ICategoryCollectionState, oldState: ICategoryCollectionState | undefined): void;
|
newState: ICategoryCollectionState, oldState: ICategoryCollectionState | undefined): void;
|
||||||
protected getCurrentContextAsync(): Promise<IApplicationContext> {
|
protected getCurrentContextAsync(): Promise<IApplicationContext> {
|
||||||
|
|||||||
@@ -11,9 +11,6 @@ import { ICodeChangedEvent } from '@/application/Context/State/Code/Event/ICodeC
|
|||||||
import { IScript } from '@/domain/IScript';
|
import { IScript } from '@/domain/IScript';
|
||||||
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||||
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
||||||
import { IApplication } from '@/domain/IApplication';
|
|
||||||
import { IEventSubscription } from '@/infrastructure/Events/ISubscription';
|
|
||||||
import { IApplicationCode } from '@/application/Context/State/Code/IApplicationCode';
|
|
||||||
import { CodeBuilderFactory } from '@/application/Context/State/Code/Generation/CodeBuilderFactory';
|
import { CodeBuilderFactory } from '@/application/Context/State/Code/Generation/CodeBuilderFactory';
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@@ -22,35 +19,22 @@ export default class TheCodeArea extends StatefulVue {
|
|||||||
|
|
||||||
private editor!: ace.Ace.Editor;
|
private editor!: ace.Ace.Editor;
|
||||||
private currentMarkerId?: number;
|
private currentMarkerId?: number;
|
||||||
private codeListener: IEventSubscription;
|
|
||||||
|
|
||||||
@Prop() private theme!: string;
|
@Prop() private theme!: string;
|
||||||
|
|
||||||
public destroyed() {
|
public destroyed() {
|
||||||
this.unsubscribeCodeListening();
|
|
||||||
this.destroyEditor();
|
this.destroyEditor();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected initialize(app: IApplication): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
protected handleCollectionState(newState: ICategoryCollectionState): void {
|
protected handleCollectionState(newState: ICategoryCollectionState): void {
|
||||||
this.destroyEditor();
|
this.destroyEditor();
|
||||||
this.editor = initializeEditor(this.theme, this.editorId, newState.collection.scripting.language);
|
this.editor = initializeEditor(this.theme, this.editorId, newState.collection.scripting.language);
|
||||||
const appCode = newState.code;
|
const appCode = newState.code;
|
||||||
this.editor.setValue(appCode.current || getDefaultCode(newState.collection.scripting.language), 1);
|
this.editor.setValue(appCode.current || getDefaultCode(newState.collection.scripting.language), 1);
|
||||||
this.unsubscribeCodeListening();
|
this.events.unsubscribeAll();
|
||||||
this.subscribe(appCode);
|
this.events.register(appCode.changed.on((code) => this.updateCodeAsync(code)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private subscribe(appCode: IApplicationCode) {
|
|
||||||
this.codeListener = appCode.changed.on((code) => this.updateCodeAsync(code));
|
|
||||||
}
|
|
||||||
private unsubscribeCodeListening() {
|
|
||||||
if (this.codeListener) {
|
|
||||||
this.codeListener.unsubscribe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private async updateCodeAsync(event: ICodeChangedEvent) {
|
private async updateCodeAsync(event: ICodeChangedEvent) {
|
||||||
this.removeCurrentHighlighting();
|
this.removeCurrentHighlighting();
|
||||||
if (event.isEmpty()) {
|
if (event.isEmpty()) {
|
||||||
|
|||||||
@@ -9,15 +9,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Prop, Watch } from 'vue-property-decorator';
|
import { Component, Prop, Watch, Vue } from 'vue-property-decorator';
|
||||||
import { StatefulVue } from '@/presentation/StatefulVue';
|
|
||||||
import { Environment } from '@/application/Environment/Environment';
|
import { Environment } from '@/application/Environment/Environment';
|
||||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
import { ApplicationFactory } from '@/application/ApplicationFactory';
|
||||||
import { IApplication } from '@/domain/IApplication';
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
export default class DownloadUrlListItem extends StatefulVue {
|
export default class DownloadUrlListItem extends Vue {
|
||||||
@Prop() public operatingSystem!: OperatingSystem;
|
@Prop() public operatingSystem!: OperatingSystem;
|
||||||
|
|
||||||
public downloadUrl: string = '';
|
public downloadUrl: string = '';
|
||||||
@@ -38,16 +36,9 @@ export default class DownloadUrlListItem extends StatefulVue {
|
|||||||
this.hasCurrentOsDesktopVersion = hasDesktopVersion(currentOs);
|
this.hasCurrentOsDesktopVersion = hasDesktopVersion(currentOs);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected initialize(app: IApplication): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getDownloadUrlAsync(os: OperatingSystem): Promise<string> {
|
private async getDownloadUrlAsync(os: OperatingSystem): Promise<string> {
|
||||||
const context = await this.getCurrentContextAsync();
|
const context = await ApplicationFactory.Current.getAppAsync();
|
||||||
return context.app.info.getDownloadUrl(os);
|
return context.info.getDownloadUrl(os);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,25 +31,27 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component } from 'vue-property-decorator';
|
import { Component, Vue } from 'vue-property-decorator';
|
||||||
import { StatefulVue } from '@/presentation/StatefulVue';
|
|
||||||
import { Environment } from '@/application/Environment/Environment';
|
import { Environment } from '@/application/Environment/Environment';
|
||||||
|
import { ApplicationFactory } from '@/application/ApplicationFactory';
|
||||||
import { IApplication } from '@/domain/IApplication';
|
import { IApplication } from '@/domain/IApplication';
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
export default class PrivacyPolicy extends StatefulVue {
|
export default class PrivacyPolicy extends Vue {
|
||||||
public repositoryUrl: string = '';
|
public repositoryUrl: string = '';
|
||||||
public feedbackUrl: string = '';
|
public feedbackUrl: string = '';
|
||||||
public isDesktop = Environment.CurrentEnvironment.isDesktop;
|
public isDesktop = Environment.CurrentEnvironment.isDesktop;
|
||||||
|
|
||||||
protected initialize(app: IApplication): void {
|
public async created() {
|
||||||
|
const app = await ApplicationFactory.Current.getAppAsync();
|
||||||
|
this.initialize(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
private initialize(app: IApplication) {
|
||||||
const info = app.info;
|
const info = app.info;
|
||||||
this.repositoryUrl = info.repositoryWebUrl;
|
this.repositoryUrl = info.repositoryWebUrl;
|
||||||
this.feedbackUrl = info.feedbackUrl;
|
this.feedbackUrl = info.feedbackUrl;
|
||||||
}
|
}
|
||||||
protected handleCollectionState(): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -47,22 +47,21 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component } from 'vue-property-decorator';
|
import { Component, Vue } from 'vue-property-decorator';
|
||||||
import { StatefulVue } from '@/presentation/StatefulVue';
|
|
||||||
import { Environment } from '@/application/Environment/Environment';
|
import { Environment } from '@/application/Environment/Environment';
|
||||||
import PrivacyPolicy from './PrivacyPolicy.vue';
|
import PrivacyPolicy from './PrivacyPolicy.vue';
|
||||||
import DownloadUrlList from './DownloadUrlList.vue';
|
import DownloadUrlList from './DownloadUrlList.vue';
|
||||||
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
|
||||||
import { IApplication } from '@/domain/IApplication';
|
import { IApplication } from '@/domain/IApplication';
|
||||||
|
import { ApplicationFactory } from '@/application/ApplicationFactory';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
PrivacyPolicy, DownloadUrlList,
|
PrivacyPolicy, DownloadUrlList,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
export default class TheFooter extends StatefulVue {
|
export default class TheFooter extends Vue {
|
||||||
public readonly modalName = 'privacy-policy';
|
public readonly modalName = 'privacy-policy';
|
||||||
public readonly isDesktop: boolean;
|
public readonly isDesktop = Environment.CurrentEnvironment.isDesktop;
|
||||||
|
|
||||||
public version: string = '';
|
public version: string = '';
|
||||||
public repositoryUrl: string = '';
|
public repositoryUrl: string = '';
|
||||||
@@ -70,12 +69,12 @@ export default class TheFooter extends StatefulVue {
|
|||||||
public feedbackUrl: string = '';
|
public feedbackUrl: string = '';
|
||||||
public homepageUrl: string = '';
|
public homepageUrl: string = '';
|
||||||
|
|
||||||
constructor() {
|
public async created() {
|
||||||
super();
|
const app = await ApplicationFactory.Current.getAppAsync();
|
||||||
this.isDesktop = Environment.CurrentEnvironment.isDesktop;
|
this.initialize(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected initialize(app: IApplication): void {
|
private initialize(app: IApplication) {
|
||||||
const info = app.info;
|
const info = app.info;
|
||||||
this.version = info.version;
|
this.version = info.version;
|
||||||
this.homepageUrl = info.homepage;
|
this.homepageUrl = info.homepage;
|
||||||
@@ -83,10 +82,6 @@ export default class TheFooter extends StatefulVue {
|
|||||||
this.releaseUrl = info.releaseUrl;
|
this.releaseUrl = info.releaseUrl;
|
||||||
this.feedbackUrl = info.feedbackUrl;
|
this.feedbackUrl = info.feedbackUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,27 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="container">
|
<div id="container">
|
||||||
<h1 class="child title" >{{ title }}</h1>
|
<h1 class="child title" >{{ title }}</h1>
|
||||||
<h2 class="child subtitle">Enforce privacy & security on Windows and macOS</h2>
|
<h2 class="child subtitle">Enforce privacy & security on Windows and macOS</h2>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
import { ApplicationFactory } from '@/application/ApplicationFactory';
|
||||||
import { IApplication } from '@/domain/IApplication';
|
import { Component, Vue } from 'vue-property-decorator';
|
||||||
import { Component } from 'vue-property-decorator';
|
|
||||||
import { StatefulVue } from './StatefulVue';
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
export default class TheHeader extends StatefulVue {
|
export default class TheHeader extends Vue {
|
||||||
public title = '';
|
public title = '';
|
||||||
public subtitle = '';
|
public subtitle = '';
|
||||||
|
|
||||||
protected initialize(app: IApplication): void {
|
public async created() {
|
||||||
|
const app = await ApplicationFactory.Current.getAppAsync();
|
||||||
this.title = app.info.name;
|
this.title = app.info.name;
|
||||||
}
|
}
|
||||||
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -15,9 +15,7 @@ import { StatefulVue } from './StatefulVue';
|
|||||||
import { NonCollapsing } from '@/presentation/Scripts/Cards/NonCollapsingDirective';
|
import { NonCollapsing } from '@/presentation/Scripts/Cards/NonCollapsingDirective';
|
||||||
import { IUserFilter } from '@/application/Context/State/Filter/IUserFilter';
|
import { IUserFilter } from '@/application/Context/State/Filter/IUserFilter';
|
||||||
import { IFilterResult } from '@/application/Context/State/Filter/IFilterResult';
|
import { IFilterResult } from '@/application/Context/State/Filter/IFilterResult';
|
||||||
import { IApplication } from '@/domain/IApplication';
|
|
||||||
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
|
||||||
import { IEventSubscription } from '@/infrastructure/Events/ISubscription';
|
|
||||||
|
|
||||||
@Component( {
|
@Component( {
|
||||||
directives: { NonCollapsing },
|
directives: { NonCollapsing },
|
||||||
@@ -27,8 +25,6 @@ export default class TheSearchBar extends StatefulVue {
|
|||||||
public searchPlaceHolder = 'Search';
|
public searchPlaceHolder = 'Search';
|
||||||
public searchQuery = '';
|
public searchQuery = '';
|
||||||
|
|
||||||
private readonly listeners = new Array<IEventSubscription>();
|
|
||||||
|
|
||||||
@Watch('searchQuery')
|
@Watch('searchQuery')
|
||||||
public async updateFilterAsync(newFilter: |string) {
|
public async updateFilterAsync(newFilter: |string) {
|
||||||
const context = await this.getCurrentContextAsync();
|
const context = await this.getCurrentContextAsync();
|
||||||
@@ -39,28 +35,18 @@ export default class TheSearchBar extends StatefulVue {
|
|||||||
filter.setFilter(newFilter);
|
filter.setFilter(newFilter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public destroyed() {
|
|
||||||
this.unsubscribeAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected initialize(app: IApplication): void {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState | undefined) {
|
protected handleCollectionState(newState: ICategoryCollectionState, oldState: ICategoryCollectionState | undefined) {
|
||||||
const totalScripts = newState.collection.totalScripts;
|
const totalScripts = newState.collection.totalScripts;
|
||||||
this.searchPlaceHolder = `Search in ${totalScripts} scripts`;
|
this.searchPlaceHolder = `Search in ${totalScripts} scripts`;
|
||||||
this.searchQuery = newState.filter.currentFilter ? newState.filter.currentFilter.query : '';
|
this.searchQuery = newState.filter.currentFilter ? newState.filter.currentFilter.query : '';
|
||||||
this.unsubscribeAll();
|
this.events.unsubscribeAll();
|
||||||
this.subscribe(newState.filter);
|
this.subscribeFilter(newState.filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private subscribe(filter: IUserFilter) {
|
private subscribeFilter(filter: IUserFilter) {
|
||||||
this.listeners.push(filter.filtered.on((result) => this.handleFiltered(result)));
|
this.events.register(filter.filtered.on((result) => this.handleFiltered(result)));
|
||||||
this.listeners.push(filter.filterRemoved.on(() => this.handleFilterRemoved()));
|
this.events.register(filter.filterRemoved.on(() => this.handleFilterRemoved()));
|
||||||
}
|
|
||||||
private unsubscribeAll() {
|
|
||||||
this.listeners.forEach((listener) => listener.unsubscribe());
|
|
||||||
this.listeners.splice(0, this.listeners.length);
|
|
||||||
}
|
}
|
||||||
private handleFiltered(result: IFilterResult) {
|
private handleFiltered(result: IFilterResult) {
|
||||||
this.searchQuery = result.query;
|
this.searchQuery = result.query;
|
||||||
|
|||||||
60
tests/unit/application/ApplicationFactory.spec.ts
Normal file
60
tests/unit/application/ApplicationFactory.spec.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import 'mocha';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { ApplicationFactory, ApplicationGetter } from '@/application/ApplicationFactory';
|
||||||
|
import { ApplicationStub } from '../stubs/ApplicationStub';
|
||||||
|
|
||||||
|
describe('ApplicationFactory', () => {
|
||||||
|
describe('ctor', () => {
|
||||||
|
it('throws if getter is undefined', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedError = 'undefined getter';
|
||||||
|
const getter = undefined;
|
||||||
|
// act
|
||||||
|
const act = () => new SystemUnderTest(getter);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('getAppAsync', () => {
|
||||||
|
it('returns result from the getter', async () => {
|
||||||
|
// arrange
|
||||||
|
const expected = new ApplicationStub();
|
||||||
|
const getter: ApplicationGetter = () => expected;
|
||||||
|
const sut = new SystemUnderTest(getter);
|
||||||
|
// act
|
||||||
|
const actual = await Promise.all( [
|
||||||
|
sut.getAppAsync(),
|
||||||
|
sut.getAppAsync(),
|
||||||
|
sut.getAppAsync(),
|
||||||
|
sut.getAppAsync(),
|
||||||
|
]);
|
||||||
|
// assert
|
||||||
|
expect(actual.every((value) => value === expected));
|
||||||
|
});
|
||||||
|
it('only executes getter once', async () => {
|
||||||
|
// arrange
|
||||||
|
let totalExecution = 0;
|
||||||
|
const expected = new ApplicationStub();
|
||||||
|
const getter: ApplicationGetter = () => {
|
||||||
|
totalExecution++;
|
||||||
|
return expected;
|
||||||
|
};
|
||||||
|
const sut = new SystemUnderTest(getter);
|
||||||
|
// act
|
||||||
|
await Promise.all( [
|
||||||
|
sut.getAppAsync(),
|
||||||
|
sut.getAppAsync(),
|
||||||
|
sut.getAppAsync(),
|
||||||
|
sut.getAppAsync(),
|
||||||
|
]);
|
||||||
|
// assert
|
||||||
|
expect(totalExecution).to.equal(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
class SystemUnderTest extends ApplicationFactory {
|
||||||
|
public constructor(costlyGetter: ApplicationGetter) {
|
||||||
|
super(costlyGetter);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import 'mocha';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
|
import { ICategoryCollection } from '@/domain/ICategoryCollection';
|
||||||
|
import { CategoryCollectionStub } from '../../stubs/CategoryCollectionStub';
|
||||||
|
import { EnvironmentStub } from '../../stubs/EnvironmentStub';
|
||||||
|
import { ApplicationStub } from '../../stubs/ApplicationStub';
|
||||||
|
import { buildContextAsync } from '@/application/Context/ApplicationContextFactory';
|
||||||
|
import { IApplicationFactory } from '@/application/IApplicationFactory';
|
||||||
|
import { IApplication } from '@/domain/IApplication';
|
||||||
|
|
||||||
|
describe('ApplicationContextFactory', () => {
|
||||||
|
describe('buildContextAsync', () => {
|
||||||
|
describe('factory', () => {
|
||||||
|
it('sets application from factory', async () => {
|
||||||
|
// arrange
|
||||||
|
const expected = new ApplicationStub().withCollection(
|
||||||
|
new CategoryCollectionStub().withOs(OperatingSystem.macOS));
|
||||||
|
const factoryMock = mockFactoryWithApp(expected);
|
||||||
|
// act
|
||||||
|
const context = await buildContextAsync(factoryMock);
|
||||||
|
// assert
|
||||||
|
expect(expected).to.equal(context.app);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('environment', () => {
|
||||||
|
describe('sets initial OS as expected', () => {
|
||||||
|
it('returns currentOs if it is supported', async () => {
|
||||||
|
// arrange
|
||||||
|
const expected = OperatingSystem.Windows;
|
||||||
|
const environment = new EnvironmentStub().withOs(expected);
|
||||||
|
const collection = new CategoryCollectionStub().withOs(expected);
|
||||||
|
const factoryMock = mockFactoryWithCollection(collection);
|
||||||
|
// act
|
||||||
|
const context = await buildContextAsync(factoryMock, environment);
|
||||||
|
// assert
|
||||||
|
const actual = context.state.os;
|
||||||
|
expect(expected).to.equal(actual);
|
||||||
|
});
|
||||||
|
it('fallbacks to other os if OS in environment is not supported', async () => {
|
||||||
|
// arrange
|
||||||
|
const expected = OperatingSystem.Windows;
|
||||||
|
const currentOs = OperatingSystem.macOS;
|
||||||
|
const environment = new EnvironmentStub().withOs(currentOs);
|
||||||
|
const collection = new CategoryCollectionStub().withOs(expected);
|
||||||
|
const factoryMock = mockFactoryWithCollection(collection);
|
||||||
|
// act
|
||||||
|
const context = await buildContextAsync(factoryMock, environment);
|
||||||
|
// assert
|
||||||
|
const actual = context.state.os;
|
||||||
|
expect(expected).to.equal(actual);
|
||||||
|
});
|
||||||
|
it('fallbacks to most supported os if current os is not supported', async () => {
|
||||||
|
// arrange
|
||||||
|
const expectedOs = OperatingSystem.Android;
|
||||||
|
const allCollections = [
|
||||||
|
new CategoryCollectionStub().withOs(OperatingSystem.Linux).withTotalScripts(3),
|
||||||
|
new CategoryCollectionStub().withOs(expectedOs).withTotalScripts(5),
|
||||||
|
new CategoryCollectionStub().withOs(OperatingSystem.Windows).withTotalScripts(4),
|
||||||
|
];
|
||||||
|
const environment = new EnvironmentStub().withOs(OperatingSystem.macOS);
|
||||||
|
const app = new ApplicationStub().withCollections(...allCollections);
|
||||||
|
const factoryMock = mockFactoryWithApp(app);
|
||||||
|
// act
|
||||||
|
const context = await buildContextAsync(factoryMock, environment);
|
||||||
|
// assert
|
||||||
|
const actual = context.state.os;
|
||||||
|
expect(expectedOs).to.equal(actual, `Expected: ${OperatingSystem[expectedOs]}, actual: ${OperatingSystem[actual]}`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function mockFactoryWithCollection(result: ICategoryCollection): IApplicationFactory {
|
||||||
|
return mockFactoryWithApp(new ApplicationStub().withCollection(result));
|
||||||
|
}
|
||||||
|
|
||||||
|
function mockFactoryWithApp(app: IApplication): IApplicationFactory {
|
||||||
|
return {
|
||||||
|
getAppAsync: () => Promise.resolve(app),
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
import 'mocha';
|
|
||||||
import { expect } from 'chai';
|
|
||||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
|
||||||
import { ICategoryCollection } from '@/domain/ICategoryCollection';
|
|
||||||
import { ApplicationParserType, buildContext } from '@/application/Context/ApplicationContextProvider';
|
|
||||||
import { CategoryCollectionStub } from '../../stubs/CategoryCollectionStub';
|
|
||||||
import { EnvironmentStub } from '../../stubs/EnvironmentStub';
|
|
||||||
import { ApplicationStub } from '../../stubs/ApplicationStub';
|
|
||||||
|
|
||||||
describe('ApplicationContextProvider', () => {
|
|
||||||
describe('buildContext', () => {
|
|
||||||
it('sets application from parser', () => {
|
|
||||||
// arrange
|
|
||||||
const expected = new ApplicationStub().withCollection(
|
|
||||||
new CategoryCollectionStub().withOs(OperatingSystem.macOS));
|
|
||||||
const parserMock: ApplicationParserType = () => expected;
|
|
||||||
// act
|
|
||||||
const context = buildContext(parserMock);
|
|
||||||
// assert
|
|
||||||
expect(expected).to.equal(context.app);
|
|
||||||
});
|
|
||||||
describe('sets initial OS as expected', () => {
|
|
||||||
it('returns currentOs if it is supported', () => {
|
|
||||||
// arrange
|
|
||||||
const expected = OperatingSystem.Windows;
|
|
||||||
const environment = new EnvironmentStub().withOs(expected);
|
|
||||||
const parser = mockParser(new CategoryCollectionStub().withOs(expected));
|
|
||||||
// act
|
|
||||||
const context = buildContext(parser, environment);
|
|
||||||
// assert
|
|
||||||
const actual = context.state.os;
|
|
||||||
expect(expected).to.equal(actual);
|
|
||||||
});
|
|
||||||
it('fallbacks to other os if OS in environment is not supported', () => {
|
|
||||||
// arrange
|
|
||||||
const expected = OperatingSystem.Windows;
|
|
||||||
const currentOs = OperatingSystem.macOS;
|
|
||||||
const environment = new EnvironmentStub().withOs(currentOs);
|
|
||||||
const parser = mockParser(new CategoryCollectionStub().withOs(expected));
|
|
||||||
// act
|
|
||||||
const context = buildContext(parser, environment);
|
|
||||||
// assert
|
|
||||||
const actual = context.state.os;
|
|
||||||
expect(expected).to.equal(actual);
|
|
||||||
});
|
|
||||||
it('fallbacks to most supported os if current os is not supported', () => {
|
|
||||||
// arrange
|
|
||||||
const expectedOs = OperatingSystem.Android;
|
|
||||||
const allCollections = [
|
|
||||||
new CategoryCollectionStub().withOs(OperatingSystem.Linux).withTotalScripts(3),
|
|
||||||
new CategoryCollectionStub().withOs(expectedOs).withTotalScripts(5),
|
|
||||||
new CategoryCollectionStub().withOs(OperatingSystem.Windows).withTotalScripts(4),
|
|
||||||
];
|
|
||||||
const environment = new EnvironmentStub().withOs(OperatingSystem.macOS);
|
|
||||||
const app = new ApplicationStub().withCollections(...allCollections);
|
|
||||||
const parser: ApplicationParserType = () => app;
|
|
||||||
// act
|
|
||||||
const context = buildContext(parser, environment);
|
|
||||||
// assert
|
|
||||||
const actual = context.state.os;
|
|
||||||
expect(expectedOs).to.equal(actual, `Expected: ${OperatingSystem[expectedOs]}, actual: ${OperatingSystem[actual]}`);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function mockParser(result: ICategoryCollection): ApplicationParserType {
|
|
||||||
return () => new ApplicationStub().withCollection(result);
|
|
||||||
}
|
|
||||||
@@ -10,6 +10,10 @@ import { mockEnumParser } from '../../stubs/EnumParserStub';
|
|||||||
import { ProjectInformationStub } from '../../stubs/ProjectInformationStub';
|
import { ProjectInformationStub } from '../../stubs/ProjectInformationStub';
|
||||||
import { getCategoryStub, CollectionDataStub } from '../../stubs/CollectionDataStub';
|
import { getCategoryStub, CollectionDataStub } from '../../stubs/CollectionDataStub';
|
||||||
import { CategoryCollectionParseContextStub } from '../../stubs/CategoryCollectionParseContextStub';
|
import { CategoryCollectionParseContextStub } from '../../stubs/CategoryCollectionParseContextStub';
|
||||||
|
import { CategoryDataStub } from '../../stubs/CategoryDataStub';
|
||||||
|
import { ScriptDataStub } from '../../stubs/ScriptDataStub';
|
||||||
|
import { FunctionDataStub } from '../../stubs/FunctionDataStub';
|
||||||
|
import { RecommendationLevel } from '../../../../src/domain/RecommendationLevel';
|
||||||
|
|
||||||
describe('CategoryCollectionParser', () => {
|
describe('CategoryCollectionParser', () => {
|
||||||
describe('parseCategoryCollection', () => {
|
describe('parseCategoryCollection', () => {
|
||||||
@@ -93,5 +97,35 @@ describe('CategoryCollectionParser', () => {
|
|||||||
expect(actual.os).to.equal(expectedOs);
|
expect(actual.os).to.equal(expectedOs);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('functions', () => {
|
||||||
|
it('compiles script call with given function', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedCode = 'code-from-the-function';
|
||||||
|
const functionName = 'function-name';
|
||||||
|
const scriptName = 'script-name';
|
||||||
|
const script = ScriptDataStub.createWithCall({ function: functionName })
|
||||||
|
.withName(scriptName);
|
||||||
|
const func = new FunctionDataStub()
|
||||||
|
.withName(functionName)
|
||||||
|
.withCode(expectedCode);
|
||||||
|
const category = new CategoryDataStub()
|
||||||
|
.withChildren([ script,
|
||||||
|
ScriptDataStub.createWithCode().withName('2')
|
||||||
|
.withRecommendationLevel(RecommendationLevel.Standard),
|
||||||
|
ScriptDataStub.createWithCode()
|
||||||
|
.withName('3').withRecommendationLevel(RecommendationLevel.Strict),
|
||||||
|
]);
|
||||||
|
const collection = new CollectionDataStub()
|
||||||
|
.withActions([ category ])
|
||||||
|
.withFunctions([ func ]);
|
||||||
|
const info = new ProjectInformationStub();
|
||||||
|
// act
|
||||||
|
const actual = parseCategoryCollection(collection, info);
|
||||||
|
// assert
|
||||||
|
const actualScript = actual.findScript(scriptName);
|
||||||
|
const actualCode = actualScript.code.execute;
|
||||||
|
expect(actualCode).to.equal(expectedCode);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import 'mocha';
|
import 'mocha';
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { parseCategory } from '@/application/Parser/CategoryParser';
|
import { parseCategory } from '@/application/Parser/CategoryParser';
|
||||||
import { CategoryData, CategoryOrScriptData } from 'js-yaml-loader!@/*';
|
|
||||||
import { parseScript } from '@/application/Parser/Script/ScriptParser';
|
import { parseScript } from '@/application/Parser/Script/ScriptParser';
|
||||||
import { parseDocUrls } from '@/application/Parser/DocumentationParser';
|
import { parseDocUrls } from '@/application/Parser/DocumentationParser';
|
||||||
import { ScriptCompilerStub } from '../../stubs/ScriptCompilerStub';
|
import { ScriptCompilerStub } from '../../stubs/ScriptCompilerStub';
|
||||||
import { ScriptDataStub } from '../../stubs/ScriptDataStub';
|
import { ScriptDataStub } from '../../stubs/ScriptDataStub';
|
||||||
import { CategoryCollectionParseContextStub } from '../../stubs/CategoryCollectionParseContextStub';
|
import { CategoryCollectionParseContextStub } from '../../stubs/CategoryCollectionParseContextStub';
|
||||||
import { LanguageSyntaxStub } from '../../stubs/LanguageSyntaxStub';
|
import { LanguageSyntaxStub } from '../../stubs/LanguageSyntaxStub';
|
||||||
|
import { CategoryDataStub } from '../../stubs/CategoryDataStub';
|
||||||
|
|
||||||
describe('CategoryParser', () => {
|
describe('CategoryParser', () => {
|
||||||
describe('parseCategory', () => {
|
describe('parseCategory', () => {
|
||||||
@@ -26,10 +26,9 @@ describe('CategoryParser', () => {
|
|||||||
// arrange
|
// arrange
|
||||||
const categoryName = 'test';
|
const categoryName = 'test';
|
||||||
const expectedMessage = `category has no children: "${categoryName}"`;
|
const expectedMessage = `category has no children: "${categoryName}"`;
|
||||||
const category: CategoryData = {
|
const category = new CategoryDataStub()
|
||||||
category: categoryName,
|
.withName(categoryName)
|
||||||
children: [],
|
.withChildren([]);
|
||||||
};
|
|
||||||
const context = new CategoryCollectionParseContextStub();
|
const context = new CategoryCollectionParseContextStub();
|
||||||
// act
|
// act
|
||||||
const act = () => parseCategory(category, context);
|
const act = () => parseCategory(category, context);
|
||||||
@@ -40,10 +39,9 @@ describe('CategoryParser', () => {
|
|||||||
// arrange
|
// arrange
|
||||||
const categoryName = 'test';
|
const categoryName = 'test';
|
||||||
const expectedMessage = `category has no children: "${categoryName}"`;
|
const expectedMessage = `category has no children: "${categoryName}"`;
|
||||||
const category: CategoryData = {
|
const category = new CategoryDataStub()
|
||||||
category: categoryName,
|
.withName(categoryName)
|
||||||
children: undefined,
|
.withChildren(undefined);
|
||||||
};
|
|
||||||
const context = new CategoryCollectionParseContextStub();
|
const context = new CategoryCollectionParseContextStub();
|
||||||
// act
|
// act
|
||||||
const act = () => parseCategory(category, context);
|
const act = () => parseCategory(category, context);
|
||||||
@@ -55,10 +53,8 @@ describe('CategoryParser', () => {
|
|||||||
const expectedMessage = 'category has no name';
|
const expectedMessage = 'category has no name';
|
||||||
const invalidNames = ['', undefined];
|
const invalidNames = ['', undefined];
|
||||||
invalidNames.forEach((invalidName) => {
|
invalidNames.forEach((invalidName) => {
|
||||||
const category: CategoryData = {
|
const category = new CategoryDataStub()
|
||||||
category: invalidName,
|
.withName(invalidName);
|
||||||
children: getTestChildren(),
|
|
||||||
};
|
|
||||||
const context = new CategoryCollectionParseContextStub();
|
const context = new CategoryCollectionParseContextStub();
|
||||||
// act
|
// act
|
||||||
const act = () => parseCategory(category, context);
|
const act = () => parseCategory(category, context);
|
||||||
@@ -71,7 +67,7 @@ describe('CategoryParser', () => {
|
|||||||
// arrange
|
// arrange
|
||||||
const expectedError = 'undefined context';
|
const expectedError = 'undefined context';
|
||||||
const context = undefined;
|
const context = undefined;
|
||||||
const category = getValidCategory();
|
const category = new CategoryDataStub();
|
||||||
// act
|
// act
|
||||||
const act = () => parseCategory(category, context);
|
const act = () => parseCategory(category, context);
|
||||||
// assert
|
// assert
|
||||||
@@ -81,11 +77,8 @@ describe('CategoryParser', () => {
|
|||||||
// arrange
|
// arrange
|
||||||
const url = 'https://privacy.sexy';
|
const url = 'https://privacy.sexy';
|
||||||
const expected = parseDocUrls({ docs: url });
|
const expected = parseDocUrls({ docs: url });
|
||||||
const category: CategoryData = {
|
const category = new CategoryDataStub()
|
||||||
category: 'category name',
|
.withDocs(url);
|
||||||
children: getTestChildren(),
|
|
||||||
docs: url,
|
|
||||||
};
|
|
||||||
const context = new CategoryCollectionParseContextStub();
|
const context = new CategoryCollectionParseContextStub();
|
||||||
// act
|
// act
|
||||||
const actual = parseCategory(category, context).documentationUrls;
|
const actual = parseCategory(category, context).documentationUrls;
|
||||||
@@ -98,10 +91,8 @@ describe('CategoryParser', () => {
|
|||||||
const script = ScriptDataStub.createWithCode();
|
const script = ScriptDataStub.createWithCode();
|
||||||
const context = new CategoryCollectionParseContextStub();
|
const context = new CategoryCollectionParseContextStub();
|
||||||
const expected = [ parseScript(script, context) ];
|
const expected = [ parseScript(script, context) ];
|
||||||
const category: CategoryData = {
|
const category = new CategoryDataStub()
|
||||||
category: 'category name',
|
.withChildren([ script ]);
|
||||||
children: [ script ],
|
|
||||||
};
|
|
||||||
// act
|
// act
|
||||||
const actual = parseCategory(category, context).scripts;
|
const actual = parseCategory(category, context).scripts;
|
||||||
// assert
|
// assert
|
||||||
@@ -115,10 +106,8 @@ describe('CategoryParser', () => {
|
|||||||
const context = new CategoryCollectionParseContextStub()
|
const context = new CategoryCollectionParseContextStub()
|
||||||
.withCompiler(compiler);
|
.withCompiler(compiler);
|
||||||
const expected = [ parseScript(script, context) ];
|
const expected = [ parseScript(script, context) ];
|
||||||
const category: CategoryData = {
|
const category = new CategoryDataStub()
|
||||||
category: 'category name',
|
.withChildren([ script ]);
|
||||||
children: [ script ],
|
|
||||||
};
|
|
||||||
// act
|
// act
|
||||||
const actual = parseCategory(category, context).scripts;
|
const actual = parseCategory(category, context).scripts;
|
||||||
// assert
|
// assert
|
||||||
@@ -128,10 +117,8 @@ describe('CategoryParser', () => {
|
|||||||
// arrange
|
// arrange
|
||||||
const callableScript = ScriptDataStub.createWithCall();
|
const callableScript = ScriptDataStub.createWithCall();
|
||||||
const scripts = [ callableScript, ScriptDataStub.createWithCode() ];
|
const scripts = [ callableScript, ScriptDataStub.createWithCode() ];
|
||||||
const category: CategoryData = {
|
const category = new CategoryDataStub()
|
||||||
category: 'category name',
|
.withChildren(scripts);
|
||||||
children: scripts,
|
|
||||||
};
|
|
||||||
const compiler = new ScriptCompilerStub()
|
const compiler = new ScriptCompilerStub()
|
||||||
.withCompileAbility(callableScript);
|
.withCompileAbility(callableScript);
|
||||||
const context = new CategoryCollectionParseContextStub()
|
const context = new CategoryCollectionParseContextStub()
|
||||||
@@ -148,19 +135,16 @@ describe('CategoryParser', () => {
|
|||||||
const duplicatedCode = `${commentDelimiter} duplicate-line\n${commentDelimiter} duplicate-line`;
|
const duplicatedCode = `${commentDelimiter} duplicate-line\n${commentDelimiter} duplicate-line`;
|
||||||
const parseContext = new CategoryCollectionParseContextStub()
|
const parseContext = new CategoryCollectionParseContextStub()
|
||||||
.withSyntax(new LanguageSyntaxStub().withCommentDelimiters(commentDelimiter));
|
.withSyntax(new LanguageSyntaxStub().withCommentDelimiters(commentDelimiter));
|
||||||
const category: CategoryData = {
|
const category = new CategoryDataStub()
|
||||||
category: 'category name',
|
.withChildren([
|
||||||
children: [
|
new CategoryDataStub()
|
||||||
{
|
.withName('sub-category')
|
||||||
category: 'sub-category',
|
.withChildren([
|
||||||
children: [
|
|
||||||
ScriptDataStub
|
ScriptDataStub
|
||||||
.createWithoutCallOrCodes()
|
.createWithoutCallOrCodes()
|
||||||
.withCode(duplicatedCode),
|
.withCode(duplicatedCode),
|
||||||
],
|
]),
|
||||||
},
|
]);
|
||||||
],
|
|
||||||
};
|
|
||||||
// act
|
// act
|
||||||
const act = () => parseCategory(category, parseContext).scripts;
|
const act = () => parseCategory(category, parseContext).scripts;
|
||||||
// assert
|
// assert
|
||||||
@@ -169,14 +153,13 @@ describe('CategoryParser', () => {
|
|||||||
});
|
});
|
||||||
it('returns expected subcategories', () => {
|
it('returns expected subcategories', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const expected: CategoryData[] = [ {
|
const expected = [ new CategoryDataStub()
|
||||||
category: 'test category',
|
.withName('test category')
|
||||||
children: [ ScriptDataStub.createWithCode() ],
|
.withChildren([ ScriptDataStub.createWithCode() ]),
|
||||||
}];
|
];
|
||||||
const category: CategoryData = {
|
const category = new CategoryDataStub()
|
||||||
category: 'category name',
|
.withName('category name')
|
||||||
children: expected,
|
.withChildren(expected);
|
||||||
};
|
|
||||||
const context = new CategoryCollectionParseContextStub();
|
const context = new CategoryCollectionParseContextStub();
|
||||||
// act
|
// act
|
||||||
const actual = parseCategory(category, context).subCategories;
|
const actual = parseCategory(category, context).subCategories;
|
||||||
@@ -187,17 +170,3 @@ describe('CategoryParser', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function getValidCategory(): CategoryData {
|
|
||||||
return {
|
|
||||||
category: 'category name',
|
|
||||||
children: getTestChildren(),
|
|
||||||
docs: undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTestChildren(): ReadonlyArray<CategoryOrScriptData> {
|
|
||||||
return [
|
|
||||||
ScriptDataStub.createWithCode(),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -206,6 +206,26 @@ describe('ScriptCompiler', () => {
|
|||||||
expect(act).to.throw(expectedError);
|
expect(act).to.throw(expectedError);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
it('throws if provided parameters does not match given ones', () => {
|
||||||
|
// arrange
|
||||||
|
const unexpectedParameterName = 'unexpected-parameter-name';
|
||||||
|
const functionName = 'test-function-name';
|
||||||
|
const expectedError = `function "${functionName}" has unexpected parameter(s) provided: "${unexpectedParameterName}"`;
|
||||||
|
const sut = new ScriptCompilerBuilder()
|
||||||
|
.withFunctions(
|
||||||
|
new FunctionDataStub()
|
||||||
|
.withName(functionName)
|
||||||
|
.withParameters('another-parameter'))
|
||||||
|
.build();
|
||||||
|
const params: FunctionCallParametersData = {};
|
||||||
|
params[unexpectedParameterName] = 'unexpected-parameter-value';
|
||||||
|
const call: ScriptFunctionCallData = { function: functionName, parameters: params };
|
||||||
|
const script = ScriptDataStub.createWithCall(call);
|
||||||
|
// act
|
||||||
|
const act = () => sut.compile(script);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
describe('builds code as expected', () => {
|
describe('builds code as expected', () => {
|
||||||
it('creates code with expected syntax', () => { // test through script validation logic
|
it('creates code with expected syntax', () => { // test through script validation logic
|
||||||
@@ -304,7 +324,7 @@ describe('ScriptCompiler', () => {
|
|||||||
expect(actual).to.deep.equal(expected);
|
expect(actual).to.deep.equal(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('throws when parameters is undefined', () => {
|
it('throws when parameters are undefined', () => {
|
||||||
// arrange
|
// arrange
|
||||||
const env = new TestEnvironment({
|
const env = new TestEnvironment({
|
||||||
code: '{{ $parameter }} {{ $parameter }}!',
|
code: '{{ $parameter }} {{ $parameter }}!',
|
||||||
|
|||||||
@@ -1,45 +1,43 @@
|
|||||||
import { ISignal } from '@/infrastructure/Events/ISignal';
|
import { EventHandler, IEventSource, IEventSubscription } from '@/infrastructure/Events/IEventSource';
|
||||||
import { IEventSubscription } from '@/infrastructure/Events/ISubscription';
|
import { EventSource } from '@/infrastructure/Events/EventSource';
|
||||||
import { Signal } from '@/infrastructure/Events/Signal';
|
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { EventHandler } from '@/infrastructure/Events/ISignal';
|
import 'mocha';
|
||||||
|
|
||||||
|
describe('EventSource', () => {
|
||||||
describe('Signal', () => {
|
|
||||||
class ObserverMock {
|
class ObserverMock {
|
||||||
public readonly onReceiveCalls = new Array<number>();
|
public readonly onReceiveCalls = new Array<number>();
|
||||||
public readonly callbacks = new Array<EventHandler<number>>();
|
public readonly callbacks = new Array<EventHandler<number>>();
|
||||||
public readonly subscription: IEventSubscription;
|
public readonly subscription: IEventSubscription;
|
||||||
constructor(subject: ISignal<number>) {
|
constructor(subject: IEventSource<number>) {
|
||||||
this.callbacks.push((arg) => this.onReceiveCalls.push(arg));
|
this.callbacks.push((arg) => this.onReceiveCalls.push(arg));
|
||||||
this.subscription = subject.on((arg) => this.callbacks.forEach((action) => action(arg)));
|
this.subscription = subject.on((arg) => this.callbacks.forEach((action) => action(arg)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let signal: Signal<number>;
|
let sut: EventSource<number>;
|
||||||
beforeEach(() => signal = new Signal());
|
beforeEach(() => sut = new EventSource());
|
||||||
describe('single observer', () => {
|
describe('single observer', () => {
|
||||||
// arrange
|
// arrange
|
||||||
let observer: ObserverMock;
|
let observer: ObserverMock;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
observer = new ObserverMock(signal);
|
observer = new ObserverMock(sut);
|
||||||
});
|
});
|
||||||
it('notify() executes the callback', () => {
|
it('notify() executes the callback', () => {
|
||||||
// act
|
// act
|
||||||
signal.notify(5);
|
sut.notify(5);
|
||||||
// assert
|
// assert
|
||||||
expect(observer.onReceiveCalls).to.have.length(1);
|
expect(observer.onReceiveCalls).to.have.length(1);
|
||||||
});
|
});
|
||||||
it('notify() executes the callback with the payload', () => {
|
it('notify() executes the callback with the payload', () => {
|
||||||
const expected = 5;
|
const expected = 5;
|
||||||
// act
|
// act
|
||||||
signal.notify(expected);
|
sut.notify(expected);
|
||||||
// assert
|
// assert
|
||||||
expect(observer.onReceiveCalls).to.deep.equal([expected]);
|
expect(observer.onReceiveCalls).to.deep.equal([expected]);
|
||||||
});
|
});
|
||||||
it('notify() does not call callback when unsubscribed', () => {
|
it('notify() does not call callback when unsubscribed', () => {
|
||||||
// act
|
// act
|
||||||
observer.subscription.unsubscribe();
|
observer.subscription.unsubscribe();
|
||||||
signal.notify(5);
|
sut.notify(5);
|
||||||
// assert
|
// assert
|
||||||
expect(observer.onReceiveCalls).to.have.lengthOf(0);
|
expect(observer.onReceiveCalls).to.have.lengthOf(0);
|
||||||
});
|
});
|
||||||
@@ -50,13 +48,13 @@ describe('Signal', () => {
|
|||||||
let observers: ObserverMock[];
|
let observers: ObserverMock[];
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
observers = [
|
observers = [
|
||||||
new ObserverMock(signal), new ObserverMock(signal),
|
new ObserverMock(sut), new ObserverMock(sut),
|
||||||
new ObserverMock(signal), new ObserverMock(signal),
|
new ObserverMock(sut), new ObserverMock(sut),
|
||||||
];
|
];
|
||||||
});
|
});
|
||||||
it('notify() should execute all callbacks', () => {
|
it('notify() should execute all callbacks', () => {
|
||||||
// act
|
// act
|
||||||
signal.notify(5);
|
sut.notify(5);
|
||||||
// assert
|
// assert
|
||||||
observers.forEach((observer) => {
|
observers.forEach((observer) => {
|
||||||
expect(observer.onReceiveCalls).to.have.length(1);
|
expect(observer.onReceiveCalls).to.have.length(1);
|
||||||
@@ -65,7 +63,7 @@ describe('Signal', () => {
|
|||||||
it('notify() should execute all callbacks with payload', () => {
|
it('notify() should execute all callbacks with payload', () => {
|
||||||
const expected = 5;
|
const expected = 5;
|
||||||
// act
|
// act
|
||||||
signal.notify(expected);
|
sut.notify(expected);
|
||||||
// assert
|
// assert
|
||||||
observers.forEach((observer) => {
|
observers.forEach((observer) => {
|
||||||
expect(observer.onReceiveCalls).to.deep.equal([expected]);
|
expect(observer.onReceiveCalls).to.deep.equal([expected]);
|
||||||
@@ -79,7 +77,7 @@ describe('Signal', () => {
|
|||||||
observers[i].callbacks.push(() => actualSequence.push(i));
|
observers[i].callbacks.push(() => actualSequence.push(i));
|
||||||
}
|
}
|
||||||
// act
|
// act
|
||||||
signal.notify(5);
|
sut.notify(5);
|
||||||
// assert
|
// assert
|
||||||
expect(actualSequence).to.deep.equal(expectedSequence);
|
expect(actualSequence).to.deep.equal(expectedSequence);
|
||||||
});
|
});
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { EventSubscriptionCollection } from '@/infrastructure/Events/EventSubscriptionCollection';
|
||||||
|
import { IEventSubscription } from '@/infrastructure/Events/IEventSource';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import 'mocha';
|
||||||
|
|
||||||
|
describe('EventSubscriptionCollection', () => {
|
||||||
|
it('unsubscribeAll unsubscribes from all registered subscriptions', () => {
|
||||||
|
// arrange
|
||||||
|
const sut = new EventSubscriptionCollection();
|
||||||
|
const expected = [ 'unsubscribed1', 'unsubscribed2'];
|
||||||
|
const actual = new Array<string>();
|
||||||
|
const subscriptions: IEventSubscription[] = [
|
||||||
|
{ unsubscribe: () => actual.push(expected[0]) },
|
||||||
|
{ unsubscribe: () => actual.push(expected[1]) },
|
||||||
|
];
|
||||||
|
// act
|
||||||
|
sut.register(...subscriptions);
|
||||||
|
sut.unsubscribeAll();
|
||||||
|
// assert
|
||||||
|
expect(actual).to.deep.equal(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import 'mocha';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { NonCollapsing } from '@/presentation/Scripts/Cards/NonCollapsingDirective';
|
||||||
|
import { hasDirective } from '@/presentation/Scripts/Cards/NonCollapsingDirective';
|
||||||
|
|
||||||
|
const expectedAttributeName = 'data-interaction-does-not-collapse';
|
||||||
|
|
||||||
|
describe('NonCollapsingDirective', () => {
|
||||||
|
describe('NonCollapsing', () => {
|
||||||
|
it('adds expected attribute to the element when inserted', () => {
|
||||||
|
// arrange
|
||||||
|
const element = getElementMock();
|
||||||
|
// act
|
||||||
|
NonCollapsing.inserted(element, undefined, undefined, undefined);
|
||||||
|
// assert
|
||||||
|
expect(element.hasAttribute(expectedAttributeName));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('hasDirective', () => {
|
||||||
|
it('returns true if the element has expected attribute', () => {
|
||||||
|
// arrange
|
||||||
|
const element = getElementMock();
|
||||||
|
element.setAttribute(expectedAttributeName, undefined);
|
||||||
|
// act
|
||||||
|
const actual = hasDirective(element);
|
||||||
|
// assert
|
||||||
|
expect(actual).to.equal(true);
|
||||||
|
});
|
||||||
|
it('returns true if the element has a parent with expected attribute', () => {
|
||||||
|
// arrange
|
||||||
|
const parent = getElementMock();
|
||||||
|
const element = getElementMock();
|
||||||
|
parent.appendChild(element);
|
||||||
|
element.setAttribute(expectedAttributeName, undefined);
|
||||||
|
// act
|
||||||
|
const actual = hasDirective(element);
|
||||||
|
// assert
|
||||||
|
expect(actual).to.equal(true);
|
||||||
|
});
|
||||||
|
it('returns false if nor the element or its parent has expected attribute', () => {
|
||||||
|
// arrange
|
||||||
|
const element = getElementMock();
|
||||||
|
// act
|
||||||
|
const actual = hasDirective(element);
|
||||||
|
// assert
|
||||||
|
expect(actual).to.equal(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function getElementMock(): HTMLElement {
|
||||||
|
const element = document.createElement('div');
|
||||||
|
return element;
|
||||||
|
}
|
||||||
21
tests/unit/stubs/CategoryDataStub.ts
Normal file
21
tests/unit/stubs/CategoryDataStub.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import { CategoryData, CategoryOrScriptData, DocumentationUrlsData } from 'js-yaml-loader!@/*';
|
||||||
|
import { ScriptDataStub } from './ScriptDataStub';
|
||||||
|
|
||||||
|
export class CategoryDataStub implements CategoryData {
|
||||||
|
public children: readonly CategoryOrScriptData[] = [ ScriptDataStub.createWithCode() ];
|
||||||
|
public category = 'category name';
|
||||||
|
public docs?: DocumentationUrlsData;
|
||||||
|
|
||||||
|
public withChildren(children: readonly CategoryOrScriptData[]) {
|
||||||
|
this.children = children;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public withName(name: string) {
|
||||||
|
this.category = name;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
public withDocs(docs: DocumentationUrlsData) {
|
||||||
|
this.docs = docs;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,26 +1,29 @@
|
|||||||
import { RecommendationLevel } from '@/domain/RecommendationLevel';
|
import { RecommendationLevel } from '@/domain/RecommendationLevel';
|
||||||
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
||||||
import { CategoryData, ScriptData, CollectionData, ScriptingDefinitionData } from 'js-yaml-loader!@/*';
|
import { CategoryData, ScriptData, CollectionData, ScriptingDefinitionData, FunctionData } from 'js-yaml-loader!@/*';
|
||||||
|
|
||||||
export class CollectionDataStub implements CollectionData {
|
export class CollectionDataStub implements CollectionData {
|
||||||
public os = 'windows';
|
public os = 'windows';
|
||||||
public actions: readonly CategoryData[] = [ getCategoryStub() ];
|
public actions: readonly CategoryData[] = [ getCategoryStub() ];
|
||||||
public scripting: ScriptingDefinitionData = getTestDefinitionStub();
|
public scripting: ScriptingDefinitionData = getTestDefinitionStub();
|
||||||
|
public functions?: ReadonlyArray<FunctionData>;
|
||||||
|
|
||||||
public withActions(actions: readonly CategoryData[]): CollectionDataStub {
|
public withActions(actions: readonly CategoryData[]): CollectionDataStub {
|
||||||
this.actions = actions;
|
this.actions = actions;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public withOs(os: string): CollectionDataStub {
|
public withOs(os: string): CollectionDataStub {
|
||||||
this.os = os;
|
this.os = os;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public withScripting(scripting: ScriptingDefinitionData): CollectionDataStub {
|
public withScripting(scripting: ScriptingDefinitionData): CollectionDataStub {
|
||||||
this.scripting = scripting;
|
this.scripting = scripting;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
public withFunctions(functions: ReadonlyArray<FunctionData>) {
|
||||||
|
this.functions = functions;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCategoryStub(scriptPrefix = 'testScript'): CategoryData {
|
export function getCategoryStub(scriptPrefix = 'testScript'): CategoryData {
|
||||||
|
|||||||
@@ -55,4 +55,8 @@ export class ScriptDataStub implements ScriptData {
|
|||||||
this.recommend = recommend;
|
this.recommend = recommend;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
public withRecommendationLevel(level: RecommendationLevel): ScriptDataStub {
|
||||||
|
this.recommend = RecommendationLevel[level].toLowerCase();
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
"allowSyntheticDefaultImports": true,
|
"allowSyntheticDefaultImports": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
|
|||||||
Reference in New Issue
Block a user