├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .gitlab-ci.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bin └── cli.js ├── design ├── logo-box-evil-no-bg.png ├── logo-box-evil-with-bg.png ├── logo-box-stupid-no-bg.png ├── logo-box-stupid-with-bg.png ├── logo-long-evil-no-bg.png ├── logo-long-evil-with-bg.png ├── logo-long-stupid-no-bg.png ├── logo-long-stupid-with-bg.png └── source │ ├── logo-all.eps │ ├── logo-evil.svg │ └── logo-stupid.svg ├── docs ├── addons.md ├── application-lifecycle.md ├── config.md ├── configuration.md ├── migrations │ ├── 0.2.0.md │ ├── 2.0.0.md │ ├── 2.1.0.md │ ├── 2.2.0.md │ ├── 2.3.0.md │ ├── 2.5.0.md │ ├── 2.6.0.md │ └── dependency-injection.md ├── outdated │ ├── 04_devtools.md │ ├── 05_console.md │ ├── 05_framework.md │ ├── 07_abstraction.md │ ├── 08_components.md │ ├── 10_architecture.md │ ├── 11_layers.md │ ├── 12_navigation.md │ ├── 13_history.md │ ├── 15_input.md │ └── 16_querying.md └── video │ ├── Video States.jpg │ ├── Video States.xml │ ├── drm.md │ ├── index.md │ └── legacy-ivideo.md ├── lib ├── addons │ ├── abstract-addon.js │ ├── abstract-extension.js │ ├── abstract-platform.js │ └── loader.js ├── application.js ├── build-helper.js ├── config │ ├── config.js │ ├── default.js │ ├── interface.js │ └── schema.json ├── index.js ├── logger.js ├── path-helper.js ├── scaffolding.js ├── server-cache.js ├── server.js ├── sources │ ├── code-source.js │ ├── i-source-provider.js │ ├── source-provider-base.js │ ├── source-provider-fs.js │ ├── source-provider-generated.js │ └── source-provider-group.js ├── template-helper.js ├── utils.js └── versions-checker.js ├── package.json ├── scripts └── generate-config-docs.js ├── templates ├── app.js.tpl ├── base-application.js.tpl ├── boilerplate │ ├── rename.json │ └── root │ │ ├── config.js.tpl │ │ ├── src │ │ ├── application.js.tpl │ │ ├── dev.js │ │ ├── scenes │ │ │ ├── home │ │ │ │ ├── home.css │ │ │ │ ├── home.js.tpl │ │ │ │ └── home.jst.tpl │ │ │ └── player │ │ │ │ ├── player.css │ │ │ │ ├── player.js.tpl │ │ │ │ └── player.jst.tpl │ │ └── widgets │ │ │ ├── button │ │ │ ├── button.js.tpl │ │ │ ├── button.jst.tpl │ │ │ └── buttons.css │ │ │ └── greeting │ │ │ ├── greeting.css │ │ │ ├── greeting.js.tpl │ │ │ └── greeting.jst │ │ └── static │ │ └── .placeholder ├── index.html.tpl └── webpack.config.js.tpl ├── test ├── framework │ ├── generated │ │ ├── app.js │ │ └── define.js │ ├── karma.conf.js │ └── suites │ │ ├── events │ │ └── event-publisher.js │ │ ├── geometry │ │ ├── area.js │ │ ├── axis.js │ │ ├── direction.js │ │ ├── point.js │ │ └── rect.js │ │ ├── html │ │ └── html.js │ │ ├── http │ │ └── http.js │ │ └── state-machine.js └── tools │ ├── fixtures │ ├── application │ │ ├── _templates │ │ │ └── index.html.tpl │ │ ├── config.js │ │ ├── custom-compilation-files │ │ │ └── lib.js │ │ ├── dev.js │ │ ├── package.json │ │ ├── src │ │ │ ├── application.css │ │ │ ├── application.js │ │ │ └── background.png │ │ ├── static │ │ │ └── data.json │ │ └── vendor │ │ │ ├── lodash.css │ │ │ ├── lodash.js │ │ │ ├── react.css │ │ │ └── react.js │ ├── zombiebox-extension-blockchain │ │ ├── index.js │ │ ├── lib │ │ │ └── service.js │ │ ├── package.json │ │ └── private-key │ └── zombiebox-platform-tamagotchi │ │ ├── index.js │ │ ├── lib │ │ ├── device.js │ │ └── factory.js │ │ └── package.json │ ├── mock-google-closure-compiler.js │ ├── references │ └── code-generation │ │ ├── app.js │ │ ├── base-application.js │ │ └── define.js │ ├── suites │ ├── building.js │ ├── code-generation.js │ ├── server.js │ └── starting.js │ └── temporary-application-container.js ├── vendor ├── a.js └── raf.js └── zb ├── abstract-application.js ├── back-button-listener.js ├── block.js ├── console ├── console.js ├── interfaces │ └── i-logger.js └── loggers │ ├── alert.js │ ├── base-logger.js │ ├── console.js │ ├── remote.js │ ├── screen.css │ └── screen.js ├── css ├── reset.css └── zb.css ├── device ├── abstract-device.js ├── abstract-info.js ├── abstract-input.js ├── abstract-stateful-video.js ├── abstract-video.js ├── abstract-view-port.js ├── aspect-ratio │ ├── aspect-ratio.js │ └── proportion.js ├── common │ ├── HTML5-video.js │ ├── HTML5-view-port.js │ ├── local-storage.js │ └── stateful-html5-video.js ├── drm │ ├── abstract-drm-client.js │ ├── drm.js │ ├── playready-client.js │ └── verimatrix-client.js ├── errors │ └── unsupported-feature.js ├── input │ └── key.js ├── interfaces │ ├── i-device.js │ ├── i-drm-client.js │ ├── i-info.js │ ├── i-input.js │ ├── i-stateful-video.js │ ├── i-storage.js │ ├── i-video.js │ └── i-view-port.js └── resolutions.js ├── events ├── event-publisher.js └── interfaces │ └── i-event-publisher.js ├── geometry ├── area.js ├── axis.js ├── corner.js ├── direction.js ├── point.js ├── rect.js └── scale.js ├── hacks.js ├── history ├── history-manager.js └── interfaces │ ├── i-history-manager.js │ └── i-stateful.js ├── html.js ├── http ├── abstract-transport.js ├── http.js └── xhr.js ├── image-preload.js ├── input-dispatcher.js ├── interfaces ├── i-focusable.js └── i-key-handler.js ├── layer-manager.js ├── layers ├── layer.js ├── popup.js └── scene.js ├── limit.js ├── promise.js ├── scene-opener.js ├── state-machine.js ├── structures ├── abstract-model-api.js ├── abstract-model.js └── data │ ├── cyclical-list.js │ ├── dynamic-list.js │ ├── i-list.js │ ├── list.js │ └── random.js ├── stub.js ├── timeout.js └── widgets ├── container.js ├── interfaces ├── i-navigation.js └── i-widget.js ├── navigation ├── abstract-navigation.js ├── order-navigation.js ├── principal-axis-navigation.js └── spatial-navigation.js └── widget.js /.eslintignore: -------------------------------------------------------------------------------- 1 | design 2 | docs 3 | vendor 4 | /test/tools/fixtures 5 | /test/tools/references 6 | /test/framework/generated 7 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const sinonPath = path.join(path.dirname(require.resolve('sinon/package.json')), 'pkg'); 3 | 4 | module.exports = { 5 | extends: 'interfaced', 6 | overrides: [ 7 | { 8 | files: ['zb/**', 'test/framework/*/**', 'test/tools/addons/*/lib/**', 'templates/**/*.js'], 9 | extends: 'interfaced/esm', 10 | settings: { 11 | 'import/resolver': { 12 | 'zombiebox': {}, 13 | 'alias': { 14 | 'map': [ 15 | ['sinon', sinonPath] 16 | ] 17 | } 18 | } 19 | } 20 | }, 21 | { 22 | files: ['zb/**'], 23 | plugins: [ 24 | 'header' 25 | ], 26 | 'rules': { 27 | 'header/header': ['error', 'block', [ 28 | '', 29 | ' * This file is part of the ZombieBox package.', 30 | ' *', 31 | {pattern: `\\* Copyright © 2012\\-${(new Date()).getFullYear()}, Interfaced`}, 32 | ' *', 33 | ' * For the full copyright and license information, please view the LICENSE', 34 | ' * file that was distributed with this source code.', 35 | ' ' 36 | ]] 37 | } 38 | }, 39 | { 40 | files: ['templates/**/*.js'], 41 | rules: { 42 | 'import/no-unresolved': 'off', 43 | 'no-useless-constructor': 'off' 44 | } 45 | }, 46 | { 47 | files: ['test/framework/suites/**/*.js'], 48 | rules: { 49 | 'import/no-unused-modules': 'off' 50 | } 51 | }, 52 | { 53 | files: [ 54 | 'scripts/**', 55 | 'bin/**', 56 | 'lib/**', 57 | '.eslintrc.js', 58 | 'test/tools/*.js', 59 | 'test/tools/helpers/**', 60 | 'test/tools/suites/**', 61 | 'test/tools/addons/*/index.js', 62 | 'test/framework/karma.conf.js' 63 | ], 64 | extends: 'interfaced/node', 65 | rules: { 66 | 'jsdoc/no-undefined-types': 'off', // Introduces circular dependencies 67 | 'no-console': 'error' // Winston logger should be used for output 68 | } 69 | }, 70 | { 71 | files: ['test/framework/suites/**', 'test/tools/suites/**'], 72 | extends: 'interfaced/mocha-chai' 73 | } 74 | ] 75 | }; 76 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /node_modules 3 | /test/framework/coverage/ 4 | package-lock.json 5 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | image: registry.gitlab.com/interfaced/dev-server/base-ci-image:zb 2 | workflow: 3 | rules: 4 | - if: $CI_MERGE_REQUEST_IID 5 | - if: $CI_COMMIT_TAG 6 | - if: $CI_COMMIT_BRANCH == 'develop' 7 | 8 | before_script: 9 | - npm ci 10 | 11 | lint: 12 | stage: test 13 | script: 14 | - npm run lint 15 | 16 | test_tools: 17 | stage: test 18 | script: 19 | - npm run test.tools 20 | 21 | test_framework: 22 | stage: test 23 | script: 24 | - npm run test.framework 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright © 2012-2020 Interfaced 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /design/logo-box-evil-no-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interfaced/zombiebox/68a4f379359f630d03f7d1420d969be2fa38d33c/design/logo-box-evil-no-bg.png -------------------------------------------------------------------------------- /design/logo-box-evil-with-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interfaced/zombiebox/68a4f379359f630d03f7d1420d969be2fa38d33c/design/logo-box-evil-with-bg.png -------------------------------------------------------------------------------- /design/logo-box-stupid-no-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interfaced/zombiebox/68a4f379359f630d03f7d1420d969be2fa38d33c/design/logo-box-stupid-no-bg.png -------------------------------------------------------------------------------- /design/logo-box-stupid-with-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interfaced/zombiebox/68a4f379359f630d03f7d1420d969be2fa38d33c/design/logo-box-stupid-with-bg.png -------------------------------------------------------------------------------- /design/logo-long-evil-no-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interfaced/zombiebox/68a4f379359f630d03f7d1420d969be2fa38d33c/design/logo-long-evil-no-bg.png -------------------------------------------------------------------------------- /design/logo-long-evil-with-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interfaced/zombiebox/68a4f379359f630d03f7d1420d969be2fa38d33c/design/logo-long-evil-with-bg.png -------------------------------------------------------------------------------- /design/logo-long-stupid-no-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interfaced/zombiebox/68a4f379359f630d03f7d1420d969be2fa38d33c/design/logo-long-stupid-no-bg.png -------------------------------------------------------------------------------- /design/logo-long-stupid-with-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interfaced/zombiebox/68a4f379359f630d03f7d1420d969be2fa38d33c/design/logo-long-stupid-with-bg.png -------------------------------------------------------------------------------- /design/source/logo-all.eps: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interfaced/zombiebox/68a4f379359f630d03f7d1420d969be2fa38d33c/design/source/logo-all.eps -------------------------------------------------------------------------------- /docs/addons.md: -------------------------------------------------------------------------------- 1 | # ZombieBox Addons 2 | 3 | ZombieBox functionality can be extended with Addons. Currrently there are two types of addons: Platforms and Extensions. 4 | 5 | **Platforms** add an implementation of hardware layer abstraction for a specific set of devices. Their core are implementations of [`IDevice`](../zb/device/interfaces/i-device.js), [`IVideo`](../zb/device/interfaces/i-video.js), [`IInfo`](../zb/device/interfaces/i-info.js) and [`IInput`](../zb/device/interfaces/i-input.js) interfaces, scripts that build application into platform-specific artifact (e.g. binary package). 6 | 7 | **Extensions** typically add new functionality to client-side runtime code base (e.g. widgets, utilities). Extensions can also generate new code based on existing code base. 8 | 9 | **Both** Platforms and Extensions can add CLI commands, add client-side code, include additional files into build and influence project configuration. 10 | 11 | ## Installing addons 12 | 13 | Addons are loaded from application `package.json` file. To add an addon, simply install it with npm: 14 | 15 | ```bash 16 | npm install zombiebox-platform-foo 17 | npm install zombiebox-extension-bar 18 | ``` 19 | 20 | By convention, Platform package name are prefixed with `zombiebox-platform-` and Extension's with `zombiebox-extension-`. ZombieBox scans for project dependencies prefixed with `zombiebox-` and treats them as addons. 21 | 22 | See npm for list of publicly available [platforms](https://www.npmjs.com/search?q=zombiebox-platform) and [extensions](https://www.npmjs.com/search?q=zombiebox-extension). 23 | 24 | ## Using addons 25 | 26 | Each addons implements [`AbstractAddon`](../lib/addons/abstract-addon.js) and its subclasses ([`AbstractPlatform`](../lib/addons/abstract-platform.js) or [`AbstractExtension`](../lib/addons/abstract-extension.js)) which dictate three basic properties: Name, source directory and config. 27 | 28 | Addon name should be a unique identifier. For convention npm package name is typically named same as addon name with appropriate prefix. 29 | 30 | Source directory is the location of client-side files to be included into build. Its root will be aliases as addon name. 31 | 32 | Addon config can both change global project configuration and introduce its own configuration under `platforms` or `extensions` fields. See [configuration](./configuration.md) for more details. 33 | 34 | ## Developing addons 35 | 36 | Each addon must have a CommonJS module providing a class inheriting from `AbstractAddon` (`AbstractPlatform`, `AbstractExtension`) referenced in its `package.json` as `main` and implementing its abstract methods. 37 | -------------------------------------------------------------------------------- /docs/application-lifecycle.md: -------------------------------------------------------------------------------- 1 | # Application lifecycle 2 | 3 | ## Build 4 | 5 | Main Application module (as defined in `project.entry` config dir) should export a constructor for main application class, typically one inheriting from `BaseApplication` which in turns inherits `AbstractApplication`. `BaseApplication` is an automatically generated class that includes initialisation of platforms for the specific application. 6 | 7 | Build system will generate another module that imports the main application constructor and instantiates it (`app.js` in `generatedCode` directory). This module wil be added to compilation sources. 8 | 9 | ## Application start 10 | 11 | `AbstractApplication` waits for window `load` event and creates base application DOM nodes. `EVENT_DOM_READY` is fired after this stage is finished. 12 | 13 | Then `BaseApplication` selects and initialises appropriate device abstraction layer classes. `EVENT_DEVICE_READY` signals this is done. 14 | 15 | After that `AbstractApplication` finishes up initialisation of framework components (`InputDispatcher`, `LayerManger`, etc...) and calls its `onStart()` hook passing control to `Application`. Initialise your application components and business logic here. 16 | 17 | Typically, the application will display a splash screen at this point and after loading is done call `home()` to show its first scene. 18 | 19 | ## App exit 20 | 21 | Calling `exit()` or pressing back button when history is empty will invoke `onExit` hook and then call device `exit()` method which closes application in accordance with device specific procedure. 22 | -------------------------------------------------------------------------------- /docs/configuration.md: -------------------------------------------------------------------------------- 1 | # ZombieBox project configuration 2 | 3 | ZombieBox project uses several layers of configuration applied and merged together in this order: 4 | 5 | * Default ZombieBox config, [lib/config/default.js](../lib/config/default.js) 6 | * Configs for each Addon (Platform or Extension) 7 | * Project config(s): either `config.js` under its root or any other files added with `--config` CLI option 8 | * CLI overrides (`--config.field value`) 9 | 10 | Each of the configs must adhere to config [schema](../lib/config/schema.json). 11 | 12 | Config files are CommonJS modules that should provide a function. The function accepts config as it was accumulated so far and should return new config which will merged with previous configs. 13 | 14 | All filesystem paths in configs should be either absolute or relative to project root (directory containing `package.json` file). 15 | 16 | ## Config options 17 | 18 | See [config](config.md) for detailed description of config options 19 | 20 | Mandatory options are `name`, `src` and `entry` under `project` field: 21 | 22 | ```js 23 | module.exports = () => ({ 24 | project: { 25 | name: 'my-project', 26 | src: './src', 27 | entry: './src/application.js' 28 | } 29 | }) 30 | ``` 31 | 32 | ## Including files to artifact 33 | 34 | For each type of file: script, module, css and static file there are several ways of including them into resulting HTML: Bundling them through compilation pipeline (GCC or PostCSS), inlining them into html (i.e. `\n\t'); 14 | } %> 15 | 16 | 17 | <% if (modules.length) { 18 | print('\n\t'); 19 | } %> 20 | 21 | 22 | <% if (scripts.length) { 23 | print('\n\t'); 24 | } %> 25 | 26 | 27 | <% if (inlineScripts.length) { 28 | print('\n\t'); 29 | } %> 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /templates/webpack.config.js.tpl: -------------------------------------------------------------------------------- 1 | // This application does not actually use webpack 2 | // This config only exists to define module aliases for development tools such as WebStorm 3 | // It is generated automatically with "zb generateAliases" 4 | 5 | const aliases = <%= JSON.stringify(map, null, '\t') %>; 6 | 7 | module.exports = { 8 | resolve: { 9 | alias: aliases 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /test/framework/generated/app.js: -------------------------------------------------------------------------------- 1 | export default null; 2 | -------------------------------------------------------------------------------- /test/framework/generated/define.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @const {boolean} 3 | */ 4 | export const ENABLE_CONSOLE = true; 5 | 6 | /** 7 | * @const {string} 8 | */ 9 | export const NPM_PACKAGE_NAME = ""; 10 | 11 | /** 12 | * @const {string} 13 | */ 14 | export const NPM_PACKAGE_VERSION = "0.0.0"; 15 | 16 | /** 17 | * @const {string} 18 | */ 19 | export const ZOMBIEBOX_VERSION = "0.0.0"; 20 | -------------------------------------------------------------------------------- /test/framework/karma.conf.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const zbPath = '../../zb'; 4 | const generatedPath = 'generated'; 5 | const testPath = 'suites'; 6 | const sinonPath = path.join(path.dirname(require.resolve('sinon/package.json')), 'pkg'); 7 | 8 | const [zbFiles, generatedFiles, testFiles] = 9 | [zbPath, generatedPath, testPath].map((root) => root + '/**/*.js'); 10 | 11 | module.exports = (config) => { 12 | config.set({ 13 | autoWatch: false, 14 | singleRun: true, 15 | 16 | frameworks: ['mocha', 'chai'], 17 | reporters: ['mocha'], 18 | browsers: ['ChromeHeadlessNoSandbox'], 19 | customLaunchers: { 20 | ChromeHeadlessNoSandbox: { 21 | base: 'ChromeHeadless', 22 | flags: ['--no-sandbox'] 23 | } 24 | }, 25 | files: [ 26 | {type: 'module', pattern: zbFiles}, 27 | {type: 'module', pattern: generatedFiles}, 28 | {type: 'module', pattern: testFiles}, 29 | {type: 'module', pattern: sinonPath + '/sinon-esm.js'} 30 | ], 31 | 32 | preprocessors: { 33 | [zbFiles]: ['module-resolver'], 34 | [generatedFiles]: ['module-resolver'], 35 | [testFiles]: ['module-resolver'] 36 | }, 37 | 38 | moduleResolverPreprocessor: { 39 | aliases: { 40 | 'zb': zbPath, 41 | 'generated': generatedPath, 42 | 'sinon': sinonPath 43 | }, 44 | ecmaVersion: 2019 45 | } 46 | }); 47 | }; 48 | -------------------------------------------------------------------------------- /test/framework/suites/geometry/axis.js: -------------------------------------------------------------------------------- 1 | import Axis from 'zb/geometry/axis'; 2 | 3 | describe('zb.std.plain.Axis', () => { 4 | it('X', () => { 5 | expect(Axis.X).not.undefined; 6 | }); 7 | 8 | it('Y', () => { 9 | expect(Axis.Y).not.undefined; 10 | }); 11 | 12 | it('X != Y', () => { 13 | expect(Axis.X).not.equal(Axis.Y); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /test/framework/suites/geometry/direction.js: -------------------------------------------------------------------------------- 1 | import Direction, {Value} from 'zb/geometry/direction'; 2 | 3 | describe('Direction', () => { 4 | describe('Class', () => { 5 | it('function', () => { 6 | expect(Direction) 7 | .is.a('function'); 8 | }); 9 | }); 10 | 11 | describe('Constants', () => { 12 | it('constant: LEFT', () => { 13 | expect(Direction.createLeft()) 14 | .instanceOf(Direction); 15 | expect(Direction.createLeft().getKey()) 16 | .equal(Value.LEFT); 17 | }); 18 | 19 | it('constant: RIGHT', () => { 20 | expect(Direction.createRight()) 21 | .instanceOf(Direction); 22 | expect(Direction.createRight().getKey()) 23 | .equal(Value.RIGHT); 24 | }); 25 | 26 | it('constant: UP', () => { 27 | expect(Direction.createUp()) 28 | .instanceOf(Direction); 29 | expect(Direction.createUp().getKey()) 30 | .equal(Value.UP); 31 | }); 32 | 33 | it('constant: DOWN', () => { 34 | expect(Direction.createDown()) 35 | .instanceOf(Direction); 36 | expect(Direction.createDown().getKey()) 37 | .equal(Value.DOWN); 38 | }); 39 | }); 40 | 41 | describe('.domPropertyName()', () => { 42 | it('function', () => { 43 | expect(Direction.createLeft().domPropertyName) 44 | .is.a('function'); 45 | }); 46 | 47 | it('left', () => { 48 | expect(Direction.createLeft().domPropertyName()) 49 | .equal('left'); 50 | }); 51 | 52 | it('right', () => { 53 | expect(Direction.createRight().domPropertyName()) 54 | .equal('right'); 55 | }); 56 | 57 | it('up', () => { 58 | expect(Direction.createUp().domPropertyName()) 59 | .equal('top'); 60 | }); 61 | 62 | it('down', () => { 63 | expect(Direction.createDown().domPropertyName()) 64 | .equal('bottom'); 65 | }); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /test/framework/suites/http/http.js: -------------------------------------------------------------------------------- 1 | import {buildQueryString} from 'zb/http/http'; 2 | 3 | describe('zb/http', () => { 4 | describe('buildQueryUrl', () => { 5 | it('Doesn\'t place trailing ? if query params argument is UNDEFINED', () => { 6 | const string = 'http://example.com'; 7 | expect(buildQueryString(string)).eq(string); 8 | }); 9 | 10 | it('Doesn\'t place trailing ? if query params argument is EMPTY', () => { 11 | const string = 'http://example.com'; 12 | expect(buildQueryString(string, {})).eq(string); 13 | }); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /test/tools/fixtures/application/_templates/index.html.tpl: -------------------------------------------------------------------------------- 1 | Custom template 2 | -------------------------------------------------------------------------------- /test/tools/fixtures/application/config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | /** 4 | * @return {IZombieBoxConfig} 5 | */ 6 | module.exports = () => ({ 7 | project: { 8 | name: 'zb-test', 9 | entry: 'src/application.js', 10 | src: 'src' 11 | }, 12 | devServer: { 13 | port: 7777 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /test/tools/fixtures/application/custom-compilation-files/lib.js: -------------------------------------------------------------------------------- 1 | export default 'custom library' 2 | -------------------------------------------------------------------------------- /test/tools/fixtures/application/dev.js: -------------------------------------------------------------------------------- 1 | // Development stuff 2 | -------------------------------------------------------------------------------- /test/tools/fixtures/application/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zb-test", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "zombiebox": "*", 6 | "zombiebox-platform-pc": "*" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /test/tools/fixtures/application/src/application.css: -------------------------------------------------------------------------------- 1 | .my { 2 | background: url(background.png) 3 | } 4 | -------------------------------------------------------------------------------- /test/tools/fixtures/application/src/application.js: -------------------------------------------------------------------------------- 1 | import BaseApplication from 'generated/base-application'; 2 | 3 | 4 | /** 5 | */ 6 | export default class Application extends BaseApplication {}; 7 | -------------------------------------------------------------------------------- /test/tools/fixtures/application/src/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/interfaced/zombiebox/68a4f379359f630d03f7d1420d969be2fa38d33c/test/tools/fixtures/application/src/background.png -------------------------------------------------------------------------------- /test/tools/fixtures/application/static/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "some": { 3 | "arbitrary": "data" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/tools/fixtures/application/vendor/lodash.css: -------------------------------------------------------------------------------- 1 | .lodash { 2 | background: red; 3 | } 4 | -------------------------------------------------------------------------------- /test/tools/fixtures/application/vendor/lodash.js: -------------------------------------------------------------------------------- 1 | var lodash = {}; 2 | -------------------------------------------------------------------------------- /test/tools/fixtures/application/vendor/react.css: -------------------------------------------------------------------------------- 1 | .react { 2 | background: blue; 3 | } 4 | -------------------------------------------------------------------------------- /test/tools/fixtures/application/vendor/react.js: -------------------------------------------------------------------------------- 1 | var react = {}; 2 | -------------------------------------------------------------------------------- /test/tools/fixtures/zombiebox-extension-blockchain/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | // This file gets copied, symlinked and referenced in tests, its location can vary wildly and because of this 5 | // we need this hack for find zombiebox. Actual addons should only do require('zombiebox').AbstractPlatform 6 | let AbstractExtension; 7 | try { 8 | AbstractExtension = require('zombiebox').AbstractExtension; 9 | } catch (e) { 10 | AbstractExtension = require('../../../../lib/addons/abstract-extension') 11 | } 12 | 13 | 14 | /** 15 | */ 16 | class BlockchainExtension extends AbstractExtension { 17 | /** 18 | * @override 19 | */ 20 | getName() { 21 | return 'blockchain'; 22 | } 23 | 24 | /** 25 | * @override 26 | */ 27 | getSourcesDir() { 28 | return path.join(__dirname, 'lib'); 29 | } 30 | 31 | /** 32 | * @override 33 | */ 34 | getConfig() { 35 | return {}; 36 | } 37 | 38 | /** 39 | * @override 40 | */ 41 | generateCode() { 42 | return { 43 | 'private-key': fs.readFileSync(path.join(__dirname, 'private-key'), 'utf-8') 44 | }; 45 | } 46 | } 47 | 48 | 49 | module.exports = BlockchainExtension; 50 | -------------------------------------------------------------------------------- /test/tools/fixtures/zombiebox-extension-blockchain/lib/service.js: -------------------------------------------------------------------------------- 1 | /** 2 | */ 3 | export default class Service {} 4 | -------------------------------------------------------------------------------- /test/tools/fixtures/zombiebox-extension-blockchain/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zombiebox-extension-blockchain" 3 | } 4 | -------------------------------------------------------------------------------- /test/tools/fixtures/zombiebox-extension-blockchain/private-key: -------------------------------------------------------------------------------- 1 | E9873D79C6D87DC0FB6A5778633389F4453213303DA61F20BD67FC233AA33262 2 | -------------------------------------------------------------------------------- /test/tools/fixtures/zombiebox-platform-tamagotchi/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | // This file gets copied, symlinked and referenced in tests, its location can vary wildly and because of this 4 | // we need this hack for find zombiebox. Actual addons should only do require('zombiebox').AbstractPlatform 5 | let AbstractPlatform; 6 | try { 7 | AbstractPlatform = require('zombiebox').AbstractPlatform; 8 | } catch (e) { 9 | AbstractPlatform = require('../../../../lib/addons/abstract-platform') 10 | } 11 | 12 | 13 | /** 14 | */ 15 | class TamagotchiPlatform extends AbstractPlatform { 16 | /** 17 | * @override 18 | */ 19 | getName() { 20 | return 'tamagotchi'; 21 | } 22 | 23 | /** 24 | * @override 25 | */ 26 | getSourcesDir() { 27 | return path.join(__dirname, 'lib'); 28 | } 29 | 30 | /** 31 | * @override 32 | */ 33 | getConfig() { 34 | return {}; 35 | } 36 | 37 | /** 38 | * @override 39 | */ 40 | buildApp() { 41 | return Promise.resolve([]); 42 | } 43 | } 44 | 45 | 46 | module.exports = TamagotchiPlatform; 47 | -------------------------------------------------------------------------------- /test/tools/fixtures/zombiebox-platform-tamagotchi/lib/device.js: -------------------------------------------------------------------------------- 1 | import AbstractDevice from 'zb/device/abstract-device'; 2 | import UnsupportedFeature from 'zb/device/errors/unsupported-feature'; 3 | 4 | 5 | /** 6 | */ 7 | export default class Device extends AbstractDevice { 8 | /** 9 | * @override 10 | */ 11 | init() { 12 | this._fireEvent(this.EVENT_READY); 13 | } 14 | 15 | /** 16 | * @override 17 | */ 18 | createVideo() { 19 | throw new UnsupportedFeature('Video creation'); 20 | } 21 | 22 | /** 23 | * @override 24 | */ 25 | exit() { 26 | throw new UnsupportedFeature('Exit'); 27 | } 28 | 29 | /** 30 | * @override 31 | */ 32 | getMAC() { 33 | throw new UnsupportedFeature('MAC address getting'); 34 | } 35 | 36 | /** 37 | * @override 38 | */ 39 | getIP() { 40 | throw new UnsupportedFeature('IP address getting'); 41 | } 42 | 43 | /** 44 | * @override 45 | */ 46 | setOSDOpacity() { 47 | throw new UnsupportedFeature('OSD opacity setting'); 48 | } 49 | 50 | /** 51 | * @override 52 | */ 53 | getOSDOpacity() { 54 | throw new UnsupportedFeature('OSD opacity getting'); 55 | } 56 | 57 | /** 58 | * @override 59 | */ 60 | setOSDChromaKey(chromaKey) { 61 | throw new UnsupportedFeature('OSD chroma key setting'); 62 | } 63 | 64 | /** 65 | * @override 66 | */ 67 | getOSDChromaKey() { 68 | throw new UnsupportedFeature('OSD chroma key getting'); 69 | } 70 | 71 | /** 72 | * @override 73 | */ 74 | removeOSDChromaKey() { 75 | throw new UnsupportedFeature('OSD chroma key removing'); 76 | } 77 | 78 | /** 79 | * @override 80 | */ 81 | getLaunchParams() { 82 | throw new UnsupportedFeature('Launch params getting'); 83 | } 84 | 85 | /** 86 | * @override 87 | */ 88 | getEnvironment() { 89 | throw new UnsupportedFeature('Environment getting'); 90 | } 91 | 92 | /** 93 | * @override 94 | */ 95 | hasOSDOpacityFeature() { 96 | return false; 97 | } 98 | 99 | /** 100 | * @override 101 | */ 102 | hasOSDAlphaBlendingFeature() { 103 | return false; 104 | } 105 | 106 | /** 107 | * @override 108 | */ 109 | hasOSDChromaKeyFeature() { 110 | return false; 111 | } 112 | 113 | /** 114 | * @override 115 | */ 116 | isUHDSupported() { 117 | return false; 118 | } 119 | 120 | /** 121 | * @return {boolean} 122 | */ 123 | static detect() { 124 | return true; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /test/tools/fixtures/zombiebox-platform-tamagotchi/lib/factory.js: -------------------------------------------------------------------------------- 1 | import Device from './device'; 2 | 3 | 4 | /** 5 | * @return {?Device} 6 | */ 7 | export default function create() { 8 | if (Device.detect()) { 9 | return new Device(); 10 | } 11 | 12 | return null; 13 | } 14 | -------------------------------------------------------------------------------- /test/tools/fixtures/zombiebox-platform-tamagotchi/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zombiebox-platform-tamagotchi" 3 | } 4 | -------------------------------------------------------------------------------- /test/tools/mock-google-closure-compiler.js: -------------------------------------------------------------------------------- 1 | const mockRequire = require('mock-require'); 2 | const {spy} = require('sinon'); 3 | 4 | const gccSpy = spy(); 5 | 6 | 7 | /** 8 | */ 9 | class ClosureCompilerStub { 10 | /** 11 | * @param {...?} args 12 | */ 13 | constructor(...args) { 14 | gccSpy(...args); 15 | } 16 | 17 | /** 18 | * @param {Function} callback 19 | */ 20 | run(callback) { 21 | callback(0, '', ''); 22 | } 23 | } 24 | 25 | 26 | const mock = () => mockRequire('google-closure-compiler', {compiler: ClosureCompilerStub}); 27 | 28 | const stop = () => mockRequire.stop('google-closure-compiler'); 29 | 30 | // Should be ran ASAP to prevent other modules from requiring the real compiler and caching it 31 | mock(); 32 | 33 | module.exports = { 34 | mock, 35 | stop, 36 | spy: gccSpy 37 | }; 38 | -------------------------------------------------------------------------------- /test/tools/references/code-generation/app.js: -------------------------------------------------------------------------------- 1 | import 'zb/hacks.js'; 2 | import Application from 'zb-test/application'; 3 | 4 | /** 5 | * @type {Application} 6 | */ 7 | const app = new Application(); 8 | 9 | export default app; 10 | -------------------------------------------------------------------------------- /test/tools/references/code-generation/base-application.js: -------------------------------------------------------------------------------- 1 | import {PLATFORM_NAME} from 'generated/define'; 2 | import AbstractApplication from 'zb/abstract-application'; 3 | 4 | import createTamagotchiDevice from 'tamagotchi/factory'; 5 | import tamagotchiDevice from 'tamagotchi/device'; 6 | import createPcDevice from 'pc/factory'; 7 | import pcDevice from 'pc/device'; 8 | 9 | 10 | 11 | /** 12 | * @abstract 13 | */ 14 | export default class BaseApplication extends AbstractApplication { 15 | /** 16 | */ 17 | constructor() { 18 | super(); 19 | } 20 | 21 | 22 | /** 23 | * @return {boolean} 24 | */ 25 | isDeviceTamagotchi() { 26 | return this.device instanceof tamagotchiDevice; 27 | } 28 | 29 | /** 30 | * @return {boolean} 31 | */ 32 | isDevicePc() { 33 | return this.device instanceof pcDevice; 34 | } 35 | 36 | /** 37 | * @override 38 | */ 39 | _createDevice() { 40 | let device; 41 | 42 | if (PLATFORM_NAME) { 43 | const factory = { 44 | 'tamagotchi': createTamagotchiDevice, 45 | 'pc': createPcDevice, 46 | }[PLATFORM_NAME]; 47 | 48 | if (factory) { 49 | device = factory(); 50 | } 51 | } else { 52 | device = ( 53 | createTamagotchiDevice() || 54 | createPcDevice() || 55 | null 56 | ); 57 | } 58 | 59 | if (!device) { 60 | throw new Error('Can\'t detect a platform.'); 61 | } 62 | 63 | return device; 64 | } 65 | }; 66 | -------------------------------------------------------------------------------- /test/tools/references/code-generation/define.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @const {boolean} 3 | */ 4 | export const ENABLE_CONSOLE = true; 5 | 6 | /** 7 | * @const {string} 8 | */ 9 | export const PLATFORM_NAME = ""; 10 | 11 | /** 12 | * @const {string} 13 | */ 14 | export const string = "foo"; 15 | 16 | /** 17 | * @const {number} 18 | */ 19 | export const number = 30; 20 | 21 | /** 22 | * @struct 23 | */ 24 | export const object = { 25 | /** 26 | * @const {boolean} 27 | */ 28 | boolean: true, 29 | 30 | /** 31 | * @const {number} 32 | */ 33 | number: 0 34 | }; 35 | 36 | /** 37 | * @struct 38 | */ 39 | export const several = { 40 | /** 41 | * @struct 42 | */ 43 | layers: { 44 | /** 45 | * @struct 46 | */ 47 | deep: { 48 | /** 49 | * @const {number} 50 | */ 51 | number: 10 52 | } 53 | } 54 | }; 55 | 56 | /** 57 | * @struct 58 | */ 59 | export const emptyObject = { 60 | 61 | }; 62 | 63 | /** 64 | * @const {undefined} 65 | */ 66 | export const undefined = undefined; 67 | 68 | /** 69 | * @const {null} 70 | */ 71 | export const nullValue = null; 72 | 73 | /** 74 | * @const {Array} 75 | */ 76 | export const stringArray = ["zero","one","two"]; 77 | 78 | /** 79 | * @const {Array} 80 | */ 81 | export const numbersArray = [1,2]; 82 | 83 | /** 84 | * @const {Array} 85 | */ 86 | export const mixedArray = ["zero",true,2]; 87 | 88 | /** 89 | * @const {Array>} 90 | */ 91 | export const arrayArray = [[1],[2,3]]; 92 | 93 | /** 94 | * @const {Array|Array>} 95 | */ 96 | export const mixedArrayArray = [["foo"],[2,3]]; 97 | 98 | /** 99 | * @const {Array} 100 | */ 101 | export const objectArray = [{"string":"foo"},{"string":"bar"}]; 102 | 103 | /** 104 | * @const {Array<*>} 105 | */ 106 | export const emptyArray = []; 107 | 108 | /** 109 | * @const {Function} 110 | */ 111 | export const functionValue = () => 0; 112 | 113 | /** 114 | * @const {string} 115 | */ 116 | export const NPM_PACKAGE_NAME = "zb-test"; 117 | 118 | /** 119 | * @const {string} 120 | */ 121 | export const NPM_PACKAGE_VERSION = "0.0.0"; 122 | 123 | /** 124 | * @const {string} 125 | */ 126 | export const ZOMBIEBOX_VERSION = "0.0.0"; -------------------------------------------------------------------------------- /test/tools/temporary-application-container.js: -------------------------------------------------------------------------------- 1 | const tmp = require('tmp'); 2 | const path = require('path'); 3 | const fse = require('fs-extra'); 4 | 5 | 6 | const zombieboxRoot = path.resolve(__dirname, '..', '..'); 7 | const boilerplate = path.resolve(__dirname, 'fixtures', 'application'); 8 | 9 | const Application = require('../../lib/application'); 10 | 11 | 12 | /** 13 | */ 14 | class TemporaryApplicationContainer { 15 | /** 16 | */ 17 | constructor() { 18 | /** 19 | * @type {string} 20 | * @private 21 | */ 22 | this._root; 23 | } 24 | 25 | /** 26 | * @return {Promise} 27 | */ 28 | async init() { 29 | tmp.setGracefulCleanup(); 30 | 31 | const tmpDir = tmp.dirSync({ 32 | prefix: 'zombiebox-tools-test-', 33 | keep: false, 34 | unsafeCleanup: true 35 | }); 36 | 37 | this._root = tmpDir.name; 38 | 39 | const nodeModules = path.resolve(this._root, 'node_modules'); 40 | 41 | await fse.mkdir(nodeModules); 42 | await this.installDependency('zombiebox', zombieboxRoot); 43 | await this.installDependency( 44 | 'zombiebox-platform-pc', 45 | path.dirname(require.resolve('zombiebox-platform-pc/package.json')), 46 | true 47 | ); 48 | // eslint-disable-next-line node/global-require 49 | const pcPackageJson = require('zombiebox-platform-pc/package.json'); 50 | for (const pcDependency of Object.keys(pcPackageJson['dependencies'])) { 51 | await this.installDependency( 52 | pcDependency, 53 | path.dirname(require.resolve(pcDependency + '/package.json')) 54 | ); 55 | } 56 | 57 | await fse.copy(boilerplate, this._root); 58 | } 59 | 60 | /** 61 | * @return {Promise} 62 | */ 63 | async cleanup() { 64 | await fse.remove(this._root); 65 | } 66 | 67 | /** 68 | * @param {string} name 69 | * @param {string} source 70 | * @param {boolean} copy 71 | * @return {Promise} 72 | */ 73 | async installDependency(name, source, copy = false) { 74 | const target = path.resolve(this._root, 'node_modules', name); 75 | 76 | if (copy) { 77 | await fse.copy(source, target); 78 | } else { 79 | await fse.symlink(source, target, 'junction'); 80 | } 81 | } 82 | 83 | /** 84 | * @param {...?} args 85 | * @return {Application} 86 | */ 87 | createZbApplication(...args) { 88 | return new Application(this._root, ...args); 89 | } 90 | 91 | /** 92 | * @param {string} file 93 | * @return {string} 94 | */ 95 | getFilePath(file) { 96 | return path.resolve(this._root, file); 97 | } 98 | 99 | /** 100 | * @param {string} file 101 | * @param {string} encoding 102 | * @return {Promise} 103 | */ 104 | async readFile(file, encoding = 'utf-8') { 105 | return fse.readFile(this.getFilePath(file), encoding); 106 | } 107 | 108 | /** 109 | * @param {string} file 110 | * @param {string} encoding 111 | * @return {Promise} 112 | */ 113 | async writeFile(file, encoding = 'utf-8') { 114 | return fse.writeFile(this.getFilePath(file), encoding); 115 | } 116 | } 117 | 118 | 119 | module.exports = TemporaryApplicationContainer; 120 | -------------------------------------------------------------------------------- /vendor/raf.js: -------------------------------------------------------------------------------- 1 | /* 2 | * raf.js 3 | * https://github.com/ngryman/raf.js 4 | * 5 | * original requestAnimationFrame polyfill by Erik Möller 6 | * inspired from paul_irish gist and post 7 | * 8 | * Copyright © 2013 ngryman 9 | * Licensed under the MIT license. 10 | */ 11 | 12 | (function(window) { 13 | var lastTime = 0, 14 | vendors = ['webkit', 'moz', 'o', 'ms'], 15 | requestAnimationFrame = window['requestAnimationFrame'], 16 | cancelAnimationFrame = window['cancelAnimationFrame'], 17 | i = vendors.length; 18 | 19 | // try to un-prefix existing raf 20 | while (--i >= 0 && !requestAnimationFrame) { 21 | requestAnimationFrame = window[vendors[i] + 'RequestAnimationFrame']; 22 | cancelAnimationFrame = window[vendors[i] + 'CancelAnimationFrame']; 23 | } 24 | 25 | // polyfill with setTimeout fallback 26 | // heavily inspired from @darius gist mod: https://gist.github.com/paulirish/1579671#comment-837945 27 | if (!requestAnimationFrame || !cancelAnimationFrame) { 28 | requestAnimationFrame = function(callback) { 29 | var now = Date.now(), nextTime = Math.max(lastTime + 16, now); 30 | return setTimeout(function() { 31 | callback(lastTime = nextTime); 32 | }, nextTime - now); 33 | }; 34 | 35 | cancelAnimationFrame = clearTimeout; 36 | } 37 | 38 | // export to window 39 | window['requestAnimationFrame'] = requestAnimationFrame; 40 | window['cancelAnimationFrame'] = cancelAnimationFrame; 41 | }(window)); 42 | -------------------------------------------------------------------------------- /zb/back-button-listener.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | 11 | /** 12 | * According to spec, browsers may impose limitations to calls for pushState: 13 | * https://html.spec.whatwg.org/multipage/history.html#history-notes 14 | * Additionally it's been observed that rapidly triggering history back causes state both in popstate and history.state 15 | * to become null. This is deduced to be a limitation on browser's part as well. 16 | * This is impossible to work around on ZombieBox level. 17 | * Therefore platforms relying on History API and BackButtonListener should be ready for these limitations. 18 | * Common case that causes problems are rapid keypress events emitted while back button is being held. 19 | * webOS does not trigger those for Back key, likely to avoid this problem. 20 | * Android platform was implemented to ignore key repeats as well. 21 | */ 22 | export default class BackButtonListener { 23 | /** 24 | * @param {function()} callback 25 | */ 26 | constructor(callback) { 27 | /** 28 | * @type {function()} 29 | * @protected 30 | */ 31 | this._callback = callback; 32 | 33 | // To avoid receiving null in popstate handler we need additional record 34 | // When user clicks Back for the first time one of these two will be 35 | // discarded and event callback will receive the second one 36 | this._pushDummyRecord(); 37 | this._pushDummyRecord(); 38 | 39 | this._bindHistoryListener(); 40 | } 41 | 42 | /** 43 | * @protected 44 | */ 45 | _pushDummyRecord() { 46 | if (window.history && window.history.pushState) { 47 | window.history.pushState({}, 'ZombieBox history'); 48 | } 49 | } 50 | 51 | /** 52 | * @protected 53 | */ 54 | _bindHistoryListener() { 55 | window.addEventListener('popstate', this._onHistoryPopState.bind(this)); 56 | } 57 | 58 | /** 59 | * @param {Event} event 60 | * @protected 61 | */ 62 | _onHistoryPopState(event) { 63 | const typedEvent = /** @type {PopStateEvent} */ (event); 64 | // WebKit approximately from 534.7 to 537.36 always fire additional popstate event on page load 65 | // see http://www.splefty.com/js/popstate.html 66 | // event.state can be null either on affected webkit or when we navigated through all the page states 67 | // all the way to the initial, which should never happen since we keep adding additional records and 68 | // also have a spare record inserted in the constructor. 69 | // Therefore we can discard such events. 70 | if (typedEvent.state === null) { 71 | this._pushDummyRecord(); 72 | 73 | return; 74 | } 75 | 76 | this._callback(); 77 | this._pushDummyRecord(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /zb/block.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import EventPublisher from './events/event-publisher'; 10 | 11 | 12 | /** 13 | */ 14 | export default class Block extends EventPublisher { 15 | /** 16 | */ 17 | constructor() { 18 | super(); 19 | 20 | /** 21 | * @type {boolean} 22 | * @protected 23 | */ 24 | this._isBlocked = false; 25 | 26 | /** 27 | * @type {Array} 28 | * @protected 29 | */ 30 | this._blocks = []; 31 | 32 | /** 33 | * Fired with: nothing 34 | * @const {string} 35 | */ 36 | this.EVENT_BLOCK = 'block'; 37 | 38 | /** 39 | * Fired with: nothing 40 | * @const {string} 41 | */ 42 | this.EVENT_UNBLOCK = 'unblock'; 43 | } 44 | 45 | /** 46 | * @param {Promise} promise 47 | * @return {Promise} 48 | */ 49 | block(promise) { 50 | if (-1 !== this._blocks.indexOf(promise)) { 51 | return promise; 52 | } 53 | 54 | this._blocks.push(promise); 55 | this._setBlocked(true); 56 | 57 | promise.finally(() => this._unblock(promise)); 58 | 59 | return promise; 60 | } 61 | 62 | /** 63 | * @return {boolean} 64 | */ 65 | isBlocked() { 66 | return this._isBlocked; 67 | } 68 | 69 | /** 70 | * @param {boolean} isBlocked 71 | * @protected 72 | */ 73 | _setBlocked(isBlocked) { 74 | if (this._isBlocked === isBlocked) { 75 | return; 76 | } 77 | 78 | this._isBlocked = isBlocked; 79 | 80 | if (isBlocked) { 81 | this._fireEvent(this.EVENT_BLOCK); 82 | } else { 83 | this._fireEvent(this.EVENT_UNBLOCK); 84 | } 85 | } 86 | 87 | /** 88 | * @param {Promise} promise 89 | * @protected 90 | */ 91 | _unblock(promise) { 92 | const index = this._blocks.indexOf(promise); 93 | if (index > -1) { 94 | this._blocks.splice(index, 1); 95 | } 96 | 97 | if (!this._blocks.length) { 98 | this._setBlocked(false); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /zb/console/console.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import {ENABLE_CONSOLE} from 'generated/define'; 10 | import ILogger from './interfaces/i-logger'; 11 | 12 | 13 | /** 14 | * Levels mask, 15 | * @param {number} levels 16 | */ 17 | export const setLevel = (levels) => { 18 | if (!ENABLE_CONSOLE) { 19 | return; 20 | } 21 | 22 | _levels = levels; 23 | }; 24 | 25 | 26 | /** 27 | * @param {Level} level 28 | */ 29 | export const enableLevel = (level) => { 30 | if (!ENABLE_CONSOLE) { 31 | return; 32 | } 33 | 34 | setLevel(_levels | level); 35 | }; 36 | 37 | 38 | /** 39 | * @param {Level} level 40 | */ 41 | export const disableLevel = (level) => { 42 | if (!ENABLE_CONSOLE) { 43 | return; 44 | } 45 | 46 | setLevel(_levels ^ level); 47 | }; 48 | 49 | 50 | /** 51 | * @param {ILogger} logger 52 | */ 53 | export const setLogger = (logger) => { 54 | if (!ENABLE_CONSOLE) { 55 | return; 56 | } 57 | 58 | _logger = logger; 59 | setLevel(_levels); 60 | }; 61 | 62 | 63 | /** 64 | * @return {ILogger} 65 | */ 66 | export const getLogger = () => _logger; 67 | 68 | 69 | /** 70 | * @param {...*} args 71 | */ 72 | export const log = (...args) => { 73 | if (_shouldPrint(Level.LOG)) { 74 | _logger.log(...args); 75 | } 76 | }; 77 | 78 | 79 | /** 80 | * @param {...*} args 81 | */ 82 | export const debug = (...args) => { 83 | if (_shouldPrint(Level.DEBUG)) { 84 | _logger.debug(...args); 85 | } 86 | }; 87 | 88 | 89 | /** 90 | * @param {...*} args 91 | */ 92 | export const info = (...args) => { 93 | if (_shouldPrint(Level.INFO)) { 94 | _logger.info(...args); 95 | } 96 | }; 97 | 98 | 99 | /** 100 | * @param {...*} args 101 | */ 102 | export const warn = (...args) => { 103 | if (_shouldPrint(Level.WARN)) { 104 | _logger.warn(...args); 105 | } 106 | }; 107 | 108 | 109 | /** 110 | * @param {...*} args 111 | */ 112 | export const error = (...args) => { 113 | if (_shouldPrint(Level.ERROR)) { 114 | _logger.error(...args); 115 | } 116 | }; 117 | 118 | 119 | /** 120 | * @param {...*} args 121 | */ 122 | export const assert = (...args) => { 123 | if (_shouldPrint(Level.ASSERT)) { 124 | _logger.assert(...args); 125 | } 126 | }; 127 | 128 | 129 | /** 130 | * @param {...*} args 131 | */ 132 | export const dir = (...args) => { 133 | if (_shouldPrint(Level.DIR)) { 134 | _logger.dir(...args); 135 | } 136 | }; 137 | 138 | 139 | /** 140 | * @param {string} label 141 | */ 142 | export const time = (label) => { 143 | if (_shouldPrint(Level.TIME)) { 144 | _logger.time(label); 145 | } 146 | }; 147 | 148 | 149 | /** 150 | * @param {string} label 151 | */ 152 | export const timeEnd = (label) => { 153 | if (_shouldPrint(Level.TIME)) { 154 | _logger.timeEnd(label); 155 | } 156 | }; 157 | 158 | 159 | /** 160 | * @param {number} level 161 | * @return {boolean} 162 | * @private 163 | */ 164 | const _shouldPrint = (level) => Boolean( 165 | ENABLE_CONSOLE && 166 | _logger && 167 | _levels & level 168 | ); 169 | 170 | 171 | /** 172 | * @type {?ILogger} 173 | * @private 174 | */ 175 | let _logger = null; 176 | 177 | 178 | /** 179 | * @enum {number} 180 | */ 181 | export const Level = { 182 | ALL: 255, 183 | LOG: 1, 184 | DEBUG: 2, 185 | INFO: 4, 186 | WARN: 8, 187 | ERROR: 16, 188 | ASSERT: 32, 189 | DIR: 64, 190 | TIME: 128 191 | }; 192 | 193 | 194 | /** 195 | * @type {number} 196 | * @private 197 | */ 198 | let _levels = Level.ALL; 199 | -------------------------------------------------------------------------------- /zb/console/interfaces/i-logger.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | 11 | /** 12 | * @interface 13 | */ 14 | export default class ILogger { 15 | /** 16 | * @param {...*} args 17 | */ 18 | log(args) {} 19 | 20 | /** 21 | * @param {...*} args 22 | */ 23 | debug(args) {} 24 | 25 | /** 26 | * @param {...*} args 27 | */ 28 | info(args) {} 29 | 30 | /** 31 | * @param {...*} args 32 | */ 33 | warn(args) {} 34 | 35 | /** 36 | * @param {...*} args 37 | */ 38 | error(args) {} 39 | 40 | /** 41 | * @param {...*} args 42 | */ 43 | assert(args) {} 44 | 45 | /** 46 | * @param {...*} args 47 | */ 48 | dir(args) {} 49 | 50 | /** 51 | * @param {string} label 52 | */ 53 | time(label) {} 54 | 55 | /** 56 | * @param {string} label 57 | */ 58 | timeEnd(label) {} 59 | } 60 | -------------------------------------------------------------------------------- /zb/console/loggers/alert.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import BaseLogger from './base-logger'; 10 | 11 | 12 | /** 13 | */ 14 | export default class Alert extends BaseLogger { 15 | /** 16 | * @override 17 | */ 18 | _send(level, args) { 19 | const strings = []; 20 | for (let i = 0; i < args.length; i++) { 21 | strings.push(String(args[i])); 22 | } 23 | 24 | const logStr = `Level: ${level} ${strings.join(' ')}`; 25 | window.alert(logStr); // eslint-disable-line no-alert 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /zb/console/loggers/base-logger.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import ILogger from '../interfaces/i-logger'; 10 | import {Level} from '../console'; 11 | 12 | 13 | /** 14 | * @implements {ILogger} 15 | */ 16 | export default class BaseLogger { 17 | /** 18 | */ 19 | constructor() { 20 | /** 21 | * @type {function(Level, Array<*>)} 22 | * @protected 23 | */ 24 | this._send; 25 | 26 | /** 27 | * @type {Object} 28 | * @protected 29 | */ 30 | this._timers = {}; 31 | } 32 | 33 | /** 34 | * @override 35 | */ 36 | log(...args) { 37 | this._log(Level.LOG, args); 38 | } 39 | 40 | /** 41 | * @override 42 | */ 43 | debug(...args) { 44 | this._log(Level.DEBUG, args); 45 | } 46 | 47 | /** 48 | * @override 49 | */ 50 | info(...args) { 51 | this._log(Level.INFO, args); 52 | } 53 | 54 | /** 55 | * @override 56 | */ 57 | warn(...args) { 58 | this._log(Level.WARN, args); 59 | } 60 | 61 | /** 62 | * @override 63 | */ 64 | error(...args) { 65 | this._log(Level.ERROR, args); 66 | } 67 | 68 | /** 69 | * @override 70 | */ 71 | assert(...args) { 72 | this._log(Level.ASSERT, args); 73 | } 74 | 75 | /** 76 | * @override 77 | */ 78 | dir(...args) { 79 | this._log(Level.DIR, args); 80 | } 81 | 82 | /** 83 | * @override 84 | */ 85 | time(label) { 86 | if (label) { 87 | this._timers[label] = Date.now(); 88 | } 89 | } 90 | 91 | /** 92 | * @override 93 | */ 94 | timeEnd(label) { 95 | const timers = this._timers; 96 | if (timers[label]) { 97 | this._log(Level.TIME, [`${label}: ${Date.now() - timers[label]}ms`]); 98 | delete timers[label]; 99 | } 100 | } 101 | 102 | /** 103 | * 104 | * @param {Level} level 105 | * @param {Array} args 106 | * @protected 107 | */ 108 | _log(level, args) { 109 | if (!this._send) { 110 | return; 111 | } 112 | this._send(level, Array.prototype.slice.call(args, 0)); 113 | } 114 | 115 | /** 116 | * @param {Level} level 117 | * @return {string} 118 | */ 119 | static level2string(level) { 120 | switch (level) { 121 | case Level.LOG: return 'LOG'; 122 | case Level.DEBUG: return 'DEBUG'; 123 | case Level.INFO: return 'INFO'; 124 | case Level.WARN: return 'WARN'; 125 | case Level.ERROR: return 'ERROR'; 126 | case Level.ASSERT: return 'ASSERT'; 127 | case Level.DIR: return 'DIR'; 128 | case Level.TIME: return 'TIME'; 129 | default: 130 | return 'ALL'; 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /zb/console/loggers/console.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import ILogger from '../interfaces/i-logger'; 10 | 11 | 12 | /** 13 | * @implements {ILogger} 14 | */ 15 | export default class Console { 16 | /** 17 | */ 18 | constructor() { 19 | const console = window['console'] || null; 20 | if (console) { 21 | this.info = console['info'].bind(console); 22 | this.dir = console['dir'].bind(console); 23 | this.log = console['log'].bind(console); 24 | this.debug = console['debug'].bind(console); 25 | this.assert = console['assert'].bind(console); 26 | this.error = console['error'].bind(console); 27 | this.warn = console['warn'].bind(console); 28 | this.time = console['time'].bind(console); 29 | this.timeEnd = console['timeEnd'].bind(console); 30 | } 31 | } 32 | 33 | /** 34 | * @override 35 | */ 36 | info() {/* Override in constructor */} 37 | 38 | /** 39 | * @override 40 | */ 41 | dir() {/* Override in constructor */} 42 | 43 | /** 44 | * @override 45 | */ 46 | log() {/* Override in constructor */} 47 | 48 | /** 49 | * @override 50 | */ 51 | debug() {/* Override in constructor */} 52 | 53 | /** 54 | * @override 55 | */ 56 | assert() {/* Override in constructor */} 57 | 58 | /** 59 | * @override 60 | */ 61 | error() {/* Override in constructor */} 62 | 63 | /** 64 | * @override 65 | */ 66 | warn() {/* Override in constructor */} 67 | 68 | /** 69 | * @override 70 | */ 71 | time() {/* Override in constructor */} 72 | 73 | /** 74 | * @override 75 | */ 76 | timeEnd() {/* Override in constructor */} 77 | } 78 | -------------------------------------------------------------------------------- /zb/console/loggers/screen.css: -------------------------------------------------------------------------------- 1 | .zb-debug-screen { 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | right: 0; 6 | z-index: 99999; 7 | padding: 10px; 8 | background-color: rgba(0, 0, 0, 0.6); 9 | } 10 | .zb-debug-screen__item { 11 | color: green; 12 | } 13 | -------------------------------------------------------------------------------- /zb/console/loggers/screen.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import {div, text} from '../../html'; 10 | import BaseLogger from './base-logger'; 11 | 12 | 13 | /** 14 | */ 15 | export default class Screen extends BaseLogger { 16 | /** 17 | * @param {number=} liveTime 18 | * @param {number=} itemRemoveDelay 19 | */ 20 | constructor(liveTime = DEFAULT_ITEM_LIVE_TIME, itemRemoveDelay = DEFAULT_ITEM_REMOVE_DELAY) { 21 | super(); 22 | 23 | /** 24 | * @type {number} 25 | * @protected 26 | */ 27 | this._liveTime = liveTime; 28 | 29 | /** 30 | * @type {number} 31 | * @protected 32 | */ 33 | this._itemRemoveDelay = itemRemoveDelay; 34 | 35 | /** 36 | * @type {?HTMLElement} 37 | * @protected 38 | */ 39 | this._container = null; 40 | 41 | /** 42 | * @type {number} 43 | * @protected 44 | */ 45 | this._itemsCount = 0; 46 | } 47 | 48 | /** 49 | * @override 50 | */ 51 | _send(level, args) { 52 | const item = div('zb-debug-screen__item'); 53 | 54 | text(item, args.map(String).join(' ')); 55 | 56 | this._getContainer().appendChild(item); 57 | this._itemsCount++; 58 | 59 | setTimeout( 60 | this._removeItem.bind(this, item), 61 | this._liveTime + (this._itemsCount * this._itemRemoveDelay) 62 | ); 63 | } 64 | 65 | /** 66 | * @return {!HTMLDivElement} 67 | * @protected 68 | */ 69 | _getContainer() { 70 | if (!this._container) { 71 | this._container = div('zb-debug-screen'); 72 | document.body.appendChild(this._container); 73 | } 74 | 75 | return /** @type {!HTMLDivElement} */ (this._container); 76 | } 77 | 78 | /** 79 | * @param {HTMLDivElement} item 80 | * @protected 81 | */ 82 | _removeItem(item) { 83 | this._container.removeChild(item); 84 | this._itemsCount--; 85 | 86 | if (!this._itemsCount) { 87 | document.body.removeChild(this._container); 88 | this._container = null; 89 | } 90 | } 91 | } 92 | 93 | 94 | /** 95 | * @const {number} 96 | */ 97 | export const DEFAULT_ITEM_LIVE_TIME = 5 * 1000; 98 | 99 | 100 | /** 101 | * @const {number} 102 | */ 103 | export const DEFAULT_ITEM_REMOVE_DELAY = 1 * 1000; 104 | -------------------------------------------------------------------------------- /zb/css/reset.css: -------------------------------------------------------------------------------- 1 | /* Author: Htmlhero (http://htmlhero.ru) */ 2 | 3 | /* Reset 4 | *******************************/ 5 | 6 | body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, pre, code, form, fieldset, legend, input, button, textarea, p, blockquote, th, td, 7 | article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { 8 | padding: 0; 9 | margin: 0; 10 | } 11 | table { 12 | border-collapse: collapse; 13 | border-spacing: 0; 14 | } 15 | table, fieldset, img { 16 | border: 0; 17 | } 18 | address, caption, cite, code, dfn, th, var { 19 | font-weight: normal; 20 | font-style: normal; 21 | } 22 | caption, th { 23 | text-align: left; 24 | } 25 | tr { 26 | vertical-align: top; 27 | } 28 | ul, ol { 29 | list-style: none; 30 | } 31 | h1, h2, h3, h4, h5, h6 { 32 | font-size: 100%; 33 | font-weight: normal; 34 | } 35 | blockquote, q { 36 | quotes: none; 37 | } 38 | blockquote:before, blockquote:after, q:before, q:after { 39 | content: ''; 40 | content: none; 41 | } 42 | input, textarea, select, button { 43 | font-family: inherit; 44 | font-size: 100%; 45 | } 46 | textarea { 47 | overflow: auto; 48 | resize: vertical; 49 | } 50 | button { 51 | width: auto; 52 | overflow: visible; 53 | cursor: pointer; 54 | } 55 | button::-moz-focus-inner { 56 | padding: 0; 57 | border: 0; 58 | } 59 | img { 60 | vertical-align: bottom; 61 | } 62 | a:active, 63 | :focus { 64 | outline: 0; 65 | } 66 | -------------------------------------------------------------------------------- /zb/css/zb.css: -------------------------------------------------------------------------------- 1 | /* Author: Htmlhero (http://htmlhero.ru) */ 2 | 3 | /* Globals 4 | *******************************/ 5 | 6 | html, body { 7 | height: 100%; 8 | } 9 | body { 10 | font-family: Arial, Helvetica, sans-serif; 11 | font-size: 12px; 12 | line-height: 1.25; 13 | overflow: hidden; 14 | } 15 | 16 | /* Helpers 17 | *******************************/ 18 | 19 | .zb-clear {} 20 | .zb-clear:before, 21 | .zb-clear:after { 22 | content: ''; 23 | display: table; 24 | } 25 | .zb-clear:after { 26 | clear: both; 27 | } 28 | 29 | .zb-valign { 30 | display: inline-block; 31 | vertical-align: middle; 32 | } 33 | .zb-valign-helper { 34 | width: 0; 35 | height: 100%; 36 | display: inline-block; 37 | vertical-align: middle; 38 | } 39 | 40 | .zb-center { 41 | width: 100%; 42 | height: 100%; 43 | display: table; 44 | position: absolute; 45 | top: 0; 46 | left: 0; 47 | } 48 | .zb-center__cell { 49 | text-align: center; 50 | vertical-align: middle; 51 | display: table-cell; 52 | } 53 | .zb-center__container { 54 | text-align: left; 55 | vertical-align: top; 56 | display: inline-block; 57 | } 58 | 59 | .zb-fullscreen { 60 | width: 100%; 61 | height: 100%; 62 | position: absolute; 63 | top: 0; 64 | left: 0; 65 | } 66 | 67 | /* Structure 68 | *******************************/ 69 | 70 | .zb-body { 71 | position: relative; 72 | user-select: none; 73 | } 74 | 75 | .zb-layer-container { 76 | background: #000; 77 | color: #fff; 78 | z-index: 1; 79 | } 80 | 81 | .zb-layer-container._transparent { 82 | background: transparent; 83 | } 84 | 85 | .zb-system-container { 86 | color: #fff; 87 | z-index: 2; 88 | } 89 | 90 | .zb-plugin-container { 91 | width: 0; 92 | height: 0; 93 | visibility: hidden; 94 | overflow: hidden; 95 | position: absolute; 96 | top: 100%; 97 | left: 100%; 98 | } 99 | 100 | /* Resolution 101 | *******************************/ 102 | 103 | .zb-qhd { 104 | width: 960px; 105 | height: 540px; 106 | } 107 | .zb-hd { 108 | width: 1280px; 109 | height: 720px; 110 | } 111 | .zb-full-hd { 112 | width: 1920px; 113 | height: 1080px; 114 | } 115 | 116 | /* Layer */ 117 | 118 | .zb-layer { 119 | z-index: 1; 120 | overflow: hidden; 121 | } 122 | .zb-layer__container { 123 | z-index: 1; 124 | } 125 | .zb-layer__children { 126 | z-index: 2; 127 | } 128 | .zb-layer__pointer-block { 129 | z-index: 3; 130 | } 131 | 132 | /* Widget */ 133 | 134 | .zb-widget {} 135 | -------------------------------------------------------------------------------- /zb/device/abstract-device.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import EventPublisher from '../events/event-publisher'; 10 | import IDevice from './interfaces/i-device'; 11 | import IInfo from './interfaces/i-info'; 12 | import IInput from './interfaces/i-input'; 13 | import IStorage from './interfaces/i-storage'; 14 | 15 | 16 | /** 17 | * @abstract 18 | * @implements {IDevice} 19 | */ 20 | export default class AbstractDevice extends EventPublisher { 21 | /** 22 | */ 23 | constructor() { 24 | super(); 25 | 26 | /** 27 | * @type {IInfo} 28 | */ 29 | this.info = null; 30 | 31 | /** 32 | * @type {IInput} 33 | */ 34 | this.input = null; 35 | 36 | /** 37 | * @type {IStorage} 38 | */ 39 | this.storage = null; 40 | 41 | /** 42 | * Fired with: nothing 43 | * @const {string} 44 | */ 45 | this.EVENT_READY = 'ready'; 46 | } 47 | 48 | /** 49 | * @abstract 50 | * @override 51 | */ 52 | init() {} 53 | 54 | /** 55 | * @abstract 56 | * @override 57 | */ 58 | createVideo(rect) {} 59 | 60 | /** 61 | * @abstract 62 | * @override 63 | */ 64 | createStatefulVideo() {} 65 | 66 | /** 67 | * @abstract 68 | * @override 69 | */ 70 | exit() {} 71 | 72 | /** 73 | * @abstract 74 | * @override 75 | */ 76 | getMAC() {} 77 | 78 | /** 79 | * @abstract 80 | * @override 81 | */ 82 | getIP() {} 83 | 84 | /** 85 | * @abstract 86 | * @override 87 | */ 88 | setOSDOpacity(value) {} 89 | 90 | /** 91 | * @abstract 92 | * @override 93 | */ 94 | getOSDOpacity() {} 95 | 96 | /** 97 | * @abstract 98 | * @override 99 | */ 100 | setOSDChromaKey(chromaKey) {} 101 | 102 | /** 103 | * @abstract 104 | * @override 105 | */ 106 | getOSDChromaKey() {} 107 | 108 | /** 109 | * @abstract 110 | * @override 111 | */ 112 | removeOSDChromaKey() {} 113 | 114 | /** 115 | * @abstract 116 | * @override 117 | */ 118 | getLaunchParams() {} 119 | 120 | /** 121 | * @abstract 122 | * @override 123 | */ 124 | getEnvironment() {} 125 | 126 | /** 127 | * @abstract 128 | * @override 129 | */ 130 | hasOSDOpacityFeature() {} 131 | 132 | /** 133 | * @abstract 134 | * @override 135 | */ 136 | hasOSDAlphaBlendingFeature() {} 137 | 138 | /** 139 | * @abstract 140 | * @override 141 | */ 142 | hasOSDChromaKeyFeature() {} 143 | 144 | /** 145 | * @abstract 146 | * @override 147 | */ 148 | isUHDSupported() {} 149 | 150 | /** 151 | * @abstract 152 | * @override 153 | */ 154 | isUHD8KSupported() {} 155 | } 156 | -------------------------------------------------------------------------------- /zb/device/aspect-ratio/aspect-ratio.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import {Proportion, Common} from './proportion'; 10 | 11 | 12 | /** 13 | */ 14 | export class AspectRatio { 15 | /** 16 | * @param {Proportion=} proportion 17 | * @param {Transferring=} transferring 18 | */ 19 | constructor(proportion = Common.KEEP, transferring = Transferring.KEEP) { 20 | /** 21 | * @type {Proportion} 22 | * @protected 23 | */ 24 | this._proportion = proportion; 25 | 26 | /** 27 | * @type {Transferring} 28 | * @protected 29 | */ 30 | this._transferring = transferring; 31 | } 32 | 33 | /** 34 | * @param {Proportion} proportion 35 | */ 36 | setProportion(proportion) { 37 | this._proportion = proportion; 38 | } 39 | 40 | /** 41 | * @return {Proportion} 42 | */ 43 | getProportion() { 44 | return this._proportion; 45 | } 46 | 47 | /** 48 | * @param {Transferring} transferring 49 | */ 50 | setTransferring(transferring) { 51 | this._transferring = transferring; 52 | } 53 | 54 | /** 55 | * @return {Transferring} 56 | */ 57 | getTransferring() { 58 | return this._transferring; 59 | } 60 | 61 | /** 62 | * @param {Proportion=} proportion 63 | * @return {boolean} 64 | */ 65 | checkProportionChangesFrom(proportion) { 66 | const currentProportion = this.getProportion(); 67 | 68 | const keep = (currentProportion === Common.KEEP); 69 | const diff = (currentProportion !== proportion); 70 | const unknown = (typeof proportion === 'undefined'); 71 | 72 | return (unknown || (!keep && diff)); 73 | } 74 | 75 | /** 76 | * @param {Transferring=} transferring 77 | * @return {boolean} 78 | */ 79 | checkTransferringChangesFrom(transferring) { 80 | const currentTransferring = this.getTransferring(); 81 | 82 | const keep = (currentTransferring === Transferring.KEEP); 83 | const diff = (currentTransferring !== transferring); 84 | const unknown = (typeof transferring === 'undefined'); 85 | 86 | return (unknown || (!keep && diff)); 87 | } 88 | 89 | /** 90 | * Check if this aspect ratio mode equals to another 91 | * @param {AspectRatio} that 92 | * @return {boolean} 93 | */ 94 | eq(that) { 95 | return (this.getProportion() === that.getProportion() && 96 | this.getTransferring() === that.getTransferring() 97 | ); 98 | } 99 | 100 | /** 101 | * Show human-readable value 102 | * @return {string} 103 | */ 104 | explain() { 105 | const proportion = AspectRatio.explainProportion(this.getProportion()); 106 | const transferring = AspectRatio.explainTransferring(this.getTransferring()); 107 | 108 | return (`${proportion}@${transferring}`); 109 | } 110 | 111 | /** 112 | * @param {Proportion} proportion 113 | * @return {string} 114 | */ 115 | static explainProportion(proportion) { 116 | return proportion.name; 117 | } 118 | 119 | /** 120 | * @param {Transferring} transferring 121 | * @return {string} 122 | */ 123 | static explainTransferring(transferring) { 124 | return transferring; 125 | } 126 | } 127 | 128 | 129 | /** 130 | * Video Aspect Ratio modes 131 | * @enum {string} 132 | */ 133 | export const Transferring = { 134 | KEEP: 'KEEP', 135 | AUTO: 'AUTO', 136 | LETTERBOX: 'LETTERBOX', 137 | CROP: 'CROP', 138 | STRETCH: 'STRETCH' 139 | }; 140 | -------------------------------------------------------------------------------- /zb/device/aspect-ratio/proportion.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | /** 11 | * @type {Object} 12 | */ 13 | const registry = {}; 14 | 15 | 16 | /** 17 | */ 18 | export class Proportion { 19 | /** 20 | * @param {string} name 21 | * @param {number=} x 22 | * @param {number=} y 23 | */ 24 | constructor(name, x = NaN, y = NaN) { 25 | /** 26 | * @type {string} 27 | * @protected 28 | */ 29 | this._name = name; 30 | 31 | /** 32 | * @type {number} 33 | * @protected 34 | */ 35 | this._x = x; 36 | 37 | /** 38 | * @type {number} 39 | * @protected 40 | */ 41 | this._y = y; 42 | 43 | const proportionString = this.toString(); 44 | 45 | if (!registry.hasOwnProperty(proportionString)) { 46 | registry[proportionString] = this; 47 | } 48 | 49 | // eslint-disable-next-line no-constructor-return 50 | return /** @type {!Proportion} */ (registry[proportionString]); 51 | } 52 | 53 | /** 54 | * @override 55 | */ 56 | toString() { 57 | return `${this._name}|${this._x}:${this._y}`; 58 | } 59 | 60 | /** 61 | * @return {number} 62 | */ 63 | get x() { 64 | return this._x; 65 | } 66 | 67 | /** 68 | * @param {number} value 69 | * @throws {Error} Inconditionally. 70 | */ 71 | set x(value) { 72 | throw new Error('Cannot set property x of # which has only a getter'); 73 | } 74 | 75 | /** 76 | * @return {number} 77 | */ 78 | get y() { 79 | return this._y; 80 | } 81 | 82 | /** 83 | * @param {number} value 84 | * @throws {Error} Inconditionally. 85 | */ 86 | set y(value) { 87 | throw new Error('Cannot set property y of # which has only a getter'); 88 | } 89 | 90 | /** 91 | * @return {string} 92 | */ 93 | get name() { 94 | return this._name; 95 | } 96 | 97 | /** 98 | * @param {string} value 99 | * @throws {Error} Inconditionally. 100 | */ 101 | set name(value) { 102 | throw new Error('Cannot set property name of # which has only a getter'); 103 | } 104 | } 105 | 106 | 107 | /** 108 | * Video Aspect Ratio modes 109 | * @enum {Proportion} 110 | */ 111 | export const Common = { 112 | KEEP: new Proportion('KEEP'), 113 | AUTO: new Proportion('AUTO'), 114 | X4X3: new Proportion('4:3', 4, 3), 115 | X16X9: new Proportion('16:9', 16, 9) 116 | }; 117 | -------------------------------------------------------------------------------- /zb/device/common/local-storage.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import {error} from '../../console/console'; 10 | import IStorage from '../interfaces/i-storage'; 11 | 12 | 13 | /** 14 | * @implements {IStorage} 15 | */ 16 | export default class LocalStorage { 17 | /** 18 | */ 19 | constructor() { 20 | /** 21 | * @type {string} 22 | * @protected 23 | */ 24 | this._prefix = ''; 25 | } 26 | 27 | /** 28 | * @override 29 | */ 30 | setKeyPrefix(prefix) { 31 | this._prefix = prefix; 32 | } 33 | 34 | /** 35 | * @override 36 | */ 37 | getItem(key) { 38 | return window.localStorage.getItem(this._prefix + key); 39 | } 40 | 41 | /** 42 | * @override 43 | */ 44 | setItem(key, value) { 45 | try { 46 | window.localStorage.setItem(this._prefix + key, value); 47 | } catch (e) { 48 | let msg = `Storage error: ${e}`; 49 | if (e.name === 'QUOTA_EXCEEDED_ERR') { 50 | msg = 'No space left on device'; 51 | } 52 | error(msg); 53 | throw msg; 54 | } 55 | } 56 | 57 | /** 58 | * @override 59 | */ 60 | removeItem(key) { 61 | window.localStorage.removeItem(this._prefix + key); 62 | } 63 | 64 | /** 65 | * @return {boolean} 66 | */ 67 | static isSupported() { 68 | return !!window.localStorage; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /zb/device/drm/abstract-drm-client.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import IDrmClient from '../interfaces/i-drm-client'; 10 | import EventPublisher from '../../events/event-publisher'; 11 | 12 | 13 | /** 14 | * @implements {IDrmClient} 15 | */ 16 | export default class AbstractDRMClient extends EventPublisher { 17 | /** 18 | */ 19 | constructor() { 20 | super(); 21 | 22 | /** 23 | * @type {string} 24 | */ 25 | this.type; 26 | 27 | /** 28 | * Fired with: {Error} 29 | * @const {string} 30 | */ 31 | this.EVENT_ERROR = 'error'; 32 | } 33 | 34 | /** 35 | * @return {Promise} 36 | */ 37 | init() { 38 | return Promise.resolve(); 39 | } 40 | 41 | /** 42 | * @return {Promise} 43 | */ 44 | prepare() { 45 | return Promise.resolve(); 46 | } 47 | 48 | /** 49 | * @return {Promise} 50 | */ 51 | destroy() { 52 | return Promise.resolve(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /zb/device/drm/drm.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | /* eslint-disable import/prefer-default-export */ 11 | 12 | /** 13 | * @enum {string} 14 | */ 15 | export const Type = { 16 | PLAYREADY: 'playready', 17 | VERIMATRIX: 'verimatrix', 18 | WIDEVINE_CLASSIC: 'widevine_classic', 19 | WIDEVINE_MODULAR: 'widevine_modular' 20 | }; 21 | -------------------------------------------------------------------------------- /zb/device/drm/playready-client.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import AbstractDRMClient from './abstract-drm-client'; 11 | import {Type} from './drm'; 12 | 13 | 14 | /** 15 | */ 16 | export default class PlayReadyClient extends AbstractDRMClient { 17 | /** 18 | * @param {string=} licenseServer 19 | */ 20 | constructor(licenseServer) { 21 | super(); 22 | 23 | this.type = Type.PLAYREADY; 24 | 25 | /** 26 | * @type {?string} 27 | */ 28 | this.licenseServer = licenseServer || null; 29 | } 30 | 31 | /** 32 | * @return {?string} 33 | */ 34 | getCustomData() { 35 | return null; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /zb/device/drm/verimatrix-client.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import AbstractDRMClient from './abstract-drm-client'; 11 | import {Type} from './drm'; 12 | 13 | 14 | /** 15 | */ 16 | export default class VerimatrixClient extends AbstractDRMClient { 17 | /** 18 | * @param {Params} params 19 | */ 20 | constructor(params) { 21 | super(); 22 | 23 | this.type = Type.VERIMATRIX; 24 | 25 | /** 26 | * @type {Params} 27 | * @protected 28 | */ 29 | this._params = params; 30 | } 31 | 32 | /** 33 | * @return {Params} 34 | */ 35 | getParams() { 36 | return this._params; 37 | } 38 | } 39 | 40 | 41 | /** 42 | * @typedef {{ 43 | * company: (string|undefined), 44 | * address: (string|undefined), 45 | * iptv: (string|undefined) 46 | * }} 47 | */ 48 | export let Params; 49 | -------------------------------------------------------------------------------- /zb/device/errors/unsupported-feature.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | 11 | /** 12 | */ 13 | export default class UnsupportedFeature extends Error { 14 | /** 15 | * @param {string} featureName 16 | */ 17 | constructor(featureName) { 18 | super(`Unsupported feature "${featureName}"`); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /zb/device/input/key.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | 11 | /** 12 | * @enum {number} 13 | */ 14 | const Key = { 15 | UNKNOWN: 0, 16 | LEFT: 1, 17 | UP: 2, 18 | RIGHT: 3, 19 | DOWN: 4, 20 | 21 | DIGIT_0: 5, 22 | DIGIT_1: 6, 23 | DIGIT_2: 7, 24 | DIGIT_3: 8, 25 | DIGIT_4: 9, 26 | DIGIT_5: 10, 27 | DIGIT_6: 11, 28 | DIGIT_7: 12, 29 | DIGIT_8: 13, 30 | DIGIT_9: 14, 31 | 32 | PLAY: 15, 33 | PAUSE: 16, 34 | STOP: 17, 35 | FWD: 18, 36 | REW: 19, 37 | 38 | BACK: 20, 39 | ENTER: 21, 40 | MENU: 22, 41 | INFO: 23, 42 | PAGE_UP: 24, 43 | PAGE_DOWN: 25, 44 | NEXT_CHAPTER: 26, 45 | PREV_CHAPTER: 27, 46 | 47 | VOLUME_DOWN: 28, 48 | VOLUME_UP: 29, 49 | 50 | RED: 30, 51 | GREEN: 31, 52 | BLUE: 32, 53 | YELLOW: 33, 54 | 55 | EXIT: 34, 56 | POWER: 35, 57 | REFRESH: 36, 58 | MUTE: 37, 59 | BACKSPACE: 38, 60 | PLAY_PAUSE: 39, 61 | TOOLS: 40, 62 | 63 | ASPECT_RATIO: 41, 64 | 65 | MOUSE_WHEEL_UP: 42, 66 | MOUSE_WHEEL_DOWN: 43, 67 | MOUSE_WHEEL_LEFT: 44, 68 | MOUSE_WHEEL_RIGHT: 45 69 | }; 70 | 71 | export default Key; 72 | -------------------------------------------------------------------------------- /zb/device/interfaces/i-device.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import UnsupportedFeature from '../errors/unsupported-feature'; 10 | import IEventPublisher from '../../events/interfaces/i-event-publisher'; 11 | import Rect from '../../geometry/rect'; 12 | import IInfo from './i-info'; 13 | import IInput from './i-input'; 14 | import IStorage from './i-storage'; 15 | import IVideo from './i-video'; 16 | import IStatefulVideo from './i-stateful-video'; 17 | 18 | 19 | /** 20 | * @interface 21 | * @extends {IEventPublisher} 22 | */ 23 | export default class IDevice { 24 | /** 25 | * Initializes all device properties: info, input, storage, etc.. 26 | */ 27 | init() {} 28 | 29 | /** 30 | * @param {Rect} rect 31 | * @return {IVideo} 32 | * @throws {UnsupportedFeature} 33 | */ 34 | createVideo(rect) {} 35 | 36 | /** 37 | * @return {IStatefulVideo} 38 | * @throws {UnsupportedFeature} 39 | */ 40 | createStatefulVideo() {} 41 | 42 | /** 43 | * Returns MAC address of active network connection 44 | * @return {string} 45 | * @throws {UnsupportedFeature} 46 | */ 47 | getMAC() {} 48 | 49 | /** 50 | * Returns IP address of active network connection 51 | * @return {string} 52 | * @throws {UnsupportedFeature} 53 | */ 54 | getIP() {} 55 | 56 | /** 57 | * @throws {UnsupportedFeature} 58 | */ 59 | exit() {} 60 | 61 | /** 62 | * @param {number} value 0..1 63 | * @throws {UnsupportedFeature} 64 | */ 65 | setOSDOpacity(value) {} 66 | 67 | /** 68 | * @return {number} 0..1 69 | * @throws {UnsupportedFeature} 70 | */ 71 | getOSDOpacity() {} 72 | 73 | /** 74 | * @param {string} chromaKey 75 | * @throws {UnsupportedFeature} 76 | */ 77 | setOSDChromaKey(chromaKey) {} 78 | 79 | /** 80 | * @return {?string} 81 | * @throws {UnsupportedFeature} 82 | */ 83 | getOSDChromaKey() {} 84 | 85 | /** 86 | * @throws {UnsupportedFeature} 87 | */ 88 | removeOSDChromaKey() {} 89 | 90 | /** 91 | * @return {boolean} 92 | */ 93 | hasOSDOpacityFeature() {} 94 | 95 | /** 96 | * @return {boolean} 97 | */ 98 | hasOSDAlphaBlendingFeature() {} 99 | 100 | /** 101 | * @return {boolean} 102 | */ 103 | hasOSDChromaKeyFeature() {} 104 | 105 | /** 106 | * Whether quad (1440p) and ultra (2160p) resolutions are supported by device 107 | * @return {boolean} 108 | */ 109 | isUHDSupported() {} 110 | 111 | /** 112 | * Whether 4320 resolution is supported by device 113 | * @return {boolean} 114 | */ 115 | isUHD8KSupported() {} 116 | 117 | /** 118 | * Returns system environment variables (if any) 119 | * @return {Object} 120 | * @throws {UnsupportedFeature} 121 | */ 122 | getEnvironment() {} 123 | 124 | /** 125 | * Returns params that application was launched with (if any) 126 | * @return {Object} 127 | * @throws {UnsupportedFeature} 128 | */ 129 | getLaunchParams() {} 130 | 131 | /** 132 | * Detects that current environment is device environment 133 | * @return {boolean} 134 | */ 135 | static detect() {} 136 | } 137 | 138 | 139 | /** 140 | * @type {IInfo} 141 | */ 142 | IDevice.prototype.info; 143 | 144 | 145 | /** 146 | * @type {IInput} 147 | */ 148 | IDevice.prototype.input; 149 | 150 | 151 | /** 152 | * @type {IStorage} 153 | */ 154 | IDevice.prototype.storage; 155 | 156 | 157 | /** 158 | * Fired with: nothing 159 | * @const {string} 160 | */ 161 | IDevice.prototype.EVENT_READY; 162 | -------------------------------------------------------------------------------- /zb/device/interfaces/i-drm-client.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import {Type} from '../drm/drm'; 10 | import IEventPublisher from '../../events/interfaces/i-event-publisher'; 11 | 12 | 13 | /** 14 | * @interface 15 | * @extends {IEventPublisher} 16 | */ 17 | export default class IDrmClient { 18 | /** 19 | */ 20 | constructor() { 21 | /** 22 | * @type {Type|string} 23 | */ 24 | this.type; 25 | 26 | /** 27 | * Fired with: {Error} 28 | * @const {string} 29 | */ 30 | this.EVENT_ERROR; 31 | } 32 | 33 | /** 34 | * Called when DRM client is attached to Video object. Do device or backend specific initialisation here. 35 | * @return {Promise} 36 | */ 37 | init() {} 38 | 39 | /** 40 | * Called when Video starts preparing media stream, do stream specific initialisation here. 41 | * @return {Promise} 42 | */ 43 | prepare() {} 44 | 45 | /** 46 | * @return {Promise} 47 | */ 48 | destroy() {} 49 | } 50 | -------------------------------------------------------------------------------- /zb/device/interfaces/i-info.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import {Resolution} from '../resolutions'; 10 | import UnsupportedFeature from '../errors/unsupported-feature'; 11 | 12 | 13 | /** 14 | * @interface 15 | */ 16 | export default class IInfo { 17 | /** 18 | * Returns device type (e.g. 'pc', 'dune', 'samsung' etc.) 19 | * @return {string} 20 | * @throws {UnsupportedFeature} 21 | */ 22 | type() {} 23 | 24 | /** 25 | * Returns device OS version 26 | * @return {string} 27 | * @throws {UnsupportedFeature} 28 | */ 29 | version() {} 30 | 31 | /** 32 | * Returns device manufacturer/vendor 33 | * @return {string} 34 | * @throws {UnsupportedFeature} 35 | */ 36 | manufacturer() {} 37 | 38 | /** 39 | * Returns device model string 40 | * @return {string} 41 | * @throws {UnsupportedFeature} 42 | */ 43 | model() {} 44 | 45 | /** 46 | * Returns device serial number 47 | * @return {string} 48 | * @throws {UnsupportedFeature} 49 | */ 50 | serialNumber() {} 51 | 52 | /** 53 | * Returns device software version 54 | * @return {string} 55 | * @throws {UnsupportedFeature} 56 | */ 57 | softwareVersion() {} 58 | 59 | /** 60 | * Returns device hardware version 61 | * @return {string} 62 | * @throws {UnsupportedFeature} 63 | */ 64 | hardwareVersion() {} 65 | 66 | /** 67 | * @deprecated Use getOSDResolution instead 68 | * @return {Resolution} 69 | * @throws {UnsupportedFeature} 70 | */ 71 | osdResolutionType() {} 72 | 73 | /** 74 | * Returns physical screen panel resolution, typically largest possible resolution for video 75 | * @return {!Resolution} 76 | * @throws {UnsupportedFeature} 77 | */ 78 | getPanelResolution() {} 79 | 80 | /** 81 | * Returns html viewport resolution 82 | * @return {!Resolution} 83 | * @throws {UnsupportedFeature} 84 | */ 85 | getOSDResolution() {} 86 | 87 | /** 88 | * Returns current locale valid for BCP47 format or null 89 | * @return {?string} 90 | * @throws {UnsupportedFeature} 91 | */ 92 | locale() {} 93 | } 94 | -------------------------------------------------------------------------------- /zb/device/interfaces/i-input.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import UnsupportedFeature from '../errors/unsupported-feature'; 10 | import Key from '../input/key'; 11 | import IEventPublisher from '../../events/interfaces/i-event-publisher'; 12 | 13 | 14 | /** 15 | * @interface 16 | * @extends {IEventPublisher} 17 | */ 18 | export default class IInput { 19 | /** 20 | * @param {(KeyboardEvent|WheelEvent)} e 21 | * @return {Key} 22 | */ 23 | eventToKeyCode(e) {} 24 | 25 | /** 26 | * @param {KeyboardEvent} keyboardEvent 27 | * @return {?string} Printable char or group of chars 28 | */ 29 | eventToPrintableChar(keyboardEvent) {} 30 | 31 | /** 32 | * @param {Key} zbKey 33 | * @return {?string} Printable char or group of chars 34 | */ 35 | keyToPrintableChar(zbKey) {} 36 | 37 | /** 38 | * @param {function((KeyboardEvent|WheelEvent))} handler 39 | */ 40 | setKeyEventHandler(handler) {} 41 | 42 | /** 43 | * Blocks user input and doesn't pass input device events to handlers 44 | * @return {number} Block id. 45 | */ 46 | block() {} 47 | 48 | /** 49 | * Unblocks user input and continues passing input device events to handlers 50 | * @param {number} id Block id from .block() result 51 | */ 52 | unblock(id) {} 53 | 54 | /** 55 | * Is input blocked? 56 | * @return {boolean} 57 | */ 58 | isBlocked() {} 59 | 60 | /** 61 | * Checks if platform supports pointing device 62 | * @return {boolean} 63 | */ 64 | isPointingDeviceSupported() {} 65 | 66 | /** 67 | * Checks if pointing device is active now 68 | * @return {boolean} 69 | */ 70 | isPointingDeviceActive() {} 71 | 72 | /** 73 | * Enables pointing device 74 | * @throws {UnsupportedFeature} 75 | */ 76 | enablePointingDevice() {} 77 | 78 | /** 79 | * Disables pointing device 80 | * @param {number=} timeout 81 | * @throws {UnsupportedFeature} 82 | */ 83 | disablePointingDevice(timeout) {} 84 | 85 | /** 86 | * Enables IDLE deactivation of pointing device 87 | * @param {number=} timeout 88 | */ 89 | enablePointingDeviceIdle(timeout) {} 90 | 91 | /** 92 | * Disables IDLE deactivation of pointing device 93 | */ 94 | disablePointingDeviceIdle() {} 95 | 96 | /** 97 | * @param {number} ms 98 | * @return {boolean} 99 | */ 100 | setPointingDeviceIdleTime(ms) {} 101 | } 102 | 103 | 104 | /** 105 | * UI is now controlling using device with pointer support 106 | * Fired with: nothing 107 | * @const {string} 108 | */ 109 | IInput.prototype.EVENT_POINTING_DEVICE_ACTIVATED; 110 | 111 | 112 | /** 113 | * UI is now controlling using device without pointer support 114 | * Fired with: nothing 115 | * @const {string} 116 | */ 117 | IInput.prototype.EVENT_POINTING_DEVICE_DEACTIVATED; 118 | -------------------------------------------------------------------------------- /zb/device/interfaces/i-storage.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import UnsupportedFeature from '../errors/unsupported-feature'; 10 | 11 | 12 | /** 13 | * @interface 14 | */ 15 | export default class IStorage { 16 | /** 17 | * Auto append prefix for all keys 18 | * @param {string} prefix 19 | * @throws {UnsupportedFeature} 20 | */ 21 | setKeyPrefix(prefix) {} 22 | 23 | /** 24 | * @param {string} key 25 | * @return {?string} 26 | * @throws {UnsupportedFeature} 27 | */ 28 | getItem(key) {} 29 | 30 | /** 31 | * @param {string} key 32 | * @param {string} data 33 | * @throws {UnsupportedFeature} 34 | */ 35 | setItem(key, data) {} 36 | 37 | /** 38 | * @param {string} key 39 | * @throws {UnsupportedFeature} 40 | */ 41 | removeItem(key) {} 42 | } 43 | -------------------------------------------------------------------------------- /zb/device/interfaces/i-view-port.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import {AspectRatio} from '../aspect-ratio/aspect-ratio'; 10 | import UnsupportedFeature from '../errors/unsupported-feature'; 11 | import Rect from '../../geometry/rect'; 12 | 13 | 14 | /** 15 | * @interface 16 | */ 17 | export default class IViewPort { 18 | /** 19 | * @param {AspectRatio} ratio 20 | * @throws {UnsupportedFeature} 21 | */ 22 | setAspectRatio(ratio) {} 23 | 24 | /** 25 | * @return {?AspectRatio} 26 | * @throws {UnsupportedFeature} 27 | */ 28 | getAspectRatio() {} 29 | 30 | /** 31 | * Toggles aspect ratio from array 32 | * @param {Array} bunch 33 | * @throws {UnsupportedFeature} 34 | */ 35 | toggleAspectRatio(bunch) {} 36 | 37 | /** 38 | * Sets display area 39 | * @param {Rect} rect 40 | * @throws {UnsupportedFeature} 41 | */ 42 | setArea(rect) {} 43 | 44 | /** 45 | * Returns display area 46 | * @return {?Rect} 47 | * @throws {UnsupportedFeature} 48 | */ 49 | getArea() {} 50 | 51 | /** 52 | * Returns display area in the current moment 53 | * @return {Rect} 54 | */ 55 | getCurrentArea() {} 56 | 57 | /** 58 | * Sets resolution to full screen 59 | * @param {boolean} state 60 | */ 61 | setFullScreen(state) {} 62 | 63 | /** 64 | * @deprecated Use isFullScreen 65 | * Returns full screen state 66 | * @return {boolean} 67 | */ 68 | getFullScreen() {} 69 | 70 | /** 71 | * Returns full screen area 72 | * @return {Rect} 73 | */ 74 | getFullScreenArea() {} 75 | 76 | /** 77 | * Checks whether display resolution is full screen 78 | * @return {boolean} 79 | */ 80 | isFullScreen() {} 81 | 82 | /** 83 | * Updates viewport with current area 84 | */ 85 | updateViewPort() {} 86 | 87 | /** 88 | * @param {AspectRatio} ratio 89 | * @return {boolean} true if requested ratio supported 90 | */ 91 | isAspectRatioSupported(ratio) {} 92 | 93 | /** 94 | * Checks support of getAspectRatio, setAspectRatio, toggleAspectRatio 95 | * @return {boolean} 96 | */ 97 | hasAspectRatioFeature() {} 98 | 99 | /** 100 | * Checks support of setArea, getArea 101 | * @return {boolean} 102 | */ 103 | hasAreaChangeFeature() {} 104 | } 105 | -------------------------------------------------------------------------------- /zb/device/resolutions.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import Rect from '../geometry/rect'; 10 | 11 | 12 | /** 13 | * @enum {number} 14 | */ 15 | export const Resolution = { 16 | HD: 2, 17 | FULL_HD: 3, 18 | UHD_4K: 4, 19 | UHD_8K: 5 20 | }; 21 | 22 | 23 | /** 24 | * @typedef {{ 25 | * name: string, 26 | * width: number, 27 | * height: number 28 | * }} 29 | */ 30 | export let ResolutionInfoItem; 31 | 32 | 33 | /** 34 | * @type {Object} 35 | */ 36 | export const ResolutionInfo = { 37 | [Resolution.HD]: { 38 | name: 'hd', 39 | width: 1280, 40 | height: 720 41 | }, 42 | 43 | [Resolution.FULL_HD]: { 44 | name: 'full-hd', 45 | width: 1920, 46 | height: 1080 47 | }, 48 | 49 | [Resolution.UHD_4K]: { 50 | name: '4k', 51 | width: 3840, 52 | height: 2160 53 | }, 54 | 55 | [Resolution.UHD_8K]: { 56 | name: '4k', 57 | width: 7680, 58 | height: 4320 59 | } 60 | }; 61 | 62 | 63 | /** 64 | * @param {Rect} rect 65 | * @return {Resolution} 66 | */ 67 | export const findLargest = (rect) => { 68 | const size = rect.getSize(); 69 | 70 | return Object.values(Resolution) 71 | .filter((resolution) => { 72 | const info = ResolutionInfo[resolution]; 73 | return info.width <= size.x && info.height <= size.y; 74 | }) 75 | .sort((a, b) => { 76 | const aInfo = ResolutionInfo[a]; 77 | const bInfo = ResolutionInfo[b]; 78 | 79 | if (aInfo.width === bInfo.width) { 80 | return bInfo.height - aInfo.height; 81 | } 82 | 83 | return bInfo.width - aInfo.width; 84 | }) 85 | .shift() || null; 86 | }; 87 | 88 | /** 89 | * @param {Rect} rect 90 | * @param {ResolutionInfoItem} from 91 | * @param {ResolutionInfoItem} to 92 | * @param {function(number): number} round 93 | * @return {Rect} 94 | */ 95 | export const translate = (rect, from, to, round = Math.round) => { 96 | const widthScale = to.width / from.width; 97 | const heightScale = to.height / from.height; 98 | 99 | return new Rect({ 100 | x0: round(rect.x0 * widthScale), 101 | y0: round(rect.y0 * heightScale), 102 | x1: round(rect.x1 * widthScale), 103 | y1: round(rect.y1 * heightScale) 104 | }); 105 | }; 106 | 107 | /** 108 | * @param {ResolutionInfoItem} info 109 | * @return {Rect} 110 | */ 111 | export const createResolutionRect = (info) => new Rect({ 112 | x0: 0, 113 | y0: 0, 114 | x1: info.width, 115 | y1: info.height 116 | }); 117 | -------------------------------------------------------------------------------- /zb/events/interfaces/i-event-publisher.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | 11 | /** 12 | * Event Object 13 | * Implements event-model to mixin for classes require 14 | * @interface 15 | */ 16 | export default class IEventPublisher { 17 | /** 18 | * Add event listener 19 | * @param {string} event 20 | * @param {Listener} callback 21 | */ 22 | on(event, callback) {} 23 | 24 | /** 25 | * Remove event listener 26 | * @param {string} event 27 | * @param {Listener} callback 28 | */ 29 | off(event, callback) {} 30 | 31 | /** 32 | * Add event listener once 33 | * @param {string} event 34 | * @param {Listener} callback 35 | */ 36 | once(event, callback) {} 37 | 38 | /** 39 | * Remove event listener 40 | * @param {string=} event 41 | */ 42 | removeAllListeners(event) {} 43 | } 44 | 45 | 46 | /** 47 | * @const {string} 48 | */ 49 | IEventPublisher.prototype.EVENT_ANY; 50 | 51 | 52 | /** 53 | * @typedef {function(string, ...?)} 54 | */ 55 | export let Listener; 56 | -------------------------------------------------------------------------------- /zb/geometry/area.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import Rect, {Value as RectValue} from './rect'; 10 | 11 | 12 | /** 13 | */ 14 | export default class Area { 15 | /** 16 | * @param {Value} value 17 | */ 18 | constructor(value) { 19 | /** 20 | * @type {Value} 21 | */ 22 | this.value = value; 23 | } 24 | 25 | /** 26 | * @return {Value} 27 | */ 28 | getValue() { 29 | return this.value; 30 | } 31 | 32 | /** 33 | * @return {boolean} 34 | */ 35 | isEmpty() { 36 | return this.getValue().every((rect) => rect.isEmpty()); 37 | } 38 | 39 | /** 40 | * @return {Rect} 41 | */ 42 | extrapolate() { 43 | const rects = this.getValue(); 44 | if (!rects.length) { 45 | return Rect.createEmptyRect(); 46 | } 47 | 48 | const config = rects.reduce((prev, next) => ({ 49 | x0: Math.min(prev.x0, next.x0), 50 | y0: Math.min(prev.y0, next.y0), 51 | x1: Math.max(prev.x1, next.x1), 52 | y1: Math.max(prev.y1, next.y1) 53 | })); 54 | 55 | return Rect.create(/** @type {RectValue} **/ (config)); 56 | } 57 | 58 | /** 59 | * @param {Value} value 60 | * @return {Area} 61 | */ 62 | static create(value) { 63 | return new Area(value); 64 | } 65 | } 66 | 67 | 68 | /** 69 | * @typedef {Array} 70 | */ 71 | export let Value; 72 | -------------------------------------------------------------------------------- /zb/geometry/axis.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | 11 | /** 12 | * @enum {number} 13 | */ 14 | const Axis = { 15 | X: 0, 16 | Y: 1 17 | }; 18 | 19 | export default Axis; 20 | -------------------------------------------------------------------------------- /zb/geometry/corner.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import Axis from './axis'; 10 | import Direction, {Sign} from './direction'; 11 | 12 | 13 | /** 14 | */ 15 | export default class Corner { 16 | /** 17 | * @param {Array} signs 18 | */ 19 | constructor(signs) { 20 | /** 21 | * @type {Array} 22 | * @protected 23 | */ 24 | this._signs; 25 | 26 | this.setSigns(signs); 27 | } 28 | 29 | /** 30 | * @param {Axis} axis 31 | * @return {Direction} 32 | */ 33 | getDirectionByAxis(axis) { 34 | const sign = this.getSignByAxis(axis); 35 | 36 | return Direction.createByCode(sign * (axis + 1)); 37 | } 38 | 39 | /** 40 | * @param {Axis} axis 41 | * @return {Sign} 42 | */ 43 | getSignByAxis(axis) { 44 | const signs = this.getSigns(); 45 | 46 | return signs[axis]; 47 | } 48 | 49 | /** 50 | * @return {Array} 51 | */ 52 | getDirections() { 53 | return this.getSigns() 54 | .map((sign, axis) => Direction.createByCode(sign * (axis + 1))); 55 | } 56 | 57 | /** 58 | * @return {Array} 59 | */ 60 | getSigns() { 61 | return this._signs; 62 | } 63 | 64 | /** 65 | * @param {Array} signs 66 | */ 67 | setSigns(signs) { 68 | this._signs = signs; 69 | } 70 | 71 | /** 72 | * @param {Array} signs 73 | * @return {Corner} 74 | */ 75 | static createBySigns(signs) { 76 | return new Corner(signs); 77 | } 78 | 79 | /** 80 | * @param {Array} directions 81 | * @return {Corner} 82 | */ 83 | static createByDirections(directions) { 84 | const signs = []; 85 | directions.forEach((dir) => { 86 | signs[dir.getAxis()] = dir.getSign(); 87 | }); 88 | 89 | return Corner.createBySigns(signs); 90 | } 91 | 92 | /** 93 | * @return {Corner} 94 | */ 95 | static createLeftUp() { 96 | return Corner.createByDirections([ 97 | Direction.createLeft(), 98 | Direction.createUp() 99 | ]); 100 | } 101 | 102 | /** 103 | * @return {Corner} 104 | */ 105 | static createLeftDown() { 106 | return Corner.createByDirections([ 107 | Direction.createLeft(), 108 | Direction.createDown() 109 | ]); 110 | } 111 | 112 | /** 113 | * @return {Corner} 114 | */ 115 | static createRightUp() { 116 | return Corner.createByDirections([ 117 | Direction.createRight(), 118 | Direction.createUp() 119 | ]); 120 | } 121 | 122 | /** 123 | * @return {Corner} 124 | */ 125 | static createRightDown() { 126 | return Corner.createByDirections([ 127 | Direction.createRight(), 128 | Direction.createDown() 129 | ]); 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /zb/hacks.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | // Workaround for https://github.com/google/closure-compiler/issues/3139 4 | // TODO: Remove once issue is resolved 5 | window[Symbol('Object spread Object.assign polyfill hack')] = Object.assign({}, {}); 6 | -------------------------------------------------------------------------------- /zb/history/history-manager.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import IHistoryManager from './interfaces/i-history-manager'; 10 | import IStateful from './interfaces/i-stateful'; 11 | 12 | 13 | /** 14 | * @implements {IHistoryManager} 15 | */ 16 | export default class HistoryManager { 17 | /** 18 | */ 19 | constructor() { 20 | /** 21 | * @type {Array} 22 | * @protected 23 | */ 24 | this._records = []; 25 | 26 | /** 27 | * @type {number} 28 | * @protected 29 | */ 30 | this._currentPosition = -1; 31 | } 32 | 33 | /** 34 | * @param {Array} objects 35 | */ 36 | addRecord(objects) { 37 | this._saveCurrentState(); 38 | this._currentPosition++; 39 | 40 | this._records = this._records.slice(0, this._currentPosition); 41 | this._records.push({ 42 | objects: objects, 43 | state: [] 44 | }); 45 | } 46 | 47 | /** 48 | * @param {number} delta 49 | * @return {Promise} 50 | */ 51 | go(delta) { 52 | const record = this._records[this._currentPosition + delta]; 53 | if (delta === 0 || !record) { 54 | return Promise.resolve(); 55 | } 56 | 57 | return Promise.resolve() 58 | .then(() => { 59 | this._saveCurrentState(); 60 | 61 | return this._loadState(record); 62 | }) 63 | .then(() => { 64 | this._currentPosition += delta; 65 | }); 66 | } 67 | 68 | /** 69 | * Clear all history records 70 | */ 71 | clear() { 72 | this._records = []; 73 | this._currentPosition = -1; 74 | } 75 | 76 | /** 77 | * @return {Promise} 78 | */ 79 | forward() { 80 | return this.go(+1); 81 | } 82 | 83 | /** 84 | * @return {Promise} 85 | */ 86 | back() { 87 | return this.go(-1); 88 | } 89 | 90 | /** 91 | * @return {boolean} 92 | */ 93 | canBack() { 94 | return this._currentPosition > 0; 95 | } 96 | 97 | /** 98 | * @return {boolean} 99 | */ 100 | canForward() { 101 | return this._currentPosition < (this._records.length - 1); 102 | } 103 | 104 | /** 105 | * @return {number} 106 | */ 107 | get length() { 108 | return this._records.length; 109 | } 110 | 111 | /** 112 | * @return {HistoryRecord} 113 | * @protected 114 | */ 115 | _getCurrentRecord() { 116 | return this._records[this._currentPosition] || null; 117 | } 118 | 119 | /** 120 | * @protected 121 | */ 122 | _saveCurrentState() { 123 | const currentRecord = this._getCurrentRecord(); 124 | if (currentRecord) { 125 | this._saveState(currentRecord); 126 | } 127 | } 128 | 129 | /** 130 | * @param {HistoryRecord} record 131 | * @protected 132 | */ 133 | _saveState(record) { 134 | record.state = record.objects 135 | .map((object) => object.takeSnapshot()); 136 | } 137 | 138 | /** 139 | * @param {HistoryRecord} record 140 | * @return {Promise} 141 | * @protected 142 | */ 143 | _loadState(record) { 144 | return record.state.filter(Boolean) 145 | .reduce((thread, snapshot) => thread.then(snapshot), Promise.resolve()); 146 | } 147 | } 148 | 149 | 150 | /** 151 | * @typedef {{ 152 | * objects: Array, 153 | * state: Array 154 | * }} 155 | */ 156 | export let HistoryRecord; 157 | -------------------------------------------------------------------------------- /zb/history/interfaces/i-history-manager.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import IStateful from './i-stateful'; 10 | 11 | 12 | /** 13 | * @interface 14 | */ 15 | export default class IHistoryManager { 16 | /** 17 | * @param {Array} objects 18 | */ 19 | addRecord(objects) {} 20 | 21 | /** 22 | * @param {number} delta 23 | * @return {Promise} 24 | */ 25 | go(delta) {} 26 | 27 | /** 28 | * Clear all history records 29 | */ 30 | clear() {} 31 | 32 | /** 33 | * @return {Promise} 34 | */ 35 | forward() {} 36 | 37 | /** 38 | * @return {Promise} 39 | */ 40 | back() {} 41 | 42 | /** 43 | * @return {boolean} 44 | */ 45 | canBack() {} 46 | 47 | /** 48 | * @return {boolean} 49 | */ 50 | canForward() {} 51 | } 52 | 53 | 54 | /** 55 | * @type {number} 56 | */ 57 | IHistoryManager.prototype.length; 58 | -------------------------------------------------------------------------------- /zb/history/interfaces/i-stateful.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | 11 | /** 12 | * @interface 13 | */ 14 | export default class IStateful { 15 | /** 16 | * @return {function()} function that restores state 17 | */ 18 | takeSnapshot() {} 19 | } 20 | -------------------------------------------------------------------------------- /zb/http/abstract-transport.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import app from 'generated/app'; 10 | 11 | 12 | /** 13 | * @template REQUEST_PARAMS 14 | * @abstract 15 | */ 16 | export default class AbstractTransport { 17 | /** 18 | */ 19 | constructor() { 20 | /** 21 | * @type {string} 22 | * @protected 23 | */ 24 | this._baseUrl = ''; 25 | 26 | /** 27 | * @type {function(Error, REQUEST_PARAMS): Promise} 28 | * @protected 29 | */ 30 | this._transportErrorHandler; 31 | 32 | this.setTransportErrorHandler(this.defaultTransportErrorHandler.bind(this)); 33 | } 34 | 35 | /** 36 | * @param {function(Error, REQUEST_PARAMS): Promise} handler 37 | */ 38 | setTransportErrorHandler(handler) { 39 | this._transportErrorHandler = handler; 40 | } 41 | 42 | /** 43 | * @param {string} url 44 | */ 45 | setBaseUrl(url) { 46 | this._baseUrl = url; 47 | } 48 | 49 | /** 50 | * @return {string} baseUrl 51 | */ 52 | getBaseUrl() { 53 | return this._baseUrl; 54 | } 55 | 56 | /** 57 | * @return {Promise} 58 | */ 59 | defaultTransportErrorHandler() { 60 | return Promise.resolve(HandleCode.ABORT); 61 | } 62 | 63 | /** 64 | * Does the Same as overridden doRequest 65 | * @param {REQUEST_PARAMS} params 66 | * @return {Promise} 67 | */ 68 | doPersistentRequest(params) { 69 | return this._persistRequest((params) => this.doRequest(params), params, this._transportErrorHandler); 70 | } 71 | 72 | /** 73 | * API defines type of arguments 74 | * @abstract 75 | * @param {REQUEST_PARAMS} params 76 | * @return {Promise} 77 | */ 78 | doRequest(params) {} 79 | 80 | /** 81 | * @param {function(REQUEST_PARAMS): Promise} request 82 | * @param {REQUEST_PARAMS} requestParams 83 | * @param {function(Error, REQUEST_PARAMS): Promise} errorHandler 84 | * @return {Promise} 85 | * @protected 86 | */ 87 | _persistRequest(request, requestParams, errorHandler) { 88 | return new Promise((resolve, reject) => { 89 | const code = HandleCode; 90 | 91 | const onFail = (error) => errorHandler(error, requestParams) 92 | .then((reason) => { 93 | switch (reason) { 94 | case code.RETRY: 95 | request(requestParams).then(resolve, onFail); 96 | break; 97 | case code.ABORT: 98 | reject(error); 99 | break; 100 | case code.EXIT: 101 | app.exit(); 102 | break; 103 | } 104 | }); 105 | 106 | request(requestParams).then(resolve, onFail); 107 | }); 108 | } 109 | } 110 | 111 | 112 | /** 113 | * @enum {string} 114 | */ 115 | export const HandleCode = { 116 | RETRY: 'retry', 117 | ABORT: 'abort', 118 | EXIT: 'exit' 119 | }; 120 | -------------------------------------------------------------------------------- /zb/http/http.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | 11 | /** 12 | * @param {string} url 13 | * @param {Object} query 14 | * @return {string} 15 | */ 16 | export const buildQueryString = (url, query) => { 17 | const encodedParams = encodeParams(query); 18 | let result = url; 19 | if (encodedParams) { 20 | const delimiter = result.indexOf('?') !== -1 ? '&' : '?'; 21 | result = result + delimiter + encodedParams; 22 | } 23 | 24 | return result; 25 | }; 26 | 27 | 28 | /** 29 | * @param {Object} params 30 | * @return {string} 31 | */ 32 | export const encodeParams = (params) => { 33 | let i; 34 | const parts = []; 35 | for (i in params) { 36 | if (params.hasOwnProperty(i)) { 37 | const param = params[i]; 38 | if (typeof param === 'undefined') { 39 | continue; 40 | } 41 | if (Array.isArray(param)) { 42 | for (let j = 0; j < param.length; j++) { 43 | parts.push(`${encodeURIComponent(i)}[]=${encodeURIComponent(param[j])}`); 44 | } 45 | } else { 46 | parts.push(`${encodeURIComponent(i)}=${encodeURIComponent(param)}`); 47 | } 48 | } 49 | } 50 | 51 | return parts.join('&').replace(/%20/g, '+'); 52 | }; 53 | 54 | 55 | /** 56 | * @param {string} queryString 57 | * @return {Object)>} 58 | */ 59 | export const decodeParams = (queryString) => { 60 | if (queryString.length === 0) { 61 | return {}; 62 | } 63 | 64 | return queryString.split('&').reduce((accumulator, param) => { 65 | const pair = param.split('='); 66 | 67 | const name = decodeURIComponent(pair[0]); 68 | const value = pair[1] ? decodeURIComponent(pair[1]) : ''; 69 | 70 | if (name.slice(-2) === '[]') { 71 | if (!accumulator[name]) { 72 | accumulator[name] = []; 73 | } 74 | accumulator[name].push(value); 75 | } else { 76 | accumulator[name] = value; 77 | } 78 | 79 | return accumulator; 80 | }, {}); 81 | }; 82 | 83 | 84 | /** 85 | * @param {string} variableName 86 | * @return {?string|Array|undefined} 87 | */ 88 | export const getQueryVariable = (variableName) => { 89 | const query = window.location.search.substring(1); 90 | 91 | return decodeParams(query)[variableName]; 92 | }; 93 | 94 | 95 | /** 96 | * @param {Method} method 97 | * @return {boolean} 98 | */ 99 | export const shouldMethodSendData = (method) => [ 100 | Method.POST, 101 | Method.PUT, 102 | Method.PATCH 103 | ].indexOf(method) !== -1; 104 | 105 | 106 | /** 107 | * @enum {string} 108 | */ 109 | export const Method = { 110 | OPTIONS: 'OPTIONS', 111 | GET: 'GET', 112 | HEAD: 'HEAD', 113 | POST: 'POST', 114 | DELETE: 'DELETE', 115 | PUT: 'PUT', 116 | PATCH: 'PATCH', 117 | TRACE: 'TRACE', 118 | CONNECT: 'CONNECT' 119 | }; 120 | -------------------------------------------------------------------------------- /zb/http/xhr.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import {encodeParams, shouldMethodSendData, buildQueryString, Method} from './http'; 10 | 11 | 12 | /** 13 | * @param {string} url 14 | * @param {SendingOptions=} options 15 | * @return {Promise} 16 | */ 17 | export const send = (url, options = {}) => { 18 | const method = options.method || Method.GET; 19 | const timeout = options.timeout || DEFAULT_TIMEOUT; 20 | const builtUrl = options.query ? buildQueryString(url, options.query) : url; 21 | const hasBody = typeof options.body !== 'undefined'; 22 | 23 | return new Promise((resolve, reject) => { 24 | if (hasBody && !shouldMethodSendData(method)) { 25 | throw new TypeError(`HTTP method ${options.method} should not have body`); 26 | } 27 | 28 | const xhr = new XMLHttpRequest(); 29 | const timeoutId = setTimeout(xhr.abort.bind(xhr), timeout); 30 | 31 | xhr.onreadystatechange = () => { 32 | if (xhr.readyState === 4) { // Request completed 33 | clearTimeout(timeoutId); 34 | if (xhr.status >= 200 && xhr.status <= 299) { 35 | resolve(xhr); 36 | } else { 37 | reject(xhr); 38 | } 39 | } 40 | }; 41 | 42 | xhr.open(method, builtUrl, true); 43 | 44 | if (options.headers) { 45 | Object.keys(options.headers) 46 | .forEach((key) => xhr.setRequestHeader(key, options.headers[key])); 47 | } 48 | 49 | let encodedBody = null; 50 | 51 | if (hasBody) { 52 | if (typeof options.body === 'object') { 53 | const contentType = options.headers && options.headers['Content-Type']; 54 | 55 | if (contentType === 'application/json') { 56 | encodedBody = JSON.stringify(options.body); 57 | } else if (contentType === 'application/x-www-form-urlencoded') { 58 | encodedBody = encodeParams(options.body); 59 | } 60 | } 61 | 62 | if (!encodedBody) { 63 | encodedBody = String(options.body); 64 | } 65 | } 66 | 67 | xhr.send(encodedBody); 68 | }); 69 | }; 70 | 71 | 72 | /** 73 | * @typedef {{ 74 | * method: (Method|undefined), 75 | * headers: (Object|undefined), 76 | * query: (Object|undefined), 77 | * body: (Object|string|undefined), 78 | * timeout: (number|undefined) 79 | * }} 80 | */ 81 | export let SendingOptions; 82 | 83 | 84 | /** 85 | * @type {number} 86 | */ 87 | export const DEFAULT_TIMEOUT = 15 * 1000; 88 | -------------------------------------------------------------------------------- /zb/image-preload.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | /** 11 | * @param {string} url 12 | * @param {number=} timeout 13 | * @return {Promise} 14 | */ 15 | const imagePreload = (url, timeout) => { 16 | const image = new Image(); 17 | image.src = url; 18 | 19 | return new Promise((resolve, reject) => { 20 | if (image.complete) { 21 | resolve(image); 22 | } else { 23 | const onLoad = () => { 24 | off(); 25 | resolve(image); 26 | }; 27 | const onError = () => { 28 | off(); 29 | reject(image); 30 | }; 31 | const timeoutId = typeof timeout === 'number' ? setTimeout(onError, timeout) : NaN; 32 | const off = () => { 33 | image.removeEventListener('load', onLoad); 34 | image.removeEventListener('error', onError); 35 | if (timeoutId) { 36 | clearTimeout(timeoutId); 37 | } 38 | }; 39 | 40 | image.addEventListener('load', onLoad); 41 | image.addEventListener('error', onError); 42 | } 43 | }); 44 | }; 45 | 46 | export default imagePreload; 47 | -------------------------------------------------------------------------------- /zb/input-dispatcher.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import IKeyHandler from './interfaces/i-key-handler'; 10 | import IInput from './device/interfaces/i-input'; 11 | import IWidget from './widgets/interfaces/i-widget'; 12 | 13 | 14 | /** 15 | */ 16 | export default class InputDispatcher { 17 | /** 18 | * @param {IKeyHandler} target 19 | */ 20 | constructor(target) { 21 | /** 22 | * @type {IKeyHandler} 23 | * @protected 24 | */ 25 | this._target = target; 26 | 27 | /** 28 | * @type {IInput} 29 | * @protected 30 | */ 31 | this._input = null; 32 | 33 | /** 34 | * @type {Map} 35 | * @protected 36 | */ 37 | this._mouseHoverAreaMapElementToListener = new Map(); 38 | } 39 | 40 | /** 41 | * 42 | * @param {IInput} input 43 | */ 44 | setInput(input) { 45 | this._input = input; 46 | } 47 | 48 | /** 49 | */ 50 | init() { 51 | this._input.setKeyEventHandler(this._onKeyEvent.bind(this)); 52 | } 53 | 54 | /** 55 | * @param {IWidget} widget 56 | * @param {HTMLElement} element 57 | */ 58 | addMouseHoverArea(widget, element) { 59 | if (this._input.isPointingDeviceSupported() && !this._mouseHoverAreaMapElementToListener.has(element)) { 60 | const listener = this._widgetMouseOverListener.bind(this, widget); 61 | 62 | element.addEventListener('mouseover', listener, false); 63 | element.addEventListener('click', listener, false); 64 | this._mouseHoverAreaMapElementToListener.set(element, listener); 65 | } 66 | } 67 | 68 | /** 69 | * @param {IWidget} widget 70 | * @param {HTMLElement} element 71 | */ 72 | removeMouseHoverArea(widget, element) { 73 | if (this._input.isPointingDeviceSupported()) { 74 | const listener = this._mouseHoverAreaMapElementToListener.get(element); 75 | if (listener) { 76 | element.removeEventListener('mouseover', listener, false); 77 | element.removeEventListener('click', listener, false); 78 | this._mouseHoverAreaMapElementToListener.delete(element); 79 | } 80 | } 81 | } 82 | 83 | /** 84 | * @param {IWidget} widget 85 | * @param {Event} event 86 | * @protected 87 | */ 88 | _widgetMouseOverListener(widget, event) { 89 | if (!this._input.isBlocked()) { 90 | switch (event.type) { 91 | case 'mouseover': 92 | widget.mouseOver(/** @type {MouseEvent} */ (event)); 93 | break; 94 | case 'click': 95 | widget.mouseClick(/** @type {MouseEvent} */ (event)); 96 | break; 97 | } 98 | } 99 | } 100 | 101 | /** 102 | * 103 | * @param {(KeyboardEvent|WheelEvent)} e 104 | * @return {boolean} 105 | * @protected 106 | */ 107 | _onKeyEvent(e) { 108 | if (!this._input.isBlocked()) { 109 | const zbKey = this._input.eventToKeyCode(e); 110 | const result = this._target.processKey(zbKey, e); 111 | 112 | if (result) { 113 | e.preventDefault(); 114 | 115 | return false; 116 | } 117 | } else { 118 | e.preventDefault(); 119 | } 120 | 121 | return true; 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /zb/interfaces/i-focusable.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import Rect from '../geometry/rect'; 10 | 11 | 12 | /** 13 | * @interface 14 | */ 15 | export default class IFocusable { 16 | /** 17 | * @param {?Rect=} fromRect 18 | */ 19 | focus(fromRect) {} 20 | 21 | /** 22 | * Object looses focus 23 | */ 24 | blur() {} 25 | 26 | /** 27 | * @return {boolean} 28 | */ 29 | isFocused() {} 30 | } 31 | -------------------------------------------------------------------------------- /zb/interfaces/i-key-handler.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import Key from '../device/input/key'; 10 | 11 | 12 | /** 13 | * @interface 14 | */ 15 | export default class IKeyHandler { 16 | /** 17 | * @param {Key} zbKey 18 | * @param {(KeyboardEvent|MouseEvent)=} event 19 | * @return {boolean} True if Key handled, false if not 20 | */ 21 | processKey(zbKey, event) {} 22 | } 23 | -------------------------------------------------------------------------------- /zb/layers/popup.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import Layer from './layer'; 10 | 11 | 12 | /** 13 | */ 14 | export default class Popup extends Layer { 15 | /** 16 | */ 17 | constructor() { 18 | super(); 19 | 20 | /** 21 | * Fired with: {*} status 22 | * @const {string} 23 | */ 24 | this.EVENT_CLOSE = 'close'; 25 | 26 | this._addContainerClass('_popup'); 27 | } 28 | 29 | /** 30 | * @param {*} status 31 | */ 32 | close(status) { 33 | // Notify parent layer about close 34 | this._fireEvent(this.EVENT_NEED_TO_BE_HIDDEN); 35 | 36 | // Notify popup opener 37 | this._fireEvent(this.EVENT_CLOSE, status); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /zb/layers/scene.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import Layer from './layer'; 10 | 11 | 12 | /** 13 | */ 14 | export default class Scene extends Layer { 15 | /** 16 | */ 17 | constructor() { 18 | super(); 19 | 20 | this._addContainerClass('_scene'); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /zb/limit.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | /** 11 | * @param {function()|function(...*)} targetFunction 12 | * @param {number} waitTime 13 | * @return {function()|function(...*)} 14 | */ 15 | export const debounce = (targetFunction, waitTime) => { 16 | let timer = null; 17 | 18 | const debouncedFunction = function(...args) { 19 | if (timer) { 20 | clearTimeout(timer); 21 | } 22 | 23 | timer = setTimeout(() => { 24 | timer = null; 25 | targetFunction(...args); 26 | }, waitTime); 27 | }; 28 | 29 | debouncedFunction.cancel = () => { 30 | clearTimeout(timer); 31 | timer = null; 32 | }; 33 | 34 | return debouncedFunction; 35 | }; 36 | 37 | 38 | /** 39 | * @param {function()|function(...*)} targetFunction 40 | * @param {number} waitTime 41 | * @return {function()|function(...*)} 42 | */ 43 | export const throttle = (targetFunction, waitTime) => { 44 | let lastEventTimestamp = null; 45 | 46 | return function(...args) { 47 | const now = Date.now(); 48 | 49 | if (!lastEventTimestamp || now - lastEventTimestamp >= waitTime) { 50 | lastEventTimestamp = now; 51 | targetFunction(...args); 52 | } 53 | }; 54 | }; 55 | -------------------------------------------------------------------------------- /zb/promise.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | 11 | /** 12 | * @deprecated – Use default browser Promise implementation 13 | * @template TYPE 14 | */ 15 | class CustomPromise { 16 | /** 17 | * @deprecated – Use default browser Promise implementation 18 | * @param {function( 19 | * function((TYPE|Promise)), 20 | * function(*) 21 | * )} resolver 22 | */ 23 | constructor(resolver) { 24 | /** 25 | * @type {boolean} 26 | * @protected 27 | */ 28 | this._isCanceled = false; 29 | 30 | /** 31 | * @type {Promise} 32 | * @protected 33 | */ 34 | this._promise = new Promise((resolve, reject) => { 35 | resolver( 36 | (result) => { 37 | if (!this._isCanceled) { 38 | resolve(result); 39 | } 40 | }, 41 | (error) => { 42 | if (!this._isCanceled) { 43 | reject(error); 44 | } 45 | } 46 | ); 47 | }); 48 | } 49 | 50 | /** 51 | * @deprecated – Use default browser Promise implementation 52 | * @override 53 | * @return {!Promise} 54 | */ 55 | then(onResolved, onRejected) { 56 | return Promise.wrap(this._promise.then(onResolved, onRejected)); 57 | } 58 | 59 | /** 60 | * @deprecated – Use default browser Promise implementation 61 | * Cancel promise fulfilling 62 | */ 63 | cancel() { 64 | this._isCanceled = true; 65 | } 66 | 67 | /** 68 | * @deprecated – Use default browser Promise implementation 69 | * @param {function(*): *} resolveOrReject 70 | * @return {!Promise} 71 | */ 72 | always(resolveOrReject) { 73 | return Promise.wrap(always(this, resolveOrReject)); 74 | } 75 | 76 | /** 77 | * @deprecated – Use default browser Promise implementation 78 | * @param {Promise} promise 79 | * @return {!Promise} 80 | */ 81 | static wrap(promise) { 82 | return new Promise((resolve, reject) => { 83 | promise.then(resolve, reject); 84 | }); 85 | } 86 | } 87 | 88 | 89 | /** 90 | * @deprecated – Use default browser Promise implementation 91 | * @param {Promise} promise 92 | * @param {function(*): *} callback 93 | * @return {Promise} 94 | */ 95 | export const always = (promise, callback) => promise.then(callback, callback); 96 | 97 | 98 | /** 99 | * @deprecated – Use default browser Promise implementation 100 | * @param {Array|Object} promises 101 | * @return {Promise} 102 | */ 103 | export const all = (promises) => { 104 | if (Array.isArray(promises)) { 105 | return Promise['all'](promises); 106 | } 107 | 108 | if (!(promises instanceof Object)) { 109 | return Promise['all'](); 110 | } 111 | 112 | const keys = Object.keys(/** @type {!Object} */ (promises)); 113 | const arr = keys.map((key) => promises[key]); 114 | 115 | return Promise['all'](arr) 116 | .then((results) => { 117 | const obj = {}; 118 | results.forEach((val, i) => { 119 | obj[keys[i]] = val; 120 | }); 121 | 122 | return obj; 123 | }); 124 | }; 125 | 126 | 127 | /** 128 | * @deprecated – Use default browser Promise implementation 129 | */ 130 | export const Promise = CustomPromise; 131 | -------------------------------------------------------------------------------- /zb/scene-opener.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import LayerManager from './layer-manager'; 10 | import IHistoryManager from './history/interfaces/i-history-manager'; 11 | import IStateful from './history/interfaces/i-stateful'; 12 | import Layer from './layers/layer'; 13 | 14 | 15 | /** 16 | * @implements {IStateful} 17 | */ 18 | export default class SceneOpener { 19 | /** 20 | */ 21 | constructor() { 22 | /** 23 | * @type {LayerManager} 24 | */ 25 | this.layerManager; 26 | 27 | /** 28 | * @type {IHistoryManager} 29 | */ 30 | this.historyManager; 31 | } 32 | 33 | /** 34 | * @override 35 | */ 36 | takeSnapshot() { 37 | const currentLayer = this.layerManager.getCurrentLayer(); 38 | const layerSnapshot = currentLayer ? currentLayer.takeSnapshot() : () => {/* Noop */}; 39 | const switcherSnapshot = this.layerManager.takeSnapshot(); 40 | const hideLayer = this.layerManager.hide.bind(this.layerManager); 41 | 42 | return () => Promise.resolve() 43 | .then(hideLayer) 44 | .then(layerSnapshot) 45 | .then(switcherSnapshot); 46 | } 47 | 48 | /** 49 | * @param {Layer} layer 50 | * @param {Function=} snapshot 51 | * @return {Promise} 52 | */ 53 | open(layer, snapshot) { 54 | this.historyManager.addRecord([this]); 55 | 56 | return Promise.resolve() 57 | .then(() => this.layerManager.hide()) 58 | .then(() => { 59 | layer.activateWidget(null); // Default widget 60 | 61 | return snapshot ? snapshot() : null; 62 | }) 63 | .then(() => this.layerManager.open(layer)); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /zb/structures/abstract-model-api.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import AbstractModel from './abstract-model'; 10 | 11 | 12 | /** 13 | * @template INPUT_TYPE 14 | * @abstract 15 | */ 16 | export default class AbstractModelApi extends AbstractModel { 17 | /** 18 | * @param {INPUT_TYPE=} data 19 | */ 20 | constructor(data) { 21 | super(); 22 | 23 | if (data) { 24 | this.parse(data); 25 | } 26 | } 27 | 28 | /** 29 | * @abstract 30 | * @param {INPUT_TYPE} data 31 | */ 32 | parse(data) {} 33 | } 34 | -------------------------------------------------------------------------------- /zb/structures/abstract-model.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import EventPublisher from '../events/event-publisher'; 10 | 11 | 12 | /** 13 | */ 14 | export default class AbstractModel extends EventPublisher { 15 | /** 16 | */ 17 | constructor() { 18 | super(); 19 | 20 | /** 21 | * @type {Object} 22 | * @protected 23 | */ 24 | this._data = {}; 25 | 26 | /** 27 | * Fired with: nothing 28 | * @const {string} 29 | */ 30 | this.EVENT_CHANGE = 'change'; 31 | } 32 | 33 | /** 34 | * @return {string} 35 | */ 36 | toString() { 37 | const props = ['name', 'title', 'label', 'subject', 'value', 'description', 'desc']; 38 | for (let i = 0; i < props.length; i++) { 39 | if (this._data.hasOwnProperty(props[i])) { 40 | return String(this._data[props[i]]); 41 | } 42 | } 43 | 44 | return Object.prototype.toString.call(this); 45 | } 46 | 47 | /** 48 | * @param {string} name 49 | * @return {*} 50 | * @protected 51 | */ 52 | _getPureProperty(name) { 53 | return this._data[name]; 54 | } 55 | 56 | /** 57 | * @param {string} name 58 | * @param {*} value 59 | * @protected 60 | */ 61 | _setPureProperty(name, value) { 62 | if (this._data.hasOwnProperty(name)) { 63 | const oldValue = this._data[name]; 64 | 65 | this._data[name] = value; 66 | 67 | if (oldValue !== value) { 68 | this._fireEvent(this.EVENT_CHANGE, name, value, oldValue); 69 | this._fireEvent(`change:${name}`, name, value, oldValue); 70 | } 71 | } else { 72 | this._data[name] = value; 73 | this._fireEvent(`add:${name}`, name, value); 74 | } 75 | } 76 | 77 | /** 78 | * @param {Function} childConstructor 79 | * @param {!Object} props 80 | */ 81 | static appendProperties(childConstructor, props) { 82 | Object.defineProperties(childConstructor.prototype, props); 83 | } 84 | 85 | /** 86 | * @param {string} name 87 | * @param {PropertyConfig=} config 88 | * @return {?} 89 | */ 90 | static prop(name, config = {}) { 91 | return { 92 | enumerable: config.enumerable !== undefined ? config.enumerable : true, 93 | get: config.getter || AbstractModel._getDefaultGetter(name), 94 | set: config.setter || AbstractModel._getDefaultSetter(name) 95 | }; 96 | } 97 | 98 | /** 99 | * @param {string} name 100 | * @return {function(this: AbstractModel): *} 101 | * @protected 102 | */ 103 | static _getDefaultGetter(name) { 104 | return function() { 105 | return this._getPureProperty(name); // eslint-disable-line no-invalid-this 106 | }; 107 | } 108 | 109 | /** 110 | * @param {string} name 111 | * @return {function(this: AbstractModel, *): *} 112 | * @protected 113 | */ 114 | static _getDefaultSetter(name) { 115 | return function(value) { 116 | return this._setPureProperty(name, value); // eslint-disable-line no-invalid-this 117 | }; 118 | } 119 | } 120 | 121 | 122 | /** 123 | * @typedef {{ 124 | * getter: (function()|undefined), 125 | * setter: (function()|undefined), 126 | * enumerable: (boolean|undefined) 127 | * }} 128 | */ 129 | export let PropertyConfig; 130 | -------------------------------------------------------------------------------- /zb/structures/data/cyclical-list.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import List from './list'; 10 | 11 | 12 | /** 13 | * @template ItemType 14 | */ 15 | export default class CyclicalDataList extends List { 16 | /** 17 | * @override 18 | */ 19 | itemAt(index) { 20 | const remainder = index % this.size(); 21 | let fixedIndex = index; 22 | if (remainder < 0) { 23 | fixedIndex = this.size() + remainder; 24 | } 25 | return super.itemAt(fixedIndex); 26 | } 27 | 28 | /** 29 | * @override 30 | */ 31 | selectNextItem(step) { 32 | const fixedStep = isNaN(step) ? 1 : (step < 1 ? 1 : step); 33 | let index = this.currentIndex() + fixedStep; 34 | if (index > this.size() - 1) { 35 | index = index - this.size(); 36 | } 37 | return this.selectAt(index); 38 | } 39 | 40 | /** 41 | * @override 42 | */ 43 | selectPrevItem(step) { 44 | const fixedStep = isNaN(step) ? 1 : (step < 1 ? 1 : step); 45 | let index = this.currentIndex() - fixedStep; 46 | if (index < 0) { 47 | index = this.size() + index; 48 | } 49 | return this.selectAt(index); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /zb/structures/data/random.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | 11 | /** 12 | * @param {number=} probability 13 | * @return {boolean} 14 | */ 15 | export const bool = (probability = 0.5) => Math.random() < probability; 16 | 17 | 18 | /** 19 | * @param {number} min 20 | * @param {number} max 21 | * @return {number} 22 | */ 23 | export const num = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min; 24 | 25 | 26 | /** 27 | * @param {number} length 28 | * @param {number=} maxLength 29 | * @return {Array} 30 | */ 31 | export const array = (length, maxLength = length) => Array.from(new Array(num(length, maxLength))); 32 | 33 | 34 | /** 35 | * @template T 36 | * @param {IArrayLike|string} source 37 | * @return {T|string} 38 | */ 39 | export const item = (source) => source[num(0, source.length - 1)]; 40 | 41 | 42 | /** 43 | * @param {number} length 44 | * @param {number=} maxLength 45 | * @param {string=} alphabet 46 | * @return {string} 47 | */ 48 | export const str = (length, maxLength = length, alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZabcdefghiklmnopqrstuvwxyz') => { 49 | let str = ''; 50 | 51 | for (let i = num(length, maxLength); i--;) { 52 | str += item(alphabet); 53 | } 54 | 55 | return str; 56 | }; 57 | -------------------------------------------------------------------------------- /zb/stub.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | import * as random from './structures/data/random'; 11 | 12 | /** 13 | * @deprecated You should use the real data instead. But don`t delete stub from zombiebox. 14 | * @param {number=} probability 15 | * @return {boolean} 16 | */ 17 | export const bool = (probability = 0.5) => random.bool(probability); 18 | 19 | 20 | /** 21 | * @deprecated You should use the real data instead. But don`t delete stub from zombiebox. 22 | * @param {number} min 23 | * @param {number} max 24 | * @return {number} 25 | */ 26 | export const num = (min, max) => random.num(min, max); 27 | 28 | 29 | /** 30 | * @deprecated Use num() instead. 31 | * @param {number} min 32 | * @param {number} max 33 | * @return {number} 34 | */ 35 | export const number = (min, max) => num(min, max); 36 | 37 | 38 | /** 39 | * @deprecated You should use the real data instead. But don`t delete stub from zombiebox. 40 | * @param {number} length 41 | * @param {number=} maxLength 42 | * @return {Array} 43 | */ 44 | export const array = (length, maxLength = length) => random.array(length, maxLength); 45 | 46 | 47 | /** 48 | * @deprecated You should use the real data instead. But don`t delete stub from zombiebox. 49 | * @param {number} length 50 | * @param {number=} maxLength 51 | * @return {string} 52 | */ 53 | export const str = (length, maxLength = length) => random.str(length, maxLength); 54 | 55 | 56 | /** 57 | * @deprecated Use str() instead. 58 | * @param {number} length 59 | * @param {number=} maxLength 60 | * @return {string} 61 | */ 62 | export const string = (length, maxLength = length) => str(length, maxLength); 63 | 64 | 65 | /** 66 | * @deprecated You should use the real data instead. But don`t delete stub from zombiebox. 67 | * @param {number} length 68 | * @param {number=} maxLength 69 | * @return {string} 70 | */ 71 | export const lorem = (length, maxLength = length) => { 72 | const chunk = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. ' + 73 | 'Nunc lobortis in sapien nec sodales. Integer quis tortor dictum, aliquet arcu quis, dignissim neque. ' + 74 | 'Proin at tellus nibh. Quisque mollis quis velit quis viverra. ' + 75 | 'Phasellus at tortor ac est lobortis dictum ac in sapien. ' + 76 | 'Phasellus id augue tempor, tristique ex vel, fringilla ante. ' + 77 | 'Nulla tortor ex, tempor at bibendum eget, pellentesque vel lacus. ' + 78 | 'Nam molestie felis sed eros gravida, in vulputate turpis interdum. ' + 79 | 'Morbi a laoreet urna, eget feugiat elit. Nulla quam elit, rutrum quis luctus a, posuere vitae sapien.'; 80 | 81 | const len = random.num(length, maxLength); 82 | const count = Math.ceil(len / chunk.length); 83 | return chunk.repeat(count).substr(0, len); 84 | }; 85 | 86 | 87 | /** 88 | * @deprecated You should use the real data instead. But don`t delete stub from zombiebox. 89 | * @param {number} width 90 | * @param {number} height 91 | * @param {string=} text 92 | * @return {string} 93 | */ 94 | export const image = (width, height, text = '') => `http://placehold.it/${width}x${height}&text=${text}`; 95 | -------------------------------------------------------------------------------- /zb/timeout.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | 11 | /** 12 | */ 13 | export default class Timeout { 14 | /** 15 | * @param {Function} callback 16 | * @param {number} delay 17 | */ 18 | constructor(callback, delay) { 19 | /** 20 | * @type {number} 21 | * @private 22 | */ 23 | this._timeoutId = NaN; 24 | 25 | /** 26 | * @type {boolean} 27 | * @private 28 | */ 29 | this._fired = false; 30 | 31 | /** 32 | * @type {?Function} 33 | * @private 34 | */ 35 | this._callback = callback; 36 | 37 | /** 38 | * @type {number} 39 | * @private 40 | */ 41 | this._delay = delay; 42 | 43 | /** 44 | * @type {Function} 45 | * @private 46 | */ 47 | this._executeCallback = () => { 48 | this._fired = true; 49 | if (this._callback) { 50 | this._callback(); 51 | } 52 | }; 53 | } 54 | 55 | /** 56 | * Start timeout. To restart, use the method "reset". 57 | */ 58 | start() { 59 | if (this.isInProgress()) { 60 | // Already started 61 | return; 62 | } 63 | 64 | this._fired = false; 65 | this._timeoutId = setTimeout(this._executeCallback, this._delay); 66 | } 67 | 68 | /** 69 | * Stop callback execution. 70 | */ 71 | stop() { 72 | clearTimeout(this._timeoutId); 73 | this._timeoutId = NaN; 74 | } 75 | 76 | /** 77 | * Reset timeout. 78 | */ 79 | restart() { 80 | this.stop(); 81 | this.start(); 82 | } 83 | 84 | /** 85 | * Stop timeout and force callback execution. 86 | */ 87 | force() { 88 | this.stop(); 89 | this._executeCallback(); 90 | } 91 | 92 | /** 93 | * @return {boolean} 94 | */ 95 | isInProgress() { 96 | return !Number.isNaN(this._timeoutId); 97 | } 98 | 99 | /** 100 | * @return {boolean} 101 | */ 102 | isFired() { 103 | return this._fired; 104 | } 105 | 106 | /** 107 | * @param {Function=} callback 108 | */ 109 | setCallback(callback = null) { 110 | this._callback = callback; 111 | } 112 | 113 | /** 114 | * @param {number} delay 115 | */ 116 | setDelay(delay) { 117 | this._delay = delay; 118 | } 119 | 120 | /** 121 | * @return {number} 122 | */ 123 | getDelay() { 124 | return this._delay; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /zb/widgets/interfaces/i-navigation.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import {Value} from '../../geometry/direction'; 10 | import IWidget from './i-widget'; 11 | 12 | 13 | /** 14 | * @interface 15 | */ 16 | export default class INavigation { 17 | /** 18 | * @param {?IWidget} fromWidget 19 | * @param {Value} direction 20 | * @return {?Array} 21 | */ 22 | findWidgets(fromWidget, direction) {} 23 | 24 | /** 25 | * @param {IWidget} widget 26 | */ 27 | addWidget(widget) {} 28 | 29 | /** 30 | * @param {IWidget} widget 31 | */ 32 | removeWidget(widget) {} 33 | 34 | /** 35 | * @param {Array} widgets 36 | */ 37 | setWidgets(widgets) {} 38 | 39 | /** 40 | * @param {IWidget} from 41 | * @param {Value} direction 42 | * @param {?IWidget} to 43 | */ 44 | setRule(from, direction, to) {} 45 | 46 | /** 47 | * @param {IWidget} from 48 | * @param {Value} direction 49 | */ 50 | removeRule(from, direction) {} 51 | 52 | /** 53 | * Drop all rules (forget, empty); 54 | */ 55 | clearRules() {} 56 | } 57 | -------------------------------------------------------------------------------- /zb/widgets/interfaces/i-widget.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import IFocusable from '../../interfaces/i-focusable'; 10 | import IKeyHandler from '../../interfaces/i-key-handler'; 11 | import IEventPublisher from '../../events/interfaces/i-event-publisher'; 12 | import IStateful from '../../history/interfaces/i-stateful'; 13 | import Rect from '../../geometry/rect'; 14 | 15 | 16 | /** 17 | * @interface 18 | * @extends {IEventPublisher} 19 | * @extends {IKeyHandler} 20 | * @extends {IStateful} 21 | * @extends {IFocusable} 22 | */ 23 | export default class IWidget { 24 | /** 25 | * @return {HTMLElement} 26 | */ 27 | getContainer() {} 28 | 29 | /** 30 | * @param {HTMLElement} container 31 | */ 32 | setContainer(container) {} 33 | 34 | /** 35 | * @return {Array} 36 | */ 37 | getFocusableRects() {} 38 | 39 | /** 40 | * @return {?Rect} 41 | */ 42 | getFocusedRect() {} 43 | 44 | /** 45 | * @return {?Rect} 46 | */ 47 | getContainerRect() {} 48 | 49 | /** 50 | * Enable widget 51 | */ 52 | enable() {} 53 | 54 | /** 55 | * Disable widget 56 | */ 57 | disable() {} 58 | 59 | /** 60 | * @return {boolean} 61 | */ 62 | isEnabled() {} 63 | 64 | /** 65 | * @return {boolean} 66 | */ 67 | getEnabled() {} 68 | 69 | /** 70 | * @param {boolean} value 71 | */ 72 | setEnabled(value) {} 73 | 74 | /** 75 | * Show Widget container and set its visibility to true 76 | */ 77 | show() {} 78 | 79 | /** 80 | * Hide Widget container and set its visibility to false 81 | */ 82 | hide() {} 83 | 84 | /** 85 | * @param {boolean} visible 86 | */ 87 | setParentDOMVisible(visible) {} 88 | 89 | /** 90 | * @return {boolean} 91 | */ 92 | getParentDOMVisible() {} 93 | 94 | /** 95 | * @return {boolean} 96 | */ 97 | isParentDOMVisible() {} 98 | 99 | /** 100 | * @return {boolean} 101 | */ 102 | isDOMVisible() {} 103 | 104 | /** 105 | * Called before widget shown. 106 | */ 107 | beforeDOMShow() {} 108 | 109 | /** 110 | * Called before widget hidden. 111 | */ 112 | beforeDOMHide() {} 113 | 114 | /** 115 | * Called after widget shown. 116 | */ 117 | afterDOMShow() {} 118 | 119 | /** 120 | * Called after widget hidden. 121 | */ 122 | afterDOMHide() {} 123 | 124 | /** 125 | * @return {boolean} 126 | */ 127 | isVisible() {} 128 | 129 | /** 130 | * @return {boolean} 131 | */ 132 | getVisible() {} 133 | 134 | /** 135 | * @param {boolean} value 136 | */ 137 | setVisible(value) {} 138 | 139 | /** 140 | * Check if Widget can be focused. 141 | * It's possible if Widget is both visible and enabled 142 | * @return {boolean} 143 | */ 144 | isFocusable() {} 145 | 146 | /** 147 | * Handle mouse over event 148 | * @param {MouseEvent} event 149 | */ 150 | mouseOver(event) {} 151 | 152 | /** 153 | * Handle mouse click event 154 | * @param {MouseEvent} event 155 | */ 156 | mouseClick(event) {} 157 | } 158 | 159 | 160 | /** 161 | * Fired with: (?Rect|undefined) 162 | * @const {string} Widget focused. 163 | */ 164 | IWidget.prototype.EVENT_FOCUS; 165 | 166 | 167 | /** 168 | * @const {string} widget loose focus 169 | */ 170 | IWidget.prototype.EVENT_BLUR; 171 | 172 | 173 | /** 174 | * @const {string} widget want to focus 175 | */ 176 | IWidget.prototype.EVENT_WANT_FOCUS; 177 | 178 | 179 | /** 180 | * @const {string} focus changed somewhere between widget descendants 181 | */ 182 | IWidget.prototype.EVENT_INNER_FOCUS; 183 | -------------------------------------------------------------------------------- /zb/widgets/navigation/abstract-navigation.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import Direction, {Value} from '../../geometry/direction'; 10 | import INavigation from '../interfaces/i-navigation'; 11 | import IWidget from '../interfaces/i-widget'; 12 | 13 | 14 | /** 15 | * @abstract 16 | * @implements {INavigation} 17 | */ 18 | export default class AbstractNavigation { 19 | /** 20 | */ 21 | constructor() { 22 | /** 23 | * @type {Array} 24 | * @protected 25 | */ 26 | this._rules = []; 27 | 28 | /** 29 | * @type {Array} 30 | * @protected 31 | */ 32 | this._widgets = []; 33 | } 34 | 35 | /** 36 | * @override 37 | */ 38 | findWidgets(fromWidget, direction) { 39 | const rule = fromWidget && this._getWidgetRule(fromWidget); 40 | 41 | if (rule && rule.hasOwnProperty(direction)) { 42 | const value = rule[direction]; 43 | 44 | return value && [value]; 45 | } 46 | 47 | return this._autoNavigate(fromWidget, Direction.create(direction)); 48 | } 49 | 50 | /** 51 | * @override 52 | */ 53 | setWidgets(widgets) { 54 | widgets.forEach(this.addWidget.bind(this)); 55 | } 56 | 57 | /** 58 | * @override 59 | */ 60 | addWidget(widget) { 61 | this._widgets.push(widget); 62 | this._rules.push({}); 63 | } 64 | 65 | /** 66 | * @override 67 | */ 68 | removeWidget(widget) { 69 | const index = this._widgets.indexOf(widget); 70 | if (index !== -1) { 71 | this._widgets.splice(index, 1); 72 | this._rules.splice(index, 1); 73 | 74 | this._rules.forEach((rule) => { 75 | Object.keys(rule) 76 | .forEach((direction) => { 77 | direction = /** @type {Value} */ (direction); 78 | 79 | if (rule[direction] === widget) { 80 | delete rule[direction]; 81 | } 82 | }); 83 | }); 84 | } 85 | } 86 | 87 | /** 88 | * @override 89 | */ 90 | setRule(from, direction, to) { 91 | this._getWidgetRule(from)[direction] = to; 92 | } 93 | 94 | /** 95 | * @override 96 | */ 97 | removeRule(from, direction) { 98 | delete this._getWidgetRule(from)[direction]; 99 | } 100 | 101 | /** 102 | * @override 103 | */ 104 | clearRules() { 105 | for (let i = 0; i < this._rules.length; i++) { 106 | this._rules[i] = {}; 107 | } 108 | } 109 | 110 | /** 111 | * @abstract 112 | * @param {?IWidget} fromWidget 113 | * @param {Direction} direction 114 | * @return {Array} 115 | * @protected 116 | */ 117 | _autoNavigate(fromWidget, direction) {} 118 | 119 | /** 120 | * @param {IWidget} widget 121 | * @return {Rule} 122 | * @protected 123 | */ 124 | _getWidgetRule(widget) { 125 | return this._rules[this._getWidgetIndex(widget)]; 126 | } 127 | 128 | /** 129 | * @param {IWidget} widget 130 | * @return {number} 131 | * @protected 132 | * 133 | */ 134 | _getWidgetIndex(widget) { 135 | const index = this._widgets.indexOf(widget); 136 | if (index !== -1) { 137 | return index; 138 | } 139 | 140 | throw new Error('Unknown widget'); 141 | } 142 | } 143 | 144 | 145 | /** 146 | * @typedef {!Object} 147 | */ 148 | export let Rule; 149 | -------------------------------------------------------------------------------- /zb/widgets/navigation/order-navigation.js: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the ZombieBox package. 3 | * 4 | * Copyright © 2012-2021, Interfaced 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | import {Value as DirectionValue} from '../../geometry/direction'; 10 | import AbstractNavigation from './abstract-navigation'; 11 | 12 | 13 | /** 14 | */ 15 | export default class OrderNavigation extends AbstractNavigation { 16 | /** 17 | * @override 18 | */ 19 | _autoNavigate(fromWidget, direction) { 20 | const widgetIndex = this._getWidgetIndex(fromWidget); 21 | const directionKey = direction.getKey(); 22 | if (directionKey === DirectionValue.UP || directionKey === DirectionValue.LEFT) { 23 | // Previous widgets 24 | return this._widgets.slice(0, widgetIndex - 1) 25 | .reverse(); 26 | } 27 | 28 | // DOWN|RIGHT next widgets 29 | return this._widgets.slice(widgetIndex + 1); 30 | } 31 | } 32 | --------------------------------------------------------------------------------