├── .gitignore ├── src ├── patches │ └── default │ │ ├── screenshot.png │ │ ├── credits.txt │ │ ├── legal.txt │ │ ├── cables.txt │ │ ├── LICENCE │ │ └── index.html ├── package.json ├── package-lock.json ├── prompt.html ├── DaPlaya.js ├── Store.js └── main.js ├── package.json ├── update_electron.sh ├── .github └── workflows │ └── release.yml ├── LICENCE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml 3 | build/ 4 | node_modules/ 5 | src/node_modules/ 6 | electron/ 7 | -------------------------------------------------------------------------------- /src/patches/default/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cables-gl/cables-daplaya/master/src/patches/default/screenshot.png -------------------------------------------------------------------------------- /src/patches/default/credits.txt: -------------------------------------------------------------------------------- 1 | - ops by cables user pandur (https://cables.gl/user/pandur) 2 | - cables by undefined development (http://undev.studio) 3 | - glmatrix by toji (https://github.com/toji/gl-matrix) 4 | - Photoshop math GLSL shaders by Romain Dura (https://mouaif.wordpress.com/2009/01/05/photoshop-math-with-glsl-shaders/) 5 | - ops by cables user cabpandurles (https://cables.gl/user/cabpandurles) 6 | - ops by cables user andro (https://cables.gl/user/andro) -------------------------------------------------------------------------------- /src/patches/default/legal.txt: -------------------------------------------------------------------------------- 1 | All cables core code is licenced under the MIT licence 2 | 3 | Some ops may be using code from contributors or external libraries under different licences. 4 | 5 | Additional code from contributors and libraries in this export are licenced as follows: 6 | 7 | MIT: 8 | 9 | - glmatrix by toji (https://github.com/toji/gl-matrix) 10 | 11 | Public Domain: 12 | 13 | - Photoshop math GLSL shaders by Romain Dura (https://mouaif.wordpress.com/2009/01/05/photoshop-math-with-glsl-shaders/) 14 | -------------------------------------------------------------------------------- /src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cables-daplaya", 3 | "version": "0.3.0", 4 | "license": "MIT", 5 | "description": "a standalone player for cables.gl-patches using the electron framework\n", 6 | "author": "undefined development ", 7 | "repository": "github:cables-gl/cables-daplaya", 8 | "keywords": [ 9 | "cables", 10 | "cables.gl", 11 | "webgl", 12 | "daplaya", 13 | "electron" 14 | ], 15 | "main": "main.js", 16 | "dependencies": { 17 | "adm-zip": "0.4.11" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cables-daplaya-build-env", 3 | "version": "0.3.0", 4 | "license": "MIT", 5 | "description": "electron build environment for cables-daplaya\n", 6 | "author": "undefined development ", 7 | "repository": "github:cables-gl/cables-daplaya", 8 | "keywords": [ 9 | "cables", 10 | "cables.gl", 11 | "webgl", 12 | "daplaya", 13 | "electron" 14 | ], 15 | "scripts": { 16 | "clean": "rm -rf node_modules/ && rm -rf src/node_modules/ && find src/patches/ -maxdepth 1 -mindepth 1 ! -name default ! -name .gitignore -exec rm -rf {} \\;", 17 | "update": "./update_electron.sh 20.0.0", 18 | "build:linux": "./build.sh linux-x64", 19 | "build:osx": "./build.sh darwin-x64 && ./build.sh darwin-arm64", 20 | "build:win": "./build.sh win32-x64", 21 | "package": "./update_electron.sh 20.0.0 && ./build.sh win32-x64 zip && ./build.sh darwin-x64 zip && ./build.sh linux-x64 zip" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /update_electron.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | electron_version=${1?missing electron version as first parameter}; 5 | platforms=(linux-x64 darwin-x64 darwin-arm64 win32-x64); 6 | download_and_unzip() { 7 | wget -q --show-progress https://github.com/electron/electron/releases/download/v${electron_version}/${1} && unzip -q ${1} && rm -f ${1} 8 | } 9 | 10 | mkdir -p electron/ 11 | cd electron/ 12 | 13 | for platform in "${platforms[@]}"; do 14 | mkdir -p $platform; 15 | cd $platform; 16 | skip=false; 17 | if [ -f ./version ]; then 18 | current_version=`cat version`; 19 | if [[ "${current_version}" == "${electron_version}" ]]; then 20 | echo "skipping ${platform}, same version (${current_version})"; 21 | cd .. 22 | continue; 23 | fi 24 | fi 25 | rm -rf *; 26 | zipname=electron-v${electron_version}-${platform}.zip 27 | download_and_unzip $zipname 28 | cd .. 29 | done 30 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: 'release' 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - master 8 | 9 | jobs: 10 | nightly-merge: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v1 15 | - uses: actions/setup-node@v1 16 | with: 17 | node-version: '16.x' 18 | - name: get-npm-version 19 | id: package-version 20 | uses: martinbeentjes/npm-get-version-action@main 21 | - name: create executable 22 | id: create-executable 23 | run: exec npm run package 24 | - name: upload build 25 | uses: softprops/action-gh-release@v1 26 | with: 27 | name: v${{ steps.package-version.outputs.current-version}}-${{ github.run_number }} 28 | token: ${{ secrets.GITHUB_TOKEN }} 29 | tag_name: v${{ steps.package-version.outputs.current-version}}-${{ github.run_number }} 30 | files: | 31 | ./build/daplaya-win32-x64.zip 32 | ./build/daplaya-darwin-x64.zip 33 | ./build/daplaya-linux-x64.zip 34 | -------------------------------------------------------------------------------- /src/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cables-daplaya", 3 | "version": "0.3.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "cables-daplaya", 9 | "version": "0.3.0", 10 | "license": "MIT", 11 | "dependencies": { 12 | "adm-zip": "0.4.11" 13 | } 14 | }, 15 | "node_modules/adm-zip": { 16 | "version": "0.4.11", 17 | "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.11.tgz", 18 | "integrity": "sha512-L8vcjDTCOIJk7wFvmlEUN7AsSb8T+2JrdP7KINBjzr24TJ5Mwj590sLu3BC7zNZowvJWa/JtPmD8eJCzdtDWjA==", 19 | "engines": { 20 | "node": ">=0.3.0" 21 | } 22 | } 23 | }, 24 | "dependencies": { 25 | "adm-zip": { 26 | "version": "0.4.11", 27 | "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.11.tgz", 28 | "integrity": "sha512-L8vcjDTCOIJk7wFvmlEUN7AsSb8T+2JrdP7KINBjzr24TJ5Mwj590sLu3BC7zNZowvJWa/JtPmD8eJCzdtDWjA==" 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/patches/default/cables.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | M A D E W I T H 4 | 5 | C A B L E S >>> ___:_ _ 6 | _ _:_______________ _____________ / |\ _ _______ 7 | | _ /\\_ \ |\\. _) /\ ______ 8 | _____ | (/) _/\\\(_______ / |\\| / __/\\\ / /\ 9 | / _/\_ _ /_\\\/_\\\\ _/ / |\\|/ \_\\\/___ / /\\\ 10 | / /_\\|_ |\ \\( /\ \ /_ _:\\/__ /\_/ /_\ _/_\\/___ 11 | _/ /( \ |_\ \__ /_\\_) \ (_ / \ /\ 12 | \ _\ \___ _/ \ / / \_ /\\\ 13 | \_________( _|\\\\ \_ ___________/ _ /_________/ / /\\\/ 14 | \\\\\\\\|_____)\\\ |\\\\\/ (/) /\\\\\\\/ /_\\/ 15 | \\\\\\\\\\\\\\\\\\_______:\\.\/______________/\\\\\\\\\_________________(\\ 16 | _|.._ \\\\\\\) \\\\\\\\\\| \\\\\\\\\\\\\\\\\\\/ \\\\\\\\\\\\\\\\\\\\ 17 | (_|||_) \\\\\\\\\| \\\\\\\\\\\\\\\\\/ \\\\\\\\\\\\\\\\\\( 18 | ---|-->> 19 | 20 | 21 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019-present undefined development 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/patches/default/LICENCE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-present undefined development 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/prompt.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 29 | 30 | 31 | 32 |

33 |

34 | apiKeys:
35 | patchId:

36 | 37 | 38 |

39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /src/patches/default/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | cables 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /src/DaPlaya.js: -------------------------------------------------------------------------------- 1 | const { app, net } = require('electron'); 2 | 3 | const Stream = require('stream').Transform; 4 | const path = require('path'); 5 | const fs = require('fs'); 6 | const AdmZip = require('adm-zip'); 7 | 8 | class DaPlaya { 9 | 10 | static getPatchFile(store, successCallback, errorCallback) { 11 | const patchId = store.getPatchId(); 12 | const apiKey = store.getApiKey(); 13 | const baseUrl = 'https://cables.gl'; 14 | const url = `${baseUrl}/api/project/${patchId}/export?a=1&type=download&assets=auto&combineJS=true&skipBackups=true`; 15 | const request = net.request({ 16 | url: url 17 | }); 18 | request.setHeader('X-apikey', apiKey); 19 | request.on('response', (response) => { 20 | if (response.statusCode !== 200) { 21 | if (typeof errorCallback === 'function') { 22 | errorCallback(response); 23 | } 24 | } 25 | let exportInfo = ''; 26 | response.on('data', (chunk) => { 27 | exportInfo += chunk; 28 | }); 29 | response.on('end', () => { 30 | const info = JSON.parse(exportInfo); 31 | const zipUrl = `${baseUrl}${info.path}`; 32 | const originalBasename = path.basename(info.path, '.zip'); 33 | const patchDir = originalBasename.substr(0, originalBasename.lastIndexOf('_')); 34 | const zipRequest = net.request({ 35 | url: zipUrl 36 | }); 37 | // zipRequest.chunkedEncoding = true; 38 | zipRequest.setHeader('X-apikey', apiKey); 39 | zipRequest.on('response', (zipResponse) => { 40 | if (zipResponse.statusCode !== 200) { 41 | if (typeof errorCallback === 'function') { 42 | errorCallback(zipResponse); 43 | } 44 | } 45 | let zipContent = new Stream(); 46 | zipResponse.on('data', (zipChunk) => { 47 | zipContent.push(zipChunk); 48 | }); 49 | zipResponse.on('end', () => { 50 | successCallback(patchDir, zipContent); 51 | }); 52 | }); 53 | zipRequest.on('error', (e) => { 54 | throw `http error (${zipResponse.statusCode})`; 55 | }); 56 | zipRequest.end(); 57 | 58 | }); 59 | }); 60 | request.on('error', (response) => { 61 | throw `http error (${response.statusCode})`; 62 | }); 63 | request.end(); 64 | } 65 | 66 | static importPatch(store, successCallback, errorCallback) { 67 | DaPlaya.getPatchFile(store, (patchDir, zipContent) => { 68 | const storageLocation = path.join(store.getStorageDir(), patchDir); 69 | if (!fs.existsSync(storageLocation)) { 70 | fs.mkdirSync(storageLocation, { recursive: true }); 71 | } 72 | fs.writeFileSync(`${storageLocation}.zip`, zipContent.read()); 73 | const zip = AdmZip(`${storageLocation}.zip`); 74 | zip.extractAllTo(storageLocation, true); 75 | let patchIndexHtml = path.join( 76 | store.getStorageDir(), 77 | "default", 78 | "index.html" 79 | ); 80 | fs.copyFileSync(patchIndexHtml, storageLocation + "/index.html"); 81 | if (typeof successCallback === 'function') { 82 | store.setCurrentPatchDir(storageLocation); 83 | successCallback(storageLocation); 84 | } 85 | }, () => { 86 | if (typeof errorCallback === 'function') { 87 | errorCallback(); 88 | } 89 | }); 90 | } 91 | 92 | static reloadPatch(store, successCallback, errorCallback) { 93 | DaPlaya.importPatch(store, (patchId, zipContent) => { 94 | if (typeof successCallback === 'function') { 95 | successCallback(); 96 | } 97 | }, () => { 98 | if (typeof errorCallback === 'function') { 99 | errorCallback(); 100 | } 101 | }); 102 | }; 103 | } 104 | 105 | // expose the class 106 | module.exports = DaPlaya; 107 | -------------------------------------------------------------------------------- /src/Store.js: -------------------------------------------------------------------------------- 1 | const electron = require('electron'); 2 | const path = require('path'); 3 | const fs = require('fs'); 4 | 5 | class Store { 6 | 7 | constructor(storageDir) { 8 | this.MAIN_CONFIG_NAME = 'daplaya-preferences'; 9 | this.APIKEY_FIELD = 'apiKey'; 10 | this.PATCHID_FIELD = 'patchId'; 11 | this.CURRENTPATCHDIR_FIELD = 'currentPatchDir'; 12 | this.STORAGEDIR_FIELD = 'storageDir'; 13 | this.WINDOW_X_POS_FIELD = 'windowX'; 14 | this.WINDOW_Y_POS_FIELD = 'windowY'; 15 | this.WINDOW_FULLSCREEN = 'windowFullscreen'; 16 | this.WINDOW_HEIGHT = 'windowHeight'; 17 | this.WINDOW_WIDTH = 'windowWidth'; 18 | 19 | this.opts = {}; 20 | this.opts.defaults = {}; 21 | this.opts.configName = this.MAIN_CONFIG_NAME; 22 | this.opts.defaults[this.APIKEY_FIELD] = null; 23 | this.opts.defaults[this.PATCHID_FIELD] = null; 24 | this.opts.defaults[this.CURRENTPATCHDIR_FIELD] = null; 25 | this.opts.defaults[this.STORAGEDIR_FIELD] = storageDir; 26 | 27 | this.opts.defaults[this.WINDOW_X_POS_FIELD] = null; 28 | this.opts.defaults[this.WINDOW_Y_POS_FIELD] = null; 29 | this.opts.defaults[this.WINDOW_FULLSCREEN] = false; 30 | this.opts.defaults[this.WINDOW_HEIGHT] = 768; 31 | this.opts.defaults[this.WINDOW_WIDTH] = 1366; 32 | 33 | this.data = this.opts.defaults; 34 | this.refresh(); 35 | } 36 | 37 | refresh() { 38 | if (this.data && this.data.hasOwnProperty(this.STORAGEDIR_FIELD) && this.data[this.STORAGEDIR_FIELD]) { 39 | const userDataPath = path.join(this.data[this.STORAGEDIR_FIELD], this.opts.configName + '.json'); 40 | this.data = Store.parseDataFile(userDataPath, this.opts.defaults); 41 | } 42 | } 43 | 44 | get(key) { 45 | this.refresh(); 46 | if (!this.data) { 47 | return null; 48 | } 49 | return this.data[key]; 50 | } 51 | 52 | set(key, val, silent) { 53 | this.data[key] = val; 54 | let configFileName = path.join(this.data[this.STORAGEDIR_FIELD], this.opts.configName + '.json'); 55 | if (!silent) { 56 | fs.writeFileSync(configFileName, JSON.stringify(this.data)); 57 | } 58 | } 59 | 60 | // convenience methods 61 | getApiKey() { 62 | return this.get(this.APIKEY_FIELD); 63 | } 64 | 65 | setApiKey(value) { 66 | this.set(this.APIKEY_FIELD, value); 67 | } 68 | 69 | getCurrentPatchDir() { 70 | return this.get(this.CURRENTPATCHDIR_FIELD); 71 | } 72 | 73 | setCurrentPatchDir(value) { 74 | this.set(this.CURRENTPATCHDIR_FIELD, value); 75 | } 76 | 77 | getStorageDir() { 78 | return this.get(this.STORAGEDIR_FIELD); 79 | } 80 | 81 | setStorageDir(value) { 82 | this.set(this.STORAGEDIR_FIELD, value, true); 83 | } 84 | 85 | getPatchId() { 86 | return this.get(this.PATCHID_FIELD); 87 | } 88 | 89 | setPatchId(value) { 90 | this.set(this.PATCHID_FIELD, value); 91 | } 92 | 93 | getWindowXPos() { 94 | return this.get(this.WINDOW_X_POS_FIELD); 95 | } 96 | 97 | setWindowXPos(value) { 98 | this.set(this.WINDOW_X_POS_FIELD, value); 99 | } 100 | 101 | getWindowYPos() { 102 | return this.get(this.WINDOW_Y_POS_FIELD); 103 | } 104 | 105 | setWindowYPos(value) { 106 | this.set(this.WINDOW_Y_POS_FIELD, value); 107 | } 108 | 109 | getFullscreen() { 110 | return this.get(this.WINDOW_FULLSCREEN); 111 | } 112 | 113 | setFullscreen(value) { 114 | this.set(this.WINDOW_FULLSCREEN, value); 115 | } 116 | 117 | getWindowHeight() { 118 | return this.get(this.WINDOW_HEIGHT); 119 | } 120 | 121 | setWindowHeight(value) { 122 | this.set(this.WINDOW_HEIGHT, value); 123 | } 124 | 125 | getWindowWidth() { 126 | return this.get(this.WINDOW_WIDTH); 127 | } 128 | 129 | setWindowWidth(value) { 130 | this.set(this.WINDOW_WIDTH, value); 131 | } 132 | 133 | // helper methods 134 | static parseDataFile(filePath, defaults) { 135 | try { 136 | let jsonContent = fs.readFileSync(filePath); 137 | return JSON.parse(jsonContent); 138 | } catch (error) { 139 | return defaults; 140 | } 141 | } 142 | } 143 | 144 | // expose the class 145 | module.exports = Store; 146 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cables daplaya 2 | 3 | **THIS REPOSITORY IS NO LONGER MAINTAINED: check [cables standalone](https://cables.gl/standalone) or the cables [exe export](https://cables.gl/docs/4_export_embed/dev_embed/export_exe/export_exe) for alternatives!** 4 | 5 | a simple offline player for [cables](https://cables.gl) patches using the electron framework. 6 | 7 | can be used on different platforms (win, osx, linux) to use cables patches offline after importing them through daplaya. 8 | 9 | ## CAVEATS MAC OS 10 | - Apple introduced some security features that make it next to impossible to build these kind of apps at the moment, so there is a few caveats when using this on MacOS. 11 | - The executable is not signed, you will need to whitelist it on every machine you run it on (right click, "open" usually does that). 12 | - We cannot sign this executable, as it's contents do change when downloading a patch, this is intended...sorry... 13 | - The build for arm/m1-architecture is even harder to run, apple forbids running unsigned apps that are build for arm entirely, not even whitelisting is possible...you may sign it on your own, though. 14 | - Once you downloaded the app, USE THE FINDER to move it basically anywhere else (like your desktop) before opening it 15 | - Apple puts downloaded apps into a random read-only directory UNTIL YOU MOVE IT USING FINDER, we need to write to the directory...sorry... 16 | - There are ways to make the executable/app work on your mac: [on this website](https://appuals.com/damaged-app-cannot-be-opened-on-macos-error/) . We had success using the command line snippet: `sudo xattr -cr appName.app` 17 | 18 | 19 | ## features: 20 | * import your patches by using patchid and apikey 21 | * copy whole directory to usb stick and run from there 22 | * cables integrations should work as expected (i.e. midi-ops) 23 | * runs on windows, osx and linux (all 64 bit architecture) 24 | * might be useful to circumvent browser restrictions for external inputs 25 | 26 | ## usage 27 | * download the appropriate zip file from the [releases page](https://github.com/cables-gl/cables-daplaya/releases) 28 | * create an apikey in [cables](https://cables.gl/settings) (apikeys) (try to use one per instance) 29 | * find the patch you want to export and copy the id from the URL (or the whole url, who cares...) 30 | * extract the zipfile somewhere on your computer (i.e. an usb stick) 31 | * start daplaya (win: daplaya.exe, osx: daplaya.app, linux: ./daplaya) 32 | * you will see a default patch with a cables logo 33 | * press `ctrl-n` to enter apikey and patchid, wait for export 34 | * press `escape` for fullscreen 35 | 36 | ## keyboard commands (also availabe via menu) 37 | * `alt` to toggle the menu 38 | * `esc` to toggle fullscreen 39 | * `ctrl-n` to import a new patch 40 | * `ctrl-r` to synchronize the patch with the version in the cables editor (reimport) 41 | * `ctrl-o` open filebrowser to open patches that you have exported manually from cables 42 | 43 | # development 44 | 45 | ## building 46 | * building requires you have run `npm run update` or `./update_electron.sh ` before 47 | * `npm run clean`to clean up nodemodules and preimported patches and settings 48 | * `npm run build:linux` build linux app in build/linux-x64 49 | * `npm run build:win` build linux app in build/win32-x64 50 | * `npm run build:osx` build linux app in build/darwin-x64 51 | * build artefacts are in build/ 52 | 53 | ## releases 54 | * releases will try to update electron and build all three platforms as well as the corresponding zipfiles 55 | * this is usually done by github actions 56 | * `npm run package` 57 | 58 | # LICENCE 59 | 60 | The MIT License (MIT) 61 | 62 | Copyright (c) 2019-present undefined development 63 | 64 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 65 | 66 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 67 | 68 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 69 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | const { dialog, app, ipcMain, BrowserWindow, Menu } = require("electron"); 2 | 3 | const path = require("path"); 4 | 5 | const Store = require("./Store"); 6 | const DaPlaya = require("./DaPlaya"); 7 | 8 | const url = require("url"); 9 | const fs = require("fs"); 10 | 11 | // global reference to mainWindow (necessary to prevent window from being garbage collected) 12 | let mainWindow; 13 | 14 | const store = new Store(path.join(__dirname, 'patches')); 15 | 16 | let isDefaultPatch = true; 17 | 18 | function createWindow() { 19 | 20 | // Create the browser window. 21 | mainWindow = new BrowserWindow({ 22 | backgroundColor: "#000000", 23 | show: false, 24 | x: store.getWindowXPos(), 25 | y: store.getWindowYPos(), 26 | width: store.getWindowWidth(), 27 | height: store.getWindowHeight(), 28 | webPreferences: { 29 | nodeIntegration: true, 30 | webSecurity: false, 31 | contextIsolation: false, 32 | } 33 | }); 34 | // mainWindow.webContents.openDevTools() 35 | 36 | mainWindow.on("move", () => { 37 | const pos = mainWindow.getPosition(); 38 | store.setWindowXPos(pos[0]); 39 | store.setWindowYPos(pos[1]); 40 | }); 41 | 42 | mainWindow.on("resize", () => { 43 | const size = mainWindow.getSize(); 44 | store.setWindowWidth(size[0]); 45 | store.setWindowHeight(size[1]); 46 | }); 47 | 48 | mainWindow.switchPatch = () => { 49 | const patchDir = store.getCurrentPatchDir(); 50 | let patchIndexHtml = path.join(patchDir, "index.html"); 51 | mainWindow.loadURL( 52 | url.format({ 53 | pathname: patchIndexHtml, 54 | protocol: "file:", 55 | slashes: true 56 | }) 57 | ); 58 | }; 59 | mainWindow.getNewPatch = () => { 60 | let child = new BrowserWindow({ 61 | parent: mainWindow, 62 | width: 320, 63 | height: 150, 64 | modal: true, 65 | show: false, 66 | center: true, 67 | movable: false, 68 | closable: true, 69 | skipTaskbar: true, 70 | frame: false, 71 | hasShadow: true, 72 | titleBarStyle: "hidden", 73 | webPreferences: { 74 | nodeIntegration: true, 75 | webSecurity: false, 76 | contextIsolation: false, 77 | } 78 | }); 79 | child.loadURL( 80 | url.format({ 81 | pathname: path.join(__dirname, "prompt.html"), 82 | protocol: "file:", 83 | slashes: true 84 | }) 85 | ); 86 | child.once("ready-to-show", () => { 87 | child.show(); 88 | }); 89 | }; 90 | mainWindow.setFullScreen(store.getFullscreen()); 91 | mainWindow.setAutoHideMenuBar(true); 92 | 93 | // load the index.html of the patch, default to the prepackaged version 94 | let patchIndexHtml = path.join( 95 | store.getStorageDir(), 96 | "default", 97 | "index.html" 98 | ); 99 | const patchId = store.getPatchId(); 100 | if (patchId) { 101 | const patchLocation = store.getCurrentPatchDir(); 102 | if (fs.existsSync(patchLocation)) { 103 | patchIndexHtml = path.join(patchLocation, "index.html"); 104 | isDefaultPatch = false; 105 | } 106 | } 107 | mainWindow.loadURL( 108 | url.format({ 109 | pathname: patchIndexHtml, 110 | protocol: "file:", 111 | slashes: true 112 | }) 113 | ); 114 | 115 | mainWindow.webContents.on("devtools-opened", () => { 116 | mainWindow.focus(); 117 | setImmediate(() => { 118 | mainWindow.focus(); 119 | }); 120 | }); 121 | 122 | mainWindow.once("ready-to-show", () => { 123 | mainWindow.show(); 124 | // needs a timeout so the below window will still draw 125 | setTimeout(() => { 126 | if (isDefaultPatch) { 127 | dialog.showMessageBox(mainWindow, { 128 | type: "info", 129 | title: "hello!", 130 | message: 131 | "this seems to be your first time here, \npress ctrl-n to import a patch and get going \nor use the menu (press alt to show) to open existing patches\n press escape to toggle fullscreen", 132 | buttons: ["OK"] 133 | }); 134 | } 135 | }, 500); 136 | }); 137 | 138 | mainWindow.on("closed", () => { 139 | mainWindow = null; 140 | }); 141 | 142 | var menu = Menu.buildFromTemplate([ 143 | { 144 | label: "Menu", 145 | submenu: [ 146 | { 147 | label: "Reload patch", 148 | accelerator: "CmdOrCtrl+R", 149 | click() { 150 | try { 151 | DaPlaya.reloadPatch( 152 | store, 153 | () => { 154 | mainWindow.switchPatch(); 155 | }, 156 | message => { 157 | dialog.showMessageBox(mainWindow, { 158 | type: "error", 159 | title: "error", 160 | message: message 161 | }); 162 | } 163 | ); 164 | } catch (e) { 165 | dialog.showMessageBox(mainWindow, { 166 | type: "error", 167 | title: "error", 168 | message: e.messsage 169 | }); 170 | } 171 | } 172 | }, 173 | { 174 | label: "Download new patch", 175 | accelerator: "CmdOrCtrl+N", 176 | click() { 177 | try { 178 | mainWindow.getNewPatch(); 179 | } catch (e) { 180 | dialog.showMessageBox(mainWindow, { 181 | type: "error", 182 | title: "error", 183 | message: e.messsage 184 | }); 185 | } 186 | } 187 | }, 188 | { 189 | label: "Open patch", 190 | accelerator: "CmdOrCtrl+O", 191 | click() { 192 | try { 193 | dialog.showOpenDialog( 194 | mainWindow, 195 | { 196 | title: "select patchdir", 197 | defaultPath: store.getStorageDir(), 198 | properties: ["openDirectory"] 199 | }, 200 | dirNames => { 201 | if (!dirNames || dirNames.length > 1) { 202 | dialog.showMessageBox(mainWindow, { 203 | type: "warning", 204 | title: "pick one", 205 | message: "choose exactly one directory" 206 | }); 207 | } else { 208 | store.setCurrentPatchDir(dirNames[0]); 209 | mainWindow.switchPatch(); 210 | } 211 | } 212 | ); 213 | } catch (e) { 214 | dialog.showMessageBox(mainWindow, { 215 | type: "error", 216 | title: "error", 217 | message: e.messsage 218 | }); 219 | } 220 | } 221 | }, 222 | { 223 | label: "Toggle fullscreen", 224 | accelerator: "Escape", 225 | click() { 226 | if (mainWindow.isFullScreen()) { 227 | mainWindow.setFullScreen(false); 228 | store.setFullscreen(false); 229 | } else { 230 | mainWindow.setFullScreen(true); 231 | store.setFullscreen(true); 232 | } 233 | } 234 | }, 235 | { 236 | label: "Exit", 237 | click() { 238 | app.quit(); 239 | } 240 | } 241 | ] 242 | }, 243 | { 244 | label: "Edit", 245 | submenu: [ 246 | { role: "undo" }, 247 | { role: "redo" }, 248 | { type: "separator" }, 249 | { role: "cut" }, 250 | { role: "copy" }, 251 | { role: "paste" }, 252 | { role: "pasteandmatchstyle" }, 253 | { role: "delete" }, 254 | { role: "selectall" } 255 | ] 256 | } 257 | ]); 258 | 259 | Menu.setApplicationMenu(menu); 260 | } 261 | 262 | ipcMain.on("set-new-credentials", (e, arg) => { 263 | let patchId = arg.patchId; 264 | if (patchId.includes("cables.gl")) { 265 | const parts = patchId.split("/"); 266 | const path = parts[parts.length - 1].split("?"); 267 | patchId = path[0]; 268 | } 269 | 270 | store.setApiKey(arg.apiKey); 271 | store.setPatchId(patchId); 272 | DaPlaya.importPatch( 273 | store, 274 | () => { 275 | mainWindow.switchPatch(); 276 | // needs a timeout so the below window will still draw 277 | setTimeout(() => { 278 | dialog.showMessageBox(mainWindow, { 279 | type: "info", 280 | title: "yay!", 281 | message: 282 | "patch successfully imported, you can use ctrl-r to resync with the cables editor", 283 | buttons: ["OK"] 284 | }); 285 | }, 500); 286 | }, 287 | message => { 288 | dialog.showMessageBox(mainWindow, { 289 | type: "error", 290 | title: "error", 291 | message, 292 | buttons: ["damn it"] 293 | }); 294 | } 295 | ); 296 | }); 297 | 298 | app.on("ready", function() { 299 | createWindow(); 300 | }); 301 | 302 | // Quit when all windows are closed. 303 | app.on("window-all-closed", function() { 304 | // On OS X it is common for applications and their menu bar 305 | // to stay active until the user quits explicitly with Cmd + Q 306 | if (process.platform !== "darwin") { 307 | app.quit(); 308 | } 309 | }); 310 | 311 | app.on("activate", function() { 312 | // On OS X it's common to re-create a window in the app when the 313 | // dock icon is clicked and there are no other windows open. 314 | if (mainWindow === null) { 315 | createWindow(); 316 | } 317 | }); 318 | --------------------------------------------------------------------------------