├── .editorconfig ├── .gitignore ├── .prettierrc ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── README.md ├── angular.json ├── config.json ├── package-lock.json ├── package.js ├── package.json ├── src ├── common │ ├── asset.spec.ts │ ├── asset.ts │ ├── environment.ts │ ├── event-emitter.spec.ts │ ├── event-emitter.ts │ ├── exception.ts │ ├── exception │ │ ├── scene.ts │ │ └── scene │ │ │ ├── component.ts │ │ │ └── entity.ts │ ├── interfaces │ │ ├── config.ts │ │ ├── environment.ts │ │ ├── exportable.ts │ │ ├── logger.ts │ │ ├── resource.ts │ │ ├── serialize-context.interface.ts │ │ └── sortable.interface.ts │ ├── is.mac.ts │ ├── logger.spec.ts │ ├── logger.ts │ ├── math.ts │ ├── plugin.manager.ts │ ├── plugin.ts │ ├── pubsub.spec.ts │ ├── pubsub.ts │ ├── require.ts │ ├── scene │ │ ├── component.collection.ts │ │ ├── component.io.ts │ │ ├── component.ts │ │ ├── component │ │ │ ├── asset.ts │ │ │ ├── boolean.ts │ │ │ ├── color.ts │ │ │ ├── group.ts │ │ │ ├── number.ts │ │ │ ├── point.ts │ │ │ ├── range.ts │ │ │ ├── size.ts │ │ │ ├── string.ts │ │ │ └── transformation.ts │ │ ├── entity.ts │ │ └── index.ts │ ├── sort.ts │ ├── tsconfig.app.json │ ├── tween.ts │ └── type.ts ├── environments │ ├── index.prod.ts │ └── index.ts ├── main │ ├── environment.ts │ ├── index.ts │ ├── io │ │ ├── directory.spec.ts │ │ ├── directory.ts │ │ ├── file.spec.ts │ │ └── file.ts │ ├── ipc.spec.ts │ ├── ipc.ts │ ├── ipc │ │ ├── abstract.spec.ts │ │ ├── abstract.ts │ │ ├── dialog.spec.ts │ │ ├── dialog.ts │ │ ├── directory.spec.ts │ │ ├── directory.ts │ │ ├── file.ts │ │ └── plugins.ts │ ├── main.ts │ ├── plugin │ │ └── manager.ts │ ├── test.ts │ ├── tsconfig.app.json │ └── tsconfig.d.json └── ng │ ├── app.module.ts │ ├── assets │ ├── .gitkeep │ ├── fa │ │ ├── layer.svg │ │ ├── mouse-solid.svg │ │ └── touchpad.svg │ ├── grid.png │ ├── i18n │ │ ├── de.json │ │ └── en.json │ ├── loading.css │ ├── resize-icon.svg │ ├── rotate-icon.svg │ └── skew-icon.svg │ ├── components │ ├── app │ │ ├── app.component.html │ │ ├── app.component.scss │ │ └── app.component.ts │ └── main │ │ ├── main.component.html │ │ ├── main.component.scss │ │ └── main.component.ts │ ├── decorators │ └── serializer.decorator.ts │ ├── environment.ts │ ├── favicon.ico │ ├── index.html │ ├── index.ts │ ├── karma.conf.js │ ├── main.ts │ ├── modules │ ├── asset │ │ ├── asset.module.ts │ │ ├── components │ │ │ ├── add-source │ │ │ │ ├── add-source.component.html │ │ │ │ ├── add-source.component.scss │ │ │ │ └── add-source.component.ts │ │ │ ├── asset-type │ │ │ │ ├── asset-type.component.html │ │ │ │ ├── asset-type.component.scss │ │ │ │ └── asset-type.component.ts │ │ │ ├── details-tab │ │ │ │ ├── details-tab.component.html │ │ │ │ ├── details-tab.component.scss │ │ │ │ └── details-tab.component.ts │ │ │ ├── details │ │ │ │ └── dimensions │ │ │ │ │ ├── dimensions.component.html │ │ │ │ │ └── dimensions.component.ts │ │ │ ├── explorer │ │ │ │ ├── explorer.component.html │ │ │ │ ├── explorer.component.scss │ │ │ │ └── explorer.component.ts │ │ │ ├── index.ts │ │ │ ├── inspector │ │ │ │ ├── inspector.component.html │ │ │ │ ├── inspector.component.scss │ │ │ │ └── inspector.component.ts │ │ │ ├── previews │ │ │ │ ├── default │ │ │ │ │ ├── default.component.html │ │ │ │ │ ├── default.component.scss │ │ │ │ │ └── default.component.ts │ │ │ │ ├── image │ │ │ │ │ ├── image.component.html │ │ │ │ │ ├── image.component.scss │ │ │ │ │ └── image.component.ts │ │ │ │ └── index.ts │ │ │ └── tree │ │ │ │ ├── tree.component.html │ │ │ │ ├── tree.component.scss │ │ │ │ └── tree.component.ts │ │ ├── decorators │ │ │ ├── details.decorator.ts │ │ │ ├── index.ts │ │ │ ├── preview.decorator.ts │ │ │ └── tab.decorator.ts │ │ ├── directives │ │ │ ├── index.ts │ │ │ ├── preview.directive.ts │ │ │ └── tab.directive.ts │ │ ├── index.ts │ │ ├── interfaces │ │ │ ├── asset-details-component.interface.ts │ │ │ ├── asset-owner.interface.ts │ │ │ └── index.ts │ │ ├── states │ │ │ ├── actions │ │ │ │ └── asset.action.ts │ │ │ ├── asset.state.ts │ │ │ └── index.ts │ │ └── tools │ │ │ └── add.tool.ts │ ├── camera │ │ ├── camera-zoom.interface.ts │ │ ├── camera.ids.ts │ │ ├── camera.module.ts │ │ ├── camera.tool.ts │ │ ├── components │ │ │ ├── index.ts │ │ │ └── tool │ │ │ │ ├── tool.component.html │ │ │ │ ├── tool.component.scss │ │ │ │ └── tool.component.ts │ │ ├── directives │ │ │ ├── camera.directive.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ └── states │ │ │ ├── actions │ │ │ └── camera.action.ts │ │ │ ├── camera.state.ts │ │ │ └── index.ts │ ├── electron │ │ ├── electron.exception.ts │ │ ├── electron.mock.service.ts │ │ ├── electron.module.ts │ │ ├── electron.provider.spec.ts │ │ ├── electron.provider.ts │ │ ├── electron.service.spec.ts │ │ ├── electron.service.ts │ │ ├── exception │ │ │ ├── provider.exception.ts │ │ │ ├── providers │ │ │ │ ├── dialog.exception.ts │ │ │ │ └── directory.exception.ts │ │ │ ├── service.exception.ts │ │ │ └── services │ │ │ │ ├── not-found.exception.ts │ │ │ │ └── registered.exception.ts │ │ ├── index.ts │ │ └── providers │ │ │ ├── dialog.provider.spec.ts │ │ │ ├── dialog.provider.ts │ │ │ ├── directory.provider.spec.ts │ │ │ ├── directory.provider.ts │ │ │ └── file.provider.ts │ ├── onbefore-plugin │ │ ├── onbefore-plugin.module.ts │ │ └── onbefore.plugin.ts │ ├── pixi │ │ ├── components │ │ │ └── renderer.component.ts │ │ ├── exceptions │ │ │ └── pixi.exception.ts │ │ ├── index.ts │ │ ├── pixi.module.ts │ │ ├── pixi.ts │ │ ├── services │ │ │ ├── camera.service.ts │ │ │ ├── renderer.service.ts │ │ │ ├── selection.service.ts │ │ │ └── selection │ │ │ │ ├── container.service.ts │ │ │ │ ├── handlers │ │ │ │ ├── pivot.service.ts │ │ │ │ ├── position.service.ts │ │ │ │ ├── resize.service.ts │ │ │ │ ├── resize │ │ │ │ │ └── anchor.ts │ │ │ │ ├── rotation.service.ts │ │ │ │ └── skew.service.ts │ │ │ │ └── renderer.service.ts │ │ ├── systems │ │ │ ├── backdrop.system.ts │ │ │ ├── camera.system.ts │ │ │ ├── debug.system.ts │ │ │ ├── foreground.system.ts │ │ │ ├── grid.system.ts │ │ │ ├── rendering.system.ts │ │ │ ├── sprite.system.ts │ │ │ └── transformation.system.ts │ │ └── utils │ │ │ ├── bounds.utils.ts │ │ │ ├── camera.spec.ts │ │ │ ├── camera.ts │ │ │ ├── grid.spec.ts │ │ │ ├── grid.ts │ │ │ ├── index.ts │ │ │ └── transform.utils.ts │ ├── plugin │ │ ├── plugin.interface.ts │ │ ├── plugin.manager.ts │ │ └── plugin.module.ts │ ├── preferences │ │ ├── components │ │ │ ├── menu │ │ │ │ ├── menu.component.html │ │ │ │ ├── menu.component.scss │ │ │ │ └── menu.component.ts │ │ │ └── settings │ │ │ │ ├── settings.component.html │ │ │ │ ├── settings.component.scss │ │ │ │ ├── settings.component.ts │ │ │ │ └── types │ │ │ │ ├── abstract.component.ts │ │ │ │ ├── checkbox │ │ │ │ ├── checkbox.component.html │ │ │ │ └── checkbox.component.ts │ │ │ │ ├── input │ │ │ │ ├── input.component.html │ │ │ │ └── input.component.ts │ │ │ │ ├── number │ │ │ │ ├── number.component.html │ │ │ │ └── number.component.ts │ │ │ │ ├── selection │ │ │ │ ├── selection.component.html │ │ │ │ └── selection.component.ts │ │ │ │ ├── slider │ │ │ │ ├── slider.component.html │ │ │ │ └── slider.component.ts │ │ │ │ └── toggle │ │ │ │ ├── toggle.component.html │ │ │ │ └── toggle.component.ts │ │ ├── directives │ │ │ └── settings-option.directive.ts │ │ ├── interfaces │ │ │ ├── preference-option.interface.ts │ │ │ ├── settings-option.interface.ts │ │ │ └── settings-section.interface.ts │ │ ├── preferences.module.ts │ │ ├── preferences.tool.ts │ │ └── states │ │ │ ├── actions │ │ │ ├── preferences.action.ts │ │ │ └── settings.action.ts │ │ │ ├── preferences.state.ts │ │ │ └── settings.state.ts │ ├── scene │ │ ├── components │ │ │ └── scene │ │ │ │ ├── scene.component.html │ │ │ │ ├── scene.component.scss │ │ │ │ └── scene.component.ts │ │ ├── decorators │ │ │ ├── component.decorator.ts │ │ │ ├── converter.decorator.ts │ │ │ └── index.ts │ │ ├── directives │ │ │ └── renderer.directive.ts │ │ ├── exceptions │ │ │ ├── converter.service.exception.ts │ │ │ ├── scene.exception.ts │ │ │ └── scene │ │ │ │ └── entity-not-found.exception.ts │ │ ├── forms │ │ │ └── validators │ │ │ │ └── component-id.validator.ts │ │ ├── index.ts │ │ ├── scene.module.ts │ │ ├── services │ │ │ ├── component.service.ts │ │ │ ├── converter.service.ts │ │ │ ├── engine.service.ts │ │ │ ├── image-asset.service.ts │ │ │ └── scene.service.ts │ │ └── states │ │ │ ├── actions │ │ │ ├── entity.action.ts │ │ │ ├── history.action.ts │ │ │ ├── scene.action.ts │ │ │ └── select.action.ts │ │ │ ├── history.state.ts │ │ │ ├── scene.state.ts │ │ │ └── select.state.ts │ ├── sidebar │ │ ├── components │ │ │ ├── hierarchy │ │ │ │ ├── add │ │ │ │ │ ├── add.component.html │ │ │ │ │ ├── add.component.scss │ │ │ │ │ └── add.component.ts │ │ │ │ ├── hierarchy.component.html │ │ │ │ ├── hierarchy.component.scss │ │ │ │ └── hierarchy.component.ts │ │ │ ├── selection │ │ │ │ ├── add │ │ │ │ │ ├── add.component.html │ │ │ │ │ ├── add.component.scss │ │ │ │ │ └── add.component.ts │ │ │ │ ├── selection.component.html │ │ │ │ ├── selection.component.scss │ │ │ │ ├── selection.component.ts │ │ │ │ └── types │ │ │ │ │ ├── abstract.ts │ │ │ │ │ ├── color │ │ │ │ │ ├── color.component.html │ │ │ │ │ ├── color.component.scss │ │ │ │ │ └── color.component.ts │ │ │ │ │ ├── group │ │ │ │ │ ├── group.component.html │ │ │ │ │ ├── group.component.scss │ │ │ │ │ └── group.component.ts │ │ │ │ │ ├── inline.actions.scss │ │ │ │ │ ├── input │ │ │ │ │ ├── input.component.html │ │ │ │ │ ├── input.component.scss │ │ │ │ │ └── input.component.ts │ │ │ │ │ ├── number │ │ │ │ │ ├── number.component.html │ │ │ │ │ ├── number.component.scss │ │ │ │ │ └── number.component.ts │ │ │ │ │ ├── point │ │ │ │ │ ├── point.component.html │ │ │ │ │ ├── point.component.scss │ │ │ │ │ └── point.component.ts │ │ │ │ │ ├── range │ │ │ │ │ ├── range.component.html │ │ │ │ │ ├── range.component.scss │ │ │ │ │ └── range.component.ts │ │ │ │ │ ├── size │ │ │ │ │ ├── size.component.html │ │ │ │ │ ├── size.component.scss │ │ │ │ │ └── size.component.ts │ │ │ │ │ └── style.scss │ │ │ ├── sidebar.component.html │ │ │ ├── sidebar.component.scss │ │ │ └── sidebar.component.ts │ │ ├── directives │ │ │ └── entity-components.directive.ts │ │ ├── exceptions │ │ │ ├── service │ │ │ │ ├── entity-type.exception.ts │ │ │ │ ├── invalid-property-component.exception.ts │ │ │ │ └── scene-components.exception.ts │ │ │ └── sidebar.exception.ts │ │ ├── services │ │ │ ├── entity-type.service.ts │ │ │ └── scene-components.service.ts │ │ └── sidebar.module.ts │ ├── tileset │ │ ├── components │ │ │ ├── index.ts │ │ │ ├── tileset-canvas │ │ │ │ ├── tileset-canvas.component.html │ │ │ │ ├── tileset-canvas.component.scss │ │ │ │ └── tileset-canvas.component.ts │ │ │ └── tileset-tab │ │ │ │ ├── tileset-tab.component.html │ │ │ │ ├── tileset-tab.component.scss │ │ │ │ └── tileset-tab.component.ts │ │ ├── index.ts │ │ ├── interceptors │ │ │ ├── index.ts │ │ │ └── tile-brush.interceptor.ts │ │ ├── interfaces │ │ │ ├── index.ts │ │ │ ├── tileset-settings.interface.ts │ │ │ └── tileset.interface.ts │ │ ├── states │ │ │ ├── actions │ │ │ │ ├── index.ts │ │ │ │ └── tileset.actions.ts │ │ │ ├── index.ts │ │ │ └── tileset.state.ts │ │ ├── systems │ │ │ ├── index.ts │ │ │ ├── overlay.system.ts │ │ │ └── tileset.system.ts │ │ └── tileset.module.ts │ ├── toolbar │ │ ├── components │ │ │ ├── tool │ │ │ │ └── default │ │ │ │ │ ├── default.component.spec.ts │ │ │ │ │ └── default.component.ts │ │ │ └── toolbar │ │ │ │ ├── toolbar.component.html │ │ │ │ ├── toolbar.component.scss │ │ │ │ ├── toolbar.component.spec.ts │ │ │ │ └── toolbar.component.ts │ │ ├── directives │ │ │ ├── tool.directive.spec.ts │ │ │ └── tool.directive.ts │ │ ├── exception.ts │ │ ├── index.ts │ │ ├── interceptor.ts │ │ ├── states │ │ │ ├── actions │ │ │ │ └── toolbar.action.ts │ │ │ └── toolbar.state.ts │ │ ├── tool.spec.ts │ │ ├── tool.ts │ │ ├── toolbar.module.ts │ │ └── tools │ │ │ ├── selection.spec.ts │ │ │ └── selection.ts │ └── utils │ │ ├── components │ │ ├── index.ts │ │ ├── nested-dropdown │ │ │ ├── nested-dropdown.component.html │ │ │ ├── nested-dropdown.component.scss │ │ │ └── nested-dropdown.component.ts │ │ ├── point-input │ │ │ ├── point-input.component.html │ │ │ ├── point-input.component.scss │ │ │ └── point-input.component.ts │ │ ├── resizable │ │ │ ├── resizable.html │ │ │ ├── resizable.spec.ts │ │ │ └── resizable.ts │ │ └── type-label │ │ │ ├── type-label.component.html │ │ │ ├── type-label.component.scss │ │ │ └── type-label.component.ts │ │ ├── directives │ │ └── number.directive.ts │ │ ├── index.ts │ │ ├── lifecycles │ │ ├── destroy.lifecycle.ts │ │ └── index.ts │ │ ├── pipes │ │ └── color.pipe.ts │ │ ├── rx │ │ ├── index.ts │ │ └── notify.ts │ │ └── utils.module.ts │ ├── polyfills.ts │ ├── services │ ├── cursor.service.ts │ └── hotkey.service.ts │ ├── states │ ├── actions │ │ └── editor.action.ts │ ├── editor.state.ts │ └── hotkey.state.ts │ ├── style.scss │ ├── styles │ └── utils.scss │ ├── test.ts │ ├── tsconfig.app.json │ ├── tsconfig.spec.json │ ├── variables.scss │ └── workers │ └── pixi.culling.worker.ts ├── tsconfig.json └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /out 5 | /dist 6 | /tmp 7 | /out-tsc 8 | /app-builds 9 | 10 | # dependencies 11 | /node_modules 12 | 13 | # IDEs and editors 14 | /.idea 15 | .project 16 | .classpath 17 | .c9/ 18 | *.launch 19 | .settings/ 20 | *.sublime-workspace 21 | 22 | # IDE - VSCode 23 | .vscode/* 24 | !.vscode/settings.json 25 | !.vscode/tasks.json 26 | !.vscode/launch.json 27 | !.vscode/extensions.json 28 | 29 | # misc 30 | /.angular/cache 31 | /.nyc_output 32 | /.sass-cache 33 | /connect.lock 34 | /coverage 35 | /libpeerconnection.log 36 | npm-debug.log 37 | testem.log 38 | /typings 39 | 40 | # e2e 41 | /e2e/*.js 42 | /e2e/*.map 43 | 44 | # System Files 45 | .DS_Store 46 | Thumbs.db 47 | 48 | plugins 49 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "semi": true, 5 | "singleQuote": true, 6 | "bracketSpacing": true, 7 | "printWidth": 140, 8 | "endOfLine": "lf", 9 | "useTabs": false, 10 | "arrowParens": "avoid", 11 | "bracketSameLine": true 12 | } 13 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "cyrilletuzi.angular-schematics", 4 | "angular.ng-template" 5 | ] 6 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible Node.js debug attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Electron: Main", 9 | "type": "node", 10 | "request": "launch", 11 | "cwd": "${workspaceFolder}/out", 12 | "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron", 13 | "args": ["--remote-debugging-port=9223", "."], 14 | "outputCapture": "std", 15 | "preLaunchTask": "start" 16 | }, 17 | { 18 | "name": "Electron: Renderer", 19 | "type": "chrome", 20 | "request": "attach", 21 | "port": 9223, 22 | "webRoot": "${workspaceFolder}", 23 | "timeout": 30000 24 | } 25 | ], 26 | "compounds": [ 27 | { 28 | "name": "Electron: All", 29 | "configurations": ["Electron: Main", "Electron: Renderer"], 30 | "preLaunchTask": "start" 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "vsicons.presets.angular": true, 3 | "cSpell.words": [ 4 | "ngxs", 5 | "pixi" 6 | ] 7 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "npm", 6 | "script": "start", 7 | "group": "none", 8 | "label": "start", 9 | "isBackground": true, 10 | "detail": "Starts development mode", 11 | "problemMatcher": [ 12 | { 13 | "pattern": [ 14 | { 15 | "regexp": ".", 16 | "file": 1, 17 | "location": 2, 18 | "message": 3 19 | } 20 | ], 21 | "background": { 22 | "activeOnStart": true, 23 | "beginsPattern": "npm run", 24 | "endsPattern": "Index html generation complete\\." 25 | } 26 | } 27 | ] 28 | }, 29 | { 30 | "type": "npm", 31 | "script": "build.electron", 32 | "group": "none", 33 | "label": "build.electron", 34 | "detail": "Builds the electron backend" 35 | }, 36 | { 37 | "type": "npm", 38 | "script": "electron", 39 | "group": "none", 40 | "label": "electron", 41 | "detail": "Runs the app inside electron", 42 | "dependsOn": "build.electron", 43 | "problemMatcher": [ 44 | { 45 | "pattern": [ 46 | { 47 | "regexp": ".", 48 | "file": 1, 49 | "location": 2, 50 | "message": 3 51 | } 52 | ], 53 | "background": { 54 | "activeOnStart": true, 55 | "beginsPattern": ".", 56 | "endsPattern": "." 57 | } 58 | } 59 | ] 60 | } 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "devMode": true, 3 | "debugMode": true, 4 | "plugins": [ "./node_modules/@yame/*" ] 5 | } 6 | -------------------------------------------------------------------------------- /package.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var packager = require('electron-packager'); 4 | const pkg = require('./package.json'); 5 | const argv = require('minimist')(process.argv.slice(1)); 6 | 7 | const appName = argv.name || pkg.name; 8 | const buildVersion = pkg.version || '1.0'; 9 | const shouldUseAsar = argv.asar || false; 10 | const shouldBuildAll = argv.all || false; 11 | const arch = argv.arch || 'all'; 12 | const platform = argv.platform || 'darwin'; 13 | 14 | const DEFAULT_OPTS = { 15 | dir: './dist', 16 | name: appName, 17 | asar: shouldUseAsar, 18 | buildVersion: buildVersion, 19 | }; 20 | 21 | pack(platform, arch, function done(err, appPath) { 22 | if (err) { 23 | console.log(err); 24 | } else { 25 | console.log('Application packaged successfully!', appPath); 26 | } 27 | }); 28 | 29 | function pack(plat, arch, cb) { 30 | // there is no darwin ia32 electron 31 | if (plat === 'darwin' && arch === 'ia32') return; 32 | 33 | let icon = 'src/ng/favicon'; 34 | 35 | if (icon) { 36 | DEFAULT_OPTS.icon = 37 | icon + 38 | (() => { 39 | let extension = '.png'; 40 | if (plat === 'darwin') { 41 | extension = '.icns'; 42 | } else if (plat === 'win32') { 43 | extension = '.ico'; 44 | } 45 | return extension; 46 | })(); 47 | } 48 | 49 | const opts = Object.assign({}, DEFAULT_OPTS, { 50 | platform: plat, 51 | arch, 52 | prune: true, 53 | overwrite: true, 54 | all: shouldBuildAll, 55 | out: `app-builds`, 56 | }); 57 | 58 | console.log(opts); 59 | packager(opts, cb); 60 | } 61 | -------------------------------------------------------------------------------- /src/common/asset.spec.ts: -------------------------------------------------------------------------------- 1 | import { Asset } from './asset'; 2 | 3 | class MyAsset extends Asset { 4 | type = 'my-asset'; 5 | } 6 | 7 | describe('Asset', function() { 8 | 9 | it('should exist', () => expect(Asset != null).toBe(true) ); 10 | 11 | it('should be extendable', () => expect(new MyAsset() instanceof Asset).toBe(true) ); 12 | 13 | it('should have type', () => expect(typeof new MyAsset().type).toEqual('string')); 14 | 15 | it('should have type "my-asset"', () => expect(new MyAsset().type).toEqual('my-asset') ); 16 | 17 | it('should have a content object', () => expect(new MyAsset().resource).toBeDefined() ); 18 | 19 | it('should have no parent', () => expect(new MyAsset().parent).toBeUndefined() ); 20 | 21 | it('should have no id', () => expect(new MyAsset().id).toBeUndefined() ); 22 | 23 | }); 24 | -------------------------------------------------------------------------------- /src/common/asset.ts: -------------------------------------------------------------------------------- 1 | import { IResource } from './interfaces/resource'; 2 | 3 | /** 4 | * An asset represents any resource which can be part of an entity. 5 | * 6 | * For instance this could be an image, atlas texture, particle object, sound file, etc. 7 | */ 8 | export class Asset { 9 | 10 | /** 11 | * The unique identifier of the asset. 12 | */ 13 | id!: string; 14 | 15 | /** 16 | * The type of the asset. 17 | */ 18 | type!: string; 19 | 20 | /** 21 | * The parent of this asset. Is `null` if it is the root group. 22 | */ 23 | parent: string | null = null; 24 | 25 | /** 26 | * A list of asset child ids, if any. 27 | */ 28 | children: string[] = []; 29 | 30 | /** 31 | * The resource reference of the asset. Contains the actual data and further information 32 | */ 33 | resource: IResource = { 34 | uri: '', 35 | type: '', 36 | name: '', 37 | source: '' 38 | }; 39 | 40 | /** 41 | * The icon to display. 42 | */ 43 | icon?: string; 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/common/environment.ts: -------------------------------------------------------------------------------- 1 | import { IYameEnvironment } from './interfaces/environment'; 2 | 3 | const env: IYameEnvironment = { 4 | appDir: '', 5 | commonDir: '', 6 | ngDir: '', 7 | electronDir: '', 8 | config: { }, 9 | plugins: [] 10 | }; 11 | 12 | export const Environment = env; 13 | -------------------------------------------------------------------------------- /src/common/event-emitter.spec.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter as eventemitter3 } from 'eventemitter3'; 2 | import { EventEmitter } from './event-emitter'; 3 | 4 | class MyEmitter extends EventEmitter { 5 | publicChange(property: string, currentValue: any, newValue: any, fn?: Function): boolean { 6 | return this.change(property, currentValue, newValue, fn); 7 | } 8 | } 9 | 10 | describe('EventEmitter', () => { 11 | 12 | let spy, changeSpy; 13 | 14 | beforeEach(() => { 15 | spy = { fn: function() {} }; 16 | changeSpy = { fn: function() {} }; 17 | spyOn(spy, 'fn'); 18 | spyOn(changeSpy, 'fn'); 19 | }); 20 | 21 | it('should extend eventemitter3', () => expect(new EventEmitter() instanceof eventemitter3).toBe(true)); 22 | 23 | it('should re-trigger "my-event" from another event emitter', () => { 24 | let toObserve = new EventEmitter(); 25 | let delegator = new EventEmitter(); 26 | delegator.delegateOn('my-event', toObserve); 27 | delegator.on('my-event', spy.fn); 28 | toObserve.emit('my-event', true); 29 | expect(spy.fn).toHaveBeenCalled(); 30 | }); 31 | 32 | it('should re-trigger "my-event" as a "my-new-event"', () => { 33 | let toObserve = new EventEmitter(); 34 | let delegator = new EventEmitter(); 35 | delegator.delegateOn('my-event', toObserve, 'my-new-event'); 36 | delegator.on('my-new-event', spy.fn); 37 | toObserve.emit('my-event', true); 38 | expect(spy.fn).toHaveBeenCalled(); 39 | }); 40 | 41 | it('should trigger on change', () => { 42 | let toObserve = new MyEmitter(); 43 | toObserve.on('change:myProp', changeSpy.fn); 44 | toObserve.publicChange('myProp', true, false); 45 | expect(changeSpy.fn).toHaveBeenCalled(); 46 | }); 47 | 48 | it('should call the callback on change', () => { 49 | let toObserve = new MyEmitter(); 50 | toObserve.publicChange('myProp', true, false, changeSpy.fn); 51 | expect(changeSpy.fn).toHaveBeenCalled(); 52 | }); 53 | 54 | it('should not trigger on change', () => { 55 | let toObserve = new MyEmitter(); 56 | toObserve.on('change:myProp', changeSpy.fn); 57 | toObserve.publicChange('myProp', true, true); 58 | expect(changeSpy.fn).not.toHaveBeenCalled(); 59 | }); 60 | 61 | }); 62 | -------------------------------------------------------------------------------- /src/common/exception.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * An exception is an extendable error. 3 | * Using an exception we can throw custom errors, 4 | * which may contain more useful information than just a trace and a message. 5 | * 6 | * @export 7 | * @class Exception 8 | * @extends {Error} 9 | */ 10 | export class Exception extends Error { 11 | 12 | constructor(message: string) { 13 | super(message); 14 | this.name = this.constructor.name; 15 | if (typeof Error.captureStackTrace === 'function') { 16 | Error.captureStackTrace(this, this.constructor); 17 | } else { 18 | this.stack = (new Error(message)).stack; 19 | } 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/common/exception/scene.ts: -------------------------------------------------------------------------------- 1 | import { Exception } from '../exception'; 2 | 3 | export class SceneException extends Exception { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/common/exception/scene/component.ts: -------------------------------------------------------------------------------- 1 | import { SceneException } from '../scene'; 2 | 3 | export class SceneComponentException extends SceneException { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/common/exception/scene/entity.ts: -------------------------------------------------------------------------------- 1 | import { SceneException } from '../scene'; 2 | 3 | export class SceneEntityException extends SceneException { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/common/interfaces/config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Defines the schema of the config.json in yame. 3 | */ 4 | export interface IConfig { 5 | 6 | /** 7 | * Whether dev mode is active. 8 | * 9 | * @type {boolean} 10 | */ 11 | devMode?: boolean; 12 | 13 | /** 14 | * Whether debug mode is active. 15 | * 16 | * @type {boolean} 17 | */ 18 | debugMode?: boolean; 19 | 20 | /** 21 | * The yame plugins path. 22 | * This attribute can have a single path or multiple directories or files. 23 | * It is recommended to use glob patterns. 24 | * 25 | * @type {(string | string[])} 26 | */ 27 | plugins?: string | string[] 28 | } 29 | -------------------------------------------------------------------------------- /src/common/interfaces/environment.ts: -------------------------------------------------------------------------------- 1 | import { IConfig } from "./config"; 2 | import { YamePlugin } from "../plugin"; 3 | 4 | /** 5 | * The yame environment on the electron side. 6 | * This can actually contain any attributes, but the defined one in the interface are always accessible. 7 | * 8 | * @interface YameEnvironment 9 | */ 10 | export interface IYameEnvironment { 11 | 12 | /** 13 | * A list of all successfully initialized plugins. 14 | */ 15 | plugins: YamePlugin[]; 16 | 17 | /** 18 | * The app directory, i.e. the root of the electron and ng modules. 19 | */ 20 | appDir: string; 21 | 22 | /** 23 | * The common directory, i.e. the source of the common modules. 24 | */ 25 | commonDir: string; 26 | 27 | /** 28 | * The angular directory, i.e. the source of the angular code. 29 | */ 30 | ngDir: string; 31 | 32 | /** 33 | * The electron directory, i.e. the source of the electron code. 34 | */ 35 | electronDir: string; 36 | 37 | /** 38 | * The config. 39 | */ 40 | config: IConfig; 41 | 42 | /** 43 | * Any additional config. 44 | */ 45 | [key: string]: any; 46 | } 47 | -------------------------------------------------------------------------------- /src/common/interfaces/exportable.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Export options which should be a map. 3 | * 4 | * @interface ExportOptions 5 | */ 6 | export interface ExportOptions { 7 | [key: string]: any; 8 | } 9 | 10 | /** 11 | * Representation of an entity which can exported to a given type. 12 | */ 13 | export interface IExportable { 14 | 15 | /** 16 | * Converts the instance if this interface to the type `T` and returns it. 17 | * 18 | * @param options Any export options 19 | * @return The exported value of this instance. 20 | */ 21 | export(options?: ExportOptions): T; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/common/interfaces/logger.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * An interface which has to implemented by anyone who wants to log data. 3 | * 4 | * @export 5 | * @interface Logger 6 | */ 7 | export interface Logger { 8 | 9 | /** @returns {void} Logs the given argumens to the default log level. */ 10 | log(... args: any[]): void; 11 | 12 | /** @returns {void} Logs the given argumens as an information. */ 13 | info(... args: any[]): void; 14 | 15 | /** @returns {void} Logs the given argumens as a warning. */ 16 | warn(... args: any[]): void; 17 | 18 | /** @returns {void} Logs the given argumens as an error. */ 19 | error(... args: any[]): void; 20 | 21 | /** @returns {void} Logs the given argumens as a debug log. */ 22 | debug(... args: any[]): void; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/common/interfaces/resource.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A resource represents an object which may be used as an asset. 3 | * 4 | * This can be any data, such as size, dimensions, children, length, etc. 5 | */ 6 | export interface IResource { 7 | 8 | /** 9 | * The uniform resource identifier. May point to the local machine or remote storage. 10 | */ 11 | uri: string; 12 | 13 | /** 14 | * The name of the resource. 15 | */ 16 | name: string; 17 | 18 | /** 19 | * The resource type. 20 | */ 21 | type: string; 22 | 23 | /** 24 | * The source, from which this resource got loaded. 25 | */ 26 | source: string; 27 | 28 | /** 29 | * A label for the resource, for pretty printing it on the screen. 30 | */ 31 | label?: string; 32 | 33 | /** 34 | * The actual resource data. 35 | */ 36 | data?: T; 37 | 38 | /** 39 | * The size in bytes. 40 | */ 41 | size?: number; 42 | 43 | /** 44 | * Unix timestamp for the date of creation. 45 | */ 46 | created?: number; 47 | 48 | /** 49 | * Unix timestamp for the last date of modification. 50 | */ 51 | changed?: number; 52 | 53 | [key: string]: any; 54 | } 55 | 56 | export interface IResourceGroup extends IResource[]> {} -------------------------------------------------------------------------------- /src/common/interfaces/serialize-context.interface.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A context representing meta data when data is being serialized or deserialized. 3 | */ 4 | export interface ISerializeContext { 5 | 6 | /** 7 | * The uri of the resource holding the file data. 8 | */ 9 | uri: string; 10 | 11 | /** 12 | * The source type. 13 | */ 14 | source: string; 15 | 16 | /** 17 | * The protocol being used. 18 | */ 19 | protocol: string; 20 | 21 | /** 22 | * Whether serialization is being done to a new uri. 23 | */ 24 | as?: boolean; 25 | 26 | /** 27 | * The whole data, being serialized. 28 | */ 29 | data?: T; 30 | 31 | [key: string]: unknown; 32 | } -------------------------------------------------------------------------------- /src/common/interfaces/sortable.interface.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Defines an object which can be sorted by a position value within an array. 3 | */ 4 | export interface ISortable { 5 | /** 6 | * The value to compare while being sorted. 7 | */ 8 | position: number; 9 | } -------------------------------------------------------------------------------- /src/common/is.mac.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Returns whether the current system is a mac or not. 3 | * Can be used to implement macOS specific behaviour. 4 | */ 5 | export default function (): boolean { 6 | if (globalThis.process) { 7 | return globalThis.process.platform === 'darwin'; 8 | } else if (globalThis.navigator) { 9 | return ( 10 | globalThis.navigator.platform.toLowerCase().indexOf('mac') >= 0 || 11 | globalThis.navigator.userAgent.toLowerCase().indexOf('mac') >= 0 12 | ); 13 | } 14 | 15 | return false; 16 | } 17 | -------------------------------------------------------------------------------- /src/common/logger.ts: -------------------------------------------------------------------------------- 1 | import { Logger as ILogger } from './interfaces/logger'; 2 | 3 | /** 4 | * Wrapper for logging to a specific target. 5 | * The default target is the console. 6 | * @class Logger 7 | * @implements {ILogger} 8 | */ 9 | export class Logger implements ILogger { 10 | 11 | /** 12 | * The logging target. 13 | * Defaults to `console`. 14 | * 15 | * @protected 16 | * @type {ILogger} 17 | */ 18 | protected loggingTarget: ILogger = console; 19 | 20 | /** 21 | * The logging target target. 22 | * @type {ILogger} 23 | */ 24 | get target(): ILogger { 25 | return this.loggingTarget; 26 | } 27 | 28 | /** 29 | * Sets the logger. 30 | * @memberof Logger 31 | * @throws {Exception} If the set target does not implement the logger interface. 32 | */ 33 | set target(target: ILogger) { 34 | // We can not use instanceof in TS -.- 35 | if (!target || !target.debug || !target.error || !target.info || !target.warn || !target.log) 36 | throw 'The set target has to implement the logger interface!'; 37 | this.loggingTarget = target; 38 | } 39 | 40 | /** @inheritdoc */ 41 | log(... args: any[]) { 42 | this.loggingTarget.log.apply(this.loggingTarget, args); 43 | } 44 | 45 | /** @inheritdoc */ 46 | info(... args: any[]) { 47 | this.loggingTarget.info.apply(this.loggingTarget, args); 48 | } 49 | 50 | /** @inheritdoc */ 51 | warn(... args: any[]) { 52 | this.loggingTarget.warn.apply(this.loggingTarget, args); 53 | } 54 | 55 | /** @inheritdoc */ 56 | error(... args: any[]) { 57 | this.loggingTarget.error.apply(this.loggingTarget, args); 58 | } 59 | 60 | /** @inheritdoc */ 61 | debug(... args: any[]) { 62 | this.loggingTarget.debug.apply(this.loggingTarget, args); 63 | } 64 | 65 | } 66 | 67 | /** @type {Logger} The default logger instance. */ 68 | export let logger: Logger = new Logger(); 69 | 70 | export default logger; 71 | -------------------------------------------------------------------------------- /src/common/pubsub.spec.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from './event-emitter'; 2 | import { Pubsub } from './pubsub'; 3 | 4 | describe('Public subscriptions', () => { 5 | 6 | let spy; 7 | 8 | beforeEach(() => { 9 | spy = { fn: function() { } }; 10 | spyOn(spy, 'fn'); 11 | Pubsub.on('my-event', spy.fn); 12 | }) 13 | 14 | it('should be an instance of EventEmitter', () => expect(Pubsub instanceof EventEmitter).toBe(true)); 15 | 16 | it('should call fn when triggering the public event "my-event"', () => { 17 | Pubsub.emit('my-event'); 18 | expect(spy.fn).toHaveBeenCalled(); 19 | }); 20 | 21 | }); 22 | -------------------------------------------------------------------------------- /src/common/pubsub.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from './event-emitter'; 2 | 3 | let emitter = new EventEmitter(); 4 | 5 | export let Pubsub = emitter; 6 | 7 | export default emitter; 8 | -------------------------------------------------------------------------------- /src/common/require.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Extends the default require function. 3 | * The 'yame' module gets faked, by overriding the require function and checking for that 4 | * specific module name. The given indexModule will be exported in that case. 5 | * 6 | * @param {any} mapping The module to export, if 'yame' will be required. 7 | * @return {void} 8 | */ 9 | export function extend(mapping: { [key: string]: any }) { 10 | const requireFn = typeof (global as any).require === 'function' ? (global as any).require : require; 11 | 12 | const Module = requireFn('module'); 13 | const originalRequire = Module.prototype.require; 14 | const nodeModules = requireFn('path').resolve(__dirname, '..', 'node_modules'); 15 | 16 | Module.prototype.require = function(name: string) { 17 | if (!this._yame_pushed && name === 'yame') { 18 | this.paths.unshift(nodeModules); 19 | this._yame_pushed = true; 20 | } 21 | const found = mapping[name]; 22 | return found === void 0 ? originalRequire.apply(this, arguments) : found; 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /src/common/scene/component/asset.ts: -------------------------------------------------------------------------------- 1 | import { SceneComponent } from 'common/scene/component'; 2 | import { PlatformPath } from 'path'; 3 | import { registerIO } from '../component.io'; 4 | 5 | const path = (global as any).require('path') as PlatformPath; 6 | 7 | export interface AssetSceneComponent extends SceneComponent { 8 | /** 9 | * The asset id. 10 | */ 11 | asset?: string | null; 12 | 13 | /** 14 | * The asset types, this component can be assigned to. 15 | */ 16 | allowedTypes?: string | string[]; 17 | } 18 | 19 | /** 20 | * Creates a new asset component with the given parameters. 21 | * 22 | * @param id 23 | * @param asset 24 | * @param group 25 | */ 26 | export function createAssetComponent(id: string, asset?: string, group?: string): AssetSceneComponent { 27 | return { 28 | id, 29 | type: 'asset', 30 | group, 31 | asset, 32 | allowedTypes: [], 33 | }; 34 | } 35 | 36 | registerIO({ 37 | type: 'asset', 38 | 39 | async serialize(comp: AssetSceneComponent, _entity, ctx) { 40 | if (ctx.uri) 41 | return { 42 | asset: 43 | './' + 44 | path 45 | .relative(path.dirname(ctx.uri as string), (comp.asset as string).replace(ctx.protocol as string, '')) 46 | .replace(/\\/g, '/'), 47 | } as any; 48 | else return null; 49 | }, 50 | 51 | async deserialize(comp: Partial, entity, ctx) { 52 | if (ctx.uri) 53 | return { 54 | asset: 55 | ctx.protocol + 56 | path.resolve(path.dirname(ctx.uri as string), (comp.asset as string).replace(ctx.protocol as string, '')), 57 | } as any; 58 | else return null; 59 | }, 60 | }); 61 | -------------------------------------------------------------------------------- /src/common/scene/component/boolean.ts: -------------------------------------------------------------------------------- 1 | import { SceneComponent } from '../component'; 2 | 3 | /** 4 | * The boolean component can hold either `true` or `false` as a value. 5 | */ 6 | export interface BooleanSceneComponent extends SceneComponent { 7 | 8 | /** 9 | * The boolean value. 10 | */ 11 | bool: boolean; 12 | } 13 | 14 | /** 15 | * Creates a boolean component with the given parameters. 16 | * 17 | * @param id 18 | * @param bool 19 | * @param group 20 | */ 21 | export function createBooleanComponent(id: string, bool: boolean, group?: string): BooleanSceneComponent { 22 | return { 23 | id, 24 | type: 'boolean', 25 | bool, 26 | group, 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/common/scene/component/color.ts: -------------------------------------------------------------------------------- 1 | import { SceneComponent } from '../component'; 2 | 3 | /** 4 | * The color component can hold rgb values and an alpha value. 5 | */ 6 | export interface ColorSceneComponent extends SceneComponent { 7 | 8 | /** 9 | * The red value, between 0 and 255. 10 | */ 11 | red: number; 12 | 13 | /** 14 | * The green value, between 0 and 255. 15 | */ 16 | green: number; 17 | 18 | /** 19 | * The blue value, between 0 and 255. 20 | */ 21 | blue: number; 22 | 23 | /** 24 | * The alpha value, between 0 and 1. 25 | */ 26 | alpha?: number; 27 | } 28 | 29 | 30 | /** 31 | * Creates a new black non-transparent color component with the given parameters. 32 | * 33 | * @param id 34 | * @param group 35 | */ 36 | export function createColorComponent(id: string, group?: string): ColorSceneComponent { 37 | return { 38 | id, 39 | type: 'color', 40 | red: 0, 41 | green: 0, 42 | blue: 0, 43 | alpha: 1, 44 | group, 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /src/common/scene/component/number.ts: -------------------------------------------------------------------------------- 1 | import { SceneComponent } from '../component'; 2 | 3 | /** 4 | * The number component can hold any number the platform can hold. 5 | */ 6 | export interface NumberSceneComponent extends SceneComponent { 7 | 8 | /** 9 | * The number value of the component. 10 | */ 11 | number: number; 12 | } 13 | 14 | /** 15 | * Creates a number component with the given parameters. 16 | * 17 | * @param id 18 | * @param number 19 | * @param group 20 | */ 21 | export function createNumberComponent(id: string, number: number, group?: string): NumberSceneComponent { 22 | return { 23 | id, 24 | type: 'number', 25 | number, 26 | group, 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/common/scene/component/point.ts: -------------------------------------------------------------------------------- 1 | import { SceneComponent } from '../component'; 2 | 3 | /** 4 | * The point component can hold an x and y value. 5 | */ 6 | export interface PointSceneComponent extends SceneComponent { 7 | 8 | /** 9 | * The x-coordinate. 10 | */ 11 | x: number; 12 | 13 | /** 14 | * The y-coordinate. 15 | */ 16 | y: number; 17 | } 18 | 19 | /** 20 | * Creates a position component with the given parameters. 21 | * 22 | * @param id 23 | * @param x 24 | * @param y 25 | * @param group 26 | */ 27 | export function createPointComponent(id: string, x = 0, y = 0, group?: string): PointSceneComponent { 28 | return { 29 | id, 30 | type: 'point', 31 | x, 32 | y, 33 | group 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /src/common/scene/component/range.ts: -------------------------------------------------------------------------------- 1 | import { SceneComponent } from '../component'; 2 | 3 | /** 4 | * The range component can hold any number in the configured range. 5 | */ 6 | export interface RangeSceneComponent extends SceneComponent { 7 | /** 8 | * The current value. 9 | */ 10 | value: number; 11 | 12 | /** 13 | * The minimal possible value. 14 | */ 15 | min?: number; 16 | 17 | /** 18 | * The maximum possible value. 19 | */ 20 | max?: number; 21 | 22 | /** 23 | * The value for making a step towards the max. or min. value. 24 | */ 25 | step?: number; 26 | 27 | /** 28 | * Ticks to display in the gui. 29 | */ 30 | ticks?: number | { [key: string]: string }; 31 | } 32 | 33 | export function createRangeComponent(id: string, value: number, group?: string): RangeSceneComponent { 34 | return { 35 | id, 36 | value, 37 | type: 'range', 38 | group, 39 | }; 40 | } 41 | -------------------------------------------------------------------------------- /src/common/scene/component/size.ts: -------------------------------------------------------------------------------- 1 | import { SceneComponent } from '../component'; 2 | 3 | /** 4 | * The size component can hold a with and a height value. 5 | */ 6 | export interface SizeSceneComponent extends SceneComponent { 7 | 8 | /** 9 | * The width value. 10 | */ 11 | width: number; 12 | 13 | /** 14 | * The height value. 15 | */ 16 | height: number; 17 | 18 | /** 19 | * The local (internal) width. 20 | */ 21 | localWidth: number; 22 | 23 | /** 24 | * The local (internal) height. 25 | */ 26 | localHeight: number; 27 | } 28 | 29 | /** 30 | * Creates a size component with the given parameters. 31 | * 32 | * @param id 33 | * @param x 34 | * @param y 35 | * @param group 36 | */ 37 | export function createSizeComponent(id: string, width = 50, height = 50, group?: string): SizeSceneComponent { 38 | return { 39 | id, 40 | type: 'size', 41 | width, 42 | height, 43 | localWidth: width, 44 | localHeight: height, 45 | group 46 | }; 47 | } 48 | -------------------------------------------------------------------------------- /src/common/scene/component/string.ts: -------------------------------------------------------------------------------- 1 | import { SceneComponent } from '../component'; 2 | 3 | /** 4 | * The string component can hold any string. 5 | */ 6 | export interface StringSceneComponent extends SceneComponent { 7 | 8 | /** 9 | * The string value. 10 | */ 11 | string: string; 12 | } 13 | 14 | /** 15 | * Creates a number component with the given parameters. 16 | * 17 | * @param id 18 | * @param number 19 | * @param group 20 | */ 21 | export function createStringComponent(id: string, string: string, group?: string): StringSceneComponent { 22 | return { 23 | id, 24 | type: 'string', 25 | string, 26 | group, 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/common/scene/index.ts: -------------------------------------------------------------------------------- 1 | export * from './entity'; 2 | export * from './component'; 3 | export * from './component/boolean'; 4 | export * from './component/group'; 5 | export * from './component/number'; 6 | export * from './component/point'; 7 | export * from './component/string'; 8 | export * from './component/range'; 9 | export * from './component/color'; 10 | export * from './component/asset'; 11 | export * from './component/transformation'; 12 | export * from './component.collection'; 13 | -------------------------------------------------------------------------------- /src/common/sort.ts: -------------------------------------------------------------------------------- 1 | import { ISortable } from './interfaces/sortable.interface'; 2 | 3 | /** 4 | * Sorts the given list of options by their positions. 5 | * 6 | * @param options The list to sort. 7 | */ 8 | export function sort[]>(options: T): T { 9 | return options.sort((a, b) => { 10 | if (typeof a.position === 'number' && typeof b.position === 'number') 11 | return a.position - b.position; 12 | else if (typeof a.position === 'number') 13 | return -1; 14 | else if (typeof b.position === 'number') 15 | return 1; 16 | else 17 | return 0; 18 | }); 19 | } -------------------------------------------------------------------------------- /src/common/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out", 5 | "module": "commonjs", 6 | "target": "esnext", 7 | "baseUrl": "src", 8 | "sourceRoot": "./", 9 | "types": [] 10 | }, 11 | "exclude": [ 12 | "test.ts", 13 | "**/*.spec.ts", 14 | "dist", 15 | "out", 16 | "app-builds", 17 | "node_modules" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /src/common/type.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 3 | * 4 | * Represents a type. 5 | * 6 | * An example of a `Type` is `MyCustomComponent` class, which in JavaScript is be represented by 7 | * the `MyCustomComponent` constructor function. 8 | */ 9 | export declare type Type = FunctionConstructor; -------------------------------------------------------------------------------- /src/environments/index.prod.ts: -------------------------------------------------------------------------------- 1 | // This file contains production variables. (When you work in PROD MODE) 2 | // This file is use by webpack. Please don't rename it and don't move it to another directory. 3 | export const environment = { 4 | production: true 5 | }; 6 | -------------------------------------------------------------------------------- /src/environments/index.ts: -------------------------------------------------------------------------------- 1 | // This file contains development variables. (When you work in DEV MODE) 2 | // This file is use by webpack. Please don't rename it and don't move it to another directory. 3 | export const environment = { 4 | production: false, 5 | }; 6 | -------------------------------------------------------------------------------- /src/main/environment.ts: -------------------------------------------------------------------------------- 1 | import { BrowserWindow, App } from 'electron'; 2 | import { IYameEnvironment } from 'common/interfaces/environment'; 3 | 4 | /** 5 | * The yame environment on the electron side. 6 | * This can actually contain any attributes, but the defined one in the interface are always accessible. 7 | */ 8 | export interface IYameElectronEnvironment extends IYameEnvironment { 9 | 10 | /** 11 | * The main window of the editor. 12 | */ 13 | window: BrowserWindow | null; 14 | 15 | /** 16 | * The electron app itself. 17 | */ 18 | app: App | null; 19 | } 20 | 21 | const env: IYameElectronEnvironment = { 22 | window: null, 23 | app: null, 24 | appDir: '', 25 | commonDir: '', 26 | ngDir: '', 27 | electronDir: '', 28 | config: { }, 29 | plugins: [] 30 | }; 31 | 32 | export const Environment = env; 33 | -------------------------------------------------------------------------------- /src/main/index.ts: -------------------------------------------------------------------------------- 1 | export { Environment } from './environment'; 2 | export { Pubsub } from '../common/pubsub'; 3 | export { YamePlugin } from '../common/plugin'; 4 | export { IpcAction } from './ipc/abstract'; 5 | export { IpcDialog } from './ipc/dialog'; 6 | export { IpcDirectory } from './ipc/directory'; -------------------------------------------------------------------------------- /src/main/ipc.spec.ts: -------------------------------------------------------------------------------- 1 | import { IpcAction } from './ipc/abstract'; 2 | import ipc from './ipc'; 3 | import PubSub from '../common/pubsub'; 4 | 5 | describe('Ipc action loader', function() { 6 | 7 | it('should load ipc handlers and resolve', function() { 8 | return ipc() 9 | .then(() => expect(true).toBe(true)) 10 | .catch(() => expect(false).toBe(true)); 11 | }); 12 | 13 | it('should trigger the ipc:init public event', function() { 14 | PubSub.on('ipc:init', () => { 15 | expect(true).toBe(true); 16 | }); 17 | return ipc(); 18 | }); 19 | 20 | it('should trigger the ipc:init public event', function() { 21 | 22 | class CustomIpcAction extends IpcAction { 23 | init(): Promise { 24 | return new Promise((resolve) => { 25 | this._initialized = true; 26 | resolve(); 27 | }); 28 | } 29 | } 30 | 31 | let action = new CustomIpcAction(); 32 | PubSub.on('ipc:init', (list: IpcAction[]) => list.push(action)); 33 | return ipc() 34 | .then(() => expect(action.isInitialized).toBe(true)); 35 | }); 36 | }); 37 | -------------------------------------------------------------------------------- /src/main/ipc.ts: -------------------------------------------------------------------------------- 1 | import { IpcAction } from './ipc/abstract'; 2 | import { IpcDirectory } from './ipc/directory'; 3 | import { IpcDialog } from './ipc/dialog'; 4 | import PubSub from '../common/pubsub'; 5 | import { IpcPlugins } from './ipc/plugins'; 6 | import { IpcFile } from './ipc/file'; 7 | 8 | /** 9 | * Sets all possible ipc messages up. 10 | * 11 | * @export 12 | * @todo Plugins should be able to add their ipc setup. 13 | */ 14 | export default async function() { 15 | 16 | let ipcActions: IpcAction[] = [ 17 | new IpcDialog(), 18 | new IpcDirectory(), 19 | new IpcFile(), 20 | new IpcPlugins(), 21 | ]; 22 | 23 | PubSub.emit('ipc:init', ipcActions); 24 | await Promise.all(ipcActions.map(action => action.init())); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/ipc/abstract.spec.ts: -------------------------------------------------------------------------------- 1 | import { IpcAction } from './abstract'; 2 | 3 | class CustomIpcAction extends IpcAction { 4 | 5 | /** @inheritdoc */ 6 | init(): Promise { 7 | return new Promise((resolve) => { 8 | this._initialized = true; 9 | resolve(); 10 | }); 11 | } 12 | } 13 | 14 | describe('Abstract ipc action', function() { 15 | 16 | it('should be initialized after calling init()', function() { 17 | let action = new CustomIpcAction(); 18 | return action.init().then(() => expect(action.isInitialized).toBe(true)); 19 | }); 20 | 21 | it('should not be initialized after not calling init()', function() { 22 | let action = new CustomIpcAction(); 23 | expect(action.isInitialized).toBe(false); 24 | }); 25 | }) 26 | -------------------------------------------------------------------------------- /src/main/ipc/abstract.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * An ipc action sets a specific set of ipc messages up. 3 | * Simply used to have a better ipc messaging overview. 4 | */ 5 | export abstract class IpcAction { 6 | 7 | /** 8 | * @inheritdoc 9 | */ 10 | protected _initialized = false; 11 | 12 | /** 13 | * Initializes the message handling. Promise, in case initialization is asynchronous. 14 | */ 15 | abstract init(): Promise; 16 | 17 | /** 18 | * Whether the action is initialized or not. 19 | */ 20 | get isInitialized(): boolean { 21 | return this._initialized; 22 | }; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/ipc/dialog.spec.ts: -------------------------------------------------------------------------------- 1 | import { IpcDialog } from './dialog'; 2 | 3 | describe('Dialog ipc actions', function() { 4 | 5 | let action = new IpcDialog(); 6 | 7 | it('Should initialize the ipc main handler for opening a file dialog', function() { 8 | action.init(this.app.electron.remote); 9 | expect(action.isInitialized).toBe(true); 10 | }); 11 | 12 | }); 13 | -------------------------------------------------------------------------------- /src/main/ipc/dialog.ts: -------------------------------------------------------------------------------- 1 | import { dialog, ipcMain } from 'electron'; 2 | import { IpcAction } from './abstract'; 3 | 4 | /** 5 | * Ipc action for triggering save- and open-dialogs. 6 | */ 7 | export class IpcDialog extends IpcAction { 8 | 9 | /** @inheritdoc */ 10 | async init(): Promise { 11 | ipcMain.on('dialog:open', async (event, options, id) => { 12 | const result = await dialog.showOpenDialog(options); 13 | if (id) { 14 | event.sender.send(`dialog:open:${id}`, result); 15 | } else { 16 | event.sender.send('dialog:open', result); 17 | } 18 | }); 19 | 20 | ipcMain.on('dialog:save', async (event, options, id) => { 21 | const result = await dialog.showSaveDialog(options); 22 | if (id) { 23 | event.sender.send(`dialog:save:${id}`, result); 24 | } else { 25 | event.sender.send('dialog:save', result); 26 | } 27 | }); 28 | this._initialized = true; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/ipc/directory.spec.ts: -------------------------------------------------------------------------------- 1 | import { IpcDirectory } from './directory'; 2 | 3 | describe('Directory ipc actions', function() { 4 | 5 | let action = new IpcDirectory(); 6 | 7 | it('Should initialize the ipc main handler for scanning a directory', function() { 8 | action.init(this.app.electron.remote); 9 | expect(action.isInitialized).toBe(true); 10 | }); 11 | 12 | }); 13 | -------------------------------------------------------------------------------- /src/main/ipc/directory.ts: -------------------------------------------------------------------------------- 1 | import { Directory } from '../io/directory'; 2 | import { IpcAction } from './abstract'; 3 | import * as electron from 'electron'; 4 | 5 | /** 6 | * Ipc action for scanning directories. 7 | */ 8 | export class IpcDirectory extends IpcAction { 9 | 10 | /** 11 | * @inheritdoc 12 | */ 13 | async init(): Promise { 14 | electron.ipcMain.on('directory:scan', (event, dirpath: string, id: string, deep = true) => { 15 | const directory = new Directory(dirpath); 16 | directory.on('scan:file', file => event.sender.send(`directory:scan:${id}:file`, file) ); 17 | directory.on('scan:dir', dir => event.sender.send(`directory:scan:${id}:dir`, dir.path) ); 18 | directory.on('scan:dir:done', dir => event.sender.send(`directory:scan:${id}:dir:done`, dir.export()) ); 19 | directory.on('scan:done', () => event.sender.send(`directory:scan:${id}:done`, directory.export()) ); 20 | directory.on('scan:fail', e => event.sender.send(`directory:scan:${id}:fail`, e) ); 21 | directory.scan(false, deep); 22 | }); 23 | this._initialized = true; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/ipc/file.ts: -------------------------------------------------------------------------------- 1 | import { IpcAction } from './abstract'; 2 | import { File } from '../io/file'; 3 | import { ipcMain } from 'electron'; 4 | 5 | /** 6 | * Ipc action for scanning files. 7 | */ 8 | export class IpcFile extends IpcAction { 9 | 10 | /** 11 | * @inheritdoc 12 | */ 13 | async init(): Promise { 14 | ipcMain.on('file:scan', async (event, uri: string) => { 15 | const file = new File(uri); 16 | try { 17 | await file.getStats(); 18 | event.sender.send(`file:scan:${uri}:done`, file.export()); 19 | } catch (e) { 20 | event.sender.send(`file:scan:${uri}:fail`, e); 21 | } 22 | }); 23 | 24 | ipcMain.on('file:read', async (event, uri: string, encoding?: string) => { 25 | const file = new File(uri); 26 | try { 27 | await file.getStats(); 28 | event.sender.send(`file:read:${uri}:done`, await file.read(encoding)); 29 | } catch (e) { 30 | event.sender.send(`file:read:${uri}:fail`, e); 31 | } 32 | }); 33 | 34 | ipcMain.on('file:write', async (event, uri: string, data: string, encoding?: string) => { 35 | const file = new File(uri); 36 | try { 37 | await file.write(data, encoding); 38 | event.sender.send(`file:write:${uri}:done`); 39 | } catch (e) { 40 | event.sender.send(`file:scan:${uri}:fail`, e); 41 | } 42 | }); 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /src/main/ipc/plugins.ts: -------------------------------------------------------------------------------- 1 | import { IpcAction } from "./abstract"; 2 | import * as electron from 'electron'; 3 | import { PluginManager } from "../plugin/manager"; 4 | 5 | /** 6 | * Ipc action for reading plugin paths. 7 | * 8 | * @class IpcPlugins 9 | * @extends {IpcAction} 10 | */ 11 | export class IpcPlugins extends IpcAction { 12 | 13 | 14 | async init(): Promise { 15 | electron.ipcMain.on('plugins:files', (event, id) => { 16 | PluginManager.getFiles() 17 | .then(files => { 18 | if (id) event.sender.send(`plugins:files:${id}`, files); 19 | else event.sender.send('plugins:files', files); 20 | }) 21 | .catch(e => { 22 | console.log(e); 23 | if (id) event.sender.send(`plugins:files:${id}:eror`, e.message); 24 | else event.sender.send('plugins:files:error', e.message); 25 | }); 26 | }); 27 | this._initialized = true; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/plugin/manager.ts: -------------------------------------------------------------------------------- 1 | import { glob } from 'glob'; 2 | import * as path from 'path'; 3 | import { Environment, IYameElectronEnvironment } from '../environment'; 4 | import * as _ from 'lodash'; 5 | import { CommonPluginManager } from '../../common/plugin.manager'; 6 | import { File } from '../io/file'; 7 | import { YamePlugin } from '../../common/plugin'; 8 | 9 | /** 10 | * Plugin manager for the electron side of the editor. 11 | * 12 | * @class PluginManager 13 | * @extends CommonPluginManager 14 | */ 15 | export class PluginManager extends CommonPluginManager { 16 | /** @inheritdoc */ 17 | protected environment: IYameElectronEnvironment = Environment; 18 | 19 | /** 20 | * Files read from the config. 21 | */ 22 | protected static files: string[]; 23 | 24 | /** 25 | * Require electron type entry points. 26 | * 27 | * @inheritdoc 28 | */ 29 | protected type = 'electron'; 30 | 31 | /** 32 | * @inheritdoc 33 | */ 34 | getFiles(): Promise { 35 | return PluginManager.getFiles(); 36 | } 37 | 38 | /** 39 | * @inheritdoc 40 | */ 41 | require(path: string) { 42 | return require(path); 43 | } 44 | 45 | /** 46 | * Persists the config for the given plugin. 47 | * 48 | * @todo Write to local user storage and not to the package.json. 49 | */ 50 | protected persistConfig(plugin: YamePlugin): Promise { 51 | const file = new File(path.resolve(this.pluginPaths[plugin.id], 'package.json')); 52 | return file.write(JSON.stringify(plugin.config, null, 2)); 53 | } 54 | 55 | /** 56 | * Reads all plugin files from the config and resolves them. 57 | */ 58 | static getFiles(force = false): Promise { 59 | if (PluginManager.files && !force) return Promise.resolve(PluginManager.files); 60 | let globs = (Environment.config || {}).plugins; 61 | if (!globs) return Promise.resolve([]); 62 | if (!Array.isArray(globs)) globs = [globs]; 63 | const proms: Promise[] = globs.map(pattern => glob(pattern)); 64 | return Promise.all(proms) 65 | .then(re => _.flatten(re).map(uri => path.resolve(uri))) 66 | .then(re => (PluginManager.files = re)); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/test.ts: -------------------------------------------------------------------------------- 1 | import { Application } from 'spectron'; 2 | import * as path from 'path'; 3 | 4 | let electronPath = path.join(__dirname, '..', '..', 'node_modules', '.bin', 'electron'); 5 | if (process.platform === 'win32') electronPath += '.cmd'; 6 | 7 | beforeAll(function () { 8 | this.app= new Application({ path: electronPath }); 9 | return this.app.start(); 10 | }); 11 | 12 | afterAll(function () { 13 | if (this.app && this.app.isRunning()) { 14 | return this.app.stop() 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /src/main/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out", 5 | "module": "commonjs", 6 | "target": "es2019", 7 | "baseUrl": "../", 8 | "sourceRoot": "./", 9 | "types": [ 10 | "node", 11 | "electron" 12 | ] 13 | }, 14 | "exclude": [ 15 | "test.ts", 16 | "**/*.spec.ts", 17 | "dist", 18 | "out", 19 | "app-builds", 20 | "node_modules" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /src/main/tsconfig.d.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "emitDeclarationOnly": true, 4 | "outDir": "../../typings", 5 | "noEmitOnError": false, 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "declaration": true, 9 | "target": "es6", 10 | "baseUrl": "../", 11 | "types": [ 12 | "node" 13 | ] 14 | }, 15 | "exclude": [ 16 | "test.ts", 17 | "**/*.spec.ts", 18 | "dist", 19 | "app-builds", 20 | "node_modules" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /src/ng/app.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 4 | import { MainComponent } from './components/main/main.component'; 5 | 6 | // Modules 7 | import { SidebarModule } from './modules/sidebar/sidebar.module'; 8 | import { ElectronModule } from './modules/electron/electron.module'; 9 | import { UtilsModule } from './modules/utils'; 10 | 11 | import { AppComponent } from './components/app/app.component'; 12 | import { PluginModule } from './modules/plugin/plugin.module'; 13 | import { NgxsModule } from '@ngxs/store'; 14 | import { SceneModule } from './modules/scene'; 15 | import { PixiModule } from './modules/pixi/pixi.module'; 16 | import { ToolbarModule } from './modules/toolbar/toolbar.module'; 17 | import { HotkeyState } from 'ng/states/hotkey.state'; 18 | import { AssetModule } from './modules/asset/asset.module'; 19 | import { CameraModule } from './modules/camera/camera.module'; 20 | import { PreferencesModule } from './modules/preferences/preferences.module'; 21 | import { EditorState } from './states/editor.state'; 22 | import { NgxsOnBeforePluginModule } from './modules/onbefore-plugin/onbefore-plugin.module'; 23 | import { TilesetModule } from './modules/tileset'; 24 | 25 | @NgModule({ 26 | imports: [ 27 | BrowserModule, 28 | BrowserAnimationsModule, 29 | NgxsModule.forRoot([EditorState, HotkeyState]), 30 | NgxsOnBeforePluginModule.forRoot(), 31 | UtilsModule, 32 | SceneModule, 33 | PluginModule, 34 | ElectronModule, 35 | AssetModule, 36 | SidebarModule, 37 | ToolbarModule, 38 | PreferencesModule, 39 | CameraModule, 40 | PixiModule, 41 | TilesetModule, 42 | ], 43 | declarations: [AppComponent, MainComponent], 44 | bootstrap: [AppComponent], 45 | }) 46 | export class AppModule {} 47 | -------------------------------------------------------------------------------- /src/ng/assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trixt0r/yame/5910bc15431ba172db039aa914fc3120882ecd77/src/ng/assets/.gitkeep -------------------------------------------------------------------------------- /src/ng/assets/fa/layer.svg: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /src/ng/assets/fa/mouse-solid.svg: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /src/ng/assets/fa/touchpad.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /src/ng/assets/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trixt0r/yame/5910bc15431ba172db039aa914fc3120882ecd77/src/ng/assets/grid.png -------------------------------------------------------------------------------- /src/ng/assets/loading.css: -------------------------------------------------------------------------------- 1 | body { 2 | height: 100%; 3 | background: #303030; 4 | color: white; 5 | margin: 0; 6 | } 7 | 8 | .loading { 9 | top: 50%; 10 | left: 50%; 11 | transform: translate(-50%, -50%); 12 | position: absolute; 13 | -webkit-user-select: none; 14 | user-select: none; 15 | text-align: center; 16 | font-family: 'Arial'; 17 | font-size: 24px; 18 | color: white; 19 | text-shadow: 2px 2px black; 20 | cursor: default; 21 | width: 60px; 22 | } 23 | 24 | .loading #spinner.active { 25 | display: block; 26 | } 27 | 28 | .loading #spinner { 29 | position: relative; 30 | height: 60px; 31 | width: 60px; 32 | animation-name: rotate; 33 | animation-duration: 5s; 34 | animation-iteration-count: infinite; 35 | animation-timing-function: cubic-bezier(.42, 1.16, .7,.35); 36 | } 37 | 38 | .loading #spinner .ball { 39 | position: absolute; 40 | display: block; 41 | background-color: #3388cc; 42 | left: 24px; 43 | width: 6px; 44 | height: 6px; 45 | border-radius: 100%; 46 | animation-name: rotate; 47 | animation-duration: 1.25s; 48 | animation-iteration-count: infinite; 49 | transform-origin: 6px 30px; 50 | } 51 | 52 | .loading #spinner .ball:nth-child(1) { 53 | animation-timing-function: cubic-bezier(0.5, 0.1, 1, 1); 54 | } 55 | 56 | .loading #spinner .ball:nth-child(2) { 57 | animation-timing-function: cubic-bezier(0.5, 0.5, 1, 1); 58 | } 59 | 60 | .loading #spinner .ball:nth-child(3) { 61 | animation-timing-function: cubic-bezier(0.5, 0.9, 1, 1); 62 | } 63 | 64 | @keyframes rotate { 65 | 0% { 66 | transform: rotate(0deg); 67 | } 68 | 100% { 69 | transform: rotate(360deg); 70 | } 71 | } 72 | 73 | 74 | .loading .info { 75 | margin-top: 2em; 76 | font-size: 1rem; 77 | } 78 | -------------------------------------------------------------------------------- /src/ng/assets/resize-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/ng/assets/rotate-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/ng/assets/skew-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/ng/components/app/app.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/ng/components/app/app.component.scss: -------------------------------------------------------------------------------- 1 | 2 | :host { 3 | display: block; 4 | width: 100%; 5 | height: 100%; 6 | position: absolute; 7 | background: #303030; 8 | overflow: hidden; 9 | } 10 | -------------------------------------------------------------------------------- /src/ng/components/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { MainComponent } from '../main/main.component'; 2 | import { Component, ViewChild, AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef } from '@angular/core'; 3 | import { SidebarComponent } from '../../modules/sidebar/components/sidebar.component'; 4 | 5 | /** 6 | * Entry point for the main application. 7 | */ 8 | @Component({ 9 | selector: 'yame-root', 10 | templateUrl: 'app.component.html', 11 | styleUrls: ['app.component.scss'], 12 | changeDetection: ChangeDetectionStrategy.OnPush, 13 | standalone: false 14 | }) 15 | export class AppComponent implements AfterViewInit { 16 | name = 'YAME'; 17 | 18 | mainWidth = window.innerWidth; 19 | 20 | /** 21 | * Minimum resize value for the sidebar. 22 | */ 23 | get sidebarMinVal(): number { 24 | return Math.max(window.innerWidth * 0.66, 400); 25 | } 26 | 27 | private initialized = false; 28 | 29 | @ViewChild(MainComponent, { static: false }) main!: MainComponent; 30 | @ViewChild(SidebarComponent, { static: false }) sidebar!: SidebarComponent; 31 | 32 | constructor(private cdr: ChangeDetectorRef) {} 33 | 34 | /** @inheritdoc */ 35 | ngAfterViewInit() { 36 | this.initialized = true; 37 | } 38 | 39 | /** 40 | * Handles the sidebar size update event. 41 | * 42 | * @param left The new size. 43 | */ 44 | onSidebarSizeUpdate(left: number): void { 45 | if (!this.initialized) return; 46 | this.mainWidth = left; 47 | this.cdr.detectChanges(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/ng/components/main/main.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /src/ng/components/main/main.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | position: absolute; 3 | left: 0; 4 | top: 0; 5 | width: 75%; 6 | height: 100%; 7 | overflow: hidden; 8 | } 9 | -------------------------------------------------------------------------------- /src/ng/components/main/main.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | ElementRef, 4 | ChangeDetectionStrategy, 5 | ChangeDetectorRef, 6 | Input, 7 | OnChanges, 8 | SimpleChanges, 9 | } from '@angular/core'; 10 | 11 | @Component({ 12 | selector: 'yame-main', 13 | templateUrl: './main.component.html', 14 | styleUrls: ['./main.component.scss'], 15 | changeDetection: ChangeDetectionStrategy.OnPush, 16 | standalone: false 17 | }) 18 | export class MainComponent implements OnChanges { 19 | static get DEFAULT_SIZE(): number { 20 | return window.innerHeight * 0.66; 21 | } 22 | 23 | @Input() width!: number; 24 | 25 | constructor(public ref: ElementRef, protected cdr: ChangeDetectorRef) {} 26 | 27 | /** 28 | * @inheritdoc 29 | */ 30 | ngOnChanges(changes: SimpleChanges) { 31 | if (changes.width) { 32 | this.ref.nativeElement.style.width = `${changes.width.currentValue}px`; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/ng/environment.ts: -------------------------------------------------------------------------------- 1 | import { IYameEnvironment } from "common/interfaces/environment"; 2 | 3 | const env: IYameEnvironment = { 4 | appDir: '', 5 | commonDir: '', 6 | ngDir: '', 7 | electronDir: '', 8 | config: { }, 9 | plugins: [] 10 | }; 11 | 12 | export const Environment = env; 13 | -------------------------------------------------------------------------------- /src/ng/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trixt0r/yame/5910bc15431ba172db039aa914fc3120882ecd77/src/ng/favicon.ico -------------------------------------------------------------------------------- /src/ng/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Y.A.M.E. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 | 17 | 18 | 19 |
20 |
Loading
21 |
22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /src/ng/index.ts: -------------------------------------------------------------------------------- 1 | export { Environment } from './environment'; 2 | export { YamePlugin } from '../common/plugin'; 3 | export { Pubsub } from '../common/pubsub'; 4 | export { AppModule } from './app.module'; 5 | export { ElectronModule } from './modules/electron/electron.module'; 6 | export { PluginModule } from './modules/plugin/plugin.module'; 7 | export { SidebarModule } from './modules/sidebar/sidebar.module'; 8 | export { UtilsModule } from './modules/utils/utils.module'; 9 | export { ToolbarModule } from './modules/toolbar/toolbar.module'; 10 | export { AssetModule } from './modules/asset/asset.module'; 11 | export * from './modules/utils'; 12 | -------------------------------------------------------------------------------- /src/ng/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/0.13/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: './', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('karma-electron'), 14 | require('@angular-devkit/build-angular/plugins/karma') 15 | ], 16 | client:{ 17 | clearContext: false, // leave Jasmine Spec Runner output visible in browser 18 | useIframe: false 19 | }, 20 | preprocessors: { 21 | './src/ng/test.ts': ['electron'] 22 | }, 23 | mime: { 24 | 'text/x-typescript': ['ts','tsx'] 25 | }, 26 | coverageIstanbulReporter: { 27 | reports: [ 'html', 'lcovonly' ], 28 | fixWebpackSourcePaths: true 29 | }, 30 | angularCli: { 31 | environment: 'dev' 32 | }, 33 | webpack: { 34 | target: 'electron-renderer' 35 | }, 36 | reporters: config.angularCli && config.angularCli.codeCoverage 37 | ? ['progress', 'coverage-istanbul'] 38 | : ['progress', 'kjhtml'], 39 | port: 9876, 40 | colors: true, 41 | logLevel: config.LOG_INFO, 42 | autoWatch: true, 43 | browsers: ['ElectronDebugging'], 44 | customLaunchers: { 45 | ElectronDebugging: { 46 | base: 'Electron', 47 | flags: ['--show'] 48 | }, 49 | }, 50 | singleRun: false 51 | }); 52 | }; 53 | -------------------------------------------------------------------------------- /src/ng/main.ts: -------------------------------------------------------------------------------- 1 | import { Pubsub } from '.'; 2 | import { extend } from 'common/require'; 3 | 4 | const mapping = {}; 5 | 6 | // Define a way to require yame 7 | extend(mapping); 8 | 9 | // import './style.scss'; 10 | // import './polyfills'; 11 | 12 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 13 | 14 | import { enableProdMode } from '@angular/core'; 15 | import { environment } from '../environments'; 16 | import { PluginManager } from './modules/plugin/plugin.manager'; 17 | 18 | if (environment.production) enableProdMode(); 19 | 20 | /** 21 | * Initializes the angular app. 22 | */ 23 | function initNg(): Promise { 24 | return import('./app.module').then(module => { 25 | return platformBrowserDynamic() 26 | .bootstrapModule(module.AppModule) 27 | .then(componentRef => Pubsub.emit('ready', componentRef)); 28 | }); 29 | } 30 | 31 | const pluginManager = new PluginManager(); 32 | pluginManager.initialize().then(initNg).catch(initNg); 33 | -------------------------------------------------------------------------------- /src/ng/modules/asset/components/add-source/add-source.component.html: -------------------------------------------------------------------------------- 1 | 14 | 15 | 28 | 29 | 30 |
    31 |
  • 32 | 33 | {{ 'asset.source.' + source.type | translate }} 34 |
  • 35 |
36 |
37 | -------------------------------------------------------------------------------- /src/ng/modules/asset/components/add-source/add-source.component.scss: -------------------------------------------------------------------------------- 1 | $init-padding: 8px; 2 | 3 | yame-asset-add-source { 4 | text-align: center; 5 | &.icon { 6 | display: inline-block; 7 | } 8 | &.text { 9 | display: block; 10 | 11 | button[nz-button]:not(.no-margin) { 12 | width: calc(100% - #{$init-padding} * 2); 13 | margin: $init-padding; 14 | max-width: 240px; 15 | overflow: hidden; 16 | span { 17 | width: calc(100% - 8px); 18 | overflow: hidden; 19 | white-space: nowrap; 20 | text-overflow: ellipsis; 21 | line-height: 1; 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/ng/modules/asset/components/add-source/add-source.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ChangeDetectionStrategy, 3 | ChangeDetectorRef, 4 | Component, 5 | HostBinding, 6 | Input, 7 | ViewEncapsulation, 8 | } from '@angular/core'; 9 | import { Select, Store } from '@ngxs/store'; 10 | import { DestroyLifecycle, notify } from 'ng/modules/utils'; 11 | import { Observable, takeUntil } from 'rxjs'; 12 | import { AssetState, IAssetsSource, LoadFromAssetsSource } from '../../states'; 13 | 14 | @Component({ 15 | selector: 'yame-asset-add-source', 16 | templateUrl: './add-source.component.html', 17 | styleUrls: ['./add-source.component.scss'], 18 | encapsulation: ViewEncapsulation.None, 19 | changeDetection: ChangeDetectionStrategy.OnPush, 20 | providers: [DestroyLifecycle], 21 | standalone: false 22 | }) 23 | export class AssetAddSourceComponent { 24 | @Input() mode: 'icon' | 'text' = 'icon'; 25 | 26 | /** 27 | * Selector for subscribing to asset source updates. 28 | */ 29 | @Select(AssetState.sources) assetSources$!: Observable; 30 | 31 | /** 32 | * The currently available asset sources. 33 | */ 34 | assetSources: IAssetsSource[] = []; 35 | 36 | @HostBinding('class') 37 | get display(): string { 38 | return this.mode === 'icon' ? 'icon' : 'text'; 39 | } 40 | 41 | constructor(protected store: Store, destroy$: DestroyLifecycle, cdr: ChangeDetectorRef) { 42 | this.assetSources$.pipe(notify(cdr), takeUntil(destroy$)).subscribe((sources) => (this.assetSources = sources)); 43 | } 44 | 45 | /** 46 | * Opens a dialog for opening a folder. 47 | * 48 | * @return A stream emitting on success or fail. 49 | */ 50 | addFromSource(source: IAssetsSource): Observable { 51 | return this.store.dispatch(new LoadFromAssetsSource(source)); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/ng/modules/asset/components/asset-type/asset-type.component.html: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | {{ asset.resource.label }} 16 | 17 | 18 | 19 | 20 | 21 | {{ selected.nzLabel }} 22 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | -------------------------------------------------------------------------------- /src/ng/modules/asset/components/asset-type/asset-type.component.scss: -------------------------------------------------------------------------------- 1 | yame-component-type-asset { 2 | position: relative; 3 | display: block; 4 | margin-top: 8px; 5 | margin-bottom: 8px; 6 | padding: 0 8px; 7 | 8 | button[nz-button] { 9 | position: absolute; 10 | top: 0; 11 | right: 4px; 12 | } 13 | } 14 | 15 | .asset-name { 16 | margin-left: 28px; 17 | } 18 | .asset-preview { 19 | max-height: 20px; 20 | max-width: 20px; 21 | height: auto; 22 | position: absolute; 23 | top: 50%; 24 | transform: translateY(-50%); 25 | } 26 | -------------------------------------------------------------------------------- /src/ng/modules/asset/components/details-tab/details-tab.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 | 7 | 8 | {{ getLabel(asset) | translate }} 9 | 10 | 11 | {{ asset?.resource.size | bytes: 0 }} 12 |
13 | {{ asset?.resource.size | number }} bytes 14 |
15 | 16 | {{ asset.resource.created | date: 'mediumDate':undefined:lang }} 17 |
18 | {{ asset.resource.created | date: 'shortTime':undefined:lang }} 19 |
20 | 21 | {{ asset.resource.changed | date: 'mediumDate':undefined:lang }} 22 |
23 | {{ asset.resource.changed | date: 'shortTime':undefined:lang }} 24 |
25 | 26 | 27 | 28 |
29 | -------------------------------------------------------------------------------- /src/ng/modules/asset/components/details-tab/details-tab.component.scss: -------------------------------------------------------------------------------- 1 | yame-asset-details-tab { 2 | display: block; 3 | .preview { 4 | height: 128px; 5 | max-height: 50%; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/ng/modules/asset/components/details/dimensions/dimensions.component.html: -------------------------------------------------------------------------------- 1 | {{ asset!.resource.width }} x {{ asset!.resource.height }} 2 | -------------------------------------------------------------------------------- /src/ng/modules/asset/components/explorer/explorer.component.html: -------------------------------------------------------------------------------- 1 |
2 |

{{ 'asset.explorer.title' | translate }}

3 | 4 |
5 | 6 | 11 | 17 | 18 | 23 | -------------------------------------------------------------------------------- /src/ng/modules/asset/components/explorer/explorer.component.scss: -------------------------------------------------------------------------------- 1 | yame-asset-explorer { 2 | display: block; 3 | height: 100%; 4 | padding: 8px; 5 | 6 | h4 { 7 | width: calc(100% - 32px); 8 | display: inline-block; 9 | } 10 | 11 | yame-resizable { 12 | position: absolute; 13 | width: 100%; 14 | height: 5px; 15 | left: 0; 16 | cursor: ns-resize; 17 | } 18 | nz-divider.resizer { 19 | position: absolute; 20 | z-index: 999; 21 | left: 8px; 22 | width: calc(100% - 16px); 23 | min-width: unset; 24 | margin: 4px 0; 25 | } 26 | yame-asset-preview { 27 | position: absolute; 28 | left: 8px; 29 | width: calc(100% - 16px); 30 | overflow: auto; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/ng/modules/asset/components/explorer/explorer.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ChangeDetectionStrategy, 3 | ChangeDetectorRef, 4 | Component, 5 | HostListener, 6 | NgZone, 7 | ViewChild, 8 | ViewEncapsulation, 9 | } from '@angular/core'; 10 | import { Select, Store } from '@ngxs/store'; 11 | import { Asset } from 'common/asset'; 12 | import { DestroyLifecycle, notify, ResizableComponent } from 'ng'; 13 | import { Observable, takeUntil } from 'rxjs'; 14 | import { AssetState, SelectAsset, UnselectAsset } from '../../states'; 15 | 16 | @Component({ 17 | selector: 'yame-asset-explorer', 18 | templateUrl: './explorer.component.html', 19 | styleUrls: ['./explorer.component.scss'], 20 | encapsulation: ViewEncapsulation.None, 21 | changeDetection: ChangeDetectionStrategy.OnPush, 22 | standalone: false 23 | }) 24 | export class AssetExplorerComponent { 25 | static readonly PREVIEW_MIN_HEIGHT = 250; 26 | 27 | /** 28 | * Selector for subscribing to asset selection. 29 | */ 30 | @Select(AssetState.selectedAsset) asset$!: Observable; 31 | 32 | /** 33 | * The currently selected asset. 34 | */ 35 | set asset(val: Asset | null) { 36 | this.store.dispatch(!val ? new UnselectAsset() : new SelectAsset(val!)); 37 | } 38 | get asset(): Asset | null { 39 | return this._asset; 40 | } 41 | 42 | get maxResizerValue(): number { 43 | return this._asset ? window.innerHeight - AssetExplorerComponent.PREVIEW_MIN_HEIGHT : window.innerHeight; 44 | } 45 | 46 | protected _asset: Asset | null = null; 47 | 48 | constructor(protected store: Store, cdr: ChangeDetectorRef, zone: NgZone, destroy$: DestroyLifecycle) { 49 | zone.runOutsideAngular(() => { 50 | this.asset$.pipe(takeUntil(destroy$), notify(cdr)).subscribe(_ => (this._asset = _)); 51 | }); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/ng/modules/asset/components/index.ts: -------------------------------------------------------------------------------- 1 | export { AssetTypeComponent } from './asset-type/asset-type.component'; 2 | export { AssetTreeComponent } from './tree/tree.component'; 3 | export { AssetDimensionsDetailsComponent } from './details/dimensions/dimensions.component'; 4 | export { AssetDefaultPreviewComponent, AssetImagePreviewComponent } from './previews'; 5 | export { AssetAddSourceComponent } from './add-source/add-source.component'; 6 | export { AssetExplorerComponent } from './explorer/explorer.component'; 7 | export { AssetInspectorComponent } from './inspector/inspector.component'; 8 | export { AssetDetailsTabComponent } from './details-tab/details-tab.component'; 9 | -------------------------------------------------------------------------------- /src/ng/modules/asset/components/inspector/inspector.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 |
6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | -------------------------------------------------------------------------------- /src/ng/modules/asset/components/inspector/inspector.component.scss: -------------------------------------------------------------------------------- 1 | yame-asset-inspector { 2 | display: block; 3 | overflow: auto; 4 | nz-tabset nz-tabs-nav .ant-tabs-tab .anticon { 5 | margin: 0; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/ng/modules/asset/components/previews/default/default.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /src/ng/modules/asset/components/previews/default/default.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | } -------------------------------------------------------------------------------- /src/ng/modules/asset/components/previews/default/default.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ChangeDetectionStrategy } from '@angular/core'; 2 | 3 | import { Asset } from 'common/asset'; 4 | import { IAssetOwner } from '../../../interfaces'; 5 | 6 | @Component({ 7 | templateUrl: './default.component.html', 8 | styleUrls: ['./default.component.scss'], 9 | changeDetection: ChangeDetectionStrategy.OnPush, 10 | standalone: false 11 | }) 12 | export class AssetDefaultPreviewComponent implements IAssetOwner { 13 | asset!: Asset; 14 | } 15 | -------------------------------------------------------------------------------- /src/ng/modules/asset/components/previews/image/image.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /src/ng/modules/asset/components/previews/image/image.component.scss: -------------------------------------------------------------------------------- 1 | .image-wrapper { 2 | img { 3 | height: 100%; 4 | width: auto; 5 | max-width: 100%; 6 | margin: 0; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/ng/modules/asset/components/previews/image/image.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ChangeDetectionStrategy } from '@angular/core'; 2 | 3 | import { Asset } from 'common/asset'; 4 | import { AssetPreviewComponent } from '../../../decorators/preview.decorator'; 5 | import { IAssetOwner } from '../../../interfaces'; 6 | 7 | @Component({ 8 | templateUrl: './image.component.html', 9 | styleUrls: ['./image.component.scss'], 10 | changeDetection: ChangeDetectionStrategy.OnPush, 11 | standalone: false 12 | }) 13 | @AssetPreviewComponent('png', 'jpg', 'jpeg', 'gif', 'svg') 14 | export class AssetImagePreviewComponent implements IAssetOwner { 15 | /** 16 | * @inheritdoc 17 | */ 18 | asset!: Asset; 19 | } 20 | -------------------------------------------------------------------------------- /src/ng/modules/asset/components/previews/index.ts: -------------------------------------------------------------------------------- 1 | export * from './default/default.component'; 2 | export * from './image/image.component'; -------------------------------------------------------------------------------- /src/ng/modules/asset/components/tree/tree.component.html: -------------------------------------------------------------------------------- 1 | 10 | 11 |
12 |
13 | 14 |
15 |
{{ node.title }}
16 |
17 |
18 |
19 | 28 |
29 |
30 |
31 |
32 | -------------------------------------------------------------------------------- /src/ng/modules/asset/components/tree/tree.component.scss: -------------------------------------------------------------------------------- 1 | @import '/src/ng/variables.scss'; 2 | $init-padding: 16px; 3 | $icon-width: 24px; 4 | 5 | yame-asset-tree { 6 | display: block; 7 | overflow: hidden; 8 | overflow-y: auto; 9 | // box-shadow: 3px 0px 1px -2px rgba(0, 0, 0, 0.2), 2px 0px 2px 0 rgba(0, 0, 0, 0.14), 1px 0px 5px 0 rgba(0, 0, 0, 0.12); 10 | 11 | &.no-overflow { 12 | overflow-y: hidden; 13 | } 14 | text-align: center; 15 | 16 | // .tree-wrapper { 17 | // height: calc(100% - 69.5px); 18 | // width: calc(100% - #{$resizer-size * 2}); 19 | // padding-left: $resizer-size; 20 | // overflow-x: hidden; 21 | // overflow-y: auto; 22 | 23 | .inline-actions-wrapper { 24 | min-width: unset; 25 | width: $icon-width; 26 | .inline-actions { 27 | float: right; 28 | transform: translateX(100%); 29 | opacity: 0; 30 | transition: all ease-in-out 100ms; 31 | } 32 | } 33 | 34 | nz-tree-node.ant-tree-treenode { 35 | padding: 0; 36 | margin-bottom: 4px; 37 | user-select: none; 38 | overflow: hidden; 39 | transition: all 0.3s, border 0s, line-height 0s, box-shadow 0s; 40 | &:hover { 41 | background-color: rgba(255, 255, 255, 0.08); 42 | .inline-actions { 43 | transform: none; 44 | opacity: inherit; 45 | } 46 | } 47 | &.ant-tree-treenode-selected { 48 | background-color: #11263c; 49 | } 50 | .ant-tree-node-content-wrapper:hover { 51 | background: unset; 52 | } 53 | } 54 | 55 | .ant-tree-node-content-wrapper { 56 | min-width: 0; 57 | } 58 | 59 | .root { 60 | width: calc(100% - #{$icon-width}); 61 | } 62 | 63 | .icon-wrapper { 64 | width: $icon-width; 65 | } 66 | .node-title { 67 | width: calc(100% - #{$icon-width}); 68 | } 69 | // } 70 | 71 | // .empty { 72 | // padding: $init-padding; 73 | // opacity: 0.5; 74 | // } 75 | } 76 | -------------------------------------------------------------------------------- /src/ng/modules/asset/decorators/details.decorator.ts: -------------------------------------------------------------------------------- 1 | import { AssetState } from '../states/asset.state'; 2 | import { IAssetDetailsComponent } from '../interfaces'; 3 | 4 | /** 5 | * Registers an asset details component for the given types. 6 | * 7 | * @param type The asset types for which the asset details will be used. 8 | * @returns A function, which adds the given component to the internal registry. 9 | */ 10 | export function AssetDetailsComponent(...types: string[]): Function { 11 | return function decorator(component: IAssetDetailsComponent): void { 12 | AssetState._initDetailsComponent(component, ...types); 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /src/ng/modules/asset/decorators/index.ts: -------------------------------------------------------------------------------- 1 | export { AssetDetailsComponent } from './details.decorator'; 2 | export { AssetPreviewComponent } from './preview.decorator'; 3 | export { AssetTabComponent } from './tab.decorator'; 4 | -------------------------------------------------------------------------------- /src/ng/modules/asset/decorators/preview.decorator.ts: -------------------------------------------------------------------------------- 1 | import { Type } from '@angular/core'; 2 | import { IAssetOwner } from '../interfaces'; 3 | import { AssetState } from '../states/asset.state'; 4 | 5 | /** 6 | * Registers an asset preview component for the given types. 7 | * 8 | * @param type The asset types for which the asset preview will be used. 9 | * @return A function, which adds the given component to the internal registry. 10 | */ 11 | export function AssetPreviewComponent(...types: string[]): Function { 12 | return (component: Type) => { 13 | AssetState._initPreviewComponent(component, ...types); 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /src/ng/modules/asset/decorators/tab.decorator.ts: -------------------------------------------------------------------------------- 1 | import { Type } from '@angular/core'; 2 | import { IAssetOwner } from '../interfaces'; 3 | import { AssetState } from '../states/asset.state'; 4 | 5 | /** 6 | * Registers an asset tab component for the given types. 7 | * 8 | * @param type The asset types for which the asset tab will be used. 9 | * @return A function, which adds the given component to the internal registry. 10 | */ 11 | export function AssetTabComponent(...types: string[]): Function { 12 | return (component: Type) => { 13 | AssetState._initTabComponent(component, ...types); 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /src/ng/modules/asset/directives/index.ts: -------------------------------------------------------------------------------- 1 | export { AssetPreviewDirective } from './preview.directive'; 2 | export { AssetTabDirective } from './tab.directive'; 3 | -------------------------------------------------------------------------------- /src/ng/modules/asset/directives/tab.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, Input, OnChanges, Type, ViewContainerRef } from '@angular/core'; 2 | import { Asset } from 'common/asset'; 3 | import { IAssetOwner } from '../interfaces'; 4 | 5 | /** 6 | * Directive for injecting a preview component for a certain asset. 7 | */ 8 | @Directive({ 9 | selector: '[yameAssetTab]', 10 | standalone: false 11 | }) 12 | export class AssetTabDirective implements OnChanges { 13 | /** 14 | * The asset to render. 15 | */ 16 | @Input('yameAssetTab') asset!: Asset; 17 | 18 | /** 19 | * The component to render. 20 | */ 21 | @Input('yameAssetTabContent') content!: Type; 22 | 23 | constructor(protected viewContainerRef: ViewContainerRef) {} 24 | 25 | /** 26 | * Renders the current asset tab content. 27 | */ 28 | private render(): void { 29 | this.viewContainerRef.clear(); 30 | const componentRef = this.viewContainerRef.createComponent(this.content); 31 | componentRef.instance.asset = this.asset; 32 | } 33 | 34 | /** 35 | * @inheritdoc 36 | */ 37 | ngOnChanges(): void { 38 | if (!this.asset || !this.content) return; 39 | this.render(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/ng/modules/asset/index.ts: -------------------------------------------------------------------------------- 1 | export * from './decorators'; 2 | export * from './states'; 3 | export * from './interfaces'; 4 | export * from './tools/add.tool'; 5 | export { AssetModule } from './asset.module'; 6 | -------------------------------------------------------------------------------- /src/ng/modules/asset/interfaces/asset-details-component.interface.ts: -------------------------------------------------------------------------------- 1 | import { Type } from '@angular/core'; 2 | import { Asset } from 'common/asset'; 3 | 4 | export interface IAssetDetailsComponent extends Type> { 5 | label: string; 6 | isVisibleFor?(asset?: Asset): boolean; 7 | } 8 | -------------------------------------------------------------------------------- /src/ng/modules/asset/interfaces/asset-owner.interface.ts: -------------------------------------------------------------------------------- 1 | import { Asset } from 'common/asset'; 2 | 3 | /** 4 | * An interface for defining a type which owns an asset. 5 | */ 6 | export interface IAssetOwner { 7 | /** 8 | * The asset instance. 9 | */ 10 | asset: Asset; 11 | } 12 | -------------------------------------------------------------------------------- /src/ng/modules/asset/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export { IAssetDetailsComponent } from './asset-details-component.interface'; 2 | export { IAssetOwner } from './asset-owner.interface'; 3 | -------------------------------------------------------------------------------- /src/ng/modules/asset/states/index.ts: -------------------------------------------------------------------------------- 1 | export * from './asset.state'; 2 | export * from './actions/asset.action'; 3 | -------------------------------------------------------------------------------- /src/ng/modules/camera/camera-zoom.interface.ts: -------------------------------------------------------------------------------- 1 | import { IPoint } from 'common/math'; 2 | 3 | export interface CameraZoom { 4 | /** 5 | * The zoom value. 6 | */ 7 | value: number; 8 | 9 | /** 10 | * The minimum zoom value. 11 | */ 12 | min: number; 13 | 14 | /** 15 | * The maximum zoom value. 16 | */ 17 | max: number; 18 | 19 | /** 20 | * The step value for zooming in and out. 21 | */ 22 | step: number; 23 | 24 | /** 25 | * The target position to zoom to. 26 | */ 27 | target: IPoint; 28 | } -------------------------------------------------------------------------------- /src/ng/modules/camera/camera.ids.ts: -------------------------------------------------------------------------------- 1 | export class CameraId { 2 | static readonly SCENE = 'scene'; 3 | } 4 | -------------------------------------------------------------------------------- /src/ng/modules/camera/camera.tool.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Store } from '@ngxs/store'; 3 | import { ShowToolbarOptions } from '../toolbar/states/actions/toolbar.action'; 4 | import { Tool, ToolType } from '../toolbar/tool'; 5 | import { CameraToolComponent } from './components/tool/tool.component'; 6 | 7 | @Injectable({ providedIn: 'root' }) 8 | export class CameraTool extends Tool { 9 | /** 10 | * @inheritdoc 11 | */ 12 | type = ToolType.CLICK; 13 | 14 | constructor(public store: Store) { 15 | super('camera', 'video-camera'); 16 | } 17 | 18 | /** 19 | * @inheritdoc 20 | */ 21 | async onActivate(event?: MouseEvent): Promise { 22 | await this.store.dispatch(new ShowToolbarOptions(CameraToolComponent, event?.currentTarget as Element)).toPromise(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/ng/modules/camera/components/index.ts: -------------------------------------------------------------------------------- 1 | export { CameraToolComponent } from './tool/tool.component'; 2 | -------------------------------------------------------------------------------- /src/ng/modules/camera/components/tool/tool.component.html: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 11 | 19 | 22 | 23 | -------------------------------------------------------------------------------- /src/ng/modules/camera/components/tool/tool.component.scss: -------------------------------------------------------------------------------- 1 | yame-camera-tool { 2 | nz-slider { 3 | min-width: 100px !important; 4 | width: 15vw !important; 5 | margin: 0 !important; 6 | .ant-slider { 7 | margin: 0 6px; 8 | } 9 | } 10 | 11 | .ant-card-body { 12 | padding: 0; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/ng/modules/camera/directives/index.ts: -------------------------------------------------------------------------------- 1 | export { CameraDirective } from './camera.directive'; 2 | -------------------------------------------------------------------------------- /src/ng/modules/camera/index.ts: -------------------------------------------------------------------------------- 1 | export * from './states'; 2 | export * from './directives'; 3 | export * from './components'; 4 | export { CameraId } from './camera.ids'; 5 | export { CameraZoom } from './camera-zoom.interface'; 6 | export { CameraTool } from './camera.tool'; 7 | export { CameraModule } from './camera.module'; 8 | -------------------------------------------------------------------------------- /src/ng/modules/camera/states/actions/camera.action.ts: -------------------------------------------------------------------------------- 1 | import { IPoint } from 'common/math'; 2 | import { CameraZoom } from '../../camera-zoom.interface'; 3 | 4 | export class ZoomCameraOut { 5 | static type = '[Camera] Zoom camera out'; 6 | constructor(public id: string, public entities: string[] = []) {} 7 | } 8 | 9 | export class UpdateCameraZoom { 10 | static type = '[Camera] Update camera zoom'; 11 | constructor(public id: string, public zoom: Partial) {} 12 | } 13 | 14 | export class UpdateCameraPosition { 15 | static type = '[Camera] Update camera position'; 16 | constructor(public id: string, public position: IPoint) {} 17 | } 18 | -------------------------------------------------------------------------------- /src/ng/modules/camera/states/index.ts: -------------------------------------------------------------------------------- 1 | export * from './actions/camera.action'; 2 | export { CameraState, ICameraState } from './camera.state'; 3 | -------------------------------------------------------------------------------- /src/ng/modules/electron/electron.exception.ts: -------------------------------------------------------------------------------- 1 | import { Exception } from 'common/exception'; 2 | 3 | export class ElectronException extends Exception { } 4 | -------------------------------------------------------------------------------- /src/ng/modules/electron/electron.mock.service.ts: -------------------------------------------------------------------------------- 1 | import { ElectronService } from './electron.service'; 2 | import { EventEmitter } from 'eventemitter3'; 3 | 4 | class MockedIpc extends EventEmitter { 5 | 6 | send(channel: string, ...args: any[]) { }; 7 | sendSync(channel: string, ...args: any[]) { }; 8 | sendToHost(channel: string, ...args: any[]) { }; 9 | } 10 | 11 | /** 12 | * Helper class for mocking the electron service. 13 | * This class mocks only electron dependencies, such as the ipcRenderer instance. 14 | */ 15 | export class ElectronMockService extends ElectronService { 16 | 17 | private myIpc = new MockedIpc(); 18 | 19 | get ipc() { 20 | return this.myIpc; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/ng/modules/electron/electron.provider.spec.ts: -------------------------------------------------------------------------------- 1 | import { ElectronProvider } from './electron.provider'; 2 | import { ElectronMockService } from './electron.mock.service'; 3 | 4 | class MyProvider extends ElectronProvider { 5 | 6 | } 7 | 8 | describe('ElectronProvider', () => { 9 | 10 | it('should be extendable', () => expect(new MyProvider(new ElectronMockService()) instanceof ElectronProvider).toBe(true)); 11 | 12 | }); 13 | -------------------------------------------------------------------------------- /src/ng/modules/electron/electron.provider.ts: -------------------------------------------------------------------------------- 1 | import { ElectronService } from './electron.service'; 2 | 3 | 4 | export abstract class ElectronProvider { 5 | constructor(protected service: ElectronService) { 6 | } 7 | 8 | /** 9 | * @readonly 10 | * @type {Electron.IpcRenderer} 11 | */ 12 | get ipc() { 13 | return this.service.ipc; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/ng/modules/electron/electron.service.ts: -------------------------------------------------------------------------------- 1 | import { ElectronProviderAlreadyRegistered } from './exception/services/registered.exception'; 2 | import { ElectronProviderNotFound } from './exception/services/not-found.exception'; 3 | import { Injectable, Type } from '@angular/core'; 4 | import { ElectronProvider } from './electron.provider'; 5 | 6 | /** 7 | * The electron service provides functionality for registering electron providers. 8 | * Electron providers can be used to abstract and simplify the usage for the ipc communication between 9 | * the browser process and the renderer process. 10 | */ 11 | @Injectable({ providedIn: 'root' }) 12 | export class ElectronService { 13 | private providers: Record = {}; 14 | 15 | /** 16 | * Reference to the ipc renderer 17 | */ 18 | get ipc() { 19 | return (global as any).require('electron').ipcRenderer; 20 | } 21 | 22 | /** 23 | * Registers electron providers. 24 | * Registering means creating an instance of the electron provider class and storing it in the internal registry. 25 | * 26 | * @param types The types to register. 27 | */ 28 | registerProvider(...types: Type[]): void { 29 | types.forEach(type => { 30 | if (this.providers[type.name]) throw new ElectronProviderAlreadyRegistered(type); 31 | this.providers[type.name] = new type(this); 32 | }); 33 | } 34 | 35 | /** 36 | * Returns the provider instance for the given type. 37 | * 38 | * @param type 39 | * @return The instance for the given provider class. 40 | */ 41 | getProvider(type: Type): T { 42 | if (!this.providers[type.name]) throw new ElectronProviderNotFound(type); 43 | return this.providers[type.name]; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/ng/modules/electron/exception/provider.exception.ts: -------------------------------------------------------------------------------- 1 | import { ElectronException } from '../electron.exception'; 2 | 3 | export class ElectronProviderException extends ElectronException { } 4 | -------------------------------------------------------------------------------- /src/ng/modules/electron/exception/providers/dialog.exception.ts: -------------------------------------------------------------------------------- 1 | import { ElectronProviderException } from '../provider.exception'; 2 | 3 | export class DialogProviderException extends ElectronProviderException { } 4 | -------------------------------------------------------------------------------- /src/ng/modules/electron/exception/providers/directory.exception.ts: -------------------------------------------------------------------------------- 1 | import { ElectronProviderException } from '../provider.exception'; 2 | 3 | export class DirectoryProviderException extends ElectronProviderException { } 4 | -------------------------------------------------------------------------------- /src/ng/modules/electron/exception/service.exception.ts: -------------------------------------------------------------------------------- 1 | import { Type } from '@angular/core'; 2 | import { ElectronProvider } from '../electron.provider'; 3 | import { ElectronException } from '../electron.exception'; 4 | 5 | export class ElectronServiceException extends ElectronException { 6 | 7 | constructor(message: string, protected clazz: Type) { 8 | super(message); 9 | } 10 | 11 | get provider(): Type { 12 | return this.clazz; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/ng/modules/electron/exception/services/not-found.exception.ts: -------------------------------------------------------------------------------- 1 | import { ElectronProvider } from '../../electron.provider'; 2 | import { Type } from '@angular/core'; 3 | import { ElectronServiceException } from '../service.exception'; 4 | 5 | export class ElectronProviderNotFound extends ElectronServiceException { 6 | 7 | constructor(protected clazz: Type) { 8 | super(`No electron provider found for ${clazz.name}`, clazz); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/ng/modules/electron/exception/services/registered.exception.ts: -------------------------------------------------------------------------------- 1 | import { ElectronProvider } from '../../electron.provider'; 2 | import { Type } from '@angular/core'; 3 | import { ElectronServiceException } from '../service.exception'; 4 | 5 | export class ElectronProviderAlreadyRegistered extends ElectronServiceException { 6 | 7 | constructor(protected clazz: Type) { 8 | super (`Provider with name ${clazz.name} has been already registered`, clazz); 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/ng/modules/electron/index.ts: -------------------------------------------------------------------------------- 1 | export { ElectronService } from './electron.service'; 2 | export { ElectronProvider } from './electron.provider'; 3 | export { ElectronException } from './electron.exception'; 4 | export { ElectronServiceException } from './exception/service.exception'; 5 | export { ElectronProviderNotFound } from './exception/services/not-found.exception'; 6 | export { ElectronProviderAlreadyRegistered } from './exception/services/registered.exception'; 7 | export { ElectronProviderException } from './exception/provider.exception'; 8 | export { DialogProviderException } from './exception/providers/dialog.exception'; 9 | export { DirectoryProviderException } from './exception/providers/directory.exception'; 10 | -------------------------------------------------------------------------------- /src/ng/modules/electron/providers/dialog.provider.ts: -------------------------------------------------------------------------------- 1 | import { DialogProviderException } from '../exception/providers/dialog.exception'; 2 | import { ElectronProvider } from '../electron.provider'; 3 | import { IpcRendererEvent } from 'electron/main'; 4 | import { uniqueId } from 'lodash'; 5 | 6 | export class DialogProvider extends ElectronProvider { 7 | 8 | /** 9 | * Opens a native desktop dialog for selecting file(s) or folder(s). 10 | * 11 | * @param options Options to be passed to the electron dialog provider. 12 | * @return A list of file paths 13 | */ 14 | open(options: Electron.OpenDialogOptions): Promise { 15 | return new Promise((resolve, reject) => { 16 | const id = uniqueId('dialog-'); 17 | this.ipc.send('dialog:open', options, id); 18 | this.ipc.once(`dialog:open:${id}`, (event: IpcRendererEvent, dialog: { filePaths: string[] }) => { 19 | if (dialog && dialog.filePaths && dialog.filePaths.length > 0) resolve(dialog.filePaths); 20 | else reject(new DialogProviderException('No files chosen')); 21 | }); 22 | }); 23 | } 24 | 25 | async save(options: Electron.SaveDialogOptions): Promise { 26 | return new Promise((resolve, reject) => { 27 | const id = uniqueId('dialog-'); 28 | this.ipc.send('dialog:save', options, id); 29 | this.ipc.once(`dialog:save:${id}`, (event: IpcRendererEvent, dialog: { filePath: string }) => { 30 | if (dialog && dialog.filePath) resolve(dialog.filePath); 31 | else reject(new DialogProviderException('No files chosen')); 32 | }); 33 | }); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/ng/modules/electron/providers/directory.provider.ts: -------------------------------------------------------------------------------- 1 | import { DirectoryProviderException } from '../exception/providers/directory.exception'; 2 | import { ElectronProvider } from '../electron.provider'; 3 | import { uniqueId } from 'lodash'; 4 | import { IpcRendererEvent } from 'electron/main'; 5 | import { IResource } from 'common/interfaces/resource'; 6 | 7 | export class DirectoryProvider extends ElectronProvider { 8 | 9 | /** 10 | * Scans the given directory via electron. 11 | * 12 | * @param dir The directory to load. 13 | * @param deep Whether to load all nested folders. 14 | * @return The content of the given directory. 15 | */ 16 | async scan(dir: string, deep = true): Promise> { 17 | return new Promise((resolve, reject) => { 18 | const id = uniqueId('directory-'); 19 | this.ipc.send('directory:scan', dir, id, deep); 20 | this.ipc.once(`directory:scan:${id}:done`, (event: IpcRendererEvent, json: any) => { 21 | this.ipc.removeAllListeners(`directory:scan:${id}:fail`); 22 | resolve(json); 23 | }); 24 | this.ipc.once(`directory:scan:${id}:fail`, (event: IpcRendererEvent, e: Error) => { 25 | this.ipc.removeAllListeners(`directory:scan:${id}:done`); 26 | reject(new DirectoryProviderException(e.message)); 27 | }); 28 | }); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/ng/modules/onbefore-plugin/onbefore-plugin.module.ts: -------------------------------------------------------------------------------- 1 | import { ModuleWithProviders, NgModule } from '@angular/core'; 2 | import { NGXS_PLUGINS } from '@ngxs/store'; 3 | import { OnBeforePlugin } from './onbefore.plugin'; 4 | 5 | 6 | @NgModule() 7 | export class NgxsOnBeforePluginModule { 8 | static forRoot(): ModuleWithProviders { 9 | return { 10 | ngModule: NgxsOnBeforePluginModule, 11 | providers: [ 12 | OnBeforePlugin, 13 | { 14 | provide: NGXS_PLUGINS, 15 | useClass: OnBeforePlugin, 16 | multi: true 17 | } 18 | ] 19 | }; 20 | } 21 | } -------------------------------------------------------------------------------- /src/ng/modules/pixi/components/renderer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ChangeDetectionStrategy, ElementRef } from '@angular/core'; 2 | import { CameraId } from 'ng/modules/camera'; 3 | import { ISceneRendererComponent } from 'ng/modules/scene'; 4 | 5 | @Component({ 6 | template: ``, 7 | styles: [ 8 | ` 9 | canvas { 10 | cursor: auto; 11 | } 12 | `, 13 | ], 14 | changeDetection: ChangeDetectionStrategy.OnPush, 15 | standalone: false 16 | }) 17 | export class PixiRendererComponent implements ISceneRendererComponent { 18 | readonly cameraId = CameraId.SCENE; 19 | constructor(public readonly ref: ElementRef) {} 20 | } 21 | -------------------------------------------------------------------------------- /src/ng/modules/pixi/exceptions/pixi.exception.ts: -------------------------------------------------------------------------------- 1 | import { Exception } from 'common/exception'; 2 | 3 | export class PixiException extends Exception { } 4 | -------------------------------------------------------------------------------- /src/ng/modules/pixi/index.ts: -------------------------------------------------------------------------------- 1 | export * from './utils'; 2 | export { PixiModule } from './pixi.module'; 3 | -------------------------------------------------------------------------------- /src/ng/modules/pixi/services/camera.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | 3 | @Injectable({ providedIn: 'root' }) 4 | export class PixiCameraService {} 5 | -------------------------------------------------------------------------------- /src/ng/modules/pixi/systems/debug.system.ts: -------------------------------------------------------------------------------- 1 | import { System } from '@trixt0r/ecs'; 2 | import { Container, Text } from 'pixi.js'; 3 | import { PixiRendererService } from '../services/renderer.service'; 4 | 5 | export class PixiDebugSystem extends System { 6 | private debugDisplay: Container; 7 | private debugDuration?: Text; 8 | 9 | constructor(protected service: PixiRendererService, priority?: number) { 10 | super(priority); 11 | this.debugDisplay = new Container(); 12 | this.debugDisplay.name = 'debug'; 13 | this.debugDisplay.position.set(10, 8); 14 | this.service.init$.subscribe(() => { 15 | this.service.stage?.addChild(this.debugDisplay); 16 | this.debugDuration = new Text('0ms', { fill: 0xffffff, fontSize: 12, fontFamily: 'Courier New' }); 17 | this.debugDuration.name = 'duration'; 18 | this.debugDisplay.addChild(this.debugDuration); 19 | this.process(); 20 | }); 21 | } 22 | 23 | /** 24 | * @inheritdoc 25 | */ 26 | process(): void { 27 | if (!this.debugDuration) return; 28 | const now = performance.now(); 29 | const diagnostics = this.service.diagnostics; 30 | const time = this.service.diagnostics.startTime as number; 31 | const overAllTime = now - time; 32 | const renderingTime = (diagnostics.endRenderingTime as number) - (diagnostics.startRenderingTime as number); 33 | const iterationTime = overAllTime - renderingTime; 34 | this.debugDuration.text = `Entities: ${diagnostics.entities || 0} 35 | Iteration: ${iterationTime || 0} ms 36 | Rendering: ${renderingTime || 0} ms 37 | Overall: ${overAllTime || 0} ms`; 38 | this.service.renderer?.render(this.debugDisplay, { clear: false }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/ng/modules/pixi/systems/foreground.system.ts: -------------------------------------------------------------------------------- 1 | import { System } from '@trixt0r/ecs'; 2 | import { Container } from 'pixi.js'; 3 | import { PixiRendererService } from '../services/renderer.service'; 4 | 5 | export class PixiForegroundSystem extends System { 6 | private foreground: Container; 7 | 8 | constructor(protected service: PixiRendererService, priority?: number) { 9 | super(priority); 10 | this.foreground = new Container(); 11 | this.foreground.name = 'foreground'; 12 | this.foreground.sortableChildren = true; 13 | this.service.init$.subscribe(() => { 14 | this.service.app?.stage.addChild(this.foreground); 15 | }); 16 | } 17 | 18 | /** 19 | * @inheritdoc 20 | */ 21 | process(): void { 22 | this.service.renderer?.render(this.foreground, { clear: false }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/ng/modules/pixi/systems/grid.system.ts: -------------------------------------------------------------------------------- 1 | import { System } from '@trixt0r/ecs'; 2 | import { Grid } from '../utils/grid'; 3 | import { PixiRendererService } from '../services/renderer.service'; 4 | 5 | export class PixiGridSystem extends System { 6 | 7 | protected grid: Grid; 8 | 9 | constructor(protected service: PixiRendererService, priority?: number) { 10 | super(priority); 11 | this.grid = new Grid(service.scene); 12 | } 13 | 14 | /** 15 | * @inheritdoc 16 | */ 17 | process(): void { 18 | this.grid.update(this.service.component.width, this.service.component.height); 19 | if (!this.grid.isReady) this.grid.once('ready', () => requestAnimationFrame(() => this.service.app?.render())); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/ng/modules/pixi/systems/rendering.system.ts: -------------------------------------------------------------------------------- 1 | import { Collection, System } from '@trixt0r/ecs'; 2 | import { PixiRendererService } from '../services/renderer.service'; 3 | import { SceneEntity } from 'common/scene'; 4 | 5 | export class PixiRenderingSystem extends System { 6 | constructor(private service: PixiRendererService, priority?: number) { 7 | super(priority); 8 | } 9 | 10 | /** 11 | * @inheritdoc 12 | */ 13 | process(): void { 14 | (this.engine?.entities as Collection).forEach((entity: SceneEntity) => { 15 | const container = this.service.getContainer(entity.id); 16 | if (!container) return; 17 | container.visible = entity.components.getValue('visible', 'bool', true) as boolean; 18 | if (!entity.components.byId('transform-off')) 19 | container.zIndex = entity.components.getValue('index', 'index', 1) as number; 20 | }); 21 | this.service.diagnostics.startRenderingTime = performance.now(); 22 | this.service.renderer?.render(this.service.scene); 23 | this.service.diagnostics.endRenderingTime = performance.now(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/ng/modules/pixi/systems/transformation.system.ts: -------------------------------------------------------------------------------- 1 | import { AbstractEntitySystem } from '@trixt0r/ecs'; 2 | import { PixiRendererService } from '../services/renderer.service'; 3 | import { SceneEntity, PointSceneComponent, RangeSceneComponent } from 'common/scene'; 4 | import { SizeSceneComponent } from 'common/scene/component/size'; 5 | 6 | export class PixiTransformationSystem extends AbstractEntitySystem { 7 | 8 | constructor(private service: PixiRendererService, priority?: number) { 9 | super(priority, [ { id: 'transformation' } ], [ { id: 'transform-off' } ]); 10 | } 11 | 12 | /** 13 | * @inheritdoc 14 | */ 15 | processEntity(entity: SceneEntity) { 16 | const container = this.service.getContainer(entity.id); 17 | if (!container) return; 18 | const position = entity.components.byId('transformation.position') as PointSceneComponent; 19 | const scale = entity.components.byId('transformation.scale') as PointSceneComponent; 20 | const skew = entity.components.byId('transformation.skew') as PointSceneComponent; 21 | const pivot = entity.components.byId('transformation.pivot') as PointSceneComponent; 22 | const rotation = entity.components.byId('transformation.rotation') as RangeSceneComponent; 23 | 24 | if (position) container.position.copyFrom(position); 25 | if (scale) container.scale.copyFrom(scale); 26 | if (skew) container.skew.copyFrom(skew); 27 | if (pivot) container.pivot.copyFrom(pivot); 28 | if (rotation) container.rotation = rotation.value; 29 | 30 | const size = entity.components.byId('transformation.size') as SizeSceneComponent; 31 | if (size) { 32 | size.width = container.width; 33 | size.height = container.height; 34 | size.localWidth = container.getLocalBounds().width; 35 | size.localHeight = container.getLocalBounds().height; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/ng/modules/pixi/utils/index.ts: -------------------------------------------------------------------------------- 1 | export { Camera } from './camera'; 2 | export { Grid } from './grid'; 3 | export { transformTo } from './transform.utils'; 4 | export { getBoundingRect, getCornerPoints } from './bounds.utils'; 5 | -------------------------------------------------------------------------------- /src/ng/modules/pixi/utils/transform.utils.ts: -------------------------------------------------------------------------------- 1 | import { DisplayObject, Matrix, Point } from 'pixi.js'; 2 | 3 | const tmpPivot = new Point(); 4 | const invertedParentTransform = new Matrix(); 5 | 6 | /** 7 | * Transforms the given object with its world transform 8 | * to the coordinate space of the given parent object. 9 | * 10 | * Note: Make sure, that the object is already a child of the given parent. 11 | * 12 | * @param object The object to transform. 13 | * @param parent The parent object into which to transform the values. 14 | */ 15 | export function transformTo(object: DisplayObject, parent: DisplayObject): void { 16 | tmpPivot.copyFrom(object.pivot); 17 | object.pivot.set(0, 0); 18 | const mat = invertedParentTransform.copyFrom(parent.worldTransform).invert().append(object.worldTransform); 19 | object.transform.setFromMatrix(mat); 20 | // Correct the position, since pivots are not respected when decomposing the transformation matrix 21 | parent.toLocal(tmpPivot, object, object.position); 22 | object.transform.updateLocalTransform(); 23 | object.pivot.copyFrom(tmpPivot); 24 | } 25 | -------------------------------------------------------------------------------- /src/ng/modules/plugin/plugin.interface.ts: -------------------------------------------------------------------------------- 1 | import { YamePlugin } from 'common/plugin'; 2 | import { Type, ModuleWithProviders } from '@angular/core'; 3 | 4 | export interface NgYamePlugin extends YamePlugin { 5 | 6 | /** 7 | * A list of angular modules this plugin provides. 8 | * This attribute will be read once, before initializing the main module 9 | * and only if the plugin has been initialized successfully. 10 | */ 11 | ngModules?: Array | ModuleWithProviders | any[]>; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/ng/modules/plugin/plugin.manager.ts: -------------------------------------------------------------------------------- 1 | import { CommonPluginManager } from 'common/plugin.manager'; 2 | import { ipcRenderer, IpcRendererEvent } from 'electron'; 3 | import { IYameEnvironment } from 'common/interfaces/environment'; 4 | import { Environment } from '../../environment'; 5 | import { YamePlugin } from 'common/plugin'; 6 | import { uniqueId } from 'lodash'; 7 | 8 | /** 9 | * Plugin manager for the angular side of the editor. 10 | */ 11 | export class PluginManager extends CommonPluginManager { 12 | /** 13 | * @inheritdoc 14 | */ 15 | protected environment: IYameEnvironment = Environment; 16 | 17 | /** 18 | * @inheritdoc 19 | */ 20 | protected type = 'ng'; 21 | 22 | protected persistConfig(plugin: YamePlugin): Promise { 23 | return Promise.resolve(); 24 | } 25 | 26 | /** 27 | * @inheritdoc 28 | */ 29 | require(path: string) { 30 | return (global).require(path); 31 | } 32 | 33 | /** 34 | * @inheritdoc 35 | */ 36 | getFiles(): Promise { 37 | return new Promise((resolve, reject) => { 38 | const id = uniqueId('plugins-'); 39 | let resolver: (event: IpcRendererEvent, files: string[]) => void, rejecter: (event: IpcRendererEvent, message: string) => void; 40 | resolver = (event: IpcRendererEvent, files: string[]) => { 41 | ipcRenderer.removeListener(`plugins:files:${id}:error`, rejecter); 42 | resolve(files); 43 | }; 44 | rejecter = (event: IpcRendererEvent, message: string) => { 45 | ipcRenderer.removeListener(`plugins:files:${id}`, resolver); 46 | reject(new Error(message)); 47 | }; 48 | ipcRenderer.once(`plugins:files:${id}`, resolver); 49 | ipcRenderer.once(`plugins:files:${id}:error`, rejecter); 50 | ipcRenderer.send(`plugins:files`, id); 51 | }); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/ng/modules/plugin/plugin.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { Pubsub } from 'common/pubsub'; 3 | import { Environment } from '../../environment'; 4 | import { NgYamePlugin } from './plugin.interface'; 5 | 6 | let pluginModules: any[] = []; 7 | 8 | Environment.plugins.forEach((plugin: NgYamePlugin) => { 9 | if (!Array.isArray(plugin.ngModules)) return; 10 | pluginModules = pluginModules.concat(plugin.ngModules); 11 | }); 12 | 13 | Pubsub.emit('plugin:NgModules', pluginModules); 14 | 15 | @NgModule({ 16 | declarations: [], 17 | imports: pluginModules, 18 | exports: pluginModules, 19 | }) 20 | export class PluginModule {} 21 | -------------------------------------------------------------------------------- /src/ng/modules/preferences/components/menu/menu.component.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /src/ng/modules/preferences/components/menu/menu.component.scss: -------------------------------------------------------------------------------- 1 | button.btn-trigger { 2 | position: absolute; 3 | left: 0; 4 | top: 0; 5 | width: 100%; 6 | height: 100%; 7 | background: none; 8 | border: none; 9 | color: inherit; 10 | cursor: pointer; 11 | } 12 | 13 | .tool-icon { 14 | position: absolute; 15 | left: 50%; 16 | top: 50%; 17 | transform: translate(-50%, -50%); 18 | font-size: 18px; 19 | } 20 | -------------------------------------------------------------------------------- /src/ng/modules/preferences/components/menu/menu.component.ts: -------------------------------------------------------------------------------- 1 | import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, OnDestroy } from '@angular/core'; 2 | import { Select, Store } from '@ngxs/store'; 3 | import { IToolComponent, Tool } from 'ng/modules/toolbar/tool'; 4 | import { Observable, Subject } from 'rxjs'; 5 | import { takeUntil } from 'rxjs/operators'; 6 | import { IPreferenceOption } from '../../interfaces/preference-option.interface'; 7 | import { PreferencesState } from '../../states/preferences.state'; 8 | 9 | @Component({ 10 | templateUrl: 'menu.component.html', 11 | styleUrls: ['menu.component.scss'], 12 | changeDetection: ChangeDetectionStrategy.OnPush, 13 | standalone: false 14 | }) 15 | export class PreferencesMenuComponent implements IToolComponent, OnDestroy { 16 | /** 17 | * @inheritdoc 18 | */ 19 | tool!: Tool; 20 | 21 | /** 22 | * Selector for getting the current preference menu options. 23 | */ 24 | @Select(PreferencesState.options) options$!: Observable; 25 | 26 | /** 27 | * The list of preference options to render. 28 | */ 29 | options: IPreferenceOption[] = []; 30 | 31 | /** 32 | * Triggered as soon as this component gets destroyed. 33 | */ 34 | protected destroy$ = new Subject(); 35 | 36 | constructor(protected cdr: ChangeDetectorRef, protected store: Store) { 37 | 38 | this.options$.pipe(takeUntil(this.destroy$)).subscribe((options) => { 39 | this.options = options; 40 | this.cdr.markForCheck(); 41 | }); 42 | } 43 | 44 | /** 45 | * Handles an option click. 46 | * 47 | * @param option The clicked option. 48 | */ 49 | onClick(option: IPreferenceOption): void { 50 | if (!option.action) return console.warn('[Preferences] Option has no action', option); 51 | this.store.dispatch(new option.action()); 52 | } 53 | 54 | /** 55 | * @inheritdoc 56 | */ 57 | ngOnDestroy(): void { 58 | this.destroy$.next(); 59 | this.destroy$.complete(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/ng/modules/preferences/components/settings/settings.component.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
    4 |
  • 10 | 11 | {{ section.label | translate }} 12 |
  • 13 |
14 |
15 |
16 | 17 |
18 | 19 |
20 | 21 | 22 | {{ option.label | translate }} 23 |
    24 | 25 | 26 | 27 |
28 |
29 |
30 |
31 |
32 |
33 | -------------------------------------------------------------------------------- /src/ng/modules/preferences/components/settings/settings.component.scss: -------------------------------------------------------------------------------- 1 | yame-settings { 2 | .sections { 3 | float: left; 4 | display: block; 5 | box-sizing: border-box; 6 | padding-bottom: 0; 7 | width: 25%; 8 | height: 100%; 9 | overflow: hidden; 10 | margin-right: 16px; 11 | background: rgba(0, 0, 0, 0.25) !important; 12 | 13 | .list-wrapper { 14 | overflow: auto; 15 | padding-bottom: 8px; 16 | nz-list { 17 | padding-top: 0; 18 | nz-list-item { 19 | nz-icon { 20 | transition: all ease-in 100ms; 21 | } 22 | } 23 | } 24 | } 25 | 26 | .ant-card-body { 27 | padding: 0; 28 | } 29 | } 30 | .content { 31 | float: right; 32 | box-sizing: border-box; 33 | width: calc(75% - 16px); 34 | max-height: 100%; 35 | overflow: auto; 36 | padding: 16px; 37 | padding-top: 0; 38 | section { 39 | &:last-child { 40 | min-height: calc(80vh - 60px); // margin bottom of card + height of header 41 | } 42 | .ant-list-item-action li { 43 | width: 100%; 44 | overflow: hidden; 45 | & > * { 46 | display: inline-block; 47 | width: 100%; 48 | text-align: right; 49 | } 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/ng/modules/preferences/components/settings/types/checkbox/checkbox.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/ng/modules/preferences/components/settings/types/checkbox/checkbox.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component } from '@angular/core'; 2 | import { SettingsAbstractComponent } from '../abstract.component'; 3 | 4 | @Component({ 5 | templateUrl: 'checkbox.component.html', 6 | changeDetection: ChangeDetectionStrategy.OnPush, 7 | standalone: false 8 | }) 9 | export class SettingsCheckboxComponent extends SettingsAbstractComponent {} 10 | -------------------------------------------------------------------------------- /src/ng/modules/preferences/components/settings/types/input/input.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/ng/modules/preferences/components/settings/types/input/input.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component } from '@angular/core'; 2 | import { SettingsAbstractComponent } from '../abstract.component'; 3 | 4 | @Component({ 5 | templateUrl: 'input.component.html', 6 | changeDetection: ChangeDetectionStrategy.OnPush, 7 | standalone: false 8 | }) 9 | export class SettingsInputComponent extends SettingsAbstractComponent { 10 | 11 | } -------------------------------------------------------------------------------- /src/ng/modules/preferences/components/settings/types/number/number.component.html: -------------------------------------------------------------------------------- 1 | 7 | -------------------------------------------------------------------------------- /src/ng/modules/preferences/components/settings/types/number/number.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component } from '@angular/core'; 2 | import { SettingsAbstractComponent } from '../abstract.component'; 3 | 4 | @Component({ 5 | templateUrl: 'number.component.html', 6 | changeDetection: ChangeDetectionStrategy.OnPush, 7 | standalone: false 8 | }) 9 | export class SettingsNumberComponent extends SettingsAbstractComponent { 10 | } -------------------------------------------------------------------------------- /src/ng/modules/preferences/components/settings/types/selection/selection.component.html: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | 14 | {{ 15 | (option.label ? option.label : option.value ? option.value : option) | translate 16 | }} 17 | 18 | 19 | 20 | 21 | 22 | {{ 23 | (currentOption?.label ? currentOption?.label : currentOption?.value ? currentOption?.value : currentOption) 24 | | translate 25 | }} 26 | 27 | -------------------------------------------------------------------------------- /src/ng/modules/preferences/components/settings/types/selection/selection.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component } from '@angular/core'; 2 | import { SettingsAbstractComponent } from '../abstract.component'; 3 | 4 | @Component({ 5 | templateUrl: 'selection.component.html', 6 | styleUrls: ['../../../../../../styles/utils.scss'], 7 | changeDetection: ChangeDetectionStrategy.OnPush, 8 | standalone: false 9 | }) 10 | export class SettingsSelectionComponent extends SettingsAbstractComponent { 11 | 12 | public currentOption?: string & { id?: string; value: string; label?: string; icon?: string; }; 13 | 14 | protected updateCurrentOptions(val: string) { 15 | const options = this.option.componentSettings?.options; 16 | 17 | if (Array.isArray(options)) 18 | this.currentOption = options.find(it => it === val || it.value === val); 19 | } 20 | 21 | /** 22 | * @inheritdoc 23 | */ 24 | ngAfterViewInit() { 25 | this.beforeChangeDetection$.subscribe(() => this.updateCurrentOptions(this.value)); 26 | super.ngAfterViewInit(); 27 | } 28 | } -------------------------------------------------------------------------------- /src/ng/modules/preferences/components/settings/types/slider/slider.component.html: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /src/ng/modules/preferences/components/settings/types/slider/slider.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component } from '@angular/core'; 2 | import { NzMarks } from 'ng-zorro-antd/slider'; 3 | import { SettingsAbstractComponent } from '../abstract.component'; 4 | 5 | @Component({ 6 | templateUrl: 'slider.component.html', 7 | styleUrls: ['../../../../../../styles/utils.scss'], 8 | changeDetection: ChangeDetectionStrategy.OnPush, 9 | standalone: false 10 | }) 11 | export class SettingsSliderComponent extends SettingsAbstractComponent { 12 | /** 13 | * The ticks value. 14 | */ 15 | get ticks(): NzMarks | null { 16 | const settings = this.option.componentSettings; 17 | if (!settings?.ticks) return null; 18 | if (typeof settings?.ticks === 'number') { 19 | const min = (settings.min as number) ?? 0; 20 | const max = (settings.max as number) ?? 100; 21 | const marks: NzMarks = {}; 22 | for (let i = min; i <= max; i += settings?.ticks) marks[i] = String(i); 23 | return marks; 24 | } 25 | return settings?.ticks as NzMarks | null; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/ng/modules/preferences/components/settings/types/toggle/toggle.component.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/ng/modules/preferences/components/settings/types/toggle/toggle.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component } from '@angular/core'; 2 | import { SettingsAbstractComponent } from '../abstract.component'; 3 | 4 | @Component({ 5 | templateUrl: 'toggle.component.html', 6 | changeDetection: ChangeDetectionStrategy.OnPush, 7 | standalone: false 8 | }) 9 | export class SettingsToggleComponent extends SettingsAbstractComponent {} 10 | -------------------------------------------------------------------------------- /src/ng/modules/preferences/directives/settings-option.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, Input, OnChanges, SimpleChanges, ViewContainerRef } from '@angular/core'; 2 | import { ISettingsOption } from '../interfaces/settings-option.interface'; 3 | 4 | @Directive({ 5 | selector: '[yameSettingOption]', 6 | standalone: false 7 | }) 8 | export class SettingsOptionDirective implements OnChanges { 9 | @Input('yameSettingOption') settingsOption?: ISettingsOption; 10 | 11 | constructor(protected vcr: ViewContainerRef) {} 12 | 13 | /** 14 | * @inheritdoc 15 | */ 16 | ngOnChanges(changes: SimpleChanges): void { 17 | if (!changes.settingsOption) return; 18 | this.vcr.clear(); 19 | if (!this.settingsOption) return; 20 | const comp = this.vcr.createComponent(this.settingsOption.component); 21 | comp.instance.option = this.settingsOption; 22 | comp.changeDetectorRef.markForCheck(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/ng/modules/preferences/interfaces/preference-option.interface.ts: -------------------------------------------------------------------------------- 1 | import { ISortable } from 'common/interfaces/sortable.interface'; 2 | 3 | export interface IPreferenceOption extends Partial { 4 | 5 | /** 6 | * The id of the preference. 7 | */ 8 | id: string; 9 | 10 | /** 11 | * The label of the preference. 12 | */ 13 | label: string; 14 | 15 | /** 16 | * The action to dispatch. 17 | */ 18 | action: T; 19 | 20 | /** 21 | * The icon of the preference, if any. 22 | */ 23 | icon?: string; 24 | 25 | /** 26 | * The group to put the option in. Note, that groups will be separated by a horizontal line. 27 | */ 28 | group?: number; 29 | } -------------------------------------------------------------------------------- /src/ng/modules/preferences/interfaces/settings-option.interface.ts: -------------------------------------------------------------------------------- 1 | import { Type } from '@angular/core'; 2 | import { ISortable } from 'common/interfaces/sortable.interface'; 3 | 4 | export interface IComponentSettings { 5 | [key: string]: unknown 6 | } 7 | 8 | /** 9 | * Settings option definition in the ui. 10 | */ 11 | export interface ISettingsOption extends Partial { 12 | 13 | /** 14 | * The setting id, this option manipulates. 15 | */ 16 | id: string; 17 | 18 | /** 19 | * The section this options lies in. 20 | */ 21 | section: string; 22 | 23 | /** 24 | * The label of the option- 25 | */ 26 | label: string; 27 | 28 | /** 29 | * The component to display. 30 | */ 31 | component: Type; 32 | 33 | /** 34 | * Any settings for the component visualization. 35 | */ 36 | componentSettings?: T; 37 | } 38 | 39 | export interface ISettingsOptionComponent { 40 | option: ISettingsOption 41 | } -------------------------------------------------------------------------------- /src/ng/modules/preferences/interfaces/settings-section.interface.ts: -------------------------------------------------------------------------------- 1 | import { ISortable } from 'common/interfaces/sortable.interface'; 2 | 3 | /** 4 | * Settings section definition in the ui. 5 | */ 6 | export interface ISettingsSection extends Partial { 7 | /** 8 | * The id of the section. 9 | */ 10 | id: string; 11 | 12 | /** 13 | * The label of the section. 14 | */ 15 | label: string; 16 | 17 | /** 18 | * The icon of the section. 19 | */ 20 | icon?: string; 21 | } -------------------------------------------------------------------------------- /src/ng/modules/preferences/preferences.tool.ts: -------------------------------------------------------------------------------- 1 | import { Tool, ToolType } from '../toolbar/tool'; 2 | import { PreferencesMenuComponent } from './components/menu/menu.component'; 3 | 4 | export class PreferencesTool extends Tool { 5 | /** 6 | * @inheritdoc 7 | */ 8 | readonly type = ToolType.CLICK; 9 | 10 | /** 11 | * @inheritdoc 12 | */ 13 | readonly component = PreferencesMenuComponent; 14 | 15 | constructor() { 16 | super('preferences', 'setting', 0); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/ng/modules/preferences/states/actions/preferences.action.ts: -------------------------------------------------------------------------------- 1 | import { IPreferenceOption } from '../../interfaces/preference-option.interface'; 2 | 3 | export class AddPreferenceOption { 4 | static type = '[Preferences] Add preference option'; 5 | constructor(public option: IPreferenceOption | IPreferenceOption[]) { } 6 | } 7 | 8 | export class RemovePreferenceOption { 9 | static type = '[Preferences] Remove preference option'; 10 | constructor(public option: string | string[]) { } 11 | } 12 | 13 | export class OpenSettings { 14 | static type = '[Preferences] Open settings'; 15 | } 16 | 17 | export class OpenKeyboardShortcuts { 18 | static type = '[Preferences] Open keyboard shortcuts'; 19 | } -------------------------------------------------------------------------------- /src/ng/modules/preferences/states/actions/settings.action.ts: -------------------------------------------------------------------------------- 1 | import { ISettingsOption } from '../../interfaces/settings-option.interface'; 2 | import { ISettingsSection } from '../../interfaces/settings-section.interface'; 3 | 4 | export class AddSettingsSection { 5 | static type = '[Settings] Add settings section'; 6 | constructor(public section: ISettingsSection | ISettingsSection[]) { } 7 | } 8 | 9 | export class RemoveSettingsSection { 10 | static type = '[Settings] Remove settings section'; 11 | constructor(public section: string | string[]) { } 12 | } 13 | 14 | export class SelectSettingsSection { 15 | static type = '[Settings] Select settings section'; 16 | constructor(public section: string) { } 17 | } 18 | 19 | export class AddSettingsOption { 20 | static type = '[Settings] Add settings option'; 21 | constructor(public option: ISettingsOption | ISettingsOption[]) { } 22 | } 23 | 24 | export class RemoveSettingsOption { 25 | static type = '[Settings] Remove settings option'; 26 | constructor(public option: string | string[]) { } 27 | } 28 | 29 | export class UpdateSettingsValue { 30 | static type = '[Settings] Update settings value'; 31 | constructor(public id: string, public value: T) { } 32 | } 33 | 34 | export class InitDefaultSettingsValue { 35 | static type = '[Settings] Init default settings value'; 36 | constructor(public id: string, public value: T) { } 37 | } -------------------------------------------------------------------------------- /src/ng/modules/scene/components/scene/scene.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | -------------------------------------------------------------------------------- /src/ng/modules/scene/components/scene/scene.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | display: block; 3 | position: absolute; 4 | top: 0; 5 | width: 100%; 6 | height: 100%; 7 | user-select: none; 8 | background: transparent; 9 | } 10 | 11 | .wrapper { 12 | width: 100%; 13 | height: 100%; 14 | position: relative; 15 | cursor: auto !important; 16 | } 17 | -------------------------------------------------------------------------------- /src/ng/modules/scene/decorators/index.ts: -------------------------------------------------------------------------------- 1 | export * from './component.decorator'; 2 | export * from './converter.decorator'; -------------------------------------------------------------------------------- /src/ng/modules/scene/directives/renderer.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, ViewContainerRef, ComponentFactoryResolver, AfterViewInit, Inject, Type } from '@angular/core'; 2 | import { ISceneRendererComponent, YAME_RENDERER_COMPONENT } from '../services/scene.service'; 3 | 4 | @Directive({ 5 | selector: '[yameSceneRendererComponent]', 6 | standalone: false 7 | }) 8 | export class SceneRendererComponentDirective implements AfterViewInit { 9 | constructor( 10 | @Inject(YAME_RENDERER_COMPONENT) protected readonly rendererComponent: Type>, 11 | protected viewContainerRef: ViewContainerRef 12 | ) {} 13 | 14 | /** 15 | * @inheritdoc 16 | */ 17 | ngAfterViewInit(): void { 18 | const compType = this.rendererComponent; 19 | if (!compType) return; 20 | this.viewContainerRef.clear(); 21 | const componentRef = this.viewContainerRef.createComponent(compType); 22 | componentRef.changeDetectorRef.detectChanges(); 23 | componentRef.changeDetectorRef.detach(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/ng/modules/scene/exceptions/converter.service.exception.ts: -------------------------------------------------------------------------------- 1 | import { SceneException } from './scene.exception'; 2 | 3 | export class SceneConverterException extends SceneException { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/ng/modules/scene/exceptions/scene.exception.ts: -------------------------------------------------------------------------------- 1 | import { Exception } from 'common/exception'; 2 | 3 | export class SceneException extends Exception { } 4 | -------------------------------------------------------------------------------- /src/ng/modules/scene/exceptions/scene/entity-not-found.exception.ts: -------------------------------------------------------------------------------- 1 | import { SceneException } from '../scene.exception'; 2 | import { SceneEntity } from 'common/scene'; 3 | 4 | 5 | export class EntityNotFoundException extends SceneException { 6 | constructor(message: string, entityOrId: string | SceneEntity) { 7 | super(`${message}: ${ entityOrId instanceof SceneEntity ? entityOrId.id : entityOrId }`); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/ng/modules/scene/forms/validators/component-id.validator.ts: -------------------------------------------------------------------------------- 1 | import { SceneEntity } from 'common/scene'; 2 | import { SceneComponentService } from '../../services/component.service'; 3 | import { ValidatorFn, AbstractControl } from '@angular/forms'; 4 | 5 | 6 | /** 7 | * Validator for validating component ids. 8 | * 9 | * @param initial The initial value. 10 | * @param entities The scene entities to check for. 11 | * @param components The scene component service instance. 12 | * @return A validator function for validating component ids. 13 | */ 14 | export function componentIdValidator( 15 | initial: string, 16 | entities: SceneEntity[], 17 | components: SceneComponentService 18 | ): ValidatorFn { 19 | return (control: AbstractControl): { idUsed?: any; idReserved?: any } | null => { 20 | if (control.value === initial) return null; 21 | const found = entities.find(it => components.isIdInUse(control.value, it.id)); 22 | if (found) return { idUsed: { value: control.value } }; 23 | if (components.isIdReserved(control.value)) return { idReserved: { value: control.value } }; 24 | return null; 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /src/ng/modules/scene/index.ts: -------------------------------------------------------------------------------- 1 | export * from './scene.module'; 2 | export * from './services/component.service'; 3 | export * from './services/converter.service'; 4 | export * from './services/engine.service'; 5 | export * from './services/scene.service'; 6 | export * from './components/scene/scene.component'; 7 | export * from './directives/renderer.directive'; 8 | export * from './states/history.state'; 9 | export * from './states/scene.state'; 10 | export * from './states/select.state'; 11 | export * from './states/actions/entity.action'; 12 | export * from './states/actions/history.action'; 13 | export * from './states/actions/select.action'; 14 | export * from './forms/validators/component-id.validator'; 15 | -------------------------------------------------------------------------------- /src/ng/modules/scene/scene.module.ts: -------------------------------------------------------------------------------- 1 | import { APP_INITIALIZER, Injector, NgModule } from '@angular/core'; 2 | import { SceneComponent } from './components/scene/scene.component'; 3 | import { SceneRendererComponentDirective } from './directives/renderer.directive'; 4 | import { NoopSceneRendererComponent } from './services/scene.service'; 5 | import { NgxsModule } from '@ngxs/store'; 6 | import { SceneState } from './states/scene.state'; 7 | import { SelectState } from './states/select.state'; 8 | import { SceneComponentService } from './services/component.service'; 9 | import { HistoryState } from './states/history.state'; 10 | import { ImageAssetService } from './services/image-asset.service'; 11 | import { decorateComponentIOInstances, decorateConverterInstances } from './decorators'; 12 | 13 | @NgModule({ 14 | imports: [NgxsModule.forFeature([SceneState, HistoryState, SelectState])], 15 | 16 | declarations: [SceneComponent, SceneRendererComponentDirective, NoopSceneRendererComponent], 17 | exports: [SceneComponent], 18 | providers: [ 19 | ImageAssetService, 20 | { 21 | provide: APP_INITIALIZER, 22 | useFactory: (components: SceneComponentService, injector: Injector) => () => { 23 | // Reserve specific component ids 24 | components.reserveId('transformation'); 25 | components.reserveId('transformation.position'); 26 | components.reserveId('transformation.rotation'); 27 | components.reserveId('transformation.scale'); 28 | components.reserveId('sprite'); 29 | components.reserveId('sprite.texture'); 30 | components.reserveId('sprite.color'); 31 | 32 | // Make sure decorated class instance are actually registered 33 | decorateConverterInstances(injector); 34 | decorateComponentIOInstances(injector); 35 | }, 36 | deps: [SceneComponentService, Injector], 37 | multi: true, 38 | }, 39 | ], 40 | }) 41 | export class SceneModule {} 42 | -------------------------------------------------------------------------------- /src/ng/modules/scene/services/image-asset.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Asset } from 'common/asset'; 3 | import { createAssetComponent, createColorComponent, createGroupComponent, SceneComponent } from 'common/scene'; 4 | import { Deserialize, Convert } from '../decorators'; 5 | 6 | /** 7 | * Service for providing image asset specific functionality, 8 | * like converting assets and (de-)serializing components. 9 | */ 10 | @Injectable({ providedIn: 'root' }) 11 | export class ImageAssetService { 12 | /** 13 | * A list of asset types which are allowed to be classified as images. 14 | */ 15 | static readonly ALLOWED_TYPES: readonly string[] = ['png', 'jpg', 'jpeg', 'gif', 'svg']; 16 | 17 | /** 18 | * Converts the given asset to a list of components representing an image. 19 | * 20 | * @param asset The asset to convert. 21 | */ 22 | @Convert(...ImageAssetService.ALLOWED_TYPES) 23 | async convert(asset: Asset): Promise { 24 | const color = createColorComponent('sprite.color', 'sprite'); 25 | color.red = 255; 26 | color.green = 255; 27 | color.blue = 255; 28 | color.alpha = 1; 29 | const assetComp = createAssetComponent('sprite.texture', asset.id, 'sprite'); 30 | assetComp.allowedTypes = [...ImageAssetService.ALLOWED_TYPES]; 31 | const sprite = createGroupComponent('sprite', ['sprite.texture', 'sprite.color']); 32 | sprite.allowedMemberTypes = []; 33 | sprite.allowedMemberItems = []; 34 | return [sprite, assetComp, color]; 35 | } 36 | 37 | /** 38 | * Returns component data for components with the `sprite.texture` id. 39 | */ 40 | @Deserialize({ id: 'sprite.texture' }) 41 | async deserialize(): Promise> { 42 | return { allowedTypes: [...ImageAssetService.ALLOWED_TYPES] }; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/ng/modules/scene/states/actions/history.action.ts: -------------------------------------------------------------------------------- 1 | export class PushHistory { 2 | static readonly type = '[History] Push'; 3 | constructor(public readonly actions: {}[], public readonly last: {}[], public readonly override: boolean = false) { } 4 | } 5 | 6 | export class UndoHistory { 7 | static readonly type = '[History] Undo'; 8 | constructor() { } 9 | } 10 | 11 | export class RedoHistory { 12 | static readonly type = '[History] Redo'; 13 | constructor() { } 14 | } 15 | 16 | export class ResetHistory { 17 | static readonly type = '[History] Reset'; 18 | constructor() { } 19 | } 20 | 21 | export type HistoryActions = PushHistory | UndoHistory | RedoHistory | ResetHistory; 22 | -------------------------------------------------------------------------------- /src/ng/modules/scene/states/actions/scene.action.ts: -------------------------------------------------------------------------------- 1 | export class LoadScene { 2 | static readonly type = '[Scene] Load'; 3 | constructor(public ctx = { }) {} 4 | } 5 | 6 | export class SaveScene { 7 | static readonly type = '[Scene] Save'; 8 | constructor(public ctx = { }) {} 9 | } 10 | 11 | export class ResetScene { 12 | static readonly type = '[Scene] Reset'; 13 | constructor() {} 14 | } 15 | 16 | export type SceneAction = LoadScene | ResetScene; -------------------------------------------------------------------------------- /src/ng/modules/scene/states/actions/select.action.ts: -------------------------------------------------------------------------------- 1 | import { SceneComponent, SceneEntity } from 'common/scene'; 2 | 3 | export class Select { 4 | static readonly type = '[Selection] Select'; 5 | constructor( 6 | public entities: string[], 7 | public components: SceneComponent[] = [], 8 | public persist: boolean = true, 9 | public unselectCurrent: boolean = false 10 | ) {} 11 | } 12 | 13 | export class UpdateComponents { 14 | static readonly type = '[Selection] UpdateComponents'; 15 | constructor(public components: SceneComponent[] = [], public patch = false) {} 16 | } 17 | 18 | export class Unselect { 19 | static readonly type = '[Selection] Unselect'; 20 | constructor(public entities?: string[], public components: SceneComponent[] = [], public persist: boolean = true) {} 21 | } 22 | 23 | export class Isolate { 24 | static readonly type = '[Selection] Isolate'; 25 | constructor(public entity: SceneEntity | null, public persist: boolean = true) {} 26 | } 27 | 28 | export class Input { 29 | static readonly type = '[Selection] Input'; 30 | constructor(public actions: {}[], public source?: unknown) {} 31 | } 32 | 33 | export type SelectActions = Select | Unselect | UpdateComponents | Isolate | Input; 34 | -------------------------------------------------------------------------------- /src/ng/modules/sidebar/components/hierarchy/add/add.component.html: -------------------------------------------------------------------------------- 1 | 12 | 13 | 14 |
    15 | 16 |
17 |
18 | -------------------------------------------------------------------------------- /src/ng/modules/sidebar/components/hierarchy/add/add.component.scss: -------------------------------------------------------------------------------- 1 | @import '../../../../../variables.scss'; 2 | 3 | :host.embedded { 4 | button { 5 | height: 100%; 6 | line-height: inherit; 7 | width: $tree-inline-btn-size; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/ng/modules/sidebar/components/hierarchy/hierarchy.component.html: -------------------------------------------------------------------------------- 1 |
2 |

{{ 'sidebar.hierarchy.title' | translate }}

3 |
4 | 5 |
6 |
7 | 8 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | {{ node.title }} 28 | 29 | 30 | 31 | 32 | 35 | 38 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/ng/modules/sidebar/components/hierarchy/hierarchy.component.scss: -------------------------------------------------------------------------------- 1 | @import '../../../../variables.scss'; 2 | 3 | $tree-node-toggle-size: $tree-indent-size; 4 | $tree-node-icon-size: $tree-indent-size + 8px; 5 | $icon-width: 24px; 6 | 7 | :host { 8 | display: block; 9 | } 10 | 11 | yame-hierarchy { 12 | .header { 13 | display: inline-block; 14 | width: calc(100% - #{$resizer-size}); 15 | h4 { 16 | float: left; 17 | margin: 0 0 4px 0; 18 | padding: 0 12px 0 0; 19 | } 20 | .btn-group { 21 | float: right; 22 | } 23 | } 24 | 25 | .isolated { 26 | position: absolute; 27 | left: -100%; 28 | bottom: 0; 29 | width: 200%; 30 | height: 100%; 31 | z-index: -1; 32 | background-color: #095cb5; 33 | } 34 | 35 | .inline-actions-wrapper { 36 | min-width: unset; 37 | .inline-actions { 38 | float: right; 39 | transform: translateX(100%); 40 | opacity: 0; 41 | transition: all ease-in-out 100ms; 42 | } 43 | } 44 | 45 | nz-tree { 46 | overflow: auto; 47 | } 48 | 49 | nz-tree-node.ant-tree-treenode { 50 | padding: 0 !important; 51 | margin-bottom: 4px; 52 | overflow: hidden; 53 | user-select: none; 54 | &::after { 55 | display: none; 56 | } 57 | transition: all 0.3s, border 0s, line-height 0s, box-shadow 0s; 58 | &:hover { 59 | background-color: rgba(255, 255, 255, 0.08); 60 | } 61 | &.ant-tree-treenode-selected { 62 | background-color: #11263c; 63 | } 64 | &.ant-tree-treenode-selected, 65 | &:hover { 66 | .inline-actions { 67 | transform: none; 68 | opacity: inherit; 69 | } 70 | } 71 | .ant-tree-node-content-wrapper:hover { 72 | background: unset; 73 | } 74 | } 75 | .ant-tree-node-content-wrapper { 76 | min-width: 0; 77 | } 78 | .icon-wrapper { 79 | width: $icon-width; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/ng/modules/sidebar/components/selection/add/add.component.html: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 |
    8 | 9 |
10 |
11 | -------------------------------------------------------------------------------- /src/ng/modules/sidebar/components/selection/add/add.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trixt0r/yame/5910bc15431ba172db039aa914fc3120882ecd77/src/ng/modules/sidebar/components/selection/add/add.component.scss -------------------------------------------------------------------------------- /src/ng/modules/sidebar/components/selection/selection.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/ng/modules/sidebar/components/selection/selection.component.scss: -------------------------------------------------------------------------------- 1 | @import '../../../../variables.scss'; 2 | 3 | :host { 4 | width: 100%; 5 | bottom: 0; 6 | position: absolute; 7 | display: block; 8 | left: $resizer-size; 9 | width: calc(100% - 10px); 10 | overflow: hidden; 11 | border-radius: 4px; 12 | background: $main-background-color; 13 | margin-bottom: 5px; 14 | } 15 | 16 | ng-component { 17 | width: calc(50% - 20px); 18 | } 19 | 20 | .resizer { 21 | width: 100%; 22 | height: $resizer-size; 23 | cursor: ns-resize; 24 | margin-top: -1px; 25 | } 26 | 27 | .flex.wrap { 28 | max-height: 100%; 29 | overflow-x: hidden; 30 | overflow-y: auto; 31 | } 32 | -------------------------------------------------------------------------------- /src/ng/modules/sidebar/components/selection/types/color/color.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |
11 | 12 | 13 |
14 |
15 | 18 |
19 |
20 |
21 | 22 |
23 | -------------------------------------------------------------------------------- /src/ng/modules/sidebar/components/selection/types/color/color.component.scss: -------------------------------------------------------------------------------- 1 | yame-component-type-color { 2 | display: block; 3 | 4 | sketch-picker { 5 | width: 100% !important; 6 | background: transparent !important; 7 | box-shadow: none !important; 8 | padding: 0 !important; 9 | 10 | .presentation input { 11 | border: 1px solid #434343 !important; 12 | color: #ffffffd9 !important; 13 | background: transparent !important; 14 | height: 28px !important; 15 | font-size: 14px !important; 16 | transition: border-color 0.3s; 17 | 18 | &:hover { 19 | border-color: #165996 !important; 20 | border-right-width: 1px !important; 21 | } 22 | } 23 | 24 | color-presets-component { 25 | border-top: 1px solid #434343 !important; 26 | } 27 | } 28 | 29 | .inline-block.header { 30 | width: calc(100% - 32px); 31 | } 32 | 33 | .preview-wrapper { 34 | position: relative !important; 35 | display: inline-block; 36 | transition: all ease-in-out 50ms; 37 | } 38 | 39 | .preview { 40 | position: absolute; 41 | left: 50%; 42 | top: 50%; 43 | width: 100%; 44 | height: 10px; 45 | transform: translate(-50%, -50%); 46 | } 47 | 48 | .preview.bg { 49 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMUlEQVQ4T2NkYGAQYcAP3uCTZhw1gGGYhAGBZIA/nYDCgBDAm9BGDWAAJyRCgLaBCAAgXwixzAS0pgAAAABJRU5ErkJggg==); 50 | background-position: left center; 51 | background-color: lightgray; 52 | } 53 | 54 | .ant-collapse-header { 55 | padding: 4px !important; 56 | & > div { 57 | display: inline-block; 58 | margin-right: 8px; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/ng/modules/sidebar/components/selection/types/group/group.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 8 |
9 |
10 | 17 | 18 |
19 | -------------------------------------------------------------------------------- /src/ng/modules/sidebar/components/selection/types/group/group.component.scss: -------------------------------------------------------------------------------- 1 | yame-component-type-group { 2 | .ant-collapse-header { 3 | padding: 4px !important; 4 | & > div { 5 | display: inline-block; 6 | margin-right: 8px; 7 | } 8 | } 9 | 10 | yame-type-label { 11 | width: calc(100% - 54px); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/ng/modules/sidebar/components/selection/types/inline.actions.scss: -------------------------------------------------------------------------------- 1 | .slider-container { 2 | &.editable, 3 | &.removable { 4 | width: calc(100% - 48px) !important; 5 | } 6 | 7 | &.editable.removable { 8 | width: calc(100% - 88px) !important; 9 | } 10 | 11 | &.slider-container.editable.removable { 12 | width: calc(100% - 96px) !important; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/ng/modules/sidebar/components/selection/types/input/input.component.html: -------------------------------------------------------------------------------- 1 | 2 | 10 | 13 | -------------------------------------------------------------------------------- /src/ng/modules/sidebar/components/selection/types/input/input.component.scss: -------------------------------------------------------------------------------- 1 | yame-component-type-string { 2 | display: block; 3 | width: 100%; 4 | position: relative; 5 | padding: 0 8px; 6 | margin-bottom: 4px; 7 | yame-type-label { 8 | width: calc(100% - 24px); 9 | } 10 | button[nz-button] { 11 | position: absolute; 12 | top: 0; 13 | right: 4px; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/ng/modules/sidebar/components/selection/types/input/input.component.ts: -------------------------------------------------------------------------------- 1 | import { AbstractTypeComponent } from '../abstract'; 2 | import { 3 | Component, 4 | ChangeDetectionStrategy, 5 | OnChanges, 6 | SimpleChanges, 7 | ViewEncapsulation, 8 | ChangeDetectorRef, 9 | } from '@angular/core'; 10 | import { SceneComponent, StringSceneComponent } from 'common/scene'; 11 | import { TranslateService } from '@ngx-translate/core'; 12 | 13 | @Component({ 14 | selector: 'yame-component-type-string', 15 | templateUrl: './input.component.html', 16 | styleUrls: ['../style.scss', './input.component.scss'], 17 | changeDetection: ChangeDetectionStrategy.OnPush, 18 | encapsulation: ViewEncapsulation.None, 19 | standalone: false 20 | }) 21 | export class InputTypeComponent 22 | extends AbstractTypeComponent 23 | implements OnChanges 24 | { 25 | static readonly type: string = 'string'; 26 | 27 | constructor(protected translate: TranslateService, protected cdr: ChangeDetectorRef) { 28 | super(translate); 29 | } 30 | 31 | /** 32 | * @inheritdoc 33 | */ 34 | onUpdate(event: InputEvent): void { 35 | if (!this.component) return super.onUpdate(event); 36 | const comp = this.component as unknown as StringSceneComponent; 37 | comp.string = this.reverse((event.currentTarget as HTMLInputElement)!.value) as string; 38 | delete this.component?.mixed; 39 | return super.onUpdate(event); 40 | } 41 | 42 | /** 43 | * @inheritdoc 44 | */ 45 | onExternalUpdate(): void { 46 | this.cdr.markForCheck(); 47 | } 48 | 49 | /** 50 | * @inheritdoc 51 | */ 52 | ngOnChanges(changes: SimpleChanges) { 53 | if (!changes.component) return; 54 | this.cdr.markForCheck(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/ng/modules/sidebar/components/selection/types/number/number.component.html: -------------------------------------------------------------------------------- 1 | 2 | 12 | 15 | -------------------------------------------------------------------------------- /src/ng/modules/sidebar/components/selection/types/number/number.component.scss: -------------------------------------------------------------------------------- 1 | yame-component-type-number { 2 | display: block; 3 | width: 100%; 4 | position: relative; 5 | padding: 0 8px; 6 | margin-bottom: 4px; 7 | yame-type-label { 8 | width: calc(100% - 24px); 9 | } 10 | button[nz-button] { 11 | position: absolute; 12 | top: 0; 13 | right: 4px; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/ng/modules/sidebar/components/selection/types/number/number.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | ChangeDetectionStrategy, 4 | ViewChild, 5 | OnChanges, 6 | SimpleChanges, 7 | ViewEncapsulation, 8 | ChangeDetectorRef, 9 | } from '@angular/core'; 10 | import { AbstractTypeComponent } from '../abstract'; 11 | import { NumberSceneComponent } from 'common/scene'; 12 | import { TranslateService } from '@ngx-translate/core'; 13 | 14 | @Component({ 15 | selector: 'yame-component-type-number', 16 | templateUrl: './number.component.html', 17 | styleUrls: ['../style.scss', '../inline.actions.scss', './number.component.scss'], 18 | changeDetection: ChangeDetectionStrategy.OnPush, 19 | encapsulation: ViewEncapsulation.None, 20 | standalone: false 21 | }) 22 | export class NumberTypeComponent extends AbstractTypeComponent implements OnChanges { 23 | static readonly type: string = 'number'; 24 | 25 | /** 26 | * The value for rounding values. 27 | */ 28 | decimal = 3; 29 | 30 | /** 31 | * THe current number value. 32 | */ 33 | get number(): number { 34 | return typeof this.component?.number === 'number' ? (this.transform(this.component.number) as number) : 0; 35 | } 36 | 37 | constructor(protected translate: TranslateService, protected cdr: ChangeDetectorRef) { 38 | super(translate); 39 | } 40 | 41 | /** 42 | * @inheritdoc 43 | */ 44 | onUpdate(event: unknown): void { 45 | if (!this.component) return; 46 | const val: string | number = ((event as InputEvent).currentTarget as HTMLInputElement)!.value; 47 | this.component.number = this.reverse(typeof val === 'string' ? parseFloat(val.replace(',', '.')) : val) as number; 48 | delete this.component.mixed; 49 | return super.onUpdate(event); 50 | } 51 | 52 | /** 53 | * @inheritdoc 54 | */ 55 | onExternalUpdate(): void { 56 | // if (this.input) this.input.stateChanges.next(); 57 | } 58 | 59 | /** 60 | * @inheritdoc 61 | */ 62 | ngOnChanges(changes: SimpleChanges) { 63 | if (!changes.component) return; 64 | this.cdr.markForCheck(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/ng/modules/sidebar/components/selection/types/point/point.component.html: -------------------------------------------------------------------------------- 1 | 2 | 9 | 20 | -------------------------------------------------------------------------------- /src/ng/modules/sidebar/components/selection/types/point/point.component.scss: -------------------------------------------------------------------------------- 1 | yame-component-type-point { 2 | position: relative; 3 | display: block; 4 | width: 100%; 5 | padding: 0 8px; 6 | margin-bottom: 4px; 7 | 8 | .remove { 9 | top: 0; 10 | right: 4px; 11 | } 12 | 13 | yame-type-label { 14 | width: calc(100% - 24px); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/ng/modules/sidebar/components/selection/types/range/range.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | -------------------------------------------------------------------------------- /src/ng/modules/sidebar/components/selection/types/range/range.component.scss: -------------------------------------------------------------------------------- 1 | yame-type-component-range { 2 | position: relative; 3 | display: block; 4 | width: 100%; 5 | padding: 0 8px; 6 | margin-bottom: 4px; 7 | 8 | .remove { 9 | top: 0; 10 | right: 4px; 11 | } 12 | 13 | yame-type-label { 14 | width: calc(100% - 24px); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/ng/modules/sidebar/components/selection/types/size/size.component.html: -------------------------------------------------------------------------------- 1 | 2 | 9 | 20 | -------------------------------------------------------------------------------- /src/ng/modules/sidebar/components/selection/types/size/size.component.scss: -------------------------------------------------------------------------------- 1 | yame-component-type-size { 2 | position: relative; 3 | display: block; 4 | width: 100%; 5 | padding: 0 8px; 6 | margin-bottom: 4px; 7 | 8 | .remove { 9 | top: 0; 10 | right: 4px; 11 | } 12 | 13 | yame-type-label { 14 | width: calc(100% - 24px); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/ng/modules/sidebar/components/selection/types/style.scss: -------------------------------------------------------------------------------- 1 | :host:not(.embedded) { 2 | .slider-container { 3 | box-sizing: border-box; 4 | padding-left: 8px; 5 | } 6 | } 7 | 8 | :host { 9 | width: 100%; 10 | display: block; 11 | } 12 | 13 | .wrapper { 14 | display: inline-block; 15 | } 16 | 17 | .wrapper, 18 | input, 19 | label, 20 | .slider-container { 21 | margin: 0 4px; 22 | &.full { 23 | width: 100%; 24 | } 25 | &.threequarter { 26 | width: 75%; 27 | } 28 | &.half { 29 | width: 50%; 30 | } 31 | &.quarter { 32 | width: 25%; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/ng/modules/sidebar/components/sidebar.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/ng/modules/sidebar/components/sidebar.component.scss: -------------------------------------------------------------------------------- 1 | @import '../../../variables.scss'; 2 | 3 | :host { 4 | display: block; 5 | position: absolute; 6 | right: 0; 7 | left: 75%; 8 | top: 0; 9 | // min-width: 400px; 10 | height: 100%; 11 | overflow-y: hidden; 12 | z-index: 999; 13 | box-shadow: rgba(0, 0, 0, 0.3) -3px 0px 5px -1px, 14 | rgba(0, 0, 0, 0.25) -6px 0px 10px 0px, 15 | rgba(0, 0, 0, 0.2) -1px 0px 18px 0px; 16 | } 17 | 18 | .resizer { 19 | float: left; 20 | width: $resizer-size; 21 | height: 100%; 22 | cursor: ew-resize; 23 | } 24 | -------------------------------------------------------------------------------- /src/ng/modules/sidebar/exceptions/service/entity-type.exception.ts: -------------------------------------------------------------------------------- 1 | import { SidebarException } from '../sidebar.exception'; 2 | 3 | export class EntityTypeServiceException extends SidebarException { } 4 | -------------------------------------------------------------------------------- /src/ng/modules/sidebar/exceptions/service/invalid-property-component.exception.ts: -------------------------------------------------------------------------------- 1 | import { SceneComponentsServiceException } from './scene-components.exception'; 2 | 3 | export class InvalidSceneTypeComponentException extends SceneComponentsServiceException { } 4 | -------------------------------------------------------------------------------- /src/ng/modules/sidebar/exceptions/service/scene-components.exception.ts: -------------------------------------------------------------------------------- 1 | import { SidebarException } from '../sidebar.exception'; 2 | 3 | export class SceneComponentsServiceException extends SidebarException { } 4 | -------------------------------------------------------------------------------- /src/ng/modules/sidebar/exceptions/sidebar.exception.ts: -------------------------------------------------------------------------------- 1 | import { Exception } from 'common/exception'; 2 | 3 | export class SidebarException extends Exception { } 4 | -------------------------------------------------------------------------------- /src/ng/modules/tileset/components/index.ts: -------------------------------------------------------------------------------- 1 | export { TilesetTabComponent } from './tileset-tab/tileset-tab.component'; 2 | export { TilesetCanvasComponent } from './tileset-canvas/tileset-canvas.component'; 3 | -------------------------------------------------------------------------------- /src/ng/modules/tileset/components/tileset-canvas/tileset-canvas.component.html: -------------------------------------------------------------------------------- 1 |
2 | -------------------------------------------------------------------------------- /src/ng/modules/tileset/components/tileset-canvas/tileset-canvas.component.scss: -------------------------------------------------------------------------------- 1 | yame-tileset-canvas { 2 | display: block; 3 | 4 | * { 5 | height: 100%; 6 | width: 100%; 7 | max-width: 100%; 8 | margin: 0; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/ng/modules/tileset/components/tileset-tab/tileset-tab.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 10 |
11 |
12 |
13 | 14 |
15 |
16 | Grid size 17 | 18 |
19 |
20 | Spacing 21 | 22 |
23 |
24 | Offset 25 | 26 |
27 |
28 | -------------------------------------------------------------------------------- /src/ng/modules/tileset/components/tileset-tab/tileset-tab.component.scss: -------------------------------------------------------------------------------- 1 | yame-tileset-tab { 2 | display: block; 3 | .preview { 4 | height: 200px; 5 | max-height: 50%; 6 | &.expanded { 7 | position: fixed; 8 | width: calc(40vw - 48px); 9 | left: 48px; 10 | height: 100vh; 11 | top: 0; 12 | z-index: 999999; 13 | &::before { 14 | content: ' '; 15 | position: fixed; 16 | top: 0; 17 | left: 40vw; 18 | width: calc(100vw - 40vw); 19 | height: 100vh; 20 | background: rgba(0, 0, 0, 0.25); 21 | pointer-events: none; 22 | } 23 | .image-wrapper { 24 | height: 100vh; 25 | } 26 | } 27 | } 28 | .expansion-cancel { 29 | display: none; 30 | } 31 | 32 | .preview.expanded + .expansion-cancel { 33 | display: block; 34 | position: fixed; 35 | left: 0; 36 | top: 0; 37 | width: 100vw; 38 | height: 100vh; 39 | z-index: 999998; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/ng/modules/tileset/index.ts: -------------------------------------------------------------------------------- 1 | export { TilesetModule } from './tileset.module'; 2 | -------------------------------------------------------------------------------- /src/ng/modules/tileset/interceptors/index.ts: -------------------------------------------------------------------------------- 1 | export * from './tile-brush.interceptor'; 2 | -------------------------------------------------------------------------------- /src/ng/modules/tileset/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export { ITileset } from './tileset.interface'; 2 | export { ITilesetSetting } from './tileset-settings.interface'; 3 | -------------------------------------------------------------------------------- /src/ng/modules/tileset/interfaces/tileset-settings.interface.ts: -------------------------------------------------------------------------------- 1 | import { IPoint } from 'common/math'; 2 | 3 | export interface ITilesetSetting { 4 | id: number; 5 | label: string; 6 | size: IPoint; 7 | spacing: IPoint; 8 | offset: IPoint; 9 | selections: IPoint[]; 10 | } 11 | -------------------------------------------------------------------------------- /src/ng/modules/tileset/interfaces/tileset.interface.ts: -------------------------------------------------------------------------------- 1 | import { Asset } from 'common/asset'; 2 | import { ITilesetSetting } from './tileset-settings.interface'; 3 | 4 | export interface ITileset { 5 | asset: Asset; 6 | settings: ITilesetSetting[]; 7 | width?: number; 8 | height?: number; 9 | } 10 | -------------------------------------------------------------------------------- /src/ng/modules/tileset/states/actions/index.ts: -------------------------------------------------------------------------------- 1 | export { SaveTilesetSettings } from './tileset.actions'; 2 | -------------------------------------------------------------------------------- /src/ng/modules/tileset/states/actions/tileset.actions.ts: -------------------------------------------------------------------------------- 1 | import { Asset } from 'common/asset'; 2 | import { ITilesetSetting } from '../../interfaces'; 3 | 4 | export class SaveTilesetSettings { 5 | static readonly type = '[Tileset] Save tileset settings'; 6 | constructor(public readonly asset: Asset, public readonly settings: Partial[]) {} 7 | } 8 | -------------------------------------------------------------------------------- /src/ng/modules/tileset/states/index.ts: -------------------------------------------------------------------------------- 1 | export * from './actions'; 2 | export { ITilesetState, TilesetState, DEFAULT_SETTINGS } from './tileset.state'; 3 | -------------------------------------------------------------------------------- /src/ng/modules/tileset/states/tileset.state.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Action, Selector, State, StateContext } from '@ngxs/store'; 3 | import { cloneDeep, merge } from 'lodash'; 4 | import { SaveTilesetSettings } from '.'; 5 | import { ITileset, ITilesetSetting } from '../interfaces'; 6 | 7 | export interface ITilesetState { 8 | tilesets: ITileset[]; 9 | } 10 | 11 | export const DEFAULT_SETTINGS: Omit = { 12 | offset: { x: 0, y: 0 }, 13 | selections: [{ x: 0, y: 0 }], 14 | size: { x: 16, y: 16 }, 15 | spacing: { x: 0, y: 0 }, 16 | }; 17 | 18 | @State({ 19 | name: 'tilset', 20 | defaults: { 21 | tilesets: [], 22 | }, 23 | }) 24 | @Injectable({ providedIn: 'root' }) 25 | export class TilesetState { 26 | @Selector() static tilesets(state: ITilesetState): ITileset[] { 27 | return state.tilesets; 28 | } 29 | 30 | @Action(SaveTilesetSettings) 31 | save(ctx: StateContext, action: SaveTilesetSettings): void { 32 | const tilesets = ctx.getState().tilesets.slice(); 33 | let found = tilesets.find(_ => _.asset.id === action.asset.id); 34 | if (!found) { 35 | found = { asset: action.asset, settings: [] }; 36 | tilesets.push(found); 37 | } 38 | const settings = [...found.settings]; 39 | action.settings.forEach(_ => { 40 | let idx = settings.findIndex(s => s.id === _.id); 41 | const setting = settings[idx] ?? { id: settings.length, label: 'default', ...cloneDeep(DEFAULT_SETTINGS) }; 42 | if (idx < 0) settings.push(setting); 43 | Object.keys(_).forEach(k => ((setting as any)[k] = (_ as any)[k])); 44 | }); 45 | found.settings = settings; 46 | ctx.patchState({ tilesets }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/ng/modules/tileset/systems/index.ts: -------------------------------------------------------------------------------- 1 | export * from './tileset.system'; 2 | export * from './overlay.system'; 3 | -------------------------------------------------------------------------------- /src/ng/modules/tileset/systems/overlay.system.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Injectable } from '@angular/core'; 2 | import { Graphics } from '@pixi/graphics'; 3 | import { AbstractEntitySystem, System } from '@trixt0r/ecs'; 4 | import { PointSceneComponent, SceneEntity } from 'common/scene'; 5 | import { SizeSceneComponent } from 'common/scene/component/size'; 6 | import { PixiRendererService } from 'ng/modules/pixi/services/renderer.service'; 7 | import { YAME_RENDERER } from 'ng/modules/scene'; 8 | 9 | @Injectable({ providedIn: 'root' }) 10 | export class TileOverlaySystem extends AbstractEntitySystem { 11 | constructor(@Inject(YAME_RENDERER) private renderer: PixiRendererService) { 12 | super(4, [{ id: 'tileset.overlay.remove' }]); 13 | } 14 | 15 | processEntity( 16 | entity: SceneEntity, 17 | _index?: number | undefined, 18 | _entities?: SceneEntity[] | undefined, 19 | _options?: U | undefined 20 | ): void { 21 | const container = this.renderer.getContainer(entity.id); 22 | if (!container) return; 23 | 24 | const component = entity.components.byId('tileset.overlay.remove') as SizeSceneComponent & { points: PointSceneComponent[] }; 25 | 26 | const graphics = (container.getChildByName('tile-overlay') as Graphics) ?? new Graphics(); 27 | graphics.name ??= 'tile-overlay'; 28 | if (!container.getChildByName(graphics.name)) container.addChild(graphics); 29 | graphics.clear(); 30 | 31 | // graphics.beginFill(0xaa1100); 32 | graphics.lineStyle(2, 0x113399, 1, 1); 33 | 34 | graphics.moveTo(component.points[0].x, component.points[0].y); 35 | for (let i = 0; i <= 4; i++) graphics.lineTo(component.points[i % 4].x, component.points[i % 4].y); 36 | 37 | // graphics.drawRect(0, 0, component.width, component.height); 38 | // graphics.endFill(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/ng/modules/tileset/tileset.module.ts: -------------------------------------------------------------------------------- 1 | import { APP_INITIALIZER, NgModule } from '@angular/core'; 2 | import { NgxsModule } from '@ngxs/store'; 3 | import { AddToolService, AssetModule } from '../asset'; 4 | import { CameraModule } from '../camera'; 5 | import { EngineService } from '../scene'; 6 | import { ToolInterceptor } from '../toolbar/interceptor'; 7 | import { provideAsDecorated, UtilsModule } from '../utils'; 8 | import { TilesetCanvasComponent, TilesetTabComponent } from './components'; 9 | import { TileBrushInterceptor } from './interceptors'; 10 | import { TilesetState } from './states'; 11 | import { TileOverlaySystem, TilesetSystem } from './systems'; 12 | 13 | @NgModule({ 14 | imports: [AssetModule, CameraModule, UtilsModule, NgxsModule.forFeature([TilesetState])], 15 | declarations: [TilesetTabComponent, TilesetCanvasComponent], 16 | providers: [ 17 | provideAsDecorated(TilesetTabComponent), 18 | ToolInterceptor.forTool(AddToolService, TileBrushInterceptor), 19 | { 20 | provide: APP_INITIALIZER, 21 | useFactory: (engineService: EngineService, tilesetSystem: TilesetSystem, overlaySystem: TileOverlaySystem) => () => { 22 | engineService.engine.systems.add(tilesetSystem, overlaySystem); 23 | }, 24 | deps: [EngineService, TilesetSystem, TileOverlaySystem], 25 | multi: true, 26 | }, 27 | ], 28 | exports: [TilesetCanvasComponent], 29 | }) 30 | export class TilesetModule {} 31 | -------------------------------------------------------------------------------- /src/ng/modules/toolbar/components/tool/default/default.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { DefaultToolComponent } from './default.component'; 2 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 3 | import { Tool } from '../../../tool'; 4 | import { By } from '@angular/platform-browser'; 5 | 6 | describe('DefaultToolComponent', () => { 7 | let comp: DefaultToolComponent; 8 | let fixture: ComponentFixture; 9 | 10 | beforeEach(() => { 11 | TestBed.configureTestingModule({ 12 | declarations: [DefaultToolComponent], 13 | }).compileComponents(); 14 | fixture = TestBed.createComponent(DefaultToolComponent); 15 | comp = fixture.componentInstance; 16 | comp.tool = new Tool('edit', 'edit'); 17 | fixture.detectChanges(); 18 | }); 19 | 20 | it('should show the icon of the tool in a mat-icon', () => { 21 | let iconDe = fixture.debugElement.query(By.css('mat-icon')); 22 | expect(iconDe).toBeDefined('No icon defined'); 23 | expect(iconDe).not.toBeNull('No icon visible'); 24 | expect(iconDe.nativeElement.innerHTML).toEqual(comp.tool.icon, 'Wrong icon set'); 25 | }); 26 | 27 | it('should display the build mat-icon by default, if the tool has no icon', () => { 28 | comp.tool.icon = ''; 29 | fixture.detectChanges(); 30 | let iconDe = fixture.debugElement.query(By.css('mat-icon')); 31 | expect(iconDe).toBeDefined('No icon defined'); 32 | expect(iconDe).not.toBeNull('No icon visible'); 33 | expect(iconDe.nativeElement.innerHTML).toEqual(`build`, 'Wrong icon set'); 34 | }); 35 | 36 | afterAll(() => { 37 | document.body.removeChild(fixture.componentRef.location.nativeElement); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /src/ng/modules/toolbar/components/tool/default/default.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ChangeDetectionStrategy } from '@angular/core'; 2 | import { Tool, IToolComponent } from '../../../../toolbar/tool'; 3 | 4 | /** 5 | * The default tool component displays the icon of the tool, 6 | */ 7 | @Component({ 8 | template: ` `, 9 | styles: [ 10 | ` 11 | i[nz-icon] { 12 | position: absolute; 13 | left: 50%; 14 | top: 50%; 15 | transform: translate(-50%, -50%); 16 | font-size: 18px; 17 | } 18 | `, 19 | ], 20 | changeDetection: ChangeDetectionStrategy.OnPush, 21 | standalone: false 22 | }) 23 | export class DefaultToolComponent implements IToolComponent { 24 | /** 25 | * @inheritdoc 26 | */ 27 | tool!: Tool; 28 | } 29 | -------------------------------------------------------------------------------- /src/ng/modules/toolbar/components/toolbar/toolbar.component.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 14 | 15 |
16 | 24 |
25 |
26 |
27 | 28 | 33 | 34 | 35 |
36 | 37 | 44 | -------------------------------------------------------------------------------- /src/ng/modules/toolbar/components/toolbar/toolbar.component.scss: -------------------------------------------------------------------------------- 1 | @use '/src/ng/variables.scss' as *; 2 | 3 | $toolbar-min-width: 48px; 4 | 5 | yame-toolbar { 6 | height: 100%; 7 | min-width: $toolbar-min-width; 8 | background: #303030; 9 | overflow: visible; 10 | position: absolute; 11 | top: 0; 12 | left: 0; 13 | box-shadow: rgba(0, 0, 0, 0.3) 3px -3px 5px 1px, rgba(0, 0, 0, 0.25) 6px -5px 10px 0px, 14 | rgba(0, 0, 0, 0.2) 1px -9px 18px 0px; 15 | user-select: none; 16 | 17 | .content { 18 | width: $toolbar-min-width; 19 | max-height: 100%; 20 | overflow-x: hidden; 21 | overflow-y: auto; 22 | 23 | nz-radio-group { 24 | padding-bottom: 8px; 25 | width: 100%; 26 | > label[nz-radio-button] { 27 | width: 100%; 28 | height: 40px; 29 | border: none; 30 | border-radius: 0; 31 | } 32 | } 33 | .clickers { 34 | padding-bottom: 0; 35 | position: absolute; 36 | left: 0; 37 | bottom: 0; 38 | width: $toolbar-min-width; 39 | 40 | button[nz-button] { 41 | width: 100%; 42 | height: 40px; 43 | } 44 | } 45 | } 46 | 47 | i[nz-icon].anticon-caret-right { 48 | position: absolute; 49 | opacity: 0.25; 50 | &.active { 51 | opacity: 1; 52 | } 53 | right: 0; 54 | bottom: 0; 55 | transform: rotate(45deg); 56 | } 57 | 58 | nz-layout { 59 | background: none !important; 60 | overflow: hidden; 61 | } 62 | 63 | nz-sider { 64 | transition: none !important; 65 | overflow: auto; 66 | } 67 | 68 | yame-resizable { 69 | float: left; 70 | position: absolute; 71 | width: $resizer-size; 72 | height: 100%; 73 | cursor: ew-resize; 74 | top: 0; 75 | z-index: 9999; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/ng/modules/toolbar/directives/tool.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, Input, ViewContainerRef, ComponentRef, OnChanges, SimpleChanges } from '@angular/core'; 2 | import { Tool, IToolComponent } from '../tool'; 3 | import { DefaultToolComponent } from '../components/tool/default/default.component'; 4 | 5 | /** 6 | * Directive for rendering a tool component in a template. 7 | */ 8 | @Directive({ 9 | selector: '[yameTool]', 10 | standalone: false 11 | }) 12 | export class ToolDirective implements OnChanges { 13 | /** 14 | * The tool to display. 15 | */ 16 | @Input('yameTool') tool!: Tool; 17 | 18 | @Input('yameToolProperty') property: keyof Tool = 'component'; 19 | 20 | constructor(private viewContainerRef: ViewContainerRef) {} 21 | 22 | /** @inheritdoc */ 23 | ngOnChanges(changes: SimpleChanges) { 24 | if (changes.tool) this.render(); 25 | } 26 | 27 | /** 28 | * Renders the tool, if a component type for the currently set tool is registered. 29 | * 30 | * @return The created component reference or `null` if no component found for the current tool. 31 | */ 32 | render(): ComponentRef | null { 33 | const compType = this.tool.component || DefaultToolComponent; 34 | const viewContainerRef = this.viewContainerRef; 35 | viewContainerRef.clear(); 36 | if (!compType) return null; 37 | const componentRef = viewContainerRef.createComponent(compType); 38 | componentRef.instance.tool = this.tool; 39 | return componentRef; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/ng/modules/toolbar/exception.ts: -------------------------------------------------------------------------------- 1 | export class ToolbarException extends Error { } 2 | -------------------------------------------------------------------------------- /src/ng/modules/toolbar/index.ts: -------------------------------------------------------------------------------- 1 | export * from './states/toolbar.state'; 2 | export * from './interceptor'; 3 | -------------------------------------------------------------------------------- /src/ng/modules/toolbar/states/actions/toolbar.action.ts: -------------------------------------------------------------------------------- 1 | import { FlexibleConnectedPositionStrategyOrigin } from '@angular/cdk/overlay'; 2 | import { ComponentType } from '@angular/cdk/portal'; 3 | import { Tool } from '../../tool'; 4 | import { IToolbarUISettings } from '../toolbar.state'; 5 | 6 | export class RegisterTool { 7 | static type = '[Toolbar] Register tool'; 8 | constructor(public tool: Tool | Tool[]) {} 9 | } 10 | 11 | export class ActivateTool { 12 | static type = '[Toolbar] Activate tool'; 13 | constructor(public tool: Tool | string, public event?: Event) {} 14 | } 15 | 16 | export class DeactivateTool { 17 | static type = '[Toolbar] Deactivate tool'; 18 | constructor(public tool?: Tool | string, public event?: Event) {} 19 | } 20 | 21 | export class ShowToolbarOptions { 22 | static type = '[Toolbar] Show overlay'; 23 | constructor(public component: ComponentType, public origin?: FlexibleConnectedPositionStrategyOrigin) {} 24 | } 25 | 26 | export class UpdateToolbarUI { 27 | static type = '[Toolbar] Update toolbar ui'; 28 | 29 | constructor(public properties: Partial) {} 30 | } 31 | -------------------------------------------------------------------------------- /src/ng/modules/toolbar/toolbar.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, APP_INITIALIZER } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { ToolDirective } from './directives/tool.directive'; 4 | import { DefaultToolComponent } from './components/tool/default/default.component'; 5 | import { SelectionToolService } from './tools/selection'; 6 | import { NgxsModule, Store } from '@ngxs/store'; 7 | import { ToolbarState } from './states/toolbar.state'; 8 | import { ToolbarComponent } from './components/toolbar/toolbar.component'; 9 | import { RegisterTool } from './states/actions/toolbar.action'; 10 | import { OverlayModule } from '@angular/cdk/overlay'; 11 | import { FormsModule } from '@angular/forms'; 12 | import { AddShortcut } from 'ng/states/hotkey.state'; 13 | import { HotkeyService } from 'ng/services/hotkey.service'; 14 | import { NzIconModule } from 'ng-zorro-antd/icon'; 15 | import { NzButtonModule } from 'ng-zorro-antd/button'; 16 | import { NzRadioModule } from 'ng-zorro-antd/radio'; 17 | import { NzCardModule } from 'ng-zorro-antd/card'; 18 | import { NzLayoutModule } from 'ng-zorro-antd/layout'; 19 | import { UtilsModule } from '../utils'; 20 | 21 | @NgModule({ 22 | imports: [ 23 | CommonModule, 24 | OverlayModule, 25 | FormsModule, 26 | NzIconModule, 27 | NzButtonModule, 28 | NzRadioModule, 29 | NzCardModule, 30 | NzLayoutModule, 31 | UtilsModule, 32 | NgxsModule.forFeature([ToolbarState]), 33 | ], 34 | declarations: [ToolbarComponent, DefaultToolComponent, ToolDirective], 35 | exports: [ToolbarComponent], 36 | providers: [ 37 | { 38 | provide: APP_INITIALIZER, 39 | useFactory: (store: Store, tool: SelectionToolService) => () => { 40 | store.dispatch([ 41 | new RegisterTool(tool), 42 | new AddShortcut({ id: 'select.all', label: 'Select all', keys: [`${HotkeyService.commandOrControl}.a`] }), 43 | ]); 44 | }, 45 | deps: [Store, SelectionToolService], 46 | multi: true, 47 | }, 48 | ], 49 | }) 50 | export class ToolbarModule {} 51 | -------------------------------------------------------------------------------- /src/ng/modules/utils/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from './nested-dropdown/nested-dropdown.component'; 2 | export * from './point-input/point-input.component'; 3 | export * from './type-label/type-label.component'; 4 | export * from './resizable/resizable'; 5 | -------------------------------------------------------------------------------- /src/ng/modules/utils/components/nested-dropdown/nested-dropdown.component.html: -------------------------------------------------------------------------------- 1 | 2 |
  • 3 | 4 | 5 | {{ it.label! | translate }} 6 | 7 |
      8 | 9 |
    10 |
  • 11 | 12 |
  • 13 | 14 | {{ it.label! | translate }} 15 |
  • 16 |
    17 | -------------------------------------------------------------------------------- /src/ng/modules/utils/components/nested-dropdown/nested-dropdown.component.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Trixt0r/yame/5910bc15431ba172db039aa914fc3120882ecd77/src/ng/modules/utils/components/nested-dropdown/nested-dropdown.component.scss -------------------------------------------------------------------------------- /src/ng/modules/utils/components/nested-dropdown/nested-dropdown.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; 2 | 3 | export interface NavItem { 4 | id: string; 5 | label?: string; 6 | icon?: string; 7 | children?: NavItem[]; 8 | } 9 | 10 | @Component({ 11 | selector: 'nested-dropdown', 12 | templateUrl: './nested-dropdown.component.html', 13 | styleUrls: ['./nested-dropdown.component.scss'], 14 | changeDetection: ChangeDetectionStrategy.OnPush, 15 | standalone: false 16 | }) 17 | export class NestedDropdownComponent { 18 | @Input() items!: NavItem[]; 19 | @Output() selected: EventEmitter = new EventEmitter(); 20 | 21 | hasChildren(item: NavItem): boolean { 22 | return !!item.children?.length; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/ng/modules/utils/components/point-input/point-input.component.html: -------------------------------------------------------------------------------- 1 | 2 |
    3 | 4 | 5 | x 6 | 15 | 16 | 17 | 18 | 19 | y 20 | 30 |
    31 |
    32 | -------------------------------------------------------------------------------- /src/ng/modules/utils/components/point-input/point-input.component.scss: -------------------------------------------------------------------------------- 1 | point-input { 2 | display: block; 3 | 4 | nz-form-item { 5 | margin-bottom: 8px !important; 6 | } 7 | 8 | nz-input-group { 9 | border-radius: 0 !important; 10 | } 11 | 12 | .ant-input-group-addon { 13 | user-select: none; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/ng/modules/utils/components/resizable/resizable.html: -------------------------------------------------------------------------------- 1 |
    2 | -------------------------------------------------------------------------------- /src/ng/modules/utils/components/type-label/type-label.component.html: -------------------------------------------------------------------------------- 1 | {{ label }} 2 | 10 | -------------------------------------------------------------------------------- /src/ng/modules/utils/components/type-label/type-label.component.scss: -------------------------------------------------------------------------------- 1 | yame-type-label { 2 | display: inline-block; 3 | 4 | input.ant-input { 5 | padding: 0; 6 | overflow: hidden; 7 | text-overflow: ellipsis; 8 | max-width: 100%; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/ng/modules/utils/components/type-label/type-label.component.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectionStrategy, Component, Input, ViewChild, ViewEncapsulation } from '@angular/core'; 2 | import { TranslateService } from '@ngx-translate/core'; 3 | import { SceneComponent, SceneEntity } from 'common/scene'; 4 | import { cloneDeep } from 'lodash'; 5 | import { SceneComponentsService } from 'ng/modules/sidebar/services/scene-components.service'; 6 | 7 | @Component({ 8 | selector: 'yame-type-label', 9 | templateUrl: './type-label.component.html', 10 | styleUrls: ['./type-label.component.scss'], 11 | changeDetection: ChangeDetectionStrategy.OnPush, 12 | encapsulation: ViewEncapsulation.None, 13 | standalone: false 14 | }) 15 | export class TypeLabelComponent { 16 | /** 17 | * The scene entity reference. 18 | */ 19 | @Input() entities: SceneEntity[] = []; 20 | 21 | /** 22 | * The scene component reference. 23 | */ 24 | @Input() component?: SceneComponent; 25 | 26 | /** 27 | * The label to display. 28 | */ 29 | get label(): string { 30 | if (!this.component || (!this.component.id && !this.component.label)) return ''; 31 | let found = this.translate.instant(`componentLabel.${this.component.id}`); 32 | if (typeof found === 'string' && found.indexOf('componentLabel.') < 0) return found; 33 | found = this.translate.instant(`componentLabel.${this.component.label}`); 34 | if (typeof found === 'string' && found.indexOf('componentLabel.') < 0) return found; 35 | return this.component.id; 36 | } 37 | 38 | constructor(protected components: SceneComponentsService, protected translate: TranslateService) {} 39 | 40 | /** 41 | * Handles the input change event. 42 | * Applies the current input value to the component id. 43 | * 44 | * @param event The triggered event. 45 | */ 46 | onChange(event: InputEvent): void { 47 | const val = (event.currentTarget as HTMLInputElement).value; 48 | if (!val || !this.component) return; 49 | const old = cloneDeep(this.component); 50 | this.component.id = val; 51 | this.components.updateSceneComponent(this.entities, this.component, old); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/ng/modules/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { InjectionToken, ValueProvider } from '@angular/core'; 2 | 3 | /** 4 | * Injection token for providing decorated classes. 5 | */ 6 | export const DECORATOR_TOKEN = new InjectionToken('DECORATOR_TOKEN'); 7 | 8 | /** 9 | * Creates a provider for the given arguments, 10 | * which are injected under the `DECORATOR_TOKEN`. 11 | * 12 | * Can be used, if classes are decorated, but are only used on demand. 13 | * 14 | * @param values The values to provide. 15 | */ 16 | export function provideAsDecorated(...values: unknown[]): ValueProvider { 17 | return { 18 | provide: DECORATOR_TOKEN, 19 | useValue: values, 20 | multi: true, 21 | }; 22 | } 23 | 24 | export * from './lifecycles'; 25 | export * from './rx'; 26 | export { ResizableComponent } from './components/resizable/resizable'; 27 | export { PointInputComponent } from './components/point-input/point-input.component'; 28 | export { ColorPipe } from './pipes/color.pipe'; 29 | export { UtilsModule } from './utils.module'; 30 | -------------------------------------------------------------------------------- /src/ng/modules/utils/lifecycles/destroy.lifecycle.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, OnDestroy } from '@angular/core'; 2 | import { Subject } from 'rxjs'; 3 | 4 | /** 5 | * Injectable for being used to auto unsubscribe to streams on the ngOnDestroy life-cycle. 6 | */ 7 | @Injectable() 8 | export class DestroyLifecycle extends Subject implements OnDestroy { 9 | /** 10 | * @inheritdoc 11 | */ 12 | ngOnDestroy(): void { 13 | this.next(); 14 | this.complete(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/ng/modules/utils/lifecycles/index.ts: -------------------------------------------------------------------------------- 1 | export { DestroyLifecycle } from './destroy.lifecycle'; 2 | -------------------------------------------------------------------------------- /src/ng/modules/utils/pipes/color.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ 4 | name: 'color', 5 | standalone: false 6 | }) 7 | export class ColorPipe implements PipeTransform { 8 | transform(value: any): any { 9 | if (typeof value === 'number') { 10 | let re = value.toString(16); 11 | if (re.length % 2) re = '0' + re; 12 | re = '#' + re; 13 | return re; 14 | } 15 | return value; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/ng/modules/utils/rx/index.ts: -------------------------------------------------------------------------------- 1 | export { notify } from './notify'; 2 | -------------------------------------------------------------------------------- /src/ng/modules/utils/rx/notify.ts: -------------------------------------------------------------------------------- 1 | import { ChangeDetectorRef } from '@angular/core'; 2 | import { MonoTypeOperatorFunction, Observable } from 'rxjs'; 3 | 4 | /** 5 | * Makes sure the given change detector reference will be notified as soon as the source emitted a value. 6 | * 7 | * @param cdr The change detector reference to notify about changes. 8 | */ 9 | export function notify(cdr: ChangeDetectorRef): MonoTypeOperatorFunction { 10 | return (source: Observable) => { 11 | return new Observable(sub => { 12 | source.subscribe({ 13 | next: val => { 14 | sub.next(val); 15 | cdr.markForCheck(); 16 | }, 17 | error: error => { 18 | sub.error(error); 19 | cdr.markForCheck(); 20 | }, 21 | complete: () => sub.complete(), 22 | }); 23 | }); 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /src/ng/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/docs/ts/latest/guide/browser-support.html 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | import 'reflect-metadata'; 22 | 23 | /*************************************************************************************************** 24 | * Zone JS is required by Angular itself. 25 | */ 26 | import 'zone.js'; // Included with Angular CLI. 27 | -------------------------------------------------------------------------------- /src/ng/states/actions/editor.action.ts: -------------------------------------------------------------------------------- 1 | import { ISerializeContext } from 'common/interfaces/serialize-context.interface'; 2 | import { IFileState, IFileSerializer } from '../editor.state'; 3 | 4 | export class AddEditorFile { 5 | static readonly type = '[Editor] Add file'; 6 | constructor(public readonly file?: IFileState, public readonly autoSwitch: boolean = true) { } 7 | } 8 | 9 | export class SetEditorFile { 10 | static readonly type = '[Editor] Set file'; 11 | constructor(public readonly file?: string | IFileState) { } 12 | } 13 | 14 | export class RemoveEditorFile { 15 | static readonly type = '[Editor] Remove file'; 16 | constructor(public readonly file?: string | IFileState) { } 17 | } 18 | 19 | export class AddEditorFileSerializer { 20 | static readonly type = '[Editor] Add file processor'; 21 | constructor(public readonly fileProcessor: IFileSerializer) { } 22 | } 23 | 24 | export class RemoveEditorFileProcessor { 25 | static readonly type = '[Editor] Remove file processor'; 26 | constructor(public readonly fileProcessor: string | IFileSerializer) { } 27 | } 28 | 29 | export class SaveEditorFile { 30 | static readonly type = '[Editor] Save file'; 31 | constructor(public readonly context: ISerializeContext) { } 32 | } 33 | export class LoadEditorFile { 34 | static readonly type = '[Editor] Load file'; 35 | constructor(public readonly context: ISerializeContext) { } 36 | } 37 | 38 | export class OpenEditorFile { 39 | static readonly type = '[Editor] Open file'; 40 | constructor(public uri: string, public protocol: string, public source: string) { } 41 | } -------------------------------------------------------------------------------- /src/ng/style.scss: -------------------------------------------------------------------------------- 1 | @import '~ng-zorro-antd/ng-zorro-antd.dark.min.css'; 2 | @import './styles/utils.scss'; 3 | @import './variables.scss'; 4 | 5 | body { 6 | background: #303030; 7 | color: white; 8 | margin: 0; 9 | } 10 | 11 | ::-webkit-scrollbar { 12 | width: 4px; 13 | height: 4px; 14 | } 15 | 16 | ::-webkit-scrollbar-track { 17 | background-color: rgba(255, 255, 255, 0.3); 18 | } 19 | 20 | ::-webkit-scrollbar-thumb { 21 | background-color: rgba(255, 255, 255, 0.5); 22 | } 23 | 24 | *:focus { 25 | outline: none; 26 | } 27 | 28 | .dnd-drag-start, 29 | .dnd-drag-enter, 30 | .dnd-drag-over { 31 | transform: initial; 32 | opacity: initial; 33 | border: none; 34 | } 35 | 36 | .preferences-menu { 37 | margin-left: 64px; 38 | } 39 | 40 | .titlebar { 41 | font: 400 12px/28px Roboto, 'Helvetica New', sans-serif; 42 | 43 | &.inactive { 44 | opacity: 0.5; 45 | } 46 | } 47 | 48 | li[nz-submenu] [nz-submenu-title] i[nz-icon] + span { 49 | margin-left: 8px; 50 | } 51 | 52 | .image-wrapper { 53 | text-align: center; 54 | height: 100%; 55 | 56 | background: #aaa 57 | url('data:image/svg+xml,\ 58 | \ 59 | \ 60 | \ 61 | '); 62 | background-size: 30px 30px; 63 | } 64 | -------------------------------------------------------------------------------- /src/ng/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/testing'; 4 | import { getTestBed } from '@angular/core/testing'; 5 | import { 6 | BrowserDynamicTestingModule, 7 | platformBrowserDynamicTesting 8 | } from '@angular/platform-browser-dynamic/testing'; 9 | 10 | declare const require: any; 11 | 12 | // First, initialize the Angular testing environment. 13 | getTestBed().initTestEnvironment( 14 | BrowserDynamicTestingModule, 15 | platformBrowserDynamicTesting(), { 16 | teardown: { destroyAfterEach: false } 17 | } 18 | ); 19 | // Then we find all the tests. 20 | const context = require.context('./', true, /\.spec\.ts$/); 21 | // And load the modules. 22 | context.keys().map(context); 23 | -------------------------------------------------------------------------------- /src/ng/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/app", 5 | "module": "esnext", 6 | "baseUrl": "../" 7 | }, 8 | "exclude": ["test.ts", "**/*.spec.ts", "dist", "app-builds", "out", "node_modules"], 9 | "angularCompilerOptions": { 10 | "enableIvy": true, 11 | "fullTemplateTypeCheck": true, 12 | "strictInjectionParameters": true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/ng/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.app.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/spec", 5 | "module": "es2020", 6 | "baseUrl": "../", 7 | "types": [ 8 | "jasmine", 9 | "node" 10 | ] 11 | }, 12 | "files": [ 13 | "test.ts", 14 | "polyfills.ts" 15 | ], 16 | "include": [ 17 | "**/*.spec.ts", 18 | "**/*.d.ts" 19 | ], 20 | "exclude": [ 21 | "dist", 22 | "out", 23 | "app-builds", 24 | "node_modules" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /src/ng/variables.scss: -------------------------------------------------------------------------------- 1 | /* The global background color */ 2 | $main-background-color: #303030; 3 | 4 | /* The size of a resizer */ 5 | $resizer-size: 5px; 6 | 7 | /* The indent level for tree nodes */ 8 | $tree-indent-size: 16px; 9 | 10 | $tree-icon-font-size: 1.125em; 11 | 12 | $tree-inline-btn-size: 32px; 13 | 14 | $tree-node-height: 32px; 15 | -------------------------------------------------------------------------------- /src/ng/workers/pixi.culling.worker.ts: -------------------------------------------------------------------------------- 1 | import { v4 } from 'uuid'; 2 | 3 | declare function postMessage(data: string): void; 4 | 5 | // const t = performance.now(); 6 | const tmp = []; 7 | 8 | addEventListener('message', (message: MessageEvent) => { 9 | // const timeGone = performance.now() - t; 10 | const data = JSON.parse(message.data); 11 | const keys = Object.keys(data); 12 | for (let i = 0, l = keys.length; i < l; i++) 13 | tmp.push(v4()); 14 | // console.log('received data', JSON.parse(message.data)); 15 | postMessage('done'); 16 | }); 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./src", 5 | "outDir": "./out", 6 | "forceConsistentCasingInFileNames": true, 7 | "strict": true, 8 | "noImplicitReturns": true, 9 | "noFallthroughCasesInSwitch": true, 10 | "sourceMap": true, 11 | "declaration": false, 12 | "downlevelIteration": true, 13 | "experimentalDecorators": true, 14 | "moduleResolution": "node", 15 | "importHelpers": true, 16 | "target": "ES2022", 17 | "module": "ESNext", 18 | "lib": [ 19 | "es2018", 20 | "dom" 21 | ], 22 | "allowSyntheticDefaultImports": true, 23 | "skipLibCheck": true, 24 | "skipDefaultLibCheck": true, 25 | "paths": { 26 | "common/*": [ 27 | "./common/*" 28 | ], 29 | "pixi.js": [ 30 | "./ng/modules/pixi/pixi" 31 | ] 32 | }, 33 | "useDefineForClassFields": false 34 | }, 35 | "exclude": ["app-builds", "dist", "out", "node_modules"] 36 | } 37 | --------------------------------------------------------------------------------