├── .gitignore ├── LICENSE ├── README.md ├── cart └── getdismoney-community.js ├── config.xml ├── config ├── copyConfig.js └── picoDeployConfig.json ├── docs ├── EXAMPLE_PROJECTS.md ├── README.md ├── main.greenworks.js └── readmeAssets │ ├── greenworksLayout.png │ ├── picoDeployAndroidExample.gif │ ├── picoDeployAndroidExample.mov │ ├── picoDeployCartExport.gif │ ├── picoDeployCartExport.mov │ ├── picoDeployElectronBuild.gif │ └── picoDeployElectronBuild.mov ├── ionic-android-deploy.sh ├── ionic.config.json ├── main.js ├── package-lock.json ├── package.json ├── picoDeployConfig.json ├── resources ├── android │ ├── icon │ │ ├── drawable-hdpi-icon.png │ │ ├── drawable-ldpi-icon.png │ │ ├── drawable-mdpi-icon.png │ │ ├── drawable-xhdpi-icon.png │ │ ├── drawable-xxhdpi-icon.png │ │ └── drawable-xxxhdpi-icon.png │ └── splash │ │ ├── drawable-land-hdpi-screen.png │ │ ├── drawable-land-ldpi-screen.png │ │ ├── drawable-land-mdpi-screen.png │ │ ├── drawable-land-xhdpi-screen.png │ │ ├── drawable-land-xxhdpi-screen.png │ │ ├── drawable-land-xxxhdpi-screen.png │ │ ├── drawable-port-hdpi-screen.png │ │ ├── drawable-port-ldpi-screen.png │ │ ├── drawable-port-mdpi-screen.png │ │ ├── drawable-port-xhdpi-screen.png │ │ ├── drawable-port-xxhdpi-screen.png │ │ └── drawable-port-xxxhdpi-screen.png ├── icon.icns ├── icon.ico ├── icon.png ├── icon.png.md5 ├── icons │ └── 512x512.png ├── ionic_icon.png ├── ionic_splash.png ├── ios │ ├── icon │ │ ├── icon-40.png │ │ ├── icon-40@2x.png │ │ ├── icon-40@3x.png │ │ ├── icon-50.png │ │ ├── icon-50@2x.png │ │ ├── icon-60.png │ │ ├── icon-60@2x.png │ │ ├── icon-60@3x.png │ │ ├── icon-72.png │ │ ├── icon-72@2x.png │ │ ├── icon-76.png │ │ ├── icon-76@2x.png │ │ ├── icon-83.5@2x.png │ │ ├── icon-small.png │ │ ├── icon-small@2x.png │ │ ├── icon-small@3x.png │ │ ├── icon.png │ │ └── icon@2x.png │ └── splash │ │ ├── Default-568h@2x~iphone.png │ │ ├── Default-667h.png │ │ ├── Default-736h.png │ │ ├── Default-Landscape-736h.png │ │ ├── Default-Landscape@2x~ipad.png │ │ ├── Default-Landscape@~ipadpro.png │ │ ├── Default-Landscape~ipad.png │ │ ├── Default-Portrait@2x~ipad.png │ │ ├── Default-Portrait@~ipadpro.png │ │ ├── Default-Portrait~ipad.png │ │ ├── Default@2x~iphone.png │ │ └── Default~iphone.png ├── splash.png └── splash.png.md5 ├── src ├── app │ ├── app.component.ts │ ├── app.html │ ├── app.module.ts │ ├── app.scss │ └── main.ts ├── assets │ ├── 3pLibs │ │ └── pico8gamepad │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ └── pico8gamepad.js │ ├── fonts │ │ └── PICO-8MonoUpper.ttf │ ├── gamepad │ │ ├── materialButton.svg │ │ ├── materialDpad.svg │ │ ├── materialGear.svg │ │ └── materialPause.svg │ ├── icon │ │ └── favicon.ico │ └── picomedia │ │ ├── splash.gif │ │ └── splash.mp4 ├── components │ ├── cart │ │ ├── cart.html │ │ ├── cart.scss │ │ └── cart.ts │ ├── components.module.ts │ ├── gamepad │ │ ├── gamepad.html │ │ ├── gamepad.scss │ │ └── gamepad.ts │ ├── picomedia │ │ ├── picomedia.html │ │ ├── picomedia.scss │ │ └── picomedia.ts │ └── picosplash │ │ ├── picosplash.html │ │ ├── picosplash.scss │ │ └── picosplash.ts ├── index.html ├── manifest.json ├── pages │ └── home │ │ ├── home.html │ │ ├── home.module.ts │ │ ├── home.scss │ │ ├── home.ts │ │ └── settings │ │ ├── settings.html │ │ ├── settings.module.ts │ │ ├── settings.scss │ │ └── settings.ts ├── providers │ ├── pico-db │ │ └── pico-db.ts │ ├── platform-sdk-wrapper │ │ └── platform-sdk-wrapper.ts │ └── settings │ │ └── settings.ts ├── service-worker.js └── theme │ └── variables.scss ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Specifies intentionally untracked files to ignore when using Git 2 | # http://git-scm.com/docs/gitignore 3 | 4 | package-lock.json 5 | 6 | # Our dist directory for app builds 7 | dist/ 8 | 9 | # Our project-level picoDeployConfig.json, since it is being replaced with copyConfig 10 | ./picoDeployConfig.json 11 | 12 | *~ 13 | *.sw[mnpcod] 14 | *.log 15 | *.tmp 16 | *.tmp.* 17 | log.txt 18 | *.sublime-project 19 | *.sublime-workspace 20 | .vscode/ 21 | npm-debug.log* 22 | 23 | .idea/ 24 | .sourcemaps/ 25 | .sass-cache/ 26 | .tmp/ 27 | .versions/ 28 | coverage/ 29 | node_modules/ 30 | tmp/ 31 | temp/ 32 | hooks/ 33 | platforms/ 34 | plugins/ 35 | plugins/android.json 36 | plugins/ios.json 37 | www/ 38 | $RECYCLE.BIN/ 39 | 40 | .DS_Store 41 | Thumbs.db 42 | UserInterfaceState.xcuserstate 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # picoDeploy 2 | 3 | 👾 *Deploy Pico-8 Games Anywhere and Everywhere!* 👾 4 | 5 | ![Electron Desktop Example of picoDeploy](./docs/readmeAssets/picoDeployElectronBuild.gif) 6 | ![Ionic Mobile Android Example of picoDeploy](./docs/readmeAssets/picoDeployAndroidExample.gif) 7 | 8 | **Example .gifs show the jelpi demo cart. Included cart is different.** 9 | 10 | # Table Of Contents 📚 11 | 12 | * [Introduction](#introduction) 13 | * [Features](#features) 14 | * [Getting Started](#getting-started) 15 | * [Key Gotchas and Caveats](#key-gotchas-and-caveats) 16 | * [Keyboard Controls](#keyboard-controls) 17 | * [Project Layout](#project-layout) 18 | * [Usage](#usage) 19 | * [To add your own carts](#to-add-your-own-carts) 20 | * [Npm Scripts / CLI](#npm-scripts--cli) 21 | * [picoDeployConfig.json](#picodeployconfigjson) 22 | * [Example Projects](#example-projects) 23 | * [Building and Deploying for Web](#building-and-deploying-for-web) 24 | * [Building and Deploying for Desktop](#building-and-deploying-for-desktop) 25 | * [Building and Deploying for Android](#building-and-deploying-for-android) 26 | * [Building and Deploying for iOS](#building-and-deploying-for-ios) 27 | * [Adding Support for Game Distribution Platforms](#adding-support-for-game-distribution-platforms) 28 | * [Google Play](#google-play-games) 29 | * [Steam (Greenworks)](#steam-greenworks) 30 | * [Parent Projects and Dependencies](#parent-projects-and-dependencies) 31 | * [Contributing](#contributing) 32 | * [LICENSE](#license) 33 | 34 | # Introduction 35 | 36 | This project started because I found pico-8 and started building a small hackathon-style game, called [Get Dis Money](https://getdismoney.com), for a local meetup called Code && Coffee, Long Beach. I was used to building games in Unity, but wanted to try something less serious and more fun. After showing the game to some friends, and writing more and more code for the game. I thought, what if I actually tried to write a deployable version of the game? Then I came across [this article](http://missingsentinelsoftware.com/blog/deploying-pico-8-cartridges-electron) by josefnpat, and figured why not. I have been developing Javascript (Node.js) for about 4 years, and have built plenty of Electron apps in the past. However, I also wanted to build for mobile, (Android and iOS), and at the time pico-8 only deployed to web. I thought I'd take it even further, and commit the ultimate web developer sin 😈. I have been using Ionic since when it was still in beta (before v1), and thought, why not just put Ionic inside of electron? I mean this whole project was all for fun, why not make the write-it-once-and-literally-deploy-anywhere wrapper for pico-8? And that is how we got **picoDeploy**. 37 | 38 | # Features 39 | 40 | * 🖥️ Deploy to Windows, Mac, and Linux using [Electron](https://electronjs.org/) and [Electron Builder](https://github.com/electron-userland/electron-builder) 41 | * 📱 Deploy to Android, iOS, and Web with all listed features using [Ionic](https://ionicframework.com/) 42 | * 🚀 Performant mobile builds using [Crosswalk](https://github.com/crosswalk-project/cordova-plugin-crosswalk-webview) 43 | * 💾 Save file (Indexedb) listener, with Pub / Sub functionality to perform actions when the save file is changed 44 | * 🎮 Mobile on screen gamepad using [Google Material Icons](https://material.io/icons/) 45 | * 🎮 Usb Gamepad / Xbox 360 controller / PS3 controller support using a modified [pico8gamepad](https://github.com/krajzeg/pico8gamepad) 46 | * 💦 Splashscreen / Video to hide the Pico 8 boot screen (Not to be mistaken with Ionic mobile app splash screen) 47 | * 📺 Background image or video to be displayed behind your game 48 | * ⚙️ Settings screen, with support for turning sound on / off, fullscreen for desktop, customizable gamepad color, customizable background color (if not background media), Stretch the game to full resolution, and dropdown credits. 49 | * 🛠️ Build System to support multiple `picoDeployConfig.json` files, and copied for the correct build target set in ENV variables. 50 | * ♻️ Compatible with libraries like [greenworks](https://github.com/greenheartgames/greenworks) and [Cordova Plugin Play Games Services](https://github.com/artberri/cordova-plugin-play-games-services). That can be plugged into a commented PlatformSdkWrapper service, which taps into the Save file listener I mentioned above 51 | 52 | P.S I totally started this project before [version 0.1.11](https://www.lexaloffle.com/bbs/?tid=30219) which included binary exports for Windows, Mac, and Linux. However, this project still offers a great amount of functionality for deploying and building games, that binary exports do not offer currently. 53 | 54 | # Getting Started 55 | 56 | 1. Install [Nodejs](https://nodejs.org/en/download/package-manager/). I suggest using [nvm](https://github.com/creationix/nvm). 57 | 58 | 2. Download this project as a zip, or fork it. This is a base project that yours project will be based upon. 59 | 60 | 2. Run `npm install` in the base directory of the project 61 | 62 | 3. Run `npm run ionic:serve`. And open [localhost:8100](http://localhost:8100) if it does not open it for you. 63 | 64 | 4. If a game starts running in your browser, then you are ready to go. Enjoy! 😊 65 | 66 | # Key Gotchas and Caveats 67 | 68 | * Minimum android version is 7.1.1 (sdk version 25). This is due to the [Web Audio API](https://caniuse.com/#feat=audio-api) and how around Android version 7, [android replaced their webview with Mobile Chrome](https://developer.android.com/about/versions/nougat/android-7.0.html#webview). Games can technically be played on Android versions below this, but there will be a weird [Audio Jitter / Crackle](https://www.lexaloffle.com/bbs/?tid=30573). If you are skeptical about any device, visit the new and awesome [playpico](http://playpico.com/) on the device to test the audio. Please feel free to click the previous link to join the discussion, or open an issue if you feel you may have a solution. I can confirm on my Pixel 2, and Nexus 5X the audio was fine. But on my roomate's LG G4, the audio jitters 😞. 69 | 70 | * Adding support for Steamworks using [greenworks](https://github.com/greenheartgames/greenworks) will have many missing steam features, and can be quite finnicky. For instance, the Steam UI cannot be opened, even with mentioned hacks within the projects issues. However, most functionality such as logging into steam, and launching achievements should work. 71 | 72 | # Keyboard Controls 73 | 74 | ![Pico 8 Controller Keyboard Layout](https://neko250.github.io/pico8-api/img/input.png) 75 | 76 | ([Image from pico8-api](https://neko250.github.io/pico8-api/)) 77 | 78 | Additional keyboard (⌨️) commands are: 79 | 80 | * `o` for settings (options) 81 | * `p` for pause 82 | * `CMD + q` to quit 83 | 84 | # Project Layout 85 | 86 | * `cart/` - Where `cart.js` files are stored. 87 | * `config/` - Where `picoDeployConfig.whatever.json` files are placed, and the `copyConfig.js` file lives to copy `picoDeployConfig.json` to the base of the project to be used by the application. 88 | * `resources/` - Where app icons for Electron Builder and Ionic are stored 89 | * `src/` - Where the source code for the Ionic application lives. 90 | * `src/app` - The main app component 91 | * `src/assets` - Where asstets for the application lives. Please take note of `src/assets/picomedia` where the splashscreen and things are stored 92 | * `src/components` - Where The application components are stored. Such as the cart, and gamepad components 93 | * `src/pages` - Where the Home and Settings page components are stored 94 | * `src/providers` - Where the services are stored. Such as the Save file listener provider, and the Settings provider. 95 | * `config.xml` - Ionic / Cordova Config for building the application 96 | * `ionic-android-deploy.sh` - Script for easing the proccess of building signed android apks to upload to the Google Play Store. 97 | * `main.js` - Electron Main process Javascript file. See [Electron Quick Start](https://github.com/electron/electron/blob/master/docs/tutorial/quick-start.md) 98 | * `package.json` - Project file to define scripts and dependencies 99 | 100 | # Usage 101 | 102 | ### To add your own carts 103 | 104 | 1. open pico-8 105 | 2. load your cart 🛒 106 | 3. export your cart with `CART_NAME.html` 107 | 108 | Or just watch these steps in the gif below: 109 | 110 | ![Exporting pico-8 cart as gif](./docs/readmeAssets/picoDeployCartExport.gif) 111 | 112 | 4. Copy the `.js` file that was export into `picoDeploy/cart`. 113 | 5. edit the `picoDeployConfig.json` file to include your cart instead of the included one. 114 | 6. Run `npm run ionic:serve`, and ensure that your cart is now the one being loaded! 115 | 116 | ### Npm Scripts / CLI 117 | 118 | All of the scripts should be prepended with `npm run [script name]`, for instance `npm run ionic:serve` 119 | 120 | The main scripts you will be using are the `ionic:x`, `electron:x`, and `android:x` scripts. 121 | 122 | The android scripts will require you to set up your android environment, outlined in [Building and Deploying for Android](#building-and-deploying-for-android). 123 | 124 | * `clean` - The default ionic project clean script 125 | * `lint` - The default ionic project lint script 126 | * `cart` - copies to the files within the `cart/` folder to the built `www/cart` folder 127 | * `config` - runs the `copyConfig.js` script, to copy the appropriate `picoDeployConfig.json` according to the ENV variables 128 | * `build` - runs the default ionic project build script 129 | * `serve` - runs the default ionic project build script 130 | * `ionic:build` - runs the `cart`, `config`, and `build` scripts to build the complete picoDeploy app 131 | * `ionic:serve` - runs the `cart`, `config`, and `build` scripts to serve / livereload the complete picoDeploy app for development 132 | * `android:run` - exports Mobile ENV Variables, runs `cart`, and `config`, and then runs `ionic cordova run android --prod` to preview a build of the app on a connected android device, or android emulator 133 | * `android:serve` - Same functionality as `ionic:serve`, but runs on an android device like `android:run` 134 | * `android:build` - Similar to `android:run`, but builds an unsigned apk to be distributed. 135 | * `android:deploy` - similar to `android:build`, but uses (and requires) a keystore to zipalign and sign built apks, by running the `ionic-android-deplpoy.sh` script 136 | * `electron:serve` - Runs `ionic:build`, and runs the built files inside an electron container 137 | * `electron:serve:nobuild` - Similar to `electron:serve`, but does not re-build the app. This is useful to testing changes on only the `main.js` file 138 | * `electron:build` - Builds the application using [Electron Builder](https://github.com/electron-userland/electron-builder) for the current Operating system. This is useful for testing final production builds. 139 | * `electron:deploy` - Similar to `electron:build`, but builds for all desktop platforms (Windows, Mac, and Linux), using the Electron builder configuration outlined in the `package.json` 140 | 141 | 142 | ### picoDeployConfig.json 143 | 144 | Example / Inlined `picoDeployConfig.json` file with invalid json comment lines to describe each key: 145 | 146 | ``` 147 | { 148 | "cart": { 149 | "cartName": "getdismoney-community.js" // Name or path of the cart inside the cart/ directory 150 | }, 151 | "picosplash": { // Settings reffering to the introduction splash screen that hides pico-8 booting 152 | "enable": true, // Boolean to enable or disable the pico splash 153 | "splashMedia": "assets/picomedia/splash.mp4" // HTML5 compliant image or video file to be displayed over the cart booting 154 | }, 155 | "backgroundMedia": false, // HTML5 compliant image or video file to be displayed behind the cart. Set to false to disable 156 | "inactiveToExitDelayInMilli": 300000, // How long before killing the app if left in the background 157 | "defaultSettings": { // Default settings to set on the settings menu 158 | "fullscreen": true, // Start the app in full screen or not by default 159 | "sound": true, // Enable or disable sound by default 160 | "backgroundColor": "#272727", // backgroundColor displayed behind cart if no backgroundMedia 161 | "gamepadColor": "#FFFFFF", // Default color of the gamepad 162 | "stretch": false // Default to stretching the game to match the aspect ratio 163 | }, 164 | "dbWatcher": { // Save file watcher config 165 | "enable": true, // Boolean to enable or disable the save file (Indexedb) watcher 166 | "cartDataName": "nocomplygames_letsgetdismoney_communitty_edition_v1" // String passed to cartdata() to be found in the Indexedb 167 | } 168 | } 169 | ``` 170 | 171 | # Example Projects 172 | 173 | A list of example project can be found at: [The Example Projects .md](./docs/EXAMPLE_PROJECTS.md) 174 | 175 | # Building and Deploying for Web 176 | 177 | For development, `npm run ionic:serve` will provide as a great place to test your carts, or any changes you make to the project. It offers livereloading for the project. However, for cart or config changes, this command will have to be re-run. 178 | 179 | Simply running `npm run ionic:build` should give you a good desktop build. The built website should be located in the `www/` folder. Which can then be opened directly from your web browser, or uploaded to a site like [itch.io](https://itch.io/). I haven't personally tested this, but it should all work *in theory* 🤔. Please run at your own discretion. 180 | 181 | # Building and Deploying for Desktop 182 | 183 | For Development, run `npm run electron:serve`, which simply runs `npm run ionic:build`, and wraps the built project in an electron shell. This is really useful for testing changes to your `main.js` or desktop specific. Otherwise use `npm run ionic:serve`. 184 | 185 | To test builds for desktop, run `npm run electron:build`. This really is just packing you www/ directory into an electron app that can be distributed. However the `electron:build` command will only build for the current OS and architecture. Which means it is great for ensuring that your electron builds are building correctly, and running great. Please notes, that my builds for [Get Dis Money](https://getdismoney.com), come out to be about ~200MB. Built files can be found in the `dist/` directory. 186 | 187 | To build production builds for all platforms to be distributed and downloading on places like [itch.io](https://itch.io/) or your own distribution platform, run `npm run electron:deploy`. This will build for all available OS and architectures to be run by your users. Built files can be found in the `dist/android` directory. 188 | 189 | For Icons and Backgrounds of builds, please refer to the [Electron Builder Icons Guide](https://www.electron.build/icons). I know it sates that for the Icons on Linux, it can optionally share the same Icon as macOS. However, It gave me strange builder errors, and I highly suggest just adding your linux icon as `resources/icons/512x512.png`. Like it states in the guide. 190 | 191 | For any additional help, I'd highly suggest looking through the [Electron Builder Documentation](https://www.electron.build/), for anything you may be trying to achieve. 192 | 193 | # Building and Deploying for Android 194 | 195 | First, you will need to [add the android platform](https://ionicframework.com/docs/cli/cordova/platform/). However, you will need to set some paths in you `.bashrc`, which are outlined in [this guide](https://cordova.apache.org/docs/en/latest/guide/platforms/android/). This can be done with `ionic cordova platform add android@6.3.0`. If you notice, we are specifying cordova android 6.3.0. If you Google around, I guess there are some weird conflicts with Ionic, Cordova, and Crosswalk on Android 7.0, therefore I am specifying the version here. Use other versions at your own discretion. This should automatically add cordova plugins like [Crosswalk](https://github.com/crosswalk-project/cordova-plugin-crosswalk-webview). From there, you should now be able to run the android commands like, `npm run android:build`. 196 | 197 | For generating icons and resources, please follow the [Ionic Docs on generating resources](https://ionicframework.com/docs/cli/cordova/resources/). 198 | 199 | For development, run `npm run android:serve`, which will run your app inside the android Ionic shell, with livereloading. Similar to `npm run ionic:serve`, for cart or config changes, this command will have to be re-run. This is great for testing changes to the gamepad, or anything mobile specific. Otherwise use `npm run ionic:serve`. 200 | 201 | To test builds, run `npm run android:build`, this will give you an unsigned development apk that you can install on your device manually. However, you need to enable [Unknown Sources](https://www.androidcentral.com/unknown-sources) on your android device. Though, newer android version will just prompt you for it. This apk cannot be placed on Google Play, as it needs to be signed and things. Built files can be found in the `dist/android` directory. 202 | 203 | For production builds, run `npm run android:deploy`. This requires that you follow the [Ionic Deployment Guide](https://ionicframework.com/docs/intro/deploying/), and create a keystore for signing apks. This will run the `ionic-android-deploy.sh` script. And you will need to edit the script inside of the `package.json`, and insert the path to your keystore, and your keystore password (Please note, you may just want to run the `ionic-android-deploy.sh` script manually, if you are building your application publicly. As you should be very wary of uploading / sharing your keystore or password). Built files can be found in the `dist/android` directory. These built apks may be uploaded and distributed on Google Play. 204 | 205 | If things aren't working, please refer to the [Ionic Deployment Guide](https://ionicframework.com/docs/intro/deploying/) as it should relatively be the same for this project. 206 | 207 | # Building and Deploying for iOS 208 | 209 | Personally, I do not have access to an iOS developer account, the fees are pretty high, or a physical iOS device. I could test this on my Mac iOS simulator, but I like testing on real hardware. Please feel free to write up some instructions for this, and contribute them to the project. 210 | 211 | In the meantime, I'd suggest simply following the [Ionic Deployment Guide](https://ionicframework.com/docs/intro/deploying/) As it should relatively be the same for this project. Also, You will have to add the `ios` platform, similar to android. 212 | 213 | For generating icons and resources, please follow the [Ionic Docs on generating resources](https://ionicframework.com/docs/cli/cordova/resources/). 214 | 215 | # Adding Support for Game Distribution Platforms 216 | 217 | ### Google Play Games 218 | 219 | Adding support for google play games is not too bad. Simply add the [Cordova Plugin Play Games Services](https://github.com/artberri/cordova-plugin-play-games-services), which is outlined on in its README on the repo I just linked. Then, refer to the [PlatformSdkWrapper](./src/providers/platform-sdk-wrapper/platform-sdk-wrapper.ts) for usage in the project. 220 | 221 | ### Steam (Greenworks) 222 | 223 | This requires a Steamworks account, and application ID ready to be used. For information on getting your game on steam, [see here](https://support.steampowered.com/kb_article.php?ref=1657-WHKN-6841). 224 | 225 | Okay, this one is quite a doozy. To add support for Steam, you will need to use [Greenworks](https://github.com/greenheartgames/greenworks). You may want to start by reading through the [greenworks Docs](https://github.com/greenheartgames/greenworks/tree/master/docs), but here is how I got everything working on my game, [Get Dis Money](https://getdismoney.com). 226 | 227 | First, You need to have a specifc version of Electron and Greenworks to use the prebuilt Greenworks builds. Currently, I am running [Electron v1.8.1](https://github.com/electron/electron/releases/tag/v1.8.1), the node version specified by [the electron NODE_MODULE_VERSION]([node](https://github.com/electron/electron/blob/04430c6dda80c25d24b7752f38f87003ac7ab3aa/.node-version), and the [prebuilt greenworks node addons v0.11.0](https://github.com/greenheartgames/greenworks/releases/tag/v0.11.0). 228 | 229 | Once you have that all correctly installed, you need to download the `.zip` files for each OS and architecture you want to target. Each `.zip` will have a `greenworks.js` file, and a `lib/` folder containing a `.node` file. You want to make a `greenworks/` directory at the base of the picoDeploy project. For instance: `~/source/picoDeploy/greenworks/`. And then add the `greenworks.js` file to your `greenworks/` folder, as well as add the `.node` file to a `lib/` folder you should create in your `greenworks` folder. 230 | 231 | Then, as it states in the [greenworks docs](https://github.com/greenheartgames/greenworks/blob/master/docs/quick-start-nwjs.md), "Copy `steam_api.dll`/`libsteam_api.dylib`/`libsteam_api.so` (based on your 232 | OS, e.g. `dll` for windows) from 233 | `/redistributable_bin/[win64|linux32|linux64|osx32]` to 234 | `/lib/`". Please note for this step, linux32 and linux64 have the same file name. Therefore, you need to run two seperate build cycles for each architecture. And because of this, I decided not to release my game for linux32 😛. 235 | 236 | In the end, your `greenworks` folder should have something looking like this: 237 | 238 | ![Greenworks Folder Layout](./docs/readmeAssets/greenworksLayout.png) 239 | 240 | Next, we need to create our [Steam App Id file](https://github.com/greenheartgames/greenworks/blob/master/docs/gotchas.md#the-steam-appid). Create a file in the base picoDeploy directory called `steam_appid.txt`. All it should contain is the appid to your game. That is it, nothing crazy, just those numbers. What this does is allow us to test our game with steam running, to allow us to fire off acheivements and things. However, please note that this file can only work when the game is run on macOS, run with an installed windows version of the game, or the `.zip` linux build type. Since the file needs to be copied to where the electron executable is running from, and some build types wont allow this. However, don't worry, when you launch the game from steam itself, it will handle this for us, and will not throw any errors when trying to do this. 241 | 242 | Next, you need to add some things to the `"build": {}` key of your `package.json`. You just need to state your [extra resources](), and should be placed underneath the `"files": []` array. You need to add the greenworks directory, with glob matching, and the `steam_appid.txt`. For instance: 243 | 244 | ``` 245 | ... package.json stuff here ... 246 | 247 | "files": [ 248 | "www/**/*", 249 | "picoDeployConfig.json", 250 | "main.js", 251 | "!**/node_modules/**/*", 252 | "!.editorconfig", 253 | "!**/{.DS_Store,.git,.hg,.svn,CVS,RCS,SCCS,__pycache__,thumbs.db,.gitignore,.gitattributes,.flowconfig,.yarn-metadata.json,.idea,.vs,appveyor.yml,.travis.yml,circle.yml,npm-debug.log,.nyc_output,yarn.lock,.yarn-integrity}", 254 | "!**/._*", 255 | "!**/*.{iml,o,hprof,orig,pyc,pyo,rbc,swp,csproj,sln,xproj}" 256 | ], 257 | "extraResources": [ 258 | "greenworks/**/*", 259 | "steam_appid.txt" 260 | ], 261 | 262 | ... package.json stuff here ... 263 | ``` 264 | 265 | Awesome! Next, let's add some helper scripts to our `package.json` `"scripts": {}` object to make things easier. Be sure not to copy the `... other scripts here ...` part! 266 | 267 | ``` 268 | 269 | ... package.json stuff here ... 270 | 271 | "scripts": { 272 | 273 | ... Other scripts here ... 274 | 275 | "steam:serve": "export DEV=\"true\" && export STEAM=\"true\" && npm run ionic:build --prod && ./node_modules/.bin/electron .", 276 | "steam:serve:nobuild": "export DEV=\"true\" && export STEAM=\"true\" && ./node_modules/.bin/electron .", 277 | "steam:build": "export DEV=\"false\" && export STEAM=\"true\" && npm run ionic:build --prod && electron-builder", 278 | "steam:deploy": "export DEV=\"false\" && export STEAM=\"true\" && export MOBILE=\"false\" && npm run ionic:build --prod && electron-builder -mw --x64 --ia32 && electron-builder -l --x64" 279 | }, 280 | 281 | ... package.json stuff here ... 282 | 283 | ``` 284 | 285 | These scripts will simply run the same `electron:x` commands but, will set a `STEAM` ENV variable that we can utilize. in both our `copyConfig.js` (optional), or our `main.js`. Which we will move on to next. 286 | 287 | Let's go ahead and get greenworks to start the Steam api when we run the app. This needs to be done in the `main.js` file. Simply [copy and paste the `main.greenworks.js`](./docs/main.greenworks.js) file, into your projects `main.js` file. 288 | 289 | To test, open up Steam, and run `npm run steam:serve`, and see if the Steam UI reacts by saying that your Steam game is running. If it is, congrats! If not, please open an issue to let me know, as I wrote this on the top of my head, and will probably need some debugging. Worst comes to worse, your best bet is to follow the [greenworks Docs](https://github.com/greenheartgames/greenworks/tree/master/docs), but in particular, try going the NW.js route instead of the electron, since we are using prebuilts, and not electron version specific builds. 290 | 291 | Lastly, to make builds, run `npm run steam:deploy`, and go ahead use the built files inside of `dist/` to [upload your game using Steampipe](https://partner.steamgames.com/doc/sdk/uploading). 292 | 293 | # Parent Projects and Dependencies 294 | 295 | This project is built on top of the following projects: 296 | 297 | * [Ionic for Mobile Builds](https://ionicframework.com/) 298 | * [Crosswalk for a more performant modern webview](https://github.com/crosswalk-project/cordova-plugin-crosswalk-webview) 299 | * [Electron for Desktop Builds](https://electronjs.org/) 300 | * [Electron Builder for Deployable Desktop Builds](https://github.com/electron-userland/electron-builder) 301 | * [pico8gamepad for Desktop Controller Support](https://github.com/krajzeg/pico8gamepad) 302 | 303 | This project could not exist without these projects, and I would like to give a huge shoutout to them for their amazing work 💕. A lot of problems can be resolved by referring to these projects issues and stackoverflow answers, and troubleshooting there. 304 | 305 | For problems with Crosswalk please refer to [this guide](https://www.techiediaries.com/mobiledev/boosting-ionic2-apps-performance-with-crosswalk/) that I followed and the [Crosswalk repo](https://github.com/crosswalk-project/cordova-plugin-crosswalk-webview). 306 | 307 | # Contributing 308 | 309 | Feel free to fork the project, open up a PR, and give any contributions! I'd suggest opening an issue first however, just so everyone is aware and can discuss the proposed changes 🤘. 310 | 311 | # LICENSE 312 | 313 | LICENSE under [Apache 2.0](https://oss.ninja/apache-2.0) 🐦. 314 | -------------------------------------------------------------------------------- /config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | PicoDeploy 4 | Deploy Pico-8 Games Anywhere and Everywhere! 5 | Ionic Framework Team 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /config/copyConfig.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var fs = require('fs'); 4 | 5 | function copyConfig(configFileName) { 6 | fs.createReadStream(`./config/${configFileName}`).pipe(fs.createWriteStream('picoDeployConfig.json')); 7 | } 8 | 9 | let consoleMsg = 'Using the following config: '; 10 | 11 | //If tou have multiple configs for the same cart 12 | // if (process.env.MOBILE && process.env.MOBILE !== "false") { 13 | // const mobileConfig = 'picoDeployConfig.mobile.json'; 14 | // copyConfig(mobileConfig); 15 | // consoleMsg += mobileConfig; 16 | // } 17 | 18 | // Include the Jelpi Config 19 | const picoDeployConfig = 'picoDeployConfig.json'; 20 | copyConfig(picoDeployConfig); 21 | consoleMsg += picoDeployConfig; 22 | 23 | console.log(consoleMsg); 24 | -------------------------------------------------------------------------------- /config/picoDeployConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "cart": { 3 | "cartName": "getdismoney-community.js" 4 | }, 5 | "picosplash": { 6 | "enable": true, 7 | "splashMedia": "assets/picomedia/splash.mp4" 8 | }, 9 | "backgroundMedia": false, 10 | "inactiveToExitDelayInMilli": 300000, 11 | "defaultSettings": { 12 | "fullscreen": true, 13 | "sound": true, 14 | "backgroundColor": "#272727", 15 | "gamepadColor": "#FFFFFF", 16 | "stretch": false 17 | }, 18 | "dbWatcher": { 19 | "enable": true, 20 | "cartDataName": "nocomplygames_letsgetdismoney_community_edition_v1" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /docs/EXAMPLE_PROJECTS.md: -------------------------------------------------------------------------------- 1 | # Example Projects Using picoDeploy 2 | 3 | ![Get Dis Money Gif](https://img.itch.zone/aW1hZ2UvMTgzNjM1Lzg1ODU1OS5naWY=/347x500/08YXuN.gif) 4 | 5 | [Get Dis Money](https://getdismoney.com) 6 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | Folders and Files for addtional documentation 2 | -------------------------------------------------------------------------------- /docs/main.greenworks.js: -------------------------------------------------------------------------------- 1 | const {app, BrowserWindow} = require('electron'); 2 | const path = require('path'); 3 | const url = require('url'); 4 | const fs = require('fs'); 5 | const picoDeployConfig = require('./picoDeployConfig.json'); 6 | 7 | // Keep a global reference of the window object, if you don't, the window will 8 | // be closed automatically when the JavaScript object is garbage collected. 9 | let win; 10 | 11 | function createWindow () { 12 | // Create the browser window. 13 | win = new BrowserWindow({ 14 | width: 800, 15 | height: 600, 16 | title: "Get Dis Money" 17 | }); 18 | 19 | // and load the index.html of the app. 20 | win.loadURL(url.format({ 21 | pathname: path.join(__dirname, 'www/index.html'), 22 | protocol: 'file:', 23 | slashes: true 24 | })) 25 | 26 | // Open the DevTools. 27 | if(process.env.DEV) { 28 | win.webContents.openDevTools(); 29 | } 30 | 31 | // Check for Steam Builds 32 | if(picoDeployConfig.steam) { 33 | 34 | // Copy our steam_appid to the correct place if we have one, and doesn't already exist 35 | // This is only for electron-builder builds, where steam_appid is placed in Resources/ 36 | try { 37 | const steamAppIdProjectPath = `${process.resourcesPath}/steam_appid.txt`; 38 | let steamAppIdExePath = `${path.dirname(app.getPath('exe'))}/steam_appid.txt`; 39 | if (fs.existsSync(steamAppIdProjectPath) && !fs.existsSync(steamAppIdExePath)) { 40 | fs.writeFileSync(steamAppIdExePath, fs.readFileSync(steamAppIdProjectPath)); 41 | } 42 | } catch(e) { 43 | console.log('Problem occured while trying to copy over your steam_appid: ', e); 44 | } 45 | 46 | // Initialize greenworks 47 | let greenworks; 48 | if (process.env.DEV) { 49 | greenworks = require(`./greenworks/greenworks.js`); 50 | } else { 51 | greenworks = require(`${process.resourcesPath}/greenworks/greenworks.js`); 52 | } 53 | 54 | // Using Init API vs. init, as init was returning false incorrectly 55 | if (greenworks.initAPI()) { 56 | console.log('Greenworks: Steam API has been initalized.'); 57 | // Attatch greenworks to the window 58 | //https://github.com/electron/electron/issues/1095 59 | win.greenworks = greenworks; 60 | 61 | // Try to get the steam overlay working 62 | // --in-process-gpu 63 | var os = require('os'); 64 | if (os.platform() === 'win32' && parseInt(os.release().split('.')[0]) >= 8) { 65 | app.commandLine.appendSwitch('in-process-gpu') 66 | } 67 | 68 | // Canvas hack 69 | // https://github.com/greenheartgames/greenworks/wiki/Troubleshooting#steam-overlay-is-unresponsive--frozen 70 | // https://stackoverflow.com/questions/38962385/how-can-i-access-dom-element-via-electron 71 | win.webContents.executeJavaScript(` 72 | 73 | console.log('Greenworks: Starting Greenworks canvas hack...'); 74 | 75 | function forceRefresh() { 76 | var canvas = document.getElementById("forceRefreshCanvas"); 77 | var ctx = canvas.getContext("2d"); 78 | ctx.clearRect(0, 0, canvas.width, canvas.height); 79 | window.requestAnimationFrame(forceRefresh); 80 | } 81 | 82 | const forceRefreshCanvas = document.createElement('canvas'); 83 | forceRefreshCanvas.id = 'forceRefreshCanvas'; 84 | forceRefreshCanvas.width = 1; 85 | forceRefreshCanvas.height = 1; 86 | document.body.appendChild(forceRefreshCanvas); 87 | 88 | forceRefresh(); 89 | `); 90 | } else { 91 | console.log('Greenworks: Could not start greenworks'); 92 | } 93 | } 94 | 95 | // Emitted when the window is closed. 96 | win.on('closed', () => { 97 | // Dereference the window object, usually you would store windows 98 | // in an array if your app supports multi windows, this is the time 99 | // when you should delete the corresponding element. 100 | win = null 101 | }) 102 | } 103 | 104 | // This method will be called when Electron has finished 105 | // initialization and is ready to create browser windows. 106 | // Some APIs can only be used after this event occurs. 107 | app.on('ready', createWindow) 108 | 109 | // Quit when all windows are closed. 110 | app.on('window-all-closed', () => { 111 | // On macOS it is common for applications and their menu bar 112 | // to stay active until the user quits explicitly with Cmd + Q 113 | if (process.platform !== 'darwin') { 114 | app.quit() 115 | } 116 | }) 117 | 118 | app.on('activate', () => { 119 | // On macOS it's common to re-create a window in the app when the 120 | // dock icon is clicked and there are no other windows open. 121 | if (win === null) { 122 | createWindow() 123 | } 124 | }) 125 | 126 | // In this file you can include the rest of your app's specific main process 127 | // code. You can also put them in separate files and require them here. 128 | -------------------------------------------------------------------------------- /docs/readmeAssets/greenworksLayout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/docs/readmeAssets/greenworksLayout.png -------------------------------------------------------------------------------- /docs/readmeAssets/picoDeployAndroidExample.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/docs/readmeAssets/picoDeployAndroidExample.gif -------------------------------------------------------------------------------- /docs/readmeAssets/picoDeployAndroidExample.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/docs/readmeAssets/picoDeployAndroidExample.mov -------------------------------------------------------------------------------- /docs/readmeAssets/picoDeployCartExport.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/docs/readmeAssets/picoDeployCartExport.gif -------------------------------------------------------------------------------- /docs/readmeAssets/picoDeployCartExport.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/docs/readmeAssets/picoDeployCartExport.mov -------------------------------------------------------------------------------- /docs/readmeAssets/picoDeployElectronBuild.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/docs/readmeAssets/picoDeployElectronBuild.gif -------------------------------------------------------------------------------- /docs/readmeAssets/picoDeployElectronBuild.mov: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/docs/readmeAssets/picoDeployElectronBuild.mov -------------------------------------------------------------------------------- /ionic-android-deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Commands to run sign and zipalign all ionic android output files 4 | if [ $# -ne 4 ]; then 5 | # Print Help 6 | echo " " 7 | echo "Wrong number of commands. Printing Help..." 8 | echo " " 9 | echo "Ionic Android Deploy. Simply follows commands from: http://ionicframework.com/docs/v1/guide/publishing.html" 10 | echo " " 11 | echo "Keystore can be created by following the keytool command in the above link" 12 | echo " " 13 | echo "USAGE:" 14 | echo " " 15 | echo "./ionic-android-deploy.sh [Path to keystore] [Keystore Password] [Output directory (No Trailing slash)] [apk file name (without trailing .apk)]" 16 | echo " " 17 | else 18 | 19 | # Sign files with passed keystore 20 | jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 \ 21 | -keystore $1 -storepass $2 \ 22 | platforms/android/build/outputs/apk/android-armv7-release-unsigned.apk getdismoney 23 | 24 | jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 \ 25 | -keystore $1 -storepass $2 \ 26 | platforms/android/build/outputs/apk/android-x86-release-unsigned.apk getdismoney 27 | 28 | # Make our passed output directory/clean out already existing apks 29 | mkdir -p $3 30 | rm $3/$4-armv7.apk 31 | rm $3/$4-x86.apk 32 | 33 | # Zip align apks. Using crazy ls to get the zipalign tool 34 | ~/Library/Android/sdk/build-tools/$(ls ~/Library/Android/sdk/build-tools | head -n 1)/zipalign \ 35 | -v 4 \ 36 | platforms/android/build/outputs/apk/android-armv7-release-unsigned.apk \ 37 | $3/$4-armv7.apk 38 | 39 | ~/Library/Android/sdk/build-tools/$(ls ~/Library/Android/sdk/build-tools | head -n 1)/zipalign \ 40 | -v 4 \ 41 | platforms/android/build/outputs/apk/android-x86-release-unsigned.apk \ 42 | $3/$4-x86.apk 43 | fi 44 | -------------------------------------------------------------------------------- /ionic.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "picoDeploy", 3 | "app_id": "", 4 | "type": "ionic-angular", 5 | "integrations": { 6 | "cordova": {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | const {app, BrowserWindow} = require('electron'); 2 | const path = require('path'); 3 | const url = require('url'); 4 | const fs = require('fs'); 5 | const picoDeployConfig = require('./picoDeployConfig.json'); 6 | 7 | // Keep a global reference of the window object, if you don't, the window will 8 | // be closed automatically when the JavaScript object is garbage collected. 9 | let win; 10 | 11 | function createWindow () { 12 | // Create the browser window. 13 | win = new BrowserWindow({ 14 | width: 800, 15 | height: 600, 16 | title: "picoDeploy" 17 | }); 18 | 19 | // and load the index.html of the app. 20 | win.loadURL(url.format({ 21 | pathname: path.join(__dirname, 'www/index.html'), 22 | protocol: 'file:', 23 | slashes: true 24 | })) 25 | 26 | // Open the DevTools. 27 | if(process.env.DEV) { 28 | win.webContents.openDevTools(); 29 | } 30 | 31 | 32 | // Emitted when the window is closed. 33 | win.on('closed', () => { 34 | // Dereference the window object, usually you would store windows 35 | // in an array if your app supports multi windows, this is the time 36 | // when you should delete the corresponding element. 37 | win = null 38 | }) 39 | } 40 | 41 | // This method will be called when Electron has finished 42 | // initialization and is ready to create browser windows. 43 | // Some APIs can only be used after this event occurs. 44 | app.on('ready', createWindow) 45 | 46 | // Quit when all windows are closed. 47 | app.on('window-all-closed', () => { 48 | // On macOS it is common for applications and their menu bar 49 | // to stay active until the user quits explicitly with Cmd + Q 50 | if (process.platform !== 'darwin') { 51 | app.quit() 52 | } 53 | }) 54 | 55 | app.on('activate', () => { 56 | // On macOS it's common to re-create a window in the app when the 57 | // dock icon is clicked and there are no other windows open. 58 | if (win === null) { 59 | createWindow() 60 | } 61 | }) 62 | 63 | // In this file you can include the rest of your app's specific main process 64 | // code. You can also put them in separate files and require them here. 65 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "picoDeploy", 3 | "description": "Deploy Pico-8 Games Anywhere and Everywhere!", 4 | "version": "0.0.1", 5 | "author": "Aaron Turner", 6 | "homepage": "https://aaronthedev.com", 7 | "private": false, 8 | "main": "main.js", 9 | "scripts": { 10 | "clean": "ionic-app-scripts clean", 11 | "lint": "ionic-app-scripts lint", 12 | "cart": "rm -rf www/cart && mkdirp www/cart && cp -r cart/* www/cart", 13 | "config": "node config/copyConfig.js", 14 | "build": "npm run ionic:build", 15 | "serve": "npm run ionic:serve", 16 | "ionic:build": "npm run config && npm run cart && ionic-app-scripts build", 17 | "ionic:serve": "npm run config && npm run cart && ionic-app-scripts serve", 18 | "android:run": "export DEV=\"true\" && export MOBILE=\"true\" && npm run config && npm run cart && ionic cordova run android --prod", 19 | "android:serve": "export DEV=\"true\" && export MOBILE=\"true\" && npm run config && npm run cart && ionic cordova run android --prod --livereload --consolelogs", 20 | "android:build": "export DEV=\"false\" && export MOBILE=\"true\" && npm run config && npm run cart && ionic cordova build android --prod --release", 21 | "android:deploy": "npm run android:build && ./ionic-android-deploy.sh YOUR_KEYSTORE_HERE KEYSTORE_PASSWORD_HERE dist/android pico-deploy", 22 | "electron:serve": "export DEV=\"true\" && export MOBILE=\"false\" && npm run ionic:build --prod && ./node_modules/.bin/electron .", 23 | "electron:serve:nobuild": "export DEV=\"true\" && export MOBILE=\"false\" && npm run config && npm run cart && ./node_modules/.bin/electron .", 24 | "electron:build": "export DEV=\"false\" && export MOBILE=\"false\" && npm run ionic:build --prod && electron-builder", 25 | "electron:deploy": "export DEV=\"false\" && export MOBILE=\"false\" && npm run ionic:build --prod && electron-builder -mwl --x64 --ia32" 26 | }, 27 | "build": { 28 | "appId": "com.example.picoDeploy", 29 | "productName": "picoDeploy", 30 | "directories": { 31 | "buildResources": "resources" 32 | }, 33 | "files": [ 34 | "www/**/*", 35 | "picoDeployConfig.json", 36 | "main.js", 37 | "!**/node_modules/**/*", 38 | "!.editorconfig", 39 | "!**/{.DS_Store,.git,.hg,.svn,CVS,RCS,SCCS,__pycache__,thumbs.db,.gitignore,.gitattributes,.flowconfig,.yarn-metadata.json,.idea,.vs,appveyor.yml,.travis.yml,circle.yml,npm-debug.log,.nyc_output,yarn.lock,.yarn-integrity}", 40 | "!**/._*", 41 | "!**/*.{iml,o,hprof,orig,pyc,pyo,rbc,swp,csproj,sln,xproj}" 42 | ], 43 | "extraResources": [], 44 | "mac": { 45 | "category": "public.app-category.games", 46 | "target": [ 47 | "dmg", 48 | "zip" 49 | ] 50 | }, 51 | "win": { 52 | "target": [ 53 | "nsis", 54 | "portable" 55 | ] 56 | }, 57 | "linux": { 58 | "target": [ 59 | "AppImage", 60 | "zip" 61 | ] 62 | } 63 | }, 64 | "dependencies": { 65 | "@angular/common": "4.1.3", 66 | "@angular/compiler": "4.1.3", 67 | "@angular/compiler-cli": "4.1.3", 68 | "@angular/core": "4.1.3", 69 | "@angular/forms": "4.1.3", 70 | "@angular/http": "4.1.3", 71 | "@angular/platform-browser": "4.1.3", 72 | "@angular/platform-browser-dynamic": "4.1.3", 73 | "@ionic-native/core": "3.12.1", 74 | "@ionic-native/screen-orientation": "^4.2.1", 75 | "@ionic-native/splash-screen": "3.12.1", 76 | "@ionic-native/status-bar": "3.12.1", 77 | "@ionic/storage": "^2.0.1", 78 | "@types/node": "^8.5.2", 79 | "angular2-expandable-list": "^0.5.0", 80 | "buffer": "^5.0.8", 81 | "cordova-plugin-console": "^1.0.5", 82 | "cordova-plugin-crosswalk-webview": "^2.3.0", 83 | "cordova-plugin-device": "^1.1.4", 84 | "cordova-plugin-screen-orientation": "^2.0.1", 85 | "cordova-plugin-splashscreen": "^4.0.3", 86 | "cordova-plugin-statusbar": "^2.2.2", 87 | "cordova-plugin-whitelist": "^1.3.1", 88 | "cordova-sqlite-storage": "^2.0.4", 89 | "es6-promise-plugin": "git+https://github.com/vstirbu/PromisesPlugin.git", 90 | "idb": "^2.0.4", 91 | "ionic-angular": "3.6.1", 92 | "ionic-plugin-keyboard": "^2.2.1", 93 | "ionicons": "3.0.0", 94 | "ngx-color-picker": "^4.3.3", 95 | "rxjs": "5.4.0", 96 | "sass-media-queries": "^1.0.3", 97 | "sass-theme-defaults": "^1.0.2", 98 | "sw-toolbox": "3.6.0", 99 | "zone.js": "0.8.12" 100 | }, 101 | "devDependencies": { 102 | "@ionic/app-scripts": "^3.2.4", 103 | "electron": "^7.2.4", 104 | "electron-builder": "^19.50.0", 105 | "mkdirp": "^0.5.1", 106 | "typescript": "2.3.4" 107 | }, 108 | "cordova": { 109 | "plugins": { 110 | "cordova-plugin-console": {}, 111 | "cordova-plugin-device": {}, 112 | "cordova-plugin-splashscreen": {}, 113 | "cordova-plugin-statusbar": {}, 114 | "cordova-plugin-whitelist": {}, 115 | "ionic-plugin-keyboard": {}, 116 | "cordova-plugin-screen-orientation": {}, 117 | "cordova-sqlite-storage": {}, 118 | "cordova-plugin-crosswalk-webview": { 119 | "XWALK_VERSION": "23+", 120 | "XWALK_LITEVERSION": "xwalk_core_library_canary:17+", 121 | "XWALK_COMMANDLINE": "--disable-pull-to-refresh-effect", 122 | "XWALK_MODE": "embedded", 123 | "XWALK_MULTIPLEAPK": "true" 124 | } 125 | }, 126 | "platforms": [] 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /picoDeployConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "cart": { 3 | "cartName": "getdismoney-community.js" 4 | }, 5 | "picosplash": { 6 | "enable": true, 7 | "splashMedia": "assets/picomedia/splash.mp4" 8 | }, 9 | "backgroundMedia": false, 10 | "inactiveToExitDelayInMilli": 300000, 11 | "defaultSettings": { 12 | "fullscreen": true, 13 | "sound": true, 14 | "backgroundColor": "#272727", 15 | "gamepadColor": "#FFFFFF", 16 | "stretch": false 17 | }, 18 | "dbWatcher": { 19 | "enable": true, 20 | "cartDataName": "nocomplygames_letsgetdismoney_community_edition_v1" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /resources/android/icon/drawable-hdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/android/icon/drawable-hdpi-icon.png -------------------------------------------------------------------------------- /resources/android/icon/drawable-ldpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/android/icon/drawable-ldpi-icon.png -------------------------------------------------------------------------------- /resources/android/icon/drawable-mdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/android/icon/drawable-mdpi-icon.png -------------------------------------------------------------------------------- /resources/android/icon/drawable-xhdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/android/icon/drawable-xhdpi-icon.png -------------------------------------------------------------------------------- /resources/android/icon/drawable-xxhdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/android/icon/drawable-xxhdpi-icon.png -------------------------------------------------------------------------------- /resources/android/icon/drawable-xxxhdpi-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/android/icon/drawable-xxxhdpi-icon.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-hdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/android/splash/drawable-land-hdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-ldpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/android/splash/drawable-land-ldpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-mdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/android/splash/drawable-land-mdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-xhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/android/splash/drawable-land-xhdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-xxhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/android/splash/drawable-land-xxhdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-land-xxxhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/android/splash/drawable-land-xxxhdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-hdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/android/splash/drawable-port-hdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-ldpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/android/splash/drawable-port-ldpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-mdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/android/splash/drawable-port-mdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-xhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/android/splash/drawable-port-xhdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-xxhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/android/splash/drawable-port-xxhdpi-screen.png -------------------------------------------------------------------------------- /resources/android/splash/drawable-port-xxxhdpi-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/android/splash/drawable-port-xxxhdpi-screen.png -------------------------------------------------------------------------------- /resources/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/icon.icns -------------------------------------------------------------------------------- /resources/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/icon.ico -------------------------------------------------------------------------------- /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/icon.png -------------------------------------------------------------------------------- /resources/icon.png.md5: -------------------------------------------------------------------------------- 1 | 1a762d7146108cbbc470135a9f14bc3e -------------------------------------------------------------------------------- /resources/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/icons/512x512.png -------------------------------------------------------------------------------- /resources/ionic_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/ionic_icon.png -------------------------------------------------------------------------------- /resources/ionic_splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/ionic_splash.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/ios/icon/icon-40.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/ios/icon/icon-40@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/ios/icon/icon-40@3x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/ios/icon/icon-50.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-50@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/ios/icon/icon-50@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/ios/icon/icon-60.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/ios/icon/icon-60@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/ios/icon/icon-60@3x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/ios/icon/icon-72.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-72@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/ios/icon/icon-72@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/ios/icon/icon-76.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/ios/icon/icon-76@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/ios/icon/icon-83.5@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/ios/icon/icon-small.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/ios/icon/icon-small@2x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon-small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/ios/icon/icon-small@3x.png -------------------------------------------------------------------------------- /resources/ios/icon/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/ios/icon/icon.png -------------------------------------------------------------------------------- /resources/ios/icon/icon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/ios/icon/icon@2x.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-568h@2x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/ios/splash/Default-568h@2x~iphone.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-667h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/ios/splash/Default-667h.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-736h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/ios/splash/Default-736h.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Landscape-736h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/ios/splash/Default-Landscape-736h.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Landscape@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/ios/splash/Default-Landscape@2x~ipad.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Landscape@~ipadpro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/ios/splash/Default-Landscape@~ipadpro.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Landscape~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/ios/splash/Default-Landscape~ipad.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Portrait@2x~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/ios/splash/Default-Portrait@2x~ipad.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Portrait@~ipadpro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/ios/splash/Default-Portrait@~ipadpro.png -------------------------------------------------------------------------------- /resources/ios/splash/Default-Portrait~ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/ios/splash/Default-Portrait~ipad.png -------------------------------------------------------------------------------- /resources/ios/splash/Default@2x~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/ios/splash/Default@2x~iphone.png -------------------------------------------------------------------------------- /resources/ios/splash/Default~iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/ios/splash/Default~iphone.png -------------------------------------------------------------------------------- /resources/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/resources/splash.png -------------------------------------------------------------------------------- /resources/splash.png.md5: -------------------------------------------------------------------------------- 1 | 1f925ce12acdf00a850c0890e432d898 -------------------------------------------------------------------------------- /src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { Platform } from 'ionic-angular'; 3 | import { StatusBar } from '@ionic-native/status-bar'; 4 | import { SplashScreen } from '@ionic-native/splash-screen'; 5 | import { ScreenOrientation } from '@ionic-native/screen-orientation'; 6 | import { PlatformSdkWrapperProvider } from '../providers/platform-sdk-wrapper/platform-sdk-wrapper'; 7 | const picoDeployConfig = require('../../picoDeployConfig.json'); 8 | 9 | @Component({ 10 | templateUrl: 'app.html' 11 | }) 12 | export class MyApp { 13 | rootPage:any = 'HomePage'; 14 | 15 | constructor(platform: Platform, 16 | statusBar: StatusBar, 17 | splashScreen: SplashScreen, 18 | screenOrientation: ScreenOrientation, 19 | platformSdkWrapper: PlatformSdkWrapperProvider) { 20 | platform.ready().then(() => { 21 | // Okay, so the platform is ready and our plugins are available. 22 | // Here you can do any higher level native things you might need. 23 | statusBar.styleDefault(); 24 | splashScreen.hide(); 25 | if (platform.is('cordova')) { 26 | screenOrientation.lock(screenOrientation.ORIENTATIONS.LANDSCAPE); 27 | } 28 | 29 | // Finally start our platform sdk wrapper, if we are watching the db 30 | if(picoDeployConfig.dbWatcher.enable) { 31 | platformSdkWrapper.initialize(); 32 | } 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/app/app.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { ErrorHandler, NgModule } from '@angular/core'; 3 | import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular'; 4 | import { SplashScreen } from '@ionic-native/splash-screen'; 5 | import { StatusBar } from '@ionic-native/status-bar'; 6 | import { ScreenOrientation } from '@ionic-native/screen-orientation'; 7 | import { IonicStorageModule } from '@ionic/storage'; 8 | 9 | import { MyApp } from './app.component'; 10 | import { SettingsProvider } from '../providers/settings/settings'; 11 | import { PicoDbProvider } from '../providers/pico-db/pico-db'; 12 | import { PlatformSdkWrapperProvider } from '../providers/platform-sdk-wrapper/platform-sdk-wrapper'; 13 | 14 | @NgModule({ 15 | declarations: [ 16 | MyApp 17 | ], 18 | imports: [ 19 | BrowserModule, 20 | IonicModule.forRoot(MyApp), 21 | IonicStorageModule.forRoot() 22 | ], 23 | bootstrap: [IonicApp], 24 | entryComponents: [ 25 | MyApp 26 | ], 27 | providers: [ 28 | StatusBar, 29 | SplashScreen, 30 | ScreenOrientation, 31 | { provide: ErrorHandler, useClass: IonicErrorHandler }, 32 | SettingsProvider, 33 | PicoDbProvider, 34 | PlatformSdkWrapperProvider 35 | ] 36 | }) 37 | export class AppModule {} 38 | -------------------------------------------------------------------------------- /src/app/app.scss: -------------------------------------------------------------------------------- 1 | // http://ionicframework.com/docs/theming/ 2 | 3 | 4 | // App Global Sass 5 | // -------------------------------------------------- 6 | // Put style rules here that you want to apply globally. These 7 | // styles are for the entire app and not just one component. 8 | // Additionally, this file can be also used as an entry point 9 | // to import other Sass files to be included in the output CSS. 10 | // 11 | // Shared Sass variables, which can be used to adjust Ionic's 12 | // default Sass variables, belong in "theme/variables.scss". 13 | // 14 | // To declare rules for a specific mode, create a child rule 15 | // for the .md, .ios, or .wp mode classes. The mode class is 16 | // automatically applied to the element in the app. 17 | @import './node_modules/sass-media-queries/index.scss'; 18 | @import './node_modules/sass-theme-defaults/index.scss'; 19 | 20 | /* Hide Scrollbars for electron */ 21 | 22 | html, body { 23 | overflow: hidden; 24 | } 25 | 26 | ::-webkit-scrollbar { 27 | display: none; 28 | width: 0px; /* remove scrollbar space */ 29 | background: transparent; /* optional: just make scrollbar invisible */ 30 | } 31 | /* optional: show position indicator in red */ 32 | ::-webkit-scrollbar-thumb { 33 | } 34 | -------------------------------------------------------------------------------- /src/app/main.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | 3 | import { AppModule } from './app.module'; 4 | 5 | platformBrowserDynamic().bootstrapModule(AppModule); 6 | -------------------------------------------------------------------------------- /src/assets/3pLibs/pico8gamepad/LICENSE: -------------------------------------------------------------------------------- 1 | zlib license 2 | 3 | Copyright (c) 2016 Jakub Wasilewski 4 | 5 | This software is provided 'as-is', without any express or implied 6 | warranty. In no event will the authors be held liable for any damages 7 | arising from the use of this software. 8 | 9 | Permission is granted to anyone to use this software for any purpose, 10 | including commercial applications, and to alter it and redistribute it 11 | freely, subject to the following restrictions: 12 | 13 | 1. The origin of this software must not be misrepresented; you must not 14 | claim that you wrote the original software. If you use this software 15 | in a product, an acknowledgement in the product documentation would be 16 | appreciated but is not required. 17 | 2. Altered source versions must be plainly marked as such, and must not be 18 | misrepresented as being the original software. 19 | 3. This notice may not be removed or altered from any source distribution. 20 | -------------------------------------------------------------------------------- /src/assets/3pLibs/pico8gamepad/README.md: -------------------------------------------------------------------------------- 1 | The default HTML export the PICO-8 produces does not support gamepad controls. 2 | Both Chrome and Firefox now are able to handle them, so I created a small 3 | script that rectifies that. 4 | 5 | Tested with Xbox pads and Logitech F710 - should work with other gamepads as well. 6 | 7 | ## Usage 8 | 9 | * Drop [pico8gamepad.js](https://raw.githubusercontent.com/krajzeg/pico8gamepad/master/pico8gamepad.js) in the same directory with your game's HTML and JS. 10 | * In your HTML file, add the following, right before the ` 14 | ``` 15 | 16 | And you're done! :) 17 | 18 | ## Configuration 19 | 20 | If you like, you can edit the configuration at the top of `pico8gamepad.js` to suit your needs (defaults should be fine most of the time): 21 | 22 | * `supportedPlayers` - set to 1 if your game is single player to map all controllers to player #1. In general, set it to the number of 23 | players supported by your game. 24 | * `mapXXX` - you can choose whether different types of buttons on the controller should be mapped to PICO-8's O and X 25 | * `stickDeadzone` - controls how far you have to push an analog stick for it to register as a PICO-8 d-pad press. 26 | 27 | ## License: zlib license 28 | 29 | Copyright (c) 2016 Jakub Wasilewski 30 | 31 | This software is provided 'as-is', without any express or implied 32 | warranty. In no event will the authors be held liable for any damages 33 | arising from the use of this software. 34 | 35 | Permission is granted to anyone to use this software for any purpose, 36 | including commercial applications, and to alter it and redistribute it 37 | freely, subject to the following restrictions: 38 | 39 | 1. The origin of this software must not be misrepresented; you must not 40 | claim that you wrote the original software. If you use this software 41 | in a product, an acknowledgement in the product documentation would be 42 | appreciated but is not required. 43 | 2. Altered source versions must be plainly marked as such, and must not be 44 | misrepresented as being the original software. 45 | 3. This notice may not be removed or altered from any source distribution. 46 | -------------------------------------------------------------------------------- /src/assets/3pLibs/pico8gamepad/pico8gamepad.js: -------------------------------------------------------------------------------- 1 | // https://github.com/krajzeg/pico8gamepad 2 | // slightly modified by @torch2424 3 | 4 | // ====== [CONFIGURATION] - tailor to your specific needs 5 | 6 | // How many PICO-8 players to support? 7 | // - if set to 1, all connected controllers will control PICO-8 player 1 8 | // - if set to 2, controller #0 will control player 1, controller #2 - player 2, controller #3 - player 1, and so on 9 | // - higher numbers will distribute the controls among the players in the same way 10 | var supportedPlayers = 2; 11 | 12 | // These flags control whether or not different types of buttons should 13 | // be mapped to PICO-8 O and X buttons. 14 | var mapFaceButtons = true; 15 | var mapShoulderButtons = true; 16 | var mapTriggerButtons = true; 17 | var mapStickButtons = false; 18 | 19 | // How far you have to pull an analog stick before it register as a PICO-8 d-pad direction 20 | var stickDeadzone = 0.5; 21 | 22 | // ====== [IMPLEMENTATION] 23 | 24 | // Array through which we'll communicate with PICO-8. 25 | var pico8_buttons = [0,0,0,0,0,0,0,0]; 26 | 27 | // Start polling gamepads (if supported by browser) 28 | if (navigator.getGamepads) 29 | requestAnimationFrame(updateGamepads); 30 | 31 | // Workhorse function, updates pico8_buttons once per frame. 32 | function updateGamepads() { 33 | var gamepads = navigator.getGamepads ? navigator.getGamepads() : []; 34 | // Reset the array. 35 | for (var p = 0; p < supportedPlayers; p++) 36 | pico8_buttons[p] = 0; 37 | // Gather input from all known gamepads. 38 | for (var i = 0; i < gamepads.length; i++) { 39 | var gp = gamepads[i]; 40 | if (!gp || !gp.connected) continue; 41 | 42 | // which player is this assigned to? 43 | var player = i % supportedPlayers; 44 | 45 | var bitmask = 0; 46 | // directions (from axes or d-pad "buttons") 47 | bitmask |= (axis(gp,0) < -stickDeadzone || axis(gp,2) < -stickDeadzone || btn(gp,14)) ? 1 : 0; // left 48 | bitmask |= (axis(gp,0) > +stickDeadzone || axis(gp,2) > +stickDeadzone || btn(gp,15)) ? 2 : 0; // right 49 | bitmask |= (axis(gp,1) < -stickDeadzone || axis(gp,3) < -stickDeadzone || btn(gp,12)) ? 4 : 0; // up 50 | bitmask |= (axis(gp,1) > +stickDeadzone || axis(gp,3) > +stickDeadzone || btn(gp,13)) ? 8 : 0; // down 51 | // O and X buttons 52 | var pressedO = 53 | (mapFaceButtons && (btn(gp,2) || btn(gp,3))) || 54 | (mapShoulderButtons && btn(gp,5)) || 55 | (mapTriggerButtons && btn(gp,7)) || 56 | (mapStickButtons && btn(gp,11)); 57 | var pressedX = 58 | (mapFaceButtons && (btn(gp,0) || btn(gp,1))) || 59 | (mapShoulderButtons && btn(gp,4)) || 60 | (mapTriggerButtons && btn(gp,6)) || 61 | (mapStickButtons && btn(gp,10)); 62 | bitmask |= pressedO ? 16 : 0; 63 | bitmask |= pressedX ? 32 : 0; 64 | // update array for the player (keeping any info from previous controllers) 65 | pico8_buttons[player] |= bitmask; 66 | // pause button is a bit different - PICO-8 only respects the 6th bit on the first player's input 67 | // we allow all controllers to influence it, regardless of number of players 68 | // NOTE: (torch2424): Not allowing the native pico8 pause menu 69 | //pico8_buttons[0] |= (btn(gp,8) || btn(gp,9)) ? 64 : 0; 70 | if((btn(gp,8) || btn(gp,9))) { 71 | window.Module.pico8TogglePaused(); 72 | } 73 | } 74 | 75 | requestAnimationFrame(updateGamepads); 76 | } 77 | 78 | // Helpers for accessing gamepad 79 | function axis(gp,n) { return gp.axes[n] || 0.0; } 80 | function btn(gp,b) { return gp.buttons[b] ? gp.buttons[b].pressed : false; } 81 | -------------------------------------------------------------------------------- /src/assets/fonts/PICO-8MonoUpper.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/src/assets/fonts/PICO-8MonoUpper.ttf -------------------------------------------------------------------------------- /src/assets/gamepad/materialButton.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/gamepad/materialDpad.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/gamepad/materialGear.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/gamepad/materialPause.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/src/assets/icon/favicon.ico -------------------------------------------------------------------------------- /src/assets/picomedia/splash.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/src/assets/picomedia/splash.gif -------------------------------------------------------------------------------- /src/assets/picomedia/splash.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/torch2424/picoDeploy/72101772e76a8cbe1e228cc3586821fda2c2b4f4/src/assets/picomedia/splash.mp4 -------------------------------------------------------------------------------- /src/components/cart/cart.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 7 |
8 | -------------------------------------------------------------------------------- /src/components/cart/cart.scss: -------------------------------------------------------------------------------- 1 | cart { 2 | height: 100%; 3 | 4 | // Our container 5 | .cart { 6 | display: flex; 7 | align-items: center; 8 | justify-content: center; 9 | height: 100%; 10 | } 11 | 12 | // Our canvas object 13 | canvas#canvas { 14 | height: 100%; 15 | width: auto; 16 | 17 | image-rendering: optimizeSpeed; 18 | image-rendering: -moz-crisp-edges; 19 | image-rendering: -webkit-optimize-contrast; 20 | image-rendering: optimize-contrast; 21 | image-rendering: pixelated; 22 | -ms-interpolation-mode: nearest-neighbor; 23 | border: 0px 24 | } 25 | 26 | .stretch { 27 | width: 100% !important; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/components/cart/cart.ts: -------------------------------------------------------------------------------- 1 | import { Component, Output, EventEmitter } from '@angular/core'; 2 | import { Subscription } from 'rxjs'; 3 | import { Platform } from 'ionic-angular'; 4 | 5 | import { SettingsProvider } from '../../providers/settings/settings'; 6 | 7 | const picoDeployConfig = require('../../../picoDeployConfig.json'); 8 | 9 | /** 10 | * Generated class for the CartComponent component. 11 | * 12 | * See https://angular.io/api/core/Component for more info on Angular 13 | * Components. 14 | */ 15 | @Component({ 16 | selector: 'cart', 17 | templateUrl: 'cart.html' 18 | }) 19 | export class CartComponent { 20 | @Output() onPause: EventEmitter = new EventEmitter(); 21 | @Output() onOption: EventEmitter = new EventEmitter(); 22 | 23 | canvas: HTMLCanvasElement; 24 | platform: Platform; 25 | private onResumeSubscription: Subscription; 26 | private onPauseSubscription: Subscription; 27 | private exitTimeout: any; 28 | 29 | constructor(platform: Platform, public settingsProvider: SettingsProvider) { 30 | // Subscribe to pause and resume events 31 | // to pause the cart from running to save battery 32 | this.platform = platform; 33 | this.platform.ready().then(() => { 34 | // Ensure that the platform is mobile 35 | if(this.platform.is('cordova')) { 36 | 37 | // Subscribe to pause 38 | this.onPauseSubscription = this.platform.pause.subscribe(() => { 39 | if((window).Module && (window).Module.pico8SetPaused) { 40 | // Set paused 41 | (window).Module.pico8SetPaused(true); 42 | 43 | // If we have an inactive to exit delay, create the timeout 44 | if(picoDeployConfig.inactiveToExitDelayInMilli && 45 | picoDeployConfig.inactiveToExitDelayInMilli >= 0) { 46 | this.exitTimeout = setTimeout(() => { 47 | this.platform.exitApp(); 48 | }, picoDeployConfig.inactiveToExitDelayInMilli); 49 | } 50 | } 51 | }); 52 | 53 | // Subscribe to resume 54 | this.onResumeSubscription = this.platform.resume.subscribe(() => { 55 | if((window).Module && (window).Module.pico8SetPaused) { 56 | // Unpause 57 | (window).Module.pico8SetPaused(false); 58 | 59 | // Clear the exitTimeout if we set one 60 | if(this.exitTimeout) { 61 | clearTimeout(this.exitTimeout); 62 | } 63 | } 64 | }); 65 | } 66 | }); 67 | } 68 | 69 | ngOnInit() { 70 | // Get our canvas element 71 | this.canvas = document.getElementById("canvas"); 72 | this.canvas.width = window.innerWidth; 73 | this.canvas.height = window.innerHeight; 74 | 75 | // show Emscripten environment where the canvas is 76 | // arguments are passed to PICO-8 77 | 78 | (window).Module = {}; 79 | (window).Module.canvas = this.canvas; 80 | 81 | // key blocker. prevent cursor keys from scrolling page while playing cart. 82 | // Generated with cart.html 83 | document.addEventListener('keydown', (event: any) => { 84 | event = event || window.event; 85 | var o = document.activeElement; 86 | if (!o || o == document.body || o.tagName == "canvas") { 87 | if ([32, 37, 38, 39, 40].indexOf(event.keyCode) > -1) { 88 | if (event.preventDefault) event.preventDefault(); 89 | } 90 | // Also check for Enter/Pause and O for onOption 91 | // Escape will cause a lot of problems with Ionic :( 92 | // Doing everything to stop this propogation, even on other handlers 93 | // https://javascript.info/bubbling-and-capturing 94 | if ([13, 80].indexOf(event.keyCode) > -1) { 95 | if (event.stopImmediatePropagation) event.stopImmediatePropagation(); 96 | if (event.stopPropagation) event.stopPropagation(); 97 | if (event.preventDefault) event.preventDefault(); 98 | this.onPause.emit(true); 99 | } 100 | if ([79].indexOf(event.keyCode) > -1) { 101 | if (event.stopImmediatePropagation) event.stopImmediatePropagation(); 102 | if (event.stopPropagation) event.stopPropagation(); 103 | if (event.preventDefault) event.preventDefault(); 104 | // In a timeout to stop modal from closing itself 105 | this.onOption.emit(true); 106 | } 107 | } 108 | }, false); 109 | 110 | // Lastly, Load the cart 111 | const cartScript = document.createElement('script'); 112 | cartScript.setAttribute('src', `cart/${picoDeployConfig.cart.cartName}`); 113 | cartScript.setAttribute('type', 'text/javascript'); 114 | document.body.appendChild(cartScript); 115 | 116 | // Listen for the cart to run to override some default functions 117 | this.listenForCartRun(); 118 | } 119 | 120 | listenForCartRun() { 121 | if((window).Module && 122 | (window).Module["calledRun"]) { 123 | // Override / create events for Anything here 124 | 125 | // Create our paused event 126 | const pauseEvent = new Event('picoDeployPause'); 127 | 128 | // Replace the pause functions so we can access them, and throttle them 129 | (window).Module._pico8PicoDeployPauseThrottleStatus = false; 130 | (window).Module.pico8PicoDeployPauseThrottle = () => { 131 | (window).Module._pico8PicoDeployPauseThrottleStatus = true; 132 | setTimeout(() => { 133 | (window).Module._pico8PicoDeployPauseThrottleStatus = false; 134 | }, 750); 135 | }; 136 | 137 | (window).Module.pico8TogglePaused = () => { 138 | 139 | if((window).Module._pico8PicoDeployPauseThrottleStatus) { 140 | return; 141 | } 142 | 143 | (window).codo_command = 4; 144 | (window).Module.pico8IsPaused = !(window).Module.pico8IsPaused; 145 | 146 | (window).Module.pico8PicoDeployPauseThrottle(); 147 | (window).dispatchEvent(pauseEvent); 148 | } 149 | 150 | (window).Module.pico8SetPaused = (shouldPause) => { 151 | 152 | if((window).Module._pico8PicoDeployPauseThrottleStatus) { 153 | return; 154 | } 155 | 156 | (window).codo_command = 5; 157 | (window).codo_command_p = 0; 158 | if(shouldPause) { 159 | (window).Module.pico8IsPaused = true; 160 | (window).codo_command_p = 1; 161 | } else { 162 | (window).Module.pico8IsPaused = false; 163 | } 164 | 165 | (window).Module.pico8PicoDeployPauseThrottle(); 166 | (window).dispatchEvent(pauseEvent); 167 | } 168 | 169 | 170 | // Lastly create/displatch our called run event 171 | const cartCalledRunEvent = new Event('picoDeployCartCalledRun'); 172 | (window).dispatchEvent(cartCalledRunEvent); 173 | } 174 | 175 | if((window).Module && (window)._cdpos > 0) { 176 | 177 | // Lastly create/displatch our called run event 178 | const cartPlayableEvent = new Event('picoDeployCartPlayable'); 179 | (window).dispatchEvent(cartPlayableEvent); 180 | 181 | // Return stop listening 182 | return; 183 | } 184 | 185 | 186 | // Keep Listening 187 | setTimeout(() => { 188 | this.listenForCartRun(); 189 | }, 250); 190 | } 191 | 192 | ngOnDestroy() { 193 | if(this.platform.is('cordova') && 194 | this.onResumeSubscription && 195 | this.onPauseSubscription) { 196 | // always unsubscribe your subscriptions to prevent leaks 197 | this.onResumeSubscription.unsubscribe(); 198 | this.onPauseSubscription.unsubscribe(); 199 | } 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /src/components/components.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { IonicModule } from 'ionic-angular'; 3 | import { CartComponent } from './cart/cart'; 4 | import { GamepadComponent } from './gamepad/gamepad'; 5 | import { PicosplashComponent } from './picosplash/picosplash'; 6 | import { PicomediaComponent } from './picomedia/picomedia'; 7 | @NgModule({ 8 | declarations: [ 9 | CartComponent, 10 | GamepadComponent, 11 | PicosplashComponent, 12 | PicomediaComponent 13 | ], 14 | imports: [IonicModule], 15 | exports: [ 16 | CartComponent, 17 | GamepadComponent, 18 | PicosplashComponent, 19 | PicomediaComponent 20 | ] 21 | }) 22 | export class ComponentsModule {} 23 | -------------------------------------------------------------------------------- /src/components/gamepad/gamepad.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | {{currentTouch}} 5 |
6 | 7 | 8 | 9 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 20 | 21 | 22 | 23 | 24 | 25 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | 35 | 36 | 37 | 38 | 39 | 41 | 42 | 43 | 44 |
45 | -------------------------------------------------------------------------------- /src/components/gamepad/gamepad.scss: -------------------------------------------------------------------------------- 1 | 2 | // Define some nice variables for our UI 3 | $dpadSize: 30vw; 4 | $buttonSize: 17vw; 5 | $settingsSize: 7vw; 6 | 7 | gamepad { 8 | position: absolute; 9 | top: 0px; 10 | left: 0px; 11 | height: 100%; 12 | width: 100%; 13 | 14 | z-index: 1; 15 | opacity: 0.5; 16 | 17 | @include smq__not-mobile { 18 | display: none; 19 | } 20 | 21 | .current-touch { 22 | color: red; 23 | } 24 | 25 | .pauseBtn { 26 | position: absolute; 27 | top: 0; 28 | left: 0; 29 | 30 | width: $settingsSize; 31 | height: auto; 32 | } 33 | 34 | .settingsBtn { 35 | position: absolute; 36 | top: 0; 37 | right: 0; 38 | 39 | width: $settingsSize; 40 | height: auto; 41 | } 42 | 43 | .dpad { 44 | position: absolute; 45 | bottom: 0; 46 | left: 0; 47 | 48 | width: $dpadSize; 49 | height: auto; 50 | } 51 | 52 | .squareBtn { 53 | position: absolute; 54 | bottom: 0; 55 | right: calc(#{$buttonSize} + 1vw); 56 | 57 | width: $buttonSize; 58 | height: auto; 59 | } 60 | 61 | .crossBtn { 62 | position: absolute; 63 | bottom: 0; 64 | right: 0; 65 | 66 | width: $buttonSize; 67 | height: auto; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/components/gamepad/gamepad.ts: -------------------------------------------------------------------------------- 1 | import { Component, Output, EventEmitter } from '@angular/core'; 2 | import { SettingsProvider } from '../../providers/settings/settings'; 3 | import { Platform } from 'ionic-angular'; 4 | 5 | /** 6 | * Generated class for the GamepadComponent component. 7 | * 8 | * See https://angular.io/api/core/Component for more info on Angular 9 | * Components. 10 | */ 11 | @Component({ 12 | selector: 'gamepad', 13 | templateUrl: 'gamepad.html' 14 | }) 15 | export class GamepadComponent { 16 | @Output() onSettingsClick: EventEmitter = new EventEmitter(); 17 | 18 | buttons: any 19 | currentTouch: any 20 | showVirtualGamepad: boolean 21 | 22 | constructor(public settingsProvider: SettingsProvider, public platform: Platform) { 23 | 24 | // Set up our buttons 25 | (window).pico8_buttons = [0,0,0,0,0,0,0,0]; 26 | // Show the virtualgamepad if we are on a cordova device, or a mobile device 27 | // Inside a web browser (e.g chrome mobile device emeulator) 28 | if(this.platform.is('cordova') || 29 | this.platform.is('mobileweb')) { 30 | this.showVirtualGamepad = true; 31 | } else { 32 | this.showVirtualGamepad = false; 33 | // Also, Add the gamepad script 34 | const picoGamepadScript = document.createElement('script'); 35 | picoGamepadScript.setAttribute('src', 'assets/3pLibs/pico8gamepad/pico8gamepad.js'); 36 | picoGamepadScript.setAttribute('type', 'text/javascript'); 37 | document.body.appendChild(picoGamepadScript); 38 | } 39 | } 40 | 41 | ngOnInit() { 42 | 43 | // Check if we are showing the gamepad. If not, simply return here 44 | if(!this.showVirtualGamepad) { 45 | return; 46 | } 47 | 48 | // Get our buttons, and their position 49 | this.buttons = { 50 | dpad: { 51 | element: document.getElementById('dpad'), 52 | pressed: { 53 | left: false, 54 | right: false, 55 | up: false, 56 | down: false 57 | } 58 | }, 59 | squareBtn: { 60 | element: document.getElementById('squareBtn'), 61 | pressed: false 62 | }, 63 | crossBtn: { 64 | element: document.getElementById('crossBtn'), 65 | pressed: false 66 | }, 67 | settingsBtn: { 68 | element: document.getElementById('settingsBtn'), 69 | }, 70 | pauseBtn: { 71 | element: document.getElementById('pauseBtn'), 72 | } 73 | } 74 | 75 | // Add a resize listen to update the gamepad rect on resize 76 | window.addEventListener("resize", () => { 77 | this.updateGamepadRect(); 78 | }); 79 | this.updateGamepadRect(); 80 | 81 | // Loop through our buttons and add touch events 82 | Object.keys(this.buttons).forEach((button) => { 83 | this.buttons[button].element.addEventListener("touchstart", (event) => { 84 | this.touchEventHandler(event); 85 | }, false); 86 | this.buttons[button].element.addEventListener("touchmove", (event) => { 87 | this.touchEventHandler(event); 88 | }, false); 89 | this.buttons[button].element.addEventListener("touchend", (event) => { 90 | this.touchEventHandler(event); 91 | }, false); 92 | this.buttons[button].element.addEventListener("mousedown", (event) => { 93 | this.touchEventHandler(event); 94 | }, false); 95 | this.buttons[button].element.addEventListener("mouseup", (event) => { 96 | this.touchEventHandler(event); 97 | }, false); 98 | }); 99 | 100 | // Poll to keep updating the controller state 101 | window.requestAnimationFrame(() => this.updatePico8Controller()); 102 | } 103 | 104 | //Function to get an element target from SVGs being embedded in HTML 105 | getEventTargetElementId(event) { 106 | 107 | let targetId = event.target.id; 108 | // Return the first id in the event path 109 | event.path.some((element) => { 110 | if(element.id && element.id.length > 0) { 111 | targetId = element.id; 112 | return true; 113 | } 114 | return false; 115 | }); 116 | 117 | return targetId; 118 | } 119 | 120 | // Function to update button position and size 121 | updateGamepadRect() { 122 | // Read from the DOM, and get each of our elements position, doing this here, as it is best to read from the dom in sequence 123 | // use element.getBoundingRect() top, bottom, left, right to get clientX and clientY in touch events :) 124 | // https://stackoverflow.com/questions/442404/retrieve-the-position-x-y-of-an-html-element 125 | //console.log("GamepadComponent: Updating Rect()..."); 126 | Object.keys(this.buttons).forEach((button) => { 127 | const buttonBoundingRect = this.buttons[button].element.getBoundingClientRect(); 128 | this.buttons[button].rect = buttonBoundingRect; 129 | }); 130 | } 131 | 132 | // Our handler function for touch events 133 | // Will stop event from propogating, and pass to the correct handler 134 | touchEventHandler(event) { 135 | if (!event || !event.touches) return 136 | 137 | //event.stopPropagation(); 138 | event.preventDefault(); 139 | 140 | //this.debugCurrentTouch(event); 141 | 142 | // Get our key event info 143 | if(event.type === "touchstart" || 144 | event.type === "touchmove" || 145 | event.type === "mousedown") { 146 | 147 | // Handle Dpad events 148 | if(this.getEventTargetElementId(event) === 'dpad') { 149 | // Reset the dpad, only one direction at a time 150 | this.resetDpad(); 151 | 152 | // Calculate for the correct key 153 | // Only using the first touch, since we shouldn't be having two fingers on the dpad 154 | let touch; 155 | if (event.type.includes('touch')) { 156 | touch = event.touches[0]; 157 | } else if (event.type.includes('mouse')) { 158 | touch = event; 159 | } 160 | 161 | // Find if the horizontal or vertical influence is greater 162 | // Find our centers of our rectangles, and our unbiased X Y values on the rect 163 | const rectCenterX = (this.buttons.dpad.rect.right - this.buttons.dpad.rect.left) / 2; 164 | const rectCenterY = (this.buttons.dpad.rect.bottom - this.buttons.dpad.rect.top) / 2; 165 | const touchX = touch.clientX - this.buttons.dpad.rect.left; 166 | let touchY = touch.clientY - this.buttons.dpad.rect.top; 167 | 168 | // Fix for shoot button causing the character to move right on multi touch error 169 | // + 50 for some buffer 170 | if(touchX > (rectCenterX + (this.buttons.dpad.rect.width / 2) + 50)) { 171 | // Ignore the event 172 | return; 173 | } 174 | 175 | // Create an additonal influece for horizontal, to make it feel better 176 | const horizontalInfluence = this.buttons.dpad.rect.width / 8; 177 | 178 | // Determine if we are horizontal or vertical 179 | const isHorizontal = Math.abs(rectCenterX - touchX) + horizontalInfluence > Math.abs(rectCenterY - touchY); 180 | 181 | // Find if left or right from width, vice versa for height 182 | if(isHorizontal) { 183 | // Add a horizontal dead zone 184 | const deadzoneSize = this.buttons.dpad.rect.width / 20; 185 | if(Math.abs((this.buttons.dpad.rect.width / 2) - touchX) > deadzoneSize) { 186 | const isLeft = touchX < (this.buttons.dpad.rect.width / 2); 187 | if(isLeft) { 188 | this.buttons.dpad.pressed.left = true; 189 | } else { 190 | this.buttons.dpad.pressed.right = true; 191 | } 192 | } 193 | } else { 194 | const isUp = touchY < (this.buttons.dpad.rect.height / 2); 195 | if(isUp) { 196 | this.buttons.dpad.pressed.up = true; 197 | } else { 198 | this.buttons.dpad.pressed.down = true; 199 | } 200 | } 201 | } 202 | 203 | // Handle Square button 204 | if(this.getEventTargetElementId(event) === 'squareBtn') { 205 | this.buttons.squareBtn.pressed = true; 206 | } 207 | 208 | // Handle Cross Button 209 | if(this.getEventTargetElementId(event) === 'crossBtn') { 210 | this.buttons.crossBtn.pressed = true; 211 | } 212 | 213 | // Handle Settings Button 214 | if(this.getEventTargetElementId(event) === 'settingsBtn') { 215 | // Only want to respect the touch start event 216 | if(event.type !== "touchmove") { 217 | this.onSettingsClick.emit(true); 218 | } 219 | } 220 | 221 | // Handle Pause Button 222 | if(this.getEventTargetElementId(event) === 'pauseBtn') { 223 | // Only want to respect the touch start event 224 | if(event.type !== "touchmove") { 225 | (window).Module.pico8TogglePaused(); 226 | } 227 | } 228 | } else { 229 | 230 | // Handle Dpad events 231 | if(this.getEventTargetElementId(event) === 'dpad') { 232 | this.resetDpad(); 233 | } 234 | 235 | // Handle Square button 236 | if(this.getEventTargetElementId(event) === 'squareBtn') { 237 | this.buttons.squareBtn.pressed = false; 238 | } 239 | 240 | // Handle Cross Button 241 | if(this.getEventTargetElementId(event) === 'crossBtn') { 242 | this.buttons.crossBtn.pressed = false; 243 | } 244 | } 245 | } 246 | 247 | resetDpad() { 248 | if(!this.buttons || !this.buttons.dpad || !this.buttons.dpad.pressed) { 249 | return; 250 | } 251 | 252 | Object.keys(this.buttons.dpad.pressed).forEach(dpadKey => { 253 | this.buttons.dpad.pressed[dpadKey] = false; 254 | }); 255 | } 256 | 257 | // Our Pico 8 Gamepad with help from: https://github.com/krajzeg/pico8gamepad 258 | updatePico8Controller() { 259 | (window).pico8_buttons[0] = 0; 260 | 261 | let bitmask = 0; 262 | 263 | // Go through all of our buttons for the bitmask 264 | if(this.buttons.dpad.pressed.left) bitmask |= 1; 265 | if(this.buttons.dpad.pressed.right) bitmask |= 2; 266 | if(this.buttons.dpad.pressed.up) bitmask |= 4; 267 | if(this.buttons.dpad.pressed.down) bitmask |= 8; 268 | if(this.buttons.squareBtn.pressed) bitmask |= 16; 269 | if(this.buttons.crossBtn.pressed) bitmask |= 32; 270 | 271 | // Set for player one 272 | (window).pico8_buttons[0] |= bitmask; 273 | 274 | // Call the update next frame 275 | window.requestAnimationFrame(() => this.updatePico8Controller()); 276 | } 277 | 278 | debugCurrentTouch(event) { 279 | if (event.touches[0]) { 280 | this.currentTouch = JSON.stringify({ 281 | target: this.getEventTargetElementId(event), 282 | clientX: event.touches[0].clientX, 283 | clientY: event.touches[0].clientY 284 | }, null, 2); 285 | } 286 | } 287 | 288 | } 289 | -------------------------------------------------------------------------------- /src/components/picomedia/picomedia.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 10 |
11 | -------------------------------------------------------------------------------- /src/components/picomedia/picomedia.scss: -------------------------------------------------------------------------------- 1 | .picomedia { 2 | width: 100%; 3 | height: 100%; 4 | 5 | img { 6 | width: 100%; 7 | height: 100%; 8 | } 9 | 10 | video { 11 | width: 100%; 12 | height: 100%; 13 | object-fit: fill; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/components/picomedia/picomedia.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input, AfterViewInit } from '@angular/core'; 2 | 3 | /** 4 | * Generated class for the PicomediaComponent component. 5 | * 6 | * See https://angular.io/api/core/Component for more info on Angular 7 | * Components. 8 | */ 9 | @Component({ 10 | selector: 'picomedia', 11 | templateUrl: 'picomedia.html' 12 | }) 13 | export class PicomediaComponent implements AfterViewInit { 14 | @Input() filepath: string; 15 | 16 | isImage: boolean; 17 | isVideo: boolean; 18 | videoElementId: any; 19 | videoElement: any; 20 | 21 | constructor() { 22 | // Set our variables to false 23 | this.isImage = false; 24 | this.isVideo = false; 25 | } 26 | 27 | ngAfterViewInit() { 28 | // Error and return if we do not have a passed path 29 | if(!this.filepath) { 30 | console.error('Picomedia Component: No passed path was found.'); 31 | return; 32 | } 33 | 34 | // Regex for isImage and is video 35 | if((/\.(gif|jpg|jpeg|tiff|png)$/i).test(this.filepath)) { 36 | this.isImage = true; 37 | } else if((/\.(mp4|webm|)$/i).test(this.filepath)) { 38 | this.isVideo = true; 39 | } else { 40 | // File type not supported by native HTML5 41 | console.error(`Picomedia Component: Supplied path is not a supported HTML5 file type: ${this.filepath}`); 42 | return; 43 | } 44 | 45 | // Start our listener for the media to pause if the media is video 46 | // Find when the game is paused 47 | if (this.isVideo) { 48 | this.videoElementId = `picomedia-video${Math.floor(Math.random() * 10000)}` 49 | // Timeout to wait for ngIf to apply 50 | setTimeout(() => { 51 | // Get our video element 52 | this.videoElement = document.getElementById(this.videoElementId); 53 | // Play the video 54 | // TODO: Need to think of a solution from Chrome autoplay policy, 55 | // as video wont play without interaction 56 | // this.videoElement.play(); 57 | 58 | // Listen to our pause event from cart.js 59 | (window).addEventListener('picoDeployPause', () => { 60 | if(!(window).Module.pico8IsPaused && 61 | this.videoElement.paused) { 62 | // resume 63 | this.videoElement.play(); 64 | } else if ((window).Module.pico8IsPaused && 65 | !this.videoElement.paused) { 66 | // Pause 67 | this.videoElement.pause() 68 | } 69 | }); 70 | }); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/components/picosplash/picosplash.html: -------------------------------------------------------------------------------- 1 | 2 |
5 | 6 | 7 | 8 | 9 | 10 |
11 | -------------------------------------------------------------------------------- /src/components/picosplash/picosplash.scss: -------------------------------------------------------------------------------- 1 | picosplash { 2 | .picosplash__container { 3 | position: fixed; 4 | width: 100%; 5 | height: 100%; 6 | 7 | background-color: #000; 8 | z-index: 2147483647; 9 | text-align: center; 10 | 11 | .picosplash__background-media { 12 | position: absolute; 13 | z-index: -100; 14 | left: 0; 15 | top: 0; 16 | width: 100%; 17 | height: 100%; 18 | } 19 | 20 | 21 | .picosplash__media .picomedia { 22 | height: 100%; 23 | width: auto; 24 | max-width: 100vh; 25 | z-index: 100; 26 | 27 | margin-left: auto; 28 | margin-right: auto; 29 | text-align: center; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/components/picosplash/picosplash.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { SettingsProvider } from '../../providers/settings/settings'; 3 | 4 | const picoDeployConfig = require('../../../picoDeployConfig.json'); 5 | 6 | /** 7 | * Generated class for the PicosplashComponent component. 8 | * 9 | * See https://angular.io/api/core/Component for more info on Angular 10 | * Components. 11 | */ 12 | @Component({ 13 | selector: 'picosplash', 14 | templateUrl: 'picosplash.html' 15 | }) 16 | export class PicosplashComponent { 17 | 18 | initializingCart: boolean 19 | splashMedia: string 20 | splashBackgroundMedia: any 21 | cartMuted: boolean 22 | 23 | constructor(private settingsProvider: SettingsProvider) { 24 | this.initializingCart = false; 25 | this.splashMedia = "" 26 | if(picoDeployConfig.picosplash.enable) { 27 | this.initializingCart = true; 28 | this.splashMedia = picoDeployConfig.picosplash.splashMedia 29 | } 30 | // Also, Get our cart background media, that way we can loop it above the cart and all looks good 31 | this.splashBackgroundMedia = false; 32 | if(picoDeployConfig.backgroundMedia) { 33 | this.splashBackgroundMedia = picoDeployConfig.backgroundMedia; 34 | } 35 | this.cartMuted = false; 36 | } 37 | 38 | ngOnInit() { 39 | if(picoDeployConfig.picosplash.enable) { 40 | // Listen to events on the cart.js attatched to the window for picoDeploy 41 | (window).addEventListener('picoDeployCartCalledRun', () => { 42 | // Toggle sound off 43 | if(!this.cartMuted) { 44 | (window).Module.pico8ToggleSound(); 45 | this.cartMuted = true; 46 | } 47 | }); 48 | 49 | (window).addEventListener('picoDeployCartPlayable', () => { 50 | // Toggle sound back on in a second 51 | setTimeout(() => { 52 | // Toggle sound back on 53 | if(this.cartMuted && this.settingsProvider.settings.sound) { 54 | (window).Module.pico8ToggleSound(); 55 | } 56 | 57 | // Wait for the sound on prompt to disappear 58 | setTimeout(() => { 59 | this.initializingCart = false; 60 | }, 1000) 61 | }, 1000); 62 | }); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | picoDeploy 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "picoDeploy", 3 | "short_name": "picodeploy", 4 | "start_url": "index.html", 5 | "display": "standalone", 6 | "icons": [{ 7 | "src": "assets/imgs/logo.png", 8 | "sizes": "512x512", 9 | "type": "image/png" 10 | }], 11 | "background_color": "#4e8ef7", 12 | "theme_color": "#4e8ef7" 13 | } 14 | -------------------------------------------------------------------------------- /src/pages/home/home.html: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/pages/home/home.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { IonicPageModule } from 'ionic-angular'; 3 | import { HomePage } from './home'; 4 | import { ComponentsModule } from '../../components/components.module'; 5 | @NgModule({ 6 | declarations: [ 7 | HomePage 8 | ], 9 | imports: [ 10 | ComponentsModule, 11 | IonicPageModule.forChild(HomePage) 12 | ] 13 | }) 14 | export class HomePageModule { } 15 | -------------------------------------------------------------------------------- /src/pages/home/home.scss: -------------------------------------------------------------------------------- 1 | .home { 2 | position: relative; 3 | 4 | .cart-background { 5 | position: fixed; 6 | z-index: -10; 7 | width: 100%; 8 | height: 100%; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/pages/home/home.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { ModalController, NavController, IonicPage } from 'ionic-angular'; 3 | import { SettingsProvider } from '../../providers/settings/settings'; 4 | 5 | const picoDeployConfig = require('../../../picoDeployConfig.json'); 6 | 7 | @IonicPage() 8 | @Component({ 9 | selector: 'page-home', 10 | templateUrl: 'home.html' 11 | }) 12 | export class HomePage { 13 | 14 | settingsModal: any 15 | settingsModalShown: boolean 16 | cartBackgroundMedia: any 17 | 18 | 19 | constructor(public modalCtrl: ModalController, 20 | public navCtrl: NavController, 21 | public settingsProvider: SettingsProvider) { 22 | this.settingsModalShown = false; 23 | 24 | // Get our cart background media 25 | this.cartBackgroundMedia = false; 26 | if(picoDeployConfig.backgroundMedia) { 27 | this.cartBackgroundMedia = picoDeployConfig.backgroundMedia; 28 | } 29 | } 30 | 31 | ngOnInit() { 32 | this.settingsModal = this.modalCtrl.create('SettingsModal', {onClose: () => { 33 | this.settingsModalShown = false; 34 | }}); 35 | } 36 | 37 | openSettings() { 38 | if(!this.settingsModalShown) { 39 | (window).Module.pico8SetPaused(true); 40 | this.settingsModal.present(); 41 | this.settingsModalShown = true; 42 | } 43 | } 44 | 45 | pauseGame() { 46 | (window).Module.pico8TogglePaused(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/pages/home/settings/settings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Settings 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | Fullscreen 16 | 17 | 18 | 19 | 20 | Sound 21 | 22 | 23 | 24 | 25 | 26 |
27 |
Gamepad Color
28 |
29 | 35 |
36 |
37 | 38 | 39 | 40 |
41 |
Background Color
42 |
43 | 49 |
50 |
51 | 52 | 53 | Stretch 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | Credits 62 | 63 |
64 | 65 |
Aaron Turner (@torch2424): Author of Pico Deploy
66 | 67 |
Lexaloffle, the Pico-8 team
68 | 69 |
Zep, the Pico-8 dude, and the demo carts they helped to create
70 | 71 |
the Pico-8 community
72 | 73 |
The Angular Team
74 | 75 |
The Ionic Team
76 | 77 |
The Electron Team
78 | 79 |
The Node.js Team
80 | 81 |
@andreasonny83: angular2-expandable-list
82 | 83 |
@zefoy: ngx-color-picker
84 | 85 |
Google Material Icons: https://material.io/icons/
86 |
87 |
88 |
89 |
90 |
91 | -------------------------------------------------------------------------------- /src/pages/home/settings/settings.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { IonicPageModule } from 'ionic-angular'; 3 | import { ColorPickerModule } from 'ngx-color-picker'; 4 | import { ExpandableListModule } from 'angular2-expandable-list'; 5 | 6 | import { SettingsModal } from './settings'; 7 | @NgModule({ 8 | declarations: [ 9 | SettingsModal 10 | ], 11 | imports: [ 12 | ColorPickerModule, 13 | ExpandableListModule, 14 | IonicPageModule.forChild(SettingsModal) 15 | ] 16 | }) 17 | export class SettingsModalModule { } 18 | -------------------------------------------------------------------------------- /src/pages/home/settings/settings.scss: -------------------------------------------------------------------------------- 1 | @media only screen and (min-height: 600px) and (min-width: 768px) { 2 | .modal-wrapper { 3 | width: 100%; 4 | height: 100%; 5 | top: 0; 6 | left: 0; 7 | } 8 | } 9 | 10 | .settings__header, .settings__header * { 11 | @extend .pico-font; 12 | color: $inverse; 13 | } 14 | 15 | .settings__header, .settings__header .toolbar-background { 16 | background-color: $transparent-75; 17 | } 18 | 19 | .settings__container .item { 20 | color: $inverse; 21 | background-color: transparent; 22 | } 23 | 24 | .settings__container, .settings__container * { 25 | @extend .pico-font; 26 | } 27 | 28 | .settings__container { 29 | color: $inverse; 30 | background-color: $transparent-75; 31 | 32 | .scroll-content { 33 | padding: 10px; 34 | } 35 | 36 | .color-picker { 37 | .hsla-text, .rgba-text, .hex-text, .type-policy { 38 | display: none !important; 39 | } 40 | 41 | .box { 42 | padding: 0px !important; 43 | } 44 | } 45 | 46 | .ion-item--no-padding ion-label { 47 | margin: 0px !important; 48 | } 49 | 50 | .expandable-list-item, .expandable-list-item * { 51 | color: $inverse !important; 52 | background-color: transparent !important; 53 | padding: 0px !important; 54 | } 55 | 56 | .expandable-list-item div[item] { 57 | width: 100%; 58 | word-break: break-word; 59 | white-space: normal; 60 | } 61 | 62 | .expandable-list-divider { 63 | margin: 0px !important; 64 | margin-top: 10px; 65 | margin-bottom: 10px; 66 | } 67 | 68 | .expandable-list-item__dropdown-chevron-line1, .expandable-list-item__dropdown-chevron-line2 { 69 | stroke: $inverse !important; 70 | } 71 | } 72 | 73 | .fake-ion-item { 74 | margin-left: 16px; 75 | padding-top: 11px; 76 | padding-bottom: 11px; 77 | padding-right: 8px; 78 | 79 | border-bottom: 0.55px solid #c8c7cc; 80 | 81 | display: flex; 82 | align-items: center; 83 | 84 | .fake-ion-item__label { 85 | line-height: 2; 86 | } 87 | 88 | .fake-ion-item__content { 89 | margin-left: auto; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/pages/home/settings/settings.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { IonicPage, ViewController, NavParams } from 'ionic-angular'; 3 | 4 | import { SettingsProvider } from '../../../providers/settings/settings'; 5 | import { Platform } from 'ionic-angular'; 6 | 7 | const picoDeployConfig = require('../../../../picoDeployConfig.json'); 8 | 9 | // Settings Modal 10 | // How to: https://github.com/PaulHalliday/Ionic-3-Modal-Example/tree/master/src/pages 11 | @IonicPage() 12 | @Component({ 13 | templateUrl: 'settings.html' 14 | }) 15 | export class SettingsModal { 16 | 17 | isOpen: boolean 18 | hasCartBackgroundMedia: boolean 19 | // Allow window to be checked by the template for requre 20 | window: any = (window); 21 | 22 | constructor(private params: NavParams, private view: ViewController, public settingsProvider: SettingsProvider, public platform: Platform) { 23 | 24 | // Check if we have cart background media 25 | this.hasCartBackgroundMedia = false; 26 | if(picoDeployConfig.backgroundMedia) { 27 | this.hasCartBackgroundMedia = true; 28 | } 29 | } 30 | 31 | fullscreenChange(event) { 32 | // Check if we can fullscreen the app 33 | if(!this.platform.is('cordova') && !!(window).require) { 34 | (window).require('electron').remote.getCurrentWindow().setFullScreen(event); 35 | // ng-model'd don't need to set to save 36 | this.settingsProvider.save(); 37 | } 38 | } 39 | 40 | soundChange() { 41 | (window).Module.pico8ToggleSound(); 42 | // ng-model'd don't need to set to save 43 | this.settingsProvider.save(); 44 | } 45 | 46 | gamepadColorChange(event) { 47 | this.settingsProvider.settings.gamepadColor = event; 48 | this.settingsProvider.save(); 49 | } 50 | 51 | bgColorChange(event) { 52 | this.settingsProvider.settings.backgroundColor = event; 53 | this.settingsProvider.save(); 54 | } 55 | 56 | closeModal() { 57 | this.view.dismiss(); 58 | (window).Module.pico8SetPaused(false); 59 | if(this.params.get('onClose')) { 60 | this.params.get('onClose')(); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/providers/pico-db/pico-db.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | const idb = require('idb'); 3 | const Buffer = require('buffer/').Buffer 4 | const picoDeployConfig = require('../../../picoDeployConfig.json'); 5 | 6 | /* 7 | Generated class for the PicoDbProvider provider. 8 | 9 | See https://angular.io/guide/dependency-injection for more info on providers 10 | and Angular DI. 11 | */ 12 | // Simple Pub/Sub Listener for changes on the Pico 8 IndexedDB 13 | @Injectable() 14 | export class PicoDbProvider { 15 | 16 | idbKeyval: any; 17 | cartDataKey: string; 18 | currentValue: any = []; 19 | subscribers: any = {}; 20 | 21 | constructor() { 22 | 23 | // Enable or disable the indexedDB provider 24 | if(!picoDeployConfig.dbWatcher.enable) { 25 | return; 26 | } 27 | 28 | window.addEventListener('picoDeployCartPlayable', () => { 29 | // initialize our DB 30 | this.initialize(); 31 | }); 32 | } 33 | 34 | // Function called by the constructor once the cart playable event is fired on the window 35 | initialize() { 36 | // Set our defaults 37 | const dbName = '/user_data'; 38 | const objectStoreName = 'FILE_DATA'; 39 | const cartDataName = picoDeployConfig.dbWatcher.cartDataName; 40 | const cartDataKey = `/user_data/cdata/${cartDataName}.p8d.txt`; 41 | this.cartDataKey = cartDataKey; 42 | 43 | 44 | // pico 8 idb version is 21 45 | const dbPromise = idb.open(dbName, 21, upgradeDB => { 46 | //TODO: Handle Upgrade 47 | console.log('Pico-db: Upgrading db by creating the object store for pico-8', upgradeDB); 48 | 49 | // Create the object store 50 | upgradeDB.createObjectStore(objectStoreName); 51 | }); 52 | 53 | // Only need the get functionality of idbKeyval 54 | // https://github.com/jakearchibald/idb 55 | this.idbKeyval = { 56 | get(key) { 57 | return dbPromise.then(db => { 58 | if(db) { 59 | return db.transaction(objectStoreName) 60 | .objectStore(objectStoreName).get(key); 61 | } 62 | return new Promise((reject) => { 63 | reject(); 64 | }); 65 | }); 66 | } 67 | }; 68 | 69 | this.idbKeyval.get(cartDataKey).then(val => { 70 | if(val === undefined) { 71 | console.warn('PicoDbProvider: The Returned value for the cart data came undefined. Either the cart data has not been created, or there is an invalid cartDataKey.'); 72 | return; 73 | } 74 | this.currentValue = this.bufferToPico8Text(val.contents); 75 | }); 76 | 77 | this.listenForIdbChanges(); 78 | } 79 | 80 | // idb value will be returned as a UTF8 array buffer 81 | // This will convert the buffer to text, and return a proper decimal based array 82 | bufferToPico8Text(buffer) { 83 | // How to buffer: https://stackoverflow.com/questions/6182315/how-to-do-base64-encoding-in-node-js 84 | // By manual testing and reading cart code, the save file is text, that is saved as a buffer 85 | // Converting the buffer to a utf8 string will give you some random 520 character string 86 | // By manually testing, I compared values in the string (that are in hex), and finally 87 | // realized that they were the numbers in my game. Thus, we need to convert the buffer to a 88 | // utf8 string. .replace() to remove all whitespace and new lines 89 | let utf8HexString = Buffer.from(buffer).toString('utf8').replace(/\r?\n|\r/g, ''); 90 | 91 | // From: http://pico-8.wikia.com/wiki/Dset we know that there are 64 numbers. 92 | // And since http://pico-8.wikia.com/wiki/Math tells us the max number is 32767. Therefore 93 | // The maximum value we can store in hex is: 7FFF. From this, I am going to assume we can chop 94 | // This string by every 4 characters. Since, I also noticed my personal save had the numbers 95 | // 03e6 -> 998 . And the preceding zero is space for the max? 96 | /* 97 | Also, after additional testing the code of: 98 | 99 | for i = 0, 63 do 100 | dset(i, -32767) 101 | end 102 | 103 | Which would fill the save file with only the maximum negative number, Gave us: 104 | 105 | 8001000080010000800100008001000080010000800100008001000080010000 106 | 8001000080010000800100008001000080010000800100008001000080010000 107 | 8001000080010000800100008001000080010000800100008001000080010000 108 | 8001000080010000800100008001000080010000800100008001000080010000 109 | 8001000080010000800100008001000080010000800100008001000080010000 110 | 8001000080010000800100008001000080010000800100008001000080010000 111 | 8001000080010000800100008001000080010000800100008001000080010000 112 | 8001000080010000800100008001000080010000800100008001000080010000 113 | 114 | Filling with the postive gave the same effect, except with 7fff. 115 | Meaning we take every other 4 sets of numbers to replicate our dget() from pico 8 :) 116 | */ 117 | 118 | let pico8TextArray = []; 119 | for(let i = 0; i < 64; i++) { 120 | let currentIndex = i * 8; 121 | // Get the string represenatation of the number of hex, and then user parseInt() to get the decimal value 122 | let currentValueInHex = utf8HexString.substring(currentIndex, currentIndex + 4); 123 | pico8TextArray.push(parseInt(currentValueInHex, 16)); 124 | } 125 | 126 | return pico8TextArray; 127 | } 128 | 129 | listenForIdbChanges() { 130 | setTimeout(() => { 131 | 132 | // Check if the value changes 133 | this.idbKeyval.get(this.cartDataKey).then(val => { 134 | if(val !== undefined) { 135 | // Check for differences between the two arrays 136 | const currentDbValue = this.bufferToPico8Text(val.contents); 137 | const diff = this.currentValue.filter(x => currentDbValue.indexOf(x) < 0); 138 | if(diff.length > 0 || 139 | currentDbValue.length > this.currentValue.length) { 140 | // Publish to our subscribers 141 | this.publish(currentDbValue, this.currentValue); 142 | 143 | // Save the new current value 144 | this.currentValue = currentDbValue; 145 | } 146 | } 147 | 148 | // Continue Listening 149 | this.listenForIdbChanges(); 150 | }); 151 | }, 3500); 152 | } 153 | 154 | get() { 155 | return this.currentValue; 156 | } 157 | 158 | publish(newValue, oldValue) { 159 | Object.keys(this.subscribers).forEach(subscriberKey => { 160 | this.subscribers[subscriberKey](newValue, oldValue); 161 | }); 162 | } 163 | 164 | subscribe(key, callback) { 165 | this.subscribers[key] = callback; 166 | } 167 | 168 | unsubscribe(key) { 169 | delete this.subscribers[key]; 170 | } 171 | 172 | } 173 | -------------------------------------------------------------------------------- /src/providers/platform-sdk-wrapper/platform-sdk-wrapper.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Platform } from 'ionic-angular'; 3 | import { PicoDbProvider } from '../pico-db/pico-db'; 4 | 5 | /* 6 | Generated class for the PlatformSdkWrapperProvider provider. 7 | 8 | This will be used as a single service 9 | 10 | See https://angular.io/guide/dependency-injection for more info on providers 11 | and Angular DI. 12 | */ 13 | @Injectable() 14 | export class PlatformSdkWrapperProvider { 15 | 16 | isServiceReady: any 17 | picoDbProvider: PicoDbProvider 18 | dbProviderKey: any 19 | platform: Platform 20 | CONSTANTS: any 21 | //greenworks: any 22 | 23 | constructor( 24 | platform: Platform, 25 | picoDbProvider: PicoDbProvider 26 | ) { 27 | 28 | // Prepare our isService ready 29 | this.isServiceReady = {}; 30 | 31 | // Get our electron window for greenworks 32 | // https://github.com/electron/electron/issues/1095 33 | // Using window.require because: https://github.com/electron/electron/issues/7300 34 | // if((window).require) { 35 | // const electron = (window).require('electron'); 36 | // const electronWindow = electron.remote.getCurrentWindow(); 37 | // this.greenworks = electronWindow.greenworks; 38 | // } 39 | this.platform = platform; 40 | this.picoDbProvider = picoDbProvider; 41 | 42 | // Define our achievements 43 | this.CONSTANTS = { 44 | ACHIEVEMENTS: { 45 | EXAMPLE: { 46 | STEAM_ID: 'EXAMPLE_STEAM_ID', 47 | GOOGLE_PLAY: 'EXAMPLE_GOOGLE_PLAY_ID' 48 | } 49 | }, 50 | LEADERBOARD: { 51 | STEAM: { 52 | EXAMPLE_ID: false 53 | }, 54 | GOOGLE_PLAY: { 55 | EXAMPLE_ID: 'EXAMPLE_LEADERBOARD_ID', 56 | } 57 | } 58 | } 59 | } 60 | 61 | // Function to start watching the DB to send events to our platforms 62 | initialize() { 63 | console.log('PlatformSdkWrapperProvider: Starting SDK Services...'); 64 | 65 | // Attempt to start greenworks 66 | // if (!this.platform.is('mobile') && this.greenworks) { 67 | // this.isServiceReady.greenworks = true; 68 | // console.log('PlatformSdkWrapperProvider: Steamworks working!'); 69 | // } 70 | 71 | // https://www.npmjs.com/package/cordova-plugin-play-games-services 72 | // Attempt to start play games 73 | // if (this.platform.is('android') && 74 | // (window).plugins && 75 | // (window).plugins.playGamesServices) { 76 | // // Sign into play games 77 | // (window).plugins.playGamesServices.auth(() => { 78 | // // Success 79 | // console.log('Google Play Games Ready!'); 80 | // this.isServiceReady.playGames = true; 81 | // }, () => { 82 | // // Failed 83 | // console.log('Google Play Games Failed!'); 84 | // }); 85 | // } 86 | 87 | this.dbProviderKey = 'PlatformSdkWrapperProvider-picoDeploy'; 88 | this.picoDbProvider.subscribe(this.dbProviderKey, (newValue, oldValue) => { 89 | this.checkAchievements(newValue, oldValue); 90 | this.postToLeaderboard(newValue, oldValue); 91 | }); 92 | } 93 | 94 | // Function called everythime the db watcher tells us something changed 95 | checkAchievements(newValue, oldValue) { 96 | console.log('PlatformSdkWrapperProvider: ', 'Checking Achievements...') 97 | 98 | // Example: Launch an achievement when the first value changes 99 | if (newValue[0] > 0) { 100 | // Unlock Achievements 101 | this.unlockAchievement(this.CONSTANTS.ACHIEVEMENTS.EXAMPLE); 102 | } 103 | } 104 | 105 | // Function to unlock an achievement 106 | unlockAchievement(achievementObject) { 107 | 108 | console.log('PlatformSdkWrapperProvider: Unlocking Achievement: ', achievementObject); 109 | 110 | // Unlock Achievements for Google Play example 111 | // if (this.isServiceReady.playGames) { 112 | // (window).plugins.playGamesServices.unlockAchievement({ 113 | // achievementId: achievementObject.GOOGLE_PLAY_ID 114 | // }); 115 | // } 116 | 117 | // Unlock Achievements for steam using greenworks example 118 | // if (this.isServiceReady.greenworks) { 119 | // this.greenworks.getAchievement( 120 | // achievementObject.STEAM_ID, 121 | // (isAchieved) => { 122 | // // Success 123 | // if (!isAchieved) { 124 | // this.greenworks.activateAchievement( 125 | // achievementObject.STEAM_ID, 126 | // () => { 127 | // // Success 128 | // console.log('Success Unlocked Achievement: ', achievementObject); 129 | // }, () => { 130 | // // Error 131 | // console.log('Error unlocking Achievement: ', achievementObject); 132 | // } 133 | // ); 134 | // } 135 | // // Achievement already unlocked, ignoring... 136 | // }, () => { 137 | // // Error 138 | // console.log('Could Not check achievement status.', achievementObject); 139 | // } 140 | // ); 141 | // } 142 | } 143 | 144 | // Function to Post to a Leaderboard 145 | postToLeaderboard(newValue, oldValue) { 146 | 147 | console.log('PlatformSdkWrapperProvider: Posting to leaderboard: ', newValue, oldValue); 148 | 149 | 150 | // Example Posting to a Google Play Leaderboard 151 | 152 | // // Create an array of scores to submit 153 | // // Push data in the format of: https://github.com/artberri/cordova-plugin-play-games-services#sumit-score-now 154 | // var scoresToSubmit = []; 155 | // 156 | // if (newValue[1] !== oldValue[1] && 157 | // newValue[1] < oldValue[1]) { 158 | // scoresToSubmit.push({ 159 | // score: newValue[1], 160 | // leaderboardId: this.CONSTANTS.LEADERBOARD.EXAMPLE, 161 | // }); 162 | // } 163 | // 164 | // if (newValue[6] !== oldValue[6] && 165 | // newValue[6] > oldValue[6]) { 166 | // scoresToSubmit.push({ 167 | // score: newValue[6], 168 | // leaderboardId: this.CONSTANTS.LEADERBOARD.EXAMPLE, 169 | // }); 170 | // } 171 | // 172 | // scoresToSubmit.forEach(scoreObject => { 173 | // if (this.isServiceReady.playGames) { 174 | // (window).plugins.playGamesServices.submitScoreNow(scoreObject); 175 | // } 176 | // 177 | // if (this.isServiceReady.greenworks) { 178 | // // Do Nothing. 179 | // // Leaderboards for steam is not supported by greenworks, and will require their web API. 180 | // // Because of this, will need to be hosted on a secure server, and then that server, will interact 181 | // // with the steam web api. 182 | // // https://github.com/greenheartgames/greenworks/issues/12 183 | // // https://partner.steamgames.com/doc/webapi/ISteamLeaderboards#GetLeaderboardEntries 184 | // } 185 | // }); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/providers/settings/settings.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Storage } from '@ionic/storage'; 3 | import { Platform } from 'ionic-angular'; 4 | 5 | const picoDeployConfig = require('../../../picoDeployConfig.json'); 6 | 7 | /* 8 | Generated class for the SettingsProvider provider. 9 | 10 | See https://angular.io/guide/dependency-injection for more info on providers 11 | and Angular DI. 12 | */ 13 | @Injectable() 14 | export class SettingsProvider { 15 | 16 | // Our default settings 17 | // Can be edited directly by components 18 | settings: any 19 | 20 | constructor(private storage: Storage, private platform: Platform) { 21 | // Set our default settings 22 | this.settings = { 23 | fullscreen: true, 24 | sound: true, 25 | backgroundColor: "#272727", 26 | gamepadColor: "#FFFFFF", 27 | stretch: false 28 | } 29 | 30 | // Apply the default passed in by picoDeployConfig 31 | this.settings = Object.assign({}, this.settings, picoDeployConfig.defaultSettings); 32 | 33 | // Get our saved settings 34 | const savedSettingsPromises = []; 35 | Object.keys(this.settings).forEach(settingKey => { 36 | savedSettingsPromises.push(new Promise((resolve, reject) => { 37 | storage.get(settingKey).then(value => { 38 | if(value !== undefined && value !== null) { 39 | this.settings[settingKey] = value; 40 | } 41 | resolve(); 42 | }).catch((error) => { 43 | reject(error); 44 | }); 45 | })); 46 | }); 47 | 48 | // Check if we can fullscreen the app 49 | Promise.all(savedSettingsPromises).then(() => { 50 | if (this.settings.fullscreen && !this.platform.is('cordova') && !!(window).require) { 51 | (window).require('electron').remote.getCurrentWindow().setFullScreen(true); 52 | } 53 | }); 54 | } 55 | 56 | // Save our settings object back into the storage 57 | save() { 58 | Object.keys(this.settings).forEach(settingKey => { 59 | this.storage.set(settingKey, this.settings[settingKey]); 60 | }); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/service-worker.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Check out https://googlechrome.github.io/sw-toolbox/ for 3 | * more info on how to use sw-toolbox to custom configure your service worker. 4 | */ 5 | 6 | 7 | 'use strict'; 8 | importScripts('./build/sw-toolbox.js'); 9 | 10 | self.toolbox.options.cache = { 11 | name: 'ionic-cache' 12 | }; 13 | 14 | // pre-cache our key assets 15 | self.toolbox.precache( 16 | [ 17 | './build/main.js', 18 | './build/vendor.js', 19 | './build/main.css', 20 | './build/polyfills.js', 21 | 'index.html', 22 | 'manifest.json' 23 | ] 24 | ); 25 | 26 | // dynamically cache any other local assets 27 | self.toolbox.router.any('/*', self.toolbox.cacheFirst); 28 | 29 | // for any other requests go to the network, cache, 30 | // and then only use that cached resource if your user goes offline 31 | self.toolbox.router.default = self.toolbox.networkFirst; 32 | -------------------------------------------------------------------------------- /src/theme/variables.scss: -------------------------------------------------------------------------------- 1 | // Ionic Variables and Theming. For more info, please see: 2 | // http://ionicframework.com/docs/theming/ 3 | 4 | // Font path is used to include ionicons, 5 | // roboto, and noto sans fonts 6 | $font-path: "../assets/fonts"; 7 | 8 | // Import our fonts 9 | // https://www.lexaloffle.com/bbs/?tid=3760 10 | @font-face { 11 | font-family: "pico8"; 12 | src: url($font-path + "/PICO-8MonoUpper.ttf") format("truetype"); 13 | } 14 | 15 | .pico-font { 16 | font-family: 'pico8', "Roboto", "Helvetica Neue", sans-serif !important; 17 | } 18 | 19 | 20 | // The app direction is used to include 21 | // rtl styles in your app. For more info, please see: 22 | // http://ionicframework.com/docs/theming/rtl-support/ 23 | $app-direction: ltr; 24 | 25 | 26 | @import "ionic.globals"; 27 | 28 | 29 | // Shared Variables 30 | // -------------------------------------------------- 31 | // To customize the look and feel of this app, you can override 32 | // the Sass variables found in Ionic's source scss files. 33 | // To view all the possible Ionic variables, see: 34 | // http://ionicframework.com/docs/theming/overriding-ionic-variables/ 35 | 36 | 37 | 38 | 39 | // Named Color Variables 40 | // -------------------------------------------------- 41 | // Named colors makes it easy to reuse colors on various components. 42 | // It's highly recommended to change the default colors 43 | // to match your app's branding. Ionic uses a Sass map of 44 | // colors so you can add, rename and remove colors as needed. 45 | // The "primary" color is the only required color in the map. 46 | 47 | $colors: ( 48 | primary: #488aff, 49 | secondary: #32db64, 50 | danger: #f53d3d, 51 | light: #f4f4f4, 52 | dark: #222 53 | ); 54 | 55 | 56 | // App iOS Variables 57 | // -------------------------------------------------- 58 | // iOS only Sass variables can go here 59 | 60 | 61 | 62 | 63 | // App Material Design Variables 64 | // -------------------------------------------------- 65 | // Material Design only Sass variables can go here 66 | 67 | 68 | 69 | 70 | // App Windows Variables 71 | // -------------------------------------------------- 72 | // Windows only Sass variables can go here 73 | 74 | 75 | 76 | 77 | // App Theme 78 | // -------------------------------------------------- 79 | // Ionic apps can have different themes applied, which can 80 | // then be future customized. This import comes last 81 | // so that the above variables are used and Ionic's 82 | // default are overridden. 83 | 84 | @import "ionic.theme.default"; 85 | 86 | 87 | // Ionicons 88 | // -------------------------------------------------- 89 | // The premium icon font for Ionic. For more info, please see: 90 | // http://ionicframework.com/docs/ionicons/ 91 | 92 | @import "ionic.ionicons"; 93 | 94 | 95 | // Fonts 96 | // -------------------------------------------------- 97 | 98 | @import "roboto"; 99 | @import "noto-sans"; 100 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "declaration": false, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "lib": [ 8 | "dom", 9 | "es2015" 10 | ], 11 | "module": "es2015", 12 | "moduleResolution": "node", 13 | "sourceMap": true, 14 | "target": "es5" 15 | }, 16 | "include": [ 17 | "src/**/*.ts" 18 | ], 19 | "exclude": [ 20 | "node_modules" 21 | ], 22 | "compileOnSave": false, 23 | "atom": { 24 | "rewriteTsconfig": false 25 | } 26 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-duplicate-variable": true, 4 | "no-unused-variable": [ 5 | true 6 | ] 7 | }, 8 | "rulesDirectory": [ 9 | "node_modules/tslint-eslint-rules/dist/rules" 10 | ] 11 | } 12 | --------------------------------------------------------------------------------