├── .editorconfig ├── .eslintrc.json ├── .github ├── FUNDING.yml └── stale.yml ├── .gitignore ├── .npmrc ├── .travis.yml ├── .vscode ├── launch.json └── tasks.json ├── 0resources └── Soft_Cover_Book_Mockup_01.png ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── _config.yml ├── angular.json ├── angular.webpack.js ├── e2e ├── common-setup.ts ├── main.e2e.ts └── tsconfig.e2e.json ├── icon.icns ├── icon.ico ├── lib ├── ControlToolbar.js ├── ControlToolbar.ts ├── DisplaySlide.js ├── DisplaySlide.ts ├── EpubManager.js ├── EpubManager.ts └── workers │ └── ImportEpub.js ├── main.ts ├── package.json ├── src ├── app │ ├── app.component.html │ ├── app.component.scss │ ├── app.component.spec.ts │ ├── app.component.ts │ ├── app.module.ts │ ├── display │ │ ├── controller │ │ │ ├── bible │ │ │ │ ├── controller.component.html │ │ │ │ ├── controller.component.spec.ts │ │ │ │ └── controller.component.ts │ │ │ ├── controller.component.scss │ │ │ ├── controller.component.ts │ │ │ ├── pub │ │ │ │ ├── controller.component.html │ │ │ │ ├── controller.component.spec.ts │ │ │ │ └── controller.component.ts │ │ │ └── toolbar.component.ts │ │ └── display │ │ │ ├── display.component.html │ │ │ ├── display.component.scss │ │ │ └── display.component.ts │ ├── home │ │ ├── home.component.html │ │ ├── home.component.scss │ │ ├── home.component.spec.ts │ │ └── home.component.ts │ └── shared │ │ ├── components │ │ ├── display-controller │ │ │ ├── display-controller.component.html │ │ │ ├── display-controller.component.scss │ │ │ ├── display-controller.component.spec.ts │ │ │ └── display-controller.component.ts │ │ └── menu-layout │ │ │ ├── menu-layout.component.html │ │ │ ├── menu-layout.component.scss │ │ │ ├── menu-layout.component.spec.ts │ │ │ └── menu-layout.component.ts │ │ ├── modals │ │ ├── help │ │ │ ├── help.component.html │ │ │ ├── help.component.scss │ │ │ └── help.component.ts │ │ └── settings │ │ │ ├── settings.component.html │ │ │ ├── settings.component.scss │ │ │ └── settings.component.ts │ │ ├── pipes │ │ └── santizer.pipe.ts │ │ ├── services │ │ ├── modal.service.ts │ │ └── slides.service.ts │ │ └── shared.module.ts ├── assets │ ├── .gitkeep │ ├── bible-bg.jpg │ ├── icons │ │ ├── favicon.256x256.png │ │ ├── favicon.512x512.png │ │ ├── favicon.icns │ │ ├── favicon.ico │ │ └── favicon.png │ ├── semantic │ │ ├── .versions │ │ ├── LICENSE │ │ ├── README.md │ │ ├── package.json │ │ ├── semantic.min.css │ │ └── themes │ │ │ └── default │ │ │ └── assets │ │ │ ├── fonts │ │ │ ├── brand-icons.eot │ │ │ ├── brand-icons.svg │ │ │ ├── brand-icons.ttf │ │ │ ├── brand-icons.woff │ │ │ ├── brand-icons.woff2 │ │ │ ├── icons.eot │ │ │ ├── icons.otf │ │ │ ├── icons.svg │ │ │ ├── icons.ttf │ │ │ ├── icons.woff │ │ │ ├── icons.woff2 │ │ │ ├── outline-icons.eot │ │ │ ├── outline-icons.svg │ │ │ ├── outline-icons.ttf │ │ │ ├── outline-icons.woff │ │ │ └── outline-icons.woff2 │ │ │ └── images │ │ │ └── flags.png │ └── table-bg.jpg ├── environments │ ├── environment.dev.ts │ ├── environment.prod.ts │ ├── environment.ts │ └── environment.web.ts ├── index.html ├── karma.conf.js ├── main.ts ├── polyfills-test.ts ├── polyfills.ts ├── styles.scss ├── test.ts ├── tsconfig.app.json ├── tsconfig.spec.json └── typings.d.ts ├── tsconfig-serve.json └── tsconfig.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 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true, 5 | "es6": true, 6 | "es2017": true 7 | }, 8 | "overrides": [ 9 | { 10 | "files": ["*.ts"], 11 | "extends": [ 12 | "eslint:recommended", 13 | "plugin:@typescript-eslint/eslint-recommended", 14 | "plugin:@typescript-eslint/recommended", 15 | "plugin:@typescript-eslint/recommended-requiring-type-checking" 16 | ], 17 | "parser": "@typescript-eslint/parser", 18 | "parserOptions": { 19 | "ecmaVersion": 10, 20 | "project": [ 21 | "./src/tsconfig.app.json", 22 | "./src/tsconfig.spec.json", 23 | "./e2e/tsconfig.e2e.json" 24 | ], 25 | "sourceType": "module", 26 | "ecmaFeatures": { 27 | "modules": true 28 | } 29 | }, 30 | "plugins": [ 31 | "@typescript-eslint", 32 | "@angular-eslint/eslint-plugin" 33 | ], 34 | "rules": { 35 | "@typescript-eslint/indent": [ 36 | "error", 2, { 37 | "SwitchCase": 1, 38 | "CallExpression": {"arguments": "first"}, 39 | "FunctionExpression": {"parameters": "first"}, 40 | "FunctionDeclaration": {"parameters": "first"} 41 | } 42 | ], 43 | "@typescript-eslint/no-empty-function": 0, 44 | "@typescript-eslint/no-var-requires": 0, 45 | "@typescript-eslint/no-explicit-any": 0, 46 | "@typescript-eslint/no-unsafe-call": 0, 47 | "@typescript-eslint/no-unsafe-member-access": 0, 48 | "@typescript-eslint/no-unsafe-assignment": 0, 49 | "@typescript-eslint/no-unsafe-return": 0, 50 | "@typescript-eslint/no-floating-promises": 0, 51 | "@angular-eslint/use-injectable-provided-in": "error", 52 | "@angular-eslint/no-attribute-decorator": "error" 53 | } 54 | }, 55 | { 56 | "files": ["*.component.html"], 57 | "parser": "@angular-eslint/template-parser", 58 | "plugins": ["@angular-eslint/template"], 59 | "rules": { 60 | "@angular-eslint/template/banana-in-a-box": "error", 61 | "@angular-eslint/template/no-negated-async": "error" 62 | } 63 | } 64 | ] 65 | } 66 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 15 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /app-builds 8 | /release 9 | main.js 10 | src/**/*.js 11 | !src/karma.conf.js 12 | *.js.map 13 | 14 | # dependencies 15 | /node_modules 16 | 17 | # IDEs and editors 18 | /.idea 19 | .project 20 | .classpath 21 | .c9/ 22 | *.launch 23 | .settings/ 24 | *.sublime-workspace 25 | 26 | # IDE - VSCode 27 | .vscode/* 28 | .vscode/settings.json 29 | !.vscode/tasks.json 30 | !.vscode/launch.json 31 | !.vscode/extensions.json 32 | 33 | # misc 34 | /.sass-cache 35 | /connect.lock 36 | /coverage 37 | /libpeerconnection.log 38 | npm-debug.log 39 | testem.log 40 | /typings 41 | package-lock.json 42 | electron-builder.yml 43 | 44 | # e2e 45 | /e2e/*.js 46 | !/e2e/protractor.conf.js 47 | /e2e/*.map 48 | 49 | # System Files 50 | /config 51 | .DS_Store 52 | Thumbs.db 53 | config 54 | 55 | docs/* 56 | 57 | *.psd -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save=true 2 | save-exact=true 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | - osx 4 | language: node_js 5 | node_js: 6 | - '12' 7 | - '10' 8 | dist: xenial 9 | sudo: required 10 | services: 11 | - xvfb 12 | before_script: 13 | - export DISPLAY=:99.0 14 | install: 15 | - npm set progress=false 16 | - npm install 17 | script: 18 | - ng lint 19 | - npm run test 20 | - npm run e2e 21 | - npm run build 22 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible 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 Renderer", 9 | "type": "node", 10 | "request": "launch", 11 | "protocol": "inspector", 12 | // Prelaunch task compiles main.ts for Electron & starts Angular dev server. 13 | "preLaunchTask": "Build.All", 14 | "cwd": "${workspaceFolder}", 15 | "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron", 16 | "runtimeArgs": [ 17 | "--serve", 18 | ".", 19 | "--remote-debugging-port=9222" 20 | ], 21 | "windows": { 22 | "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd" 23 | } 24 | }, { 25 | "name": "Karma Attach Chrome", 26 | "type": "chrome", 27 | "request": "attach", 28 | "port": 9222, 29 | "webRoot": "${workspaceFolder}/", 30 | "sourceMaps": true, 31 | "timeout": 30000, 32 | "trace": true 33 | } 34 | 35 | ] 36 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Build.All", 6 | "type": "shell", 7 | "command": "npm run electron:serve-tsc && ng serve", 8 | "isBackground": true, 9 | "group": { 10 | "kind": "build", 11 | "isDefault": true 12 | }, 13 | "problemMatcher": { 14 | "owner": "typescript", 15 | "source": "ts", 16 | "applyTo": "closedDocuments", 17 | "fileLocation": ["relative", "${cwd}"], 18 | "pattern": "$tsc", 19 | "background": { 20 | "activeOnStart": true, 21 | "beginsPattern": "^.*", 22 | "endsPattern": "^.*Compiled successfully.*" 23 | } 24 | } 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /0resources/Soft_Cover_Book_Mockup_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/01CodeLT/meeting-display/dce135582e34b70dc518520c6848a19a1e89344b/0resources/Soft_Cover_Book_Mockup_01.png -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 7.2.1 (2020-06-20) 2 | 3 | * ref/ keep only 1 eslint config ([e942747](https://github.com/maximegris/angular-electron/commit/e942747)) 4 | 5 | 6 | 7 | ## 7.2.0 (2020-06-20) 8 | 9 | * [Bumped Version] 7.2.0 ([a98a84a](https://github.com/maximegris/angular-electron/commit/a98a84a)) 10 | * feat/ merge electron-builder npm scripts ([ddd92b3](https://github.com/maximegris/angular-electron/commit/ddd92b3)) 11 | * fix/ ng lint with eslint ([92d7419](https://github.com/maximegris/angular-electron/commit/92d7419)) 12 | * misc/ upgrade Electron (9.0.4) / Angular (9.1.11) ([21f7401](https://github.com/maximegris/angular-electron/commit/21f7401)) 13 | * ref/ electron remote deprecated ([a8628fc](https://github.com/maximegris/angular-electron/commit/a8628fc)) 14 | 15 | 16 | 17 | ## 7.1.0 (2020-05-02) 18 | 19 | * [Bumped Version] 7.1.0 ([8dffcea](https://github.com/maximegris/angular-electron/commit/8dffcea)) 20 | * feat/ VSCode : add Debugging config for man process (#465) + Karma ([acc62d9](https://github.com/maximegris/angular-electron/commit/acc62d9)), closes [#465](https://github.com/maximegris/angular-electron/issues/465) 21 | * fix/ Karma configuration to debug easily ([273e752](https://github.com/maximegris/angular-electron/commit/273e752)) 22 | 23 | 24 | 25 | ## 7.0.5 (2020-04-26) 26 | 27 | * [Bumped Version] 7.0.5 ([63eed52](https://github.com/maximegris/angular-electron/commit/63eed52)) 28 | * Upgrade Angular 9.1.3 & Electron 8.2.3 ([00b9d43](https://github.com/maximegris/angular-electron/commit/00b9d43)) 29 | 30 | 31 | 32 | ## 7.0.4 (2020-04-20) 33 | 34 | * [Bumped Version] 7.0.4 ([dbce7a0](https://github.com/maximegris/angular-electron/commit/dbce7a0)) 35 | * ref/ make app reloading/working with and without usehash routing strategy ([386ce67](https://github.com/maximegris/angular-electron/commit/386ce67)) 36 | * Transparent background issue fix for Linux ([4c0c169](https://github.com/maximegris/angular-electron/commit/4c0c169)) 37 | 38 | 39 | 40 | ## 7.0.3 (2020-04-11) 41 | 42 | * [Bumped Version] 7.0.3 ([6206066](https://github.com/maximegris/angular-electron/commit/6206066)) 43 | * fix/ polyfills in tsconfig ([cf4f172](https://github.com/maximegris/angular-electron/commit/cf4f172)) 44 | * misc/ changelog ([19f6027](https://github.com/maximegris/angular-electron/commit/19f6027)) 45 | 46 | 47 | 48 | ## 7.0.2 (2020-04-11) 49 | 50 | * [Bumped Version] 7.0.2 ([c4c36f6](https://github.com/maximegris/angular-electron/commit/c4c36f6)) 51 | * Fix a typo in README.md ([86ac910](https://github.com/maximegris/angular-electron/commit/86ac910)) 52 | * misc/ Changelog ([67437ba](https://github.com/maximegris/angular-electron/commit/67437ba)) 53 | * misc/ maj angular 9.1.1 & electron 8.2.1 ([061e01e](https://github.com/maximegris/angular-electron/commit/061e01e)) 54 | * misc/ maj eslint dep ([09fc1f7](https://github.com/maximegris/angular-electron/commit/09fc1f7)) 55 | * moved all app icons to assets/icons folder ([7d6bb69](https://github.com/maximegris/angular-electron/commit/7d6bb69)) 56 | * ref/ set allowRendererProcessReuse to true ([7c5c43b](https://github.com/maximegris/angular-electron/commit/7c5c43b)) 57 | 58 | 59 | 60 | ## 7.0.1 (2020-02-22) 61 | 62 | * [Bumped Version] 7.0.1 ([7a84ca0](https://github.com/maximegris/angular-electron/commit/7a84ca0)) 63 | * fix/ README dependencies version ([7276d96](https://github.com/maximegris/angular-electron/commit/7276d96)) 64 | * misc/ upgrade Angular 9.0.2 & Electron 8.0.1 ([174b36f](https://github.com/maximegris/angular-electron/commit/174b36f)) 65 | * ref/ travis test node 10 & 12 ([8b7ee5b](https://github.com/maximegris/angular-electron/commit/8b7ee5b)) 66 | 67 | 68 | 69 | ## 7.0.0 (2020-02-09) 70 | 71 | * [Bumped Version] 7.0.0 ([0f304d2](https://github.com/maximegris/angular-electron/commit/0f304d2)) 72 | * cast isElectron to boolean #429 ([ee06695](https://github.com/maximegris/angular-electron/commit/ee06695)), closes [#429](https://github.com/maximegris/angular-electron/issues/429) 73 | * feat/ update angular 8 deps ([7df49ff](https://github.com/maximegris/angular-electron/commit/7df49ff)) 74 | * feat/ update to Angular 9 & Electron 8 ([a304034](https://github.com/maximegris/angular-electron/commit/a304034)) 75 | * fix/ e2e tests ([395d2da](https://github.com/maximegris/angular-electron/commit/395d2da)) 76 | * hot reload note ([28e1854](https://github.com/maximegris/angular-electron/commit/28e1854)) 77 | * ref/ upgrade electron to v8 ([320ce2f](https://github.com/maximegris/angular-electron/commit/320ce2f)) 78 | 79 | 80 | 81 | ## 6.4.1 (2019-12-25) 82 | 83 | * feat/ update Electron (7.1.2) ([3d76ff5](https://github.com/maximegris/angular-electron/commit/3d76ff5)) 84 | * misc/ maj changelog ([eb46727](https://github.com/maximegris/angular-electron/commit/eb46727)) 85 | * reef/ update stale bot ([671b7c9](https://github.com/maximegris/angular-electron/commit/671b7c9)) 86 | * ref/ remove usunsed files ([9bf5824](https://github.com/maximegris/angular-electron/commit/9bf5824)) 87 | 88 | 89 | 90 | ## 6.4.0 (2019-11-19) 91 | 92 | * [Bumped Version] 6.4.0 ([fac9eef](https://github.com/maximegris/angular-electron/commit/fac9eef)) 93 | * #412 change selector of WebviewDirective ([76e7918](https://github.com/maximegris/angular-electron/commit/76e7918)), closes [#412](https://github.com/maximegris/angular-electron/issues/412) 94 | * eslint-migration ([7637f45](https://github.com/maximegris/angular-electron/commit/7637f45)) 95 | * eslint-migration - disable warnings/errors ([99e7ec0](https://github.com/maximegris/angular-electron/commit/99e7ec0)) 96 | * eslint-migration - fix removed translation import ([2f64f37](https://github.com/maximegris/angular-electron/commit/2f64f37)) 97 | * eslint-migration - remove tslint.json configuration ([3c1f9f6](https://github.com/maximegris/angular-electron/commit/3c1f9f6)) 98 | * karma-electron ([21f97fd](https://github.com/maximegris/angular-electron/commit/21f97fd)) 99 | * karma-electron - remove chrome deps for travis ([0f09907](https://github.com/maximegris/angular-electron/commit/0f09907)) 100 | * misc/ add npmrc ([9d94f9c](https://github.com/maximegris/angular-electron/commit/9d94f9c)) 101 | * misc/ typo README ([d52b03a](https://github.com/maximegris/angular-electron/commit/d52b03a)) 102 | * permissive eslint rules to remove linter warnings ([964b57f](https://github.com/maximegris/angular-electron/commit/964b57f)) 103 | * require is not defined ([632c454](https://github.com/maximegris/angular-electron/commit/632c454)) 104 | 105 | 106 | 107 | ## 6.3.1 (2019-11-01) 108 | 109 | * [Bumped Version] 6.3.1 ([671b6a3](https://github.com/maximegris/angular-electron/commit/671b6a3)) 110 | * #395 - require is not defined ([c4b2cb6](https://github.com/maximegris/angular-electron/commit/c4b2cb6)), closes [#395](https://github.com/maximegris/angular-electron/issues/395) 111 | * changelog ([39b0bca](https://github.com/maximegris/angular-electron/commit/39b0bca)) 112 | 113 | 114 | 115 | ## 6.3.0 (2019-10-25) 116 | 117 | * [Bumped Version] 6.3.0 ([09f9646](https://github.com/maximegris/angular-electron/commit/09f9646)) 118 | * misc/ change Electron version to 7.0.0 in README ([6a4e2de](https://github.com/maximegris/angular-electron/commit/6a4e2de)) 119 | * misc/ remove link to dependenciesci ([93d5a8c](https://github.com/maximegris/angular-electron/commit/93d5a8c)) 120 | * misc/ upgrade Electron 7 ([d732340](https://github.com/maximegris/angular-electron/commit/d732340)) 121 | 122 | 123 | 124 | ## 6.2.0 (2019-09-29) 125 | 126 | * [Bumped Version] 6.2.0 ([8838e0e](https://github.com/maximegris/angular-electron/commit/8838e0e)) 127 | * Maj Changelog ([bc4c950](https://github.com/maximegris/angular-electron/commit/bc4c950)) 128 | * misc/ add Stale to close unactive issues ([398bdf1](https://github.com/maximegris/angular-electron/commit/398bdf1)) 129 | * Upgrade Angular 8.2.8 & Electron 6.0.10 ([2ecda63](https://github.com/maximegris/angular-electron/commit/2ecda63)) 130 | 131 | 132 | 133 | ## 6.1.0 (2019-08-15) 134 | 135 | * [Bumped Version] 6.1.0 ([b47c594](https://github.com/maximegris/angular-electron/commit/b47c594)) 136 | * [DELETE]: vscode settings removed ([becc9b4](https://github.com/maximegris/angular-electron/commit/becc9b4)) 137 | * [UPDATE]: Typescript version fixes ([a284c23](https://github.com/maximegris/angular-electron/commit/a284c23)) 138 | * Angular src restructured as modular as per angular official guidelines, .travis.yml support added fo ([5983703](https://github.com/maximegris/angular-electron/commit/5983703)) 139 | * bump tslint & codelyzer versions, update tslint rules & alphabetize ([8425cdc](https://github.com/maximegris/angular-electron/commit/8425cdc)) 140 | * Corejs path updates ([88056d6](https://github.com/maximegris/angular-electron/commit/88056d6)) 141 | * Electron 6.0.0 ([1aef350](https://github.com/maximegris/angular-electron/commit/1aef350)) 142 | * fix/ Version Electron in README ([acf0d4f](https://github.com/maximegris/angular-electron/commit/acf0d4f)) 143 | * misc/ upgrade Angular 8.2 / Spectron 7 ([6e2211f](https://github.com/maximegris/angular-electron/commit/6e2211f)) 144 | * Update dependencies ([bd51f28](https://github.com/maximegris/angular-electron/commit/bd51f28)) 145 | 146 | 147 | 148 | ## 6.0.1 (2019-05-31) 149 | 150 | * [Bumped Version] 6.0.1 ([4f9cef3](https://github.com/maximegris/angular-electron/commit/4f9cef3)) 151 | * Add CI for macOS. ([3ba02e3](https://github.com/maximegris/angular-electron/commit/3ba02e3)) 152 | * ref/ strict version build-angular & zonejs ([5a24b56](https://github.com/maximegris/angular-electron/commit/5a24b56)) 153 | * ref/ strict version codelyzer ([6ede0c8](https://github.com/maximegris/angular-electron/commit/6ede0c8)) 154 | * Remove Node.js v12. ([ccbf6cd](https://github.com/maximegris/angular-electron/commit/ccbf6cd)) 155 | * Run CI for Node.js version 10 ([2538965](https://github.com/maximegris/angular-electron/commit/2538965)) 156 | * Use service xvfb. ([4db31e3](https://github.com/maximegris/angular-electron/commit/4db31e3)) 157 | 158 | 159 | 160 | ## 6.0.0 (2019-05-31) 161 | 162 | * [Bumped Version] 6.0.0 ([fb719ab](https://github.com/maximegris/angular-electron/commit/fb719ab)) 163 | * bump angular version ([7a564a0](https://github.com/maximegris/angular-electron/commit/7a564a0)) 164 | * Fix Travis CI link ([10aaa4c](https://github.com/maximegris/angular-electron/commit/10aaa4c)) 165 | * Not gitignore src/karma.conf.js ([7599320](https://github.com/maximegris/angular-electron/commit/7599320)) 166 | * ref/ upgrade Angular 8 and Electron 5 ([92334cf](https://github.com/maximegris/angular-electron/commit/92334cf)) 167 | * update versions and prepare for electron 5 ([07a5786](https://github.com/maximegris/angular-electron/commit/07a5786)) 168 | * version bump ([bb1d6bb](https://github.com/maximegris/angular-electron/commit/bb1d6bb)) 169 | * feat(CI): update Ubuntu and Node versions in Travis ([e0ff557](https://github.com/maximegris/angular-electron/commit/e0ff557)) 170 | * fix: type conflicts ([a2971bf](https://github.com/maximegris/angular-electron/commit/a2971bf)) 171 | * fix(e2e): add mocha types ([20e1e89](https://github.com/maximegris/angular-electron/commit/20e1e89)) 172 | * fix(e2e): without devTools ([2581983](https://github.com/maximegris/angular-electron/commit/2581983)), closes [/github.com/electron/spectron/issues/174#issuecomment-319242097](https://github.com//github.com/electron/spectron/issues/174/issues/issuecomment-319242097) 173 | * chore: Spectron for e2e tests ([901438a](https://github.com/maximegris/angular-electron/commit/901438a)) 174 | 175 | 176 | 177 | ## 5.1.0 (2018-11-30) 178 | 179 | * [Bumped Version] 5.1.0 ([b790e12](https://github.com/maximegris/angular-electron/commit/b790e12)) 180 | * fix/ typo Angular 7 ([fde371f](https://github.com/maximegris/angular-electron/commit/fde371f)) 181 | * fix/ typo README ([723233c](https://github.com/maximegris/angular-electron/commit/723233c)) 182 | * fix/ typo script npm electron:windows ([45bab44](https://github.com/maximegris/angular-electron/commit/45bab44)) 183 | * ref/ remve npx - fix vulnerabilities ([41aeb57](https://github.com/maximegris/angular-electron/commit/41aeb57)) 184 | * update README ([f146d5d](https://github.com/maximegris/angular-electron/commit/f146d5d)) 185 | 186 | 187 | 188 | ## 5.0.0 (2018-11-11) 189 | 190 | * Fix typos in README file ([0440ee9](https://github.com/maximegris/angular-electron/commit/0440ee9)) 191 | * ref/ Generate changelog ([a89b3ce](https://github.com/maximegris/angular-electron/commit/a89b3ce)) 192 | * ref/ Upgrade to Angular 7 ([315a79b](https://github.com/maximegris/angular-electron/commit/315a79b)) 193 | * Update electron-builder.json files rule ([82c7bcf](https://github.com/maximegris/angular-electron/commit/82c7bcf)) 194 | * Update Version Electron 2 to 3 #hacktoberfest ([f083328](https://github.com/maximegris/angular-electron/commit/f083328)) 195 | 196 | 197 | 198 | ## 4.2.2 (2018-08-22) 199 | 200 | * fix/ build serve & electron with single tsc command ([9106c8f](https://github.com/maximegris/angular-electron/commit/9106c8f)) 201 | * fix/ typo README ([a9448aa](https://github.com/maximegris/angular-electron/commit/a9448aa)) 202 | 203 | 204 | 205 | ## 4.2.1 (2018-08-22) 206 | 207 | * fix/ jslib in main process error ([ef33f5e](https://github.com/maximegris/angular-electron/commit/ef33f5e)) 208 | 209 | 210 | 211 | ## 4.2.0 (2018-08-19) 212 | 213 | * [Bumped Version] V4.2.0 ([0da3856](https://github.com/maximegris/angular-electron/commit/0da3856)) 214 | * fix/ electron builder output directories #200 ([f4535e5](https://github.com/maximegris/angular-electron/commit/f4535e5)), closes [#200](https://github.com/maximegris/angular-electron/issues/200) 215 | * Make sure tsconfig be used. ([961c8b1](https://github.com/maximegris/angular-electron/commit/961c8b1)) 216 | * ref/ remove some directories of tsconfig.app.json ([1adad4a](https://github.com/maximegris/angular-electron/commit/1adad4a)) 217 | * Upgrade Angular (6.1.2) deps ([d8818c1](https://github.com/maximegris/angular-electron/commit/d8818c1)) 218 | 219 | 220 | 221 | ## 4.1.0 (2018-06-27) 222 | 223 | * Allow Angular Using Electron Modules ([ec705ee](https://github.com/maximegris/angular-electron/commit/ec705ee)) 224 | * fix/ version angular (revert 6.0.6 -> 6.0.5) ([63a41b8](https://github.com/maximegris/angular-electron/commit/63a41b8)) 225 | * fix/ version ts-node ([0d8341a](https://github.com/maximegris/angular-electron/commit/0d8341a)) 226 | * ref/ postinstall web & electron ([50657d0](https://github.com/maximegris/angular-electron/commit/50657d0)) 227 | * update README ([1d48e32](https://github.com/maximegris/angular-electron/commit/1d48e32)) 228 | * feat(zone): add zone-patch-electron to patch Electron native APIs in polyfills ([01842e2](https://github.com/maximegris/angular-electron/commit/01842e2)) 229 | 230 | 231 | 232 | ## 4.0.0 (2018-05-25) 233 | 234 | * misc/ remove unused packages ([a7e33b6](https://github.com/maximegris/angular-electron/commit/a7e33b6)) 235 | * misc/ update Changelog ([b758122](https://github.com/maximegris/angular-electron/commit/b758122)) 236 | * ref/ upgrade angular to 6.0.3 ([e7fac6e](https://github.com/maximegris/angular-electron/commit/e7fac6e)) 237 | * refactor: update electron, electron-builder to latest (2.0.2, 20.14.7) ([f19e6ee](https://github.com/maximegris/angular-electron/commit/f19e6ee)) 238 | * refactor: upgrade to NodeJS 8, Angular 6, CLI 6, Electron 2.0, RxJS 6.1 ([e37efdb](https://github.com/maximegris/angular-electron/commit/e37efdb)) 239 | * refactor(hooks): replace hooks to ng-cli fileReplacements logic ([c940037](https://github.com/maximegris/angular-electron/commit/c940037)) 240 | * fix(test): create polyfills-test.ts for karma test & setup Travis CI ([7fbc68c](https://github.com/maximegris/angular-electron/commit/7fbc68c)) 241 | * fix(travis): set progress to false (speed up npm) ([be48531](https://github.com/maximegris/angular-electron/commit/be48531)) 242 | 243 | 244 | 245 | ## 3.4.1 (2018-05-25) 246 | 247 | * misc/ update changelog ([70b359f](https://github.com/maximegris/angular-electron/commit/70b359f)) 248 | * Modify electron builder configuration to remove source code and tests ([0cf6899](https://github.com/maximegris/angular-electron/commit/0cf6899)) 249 | * version 3.4.1 ([308ea9c](https://github.com/maximegris/angular-electron/commit/308ea9c)) 250 | 251 | 252 | 253 | ## 3.4.0 (2018-05-25) 254 | 255 | * misc/ update changelog ([7d5eeb3](https://github.com/maximegris/angular-electron/commit/7d5eeb3)) 256 | * ref/ remove contributors ([6dc97a1](https://github.com/maximegris/angular-electron/commit/6dc97a1)) 257 | * The file is unused ([05c9e39](https://github.com/maximegris/angular-electron/commit/05c9e39)) 258 | * Translation issue ([35354b1](https://github.com/maximegris/angular-electron/commit/35354b1)) 259 | * version 3.4.0 ([06d6b0f](https://github.com/maximegris/angular-electron/commit/06d6b0f)) 260 | 261 | 262 | 263 | ## 3.3.0 (2018-04-15) 264 | 265 | * add Changelog file ([71083f1](https://github.com/maximegris/angular-electron/commit/71083f1)) 266 | * fix/ typo README.md (production variables) ([a8c2b63](https://github.com/maximegris/angular-electron/commit/a8c2b63)) 267 | * version 3.3.0 ([a88bda6](https://github.com/maximegris/angular-electron/commit/a88bda6)) 268 | * version 3.3.0 changelog ([ddfbbf9](https://github.com/maximegris/angular-electron/commit/ddfbbf9)) 269 | 270 | 271 | 272 | ## 3.2.0 (2018-04-15) 273 | 274 | * fix e2e tests based on PR #161 and terminate the npm process after test execution ([fccf348](https://github.com/maximegris/angular-electron/commit/fccf348)), closes [#161](https://github.com/maximegris/angular-electron/issues/161) 275 | * fix/ app e2e spec ([8046b2a](https://github.com/maximegris/angular-electron/commit/8046b2a)) 276 | * Including electron to eliminate Electron not found err sg ([d78203f](https://github.com/maximegris/angular-electron/commit/d78203f)) 277 | * provide webFrame access ([6bd044e](https://github.com/maximegris/angular-electron/commit/6bd044e)) 278 | * ref/ add node/electron module import as exemple : fs and remote ([e3ad12d](https://github.com/maximegris/angular-electron/commit/e3ad12d)) 279 | * remove copyfiles ([9af5138](https://github.com/maximegris/angular-electron/commit/9af5138)) 280 | * update dependencies ([89963ab](https://github.com/maximegris/angular-electron/commit/89963ab)) 281 | * version 3.2.0 ([8dc69fa](https://github.com/maximegris/angular-electron/commit/8dc69fa)) 282 | 283 | 284 | 285 | ## 3.1.0 (2018-03-15) 286 | 287 | * Added option -o to script npm run ng:serve so that it really open the browser ([72aff8d](https://github.com/maximegris/angular-electron/commit/72aff8d)) 288 | * Fix to change environment ([448d68b](https://github.com/maximegris/angular-electron/commit/448d68b)) 289 | * version 3.1.0 ([f7c71e7](https://github.com/maximegris/angular-electron/commit/f7c71e7)) 290 | 291 | 292 | 293 | ## 3.0.1 (2018-03-07) 294 | 295 | * fix/ icon app ([22699ef](https://github.com/maximegris/angular-electron/commit/22699ef)) 296 | * version 3.0.1 ([5258ff1](https://github.com/maximegris/angular-electron/commit/5258ff1)) 297 | 298 | 299 | 300 | ## 3.0.0 (2018-02-25) 301 | 302 | * fix/ TranslateModule test ([7863aa9](https://github.com/maximegris/angular-electron/commit/7863aa9)) 303 | * Ng not ejected anymore ([67ab31c](https://github.com/maximegris/angular-electron/commit/67ab31c)) 304 | * pin all dependency versions ([0558d6a](https://github.com/maximegris/angular-electron/commit/0558d6a)) 305 | * update dependencies and fix unit tests ([4d3ca6e](https://github.com/maximegris/angular-electron/commit/4d3ca6e)) 306 | 307 | 308 | 309 | ## 2.7.1 (2018-02-15) 310 | 311 | * ref/ dernière version cli ([3df8158](https://github.com/maximegris/angular-electron/commit/3df8158)) 312 | * version 2.7.1 ([1ae6f7a](https://github.com/maximegris/angular-electron/commit/1ae6f7a)) 313 | 314 | 315 | 316 | ## 2.7.0 (2018-02-15) 317 | 318 | * Correction of a word. ([d6655c7](https://github.com/maximegris/angular-electron/commit/d6655c7)) 319 | * feat/ add webview directive ([e1b5600](https://github.com/maximegris/angular-electron/commit/e1b5600)) 320 | * migrate Angular to 5.2.0 ([b8cf343](https://github.com/maximegris/angular-electron/commit/b8cf343)) 321 | * ref/ Remove sponsor ([2a28239](https://github.com/maximegris/angular-electron/commit/2a28239)) 322 | * ref/ update angular & dep ([e3b1fab](https://github.com/maximegris/angular-electron/commit/e3b1fab)) 323 | * ref/ upgrade electron (security issue) ([f6a0c4e](https://github.com/maximegris/angular-electron/commit/f6a0c4e)) 324 | * version bump + logo resize ([3545d16](https://github.com/maximegris/angular-electron/commit/3545d16)) 325 | * fix: fixes maximegris/angular-electron#118 ([6d21e69](https://github.com/maximegris/angular-electron/commit/6d21e69)), closes [maximegris/angular-electron#118](https://github.com/maximegris/angular-electron/issues/118) 326 | * fix: fixes maximegris/angular-electron#98 ([136344b](https://github.com/maximegris/angular-electron/commit/136344b)), closes [maximegris/angular-electron#98](https://github.com/maximegris/angular-electron/issues/98) 327 | 328 | 329 | 330 | ## 2.4.1 (2017-12-14) 331 | 332 | * fix/ Manage icons for linux binary generation ([ccd0601](https://github.com/maximegris/angular-electron/commit/ccd0601)) 333 | * version 2.4.1 ([5fcfca0](https://github.com/maximegris/angular-electron/commit/5fcfca0)) 334 | 335 | 336 | 337 | ## 2.4.0 (2017-12-08) 338 | 339 | * Use HttpClientModule ([5704e2e](https://github.com/maximegris/angular-electron/commit/5704e2e)) 340 | * version 2.4.0 ([0437b33](https://github.com/maximegris/angular-electron/commit/0437b33)) 341 | 342 | 343 | 344 | ## 2.3.0 (2017-12-04) 345 | 346 | * add ngx translate ([facda37](https://github.com/maximegris/angular-electron/commit/facda37)) 347 | 348 | 349 | 350 | ## 2.2.0 (2017-11-28) 351 | 352 | * Brought back scripts defined in webpack.config.js ([441da3d](https://github.com/maximegris/angular-electron/commit/441da3d)) 353 | * migrate to Angular 5.0.3 ([f4bc5b2](https://github.com/maximegris/angular-electron/commit/f4bc5b2)) 354 | * Update LICENSE badge ([fa783aa](https://github.com/maximegris/angular-electron/commit/fa783aa)) 355 | * Update to electron-builder ([0e94b52](https://github.com/maximegris/angular-electron/commit/0e94b52)) 356 | 357 | 358 | 359 | ## 2.1.1 (2017-11-19) 360 | 361 | * Move codesponsor ([064be4c](https://github.com/maximegris/angular-electron/commit/064be4c)) 362 | 363 | 364 | 365 | ## 2.1.0 (2017-11-19) 366 | 367 | * Add codesponsor ([87e695d](https://github.com/maximegris/angular-electron/commit/87e695d)) 368 | * Add script for winportable ([2be2dae](https://github.com/maximegris/angular-electron/commit/2be2dae)) 369 | * Add support for building a Windows self-contained executable ([7cfa790](https://github.com/maximegris/angular-electron/commit/7cfa790)) 370 | * fix/ electron-packager need favicon >= 256x256 on Windows ([d2c253f](https://github.com/maximegris/angular-electron/commit/d2c253f)) 371 | * fix/ refact webpack config (inspired by ng eject Angular 5) ([d1c30ac](https://github.com/maximegris/angular-electron/commit/d1c30ac)) 372 | * fix/ replace aotPlugin in no prod mode ([a0caf1e](https://github.com/maximegris/angular-electron/commit/a0caf1e)) 373 | * fix/ Replace AotPlugin to AngularCompilerPlugin ([bef106e](https://github.com/maximegris/angular-electron/commit/bef106e)) 374 | * fix/ Update README Angular 5 ([93c6949](https://github.com/maximegris/angular-electron/commit/93c6949)) 375 | * fix/ webpack template path ([518b66b](https://github.com/maximegris/angular-electron/commit/518b66b)) 376 | * Mgrate to Angular 5.0.2 ([bd7bed6](https://github.com/maximegris/angular-electron/commit/bd7bed6)) 377 | * Update package.json ([b16cf73](https://github.com/maximegris/angular-electron/commit/b16cf73)) 378 | * Version 2.1.0 ([fccef2f](https://github.com/maximegris/angular-electron/commit/fccef2f)) 379 | 380 | 381 | 382 | ## 2.0.0 (2017-11-13) 383 | 384 | * Add buffer to externals ([7e797f0](https://github.com/maximegris/angular-electron/commit/7e797f0)) 385 | * Edit a typo on README ([956a2bc](https://github.com/maximegris/angular-electron/commit/956a2bc)) 386 | * Fix #55 , and also added functionality for scripts global building ([012a894](https://github.com/maximegris/angular-electron/commit/012a894)), closes [#55](https://github.com/maximegris/angular-electron/issues/55) 387 | * Fix #55 removed bootstraps.css which for example purpose before. ([41445eb](https://github.com/maximegris/angular-electron/commit/41445eb)), closes [#55](https://github.com/maximegris/angular-electron/issues/55) 388 | * License MIT ([73494b7](https://github.com/maximegris/angular-electron/commit/73494b7)) 389 | * Migrate to Angular 5 ([3a3ffe1](https://github.com/maximegris/angular-electron/commit/3a3ffe1)) 390 | 391 | 392 | 393 | ## 1.9.0 (2017-09-22) 394 | 395 | * feat/ launch electron & webpack in // (npm run start) ([8c37cc4](https://github.com/maximegris/angular-electron/commit/8c37cc4)) 396 | * ref/ Exclude node_modules (tslint) ([412a0a5](https://github.com/maximegris/angular-electron/commit/412a0a5)) 397 | 398 | 399 | 400 | ## 1.8.1 (2017-09-22) 401 | 402 | * ref/ add package-lock in gitignore ([4edd98d](https://github.com/maximegris/angular-electron/commit/4edd98d)) 403 | * remove package-lock ([8e98627](https://github.com/maximegris/angular-electron/commit/8e98627)) 404 | * upgrade angular version 4.4.3 ([10d0f87](https://github.com/maximegris/angular-electron/commit/10d0f87)) 405 | * version 1.8.1 ([70879d1](https://github.com/maximegris/angular-electron/commit/70879d1)) 406 | 407 | 408 | 409 | ## 1.8.0 (2017-09-09) 410 | 411 | * upgrade lib version ([2ac2aa0](https://github.com/maximegris/angular-electron/commit/2ac2aa0)) 412 | 413 | 414 | 415 | ## 1.7.0 (2017-08-18) 416 | 417 | * ref/ Update Angular (4.3.5) / Electron (1.7.2) / Electron Packager (8.7.2) / Typescript (2.5.0) ([f97cd81](https://github.com/maximegris/angular-electron/commit/f97cd81)) 418 | 419 | 420 | 421 | ## 1.6.1 (2017-07-27) 422 | 423 | * fix/ angular-cli error in prod compilation with aot ([c26a5ae](https://github.com/maximegris/angular-electron/commit/c26a5ae)) 424 | * version 1.6.1 ([899babd](https://github.com/maximegris/angular-electron/commit/899babd)) 425 | 426 | 427 | 428 | ## 1.6.0 (2017-07-16) 429 | 430 | * ajout package-lock npm v5 ([09c0840](https://github.com/maximegris/angular-electron/commit/09c0840)) 431 | * Change background img ([7e58717](https://github.com/maximegris/angular-electron/commit/7e58717)) 432 | * Fix npm run build:prod ([c23bade](https://github.com/maximegris/angular-electron/commit/c23bade)) 433 | * fix/ Bindings not updating automatically #44 ([2a90191](https://github.com/maximegris/angular-electron/commit/2a90191)), closes [#44](https://github.com/maximegris/angular-electron/issues/44) 434 | * fix/ e2e test with jasmine2 ([9c51f32](https://github.com/maximegris/angular-electron/commit/9c51f32)) 435 | * fix/ typescript issues ([bb0a6ab](https://github.com/maximegris/angular-electron/commit/bb0a6ab)) 436 | * increment version deps ([bde452c](https://github.com/maximegris/angular-electron/commit/bde452c)) 437 | * Revert last pull request - break production compilation ([ccc9064](https://github.com/maximegris/angular-electron/commit/ccc9064)) 438 | * upgrade angular version to 4.3.0 ([ab16959](https://github.com/maximegris/angular-electron/commit/ab16959)) 439 | 440 | 441 | 442 | ## 1.5.0 (2017-06-10) 443 | 444 | * fix/ karma Unit test ([ea13d6d](https://github.com/maximegris/angular-electron/commit/ea13d6d)) 445 | * fix/ remove yarn because of error with module dep in prod builds ([8a49a45](https://github.com/maximegris/angular-electron/commit/8a49a45)) 446 | * update yarn lock ([18c0e62](https://github.com/maximegris/angular-electron/commit/18c0e62)) 447 | 448 | 449 | 450 | ## 1.4.4 (2017-06-08) 451 | 452 | * fix/ Fix npm run lint ([db7972a](https://github.com/maximegris/angular-electron/commit/db7972a)) 453 | * ref/ electron ./dist more generic ([7e71add](https://github.com/maximegris/angular-electron/commit/7e71add)) 454 | * Replace const icon to let icon ([dadf65f](https://github.com/maximegris/angular-electron/commit/dadf65f)) 455 | 456 | 457 | 458 | ## 1.4.3 (2017-06-06) 459 | 460 | * fix/ favicon path during packaging ([aa2b012](https://github.com/maximegris/angular-electron/commit/aa2b012)) 461 | * remove build node 8 till node-sass failed ([34f201d](https://github.com/maximegris/angular-electron/commit/34f201d)) 462 | * v1.4.3 ([4961fb0](https://github.com/maximegris/angular-electron/commit/4961fb0)) 463 | 464 | 465 | 466 | ## 1.4.2 (2017-05-31) 467 | 468 | * Change dep versions ([62d08d3](https://github.com/maximegris/angular-electron/commit/62d08d3)) 469 | * install npm dep when building ([56948d0](https://github.com/maximegris/angular-electron/commit/56948d0)) 470 | * Minor update ([5f282b7](https://github.com/maximegris/angular-electron/commit/5f282b7)) 471 | * No hot reload in browser ([7892f0d](https://github.com/maximegris/angular-electron/commit/7892f0d)) 472 | * update Electron v1.6.10 ([f2f2080](https://github.com/maximegris/angular-electron/commit/f2f2080)) 473 | * upgrade ng/electron dependencies ([78b0f27](https://github.com/maximegris/angular-electron/commit/78b0f27)) 474 | * chore(package): bump dependencies ([b62c7b6](https://github.com/maximegris/angular-electron/commit/b62c7b6)) 475 | 476 | 477 | 478 | ## 1.4.0 (2017-05-23) 479 | 480 | * 1.4.0 ([23213c4](https://github.com/maximegris/angular-electron/commit/23213c4)) 481 | * Change style home page ([93dcc52](https://github.com/maximegris/angular-electron/commit/93dcc52)) 482 | * Fixed compiler warnings ([fca6b15](https://github.com/maximegris/angular-electron/commit/fca6b15)) 483 | * ref/ electron main from js to ts ([835d32b](https://github.com/maximegris/angular-electron/commit/835d32b)) 484 | * Remove caret & tilde ([dd98155](https://github.com/maximegris/angular-electron/commit/dd98155)) 485 | 486 | 487 | 488 | ## 1.3.5 (2017-05-18) 489 | 490 | * Add new tags ([cd07a86](https://github.com/maximegris/angular-electron/commit/cd07a86)) 491 | * v 1.3.5 ([d528a71](https://github.com/maximegris/angular-electron/commit/d528a71)) 492 | 493 | 494 | 495 | ## 1.3.4 (2017-05-12) 496 | 497 | * feat/ add nodejs native lib in webpack config ([27d9bc6](https://github.com/maximegris/angular-electron/commit/27d9bc6)) 498 | * Fix issue #15 ([d77cbf1](https://github.com/maximegris/angular-electron/commit/d77cbf1)), closes [#15](https://github.com/maximegris/angular-electron/issues/15) 499 | * Ref/ Electron packager in external file ([17b04e8](https://github.com/maximegris/angular-electron/commit/17b04e8)) 500 | * version 1.3.4 ([374af16](https://github.com/maximegris/angular-electron/commit/374af16)) 501 | 502 | 503 | 504 | ## 1.3.3 (2017-05-10) 505 | 506 | * Chapters order ([a772b9c](https://github.com/maximegris/angular-electron/commit/a772b9c)) 507 | * Chapters order ([06547e5](https://github.com/maximegris/angular-electron/commit/06547e5)) 508 | * Delete spec file of electron.service ([083498e](https://github.com/maximegris/angular-electron/commit/083498e)) 509 | * Fix issue #15 ([e7cd6e6](https://github.com/maximegris/angular-electron/commit/e7cd6e6)), closes [#15](https://github.com/maximegris/angular-electron/issues/15) 510 | * Move Browser mode chapter ([8818750](https://github.com/maximegris/angular-electron/commit/8818750)) 511 | * Version 1.3.3 ([f4db75b](https://github.com/maximegris/angular-electron/commit/f4db75b)) 512 | 513 | 514 | 515 | ## 1.3.2 (2017-05-05) 516 | 517 | * Add comments of how conditional import works ([e6c1b3b](https://github.com/maximegris/angular-electron/commit/e6c1b3b)) 518 | * Conditional import of Electron/NodeJS libs - The app can be launch in browser mode ([c434f8a](https://github.com/maximegris/angular-electron/commit/c434f8a)) 519 | * Fix indentation ([6a9836a](https://github.com/maximegris/angular-electron/commit/6a9836a)) 520 | * Fix prepree2e script ([b2af4fd](https://github.com/maximegris/angular-electron/commit/b2af4fd)) 521 | * Set e2e tests ([d223974](https://github.com/maximegris/angular-electron/commit/d223974)) 522 | * Suround electron browser by try/catch ([88be472](https://github.com/maximegris/angular-electron/commit/88be472)) 523 | * Update @types/node ([9d43304](https://github.com/maximegris/angular-electron/commit/9d43304)) 524 | * Update readme with e2e info ([01bbf13](https://github.com/maximegris/angular-electron/commit/01bbf13)) 525 | * update version ([0849a0a](https://github.com/maximegris/angular-electron/commit/0849a0a)) 526 | 527 | 528 | 529 | ## 1.3.1 (2017-05-05) 530 | 531 | * Add routing module ([7334ce8](https://github.com/maximegris/angular-electron/commit/7334ce8)) 532 | * Fixed hardcoded path in glob copy, blocking assets after eject ([815d519](https://github.com/maximegris/angular-electron/commit/815d519)) 533 | * update comments in dev/prod env files ([7cf6a51](https://github.com/maximegris/angular-electron/commit/7cf6a51)) 534 | * Version 1.3.1 ([f18ac77](https://github.com/maximegris/angular-electron/commit/f18ac77)) 535 | 536 | 537 | 538 | ## 1.3.0 (2017-05-01) 539 | 540 | * Fix webpack prod/dev env ([8549da1](https://github.com/maximegris/angular-electron/commit/8549da1)) 541 | 542 | 543 | 544 | ## 1.2.1 (2017-04-30) 545 | 546 | * allowJs ([4efd188](https://github.com/maximegris/angular-electron/commit/4efd188)) 547 | * Example url background in scss ([3705a35](https://github.com/maximegris/angular-electron/commit/3705a35)) 548 | * Fix electron build (extract-zip workaround) ([a7ee90e](https://github.com/maximegris/angular-electron/commit/a7ee90e)) 549 | * Fix webpack config url in css ([cea4be5](https://github.com/maximegris/angular-electron/commit/cea4be5)) 550 | * html loader ([c55558a](https://github.com/maximegris/angular-electron/commit/c55558a)) 551 | * update version 1.2.1 ([78e8da7](https://github.com/maximegris/angular-electron/commit/78e8da7)) 552 | 553 | 554 | 555 | ## 1.2.0 (2017-04-19) 556 | 557 | * Set one example of css class in app component ([a15775f](https://github.com/maximegris/angular-electron/commit/a15775f)) 558 | * Update npm dependencies ([0a93ebe](https://github.com/maximegris/angular-electron/commit/0a93ebe)) 559 | 560 | 561 | 562 | ## 1.1.2 (2017-04-18) 563 | 564 | * Fix typo & fix script electron:mac ([bd06859](https://github.com/maximegris/angular-electron/commit/bd06859)) 565 | * Set theme jekyll-theme-architect ([644d857](https://github.com/maximegris/angular-electron/commit/644d857)) 566 | * update README ([97fa63d](https://github.com/maximegris/angular-electron/commit/97fa63d)) 567 | * update README ([23fc0a9](https://github.com/maximegris/angular-electron/commit/23fc0a9)) 568 | * update README ([a8dcf6a](https://github.com/maximegris/angular-electron/commit/a8dcf6a)) 569 | * v1.1.2 ([e29e467](https://github.com/maximegris/angular-electron/commit/e29e467)) 570 | 571 | 572 | 573 | ## 1.1.1 (2017-04-12) 574 | 575 | * Fix webpack.config file path (travisci) ([a172df9](https://github.com/maximegris/angular-electron/commit/a172df9)) 576 | * live reload on disk ([7bb2f8b](https://github.com/maximegris/angular-electron/commit/7bb2f8b)) 577 | * Remove unused dependency (webpack-dev-server) ([e9150f4](https://github.com/maximegris/angular-electron/commit/e9150f4)) 578 | 579 | 580 | 581 | ## 1.1.0 (2017-04-12) 582 | 583 | * add depdencies CI & Licence ([6ceb0f2](https://github.com/maximegris/angular-electron/commit/6ceb0f2)) 584 | * Override webpack configuration ([60d6116](https://github.com/maximegris/angular-electron/commit/60d6116)) 585 | 586 | 587 | 588 | ## 1.0.3 (2017-04-07) 589 | 590 | * Add TravisCI ([e5640fd](https://github.com/maximegris/angular-electron/commit/e5640fd)) 591 | * v1.0.3 ([9866d53](https://github.com/maximegris/angular-electron/commit/9866d53)) 592 | 593 | 594 | 595 | ## 1.0.2 (2017-04-07) 596 | 597 | * Add TravisCI ([ef4b80e](https://github.com/maximegris/angular-electron/commit/ef4b80e)) 598 | * Fix typo ([f964c3f](https://github.com/maximegris/angular-electron/commit/f964c3f)) 599 | * Fix typo ([e42bb5e](https://github.com/maximegris/angular-electron/commit/e42bb5e)) 600 | * Update README ([3bb45b3](https://github.com/maximegris/angular-electron/commit/3bb45b3)) 601 | * Update README with angular-cli doc ([5a57578](https://github.com/maximegris/angular-electron/commit/5a57578)) 602 | * v1.0.2 ([1bd8e0e](https://github.com/maximegris/angular-electron/commit/1bd8e0e)) 603 | 604 | 605 | 606 | ## 1.0.1 (2017-04-03) 607 | 608 | * feat/ Add electron-packager scripts ([57891dc](https://github.com/maximegris/angular-electron/commit/57891dc)) 609 | * ref/ update README ([7fddc20](https://github.com/maximegris/angular-electron/commit/7fddc20)) 610 | * update README ([9a983c1](https://github.com/maximegris/angular-electron/commit/9a983c1)) 611 | * v1.0.0 ([7a21eb9](https://github.com/maximegris/angular-electron/commit/7a21eb9)) 612 | * v1.0.1 ([68275a3](https://github.com/maximegris/angular-electron/commit/68275a3)) 613 | * chore: initial commit from @angular/cli ([616a69e](https://github.com/maximegris/angular-electron/commit/616a69e)) 614 | 615 | 616 | 617 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2018 - Maxime GRIS 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JW Meeting display tool 2 | 3 | This is a useful tool for displaying scriptures, books & magazines in congregation meetings. You can import an epub file for a publication and then display the individual paragraphs along with the cited scriptures from an article/page on a second screen. 4 | 5 | If you have any issues or feature requests for this application please add them in the issues section. 6 | 7 | Click here to download the latest release 8 | 9 | _**Please note:** Some users may experience an issue when downloading the application. Windows does not automatically trust apps which have a small number of downloads, to bypass this issue follow the steps below..._ 10 | 11 | ![trust-meeting-display-app](https://user-images.githubusercontent.com/11212855/90187791-4c2a9e00-ddb2-11ea-81c7-c90650280afb.gif) 12 | 13 | ### Features 14 | 15 | - Display scriptures from the bible 16 | - Display paragraphs and cited scriptures from the study watchtower or other study publications 17 | - Customize the font size, font color & background of the display 18 | - Create a slideshow of content to use during the meetings 19 | - Display on a second screen for use with zoom 20 | 21 | ### Coming soon! 22 | 23 | - Display images direct from the selected publication 24 | - Upload your own images for use in talks 25 | 26 | ### Screenshots 27 | 28 | Home screen | Publication display 29 | :-------------------------:|:-------------------------: 30 | ![display-home](https://user-images.githubusercontent.com/11212855/88225486-792bdb00-cc62-11ea-9e34-77f38deb9d65.png) | ![display](https://user-images.githubusercontent.com/11212855/88225434-63b6b100-cc62-11ea-98be-3323291b9950.png) 31 | 32 | Select scriptures | Publication display 33 | :-------------------------:|:-------------------------: 34 | ![bible-controller](https://user-images.githubusercontent.com/11212855/89132175-24158200-d50a-11ea-8864-47d1bf237684.PNG) | ![bible-display](https://user-images.githubusercontent.com/11212855/89132118-a2bdef80-d509-11ea-9920-5cb1116f7838.PNG) 35 | 36 | ### Contributing 37 | Any contributions to this project would be greatly appreciated! Information about how to run it locally is available here https://github.com/01CodeLT/meeting-display/issues/3 38 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-architect -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "angular-electron": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "architect": { 11 | "build": { 12 | "builder": "@angular-builders/custom-webpack:browser", 13 | "options": { 14 | "outputPath": "dist", 15 | "index": "src/index.html", 16 | "main": "src/main.ts", 17 | "tsConfig": "src/tsconfig.app.json", 18 | "polyfills": "src/polyfills.ts", 19 | "assets": [ 20 | "src/assets" 21 | ], 22 | "styles": [ 23 | "src/assets/semantic/semantic.min.css", 24 | "src/styles.scss" 25 | ], 26 | "scripts": [], 27 | "customWebpackConfig": { 28 | "path": "./angular.webpack.js" 29 | } 30 | }, 31 | "configurations": { 32 | "dev": { 33 | "optimization": false, 34 | "outputHashing": "all", 35 | "sourceMap": true, 36 | "extractCss": true, 37 | "namedChunks": false, 38 | "aot": false, 39 | "extractLicenses": true, 40 | "vendorChunk": false, 41 | "buildOptimizer": false, 42 | "fileReplacements": [ 43 | { 44 | "replace": "src/environments/environment.ts", 45 | "with": "src/environments/environment.dev.ts" 46 | } 47 | ] 48 | }, 49 | "web": { 50 | "optimization": false, 51 | "outputHashing": "all", 52 | "sourceMap": true, 53 | "extractCss": true, 54 | "namedChunks": false, 55 | "aot": false, 56 | "extractLicenses": true, 57 | "vendorChunk": false, 58 | "buildOptimizer": false, 59 | "fileReplacements": [ 60 | { 61 | "replace": "src/environments/environment.ts", 62 | "with": "src/environments/environment.web.ts" 63 | } 64 | ] 65 | }, 66 | "production": { 67 | "optimization": true, 68 | "outputHashing": "all", 69 | "sourceMap": false, 70 | "extractCss": true, 71 | "namedChunks": false, 72 | "aot": true, 73 | "extractLicenses": true, 74 | "vendorChunk": false, 75 | "buildOptimizer": true, 76 | "fileReplacements": [ 77 | { 78 | "replace": "src/environments/environment.ts", 79 | "with": "src/environments/environment.prod.ts" 80 | } 81 | ] 82 | } 83 | } 84 | }, 85 | "serve": { 86 | "builder": "@angular-builders/custom-webpack:dev-server", 87 | "options": { 88 | "browserTarget": "angular-electron:build" 89 | }, 90 | "configurations": { 91 | "dev": { 92 | "browserTarget": "angular-electron:build:dev" 93 | }, 94 | "web": { 95 | "browserTarget": "angular-electron:build:web" 96 | }, 97 | "production": { 98 | "browserTarget": "angular-electron:build:production" 99 | } 100 | } 101 | }, 102 | "extract-i18n": { 103 | "builder": "@angular-devkit/build-angular:extract-i18n", 104 | "options": { 105 | "browserTarget": "angular-electron:build" 106 | } 107 | }, 108 | "test": { 109 | "builder": "@angular-builders/custom-webpack:karma", 110 | "options": { 111 | "main": "src/test.ts", 112 | "polyfills": "src/polyfills-test.ts", 113 | "tsConfig": "src/tsconfig.spec.json", 114 | "karmaConfig": "src/karma.conf.js", 115 | "scripts": [], 116 | "styles": [ 117 | "src/styles.scss" 118 | ], 119 | "assets": [ 120 | "src/assets" 121 | ], 122 | "customWebpackConfig": { 123 | "path": "./angular.webpack.js", 124 | "target": "electron-renderer" 125 | } 126 | } 127 | }, 128 | "lint": { 129 | "builder": "@angular-eslint/builder:lint", 130 | "options": { 131 | "eslintConfig": ".eslintrc.json", 132 | "tsConfig": [ 133 | "src/tsconfig.app.json", 134 | "src/tsconfig.spec.json" 135 | ], 136 | "exclude": [ 137 | "**/node_modules/**" 138 | ] 139 | } 140 | } 141 | } 142 | }, 143 | "angular-electron-e2e": { 144 | "root": "e2e", 145 | "projectType": "application", 146 | "architect": { 147 | "lint": { 148 | "builder": "@angular-eslint/builder:lint", 149 | "options": { 150 | "eslintConfig": ".eslintrc.json", 151 | "tsConfig": [ 152 | "e2e/tsconfig.e2e.json" 153 | ], 154 | "exclude": [ 155 | "**/node_modules/**" 156 | ] 157 | } 158 | } 159 | } 160 | } 161 | }, 162 | "defaultProject": "angular-electron", 163 | "schematics": { 164 | "@schematics/angular:component": { 165 | "prefix": "app", 166 | "styleext": "scss" 167 | }, 168 | "@schematics/angular:directive": { 169 | "prefix": "app" 170 | } 171 | }, 172 | "cli": { 173 | "analytics": false 174 | } 175 | } -------------------------------------------------------------------------------- /angular.webpack.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Custom angular webpack configuration 3 | */ 4 | 5 | module.exports = (config, options) => { 6 | config.target = 'electron-renderer'; 7 | if (options.customWebpackConfig.target) { 8 | config.target = options.customWebpackConfig.target; 9 | } else if (options.fileReplacements) { 10 | for(let fileReplacement of options.fileReplacements) { 11 | if (fileReplacement.replace !== 'src/environments/environment.ts') { 12 | continue; 13 | } 14 | 15 | let fileReplacementParts = fileReplacement['with'].split('.'); 16 | if (['dev', 'prod', 'test', 'electron-renderer'].indexOf(fileReplacementParts[1]) < 0) { 17 | config.target = fileReplacementParts[1]; 18 | } 19 | break; 20 | } 21 | } 22 | 23 | return config; 24 | } 25 | -------------------------------------------------------------------------------- /e2e/common-setup.ts: -------------------------------------------------------------------------------- 1 | const Application = require('spectron').Application; 2 | const electronPath = require('electron'); // Require Electron from the binaries included in node_modules. 3 | const path = require('path'); 4 | 5 | export default function setup(): void { 6 | beforeEach(async function () { 7 | this.app = new Application({ 8 | // Your electron path can be any binary 9 | // i.e for OSX an example path could be '/Applications/MyApp.app/Contents/MacOS/MyApp' 10 | // But for the sake of the example we fetch it from our node_modules. 11 | path: electronPath, 12 | 13 | // Assuming you have the following directory structure 14 | 15 | // |__ my project 16 | // |__ ... 17 | // |__ main.js 18 | // |__ package.json 19 | // |__ index.html 20 | // |__ ... 21 | // |__ test 22 | // |__ spec.js <- You are here! ~ Well you should be. 23 | 24 | // The following line tells spectron to look and use the main.js file 25 | // and the package.json located 1 level above. 26 | args: [path.join(__dirname, '..')], 27 | webdriverOptions: {} 28 | }); 29 | await this.app.start(); 30 | const browser = this.app.client; 31 | await browser.waitUntilWindowLoaded(); 32 | 33 | browser.timeouts('script', 15000); 34 | }); 35 | 36 | afterEach(function () { 37 | if (this.app && this.app.isRunning()) { 38 | return this.app.stop(); 39 | } 40 | }); 41 | } 42 | -------------------------------------------------------------------------------- /e2e/main.e2e.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import { SpectronClient } from 'spectron'; 3 | 4 | import commonSetup from './common-setup'; 5 | 6 | describe('angular-electron App', function () { 7 | commonSetup.apply(this); 8 | 9 | let browser: any; 10 | let client: SpectronClient; 11 | 12 | beforeEach(function () { 13 | client = this.app.client; 14 | browser = client as any; 15 | }); 16 | 17 | it('should display message saying App works !', async function () { 18 | const text = await browser.getText('app-home h1'); 19 | expect(text).to.equal('App works !'); 20 | }); 21 | 22 | it('creates initial windows', async function () { 23 | const count = await client.getWindowCount(); 24 | expect(count).to.equal(1); 25 | }); 26 | 27 | }); 28 | -------------------------------------------------------------------------------- /e2e/tsconfig.e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "types": [ 7 | "mocha" 8 | ] 9 | }, 10 | "include": [ 11 | "e2e/**/*.ts" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/01CodeLT/meeting-display/dce135582e34b70dc518520c6848a19a1e89344b/icon.icns -------------------------------------------------------------------------------- /icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/01CodeLT/meeting-display/dce135582e34b70dc518520c6848a19a1e89344b/icon.ico -------------------------------------------------------------------------------- /lib/ControlToolbar.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var url = require("url"); 4 | var path = require("path"); 5 | var main_1 = require("../main"); 6 | var electron_1 = require("electron"); 7 | var toolbarOptions = { 8 | display: true, 9 | x: 0, 10 | y: 0 11 | }; 12 | electron_1.app.on('ready', function () { 13 | //Set toolbar options on startup 14 | main_1.optionsStorage.findOne({ _id: 'toolbar' }, function (err, doc) { 15 | toolbarOptions = doc ? doc.values : toolbarOptions; 16 | }); 17 | }); 18 | function showToolbar() { 19 | //Create toolbar window 20 | if (!exports.toolbarWindow) { 21 | exports.toolbarWindow = new electron_1.BrowserWindow({ 22 | width: 370, 23 | height: 52, 24 | x: toolbarOptions.x, 25 | y: toolbarOptions.y, 26 | webPreferences: { 27 | webSecurity: false, 28 | nodeIntegration: true, 29 | allowRunningInsecureContent: (main_1.serve) ? true : false, 30 | }, 31 | frame: false, 32 | transparent: true, 33 | alwaysOnTop: true, 34 | resizable: false 35 | }); 36 | //Remove menu 37 | exports.toolbarWindow.removeMenu(); 38 | //Check that the toolbar isn't out bounds 39 | var winBounds = main_1.mainWindow.getBounds(); 40 | var activeScreen = electron_1.screen.getDisplayNearestPoint({ x: winBounds.x, y: winBounds.y }); 41 | if ((toolbarOptions.x > activeScreen.bounds.width) || (toolbarOptions.y > activeScreen.bounds.height)) { 42 | exports.toolbarWindow.setBounds({ x: 0, y: 0 }); 43 | } 44 | //Electron config 45 | if (main_1.serve) { 46 | require('electron-reload')(__dirname, { 47 | electron: require(__dirname + "/../node_modules/electron") 48 | }); 49 | exports.toolbarWindow.loadURL('http://localhost:4200#controller/toolbar'); 50 | } 51 | else { 52 | exports.toolbarWindow.loadURL(url.format({ 53 | pathname: path.join(__dirname, '../dist/index.html'), 54 | protocol: 'file:', 55 | hash: 'controller/toolbar', 56 | slashes: true 57 | })); 58 | } 59 | //Listen for winow move to launch in same place 60 | exports.toolbarWindow.on('move', function () { 61 | toolbarOptions.x = exports.toolbarWindow.getBounds().x; 62 | toolbarOptions.y = exports.toolbarWindow.getBounds().y; 63 | main_1.optionsStorage.update({ _id: 'toolbar' }, { _id: 'toolbar', values: toolbarOptions }, { upsert: true }); 64 | }); 65 | } 66 | else { 67 | exports.toolbarWindow.setAlwaysOnTop(true); 68 | exports.toolbarWindow.restore(); 69 | } 70 | } 71 | exports.showToolbar = showToolbar; 72 | function hideToolbar() { 73 | exports.toolbarWindow.setAlwaysOnTop(false); 74 | exports.toolbarWindow.minimize(); 75 | } 76 | exports.hideToolbar = hideToolbar; 77 | //# sourceMappingURL=ControlToolbar.js.map -------------------------------------------------------------------------------- /lib/ControlToolbar.ts: -------------------------------------------------------------------------------- 1 | import * as url from 'url'; 2 | import * as path from 'path'; 3 | import { serve, mainWindow, optionsStorage } from '../main'; 4 | import { BrowserWindow, app, screen } from 'electron'; 5 | 6 | export let toolbarWindow; 7 | let toolbarOptions = { 8 | display: true, 9 | x: 0, 10 | y: 0 11 | } 12 | 13 | app.on('ready', () => { 14 | //Set toolbar options on startup 15 | optionsStorage.findOne({ _id: 'toolbar' }, (err, doc) => { 16 | toolbarOptions = doc ? doc.values : toolbarOptions; 17 | }); 18 | }) 19 | 20 | export function showToolbar() { 21 | //Create toolbar window 22 | if (!toolbarWindow) { 23 | toolbarWindow = new BrowserWindow({ 24 | width: 370, 25 | height: 52, 26 | x: toolbarOptions.x, 27 | y: toolbarOptions.y, 28 | webPreferences: { 29 | webSecurity: false, 30 | nodeIntegration: true, 31 | allowRunningInsecureContent: (serve) ? true : false, 32 | }, 33 | frame: false, 34 | transparent: true, 35 | alwaysOnTop: true, 36 | resizable: false 37 | }); 38 | 39 | //Remove menu 40 | toolbarWindow.removeMenu(); 41 | 42 | //Check that the toolbar isn't out bounds 43 | let winBounds = mainWindow.getBounds(); 44 | let activeScreen = screen.getDisplayNearestPoint({ x: winBounds.x, y: winBounds.y }); 45 | if ((toolbarOptions.x > activeScreen.bounds.width) || (toolbarOptions.y > activeScreen.bounds.height)) { 46 | toolbarWindow.setBounds({ x:0, y: 0 }); 47 | } 48 | 49 | //Electron config 50 | if (serve) { 51 | require('electron-reload')(__dirname, { 52 | electron: require(`${__dirname}/../node_modules/electron`) 53 | }); 54 | toolbarWindow.loadURL('http://localhost:4200#controller/toolbar'); 55 | } else { 56 | toolbarWindow.loadURL(url.format({ 57 | pathname: path.join(__dirname, '../dist/index.html'), 58 | protocol: 'file:', 59 | hash: 'controller/toolbar', 60 | slashes: true 61 | })); 62 | } 63 | 64 | //Listen for winow move to launch in same place 65 | toolbarWindow.on('move', function () { 66 | toolbarOptions.x = toolbarWindow.getBounds().x; 67 | toolbarOptions.y = toolbarWindow.getBounds().y; 68 | optionsStorage.update({ _id: 'toolbar' }, { _id: 'toolbar', values: toolbarOptions }, { upsert: true }); 69 | }); 70 | } else { 71 | toolbarWindow.setAlwaysOnTop(true); 72 | toolbarWindow.restore(); 73 | } 74 | } 75 | 76 | export function hideToolbar() { 77 | toolbarWindow.setAlwaysOnTop(false); 78 | toolbarWindow.minimize(); 79 | } -------------------------------------------------------------------------------- /lib/DisplaySlide.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __spreadArrays = (this && this.__spreadArrays) || function () { 3 | for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; 4 | for (var r = Array(s), k = 0, i = 0; i < il; i++) 5 | for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) 6 | r[k] = a[j]; 7 | return r; 8 | }; 9 | Object.defineProperty(exports, "__esModule", { value: true }); 10 | var url = require("url"); 11 | var path = require("path"); 12 | var merge = require("lodash/merge"); 13 | var main_1 = require("../main"); 14 | var electron_1 = require("electron"); 15 | var epub; 16 | var slideshow = { slides: [], active: 0 }; 17 | var displayOptions = { 18 | fontSize: 40, 19 | textAlign: 'center', 20 | fontColor: '#696969', 21 | fontLinkColor: '#4271BD', 22 | bgType: 'pub', 23 | bgColor: '#fff', 24 | display: { 25 | windowed: false, 26 | selected: 1, 27 | list: [] 28 | } 29 | }; 30 | electron_1.app.on('ready', function () { 31 | //Set display options and listen for changes 32 | electron_1.screen.on('display-added', function () { updateDisplayOptions(null); }); 33 | electron_1.screen.on('display-removed', function () { updateDisplayOptions(null); }); 34 | }); 35 | function getDisplayList() { 36 | return Array.from({ length: electron_1.screen.getAllDisplays().length }, function (e, i) { return (i + 1); }); 37 | } 38 | function controlDisplay(action) { 39 | var _a; 40 | var args = []; 41 | for (var _i = 1; _i < arguments.length; _i++) { 42 | args[_i - 1] = arguments[_i]; 43 | } 44 | (_a = exports.displayWindow.webContents).send.apply(_a, __spreadArrays(['slides-control', action], args)); 45 | } 46 | exports.controlDisplay = controlDisplay; 47 | function updateDisplayOptions(updatedOptions) { 48 | if (updatedOptions == null) { 49 | //Get saved options 50 | main_1.optionsStorage.findOne({ _id: 'display' }, function (err, doc) { 51 | console.log(merge(displayOptions, doc.values), doc.values); 52 | displayOptions = doc ? merge(displayOptions, doc.values) : displayOptions; 53 | displayOptions.display.list = getDisplayList(); 54 | main_1.mainWindow.webContents.send('slides-options', displayOptions); 55 | if (exports.displayWindow) 56 | exports.displayWindow.webContents.send('slides-options', displayOptions); 57 | }); 58 | } 59 | else { 60 | //Move display to selected 61 | if (updatedOptions.display.selected !== displayOptions.display.selected 62 | || updatedOptions.display.windowed !== displayOptions.display.windowed) { 63 | //Set selected display 64 | var displays = electron_1.screen.getAllDisplays(); 65 | var externalDisplay = (updatedOptions.display.selected > displays.length) ? displays[0] : displays[updatedOptions.display.selected - 1]; 66 | exports.displayWindow.setBounds({ 67 | x: externalDisplay.bounds.x, 68 | y: externalDisplay.bounds.y 69 | }); 70 | //Change to windowed mode? 71 | exports.displayWindow.setFullScreen(!updatedOptions.display.windowed); 72 | if (updatedOptions.display.windowed == false) 73 | exports.displayWindow.maximize(); 74 | } 75 | //Update options 76 | displayOptions = updatedOptions; 77 | main_1.optionsStorage.update({ _id: 'display' }, { _id: 'display', values: updatedOptions }, { upsert: true }, function (err, numReplaced, upsert) { 78 | displayOptions.display.list = getDisplayList(); 79 | main_1.mainWindow.webContents.send('slides-options', displayOptions); 80 | if (exports.displayWindow) 81 | exports.displayWindow.webContents.send('slides-options', displayOptions); 82 | }); 83 | } 84 | } 85 | exports.updateDisplayOptions = updateDisplayOptions; 86 | function getSlides(event) { 87 | //Send slides to window 88 | event.sender.webContents.send('slides-update', epub, slideshow); 89 | } 90 | exports.getSlides = getSlides; 91 | function updateSlides(event, updatedEpub, updatedSlideshow) { 92 | if (updatedEpub === void 0) { updatedEpub = null; } 93 | if (updatedSlideshow === void 0) { updatedSlideshow = null; } 94 | epub = updatedEpub || epub; 95 | slideshow = updatedSlideshow || slideshow; 96 | //Publish event to other windows 97 | electron_1.BrowserWindow.getAllWindows().forEach(function (window) { 98 | if (window.id !== event.sender.id) { 99 | window.webContents.send('slides-update', epub, slideshow); 100 | } 101 | }); 102 | } 103 | exports.updateSlides = updateSlides; 104 | function toggleDisplay() { 105 | if (!exports.displayWindow) { 106 | //Create window if not exists 107 | var displays = electron_1.screen.getAllDisplays(); 108 | var externalDisplay = (displayOptions.display.selected > displays.length) ? displays[0] : displays[displayOptions.display.selected - 1]; 109 | //Create externalDisplay 110 | if (externalDisplay) { 111 | exports.displayWindow = new electron_1.BrowserWindow({ 112 | title: 'Meeting display', 113 | x: externalDisplay.bounds.x, 114 | y: externalDisplay.bounds.y, 115 | webPreferences: { 116 | webSecurity: false, 117 | nodeIntegration: true, 118 | allowRunningInsecureContent: (main_1.serve) ? true : false, 119 | }, 120 | fullscreen: !displayOptions.display.windowed, 121 | frame: false 122 | }); 123 | //Emitted when window closed 124 | exports.displayWindow.on('closed', function () { 125 | exports.displayWindow = null; 126 | }); 127 | //Set as fullscreen 128 | if (displayOptions.display.windowed == false) 129 | exports.displayWindow.maximize(); 130 | //Listen for escape 131 | exports.displayWindow.webContents.on('before-input-event', function (event, input) { 132 | if (input.key == 'Escape') { 133 | toggleDisplay(); 134 | } 135 | }); 136 | //Electron config 137 | if (main_1.serve) { 138 | exports.displayWindow.webContents.openDevTools(); 139 | require('electron-reload')(__dirname, { 140 | electron: require(__dirname + "/../node_modules/electron") 141 | }); 142 | exports.displayWindow.loadURL('http://localhost:4200#display'); 143 | } 144 | else { 145 | exports.displayWindow.loadURL(url.format({ 146 | pathname: path.join(__dirname, '../dist/index.html'), 147 | protocol: 'file:', 148 | hash: 'display', 149 | slashes: true 150 | })); 151 | } 152 | } 153 | } 154 | else { 155 | exports.displayWindow.close(); //Close window 156 | exports.displayWindow = null; 157 | } 158 | } 159 | exports.toggleDisplay = toggleDisplay; 160 | //# sourceMappingURL=DisplaySlide.js.map -------------------------------------------------------------------------------- /lib/DisplaySlide.ts: -------------------------------------------------------------------------------- 1 | import * as url from 'url'; 2 | import * as path from 'path'; 3 | import merge = require('lodash/merge'); 4 | import { serve, mainWindow, optionsStorage } from '../main'; 5 | import { BrowserWindow, screen, app } from 'electron'; 6 | 7 | interface Settings { 8 | fontSize: number, 9 | textAlign: string, 10 | fontColor: string, 11 | fontLinkColor: string, 12 | bgType: string, //Can be 'color' or 'pub' 13 | bgColor: string, 14 | display: { 15 | windowed: boolean, 16 | selected: number, 17 | list: Array 18 | } 19 | } 20 | 21 | let epub; 22 | let slideshow = { slides: [], active: 0 }; 23 | 24 | export let displayWindow: BrowserWindow; 25 | let displayOptions: Settings = { 26 | fontSize: 40, 27 | textAlign: 'center', 28 | fontColor: '#696969', 29 | fontLinkColor: '#4271BD', 30 | bgType: 'pub', 31 | bgColor: '#fff', 32 | display: { 33 | windowed: false, 34 | selected: 1, 35 | list: [] 36 | } 37 | } 38 | 39 | app.on('ready', () => { 40 | //Set display options and listen for changes 41 | screen.on('display-added', () => { updateDisplayOptions(null); }); 42 | screen.on('display-removed', () => { updateDisplayOptions(null); }) 43 | }) 44 | 45 | function getDisplayList() { 46 | return Array.from({ length: screen.getAllDisplays().length }, (e, i) => (i + 1)); 47 | } 48 | 49 | export function controlDisplay(action, ...args) { 50 | displayWindow.webContents.send('slides-control', action, ...args); 51 | } 52 | 53 | export function updateDisplayOptions(updatedOptions: Settings) { 54 | if (updatedOptions == null) { 55 | //Get saved options 56 | optionsStorage.findOne({ _id: 'display' }, (err, doc) => { 57 | console.log(merge(displayOptions, doc.values), doc.values); 58 | displayOptions = doc ? merge(displayOptions, doc.values) : displayOptions; 59 | displayOptions.display.list = getDisplayList(); 60 | mainWindow.webContents.send('slides-options', displayOptions); 61 | if (displayWindow) displayWindow.webContents.send('slides-options', displayOptions); 62 | }); 63 | } else { 64 | //Move display to selected 65 | if( 66 | updatedOptions.display.selected !== displayOptions.display.selected 67 | || updatedOptions.display.windowed !== displayOptions.display.windowed 68 | ) { 69 | //Set selected display 70 | let displays = screen.getAllDisplays(); 71 | let externalDisplay = (updatedOptions.display.selected > displays.length) ? displays[0] : displays[updatedOptions.display.selected - 1]; 72 | displayWindow.setBounds({ 73 | x: externalDisplay.bounds.x, 74 | y: externalDisplay.bounds.y 75 | }); 76 | 77 | //Change to windowed mode? 78 | displayWindow.setFullScreen(!updatedOptions.display.windowed); 79 | if (updatedOptions.display.windowed == false) displayWindow.maximize(); 80 | } 81 | 82 | //Update options 83 | displayOptions = updatedOptions; 84 | optionsStorage.update({ _id: 'display' }, { _id: 'display', values: updatedOptions }, { upsert: true }, (err, numReplaced, upsert) => { 85 | displayOptions.display.list = getDisplayList(); 86 | mainWindow.webContents.send('slides-options', displayOptions); 87 | if (displayWindow) displayWindow.webContents.send('slides-options', displayOptions); 88 | }); 89 | } 90 | } 91 | 92 | export function getSlides(event) { 93 | //Send slides to window 94 | event.sender.webContents.send('slides-update', epub, slideshow); 95 | } 96 | 97 | export function updateSlides(event, updatedEpub = null, updatedSlideshow = null) { 98 | epub = updatedEpub || epub; 99 | slideshow = updatedSlideshow || slideshow; 100 | 101 | //Publish event to other windows 102 | BrowserWindow.getAllWindows().forEach(window => { 103 | if(window.id !== event.sender.id) { 104 | window.webContents.send('slides-update', epub, slideshow); 105 | } 106 | }); 107 | } 108 | 109 | export function toggleDisplay() { 110 | if(!displayWindow) { 111 | //Create window if not exists 112 | let displays = screen.getAllDisplays(); 113 | let externalDisplay = (displayOptions.display.selected > displays.length) ? displays[0] : displays[displayOptions.display.selected - 1]; 114 | 115 | //Create externalDisplay 116 | if (externalDisplay) { 117 | displayWindow = new BrowserWindow({ 118 | title: 'Meeting display', 119 | x: externalDisplay.bounds.x, 120 | y: externalDisplay.bounds.y, 121 | webPreferences: { 122 | webSecurity: false, 123 | nodeIntegration: true, 124 | allowRunningInsecureContent: (serve) ? true : false, 125 | }, 126 | fullscreen: !displayOptions.display.windowed, 127 | frame: false 128 | }); 129 | 130 | //Emitted when window closed 131 | displayWindow.on('closed', () => { 132 | displayWindow = null; 133 | }); 134 | 135 | //Set as fullscreen 136 | if (displayOptions.display.windowed == false) displayWindow.maximize(); 137 | 138 | //Listen for escape 139 | displayWindow.webContents.on('before-input-event', (event, input) => { 140 | if (input.key == 'Escape') { toggleDisplay(); } 141 | }); 142 | 143 | //Electron config 144 | if (serve) { 145 | displayWindow.webContents.openDevTools(); 146 | require('electron-reload')(__dirname, { 147 | electron: require(`${__dirname}/../node_modules/electron`) 148 | }); 149 | displayWindow.loadURL('http://localhost:4200#display'); 150 | } else { 151 | displayWindow.loadURL(url.format({ 152 | pathname: path.join(__dirname, '../dist/index.html'), 153 | protocol: 'file:', 154 | hash: 'display', 155 | slashes: true 156 | })); 157 | } 158 | } 159 | } else { 160 | displayWindow.close(); //Close window 161 | displayWindow = null; 162 | } 163 | } -------------------------------------------------------------------------------- /lib/EpubManager.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | var fs = require("fs"); 4 | var path = require("path"); 5 | var NeDB = require("nedb"); 6 | var cp = require("child_process"); 7 | var main_1 = require("../main"); 8 | var node_html_parser_1 = require("node-html-parser"); 9 | var electron_1 = require("electron"); 10 | var storage = new NeDB({ filename: electron_1.app.getPath('userData') + '/storage/epubs', autoload: true }); 11 | function uploadEpub() { 12 | //Open Epub 13 | electron_1.dialog.showOpenDialog({ 14 | properties: ['openFile'], 15 | filters: [{ name: 'ePub files', extensions: ['epub'] }], 16 | }).then(function (result) { 17 | try { 18 | if (result.canceled == false) { 19 | //Activate loading state 20 | main_1.mainWindow.webContents.send('epub-upload'); 21 | //Setup import process in background 22 | var importProc = cp.fork(path.resolve(__dirname, 'workers/ImportEpub')); 23 | importProc.on('message', function (_a) { 24 | var status = _a.status, _b = _a.data, data = _b === void 0 ? null : _b; 25 | //Show error or insert into db 26 | if (status == false) { 27 | //Log error 28 | console.error(data); 29 | //Show error 30 | electron_1.dialog.showMessageBox(main_1.mainWindow, { 31 | message: 'An error occured whilst importing this publication...' 32 | }); 33 | } 34 | else { 35 | //Record event 36 | main_1.Nucleus.track("EPUB_IMPORTED", { title: data.title }); 37 | //Add to database 38 | storage.insert(data); 39 | } 40 | //Reload epubs and kill process 41 | listEpubs(); 42 | importProc.kill('SIGKILL'); 43 | }); 44 | //Run import process 45 | importProc.send({ 46 | storagePath: main_1.storagePath, 47 | filePath: result.filePaths[0] 48 | }); 49 | } 50 | } 51 | catch (e) { 52 | //Log error 53 | console.error(e); 54 | //Show error 55 | electron_1.dialog.showMessageBox(main_1.mainWindow, { 56 | message: 'An error occured whilst importing this publication...' 57 | }); 58 | } 59 | }); 60 | } 61 | exports.uploadEpub = uploadEpub; 62 | function removeEpub(id) { 63 | //Show confirm dialog and remove 64 | electron_1.dialog.showMessageBox(main_1.mainWindow, { 65 | type: 'question', 66 | buttons: ['Yes', 'No'], 67 | title: 'Confirm', 68 | message: 'Are you sure you want to delete this publication?' 69 | }).then(function (result) { 70 | if (result.response == 0) { 71 | fs.rmdir("" + main_1.storagePath + id, { recursive: true }, function () { 72 | storage.remove({ id: id }, {}, function (err) { 73 | main_1.mainWindow.webContents.send('epub-remove', true); 74 | }); 75 | }); 76 | } 77 | else { 78 | main_1.mainWindow.webContents.send('epub-remove', false); 79 | } 80 | }); 81 | } 82 | exports.removeEpub = removeEpub; 83 | function listEpubs() { 84 | // Find all documents in the collection 85 | storage.find({}, function (err, docs) { 86 | main_1.mainWindow.webContents.send('epub-list', docs || []); 87 | }); 88 | } 89 | exports.listEpubs = listEpubs; 90 | function listEpubsFiltered(filters) { 91 | // Find all documents in the collection 92 | storage.find({ title: { $regex: new RegExp(filters.title.toLowerCase(), 'i') } }, function (err, docs) { 93 | main_1.mainWindow.webContents.send('epub-list', docs || []); 94 | }); 95 | } 96 | exports.listEpubsFiltered = listEpubsFiltered; 97 | function getEpub(id) { 98 | // Find document by id 99 | storage.find({ id: id }, function (err, docs) { 100 | docs[0].structure = JSON.parse(docs[0].structure); 101 | main_1.mainWindow.webContents.send('epub-get', docs[0]); 102 | }); 103 | } 104 | exports.getEpub = getEpub; 105 | function parseEpubPage(id, page) { 106 | //Get all paragraphs in a page 107 | fs.readFile("" + main_1.storagePath + id + "/OEBPS/" + page, { encoding: 'utf8' }, function (err, html) { 108 | //Return error 109 | if (err) 110 | throw err; 111 | //Parse dom 112 | var htmlDom = node_html_parser_1.parse(html); 113 | //Gather scripture footnotes 114 | var scriptures = {}; 115 | htmlDom.querySelectorAll('div.extScrpCite').forEach(function (scripture) { 116 | scriptures[scripture.getAttribute('id')] = { 117 | type: 'scripture', 118 | name: scripture.querySelector('strong').text, 119 | text: scripture.querySelector('.extScrpCiteTxt') ? scripture.querySelector('.extScrpCiteTxt').toString().replace(/)/g, '') : '' 120 | }; 121 | }); 122 | //Gather content 123 | var content = []; 124 | htmlDom.querySelectorAll('.pGroup p').forEach(function (paragraph) { 125 | //Add to final array 126 | content.push({ 127 | name: null, 128 | text: paragraph.toString(), 129 | type: paragraph.classNames.includes('qu') ? 'question' : 'paragraph' 130 | }); 131 | //Add scriptures below (child elements won't work in dragula) 132 | (paragraph.toString().match(/(href="#citation)([0-9]{0,3})/gm) || []).forEach(function (scripture) { 133 | var citationId = scripture.replace('href="#', ''); 134 | content.push(scriptures[citationId]); 135 | delete scriptures[citationId]; 136 | }); 137 | }); 138 | //Dump unused scriptures at end of content array 139 | content = content.concat(Object.values(scriptures)); 140 | //Return data 141 | main_1.mainWindow.webContents.send('epub-get-page', { content: content }); 142 | }); 143 | } 144 | exports.parseEpubPage = parseEpubPage; 145 | function getEpubPageRef(id, ref) { 146 | //Get text by reference (current use for bible only) 147 | fs.readFile("" + main_1.storagePath + id + "/OEBPS/" + ref.bookPath, { encoding: 'utf8' }, function (err, html) { 148 | //Return error 149 | if (err) 150 | throw err; 151 | //Gather chapter path 152 | var htmlDom = node_html_parser_1.parse(html); 153 | var chapters = htmlDom.querySelectorAll('.w_bibleChapter a'); 154 | //Check if chapter is valid 155 | if (ref.chapter > (chapters.length == 0 ? 1 : chapters.length - 1)) { 156 | main_1.mainWindow.webContents.send('bibleepub-get-ref', { error: 'The chapter number you have entered does not exist' }); 157 | } 158 | //Check for one chapter books 159 | if (chapters.length !== 0) { 160 | //Get chapter path 161 | var chapterNav = chapters.find(function (chapter) { 162 | return chapter.text == ref.chapter; 163 | }).getAttribute('href'); 164 | //Get chapter verses html 165 | html = fs.readFileSync("" + main_1.storagePath + id + "/OEBPS/" + chapterNav, { encoding: 'utf8' }); 166 | htmlDom = node_html_parser_1.parse(html); 167 | } 168 | //Get chapter verses 169 | var verseHTML = Array.from(htmlDom.querySelectorAll('p')).filter(function (el) { return el.hasAttribute('data-pid'); }); 170 | var verseHTMLString = verseHTML.map(function (paragraph) { return paragraph.innerHTML; }).join(" "); 171 | var verses = verseHTMLString.split(/<\/span>/); 172 | //Return selected verses 173 | var selectedVerses = []; 174 | ref.verses = ref.verses.replace(/\s/g, ''); 175 | ref.verses.match(/([0-9]{0,3}-[0-9]{0,3})|([0-9]{0,3})/g).forEach(function (match) { 176 | if (match !== "") { 177 | //Create string containing verses 178 | var text = ''; 179 | if (match.includes('-')) { 180 | text = verses.slice(parseInt(match.split('-')[0]), parseInt(match.split('-')[1]) + 1).join(' '); 181 | } 182 | else { 183 | text = verses[parseInt(match)]; 184 | } 185 | //Check if selection is valid 186 | if (!text) 187 | main_1.mainWindow.webContents.send('bibleepub-get-ref', { error: 'One or more of the entered verses does not exist' }); 188 | //Clean text before adding 189 | text = text.replace(/[^] ([A-z0-9.: ]{0,40}<\/span><\/a> )|[*]<\/a>/g, ''); 190 | selectedVerses.push({ text: text, name: ref.book + " " + ref.chapter + ":" + match }); 191 | } 192 | }); 193 | main_1.mainWindow.webContents.send('bibleepub-get-ref', selectedVerses); 194 | }); 195 | } 196 | exports.getEpubPageRef = getEpubPageRef; 197 | //# sourceMappingURL=EpubManager.js.map -------------------------------------------------------------------------------- /lib/EpubManager.ts: -------------------------------------------------------------------------------- 1 | import fs = require('fs'); 2 | import * as path from 'path'; 3 | import NeDB = require('nedb'); 4 | import cp = require('child_process'); 5 | import { mainWindow, storagePath, Nucleus } from '../main'; 6 | import { parse } from 'node-html-parser'; 7 | import { dialog, app } from 'electron'; 8 | 9 | const storage = new NeDB({ filename: app.getPath('userData') + '/storage/epubs', autoload: true }); 10 | 11 | export function uploadEpub() { 12 | //Open Epub 13 | dialog.showOpenDialog({ 14 | properties: ['openFile'], 15 | filters: [{ name: 'ePub files', extensions: ['epub'] }], 16 | }).then((result) => { 17 | try { 18 | if(result.canceled == false) { 19 | //Activate loading state 20 | mainWindow.webContents.send('epub-upload'); 21 | 22 | //Setup import process in background 23 | var importProc = cp.fork(path.resolve(__dirname, 'workers/ImportEpub')); 24 | importProc.on('message', ({ status, data = null }) => { 25 | //Show error or insert into db 26 | if(status == false) { 27 | //Log error 28 | console.error(data); 29 | 30 | //Show error 31 | dialog.showMessageBox(mainWindow, { 32 | message: 'An error occured whilst importing this publication...' 33 | }); 34 | } else { 35 | //Record event 36 | Nucleus.track("EPUB_IMPORTED", { title: data.title }); 37 | 38 | //Add to database 39 | storage.insert(data); 40 | } 41 | 42 | //Reload epubs and kill process 43 | listEpubs(); 44 | importProc.kill('SIGKILL'); 45 | }); 46 | 47 | //Run import process 48 | importProc.send({ 49 | storagePath: storagePath, 50 | filePath: result.filePaths[0] 51 | }); 52 | } 53 | } catch(e) { 54 | //Log error 55 | console.error(e); 56 | 57 | //Show error 58 | dialog.showMessageBox(mainWindow, { 59 | message: 'An error occured whilst importing this publication...' 60 | }); 61 | } 62 | }); 63 | } 64 | 65 | export function removeEpub(id) { 66 | //Show confirm dialog and remove 67 | dialog.showMessageBox(mainWindow, { 68 | type: 'question', 69 | buttons: ['Yes', 'No'], 70 | title: 'Confirm', 71 | message: 'Are you sure you want to delete this publication?' 72 | }).then((result) => { 73 | if(result.response == 0) { 74 | fs.rmdir(`${storagePath}${id}`, { recursive: true }, () => { 75 | storage.remove({ id: id }, {}, (err) => { 76 | mainWindow.webContents.send('epub-remove', true); 77 | }); 78 | }); 79 | } else { 80 | mainWindow.webContents.send('epub-remove', false); 81 | } 82 | }); 83 | } 84 | 85 | export function listEpubs() { 86 | // Find all documents in the collection 87 | storage.find({}, (err, docs) => { 88 | mainWindow.webContents.send('epub-list', docs || []); 89 | }); 90 | } 91 | 92 | export function listEpubsFiltered(filters) { 93 | // Find all documents in the collection 94 | storage.find({ title: { $regex: new RegExp(filters.title.toLowerCase(), 'i') } }, (err, docs) => { 95 | mainWindow.webContents.send('epub-list', docs || []); 96 | }); 97 | } 98 | 99 | export function getEpub(id) { 100 | // Find document by id 101 | storage.find({ id: id }, (err, docs) => { 102 | docs[0].structure = JSON.parse(docs[0].structure); 103 | mainWindow.webContents.send('epub-get', docs[0]); 104 | }); 105 | } 106 | 107 | export function parseEpubPage(id, page) { 108 | //Get all paragraphs in a page 109 | fs.readFile(`${storagePath}${id}/OEBPS/${page}`, { encoding: 'utf8'}, (err, html) => { 110 | //Return error 111 | if (err) throw err; 112 | 113 | //Parse dom 114 | let htmlDom = parse(html); 115 | 116 | //Gather scripture footnotes 117 | let scriptures = {}; 118 | htmlDom.querySelectorAll('div.extScrpCite').forEach((scripture) => { 119 | scriptures[scripture.getAttribute('id')] = { 120 | type: 'scripture', 121 | name: scripture.querySelector('strong').text, 122 | text: scripture.querySelector('.extScrpCiteTxt') ? scripture.querySelector('.extScrpCiteTxt').toString().replace(/)/g, '') : '' 123 | }; 124 | }); 125 | 126 | //Gather content 127 | let content = []; 128 | htmlDom.querySelectorAll('.pGroup p').forEach((paragraph) => { 129 | //Add to final array 130 | content.push({ 131 | name: null, 132 | text: paragraph.toString(), 133 | type: paragraph.classNames.includes('qu') ? 'question' : 'paragraph' 134 | }); 135 | 136 | //Add scriptures below (child elements won't work in dragula) 137 | (paragraph.toString().match(/(href="#citation)([0-9]{0,3})/gm) || []).forEach((scripture) => { 138 | let citationId = scripture.replace('href="#', ''); 139 | content.push(scriptures[citationId]); 140 | delete scriptures[citationId]; 141 | }); 142 | }); 143 | 144 | //Dump unused scriptures at end of content array 145 | content = content.concat(Object.values(scriptures)); 146 | 147 | //Return data 148 | mainWindow.webContents.send('epub-get-page', { content: content }); 149 | }); 150 | } 151 | 152 | export function getEpubPageRef(id, ref) { 153 | //Get text by reference (current use for bible only) 154 | fs.readFile(`${storagePath}${id}/OEBPS/${ref.bookPath}`, { encoding: 'utf8' }, (err, html) => { 155 | //Return error 156 | if (err) throw err; 157 | 158 | //Gather chapter path 159 | let htmlDom = parse(html); 160 | let chapters = htmlDom.querySelectorAll('.w_bibleChapter a'); 161 | 162 | //Check if chapter is valid 163 | if(ref.chapter > (chapters.length == 0 ? 1 : chapters.length - 1)) { 164 | mainWindow.webContents.send('bibleepub-get-ref', { error: 'The chapter number you have entered does not exist' }); 165 | } 166 | 167 | //Check for one chapter books 168 | if(chapters.length !== 0) { 169 | //Get chapter path 170 | let chapterNav = chapters.find((chapter) => { 171 | return chapter.text == ref.chapter; 172 | }).getAttribute('href'); 173 | 174 | //Get chapter verses html 175 | html = fs.readFileSync(`${storagePath}${id}/OEBPS/${chapterNav}`, { encoding: 'utf8' }); 176 | htmlDom = parse(html); 177 | } 178 | 179 | //Get chapter verses 180 | let verseHTML = Array.from(htmlDom.querySelectorAll('p')).filter(el => el.hasAttribute('data-pid')); 181 | let verseHTMLString = verseHTML.map(function (paragraph) { return paragraph.innerHTML; }).join(" "); 182 | let verses = verseHTMLString.split(/<\/span>/); 183 | 184 | //Return selected verses 185 | let selectedVerses = []; 186 | ref.verses = ref.verses.replace(/\s/g, ''); 187 | ref.verses.match(/([0-9]{0,3}-[0-9]{0,3})|([0-9]{0,3})/g).forEach(match => { 188 | if (match !== "") { 189 | //Create string containing verses 190 | let text = ''; 191 | if(match.includes('-')) { 192 | text = verses.slice(parseInt(match.split('-')[0]), parseInt(match.split('-')[1]) + 1).join(' '); 193 | } else { 194 | text = verses[parseInt(match)]; 195 | } 196 | 197 | //Check if selection is valid 198 | if(!text) mainWindow.webContents.send('bibleepub-get-ref', { error: 'One or more of the entered verses does not exist'}); 199 | 200 | //Clean text before adding 201 | text = text.replace(/[^] ([A-z0-9.: ]{0,40}<\/span><\/a> )|[*]<\/a>/g, ''); 202 | selectedVerses.push({ text: text, name: `${ref.book} ${ref.chapter}:${match}`}); 203 | } 204 | }); 205 | 206 | mainWindow.webContents.send('bibleepub-get-ref', selectedVerses); 207 | }); 208 | } -------------------------------------------------------------------------------- /lib/workers/ImportEpub.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const glob = require('glob'); 4 | const extract = require('extract-zip'); 5 | const { parse } = require('node-html-parser'); 6 | const { parseEpub } = require('@gxl/epub-parser'); 7 | 8 | process.on('uncaughtException', (err) => { 9 | console.log(err); 10 | process.send({ status: false }); 11 | }); 12 | 13 | process.on('message', ({storagePath, filePath}) => { 14 | try { 15 | //Import epub 16 | parseEpub(filePath, { 17 | type: 'path', 18 | }).then((ePub) => { 19 | //Create file id 20 | let filename = path.basename(filePath).replace('.epub', ''); 21 | filename = filename.toLowerCase().replace(/[^\w ]+/g, '').replace(/ +/g, '-'); 22 | 23 | //Create storage dir 24 | fs.mkdirSync(`${storagePath}${filename}`, { recursive: true }); 25 | 26 | //Extract zip folder 27 | extract(filePath, { dir: `${storagePath}${filename}` }).then(() => { 28 | //Search for cover image 29 | glob(`{${storagePath}${filename}/OEBPS/images/*_cvr.jpg,${storagePath}${filename}/OEBPS/images/${filename}.jpg}`, {}, (err, images) => { 30 | //Get publication type 31 | let type = (/nwt(.*)/g.test(filename)) ? 'bible' : 'pub' 32 | 33 | //Check if pub 34 | if (type == 'pub') { 35 | //Insert pub 36 | process.send({ 37 | status: true, 38 | data: { 39 | id: filename, 40 | type: type, 41 | title: ePub.info.title, 42 | author: ePub.info.publisher, 43 | structure: JSON.stringify(ePub.structure), 44 | image: `${filename}/OEBPS/images/${path.basename(images[0])}`, 45 | } 46 | }); 47 | } else { 48 | //Insert bible 49 | process.send({ 50 | status: true, 51 | data: { 52 | id: filename, 53 | type: type, 54 | title: ePub.info.title, 55 | author: ePub.info.publisher, 56 | structure: (() => { 57 | //Read bible book nav file instead 58 | let html = fs.readFileSync(`${storagePath}${filename}/OEBPS/biblebooknav.xhtml`, { encoding: 'utf8' }); 59 | 60 | //Return error 61 | if (err) throw err; 62 | 63 | //Parse dom 64 | let htmlDom = parse(html); 65 | 66 | //Gather scripture footnotes 67 | let books = []; 68 | htmlDom.querySelectorAll('.w_bibleBook a').forEach((book) => { 69 | books.push({ 70 | name: book.innerHTML, 71 | path: book.getAttribute('href'), 72 | }); 73 | }); 74 | 75 | return JSON.stringify(books); 76 | })(), 77 | image: `${filename}/OEBPS/images/${path.basename(images[0])}` 78 | } 79 | }); 80 | } 81 | }); 82 | }); 83 | }); 84 | } catch(err) { 85 | process.send({ 86 | status: false, 87 | data: err 88 | }); 89 | } 90 | }); -------------------------------------------------------------------------------- /main.ts: -------------------------------------------------------------------------------- 1 | import * as url from 'url'; 2 | import * as path from 'path'; 3 | import debug = require('electron-debug'); 4 | import { autoUpdater } from 'electron-updater'; 5 | import { app, BrowserWindow, screen, protocol, ipcMain, dialog } from 'electron'; 6 | 7 | import { showToolbar, hideToolbar, toolbarWindow } from './lib/ControlToolbar'; 8 | import { displayWindow, toggleDisplay, controlDisplay, getSlides, updateSlides, updateDisplayOptions } from './lib/DisplaySlide'; 9 | import { uploadEpub, listEpubs, listEpubsFiltered, getEpub, getEpubPageRef, parseEpubPage, removeEpub } from './lib/EpubManager'; 10 | 11 | //debug(); 12 | 13 | //Setup nucleus analytics - anonymous 14 | export const Nucleus = require('nucleus-nodejs'); 15 | Nucleus.init('5f13691da5d05e6842655618', { autoUserId: true }); 16 | 17 | //Load settings db 18 | import NeDB = require('nedb'); 19 | export const optionsStorage = new NeDB({ filename: app.getPath('userData') + '/storage/preferences', autoload: true, onload: (err) => { 20 | console.log('db loaded'); 21 | console.log(err); 22 | }}); 23 | 24 | export var mainWindow: BrowserWindow = null; 25 | export const storagePath = app.getPath('userData') + '/storage/'; 26 | export const args = process.argv.slice(1), serve = args.some(val => val === '--serve'); 27 | 28 | function createWindow(): BrowserWindow { 29 | 30 | const electronScreen = screen; 31 | const size = electronScreen.getPrimaryDisplay().workAreaSize; 32 | 33 | // Create the browser window. 34 | mainWindow = new BrowserWindow({ 35 | x: 0, 36 | y: 0, 37 | frame: false, 38 | width: size.width, 39 | height: size.height, 40 | webPreferences: { 41 | webSecurity: false, 42 | nodeIntegration: true, 43 | nodeIntegrationInWorker: true, 44 | allowRunningInsecureContent: (serve) ? true : false, 45 | }, 46 | title: 'Meeting display' 47 | }); 48 | mainWindow.removeMenu(); 49 | app.commandLine.appendSwitch('ignore-certificate-errors'); 50 | 51 | //Electron config 52 | if (serve) { 53 | mainWindow.webContents.openDevTools(); 54 | require('electron-reload')(__dirname, { 55 | electron: require(`${__dirname}/node_modules/electron`) 56 | }); 57 | mainWindow.loadURL('http://localhost:4200'); 58 | } else { 59 | mainWindow.loadURL(url.format({ 60 | pathname: path.join(__dirname, 'dist/index.html'), 61 | protocol: 'file:', 62 | slashes: true 63 | })); 64 | } 65 | 66 | //Emitted when the window is closed. 67 | mainWindow.on('closed', () => { 68 | mainWindow = null; //Dereference the window object 69 | displayWindow.close(); 70 | toolbarWindow.close(); 71 | }); 72 | 73 | //Check for updates 74 | mainWindow.webContents.on('did-finish-load', () => { 75 | autoUpdater.checkForUpdatesAndNotify(); 76 | }); 77 | 78 | //Alert when new update available 79 | autoUpdater.on('update-downloaded', (event, releaseNotes, releaseName) => { 80 | const dialogOpts = { 81 | type: 'info', 82 | buttons: ['Restart now', 'Later'], 83 | title: 'Update available', 84 | message: process.platform === 'win32' ? releaseNotes : releaseName, 85 | detail: 'A new version of this application has been downloaded, restart it to apply the updates.' 86 | } 87 | 88 | dialog.showMessageBox(dialogOpts).then((returnValue) => { 89 | if (returnValue.response === 0) autoUpdater.quitAndInstall() 90 | }) 91 | }) 92 | 93 | //Log errors 94 | autoUpdater.on('error', message => { 95 | console.error('There was a problem updating the application') 96 | console.error(message) 97 | }) 98 | 99 | return mainWindow; 100 | } 101 | 102 | try { 103 | 104 | app.allowRendererProcessReuse = true; 105 | 106 | app.on('ready', () => { 107 | //Register file protocol for assets folder 108 | protocol.registerFileProtocol('assets', (request, callback) => { 109 | const url = request.url.substr(9, request.url.length) 110 | callback({ path: __dirname + '/dist/assets/' + url }) 111 | }, (error) => { 112 | if (error) console.error('Failed to register protocol') 113 | }); 114 | 115 | //Register file protocol for app data folder 116 | protocol.registerFileProtocol('app', (request, callback) => { 117 | const url = request.url.substr(6, request.url.length) 118 | callback({ path: app.getPath('userData') + '/storage/' + url }) 119 | }, (error) => { 120 | if (error) console.error('Failed to register protocol') 121 | }); 122 | 123 | //400 ms - fixes black background issue see https://github.com/electron/electron/issues/15947 124 | Nucleus.appStarted(); 125 | setTimeout(createWindow, 400); 126 | }); 127 | 128 | // Quit when all windows are closed. 129 | app.on('window-all-closed', () => { 130 | if (process.platform !== 'darwin') { 131 | app.quit(); 132 | } 133 | }); 134 | 135 | // Create window on activate 136 | app.on('activate', () => { 137 | if (mainWindow === null) { 138 | createWindow(); 139 | } 140 | }); 141 | 142 | } catch (e) { 143 | // Catch Error 144 | throw e; 145 | } 146 | 147 | //Listen for window focus & hide 148 | ipcMain.on('toggle-toolbar', (event, isHidden) => { 149 | if (isHidden) { 150 | //Add toolbar to bottom of screen 151 | showToolbar(); 152 | } else { 153 | //Close toolbar 154 | hideToolbar(); 155 | } 156 | }); 157 | 158 | // Catch node errors 159 | process.on('uncaughtException', (err) => { 160 | console.log(err); 161 | }) 162 | 163 | // Set api routes 164 | ipcMain.on('epub-list', () => { listEpubs(); }); 165 | ipcMain.on('epub-upload', () => { uploadEpub(); }); 166 | ipcMain.on('epub-get', (event, id) => { getEpub(id); }); 167 | ipcMain.on('slides-get', (event) => { getSlides(event); }); 168 | ipcMain.on('epub-remove', (event, id) => { removeEpub(id); }); 169 | ipcMain.on('slides-display', (event) => { toggleDisplay(); }); 170 | ipcMain.on('epub-list-filter', (event, filters) => { listEpubsFiltered(filters); }); 171 | ipcMain.on('epub-get-page', (event, id, page) => { parseEpubPage(id, page); }); 172 | ipcMain.on('bibleepub-get-ref', (event, id, ref) => { getEpubPageRef(id, ref); }); 173 | ipcMain.on('slides-options', (event, options = null) => { updateDisplayOptions(options); }); 174 | ipcMain.on('slides-control', (event, action, ...args) => { controlDisplay(action, ...args); }); 175 | ipcMain.on('slides-update', (event, selectedEpub = null, slideList = null) => { updateSlides(event, selectedEpub, slideList); }); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "meeting-display", 3 | "version": "2.0.4", 4 | "description": "Basic application for displaying books & magazines in epub format from the jw website in congregation meetings.", 5 | "author": { 6 | "name": "Luke Tinnion", 7 | "email": "luke.tinnion@icloud.com" 8 | }, 9 | "keywords": [], 10 | "main": "main.js", 11 | "private": true, 12 | "scripts": { 13 | "ng": "ng", 14 | "ng:serve": "ng serve", 15 | "ng:serve:web": "ng serve -c web -o", 16 | "build:dev": "npm run build -- -c dev", 17 | "build:prod": "npm run build -- -c production", 18 | "start": "npm-run-all -p electron:serve ng:serve", 19 | "electron:serve-tsc": "tsc -p tsconfig-serve.json", 20 | "build": "npm run electron:serve-tsc && ng build --base-href ./", 21 | "deploy:app": "npm run build:prod && electron-builder build --win --config electron-builder.yml --publish always", 22 | "electron:serve": "npm run electron:serve-tsc && npx electron . --serve", 23 | "electron:local": "npm run build:prod && npx electron .", 24 | "electron:build": "npm run build:prod && electron-builder build --config electron-builder.yml", 25 | "e2e": "npm run build:prod && cross-env TS_NODE_PROJECT='e2e/tsconfig.e2e.json' mocha --timeout 300000 --require ts-node/register e2e/**/*.e2e.ts", 26 | "version": "conventional-changelog -i CHANGELOG.md -s -r 0 && git add CHANGELOG.md", 27 | "test": "ng test --watch=false", 28 | "test:watch": "ng test", 29 | "lint": "ng lint" 30 | }, 31 | "devDependencies": { 32 | "@angular-builders/custom-webpack": "9.1.0", 33 | "@angular-devkit/build-angular": "0.901.9", 34 | "@angular-eslint/builder": "0.0.1-alpha.32", 35 | "@angular-eslint/eslint-plugin": "0.0.1-alpha.32", 36 | "@angular-eslint/eslint-plugin-template": "0.0.1-alpha.32", 37 | "@angular-eslint/template-parser": "0.0.1-alpha.32", 38 | "@angular/cli": "9.1.11", 39 | "@angular/common": "9.1.11", 40 | "@angular/compiler": "9.1.11", 41 | "@angular/compiler-cli": "9.1.11", 42 | "@angular/core": "9.1.11", 43 | "@angular/forms": "9.1.11", 44 | "@angular/language-service": "9.1.11", 45 | "@angular/platform-browser": "9.1.11", 46 | "@angular/platform-browser-dynamic": "9.1.11", 47 | "@angular/router": "9.1.11", 48 | "@ngx-translate/core": "12.1.2", 49 | "@ngx-translate/http-loader": "5.0.0", 50 | "@types/jasmine": "3.5.10", 51 | "@types/jasminewd2": "2.0.8", 52 | "@types/lodash": "4.14.170", 53 | "@types/mocha": "7.0.2", 54 | "@types/node": "12.11.1", 55 | "@typescript-eslint/eslint-plugin": "3.3.0", 56 | "@typescript-eslint/eslint-plugin-tslint": "3.3.0", 57 | "@typescript-eslint/parser": "3.3.0", 58 | "chai": "4.2.0", 59 | "conventional-changelog-cli": "2.0.34", 60 | "core-js": "3.6.5", 61 | "cross-env": "7.0.2", 62 | "electron": "9.4.0", 63 | "electron-builder": "22.7.0", 64 | "electron-reload": "1.5.0", 65 | "eslint": "7.3.0", 66 | "eslint-plugin-import": "2.21.2", 67 | "jasmine-core": "3.5.0", 68 | "jasmine-spec-reporter": "5.0.2", 69 | "karma": "5.1.0", 70 | "karma-coverage-istanbul-reporter": "3.0.3", 71 | "karma-electron": "6.3.0", 72 | "karma-jasmine": "3.3.1", 73 | "karma-jasmine-html-reporter": "1.5.4", 74 | "mocha": "8.0.1", 75 | "npm-run-all": "4.1.5", 76 | "rxjs": "6.5.5", 77 | "spectron": "11.0.0", 78 | "ts-node": "8.10.2", 79 | "tslib": "2.0.0", 80 | "typescript": "3.8.3", 81 | "wait-on": "5.0.1", 82 | "webdriver-manager": "12.1.7", 83 | "zone.js": "0.10.3" 84 | }, 85 | "engines": { 86 | "node": ">=10.13.0" 87 | }, 88 | "dependencies": { 89 | "@gxl/epub-parser": "2.0.2", 90 | "@types/nedb": "1.8.9", 91 | "electron-debug": "3.1.0", 92 | "electron-updater": "4.3.1", 93 | "extract-zip": "2.0.1", 94 | "glob": "7.1.6", 95 | "nedb": "1.8.0", 96 | "ng2-dragula": "2.1.1", 97 | "ngx-electron": "2.2.0", 98 | "ngx-fomantic-ui": "0.11.7", 99 | "node-html-parser": "1.2.20", 100 | "nucleus-nodejs": "3.0.6" 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/app/app.component.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
-------------------------------------------------------------------------------- /src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | :host { 2 | 3 | } -------------------------------------------------------------------------------- /src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { TestBed, async } from '@angular/core/testing'; 2 | import { RouterTestingModule } from '@angular/router/testing'; 3 | import { AppComponent } from './app.component'; 4 | import { TranslateModule } from '@ngx-translate/core'; 5 | import { ElectronService } from './core/services'; 6 | 7 | describe('AppComponent', () => { 8 | beforeEach(async(() => { 9 | TestBed.configureTestingModule({ 10 | declarations: [AppComponent], 11 | providers: [ElectronService], 12 | imports: [RouterTestingModule, TranslateModule.forRoot()] 13 | }).compileComponents(); 14 | })); 15 | 16 | it('should create the app', async(() => { 17 | const fixture = TestBed.createComponent(AppComponent); 18 | const app = fixture.debugElement.componentInstance; 19 | expect(app).toBeTruthy(); 20 | })); 21 | }); 22 | -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.scss'] 7 | }) 8 | export class AppComponent { 9 | 10 | toggles = { menu: false, addEpub: false }; 11 | 12 | constructor() {} 13 | 14 | ngOnInit() {} 15 | } 16 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import 'reflect-metadata'; 2 | import '../polyfills'; 3 | 4 | import { NgModule } from '@angular/core'; 5 | import { FormsModule } from '@angular/forms'; 6 | import { SharedModule } from './shared/shared.module'; 7 | import { Routes, RouterModule } from '@angular/router'; 8 | import { HttpClientModule } from '@angular/common/http'; 9 | import { BrowserModule } from '@angular/platform-browser'; 10 | 11 | import { AppComponent } from './app.component'; 12 | import { HomeComponent } from './home/home.component'; 13 | 14 | import { DragulaModule } from 'ng2-dragula'; 15 | import { NgxElectronModule } from 'ngx-electron'; 16 | import { DisplayComponent } from './display/display/display.component'; 17 | import { ToolbarComponent } from './display/controller/toolbar.component'; 18 | import { PubControllerComponent } from './display/controller/pub/controller.component'; 19 | import { BibleControllerComponent } from './display/controller/bible/controller.component'; 20 | import { MenuLayoutComponent } from './shared/components/menu-layout/menu-layout.component'; 21 | 22 | const routes: Routes = [ 23 | { 24 | path: '', 25 | component: MenuLayoutComponent, 26 | children: [ 27 | { 28 | path: '', 29 | component: HomeComponent 30 | }, 31 | { 32 | path: 'controller/pub/:id', 33 | component: PubControllerComponent 34 | }, 35 | { 36 | path: 'controller/bible/:id', 37 | component: BibleControllerComponent 38 | } 39 | ] 40 | }, 41 | { 42 | path: 'display', 43 | component: DisplayComponent 44 | }, 45 | { 46 | path: 'controller/toolbar', 47 | component: ToolbarComponent 48 | } 49 | ]; 50 | 51 | @NgModule({ 52 | declarations: [ 53 | AppComponent, 54 | HomeComponent, 55 | DisplayComponent, 56 | ToolbarComponent, 57 | MenuLayoutComponent, 58 | PubControllerComponent, 59 | BibleControllerComponent 60 | ], 61 | imports: [ 62 | BrowserModule, 63 | FormsModule, 64 | HttpClientModule, 65 | SharedModule, 66 | NgxElectronModule, 67 | DragulaModule.forRoot(), 68 | RouterModule.forRoot(routes, { useHash: true }) 69 | ], 70 | providers: [], 71 | bootstrap: [AppComponent] 72 | }) 73 | export class AppModule {} 74 | -------------------------------------------------------------------------------- /src/app/display/controller/bible/controller.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Display ePub

3 |
4 |
5 |

Book

6 |
7 | 8 |
9 |
10 |
11 |

Chapter

12 |
13 |
14 | 15 |
16 | Enter a single number - "22" 17 |
18 |
19 |
20 |

Verse(s)

21 |
22 |
23 | 24 |
25 | Enter a number - "1", Multiple numbers - "1,42,73" or Number range - "10-22" 26 |
27 |
28 |
29 | 30 |
31 | 32 |

{{ error }}

33 |
34 |
35 |
36 | 37 |
38 |
39 |

40 | {{ slide?.name }} 41 |
42 | 43 | 44 |
45 |
46 |

Add scriptures on the left to create a slideshow

47 |
48 |
49 |
50 |
-------------------------------------------------------------------------------- /src/app/display/controller/bible/controller.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { BibleControllerComponent } from './controller.component'; 4 | 5 | describe('BibleControllerComponent', () => { 6 | let component: BibleControllerComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [BibleControllerComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(BibleControllerComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/display/controller/bible/controller.component.ts: -------------------------------------------------------------------------------- 1 | import { ActivatedRoute, Params } from '@angular/router'; 2 | import { Component, OnInit, ChangeDetectorRef } from '@angular/core'; 3 | 4 | import { ElectronService } from 'ngx-electron'; 5 | import { ControllerComponent } from '../controller.component'; 6 | import { SlidesService, Epub } from '../../../shared/services/slides.service'; 7 | 8 | @Component({ 9 | selector: 'app-bible-controller', 10 | templateUrl: './controller.component.html', 11 | styleUrls: ['../controller.component.scss'] 12 | }) 13 | export class BibleControllerComponent extends ControllerComponent implements OnInit { 14 | 15 | error = null; 16 | selection = { bookIndex: null, bookPath: '', book: '', chapter: null, verses: '' }; 17 | 18 | constructor( 19 | public slidesService: SlidesService, 20 | public electronService: ElectronService, 21 | public changeDetector: ChangeDetectorRef, 22 | private activatedRoute: ActivatedRoute, 23 | ) { super(slidesService, electronService, changeDetector) } 24 | 25 | ngOnInit() { 26 | //Get epub content 27 | super.ngOnInit(); 28 | this.activatedRoute.params.subscribe((params: Params) => { 29 | this.electronService.ipcRenderer.send('epub-get', params['id']); 30 | this.electronService.ipcRenderer.once('epub-get', (event, epub) => { 31 | this.epub = epub; 32 | this.changeDetector.detectChanges(); 33 | }); 34 | }); 35 | } 36 | 37 | selectBook(event) { 38 | if (!event.keyCode || [37,38,39,40].includes(event.keyCode)) { 39 | if (event.keyCode) { 40 | //Select via index 41 | switch(event.keyCode) { 42 | case 37: 43 | this.selection.bookIndex--; 44 | break; 45 | case 38: 46 | this.selection.bookIndex -= 7; 47 | break; 48 | case 39: 49 | this.selection.bookIndex++; 50 | break; 51 | case 40: 52 | this.selection.bookIndex += 7; 53 | break; 54 | } 55 | } else { 56 | //Move with arrows 57 | this.selection.bookIndex = event; 58 | } 59 | 60 | this.selection.bookPath = this.epub.structure[this.selection.bookIndex].path; 61 | this.selection.book = this.epub.structure[this.selection.bookIndex].name; 62 | this.changeDetector.detectChanges(); 63 | } 64 | } 65 | 66 | addScripture() { 67 | this.electronService.ipcRenderer.send('bibleepub-get-ref', this.epub.id, this.selection); 68 | this.electronService.ipcRenderer.once('bibleepub-get-ref', (event, content) => { 69 | if(content.error) { 70 | this.error = content.error; 71 | } else { 72 | this.error = null; 73 | content.forEach((item) => { 74 | this.slideshow.slides.push({ 75 | uid: Math.floor(Math.random() * 1000 + Date.now()), 76 | spans: 1, 77 | activeSpan: 0, 78 | text: item.text, 79 | name: (item.name || null) 80 | }); 81 | }); 82 | this.slidesService.updateSlides(this.epub, this.slideshow); 83 | } 84 | this.changeDetector.detectChanges(); 85 | }); 86 | } 87 | 88 | ngOnDestroy() { 89 | //Reset slides 90 | this.slidesService.updateSlides(this.epub, { slides: [], active: 0 }); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/app/display/controller/controller.component.scss: -------------------------------------------------------------------------------- 1 | .grid:first-of-type { 2 | padding: 16px; 3 | } 4 | .pages, .paragraphs { 5 | border-style: solid; 6 | border-width: 0px 1px; 7 | padding-top: 0rem !important; 8 | padding-bottom: 0rem !important; 9 | border-color: rgba(34, 36, 38, 0.1); 10 | } 11 | 12 | .list { 13 | overflow-y: auto; 14 | padding-right: 15px; 15 | height: calc(100vh - 16rem); 16 | 17 | .item { 18 | cursor: grab; 19 | position: relative; 20 | padding: 6px 40px 0px 4px; 21 | } 22 | .item.active { 23 | background-color: #eaedef; 24 | } 25 | } 26 | 27 | .paragraphs { 28 | padding-top: 0 !important; 29 | 30 | .citation { 31 | color: #fff; 32 | background: #3e96ca; 33 | margin-bottom: 5px; 34 | display: inline-block; 35 | border-radius: 5px; 36 | padding: 2px 6px !important; 37 | font-size: 0.9rem; 38 | margin-right: 5px; 39 | } 40 | } 41 | 42 | .actions { 43 | top: 4px; 44 | right: 0px; 45 | color: #3e96ca; 46 | position: absolute; 47 | 48 | .icon { 49 | cursor: pointer; 50 | } 51 | } 52 | 53 | .input + .message { 54 | display: block; 55 | margin-top: 6px; 56 | } 57 | 58 | /* Publication pages */ 59 | .pages { 60 | 61 | .input { 62 | width: 100%; 63 | } 64 | .title { 65 | padding: 4px 0px; 66 | display: block; 67 | } 68 | 69 | } 70 | 71 | /* Bible books */ 72 | .bible-select { 73 | 74 | overflow-y: auto; 75 | margin-left: -1rem; 76 | height: calc(100vh - 11rem); 77 | padding-top: .2rem !important; 78 | 79 | .header { 80 | padding-bottom: .78571429em !important; 81 | } 82 | 83 | .input { 84 | width: 100%; 85 | } 86 | 87 | .book { 88 | margin: 0.5%; 89 | width: 13.25%; 90 | overflow: hidden; 91 | padding: 10px 4px; 92 | white-space: nowrap; 93 | text-overflow: ellipsis; 94 | } 95 | .book:hover, .book.active { 96 | color: #fff; 97 | background: #3e96ca; 98 | } 99 | 100 | .add { 101 | float: right; 102 | margin-top: 1.4rem; 103 | } 104 | 105 | } -------------------------------------------------------------------------------- /src/app/display/controller/controller.component.ts: -------------------------------------------------------------------------------- 1 | import { ElectronService } from 'ngx-electron'; 2 | import { SlidesService, Epub } from '../../shared/services/slides.service'; 3 | import { ChangeDetectorRef, Component, HostListener } from '@angular/core'; 4 | 5 | @Component({ 6 | selector: 'app-controller', 7 | template: ``, 8 | }) 9 | export class ControllerComponent { 10 | 11 | epub: Epub; 12 | slideshow = { slides: [], active: 0 }; 13 | 14 | constructor( 15 | public slidesService: SlidesService, 16 | public electronService: ElectronService, 17 | public changeDetector: ChangeDetectorRef, 18 | ) { } 19 | 20 | ngOnInit() { 21 | //Listen for slideshow updates 22 | this.slidesService.slideshow.subscribe((slideshow) => { 23 | this.slideshow = slideshow; 24 | this.changeDetector.detectChanges(); 25 | }); 26 | } 27 | 28 | ngOnDestroy() { 29 | //Reset slides 30 | this.slidesService.updateSlides(this.epub, { slides: [], active:0 }); 31 | } 32 | 33 | @HostListener('document:visibilitychange', ['$event']) 34 | visibilityChange() { 35 | this.electronService.ipcRenderer.send('toggle-toolbar', document.hidden); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/app/display/controller/pub/controller.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Display ePub

3 |
4 | 19 |
20 | 25 |
26 | 27 |
28 |

29 |
30 | 31 | {{ content.name }} 32 | 33 |
34 |

Select an article on the left to show paragraphs

35 |
36 |
37 |
38 | 39 | 50 |
51 |
52 |
-------------------------------------------------------------------------------- /src/app/display/controller/pub/controller.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { PubControllerComponent } from './controller.component'; 4 | 5 | describe('PubControllerComponent', () => { 6 | let component: PubControllerComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [PubControllerComponent ] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(PubControllerComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/display/controller/pub/controller.component.ts: -------------------------------------------------------------------------------- 1 | import { ActivatedRoute, Params } from '@angular/router'; 2 | import { Component, OnInit, ChangeDetectorRef } from '@angular/core'; 3 | 4 | import { DragulaService } from 'ng2-dragula'; 5 | import { ElectronService } from 'ngx-electron'; 6 | import { ControllerComponent } from '../controller.component'; 7 | import { SlidesService, Epub } from '../../../shared/services/slides.service'; 8 | 9 | @Component({ 10 | selector: 'app-pub-controller', 11 | templateUrl: './controller.component.html', 12 | styleUrls: ['../controller.component.scss'] 13 | }) 14 | export class PubControllerComponent extends ControllerComponent implements OnInit { 15 | 16 | epubPage = { content: [] }; 17 | 18 | constructor( 19 | public slidesService: SlidesService, 20 | public electronService: ElectronService, 21 | public changeDetector: ChangeDetectorRef, 22 | private dragulaService: DragulaService, 23 | private activatedRoute: ActivatedRoute, 24 | ) { 25 | //For parent 26 | super(slidesService, electronService, changeDetector); 27 | 28 | //Setup dragula 29 | dragulaService.createGroup('DRAGULA_SLIDES', { 30 | copy: (el, source) => { 31 | return source.id === 'left'; 32 | }, 33 | copyItem: (item) => ({ 34 | uid: Math.floor(Math.random() * 1000 + Date.now()), 35 | spans: 1, 36 | activeSpan: 0, 37 | text: item.text, 38 | name: (item.name || null) 39 | }), 40 | accepts: (el, target, source, sibling) => { 41 | // To avoid dragging from right to left container 42 | return target.id !== 'left'; 43 | } 44 | }); 45 | } 46 | 47 | ngOnInit() { 48 | //Get epub content 49 | super.ngOnInit(); 50 | this.activatedRoute.params.subscribe((params: Params) => { 51 | this.electronService.ipcRenderer.send('epub-get', params['id']); 52 | this.electronService.ipcRenderer.once('epub-get', (event, epub) => { 53 | this.epub = epub; 54 | this.epub.structure_filtered = epub.structure; 55 | this.changeDetector.detectChanges(); 56 | }); 57 | }); 58 | } 59 | 60 | searchTimeout; 61 | searchPages(keyword: string) { 62 | clearTimeout(this.searchTimeout); 63 | this.searchTimeout = setTimeout(() => { 64 | if(keyword == '') { 65 | this.epub.structure_filtered = this.epub.structure; 66 | } else { 67 | keyword = keyword.toLowerCase(); 68 | this.epub.structure_filtered = this.epub.structure.filter(page => page.name.toLowerCase().includes(keyword) ); 69 | } 70 | }, 600); 71 | } 72 | 73 | selectPage(page) { 74 | this.electronService.ipcRenderer.send('epub-get-page', this.epub.id, page); 75 | this.electronService.ipcRenderer.once('epub-get-page', (event, page) => { 76 | this.epubPage = page; 77 | this.changeDetector.detectChanges(); 78 | }); 79 | } 80 | 81 | addAllSlides() { 82 | //Filter slides 83 | let slides = []; 84 | this.epubPage.content.forEach((item) => { 85 | if(item.type == 'paragraph') { 86 | slides.push({ 87 | uid: Math.floor(Math.random() * 1000 + Date.now()), 88 | spans: 1, 89 | activeSpan: 0, 90 | text: item.text, 91 | name: (item.name || null) 92 | }); 93 | } 94 | }); 95 | 96 | //Add to final array 97 | slides = this.slideshow.slides.concat(slides); 98 | this.slidesService.updateSlides(this.epub, { 99 | slides: slides, 100 | active: this.slideshow.active 101 | }); 102 | } 103 | 104 | ngOnDestroy() { 105 | this.dragulaService.destroy('DRAGULA_SLIDES'); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/app/display/controller/toolbar.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ViewEncapsulation } from '@angular/core'; 2 | import { SlidesService, Epub } from '../../shared/services/slides.service'; 3 | 4 | @Component({ 5 | selector: 'app-toolbar', 6 | template: ` 7 | `, 25 | styles: [` 26 | body { 27 | overflow: hidden; 28 | background: transparent; 29 | } 30 | .ui.segment { 31 | border: 0px !important; 32 | padding: 0px !important; 33 | background: transparent; 34 | } 35 | .toolbar { 36 | width: fit-content; 37 | margin-left: 1px !important; 38 | border: 1px solid #9e9e9e !important; 39 | } 40 | .window-drag { 41 | font-size: 1.2rem; 42 | -webkit-app-region: drag; 43 | color: rgba(0,0,0,.6) !important; 44 | } 45 | `], 46 | encapsulation: ViewEncapsulation.None 47 | }) 48 | export class ToolbarComponent implements OnInit { 49 | 50 | constructor( 51 | public slidesService: SlidesService 52 | ) { } 53 | 54 | ngOnInit() {} 55 | } 56 | -------------------------------------------------------------------------------- /src/app/display/display/display.component.html: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | 20 |
21 |
22 |
23 | 24 |
25 |
26 | 27 |
28 |
29 |
30 | 31 | {{ slideshow.slides[slideshow.active]?.name }} 32 | 33 |
34 | 35 |
{{ epub?.title }}
36 |
37 |
-------------------------------------------------------------------------------- /src/app/display/display/display.component.scss: -------------------------------------------------------------------------------- 1 | /* General styles */ 2 | .display { 3 | height: 100vh; 4 | margin: -15px; 5 | 6 | .name { 7 | top: 78%; 8 | right: 6%; 9 | font-weight: 600; 10 | position: absolute; 11 | } 12 | .text-clip { 13 | top: 50%; 14 | margin: 0; 15 | padding: 0px 8%; 16 | overflow: hidden; 17 | position: absolute; 18 | transform: translateY(-50%); 19 | 20 | ::ng-deep p { 21 | font-size: inherit !important; 22 | line-height: inherit !important; 23 | } 24 | ::ng-deep sup { 25 | top: -.8em; 26 | font-size: 50%; 27 | } 28 | } 29 | } 30 | 31 | .publication { 32 | text-align: center; 33 | position: fixed; 34 | bottom: 15px; 35 | left: 15px; 36 | 37 | img { 38 | height: 80px; 39 | } 40 | h5 { 41 | margin-top: 0px; 42 | max-width: 110px; 43 | word-break: break-all; 44 | } 45 | } 46 | 47 | /* Note filter doesn't work unless added individually */ 48 | $pub-filter: blur(5px) brightness(30%); 49 | 50 | /* Background styles */ 51 | .pub-background { 52 | overflow: hidden; 53 | position: absolute; 54 | height: 100%; 55 | width: 100%; 56 | 57 | img { 58 | width: 100%; 59 | } 60 | 61 | &:before { 62 | content: ""; 63 | width: 100%; 64 | height: 100%; 65 | display: block; 66 | filter: $pub-filter; 67 | background-size: cover; 68 | background-image: url(assets://table-bg.jpg); 69 | background-position: bottom right; 70 | } 71 | 72 | .book { 73 | top: 5%; 74 | left: 32%; 75 | width: 38%; 76 | position: absolute; 77 | transform-style: preserve-3d; 78 | transform: rotateX(36deg) rotateZ(-27deg); 79 | 80 | img { 81 | filter: $pub-filter; 82 | } 83 | 84 | &:before { 85 | content: ""; 86 | width: 1.5rem; 87 | display: block; 88 | filter: $pub-filter; 89 | position: absolute; 90 | height: calc(100% - 1.8rem); 91 | background-color: #525252; 92 | transform: rotateY(90deg); 93 | transform-origin: 0px 0; 94 | } 95 | 96 | &:after { 97 | content: ""; 98 | height: 1.5rem; 99 | display: block; 100 | filter: $pub-filter; 101 | position: absolute; 102 | width: calc(100% - 0.4rem); 103 | background-color: #fff; 104 | transform: rotateX(90deg) translateX(-0.5rem); 105 | transform-origin: 0px 0; 106 | } 107 | } 108 | } 109 | .pub-background ~ .publication img { 110 | display: none; 111 | } 112 | .pub-background ~ .publication h5 { 113 | max-width: 400px !important; 114 | } 115 | 116 | /* Change view for bible */ 117 | .pub-background.bible { 118 | &:before { 119 | transform: none; 120 | background-position: center; 121 | background-image: url(assets://bible-bg.jpg); 122 | } 123 | .book { 124 | display: none; 125 | } 126 | } -------------------------------------------------------------------------------- /src/app/display/display/display.component.ts: -------------------------------------------------------------------------------- 1 | import { Router } from '@angular/router'; 2 | import { DOCUMENT } from '@angular/common'; 3 | import { Component, OnInit, ChangeDetectorRef, ViewChild, ElementRef, Inject, NgZone } from '@angular/core'; 4 | 5 | import { ElectronService } from 'ngx-electron'; 6 | import { MenuLayoutComponent } from '../../shared/components/menu-layout/menu-layout.component'; 7 | import { ModalService } from '../../shared/services/modal.service'; 8 | import { Epub, SlidesService } from '../../shared/services/slides.service'; 9 | 10 | @Component({ 11 | selector: 'app-display', 12 | templateUrl: './display.component.html', 13 | styleUrls: [ 14 | './display.component.scss', 15 | '../../shared/components/menu-layout/menu-layout.component.scss' 16 | ] 17 | }) 18 | export class DisplayComponent extends MenuLayoutComponent implements OnInit { 19 | 20 | @ViewChild('textElement') textElement: ElementRef; 21 | customStyleTag; 22 | 23 | epub: Epub; 24 | slideshow = { slides: [], active: 0 }; 25 | settings: any = { 26 | fontSize: 0, lineHeight: 0, numLines: 6, textHeight: 200, textY: 0, 27 | display: { windowed: false } 28 | }; 29 | 30 | constructor( 31 | zone: NgZone, 32 | router: Router, 33 | modalService: ModalService, 34 | @Inject(DOCUMENT) private doc, 35 | public slidesService: SlidesService, 36 | public electronService: ElectronService, 37 | private changeDetector: ChangeDetectorRef 38 | ) { 39 | super(zone, router, modalService, slidesService, electronService); 40 | } 41 | 42 | ngOnInit() {} 43 | 44 | ngAfterViewInit() { 45 | //Listen for slides update 46 | this.electronService.ipcRenderer.on('slides-update', (event, epub, slideshow) => { 47 | //Set epub, slides 48 | this.epub = epub; 49 | 50 | //Set slides and run change detection 51 | this.slideshow.active = slideshow.active; 52 | this.slideshow.slides = slideshow.slides; 53 | this.changeDetector.detectChanges(); 54 | 55 | //Recalculate text height 56 | this.calcTextHeight(); 57 | }); 58 | 59 | //Listen for slide controls 60 | this.electronService.ipcRenderer.on('slides-control', (event, action, ...args) => { 61 | //Convert action to camelCase and run 62 | action = action.replace(/([-])+\S/g, match => match.toUpperCase().replace('-', '')); 63 | this[action](...args); 64 | }); 65 | 66 | //Create and append custom style tag for settings 67 | this.customStyleTag = this.doc.createElement('style'); 68 | this.doc.head.appendChild(this.customStyleTag); 69 | 70 | //Listen for slide display options 71 | this.electronService.ipcRenderer.on('slides-options', (event, options) => { 72 | //Calculate max line number and set height 73 | this.settings.lineHeight = options.fontSize * 1.25; 74 | this.settings.numLines = Math.floor((window.innerHeight * 0.5) / this.settings.lineHeight); 75 | 76 | //Set style based on settings (not possible with ng style) 77 | this.customStyleTag.innerHTML = ` 78 | .display { 79 | color: ${options.fontColor}; 80 | font-size: ${options.fontSize}px; 81 | background-color: ${options.bgColor}; 82 | line-height: ${this.settings.lineHeight}px; 83 | } 84 | .display .text-clip { text-align: ${options.textAlign}; } 85 | .display a { color: ${options.fontLinkColor}; } 86 | `; 87 | 88 | setTimeout(() => { 89 | //Reset slides 90 | this.changeDetector.detectChanges(); 91 | if(this.settings.fontSize !== options.fontSize) { 92 | this.slideshow.active = 0; 93 | this.electronService.ipcRenderer.send('slides-get'); 94 | } 95 | 96 | //Assign settings 97 | this.settings = Object.assign(this.settings, options); 98 | this.changeDetector.detectChanges(); 99 | }, 500); 100 | }); 101 | this.electronService.ipcRenderer.send('slides-options'); 102 | } 103 | 104 | calcTextHeight() { 105 | //Calculate number of lines 106 | this.changeDetector.detectChanges(); 107 | let textHeight = this.textElement.nativeElement.offsetHeight; 108 | let numLines = Math.ceil(textHeight / this.settings.lineHeight); 109 | this.settings.textHeight = numLines >= this.settings.numLines ? (this.settings.numLines * this.settings.lineHeight) : textHeight; 110 | this.slideshow.slides[this.slideshow.active].spans = Math.ceil(numLines / this.settings.numLines); 111 | this.changeDetector.detectChanges(); 112 | } 113 | 114 | nextSlide() { 115 | //Check for slide or next span 116 | let activeSlide = this.slideshow.slides[this.slideshow.active]; 117 | if (activeSlide.activeSpan == (activeSlide.spans - 1)) { 118 | if (this.slideshow.slides[this.slideshow.active + 1]) { 119 | //Move slides forward 120 | this.slideshow.active++; 121 | this.calcTextHeight(); 122 | this.slideshow.slides[this.slideshow.active].activeSpan = 0; 123 | } 124 | } else { 125 | activeSlide.activeSpan++; 126 | } 127 | 128 | //Set y axis and change detection 129 | this.settings.textY = (this.settings.lineHeight * this.settings.numLines) * (this.slideshow.slides[this.slideshow.active].activeSpan); 130 | this.slidesService.updateSlides(this.epub, this.slideshow); 131 | this.changeDetector.detectChanges(); 132 | } 133 | 134 | previousSlide() { 135 | //Check for slide or next span 136 | let activeSlide = this.slideshow.slides[this.slideshow.active]; 137 | if (activeSlide.activeSpan == 0) { 138 | if (this.slideshow.slides[this.slideshow.active - 1]) { 139 | //Move slides backward 140 | this.slideshow.active--; 141 | this.calcTextHeight(); 142 | this.slideshow.slides[this.slideshow.active].activeSpan = this.slideshow.slides[this.slideshow.active].spans - 1; 143 | } 144 | } else { 145 | activeSlide.activeSpan--; 146 | } 147 | 148 | //Set y axis and change detection 149 | this.settings.textY = (this.settings.lineHeight * this.settings.numLines) * (this.slideshow.slides[this.slideshow.active].activeSpan); 150 | this.slidesService.updateSlides(this.epub, this.slideshow); 151 | this.changeDetector.detectChanges(); 152 | } 153 | 154 | switchSlide(index) { 155 | //Set slide active span 156 | this.slideshow.slides[this.slideshow.active].activeSpan = (index > this.slideshow.active) ? (this.slideshow.slides[this.slideshow.active].spans - 1) : 0; 157 | 158 | //Start new slide from beginning 159 | this.slideshow.active = index; 160 | this.slideshow.slides[this.slideshow.active].activeSpan = 0; 161 | 162 | //Set y axis 163 | this.calcTextHeight(); 164 | this.settings.textY = (this.settings.lineHeight * this.settings.numLines) * (this.slideshow.slides[this.slideshow.active].activeSpan); 165 | this.slidesService.updateSlides(this.epub, this.slideshow); 166 | this.changeDetector.detectChanges(); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /src/app/home/home.component.html: -------------------------------------------------------------------------------- 1 |
2 |

Uploaded ePubs

3 | 4 | 5 | 9 | 10 |
11 |
12 |
13 |
14 | 15 |
16 |
17 |

{{ epub.title }}

18 |
19 | 20 |
21 |
22 |

No uploaded epubs. Download a publication from jw.org and click import epub.

23 |
24 |
25 | 26 | 27 |
One moment please - this can take a while...
28 | 29 | -------------------------------------------------------------------------------- /src/app/home/home.component.scss: -------------------------------------------------------------------------------- 1 | .search { 2 | width: 30%; 3 | clear: both; 4 | float: left; 5 | margin-bottom: 1.2rem; 6 | } 7 | .books { 8 | clear: both; 9 | overflow-y: auto; 10 | padding-bottom: 20px; 11 | height: calc(100vh - 16rem); 12 | p { 13 | overflow: hidden; 14 | color: #616161; 15 | white-space: nowrap; 16 | text-overflow: ellipsis; 17 | } 18 | .card { 19 | cursor: pointer; 20 | } 21 | .trash { 22 | cursor: pointer; 23 | position: absolute; 24 | top: .5rem; 25 | right: .5rem; 26 | width: 1.8rem; 27 | line-height: 0; 28 | padding: 0.9rem 0px; 29 | background: #e0e1e2 none; 30 | border-radius: 50px !important; 31 | } 32 | } -------------------------------------------------------------------------------- /src/app/home/home.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { HomeComponent } from './home.component'; 4 | import { TranslateModule } from '@ngx-translate/core'; 5 | import { RouterTestingModule } from '@angular/router/testing'; 6 | 7 | describe('HomeComponent', () => { 8 | let component: HomeComponent; 9 | let fixture: ComponentFixture; 10 | 11 | beforeEach(async(() => { 12 | TestBed.configureTestingModule({ 13 | declarations: [HomeComponent], 14 | imports: [TranslateModule.forRoot(), RouterTestingModule] 15 | }).compileComponents(); 16 | })); 17 | 18 | beforeEach(() => { 19 | fixture = TestBed.createComponent(HomeComponent); 20 | component = fixture.componentInstance; 21 | fixture.detectChanges(); 22 | }); 23 | 24 | it('should create', () => { 25 | expect(component).toBeTruthy(); 26 | }); 27 | 28 | it('should render title in a h1 tag', async(() => { 29 | const compiled = fixture.debugElement.nativeElement; 30 | expect(compiled.querySelector('h1').textContent).toContain( 31 | 'PAGES.HOME.TITLE' 32 | ); 33 | })); 34 | }); 35 | -------------------------------------------------------------------------------- /src/app/home/home.component.ts: -------------------------------------------------------------------------------- 1 | import { Router } from '@angular/router'; 2 | import { ElectronService } from 'ngx-electron'; 3 | import { Component, OnInit, ChangeDetectorRef, NgZone } from '@angular/core'; 4 | import { ModalService } from '../shared/services/modal.service'; 5 | 6 | @Component({ 7 | selector: 'app-home', 8 | templateUrl: './home.component.html', 9 | styleUrls: ['./home.component.scss'] 10 | }) 11 | export class HomeComponent implements OnInit { 12 | 13 | epubs = []; 14 | loading = true; 15 | filters = { title: '' } 16 | 17 | constructor( 18 | private zone: NgZone, 19 | private router: Router, 20 | private modalService: ModalService, 21 | private electronService: ElectronService, 22 | private changeDetector: ChangeDetectorRef 23 | ) { } 24 | 25 | ngOnInit() { 26 | this.electronService.ipcRenderer.send('epub-list'); 27 | this.electronService.ipcRenderer.on('epub-list', (event, epubs) => { 28 | //Set epubs 29 | this.epubs = epubs; 30 | this.loading = false; 31 | this.changeDetector.detectChanges(); 32 | 33 | //If no epubs open help 34 | this.zone.run(() => { 35 | if (epubs.length == 0) { this.modalService.open('help'); } 36 | }); 37 | }); 38 | } 39 | 40 | searchTimeout; 41 | filterEpubList() { 42 | clearTimeout(this.searchTimeout); 43 | this.searchTimeout = setTimeout(() => { 44 | this.electronService.ipcRenderer.send('epub-list-filter', this.filters); 45 | }, 300); 46 | } 47 | 48 | openEpub(epub) { 49 | this.zone.run(() => { 50 | this.router.navigateByUrl(`/controller/${epub.type}/${epub.id}`); 51 | }); 52 | } 53 | 54 | uploadEpub() { 55 | this.electronService.ipcRenderer.send('epub-upload'); 56 | this.electronService.ipcRenderer.once('epub-upload', (event) => { 57 | this.loading = true; 58 | this.changeDetector.detectChanges(); 59 | }); 60 | } 61 | 62 | removeEpub(id) { 63 | this.loading = true; 64 | this.electronService.ipcRenderer.send('epub-remove', id); 65 | this.electronService.ipcRenderer.once('epub-remove', (event, status) => { 66 | this.loading = false; 67 | if(status == true) { 68 | this.epubs.splice(this.epubs.findIndex(epub => epub.id == id), 1); 69 | } 70 | this.changeDetector.detectChanges(); 71 | }); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/app/shared/components/display-controller/display-controller.component.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/shared/components/display-controller/display-controller.component.scss: -------------------------------------------------------------------------------- 1 | .menu .message.negative { 2 | font-size: 80%; 3 | max-width: 100%; 4 | color: #912d2b; 5 | margin-top: -1.2rem; 6 | padding-bottom: 0rem; 7 | white-space: break-spaces; 8 | } 9 | .dropdown.button { 10 | margin: -.5em 0; 11 | padding-top: .78571429em; 12 | padding-bottom: .78571429em; 13 | } 14 | ::ng-deep .dropdown ::ng-deep .buttons { 15 | width: auto; 16 | display: flex; 17 | min-width: 10rem; 18 | margin: 1.14285714rem .78571429rem; 19 | } -------------------------------------------------------------------------------- /src/app/shared/components/display-controller/display-controller.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { DisplayControllerComponent } from './display-controller.component'; 4 | 5 | describe('DisplayControllerComponent', () => { 6 | let component: DisplayControllerComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [DisplayControllerComponent] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(DisplayControllerComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/shared/components/display-controller/display-controller.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import { SlidesService } from '../../services/slides.service'; 3 | 4 | @Component({ 5 | selector: 'display-controller', 6 | templateUrl: './display-controller.component.html', 7 | styleUrls: ['./display-controller.component.scss'] 8 | }) 9 | export class DisplayControllerComponent implements OnInit { 10 | 11 | constructor( 12 | public slidesService: SlidesService, 13 | ) { } 14 | 15 | ngOnInit() {} 16 | } 17 | -------------------------------------------------------------------------------- /src/app/shared/components/menu-layout/menu-layout.component.html: -------------------------------------------------------------------------------- 1 | 29 | -------------------------------------------------------------------------------- /src/app/shared/components/menu-layout/menu-layout.component.scss: -------------------------------------------------------------------------------- 1 | .header.menu { 2 | margin: -1rem -1rem 1rem -1rem; 3 | min-height: 3rem !important; 4 | background-color: #3e96ca; 5 | -webkit-user-select: none; 6 | -webkit-app-region: drag; 7 | border-radius: 0px; 8 | z-index: 1000; 9 | img { 10 | width: 25px; 11 | filter: drop-shadow(0px 0px 2px #fff); 12 | } 13 | .item { 14 | color: #fff; 15 | cursor: pointer; 16 | font-size: 1.1rem; 17 | padding: 0 1.14285714em; 18 | -webkit-app-region: no-drag; 19 | } 20 | .icon { 21 | font-size: 0.9rem; 22 | } 23 | } -------------------------------------------------------------------------------- /src/app/shared/components/menu-layout/menu-layout.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { async, ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { MenuLayoutComponent } from './menu-layout.component'; 4 | 5 | describe('MenuLayoutComponent', () => { 6 | let component: MenuLayoutComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async(() => { 10 | TestBed.configureTestingModule({ 11 | declarations: [MenuLayoutComponent] 12 | }) 13 | .compileComponents(); 14 | })); 15 | 16 | beforeEach(() => { 17 | fixture = TestBed.createComponent(MenuLayoutComponent); 18 | component = fixture.componentInstance; 19 | fixture.detectChanges(); 20 | }); 21 | 22 | it('should create', () => { 23 | expect(component).toBeTruthy(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /src/app/shared/components/menu-layout/menu-layout.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, NgZone } from '@angular/core'; 2 | import { Router } from '@angular/router'; 3 | 4 | import { SlidesService } from '../../../shared/services/slides.service'; 5 | import { ModalService } from '../../services/modal.service'; 6 | import { ElectronService } from 'ngx-electron'; 7 | 8 | @Component({ 9 | selector: 'menu-layout', 10 | templateUrl: './menu-layout.component.html', 11 | styleUrls: ['./menu-layout.component.scss'] 12 | }) 13 | export class MenuLayoutComponent implements OnInit { 14 | 15 | window = { maximizable: false }; 16 | 17 | constructor( 18 | private zone: NgZone, 19 | private router: Router, 20 | public modalService: ModalService, 21 | public slidesService: SlidesService, 22 | public electronService: ElectronService 23 | ) {} 24 | 25 | ngOnInit() { 26 | //Check if window can be maximised 27 | this.window.maximizable = !this.electronService.remote.getCurrentWindow().isMaximized(); 28 | } 29 | 30 | uploadEpub() { 31 | this.zone.run(() => { this.router.navigateByUrl(`/`); }); 32 | this.electronService.ipcRenderer.send('epub-upload'); 33 | } 34 | 35 | /* Window events */ 36 | minimizeWindow() { 37 | this.electronService.remote.getCurrentWindow().minimize(); 38 | } 39 | 40 | maximizeWindow() { 41 | let mainWindow = this.electronService.remote.getCurrentWindow(); 42 | if (mainWindow.isMaximized()) { 43 | console.log('restore'); 44 | mainWindow.restore(); 45 | this.window.maximizable = true; 46 | } else { 47 | console.log('maximize'); 48 | mainWindow.maximize(); 49 | this.window.maximizable = false; 50 | } 51 | } 52 | 53 | closeWindow() { 54 | this.electronService.remote.getCurrentWindow().close(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/app/shared/modals/help/help.component.html: -------------------------------------------------------------------------------- 1 |
Help guide
2 |
3 |
4 | 12 |
13 |
14 |
15 |
16 |
-------------------------------------------------------------------------------- /src/app/shared/modals/help/help.component.scss: -------------------------------------------------------------------------------- 1 | .help { 2 | .menu { 3 | font-size: 1.1rem; 4 | 5 | .item { 6 | font-weight: bold; 7 | } 8 | .item.active { 9 | color: #3e96ca; 10 | } 11 | } 12 | .content { 13 | color: #333; 14 | font-size: 1.1rem; 15 | max-height: 50vh; 16 | overflow-y: auto; 17 | } 18 | ::ng-deep { 19 | ul, ol { 20 | padding-inline-start: 1em; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/app/shared/modals/help/help.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ChangeDetectorRef } from '@angular/core'; 2 | import { HttpClient } from '@angular/common/http'; 3 | 4 | import { SlidesService } from '../../services/slides.service'; 5 | import { ElectronService } from 'ngx-electron'; 6 | 7 | @Component({ 8 | selector: 'help', 9 | styleUrls: ['./help.component.scss'], 10 | templateUrl: './help.component.html', 11 | }) 12 | 13 | export class HelpComponent implements OnInit { 14 | 15 | contents = { list: [], active: 0, activeHtml: '' }; 16 | 17 | constructor( 18 | private http: HttpClient, 19 | public slidesService: SlidesService, 20 | private electronService: ElectronService, 21 | private changeDetector: ChangeDetectorRef 22 | ) { } 23 | 24 | ngOnInit() { 25 | //Load help docs 26 | if (this.contents.list.length == 0) { 27 | this.http.get('http://01coding.co.uk/meeting-display/docs/contents.json').subscribe((contents: any) => { 28 | this.contents.list = contents; 29 | this.selectHelpGuide(0); 30 | }); 31 | } 32 | } 33 | 34 | selectHelpGuide(index) { 35 | if (this.contents.list[index].link.includes('.html')) { 36 | this.contents.active = index; 37 | this.http.get('http://01coding.co.uk/meeting-display/docs/' + this.contents.list[index].link, { responseType: "text" }).subscribe((html: any) => { 38 | this.contents.activeHtml = html; 39 | }); 40 | } else { 41 | this.electronService.shell.openExternal(this.contents.list[index].link); 42 | } 43 | } 44 | } 45 | 46 | import { NgModule } from '@angular/core'; 47 | import { CommonModule } from '@angular/common'; 48 | import { SharedModule } from '../../shared.module'; 49 | 50 | @NgModule({ 51 | declarations: [HelpComponent], 52 | imports: [CommonModule, SharedModule] 53 | }) 54 | class HelpModule { } 55 | -------------------------------------------------------------------------------- /src/app/shared/modals/settings/settings.component.html: -------------------------------------------------------------------------------- 1 |
Display Settings
2 |
3 |
4 |
5 |
Text size
6 |
7 | 8 |
9 |
10 |
11 |
Text color
12 |
13 | 14 |
15 |
16 |
17 |
Links color
18 |
19 | 20 |
21 |
22 |
23 |
Text alignment
24 |
25 | 26 | 27 | 28 |
29 |
30 |
31 |

Changing text size during slide display restarts the slideshow from the beginning!

32 |
33 |
34 |
Background type
35 |
36 | 40 |
41 |
42 |
43 |
Background color
44 |
45 | 46 |
47 |
48 |
49 | 50 |
51 |
52 |
Display on
53 | 56 |
57 |
58 |
Windowed mode
59 | 63 |
64 |
65 | 66 |
-------------------------------------------------------------------------------- /src/app/shared/modals/settings/settings.component.scss: -------------------------------------------------------------------------------- 1 | .settings { 2 | .column > .input { 3 | width: 100%; 4 | height: 32px; 5 | margin-top: 8px; 6 | input { 7 | height: 100%; 8 | } 9 | } 10 | .buttons { 11 | margin-top: 8px; 12 | height: 32px; 13 | width: 100%; 14 | } 15 | select { 16 | height: 32px; 17 | width: 100%; 18 | border-radius: .28571429rem; 19 | } 20 | } -------------------------------------------------------------------------------- /src/app/shared/modals/settings/settings.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit, ChangeDetectorRef } from '@angular/core'; 2 | import { SlidesService } from '../../../shared/services/slides.service'; 3 | import { ElectronService } from 'ngx-electron'; 4 | import { FuiModal } from 'ngx-fomantic-ui'; 5 | 6 | @Component({ 7 | selector: 'settings', 8 | styleUrls: ['./settings.component.scss'], 9 | templateUrl: './settings.component.html', 10 | providers: [FuiModal] 11 | }) 12 | 13 | export class SettingsComponent implements OnInit { 14 | 15 | settings = { 16 | fontSize: 40, 17 | fontColor: '#696969', 18 | fontLinkColor: '#4271BD', 19 | textAlign: 'center', 20 | bgType: 'pub', //Can be 'color' or 'pub' 21 | bgColor: '#fff', 22 | display: { 23 | selected: 1, 24 | windowed: false, 25 | list: [] 26 | } 27 | }; 28 | 29 | constructor( 30 | public slidesService: SlidesService, 31 | private electronService: ElectronService, 32 | private changeDetector: ChangeDetectorRef, 33 | public modal: FuiModal 34 | ) { } 35 | 36 | ngOnInit() { 37 | //Initialise display options 38 | this.electronService.ipcRenderer.send('slides-options'); 39 | this.electronService.ipcRenderer.on('slides-options', (event, options) => { 40 | this.settings = options; 41 | this.changeDetector.detectChanges(); 42 | }); 43 | } 44 | 45 | updateOptions() { 46 | this.electronService.ipcRenderer.send('slides-options', this.settings); 47 | } 48 | } 49 | 50 | import { NgModule } from '@angular/core'; 51 | import { CommonModule } from '@angular/common'; 52 | import { SharedModule } from '../../shared.module'; 53 | 54 | @NgModule({ 55 | declarations: [SettingsComponent], 56 | imports: [CommonModule, SharedModule], 57 | providers: [] 58 | }) 59 | class SettingsModule { } 60 | -------------------------------------------------------------------------------- /src/app/shared/pipes/santizer.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | import { SafeHtml, SafeUrl, DomSanitizer } from '@angular/platform-browser'; 3 | 4 | @Pipe({ name: 'sanitizer' }) 5 | export class SanitizerPipe implements PipeTransform { 6 | constructor(private _sanitizer: DomSanitizer) {} 7 | 8 | transform(value: any = '', type: string = 'text', interpolate: object = null): any { 9 | //Check value is not null 10 | if(value == null) { return null; } 11 | 12 | //Check if the string should be interpolated 13 | if (interpolate !== null) { 14 | let vars = value.match(/{{([\w.]+)}}/g) || []; 15 | vars.forEach((varPath) => { 16 | let varValue = interpolate; 17 | let keys = varPath.replace(/{{|}}/g, '').split('.'); 18 | keys.forEach(key => { varValue = varValue[key]; }); 19 | value = value.replace(varPath, varValue); 20 | }); 21 | } 22 | 23 | //Remove script and style tags and trust html 24 | if(type == 'html') { 25 | value = value.replace(/style="(.*?)"/g, '') 26 | value = value.replace(/