├── .editorconfig ├── .eslintrc ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── BUG_REPORT.md │ └── FEATURE_REQUEST.md ├── PULL_REQUEST_TEMPLATE.md ├── semantic.yml └── workflows │ ├── release.yml │ └── validate-code.yml ├── .gitignore ├── .kopytkorc ├── .nvmrc ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs ├── cache.md ├── event-bus.md ├── http.md ├── modal.md ├── registry.md ├── renderer.md ├── router.md ├── store.md ├── theme.md └── versions-migration-guide.md ├── manifest.js ├── package-lock.json ├── package.json └── src └── components ├── cache ├── Cache.brs ├── Cache.facade.brs ├── Cache.xml ├── CacheCleaner.brs ├── CacheItem.xml ├── CacheReader.brs ├── CacheScope.xml ├── CacheWriter.brs ├── _tests │ ├── CacheCleaner.test.brs │ ├── CacheFacade │ │ ├── CacheFacade.test.brs │ │ ├── CacheFacade_clear.test.brs │ │ ├── CacheFacade_read.test.brs │ │ └── CacheFacade_write.test.brs │ ├── CacheReader.test.brs │ ├── CacheWriter.test.brs │ └── generateCacheKey.test.brs ├── generateCacheKey.brs └── policies │ ├── CachingPolicies.const.brs │ ├── DefaultCachingPolicy.brs │ ├── ExhaustibleCachingPolicy.brs │ ├── ExpirableCachingPolicy.brs │ ├── _tests │ ├── DefaultCachingPolicy.test.brs │ ├── ExhaustibleCachingPolicy.test.brs │ ├── ExpirableCachingPolicy.test.brs │ ├── getCachingPolicies.test.brs │ └── resolveCachingPolicy.test.brs │ ├── getCachingPolicies.brs │ └── resolveCachingPolicy.brs ├── eventBus ├── EventBus.facade.brs ├── EventBus.xml └── _tests │ ├── EventBusFacade.test.brs │ ├── EventBusFacade_Off.test.brs │ ├── EventBusFacade_On.test.brs │ └── EventBusFacade_Trigger.test.brs ├── http ├── HttpInterceptor.brs ├── HttpRequest.brs ├── HttpResponse.brs ├── HttpResponse.model.xml ├── HttpResponseCreator.brs ├── HttpService.brs ├── HttpStatusCodes.const.brs ├── _mocks │ └── UrlEvent.mock.brs ├── _tests │ ├── HttpRequest.test.brs │ ├── HttpResponse.test.brs │ ├── HttpResponse_Main.test.brs │ ├── HttpResponse_getMaxAge.test.brs │ ├── HttpResponse_isReusable.test.brs │ ├── HttpService.test.brs │ ├── HttpService_abort.test.brs │ ├── HttpService_fetch.test.brs │ ├── HttpService_storeCachedResponse.test.brs │ └── HttpService_timeout.test.brs ├── cache │ ├── CachedHttpResponse.brs │ ├── HttpCache.brs │ ├── _mocks │ │ └── CachedHttpResponse.mock.brs │ └── _tests │ │ ├── CachedHttpResponse.test.brs │ │ ├── CachedHttpResponse_setRevalidatedCache.test.brs │ │ └── HttpCache.test.brs └── request │ ├── AbortController.brs │ ├── AbortSignal.xml │ ├── Http.request.brs │ ├── Http.request.xml │ ├── HttpRequestResult.model.xml │ ├── Request.brs │ ├── Request.xml │ ├── RequestError.model.xml │ ├── _mocks │ ├── Request.mock.brs │ ├── Request.mock.xml │ └── createRequest.config.brs │ ├── _tests │ └── createRequest.test.brs │ └── createRequest.brs ├── modal ├── Modal.component.brs ├── Modal.component.xml ├── Modal.template.brs ├── ModalEvents.const.brs ├── _mocks │ └── Modal.mock.xml └── _tests │ └── Modal.test.brs ├── registry ├── Registry.facade.brs └── _tests │ └── RegistryFacade.test.brs ├── renderer ├── Kopytko.brs ├── KopytkoDOM.brs ├── KopytkoDiffUtility.brs ├── KopytkoGroup.xml ├── KopytkoLayoutGroup.xml ├── KopytkoUpdater.brs ├── _mocks │ ├── Kopytko.mock.xml │ ├── KopytkoGroup.mock.brs │ └── KopytkoGroup.mock.xml ├── _tests │ ├── KopytkoUpdater.test.brs │ ├── kopytkoDOM │ │ ├── KopytkoDOM.test.brs │ │ ├── KopytkoDOM_renderElement.test.brs │ │ ├── KopytkoDOM_updateDOM.test.brs │ │ ├── KopytkoTestExample.view.brs │ │ └── KopytkoTestExample.view.xml │ ├── kopytkoDiffUtility │ │ ├── KopytkoDiffUtility.test.brs │ │ └── KopytkoDiffUtility_Main.test.brs │ ├── kopytkoGroup │ │ ├── KopytkoDidMountTestExample.component.brs │ │ ├── KopytkoDidMountTestExample.component.xml │ │ ├── KopytkoGroup.test.brs │ │ ├── KopytkoGroup_Callback.test.brs │ │ └── KopytkoGroup_Main.test.brs │ └── kopytkoRoot │ │ ├── KopytkoRootTestExample.component.brs │ │ ├── KopytkoRootTestExample.component.xml │ │ └── kopytkoRoot.test.brs └── kopytkoRoot.brs ├── router ├── ActivatedRoute.xml ├── Middleware.brs ├── Middleware.xml ├── Router.brs ├── Router.facade.brs ├── Router.xml ├── RouterOutlet.component.brs ├── RouterOutlet.component.xml ├── RouterOutlet.template.brs ├── Routes.brs ├── _mocks │ └── RouterOutlet.mock.xml ├── _tests │ ├── Router.test.brs │ ├── RouterOutlet │ │ ├── AnotherTestExample.view.xml │ │ ├── OtherTestExample.view.xml │ │ ├── RouterOutlet.test.brs │ │ ├── RouterOutlet_Focus.test.brs │ │ ├── RouterOutlet_Init.test.brs │ │ ├── RouterOutlet_UrlChange.test.brs │ │ └── TestExample.view.xml │ └── Routes.test.brs └── utils │ ├── _tests │ └── buildPath.test.brs │ └── buildPath.brs ├── store ├── Store.facade.brs └── _tests │ ├── StoreFacade.test.brs │ ├── StoreFacade_Main.test.brs │ ├── StoreFacade_hasKey.test.brs │ ├── StoreFacade_set.test.brs │ ├── StoreFacade_setFields.test.brs │ ├── StoreFacade_subscribe.test.brs │ ├── StoreFacade_subscribeOnce.test.brs │ ├── StoreFacade_unsubscribe.test.brs │ └── StoreFacade_updateAA.test.brs ├── theme ├── Theme.brs ├── Theme.facade.brs ├── Theme.xml ├── _mocks │ └── Theme.facade.mock.brs └── _tests │ └── Theme.test.brs └── utils ├── KopytkoGlobalNode.brs ├── _mocks └── KopytkoGlobalNode.mock.brs ├── _tests └── imfFixdateToSeconds.test.brs ├── imfFixdateToSeconds.brs └── kopytkoWait.brs /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = UTF-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "plugin:@dazn/kopytko/recommended", 3 | "plugins": ["@dazn/kopytko"], 4 | "rules": { 5 | "@dazn/kopytko/dependencies-order": "error", 6 | "@dazn/kopytko/missing-trailing-comma": "error", 7 | "@dazn/kopytko/indent": ["error", 2], 8 | "@dazn/kopytko/no-print": "off", 9 | "@dazn/kopytko/no-stop": "error", 10 | "@dazn/kopytko/sub-to-function": "error", 11 | "@dazn/kopytko/function-no-return": "error", 12 | "@dazn/kopytko/no-uninitialized-variables": "off" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @getndazn/kopytko-team 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/BUG_REPORT.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | --- 5 | 6 | 14 | 15 | # This is a Bug Report 16 | 17 | ## Description 18 | 19 | * What went wrong? 20 | * What did you expect should have happened? 21 | * What was the config (if any) you used? 22 | * What stacktrace or error message did you see? 23 | 24 | Similar or dependent issues: 25 | * #12345 26 | 27 | ## Additional Data 28 | 29 | * ***Which version of node and npm are you using?***: 30 | * ***Operating System***: 31 | * ***Stack Trace***: 32 | * ***Provider Error messages***: 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for kopytko-framework 4 | --- 5 | 6 | 14 | 15 | # This is a Feature Proposal 16 | 17 | ## Description 18 | 19 | * What is the use case that should be solved. The more detail you describe this in the easier it is to understand for us. 20 | * If there is additional config how would it look? 21 | 22 | Similar or dependent issues: 23 | * #12345 24 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 6 | 7 | ## What did you implement: 8 | 9 | Closes #XXXXX 10 | 11 | 14 | 15 | ## How did you implement it: 16 | 17 | 20 | 21 | ## How can we verify it: 22 | 23 | 33 | 34 | ## Todos: 35 | 36 | - [ ] Write documentation (if required) 37 | - [ ] Fix linting errors 38 | - [ ] Enable "Allow edits from maintainers" for this PR 39 | - [ ] Update the messages below 40 | 41 | ***Is this ready for review?:*** NO 42 | ***Is it a breaking change?:*** NO 43 | -------------------------------------------------------------------------------- /.github/semantic.yml: -------------------------------------------------------------------------------- 1 | # Always validate the PR title AND all the commits 2 | titleAndCommits: true 3 | 4 | # Require at least one commit to be valid (overwrites validating ALL commits) 5 | anyCommit: true 6 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: 5 | - master 6 | jobs: 7 | release: 8 | runs-on: ubuntu-latest 9 | timeout-minutes: 10 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v3 13 | - name: Setup Node.js 14 | uses: actions/setup-node@v3 15 | with: 16 | node-version-file: '.nvmrc' 17 | cache: 'npm' 18 | - name: Semantic Release 19 | id: release 20 | uses: cycjimmy/semantic-release-action@v3 21 | env: 22 | GITHUB_TOKEN: ${{ github.token }} 23 | NPM_TOKEN: ${{ secrets.OSS_NPM_TOKEN }} 24 | with: 25 | extra_plugins: | 26 | @semantic-release/changelog@6 27 | @semantic-release/git@10 28 | - name: Teams notification 29 | uses: toko-bifrost/ms-teams-deploy-card@3.1.2 30 | if: always() 31 | with: 32 | github-token: ${{ github.token }} 33 | webhook-uri: ${{ secrets.TEAMS_WEBHOOK_URI }} 34 | show-on-start: false 35 | card-layout-exit: complete 36 | 37 | -------------------------------------------------------------------------------- /.github/workflows/validate-code.yml: -------------------------------------------------------------------------------- 1 | name: Validate code 2 | on: pull_request 3 | jobs: 4 | validate-code: 5 | runs-on: ubuntu-latest 6 | timeout-minutes: 10 7 | steps: 8 | - name: Checkout 9 | uses: actions/checkout@v3 10 | - name: Setup node 11 | uses: actions/setup-node@v3 12 | with: 13 | node-version-file: '.nvmrc' 14 | cache: 'npm' 15 | - name: Install Dependencies 16 | run: npm ci 17 | - name: Run linter 18 | run: npm run lint 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .env 3 | .idea/ 4 | .vscode/ 5 | 6 | dist/ 7 | node_modules/ 8 | -------------------------------------------------------------------------------- /.kopytkorc: -------------------------------------------------------------------------------- 1 | { 2 | "baseManifest": "./manifest.js", 3 | "sourceDir": "./src", 4 | "pluginDefinitions": { 5 | "generate-tests": "./node_modules/@dazn/kopytko-unit-testing-framework/plugins/generate-tests" 6 | }, 7 | "plugins": ["kopytko-copy-external-dependencies", "generate-tests"] 8 | } 9 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 16 2 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting our team at **opensource@dazn.com**. As an alternative 59 | feel free to reach out to any of us personally. All 60 | complaints will be reviewed and investigated and will result in a response that 61 | is deemed necessary and appropriate to the circumstances. The project team is 62 | obligated to maintain confidentiality with regard to the reporter of an incident. 63 | Further details of specific enforcement policies may be posted separately. 64 | 65 | Project maintainers who do not follow or enforce the Code of Conduct in good 66 | faith may face temporary or permanent repercussions as determined by other 67 | members of the project's leadership. 68 | 69 | ## Attribution 70 | 71 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 72 | available at [http://contributor-covenant.org/version/1/4][version] 73 | 74 | [homepage]: http://contributor-covenant.org 75 | [version]: http://contributor-covenant.org/version/1/4/ 76 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Welcome, and thanks in advance for your help! Please follow these simple guidelines :+1: 4 | 5 | # How to contribute 6 | 7 | ## When you propose a new feature or bug fix 8 | 9 | **Note:** Please make sure to write an issue first and get enough feedback before jumping into a Pull Request! 10 | 11 | - Please make sure there is an open issue discussing your contribution 12 | - If there isn't, please open an issue so we can talk about it before you invest time into the implementation 13 | - When creating an issue follow the guide that GitHub shows so we have enough information about your proposal 14 | 15 | ## When you want to work on an existing issue 16 | 17 | **Note:** Please write a quick comment in the corresponding issue and ask if the feature is still relevant and that you want to jump into the implementation. 18 | 19 | Check out our [help wanted](https://github.com/getndazn/kopytko-utils/labels/help%20wanted) or [good first issue](https://github.com/getndazn/kopytko-utils/labels/good%20first%20issue) labels to find issues we want to move forward on with your help. 20 | 21 | We will do our best to respond/review/merge your PR according to priority. We hope that you stay engaged with us during this period to insure QA. Please note that the PR will be closed if there hasn't been any activity for a long time (~ 30 days) to keep us focused and keep the repo clean. 22 | 23 | ## Reviewing Pull Requests 24 | 25 | Another really useful way to contribute to this project is to review other peoples Pull Requests. Having feedback from multiple people is really helpful and reduces the overall time to make a final decision about the Pull Request. 26 | 27 | ## Writing / improving documentation 28 | 29 | Our documentation lives in the README file. Do you see a typo or other ways to improve it? Feel free to edit it and submit a Pull Request! 30 | 31 | --- 32 | 33 | # Code Style 34 | 35 | We aim for clean, consistent code style. We're using ESlint to check for codestyle issues (you can run `npm run lint` to lint your code). 36 | 37 | To help reduce the effort of creating contributions with this style, an [.editorconfig file](http://editorconfig.org/) is provided that your editor may use to override any conflicting global defaults and automate a subset of the style settings. 38 | 39 | # Commit messages 40 | 41 | This project uses `Semantic release` to publish NPM updates and generate [CHANGELOG](CHANGELOG.md). For these to work, it depends on [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0-beta.3). 42 | 43 | As such, when you create a PR, you should make sure your commits follow the convention of: `: `. 44 | 45 | For example: 46 | 47 | * A bug fix should read: 48 | 49 | ```text 50 | fix: some description. 51 | ``` 52 | 53 | * A new feature should read: 54 | 55 | ```text 56 | feat: some description. 57 | ``` 58 | 59 | * A new breaking change should read: 60 | 61 | ```text 62 | feat!: some description. 63 | ``` 64 | 65 | * A `README.md` (this file) change should read: 66 | 67 | ```text 68 | docs: added Contribution Guide. 69 | ``` 70 | 71 | * A change to the build pipeline (e.g. `semantic.yml`) should read: 72 | 73 | ```text 74 | build: some description. 75 | ``` 76 | 77 | * Other misc chores should read: 78 | 79 | ```text 80 | chore: some description. 81 | ``` 82 | 83 | # Unit tests 84 | Please note that all contributions should be accompanied by unit tests. 85 | They are running via Kopytko Packager, so to be able to run them, create .env file in the root directory (it's git-ignored) with following fields: 86 | ``` 87 | ROKU_IP= 88 | ROKU_DEV_PASSWORD= 89 | ROKU_USER= 90 | ``` 91 | More info: https://github.com/getndazn/kopytko-packager#env-file 92 | 93 | Run unit tests using `npm test` command. 94 | 95 | # Our Code of Conduct 96 | 97 | Finally, to make sure you have a pleasant experience while being in our welcoming community, please read our [code of conduct](CODE_OF_CONDUCT.md). It outlines our core values and believes and will make working together a happier experience. 98 | 99 | Thanks again for being a contributor to the community :tada:! 100 | 101 | Cheers, 102 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 DAZN 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Kopytko Framework 2 | 3 | Kopytko Framework is a simple framework created to build simpler and cleaner components in Roku SceneGraph, 4 | allowing you to write component code declaratively instead of imperatively, leading to a less error-prone codebase 5 | and improving its maintainability overall. It is highly inspired by the Javascript library React and follows a lot 6 | of its patterns as well as its API, making it extremely friendly for someone coming from a React components environment. 7 | It is also inspired by router of the Javascript Angular framework and some other mechanisms taken from the Javascript world. 8 | 9 | # Kopytko Roku Ecosystem 10 | 11 | Kopytko Framework is part of Kopytko Roku Ecosystem which consists of: 12 | - [Kopytko Framework](https://github.com/getndazn/kopytko-framework), 13 | - [Kopytko Utils](https://github.com/getndazn/kopytko-utils) - a collection of modern utility functions for Brightscript applications, 14 | - [Kopytko Packager](https://github.com/getndazn/kopytko-packager) - a package builder for the Roku platform, 15 | - [Kopytko Unit Testing Framework](https://github.com/getndazn/kopytko-unit-testing-framework) - extended Roku's Unit Testing Framework with additional assert functionalities and the mocking mechanism, 16 | - [Kopytko ESLint Plugin](https://github.com/getndazn/kopytko-eslint-plugin) - set of Brightscript rules for ESLint 17 | 18 | Kopytko Framework, Utils and Unit Testing Framework are exportable as Kopytko Modules, so they can easily be installed 19 | and used in apps configured by Kopytko Packager. 20 | 21 | ## Modules 22 | 23 | - Renderer: main module, inspired by JS React library, rendering components. Full documentation available in [docs/renderer.md](docs/renderer.md) 24 | - Router: enables building an app with multiple views and allows navigation between them. Docs available in [docs/router.md](docs/router.md) 25 | - Cache: a mechanism to store expirable external data. Full documentation available in [docs/cache.md](docs/cache.md) 26 | - EventBus: implementation of Pub/Sub simplifying the communication between components. Full documentation available in [docs/event-bus.md](docs/event-bus.md) 27 | - HTTP: easy way to send HTTP requests. Full documentation available in [docs/http.md](docs/http.md) 28 | - Modal: global UI component. Full documentation available in [docs/modal.md](docs/modal.md) 29 | - Registry: a facade over native Roku's registry. Full documentation available in [docs/registry.md](docs/registry.md) 30 | - Store: a mechanism to store reusable data. Docs available in [docs/store.md](docs/store.md) 31 | - Theme: manages UI configuration and allows easy use in any place. Full documentation available in [docs/theme.md](docs/theme.md) 32 | 33 | 34 | ## Versions migration 35 | 36 | To update Kopytko Framework to the latest major version, please follow the [Versions migration guide](docs/versions-migration-guide.md) 37 | -------------------------------------------------------------------------------- /docs/cache.md: -------------------------------------------------------------------------------- 1 | # Kopytko Framework: Cache 2 | 3 | `CacheFacade` operates in global scope. 4 | It stores items in the global scope. 5 | Renderable nodes can't be cached (types of `Group` node). 6 | 7 | `CacheFacade` interface: 8 | - `read(keyData as Object, scopeName = "" as String)` - returns data associated with given key. The key can be string or AA. 9 | - `write(keyData as Object, data as Object, options = {} as Object)` - writes data to given key. 10 | - `clearScope(scopeName as String)` - removes all items from the given scope 11 | - `clearStaleItems(scopeName = "" as String)` - removes items from the scope when caching policy allows to do that. 12 | 13 | There are two types of caching policies: 14 | - `ExhaustibleCachingPolicy` - the policy allows to retrieve cached items only once. 15 | - `ExpirableCachingPolicy` - the timestamp (as seconds) controls lifetime of cached items. 16 | 17 | Stale items are not removed automatically when expired. 18 | 19 | `ExhaustibleCachingPolicy` example: 20 | ```brightscript 21 | cache = CacheFacade() 22 | cache.write("myKey", { myData: true }, { isSingleUse: true }) 23 | cachedItem = cache.read("myKey") ' Retrieves data. 24 | cachedItem = cache.read("myKey") ' The data is not retrieved anymore 25 | ``` 26 | 27 | `ExpirableCachingPolicy` example: 28 | ```brightscript 29 | cache = CacheFacade() 30 | cache.write("myKey", { myData: true }, { expirationTimestamp: 120 }) 31 | cachedItem = cache.read("myKey") ' Retrieves data. 32 | ' More than 120 seconds passed 33 | cachedItem = cache.read("myKey") ' The data is not retrieved anymore 34 | ``` 35 | 36 | Caching mechanism allows to store the same keys in different scopes. 37 | -------------------------------------------------------------------------------- /docs/event-bus.md: -------------------------------------------------------------------------------- 1 | # Kopytko Framework: EventBus 2 | 3 | The mechanism to communicate between various entities in the app. It uses global scope and implements `Pub/Sub` design pattern. 4 | 5 | Example of usage: 6 | ```brightscript 7 | sub init() 8 | eventBus = EventBusFacade() 9 | eventBus.on("OPEN_MODAL", _handler) 10 | ' or with a context: 11 | handler = { 12 | callThis: sub (payload as Object): ?payload end sub 13 | } 14 | eventBus.on("OPEN_MODAL", handler.callThis, handler) 15 | end sub 16 | 17 | sub _handler(payload as Object) 18 | ?payload 19 | end sub 20 | 21 | ' Some other entity 22 | sub init() 23 | eventBus = EventBusFacade() 24 | eventBus.trigger("OPEN_MODAL", { title: "I am title" }) 25 | end sub 26 | ``` 27 | To remove listener simply do: 28 | ```brightscript 29 | eventBus.off("OPEN_MODAL", _handler) 30 | ``` 31 | -------------------------------------------------------------------------------- /docs/modal.md: -------------------------------------------------------------------------------- 1 | # Kopytko Framework: Modal 2 | 3 | The modal element works with [EventBus](docs/event-bus.md). Example of usage: 4 | ```brightscript 5 | ' App.template.brs 6 | function render() 7 | return [ 8 | { 9 | name: "Label", 10 | props: { 11 | id: "title", 12 | text: "I am title", 13 | }, 14 | }, 15 | { 16 | name: "Video", 17 | props: { 18 | id: "video", 19 | }, 20 | }, 21 | { 22 | name: "Modal", 23 | props: { 24 | id: "modal", 25 | backdropColor: "0x00000000", 26 | opacity: 0.7, 27 | height: 1080, 28 | width: 1920, 29 | }, 30 | }, 31 | ] 32 | end function 33 | 34 | ' App.component.brs 35 | sub constructor() 36 | m._eventBus = EventBus() 37 | end sub 38 | 39 | sub componentDidMount() 40 | m._eventBus.trigger(ModalEvents().OPEN_REQUESTED, { 41 | componentName: "SomeComponent", 42 | componentProps: { 43 | text: "I am opened via Modal", 44 | }, 45 | elementToFocusOnClose: m.video, 46 | }) 47 | end sub 48 | ``` 49 | The example shows how to inject child component (`SomeComponent`) to the modal and how to open it via `EventBus`. 50 | In order to close the modal you can dispatch `ModalEvents.CLOSE_REQUESTED` event. Notice that if you pass reference to a component via `elementToFocusOnClose` `Modal` will set the focus on given element upon close. Both events can be dispatched from any place of your app. 51 | 52 | Modal interface: 53 | ```xml 54 | 55 | 56 | 57 | 58 | 59 | 60 | ``` 61 | Events: 62 | ```brightscript 63 | ModalEvents().OPEN_REQUESTED 64 | ModalEvents().CLOSE_REQUESTED 65 | ``` 66 | -------------------------------------------------------------------------------- /docs/registry.md: -------------------------------------------------------------------------------- 1 | # Kopytko Framework: Registry 2 | 3 | The registry entity is a facade for `roRegistrySection`. The section name is composed of title string and app id. For instance if manifest's title is `MyApp` and the app id is `123` the section name is `MyApp-123`. The facade provides method to get, set and delete values in the registry. 4 | 5 | ```brightscript 6 | registry = RegistryFacade() 7 | registry.set("token", "userToken") ' Returns true if writing was successful 8 | ?registy.get("token") ' prints "userToken" 9 | registry.delete("token") ' Returns true if deletion was successful 10 | ``` 11 | -------------------------------------------------------------------------------- /docs/store.md: -------------------------------------------------------------------------------- 1 | # Kopytko Framework: Store 2 | 3 | `StoreFacade` is a mechanism to easily store and manage data in the app. `StoreFacade` keeps data in a global field. 4 | Its public methods: 5 | - `set(key as String, value as Dynamic)` - stores a value under given key 6 | - `get(key as String)` - returns a stored value from given key; `Invalid` if doesn't exist 7 | - `hasKey(key as String)` - checks if given key is stored 8 | - `consume(key as String)` - return a stored value from given key and removes it from the store 9 | - `remove(key as String)` - removes value and key from the store 10 | - `setFields(newSet as Object)` - similar to `set()`, but allows setting multiple values at once 11 | - `subscribe(key as String, callback as Function, context = Invalid as Object)` - subscribes to value changes from a given key and calls the callback function on every change, optionally the context could be provided 12 | - `unsubscribe(key as String, callback as Function)` - unsubscribes specific callback of the key 13 | - `subscribeOnce(key as String, callback as Function, context = Invalid as Object)` - similar to `subscribe` but it automatically unsubscribes after the first callback run 14 | - `updateNode(key as String, value as Dynamic)` - updates fields (passed as `value` object) of the stored node from given key 15 | - `updateAA(key as String, updatedData as Object)` - updates fields of the stored AA from given key 16 | -------------------------------------------------------------------------------- /docs/theme.md: -------------------------------------------------------------------------------- 1 | # Kopytko Framework: Theme 2 | 3 | `Theme` node should be populated with the data. The data can be anything what `Node` `addFields` can accept. There are no constrains. To initialize the `Theme` just do: 4 | ```brightscript 5 | theme = CreateObject("roSGNode", "Theme") 6 | theme.callFunc("setAppTheme", getMyTheme()) 7 | ``` 8 | Setting app theme can be done only once, so the best place would be `MainScene` or component that renders before you use any `Theme` feature. 9 | Notice that `Theme` will autoregister in the global scope. 10 | 11 | In order to use built-in functions `getFont` and `getFontUri` your theme function should return `fonts` node/AA: 12 | ```brightscript 13 | function getMyTheme() as Object 14 | return { 15 | fonts: { 16 | regular: _createFont("regular.otf"), ' This needs to be Font component 17 | }, 18 | ' other fields 19 | colors: { 20 | white: "0xFFFFFF", 21 | } 22 | } 23 | end function 24 | 25 | function _createFont(fontFileName as String) as Object 26 | font = CreateObject("roSGNode", "Font") 27 | font.uri = "pkg:/fonts/" + fontFileName 28 | 29 | return font 30 | end function 31 | ``` 32 | Now you can use the facade: 33 | 34 | ```brightscript 35 | theme = ThemeFacade() 36 | theme.getFont("regular") ' It corresponds to the font name defined in the getMyTheme function. 37 | theme.getFontUri("regular")' It is the same as getFont but returns uri 38 | theme.rgba("0x00000000", 0.8) ' 1 represents "FF" and 0 - "00". All other values are between them. 39 | theme.colors.white ' Other data that is passed to theme node 40 | ``` 41 | -------------------------------------------------------------------------------- /docs/versions-migration-guide.md: -------------------------------------------------------------------------------- 1 | # Update Kopytko Framework 2 | 3 | ## Update from v2 to v3 4 | 5 | Version 3 introduced the `componentDidCatch` lifecycle method. It is not needed to implement componentDidCatch, but there could be a scenario where it is implemented and a developer wants to disable it (for example, for the development time). Because of that there is a new **bs_const** that needs to be defined in the **manifest** file - `enableKopytkoComponentDidCatch`. 6 | 7 | `enableKopytkoComponentDidCatch: true` - **enables** the `componentDidCatch` method 8 | 9 | `enableKopytkoComponentDidCatch: false` - **disables** the `componentDidCatch` method 10 | 11 | ## Update from v1 to v2 12 | 13 | ### Highlighted breaking changes in Kopytko Framework v2 14 | 15 | There were no interface changes making Kopytko Framework v2 a breaking change, but, because Kopytko Packager so far doesn't handle components and functions namespacing, the introduced new [`HttpRequest`](../src/components/http/request/Http.request.xml) component may cause name collision. It can happen if there already exist a HttpRequest component in the application the framework is used and it is very probable as Kopytko team was recommending creating own HttpRequest extending the [`Request`](../src/components/http/request/Request.xml) component. We came across Kopytko users' needs and created a helpful `HttpRequest` component implementing all necessary mechanisms to make an HTTP(S) call - we recommend switching over to Kopytko's `HttpRequest` component as soon as possible. 16 | 17 | ### Deprecations highlights in Kopytko Framework v2 18 | 19 | These APIs remain available in v2, but will be removed in future versions. 20 | 21 | - [`HttpResponseModel`](../src/components/http/HttpResponse.model.xml)`.data` field - HttpRequestResultModel is set as Request task's result field instead of HttpResponseModel 22 | - [`Request`](../src/components/http/request/Request.xml)`.response` field - use the `result` field instead 23 | - [`Request`](../src/components/http/request/Request.brs)`.initRequest()` method - use the native `init` function instead 24 | - [`Request.brs`](../src/components/http/request/Request.brs) file - this file will be removed and its code will be moved to [`Http.request.brs`](../src/components/http/request/Http.request.brs) 25 | -------------------------------------------------------------------------------- /manifest.js: -------------------------------------------------------------------------------- 1 | const baseManifest = require('@dazn/kopytko-unit-testing-framework/manifest'); 2 | 3 | module.exports = { 4 | ...baseManifest, 5 | bs_const: { 6 | ...baseManifest.bs_const, 7 | enableKopytkoComponentDidCatch: false, 8 | }, 9 | } 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dazn/kopytko-framework", 3 | "version": "3.2.0", 4 | "description": "A modern Roku's Brightscript framework", 5 | "keywords": [ 6 | "brightscript", 7 | "kopytko", 8 | "roku", 9 | "kopytko-module" 10 | ], 11 | "contributors": [ 12 | "Paweł Hertman ", 13 | "Tomasz Rejment ", 14 | "Błażej Chełkowski ", 15 | "Radosław Zambrowski ", 16 | "Mauricio Dziedzinski ", 17 | "Tomasz Bazelczuk " 18 | ], 19 | "files": [ 20 | "src", 21 | "!**/_tests" 22 | ], 23 | "scripts": { 24 | "lint": "eslint --ext .brs src/", 25 | "test": "node node_modules/@dazn/kopytko-unit-testing-framework/scripts/test.js" 26 | }, 27 | "dependencies": { 28 | "@dazn/kopytko-utils": "^2.4.0" 29 | }, 30 | "devDependencies": { 31 | "@dazn/eslint-plugin-kopytko": "^2.1.0", 32 | "@dazn/kopytko-packager": "^1.2.5", 33 | "@dazn/kopytko-unit-testing-framework": "^2.3.1", 34 | "eslint": "^8.21.0" 35 | }, 36 | "license": "MIT", 37 | "kopytkoModuleDir": "src/", 38 | "repository": { 39 | "type": "git", 40 | "url": "https://github.com/getndazn/kopytko-framework.git" 41 | }, 42 | "bugs": { 43 | "url": "https://github.com/getndazn/kopytko-framework/issues" 44 | }, 45 | "publishConfig": { 46 | "access": "public" 47 | }, 48 | "release": { 49 | "plugins": [ 50 | "@semantic-release/commit-analyzer", 51 | "@semantic-release/release-notes-generator", 52 | "@semantic-release/npm", 53 | "@semantic-release/changelog", 54 | "@semantic-release/git", 55 | "@semantic-release/github" 56 | ] 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/components/cache/Cache.brs: -------------------------------------------------------------------------------- 1 | sub init() 2 | m.top.scopes = CreateObject("roSGNode", "Node") 3 | m.top.scopes.addFields({ global: CreateObject("roSGNode", "CacheScope") }) 4 | end sub 5 | -------------------------------------------------------------------------------- /src/components/cache/Cache.facade.brs: -------------------------------------------------------------------------------- 1 | ' @import /components/isFalsy.brs from @dazn/kopytko-utils 2 | ' @import /components/ternary.brs from @dazn/kopytko-utils 3 | ' @import /components/cache/CacheCleaner.brs 4 | ' @import /components/cache/CacheReader.brs 5 | ' @import /components/cache/CacheWriter.brs 6 | ' @import /components/utils/KopytkoGlobalNode.brs 7 | 8 | ' Cache facade. 9 | ' WARNING: it pollutes component scope (m._cacheFacadeSingleton). 10 | ' @class 11 | function CacheFacade() as Object 12 | if (m._cacheFacadeSingleton <> Invalid) 13 | return m._cacheFacadeSingleton 14 | end if 15 | 16 | _global = KopytkoGlobalNode() 17 | if (NOT _global.hasField("cache")) 18 | _global.addFields({ 19 | cache: CreateObject("roSGNode", "Cache"), 20 | }) 21 | end if 22 | 23 | prototype = {} 24 | 25 | prototype._GLOBAL_SCOPE_NAME = "global" 26 | 27 | prototype._cleaner = CacheCleaner(_global.cache) 28 | prototype._reader = CacheReader(_global.cache) 29 | prototype._writer = CacheWriter(_global.cache) 30 | 31 | ' Reads value from cache. 32 | ' @param {Object|String} keyData - The key. When AA is passed it is encoded to json string. 33 | ' @param {String} [scopeName=""] - The given scope. Otherwise "global" scope is used. 34 | ' @returns {Object} 35 | prototype.read = function (keyData as Object, scopeName = "" as String) as Object 36 | return m._reader.read(keyData, m._getValidScopeName(scopeName)) 37 | end function 38 | 39 | ' Writes value to cache. 40 | ' @param {Object|String} keyData - The key. When AA is passed it is encoded to json string. 41 | ' @param {Object} data - The data to be cached. 42 | ' @param {Object} [options={}] 43 | ' @param {Integer} options.expirationTimestamp - In seconds. The timestamp after which the cached value is invalid. 44 | ' @param {Boolean} options.isSingleUse - The data can be retrieved only once and than removed. 45 | ' @param {String} options.scope - If not passed the "global" scope is used 46 | prototype.write = sub (keyData as Object, data as Object, options = {} as Object) 47 | writerOptions = { 48 | expirationTimestamp: options.expirationTimestamp, 49 | remainingUses: ternary(isFalsy(options.isSingleUse), Invalid, 1), 50 | scope: m._getValidScopeName(options.scope), 51 | } 52 | 53 | m._writer.write(keyData, data, writerOptions) 54 | end sub 55 | 56 | ' Clears the given scope. 57 | ' @param {String} scopeName - The given scope. 58 | prototype.clearScope = sub (scopeName as String) 59 | m._cleaner.clearScope(scopeName) 60 | end sub 61 | 62 | ' Removes invalid items. 63 | ' @param {String} [scopeName=""] - The given scope. Otherwise "global" scope is used. 64 | prototype.clearStaleItems = sub (scopeName = "" as String) 65 | m._cleaner.clearStaleItems(scopeName) 66 | end sub 67 | 68 | ' @private 69 | prototype._getValidScopeName = function (scopeName as Dynamic) as Object 70 | if (scopeName = Invalid OR scopeName = "") 71 | return m._GLOBAL_SCOPE_NAME 72 | end if 73 | 74 | return scopeName 75 | end function 76 | 77 | m._cacheFacadeSingleton = prototype 78 | 79 | return m._cacheFacadeSingleton 80 | end function 81 | -------------------------------------------------------------------------------- /src/components/cache/Cache.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |