├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ └── build.yml ├── .gitignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── img ├── buttonLocations-example.png └── settings-dialog.png ├── package-lock.json ├── package.json ├── src └── index.ts ├── streamdeck-plugin-vue ├── .browserslistrc ├── .editorconfig ├── .eslintrc.js ├── README.md ├── compile.bat ├── package-lock.json ├── package.json ├── public │ ├── backend.html │ ├── css │ │ ├── caret.svg │ │ ├── check.png │ │ ├── check.svg │ │ ├── elg_calendar.svg │ │ ├── elg_calendar_inv.svg │ │ ├── rcheck.svg │ │ └── sdpi.css │ ├── img │ │ ├── actionIcon.png │ │ ├── actionIcon@2x.png │ │ ├── pluginIcon.png │ │ └── pluginIcon@2x.png │ ├── manifest.json │ ├── pi.html │ └── settings.html ├── src │ ├── backend │ │ ├── App.vue │ │ ├── backend.ts │ │ └── main.ts │ ├── pi │ │ ├── App.vue │ │ ├── main.ts │ │ └── pi.ts │ ├── settings │ │ ├── App.vue │ │ └── main.ts │ └── types │ │ ├── browser-global.d.ts │ │ ├── shims-tsx.d.ts │ │ └── shims-vue.d.ts ├── tsconfig.json └── vue.config.js ├── streamdeck-plugin ├── com.zoton2.example.sdPlugin │ ├── backend.html │ ├── backend.js │ ├── css │ │ ├── caret.svg │ │ ├── check.png │ │ ├── check.svg │ │ ├── elg_calendar.svg │ │ ├── elg_calendar_inv.svg │ │ ├── rcheck.svg │ │ └── sdpi.css │ ├── img │ │ ├── actionIcon.png │ │ ├── actionIcon@2x.png │ │ ├── pluginIcon.png │ │ └── pluginIcon@2x.png │ ├── manifest.json │ ├── pi.html │ ├── pi.js │ ├── settings.html │ └── settings.js └── compile.bat ├── test └── server.ts ├── tsconfig.json ├── types └── index.d.ts └── vetur.config.js /.eslintignore: -------------------------------------------------------------------------------- 1 | .eslintrc.js 2 | node_modules/**/* 3 | types/**/* 4 | dist/**/* 5 | test/**/* 6 | streamdeck-plugin/**/* 7 | vetur.config.js 8 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true, 5 | }, 6 | parser: '@typescript-eslint/parser', 7 | parserOptions: { 8 | project: 'tsconfig.json', 9 | }, 10 | plugins: [ 11 | '@typescript-eslint', 12 | ], 13 | extends: [ 14 | 'airbnb-base', 15 | 'airbnb-typescript/base', 16 | 'eslint:recommended', 17 | 'plugin:@typescript-eslint/recommended', 18 | 'plugin:import/typescript', 19 | ], 20 | settings: { 21 | 'import/resolver': { 22 | typescript: { 23 | // intentionally left blank 24 | }, 25 | }, 26 | 'import/extensions': ['.js', '.jsx', '.ts', '.tsx'], 27 | }, 28 | rules: { 29 | '@typescript-eslint/lines-between-class-members': 'off', 30 | // max-len set to ignore "import" lines (as they usually get long and messy). 31 | 'max-len': ['error', { code: 100, ignorePattern: '^import\\s.+\\sfrom\\s.+;$' }], 32 | // I mainly have this off as it ruins auto import sorting in VSCode. 33 | 'object-curly-newline': 'off', 34 | 'import/extensions': ['error', 'ignorePackages', { 35 | js: 'never', 36 | jsx: 'never', 37 | ts: 'never', 38 | tsx: 'never', 39 | }], 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build(/Release) 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | 7 | release: 8 | types: [ published ] 9 | 10 | workflow_dispatch: 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | # Setup some Node.js stuff 18 | - name: Node.js setup 19 | uses: actions/setup-node@v2 20 | with: 21 | node-version: '16' 22 | registry-url: https://registry.npmjs.org/ 23 | 24 | # Set NPM cache options 25 | - name: Set NPM cache 26 | uses: actions/cache@v2 27 | with: 28 | path: ~/.npm 29 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 30 | restore-keys: | 31 | ${{ runner.os }}-node- 32 | 33 | # Update NPM to the latest version 34 | - name: Update NPM installation 35 | run: npm install -g npm@latest 36 | 37 | # Checkout the main branch of this repository with full depth 38 | - name: Checkout 39 | uses: actions/checkout@v2 40 | with: 41 | token: ${{ secrets['GITHUB_TOKEN'] }} 42 | ref: master 43 | fetch-depth: 0 44 | 45 | # Set the local git user config to use the GitHub Actions bot account 46 | - name: Set local git config user details 47 | run: | 48 | git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com" 49 | git config --local user.name "github-actions[bot]" 50 | 51 | # Recheckout the main branch (to solve issues with merging in a moment) 52 | - name: (Re)checkout main branch 53 | run: git checkout master 54 | 55 | # Bump version (if releasing) 56 | - name: Bump version (if releasing) 57 | if: ${{ github.event_name == 'release' }} 58 | run: | 59 | npm version --no-git-tag-version ${{ github.event.release.tag_name }} 60 | git commit -m ${{ github.event.release.tag_name }} -a 61 | git push 62 | 63 | # Checkout build branche and merge the main into build 64 | # This can "fail" if the build branch doesn't exist, but we should continue anyway 65 | - name: Checkout build branch and merge main into build 66 | continue-on-error: true 67 | run: | 68 | git checkout build 69 | git merge master 70 | 71 | # Install NPM dependencies 72 | - name: Install NPM dependencies 73 | run: npm ci 74 | 75 | # Actually build everything 76 | - name: Build 77 | run: | 78 | npm run clean 79 | npm run build 80 | 81 | # NPM publish version (if releasing) 82 | - name: NPM publish 83 | if: ${{ github.event_name == 'release' }} 84 | env: 85 | NODE_AUTH_TOKEN: ${{ secrets['NPM_TOKEN'] }} 86 | run: npm publish --tag ${{ fromJSON('["latest", "next"]')[github.event.release.prerelease] }} 87 | 88 | # Commit newly built files 89 | # This can "fail" if there are no newly changed/built files, but we should continue anyway 90 | - name: Commit built files 91 | id: commit 92 | continue-on-error: true 93 | run: | 94 | git add -f dist 95 | git commit -m "Built files" -a 96 | 97 | # Pushes the built files 98 | - name: Push built files 99 | #if: ${{ steps.commit.outcome == 'success' }} 100 | uses: ad-m/github-push-action@master 101 | with: 102 | github_token: ${{ secrets['GITHUB_TOKEN'] }} 103 | branch: build 104 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Docusaurus cache and generated files 108 | .docusaurus 109 | 110 | # Serverless directories 111 | .serverless/ 112 | 113 | # FuseBox cache 114 | .fusebox/ 115 | 116 | # DynamoDB Local files 117 | .dynamodb/ 118 | 119 | # TernJS port file 120 | .tern-port 121 | 122 | # Stores VSCode versions used for testing VSCode extensions 123 | .vscode-test 124 | 125 | # yarn v2 126 | .yarn/cache 127 | .yarn/unplugged 128 | .yarn/build-state.yml 129 | .yarn/install-state.gz 130 | .pnp.* 131 | 132 | com.zoton2.example.streamDeckPlugin 133 | DistributionTool.exe 134 | streamdeck-plugin-vue/com.zoton2.example.sdPlugin 135 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.workingDirectories": [ 3 | "streamdeck-plugin-vue" 4 | ], 5 | "typescript.tsdk": "node_modules\\typescript\\lib" 6 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 zoton2 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-streamdeck-util 2 | 3 | *Package still under heavy development by someone who doesn't usually make packages, use it at your own risk!* 4 | 5 | Node.js package to help your Node.js scripts easily interface with an official Stream Deck application plugin. 6 | 7 | This package is intended to help you develop your own Node.js based applications that interact with a Stream Deck plugin while not being limited to their choice of backends. The included barebones plugin in the `streamdeck-plugin` folder is coded to bridge the connection between the Stream Deck software and this package. 8 | 9 | This documentation will not teach you how to use/build upon the plugin or code for the Stream Deck SDK; see the [Stream Deck SDK documentation](https://developer.elgato.com/documentation/stream-deck/sdk/overview/) instead. 10 | 11 | Inside the included plugin, in the default Property Inspector for each action, you will find a "Open Settings Dialog" button, which will open a window where settings relating to the connection to the Node.js application can be changed. 12 | 13 | ![Settings Dialog](img/settings-dialog.png) 14 | 15 | At the moment, this package has only been tested to work with the regular sized Stream Deck with 15 LCD keys; any others (Stream Deck Mini, Stream Deck XL, Stream Deck Mobile, etc.) should also work, as the only difference is the amount of keys. 16 | 17 | Technically, this package sets up a simple WebSocket server that the barebones plugin in the `streamdeck-plugin` folder is set up to send/receive messages from, essentially bridging the gap between to two instead of directly connecting to their own WebSocket, but being able to code as if it was the same one. 18 | 19 | ## Install 20 | 21 | `$ npm install streamdeck-util` 22 | 23 | ### Example 24 | 25 | #### JavaScript 26 | 27 | ```javascript 28 | var StreamDeck = require('streamdeck-util'); 29 | 30 | // Create new instance of the utility. 31 | var sd = new StreamDeck(); 32 | 33 | // Start listening for connections from the Stream Deck plugin. 34 | // key - the secret key to authenticate the connection, defaults to DEFAULT_KEY 35 | // port - the port the connection will use, defaults to 9091 36 | // debug - if you want to print debug messages, useful for development 37 | sd.listen({ 38 | key: 'DEFAULT_KEY', 39 | port: 9091, 40 | debug: true 41 | }); 42 | 43 | // When the connection between the plugin and this instance is open. 44 | sd.on('open', () => { 45 | console.log('open'); 46 | }); 47 | 48 | // If the connection between the plugin and this instance is closed. 49 | sd.on('close', (code, reason) => { 50 | console.log('close: %s, %s', code, reason); 51 | }); 52 | 53 | // If there are any errors on the connection between the plugin and this instance. 54 | sd.on('error', (err) => { 55 | console.log('error:'); 56 | console.log(err); 57 | }); 58 | 59 | // Listens for the Stream Deck's events. 60 | sd.on('message', (msg) => { 61 | console.log('message:'); 62 | console.log(msg); 63 | 64 | var buttonLocations = sd.getButtonLocations(); // object, see below 65 | var pluginUUID = sd.getPluginUUID(); // sometimes needed as context when sending messages 66 | 67 | // Send a message back to the Stream Deck application; the send function stringifies it for you. 68 | sd.send({ 69 | event: 'openUrl', 70 | payload: { 71 | url: 'https://www.elgato.com' 72 | } 73 | }); 74 | }); 75 | 76 | // You can directly listen for Stream Deck's events by their name if you want to. 77 | sd.on('keyDown', (msg) => { 78 | console.log('keyDown:'); 79 | console.log(msg); 80 | }); 81 | ``` 82 | 83 | ### Button Locations 84 | 85 | Because of how you may not have a constant connection to the Stream Deck application to be able to receive all of the `willAppear`/`willDisappear` messages and so on, the barebones plugin included keeps track of the location of the buttons along with other things, for example the title. This is updated in this package's side both on initial connection and any time it changes on the plugin side. 86 | 87 | This is an object, that stores the locations of currently visible buttons related to the connected plugin, structured like this... 88 | 89 | ``` 90 | { 91 | 'DEVICE_ID': { 92 | 'ROW_NUMBER': { 93 | 'COLUMN_NUMBER': { 94 | context: 'CONTEXT_ID', 95 | action: 'ACTION_ID', 96 | title: 'BUTTON_TITLE', 97 | isInMultiAction: true/false, (see SDK README) 98 | state: 0-based value, (see SDK README) 99 | titleParameters: object (see SDK README) 100 | } 101 | } 102 | } 103 | } 104 | ``` 105 | 106 | By default, every button location is defined as `null`. 107 | 108 | Example in software: 109 | 110 | ![Example in software](img/buttonLocations-example.png) 111 | 112 | Example object: 113 | ``` 114 | { 115 | '5EC4F4448895B7B97547FDA7E8090A2D': { 116 | '0': { 117 | '0': null, 118 | '1': null, 119 | '2': null, 120 | '3': null, 121 | '4': null 122 | }, 123 | '1': { 124 | '0': null, 125 | '1': null, 126 | '2': { 127 | context: '043F87C7ED1F69CE6CD660EA20773405', 128 | action: 'com.zoton2.example.action', 129 | title: 'Example', 130 | isInMultiAction: false, 131 | state: 0, 132 | titleParameters: { 133 | fontFamily: '', 134 | fontSize: 9, 135 | fontStyle: '', 136 | fontUnderline: false, 137 | showTitle: true, 138 | titleAlignment: 'middle', 139 | titleColor: '#ffffff' 140 | } 141 | }, 142 | '3': null, 143 | '4': null 144 | }, 145 | '2': { 146 | '0': { 147 | context: '043F87C7ED1F69CE6CD660EA20773405', 148 | action: 'com.zoton2.example.action', 149 | title: 'Example', 150 | isInMultiAction: false, 151 | state: 0, 152 | titleParameters: { 153 | fontFamily: '', 154 | fontSize: 9, 155 | fontStyle: '', 156 | fontUnderline: false, 157 | showTitle: true, 158 | titleAlignment: 'middle', 159 | titleColor: '#ffffff' 160 | } 161 | }, 162 | '1': null, 163 | '2': null, 164 | '3': null, 165 | '4': null 166 | } 167 | } 168 | } 169 | ``` 170 | -------------------------------------------------------------------------------- /img/buttonLocations-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoton2/node-streamdeck-util/d2f51374eb44640240362131c830e5bf14a1ee1c/img/buttonLocations-example.png -------------------------------------------------------------------------------- /img/settings-dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoton2/node-streamdeck-util/d2f51374eb44640240362131c830e5bf14a1ee1c/img/settings-dialog.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "streamdeck-util", 3 | "version": "0.4.0", 4 | "description": "Node.js package to help your Node.js scripts easily interface with an official Stream Deck application plugin.", 5 | "keywords": [ 6 | "deck", 7 | "elgato", 8 | "helper", 9 | "stream", 10 | "streamdeck", 11 | "util", 12 | "utility" 13 | ], 14 | "homepage": "https://github.com/zoton2/node-streamdeck-util#readme", 15 | "bugs": { 16 | "url": "https://github.com/zoton2/node-streamdeck-util/issues" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/zoton2/node-streamdeck-util.git" 21 | }, 22 | "license": "MIT", 23 | "author": "zoton2", 24 | "main": "dist/index.js", 25 | "types": "dist/index.d.ts", 26 | "scripts": { 27 | "autofix": "eslint --fix --ext .ts src", 28 | "build": "tsc -b tsconfig.json", 29 | "clean": "trash node_modules/.cache && trash dist", 30 | "test-server": "ts-node test/server.ts", 31 | "watch": "tsc -b tsconfig.json -w" 32 | }, 33 | "dependencies": { 34 | "@types/ws": "^8.2.2", 35 | "ws": "^8.4.0" 36 | }, 37 | "devDependencies": { 38 | "@types/node": "^16.11.19", 39 | "@typescript-eslint/eslint-plugin": "^5.9.0", 40 | "@typescript-eslint/parser": "^5.9.0", 41 | "eslint": "^8.6.0", 42 | "eslint-config-airbnb-base": "^15.0.0", 43 | "eslint-config-airbnb-typescript": "^16.1.0", 44 | "eslint-import-resolver-typescript": "^2.5.0", 45 | "eslint-plugin-import": "^2.25.4", 46 | "trash-cli": "^5.0.0", 47 | "ts-node": "^10.4.0", 48 | "typescript": "^4.5.4" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'stream'; 2 | import * as url from 'url'; 3 | import * as util from 'util'; 4 | import ws from 'ws'; 5 | import { ButtonLocations, ButtonObject, EventReceive } from '../types'; 6 | 7 | /* eslint-disable max-len */ 8 | interface StreamDeck { 9 | on(event: 'open', listener: () => void): this; 10 | on(event: 'init', listener: () => void): this; 11 | on(event: 'error', listener: (err: Error) => void): this; 12 | on(event: 'close', listener: (code: number, reason: string) => void): this; 13 | 14 | on(event: 'message', listener: (data: { [k: string]: unknown }) => void): this; 15 | // Currently a blanket definition for all events, can be expanded in the future. 16 | on(event: 'didReceiveSettings', listener: (data: { [k: string]: unknown }) => void): this; 17 | on(event: 'didReceiveGlobalSettings', listener: (data: { [k: string]: unknown }) => void): this; 18 | on(event: 'keyDown', listener: (data: EventReceive.KeyDown) => void): this; 19 | on(event: 'keyUp', listener: (data: EventReceive.KeyUp) => void): this; 20 | on(event: 'willAppear', listener: (data: { [k: string]: unknown }) => void): this; 21 | on(event: 'willDisappear', listener: (data: { [k: string]: unknown }) => void): this; 22 | on(event: 'titleParametersDidChange', listener: (data: { [k: string]: unknown }) => void): this; 23 | on(event: 'deviceDidConnect', listener: (data: { [k: string]: unknown }) => void): this; 24 | on(event: 'deviceDidDisconnect', listener: (data: { [k: string]: unknown }) => void): this; 25 | on(event: 'applicationDidLaunch', listener: (data: { [k: string]: unknown }) => void): this; 26 | on(event: 'applicationDidTerminate', listener: (data: { [k: string]: unknown }) => void): this; 27 | on(event: 'systemDidWakeUp', listener: (data: { [k: string]: unknown }) => void): this; 28 | on(event: 'propertyInspectorDidAppear', listener: (data: { [k: string]: unknown }) => void): this; 29 | on(event: 'propertyInspectorDidDisappear', listener: (data: { [k: string]: unknown }) => void): this; 30 | on(event: 'sendToPlugin', listener: (data: { [k: string]: unknown }) => void): this; 31 | } 32 | /* eslint-enable */ 33 | 34 | class StreamDeck extends EventEmitter { 35 | wss: ws.Server | undefined; 36 | wsConnection: ws | undefined; 37 | pluginUUID: string | undefined; 38 | debug = false; 39 | buttonLocations: ButtonLocations = {}; 40 | init = 0; // Can be 0, 1 or 2 depending on what initialisation step we are at. 41 | 42 | private log = { 43 | /* eslint-disable no-console */ 44 | shared: (...msg: unknown[]) => { 45 | console.log(`[node-streamdeck-util] ${msg[0]}`, ...msg.slice(1)); 46 | }, 47 | debug: (...msg: unknown[]) => { if (this.debug) this.log.shared(...msg); }, 48 | info: (...msg: unknown[]) => { this.log.shared(...msg); }, 49 | /* eslint-enable */ 50 | }; 51 | 52 | /** 53 | * Start listening for connections from the Stream Deck plugin. 54 | * @param opts Options object (see below). 55 | * @param opts.key Secret key that will be used to connect to this server. 56 | * @param opts.port Port that this server will listen on for connections. 57 | * @param opts.debug Turn on debug logging to help development. 58 | */ 59 | listen(opts: { 60 | key?: string; 61 | port?: number; 62 | debug?: boolean; 63 | } = { key: 'DEFAULT_KEY', port: 9091, debug: false }): void { 64 | if (this.wss) { 65 | this.wss.close(); // If server is already active, close it 66 | this.wss.removeAllListeners(); 67 | } 68 | this.wss = new ws.Server({ port: opts.port }); // Create WebSocket server 69 | this.debug = opts.debug || false; 70 | this.log.debug('WebSocket server created on port %s', opts.port); 71 | 72 | // Triggered when client connects. 73 | this.wss.on('connection', (socket, req) => { 74 | this.log.debug('WebSocket client connected'); 75 | 76 | // Get key from request query. 77 | if (!req.url) return; 78 | const { query } = url.parse(req.url, true); 79 | const { key } = query; 80 | if (key) this.log.debug('WebSocket client used key %s', key); 81 | 82 | // Disconnect client if key invalid. 83 | if (!key || key !== opts.key) { 84 | this.log.debug('WebSocket client connection refused due to incorrect key'); 85 | socket.close(); 86 | return; 87 | } 88 | 89 | // Disconnect client if one is already connected. 90 | if (this.wsConnection && this.wsConnection.readyState !== 3) { 91 | this.log.debug('WebSocket client connection refused due to more than 1 connection'); 92 | socket.close(); 93 | return; 94 | } 95 | 96 | this.wsConnection = socket; 97 | this.emit('open'); 98 | 99 | socket.on('message', (message) => { 100 | const msg = JSON.parse(message.toString()); 101 | if (msg.type === 'init') { 102 | this.log.debug('WebSocket received plugin UUID: %s', msg.data.pluginUUID); 103 | this.pluginUUID = msg.data.pluginUUID; 104 | if (this.init < 2) { 105 | this.init += 1; 106 | if (this.init >= 2) this.emit('init'); 107 | } 108 | } 109 | if (msg.type === 'buttonLocationsUpdated') { 110 | this.log.debug('WebSocket received updated button locations'); 111 | this.buttonLocations = msg.data.buttonLocations; 112 | if (this.init < 2) { 113 | this.init += 1; 114 | if (this.init >= 2) this.emit('init'); 115 | } 116 | } 117 | if (msg.type === 'rawSD') { 118 | this.log.debug( 119 | 'WebSocket received raw Stream Deck message:\n%s', 120 | util.inspect(msg.data, { depth: null }), 121 | ); 122 | this.emit(msg.data.event, msg.data); 123 | this.emit('message', msg.data); 124 | } 125 | }); 126 | 127 | socket.on('error', (err) => { 128 | this.log.debug('WebSocket client connection error (%s)', err); 129 | this.emit('error', err); 130 | }); 131 | 132 | socket.once('close', (code, reason) => { 133 | this.log.debug( 134 | 'WebSocket client connection closed (%s)', 135 | `${code}${(reason) ? `, ${reason}` : ''}`, 136 | ); 137 | this.buttonLocations = {}; 138 | this.pluginUUID = undefined; 139 | this.wsConnection = undefined; 140 | this.init = 0; 141 | socket.removeAllListeners(); 142 | this.emit('close', code, reason); 143 | }); 144 | }); 145 | } 146 | 147 | /** 148 | * Gets the buttonLocations object as received from the Stream Deck plugin. 149 | */ 150 | getButtonLocations(): ButtonLocations { 151 | return this.buttonLocations; 152 | } 153 | 154 | /** 155 | * Gets the pluginUUID if set. 156 | */ 157 | getPluginUUID(): string | undefined { 158 | return this.pluginUUID; 159 | } 160 | 161 | /** 162 | * Sends message to the Stream Deck WebSocket connection. 163 | * as documented on https://developer.elgato.com/documentation/stream-deck/sdk/events-sent/ 164 | * Data will be stringified for you. 165 | * Will return true/false depending on if message was able to be sent. 166 | * @param data Object formatted to send to the Stream Deck WebSocket connection. 167 | */ 168 | send(data: { [k: string]: unknown }): boolean { 169 | if (this.wsConnection && this.wsConnection.readyState === 1) { 170 | this.wsConnection.send(JSON.stringify(data)); 171 | return true; 172 | } 173 | return false; 174 | } 175 | 176 | /** 177 | * Get raw WebSocket connection to the plugin backend if available. 178 | */ 179 | getWSConnection(): ws | undefined { 180 | return this.wsConnection; 181 | } 182 | 183 | /** 184 | * Get an array of all the buttons that are the specified action. 185 | * @param action Name of the action you're looking for. 186 | */ 187 | findButtonsWithAction(action: string): ButtonObject[] { 188 | const buttons: ButtonObject[] = []; 189 | Object.keys(this.buttonLocations).forEach((device) => { 190 | Object.keys(this.buttonLocations[device]).forEach((row) => { 191 | Object.keys(this.buttonLocations[device][row]).forEach((column) => { 192 | const button = this.buttonLocations[device][row][column]; 193 | if (button && button.action === action) { 194 | buttons.push(button); 195 | } 196 | }); 197 | }); 198 | }); 199 | return buttons; 200 | } 201 | 202 | /** 203 | * Update a button's text by it's context. 204 | * @param context Context of the button. 205 | * @param text What you want to change the text to. 206 | */ 207 | updateButtonText(context: string, text: string): void { 208 | this.send({ 209 | context, 210 | event: 'setTitle', 211 | payload: { 212 | title: text, 213 | }, 214 | }); 215 | } 216 | 217 | /** 218 | * Update the text on all buttons of a cetain action. 219 | * @param action Name of the action you're looking for. 220 | * @param text What you want to change the text to. 221 | */ 222 | setTextOnAllButtonsWithAction(action: string, text: string): void { 223 | const buttons = this.findButtonsWithAction(action); 224 | buttons.forEach((button) => { 225 | this.updateButtonText(button.context, text); 226 | }); 227 | } 228 | } 229 | 230 | export = StreamDeck; 231 | -------------------------------------------------------------------------------- /streamdeck-plugin-vue/.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /streamdeck-plugin-vue/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | end_of_line = lf 5 | trim_trailing_whitespace = true 6 | insert_final_newline = true 7 | max_line_length = 100 8 | -------------------------------------------------------------------------------- /streamdeck-plugin-vue/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | node: true, 5 | }, 6 | settings: { 7 | 'import/resolver': { 8 | typescript: { 9 | // intentionally left blank 10 | }, 11 | }, 12 | }, 13 | extends: [ 14 | 'plugin:vue/essential', 15 | '@vue/airbnb', 16 | '@vue/typescript/recommended', 17 | ], 18 | parserOptions: { 19 | ecmaVersion: 2020, 20 | }, 21 | rules: { 22 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 23 | 'max-len': ['error', { code: 100, ignorePattern: '^import\\s.+\\sfrom\\s.+;$' }], 24 | 'class-methods-use-this': 'off', 25 | 'lines-between-class-members': 'off', 26 | 'object-curly-newline': 'off', 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /streamdeck-plugin-vue/README.md: -------------------------------------------------------------------------------- 1 | # com.zoton2.example.sdplugin 2 | 3 | ## Project setup 4 | ``` 5 | npm install 6 | ``` 7 | 8 | ### Compiles and hot-reloads for development 9 | ``` 10 | npm run serve 11 | ``` 12 | 13 | ### Compiles and minifies for production 14 | ``` 15 | npm run build 16 | ``` 17 | 18 | ### Lints and fixes files 19 | ``` 20 | npm run lint 21 | ``` 22 | 23 | ### Customize configuration 24 | See [Configuration Reference](https://cli.vuejs.org/config/). 25 | -------------------------------------------------------------------------------- /streamdeck-plugin-vue/compile.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | if not exist Release\ mkdir Release 3 | del Release\com.zoton2.example.streamDeckPlugin >nul 2>&1 4 | DistributionTool.exe -b -i com.zoton2.example.sdPlugin -o Release 5 | pause 6 | -------------------------------------------------------------------------------- /streamdeck-plugin-vue/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "build": "vue-cli-service build", 4 | "lint": "vue-cli-service lint", 5 | "watch": "vue-cli-service build --watch" 6 | }, 7 | "dependencies": { 8 | "events": "^3.3.0", 9 | "vue": "^2.6.14", 10 | "vue-class-component": "^7.2.6", 11 | "vue-property-decorator": "^9.1.2" 12 | }, 13 | "devDependencies": { 14 | "@types/events": "^3.0.0", 15 | "@typescript-eslint/eslint-plugin": "^4.33.0", 16 | "@typescript-eslint/parser": "^4.33.0", 17 | "@vue/cli-plugin-eslint": "~4.5.15", 18 | "@vue/cli-plugin-typescript": "~4.5.15", 19 | "@vue/cli-service": "~4.5.15", 20 | "@vue/eslint-config-airbnb": "^5.3.0", 21 | "@vue/eslint-config-typescript": "^7.0.0", 22 | "eslint": "^6.8.0", 23 | "eslint-import-resolver-typescript": "^2.5.0", 24 | "eslint-plugin-import": "^2.25.4", 25 | "eslint-plugin-vue": "^6.2.2", 26 | "typescript": "~4.1.6", 27 | "vue-template-compiler": "^2.6.14" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /streamdeck-plugin-vue/public/backend.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | com.zoton2.example Backend 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /streamdeck-plugin-vue/public/css/caret.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /streamdeck-plugin-vue/public/css/check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoton2/node-streamdeck-util/d2f51374eb44640240362131c830e5bf14a1ee1c/streamdeck-plugin-vue/public/css/check.png -------------------------------------------------------------------------------- /streamdeck-plugin-vue/public/css/check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /streamdeck-plugin-vue/public/css/elg_calendar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /streamdeck-plugin-vue/public/css/elg_calendar_inv.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /streamdeck-plugin-vue/public/css/rcheck.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /streamdeck-plugin-vue/public/css/sdpi.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --sdpi-bgcolor: #2D2D2D; 3 | --sdpi-background: #3D3D3D; 4 | --sdpi-color: #d8d8d8; 5 | --sdpi-bordercolor: #3a3a3a; 6 | --sdpi-buttonbordercolor: #969696; 7 | --sdpi-borderradius: 0px; 8 | --sdpi-width: 224px; 9 | --sdpi-fontweight: 600; 10 | --sdpi-letterspacing: -0.25pt; 11 | } 12 | 13 | html { 14 | --sdpi-bgcolor: #2D2D2D; 15 | --sdpi-background: #3D3D3D; 16 | --sdpi-color: #d8d8d8; 17 | --sdpi-bordercolor: #3a3a3a; 18 | --sdpi-buttonbordercolor: #969696; 19 | --sdpi-borderradius: 0px; 20 | --sdpi-width: 224px; 21 | --sdpi-fontweight: 600; 22 | --sdpi-letterspacing: -0.25pt; 23 | height: 100%; 24 | width: 100%; 25 | overflow: hidden; 26 | touch-action:none; 27 | } 28 | 29 | html, body { 30 | --sdpi-bgcolor: #2D2D2D; 31 | --sdpi-background: #3D3D3D; 32 | --sdpi-color: #d8d8d8; 33 | font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 34 | font-size: 9pt; 35 | background-color: var(--sdpi-bgcolor); 36 | color: #9a9a9a; 37 | } 38 | 39 | body { 40 | height: 100%; 41 | padding: 0; 42 | overflow-x: hidden; 43 | overflow-y: auto; 44 | margin: 0; 45 | -webkit-overflow-scrolling: touch; 46 | -webkit-text-size-adjust: 100%; 47 | -webkit-font-smoothing: antialiased; 48 | } 49 | 50 | mark { 51 | background-color: var(--sdpi-bgcolor); 52 | color: var(--sdpi-color); 53 | } 54 | 55 | hr, hr2 { 56 | -webkit-margin-before: 1em; 57 | -webkit-margin-after: 1em; 58 | border-style: none; 59 | background: var(--sdpi-background); 60 | height: 1px; 61 | } 62 | 63 | hr2, 64 | .sdpi-heading { 65 | display: flex; 66 | flex-basis: 100%; 67 | align-items: center; 68 | color: inherit; 69 | font-size: 9pt; 70 | margin: 8px 0px; 71 | } 72 | 73 | .sdpi-heading::before, 74 | .sdpi-heading::after { 75 | content: ""; 76 | flex-grow: 1; 77 | background: var(--sdpi-background); 78 | height: 1px; 79 | font-size: 0px; 80 | line-height: 0px; 81 | margin: 0px 16px; 82 | } 83 | 84 | hr2 { 85 | height: 2px; 86 | } 87 | 88 | hr, hr2 { 89 | margin-left:16px; 90 | margin-right:16px; 91 | } 92 | 93 | .sdpi-item-value, 94 | option, 95 | input, 96 | select, 97 | button { 98 | font-size: 10pt; 99 | font-weight: var(--sdpi-fontweight); 100 | letter-spacing: var(--sdpi-letterspacing); 101 | } 102 | 103 | 104 | 105 | .win .sdpi-item-value, 106 | .win option, 107 | .win input, 108 | .win select, 109 | .win button { 110 | font-size: 11px; 111 | font-style: normal; 112 | letter-spacing: inherit; 113 | font-weight: 100; 114 | } 115 | 116 | .win button { 117 | font-size: 12px; 118 | } 119 | 120 | ::-webkit-progress-value, 121 | meter::-webkit-meter-optimum-value { 122 | border-radius: 2px; 123 | /* background: linear-gradient(#ccf, #99f 20%, #77f 45%, #77f 55%, #cdf); */ 124 | } 125 | 126 | ::-webkit-progress-bar, 127 | meter::-webkit-meter-bar { 128 | border-radius: 3px; 129 | background: var(--sdpi-background); 130 | } 131 | 132 | ::-webkit-progress-bar:active, 133 | meter::-webkit-meter-bar:active { 134 | border-radius: 3px; 135 | background: #222222; 136 | } 137 | ::-webkit-progress-value:active, 138 | meter::-webkit-meter-optimum-value:active { 139 | background: #99f; 140 | } 141 | 142 | progress, 143 | progress.sdpi-item-value { 144 | min-height: 5px !important; 145 | height: 5px; 146 | background-color: #303030; 147 | } 148 | 149 | progress { 150 | margin-top: 8px !important; 151 | margin-bottom: 8px !important; 152 | } 153 | 154 | .full progress, 155 | progress.full { 156 | margin-top: 3px !important; 157 | } 158 | 159 | ::-webkit-progress-inner-element { 160 | background-color: transparent; 161 | } 162 | 163 | 164 | .sdpi-item[type="progress"] { 165 | margin-top: 4px !important; 166 | margin-bottom: 12px; 167 | min-height: 15px; 168 | } 169 | 170 | .sdpi-item-child.full:last-child { 171 | margin-bottom: 4px; 172 | } 173 | 174 | .tabs { 175 | /** 176 | * Setting display to flex makes this container lay 177 | * out its children using flexbox, the exact same 178 | * as in the above "Stepper input" example. 179 | */ 180 | display: flex; 181 | 182 | border-bottom: 1px solid #D7DBDD; 183 | } 184 | 185 | .tab { 186 | cursor: pointer; 187 | padding: 5px 30px; 188 | color: #16a2d7; 189 | font-size: 9pt; 190 | border-bottom: 2px solid transparent; 191 | } 192 | 193 | .tab.is-tab-selected { 194 | border-bottom-color: #4ebbe4; 195 | } 196 | 197 | select { 198 | -webkit-appearance: none; 199 | -moz-appearance: none; 200 | -o-appearance: none; 201 | appearance: none; 202 | background: url(caret.svg) no-repeat 97% center; 203 | } 204 | 205 | label.sdpi-file-label, 206 | input[type="button"], 207 | input[type="submit"], 208 | input[type="reset"], 209 | input[type="file"], 210 | input[type=file]::-webkit-file-upload-button, 211 | button, 212 | select { 213 | color: var(--sdpi-color); 214 | border: 1pt solid #303030; 215 | font-size: 8pt; 216 | background-color: var(--sdpi-background); 217 | border-radius: var(--sdpi-borderradius); 218 | } 219 | 220 | label.sdpi-file-label, 221 | input[type="button"], 222 | input[type="submit"], 223 | input[type="reset"], 224 | input[type="file"], 225 | input[type=file]::-webkit-file-upload-button, 226 | button { 227 | border: 1pt solid var(--sdpi-buttonbordercolor); 228 | border-radius: var(--sdpi-borderradius); 229 | border-color: var(--sdpi-buttonbordercolor); 230 | min-height: 23px !important; 231 | height: 23px !important; 232 | margin-right: 8px; 233 | } 234 | 235 | input[type=number]::-webkit-inner-spin-button, 236 | input[type=number]::-webkit-outer-spin-button { 237 | -webkit-appearance: none; 238 | margin: 0; 239 | } 240 | 241 | input[type="file"] { 242 | border-radius: var(--sdpi-borderradius); 243 | max-width: 220px; 244 | } 245 | 246 | option { 247 | height: 1.5em; 248 | padding: 4px; 249 | } 250 | 251 | /* SDPI */ 252 | 253 | .sdpi-wrapper { 254 | overflow-x: hidden; 255 | height: 100%; 256 | } 257 | 258 | .sdpi-item { 259 | display: flex; 260 | flex-direction: row; 261 | min-height: 32px; 262 | align-items: center; 263 | margin-top: 2px; 264 | max-width: 344px; 265 | /* -webkit-user-drag: none; */ 266 | } 267 | 268 | .sdpi-item:first-child { 269 | margin-top:-1px; 270 | } 271 | 272 | .sdpi-item:last-child { 273 | margin-bottom: 0px; 274 | } 275 | 276 | .sdpi-item > *:not(.sdpi-item-label):not(meter):not(details):not(canvas) { 277 | min-height: 26px; 278 | padding: 0px 4px 0px 4px; 279 | } 280 | 281 | .sdpi-item > *:not(.sdpi-item-label.empty):not(meter) { 282 | min-height: 26px; 283 | padding: 0px 4px 0px 4px; 284 | } 285 | 286 | 287 | .sdpi-item-group { 288 | padding: 0 !important; 289 | } 290 | 291 | meter.sdpi-item-value { 292 | margin-left: 6px; 293 | } 294 | 295 | .sdpi-item[type="group"] { 296 | display: block; 297 | margin-top: 12px; 298 | margin-bottom: 12px; 299 | /* border: 1px solid white; */ 300 | flex-direction: unset; 301 | text-align: left; 302 | } 303 | 304 | .sdpi-item[type="group"] > .sdpi-item-label, 305 | .sdpi-item[type="group"].sdpi-item-label { 306 | width: 96%; 307 | text-align: left; 308 | font-weight: 700; 309 | margin-bottom: 4px; 310 | padding-left: 4px; 311 | } 312 | 313 | dl, 314 | ul, 315 | ol { 316 | -webkit-margin-before: 0px; 317 | -webkit-margin-after: 4px; 318 | -webkit-padding-start: 1em; 319 | max-height: 90px; 320 | overflow-y: scroll; 321 | cursor: pointer; 322 | user-select: none; 323 | } 324 | 325 | table.sdpi-item-value, 326 | dl.sdpi-item-value, 327 | ul.sdpi-item-value, 328 | ol.sdpi-item-value { 329 | -webkit-margin-before: 4px; 330 | -webkit-margin-after: 8px; 331 | -webkit-padding-start: 1em; 332 | width: var(--sdpi-width); 333 | text-align: center; 334 | } 335 | 336 | table > caption { 337 | margin: 2px; 338 | } 339 | 340 | .list, 341 | .sdpi-item[type="list"] { 342 | align-items: baseline; 343 | } 344 | 345 | .sdpi-item-label { 346 | text-align: right; 347 | flex: none; 348 | width: 94px; 349 | padding-right: 4px; 350 | font-weight: 600; 351 | -webkit-user-select: none; 352 | } 353 | 354 | .win .sdpi-item-label, 355 | .sdpi-item-label > small{ 356 | font-weight: normal; 357 | } 358 | 359 | .sdpi-item-label:after { 360 | content: ": "; 361 | } 362 | 363 | .sdpi-item-label.empty:after { 364 | content: ""; 365 | } 366 | 367 | .sdpi-test, 368 | .sdpi-item-value { 369 | flex: 1 0 0; 370 | /* flex-grow: 1; 371 | flex-shrink: 0; */ 372 | margin-right: 14px; 373 | margin-left: 4px; 374 | justify-content: space-evenly; 375 | } 376 | 377 | canvas.sdpi-item-value { 378 | max-width: 144px; 379 | max-height: 144px; 380 | width: 144px; 381 | height: 144px; 382 | margin: 0 auto; 383 | cursor: pointer; 384 | } 385 | 386 | input.sdpi-item-value { 387 | margin-left: 5px; 388 | } 389 | 390 | .sdpi-item-value button, 391 | button.sdpi-item-value { 392 | margin-left: 6px; 393 | margin-right: 14px; 394 | } 395 | 396 | .sdpi-item-value.range { 397 | margin-left: 0px; 398 | } 399 | 400 | table, 401 | dl.sdpi-item-value, 402 | ul.sdpi-item-value, 403 | ol.sdpi-item-value, 404 | .sdpi-item-value > dl, 405 | .sdpi-item-value > ul, 406 | .sdpi-item-value > ol 407 | { 408 | list-style-type: none; 409 | list-style-position: outside; 410 | margin-left: -4px; 411 | margin-right: -4px; 412 | padding: 4px; 413 | border: 1px solid var(--sdpi-bordercolor); 414 | } 415 | 416 | dl.sdpi-item-value, 417 | ul.sdpi-item-value, 418 | ol.sdpi-item-value, 419 | .sdpi-item-value > ol { 420 | list-style-type: none; 421 | list-style-position: inside; 422 | margin-left: 5px; 423 | margin-right: 12px; 424 | padding: 4px !important; 425 | display: flex; 426 | flex-direction: column; 427 | } 428 | 429 | .two-items li { 430 | display: flex; 431 | } 432 | .two-items li > *:first-child { 433 | flex: 0 0 50%; 434 | text-align: left; 435 | } 436 | .two-items.thirtyseventy li > *:first-child { 437 | flex: 0 0 30%; 438 | } 439 | 440 | ol.sdpi-item-value, 441 | .sdpi-item-value > ol[listtype="none"] { 442 | list-style-type: none; 443 | } 444 | ol.sdpi-item-value[type="decimal"], 445 | .sdpi-item-value > ol[type="decimal"] { 446 | list-style-type: decimal; 447 | } 448 | 449 | ol.sdpi-item-value[type="decimal-leading-zero"], 450 | .sdpi-item-value > ol[type="decimal-leading-zero"] { 451 | list-style-type: decimal-leading-zero; 452 | } 453 | 454 | ol.sdpi-item-value[type="lower-alpha"], 455 | .sdpi-item-value > ol[type="lower-alpha"] { 456 | list-style-type: lower-alpha; 457 | } 458 | 459 | ol.sdpi-item-value[type="upper-alpha"], 460 | .sdpi-item-value > ol[type="upper-alpha"] { 461 | list-style-type: upper-alpha; 462 | } 463 | 464 | ol.sdpi-item-value[type="upper-roman"], 465 | .sdpi-item-value > ol[type="upper-roman"] { 466 | list-style-type: upper-roman; 467 | } 468 | 469 | ol.sdpi-item-value[type="lower-roman"], 470 | .sdpi-item-value > ol[type="lower-roman"] { 471 | list-style-type: upper-roman; 472 | } 473 | 474 | tr:nth-child(even), 475 | .sdpi-item-value > ul > li:nth-child(even), 476 | .sdpi-item-value > ol > li:nth-child(even), 477 | li:nth-child(even) { 478 | background-color: rgba(0,0,0,.2) 479 | } 480 | 481 | td:hover, 482 | .sdpi-item-value > ul > li:hover:nth-child(even), 483 | .sdpi-item-value > ol > li:hover:nth-child(even), 484 | li:hover:nth-child(even), 485 | li:hover { 486 | background-color: rgba(255,255,255,.1); 487 | } 488 | 489 | td.selected, 490 | td.selected:hover, 491 | li.selected:hover, 492 | li.selected { 493 | color: white; 494 | background-color: #77f; 495 | } 496 | 497 | tr { 498 | border: 1px solid var(--sdpi-bordercolor); 499 | } 500 | 501 | td { 502 | border-right: 1px solid var(--sdpi-bordercolor); 503 | -webkit-user-select: none; 504 | } 505 | 506 | tr:last-child, 507 | td:last-child { 508 | border: none; 509 | } 510 | 511 | .sdpi-item-value.select, 512 | .sdpi-item-value > select { 513 | margin-right: 13px; 514 | margin-left: 4px; 515 | } 516 | 517 | .sdpi-item-child, 518 | .sdpi-item-group > .sdpi-item > input[type="color"] { 519 | margin-top: 0.4em; 520 | margin-right: 4px; 521 | } 522 | 523 | .full, 524 | .full *, 525 | .sdpi-item-value.full, 526 | .sdpi-item-child > full > *, 527 | .sdpi-item-child.full, 528 | .sdpi-item-child.full > *, 529 | .full > .sdpi-item-child, 530 | .full > .sdpi-item-child > *{ 531 | display: flex; 532 | flex: 1 1 0; 533 | margin-bottom: 4px; 534 | margin-left: 0px; 535 | width: 100%; 536 | 537 | justify-content: space-evenly; 538 | } 539 | 540 | .sdpi-item-group > .sdpi-item > input[type="color"] { 541 | margin-top: 0px; 542 | } 543 | 544 | ::-webkit-calendar-picker-indicator:focus, 545 | input[type=file]::-webkit-file-upload-button:focus, 546 | button:focus, 547 | textarea:focus, 548 | input:focus, 549 | select:focus, 550 | option:focus, 551 | details:focus, 552 | summary:focus, 553 | .custom-select select { 554 | outline: none; 555 | } 556 | 557 | summary { 558 | cursor: default; 559 | -webkit-user-select: none; 560 | } 561 | 562 | .pointer, 563 | summary .pointer { 564 | cursor: pointer; 565 | } 566 | 567 | details * { 568 | font-size: 12px; 569 | font-weight: normal; 570 | } 571 | 572 | details.message { 573 | padding: 4px 18px 4px 12px; 574 | } 575 | 576 | details.message summary { 577 | font-size: 10pt; 578 | font-weight: 600; 579 | min-height: 18px; 580 | } 581 | 582 | details.message:first-child { 583 | margin-top: 4px; 584 | margin-left: 0; 585 | padding-left: 102px; 586 | } 587 | 588 | details.message h1 { 589 | text-align: left; 590 | } 591 | 592 | .message > summary::-webkit-details-marker { 593 | display: none; 594 | } 595 | 596 | .info20, 597 | .question, 598 | .caution, 599 | .info { 600 | background-repeat: no-repeat; 601 | background-position: 72px center; 602 | } 603 | 604 | .info20 { 605 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath fill='%23999' d='M10,20 C4.4771525,20 0,15.5228475 0,10 C0,4.4771525 4.4771525,0 10,0 C15.5228475,0 20,4.4771525 20,10 C20,15.5228475 15.5228475,20 10,20 Z M10,8 C8.8954305,8 8,8.84275812 8,9.88235294 L8,16.1176471 C8,17.1572419 8.8954305,18 10,18 C11.1045695,18 12,17.1572419 12,16.1176471 L12,9.88235294 C12,8.84275812 11.1045695,8 10,8 Z M10,3 C8.8954305,3 8,3.88165465 8,4.96923077 L8,5.03076923 C8,6.11834535 8.8954305,7 10,7 C11.1045695,7 12,6.11834535 12,5.03076923 L12,4.96923077 C12,3.88165465 11.1045695,3 10,3 Z'/%3E%3C/svg%3E%0A"); 606 | } 607 | 608 | .info { 609 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath fill='%23999' d='M10,18 C5.581722,18 2,14.418278 2,10 C2,5.581722 5.581722,2 10,2 C14.418278,2 18,5.581722 18,10 C18,14.418278 14.418278,18 10,18 Z M10,8 C9.44771525,8 9,8.42137906 9,8.94117647 L9,14.0588235 C9,14.5786209 9.44771525,15 10,15 C10.5522847,15 11,14.5786209 11,14.0588235 L11,8.94117647 C11,8.42137906 10.5522847,8 10,8 Z M10,5 C9.44771525,5 9,5.44082732 9,5.98461538 L9,6.01538462 C9,6.55917268 9.44771525,7 10,7 C10.5522847,7 11,6.55917268 11,6.01538462 L11,5.98461538 C11,5.44082732 10.5522847,5 10,5 Z'/%3E%3C/svg%3E%0A"); 610 | } 611 | 612 | .info2 { 613 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='15' height='15' viewBox='0 0 15 15'%3E%3Cpath fill='%23999' d='M7.5,15 C3.35786438,15 0,11.6421356 0,7.5 C0,3.35786438 3.35786438,0 7.5,0 C11.6421356,0 15,3.35786438 15,7.5 C15,11.6421356 11.6421356,15 7.5,15 Z M7.5,2 C6.67157287,2 6,2.66124098 6,3.47692307 L6,3.52307693 C6,4.33875902 6.67157287,5 7.5,5 C8.32842705,5 9,4.33875902 9,3.52307693 L9,3.47692307 C9,2.66124098 8.32842705,2 7.5,2 Z M5,6 L5,7.02155172 L6,7 L6,12 L5,12.0076778 L5,13 L10,13 L10,12 L9,12.0076778 L9,6 L5,6 Z'/%3E%3C/svg%3E%0A"); 614 | } 615 | 616 | .sdpi-more-info { 617 | background-image: linear-gradient(to right, #00000000 0%,#00000040 80%), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpolygon fill='%23999' points='4 7 8 7 8 5 12 8 8 11 8 9 4 9'/%3E%3C/svg%3E%0A"); 618 | } 619 | .caution { 620 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath fill='%23999' fill-rule='evenodd' d='M9.03952676,0.746646542 C9.57068894,-0.245797319 10.4285735,-0.25196227 10.9630352,0.746646542 L19.7705903,17.2030214 C20.3017525,18.1954653 19.8777595,19 18.8371387,19 L1.16542323,19 C0.118729947,19 -0.302490098,18.2016302 0.231971607,17.2030214 L9.03952676,0.746646542 Z M10,2.25584053 L1.9601405,17.3478261 L18.04099,17.3478261 L10,2.25584053 Z M10,5.9375 C10.531043,5.9375 10.9615385,6.37373537 10.9615385,6.91185897 L10.9615385,11.6923077 C10.9615385,12.2304313 10.531043,12.6666667 10,12.6666667 C9.46895697,12.6666667 9.03846154,12.2304313 9.03846154,11.6923077 L9.03846154,6.91185897 C9.03846154,6.37373537 9.46895697,5.9375 10,5.9375 Z M10,13.4583333 C10.6372516,13.4583333 11.1538462,13.9818158 11.1538462,14.6275641 L11.1538462,14.6641026 C11.1538462,15.3098509 10.6372516,15.8333333 10,15.8333333 C9.36274837,15.8333333 8.84615385,15.3098509 8.84615385,14.6641026 L8.84615385,14.6275641 C8.84615385,13.9818158 9.36274837,13.4583333 10,13.4583333 Z'/%3E%3C/svg%3E%0A"); 621 | } 622 | 623 | .question { 624 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath fill='%23999' d='M10,18 C5.581722,18 2,14.418278 2,10 C2,5.581722 5.581722,2 10,2 C14.418278,2 18,5.581722 18,10 C18,14.418278 14.418278,18 10,18 Z M6.77783203,7.65332031 C6.77783203,7.84798274 6.85929281,8.02888914 7.0222168,8.19604492 C7.18514079,8.36320071 7.38508996,8.44677734 7.62207031,8.44677734 C8.02409055,8.44677734 8.29703704,8.20768468 8.44091797,7.72949219 C8.59326248,7.27245865 8.77945854,6.92651485 8.99951172,6.69165039 C9.2195649,6.45678594 9.56233491,6.33935547 10.027832,6.33935547 C10.4256205,6.33935547 10.7006836,6.37695313 11.0021973,6.68847656 C11.652832,7.53271484 10.942627,8.472229 10.3750916,9.1321106 C9.80755615,9.79199219 8.29492188,11.9897461 10.027832,12.1347656 C10.4498423,12.1700818 10.7027991,11.9147157 10.7832031,11.4746094 C11.0021973,9.59857178 13.1254883,8.82415771 13.1254883,7.53271484 C13.1254883,7.07568131 12.9974785,6.65250846 12.7414551,6.26318359 C12.4854317,5.87385873 12.1225609,5.56600048 11.652832,5.33959961 C11.1831031,5.11319874 10.6414419,5 10.027832,5 C9.36767248,5 8.79004154,5.13541531 8.29492187,5.40625 C7.79980221,5.67708469 7.42317837,6.01879677 7.16503906,6.43139648 C6.90689975,6.8439962 6.77783203,7.25130007 6.77783203,7.65332031 Z M10.0099668,15 C10.2713191,15 10.5016601,14.9108147 10.7009967,14.7324415 C10.9003332,14.5540682 11,14.3088087 11,13.9966555 C11,13.7157177 10.9047629,13.4793767 10.7142857,13.2876254 C10.5238086,13.0958742 10.2890379,13 10.0099668,13 C9.72646591,13 9.48726565,13.0958742 9.2923588,13.2876254 C9.09745196,13.4793767 9,13.7157177 9,13.9966555 C9,14.313268 9.10077419,14.5596424 9.30232558,14.735786 C9.50387698,14.9119295 9.73975502,15 10.0099668,15 Z'/%3E%3C/svg%3E%0A"); 625 | } 626 | 627 | 628 | .sdpi-more-info { 629 | position: fixed; 630 | left: 0px; 631 | right: 0px; 632 | bottom: 0px; 633 | min-height:16px; 634 | padding-right: 16px; 635 | text-align: right; 636 | -webkit-touch-callout: none; 637 | cursor: pointer; 638 | user-select: none; 639 | background-position: right center; 640 | background-repeat: no-repeat; 641 | border-radius: var(--sdpi-borderradius); 642 | text-decoration: none; 643 | color: var(--sdpi-color); 644 | } 645 | 646 | .sdpi-bottom-bar { 647 | display: flex; 648 | align-self: right; 649 | margin-left: auto; 650 | position: fixed; 651 | right: 17px; 652 | bottom: 0px; 653 | user-select: none; 654 | } 655 | 656 | .sdpi-bottom-bar.right { 657 | right: 0px; 658 | } 659 | 660 | .sdpi-bottom-bar button { 661 | min-height: 20px !important; 662 | height: 20px !important; 663 | } 664 | 665 | 666 | .sdpi-more-info-button { 667 | display: flex; 668 | align-self: right; 669 | margin-left: auto; 670 | position: fixed; 671 | right: 17px; 672 | bottom: 0px; 673 | user-select: none; 674 | } 675 | 676 | details a { 677 | background-position: right !important; 678 | min-height: 24px; 679 | display: inline-block; 680 | line-height: 24px; 681 | padding-right: 28px; 682 | } 683 | 684 | 685 | input:not([type="range"]), 686 | textarea { 687 | -webkit-appearance: none; 688 | background: var(--sdpi-background); 689 | color: var(--sdpi-color); 690 | font-weight: normal; 691 | font-size: 9pt; 692 | border: none; 693 | margin-top: 2px; 694 | margin-bottom: 2px; 695 | min-width: 219px; 696 | } 697 | 698 | textarea + label { 699 | display: flex; 700 | justify-content: flex-end 701 | } 702 | input[type="radio"], 703 | input[type="checkbox"] { 704 | display: none; 705 | } 706 | input[type="radio"] + label, 707 | input[type="checkbox"] + label { 708 | font-size: 9pt; 709 | color: var(--sdpi-color); 710 | font-weight: normal; 711 | margin-right: 8px; 712 | -webkit-user-select: none; 713 | } 714 | 715 | input[type="radio"] + label:after, 716 | input[type="checkbox"] + label:after { 717 | content: " " !important; 718 | } 719 | 720 | .sdpi-item[type="radio"] > .sdpi-item-value, 721 | .sdpi-item[type="checkbox"] > .sdpi-item-value { 722 | padding-top: 2px; 723 | } 724 | 725 | .sdpi-item[type="checkbox"] > .sdpi-item-value > * { 726 | margin-top: 4px; 727 | } 728 | 729 | .sdpi-item[type="checkbox"] .sdpi-item-child, 730 | .sdpi-item[type="radio"] .sdpi-item-child { 731 | display: inline-block; 732 | } 733 | 734 | .sdpi-item[type="range"] .sdpi-item-value, 735 | .sdpi-item[type="meter"] .sdpi-item-child, 736 | .sdpi-item[type="progress"] .sdpi-item-child { 737 | display: flex; 738 | } 739 | 740 | .sdpi-item[type="range"] .sdpi-item-value { 741 | min-height: 26px; 742 | } 743 | 744 | .sdpi-item[type="range"] .sdpi-item-value span, 745 | .sdpi-item[type="meter"] .sdpi-item-child span, 746 | .sdpi-item[type="progress"] .sdpi-item-child span { 747 | margin-top: -2px; 748 | min-width: 8px; 749 | text-align: right; 750 | user-select: none; 751 | cursor: pointer; 752 | -webkit-user-select: none; 753 | user-select: none; 754 | } 755 | 756 | .sdpi-item[type="range"] .sdpi-item-value span { 757 | margin-top: 7px; 758 | text-align: right; 759 | } 760 | 761 | span + input[type="range"] { 762 | display: flex; 763 | max-width: 168px; 764 | 765 | } 766 | 767 | .sdpi-item[type="range"] .sdpi-item-value span:first-child, 768 | .sdpi-item[type="meter"] .sdpi-item-child span:first-child, 769 | .sdpi-item[type="progress"] .sdpi-item-child span:first-child { 770 | margin-right: 4px; 771 | } 772 | 773 | .sdpi-item[type="range"] .sdpi-item-value span:last-child, 774 | .sdpi-item[type="meter"] .sdpi-item-child span:last-child, 775 | .sdpi-item[type="progress"] .sdpi-item-child span:last-child { 776 | margin-left: 4px; 777 | } 778 | 779 | .reverse { 780 | transform: rotate(180deg); 781 | } 782 | 783 | .sdpi-item[type="meter"] .sdpi-item-child meter + span:last-child { 784 | margin-left: -10px; 785 | } 786 | 787 | .sdpi-item[type="progress"] .sdpi-item-child meter + span:last-child { 788 | margin-left: -14px; 789 | } 790 | 791 | .sdpi-item[type="radio"] > .sdpi-item-value > * { 792 | margin-top: 2px; 793 | } 794 | 795 | details { 796 | padding: 8px 18px 8px 12px; 797 | min-width: 86px; 798 | } 799 | 800 | details > h4 { 801 | border-bottom: 1px solid var(--sdpi-bordercolor); 802 | } 803 | 804 | legend { 805 | display: none; 806 | } 807 | .sdpi-item-value > textarea { 808 | padding: 0px; 809 | width: 219px; 810 | margin-left: 1px; 811 | margin-top: 3px; 812 | padding: 4px; 813 | } 814 | 815 | input[type="radio"] + label span, 816 | input[type="checkbox"] + label span { 817 | display: inline-block; 818 | width: 16px; 819 | height: 16px; 820 | margin: 2px 4px 2px 0; 821 | border-radius: 3px; 822 | vertical-align: middle; 823 | background: var(--sdpi-background); 824 | cursor: pointer; 825 | border: 1px solid rgb(0,0,0,.2); 826 | } 827 | 828 | input[type="radio"] + label span { 829 | border-radius: 100%; 830 | } 831 | 832 | input[type="radio"]:checked + label span, 833 | input[type="checkbox"]:checked + label span { 834 | background-color: #77f; 835 | background-image: url(check.svg); 836 | background-repeat: no-repeat; 837 | background-position: center center; 838 | border: 1px solid rgb(0,0,0,.4); 839 | } 840 | 841 | input[type="radio"]:active:checked + label span, 842 | input[type="radio"]:active + label span, 843 | input[type="checkbox"]:active:checked + label span, 844 | input[type="checkbox"]:active + label span { 845 | background-color: #303030; 846 | } 847 | 848 | input[type="radio"]:checked + label span { 849 | background-image: url(rcheck.svg); 850 | } 851 | 852 | input[type="range"] { 853 | width: var(--sdpi-width); 854 | height: 30px; 855 | overflow: hidden; 856 | cursor: pointer; 857 | background: transparent !important; 858 | } 859 | 860 | .sdpi-item > input[type="range"] { 861 | margin-left: 2px; 862 | max-width: var(--sdpi-width); 863 | width: var(--sdpi-width); 864 | padding: 0px; 865 | margin-top: 2px; 866 | } 867 | 868 | /* 869 | input[type="range"], 870 | input[type="range"]::-webkit-slider-runnable-track, 871 | input[type="range"]::-webkit-slider-thumb { 872 | -webkit-appearance: none; 873 | } 874 | */ 875 | 876 | input[type="range"]::-webkit-slider-runnable-track { 877 | height: 5px; 878 | background: #979797; 879 | border-radius: 3px; 880 | padding:0px !important; 881 | border: 1px solid var(--sdpi-background); 882 | } 883 | 884 | input[type="range"]::-webkit-slider-thumb { 885 | position: relative; 886 | background-color: var(--sdpi-color); 887 | width: 12px; 888 | height: 12px; 889 | border-radius: 20px; 890 | margin-top: -5px; 891 | border: none; 892 | } 893 | input[type="range" i]{ 894 | margin: 0; 895 | } 896 | 897 | input[type="range"]::-webkit-slider-thumb::before { 898 | position: absolute; 899 | content: ""; 900 | height: 5px; /* equal to height of runnable track or 1 less */ 901 | width: 500px; /* make this bigger than the widest range input element */ 902 | left: -502px; /* this should be -2px - width */ 903 | top: 8px; /* don't change this */ 904 | background: #77f; 905 | } 906 | 907 | input[type="color"] { 908 | min-width: 32px; 909 | min-height: 32px; 910 | width: 32px; 911 | height: 32px; 912 | padding: 0; 913 | background-color: var(--sdpi-bgcolor); 914 | flex: none; 915 | } 916 | 917 | ::-webkit-color-swatch { 918 | min-width: 24px; 919 | } 920 | 921 | textarea { 922 | height: 3em; 923 | word-break: break-word; 924 | line-height: 1.5em; 925 | } 926 | 927 | .textarea { 928 | padding: 0px !important; 929 | } 930 | 931 | textarea { 932 | width: 219px; /*98%;*/ 933 | height: 96%; 934 | min-height: 6em; 935 | resize: none; 936 | border-radius: var(--sdpi-borderradius); 937 | } 938 | 939 | /* CAROUSEL */ 940 | 941 | .sdpi-item[type="carousel"]{ 942 | 943 | } 944 | 945 | .sdpi-item.card-carousel-wrapper, 946 | .sdpi-item > .card-carousel-wrapper { 947 | padding: 0; 948 | } 949 | 950 | 951 | .card-carousel-wrapper { 952 | display: flex; 953 | align-items: center; 954 | justify-content: center; 955 | margin: 12px auto; 956 | color: #666a73; 957 | } 958 | 959 | .card-carousel { 960 | display: flex; 961 | justify-content: center; 962 | width: 278px; 963 | } 964 | .card-carousel--overflow-container { 965 | overflow: hidden; 966 | } 967 | .card-carousel--nav__left, 968 | .card-carousel--nav__right { 969 | /* display: inline-block; */ 970 | width: 12px; 971 | height: 12px; 972 | border-top: 2px solid #42b883; 973 | border-right: 2px solid #42b883; 974 | cursor: pointer; 975 | margin: 0 4px; 976 | transition: transform 150ms linear; 977 | } 978 | .card-carousel--nav__left[disabled], 979 | .card-carousel--nav__right[disabled] { 980 | opacity: 0.2; 981 | border-color: black; 982 | } 983 | .card-carousel--nav__left { 984 | transform: rotate(-135deg); 985 | } 986 | .card-carousel--nav__left:active { 987 | transform: rotate(-135deg) scale(0.85); 988 | } 989 | .card-carousel--nav__right { 990 | transform: rotate(45deg); 991 | } 992 | .card-carousel--nav__right:active { 993 | transform: rotate(45deg) scale(0.85); 994 | } 995 | .card-carousel-cards { 996 | display: flex; 997 | transition: transform 150ms ease-out; 998 | transform: translatex(0px); 999 | } 1000 | .card-carousel-cards .card-carousel--card { 1001 | margin: 0 5px; 1002 | cursor: pointer; 1003 | /* box-shadow: 0 4px 15px 0 rgba(40, 44, 53, 0.06), 0 2px 2px 0 rgba(40, 44, 53, 0.08); */ 1004 | background-color: #fff; 1005 | border-radius: 4px; 1006 | z-index: 3; 1007 | } 1008 | .xxcard-carousel-cards .card-carousel--card:first-child { 1009 | margin-left: 0; 1010 | } 1011 | .xxcard-carousel-cards .card-carousel--card:last-child { 1012 | margin-right: 0; 1013 | } 1014 | .card-carousel-cards .card-carousel--card img { 1015 | vertical-align: bottom; 1016 | border-top-left-radius: 4px; 1017 | border-top-right-radius: 4px; 1018 | transition: opacity 150ms linear; 1019 | width: 60px; 1020 | } 1021 | .card-carousel-cards .card-carousel--card img:hover { 1022 | opacity: 0.5; 1023 | } 1024 | .card-carousel-cards .card-carousel--card--footer { 1025 | border-top: 0; 1026 | max-width: 80px; 1027 | overflow: hidden; 1028 | display: flex; 1029 | height: 100%; 1030 | flex-direction: column; 1031 | } 1032 | .card-carousel-cards .card-carousel--card--footer p { 1033 | padding: 3px 0; 1034 | margin: 0; 1035 | margin-bottom: 2px; 1036 | font-size: 15px; 1037 | font-weight: 500; 1038 | color: #2c3e50; 1039 | } 1040 | .card-carousel-cards .card-carousel--card--footer p:nth-of-type(2) { 1041 | font-size: 12px; 1042 | font-weight: 300; 1043 | padding: 6px; 1044 | color: #666a73; 1045 | } 1046 | 1047 | 1048 | h1 { 1049 | font-size: 1.3em; 1050 | font-weight: 500; 1051 | text-align: center; 1052 | margin-bottom: 12px; 1053 | } 1054 | 1055 | ::-webkit-datetime-edit { 1056 | font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 1057 | background: url(elg_calendar_inv.svg) no-repeat left center; 1058 | padding-right: 1em; 1059 | padding-left: 25px; 1060 | background-position: 4px 0px; 1061 | } 1062 | ::-webkit-datetime-edit-fields-wrapper { 1063 | 1064 | } 1065 | ::-webkit-datetime-edit-text { padding: 0 0.3em; } 1066 | ::-webkit-datetime-edit-month-field { } 1067 | ::-webkit-datetime-edit-day-field {} 1068 | ::-webkit-datetime-edit-year-field {} 1069 | ::-webkit-inner-spin-button { 1070 | 1071 | /* display: none; */ 1072 | } 1073 | ::-webkit-calendar-picker-indicator { 1074 | background: transparent; 1075 | font-size: 17px; 1076 | } 1077 | 1078 | ::-webkit-calendar-picker-indicator:focus { 1079 | background-color: rgba(0,0,0,0.2); 1080 | } 1081 | 1082 | input[type="date"] { 1083 | -webkit-align-items: center; 1084 | display: -webkit-inline-flex; 1085 | font-family: monospace; 1086 | overflow: hidden; 1087 | padding: 0; 1088 | -webkit-padding-start: 1px; 1089 | } 1090 | 1091 | input::-webkit-datetime-edit { 1092 | -webkit-flex: 1; 1093 | -webkit-user-modify: read-only !important; 1094 | display: inline-block; 1095 | min-width: 0; 1096 | overflow: hidden; 1097 | } 1098 | 1099 | /* 1100 | input::-webkit-datetime-edit-fields-wrapper { 1101 | -webkit-user-modify: read-only !important; 1102 | display: inline-block; 1103 | padding: 1px 0; 1104 | white-space: pre; 1105 | 1106 | } 1107 | */ 1108 | 1109 | /* 1110 | input[type="date"] { 1111 | background-color: red; 1112 | outline: none; 1113 | } 1114 | 1115 | input[type="date"]::-webkit-clear-button { 1116 | font-size: 18px; 1117 | height: 30px; 1118 | position: relative; 1119 | } 1120 | 1121 | input[type="date"]::-webkit-inner-spin-button { 1122 | height: 28px; 1123 | } 1124 | 1125 | input[type="date"]::-webkit-calendar-picker-indicator { 1126 | font-size: 15px; 1127 | } */ 1128 | 1129 | input[type="file"] { 1130 | opacity: 0; 1131 | display: none; 1132 | } 1133 | 1134 | .sdpi-item > input[type="file"] { 1135 | opacity: 1; 1136 | display: flex; 1137 | } 1138 | 1139 | input[type="file"] + span { 1140 | display: flex; 1141 | flex: 0 1 auto; 1142 | background-color: #0000ff50; 1143 | } 1144 | 1145 | label.sdpi-file-label { 1146 | cursor: pointer; 1147 | user-select: none; 1148 | display: inline-block; 1149 | min-height: 21px !important; 1150 | height: 21px !important; 1151 | line-height: 20px; 1152 | padding: 0px 4px; 1153 | margin: auto; 1154 | margin-right: 0px; 1155 | float:right; 1156 | } 1157 | 1158 | .sdpi-file-label > label:active, 1159 | .sdpi-file-label.file:active, 1160 | label.sdpi-file-label:active, 1161 | label.sdpi-file-info:active, 1162 | input[type="file"]::-webkit-file-upload-button:active, 1163 | button:active { 1164 | background-color: var(--sdpi-color); 1165 | color:#303030; 1166 | } 1167 | 1168 | input:required:invalid, input:focus:invalid { 1169 | background: var(--sdpi-background) url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5IiBoZWlnaHQ9IjkiIHZpZXdCb3g9IjAgMCA5IDkiPgogICAgPHBhdGggZmlsbD0iI0Q4RDhEOCIgZD0iTTQuNSwwIEM2Ljk4NTI4MTM3LC00LjU2NTM4NzgyZS0xNiA5LDIuMDE0NzE4NjMgOSw0LjUgQzksNi45ODUyODEzNyA2Ljk4NTI4MTM3LDkgNC41LDkgQzIuMDE0NzE4NjMsOSAzLjA0MzU5MTg4ZS0xNiw2Ljk4NTI4MTM3IDAsNC41IEMtMy4wNDM1OTE4OGUtMTYsMi4wMTQ3MTg2MyAyLjAxNDcxODYzLDQuNTY1Mzg3ODJlLTE2IDQuNSwwIFogTTQsMSBMNCw2IEw1LDYgTDUsMSBMNCwxIFogTTQuNSw4IEM0Ljc3NjE0MjM3LDggNSw3Ljc3NjE0MjM3IDUsNy41IEM1LDcuMjIzODU3NjMgNC43NzYxNDIzNyw3IDQuNSw3IEM0LjIyMzg1NzYzLDcgNCw3LjIyMzg1NzYzIDQsNy41IEM0LDcuNzc2MTQyMzcgNC4yMjM4NTc2Myw4IDQuNSw4IFoiLz4KICA8L3N2Zz4) no-repeat 98% center; 1170 | } 1171 | 1172 | input:required:valid { 1173 | background: var(--sdpi-background) url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5IiBoZWlnaHQ9IjkiIHZpZXdCb3g9IjAgMCA5IDkiPjxwb2x5Z29uIGZpbGw9IiNEOEQ4RDgiIHBvaW50cz0iNS4yIDEgNi4yIDEgNi4yIDcgMy4yIDcgMy4yIDYgNS4yIDYiIHRyYW5zZm9ybT0icm90YXRlKDQwIDQuNjc3IDQpIi8+PC9zdmc+) no-repeat 98% center; 1174 | } 1175 | 1176 | .tooltip, 1177 | :tooltip, 1178 | :title { 1179 | color: yellow; 1180 | } 1181 | /* 1182 | [title]:hover { 1183 | display: flex; 1184 | align-items: center; 1185 | justify-content: center; 1186 | } 1187 | 1188 | [title]:hover::after { 1189 | content: ''; 1190 | position: absolute; 1191 | bottom: -1000px; 1192 | left: 8px; 1193 | display: none; 1194 | color: #fff; 1195 | border: 8px solid transparent; 1196 | border-bottom: 8px solid #000; 1197 | } 1198 | 1199 | [title]:hover::before { 1200 | content: attr(title); 1201 | display: flex; 1202 | justify-content: center; 1203 | align-self: center; 1204 | padding: 6px 12px; 1205 | border-radius: 5px; 1206 | background: rgba(0,0,0,0.8); 1207 | color: var(--sdpi-color); 1208 | font-size: 9pt; 1209 | font-family: sans-serif; 1210 | opacity: 1; 1211 | position: absolute; 1212 | height: auto; 1213 | 1214 | text-align: center; 1215 | bottom: 2px; 1216 | z-index: 100; 1217 | box-shadow: 0px 3px 6px rgba(0, 0, 0, .5); 1218 | } 1219 | */ 1220 | 1221 | .sdpi-item-group.file { 1222 | width: 232px; 1223 | display: flex; 1224 | align-items: center; 1225 | } 1226 | 1227 | .sdpi-file-info { 1228 | overflow-wrap: break-word; 1229 | word-wrap: break-word; 1230 | hyphens: auto; 1231 | 1232 | min-width: 132px; 1233 | max-width: 144px; 1234 | max-height: 32px; 1235 | margin-top: 0px; 1236 | margin-left: 5px; 1237 | display: inline-block; 1238 | overflow: hidden; 1239 | padding: 6px 4px; 1240 | background-color: var(--sdpi-background); 1241 | } 1242 | 1243 | 1244 | ::-webkit-scrollbar { 1245 | width: 8px; 1246 | } 1247 | 1248 | ::-webkit-scrollbar-track { 1249 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); 1250 | } 1251 | 1252 | ::-webkit-scrollbar-thumb { 1253 | background-color: #999999; 1254 | outline: 1px solid slategrey; 1255 | border-radius: 8px; 1256 | } 1257 | 1258 | a { 1259 | color: #7397d2; 1260 | } 1261 | 1262 | .testcontainer { 1263 | display: flex; 1264 | background-color: #0000ff20; 1265 | max-width: 400px; 1266 | height: 200px; 1267 | align-content: space-evenly; 1268 | } 1269 | 1270 | input[type=range] { 1271 | /* background-color: green; */ 1272 | height:6px; 1273 | margin-top: 12px; 1274 | z-index: 0; 1275 | overflow: visible; 1276 | } 1277 | 1278 | /* 1279 | input[type="range"]::-webkit-slider-thumb { 1280 | -webkit-appearance: none; 1281 | background-color: var(--sdpi-color); 1282 | width: 12px; 1283 | height: 12px; 1284 | border-radius: 20px; 1285 | margin-top: -6px; 1286 | border: none; 1287 | } */ 1288 | 1289 | :-webkit-slider-thumb { 1290 | -webkit-appearance: none; 1291 | background-color: var(--sdpi-color); 1292 | width: 16px; 1293 | height: 16px; 1294 | border-radius: 20px; 1295 | margin-top: -6px; 1296 | border: 1px solid #999999; 1297 | } 1298 | 1299 | .sdpi-item[type="range"] .sdpi-item-group { 1300 | display: flex; 1301 | flex-direction: column; 1302 | } 1303 | 1304 | .xxsdpi-item[type="range"] .sdpi-item-group input { 1305 | max-width: 204px; 1306 | } 1307 | 1308 | .sdpi-item[type="range"] .sdpi-item-group span { 1309 | margin-left: 0px !important; 1310 | } 1311 | 1312 | .sdpi-item[type="range"] .sdpi-item-group > .sdpi-item-child { 1313 | display: flex; 1314 | flex-direction: row; 1315 | } 1316 | 1317 | .rangeLabel { 1318 | position:absolute; 1319 | font-weight:normal; 1320 | margin-top:22px; 1321 | } 1322 | 1323 | :disabled { 1324 | color: #993333; 1325 | } 1326 | 1327 | select, 1328 | select option { 1329 | color: var(--sdpi-color); 1330 | } 1331 | 1332 | select.disabled, 1333 | select option:disabled { 1334 | color: #fd9494; 1335 | font-style: italic; 1336 | } 1337 | 1338 | .runningAppsContainer { 1339 | display: none; 1340 | } 1341 | 1342 | /* debug 1343 | div { 1344 | background-color: rgba(64,128,255,0.2); 1345 | } 1346 | */ 1347 | 1348 | .one-line { 1349 | min-height: 1.5em; 1350 | } 1351 | 1352 | .two-lines { 1353 | min-height: 3em; 1354 | } 1355 | 1356 | .three-lines { 1357 | min-height: 4.5em; 1358 | } 1359 | 1360 | .four-lines { 1361 | min-height: 6em; 1362 | } 1363 | 1364 | .min80 > .sdpi-item-child { 1365 | min-width: 80px; 1366 | } 1367 | 1368 | .min100 > .sdpi-item-child { 1369 | min-width: 100px; 1370 | } 1371 | 1372 | .min120 > .sdpi-item-child { 1373 | min-width: 120px; 1374 | } 1375 | 1376 | .min140 > .sdpi-item-child { 1377 | min-width: 140px; 1378 | } 1379 | 1380 | .min160 > .sdpi-item-child { 1381 | min-width: 160px; 1382 | } 1383 | 1384 | .min200 > .sdpi-item-child { 1385 | min-width: 200px; 1386 | } 1387 | 1388 | .max40 { 1389 | flex-basis: 40%; 1390 | flex-grow: 0; 1391 | } 1392 | 1393 | .max30 { 1394 | flex-basis: 30%; 1395 | flex-grow: 0; 1396 | } 1397 | 1398 | .max20 { 1399 | flex-basis: 20%; 1400 | flex-grow: 0; 1401 | } 1402 | 1403 | .up20 { 1404 | margin-top: -20px; 1405 | } 1406 | 1407 | .alignCenter { 1408 | align-items: center; 1409 | } 1410 | 1411 | .alignTop { 1412 | align-items: flex-start; 1413 | } 1414 | 1415 | .alignBaseline { 1416 | align-items: baseline; 1417 | } 1418 | 1419 | .noMargins, 1420 | .noMargins *, 1421 | .noInnerMargins * { 1422 | margin: 0; 1423 | padding: 0; 1424 | } 1425 | 1426 | .hidden { 1427 | display: none; 1428 | } 1429 | 1430 | .icon-help, 1431 | .icon-help-line, 1432 | .icon-help-fill, 1433 | .icon-help-inv, 1434 | .icon-brighter, 1435 | .icon-darker, 1436 | .icon-warmer, 1437 | .icon-cooler { 1438 | min-width: 20px; 1439 | width: 20px; 1440 | background-repeat: no-repeat; 1441 | opacity: 1; 1442 | } 1443 | 1444 | .icon-help:active, 1445 | .icon-help-line:active, 1446 | .icon-help-fill:active, 1447 | .icon-help-inv:active, 1448 | .icon-brighter:active, 1449 | .icon-darker:active, 1450 | .icon-warmer:active, 1451 | .icon-cooler:active { 1452 | opacity: 0.5; 1453 | } 1454 | 1455 | .icon-brighter, 1456 | .icon-darker, 1457 | .icon-warmer, 1458 | .icon-cooler { 1459 | margin-top: 5px !important; 1460 | } 1461 | 1462 | .icon-help, 1463 | .icon-help-line, 1464 | .icon-help-fill, 1465 | .icon-help-inv { 1466 | cursor: pointer; 1467 | margin: 0px; 1468 | margin-left: 4px; 1469 | } 1470 | 1471 | .icon-brighter { 1472 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cg fill='%23999' fill-rule='evenodd'%3E%3Ccircle cx='10' cy='10' r='4'/%3E%3Cpath d='M14.8532861,7.77530426 C14.7173255,7.4682615 14.5540843,7.17599221 14.3666368,6.90157083 L16.6782032,5.5669873 L17.1782032,6.4330127 L14.8532861,7.77530426 Z M10.5,4.5414007 C10.2777625,4.51407201 10.051423,4.5 9.82179677,4.5 C9.71377555,4.5 9.60648167,4.50311409 9.5,4.50925739 L9.5,2 L10.5,2 L10.5,4.5414007 Z M5.38028092,6.75545367 C5.18389364,7.02383457 5.01124349,7.31068015 4.86542112,7.61289977 L2.82179677,6.4330127 L3.32179677,5.5669873 L5.38028092,6.75545367 Z M4.86542112,12.3871002 C5.01124349,12.6893198 5.18389364,12.9761654 5.38028092,13.2445463 L3.32179677,14.4330127 L2.82179677,13.5669873 L4.86542112,12.3871002 Z M9.5,15.4907426 C9.60648167,15.4968859 9.71377555,15.5 9.82179677,15.5 C10.051423,15.5 10.2777625,15.485928 10.5,15.4585993 L10.5,18 L9.5,18 L9.5,15.4907426 Z M14.3666368,13.0984292 C14.5540843,12.8240078 14.7173255,12.5317385 14.8532861,12.2246957 L17.1782032,13.5669873 L16.6782032,14.4330127 L14.3666368,13.0984292 Z'/%3E%3C/g%3E%3C/svg%3E"); 1473 | } 1474 | .icon-darker { 1475 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cg fill='%23999' fill-rule='evenodd'%3E%3Cpath d='M10 14C7.790861 14 6 12.209139 6 10 6 7.790861 7.790861 6 10 6 12.209139 6 14 7.790861 14 10 14 12.209139 12.209139 14 10 14zM10 13C11.6568542 13 13 11.6568542 13 10 13 8.34314575 11.6568542 7 10 7 8.34314575 7 7 8.34314575 7 10 7 11.6568542 8.34314575 13 10 13zM14.8532861 7.77530426C14.7173255 7.4682615 14.5540843 7.17599221 14.3666368 6.90157083L16.6782032 5.5669873 17.1782032 6.4330127 14.8532861 7.77530426zM10.5 4.5414007C10.2777625 4.51407201 10.051423 4.5 9.82179677 4.5 9.71377555 4.5 9.60648167 4.50311409 9.5 4.50925739L9.5 2 10.5 2 10.5 4.5414007zM5.38028092 6.75545367C5.18389364 7.02383457 5.01124349 7.31068015 4.86542112 7.61289977L2.82179677 6.4330127 3.32179677 5.5669873 5.38028092 6.75545367zM4.86542112 12.3871002C5.01124349 12.6893198 5.18389364 12.9761654 5.38028092 13.2445463L3.32179677 14.4330127 2.82179677 13.5669873 4.86542112 12.3871002zM9.5 15.4907426C9.60648167 15.4968859 9.71377555 15.5 9.82179677 15.5 10.051423 15.5 10.2777625 15.485928 10.5 15.4585993L10.5 18 9.5 18 9.5 15.4907426zM14.3666368 13.0984292C14.5540843 12.8240078 14.7173255 12.5317385 14.8532861 12.2246957L17.1782032 13.5669873 16.6782032 14.4330127 14.3666368 13.0984292z'/%3E%3C/g%3E%3C/svg%3E"); 1476 | } 1477 | .icon-warmer { 1478 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cg fill='%23999' fill-rule='evenodd'%3E%3Cpath d='M12.3247275 11.4890349C12.0406216 11.0007637 11.6761954 10.5649925 11.2495475 10.1998198 11.0890394 9.83238991 11 9.42659309 11 9 11 7.34314575 12.3431458 6 14 6 15.6568542 6 17 7.34314575 17 9 17 10.6568542 15.6568542 12 14 12 13.3795687 12 12.8031265 11.8116603 12.3247275 11.4890349zM17.6232392 11.6692284C17.8205899 11.4017892 17.9890383 11.1117186 18.123974 10.8036272L20.3121778 12.0669873 19.8121778 12.9330127 17.6232392 11.6692284zM18.123974 7.19637279C17.9890383 6.88828142 17.8205899 6.5982108 17.6232392 6.33077158L19.8121778 5.0669873 20.3121778 5.9330127 18.123974 7.19637279zM14.5 4.52746439C14.3358331 4.50931666 14.1690045 4.5 14 4.5 13.8309955 4.5 13.6641669 4.50931666 13.5 4.52746439L13.5 2 14.5 2 14.5 4.52746439zM13.5 13.4725356C13.6641669 13.4906833 13.8309955 13.5 14 13.5 14.1690045 13.5 14.3358331 13.4906833 14.5 13.4725356L14.5 16 13.5 16 13.5 13.4725356zM14 11C15.1045695 11 16 10.1045695 16 9 16 7.8954305 15.1045695 7 14 7 12.8954305 7 12 7.8954305 12 9 12 10.1045695 12.8954305 11 14 11zM9.5 11C10.6651924 11.4118364 11.5 12.5 11.5 14 11.5 16 10 17.5 8 17.5 6 17.5 4.5 16 4.5 14 4.5 12.6937812 5 11.5 6.5 11L6.5 7 9.5 7 9.5 11z'/%3E%3Cpath d='M12,14 C12,16.209139 10.209139,18 8,18 C5.790861,18 4,16.209139 4,14 C4,12.5194353 4.80439726,11.2267476 6,10.5351288 L6,4 C6,2.8954305 6.8954305,2 8,2 C9.1045695,2 10,2.8954305 10,4 L10,10.5351288 C11.1956027,11.2267476 12,12.5194353 12,14 Z M11,14 C11,12.6937812 10.1651924,11.5825421 9,11.1707057 L9,4 C9,3.44771525 8.55228475,3 8,3 C7.44771525,3 7,3.44771525 7,4 L7,11.1707057 C5.83480763,11.5825421 5,12.6937812 5,14 C5,15.6568542 6.34314575,17 8,17 C9.65685425,17 11,15.6568542 11,14 Z'/%3E%3C/g%3E%3C/svg%3E"); 1479 | } 1480 | 1481 | .icon-cooler { 1482 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cg fill='%23999' fill-rule='evenodd'%3E%3Cpath d='M10.4004569 11.6239517C10.0554735 10.9863849 9.57597206 10.4322632 9 9.99963381L9 9.7450467 9.53471338 9.7450467 10.8155381 8.46422201C10.7766941 8.39376637 10.7419749 8.32071759 10.7117062 8.2454012L9 8.2454012 9 6.96057868 10.6417702 6.96057868C10.6677696 6.86753378 10.7003289 6.77722682 10.7389179 6.69018783L9.44918707 5.40045694 9 5.40045694 9 4.34532219 9.32816127 4.34532219 9.34532219 2.91912025 10.4004569 2.91912025 10.4004569 4.53471338 11.6098599 5.74411634C11.7208059 5.68343597 11.8381332 5.63296451 11.9605787 5.59396526L11.9605787 3.8884898 10.8181818 2.74609294 11.5642748 2 12.5727518 3.00847706 13.5812289 2 14.3273218 2.74609294 13.2454012 3.82801356 13.2454012 5.61756719C13.3449693 5.65339299 13.4408747 5.69689391 13.5324038 5.74735625L14.7450467 4.53471338 14.7450467 2.91912025 15.8001815 2.91912025 15.8001815 4.34532219 17.2263834 4.34532219 17.2263834 5.40045694 15.6963166 5.40045694 14.4002441 6.69652946C14.437611 6.78161093 14.4692249 6.86979146 14.4945934 6.96057868L16.2570138 6.96057868 17.3994107 5.81818182 18.1455036 6.56427476 17.1370266 7.57275182 18.1455036 8.58122888 17.3994107 9.32732182 16.3174901 8.2454012 14.4246574 8.2454012C14.3952328 8.31861737 14.3616024 8.38969062 14.3240655 8.45832192L15.6107903 9.7450467 17.2263834 9.7450467 17.2263834 10.8001815 15.8001815 10.8001815 15.8001815 12.2263834 14.7450467 12.2263834 14.7450467 10.6963166 13.377994 9.32926387C13.3345872 9.34850842 13.2903677 9.36625331 13.2454012 9.38243281L13.2454012 11.3174901 14.3273218 12.3994107 13.5812289 13.1455036 12.5848864 12.1491612 11.5642748 13.1455036 10.8181818 12.3994107 11.9605787 11.2570138 11.9605787 9.40603474C11.8936938 9.38473169 11.828336 9.36000556 11.7647113 9.33206224L10.4004569 10.6963166 10.4004569 11.6239517zM12.75 8.5C13.3022847 8.5 13.75 8.05228475 13.75 7.5 13.75 6.94771525 13.3022847 6.5 12.75 6.5 12.1977153 6.5 11.75 6.94771525 11.75 7.5 11.75 8.05228475 12.1977153 8.5 12.75 8.5zM9.5 14C8.5 16.3333333 7.33333333 17.5 6 17.5 4.66666667 17.5 3.5 16.3333333 2.5 14L9.5 14z'/%3E%3Cpath d='M10,14 C10,16.209139 8.209139,18 6,18 C3.790861,18 2,16.209139 2,14 C2,12.5194353 2.80439726,11.2267476 4,10.5351288 L4,4 C4,2.8954305 4.8954305,2 6,2 C7.1045695,2 8,2.8954305 8,4 L8,10.5351288 C9.19560274,11.2267476 10,12.5194353 10,14 Z M9,14 C9,12.6937812 8.16519237,11.5825421 7,11.1707057 L7,4 C7,3.44771525 6.55228475,3 6,3 C5.44771525,3 5,3.44771525 5,4 L5,11.1707057 C3.83480763,11.5825421 3,12.6937812 3,14 C3,15.6568542 4.34314575,17 6,17 C7.65685425,17 9,15.6568542 9,14 Z'/%3E%3C/g%3E%3C/svg%3E"); 1483 | } 1484 | 1485 | .icon-help { 1486 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Cpath fill='%23999' d='M11.292 12.516l.022 1.782H9.07v-1.804c0-1.98 1.276-2.574 2.662-3.278h-.022c.814-.44 1.65-.88 1.694-2.2.044-1.386-1.122-2.728-3.234-2.728-1.518 0-2.662.902-3.366 2.354L5 5.608C5.946 3.584 7.662 2 10.17 2c3.564 0 5.632 2.442 5.588 5.06-.066 2.618-1.716 3.41-3.102 4.158-.704.374-1.364.682-1.364 1.298zm-1.122 2.442c.858 0 1.452.594 1.452 1.452 0 .682-.594 1.408-1.452 1.408-.77 0-1.386-.726-1.386-1.408 0-.858.616-1.452 1.386-1.452z'/%3E%3C/svg%3E"); 1487 | } 1488 | 1489 | .icon-help-line { 1490 | background-image: url("data:image/svg+xml,%3Csvg width='20' height='20' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%23999' fill-rule='evenodd'%3E%3Cpath d='M10 20C4.477 20 0 15.523 0 10S4.477 0 10 0s10 4.477 10 10-4.477 10-10 10zm0-1a9 9 0 1 0 0-18 9 9 0 0 0 0 18z'/%3E%3Cpath d='M10.848 12.307l.02 1.578H8.784v-1.597c0-1.753 1.186-2.278 2.474-2.901h-.02c.756-.39 1.533-.78 1.574-1.948.041-1.226-1.043-2.414-3.006-2.414-1.41 0-2.474.798-3.128 2.083L5 6.193C5.88 4.402 7.474 3 9.805 3 13.118 3 15.04 5.161 15 7.478c-.061 2.318-1.595 3.019-2.883 3.68-.654.332-1.268.604-1.268 1.15zM9.805 14.47c.798 0 1.35.525 1.35 1.285 0 .603-.552 1.246-1.35 1.246-.715 0-1.288-.643-1.288-1.246 0-.76.573-1.285 1.288-1.285z' fill-rule='nonzero'/%3E%3C/g%3E%3C/svg%3E");} 1491 | 1492 | .icon-help-fill { 1493 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Ccircle cx='10' cy='10' r='10' fill='%23999'/%3E%3Cpath fill='%23FFF' fill-rule='nonzero' d='M8.368 7.189H5C5 3.5 7.668 2 10.292 2 13.966 2 16 4.076 16 7.012c0 3.754-3.849 3.136-3.849 5.211v1.656H8.455v-1.832c0-2.164 1.4-2.893 2.778-3.6.437-.242 1.006-.574 1.006-1.236 0-2.208-3.871-2.142-3.871-.022zM10.25 18a1.75 1.75 0 1 1 0-3.5 1.75 1.75 0 0 1 0 3.5z'/%3E%3C/g%3E%3C/svg%3E"); 1494 | } 1495 | 1496 | .icon-help-inv { 1497 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Cpath fill='%23999' fill-rule='evenodd' d='M10 20C4.477 20 0 15.523 0 10S4.477 0 10 0s10 4.477 10 10-4.477 10-10 10zM8.368 7.189c0-2.12 3.87-2.186 3.87.022 0 .662-.568.994-1.005 1.236-1.378.707-2.778 1.436-2.778 3.6v1.832h3.696v-1.656c0-2.075 3.849-1.457 3.849-5.21C16 4.075 13.966 2 10.292 2 7.668 2 5 3.501 5 7.189h3.368zM10.25 18a1.75 1.75 0 1 0 0-3.5 1.75 1.75 0 0 0 0 3.5z'/%3E%3C/svg%3E"); 1498 | } 1499 | 1500 | .kelvin::after { 1501 | content: "K"; 1502 | } 1503 | 1504 | .mired::after { 1505 | content: " Mired"; 1506 | } 1507 | 1508 | .percent::after { 1509 | content: "%"; 1510 | } 1511 | 1512 | .sdpi-item-value + .icon-cooler, 1513 | .sdpi-item-value + .icon-warmer { 1514 | margin-left: 0px !important; 1515 | margin-top: 15px !important; 1516 | } 1517 | 1518 | /** 1519 | CONTROL-CENTER STYLES 1520 | */ 1521 | input[type="range"].colorbrightness::-webkit-slider-runnable-track, 1522 | input[type="range"].colortemperature::-webkit-slider-runnable-track { 1523 | height: 8px; 1524 | background: #979797; 1525 | border-radius: 4px; 1526 | background-image: linear-gradient(to right,#94d0ec, #ffb165); 1527 | } 1528 | 1529 | input[type="range"].colorbrightness::-webkit-slider-runnable-track { 1530 | background-color: #efefef; 1531 | background-image: linear-gradient(to right, black , rgba(0,0,0,0)); 1532 | } 1533 | 1534 | 1535 | input[type="range"].colorbrightness::-webkit-slider-thumb, 1536 | input[type="range"].colortemperature::-webkit-slider-thumb { 1537 | width: 16px; 1538 | height: 16px; 1539 | border-radius: 20px; 1540 | margin-top: -5px; 1541 | background-color: #86c6e8; 1542 | box-shadow: 0px 0px 1px #000000; 1543 | border: 1px solid #d8d8d8; 1544 | } 1545 | .sdpi-info-label { 1546 | display: inline-block; 1547 | user-select: none; 1548 | position: absolute; 1549 | height: 15px; 1550 | width: auto; 1551 | text-align: center; 1552 | border-radius: 4px; 1553 | min-width: 44px; 1554 | max-width: 80px; 1555 | background: white; 1556 | font-size: 11px; 1557 | color: black; 1558 | z-index: 1000; 1559 | box-shadow: 0px 0px 12px rgba(0,0,0,.8); 1560 | padding: 2px; 1561 | 1562 | } 1563 | 1564 | .sdpi-info-label.hidden { 1565 | opacity: 0; 1566 | transition: opacity 0.25s linear; 1567 | } 1568 | 1569 | .sdpi-info-label.shown { 1570 | position: absolute; 1571 | opacity: 1; 1572 | transition: opacity 0.25s ease-out; 1573 | } 1574 | 1575 | 1576 | 1577 | 1578 | /*--------- context menu ----------*/ 1579 | 1580 | .context-menu { 1581 | display: none; 1582 | position: absolute; 1583 | z-index: 10; 1584 | padding: 12px 0; 1585 | width: 120px; 1586 | background-color: #3D3D3D; 1587 | border: solid 1px #dfdfdf; 1588 | box-shadow: 1px 1px 2px #cfcfcf; 1589 | } 1590 | 1591 | .context-menu--active { 1592 | display: block; 1593 | } 1594 | 1595 | .context-menu__items { 1596 | list-style: none; 1597 | margin: 0; 1598 | padding: 0; 1599 | overflow-y: auto; 1600 | } 1601 | 1602 | .context-menu__item { 1603 | display: block; 1604 | margin-bottom: 4px; 1605 | background-color: #3D3D3D !important; 1606 | } 1607 | 1608 | .context-menu__item:last-child { 1609 | margin-bottom: 0; 1610 | } 1611 | 1612 | .context-menu__link { 1613 | display: block; 1614 | padding: 4px 12px; 1615 | color: #ffff; 1616 | text-decoration: none; 1617 | white-space: nowrap; 1618 | } 1619 | 1620 | .context-menu__link:hover { 1621 | color: #fff; 1622 | background-color: #0066aa; 1623 | } 1624 | 1625 | .context-menu_message{ 1626 | cursor: default; 1627 | } -------------------------------------------------------------------------------- /streamdeck-plugin-vue/public/img/actionIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoton2/node-streamdeck-util/d2f51374eb44640240362131c830e5bf14a1ee1c/streamdeck-plugin-vue/public/img/actionIcon.png -------------------------------------------------------------------------------- /streamdeck-plugin-vue/public/img/actionIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoton2/node-streamdeck-util/d2f51374eb44640240362131c830e5bf14a1ee1c/streamdeck-plugin-vue/public/img/actionIcon@2x.png -------------------------------------------------------------------------------- /streamdeck-plugin-vue/public/img/pluginIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoton2/node-streamdeck-util/d2f51374eb44640240362131c830e5bf14a1ee1c/streamdeck-plugin-vue/public/img/pluginIcon.png -------------------------------------------------------------------------------- /streamdeck-plugin-vue/public/img/pluginIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoton2/node-streamdeck-util/d2f51374eb44640240362131c830e5bf14a1ee1c/streamdeck-plugin-vue/public/img/pluginIcon@2x.png -------------------------------------------------------------------------------- /streamdeck-plugin-vue/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "Actions": [ 3 | { 4 | "Name": "Example", 5 | "Icon": "img/actionIcon", 6 | "States": [ 7 | { 8 | "Image": "img/actionIcon", 9 | "TitleAlignment": "middle", 10 | "Title": "Example" 11 | } 12 | ], 13 | "SupportedInMultiActions": false, 14 | "Tooltip": "Example action needed to run the plugin.", 15 | "UUID": "com.zoton2.example.action" 16 | } 17 | ], 18 | "Author": "zoton2", 19 | "Category": "node-streamdeck-util", 20 | "CodePath": "backend.html", 21 | "Description": "Barebones plugin to be used with node-streamdeck-util.", 22 | "Name": "node-streamdeck-util", 23 | "Icon": "img/pluginIcon", 24 | "URL": "https://github.com/zoton2/node-streamdeck-util", 25 | "PropertyInspectorPath": "pi.html", 26 | "DefaultWindowSize": [ 27 | 400, 28 | 200 29 | ], 30 | "Version": "1.0", 31 | "SDKVersion": 2, 32 | "OS": [ 33 | { 34 | "Platform": "mac", 35 | "MinimumVersion": "10.11" 36 | }, 37 | { 38 | "Platform": "windows", 39 | "MinimumVersion": "10" 40 | } 41 | ], 42 | "Software": { 43 | "MinimumVersion": "5" 44 | } 45 | } -------------------------------------------------------------------------------- /streamdeck-plugin-vue/public/pi.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | com.zoton2.example Property Inspector 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /streamdeck-plugin-vue/public/settings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | com.zoton2.example Settings Dialog 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /streamdeck-plugin-vue/src/backend/App.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 31 | -------------------------------------------------------------------------------- /streamdeck-plugin-vue/src/backend/backend.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events'; 2 | 3 | /* eslint-disable max-len, @typescript-eslint/no-explicit-any */ 4 | interface Backend { 5 | on(event: 'message', listener: (data: any) => void): this; 6 | } 7 | /* eslint-enable */ 8 | 9 | class Backend extends EventEmitter { 10 | sdWS!: WebSocket; 11 | serverWS!: WebSocket; 12 | serverWSReconnTimeout!: number; 13 | connectSocketData: { 14 | inPort?: string, 15 | inPluginUUID?: string, 16 | inRegisterEvent?: string, 17 | inInfo?: string, 18 | } = {}; 19 | globalSettings: { connected?: boolean, url?: string, key?: string } = {}; 20 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 21 | buttonLocations: { [device: string]: { [x: string]: { [y: string]: any } } } = {}; // to type? 22 | 23 | /** 24 | * Intended to be triggered by the Stream Deck software. 25 | */ 26 | connectElgatoStreamDeckSocket( 27 | inPort: string, 28 | inPluginUUID: string, 29 | inRegisterEvent: string, 30 | inInfo: string, 31 | ): void { 32 | this.connectSocketData.inPort = inPort; 33 | this.connectSocketData.inPluginUUID = inPluginUUID; 34 | this.connectSocketData.inRegisterEvent = inRegisterEvent; 35 | this.connectSocketData.inInfo = inInfo; 36 | this.connectToSDWS(); 37 | } 38 | 39 | /** 40 | * Initalise connection to Stream Deck's WebSocket. 41 | */ 42 | connectToSDWS(): void { 43 | if (this.sdWS) this.sdWS.close(); // Close current connection if one is active. 44 | 45 | this.sdWS = new WebSocket(`ws://127.0.0.1:${this.connectSocketData.inPort}`); 46 | console.info('Connecting to Stream Deck software'); 47 | 48 | this.sdWS.addEventListener('error', (e) => { 49 | console.error('Error occured on the Stream Deck software connection:', e); 50 | }); 51 | 52 | // Initalise Stream Deck WebSocket connection. 53 | this.sdWS.addEventListener('open', () => { 54 | console.info('Connection to Stream Deck software successful'); 55 | this.buttonLocations = {}; 56 | this.sdWS.send(JSON.stringify({ 57 | event: this.connectSocketData.inRegisterEvent, 58 | uuid: this.connectSocketData.inPluginUUID, 59 | })); 60 | this.sdWS.send(JSON.stringify({ 61 | event: 'getGlobalSettings', 62 | context: this.connectSocketData.inPluginUUID, 63 | })); 64 | }, { once: true }); 65 | 66 | this.sdWS.addEventListener('close', (e) => { 67 | console.warn('Connection to Stream Deck software closed (%s)', e.code); 68 | this.buttonLocations = {}; 69 | }, { once: true }); 70 | 71 | this.sdWS.addEventListener('message', (e) => { 72 | const data = JSON.parse(e.data); 73 | const { event, device, deviceInfo, action, context, payload } = data; 74 | 75 | // Create button location storage for this device if empty; 76 | // usually from a deviceDidConnect message. 77 | if (device && !this.buttonLocations[device]) { 78 | this.buttonLocations[device] = this 79 | .createButtonLocationStorage(deviceInfo.size.rows, deviceInfo.size.columns); 80 | } 81 | 82 | // Adjust our button locations cache when buttons are added/removed, and set defaults. 83 | // TODO: Is all this needed? Maybe even more. 84 | if (event === 'willAppear') { 85 | this.buttonLocations[device][payload.coordinates.row][ 86 | payload.coordinates.column] = {}; 87 | this.buttonLocations[device][payload.coordinates.row][ 88 | payload.coordinates.column].context = context; 89 | this.buttonLocations[device][payload.coordinates.row][ 90 | payload.coordinates.column].action = action; 91 | this.buttonLocations[device][payload.coordinates.row][ 92 | payload.coordinates.column].isInMultiAction = payload.isInMultiAction; 93 | } else if (event === 'willDisappear') { 94 | this.buttonLocations[device][payload.coordinates.row][ 95 | payload.coordinates.column] = null; 96 | } 97 | 98 | // Update title/title parameters/state we have saved if it's changed. 99 | // TODO: Is all this needed? Maybe even more. 100 | if (event === 'titleParametersDidChange') { 101 | this.buttonLocations[device][payload.coordinates.row][ 102 | payload.coordinates.column].title = payload.title; 103 | this.buttonLocations[device][payload.coordinates.row][ 104 | payload.coordinates.column].titleParameters = payload.titleParameters; 105 | this.buttonLocations[device][payload.coordinates.row][ 106 | payload.coordinates.column].state = payload.state; 107 | } 108 | 109 | // If buttonLocations were updated for any reason, relay this to node-streamdeck-util server. 110 | if (['willAppear', 'willDisappear', 'titleParametersDidChange'].includes(event)) { 111 | this.sendToServerWS('buttonLocationsUpdated', { buttonLocations: this.buttonLocations }); 112 | } 113 | 114 | // Update global settings if needed, usually for first use. 115 | // This updates local stored settings and sets any defaults if needed, 116 | // then connects to the node-streamdeck-util server. 117 | if (data.event === 'didReceiveGlobalSettings') { 118 | this.globalSettings.url = data.payload.settings.url || 'ws://localhost:9091'; // Default 119 | this.globalSettings.key = data.payload.settings.key || 'DEFAULT_KEY'; // Default 120 | this.globalSettings.connected = false; 121 | this.sdWS.send(JSON.stringify({ 122 | event: 'setGlobalSettings', 123 | context: this.connectSocketData.inPluginUUID, 124 | payload: this.globalSettings, 125 | })); 126 | this.connectToServerWS(); 127 | } 128 | 129 | // Sends the full raw message to the node-streamdeck-util server. 130 | this.sendToServerWS('rawSD', data); 131 | 132 | this.emit('message', data); 133 | }); 134 | } 135 | 136 | /** 137 | * Helper function to send messages to the Stream Deck WebSocket server if connection is ready. 138 | */ 139 | sendToSDWS(data: unknown): void { 140 | if (this.sdWS && this.sdWS.readyState === 1) { 141 | const str = typeof data !== 'string' ? JSON.stringify(data) : data; 142 | this.sdWS.send(str); 143 | } 144 | } 145 | 146 | /** 147 | * node-streamdeck-util connection. 148 | */ 149 | connectToServerWS(): void { 150 | if (this.serverWS) this.serverWS.close(); // Close current connection if one is active. 151 | clearTimeout(this.serverWSReconnTimeout); 152 | 153 | this.serverWS = new WebSocket(`${this.globalSettings.url}/?key=${this.globalSettings.key}`); 154 | console.info( 155 | 'Connecting to node-streamdeck-util server using %s and key %s', 156 | this.globalSettings.url, 157 | this.globalSettings.key, 158 | ); 159 | 160 | this.serverWS.addEventListener('error', (e) => { 161 | console.warn('Error occured on the node-streamdeck-util server connection:', e); 162 | }); 163 | 164 | // Initalise node-streamdeck-util server connection. 165 | this.serverWS.addEventListener('open', () => { 166 | console.info('Connection to node-streamdeck-util server successful'); 167 | this.sendToServerWS('init', { pluginUUID: this.connectSocketData.inPluginUUID }); 168 | this.sendToServerWS('buttonLocationsUpdated', { buttonLocations: this.buttonLocations }); 169 | this.toggleBackendConnectionStatus(true); 170 | }, { once: true }); 171 | 172 | this.serverWS.addEventListener('close', (e) => { 173 | console.warn('Connection to node-streamdeck-util server closed (%s)', e.code); 174 | this.toggleBackendConnectionStatus(false); 175 | clearTimeout(this.serverWSReconnTimeout); 176 | this.serverWSReconnTimeout = setTimeout(() => { this.connectToServerWS(); }, 5000); 177 | }, { once: true }); 178 | 179 | // Relays any messages sent from the node-streamdeck-util server to the main socket. 180 | this.serverWS.addEventListener('message', (e) => { 181 | const { data } = e; 182 | this.sendToSDWS(data); 183 | }); 184 | } 185 | 186 | /** 187 | * Helper function to send messages to the node-streamdeck-util server if connection is ready. 188 | */ 189 | sendToServerWS(type: string, data: unknown): void { 190 | if (this.serverWS && this.serverWS.readyState === 1) { 191 | this.serverWS.send(JSON.stringify({ type, data })); 192 | } 193 | } 194 | 195 | /** 196 | * Toggles global setting for connection status. 197 | */ 198 | toggleBackendConnectionStatus(connected: boolean): void { 199 | this.globalSettings.connected = connected; 200 | this.sdWS.send(JSON.stringify({ 201 | event: 'setGlobalSettings', 202 | context: this.connectSocketData.inPluginUUID, 203 | payload: this.globalSettings, 204 | })); 205 | } 206 | 207 | // Create object with structure to store button location details. 208 | /** 209 | * Create object with structure to store button location details. 210 | * @param rows Number of rows device has. 211 | * @param columns Number of columns device has. 212 | */ 213 | createButtonLocationStorage( 214 | rows: number, 215 | columns: number, 216 | ): { [x: string]: { [y: string]: unknown } } { 217 | const locations: { [x: string]: { [y: string]: unknown } } = {}; 218 | for (let x = 0; x < rows; x += 1) { 219 | locations[x] = {}; 220 | for (let y = 0; y < columns; y += 1) { 221 | locations[x][y] = null; 222 | } 223 | } 224 | return locations; 225 | } 226 | } 227 | 228 | export default Backend; 229 | -------------------------------------------------------------------------------- /streamdeck-plugin-vue/src/backend/main.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import App from './App.vue'; 3 | 4 | Vue.config.productionTip = false; 5 | 6 | new Vue({ 7 | render: (h) => h(App), 8 | }).$mount('#app'); 9 | -------------------------------------------------------------------------------- /streamdeck-plugin-vue/src/pi/App.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 47 | -------------------------------------------------------------------------------- /streamdeck-plugin-vue/src/pi/main.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import App from './App.vue'; 3 | 4 | Vue.config.productionTip = false; 5 | 6 | new Vue({ 7 | render: (h) => h(App), 8 | }).$mount('#app'); 9 | -------------------------------------------------------------------------------- /streamdeck-plugin-vue/src/pi/pi.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events'; 2 | 3 | /* eslint-disable max-len, @typescript-eslint/no-explicit-any */ 4 | interface PropertyInspector { 5 | on(event: 'open', listener: () => void): this; 6 | on(event: 'message', listener: (data: any) => void): this; 7 | } 8 | /* eslint-enable */ 9 | 10 | class PropertyInspector extends EventEmitter { 11 | sdWS!: WebSocket; 12 | connectSocketData: { 13 | inPort?: string, 14 | inPropertyInspectorUUID?: string, 15 | inRegisterEvent?: string, 16 | inInfo?: string, 17 | inActionInfo?: string, 18 | } = {}; 19 | globalSettings: { connected?: boolean, url?: string, key?: string } = {}; 20 | 21 | /** 22 | * Intended to be triggered by the Stream Deck software. 23 | */ 24 | connectElgatoStreamDeckSocket( 25 | inPort: string, 26 | inPropertyInspectorUUID: string, 27 | inRegisterEvent: string, 28 | inInfo: string, 29 | inActionInfo: string, 30 | ): void { 31 | this.connectSocketData.inPort = inPort; 32 | this.connectSocketData.inPropertyInspectorUUID = inPropertyInspectorUUID; 33 | this.connectSocketData.inRegisterEvent = inRegisterEvent; 34 | this.connectSocketData.inInfo = inInfo; 35 | this.connectSocketData.inActionInfo = inActionInfo; 36 | this.connectToSDWS(); 37 | } 38 | 39 | /** 40 | * Initalise connection to Stream Deck's WebSocket. 41 | */ 42 | connectToSDWS(): void { 43 | if (this.sdWS) this.sdWS.close(); // Close current connection if one is active. 44 | 45 | this.sdWS = new WebSocket(`ws://127.0.0.1:${this.connectSocketData.inPort}`); 46 | console.info('Connecting to Stream Deck software'); 47 | 48 | this.sdWS.addEventListener('error', (e) => { 49 | console.error('Error occured on the Stream Deck software connection:', e); 50 | }); 51 | 52 | // Initalise Stream Deck WebSocket connection. 53 | this.sdWS.addEventListener('open', () => { 54 | console.info('Connection to Stream Deck software successful'); 55 | this.sdWS.send(JSON.stringify({ 56 | event: this.connectSocketData.inRegisterEvent, 57 | uuid: this.connectSocketData.inPropertyInspectorUUID, 58 | })); 59 | this.sdWS.send(JSON.stringify({ 60 | event: 'getGlobalSettings', 61 | context: this.connectSocketData.inPropertyInspectorUUID, 62 | })); 63 | this.emit('open'); 64 | }, { once: true }); 65 | 66 | this.sdWS.addEventListener('close', (e) => { 67 | console.warn('Connection to Stream Deck software closed (%s)', e.code); 68 | }, { once: true }); 69 | 70 | this.sdWS.addEventListener('message', (e) => { 71 | const data = JSON.parse(e.data); 72 | if (data.event === 'didReceiveGlobalSettings') { 73 | this.globalSettings = data.payload.settings; 74 | } 75 | this.emit('message', data); 76 | }); 77 | } 78 | 79 | /** 80 | * Opens the settings dialog. 81 | */ 82 | openSettings(): void { 83 | window.globalSettings = this.globalSettings; 84 | window.open('settings.html'); 85 | } 86 | 87 | /** 88 | * Function triggered by the popup settings window when the settings are saved. 89 | * @param data Data passed back by popup window. 90 | */ 91 | gotCallbackFromWindow(data: { url: string, key: string }): void { 92 | console.info('URL/key settings have changed, saving settings'); 93 | this.globalSettings.url = data.url || 'ws://localhost:9091'; // Default if setting is empty 94 | this.globalSettings.key = data.key || 'DEFAULT_KEY'; // Default if setting is empty 95 | this.globalSettings.connected = false; 96 | this.sdWS.send(JSON.stringify({ 97 | event: 'setGlobalSettings', 98 | context: this.connectSocketData.inPropertyInspectorUUID, 99 | payload: this.globalSettings, 100 | })); 101 | } 102 | } 103 | 104 | export default PropertyInspector; 105 | -------------------------------------------------------------------------------- /streamdeck-plugin-vue/src/settings/App.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 54 | 55 | 78 | -------------------------------------------------------------------------------- /streamdeck-plugin-vue/src/settings/main.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue'; 2 | import App from './App.vue'; 3 | 4 | Vue.config.productionTip = false; 5 | 6 | new Vue({ 7 | render: (h) => h(App), 8 | }).$mount('#app'); 9 | -------------------------------------------------------------------------------- /streamdeck-plugin-vue/src/types/browser-global.d.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/prefer-default-export 2 | export declare global { 3 | interface Window { 4 | // Basic types to stop compiler from complaining, needs improvement. 5 | globalSettings?: { [k: string]: unknown } 6 | connectElgatoStreamDeckSocket?: unknown; 7 | gotCallbackFromWindow?: unknown; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /streamdeck-plugin-vue/src/types/shims-tsx.d.ts: -------------------------------------------------------------------------------- 1 | import Vue, { VNode } from 'vue'; 2 | 3 | declare global { 4 | namespace JSX { 5 | // tslint:disable no-empty-interface 6 | interface Element extends VNode {} 7 | // tslint:disable no-empty-interface 8 | interface ElementClass extends Vue {} 9 | interface IntrinsicElements { 10 | [elem: string]: any 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /streamdeck-plugin-vue/src/types/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue'; 3 | 4 | export default Vue; 5 | } 6 | -------------------------------------------------------------------------------- /streamdeck-plugin-vue/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "experimentalDecorators": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "sourceMap": true, 14 | "baseUrl": ".", 15 | "types": [ 16 | "webpack-env" 17 | ], 18 | "paths": { 19 | "@/*": [ 20 | "src/*" 21 | ] 22 | }, 23 | "lib": [ 24 | "esnext", 25 | "dom", 26 | "dom.iterable", 27 | "scripthost" 28 | ] 29 | }, 30 | "include": [ 31 | "src/**/*.ts", 32 | "src/**/*.tsx", 33 | "src/**/*.vue", 34 | "tests/**/*.ts", 35 | "tests/**/*.tsx" 36 | ], 37 | "exclude": [ 38 | "node_modules" 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /streamdeck-plugin-vue/vue.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | lintOnSave: false, 3 | outputDir: 'com.zoton2.example.sdPlugin', 4 | pages: { 5 | backend: 'src/backend/main.ts', 6 | pi: 'src/pi/main.ts', 7 | settings: 'src/settings/main.ts', 8 | }, 9 | publicPath: './', 10 | }; 11 | -------------------------------------------------------------------------------- /streamdeck-plugin/com.zoton2.example.sdPlugin/backend.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | com.zoton2.example Backend 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /streamdeck-plugin/com.zoton2.example.sdPlugin/backend.js: -------------------------------------------------------------------------------- 1 | var sdWS; 2 | var serverWS; 3 | var serverWSReconnTimeout; 4 | var connectSocketData = {}; 5 | var globalSettings = {}; 6 | var buttonLocations = {}; 7 | 8 | // Create object with structure to store button location details. 9 | function createButtonLocationStorage(rows, columns) { 10 | var locations = {}; 11 | for (var x = 0; x < rows; x++) { 12 | locations[x] = {}; 13 | for (var y = 0; y < columns; y++) { 14 | locations[x][y] = null; 15 | } 16 | } 17 | return locations; 18 | } 19 | 20 | // Toggles global setting for connection status. 21 | function toggleBackendConnectionStatus(connected) { 22 | globalSettings.connected = connected; 23 | sdWS.send(JSON.stringify({ event: 'setGlobalSettings', context: connectSocketData.pluginUUID, payload: globalSettings })); 24 | } 25 | 26 | // Helper function to send messages to the node-streamdeck-util server if connection is ready. 27 | function sendToServerWS(type, data) { 28 | if (serverWS && serverWS.readyState === 1) { 29 | serverWS.send(JSON.stringify({ type: type, data: data })); 30 | } 31 | } 32 | 33 | // Helper function to send messages to the Stream Deck WebSocket server if connection is ready. 34 | function sendToSDWS(data) { 35 | if (this.sdWS && this.sdWS.readyState === 1) { 36 | const str = typeof data !== 'string' ? JSON.stringify(data) : data; 37 | this.sdWS.send(str); 38 | } 39 | } 40 | 41 | // node-streamdeck-util connection. 42 | function connectToServerWS() { 43 | if (serverWS) serverWS.close(); // Close current connection if one is active. 44 | clearTimeout(serverWSReconnTimeout); 45 | 46 | serverWS = new WebSocket(`${globalSettings.url}/?key=${globalSettings.key}`); 47 | console.info( 48 | 'Connecting to node-streamdeck-util server using %s and key %s', 49 | globalSettings.url, 50 | globalSettings.key, 51 | ); 52 | 53 | serverWS.addEventListener('error', e => { 54 | console.warn('Error occured on the node-streamdeck-util server connection:', e); 55 | }); 56 | 57 | // Initalise node-streamdeck-util server connection. 58 | serverWS.addEventListener('open', () => { 59 | console.info('Connection to node-streamdeck-util server successful'); 60 | sendToServerWS('init', { pluginUUID: connectSocketData.pluginUUID }); 61 | sendToServerWS('buttonLocationsUpdated', { buttonLocations: buttonLocations }); 62 | toggleBackendConnectionStatus(true); 63 | }, { once: true }); 64 | 65 | serverWS.addEventListener('close', e => { 66 | console.warn('Connection to node-streamdeck-util server closed (%s)', e.code); 67 | toggleBackendConnectionStatus(false); 68 | clearTimeout(serverWSReconnTimeout); 69 | serverWSReconnTimeout = setTimeout(connectToServerWS, 5000); 70 | }, { once: true }); 71 | 72 | // Relays any messages sent from the node-streamdeck-util server to the main socket. 73 | serverWS.addEventListener('message', e => { 74 | const data = e.data; 75 | sendToSDWS(data); 76 | }); 77 | } 78 | 79 | // Initalise connection to Stream Deck's WebSocket. 80 | function connectToSDWS() { 81 | if (sdWS) sdWS.close(); // Close current connection if one is active. 82 | 83 | sdWS = new WebSocket(`ws://127.0.0.1:${connectSocketData.port}`); 84 | console.info('Connecting to Stream Deck software'); 85 | 86 | sdWS.addEventListener('error', e => { 87 | console.error('Error occured on the Stream Deck software connection:', e); 88 | }); 89 | 90 | // Initalise Stream Deck WebSocket connection. 91 | sdWS.addEventListener('open', () => { 92 | console.info('Connection to Stream Deck software successful'); 93 | buttonLocations = {}; 94 | sdWS.send(JSON.stringify({ event: connectSocketData.registerEvent, uuid: connectSocketData.pluginUUID })); 95 | sdWS.send(JSON.stringify({ event: 'getGlobalSettings', context: connectSocketData.pluginUUID })); 96 | }, { once: true }); 97 | 98 | sdWS.addEventListener('close', e => { 99 | console.warn('Connection to Stream Deck software closed (%s)', e.code); 100 | buttonLocations = {}; 101 | }, { once: true }); 102 | 103 | sdWS.addEventListener('message', e => { 104 | const data = JSON.parse(e.data); 105 | const { event, device, deviceInfo, action, context, payload } = data; 106 | 107 | // Create button location storage for this device if empty; usually from a deviceDidConnect message. 108 | if (device && !buttonLocations[device]) { 109 | buttonLocations[device] = createButtonLocationStorage(deviceInfo.size.rows, deviceInfo.size.columns); 110 | } 111 | 112 | // Adjust our button locations cache when buttons are added/removed, and set defaults. 113 | // TODO: Is all this needed? Maybe even more. 114 | if (event === 'willAppear') { 115 | buttonLocations[device][payload.coordinates.row][payload.coordinates.column] = {}; 116 | buttonLocations[device][payload.coordinates.row][payload.coordinates.column].context = context; 117 | buttonLocations[device][payload.coordinates.row][payload.coordinates.column].action = action; 118 | buttonLocations[device][payload.coordinates.row][payload.coordinates.column].isInMultiAction = payload.isInMultiAction; 119 | } else if (event === 'willDisappear') { 120 | buttonLocations[device][payload.coordinates.row][payload.coordinates.column] = null; 121 | } 122 | 123 | // Update title/title parameters/state we have saved if it's changed. 124 | // TODO: Is all this needed? Maybe even more. 125 | if (event === 'titleParametersDidChange') { 126 | buttonLocations[device][payload.coordinates.row][payload.coordinates.column].title = payload.title; 127 | buttonLocations[device][payload.coordinates.row][payload.coordinates.column].titleParameters = payload.titleParameters; 128 | buttonLocations[device][payload.coordinates.row][payload.coordinates.column].state = payload.state; 129 | } 130 | 131 | // If buttonLocations were updated for any reason, relay this to the node-streamdeck-util server. 132 | if (['willAppear', 'willDisappear', 'titleParametersDidChange'].includes(event)) { 133 | sendToServerWS('buttonLocationsUpdated', { buttonLocations: buttonLocations }); 134 | } 135 | 136 | // Update global settings if needed, usually for first use. 137 | // This updates local stored settings and sets any defaults if needed, 138 | // then connects to the node-streamdeck-util server. 139 | if (data.event === 'didReceiveGlobalSettings') { 140 | globalSettings.url = data.payload.settings.url || 'ws://localhost:9091'; // Default if setting is empty 141 | globalSettings.key = data.payload.settings.key || 'DEFAULT_KEY'; // Default if setting is empty 142 | globalSettings.connected = false; 143 | sdWS.send(JSON.stringify({ event: 'setGlobalSettings', context: connectSocketData.pluginUUID, payload: globalSettings })); 144 | connectToServerWS(); 145 | } 146 | 147 | // Sends the full raw message to the node-streamdeck-util server. 148 | sendToServerWS('rawSD', data); 149 | }); 150 | } 151 | 152 | // Triggered by the Stream Deck software. 153 | function connectElgatoStreamDeckSocket(port, pluginUUID, registerEvent, info) { 154 | connectSocketData.port = port; 155 | connectSocketData.pluginUUID = pluginUUID; 156 | connectSocketData.registerEvent = registerEvent; 157 | connectSocketData.info = info; 158 | connectToSDWS(); 159 | } 160 | -------------------------------------------------------------------------------- /streamdeck-plugin/com.zoton2.example.sdPlugin/css/caret.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /streamdeck-plugin/com.zoton2.example.sdPlugin/css/check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoton2/node-streamdeck-util/d2f51374eb44640240362131c830e5bf14a1ee1c/streamdeck-plugin/com.zoton2.example.sdPlugin/css/check.png -------------------------------------------------------------------------------- /streamdeck-plugin/com.zoton2.example.sdPlugin/css/check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /streamdeck-plugin/com.zoton2.example.sdPlugin/css/elg_calendar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /streamdeck-plugin/com.zoton2.example.sdPlugin/css/elg_calendar_inv.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /streamdeck-plugin/com.zoton2.example.sdPlugin/css/rcheck.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /streamdeck-plugin/com.zoton2.example.sdPlugin/css/sdpi.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --sdpi-bgcolor: #2D2D2D; 3 | --sdpi-background: #3D3D3D; 4 | --sdpi-color: #d8d8d8; 5 | --sdpi-bordercolor: #3a3a3a; 6 | --sdpi-buttonbordercolor: #969696; 7 | --sdpi-borderradius: 0px; 8 | --sdpi-width: 224px; 9 | --sdpi-fontweight: 600; 10 | --sdpi-letterspacing: -0.25pt; 11 | } 12 | 13 | html { 14 | --sdpi-bgcolor: #2D2D2D; 15 | --sdpi-background: #3D3D3D; 16 | --sdpi-color: #d8d8d8; 17 | --sdpi-bordercolor: #3a3a3a; 18 | --sdpi-buttonbordercolor: #969696; 19 | --sdpi-borderradius: 0px; 20 | --sdpi-width: 224px; 21 | --sdpi-fontweight: 600; 22 | --sdpi-letterspacing: -0.25pt; 23 | height: 100%; 24 | width: 100%; 25 | overflow: hidden; 26 | touch-action:none; 27 | } 28 | 29 | html, body { 30 | --sdpi-bgcolor: #2D2D2D; 31 | --sdpi-background: #3D3D3D; 32 | --sdpi-color: #d8d8d8; 33 | font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 34 | font-size: 9pt; 35 | background-color: var(--sdpi-bgcolor); 36 | color: #9a9a9a; 37 | } 38 | 39 | body { 40 | height: 100%; 41 | padding: 0; 42 | overflow-x: hidden; 43 | overflow-y: auto; 44 | margin: 0; 45 | -webkit-overflow-scrolling: touch; 46 | -webkit-text-size-adjust: 100%; 47 | -webkit-font-smoothing: antialiased; 48 | } 49 | 50 | mark { 51 | background-color: var(--sdpi-bgcolor); 52 | color: var(--sdpi-color); 53 | } 54 | 55 | hr, hr2 { 56 | -webkit-margin-before: 1em; 57 | -webkit-margin-after: 1em; 58 | border-style: none; 59 | background: var(--sdpi-background); 60 | height: 1px; 61 | } 62 | 63 | hr2, 64 | .sdpi-heading { 65 | display: flex; 66 | flex-basis: 100%; 67 | align-items: center; 68 | color: inherit; 69 | font-size: 9pt; 70 | margin: 8px 0px; 71 | } 72 | 73 | .sdpi-heading::before, 74 | .sdpi-heading::after { 75 | content: ""; 76 | flex-grow: 1; 77 | background: var(--sdpi-background); 78 | height: 1px; 79 | font-size: 0px; 80 | line-height: 0px; 81 | margin: 0px 16px; 82 | } 83 | 84 | hr2 { 85 | height: 2px; 86 | } 87 | 88 | hr, hr2 { 89 | margin-left:16px; 90 | margin-right:16px; 91 | } 92 | 93 | .sdpi-item-value, 94 | option, 95 | input, 96 | select, 97 | button { 98 | font-size: 10pt; 99 | font-weight: var(--sdpi-fontweight); 100 | letter-spacing: var(--sdpi-letterspacing); 101 | } 102 | 103 | 104 | 105 | .win .sdpi-item-value, 106 | .win option, 107 | .win input, 108 | .win select, 109 | .win button { 110 | font-size: 11px; 111 | font-style: normal; 112 | letter-spacing: inherit; 113 | font-weight: 100; 114 | } 115 | 116 | .win button { 117 | font-size: 12px; 118 | } 119 | 120 | ::-webkit-progress-value, 121 | meter::-webkit-meter-optimum-value { 122 | border-radius: 2px; 123 | /* background: linear-gradient(#ccf, #99f 20%, #77f 45%, #77f 55%, #cdf); */ 124 | } 125 | 126 | ::-webkit-progress-bar, 127 | meter::-webkit-meter-bar { 128 | border-radius: 3px; 129 | background: var(--sdpi-background); 130 | } 131 | 132 | ::-webkit-progress-bar:active, 133 | meter::-webkit-meter-bar:active { 134 | border-radius: 3px; 135 | background: #222222; 136 | } 137 | ::-webkit-progress-value:active, 138 | meter::-webkit-meter-optimum-value:active { 139 | background: #99f; 140 | } 141 | 142 | progress, 143 | progress.sdpi-item-value { 144 | min-height: 5px !important; 145 | height: 5px; 146 | background-color: #303030; 147 | } 148 | 149 | progress { 150 | margin-top: 8px !important; 151 | margin-bottom: 8px !important; 152 | } 153 | 154 | .full progress, 155 | progress.full { 156 | margin-top: 3px !important; 157 | } 158 | 159 | ::-webkit-progress-inner-element { 160 | background-color: transparent; 161 | } 162 | 163 | 164 | .sdpi-item[type="progress"] { 165 | margin-top: 4px !important; 166 | margin-bottom: 12px; 167 | min-height: 15px; 168 | } 169 | 170 | .sdpi-item-child.full:last-child { 171 | margin-bottom: 4px; 172 | } 173 | 174 | .tabs { 175 | /** 176 | * Setting display to flex makes this container lay 177 | * out its children using flexbox, the exact same 178 | * as in the above "Stepper input" example. 179 | */ 180 | display: flex; 181 | 182 | border-bottom: 1px solid #D7DBDD; 183 | } 184 | 185 | .tab { 186 | cursor: pointer; 187 | padding: 5px 30px; 188 | color: #16a2d7; 189 | font-size: 9pt; 190 | border-bottom: 2px solid transparent; 191 | } 192 | 193 | .tab.is-tab-selected { 194 | border-bottom-color: #4ebbe4; 195 | } 196 | 197 | select { 198 | -webkit-appearance: none; 199 | -moz-appearance: none; 200 | -o-appearance: none; 201 | appearance: none; 202 | background: url(caret.svg) no-repeat 97% center; 203 | } 204 | 205 | label.sdpi-file-label, 206 | input[type="button"], 207 | input[type="submit"], 208 | input[type="reset"], 209 | input[type="file"], 210 | input[type=file]::-webkit-file-upload-button, 211 | button, 212 | select { 213 | color: var(--sdpi-color); 214 | border: 1pt solid #303030; 215 | font-size: 8pt; 216 | background-color: var(--sdpi-background); 217 | border-radius: var(--sdpi-borderradius); 218 | } 219 | 220 | label.sdpi-file-label, 221 | input[type="button"], 222 | input[type="submit"], 223 | input[type="reset"], 224 | input[type="file"], 225 | input[type=file]::-webkit-file-upload-button, 226 | button { 227 | border: 1pt solid var(--sdpi-buttonbordercolor); 228 | border-radius: var(--sdpi-borderradius); 229 | border-color: var(--sdpi-buttonbordercolor); 230 | min-height: 23px !important; 231 | height: 23px !important; 232 | margin-right: 8px; 233 | } 234 | 235 | input[type=number]::-webkit-inner-spin-button, 236 | input[type=number]::-webkit-outer-spin-button { 237 | -webkit-appearance: none; 238 | margin: 0; 239 | } 240 | 241 | input[type="file"] { 242 | border-radius: var(--sdpi-borderradius); 243 | max-width: 220px; 244 | } 245 | 246 | option { 247 | height: 1.5em; 248 | padding: 4px; 249 | } 250 | 251 | /* SDPI */ 252 | 253 | .sdpi-wrapper { 254 | overflow-x: hidden; 255 | height: 100%; 256 | } 257 | 258 | .sdpi-item { 259 | display: flex; 260 | flex-direction: row; 261 | min-height: 32px; 262 | align-items: center; 263 | margin-top: 2px; 264 | max-width: 344px; 265 | /* -webkit-user-drag: none; */ 266 | } 267 | 268 | .sdpi-item:first-child { 269 | margin-top:-1px; 270 | } 271 | 272 | .sdpi-item:last-child { 273 | margin-bottom: 0px; 274 | } 275 | 276 | .sdpi-item > *:not(.sdpi-item-label):not(meter):not(details):not(canvas) { 277 | min-height: 26px; 278 | padding: 0px 4px 0px 4px; 279 | } 280 | 281 | .sdpi-item > *:not(.sdpi-item-label.empty):not(meter) { 282 | min-height: 26px; 283 | padding: 0px 4px 0px 4px; 284 | } 285 | 286 | 287 | .sdpi-item-group { 288 | padding: 0 !important; 289 | } 290 | 291 | meter.sdpi-item-value { 292 | margin-left: 6px; 293 | } 294 | 295 | .sdpi-item[type="group"] { 296 | display: block; 297 | margin-top: 12px; 298 | margin-bottom: 12px; 299 | /* border: 1px solid white; */ 300 | flex-direction: unset; 301 | text-align: left; 302 | } 303 | 304 | .sdpi-item[type="group"] > .sdpi-item-label, 305 | .sdpi-item[type="group"].sdpi-item-label { 306 | width: 96%; 307 | text-align: left; 308 | font-weight: 700; 309 | margin-bottom: 4px; 310 | padding-left: 4px; 311 | } 312 | 313 | dl, 314 | ul, 315 | ol { 316 | -webkit-margin-before: 0px; 317 | -webkit-margin-after: 4px; 318 | -webkit-padding-start: 1em; 319 | max-height: 90px; 320 | overflow-y: scroll; 321 | cursor: pointer; 322 | user-select: none; 323 | } 324 | 325 | table.sdpi-item-value, 326 | dl.sdpi-item-value, 327 | ul.sdpi-item-value, 328 | ol.sdpi-item-value { 329 | -webkit-margin-before: 4px; 330 | -webkit-margin-after: 8px; 331 | -webkit-padding-start: 1em; 332 | width: var(--sdpi-width); 333 | text-align: center; 334 | } 335 | 336 | table > caption { 337 | margin: 2px; 338 | } 339 | 340 | .list, 341 | .sdpi-item[type="list"] { 342 | align-items: baseline; 343 | } 344 | 345 | .sdpi-item-label { 346 | text-align: right; 347 | flex: none; 348 | width: 94px; 349 | padding-right: 4px; 350 | font-weight: 600; 351 | -webkit-user-select: none; 352 | } 353 | 354 | .win .sdpi-item-label, 355 | .sdpi-item-label > small{ 356 | font-weight: normal; 357 | } 358 | 359 | .sdpi-item-label:after { 360 | content: ": "; 361 | } 362 | 363 | .sdpi-item-label.empty:after { 364 | content: ""; 365 | } 366 | 367 | .sdpi-test, 368 | .sdpi-item-value { 369 | flex: 1 0 0; 370 | /* flex-grow: 1; 371 | flex-shrink: 0; */ 372 | margin-right: 14px; 373 | margin-left: 4px; 374 | justify-content: space-evenly; 375 | } 376 | 377 | canvas.sdpi-item-value { 378 | max-width: 144px; 379 | max-height: 144px; 380 | width: 144px; 381 | height: 144px; 382 | margin: 0 auto; 383 | cursor: pointer; 384 | } 385 | 386 | input.sdpi-item-value { 387 | margin-left: 5px; 388 | } 389 | 390 | .sdpi-item-value button, 391 | button.sdpi-item-value { 392 | margin-left: 6px; 393 | margin-right: 14px; 394 | } 395 | 396 | .sdpi-item-value.range { 397 | margin-left: 0px; 398 | } 399 | 400 | table, 401 | dl.sdpi-item-value, 402 | ul.sdpi-item-value, 403 | ol.sdpi-item-value, 404 | .sdpi-item-value > dl, 405 | .sdpi-item-value > ul, 406 | .sdpi-item-value > ol 407 | { 408 | list-style-type: none; 409 | list-style-position: outside; 410 | margin-left: -4px; 411 | margin-right: -4px; 412 | padding: 4px; 413 | border: 1px solid var(--sdpi-bordercolor); 414 | } 415 | 416 | dl.sdpi-item-value, 417 | ul.sdpi-item-value, 418 | ol.sdpi-item-value, 419 | .sdpi-item-value > ol { 420 | list-style-type: none; 421 | list-style-position: inside; 422 | margin-left: 5px; 423 | margin-right: 12px; 424 | padding: 4px !important; 425 | display: flex; 426 | flex-direction: column; 427 | } 428 | 429 | .two-items li { 430 | display: flex; 431 | } 432 | .two-items li > *:first-child { 433 | flex: 0 0 50%; 434 | text-align: left; 435 | } 436 | .two-items.thirtyseventy li > *:first-child { 437 | flex: 0 0 30%; 438 | } 439 | 440 | ol.sdpi-item-value, 441 | .sdpi-item-value > ol[listtype="none"] { 442 | list-style-type: none; 443 | } 444 | ol.sdpi-item-value[type="decimal"], 445 | .sdpi-item-value > ol[type="decimal"] { 446 | list-style-type: decimal; 447 | } 448 | 449 | ol.sdpi-item-value[type="decimal-leading-zero"], 450 | .sdpi-item-value > ol[type="decimal-leading-zero"] { 451 | list-style-type: decimal-leading-zero; 452 | } 453 | 454 | ol.sdpi-item-value[type="lower-alpha"], 455 | .sdpi-item-value > ol[type="lower-alpha"] { 456 | list-style-type: lower-alpha; 457 | } 458 | 459 | ol.sdpi-item-value[type="upper-alpha"], 460 | .sdpi-item-value > ol[type="upper-alpha"] { 461 | list-style-type: upper-alpha; 462 | } 463 | 464 | ol.sdpi-item-value[type="upper-roman"], 465 | .sdpi-item-value > ol[type="upper-roman"] { 466 | list-style-type: upper-roman; 467 | } 468 | 469 | ol.sdpi-item-value[type="lower-roman"], 470 | .sdpi-item-value > ol[type="lower-roman"] { 471 | list-style-type: upper-roman; 472 | } 473 | 474 | tr:nth-child(even), 475 | .sdpi-item-value > ul > li:nth-child(even), 476 | .sdpi-item-value > ol > li:nth-child(even), 477 | li:nth-child(even) { 478 | background-color: rgba(0,0,0,.2) 479 | } 480 | 481 | td:hover, 482 | .sdpi-item-value > ul > li:hover:nth-child(even), 483 | .sdpi-item-value > ol > li:hover:nth-child(even), 484 | li:hover:nth-child(even), 485 | li:hover { 486 | background-color: rgba(255,255,255,.1); 487 | } 488 | 489 | td.selected, 490 | td.selected:hover, 491 | li.selected:hover, 492 | li.selected { 493 | color: white; 494 | background-color: #77f; 495 | } 496 | 497 | tr { 498 | border: 1px solid var(--sdpi-bordercolor); 499 | } 500 | 501 | td { 502 | border-right: 1px solid var(--sdpi-bordercolor); 503 | -webkit-user-select: none; 504 | } 505 | 506 | tr:last-child, 507 | td:last-child { 508 | border: none; 509 | } 510 | 511 | .sdpi-item-value.select, 512 | .sdpi-item-value > select { 513 | margin-right: 13px; 514 | margin-left: 4px; 515 | } 516 | 517 | .sdpi-item-child, 518 | .sdpi-item-group > .sdpi-item > input[type="color"] { 519 | margin-top: 0.4em; 520 | margin-right: 4px; 521 | } 522 | 523 | .full, 524 | .full *, 525 | .sdpi-item-value.full, 526 | .sdpi-item-child > full > *, 527 | .sdpi-item-child.full, 528 | .sdpi-item-child.full > *, 529 | .full > .sdpi-item-child, 530 | .full > .sdpi-item-child > *{ 531 | display: flex; 532 | flex: 1 1 0; 533 | margin-bottom: 4px; 534 | margin-left: 0px; 535 | width: 100%; 536 | 537 | justify-content: space-evenly; 538 | } 539 | 540 | .sdpi-item-group > .sdpi-item > input[type="color"] { 541 | margin-top: 0px; 542 | } 543 | 544 | ::-webkit-calendar-picker-indicator:focus, 545 | input[type=file]::-webkit-file-upload-button:focus, 546 | button:focus, 547 | textarea:focus, 548 | input:focus, 549 | select:focus, 550 | option:focus, 551 | details:focus, 552 | summary:focus, 553 | .custom-select select { 554 | outline: none; 555 | } 556 | 557 | summary { 558 | cursor: default; 559 | -webkit-user-select: none; 560 | } 561 | 562 | .pointer, 563 | summary .pointer { 564 | cursor: pointer; 565 | } 566 | 567 | details * { 568 | font-size: 12px; 569 | font-weight: normal; 570 | } 571 | 572 | details.message { 573 | padding: 4px 18px 4px 12px; 574 | } 575 | 576 | details.message summary { 577 | font-size: 10pt; 578 | font-weight: 600; 579 | min-height: 18px; 580 | } 581 | 582 | details.message:first-child { 583 | margin-top: 4px; 584 | margin-left: 0; 585 | padding-left: 102px; 586 | } 587 | 588 | details.message h1 { 589 | text-align: left; 590 | } 591 | 592 | .message > summary::-webkit-details-marker { 593 | display: none; 594 | } 595 | 596 | .info20, 597 | .question, 598 | .caution, 599 | .info { 600 | background-repeat: no-repeat; 601 | background-position: 72px center; 602 | } 603 | 604 | .info20 { 605 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath fill='%23999' d='M10,20 C4.4771525,20 0,15.5228475 0,10 C0,4.4771525 4.4771525,0 10,0 C15.5228475,0 20,4.4771525 20,10 C20,15.5228475 15.5228475,20 10,20 Z M10,8 C8.8954305,8 8,8.84275812 8,9.88235294 L8,16.1176471 C8,17.1572419 8.8954305,18 10,18 C11.1045695,18 12,17.1572419 12,16.1176471 L12,9.88235294 C12,8.84275812 11.1045695,8 10,8 Z M10,3 C8.8954305,3 8,3.88165465 8,4.96923077 L8,5.03076923 C8,6.11834535 8.8954305,7 10,7 C11.1045695,7 12,6.11834535 12,5.03076923 L12,4.96923077 C12,3.88165465 11.1045695,3 10,3 Z'/%3E%3C/svg%3E%0A"); 606 | } 607 | 608 | .info { 609 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath fill='%23999' d='M10,18 C5.581722,18 2,14.418278 2,10 C2,5.581722 5.581722,2 10,2 C14.418278,2 18,5.581722 18,10 C18,14.418278 14.418278,18 10,18 Z M10,8 C9.44771525,8 9,8.42137906 9,8.94117647 L9,14.0588235 C9,14.5786209 9.44771525,15 10,15 C10.5522847,15 11,14.5786209 11,14.0588235 L11,8.94117647 C11,8.42137906 10.5522847,8 10,8 Z M10,5 C9.44771525,5 9,5.44082732 9,5.98461538 L9,6.01538462 C9,6.55917268 9.44771525,7 10,7 C10.5522847,7 11,6.55917268 11,6.01538462 L11,5.98461538 C11,5.44082732 10.5522847,5 10,5 Z'/%3E%3C/svg%3E%0A"); 610 | } 611 | 612 | .info2 { 613 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='15' height='15' viewBox='0 0 15 15'%3E%3Cpath fill='%23999' d='M7.5,15 C3.35786438,15 0,11.6421356 0,7.5 C0,3.35786438 3.35786438,0 7.5,0 C11.6421356,0 15,3.35786438 15,7.5 C15,11.6421356 11.6421356,15 7.5,15 Z M7.5,2 C6.67157287,2 6,2.66124098 6,3.47692307 L6,3.52307693 C6,4.33875902 6.67157287,5 7.5,5 C8.32842705,5 9,4.33875902 9,3.52307693 L9,3.47692307 C9,2.66124098 8.32842705,2 7.5,2 Z M5,6 L5,7.02155172 L6,7 L6,12 L5,12.0076778 L5,13 L10,13 L10,12 L9,12.0076778 L9,6 L5,6 Z'/%3E%3C/svg%3E%0A"); 614 | } 615 | 616 | .sdpi-more-info { 617 | background-image: linear-gradient(to right, #00000000 0%,#00000040 80%), url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 16 16'%3E%3Cpolygon fill='%23999' points='4 7 8 7 8 5 12 8 8 11 8 9 4 9'/%3E%3C/svg%3E%0A"); 618 | } 619 | .caution { 620 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath fill='%23999' fill-rule='evenodd' d='M9.03952676,0.746646542 C9.57068894,-0.245797319 10.4285735,-0.25196227 10.9630352,0.746646542 L19.7705903,17.2030214 C20.3017525,18.1954653 19.8777595,19 18.8371387,19 L1.16542323,19 C0.118729947,19 -0.302490098,18.2016302 0.231971607,17.2030214 L9.03952676,0.746646542 Z M10,2.25584053 L1.9601405,17.3478261 L18.04099,17.3478261 L10,2.25584053 Z M10,5.9375 C10.531043,5.9375 10.9615385,6.37373537 10.9615385,6.91185897 L10.9615385,11.6923077 C10.9615385,12.2304313 10.531043,12.6666667 10,12.6666667 C9.46895697,12.6666667 9.03846154,12.2304313 9.03846154,11.6923077 L9.03846154,6.91185897 C9.03846154,6.37373537 9.46895697,5.9375 10,5.9375 Z M10,13.4583333 C10.6372516,13.4583333 11.1538462,13.9818158 11.1538462,14.6275641 L11.1538462,14.6641026 C11.1538462,15.3098509 10.6372516,15.8333333 10,15.8333333 C9.36274837,15.8333333 8.84615385,15.3098509 8.84615385,14.6641026 L8.84615385,14.6275641 C8.84615385,13.9818158 9.36274837,13.4583333 10,13.4583333 Z'/%3E%3C/svg%3E%0A"); 621 | } 622 | 623 | .question { 624 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cpath fill='%23999' d='M10,18 C5.581722,18 2,14.418278 2,10 C2,5.581722 5.581722,2 10,2 C14.418278,2 18,5.581722 18,10 C18,14.418278 14.418278,18 10,18 Z M6.77783203,7.65332031 C6.77783203,7.84798274 6.85929281,8.02888914 7.0222168,8.19604492 C7.18514079,8.36320071 7.38508996,8.44677734 7.62207031,8.44677734 C8.02409055,8.44677734 8.29703704,8.20768468 8.44091797,7.72949219 C8.59326248,7.27245865 8.77945854,6.92651485 8.99951172,6.69165039 C9.2195649,6.45678594 9.56233491,6.33935547 10.027832,6.33935547 C10.4256205,6.33935547 10.7006836,6.37695313 11.0021973,6.68847656 C11.652832,7.53271484 10.942627,8.472229 10.3750916,9.1321106 C9.80755615,9.79199219 8.29492188,11.9897461 10.027832,12.1347656 C10.4498423,12.1700818 10.7027991,11.9147157 10.7832031,11.4746094 C11.0021973,9.59857178 13.1254883,8.82415771 13.1254883,7.53271484 C13.1254883,7.07568131 12.9974785,6.65250846 12.7414551,6.26318359 C12.4854317,5.87385873 12.1225609,5.56600048 11.652832,5.33959961 C11.1831031,5.11319874 10.6414419,5 10.027832,5 C9.36767248,5 8.79004154,5.13541531 8.29492187,5.40625 C7.79980221,5.67708469 7.42317837,6.01879677 7.16503906,6.43139648 C6.90689975,6.8439962 6.77783203,7.25130007 6.77783203,7.65332031 Z M10.0099668,15 C10.2713191,15 10.5016601,14.9108147 10.7009967,14.7324415 C10.9003332,14.5540682 11,14.3088087 11,13.9966555 C11,13.7157177 10.9047629,13.4793767 10.7142857,13.2876254 C10.5238086,13.0958742 10.2890379,13 10.0099668,13 C9.72646591,13 9.48726565,13.0958742 9.2923588,13.2876254 C9.09745196,13.4793767 9,13.7157177 9,13.9966555 C9,14.313268 9.10077419,14.5596424 9.30232558,14.735786 C9.50387698,14.9119295 9.73975502,15 10.0099668,15 Z'/%3E%3C/svg%3E%0A"); 625 | } 626 | 627 | 628 | .sdpi-more-info { 629 | position: fixed; 630 | left: 0px; 631 | right: 0px; 632 | bottom: 0px; 633 | min-height:16px; 634 | padding-right: 16px; 635 | text-align: right; 636 | -webkit-touch-callout: none; 637 | cursor: pointer; 638 | user-select: none; 639 | background-position: right center; 640 | background-repeat: no-repeat; 641 | border-radius: var(--sdpi-borderradius); 642 | text-decoration: none; 643 | color: var(--sdpi-color); 644 | } 645 | 646 | .sdpi-bottom-bar { 647 | display: flex; 648 | align-self: right; 649 | margin-left: auto; 650 | position: fixed; 651 | right: 17px; 652 | bottom: 0px; 653 | user-select: none; 654 | } 655 | 656 | .sdpi-bottom-bar.right { 657 | right: 0px; 658 | } 659 | 660 | .sdpi-bottom-bar button { 661 | min-height: 20px !important; 662 | height: 20px !important; 663 | } 664 | 665 | 666 | .sdpi-more-info-button { 667 | display: flex; 668 | align-self: right; 669 | margin-left: auto; 670 | position: fixed; 671 | right: 17px; 672 | bottom: 0px; 673 | user-select: none; 674 | } 675 | 676 | details a { 677 | background-position: right !important; 678 | min-height: 24px; 679 | display: inline-block; 680 | line-height: 24px; 681 | padding-right: 28px; 682 | } 683 | 684 | 685 | input:not([type="range"]), 686 | textarea { 687 | -webkit-appearance: none; 688 | background: var(--sdpi-background); 689 | color: var(--sdpi-color); 690 | font-weight: normal; 691 | font-size: 9pt; 692 | border: none; 693 | margin-top: 2px; 694 | margin-bottom: 2px; 695 | min-width: 219px; 696 | } 697 | 698 | textarea + label { 699 | display: flex; 700 | justify-content: flex-end 701 | } 702 | input[type="radio"], 703 | input[type="checkbox"] { 704 | display: none; 705 | } 706 | input[type="radio"] + label, 707 | input[type="checkbox"] + label { 708 | font-size: 9pt; 709 | color: var(--sdpi-color); 710 | font-weight: normal; 711 | margin-right: 8px; 712 | -webkit-user-select: none; 713 | } 714 | 715 | input[type="radio"] + label:after, 716 | input[type="checkbox"] + label:after { 717 | content: " " !important; 718 | } 719 | 720 | .sdpi-item[type="radio"] > .sdpi-item-value, 721 | .sdpi-item[type="checkbox"] > .sdpi-item-value { 722 | padding-top: 2px; 723 | } 724 | 725 | .sdpi-item[type="checkbox"] > .sdpi-item-value > * { 726 | margin-top: 4px; 727 | } 728 | 729 | .sdpi-item[type="checkbox"] .sdpi-item-child, 730 | .sdpi-item[type="radio"] .sdpi-item-child { 731 | display: inline-block; 732 | } 733 | 734 | .sdpi-item[type="range"] .sdpi-item-value, 735 | .sdpi-item[type="meter"] .sdpi-item-child, 736 | .sdpi-item[type="progress"] .sdpi-item-child { 737 | display: flex; 738 | } 739 | 740 | .sdpi-item[type="range"] .sdpi-item-value { 741 | min-height: 26px; 742 | } 743 | 744 | .sdpi-item[type="range"] .sdpi-item-value span, 745 | .sdpi-item[type="meter"] .sdpi-item-child span, 746 | .sdpi-item[type="progress"] .sdpi-item-child span { 747 | margin-top: -2px; 748 | min-width: 8px; 749 | text-align: right; 750 | user-select: none; 751 | cursor: pointer; 752 | -webkit-user-select: none; 753 | user-select: none; 754 | } 755 | 756 | .sdpi-item[type="range"] .sdpi-item-value span { 757 | margin-top: 7px; 758 | text-align: right; 759 | } 760 | 761 | span + input[type="range"] { 762 | display: flex; 763 | max-width: 168px; 764 | 765 | } 766 | 767 | .sdpi-item[type="range"] .sdpi-item-value span:first-child, 768 | .sdpi-item[type="meter"] .sdpi-item-child span:first-child, 769 | .sdpi-item[type="progress"] .sdpi-item-child span:first-child { 770 | margin-right: 4px; 771 | } 772 | 773 | .sdpi-item[type="range"] .sdpi-item-value span:last-child, 774 | .sdpi-item[type="meter"] .sdpi-item-child span:last-child, 775 | .sdpi-item[type="progress"] .sdpi-item-child span:last-child { 776 | margin-left: 4px; 777 | } 778 | 779 | .reverse { 780 | transform: rotate(180deg); 781 | } 782 | 783 | .sdpi-item[type="meter"] .sdpi-item-child meter + span:last-child { 784 | margin-left: -10px; 785 | } 786 | 787 | .sdpi-item[type="progress"] .sdpi-item-child meter + span:last-child { 788 | margin-left: -14px; 789 | } 790 | 791 | .sdpi-item[type="radio"] > .sdpi-item-value > * { 792 | margin-top: 2px; 793 | } 794 | 795 | details { 796 | padding: 8px 18px 8px 12px; 797 | min-width: 86px; 798 | } 799 | 800 | details > h4 { 801 | border-bottom: 1px solid var(--sdpi-bordercolor); 802 | } 803 | 804 | legend { 805 | display: none; 806 | } 807 | .sdpi-item-value > textarea { 808 | padding: 0px; 809 | width: 219px; 810 | margin-left: 1px; 811 | margin-top: 3px; 812 | padding: 4px; 813 | } 814 | 815 | input[type="radio"] + label span, 816 | input[type="checkbox"] + label span { 817 | display: inline-block; 818 | width: 16px; 819 | height: 16px; 820 | margin: 2px 4px 2px 0; 821 | border-radius: 3px; 822 | vertical-align: middle; 823 | background: var(--sdpi-background); 824 | cursor: pointer; 825 | border: 1px solid rgb(0,0,0,.2); 826 | } 827 | 828 | input[type="radio"] + label span { 829 | border-radius: 100%; 830 | } 831 | 832 | input[type="radio"]:checked + label span, 833 | input[type="checkbox"]:checked + label span { 834 | background-color: #77f; 835 | background-image: url(check.svg); 836 | background-repeat: no-repeat; 837 | background-position: center center; 838 | border: 1px solid rgb(0,0,0,.4); 839 | } 840 | 841 | input[type="radio"]:active:checked + label span, 842 | input[type="radio"]:active + label span, 843 | input[type="checkbox"]:active:checked + label span, 844 | input[type="checkbox"]:active + label span { 845 | background-color: #303030; 846 | } 847 | 848 | input[type="radio"]:checked + label span { 849 | background-image: url(rcheck.svg); 850 | } 851 | 852 | input[type="range"] { 853 | width: var(--sdpi-width); 854 | height: 30px; 855 | overflow: hidden; 856 | cursor: pointer; 857 | background: transparent !important; 858 | } 859 | 860 | .sdpi-item > input[type="range"] { 861 | margin-left: 2px; 862 | max-width: var(--sdpi-width); 863 | width: var(--sdpi-width); 864 | padding: 0px; 865 | margin-top: 2px; 866 | } 867 | 868 | /* 869 | input[type="range"], 870 | input[type="range"]::-webkit-slider-runnable-track, 871 | input[type="range"]::-webkit-slider-thumb { 872 | -webkit-appearance: none; 873 | } 874 | */ 875 | 876 | input[type="range"]::-webkit-slider-runnable-track { 877 | height: 5px; 878 | background: #979797; 879 | border-radius: 3px; 880 | padding:0px !important; 881 | border: 1px solid var(--sdpi-background); 882 | } 883 | 884 | input[type="range"]::-webkit-slider-thumb { 885 | position: relative; 886 | background-color: var(--sdpi-color); 887 | width: 12px; 888 | height: 12px; 889 | border-radius: 20px; 890 | margin-top: -5px; 891 | border: none; 892 | } 893 | input[type="range" i]{ 894 | margin: 0; 895 | } 896 | 897 | input[type="range"]::-webkit-slider-thumb::before { 898 | position: absolute; 899 | content: ""; 900 | height: 5px; /* equal to height of runnable track or 1 less */ 901 | width: 500px; /* make this bigger than the widest range input element */ 902 | left: -502px; /* this should be -2px - width */ 903 | top: 8px; /* don't change this */ 904 | background: #77f; 905 | } 906 | 907 | input[type="color"] { 908 | min-width: 32px; 909 | min-height: 32px; 910 | width: 32px; 911 | height: 32px; 912 | padding: 0; 913 | background-color: var(--sdpi-bgcolor); 914 | flex: none; 915 | } 916 | 917 | ::-webkit-color-swatch { 918 | min-width: 24px; 919 | } 920 | 921 | textarea { 922 | height: 3em; 923 | word-break: break-word; 924 | line-height: 1.5em; 925 | } 926 | 927 | .textarea { 928 | padding: 0px !important; 929 | } 930 | 931 | textarea { 932 | width: 219px; /*98%;*/ 933 | height: 96%; 934 | min-height: 6em; 935 | resize: none; 936 | border-radius: var(--sdpi-borderradius); 937 | } 938 | 939 | /* CAROUSEL */ 940 | 941 | .sdpi-item[type="carousel"]{ 942 | 943 | } 944 | 945 | .sdpi-item.card-carousel-wrapper, 946 | .sdpi-item > .card-carousel-wrapper { 947 | padding: 0; 948 | } 949 | 950 | 951 | .card-carousel-wrapper { 952 | display: flex; 953 | align-items: center; 954 | justify-content: center; 955 | margin: 12px auto; 956 | color: #666a73; 957 | } 958 | 959 | .card-carousel { 960 | display: flex; 961 | justify-content: center; 962 | width: 278px; 963 | } 964 | .card-carousel--overflow-container { 965 | overflow: hidden; 966 | } 967 | .card-carousel--nav__left, 968 | .card-carousel--nav__right { 969 | /* display: inline-block; */ 970 | width: 12px; 971 | height: 12px; 972 | border-top: 2px solid #42b883; 973 | border-right: 2px solid #42b883; 974 | cursor: pointer; 975 | margin: 0 4px; 976 | transition: transform 150ms linear; 977 | } 978 | .card-carousel--nav__left[disabled], 979 | .card-carousel--nav__right[disabled] { 980 | opacity: 0.2; 981 | border-color: black; 982 | } 983 | .card-carousel--nav__left { 984 | transform: rotate(-135deg); 985 | } 986 | .card-carousel--nav__left:active { 987 | transform: rotate(-135deg) scale(0.85); 988 | } 989 | .card-carousel--nav__right { 990 | transform: rotate(45deg); 991 | } 992 | .card-carousel--nav__right:active { 993 | transform: rotate(45deg) scale(0.85); 994 | } 995 | .card-carousel-cards { 996 | display: flex; 997 | transition: transform 150ms ease-out; 998 | transform: translatex(0px); 999 | } 1000 | .card-carousel-cards .card-carousel--card { 1001 | margin: 0 5px; 1002 | cursor: pointer; 1003 | /* box-shadow: 0 4px 15px 0 rgba(40, 44, 53, 0.06), 0 2px 2px 0 rgba(40, 44, 53, 0.08); */ 1004 | background-color: #fff; 1005 | border-radius: 4px; 1006 | z-index: 3; 1007 | } 1008 | .xxcard-carousel-cards .card-carousel--card:first-child { 1009 | margin-left: 0; 1010 | } 1011 | .xxcard-carousel-cards .card-carousel--card:last-child { 1012 | margin-right: 0; 1013 | } 1014 | .card-carousel-cards .card-carousel--card img { 1015 | vertical-align: bottom; 1016 | border-top-left-radius: 4px; 1017 | border-top-right-radius: 4px; 1018 | transition: opacity 150ms linear; 1019 | width: 60px; 1020 | } 1021 | .card-carousel-cards .card-carousel--card img:hover { 1022 | opacity: 0.5; 1023 | } 1024 | .card-carousel-cards .card-carousel--card--footer { 1025 | border-top: 0; 1026 | max-width: 80px; 1027 | overflow: hidden; 1028 | display: flex; 1029 | height: 100%; 1030 | flex-direction: column; 1031 | } 1032 | .card-carousel-cards .card-carousel--card--footer p { 1033 | padding: 3px 0; 1034 | margin: 0; 1035 | margin-bottom: 2px; 1036 | font-size: 15px; 1037 | font-weight: 500; 1038 | color: #2c3e50; 1039 | } 1040 | .card-carousel-cards .card-carousel--card--footer p:nth-of-type(2) { 1041 | font-size: 12px; 1042 | font-weight: 300; 1043 | padding: 6px; 1044 | color: #666a73; 1045 | } 1046 | 1047 | 1048 | h1 { 1049 | font-size: 1.3em; 1050 | font-weight: 500; 1051 | text-align: center; 1052 | margin-bottom: 12px; 1053 | } 1054 | 1055 | ::-webkit-datetime-edit { 1056 | font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 1057 | background: url(elg_calendar_inv.svg) no-repeat left center; 1058 | padding-right: 1em; 1059 | padding-left: 25px; 1060 | background-position: 4px 0px; 1061 | } 1062 | ::-webkit-datetime-edit-fields-wrapper { 1063 | 1064 | } 1065 | ::-webkit-datetime-edit-text { padding: 0 0.3em; } 1066 | ::-webkit-datetime-edit-month-field { } 1067 | ::-webkit-datetime-edit-day-field {} 1068 | ::-webkit-datetime-edit-year-field {} 1069 | ::-webkit-inner-spin-button { 1070 | 1071 | /* display: none; */ 1072 | } 1073 | ::-webkit-calendar-picker-indicator { 1074 | background: transparent; 1075 | font-size: 17px; 1076 | } 1077 | 1078 | ::-webkit-calendar-picker-indicator:focus { 1079 | background-color: rgba(0,0,0,0.2); 1080 | } 1081 | 1082 | input[type="date"] { 1083 | -webkit-align-items: center; 1084 | display: -webkit-inline-flex; 1085 | font-family: monospace; 1086 | overflow: hidden; 1087 | padding: 0; 1088 | -webkit-padding-start: 1px; 1089 | } 1090 | 1091 | input::-webkit-datetime-edit { 1092 | -webkit-flex: 1; 1093 | -webkit-user-modify: read-only !important; 1094 | display: inline-block; 1095 | min-width: 0; 1096 | overflow: hidden; 1097 | } 1098 | 1099 | /* 1100 | input::-webkit-datetime-edit-fields-wrapper { 1101 | -webkit-user-modify: read-only !important; 1102 | display: inline-block; 1103 | padding: 1px 0; 1104 | white-space: pre; 1105 | 1106 | } 1107 | */ 1108 | 1109 | /* 1110 | input[type="date"] { 1111 | background-color: red; 1112 | outline: none; 1113 | } 1114 | 1115 | input[type="date"]::-webkit-clear-button { 1116 | font-size: 18px; 1117 | height: 30px; 1118 | position: relative; 1119 | } 1120 | 1121 | input[type="date"]::-webkit-inner-spin-button { 1122 | height: 28px; 1123 | } 1124 | 1125 | input[type="date"]::-webkit-calendar-picker-indicator { 1126 | font-size: 15px; 1127 | } */ 1128 | 1129 | input[type="file"] { 1130 | opacity: 0; 1131 | display: none; 1132 | } 1133 | 1134 | .sdpi-item > input[type="file"] { 1135 | opacity: 1; 1136 | display: flex; 1137 | } 1138 | 1139 | input[type="file"] + span { 1140 | display: flex; 1141 | flex: 0 1 auto; 1142 | background-color: #0000ff50; 1143 | } 1144 | 1145 | label.sdpi-file-label { 1146 | cursor: pointer; 1147 | user-select: none; 1148 | display: inline-block; 1149 | min-height: 21px !important; 1150 | height: 21px !important; 1151 | line-height: 20px; 1152 | padding: 0px 4px; 1153 | margin: auto; 1154 | margin-right: 0px; 1155 | float:right; 1156 | } 1157 | 1158 | .sdpi-file-label > label:active, 1159 | .sdpi-file-label.file:active, 1160 | label.sdpi-file-label:active, 1161 | label.sdpi-file-info:active, 1162 | input[type="file"]::-webkit-file-upload-button:active, 1163 | button:active { 1164 | background-color: var(--sdpi-color); 1165 | color:#303030; 1166 | } 1167 | 1168 | input:required:invalid, input:focus:invalid { 1169 | background: var(--sdpi-background) url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5IiBoZWlnaHQ9IjkiIHZpZXdCb3g9IjAgMCA5IDkiPgogICAgPHBhdGggZmlsbD0iI0Q4RDhEOCIgZD0iTTQuNSwwIEM2Ljk4NTI4MTM3LC00LjU2NTM4NzgyZS0xNiA5LDIuMDE0NzE4NjMgOSw0LjUgQzksNi45ODUyODEzNyA2Ljk4NTI4MTM3LDkgNC41LDkgQzIuMDE0NzE4NjMsOSAzLjA0MzU5MTg4ZS0xNiw2Ljk4NTI4MTM3IDAsNC41IEMtMy4wNDM1OTE4OGUtMTYsMi4wMTQ3MTg2MyAyLjAxNDcxODYzLDQuNTY1Mzg3ODJlLTE2IDQuNSwwIFogTTQsMSBMNCw2IEw1LDYgTDUsMSBMNCwxIFogTTQuNSw4IEM0Ljc3NjE0MjM3LDggNSw3Ljc3NjE0MjM3IDUsNy41IEM1LDcuMjIzODU3NjMgNC43NzYxNDIzNyw3IDQuNSw3IEM0LjIyMzg1NzYzLDcgNCw3LjIyMzg1NzYzIDQsNy41IEM0LDcuNzc2MTQyMzcgNC4yMjM4NTc2Myw4IDQuNSw4IFoiLz4KICA8L3N2Zz4) no-repeat 98% center; 1170 | } 1171 | 1172 | input:required:valid { 1173 | background: var(--sdpi-background) url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5IiBoZWlnaHQ9IjkiIHZpZXdCb3g9IjAgMCA5IDkiPjxwb2x5Z29uIGZpbGw9IiNEOEQ4RDgiIHBvaW50cz0iNS4yIDEgNi4yIDEgNi4yIDcgMy4yIDcgMy4yIDYgNS4yIDYiIHRyYW5zZm9ybT0icm90YXRlKDQwIDQuNjc3IDQpIi8+PC9zdmc+) no-repeat 98% center; 1174 | } 1175 | 1176 | .tooltip, 1177 | :tooltip, 1178 | :title { 1179 | color: yellow; 1180 | } 1181 | /* 1182 | [title]:hover { 1183 | display: flex; 1184 | align-items: center; 1185 | justify-content: center; 1186 | } 1187 | 1188 | [title]:hover::after { 1189 | content: ''; 1190 | position: absolute; 1191 | bottom: -1000px; 1192 | left: 8px; 1193 | display: none; 1194 | color: #fff; 1195 | border: 8px solid transparent; 1196 | border-bottom: 8px solid #000; 1197 | } 1198 | 1199 | [title]:hover::before { 1200 | content: attr(title); 1201 | display: flex; 1202 | justify-content: center; 1203 | align-self: center; 1204 | padding: 6px 12px; 1205 | border-radius: 5px; 1206 | background: rgba(0,0,0,0.8); 1207 | color: var(--sdpi-color); 1208 | font-size: 9pt; 1209 | font-family: sans-serif; 1210 | opacity: 1; 1211 | position: absolute; 1212 | height: auto; 1213 | 1214 | text-align: center; 1215 | bottom: 2px; 1216 | z-index: 100; 1217 | box-shadow: 0px 3px 6px rgba(0, 0, 0, .5); 1218 | } 1219 | */ 1220 | 1221 | .sdpi-item-group.file { 1222 | width: 232px; 1223 | display: flex; 1224 | align-items: center; 1225 | } 1226 | 1227 | .sdpi-file-info { 1228 | overflow-wrap: break-word; 1229 | word-wrap: break-word; 1230 | hyphens: auto; 1231 | 1232 | min-width: 132px; 1233 | max-width: 144px; 1234 | max-height: 32px; 1235 | margin-top: 0px; 1236 | margin-left: 5px; 1237 | display: inline-block; 1238 | overflow: hidden; 1239 | padding: 6px 4px; 1240 | background-color: var(--sdpi-background); 1241 | } 1242 | 1243 | 1244 | ::-webkit-scrollbar { 1245 | width: 8px; 1246 | } 1247 | 1248 | ::-webkit-scrollbar-track { 1249 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); 1250 | } 1251 | 1252 | ::-webkit-scrollbar-thumb { 1253 | background-color: #999999; 1254 | outline: 1px solid slategrey; 1255 | border-radius: 8px; 1256 | } 1257 | 1258 | a { 1259 | color: #7397d2; 1260 | } 1261 | 1262 | .testcontainer { 1263 | display: flex; 1264 | background-color: #0000ff20; 1265 | max-width: 400px; 1266 | height: 200px; 1267 | align-content: space-evenly; 1268 | } 1269 | 1270 | input[type=range] { 1271 | /* background-color: green; */ 1272 | height:6px; 1273 | margin-top: 12px; 1274 | z-index: 0; 1275 | overflow: visible; 1276 | } 1277 | 1278 | /* 1279 | input[type="range"]::-webkit-slider-thumb { 1280 | -webkit-appearance: none; 1281 | background-color: var(--sdpi-color); 1282 | width: 12px; 1283 | height: 12px; 1284 | border-radius: 20px; 1285 | margin-top: -6px; 1286 | border: none; 1287 | } */ 1288 | 1289 | :-webkit-slider-thumb { 1290 | -webkit-appearance: none; 1291 | background-color: var(--sdpi-color); 1292 | width: 16px; 1293 | height: 16px; 1294 | border-radius: 20px; 1295 | margin-top: -6px; 1296 | border: 1px solid #999999; 1297 | } 1298 | 1299 | .sdpi-item[type="range"] .sdpi-item-group { 1300 | display: flex; 1301 | flex-direction: column; 1302 | } 1303 | 1304 | .xxsdpi-item[type="range"] .sdpi-item-group input { 1305 | max-width: 204px; 1306 | } 1307 | 1308 | .sdpi-item[type="range"] .sdpi-item-group span { 1309 | margin-left: 0px !important; 1310 | } 1311 | 1312 | .sdpi-item[type="range"] .sdpi-item-group > .sdpi-item-child { 1313 | display: flex; 1314 | flex-direction: row; 1315 | } 1316 | 1317 | .rangeLabel { 1318 | position:absolute; 1319 | font-weight:normal; 1320 | margin-top:22px; 1321 | } 1322 | 1323 | :disabled { 1324 | color: #993333; 1325 | } 1326 | 1327 | select, 1328 | select option { 1329 | color: var(--sdpi-color); 1330 | } 1331 | 1332 | select.disabled, 1333 | select option:disabled { 1334 | color: #fd9494; 1335 | font-style: italic; 1336 | } 1337 | 1338 | .runningAppsContainer { 1339 | display: none; 1340 | } 1341 | 1342 | /* debug 1343 | div { 1344 | background-color: rgba(64,128,255,0.2); 1345 | } 1346 | */ 1347 | 1348 | .one-line { 1349 | min-height: 1.5em; 1350 | } 1351 | 1352 | .two-lines { 1353 | min-height: 3em; 1354 | } 1355 | 1356 | .three-lines { 1357 | min-height: 4.5em; 1358 | } 1359 | 1360 | .four-lines { 1361 | min-height: 6em; 1362 | } 1363 | 1364 | .min80 > .sdpi-item-child { 1365 | min-width: 80px; 1366 | } 1367 | 1368 | .min100 > .sdpi-item-child { 1369 | min-width: 100px; 1370 | } 1371 | 1372 | .min120 > .sdpi-item-child { 1373 | min-width: 120px; 1374 | } 1375 | 1376 | .min140 > .sdpi-item-child { 1377 | min-width: 140px; 1378 | } 1379 | 1380 | .min160 > .sdpi-item-child { 1381 | min-width: 160px; 1382 | } 1383 | 1384 | .min200 > .sdpi-item-child { 1385 | min-width: 200px; 1386 | } 1387 | 1388 | .max40 { 1389 | flex-basis: 40%; 1390 | flex-grow: 0; 1391 | } 1392 | 1393 | .max30 { 1394 | flex-basis: 30%; 1395 | flex-grow: 0; 1396 | } 1397 | 1398 | .max20 { 1399 | flex-basis: 20%; 1400 | flex-grow: 0; 1401 | } 1402 | 1403 | .up20 { 1404 | margin-top: -20px; 1405 | } 1406 | 1407 | .alignCenter { 1408 | align-items: center; 1409 | } 1410 | 1411 | .alignTop { 1412 | align-items: flex-start; 1413 | } 1414 | 1415 | .alignBaseline { 1416 | align-items: baseline; 1417 | } 1418 | 1419 | .noMargins, 1420 | .noMargins *, 1421 | .noInnerMargins * { 1422 | margin: 0; 1423 | padding: 0; 1424 | } 1425 | 1426 | .hidden { 1427 | display: none; 1428 | } 1429 | 1430 | .icon-help, 1431 | .icon-help-line, 1432 | .icon-help-fill, 1433 | .icon-help-inv, 1434 | .icon-brighter, 1435 | .icon-darker, 1436 | .icon-warmer, 1437 | .icon-cooler { 1438 | min-width: 20px; 1439 | width: 20px; 1440 | background-repeat: no-repeat; 1441 | opacity: 1; 1442 | } 1443 | 1444 | .icon-help:active, 1445 | .icon-help-line:active, 1446 | .icon-help-fill:active, 1447 | .icon-help-inv:active, 1448 | .icon-brighter:active, 1449 | .icon-darker:active, 1450 | .icon-warmer:active, 1451 | .icon-cooler:active { 1452 | opacity: 0.5; 1453 | } 1454 | 1455 | .icon-brighter, 1456 | .icon-darker, 1457 | .icon-warmer, 1458 | .icon-cooler { 1459 | margin-top: 5px !important; 1460 | } 1461 | 1462 | .icon-help, 1463 | .icon-help-line, 1464 | .icon-help-fill, 1465 | .icon-help-inv { 1466 | cursor: pointer; 1467 | margin: 0px; 1468 | margin-left: 4px; 1469 | } 1470 | 1471 | .icon-brighter { 1472 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cg fill='%23999' fill-rule='evenodd'%3E%3Ccircle cx='10' cy='10' r='4'/%3E%3Cpath d='M14.8532861,7.77530426 C14.7173255,7.4682615 14.5540843,7.17599221 14.3666368,6.90157083 L16.6782032,5.5669873 L17.1782032,6.4330127 L14.8532861,7.77530426 Z M10.5,4.5414007 C10.2777625,4.51407201 10.051423,4.5 9.82179677,4.5 C9.71377555,4.5 9.60648167,4.50311409 9.5,4.50925739 L9.5,2 L10.5,2 L10.5,4.5414007 Z M5.38028092,6.75545367 C5.18389364,7.02383457 5.01124349,7.31068015 4.86542112,7.61289977 L2.82179677,6.4330127 L3.32179677,5.5669873 L5.38028092,6.75545367 Z M4.86542112,12.3871002 C5.01124349,12.6893198 5.18389364,12.9761654 5.38028092,13.2445463 L3.32179677,14.4330127 L2.82179677,13.5669873 L4.86542112,12.3871002 Z M9.5,15.4907426 C9.60648167,15.4968859 9.71377555,15.5 9.82179677,15.5 C10.051423,15.5 10.2777625,15.485928 10.5,15.4585993 L10.5,18 L9.5,18 L9.5,15.4907426 Z M14.3666368,13.0984292 C14.5540843,12.8240078 14.7173255,12.5317385 14.8532861,12.2246957 L17.1782032,13.5669873 L16.6782032,14.4330127 L14.3666368,13.0984292 Z'/%3E%3C/g%3E%3C/svg%3E"); 1473 | } 1474 | .icon-darker { 1475 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cg fill='%23999' fill-rule='evenodd'%3E%3Cpath d='M10 14C7.790861 14 6 12.209139 6 10 6 7.790861 7.790861 6 10 6 12.209139 6 14 7.790861 14 10 14 12.209139 12.209139 14 10 14zM10 13C11.6568542 13 13 11.6568542 13 10 13 8.34314575 11.6568542 7 10 7 8.34314575 7 7 8.34314575 7 10 7 11.6568542 8.34314575 13 10 13zM14.8532861 7.77530426C14.7173255 7.4682615 14.5540843 7.17599221 14.3666368 6.90157083L16.6782032 5.5669873 17.1782032 6.4330127 14.8532861 7.77530426zM10.5 4.5414007C10.2777625 4.51407201 10.051423 4.5 9.82179677 4.5 9.71377555 4.5 9.60648167 4.50311409 9.5 4.50925739L9.5 2 10.5 2 10.5 4.5414007zM5.38028092 6.75545367C5.18389364 7.02383457 5.01124349 7.31068015 4.86542112 7.61289977L2.82179677 6.4330127 3.32179677 5.5669873 5.38028092 6.75545367zM4.86542112 12.3871002C5.01124349 12.6893198 5.18389364 12.9761654 5.38028092 13.2445463L3.32179677 14.4330127 2.82179677 13.5669873 4.86542112 12.3871002zM9.5 15.4907426C9.60648167 15.4968859 9.71377555 15.5 9.82179677 15.5 10.051423 15.5 10.2777625 15.485928 10.5 15.4585993L10.5 18 9.5 18 9.5 15.4907426zM14.3666368 13.0984292C14.5540843 12.8240078 14.7173255 12.5317385 14.8532861 12.2246957L17.1782032 13.5669873 16.6782032 14.4330127 14.3666368 13.0984292z'/%3E%3C/g%3E%3C/svg%3E"); 1476 | } 1477 | .icon-warmer { 1478 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cg fill='%23999' fill-rule='evenodd'%3E%3Cpath d='M12.3247275 11.4890349C12.0406216 11.0007637 11.6761954 10.5649925 11.2495475 10.1998198 11.0890394 9.83238991 11 9.42659309 11 9 11 7.34314575 12.3431458 6 14 6 15.6568542 6 17 7.34314575 17 9 17 10.6568542 15.6568542 12 14 12 13.3795687 12 12.8031265 11.8116603 12.3247275 11.4890349zM17.6232392 11.6692284C17.8205899 11.4017892 17.9890383 11.1117186 18.123974 10.8036272L20.3121778 12.0669873 19.8121778 12.9330127 17.6232392 11.6692284zM18.123974 7.19637279C17.9890383 6.88828142 17.8205899 6.5982108 17.6232392 6.33077158L19.8121778 5.0669873 20.3121778 5.9330127 18.123974 7.19637279zM14.5 4.52746439C14.3358331 4.50931666 14.1690045 4.5 14 4.5 13.8309955 4.5 13.6641669 4.50931666 13.5 4.52746439L13.5 2 14.5 2 14.5 4.52746439zM13.5 13.4725356C13.6641669 13.4906833 13.8309955 13.5 14 13.5 14.1690045 13.5 14.3358331 13.4906833 14.5 13.4725356L14.5 16 13.5 16 13.5 13.4725356zM14 11C15.1045695 11 16 10.1045695 16 9 16 7.8954305 15.1045695 7 14 7 12.8954305 7 12 7.8954305 12 9 12 10.1045695 12.8954305 11 14 11zM9.5 11C10.6651924 11.4118364 11.5 12.5 11.5 14 11.5 16 10 17.5 8 17.5 6 17.5 4.5 16 4.5 14 4.5 12.6937812 5 11.5 6.5 11L6.5 7 9.5 7 9.5 11z'/%3E%3Cpath d='M12,14 C12,16.209139 10.209139,18 8,18 C5.790861,18 4,16.209139 4,14 C4,12.5194353 4.80439726,11.2267476 6,10.5351288 L6,4 C6,2.8954305 6.8954305,2 8,2 C9.1045695,2 10,2.8954305 10,4 L10,10.5351288 C11.1956027,11.2267476 12,12.5194353 12,14 Z M11,14 C11,12.6937812 10.1651924,11.5825421 9,11.1707057 L9,4 C9,3.44771525 8.55228475,3 8,3 C7.44771525,3 7,3.44771525 7,4 L7,11.1707057 C5.83480763,11.5825421 5,12.6937812 5,14 C5,15.6568542 6.34314575,17 8,17 C9.65685425,17 11,15.6568542 11,14 Z'/%3E%3C/g%3E%3C/svg%3E"); 1479 | } 1480 | 1481 | .icon-cooler { 1482 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20'%3E%3Cg fill='%23999' fill-rule='evenodd'%3E%3Cpath d='M10.4004569 11.6239517C10.0554735 10.9863849 9.57597206 10.4322632 9 9.99963381L9 9.7450467 9.53471338 9.7450467 10.8155381 8.46422201C10.7766941 8.39376637 10.7419749 8.32071759 10.7117062 8.2454012L9 8.2454012 9 6.96057868 10.6417702 6.96057868C10.6677696 6.86753378 10.7003289 6.77722682 10.7389179 6.69018783L9.44918707 5.40045694 9 5.40045694 9 4.34532219 9.32816127 4.34532219 9.34532219 2.91912025 10.4004569 2.91912025 10.4004569 4.53471338 11.6098599 5.74411634C11.7208059 5.68343597 11.8381332 5.63296451 11.9605787 5.59396526L11.9605787 3.8884898 10.8181818 2.74609294 11.5642748 2 12.5727518 3.00847706 13.5812289 2 14.3273218 2.74609294 13.2454012 3.82801356 13.2454012 5.61756719C13.3449693 5.65339299 13.4408747 5.69689391 13.5324038 5.74735625L14.7450467 4.53471338 14.7450467 2.91912025 15.8001815 2.91912025 15.8001815 4.34532219 17.2263834 4.34532219 17.2263834 5.40045694 15.6963166 5.40045694 14.4002441 6.69652946C14.437611 6.78161093 14.4692249 6.86979146 14.4945934 6.96057868L16.2570138 6.96057868 17.3994107 5.81818182 18.1455036 6.56427476 17.1370266 7.57275182 18.1455036 8.58122888 17.3994107 9.32732182 16.3174901 8.2454012 14.4246574 8.2454012C14.3952328 8.31861737 14.3616024 8.38969062 14.3240655 8.45832192L15.6107903 9.7450467 17.2263834 9.7450467 17.2263834 10.8001815 15.8001815 10.8001815 15.8001815 12.2263834 14.7450467 12.2263834 14.7450467 10.6963166 13.377994 9.32926387C13.3345872 9.34850842 13.2903677 9.36625331 13.2454012 9.38243281L13.2454012 11.3174901 14.3273218 12.3994107 13.5812289 13.1455036 12.5848864 12.1491612 11.5642748 13.1455036 10.8181818 12.3994107 11.9605787 11.2570138 11.9605787 9.40603474C11.8936938 9.38473169 11.828336 9.36000556 11.7647113 9.33206224L10.4004569 10.6963166 10.4004569 11.6239517zM12.75 8.5C13.3022847 8.5 13.75 8.05228475 13.75 7.5 13.75 6.94771525 13.3022847 6.5 12.75 6.5 12.1977153 6.5 11.75 6.94771525 11.75 7.5 11.75 8.05228475 12.1977153 8.5 12.75 8.5zM9.5 14C8.5 16.3333333 7.33333333 17.5 6 17.5 4.66666667 17.5 3.5 16.3333333 2.5 14L9.5 14z'/%3E%3Cpath d='M10,14 C10,16.209139 8.209139,18 6,18 C3.790861,18 2,16.209139 2,14 C2,12.5194353 2.80439726,11.2267476 4,10.5351288 L4,4 C4,2.8954305 4.8954305,2 6,2 C7.1045695,2 8,2.8954305 8,4 L8,10.5351288 C9.19560274,11.2267476 10,12.5194353 10,14 Z M9,14 C9,12.6937812 8.16519237,11.5825421 7,11.1707057 L7,4 C7,3.44771525 6.55228475,3 6,3 C5.44771525,3 5,3.44771525 5,4 L5,11.1707057 C3.83480763,11.5825421 3,12.6937812 3,14 C3,15.6568542 4.34314575,17 6,17 C7.65685425,17 9,15.6568542 9,14 Z'/%3E%3C/g%3E%3C/svg%3E"); 1483 | } 1484 | 1485 | .icon-help { 1486 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Cpath fill='%23999' d='M11.292 12.516l.022 1.782H9.07v-1.804c0-1.98 1.276-2.574 2.662-3.278h-.022c.814-.44 1.65-.88 1.694-2.2.044-1.386-1.122-2.728-3.234-2.728-1.518 0-2.662.902-3.366 2.354L5 5.608C5.946 3.584 7.662 2 10.17 2c3.564 0 5.632 2.442 5.588 5.06-.066 2.618-1.716 3.41-3.102 4.158-.704.374-1.364.682-1.364 1.298zm-1.122 2.442c.858 0 1.452.594 1.452 1.452 0 .682-.594 1.408-1.452 1.408-.77 0-1.386-.726-1.386-1.408 0-.858.616-1.452 1.386-1.452z'/%3E%3C/svg%3E"); 1487 | } 1488 | 1489 | .icon-help-line { 1490 | background-image: url("data:image/svg+xml,%3Csvg width='20' height='20' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='%23999' fill-rule='evenodd'%3E%3Cpath d='M10 20C4.477 20 0 15.523 0 10S4.477 0 10 0s10 4.477 10 10-4.477 10-10 10zm0-1a9 9 0 1 0 0-18 9 9 0 0 0 0 18z'/%3E%3Cpath d='M10.848 12.307l.02 1.578H8.784v-1.597c0-1.753 1.186-2.278 2.474-2.901h-.02c.756-.39 1.533-.78 1.574-1.948.041-1.226-1.043-2.414-3.006-2.414-1.41 0-2.474.798-3.128 2.083L5 6.193C5.88 4.402 7.474 3 9.805 3 13.118 3 15.04 5.161 15 7.478c-.061 2.318-1.595 3.019-2.883 3.68-.654.332-1.268.604-1.268 1.15zM9.805 14.47c.798 0 1.35.525 1.35 1.285 0 .603-.552 1.246-1.35 1.246-.715 0-1.288-.643-1.288-1.246 0-.76.573-1.285 1.288-1.285z' fill-rule='nonzero'/%3E%3C/g%3E%3C/svg%3E");} 1491 | 1492 | .icon-help-fill { 1493 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Ccircle cx='10' cy='10' r='10' fill='%23999'/%3E%3Cpath fill='%23FFF' fill-rule='nonzero' d='M8.368 7.189H5C5 3.5 7.668 2 10.292 2 13.966 2 16 4.076 16 7.012c0 3.754-3.849 3.136-3.849 5.211v1.656H8.455v-1.832c0-2.164 1.4-2.893 2.778-3.6.437-.242 1.006-.574 1.006-1.236 0-2.208-3.871-2.142-3.871-.022zM10.25 18a1.75 1.75 0 1 1 0-3.5 1.75 1.75 0 0 1 0 3.5z'/%3E%3C/g%3E%3C/svg%3E"); 1494 | } 1495 | 1496 | .icon-help-inv { 1497 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20'%3E%3Cpath fill='%23999' fill-rule='evenodd' d='M10 20C4.477 20 0 15.523 0 10S4.477 0 10 0s10 4.477 10 10-4.477 10-10 10zM8.368 7.189c0-2.12 3.87-2.186 3.87.022 0 .662-.568.994-1.005 1.236-1.378.707-2.778 1.436-2.778 3.6v1.832h3.696v-1.656c0-2.075 3.849-1.457 3.849-5.21C16 4.075 13.966 2 10.292 2 7.668 2 5 3.501 5 7.189h3.368zM10.25 18a1.75 1.75 0 1 0 0-3.5 1.75 1.75 0 0 0 0 3.5z'/%3E%3C/svg%3E"); 1498 | } 1499 | 1500 | .kelvin::after { 1501 | content: "K"; 1502 | } 1503 | 1504 | .mired::after { 1505 | content: " Mired"; 1506 | } 1507 | 1508 | .percent::after { 1509 | content: "%"; 1510 | } 1511 | 1512 | .sdpi-item-value + .icon-cooler, 1513 | .sdpi-item-value + .icon-warmer { 1514 | margin-left: 0px !important; 1515 | margin-top: 15px !important; 1516 | } 1517 | 1518 | /** 1519 | CONTROL-CENTER STYLES 1520 | */ 1521 | input[type="range"].colorbrightness::-webkit-slider-runnable-track, 1522 | input[type="range"].colortemperature::-webkit-slider-runnable-track { 1523 | height: 8px; 1524 | background: #979797; 1525 | border-radius: 4px; 1526 | background-image: linear-gradient(to right,#94d0ec, #ffb165); 1527 | } 1528 | 1529 | input[type="range"].colorbrightness::-webkit-slider-runnable-track { 1530 | background-color: #efefef; 1531 | background-image: linear-gradient(to right, black , rgba(0,0,0,0)); 1532 | } 1533 | 1534 | 1535 | input[type="range"].colorbrightness::-webkit-slider-thumb, 1536 | input[type="range"].colortemperature::-webkit-slider-thumb { 1537 | width: 16px; 1538 | height: 16px; 1539 | border-radius: 20px; 1540 | margin-top: -5px; 1541 | background-color: #86c6e8; 1542 | box-shadow: 0px 0px 1px #000000; 1543 | border: 1px solid #d8d8d8; 1544 | } 1545 | .sdpi-info-label { 1546 | display: inline-block; 1547 | user-select: none; 1548 | position: absolute; 1549 | height: 15px; 1550 | width: auto; 1551 | text-align: center; 1552 | border-radius: 4px; 1553 | min-width: 44px; 1554 | max-width: 80px; 1555 | background: white; 1556 | font-size: 11px; 1557 | color: black; 1558 | z-index: 1000; 1559 | box-shadow: 0px 0px 12px rgba(0,0,0,.8); 1560 | padding: 2px; 1561 | 1562 | } 1563 | 1564 | .sdpi-info-label.hidden { 1565 | opacity: 0; 1566 | transition: opacity 0.25s linear; 1567 | } 1568 | 1569 | .sdpi-info-label.shown { 1570 | position: absolute; 1571 | opacity: 1; 1572 | transition: opacity 0.25s ease-out; 1573 | } 1574 | 1575 | 1576 | 1577 | 1578 | /*--------- context menu ----------*/ 1579 | 1580 | .context-menu { 1581 | display: none; 1582 | position: absolute; 1583 | z-index: 10; 1584 | padding: 12px 0; 1585 | width: 120px; 1586 | background-color: #3D3D3D; 1587 | border: solid 1px #dfdfdf; 1588 | box-shadow: 1px 1px 2px #cfcfcf; 1589 | } 1590 | 1591 | .context-menu--active { 1592 | display: block; 1593 | } 1594 | 1595 | .context-menu__items { 1596 | list-style: none; 1597 | margin: 0; 1598 | padding: 0; 1599 | overflow-y: auto; 1600 | } 1601 | 1602 | .context-menu__item { 1603 | display: block; 1604 | margin-bottom: 4px; 1605 | background-color: #3D3D3D !important; 1606 | } 1607 | 1608 | .context-menu__item:last-child { 1609 | margin-bottom: 0; 1610 | } 1611 | 1612 | .context-menu__link { 1613 | display: block; 1614 | padding: 4px 12px; 1615 | color: #ffff; 1616 | text-decoration: none; 1617 | white-space: nowrap; 1618 | } 1619 | 1620 | .context-menu__link:hover { 1621 | color: #fff; 1622 | background-color: #0066aa; 1623 | } 1624 | 1625 | .context-menu_message{ 1626 | cursor: default; 1627 | } -------------------------------------------------------------------------------- /streamdeck-plugin/com.zoton2.example.sdPlugin/img/actionIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoton2/node-streamdeck-util/d2f51374eb44640240362131c830e5bf14a1ee1c/streamdeck-plugin/com.zoton2.example.sdPlugin/img/actionIcon.png -------------------------------------------------------------------------------- /streamdeck-plugin/com.zoton2.example.sdPlugin/img/actionIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoton2/node-streamdeck-util/d2f51374eb44640240362131c830e5bf14a1ee1c/streamdeck-plugin/com.zoton2.example.sdPlugin/img/actionIcon@2x.png -------------------------------------------------------------------------------- /streamdeck-plugin/com.zoton2.example.sdPlugin/img/pluginIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoton2/node-streamdeck-util/d2f51374eb44640240362131c830e5bf14a1ee1c/streamdeck-plugin/com.zoton2.example.sdPlugin/img/pluginIcon.png -------------------------------------------------------------------------------- /streamdeck-plugin/com.zoton2.example.sdPlugin/img/pluginIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zoton2/node-streamdeck-util/d2f51374eb44640240362131c830e5bf14a1ee1c/streamdeck-plugin/com.zoton2.example.sdPlugin/img/pluginIcon@2x.png -------------------------------------------------------------------------------- /streamdeck-plugin/com.zoton2.example.sdPlugin/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "Actions": [ 3 | { 4 | "Name": "Example", 5 | "Icon": "img/actionIcon", 6 | "States": [ 7 | { 8 | "Image": "img/actionIcon", 9 | "TitleAlignment": "middle", 10 | "Title": "Example" 11 | } 12 | ], 13 | "SupportedInMultiActions": false, 14 | "Tooltip": "Example action needed to run the plugin.", 15 | "UUID": "com.zoton2.example.action" 16 | } 17 | ], 18 | "Author": "zoton2", 19 | "Category": "node-streamdeck-util", 20 | "CodePath": "backend.html", 21 | "Description": "Barebones plugin to be used with node-streamdeck-util.", 22 | "Name": "node-streamdeck-util", 23 | "Icon": "img/pluginIcon", 24 | "URL": "https://github.com/zoton2/node-streamdeck-util", 25 | "PropertyInspectorPath": "pi.html", 26 | "DefaultWindowSize": [ 27 | 400, 28 | 200 29 | ], 30 | "Version": "1.0", 31 | "SDKVersion": 2, 32 | "OS": [ 33 | { 34 | "Platform": "mac", 35 | "MinimumVersion": "10.11" 36 | }, 37 | { 38 | "Platform": "windows", 39 | "MinimumVersion": "10" 40 | } 41 | ], 42 | "Software": { 43 | "MinimumVersion": "5" 44 | } 45 | } -------------------------------------------------------------------------------- /streamdeck-plugin/com.zoton2.example.sdPlugin/pi.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | com.zoton2.example Property Inspector 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 |
Settings
15 | 16 |
17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /streamdeck-plugin/com.zoton2.example.sdPlugin/pi.js: -------------------------------------------------------------------------------- 1 | var sdWS; 2 | var connectSocketData = {}; 3 | var globalSettings = {}; 4 | 5 | // Initalise connection to Stream Deck's WebSocket. 6 | function connectToSDWS() { 7 | if (sdWS) sdWS.close(); // Close current connection if one is active. 8 | 9 | sdWS = new WebSocket(`ws://127.0.0.1:${connectSocketData.port}`); 10 | console.info('Connecting to Stream Deck software'); 11 | 12 | sdWS.addEventListener('error', e => { 13 | console.error('Error occured on the Stream Deck software connection:', e); 14 | }); 15 | 16 | // Initalise Stream Deck WebSocket connection. 17 | sdWS.addEventListener('open', () => { 18 | console.info('Connection to Stream Deck software successful'); 19 | sdWS.send(JSON.stringify({ event: connectSocketData.registerEvent, uuid: connectSocketData.pluginUUID })); 20 | sdWS.send(JSON.stringify({ event: 'getGlobalSettings', context: connectSocketData.pluginUUID })); 21 | }, { once: true }); 22 | 23 | sdWS.addEventListener('close', e => { 24 | console.warn('Connection to Stream Deck software closed (%s)', e.code); 25 | }, { once: true }); 26 | 27 | sdWS.addEventListener('message', e => { 28 | var data = JSON.parse(e.data); 29 | if (data.event === 'didReceiveGlobalSettings') { 30 | globalSettings = data.payload.settings; 31 | } 32 | }); 33 | } 34 | 35 | // Function triggered by the popup settings window when the settings are saved. 36 | function gotCallbackFromWindow(data) { 37 | console.info('URL/key settings have changed, saving settings'); 38 | globalSettings.url = data.url || 'ws://localhost:9091'; // Default if setting is empty 39 | globalSettings.key = data.key || 'DEFAULT_KEY'; // Default if setting is empty 40 | globalSettings.connected = false; 41 | sdWS.send(JSON.stringify({ event: 'setGlobalSettings', context: connectSocketData.pluginUUID, payload: globalSettings })); 42 | } 43 | 44 | // Triggered by the Stream Deck software. 45 | function connectElgatoStreamDeckSocket(port, pluginUUID, registerEvent, info, actionInfo) { 46 | connectSocketData.port = port; 47 | connectSocketData.pluginUUID = pluginUUID; 48 | connectSocketData.registerEvent = registerEvent; 49 | connectSocketData.info = info; 50 | connectSocketData.actionInfo = actionInfo; 51 | connectToSDWS(); 52 | } 53 | 54 | // Wait for the document to fully load before doing this stuff. 55 | document.addEventListener('DOMContentLoaded', e => { 56 | // Listen for "Open Settings Dialog" button to be clicked. 57 | document.getElementById('settingsButton').addEventListener('click', e => { 58 | window.open('settings.html'); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /streamdeck-plugin/com.zoton2.example.sdPlugin/settings.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | com.zoton2.example Settings Dialog 7 | 8 | 35 | 36 | 37 | 38 | 39 |
40 |
41 |
Node.js Server URL
42 | 43 |
44 |
45 |
Key
46 | 47 |
48 |
49 | 50 |
51 |
52 | Node.js Server Status: Checking... 53 |
54 |
55 | 56 | 57 | -------------------------------------------------------------------------------- /streamdeck-plugin/com.zoton2.example.sdPlugin/settings.js: -------------------------------------------------------------------------------- 1 | function updateConnectionStatus(connected) { 2 | if (connected) { 3 | document.getElementById('connectionStatus').innerHTML = 'CONNECTED'; 4 | document.getElementById('connectionStatus').style.color = 'lightgreen'; 5 | } 6 | else { 7 | document.getElementById('connectionStatus').innerHTML = 'DISCONNECTED'; 8 | document.getElementById('connectionStatus').style.color = 'red'; 9 | } 10 | } 11 | 12 | // Wait for the document to fully load before doing this stuff. 13 | document.addEventListener('DOMContentLoaded', e => { 14 | const globalSettings = window.opener.globalSettings; 15 | 16 | document.getElementById('url').value = globalSettings.url; 17 | document.getElementById('key').value = globalSettings.key; 18 | updateConnectionStatus(globalSettings.connected); 19 | 20 | // Pass back the updated settings when saving and close the window. 21 | document.getElementById('save').addEventListener('click', () => { 22 | var url = document.getElementById('url').value; 23 | var key = document.getElementById('key').value; 24 | 25 | // If we need to update the stored values, do that now. 26 | if (url !== globalSettings.url || key !== globalSettings.key) { 27 | window.opener.gotCallbackFromWindow({ 28 | url: url, 29 | key: key 30 | }); 31 | } 32 | 33 | window.close(); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /streamdeck-plugin/compile.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | if not exist Release\ mkdir Release 3 | del Release\com.zoton2.example.streamDeckPlugin >nul 2>&1 4 | DistributionTool.exe -b -i com.zoton2.example.sdPlugin -o Release 5 | pause 6 | -------------------------------------------------------------------------------- /test/server.ts: -------------------------------------------------------------------------------- 1 | import StreamDeck from '../src'; 2 | 3 | // Create new instance of the utility. 4 | var sd = new StreamDeck(); 5 | 6 | // Start listening for connections from the Stream Deck plugin. 7 | // key - the secret key to authenticate the connection, defaults to DEFAULT_KEY 8 | // port - the port the connection will use, defaults to 9091 9 | // debug - if you want to print debug messages, useful for development 10 | sd.listen({ 11 | key: 'DEFAULT_KEY', 12 | port: 9091, 13 | debug: true 14 | }); 15 | 16 | // When the connection between the plugin and this instance is open. 17 | sd.on('open', () => { 18 | console.log('open'); 19 | }); 20 | 21 | // If the connection between the plugin and this instance is closed. 22 | sd.on('close', (code, reason) => { 23 | console.log('close: %s, %s', code, reason); 24 | }); 25 | 26 | // If there are any errors on the connection between the plugin and this instance. 27 | sd.on('error', (err) => { 28 | console.log('error:'); 29 | console.log(err); 30 | }); 31 | 32 | // Listens for the Stream Deck's events. 33 | sd.on('message', (msg) => { 34 | console.log('message:'); 35 | console.log(msg); 36 | 37 | var buttonLocations = sd.getButtonLocations(); // object, see below 38 | var pluginUUID = sd.getPluginUUID(); // sometimes needed as context when sending messages 39 | 40 | // Send a message back to the Stream Deck application; the send function stringifies it for you. 41 | /* sd.send({ 42 | event: 'openUrl', 43 | payload: { 44 | url: 'https://www.elgato.com' 45 | } 46 | }); */ 47 | }); 48 | 49 | // You can directly listen for Stream Deck's events by their name if you want to. 50 | sd.on('keyDown', (msg) => { 51 | console.log('keyDown:'); 52 | console.log(msg); 53 | }); 54 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | // "jsx": "preserve", /* Specify what JSX code is generated. */ 17 | // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */ 22 | // "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | 26 | /* Modules */ 27 | "module": "commonjs", /* Specify what module code is generated. */ 28 | "rootDir": "src", /* Specify the root folder within your source files. */ 29 | "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ 30 | "baseUrl": ".", /* Specify the base directory to resolve non-relative module names. */ 31 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 32 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 33 | // "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */ 34 | // "types": [], /* Specify type package names to be included without being referenced in a source file. */ 35 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 36 | // "resolveJsonModule": true, /* Enable importing .json files */ 37 | // "noResolve": true, /* Disallow `import`s, `require`s or ``s from expanding the number of files TypeScript should add to a project. */ 38 | 39 | /* JavaScript Support */ 40 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ 41 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 42 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */ 43 | 44 | /* Emit */ 45 | "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 46 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 47 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 48 | "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 49 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */ 50 | "outDir": "dist", /* Specify an output folder for all emitted files. */ 51 | // "removeComments": true, /* Disable emitting comments. */ 52 | // "noEmit": true, /* Disable emitting files from a compilation. */ 53 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 54 | // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */ 55 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 56 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 58 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 59 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 60 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 61 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 62 | // "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */ 63 | // "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */ 64 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 65 | // "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */ 66 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 67 | // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ 68 | 69 | /* Interop Constraints */ 70 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 71 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 72 | "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ 73 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 74 | "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ 75 | 76 | /* Type Checking */ 77 | "strict": true, /* Enable all strict type-checking options. */ 78 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */ 79 | // "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */ 80 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 81 | // "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */ 82 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 83 | // "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */ 84 | // "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */ 85 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 86 | // "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */ 87 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */ 88 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 89 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 90 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 91 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 92 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 93 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */ 94 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 95 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 96 | 97 | /* Completeness */ 98 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 99 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 100 | }, 101 | "include": [ 102 | "src/**/*.ts", 103 | "types/**/*.d.ts" 104 | ], 105 | "exclude": [ 106 | "node_modules" 107 | ] 108 | } 109 | -------------------------------------------------------------------------------- /types/index.d.ts: -------------------------------------------------------------------------------- 1 | export interface TitleParameters { 2 | fontFamily: string; 3 | fontSize: number; 4 | fontStyle: string; 5 | fontUnderline: boolean; 6 | showTitle: boolean; 7 | titleAlignment: string; 8 | titleColor: string; 9 | } 10 | 11 | export interface ButtonObject { 12 | context: string; 13 | action: string; 14 | title: string; 15 | isInMultiAction: boolean; 16 | state: number; 17 | titleParameters: TitleParameters; 18 | } 19 | 20 | export interface ButtonLocations { 21 | [device: string]: { 22 | [row: string]: { 23 | [column: string]: ButtonObject | null; 24 | }; 25 | }; 26 | } 27 | 28 | /** 29 | * Documentation reference: 30 | * https://developer.elgato.com/documentation/stream-deck/sdk/events-received/ 31 | */ 32 | export namespace EventReceive { 33 | interface KeyDown { 34 | action: string; 35 | event: 'keyDown'; 36 | context: string; 37 | device: string; 38 | payload: { 39 | settings: { [k: string]: unknown }; 40 | coordinates: { 41 | column: number; 42 | row: number; 43 | }; 44 | state: number; 45 | userDesiredState: 0 | 1; 46 | isInMultiAction: boolean; 47 | }; 48 | } 49 | interface KeyUp extends Omit { 50 | event: 'keyUp'; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /vetur.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | projects: [ { root: 'streamdeck-plugin-vue' } ], 3 | }; 4 | --------------------------------------------------------------------------------