├── .gitattributes ├── screenshot.png ├── Sources ├── pluginIcon.png ├── categoryIcon.png ├── pluginIcon@2x.png ├── categoryIcon@2x.png ├── common │ ├── css │ │ ├── check.png │ │ ├── buttons.png │ │ ├── buttons@2x.png │ │ ├── rcheck.svg │ │ ├── caret.svg │ │ ├── pi_required_ok.svg │ │ ├── check.svg │ │ ├── local.css │ │ ├── pi_required.svg │ │ ├── elg_calendar_inv.svg │ │ ├── elg_calendar_inv_13.svg │ │ ├── elg_calendar.svg │ │ ├── sdpi_win.css │ │ └── sdpi.css │ ├── common_pi.js │ ├── timers.js │ └── common.js ├── previews │ └── 1-preview.png ├── action │ ├── images │ │ ├── image.png │ │ ├── image@2x.png │ │ ├── actionIcon.png │ │ └── actionIcon@2x.png │ ├── sounds │ │ ├── digital.mp3 │ │ ├── bells-low.mp3 │ │ └── bells-high.mp3 │ └── js │ │ ├── sounds.js │ │ ├── index.html │ │ ├── clockfaces.js │ │ └── clock.js ├── index.html ├── en.json ├── manifest.json ├── propertyinspector │ ├── index_pi.js │ └── index.html └── js │ └── app.js ├── Release └── com.gallowaylabs.tomato.streamDeckPlugin ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gallowaylabs/streamdeck-tomato-timer/HEAD/screenshot.png -------------------------------------------------------------------------------- /Sources/pluginIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gallowaylabs/streamdeck-tomato-timer/HEAD/Sources/pluginIcon.png -------------------------------------------------------------------------------- /Sources/categoryIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gallowaylabs/streamdeck-tomato-timer/HEAD/Sources/categoryIcon.png -------------------------------------------------------------------------------- /Sources/pluginIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gallowaylabs/streamdeck-tomato-timer/HEAD/Sources/pluginIcon@2x.png -------------------------------------------------------------------------------- /Sources/categoryIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gallowaylabs/streamdeck-tomato-timer/HEAD/Sources/categoryIcon@2x.png -------------------------------------------------------------------------------- /Sources/common/css/check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gallowaylabs/streamdeck-tomato-timer/HEAD/Sources/common/css/check.png -------------------------------------------------------------------------------- /Sources/common/css/buttons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gallowaylabs/streamdeck-tomato-timer/HEAD/Sources/common/css/buttons.png -------------------------------------------------------------------------------- /Sources/previews/1-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gallowaylabs/streamdeck-tomato-timer/HEAD/Sources/previews/1-preview.png -------------------------------------------------------------------------------- /Sources/action/images/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gallowaylabs/streamdeck-tomato-timer/HEAD/Sources/action/images/image.png -------------------------------------------------------------------------------- /Sources/action/sounds/digital.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gallowaylabs/streamdeck-tomato-timer/HEAD/Sources/action/sounds/digital.mp3 -------------------------------------------------------------------------------- /Sources/common/css/buttons@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gallowaylabs/streamdeck-tomato-timer/HEAD/Sources/common/css/buttons@2x.png -------------------------------------------------------------------------------- /Sources/action/images/image@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gallowaylabs/streamdeck-tomato-timer/HEAD/Sources/action/images/image@2x.png -------------------------------------------------------------------------------- /Sources/action/sounds/bells-low.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gallowaylabs/streamdeck-tomato-timer/HEAD/Sources/action/sounds/bells-low.mp3 -------------------------------------------------------------------------------- /Sources/action/images/actionIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gallowaylabs/streamdeck-tomato-timer/HEAD/Sources/action/images/actionIcon.png -------------------------------------------------------------------------------- /Sources/action/sounds/bells-high.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gallowaylabs/streamdeck-tomato-timer/HEAD/Sources/action/sounds/bells-high.mp3 -------------------------------------------------------------------------------- /Sources/action/images/actionIcon@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gallowaylabs/streamdeck-tomato-timer/HEAD/Sources/action/images/actionIcon@2x.png -------------------------------------------------------------------------------- /Release/com.gallowaylabs.tomato.streamDeckPlugin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gallowaylabs/streamdeck-tomato-timer/HEAD/Release/com.gallowaylabs.tomato.streamDeckPlugin -------------------------------------------------------------------------------- /Sources/common/css/rcheck.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Sources/common/css/caret.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Sources/common/css/pi_required_ok.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Sources/common/css/check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Sources/common/css/local.css: -------------------------------------------------------------------------------- 1 | body, 2 | .localbody { 3 | height: 100%; 4 | padding: 0; 5 | overflow-x: hidden; 6 | overflow-y: auto; 7 | margin: 0; 8 | -webkit-overflow-scrolling: touch; 9 | } 10 | 11 | .localbody { 12 | width: 350px; 13 | margin: 0 auto; 14 | } 15 | -------------------------------------------------------------------------------- /Sources/action/js/sounds.js: -------------------------------------------------------------------------------- 1 | var sounds = [ 2 | { 3 | name: 'Bells (high)', 4 | filename: "action/sounds/bells-high.mp3", 5 | }, 6 | { 7 | name: 'Bells (low)', 8 | filename: "action/sounds/bells-low.mp3", 9 | }, 10 | { 11 | name: 'Digital', 12 | filename: "action/sounds/digital.mp3", 13 | } 14 | ]; 15 | -------------------------------------------------------------------------------- /Sources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.gallowaylabs.tomato 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Sources/common/css/pi_required.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /Sources/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "Description": "Timers to help balance your work and break time", 3 | "Name": "Tomato Timer", 4 | "com.gallowaylabs.tomato.clock": { 5 | "Name": "Timer", 6 | "Tooltip": "Timers to help balance your work and break time" 7 | }, 8 | 9 | "Localization": { 10 | "Face": "Face", 11 | "Black" : "Black", 12 | "Blue" : "Blue", 13 | "Yellow" : "Yellow", 14 | "Green" : "Green", 15 | "Red": "Red", 16 | "White": "White", 17 | "Transparent": "Transparent" 18 | } 19 | } -------------------------------------------------------------------------------- /Sources/common/css/elg_calendar_inv.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Sources/action/js/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 16 | 17 | 18 |
19 | 20 | 21 |
22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Sources/common/css/elg_calendar_inv_13.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Matthew Galloway 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 | -------------------------------------------------------------------------------- /Sources/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "Actions": [ 3 | { 4 | "Icon": "action/images/actionIcon", 5 | "Name": "Timer", 6 | "States": [ 7 | { 8 | "Image": "action/images/image" 9 | } 10 | ], 11 | "SupportedInMultiActions": false, 12 | "Tooltip": "Timers to help balance your work and break time", 13 | "UUID": "com.gallowaylabs.tomato.clock" 14 | } 15 | ], 16 | "SDKVersion": 2, 17 | "Author": "Galloway Labs", 18 | "CodePath": "index.html", 19 | "PropertyInspectorPath": "propertyinspector/index.html", 20 | "Description": "A configurable timer to help you balance work and break time", 21 | "Name": "Tomato Timer", 22 | "Category": "Tomato Timer", 23 | "CategoryIcon": "categoryIcon", 24 | "Icon": "pluginIcon", 25 | "URL": "https://github.com/gallowaylabs/streamdeck-tomato-timer", 26 | "Version": "0.6.0", 27 | "OS": [ 28 | { 29 | "Platform": "mac", 30 | "MinimumVersion": "10.11" 31 | }, 32 | { 33 | "Platform": "windows", 34 | "MinimumVersion": "10" 35 | } 36 | ], 37 | "Software": { 38 | "MinimumVersion": "4.1" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Sources/action/js/clockfaces.js: -------------------------------------------------------------------------------- 1 | var clockfaces = [ 2 | { 3 | name: 'White', 4 | colors: { 5 | stroke: '#e3e3e3', 6 | background: '#000000' 7 | }, 8 | text: true 9 | }, 10 | { 11 | name: 'Green', 12 | colors: { 13 | stroke: '#00cc88', 14 | background: '#000000' 15 | }, 16 | text: true 17 | }, 18 | { 19 | name: 'Yellow', 20 | colors: { 21 | stroke: '#ffff00', 22 | background: '#000000' 23 | }, 24 | text: true 25 | }, 26 | { 27 | name: 'Blue', 28 | colors: { 29 | stroke: '#3D6EE0', 30 | background: '#000000' 31 | }, 32 | text: true 33 | }, 34 | { 35 | name: 'Red', 36 | colors: { 37 | stroke: '#ff3300', 38 | background: '#000000' 39 | }, 40 | text: true 41 | }, 42 | { 43 | name: 'Black', 44 | colors: { 45 | stroke: '#333333', 46 | background: '#ffffff' 47 | }, 48 | text: true 49 | }, 50 | { 51 | name: 'Transparent', 52 | colors: { 53 | stroke: '#00ffff', 54 | background: 'transparent' 55 | }, 56 | text: true 57 | } 58 | ]; 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Stream Deck Tomato Timer 2 | 3 | A configurable timer for balancing work and break times. 4 | 5 | 6 | # Features 7 | 8 | - Configurable work, short and long break durations 9 | - Use one of 3 built-in alarm clock sounds or select an mp3 of your own 10 | - 7 color palette swap themes 11 | - cross-platform (macOS, Windows) 12 | 13 | ![](screenshot.png) 14 | 15 | 16 | # Tips 17 | 18 | - You can pause the timer at any time with a short press to pause and later resume. 19 | - Pressing and holding for 2 seconds ends the current phase and moves to the next one 20 | 21 | 22 | # Installation 23 | 24 | This plugin is available in the Stream Deck store and is genrally updated updated shortly after a release is published on GitHub. Updated are also automatically pushed to those 25 | who have the plugin installed via the store. 26 | 27 | In the Release folder, you can find the file `com.gallowaylabs.tomato.sdPlugin`. If you double-click this file on your machine, Stream Deck will install the plugin. 28 | If you don't have the repository downloaded, get the latest pre-built version on the [releases page](https://github.com/gallowaylabs/streamdeck-tomato-timer/releases) 29 | 30 | 31 | # Notice 32 | 33 | Pomodoro™ and Pomodoro Technique® are registered trademarks of Francesco Cirillo. This product is not affiliated with Francesco Cirillo. 34 | -------------------------------------------------------------------------------- /Sources/common/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 | -------------------------------------------------------------------------------- /Sources/common/common_pi.js: -------------------------------------------------------------------------------- 1 | /** Stream Deck software passes system-highlight color information 2 | * to Property Inspector. Here we 'inject' the CSS styles into the DOM 3 | * when we receive this information. */ 4 | 5 | function addDynamicStyles (clrs, fromWhere) { 6 | // console.log("addDynamicStyles", clrs.highlightColor, clrs.highlightColor.slice(0, 7)); 7 | const node = document.getElementById('#sdpi-dynamic-styles') || document.createElement('style'); 8 | if (!clrs.mouseDownColor) clrs.mouseDownColor = Utils.fadeColor(clrs.highlightColor, -100); 9 | const clr = clrs.highlightColor.slice(0, 7); 10 | const clr1 = Utils.fadeColor(clr, 100); 11 | const clr2 = Utils.fadeColor(clr, 60); 12 | const metersActiveColor = Utils.fadeColor(clr, -60); 13 | 14 | // console.log("%c ", `background-color: #${clr}`, 'addDS', clr); 15 | // console.log("%c ", `background-color: #${clr1}`, 'addDS1', clr1); 16 | // console.log("%c ", `background-color: #${clr2}`, 'addDS2', clr2); 17 | // console.log("%c ", `background-color: #${metersActiveColor}`, 'metersActiveColor', metersActiveColor); 18 | 19 | node.setAttribute('id', 'sdpi-dynamic-styles'); 20 | node.innerHTML = ` 21 | 22 | input[type="radio"]:checked + label span, 23 | input[type="checkbox"]:checked + label span { 24 | background-color: ${clrs.highlightColor}; 25 | } 26 | 27 | input[type="radio"]:active:checked + label span, 28 | input[type="checkbox"]:active:checked + label span { 29 | background-color: ${clrs.mouseDownColor}; 30 | } 31 | 32 | input[type="radio"]:active + label span, 33 | input[type="checkbox"]:active + label span { 34 | background-color: ${clrs.buttonPressedBorderColor}; 35 | } 36 | 37 | td.selected, 38 | td.selected:hover, 39 | li.selected:hover, 40 | li.selected { 41 | color: white; 42 | background-color: ${clrs.highlightColor}; 43 | } 44 | 45 | .sdpi-file-label > label:active, 46 | .sdpi-file-label.file:active, 47 | label.sdpi-file-label:active, 48 | label.sdpi-file-info:active, 49 | input[type="file"]::-webkit-file-upload-button:active, 50 | button:active { 51 | border: 1pt solid ${clrs.buttonPressedBorderColor}; 52 | background-color: ${clrs.buttonPressedBackgroundColor}; 53 | color: ${clrs.buttonPressedTextColor}; 54 | border-color: ${clrs.buttonPressedBorderColor}; 55 | } 56 | 57 | ::-webkit-progress-value, 58 | meter::-webkit-meter-optimum-value { 59 | background: linear-gradient(${clr2}, ${clr1} 20%, ${clr} 45%, ${clr} 55%, ${clr2}) 60 | } 61 | 62 | ::-webkit-progress-value:active, 63 | meter::-webkit-meter-optimum-value:active { 64 | background: linear-gradient(${clr}, ${clr2} 20%, ${metersActiveColor} 45%, ${metersActiveColor} 55%, ${clr}) 65 | } 66 | `; 67 | document.body.appendChild(node); 68 | }; 69 | -------------------------------------------------------------------------------- /Sources/common/timers.js: -------------------------------------------------------------------------------- 1 | /* global ESDTimerWorker */ 2 | /*eslint no-unused-vars: "off"*/ 3 | /*eslint-env es6*/ 4 | 5 | let ESDTimerWorker = new Worker(URL.createObjectURL( 6 | new Blob([timerFn.toString().replace(/^[^{]*{\s*/, '').replace(/\s*}[^}]*$/, '')], {type: 'text/javascript'}) 7 | )); 8 | ESDTimerWorker.timerId = 1; 9 | ESDTimerWorker.timers = {}; 10 | const ESDDefaultTimeouts = { 11 | timeout: 0, 12 | interval: 10 13 | }; 14 | 15 | Object.freeze(ESDDefaultTimeouts); 16 | 17 | function _setTimer(callback, delay, type, params) { 18 | const id = ESDTimerWorker.timerId++; 19 | ESDTimerWorker.timers[id] = {callback, params}; 20 | ESDTimerWorker.onmessage = (e) => { 21 | if(ESDTimerWorker.timers[e.data.id]) { 22 | if(e.data.type === 'clearTimer') { 23 | delete ESDTimerWorker.timers[e.data.id]; 24 | } else { 25 | const cb = ESDTimerWorker.timers[e.data.id].callback; 26 | if(cb && typeof cb === 'function') cb(...ESDTimerWorker.timers[e.data.id].params); 27 | } 28 | } 29 | }; 30 | ESDTimerWorker.postMessage({type, id, delay}); 31 | return id; 32 | } 33 | 34 | function _setTimeoutESD(...args) { 35 | let [callback, delay = 0, ...params] = [...args]; 36 | return _setTimer(callback, delay, 'setTimeout', params); 37 | } 38 | 39 | function _setIntervalESD(...args) { 40 | let [callback, delay = 0, ...params] = [...args]; 41 | return _setTimer(callback, delay, 'setInterval', params); 42 | } 43 | 44 | function _clearTimeoutESD(id) { 45 | ESDTimerWorker.postMessage({type: 'clearTimeout', id}); // ESDTimerWorker.postMessage({type: 'clearInterval', id}); = same thing 46 | delete ESDTimerWorker.timers[id]; 47 | } 48 | 49 | window.setTimeout = _setTimeoutESD; 50 | window.setInterval = _setIntervalESD; 51 | window.clearTimeout = _clearTimeoutESD; //timeout and interval share the same timer-pool 52 | window.clearInterval = _clearTimeoutESD; 53 | 54 | /** This is our worker-code 55 | * It is executed in it's own (global) scope 56 | * which is wrapped above @ `let ESDTimerWorker` 57 | */ 58 | 59 | function timerFn() { 60 | /*eslint indent: ["error", 4, { "SwitchCase": 1 }]*/ 61 | 62 | let timers = {}; 63 | let debug = false; 64 | let supportedCommands = ['setTimeout', 'setInterval', 'clearTimeout', 'clearInterval']; 65 | 66 | function log(e) {console.log('Worker-Info::Timers', timers);} 67 | 68 | function clearTimerAndRemove(id) { 69 | if(timers[id]) { 70 | if(debug) console.log('clearTimerAndRemove', id, timers[id], timers); 71 | clearTimeout(timers[id]); 72 | delete timers[id]; 73 | postMessage({type: 'clearTimer', id: id}); 74 | if(debug) log(); 75 | } 76 | } 77 | 78 | onmessage = function(e) { 79 | // first see, if we have a timer with this id and remove it 80 | // this automatically fulfils clearTimeout and clearInterval 81 | supportedCommands.includes(e.data.type) && timers[e.data.id] && clearTimerAndRemove(e.data.id); 82 | if(e.data.type === 'setTimeout') { 83 | timers[e.data.id] = setTimeout(() => { 84 | postMessage({id: e.data.id}); 85 | clearTimerAndRemove(e.data.id); //cleaning up 86 | }, Math.max(e.data.delay || 0)); 87 | } else if(e.data.type === 'setInterval') { 88 | timers[e.data.id] = setInterval(() => { 89 | postMessage({id: e.data.id}); 90 | }, Math.max(e.data.delay || ESDDefaultTimeouts.interval)); 91 | } 92 | }; 93 | } 94 | -------------------------------------------------------------------------------- /Sources/action/js/clock.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple Clock with adjustable colors (inspired by kirupa: https://www.kirupa.com/html5/create_an_analog_clock_using_the_canvas.htm) 3 | * @param {canvas} canvas an existing canvas element in the DOM 4 | */ 5 | 6 | function Clock(canvas) { 7 | if(!canvas) return; 8 | var ctx = canvas.getContext('2d'); 9 | var clockRadius = canvas.width / 2; 10 | var clockX = canvas.width / 2; 11 | var clockY = canvas.height / 2; 12 | var clockEnd = 3 * Math.PI / 2; 13 | var clockStart = -Math.PI / 2; 14 | var twopi = 2 * Math.PI; 15 | 16 | var colors = {}; 17 | var currentPhase = "WORK"; 18 | var expirationDate = null; 19 | var totalDuration = null; 20 | var savedRemainingSeconds = 0; 21 | 22 | var blinkCounter = 0; 23 | 24 | resetColors(); 25 | 26 | function resetColors() { 27 | setColors({ 28 | hour: "#efefef", 29 | minute: "#cccccc", 30 | second: "#ff9933", 31 | stroke: "#cccccc", 32 | background: "#000000" 33 | }); 34 | } 35 | 36 | function start(duration, phase) { 37 | var newDate = new Date() 38 | newDate.setSeconds(newDate.getSeconds() + duration) 39 | expirationDate = newDate 40 | totalDuration = duration 41 | currentPhase = phase 42 | } 43 | 44 | function stop() { 45 | expirationDate = null 46 | totalDuration = null 47 | savedRemainingSeconds = 0 48 | } 49 | 50 | function pause() { 51 | savedRemainingSeconds = remainingSeconds() 52 | } 53 | 54 | function unpause() { 55 | var newDate = new Date() 56 | newDate.setSeconds(newDate.getSeconds() + savedRemainingSeconds) 57 | expirationDate = newDate 58 | savedRemainingSeconds = 0 59 | } 60 | 61 | function remainingSeconds() { 62 | if (savedRemainingSeconds > 0) { 63 | return savedRemainingSeconds 64 | } else { 65 | var now = new Date(); 66 | return (expirationDate - now) / 1000; 67 | } 68 | } 69 | 70 | function drawBlink() { 71 | // Blinking state alternates between background and stroke color, nominally every 1s 72 | ctx.fillStyle = blinkCounter % 2 == 0 ? colors.stroke : (colors.background == 'transparent' ? '#000000' : colors.background); 73 | ctx.fillRect(0, 0, canvas.width, canvas.height); 74 | 75 | var color = blinkCounter % 2 == 1 ? colors.stroke : (colors.background == 'transparent' ? '#000000' : colors.background) 76 | drawWords(null, currentPhase, "OVER", color) 77 | 78 | blinkCounter += 1 79 | return 0 80 | } 81 | 82 | function drawNextPhasePreview(nextPhase) { 83 | if (colors.background == "transparent") { 84 | ctx.clearRect(0, 0, canvas.width, canvas.height); 85 | } else { 86 | ctx.fillStyle = colors.background; 87 | ctx.fillRect(0, 0, canvas.width, canvas.height); 88 | } 89 | 90 | ctx.beginPath(); 91 | ctx.arc(clockX, clockY, 60, clockStart, clockEnd, false); 92 | ctx.lineWidth = 15; 93 | ctx.strokeStyle = colors.stroke; 94 | ctx.stroke(); 95 | 96 | drawWords("NEXT:", nextPhase, null) 97 | } 98 | 99 | function drawClock() { 100 | var seconds = remainingSeconds() 101 | 102 | if (colors.background == "transparent") { 103 | ctx.clearRect(0, 0, canvas.width, canvas.height); 104 | } else { 105 | ctx.fillStyle = colors.background; 106 | ctx.fillRect(0, 0, canvas.width, canvas.height); 107 | } 108 | 109 | ctx.beginPath(); 110 | if (!totalDuration) { 111 | ctx.arc(clockX, clockY, 60, clockStart, clockEnd, false); 112 | } else { 113 | ctx.arc(clockX, clockY, 60, clockEnd - (twopi * seconds/totalDuration), clockStart, false); 114 | } 115 | ctx.lineWidth = 15; 116 | ctx.strokeStyle = colors.stroke; 117 | ctx.stroke(); 118 | 119 | drawWords(savedRemainingSeconds > 0 ? "PAUSE": null, currentPhase, null) 120 | 121 | return seconds >= 0 ? seconds : 0; 122 | } 123 | 124 | function drawWords(prefix, phase, suffix, fillOverride) { 125 | ctx.fillStyle = fillOverride || colors.stroke; 126 | ctx.font = "bold 20px arial"; 127 | ctx.textBaseline = 'middle'; 128 | ctx.textAlign = 'center'; 129 | 130 | if (prefix) { 131 | ctx.fillText(prefix, clockX, clockY-20) 132 | } 133 | 134 | if (phase.indexOf('_') > 0) { 135 | const parts = phase.split('_') 136 | if (suffix) { 137 | ctx.fillText(parts[0], clockX, clockY-20) 138 | ctx.fillText(parts[1], clockX, clockY) 139 | ctx.fillText(suffix, clockX, clockY+20) 140 | } else { 141 | ctx.fillText(parts[0], clockX, clockY) 142 | ctx.fillText(parts[1], clockX, clockY+20) 143 | } 144 | } else { 145 | ctx.fillText(phase, clockX, clockY) 146 | if (suffix) { 147 | ctx.fillText(suffix, clockX, clockY+20) 148 | } 149 | } 150 | } 151 | 152 | function setColors(jsnColors) { 153 | (typeof jsnColors === 'object') && Object.keys(jsnColors).map(c => colors[c] = jsnColors[c]); 154 | } 155 | 156 | function getImageData() { 157 | return canvas.toDataURL(); 158 | } 159 | 160 | function setOrientation(orientation) { 161 | if (orientation === 'bottom') { 162 | clockEnd = Math.PI / 2; 163 | clockStart = -3 * Math.PI / 2; 164 | } else { 165 | clockEnd = 3 * Math.PI / 2; 166 | clockStart = -Math.PI / 2; 167 | } 168 | } 169 | 170 | return { 171 | drawClock: drawClock, 172 | getImageData: getImageData, 173 | setColors: setColors, 174 | colors: colors, 175 | resetColors: resetColors, 176 | start: start, 177 | stop: stop, 178 | remainingSeconds: remainingSeconds, 179 | drawBlink: drawBlink, 180 | currentPhase: currentPhase, 181 | drawNextPhasePreview: drawNextPhasePreview, 182 | pause: pause, 183 | unpause: unpause, 184 | setOrientation: setOrientation 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /Sources/propertyinspector/index_pi.js: -------------------------------------------------------------------------------- 1 | /* global addDynamicStyles, $SD, Utils */ 2 | /* eslint-disable no-extra-boolean-cast */ 3 | 4 | var onchangeevt = 'onchange'; // 'oninput'; // change this, if you want interactive elements act on any change, or while they're modified 5 | // cache some nodes to improve performance 6 | let sdpiWrapper = document.querySelector('.sdpi-wrapper'); 7 | 8 | $SD.on('connected', (jsn) => { 9 | addDynamicStyles($SD.applicationInfo.colors, 'connectElgatoStreamDeckSocket'); 10 | sendValueToPlugin('propertyInspectorConnected', 'property_inspector'); 11 | sdpiWrapper.innerHTML = ''; 12 | 13 | new MutationObserver(function () { 14 | prepareDOMElements(); 15 | }).observe(sdpiWrapper, { 16 | attributes: true, 17 | childList: true, 18 | characterData: true 19 | }); 20 | }); 21 | 22 | $SD.on('sendToPropertyInspector', (jsn) => { 23 | const pl = jsn.payload; 24 | if (pl.hasOwnProperty('error')) { 25 | sdpiWrapper.innerHTML = `
26 |
27 | ${pl.error} 28 | ${pl.hasOwnProperty('info') ? pl.info : ''} 29 |
30 |
`; 31 | } else if (pl.hasOwnProperty('propertyInspectorDataAsString')) { 32 | const props = Utils.parseJson(jsn.payload.propertyInspectorDataAsString); 33 | sdpiWrapper.innerHTML = ''; 34 | Object.entries(props).map(e => sdpiCreateItem(e[1], sdpiWrapper)); 35 | } 36 | }); 37 | 38 | function initPropertyInspector (initDelay) { 39 | sdpiWrapper = sdpiWrapper || document.querySelector('.sdpi-wrapper'); 40 | prepareDOMElements(document); 41 | } 42 | 43 | // eslint-disable-next-line no-unused-vars 44 | function revealSdpiWrapper () { 45 | sdpiWrapper && sdpiWrapper.classList.remove('hidden'); 46 | } 47 | 48 | // our method to pass values to the plugin 49 | function sendValueToPlugin (value, param) { 50 | // console.log($SD, $SD.readyState, $SD.actionInfo, $SD.uuid, param, value); 51 | if ($SD.connection && ($SD.connection.readyState === 1)) { 52 | const json = { 53 | 'action': $SD.actionInfo['action'], 54 | 'event': 'sendToPlugin', 55 | 'context': $SD.uuid, 56 | 'payload': { 57 | [param]: value 58 | } 59 | }; 60 | $SD.connection.send(JSON.stringify(json)); 61 | } 62 | } 63 | 64 | if (!isQT) { 65 | document.addEventListener('DOMContentLoaded', function () { 66 | initPropertyInspector(100); 67 | }); 68 | } 69 | document.addEventListener('DOMContentLoaded', function () { 70 | document.body.classList.add(navigator.userAgent.includes("Mac") ? 'mac' : 'win'); 71 | }); 72 | 73 | /** the beforeunload event is fired, right before the PI will remove all nodes */ 74 | window.addEventListener('beforeunload', function (e) { 75 | e.preventDefault(); 76 | sendValueToPlugin('propertyInspectorWillDisappear', 'property_inspector'); 77 | // Don't set a returnValue to the event, otherwise Chromium with throw an error. // e.returnValue = ''; 78 | }); 79 | 80 | /** CREATE INTERACTIVE HTML-DOM 81 | * where elements can be clicked or act on their 'change' event. 82 | * Messages are then processed using the 'handleSdpiItemClick' method below. 83 | */ 84 | 85 | function prepareDOMElements (baseElement) { 86 | 87 | baseElement = baseElement || document; 88 | Array.from(baseElement.querySelectorAll('.sdpi-item-value')).forEach((el, i) => { 89 | const elementsToClick = ['BUTTON', 'OL', 'UL', 'TABLE', 'METER', 'PROGRESS', 'CANVAS'].includes(el.tagName); 90 | const evt = elementsToClick ? 'onclick' : onchangeevt || 'onchange'; 91 | 92 | /** Look for combinations, where we consider the span as label for the input 93 | * we don't use `labels` for that, because a range could have 2 labels. 94 | */ 95 | const inputGroup = el.querySelectorAll('input, span'); 96 | if (inputGroup.length === 2) { 97 | const offs = inputGroup[0].tagName === 'INPUT' ? 1 : 0; 98 | inputGroup[offs].innerText = inputGroup[1 - offs].value; 99 | inputGroup[1 - offs]['oninput'] = function () { 100 | inputGroup[offs].innerText = inputGroup[1 - offs].value; 101 | }; 102 | } 103 | /** We look for elements which have an 'clickable' attribute 104 | * we use these e.g. on an 'inputGroup' () to adjust the value of 105 | * the corresponding range-control 106 | */ 107 | Array.from(el.querySelectorAll('.clickable')).forEach((subel, subi) => { 108 | subel['onclick'] = function (e) { 109 | handleSdpiItemClick(e.target, subi); 110 | }; 111 | }); 112 | const cloneEvt = el[evt]; 113 | el[evt] = function (e) { 114 | if (cloneEvt) cloneEvt() 115 | handleSdpiItemClick(e.target, i); 116 | }; 117 | }); 118 | 119 | baseElement.querySelectorAll('textarea').forEach(e => { 120 | const maxl = e.getAttribute('maxlength'); 121 | e.targets = baseElement.querySelectorAll(`[for='${e.id}']`); 122 | if (e.targets.length) { 123 | let fn = () => { 124 | for (let x of e.targets) { 125 | x.innerText = maxl ? `${e.value.length}/${maxl}` : `${e.value.length}`; 126 | } 127 | }; 128 | fn(); 129 | e.onkeyup = fn; 130 | } 131 | }); 132 | 133 | baseElement.querySelectorAll('[data-open-url').forEach(e => { 134 | const value = e.getAttribute('data-open-url'); 135 | e.innerText = e.innerText.lox(); 136 | if (value) { 137 | e.onclick = () => { 138 | let path; 139 | if (value.indexOf('http') !== 0) { 140 | path = document.location.href.split('/'); 141 | path.pop(); 142 | path.push(value.split('/').pop()); 143 | path = path.join('/'); 144 | } else { 145 | path = value; 146 | } 147 | $SD.api.openUrl($SD.uuid, path); 148 | }; 149 | } else { 150 | console.log(`${value} is not a supported url`); 151 | } 152 | }); 153 | } 154 | 155 | function handleSdpiItemClick (e, idx) { 156 | /** Following items are containers, so we won't handle clicks on them */ 157 | if (['OL', 'UL', 'TABLE'].includes(e.tagName)) { return; } 158 | // console.log('--- handleSdpiItemClick ---', e, `type: ${e.type}`, e.tagName, `inner: ${e.innerText}`); 159 | 160 | /** SPANS are used inside a control as 'labels' 161 | * If a SPAN element calls this function, it has a class of 'clickable' set and is thereby handled as 162 | * clickable label. 163 | */ 164 | 165 | if (e.tagName === 'SPAN') { 166 | const inp = e.parentNode.querySelector('input'); 167 | if (inp && e.hasAttribute('value')) { 168 | inp.value = e.getAttribute('value'); 169 | } else return; 170 | } 171 | 172 | const selectedElements = []; 173 | const isList = ['LI', 'OL', 'UL', 'DL', 'TD'].includes(e.tagName); 174 | const sdpiItem = e.closest('.sdpi-item'); 175 | const sdpiItemGroup = e.closest('.sdpi-item-group'); 176 | let sdpiItemChildren = isList ? sdpiItem.querySelectorAll((e.tagName === 'LI' ? 'li' : 'td')) : sdpiItem.querySelectorAll('.sdpi-item-child > input'); 177 | 178 | if (isList) { 179 | const siv = e.closest('.sdpi-item-value'); 180 | if (!siv.classList.contains('multi-select')) { 181 | for (let x of sdpiItemChildren) x.classList.remove('selected'); 182 | } 183 | if (!siv.classList.contains('no-select')) { 184 | e.classList.toggle('selected'); 185 | } 186 | } 187 | 188 | if (sdpiItemGroup && !sdpiItemChildren.length) { 189 | for (let x of ['input', 'meter', 'progress']) { 190 | sdpiItemChildren = sdpiItemGroup.querySelectorAll(x); 191 | if (sdpiItemChildren.length) break; 192 | } 193 | }; 194 | 195 | if (e.selectedIndex) { 196 | idx = e.selectedIndex; 197 | } else { 198 | sdpiItemChildren.forEach((ec, i) => { 199 | if (ec.classList.contains('selected')) { 200 | selectedElements.push(ec.innerText); 201 | }; 202 | if (ec === e) { 203 | idx = i; 204 | selectedElements.push(ec.value); 205 | }; 206 | }); 207 | }; 208 | 209 | const returnValue = { 210 | key: e.id && e.id.charAt(0) !== '_' ? e.id : sdpiItem.id, 211 | value: isList ? e.innerText : e.hasAttribute('_value') ? e.getAttribute('_value') : (e.value ? (e.type === 'file' ? decodeURIComponent(e.value.replace(/^C:\\fakepath\\/, '')) : e.value) : e.getAttribute('value')), 212 | group: sdpiItemGroup ? sdpiItemGroup.id : false, 213 | index: idx, 214 | selection: selectedElements, 215 | checked: e.checked 216 | }; 217 | 218 | /** Just simulate the original file-selector: 219 | * If there's an element of class '.sdpi-file-info' 220 | * show the filename there 221 | */ 222 | if (e.type === 'file') { 223 | const info = sdpiItem.querySelector('.sdpi-file-info'); 224 | if (info) { 225 | const s = returnValue.value.split('/').pop(); 226 | info.innerText = s.length > 28 ? s.substr(0, 10) + '...' + s.substr(s.length - 10, s.length) : s; 227 | } 228 | } 229 | // console.log(returnValue); 230 | sendValueToPlugin(returnValue, 'sdpi_collection'); 231 | } 232 | 233 | /** TEMPLATES */ 234 | 235 | const createSdpiItem = (obj) => { 236 | obj.id = obj['id'] || Utils.randomString(8); // window.btoa(new Date().getTime().toString()).substr(4, 16); 237 | obj.rnd = `_${Utils.randomString(8)}_`; 238 | const sdpiItemValue = getTemplate(obj); 239 | 240 | return `
241 |
${obj.label.lox() || ''}
242 | ${sdpiItemValue} 243 |
`; 244 | }; 245 | 246 | // eslint-disable-next-line no-unused-vars 247 | function toggleDisabled (e) { 248 | const el = event.target; 249 | const opt = el[el.selectedIndex].innerText; 250 | const dis = opt.indexOf('') === 0; 251 | if (dis) { 252 | el.classList.add('disabled'); 253 | } else { 254 | el.classList.remove('disabled'); 255 | } 256 | } 257 | 258 | // eslint-disable-next-line no-unused-vars 259 | const isDisabled = (obj, isArr = 1) => { 260 | console.log('isDisabled', Object.entries(obj.value)[obj.selected][1 - isArr].indexOf('') === 0); 261 | }; 262 | 263 | const calcRangeWidth = (obj) => { 264 | let styl = ''; 265 | if (!!obj.minLabel || !!obj.maxLabel) { 266 | const mt = Math.floor(Utils.measureText(`${obj.minLabel}${obj.maxLabel}`)); 267 | if (mt > 0) { 268 | let mrgin = !!obj.minLabel ? 4 : 0; 269 | mrgin = !!obj.maxLabel ? mrgin+4 : mrgin; 270 | const w = 224 - mt - mrgin; 271 | styl = `style="max-width: ${w}px"`; 272 | } 273 | } 274 | return styl; 275 | }; 276 | 277 | /* eslint no-unreachable: 0 */ 278 | function getTemplate (obj) { 279 | const isArr = Number(Array.isArray(obj.value)); 280 | switch (obj.type) { 281 | case 'select': 282 | const isDisabledItem = Object.entries(obj.value)[obj.selected][isArr].indexOf('') === 0 ? 'disabled' : ''; 283 | return ``; 284 | break; 285 | 286 | case 'radio': 287 | case 'checkbox': 288 | return `
${Object.entries(obj.value).map((e, i) => `
289 | 290 | 291 |
`).join('')}
`; 292 | break; 293 | 294 | case 'list': 295 | return `
296 |
${obj.label || ''}
297 |
    298 | ${obj.value.map(e => `
  • ${e}
  • `).join('')} 299 |
300 |
`; 301 | break; 302 | 303 | case 'range': 304 | const styl = calcRangeWidth(obj); 305 | if (obj.reverse) { 306 | return `
307 | ${!!obj.maxLabel ? `${obj.maxLabel || obj.value.max || 100}` : ''} 308 | 309 | ${!!obj.minLabel ? `${obj.minLabel || obj.value.min || 0}` : ''} 310 |
`; 311 | } else { 312 | return `
313 | ${!!obj.minLabel ? `${obj.minLabel || obj.value.min || 0}` : ''} 314 | 315 | ${!!obj.maxLabel ? `${obj.maxLabel || obj.value.max || 100}` : ''} 316 |
`; 317 | } 318 | break; 319 | 320 | case 'range reverse': 321 | 322 | break; 323 | 324 | default: 325 | return ``; 326 | break; 327 | } 328 | }; 329 | 330 | function sdpiCreateItem (obj, el, cb) { 331 | if (el) { 332 | const newEl = document.createElement('div'); 333 | newEl.innerHTML = createSdpiItem(obj); 334 | el.appendChild(newEl.firstChild); 335 | } 336 | } 337 | 338 | // eslint-disable-next-line no-unused-vars 339 | function localizeUI () { 340 | const el = document.querySelector('.sdpi-wrapper'); 341 | Array.from(el.querySelectorAll('sdpi-item-label')).forEach(e => { 342 | e.innerHTML = e.innerHTML.replace(e.innerText, localize(e.innerText)); 343 | }); 344 | Array.from(el.querySelectorAll('*:not(script)')).forEach(e => { 345 | if (e.childNodes && e.childNodes.length > 0 && e.childNodes[0].nodeValue && typeof e.childNodes[0].nodeValue === 'string') { 346 | e.childNodes[0].nodeValue = localize(e.childNodes[0].nodeValue); 347 | } 348 | }); 349 | }; 350 | -------------------------------------------------------------------------------- /Sources/js/app.js: -------------------------------------------------------------------------------- 1 | /* global $SD */ 2 | $SD.on('connected', conn => connected(conn)); 3 | 4 | function connected(jsn) { 5 | debugLog('Connected Plugin:', jsn); 6 | 7 | /** subscribe to the willAppear event */ 8 | $SD.on('com.gallowaylabs.tomato.clock.willAppear', jsonObj => 9 | action.onWillAppear(jsonObj) 10 | ); 11 | $SD.on('com.gallowaylabs.tomato.clock.didReceiveSettings', jsonObj => 12 | action.onDidReceiveSettings(jsonObj) 13 | ); 14 | $SD.on('com.gallowaylabs.tomato.clock.willDisappear', jsonObj => 15 | action.onWillDisappear(jsonObj) 16 | ); 17 | $SD.on('com.gallowaylabs.tomato.clock.keyDown', jsonObj => 18 | action.onKeyDown(jsonObj) 19 | ); 20 | $SD.on('com.gallowaylabs.tomato.clock.keyUp', jsonObj => 21 | action.onKeyUp(jsonObj) 22 | ); 23 | } 24 | 25 | var action = { 26 | type: 'com.gallowaylabs.tomato.clock', 27 | cache: {}, 28 | 29 | onDidReceiveSettings: function(jsn) { 30 | console.log("onDidReceiveSettings", jsn); 31 | 32 | const settings = jsn.payload.settings; 33 | const instance = this.cache[jsn.context]['tomato']; 34 | 35 | if (!settings || !instance) return; 36 | 37 | instance.setCachedSettings(settings); 38 | 39 | if (settings.hasOwnProperty('clock_index')) { 40 | instance.setClockFaceNum(Number(settings.clock_index)); 41 | } 42 | if (settings.hasOwnProperty('alarm_filename')) { 43 | instance.setAlarmFileName(settings.alarm_filename); 44 | } 45 | if (settings.hasOwnProperty('alarm_volume')) { 46 | instance.setAlarmVolume(settings.alarm_volume) 47 | } 48 | if (settings.hasOwnProperty('alarm_loop_enable')) { 49 | instance.setAlarmLoop(settings.alarm_loop_enable) 50 | } 51 | if (settings.hasOwnProperty('clock_type')) { 52 | instance.setClockType(settings.clock_type); 53 | } 54 | if (settings.hasOwnProperty('work_time')) { 55 | instance.setWorkTime(parseInt(settings.work_time) * 60) 56 | } 57 | if (settings.hasOwnProperty('short_break_time')) { 58 | instance.setShortBreakTime(parseInt(settings.short_break_time) * 60) 59 | } 60 | if (settings.hasOwnProperty('long_break_time')) { 61 | instance.setLongBreakTime(parseInt(settings.long_break_time) * 60) 62 | } 63 | if (settings.hasOwnProperty('disable_blink')) { 64 | instance.setBlinkDisabled(settings.disable_blink) 65 | } 66 | if (settings.hasOwnProperty('expire_action')) { 67 | instance.setExpireAction(settings.expire_action) 68 | } 69 | if (settings.hasOwnProperty('state')) { 70 | instance.setState(settings.state) 71 | } 72 | if (settings.hasOwnProperty('orientation')) { 73 | instance.setOrientation(settings.orientation) 74 | } 75 | }, 76 | 77 | onWillAppear: function(jsn) { 78 | if (!jsn.payload || !jsn.payload.hasOwnProperty('settings')) return; 79 | 80 | console.log('onWillAppear', jsn); 81 | 82 | let instance = this.cache[jsn.context]?.['tomato']; 83 | if (!instance) { 84 | this.cache[jsn.context] = { 85 | tomato: new Tomato(jsn.context) 86 | } 87 | this.onDidReceiveSettings(jsn); 88 | } else { 89 | // Force a redraw. This event is fired both on brand new instances, a prexisting 90 | // instance becoming visible again, and the instance being deleted 91 | // an re-added to the same position on the SD 92 | instance.drawClock() 93 | } 94 | }, 95 | 96 | onWillDisappear: function(jsn) { 97 | // Likely a no-op, we want to keep the Tomato instance to keep the timers running 98 | console.log('onWillDisappear', jsn) 99 | }, 100 | 101 | onKeyDown: function(jsn) { 102 | const instance = this.cache[jsn.context]['tomato']; 103 | /** Edge case +++ */ 104 | if (!instance) this.onWillAppear(jsn); 105 | 106 | this.cache[jsn.context]['buttonCheck'] = true 107 | 108 | setTimeout(function() { 109 | if (this.cache[jsn.context]['buttonCheck']) { 110 | this.cache[jsn.context]['skipNext'] = true 111 | instance.skipToNext() 112 | } 113 | }.bind(this), 1750) 114 | 115 | }, 116 | 117 | onKeyUp: function(jsn) { 118 | const instance = this.cache[jsn.context]['tomato']; 119 | this.cache[jsn.context]['buttonCheck'] = false 120 | 121 | if (this.cache[jsn.context]['skipNext']) { 122 | this.cache[jsn.context]['skipNext'] = false 123 | } else { 124 | /** Edge case +++ */ 125 | if (!instance) this.onWillAppear(jsn); 126 | else instance.buttonPressed(); 127 | } 128 | } 129 | 130 | }; 131 | 132 | class Tomato { 133 | constructor(context) { 134 | this.context = context 135 | this.canvas = document.createElement('canvas') 136 | this.canvas.width = 144; 137 | this.canvas.height = 144; 138 | 139 | this.clock = new Clock(this.canvas); 140 | this.clockface = clockfaces[0] 141 | this.clock.setColors(this.clockface.colors); 142 | 143 | this.interval = 0 144 | this.cycleCounter = 0 145 | this.audioElement = null 146 | this.volume = 1 147 | this.cachedSettings = {} 148 | this.expireAction = "2_press" // 2_press, 1_press, auto 149 | this.autostartTimeout = null 150 | 151 | this.config = { 152 | workTime: 25 * 60, 153 | shortBreakTime: 5 * 60, 154 | longBreakTime: 25 * 60, 155 | alarmFileName: null, 156 | alarmLoop: false, 157 | blinkDisabled: false, 158 | } 159 | 160 | // PAUSED: between phases. 161 | // RUNNING: timer is counting down in a phase 162 | // ALARMING: timer has run out and is alerting the user about this 163 | this.state = 'PAUSED' 164 | 165 | this.phase = null 166 | this.nextPhase = this.workPhase() 167 | 168 | this.drawClock() 169 | } 170 | 171 | workPhase() { 172 | return { 173 | name: "WORK", 174 | duration: this.config.workTime, 175 | } 176 | } 177 | 178 | shortBreakPhase() { 179 | return { 180 | name: "BREAK", 181 | duration: this.config.shortBreakTime, 182 | } 183 | } 184 | 185 | longBreakPhase() { 186 | return { 187 | name: "LONG_BREAK", 188 | duration: this.config.longBreakTime, 189 | } 190 | } 191 | 192 | buttonPressed() { 193 | if (this.state == 'ALARMING') { 194 | this.alarmAcknowledged() 195 | } else if (this.state == 'RUNNING') { 196 | this.pause() 197 | } else if (this.state == 'MIDPHASE_PAUSE') { 198 | this.unpause() 199 | } else if (this.state == 'PAUSED') { 200 | this.start() 201 | } 202 | } 203 | 204 | start() { 205 | if (this.state == "RUNNING") { 206 | // Avoid double start with auto mode 207 | return 208 | } 209 | 210 | this.state = "RUNNING" 211 | this.phase = this.nextPhase 212 | this.clock.start(this.phase.duration, this.phase.name) 213 | 214 | if (this.phase.name == 'WORK') { 215 | if (this.cycleCounter == 4) { 216 | this.nextPhase = this.longBreakPhase() 217 | this.cycleCounter = 0; 218 | } else { 219 | this.nextPhase = this.shortBreakPhase() 220 | this.cycleCounter += 1; 221 | } 222 | } else { 223 | this.nextPhase = this.workPhase() 224 | } 225 | 226 | if (this.interval) { 227 | // Auto mode rollover will still have the interval set 228 | window.clearInterval(this.interval) 229 | } 230 | 231 | this.interval = window.setInterval(function(sx) { 232 | var remainingSeconds = this.drawClock(); 233 | if (remainingSeconds <= 0) { 234 | this.timerExpired() 235 | } 236 | }.bind(this), 1000); 237 | } 238 | 239 | pause() { 240 | this.state = "MIDPHASE_PAUSE" 241 | this.clock.pause() 242 | } 243 | 244 | unpause() { 245 | this.state = "RUNNING" 246 | this.clock.unpause() 247 | } 248 | 249 | alarmAcknowledged() { 250 | this.state = "PAUSED" 251 | 252 | window.clearInterval(this.interval); 253 | this.interval = 0; 254 | this.drawClock() 255 | 256 | if (this.audioElement) { 257 | this.audioElement.pause() 258 | } 259 | 260 | this.saveState() 261 | 262 | if (this.expireAction == '1_press' || this.expireAction == 'auto') { 263 | if (this.autostartTimeout) { 264 | window.clearTimeout(this.autostartTimeout) 265 | this.autostartTimeout = 0 266 | } 267 | this.start() 268 | } 269 | 270 | return; 271 | } 272 | 273 | timerExpired() { 274 | this.clock.stop() 275 | window.clearInterval(this.interval) 276 | this.interval = 0 277 | this.state = "ALARMING" 278 | 279 | if (this.config.blinkDisabled) { 280 | // Draw it once to show the "expired" image 281 | this.drawClock() 282 | } else { 283 | this.interval = setInterval(function(sx) { 284 | // Redraw it every second, as the background/text color alternates every time 285 | this.drawClock() 286 | }.bind(this), 1000); 287 | } 288 | 289 | if (this.config.alarmFileName) { 290 | this.audioElement = new Audio(this.config.alarmFileName) 291 | this.audioElement.volume = this.volume 292 | this.audioElement.play() 293 | 294 | if (this.config.alarmLoop) { this.audioElement.loop = true } 295 | 296 | if (this.expireAction == 'auto') { 297 | const self = this 298 | if (this.config.alarmLoop) { this.audioElement.loop = false } 299 | this.audioElement.onloadedmetadata = function() { 300 | // Start the next phase when the alarm finishes ringing 301 | self.autostartTimeout = window.setTimeout(function() { 302 | self.start() 303 | }, this.duration * 1000) 304 | }; 305 | } 306 | } else if (this.expireAction == 'auto') { 307 | // No sound effect, so arbitrarily pick 5 seconds before the next phase auto starts 308 | this.autostartTimeout = window.setTimeout(function() { 309 | this.start() 310 | }, 5000) 311 | } 312 | } 313 | 314 | drawClock() { 315 | if (this.state == 'RUNNING' || this.state == "MIDPHASE_PAUSE") { 316 | var remainingSeconds = Math.round(this.clock.drawClock()); 317 | var seconds = ("" + remainingSeconds % 60).padStart(2, 0) 318 | var minutes = Math.floor(remainingSeconds / 60) 319 | 320 | this.clockface.text === true && $SD.api.setTitle(this.context, `${minutes}:${seconds}`, null); 321 | $SD.api.setImage( 322 | this.context, 323 | this.clock.getImageData() 324 | ); 325 | 326 | return remainingSeconds 327 | } else if (this.state == 'ALARMING') { 328 | this.clock.drawBlink() 329 | this.clockface.text === true && $SD.api.setTitle(this.context, "0:00", null); 330 | $SD.api.setImage( 331 | this.context, 332 | this.clock.getImageData() 333 | ); 334 | } else { 335 | this.clock.drawNextPhasePreview(this.nextPhase.name) 336 | var minutes = Math.floor(this.nextPhase.duration / 60) 337 | this.clockface.text === true && $SD.api.setTitle(this.context, `${minutes}:00`, null); 338 | $SD.api.setImage( 339 | this.context, 340 | this.clock.getImageData() 341 | ); 342 | } 343 | 344 | return 0 345 | } 346 | 347 | skipToNext() { 348 | this.clock.stop() 349 | window.clearInterval(this.interval) 350 | this.interval = 0 351 | 352 | if (this.state === 'PAUSED') { 353 | this.phase = this.nextPhase 354 | if (this.phase.name == 'WORK') { 355 | if (this.cycleCounter == 4) { 356 | this.nextPhase = this.longBreakPhase() 357 | this.cycleCounter = 0; 358 | } else { 359 | this.nextPhase = this.shortBreakPhase() 360 | this.cycleCounter += 1; 361 | } 362 | } else { 363 | this.nextPhase = this.workPhase() 364 | } 365 | } 366 | 367 | this.state = 'PAUSED' 368 | 369 | this.saveState() 370 | this.drawClock() 371 | } 372 | 373 | reset() { 374 | this.clock.stop() 375 | window.clearInterval(this.interval) 376 | this.interval = 0 377 | this.state = 'PAUSED' 378 | this.phase = null 379 | this.nextPhase = this.workPhase() 380 | this.cycleCounter = 0 381 | 382 | this.saveState() 383 | this.drawClock() 384 | } 385 | 386 | saveState() { 387 | const state = { 388 | phase: (this.state == 'PAUSED' || this.state == 'ALARMING') ? this.nextPhase.name : this.phase.name, 389 | cycle: this.cycleCounter 390 | } 391 | this.cachedSettings['state'] = state 392 | 393 | $SD.api.setSettings(this.context, this.cachedSettings) 394 | } 395 | 396 | setState(state) { 397 | if (this.state == 'PAUSED') { 398 | this.cycleCounter = state.cycle 399 | 400 | if (state.phase == "WORK") { 401 | this.nextPhase = this.workPhase() 402 | } else if (state.phase == "BREAK") { 403 | this.nextPhase = this.shortBreakPhase() 404 | } else if (state.phase == "LONG_BREAK") { 405 | this.nextPhase = this.longBreakPhase() 406 | } 407 | 408 | this.drawClock() 409 | } 410 | } 411 | 412 | setWorkTime(time) { 413 | this.config.workTime = time || 25 * 60 414 | 415 | if (this.nextPhase.name == "WORK") { 416 | this.nextPhase.duration = this.config.workTime 417 | this.drawClock() 418 | } 419 | } 420 | 421 | setShortBreakTime(time) { 422 | this.config.shortBreakTime = time || 5 * 60 423 | 424 | if (this.nextPhase.name == "BREAK") { 425 | this.nextPhase.duration = this.config.shortBreakTime 426 | this.drawClock() 427 | } 428 | } 429 | 430 | setLongBreakTime(time) { 431 | this.config.longBreakTime = time || 25 * 60 432 | 433 | if (this.nextPhase.name == "LONG_BREAK") { 434 | this.nextPhase.duration = this.config.longBreakTime 435 | this.drawClock() 436 | } 437 | } 438 | 439 | setBlinkDisabled(disabled) { 440 | this.config.blinkDisabled = disabled 441 | } 442 | 443 | setAlarmFileName(name) { 444 | this.config.alarmFileName = name 445 | } 446 | 447 | setAlarmVolume(volume) { 448 | this.volume = volume 449 | } 450 | 451 | setAlarmLoop(enabled) { 452 | this.config.alarmLoop = enabled 453 | } 454 | 455 | setClockFaceNum(idx) { 456 | var newClockFaceIdx = Math.min(Math.max(0, idx), clockfaces.length - 1) 457 | this.clockface = clockfaces[newClockFaceIdx]; 458 | this.clock.setColors(this.clockface.colors); 459 | 460 | this.drawClock(); 461 | } 462 | 463 | setOrientation(orientation) { 464 | this.clock.setOrientation(orientation) 465 | } 466 | 467 | // This is a workaround for $SD.api.setSettings() not working in onWillDisappear. 468 | // Instead, write new settings each time that work/break phase ends 469 | setCachedSettings(settings) { 470 | this.cachedSettings = settings 471 | } 472 | 473 | setExpireAction(action) { 474 | this.expireAction = action 475 | } 476 | } 477 | -------------------------------------------------------------------------------- /Sources/propertyinspector/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.gallowaylabs.tomato PI 6 | 7 | 8 | 9 | 10 | 11 | 106 | 107 | 108 | 109 | 110 | 111 | 351 | 352 | 353 | 354 | 355 | -------------------------------------------------------------------------------- /Sources/common/css/sdpi_win.css: -------------------------------------------------------------------------------- 1 | html { 2 | --sdpi-bgcolor: #2D2D2D; 3 | --sdpi-background: #3D3D3D; 4 | --sdpi-color: #d8d8d8; 5 | --sdpi-bordercolor: #3a3a3a; 6 | --sdpi-borderradius: 0px; 7 | --sdpi-width: 224px; 8 | --sdpi-fontweight: 600; 9 | --sdpi-letterspacing: -0.25pt; 10 | height: 100%; 11 | width: 100%; 12 | overflow: hidden; 13 | } 14 | 15 | html, body { 16 | font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 17 | font-size: 9pt; 18 | background-color: var(--sdpi-bgcolor); 19 | color: #9a9a9a; 20 | } 21 | 22 | body { 23 | height: 100%; 24 | padding: 0; 25 | overflow-x: hidden; 26 | overflow-y: auto; 27 | margin: 0; 28 | -webkit-overflow-scrolling: touch; 29 | -webkit-text-size-adjust: 100%; 30 | -webkit-font-smoothing: antialiased; 31 | -webkit-font-smoothing: subpixel-antialiased; 32 | -webkit-font-smoothing: antialiased; 33 | } 34 | 35 | mark { 36 | background-color: var(--sdpi-bgcolor); 37 | color: var(--sdpi-color); 38 | } 39 | 40 | .hidden { 41 | display: none; 42 | } 43 | 44 | hr, hr2 { 45 | -webkit-margin-before: 1em; 46 | -webkit-margin-after: 1em; 47 | border-style: none; 48 | background: var(--sdpi-background); 49 | height: 1px; 50 | } 51 | 52 | hr2, 53 | .sdpi-heading { 54 | display: flex; 55 | flex-basis: 100%; 56 | align-items: center; 57 | color: inherit; 58 | font-size: 9pt; 59 | margin: 8px 0px; 60 | } 61 | 62 | .sdpi-heading::before, 63 | .sdpi-heading::after { 64 | content: ""; 65 | flex-grow: 1; 66 | background: var(--sdpi-background); 67 | height: 1px; 68 | font-size: 0px; 69 | line-height: 0px; 70 | margin: 0px 16px; 71 | } 72 | 73 | hr2 { 74 | height: 2px; 75 | } 76 | 77 | hr, hr2 { 78 | margin-left:16px; 79 | margin-right:16px; 80 | } 81 | 82 | .sdpi-item-value, 83 | option, 84 | input, 85 | select, 86 | button { 87 | font-size: 10pt; 88 | font-weight: var(--sdpi-fontweight); 89 | letter-spacing: var(--sdpi-letterspacing); 90 | } 91 | 92 | 93 | .win .sdpi-item-value, 94 | .win option, 95 | .win input, 96 | .win select, 97 | .win button { 98 | font-size: 11px; 99 | font-style: normal; 100 | letter-spacing: inherit; 101 | font-weight: 100; 102 | } 103 | 104 | .win button { 105 | font-size: 12px; 106 | } 107 | 108 | ::-webkit-progress-value, 109 | meter::-webkit-meter-optimum-value { 110 | border-radius: 2px; 111 | /* background: linear-gradient(#ccf, #99f 20%, #77f 45%, #77f 55%, #cdf); */ 112 | } 113 | 114 | ::-webkit-progress-bar, 115 | meter::-webkit-meter-bar { 116 | border-radius: 3px; 117 | background: var(--sdpi-background); 118 | } 119 | 120 | ::-webkit-progress-bar:active, 121 | meter::-webkit-meter-bar:active { 122 | border-radius: 3px; 123 | background: #222222; 124 | } 125 | ::-webkit-progress-value:active, 126 | meter::-webkit-meter-optimum-value:active { 127 | background: #99f; 128 | } 129 | 130 | progress, 131 | progress.sdpi-item-value { 132 | min-height: 5px !important; 133 | height: 5px; 134 | background-color: #303030; 135 | } 136 | 137 | progress { 138 | margin-top: 8px !important; 139 | margin-bottom: 8px !important; 140 | } 141 | 142 | .full progress, 143 | progress.full { 144 | margin-top: 3px !important; 145 | } 146 | 147 | ::-webkit-progress-inner-element { 148 | background-color: transparent; 149 | } 150 | 151 | 152 | .sdpi-item[type="progress"] { 153 | margin-top: 4px !important; 154 | margin-bottom: 12px; 155 | min-height: 15px; 156 | } 157 | 158 | .sdpi-item-child.full:last-child { 159 | margin-bottom: 4px; 160 | } 161 | 162 | .tabs { 163 | /** 164 | * Setting display to flex makes this container lay 165 | * out its children using flexbox, the exact same 166 | * as in the above "Stepper input" example. 167 | */ 168 | display: flex; 169 | 170 | border-bottom: 1px solid #D7DBDD; 171 | } 172 | 173 | .tab { 174 | cursor: pointer; 175 | padding: 5px 30px; 176 | color: #16a2d7; 177 | font-size: 9pt; 178 | border-bottom: 2px solid transparent; 179 | } 180 | 181 | .tab.is-tab-selected { 182 | border-bottom-color: #4ebbe4; 183 | } 184 | 185 | select { 186 | -webkit-appearance: none; 187 | -moz-appearance: none; 188 | -o-appearance: none; 189 | appearance: none; 190 | background: url(caret.svg) no-repeat 97% center; 191 | } 192 | 193 | label.sdpi-file-label, 194 | input[type="button"], 195 | input[type="submit"], 196 | input[type="reset"], 197 | input[type="file"], 198 | input[type=file]::-webkit-file-upload-button, 199 | button, 200 | select { 201 | color: var(--sdpi-color); 202 | border: 1pt solid #303030; 203 | font-size: 9pt; 204 | background-color: var(--sdpi-background); 205 | border-radius: var(--sdpi-borderradius); 206 | } 207 | 208 | label.sdpi-file-label, 209 | input[type="button"], 210 | input[type="submit"], 211 | input[type="reset"], 212 | input[type="file"], 213 | input[type=file]::-webkit-file-upload-button, 214 | button { 215 | border: 1pt solid var(--sdpi-color); 216 | border-radius: var(--sdpi-borderradius); 217 | min-height: 23px !important; 218 | height: 23px !important; 219 | margin-right: 8px; 220 | } 221 | 222 | input[type=number]::-webkit-inner-spin-button, 223 | input[type=number]::-webkit-outer-spin-button { 224 | -webkit-appearance: none; 225 | margin: 0; 226 | } 227 | 228 | input[type="file"] { 229 | border-radius: var(--sdpi-borderradius); 230 | max-width: 220px; 231 | } 232 | 233 | option { 234 | height: 1.5em; 235 | padding: 4px; 236 | } 237 | 238 | /* SDPI */ 239 | 240 | .sdpi-wrapper { 241 | overflow-x: hidden; 242 | } 243 | 244 | .sdpi-item { 245 | display: flex; 246 | flex-direction: row; 247 | min-height: 32px; 248 | align-items: center; 249 | margin-top: 2px; 250 | max-width: 344px; 251 | } 252 | 253 | .sdpi-item:first-child { 254 | margin-top:1px; 255 | } 256 | 257 | .sdpi-item:last-child { 258 | margin-bottom: 0px; 259 | } 260 | 261 | .sdpi-item > *:not(.sdpi-item-label):not(meter):not(details) { 262 | min-height: 26px; 263 | padding: 0px 4px 0px 4px; 264 | } 265 | 266 | .sdpi-item > *:not(.sdpi-item-label.empty):not(meter) { 267 | min-height: 26px; 268 | padding: 0px 4px 0px 4px; 269 | } 270 | 271 | 272 | .sdpi-item-group { 273 | padding: 0 !important; 274 | } 275 | 276 | meter.sdpi-item-value { 277 | margin-left: 6px; 278 | } 279 | 280 | .sdpi-item[type="group"] { 281 | display: block; 282 | margin-top: 12px; 283 | margin-bottom: 12px; 284 | /* border: 1px solid white; */ 285 | flex-direction: unset; 286 | text-align: left; 287 | } 288 | 289 | .sdpi-item[type="group"] > .sdpi-item-label, 290 | .sdpi-item[type="group"].sdpi-item-label { 291 | width: 96%; 292 | text-align: left; 293 | font-weight: 700; 294 | margin-bottom: 4px; 295 | padding-left: 4px; 296 | } 297 | 298 | dl, 299 | ul, 300 | ol { 301 | -webkit-margin-before: 0px; 302 | -webkit-margin-after: 4px; 303 | -webkit-padding-start: 1em; 304 | max-height: 90px; 305 | overflow-y: scroll; 306 | cursor: pointer; 307 | user-select: none; 308 | } 309 | 310 | table.sdpi-item-value, 311 | dl.sdpi-item-value, 312 | ul.sdpi-item-value, 313 | ol.sdpi-item-value { 314 | -webkit-margin-before: 4px; 315 | -webkit-margin-after: 8px; 316 | -webkit-padding-start: 1em; 317 | width: var(--sdpi-width); 318 | text-align: center; 319 | } 320 | 321 | table > caption { 322 | margin: 2px; 323 | } 324 | 325 | .list, 326 | .sdpi-item[type="list"] { 327 | align-items: baseline; 328 | } 329 | 330 | .sdpi-item-label { 331 | text-align: right; 332 | flex: none; 333 | width: 94px; 334 | padding-right: 4px; 335 | font-weight: bold; 336 | -webkit-user-select: none; 337 | } 338 | 339 | .win .sdpi-item-label, 340 | .sdpi-item-label > small{ 341 | font-weight: normal; 342 | } 343 | 344 | .sdpi-item-label:after { 345 | content: ": "; 346 | } 347 | 348 | .sdpi-item-label.empty:after { 349 | content: ""; 350 | } 351 | 352 | .sdpi-test, 353 | .sdpi-item-value { 354 | flex: 1 0 0; 355 | /* flex-grow: 1; 356 | flex-shrink: 0; */ 357 | margin-right: 14px; 358 | margin-left: 4px; 359 | justify-content: space-evenly; 360 | } 361 | 362 | canvas.sdpi-item-value { 363 | max-width: 144px; 364 | max-height: 144px; 365 | width: 144px; 366 | height: 144px; 367 | margin: 0 auto; 368 | cursor: pointer; 369 | } 370 | 371 | input.sdpi-item-value { 372 | margin-left: 5px; 373 | } 374 | 375 | .sdpi-item-value button, 376 | button.sdpi-item-value { 377 | margin-left: 7px; 378 | margin-right: 19px; 379 | } 380 | 381 | .sdpi-item-value.range { 382 | margin-left: 0px; 383 | } 384 | 385 | table, 386 | dl.sdpi-item-value, 387 | ul.sdpi-item-value, 388 | ol.sdpi-item-value, 389 | .sdpi-item-value > dl, 390 | .sdpi-item-value > ul, 391 | .sdpi-item-value > ol 392 | { 393 | list-style-type: none; 394 | list-style-position: outside; 395 | margin-left: -4px; 396 | margin-right: -4px; 397 | padding: 4px; 398 | border: 1px solid var(--sdpi-bordercolor); 399 | } 400 | 401 | dl.sdpi-item-value, 402 | ul.sdpi-item-value, 403 | ol.sdpi-item-value, 404 | .sdpi-item-value > ol { 405 | list-style-type: none; 406 | list-style-position: inside; 407 | margin-left: 5px; 408 | margin-right: 12px; 409 | padding: 4px !important; 410 | } 411 | 412 | ol.sdpi-item-value, 413 | .sdpi-item-value > ol[listtype="none"] { 414 | list-style-type: none; 415 | } 416 | ol.sdpi-item-value[type="decimal"], 417 | .sdpi-item-value > ol[type="decimal"] { 418 | list-style-type: decimal; 419 | } 420 | 421 | ol.sdpi-item-value[type="decimal-leading-zero"], 422 | .sdpi-item-value > ol[type="decimal-leading-zero"] { 423 | list-style-type: decimal-leading-zero; 424 | } 425 | 426 | ol.sdpi-item-value[type="lower-alpha"], 427 | .sdpi-item-value > ol[type="lower-alpha"] { 428 | list-style-type: lower-alpha; 429 | } 430 | 431 | ol.sdpi-item-value[type="upper-alpha"], 432 | .sdpi-item-value > ol[type="upper-alpha"] { 433 | list-style-type: upper-alpha; 434 | } 435 | 436 | ol.sdpi-item-value[type="upper-roman"], 437 | .sdpi-item-value > ol[type="upper-roman"] { 438 | list-style-type: upper-roman; 439 | } 440 | 441 | ol.sdpi-item-value[type="lower-roman"], 442 | .sdpi-item-value > ol[type="lower-roman"] { 443 | list-style-type: upper-roman; 444 | } 445 | 446 | tr:nth-child(even), 447 | .sdpi-item-value > ul > li:nth-child(even), 448 | .sdpi-item-value > ol > li:nth-child(even), 449 | li:nth-child(even) { 450 | background-color: rgba(0,0,0,.2) 451 | } 452 | 453 | td:hover, 454 | .sdpi-item-value > ul > li:hover:nth-child(even), 455 | .sdpi-item-value > ol > li:hover:nth-child(even), 456 | li:hover:nth-child(even), 457 | li:hover { 458 | background-color: rgba(255,255,255,.1); 459 | } 460 | 461 | td.selected, 462 | td.selected:hover, 463 | li.selected:hover, 464 | li.selected { 465 | color: white; 466 | background-color: #77f; 467 | } 468 | 469 | tr { 470 | border: 1px solid var(--sdpi-bordercolor); 471 | } 472 | 473 | td { 474 | border-right: 1px solid var(--sdpi-bordercolor); 475 | -webkit-user-select: none; 476 | } 477 | 478 | tr:last-child, 479 | td:last-child { 480 | border: none; 481 | } 482 | 483 | .sdpi-item-value.select, 484 | .sdpi-item-value > select { 485 | margin-right: 13px; 486 | margin-left: 4px; 487 | } 488 | 489 | .sdpi-item-child, 490 | .sdpi-item-group > .sdpi-item > input[type="color"] { 491 | margin-top: 0.4em; 492 | margin-right: 4px; 493 | } 494 | 495 | .full, 496 | .full *, 497 | .sdpi-item-value.full, 498 | .sdpi-item-child > full > *, 499 | .sdpi-item-child.full, 500 | .sdpi-item-child.full > *, 501 | .full > .sdpi-item-child, 502 | .full > .sdpi-item-child > *{ 503 | display: flex; 504 | flex: 1 1 0; 505 | margin-bottom: 4px; 506 | margin-left: 0px; 507 | width: 100%; 508 | 509 | justify-content: space-evenly; 510 | } 511 | 512 | .sdpi-item-group > .sdpi-item > input[type="color"] { 513 | margin-top: 0px; 514 | } 515 | 516 | ::-webkit-calendar-picker-indicator:focus, 517 | input[type=file]::-webkit-file-upload-button:focus, 518 | button:focus, 519 | textarea:focus, 520 | input:focus, 521 | select:focus, 522 | option:focus, 523 | details:focus, 524 | summary:focus, 525 | .custom-select select { 526 | outline: none; 527 | } 528 | 529 | summary { 530 | cursor: default; 531 | -webkit-user-select: none; 532 | } 533 | 534 | .pointer, 535 | summary .pointer { 536 | cursor: pointer; 537 | } 538 | 539 | details.message { 540 | padding: 4px 18px 4px 12px; 541 | } 542 | 543 | details.message summary { 544 | font-size: 10pt; 545 | font-weight: 600; 546 | min-height: 18px; 547 | } 548 | 549 | details.message:first-child { 550 | margin-top: 4px; 551 | margin-left: 0; 552 | padding-left: 106px; 553 | } 554 | 555 | details.message h1 { 556 | text-align: left; 557 | } 558 | 559 | .message > summary::-webkit-details-marker { 560 | display: none; 561 | } 562 | 563 | .info20, 564 | .question, 565 | .caution, 566 | .info { 567 | background-repeat: no-repeat; 568 | background-position: 70px center; 569 | } 570 | 571 | .info20 { 572 | 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"); 573 | } 574 | 575 | .info { 576 | 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"); 577 | } 578 | 579 | .info2 { 580 | 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"); 581 | } 582 | 583 | .sdpi-more-info { 584 | 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"); 585 | } 586 | .caution { 587 | 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"); 588 | } 589 | 590 | .question { 591 | 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"); 592 | } 593 | 594 | 595 | .sdpi-more-info { 596 | position: fixed; 597 | left: 0px; 598 | right: 0px; 599 | bottom: 0px; 600 | min-height:16px; 601 | padding-right: 16px; 602 | text-align: right; 603 | -webkit-touch-callout: none; 604 | cursor: pointer; 605 | user-select: none; 606 | background-position: right center; 607 | background-repeat: no-repeat; 608 | border-radius: var(--sdpi-borderradius); 609 | text-decoration: none; 610 | color: var(--sdpi-color); 611 | } 612 | 613 | .sdpi-more-info-button { 614 | display: flex; 615 | align-self: right; 616 | margin-left: auto; 617 | position: fixed; 618 | right: 17px; 619 | bottom: 0px; 620 | } 621 | 622 | details a { 623 | background-position: right !important; 624 | min-height: 24px; 625 | display: inline-block; 626 | line-height: 24px; 627 | padding-right: 28px; 628 | } 629 | input:not([type="range"]), 630 | textarea { 631 | -webkit-appearance: none; 632 | background: var(--sdpi-background); 633 | color: var(--sdpi-color); 634 | font-weight: normal; 635 | font-size: 9pt; 636 | border: none; 637 | margin-top: 2px; 638 | margin-bottom: 2px; 639 | } 640 | 641 | textarea + label { 642 | display: flex; 643 | justify-content: flex-end 644 | } 645 | input[type="radio"], 646 | input[type="checkbox"] { 647 | display: none; 648 | } 649 | input[type="radio"] + label, 650 | input[type="checkbox"] + label { 651 | font-size: 9pt; 652 | color: var(--sdpi-color); 653 | font-weight: normal; 654 | margin-right: 8px; 655 | -webkit-user-select: none; 656 | } 657 | 658 | input[type="radio"] + label:after, 659 | input[type="checkbox"] + label:after { 660 | content: " " !important; 661 | } 662 | 663 | .sdpi-item[type="radio"] > .sdpi-item-value, 664 | .sdpi-item[type="checkbox"] > .sdpi-item-value { 665 | padding-top: 2px; 666 | } 667 | 668 | .sdpi-item[type="checkbox"] > .sdpi-item-value > * { 669 | margin-top: 4px; 670 | } 671 | 672 | .sdpi-item[type="checkbox"] .sdpi-item-child, 673 | .sdpi-item[type="radio"] .sdpi-item-child { 674 | display: inline-block; 675 | } 676 | 677 | .sdpi-item[type="range"] .sdpi-item-value, 678 | .sdpi-item[type="meter"] .sdpi-item-child, 679 | .sdpi-item[type="progress"] .sdpi-item-child { 680 | display: flex; 681 | } 682 | 683 | .sdpi-item[type="range"] .sdpi-item-value { 684 | min-height: 26px; 685 | } 686 | 687 | .sdpi-item[type="range"] .sdpi-item-value span, 688 | .sdpi-item[type="meter"] .sdpi-item-child span, 689 | .sdpi-item[type="progress"] .sdpi-item-child span { 690 | margin-top: -2px; 691 | min-width: 8px; 692 | text-align: right; 693 | user-select: none; 694 | cursor: pointer; 695 | } 696 | 697 | .sdpi-item[type="range"] .sdpi-item-value span { 698 | margin-top: 7px; 699 | text-align: right; 700 | } 701 | 702 | span + input[type="range"] { 703 | display: flex; 704 | max-width: 168px; 705 | 706 | } 707 | 708 | .sdpi-item[type="range"] .sdpi-item-value span:first-child, 709 | .sdpi-item[type="meter"] .sdpi-item-child span:first-child, 710 | .sdpi-item[type="progress"] .sdpi-item-child span:first-child { 711 | margin-right: 4px; 712 | } 713 | 714 | .sdpi-item[type="range"] .sdpi-item-value span:last-child, 715 | .sdpi-item[type="meter"] .sdpi-item-child span:last-child, 716 | .sdpi-item[type="progress"] .sdpi-item-child span:last-child { 717 | margin-left: 4px; 718 | } 719 | 720 | .reverse { 721 | transform: rotate(180deg); 722 | } 723 | 724 | .sdpi-item[type="meter"] .sdpi-item-child meter + span:last-child { 725 | margin-left: -10px; 726 | } 727 | 728 | .sdpi-item[type="progress"] .sdpi-item-child meter + span:last-child { 729 | margin-left: -14px; 730 | } 731 | 732 | .sdpi-item[type="radio"] > .sdpi-item-value > * { 733 | margin-top: 2px; 734 | } 735 | 736 | details { 737 | padding: 8px 18px 8px 12px; 738 | min-width: 86px; 739 | } 740 | 741 | details > h4 { 742 | border-bottom: 1px solid var(--sdpi-bordercolor); 743 | } 744 | 745 | legend { 746 | display: none; 747 | } 748 | .sdpi-item-value > textarea { 749 | padding: 0px; 750 | width: 227px; 751 | margin-left: 1px; 752 | } 753 | 754 | input[type="radio"] + label span, 755 | input[type="checkbox"] + label span { 756 | display: inline-block; 757 | width: 16px; 758 | height: 16px; 759 | margin: 2px 4px 2px 0; 760 | border-radius: 3px; 761 | vertical-align: middle; 762 | background: var(--sdpi-background); 763 | cursor: pointer; 764 | border: 1px solid rgb(0,0,0,.2); 765 | } 766 | 767 | input[type="radio"] + label span { 768 | border-radius: 100%; 769 | } 770 | 771 | input[type="radio"]:checked + label span, 772 | input[type="checkbox"]:checked + label span { 773 | background-color: #77f; 774 | background-image: url(check.svg); 775 | background-repeat: no-repeat; 776 | background-position: center center; 777 | border: 1px solid rgb(0,0,0,.4); 778 | } 779 | 780 | input[type="radio"]:active:checked + label span, 781 | input[type="radio"]:active + label span, 782 | input[type="checkbox"]:active:checked + label span, 783 | input[type="checkbox"]:active + label span { 784 | background-color: #303030; 785 | } 786 | 787 | input[type="radio"]:checked + label span { 788 | background-image: url(rcheck.svg); 789 | } 790 | 791 | 792 | /* 793 | input[type="radio"] + label span { 794 | background: url(buttons.png) -38px top no-repeat; 795 | } 796 | 797 | input[type="radio"]:checked + label span { 798 | background: url(buttons.png) -57px top no-repeat; 799 | } 800 | */ 801 | 802 | input[type="range"] { 803 | width: var(--sdpi-width); 804 | height: 30px; 805 | overflow: hidden; 806 | cursor: pointer; 807 | background: transparent !important; 808 | } 809 | 810 | .sdpi-item > input[type="range"] { 811 | margin-left: 8px; 812 | max-width: var(--sdpi-width); 813 | width: var(--sdpi-width); 814 | padding: 0px; 815 | } 816 | 817 | /* 818 | input[type="range"], 819 | input[type="range"]::-webkit-slider-runnable-track, 820 | input[type="range"]::-webkit-slider-thumb { 821 | -webkit-appearance: none; 822 | } 823 | */ 824 | 825 | input[type="range"]::-webkit-slider-runnable-track { 826 | height: 5px; 827 | background: #979797; 828 | border-radius: 3px; 829 | padding:0px !important; 830 | border: 1px solid var(--sdpi-background); 831 | } 832 | 833 | input[type="range"]::-webkit-slider-thumb { 834 | position: relative; 835 | -webkit-appearance: none; 836 | background-color: var(--sdpi-color); 837 | width: 12px; 838 | height: 12px; 839 | border-radius: 20px; 840 | margin-top: -5px; 841 | border: none; 842 | 843 | } 844 | input[type="range" i]{ 845 | margin: 0; 846 | } 847 | 848 | input[type="range"]::-webkit-slider-thumb::before { 849 | position: absolute; 850 | content: ""; 851 | height: 5px; /* equal to height of runnable track or 1 less */ 852 | width: 500px; /* make this bigger than the widest range input element */ 853 | left: -502px; /* this should be -2px - width */ 854 | top: 8px; /* don't change this */ 855 | background: #77f; 856 | } 857 | 858 | input[type="color"] { 859 | min-width: 32px; 860 | min-height: 32px; 861 | width: 32px; 862 | height: 32px; 863 | padding: 0; 864 | background-color: var(--sdpi-bgcolor); 865 | flex: none; 866 | } 867 | 868 | ::-webkit-color-swatch { 869 | min-width: 24px; 870 | } 871 | 872 | textarea { 873 | height: 3em; 874 | word-break: break-word; 875 | line-height: 1.5em; 876 | } 877 | 878 | .textarea { 879 | padding: 0px !important; 880 | } 881 | 882 | textarea { 883 | width: 221px; /*98%;*/ 884 | height: 96%; 885 | min-height: 6em; 886 | resize: none; 887 | border-radius: var(--sdpi-borderradius); 888 | } 889 | 890 | /* CAROUSEL */ 891 | 892 | .sdpi-item[type="carousel"]{ 893 | 894 | } 895 | 896 | .sdpi-item.card-carousel-wrapper, 897 | .sdpi-item > .card-carousel-wrapper { 898 | padding: 0; 899 | } 900 | 901 | 902 | .card-carousel-wrapper { 903 | display: flex; 904 | align-items: center; 905 | justify-content: center; 906 | margin: 12px auto; 907 | color: #666a73; 908 | } 909 | 910 | .card-carousel { 911 | display: flex; 912 | justify-content: center; 913 | width: 278px; 914 | } 915 | .card-carousel--overflow-container { 916 | overflow: hidden; 917 | } 918 | .card-carousel--nav__left, 919 | .card-carousel--nav__right { 920 | /* display: inline-block; */ 921 | width: 12px; 922 | height: 12px; 923 | border-top: 2px solid #42b883; 924 | border-right: 2px solid #42b883; 925 | cursor: pointer; 926 | margin: 0 4px; 927 | transition: transform 150ms linear; 928 | } 929 | .card-carousel--nav__left[disabled], 930 | .card-carousel--nav__right[disabled] { 931 | opacity: 0.2; 932 | border-color: black; 933 | } 934 | .card-carousel--nav__left { 935 | transform: rotate(-135deg); 936 | } 937 | .card-carousel--nav__left:active { 938 | transform: rotate(-135deg) scale(0.85); 939 | } 940 | .card-carousel--nav__right { 941 | transform: rotate(45deg); 942 | } 943 | .card-carousel--nav__right:active { 944 | transform: rotate(45deg) scale(0.85); 945 | } 946 | .card-carousel-cards { 947 | display: flex; 948 | transition: transform 150ms ease-out; 949 | transform: translatex(0px); 950 | } 951 | .card-carousel-cards .card-carousel--card { 952 | margin: 0 5px; 953 | cursor: pointer; 954 | /* box-shadow: 0 4px 15px 0 rgba(40, 44, 53, 0.06), 0 2px 2px 0 rgba(40, 44, 53, 0.08); */ 955 | background-color: #fff; 956 | border-radius: 4px; 957 | z-index: 3; 958 | } 959 | .xxcard-carousel-cards .card-carousel--card:first-child { 960 | margin-left: 0; 961 | } 962 | .xxcard-carousel-cards .card-carousel--card:last-child { 963 | margin-right: 0; 964 | } 965 | .card-carousel-cards .card-carousel--card img { 966 | vertical-align: bottom; 967 | border-top-left-radius: 4px; 968 | border-top-right-radius: 4px; 969 | transition: opacity 150ms linear; 970 | width: 60px; 971 | } 972 | .card-carousel-cards .card-carousel--card img:hover { 973 | opacity: 0.5; 974 | } 975 | .card-carousel-cards .card-carousel--card--footer { 976 | border-top: 0; 977 | max-width: 80px; 978 | overflow: hidden; 979 | display: flex; 980 | height: 100%; 981 | flex-direction: column; 982 | } 983 | .card-carousel-cards .card-carousel--card--footer p { 984 | padding: 3px 0; 985 | margin: 0; 986 | margin-bottom: 2px; 987 | font-size: 15px; 988 | font-weight: 500; 989 | color: #2c3e50; 990 | } 991 | .card-carousel-cards .card-carousel--card--footer p:nth-of-type(2) { 992 | font-size: 12px; 993 | font-weight: 300; 994 | padding: 6px; 995 | color: #666a73; 996 | } 997 | 998 | 999 | h1 { 1000 | font-size: 1.3em; 1001 | font-weight: 500; 1002 | text-align: center; 1003 | margin-bottom: 12px; 1004 | } 1005 | 1006 | ::-webkit-datetime-edit { 1007 | font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 1008 | background: url(elg_calendar_inv.svg) no-repeat left center; 1009 | padding-right: 1em; 1010 | padding-left: 25px; 1011 | background-position: 4px 0px; 1012 | } 1013 | ::-webkit-datetime-edit-fields-wrapper { 1014 | 1015 | } 1016 | ::-webkit-datetime-edit-text { padding: 0 0.3em; } 1017 | ::-webkit-datetime-edit-month-field { } 1018 | ::-webkit-datetime-edit-day-field {} 1019 | ::-webkit-datetime-edit-year-field {} 1020 | ::-webkit-inner-spin-button { 1021 | 1022 | /* display: none; */ 1023 | } 1024 | ::-webkit-calendar-picker-indicator { 1025 | background: transparent; 1026 | font-size: 17px; 1027 | } 1028 | 1029 | ::-webkit-calendar-picker-indicator:focus { 1030 | background-color: rgba(0,0,0,0.2); 1031 | } 1032 | 1033 | input[type="date"] { 1034 | -webkit-align-items: center; 1035 | display: -webkit-inline-flex; 1036 | font-family: monospace; 1037 | overflow: hidden; 1038 | padding: 0; 1039 | -webkit-padding-start: 1px; 1040 | } 1041 | 1042 | input::-webkit-datetime-edit { 1043 | -webkit-flex: 1; 1044 | -webkit-user-modify: read-only !important; 1045 | display: inline-block; 1046 | min-width: 0; 1047 | overflow: hidden; 1048 | } 1049 | 1050 | /* 1051 | input::-webkit-datetime-edit-fields-wrapper { 1052 | -webkit-user-modify: read-only !important; 1053 | display: inline-block; 1054 | padding: 1px 0; 1055 | white-space: pre; 1056 | 1057 | } 1058 | */ 1059 | 1060 | /* 1061 | input[type="date"] { 1062 | background-color: red; 1063 | outline: none; 1064 | } 1065 | 1066 | input[type="date"]::-webkit-clear-button { 1067 | font-size: 18px; 1068 | height: 30px; 1069 | position: relative; 1070 | } 1071 | 1072 | input[type="date"]::-webkit-inner-spin-button { 1073 | height: 28px; 1074 | } 1075 | 1076 | input[type="date"]::-webkit-calendar-picker-indicator { 1077 | font-size: 15px; 1078 | } */ 1079 | 1080 | input[type="file"] { 1081 | opacity: 0; 1082 | display: none; 1083 | } 1084 | 1085 | .sdpi-item > input[type="file"] { 1086 | opacity: 1; 1087 | display: flex; 1088 | } 1089 | 1090 | input[type="file"] + span { 1091 | display: flex; 1092 | flex: 0 1 auto; 1093 | background-color: #0000ff50; 1094 | } 1095 | 1096 | label.sdpi-file-label { 1097 | cursor: pointer; 1098 | user-select: none; 1099 | display: inline-block; 1100 | min-height: 21px !important; 1101 | height: 21px !important; 1102 | line-height: 20px; 1103 | padding: 0px 4px; 1104 | margin: auto; 1105 | margin-right: 0px; 1106 | float:right; 1107 | } 1108 | 1109 | .sdpi-file-label > label:active, 1110 | .sdpi-file-label.file:active, 1111 | label.sdpi-file-label:active, 1112 | label.sdpi-file-info:active, 1113 | input[type="file"]::-webkit-file-upload-button:active, 1114 | button:active { 1115 | background-color: var(--sdpi-color); 1116 | color:#303030; 1117 | } 1118 | 1119 | 1120 | input:required:invalid, input:focus:invalid { 1121 | background: var(--sdpi-background) url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5IiBoZWlnaHQ9IjkiIHZpZXdCb3g9IjAgMCA5IDkiPgogICAgPHBhdGggZmlsbD0iI0Q4RDhEOCIgZD0iTTQuNSwwIEM2Ljk4NTI4MTM3LC00LjU2NTM4NzgyZS0xNiA5LDIuMDE0NzE4NjMgOSw0LjUgQzksNi45ODUyODEzNyA2Ljk4NTI4MTM3LDkgNC41LDkgQzIuMDE0NzE4NjMsOSAzLjA0MzU5MTg4ZS0xNiw2Ljk4NTI4MTM3IDAsNC41IEMtMy4wNDM1OTE4OGUtMTYsMi4wMTQ3MTg2MyAyLjAxNDcxODYzLDQuNTY1Mzg3ODJlLTE2IDQuNSwwIFogTTQsMSBMNCw2IEw1LDYgTDUsMSBMNCwxIFogTTQuNSw4IEM0Ljc3NjE0MjM3LDggNSw3Ljc3NjE0MjM3IDUsNy41IEM1LDcuMjIzODU3NjMgNC43NzYxNDIzNyw3IDQuNSw3IEM0LjIyMzg1NzYzLDcgNCw3LjIyMzg1NzYzIDQsNy41IEM0LDcuNzc2MTQyMzcgNC4yMjM4NTc2Myw4IDQuNSw4IFoiLz4KICA8L3N2Zz4) no-repeat 98% center; 1122 | } 1123 | 1124 | input:required:valid { 1125 | background: var(--sdpi-background) url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5IiBoZWlnaHQ9IjkiIHZpZXdCb3g9IjAgMCA5IDkiPjxwb2x5Z29uIGZpbGw9IiNEOEQ4RDgiIHBvaW50cz0iNS4yIDEgNi4yIDEgNi4yIDcgMy4yIDcgMy4yIDYgNS4yIDYiIHRyYW5zZm9ybT0icm90YXRlKDQwIDQuNjc3IDQpIi8+PC9zdmc+) no-repeat 98% center; 1126 | } 1127 | 1128 | .tooltip, 1129 | :tooltip, 1130 | :title { 1131 | color: yellow; 1132 | } 1133 | 1134 | [title]:hover { 1135 | display: flex; 1136 | align-items: center; 1137 | justify-content: center; 1138 | } 1139 | 1140 | [title]:hover::after { 1141 | content: ''; 1142 | position: absolute; 1143 | bottom: -1000px; 1144 | left: 8px; 1145 | display: none; 1146 | color: #fff; 1147 | border: 8px solid transparent; 1148 | border-bottom: 8px solid #000; 1149 | } 1150 | [title]:hover::before { 1151 | content: attr(title); 1152 | display: flex; 1153 | justify-content: center; 1154 | align-self: center; 1155 | padding: 6px 12px; 1156 | border-radius: 5px; 1157 | background: rgba(0,0,0,0.8); 1158 | color: var(--sdpi-color); 1159 | font-size: 9pt; 1160 | font-family: sans-serif; 1161 | opacity: 1; 1162 | position: absolute; 1163 | height: auto; 1164 | /* width: 50%; 1165 | left: 35%; */ 1166 | text-align: center; 1167 | bottom: 2px; 1168 | z-index: 100; 1169 | box-shadow: 0px 3px 6px rgba(0, 0, 0, .5); 1170 | /* box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); */ 1171 | } 1172 | 1173 | .sdpi-item-group.file { 1174 | width: 232px; 1175 | display: flex; 1176 | align-items: center; 1177 | } 1178 | 1179 | .sdpi-file-info { 1180 | overflow-wrap: break-word; 1181 | word-wrap: break-word; 1182 | hyphens: auto; 1183 | 1184 | min-width: 132px; 1185 | max-width: 144px; 1186 | max-height: 32px; 1187 | margin-top: 0px; 1188 | margin-left: 5px; 1189 | display: inline-block; 1190 | overflow: hidden; 1191 | padding: 6px 4px; 1192 | background-color: var(--sdpi-background); 1193 | } 1194 | 1195 | 1196 | ::-webkit-scrollbar { 1197 | width: 8px; 1198 | } 1199 | 1200 | ::-webkit-scrollbar-track { 1201 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); 1202 | } 1203 | 1204 | ::-webkit-scrollbar-thumb { 1205 | background-color: #999999; 1206 | outline: 1px solid slategrey; 1207 | border-radius: 8px; 1208 | } 1209 | 1210 | a { 1211 | color: #7397d2; 1212 | } 1213 | 1214 | .testcontainer { 1215 | display: flex; 1216 | background-color: #0000ff20; 1217 | max-width: 400px; 1218 | height: 200px; 1219 | align-content: space-evenly; 1220 | } 1221 | 1222 | input[type=range] { 1223 | -webkit-appearance: none; 1224 | /* background-color: green; */ 1225 | height:6px; 1226 | margin-top: 12px; 1227 | z-index: 0; 1228 | overflow: visible; 1229 | } 1230 | 1231 | /* 1232 | input[type="range"]::-webkit-slider-thumb { 1233 | -webkit-appearance: none; 1234 | background-color: var(--sdpi-color); 1235 | width: 12px; 1236 | height: 12px; 1237 | border-radius: 20px; 1238 | margin-top: -6px; 1239 | border: none; 1240 | } */ 1241 | 1242 | :-webkit-slider-thumb { 1243 | -webkit-appearance: none; 1244 | background-color: var(--sdpi-color); 1245 | width: 16px; 1246 | height: 16px; 1247 | border-radius: 20px; 1248 | margin-top: -6px; 1249 | border: 1px solid #999999; 1250 | } 1251 | 1252 | .sdpi-item[type="range"] .sdpi-item-group { 1253 | display: flex; 1254 | flex-direction: column; 1255 | } 1256 | 1257 | .xxsdpi-item[type="range"] .sdpi-item-group input { 1258 | max-width: 204px; 1259 | } 1260 | 1261 | .sdpi-item[type="range"] .sdpi-item-group span { 1262 | margin-left: 0px !important; 1263 | } 1264 | 1265 | .sdpi-item[type="range"] .sdpi-item-group > .sdpi-item-child { 1266 | display: flex; 1267 | flex-direction: row; 1268 | } 1269 | 1270 | :disabled { 1271 | color: #993333; 1272 | } 1273 | 1274 | select, 1275 | select option { 1276 | color: var(--sdpi-color); 1277 | } 1278 | 1279 | select.disabled, 1280 | select option:disabled { 1281 | color: #fd9494; 1282 | font-style: italic; 1283 | } 1284 | 1285 | .runningAppsContainer { 1286 | display: none; 1287 | } 1288 | 1289 | /* debug 1290 | div { 1291 | background-color: rgba(64,128,255,0.2); 1292 | } 1293 | */ 1294 | 1295 | .min80 > .sdpi-item-child { 1296 | min-width: 80px; 1297 | } 1298 | 1299 | .min100 > .sdpi-item-child { 1300 | min-width: 100px; 1301 | } 1302 | 1303 | .min120 > .sdpi-item-child { 1304 | min-width: 120px; 1305 | } 1306 | 1307 | .min140 > .sdpi-item-child { 1308 | min-width: 140px; 1309 | } 1310 | 1311 | .min160 > .sdpi-item-child { 1312 | min-width: 160px; 1313 | } 1314 | 1315 | .min200 > .sdpi-item-child { 1316 | min-width: 200px; 1317 | } 1318 | 1319 | .max40 { 1320 | flex-basis: 40%; 1321 | flex-grow: 0; 1322 | } 1323 | 1324 | .max30 { 1325 | flex-basis: 30%; 1326 | flex-grow: 0; 1327 | } 1328 | 1329 | .max20 { 1330 | flex-basis: 20%; 1331 | flex-grow: 0; 1332 | } 1333 | 1334 | .up20 { 1335 | margin-top: -20px; 1336 | } 1337 | 1338 | .alignCenter { 1339 | align-items: center; 1340 | } 1341 | 1342 | .alignTop { 1343 | align-items: flex-start; 1344 | } 1345 | 1346 | .alignBaseline { 1347 | align-items: baseline; 1348 | } 1349 | 1350 | .noMargins, 1351 | .noMargins *, 1352 | .noInnerMargins * { 1353 | margin: 0; 1354 | padding: 0; 1355 | } 1356 | 1357 | 1358 | /** 1359 | input[type=range].vVertical { 1360 | -webkit-appearance: none; 1361 | background-color: green; 1362 | margin-left: -60px; 1363 | width: 100px; 1364 | height:6px; 1365 | margin-top: 0px; 1366 | transform:rotate(90deg); 1367 | z-index: 0; 1368 | overflow: visible; 1369 | } 1370 | 1371 | input[type=range].vHorizon { 1372 | -webkit-appearance: none; 1373 | background-color: pink; 1374 | height: 10px; 1375 | width:200px; 1376 | 1377 | } 1378 | 1379 | .test2 { 1380 | background-color: #00ff0020; 1381 | display: flex; 1382 | } 1383 | 1384 | 1385 | .vertical.sdpi-item[type="range"] .sdpi-item-value { 1386 | display: block; 1387 | } 1388 | 1389 | 1390 | .vertical.sdpi-item:first-child, 1391 | .vertical { 1392 | margin-top: 12px; 1393 | margin-bottom: 16px; 1394 | } 1395 | .vertical > .sdpi-item-value { 1396 | margin-right: 16px; 1397 | } 1398 | 1399 | .vertical .sdpi-item-group { 1400 | width: 100%; 1401 | display: flex; 1402 | justify-content: space-evenly; 1403 | } 1404 | 1405 | .vertical input[type=range] { 1406 | height: 100px; 1407 | width: 21px; 1408 | -webkit-appearance: slider-vertical; 1409 | display: flex; 1410 | flex-flow: column; 1411 | } 1412 | 1413 | .vertical input[type="range"]::-webkit-slider-runnable-track { 1414 | height: auto; 1415 | width: 5px; 1416 | } 1417 | 1418 | .vertical input[type="range"]::-webkit-slider-thumb { 1419 | margin-top: 0px; 1420 | margin-left: -6px; 1421 | } 1422 | 1423 | .vertical .sdpi-item-value { 1424 | flex-flow: column; 1425 | align-items: flex-start; 1426 | } 1427 | 1428 | .vertical.sdpi-item[type="range"] .sdpi-item-value { 1429 | align-items: center; 1430 | margin-right: 16px; 1431 | text-align: center; 1432 | } 1433 | 1434 | .vertical.sdpi-item[type="range"] .sdpi-item-value span, 1435 | .vertical input[type="range"] .sdpi-item-value span { 1436 | text-align: center; 1437 | margin: 4px 0px; 1438 | } 1439 | */ 1440 | -------------------------------------------------------------------------------- /Sources/common/css/sdpi.css: -------------------------------------------------------------------------------- 1 | html { 2 | --sdpi-bgcolor: #2D2D2D; 3 | --sdpi-background: #3D3D3D; 4 | --sdpi-color: #d8d8d8; 5 | --sdpi-bordercolor: #3a3a3a; 6 | --sdpi-borderradius: 0px; 7 | --sdpi-width: 224px; 8 | --sdpi-fontweight: 600; 9 | --sdpi-letterspacing: -0.25pt; 10 | height: 100%; 11 | width: 100%; 12 | overflow: hidden; 13 | } 14 | 15 | html, body { 16 | font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 17 | font-size: 9pt; 18 | background-color: var(--sdpi-bgcolor); 19 | color: #9a9a9a; 20 | } 21 | 22 | body { 23 | height: 100%; 24 | padding: 0; 25 | overflow-x: hidden; 26 | overflow-y: auto; 27 | margin: 0; 28 | -webkit-overflow-scrolling: touch; 29 | -webkit-text-size-adjust: 100%; 30 | -webkit-font-smoothing: antialiased; 31 | } 32 | 33 | mark { 34 | background-color: var(--sdpi-bgcolor); 35 | color: var(--sdpi-color); 36 | } 37 | 38 | .hidden { 39 | display: none; 40 | } 41 | 42 | hr, hr2 { 43 | -webkit-margin-before: 1em; 44 | -webkit-margin-after: 1em; 45 | border-style: none; 46 | background: var(--sdpi-background); 47 | height: 1px; 48 | } 49 | 50 | hr2, 51 | .sdpi-heading { 52 | display: flex; 53 | flex-basis: 100%; 54 | align-items: center; 55 | color: inherit; 56 | font-size: 9pt; 57 | margin: 8px 0px; 58 | } 59 | 60 | .sdpi-heading::before, 61 | .sdpi-heading::after { 62 | content: ""; 63 | flex-grow: 1; 64 | background: var(--sdpi-background); 65 | height: 1px; 66 | font-size: 0px; 67 | line-height: 0px; 68 | margin: 0px 16px; 69 | } 70 | 71 | hr2 { 72 | height: 2px; 73 | } 74 | 75 | hr, hr2 { 76 | margin-left:16px; 77 | margin-right:16px; 78 | } 79 | 80 | .sdpi-item-value, 81 | option, 82 | input, 83 | select, 84 | button { 85 | font-size: 10pt; 86 | font-weight: var(--sdpi-fontweight); 87 | letter-spacing: var(--sdpi-letterspacing); 88 | } 89 | 90 | 91 | 92 | .win .sdpi-item-value, 93 | .win option, 94 | .win input, 95 | .win select, 96 | .win button { 97 | font-size: 11px; 98 | font-style: normal; 99 | letter-spacing: inherit; 100 | font-weight: 100; 101 | } 102 | 103 | .win button { 104 | font-size: 12px; 105 | } 106 | 107 | ::-webkit-progress-value, 108 | meter::-webkit-meter-optimum-value { 109 | border-radius: 2px; 110 | /* background: linear-gradient(#ccf, #99f 20%, #77f 45%, #77f 55%, #cdf); */ 111 | } 112 | 113 | ::-webkit-progress-bar, 114 | meter::-webkit-meter-bar { 115 | border-radius: 3px; 116 | background: var(--sdpi-background); 117 | } 118 | 119 | ::-webkit-progress-bar:active, 120 | meter::-webkit-meter-bar:active { 121 | border-radius: 3px; 122 | background: #222222; 123 | } 124 | ::-webkit-progress-value:active, 125 | meter::-webkit-meter-optimum-value:active { 126 | background: #99f; 127 | } 128 | 129 | progress, 130 | progress.sdpi-item-value { 131 | min-height: 5px !important; 132 | height: 5px; 133 | background-color: #303030; 134 | } 135 | 136 | progress { 137 | margin-top: 8px !important; 138 | margin-bottom: 8px !important; 139 | } 140 | 141 | .full progress, 142 | progress.full { 143 | margin-top: 3px !important; 144 | } 145 | 146 | ::-webkit-progress-inner-element { 147 | background-color: transparent; 148 | } 149 | 150 | 151 | .sdpi-item[type="progress"] { 152 | margin-top: 4px !important; 153 | margin-bottom: 12px; 154 | min-height: 15px; 155 | } 156 | 157 | .sdpi-item-child.full:last-child { 158 | margin-bottom: 4px; 159 | } 160 | 161 | .tabs { 162 | /** 163 | * Setting display to flex makes this container lay 164 | * out its children using flexbox, the exact same 165 | * as in the above "Stepper input" example. 166 | */ 167 | display: flex; 168 | 169 | border-bottom: 1px solid #D7DBDD; 170 | } 171 | 172 | .tab { 173 | cursor: pointer; 174 | padding: 5px 30px; 175 | color: #16a2d7; 176 | font-size: 9pt; 177 | border-bottom: 2px solid transparent; 178 | } 179 | 180 | .tab.is-tab-selected { 181 | border-bottom-color: #4ebbe4; 182 | } 183 | 184 | select { 185 | -webkit-appearance: none; 186 | -moz-appearance: none; 187 | -o-appearance: none; 188 | appearance: none; 189 | background: url(caret.svg) no-repeat 97% center; 190 | } 191 | 192 | label.sdpi-file-label, 193 | input[type="button"], 194 | input[type="submit"], 195 | input[type="reset"], 196 | input[type="file"], 197 | input[type=file]::-webkit-file-upload-button, 198 | button, 199 | select { 200 | color: var(--sdpi-color); 201 | border: 1pt solid #303030; 202 | font-size: 8pt; 203 | background-color: var(--sdpi-background); 204 | border-radius: var(--sdpi-borderradius); 205 | } 206 | 207 | label.sdpi-file-label, 208 | input[type="button"], 209 | input[type="submit"], 210 | input[type="reset"], 211 | input[type="file"], 212 | input[type=file]::-webkit-file-upload-button, 213 | button { 214 | border: 1pt solid var(--sdpi-color); 215 | border-radius: var(--sdpi-borderradius); 216 | min-height: 23px !important; 217 | height: 23px !important; 218 | margin-right: 8px; 219 | } 220 | 221 | input[type=number]::-webkit-inner-spin-button, 222 | input[type=number]::-webkit-outer-spin-button { 223 | -webkit-appearance: none; 224 | margin: 0; 225 | } 226 | 227 | input[type="file"] { 228 | border-radius: var(--sdpi-borderradius); 229 | max-width: 220px; 230 | } 231 | 232 | option { 233 | height: 1.5em; 234 | padding: 4px; 235 | } 236 | 237 | /* SDPI */ 238 | 239 | .sdpi-wrapper { 240 | overflow-x: hidden; 241 | } 242 | 243 | .sdpi-item { 244 | display: flex; 245 | flex-direction: row; 246 | min-height: 32px; 247 | align-items: center; 248 | margin-top: 2px; 249 | max-width: 344px; 250 | } 251 | 252 | .sdpi-item:first-child { 253 | margin-top:1px; 254 | } 255 | 256 | .sdpi-item:last-child { 257 | margin-bottom: 0px; 258 | } 259 | 260 | .sdpi-item > *:not(.sdpi-item-label):not(meter):not(details) { 261 | min-height: 26px; 262 | padding: 0px 4px 0px 4px; 263 | } 264 | 265 | .sdpi-item > *:not(.sdpi-item-label.empty):not(meter) { 266 | min-height: 26px; 267 | padding: 0px 4px 0px 4px; 268 | } 269 | 270 | 271 | .sdpi-item-group { 272 | padding: 0 !important; 273 | } 274 | 275 | meter.sdpi-item-value { 276 | margin-left: 6px; 277 | } 278 | 279 | .sdpi-item[type="group"] { 280 | display: block; 281 | margin-top: 12px; 282 | margin-bottom: 12px; 283 | /* border: 1px solid white; */ 284 | flex-direction: unset; 285 | text-align: left; 286 | } 287 | 288 | .sdpi-item[type="group"] > .sdpi-item-label, 289 | .sdpi-item[type="group"].sdpi-item-label { 290 | width: 96%; 291 | text-align: left; 292 | font-weight: 700; 293 | margin-bottom: 4px; 294 | padding-left: 4px; 295 | } 296 | 297 | dl, 298 | ul, 299 | ol { 300 | -webkit-margin-before: 0px; 301 | -webkit-margin-after: 4px; 302 | -webkit-padding-start: 1em; 303 | max-height: 90px; 304 | overflow-y: scroll; 305 | cursor: pointer; 306 | user-select: none; 307 | } 308 | 309 | table.sdpi-item-value, 310 | dl.sdpi-item-value, 311 | ul.sdpi-item-value, 312 | ol.sdpi-item-value { 313 | -webkit-margin-before: 4px; 314 | -webkit-margin-after: 8px; 315 | -webkit-padding-start: 1em; 316 | width: var(--sdpi-width); 317 | text-align: center; 318 | } 319 | 320 | table > caption { 321 | margin: 2px; 322 | } 323 | 324 | .list, 325 | .sdpi-item[type="list"] { 326 | align-items: baseline; 327 | } 328 | 329 | .sdpi-item-label { 330 | text-align: right; 331 | flex: none; 332 | width: 94px; 333 | padding-right: 4px; 334 | font-weight: 600; 335 | -webkit-user-select: none; 336 | } 337 | 338 | .win .sdpi-item-label, 339 | .sdpi-item-label > small{ 340 | font-weight: normal; 341 | } 342 | 343 | .sdpi-item-label:after { 344 | content: ": "; 345 | } 346 | 347 | .sdpi-item-label.empty:after { 348 | content: ""; 349 | } 350 | 351 | .sdpi-test, 352 | .sdpi-item-value { 353 | flex: 1 0 0; 354 | /* flex-grow: 1; 355 | flex-shrink: 0; */ 356 | margin-right: 14px; 357 | margin-left: 4px; 358 | justify-content: space-evenly; 359 | } 360 | 361 | canvas.sdpi-item-value { 362 | max-width: 144px; 363 | max-height: 144px; 364 | width: 144px; 365 | height: 144px; 366 | margin: 0 auto; 367 | cursor: pointer; 368 | } 369 | 370 | input.sdpi-item-value { 371 | margin-left: 5px; 372 | } 373 | 374 | .sdpi-item-value button, 375 | button.sdpi-item-value { 376 | margin-left: 7px; 377 | margin-right: 19px; 378 | } 379 | 380 | .sdpi-item-value.range { 381 | margin-left: 0px; 382 | } 383 | 384 | table, 385 | dl.sdpi-item-value, 386 | ul.sdpi-item-value, 387 | ol.sdpi-item-value, 388 | .sdpi-item-value > dl, 389 | .sdpi-item-value > ul, 390 | .sdpi-item-value > ol 391 | { 392 | list-style-type: none; 393 | list-style-position: outside; 394 | margin-left: -4px; 395 | margin-right: -4px; 396 | padding: 4px; 397 | border: 1px solid var(--sdpi-bordercolor); 398 | } 399 | 400 | dl.sdpi-item-value, 401 | ul.sdpi-item-value, 402 | ol.sdpi-item-value, 403 | .sdpi-item-value > ol { 404 | list-style-type: none; 405 | list-style-position: inside; 406 | margin-left: 5px; 407 | margin-right: 12px; 408 | padding: 4px !important; 409 | } 410 | 411 | ol.sdpi-item-value, 412 | .sdpi-item-value > ol[listtype="none"] { 413 | list-style-type: none; 414 | } 415 | ol.sdpi-item-value[type="decimal"], 416 | .sdpi-item-value > ol[type="decimal"] { 417 | list-style-type: decimal; 418 | } 419 | 420 | ol.sdpi-item-value[type="decimal-leading-zero"], 421 | .sdpi-item-value > ol[type="decimal-leading-zero"] { 422 | list-style-type: decimal-leading-zero; 423 | } 424 | 425 | ol.sdpi-item-value[type="lower-alpha"], 426 | .sdpi-item-value > ol[type="lower-alpha"] { 427 | list-style-type: lower-alpha; 428 | } 429 | 430 | ol.sdpi-item-value[type="upper-alpha"], 431 | .sdpi-item-value > ol[type="upper-alpha"] { 432 | list-style-type: upper-alpha; 433 | } 434 | 435 | ol.sdpi-item-value[type="upper-roman"], 436 | .sdpi-item-value > ol[type="upper-roman"] { 437 | list-style-type: upper-roman; 438 | } 439 | 440 | ol.sdpi-item-value[type="lower-roman"], 441 | .sdpi-item-value > ol[type="lower-roman"] { 442 | list-style-type: upper-roman; 443 | } 444 | 445 | tr:nth-child(even), 446 | .sdpi-item-value > ul > li:nth-child(even), 447 | .sdpi-item-value > ol > li:nth-child(even), 448 | li:nth-child(even) { 449 | background-color: rgba(0,0,0,.2) 450 | } 451 | 452 | td:hover, 453 | .sdpi-item-value > ul > li:hover:nth-child(even), 454 | .sdpi-item-value > ol > li:hover:nth-child(even), 455 | li:hover:nth-child(even), 456 | li:hover { 457 | background-color: rgba(255,255,255,.1); 458 | } 459 | 460 | td.selected, 461 | td.selected:hover, 462 | li.selected:hover, 463 | li.selected { 464 | color: white; 465 | background-color: #77f; 466 | } 467 | 468 | tr { 469 | border: 1px solid var(--sdpi-bordercolor); 470 | } 471 | 472 | td { 473 | border-right: 1px solid var(--sdpi-bordercolor); 474 | -webkit-user-select: none; 475 | } 476 | 477 | tr:last-child, 478 | td:last-child { 479 | border: none; 480 | } 481 | 482 | .sdpi-item-value.select, 483 | .sdpi-item-value > select { 484 | margin-right: 13px; 485 | margin-left: 4px; 486 | } 487 | 488 | .sdpi-item-child, 489 | .sdpi-item-group > .sdpi-item > input[type="color"] { 490 | margin-top: 0.4em; 491 | margin-right: 4px; 492 | } 493 | 494 | .full, 495 | .full *, 496 | .sdpi-item-value.full, 497 | .sdpi-item-child > full > *, 498 | .sdpi-item-child.full, 499 | .sdpi-item-child.full > *, 500 | .full > .sdpi-item-child, 501 | .full > .sdpi-item-child > *{ 502 | display: flex; 503 | flex: 1 1 0; 504 | margin-bottom: 4px; 505 | margin-left: 0px; 506 | width: 100%; 507 | 508 | justify-content: space-evenly; 509 | } 510 | 511 | .sdpi-item-group > .sdpi-item > input[type="color"] { 512 | margin-top: 0px; 513 | } 514 | 515 | ::-webkit-calendar-picker-indicator:focus, 516 | input[type=file]::-webkit-file-upload-button:focus, 517 | button:focus, 518 | textarea:focus, 519 | input:focus, 520 | select:focus, 521 | option:focus, 522 | details:focus, 523 | summary:focus, 524 | .custom-select select { 525 | outline: none; 526 | } 527 | 528 | summary { 529 | cursor: default; 530 | -webkit-user-select: none; 531 | } 532 | 533 | .pointer, 534 | summary .pointer { 535 | cursor: pointer; 536 | } 537 | 538 | details.message { 539 | padding: 4px 18px 4px 12px; 540 | } 541 | 542 | details.message summary { 543 | font-size: 10pt; 544 | font-weight: 600; 545 | min-height: 18px; 546 | } 547 | 548 | details.message:first-child { 549 | margin-top: 4px; 550 | margin-left: 0; 551 | padding-left: 106px; 552 | } 553 | 554 | details.message h1 { 555 | text-align: left; 556 | } 557 | 558 | .message > summary::-webkit-details-marker { 559 | display: none; 560 | } 561 | 562 | .info20, 563 | .question, 564 | .caution, 565 | .info { 566 | background-repeat: no-repeat; 567 | background-position: 70px center; 568 | } 569 | 570 | .info20 { 571 | 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"); 572 | } 573 | 574 | .info { 575 | 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"); 576 | } 577 | 578 | .info2 { 579 | 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"); 580 | } 581 | 582 | .sdpi-more-info { 583 | 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"); 584 | } 585 | .caution { 586 | 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"); 587 | } 588 | 589 | .question { 590 | 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"); 591 | } 592 | 593 | 594 | .sdpi-more-info { 595 | position: fixed; 596 | left: 0px; 597 | right: 0px; 598 | bottom: 0px; 599 | min-height:16px; 600 | padding-right: 16px; 601 | text-align: right; 602 | -webkit-touch-callout: none; 603 | cursor: pointer; 604 | user-select: none; 605 | background-position: right center; 606 | background-repeat: no-repeat; 607 | border-radius: var(--sdpi-borderradius); 608 | text-decoration: none; 609 | color: var(--sdpi-color); 610 | } 611 | 612 | .sdpi-more-info-button { 613 | display: flex; 614 | align-self: right; 615 | margin-left: auto; 616 | position: fixed; 617 | right: 17px; 618 | bottom: 0px; 619 | } 620 | 621 | details a { 622 | background-position: right !important; 623 | min-height: 24px; 624 | display: inline-block; 625 | line-height: 24px; 626 | padding-right: 28px; 627 | } 628 | input:not([type="range"]), 629 | textarea { 630 | -webkit-appearance: none; 631 | background: var(--sdpi-background); 632 | color: var(--sdpi-color); 633 | font-weight: normal; 634 | font-size: 9pt; 635 | border: none; 636 | margin-top: 2px; 637 | margin-bottom: 2px; 638 | } 639 | 640 | textarea + label { 641 | display: flex; 642 | justify-content: flex-end 643 | } 644 | input[type="radio"], 645 | input[type="checkbox"] { 646 | display: none; 647 | } 648 | input[type="radio"] + label, 649 | input[type="checkbox"] + label { 650 | font-size: 9pt; 651 | color: var(--sdpi-color); 652 | font-weight: normal; 653 | margin-right: 8px; 654 | -webkit-user-select: none; 655 | } 656 | 657 | input[type="radio"] + label:after, 658 | input[type="checkbox"] + label:after { 659 | content: " " !important; 660 | } 661 | 662 | .sdpi-item[type="radio"] > .sdpi-item-value, 663 | .sdpi-item[type="checkbox"] > .sdpi-item-value { 664 | padding-top: 2px; 665 | } 666 | 667 | .sdpi-item[type="checkbox"] > .sdpi-item-value > * { 668 | margin-top: 4px; 669 | } 670 | 671 | .sdpi-item[type="checkbox"] .sdpi-item-child, 672 | .sdpi-item[type="radio"] .sdpi-item-child { 673 | display: inline-block; 674 | } 675 | 676 | .sdpi-item[type="range"] .sdpi-item-value, 677 | .sdpi-item[type="meter"] .sdpi-item-child, 678 | .sdpi-item[type="progress"] .sdpi-item-child { 679 | display: flex; 680 | } 681 | 682 | .sdpi-item[type="range"] .sdpi-item-value { 683 | min-height: 26px; 684 | } 685 | 686 | .sdpi-item[type="range"] .sdpi-item-value span, 687 | .sdpi-item[type="meter"] .sdpi-item-child span, 688 | .sdpi-item[type="progress"] .sdpi-item-child span { 689 | margin-top: -2px; 690 | min-width: 8px; 691 | text-align: right; 692 | user-select: none; 693 | cursor: pointer; 694 | } 695 | 696 | .sdpi-item[type="range"] .sdpi-item-value span { 697 | margin-top: 7px; 698 | text-align: right; 699 | } 700 | 701 | span + input[type="range"] { 702 | display: flex; 703 | max-width: 168px; 704 | 705 | } 706 | 707 | .sdpi-item[type="range"] .sdpi-item-value span:first-child, 708 | .sdpi-item[type="meter"] .sdpi-item-child span:first-child, 709 | .sdpi-item[type="progress"] .sdpi-item-child span:first-child { 710 | margin-right: 4px; 711 | } 712 | 713 | .sdpi-item[type="range"] .sdpi-item-value span:last-child, 714 | .sdpi-item[type="meter"] .sdpi-item-child span:last-child, 715 | .sdpi-item[type="progress"] .sdpi-item-child span:last-child { 716 | margin-left: 4px; 717 | } 718 | 719 | .reverse { 720 | transform: rotate(180deg); 721 | } 722 | 723 | .sdpi-item[type="meter"] .sdpi-item-child meter + span:last-child { 724 | margin-left: -10px; 725 | } 726 | 727 | .sdpi-item[type="progress"] .sdpi-item-child meter + span:last-child { 728 | margin-left: -14px; 729 | } 730 | 731 | .sdpi-item[type="radio"] > .sdpi-item-value > * { 732 | margin-top: 2px; 733 | } 734 | 735 | details { 736 | padding: 8px 18px 8px 12px; 737 | min-width: 86px; 738 | } 739 | 740 | details > h4 { 741 | border-bottom: 1px solid var(--sdpi-bordercolor); 742 | } 743 | 744 | legend { 745 | display: none; 746 | } 747 | .sdpi-item-value > textarea { 748 | padding: 0px; 749 | width: 227px; 750 | margin-left: 1px; 751 | } 752 | 753 | input[type="radio"] + label span, 754 | input[type="checkbox"] + label span { 755 | display: inline-block; 756 | width: 16px; 757 | height: 16px; 758 | margin: 2px 4px 2px 0; 759 | border-radius: 3px; 760 | vertical-align: middle; 761 | background: var(--sdpi-background); 762 | cursor: pointer; 763 | border: 1px solid rgb(0,0,0,.2); 764 | } 765 | 766 | input[type="radio"] + label span { 767 | border-radius: 100%; 768 | } 769 | 770 | input[type="radio"]:checked + label span, 771 | input[type="checkbox"]:checked + label span { 772 | background-color: #77f; 773 | background-image: url(check.svg); 774 | background-repeat: no-repeat; 775 | background-position: center center; 776 | border: 1px solid rgb(0,0,0,.4); 777 | } 778 | 779 | input[type="radio"]:active:checked + label span, 780 | input[type="radio"]:active + label span, 781 | input[type="checkbox"]:active:checked + label span, 782 | input[type="checkbox"]:active + label span { 783 | background-color: #303030; 784 | } 785 | 786 | input[type="radio"]:checked + label span { 787 | background-image: url(rcheck.svg); 788 | } 789 | 790 | 791 | /* 792 | input[type="radio"] + label span { 793 | background: url(buttons.png) -38px top no-repeat; 794 | } 795 | 796 | input[type="radio"]:checked + label span { 797 | background: url(buttons.png) -57px top no-repeat; 798 | } 799 | */ 800 | 801 | input[type="range"] { 802 | width: var(--sdpi-width); 803 | height: 30px; 804 | overflow: hidden; 805 | cursor: pointer; 806 | background: transparent !important; 807 | } 808 | 809 | .sdpi-item > input[type="range"] { 810 | margin-left: 8px; 811 | max-width: var(--sdpi-width); 812 | width: var(--sdpi-width); 813 | padding: 0px; 814 | } 815 | 816 | /* 817 | input[type="range"], 818 | input[type="range"]::-webkit-slider-runnable-track, 819 | input[type="range"]::-webkit-slider-thumb { 820 | -webkit-appearance: none; 821 | } 822 | */ 823 | 824 | input[type="range"]::-webkit-slider-runnable-track { 825 | height: 5px; 826 | background: #979797; 827 | border-radius: 3px; 828 | padding:0px !important; 829 | border: 1px solid var(--sdpi-background); 830 | } 831 | 832 | input[type="range"]::-webkit-slider-thumb { 833 | position: relative; 834 | -webkit-appearance: none; 835 | background-color: var(--sdpi-color); 836 | width: 12px; 837 | height: 12px; 838 | border-radius: 20px; 839 | margin-top: -5px; 840 | border: none; 841 | 842 | } 843 | input[type="range" i]{ 844 | margin: 0; 845 | } 846 | 847 | input[type="range"]::-webkit-slider-thumb::before { 848 | position: absolute; 849 | content: ""; 850 | height: 5px; /* equal to height of runnable track or 1 less */ 851 | width: 500px; /* make this bigger than the widest range input element */ 852 | left: -502px; /* this should be -2px - width */ 853 | top: 8px; /* don't change this */ 854 | background: #77f; 855 | } 856 | 857 | input[type="color"] { 858 | min-width: 32px; 859 | min-height: 32px; 860 | width: 32px; 861 | height: 32px; 862 | padding: 0; 863 | background-color: var(--sdpi-bgcolor); 864 | flex: none; 865 | } 866 | 867 | ::-webkit-color-swatch { 868 | min-width: 24px; 869 | } 870 | 871 | textarea { 872 | height: 3em; 873 | word-break: break-word; 874 | line-height: 1.5em; 875 | } 876 | 877 | .textarea { 878 | padding: 0px !important; 879 | } 880 | 881 | textarea { 882 | width: 221px; /*98%;*/ 883 | height: 96%; 884 | min-height: 6em; 885 | resize: none; 886 | border-radius: var(--sdpi-borderradius); 887 | } 888 | 889 | /* CAROUSEL */ 890 | 891 | .sdpi-item[type="carousel"]{ 892 | 893 | } 894 | 895 | .sdpi-item.card-carousel-wrapper, 896 | .sdpi-item > .card-carousel-wrapper { 897 | padding: 0; 898 | } 899 | 900 | 901 | .card-carousel-wrapper { 902 | display: flex; 903 | align-items: center; 904 | justify-content: center; 905 | margin: 12px auto; 906 | color: #666a73; 907 | } 908 | 909 | .card-carousel { 910 | display: flex; 911 | justify-content: center; 912 | width: 278px; 913 | } 914 | .card-carousel--overflow-container { 915 | overflow: hidden; 916 | } 917 | .card-carousel--nav__left, 918 | .card-carousel--nav__right { 919 | /* display: inline-block; */ 920 | width: 12px; 921 | height: 12px; 922 | border-top: 2px solid #42b883; 923 | border-right: 2px solid #42b883; 924 | cursor: pointer; 925 | margin: 0 4px; 926 | transition: transform 150ms linear; 927 | } 928 | .card-carousel--nav__left[disabled], 929 | .card-carousel--nav__right[disabled] { 930 | opacity: 0.2; 931 | border-color: black; 932 | } 933 | .card-carousel--nav__left { 934 | transform: rotate(-135deg); 935 | } 936 | .card-carousel--nav__left:active { 937 | transform: rotate(-135deg) scale(0.85); 938 | } 939 | .card-carousel--nav__right { 940 | transform: rotate(45deg); 941 | } 942 | .card-carousel--nav__right:active { 943 | transform: rotate(45deg) scale(0.85); 944 | } 945 | .card-carousel-cards { 946 | display: flex; 947 | transition: transform 150ms ease-out; 948 | transform: translatex(0px); 949 | } 950 | .card-carousel-cards .card-carousel--card { 951 | margin: 0 5px; 952 | cursor: pointer; 953 | /* box-shadow: 0 4px 15px 0 rgba(40, 44, 53, 0.06), 0 2px 2px 0 rgba(40, 44, 53, 0.08); */ 954 | background-color: #fff; 955 | border-radius: 4px; 956 | z-index: 3; 957 | } 958 | .xxcard-carousel-cards .card-carousel--card:first-child { 959 | margin-left: 0; 960 | } 961 | .xxcard-carousel-cards .card-carousel--card:last-child { 962 | margin-right: 0; 963 | } 964 | .card-carousel-cards .card-carousel--card img { 965 | vertical-align: bottom; 966 | border-top-left-radius: 4px; 967 | border-top-right-radius: 4px; 968 | transition: opacity 150ms linear; 969 | width: 60px; 970 | } 971 | .card-carousel-cards .card-carousel--card img:hover { 972 | opacity: 0.5; 973 | } 974 | .card-carousel-cards .card-carousel--card--footer { 975 | border-top: 0; 976 | max-width: 80px; 977 | overflow: hidden; 978 | display: flex; 979 | height: 100%; 980 | flex-direction: column; 981 | } 982 | .card-carousel-cards .card-carousel--card--footer p { 983 | padding: 3px 0; 984 | margin: 0; 985 | margin-bottom: 2px; 986 | font-size: 15px; 987 | font-weight: 500; 988 | color: #2c3e50; 989 | } 990 | .card-carousel-cards .card-carousel--card--footer p:nth-of-type(2) { 991 | font-size: 12px; 992 | font-weight: 300; 993 | padding: 6px; 994 | color: #666a73; 995 | } 996 | 997 | 998 | h1 { 999 | font-size: 1.3em; 1000 | font-weight: 500; 1001 | text-align: center; 1002 | margin-bottom: 12px; 1003 | } 1004 | 1005 | ::-webkit-datetime-edit { 1006 | font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; 1007 | background: url(elg_calendar_inv.svg) no-repeat left center; 1008 | padding-right: 1em; 1009 | padding-left: 25px; 1010 | background-position: 4px 0px; 1011 | } 1012 | ::-webkit-datetime-edit-fields-wrapper { 1013 | 1014 | } 1015 | ::-webkit-datetime-edit-text { padding: 0 0.3em; } 1016 | ::-webkit-datetime-edit-month-field { } 1017 | ::-webkit-datetime-edit-day-field {} 1018 | ::-webkit-datetime-edit-year-field {} 1019 | ::-webkit-inner-spin-button { 1020 | 1021 | /* display: none; */ 1022 | } 1023 | ::-webkit-calendar-picker-indicator { 1024 | background: transparent; 1025 | font-size: 17px; 1026 | } 1027 | 1028 | ::-webkit-calendar-picker-indicator:focus { 1029 | background-color: rgba(0,0,0,0.2); 1030 | } 1031 | 1032 | input[type="date"] { 1033 | -webkit-align-items: center; 1034 | display: -webkit-inline-flex; 1035 | font-family: monospace; 1036 | overflow: hidden; 1037 | padding: 0; 1038 | -webkit-padding-start: 1px; 1039 | } 1040 | 1041 | input::-webkit-datetime-edit { 1042 | -webkit-flex: 1; 1043 | -webkit-user-modify: read-only !important; 1044 | display: inline-block; 1045 | min-width: 0; 1046 | overflow: hidden; 1047 | } 1048 | 1049 | /* 1050 | input::-webkit-datetime-edit-fields-wrapper { 1051 | -webkit-user-modify: read-only !important; 1052 | display: inline-block; 1053 | padding: 1px 0; 1054 | white-space: pre; 1055 | 1056 | } 1057 | */ 1058 | 1059 | /* 1060 | input[type="date"] { 1061 | background-color: red; 1062 | outline: none; 1063 | } 1064 | 1065 | input[type="date"]::-webkit-clear-button { 1066 | font-size: 18px; 1067 | height: 30px; 1068 | position: relative; 1069 | } 1070 | 1071 | input[type="date"]::-webkit-inner-spin-button { 1072 | height: 28px; 1073 | } 1074 | 1075 | input[type="date"]::-webkit-calendar-picker-indicator { 1076 | font-size: 15px; 1077 | } */ 1078 | 1079 | input[type="file"] { 1080 | opacity: 0; 1081 | display: none; 1082 | } 1083 | 1084 | .sdpi-item > input[type="file"] { 1085 | opacity: 1; 1086 | display: flex; 1087 | } 1088 | 1089 | input[type="file"] + span { 1090 | display: flex; 1091 | flex: 0 1 auto; 1092 | background-color: #0000ff50; 1093 | } 1094 | 1095 | label.sdpi-file-label { 1096 | cursor: pointer; 1097 | user-select: none; 1098 | display: inline-block; 1099 | min-height: 21px !important; 1100 | height: 21px !important; 1101 | line-height: 20px; 1102 | padding: 0px 4px; 1103 | margin: auto; 1104 | margin-right: 0px; 1105 | float:right; 1106 | } 1107 | 1108 | .sdpi-file-label > label:active, 1109 | .sdpi-file-label.file:active, 1110 | label.sdpi-file-label:active, 1111 | label.sdpi-file-info:active, 1112 | input[type="file"]::-webkit-file-upload-button:active, 1113 | button:active { 1114 | background-color: var(--sdpi-color); 1115 | color:#303030; 1116 | } 1117 | 1118 | 1119 | input:required:invalid, input:focus:invalid { 1120 | background: var(--sdpi-background) url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5IiBoZWlnaHQ9IjkiIHZpZXdCb3g9IjAgMCA5IDkiPgogICAgPHBhdGggZmlsbD0iI0Q4RDhEOCIgZD0iTTQuNSwwIEM2Ljk4NTI4MTM3LC00LjU2NTM4NzgyZS0xNiA5LDIuMDE0NzE4NjMgOSw0LjUgQzksNi45ODUyODEzNyA2Ljk4NTI4MTM3LDkgNC41LDkgQzIuMDE0NzE4NjMsOSAzLjA0MzU5MTg4ZS0xNiw2Ljk4NTI4MTM3IDAsNC41IEMtMy4wNDM1OTE4OGUtMTYsMi4wMTQ3MTg2MyAyLjAxNDcxODYzLDQuNTY1Mzg3ODJlLTE2IDQuNSwwIFogTTQsMSBMNCw2IEw1LDYgTDUsMSBMNCwxIFogTTQuNSw4IEM0Ljc3NjE0MjM3LDggNSw3Ljc3NjE0MjM3IDUsNy41IEM1LDcuMjIzODU3NjMgNC43NzYxNDIzNyw3IDQuNSw3IEM0LjIyMzg1NzYzLDcgNCw3LjIyMzg1NzYzIDQsNy41IEM0LDcuNzc2MTQyMzcgNC4yMjM4NTc2Myw4IDQuNSw4IFoiLz4KICA8L3N2Zz4) no-repeat 98% center; 1121 | } 1122 | 1123 | input:required:valid { 1124 | background: var(--sdpi-background) url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI5IiBoZWlnaHQ9IjkiIHZpZXdCb3g9IjAgMCA5IDkiPjxwb2x5Z29uIGZpbGw9IiNEOEQ4RDgiIHBvaW50cz0iNS4yIDEgNi4yIDEgNi4yIDcgMy4yIDcgMy4yIDYgNS4yIDYiIHRyYW5zZm9ybT0icm90YXRlKDQwIDQuNjc3IDQpIi8+PC9zdmc+) no-repeat 98% center; 1125 | } 1126 | 1127 | .tooltip, 1128 | :tooltip, 1129 | :title { 1130 | color: yellow; 1131 | } 1132 | 1133 | [title]:hover { 1134 | display: flex; 1135 | align-items: center; 1136 | justify-content: center; 1137 | } 1138 | 1139 | [title]:hover::after { 1140 | content: ''; 1141 | position: absolute; 1142 | bottom: -1000px; 1143 | left: 8px; 1144 | display: none; 1145 | color: #fff; 1146 | border: 8px solid transparent; 1147 | border-bottom: 8px solid #000; 1148 | } 1149 | [title]:hover::before { 1150 | content: attr(title); 1151 | display: flex; 1152 | justify-content: center; 1153 | align-self: center; 1154 | padding: 6px 12px; 1155 | border-radius: 5px; 1156 | background: rgba(0,0,0,0.8); 1157 | color: var(--sdpi-color); 1158 | font-size: 9pt; 1159 | font-family: sans-serif; 1160 | opacity: 1; 1161 | position: absolute; 1162 | height: auto; 1163 | /* width: 50%; 1164 | left: 35%; */ 1165 | text-align: center; 1166 | bottom: 2px; 1167 | z-index: 100; 1168 | box-shadow: 0px 3px 6px rgba(0, 0, 0, .5); 1169 | /* box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); */ 1170 | } 1171 | 1172 | .sdpi-item-group.file { 1173 | width: 232px; 1174 | display: flex; 1175 | align-items: center; 1176 | } 1177 | 1178 | .sdpi-file-info { 1179 | overflow-wrap: break-word; 1180 | word-wrap: break-word; 1181 | hyphens: auto; 1182 | 1183 | min-width: 132px; 1184 | max-width: 144px; 1185 | max-height: 32px; 1186 | margin-top: 0px; 1187 | margin-left: 5px; 1188 | display: inline-block; 1189 | overflow: hidden; 1190 | padding: 6px 4px; 1191 | background-color: var(--sdpi-background); 1192 | } 1193 | 1194 | 1195 | ::-webkit-scrollbar { 1196 | width: 8px; 1197 | } 1198 | 1199 | ::-webkit-scrollbar-track { 1200 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); 1201 | } 1202 | 1203 | ::-webkit-scrollbar-thumb { 1204 | background-color: #999999; 1205 | outline: 1px solid slategrey; 1206 | border-radius: 8px; 1207 | } 1208 | 1209 | a { 1210 | color: #7397d2; 1211 | } 1212 | 1213 | .testcontainer { 1214 | display: flex; 1215 | background-color: #0000ff20; 1216 | max-width: 400px; 1217 | height: 200px; 1218 | align-content: space-evenly; 1219 | } 1220 | 1221 | input[type=range] { 1222 | -webkit-appearance: none; 1223 | /* background-color: green; */ 1224 | height:6px; 1225 | margin-top: 12px; 1226 | z-index: 0; 1227 | overflow: visible; 1228 | } 1229 | 1230 | /* 1231 | input[type="range"]::-webkit-slider-thumb { 1232 | -webkit-appearance: none; 1233 | background-color: var(--sdpi-color); 1234 | width: 12px; 1235 | height: 12px; 1236 | border-radius: 20px; 1237 | margin-top: -6px; 1238 | border: none; 1239 | } */ 1240 | 1241 | :-webkit-slider-thumb { 1242 | -webkit-appearance: none; 1243 | background-color: var(--sdpi-color); 1244 | width: 16px; 1245 | height: 16px; 1246 | border-radius: 20px; 1247 | margin-top: -6px; 1248 | border: 1px solid #999999; 1249 | } 1250 | 1251 | .sdpi-item[type="range"] .sdpi-item-group { 1252 | display: flex; 1253 | flex-direction: column; 1254 | } 1255 | 1256 | .xxsdpi-item[type="range"] .sdpi-item-group input { 1257 | max-width: 204px; 1258 | } 1259 | 1260 | .sdpi-item[type="range"] .sdpi-item-group span { 1261 | margin-left: 0px !important; 1262 | } 1263 | 1264 | .sdpi-item[type="range"] .sdpi-item-group > .sdpi-item-child { 1265 | display: flex; 1266 | flex-direction: row; 1267 | } 1268 | 1269 | :disabled { 1270 | color: #993333; 1271 | } 1272 | 1273 | select, 1274 | select option { 1275 | color: var(--sdpi-color); 1276 | } 1277 | 1278 | select.disabled, 1279 | select option:disabled { 1280 | color: #fd9494; 1281 | font-style: italic; 1282 | } 1283 | 1284 | .runningAppsContainer { 1285 | display: none; 1286 | } 1287 | 1288 | /* debug 1289 | div { 1290 | background-color: rgba(64,128,255,0.2); 1291 | } 1292 | */ 1293 | 1294 | .min80 > .sdpi-item-child { 1295 | min-width: 80px; 1296 | } 1297 | 1298 | .min100 > .sdpi-item-child { 1299 | min-width: 100px; 1300 | } 1301 | 1302 | .min120 > .sdpi-item-child { 1303 | min-width: 120px; 1304 | } 1305 | 1306 | .min140 > .sdpi-item-child { 1307 | min-width: 140px; 1308 | } 1309 | 1310 | .min160 > .sdpi-item-child { 1311 | min-width: 160px; 1312 | } 1313 | 1314 | .min200 > .sdpi-item-child { 1315 | min-width: 200px; 1316 | } 1317 | 1318 | .max40 { 1319 | flex-basis: 40%; 1320 | flex-grow: 0; 1321 | } 1322 | 1323 | .max30 { 1324 | flex-basis: 30%; 1325 | flex-grow: 0; 1326 | } 1327 | 1328 | .max20 { 1329 | flex-basis: 20%; 1330 | flex-grow: 0; 1331 | } 1332 | 1333 | .up20 { 1334 | margin-top: -20px; 1335 | } 1336 | 1337 | .alignCenter { 1338 | align-items: center; 1339 | } 1340 | 1341 | .alignTop { 1342 | align-items: flex-start; 1343 | } 1344 | 1345 | .alignBaseline { 1346 | align-items: baseline; 1347 | } 1348 | 1349 | .noMargins, 1350 | .noMargins *, 1351 | .noInnerMargins * { 1352 | margin: 0; 1353 | padding: 0; 1354 | } 1355 | 1356 | 1357 | /** 1358 | input[type=range].vVertical { 1359 | -webkit-appearance: none; 1360 | background-color: green; 1361 | margin-left: -60px; 1362 | width: 100px; 1363 | height:6px; 1364 | margin-top: 0px; 1365 | transform:rotate(90deg); 1366 | z-index: 0; 1367 | overflow: visible; 1368 | } 1369 | 1370 | input[type=range].vHorizon { 1371 | -webkit-appearance: none; 1372 | background-color: pink; 1373 | height: 10px; 1374 | width:200px; 1375 | 1376 | } 1377 | 1378 | .test2 { 1379 | background-color: #00ff0020; 1380 | display: flex; 1381 | } 1382 | 1383 | 1384 | .vertical.sdpi-item[type="range"] .sdpi-item-value { 1385 | display: block; 1386 | } 1387 | 1388 | 1389 | .vertical.sdpi-item:first-child, 1390 | .vertical { 1391 | margin-top: 12px; 1392 | margin-bottom: 16px; 1393 | } 1394 | .vertical > .sdpi-item-value { 1395 | margin-right: 16px; 1396 | } 1397 | 1398 | .vertical .sdpi-item-group { 1399 | width: 100%; 1400 | display: flex; 1401 | justify-content: space-evenly; 1402 | } 1403 | 1404 | .vertical input[type=range] { 1405 | height: 100px; 1406 | width: 21px; 1407 | -webkit-appearance: slider-vertical; 1408 | display: flex; 1409 | flex-flow: column; 1410 | } 1411 | 1412 | .vertical input[type="range"]::-webkit-slider-runnable-track { 1413 | height: auto; 1414 | width: 5px; 1415 | } 1416 | 1417 | .vertical input[type="range"]::-webkit-slider-thumb { 1418 | margin-top: 0px; 1419 | margin-left: -6px; 1420 | } 1421 | 1422 | .vertical .sdpi-item-value { 1423 | flex-flow: column; 1424 | align-items: flex-start; 1425 | } 1426 | 1427 | .vertical.sdpi-item[type="range"] .sdpi-item-value { 1428 | align-items: center; 1429 | margin-right: 16px; 1430 | text-align: center; 1431 | } 1432 | 1433 | .vertical.sdpi-item[type="range"] .sdpi-item-value span, 1434 | .vertical input[type="range"] .sdpi-item-value span { 1435 | text-align: center; 1436 | margin: 4px 0px; 1437 | } 1438 | */ 1439 | 1440 | /* 1441 | .file { 1442 | box-sizing: border-box; 1443 | display: block; 1444 | overflow: hidden; 1445 | padding: 10px; 1446 | position: relative; 1447 | text-indent: 100%; 1448 | white-space: nowrap; 1449 | height: 190px; 1450 | width: 160px; 1451 | } 1452 | .file::before { 1453 | content: ""; 1454 | display: block; 1455 | position: absolute; 1456 | top: 10px; 1457 | left: 10px; 1458 | height: 170px; 1459 | width: 140px; 1460 | } 1461 | .file::after { 1462 | content: ""; 1463 | height: 90px; 1464 | width: 90px; 1465 | position: absolute; 1466 | right: 0; 1467 | bottom: 0; 1468 | overflow: visible; 1469 | } 1470 | 1471 | .list--files { 1472 | display: flex; 1473 | flex-wrap: wrap; 1474 | justify-content: center; 1475 | margin: auto; 1476 | padding: 30px 0; 1477 | width: 630px; 1478 | } 1479 | .list--files > li { 1480 | margin: 0; 1481 | padding: 15px; 1482 | } 1483 | 1484 | .type-document::before { 1485 | background-image: url(data:image/svg+xml;charset=utf-8;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiDQogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIg0KICAgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSIxNDBweCIgaGVpZ2h0PSIxNzBweCIgdmlld0JveD0iMCAwIDE0MCAxNzAiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPHBhdGggZmlsbD0iI0E3QTlBQyIgZD0iTTAsMHYxNzBoMTQwVjBIMHogTTEzMCwxNjBIMTBWMTBoMTIwVjE2MHogTTExMCw0MEgzMFYzMGg4MFY0MHogTTExMCw2MEgzMFY1MGg4MFY2MHogTTExMCw4MEgzMFY3MGg4MFY4MHoNCiAgIE0xMTAsMTAwSDMwVjkwaDgwVjEwMHogTTExMCwxMjBIMzB2LTEwaDgwVjEyMHogTTkwLDE0MEgzMHYtMTBoNjBWMTQweiIvPg0KPC9zdmc+); 1486 | } 1487 | 1488 | .type-image { 1489 | height: 160px; 1490 | } 1491 | .type-image::before { 1492 | background-image: url(data:image/svg+xml;charset=utf-8;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiDQogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWxuczphPSJodHRwOi8vbnMuYWRvYmUuY29tL0Fkb2JlU1ZHVmlld2VyRXh0ZW5zaW9ucy8zLjAvIg0KICAgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSIxNDBweCIgaGVpZ2h0PSIxNDBweCIgdmlld0JveD0iMCAwIDE0MCAxNDAiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDE0MCAxNDAiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPGc+DQogIDxwYXRoIGZpbGw9IiNBN0E5QUMiIGQ9Ik0wLDB2MTQwaDE0MFYwSDB6IE0xMzAsMTMwSDEwVjEwaDEyMFYxMzB6Ii8+DQogIDxwb2x5Z29uIGZpbGw9IiNFNkU3RTgiIHBvaW50cz0iOTAsMTEwIDQwLDQwIDEwLDgwIDEwLDEzMCA5MCwxMzAgICIvPg0KICA8cG9seWdvbiBmaWxsPSIjRDFEM0Q0IiBwb2ludHM9IjEwLDEzMCA1MCw5MCA2MCwxMDAgMTAwLDYwIDEzMCwxMzAgICIvPg0KPC9nPg0KPC9zdmc+); 1493 | height: 140px; 1494 | } 1495 | 1496 | .state-synced::after { 1497 | background-image: url(data:image/svg+xml;charset=utf-8;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiDQogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWxuczphPSJodHRwOi8vbnMuYWRvYmUuY29tL0Fkb2JlU1ZHVmlld2VyRXh0ZW5zaW9ucy8zLjAvIg0KICAgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSI5MHB4IiBoZWlnaHQ9IjkwcHgiIHZpZXdCb3g9IjAgMCA5MCA5MCIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgOTAgOTAiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPGc+DQogIDxjaXJjbGUgZmlsbD0iIzAwQTY1MSIgY3g9IjQ1IiBjeT0iNDUiIHI9IjQ1Ii8+DQogIDxwYXRoIGZpbGw9IiNGRkZGRkYiIGQ9Ik0yMCw0NUwyMCw0NWMtMi44LDIuOC0yLjgsNy4yLDAsMTBsMTAuMSwxMC4xYzIuNywyLjcsNy4yLDIuNyw5LjksMEw3MCwzNWMyLjgtMi44LDIuOC03LjIsMC0xMGwwLDANCiAgICBjLTIuOC0yLjgtNy4yLTIuOC0xMCwwTDM1LDUwbC01LTVDMjcuMiw0Mi4yLDIyLjgsNDIuMiwyMCw0NXoiLz4NCjwvZz4NCjwvc3ZnPg==); 1498 | } 1499 | 1500 | .state-deleted::after { 1501 | background-image: url(data:image/svg+xml;charset=utf-8;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiDQogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWxuczphPSJodHRwOi8vbnMuYWRvYmUuY29tL0Fkb2JlU1ZHVmlld2VyRXh0ZW5zaW9ucy8zLjAvIg0KICAgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSI5MHB4IiBoZWlnaHQ9IjkwcHgiIHZpZXdCb3g9IjAgMCA5MCA5MCIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgOTAgOTAiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPGc+DQogIDxjaXJjbGUgZmlsbD0iI0VEMUMyNCIgY3g9IjQ1IiBjeT0iNDUiIHI9IjQ1Ii8+DQogIDxwYXRoIGZpbGw9IiNGRkZGRkYiIGQ9Ik02NSwyNUw2NSwyNWMtMi44LTIuOC03LjItMi44LTEwLDBMNDUsMzVMMzUsMjVjLTIuOC0yLjgtNy4yLTIuOC0xMCwwbDAsMGMtMi44LDIuOC0yLjgsNy4yLDAsMTBsMTAsMTANCiAgICBMMjUsNTVjLTIuOCwyLjgtMi44LDcuMiwwLDEwbDAsMGMyLjgsMi44LDcuMiwyLjgsMTAsMGwxMC0xMGwxMCwxMGMyLjgsMi44LDcuMiwyLjgsMTAsMGwwLDBjMi44LTIuOCwyLjgtNy4yLDAtMTBMNTUsNDVsMTAtMTANCiAgICBDNjcuOCwzMi4yLDY3LjgsMjcuOCw2NSwyNXoiLz4NCjwvZz4NCjwvc3ZnPg==); 1502 | } 1503 | .state-deleted::before { 1504 | opacity: .25; 1505 | } 1506 | 1507 | .state-locked::after { 1508 | background-image: url(data:image/svg+xml;charset=utf-8;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiDQogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWxuczphPSJodHRwOi8vbnMuYWRvYmUuY29tL0Fkb2JlU1ZHVmlld2VyRXh0ZW5zaW9ucy8zLjAvIg0KICAgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSI5MHB4IiBoZWlnaHQ9IjkwcHgiIHZpZXdCb3g9IjAgMCA5MCA5MCIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgOTAgOTAiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPGc+DQogIDxjaXJjbGUgZmlsbD0iIzU4NTk1QiIgY3g9IjQ1IiBjeT0iNDUiIHI9IjQ1Ii8+DQogIDxyZWN0IHg9IjIwIiB5PSI0MCIgZmlsbD0iI0ZGRkZGRiIgd2lkdGg9IjUwIiBoZWlnaHQ9IjMwIi8+DQogIDxwYXRoIGZpbGw9IiNGRkZGRkYiIGQ9Ik0zMi41LDQ2LjVjLTIuOCwwLTUtMi4yLTUtNVYyOWMwLTkuNiw3LjktMTcuNSwxNy41LTE3LjVTNjIuNSwxOS40LDYyLjUsMjljMCwyLjgtMi4yLDUtNSw1cy01LTIuMi01LTUNCiAgICBjMC00LjEtMy40LTcuNS03LjUtNy41cy03LjUsMy40LTcuNSw3LjV2MTIuNUMzNy41LDQ0LjMsMzUuMyw0Ni41LDMyLjUsNDYuNXoiLz4NCjwvZz4NCjwvc3ZnPg==); 1509 | } 1510 | 1511 | 1512 | 1513 | html { 1514 | --fheight: 95px; 1515 | --fwidth: 80px; 1516 | --fspacing: 5px; 1517 | --ftotalwidth: 315px; 1518 | --bgsize: 50%; 1519 | --bgsize2: cover; 1520 | --bgsize3: contain; 1521 | } 1522 | 1523 | ul { 1524 | list-style: none; 1525 | } 1526 | 1527 | 1528 | .file { 1529 | height: var(--fheight); 1530 | width: var(--fwidth); 1531 | } 1532 | .file::before { 1533 | content: ""; 1534 | display: block; 1535 | position: absolute; 1536 | top: var(--fspacing); 1537 | left: var(--fspacing); 1538 | height: calc(var(--fheight) - var(--fspacing)*2); 1539 | width: calc(var(--fwidth) - var(--fspacing)*2); 1540 | } 1541 | .file::after { 1542 | content: ""; 1543 | height: calc(var(--fheight)/2); 1544 | width: calc(var(--fheight)/2); 1545 | position: absolute; 1546 | right: 0; 1547 | bottom: 0; 1548 | overflow: visible; 1549 | } 1550 | 1551 | .list--files { 1552 | display: flex; 1553 | flex-wrap: wrap; 1554 | justify-content: center; 1555 | margin: auto; 1556 | padding: calc(var(--fspacing)*3) 0; 1557 | width: var(--ftotalwidth); 1558 | } 1559 | .list--files > li { 1560 | margin: 0; 1561 | padding: var(--fspacing); 1562 | } 1563 | 1564 | .type-document::before { 1565 | background-image: url(data:image/svg+xml;charset=utf-8;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiDQogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIg0KICAgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSIxNDBweCIgaGVpZ2h0PSIxNzBweCIgdmlld0JveD0iMCAwIDE0MCAxNzAiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPHBhdGggZmlsbD0iI0E3QTlBQyIgZD0iTTAsMHYxNzBoMTQwVjBIMHogTTEzMCwxNjBIMTBWMTBoMTIwVjE2MHogTTExMCw0MEgzMFYzMGg4MFY0MHogTTExMCw2MEgzMFY1MGg4MFY2MHogTTExMCw4MEgzMFY3MGg4MFY4MHoNCiAgIE0xMTAsMTAwSDMwVjkwaDgwVjEwMHogTTExMCwxMjBIMzB2LTEwaDgwVjEyMHogTTkwLDE0MEgzMHYtMTBoNjBWMTQweiIvPg0KPC9zdmc+); 1566 | height: calc(var(--fheight) - var(--fspacing)*2); 1567 | background-size: var(--bgsize2); 1568 | background-repeat: no-repeat; 1569 | } 1570 | 1571 | .type-image { 1572 | height: var(--fwidth); 1573 | height: calc(var(--fheight) - var(--fspacing)*2); 1574 | } 1575 | .type-image::before { 1576 | background-image: url(data:image/svg+xml;charset=utf-8;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiDQogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWxuczphPSJodHRwOi8vbnMuYWRvYmUuY29tL0Fkb2JlU1ZHVmlld2VyRXh0ZW5zaW9ucy8zLjAvIg0KICAgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSIxNDBweCIgaGVpZ2h0PSIxNDBweCIgdmlld0JveD0iMCAwIDE0MCAxNDAiIGVuYWJsZS1iYWNrZ3JvdW5kPSJuZXcgMCAwIDE0MCAxNDAiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPGc+DQogIDxwYXRoIGZpbGw9IiNBN0E5QUMiIGQ9Ik0wLDB2MTQwaDE0MFYwSDB6IE0xMzAsMTMwSDEwVjEwaDEyMFYxMzB6Ii8+DQogIDxwb2x5Z29uIGZpbGw9IiNFNkU3RTgiIHBvaW50cz0iOTAsMTEwIDQwLDQwIDEwLDgwIDEwLDEzMCA5MCwxMzAgICIvPg0KICA8cG9seWdvbiBmaWxsPSIjRDFEM0Q0IiBwb2ludHM9IjEwLDEzMCA1MCw5MCA2MCwxMDAgMTAwLDYwIDEzMCwxMzAgICIvPg0KPC9nPg0KPC9zdmc+); 1577 | height: calc(var(--fheight) - var(--fspacing)*2); 1578 | background-size: var(--bgsize3); 1579 | background-repeat: no-repeat; 1580 | } 1581 | 1582 | .state-synced::after { 1583 | background-image: url(data:image/svg+xml;charset=utf-8;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiDQogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWxuczphPSJodHRwOi8vbnMuYWRvYmUuY29tL0Fkb2JlU1ZHVmlld2VyRXh0ZW5zaW9ucy8zLjAvIg0KICAgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSI5MHB4IiBoZWlnaHQ9IjkwcHgiIHZpZXdCb3g9IjAgMCA5MCA5MCIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgOTAgOTAiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPGc+DQogIDxjaXJjbGUgZmlsbD0iIzAwQTY1MSIgY3g9IjQ1IiBjeT0iNDUiIHI9IjQ1Ii8+DQogIDxwYXRoIGZpbGw9IiNGRkZGRkYiIGQ9Ik0yMCw0NUwyMCw0NWMtMi44LDIuOC0yLjgsNy4yLDAsMTBsMTAuMSwxMC4xYzIuNywyLjcsNy4yLDIuNyw5LjksMEw3MCwzNWMyLjgtMi44LDIuOC03LjIsMC0xMGwwLDANCiAgICBjLTIuOC0yLjgtNy4yLTIuOC0xMCwwTDM1LDUwbC01LTVDMjcuMiw0Mi4yLDIyLjgsNDIuMiwyMCw0NXoiLz4NCjwvZz4NCjwvc3ZnPg==); 1584 | background-size: var(--bgsize); 1585 | background-repeat: no-repeat; 1586 | background-position: bottom right; 1587 | } 1588 | 1589 | .state-deleted::after { 1590 | background-image: url(data:image/svg+xml;charset=utf-8;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiDQogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWxuczphPSJodHRwOi8vbnMuYWRvYmUuY29tL0Fkb2JlU1ZHVmlld2VyRXh0ZW5zaW9ucy8zLjAvIg0KICAgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSI5MHB4IiBoZWlnaHQ9IjkwcHgiIHZpZXdCb3g9IjAgMCA5MCA5MCIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgOTAgOTAiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPGc+DQogIDxjaXJjbGUgZmlsbD0iI0VEMUMyNCIgY3g9IjQ1IiBjeT0iNDUiIHI9IjQ1Ii8+DQogIDxwYXRoIGZpbGw9IiNGRkZGRkYiIGQ9Ik02NSwyNUw2NSwyNWMtMi44LTIuOC03LjItMi44LTEwLDBMNDUsMzVMMzUsMjVjLTIuOC0yLjgtNy4yLTIuOC0xMCwwbDAsMGMtMi44LDIuOC0yLjgsNy4yLDAsMTBsMTAsMTANCiAgICBMMjUsNTVjLTIuOCwyLjgtMi44LDcuMiwwLDEwbDAsMGMyLjgsMi44LDcuMiwyLjgsMTAsMGwxMC0xMGwxMCwxMGMyLjgsMi44LDcuMiwyLjgsMTAsMGwwLDBjMi44LTIuOCwyLjgtNy4yLDAtMTBMNTUsNDVsMTAtMTANCiAgICBDNjcuOCwzMi4yLDY3LjgsMjcuOCw2NSwyNXoiLz4NCjwvZz4NCjwvc3ZnPg==); 1591 | background-size: var(--bgsize); 1592 | background-repeat: no-repeat; 1593 | background-position: bottom right; 1594 | } 1595 | .state-deleted::before { 1596 | opacity: .25; 1597 | } 1598 | 1599 | .state-locked::after { 1600 | background-image: url(data:image/svg+xml;charset=utf-8;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiDQogICB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWxuczphPSJodHRwOi8vbnMuYWRvYmUuY29tL0Fkb2JlU1ZHVmlld2VyRXh0ZW5zaW9ucy8zLjAvIg0KICAgeD0iMHB4IiB5PSIwcHgiIHdpZHRoPSI5MHB4IiBoZWlnaHQ9IjkwcHgiIHZpZXdCb3g9IjAgMCA5MCA5MCIgZW5hYmxlLWJhY2tncm91bmQ9Im5ldyAwIDAgOTAgOTAiIHhtbDpzcGFjZT0icHJlc2VydmUiPg0KPGc+DQogIDxjaXJjbGUgZmlsbD0iIzU4NTk1QiIgY3g9IjQ1IiBjeT0iNDUiIHI9IjQ1Ii8+DQogIDxyZWN0IHg9IjIwIiB5PSI0MCIgZmlsbD0iI0ZGRkZGRiIgd2lkdGg9IjUwIiBoZWlnaHQ9IjMwIi8+DQogIDxwYXRoIGZpbGw9IiNGRkZGRkYiIGQ9Ik0zMi41LDQ2LjVjLTIuOCwwLTUtMi4yLTUtNVYyOWMwLTkuNiw3LjktMTcuNSwxNy41LTE3LjVTNjIuNSwxOS40LDYyLjUsMjljMCwyLjgtMi4yLDUtNSw1cy01LTIuMi01LTUNCiAgICBjMC00LjEtMy40LTcuNS03LjUtNy41cy03LjUsMy40LTcuNSw3LjV2MTIuNUMzNy41LDQ0LjMsMzUuMyw0Ni41LDMyLjUsNDYuNXoiLz4NCjwvZz4NCjwvc3ZnPg==); 1601 | background-size: var(--bgsize); 1602 | background-repeat: no-repeat; 1603 | background-position: bottom right; 1604 | } 1605 | */ 1606 | -------------------------------------------------------------------------------- /Sources/common/common.js: -------------------------------------------------------------------------------- 1 | /* global $SD, $localizedStrings */ 2 | /* exported, $localizedStrings */ 3 | /* eslint no-undef: "error", 4 | curly: 0, 5 | no-caller: 0, 6 | wrap-iife: 0, 7 | one-var: 0, 8 | no-var: 0, 9 | vars-on-top: 0, 10 | quotes: ["error", "single"] 11 | */ 12 | /*eslint-env es6*/ 13 | 14 | // don't change this to let or const, because we rely on var's hoisting 15 | // eslint-disable-next-line no-use-before-define, no-var 16 | var $localizedStrings = $localizedStrings || {}, 17 | REMOTESETTINGS = REMOTESETTINGS || {}, 18 | DestinationEnum = Object.freeze({ 19 | HARDWARE_AND_SOFTWARE: 0, 20 | HARDWARE_ONLY: 1, 21 | SOFTWARE_ONLY: 2 22 | }), 23 | // eslint-disable-next-line no-unused-vars 24 | isQT = navigator.appVersion.includes('QtWebEngine'), 25 | debug = debug || false, 26 | debugLog = function () {}; 27 | 28 | const setDebugOutput = debug => (debug === true ? console.log.bind(window.console) : function() {}); 29 | debugLog = setDebugOutput(debug); 30 | 31 | // Create a wrapper to allow passing JSON to the socket 32 | WebSocket.prototype.sendJSON = function (jsn, log) { 33 | if (log) { 34 | console.log('SendJSON', this, jsn); 35 | } 36 | // if (this.readyState) { 37 | this.send(JSON.stringify(jsn)); 38 | // } 39 | }; 40 | 41 | /* eslint no-extend-native: ["error", { "exceptions": ["String"] }] */ 42 | String.prototype.lox = function () { 43 | var a = String(this); 44 | try { 45 | a = $localizedStrings[a] || a; 46 | } catch (b) {} 47 | return a; 48 | }; 49 | 50 | String.prototype.sprintf = function (inArr) { 51 | let i = 0; 52 | const args = inArr && Array.isArray(inArr) ? inArr : arguments; 53 | return this.replace(/%s/g, function () { 54 | return args[i++]; 55 | }); 56 | }; 57 | 58 | // eslint-disable-next-line no-unused-vars 59 | const sprintf = (s, ...args) => { 60 | let i = 0; 61 | return s.replace(/%s/g, function () { 62 | return args[i++]; 63 | }); 64 | }; 65 | 66 | const loadLocalization = (lang, pathPrefix, cb) => { 67 | Utils.readJson(`${pathPrefix}${lang}.json`, function (jsn) { 68 | const manifest = Utils.parseJson(jsn); 69 | $localizedStrings = manifest && manifest.hasOwnProperty('Localization') ? manifest['Localization'] : {}; 70 | debugLog($localizedStrings); 71 | if(cb && typeof cb === 'function') cb(); 72 | }); 73 | }; 74 | 75 | var Utils = { 76 | sleep: function(milliseconds) { 77 | return new Promise(resolve => setTimeout(resolve, milliseconds)); 78 | }, 79 | isUndefined: function (value) { 80 | return typeof value === 'undefined'; 81 | }, 82 | isObject: function (o) { 83 | return typeof o === 'object' && o !== null && o.constructor && o.constructor === Object; 84 | }, 85 | isPlainObject: function (o) { 86 | return typeof o === 'object' && o !== null && o.constructor && o.constructor === Object; 87 | }, 88 | isArray: function (value) { 89 | return Array.isArray(value); 90 | }, 91 | isNumber: function (value) { 92 | return typeof value === 'number' && value !== null; 93 | }, 94 | isInteger (value) { 95 | return typeof value === 'number' && value === Number(value); 96 | }, 97 | isString (value) { 98 | return typeof value === 'string'; 99 | }, 100 | isImage (value) { 101 | return value instanceof HTMLImageElement; 102 | }, 103 | isCanvas (value) { 104 | return value instanceof HTMLCanvasElement; 105 | }, 106 | isValue: function (value) { 107 | return !this.isObject(value) && !this.isArray(value); 108 | }, 109 | isNull: function (value) { 110 | return value === null; 111 | }, 112 | toInteger: function (value) { 113 | const INFINITY = 1 / 0, 114 | MAX_INTEGER = 1.7976931348623157e308; 115 | if (!value) { 116 | return value === 0 ? value : 0; 117 | } 118 | value = Number(value); 119 | if (value === INFINITY || value === -INFINITY) { 120 | const sign = value < 0 ? -1 : 1; 121 | return sign * MAX_INTEGER; 122 | } 123 | return value === value ? value : 0; 124 | } 125 | }; 126 | Utils.minmax = function (v, min = 0, max = 100) { 127 | return Math.min(max, Math.max(min, v)); 128 | }; 129 | 130 | Utils.unique = function(arr) { 131 | return Array.from(new Set(arr)); 132 | }; 133 | 134 | Utils.transformValue = function(prcnt, min, max) { 135 | return Math.round(((max - min) * prcnt) / 100 + min); 136 | }; 137 | 138 | Utils.rangeToPercent = function(value, min, max) { 139 | return (value - min) / (max - min); 140 | }; 141 | 142 | Utils.percentToRange = function(percent, min, max) { 143 | return (max - min) * percent + min; 144 | }; 145 | 146 | Utils.setDebugOutput = debug => { 147 | return debug === true ? console.log.bind(window.console) : function() {}; 148 | }; 149 | 150 | Utils.randomComponentName = function (len = 6) { 151 | return `${Utils.randomLowerString(len)}-${Utils.randomLowerString(len)}`; 152 | }; 153 | 154 | Utils.shuffleArray = arr => { 155 | let i, j, tmp; 156 | for(i = arr.length - 1;i > 0;i--) { 157 | j = Math.floor(Math.random() * (i + 1)); 158 | tmp = arr[i]; 159 | arr[i] = arr[j]; 160 | arr[j] = tmp; 161 | } 162 | return a; 163 | }; 164 | 165 | Utils.randomElementFromArray = arr => { 166 | return arr[Math.floor(Math.random() * arr.length)]; 167 | }; 168 | 169 | Utils.arrayToObject = (arr, key) => { 170 | arr.reduce((obj, item) => { 171 | obj[item[key]] = item; 172 | return obj; 173 | }, {}); 174 | }; 175 | 176 | Utils.randomString = function (len = 8) { 177 | return Array.apply(0, Array(len)) 178 | .map(function () { 179 | return (function (charset) { 180 | return charset.charAt(Math.floor(Math.random() * charset.length)); 181 | })('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'); 182 | }) 183 | .join(''); 184 | }; 185 | 186 | Utils.rs = function (len = 8) { 187 | return [...Array(len)].map(i => (~~(Math.random() * 36)).toString(36)).join(''); 188 | }; 189 | 190 | Utils.randomLowerString = function (len = 8) { 191 | return Array.apply(0, Array(len)) 192 | .map(function () { 193 | return (function (charset) { 194 | return charset.charAt(Math.floor(Math.random() * charset.length)); 195 | })('abcdefghijklmnopqrstuvwxyz'); 196 | }) 197 | .join(''); 198 | }; 199 | 200 | Utils.capitalize = function (str) { 201 | return str.charAt(0).toUpperCase() + str.slice(1); 202 | }; 203 | 204 | Utils.generateID = (len = 4, num = Number.MAX_SAFE_INTEGER) => { 205 | return Array.from(new Array(len)) 206 | .map(() => Math.floor(Math.random() * num).toString(16)) 207 | .join("-"); 208 | }; 209 | 210 | Utils.measureText = (text, font) => { 211 | const canvas = Utils.measureText.canvas || (Utils.measureText.canvas = document.createElement('canvas')); 212 | const ctx = canvas.getContext('2d'); 213 | ctx.font = font || 'bold 10pt system-ui'; 214 | return ctx.measureText(text).width; 215 | }; 216 | 217 | Utils.fixName = (d, dName) => { 218 | let i = 1; 219 | const base = dName; 220 | while (d[dName]) { 221 | dName = `${base} (${i})`; 222 | i++; 223 | } 224 | return dName; 225 | }; 226 | 227 | Utils.isEmptyString = str => { 228 | return !str || str.length === 0; 229 | }; 230 | 231 | Utils.isBlankString = str => { 232 | return !str || /^\s*$/.test(str); 233 | }; 234 | 235 | Utils.log = function () {}; 236 | Utils.count = 0; 237 | Utils.counter = function () { 238 | return (this.count += 1); 239 | }; 240 | Utils.getPrefix = function () { 241 | return this.prefix + this.counter(); 242 | }; 243 | 244 | Utils.prefix = Utils.randomString() + '_'; 245 | 246 | Utils.getUrlParameter = function (name) { 247 | const nameA = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]'); 248 | const regex = new RegExp('[\\?&]' + nameA + '=([^&#]*)'); 249 | const results = regex.exec(location.search.replace(/\/$/, '')); 250 | return results === null ? null : decodeURIComponent(results[1].replace(/\+/g, ' ')); 251 | }; 252 | 253 | Utils.debounce = function (func, wait = 100) { 254 | let timeout; 255 | return function (...args) { 256 | clearTimeout(timeout); 257 | timeout = setTimeout(() => { 258 | func.apply(this, args); 259 | }, wait); 260 | }; 261 | }; 262 | 263 | Utils.throttle = function(fn, threshold = 250, context) { 264 | let last, timer; 265 | return function() { 266 | var ctx = context || this; 267 | var now = new Date().getTime(), 268 | args = arguments; 269 | if(last && now < last + threshold) { 270 | clearTimeout(timer); 271 | timer = setTimeout(function() { 272 | last = now; 273 | fn.apply(ctx, args); 274 | }, threshold); 275 | } else { 276 | last = now; 277 | fn.apply(ctx, args); 278 | } 279 | }; 280 | }; 281 | 282 | Utils.getRandomColor = function () { 283 | return '#' + (((1 << 24) * Math.random()) | 0).toString(16).padStart(6, 0); // just a random color padded to 6 characters 284 | }; 285 | 286 | /* 287 | Quick utility to lighten or darken a color (doesn't take color-drifting, etc. into account) 288 | Usage: 289 | fadeColor('#061261', 100); // will lighten the color 290 | fadeColor('#200867'), -100); // will darken the color 291 | */ 292 | 293 | Utils.fadeColor = function (col, amt) { 294 | const min = Math.min, max = Math.max; 295 | const num = parseInt(col.replace(/#/g, ''), 16); 296 | const r = min(255, max((num >> 16) + amt, 0)); 297 | const g = min(255, max((num & 0x0000ff) + amt, 0)); 298 | const b = min(255, max(((num >> 8) & 0x00ff) + amt, 0)); 299 | return '#' + (g | (b << 8) | (r << 16)).toString(16).padStart(6, 0); 300 | }; 301 | 302 | Utils.lerpColorWithScale = function(startColor, targetColor, amount, scale = 0.5) { 303 | if(amount < scale) { 304 | return Utils.lerpColor(startColor, '#FFF2EC', amount * 2); 305 | } 306 | return Utils.lerpColor('#FFF2EC', targetColor, amount); 307 | }; 308 | 309 | Utils.lerpColor = function (startColor, targetColor, amount) { 310 | const ah = parseInt(startColor.replace(/#/g, ''), 16); 311 | const ar = ah >> 16; 312 | const ag = (ah >> 8) & 0xff; 313 | const ab = ah & 0xff; 314 | const bh = parseInt(targetColor.replace(/#/g, ''), 16); 315 | const br = bh >> 16; 316 | var bg = (bh >> 8) & 0xff; 317 | var bb = bh & 0xff; 318 | const rr = ar + amount * (br - ar); 319 | const rg = ag + amount * (bg - ag); 320 | const rb = ab + amount * (bb - ab); 321 | 322 | return ( 323 | '#' + 324 | (((1 << 24) + (rr << 16) + (rg << 8) + rb) | 0) 325 | .toString(16) 326 | .slice(1) 327 | .toUpperCase() 328 | ); 329 | }; 330 | 331 | Utils.hexToRgb = function (hex) { 332 | const match = hex.replace(/#/, '').match(/.{1,2}/g); 333 | return { 334 | r: parseInt(match[0], 16), 335 | g: parseInt(match[1], 16), 336 | b: parseInt(match[2], 16) 337 | }; 338 | }; 339 | 340 | Utils.rgbToHex = (r, g, b) => '#' + [r, g, b].map(x => { 341 | return x.toString(16).padStart(2, 0); 342 | }).join(''); 343 | 344 | 345 | Utils.nscolorToRgb = function (rP, gP, bP) { 346 | return { 347 | r : Math.round(rP * 255), 348 | g : Math.round(gP * 255), 349 | b : Math.round(bP * 255) 350 | }; 351 | }; 352 | 353 | Utils.nsColorToHex = function (rP, gP, bP) { 354 | const c = Utils.nscolorToRgb(rP, gP, bP); 355 | return Utils.rgbToHex(c.r, c.g, c.b); 356 | }; 357 | 358 | Utils.miredToKelvin = function (mired) { 359 | return Math.round(1e6 / mired); 360 | }; 361 | 362 | Utils.kelvinToMired = function(kelvin, roundTo) { 363 | return roundTo ? Utils.roundBy(Math.round(1e6 / kelvin), roundTo) : Math.round(1e6 / kelvin); 364 | }; 365 | 366 | Utils.roundBy = function(num, x) { 367 | return Math.round((num - 10) / x) * x; 368 | }; 369 | 370 | Utils.quantizeNumber = function(val, quantum, {cover = false} = {}) { 371 | if(!quantum) { 372 | return 0; 373 | } 374 | var remainder = val % quantum; 375 | // I'm intentionally not using Math.sign so that no polyfill is 376 | // required to use this library in legacy environments. 377 | var sign = val >= 0 ? 1 : -1; 378 | var mod = cover && remainder ? quantum : 0; 379 | return val - remainder + sign * mod; 380 | }; 381 | 382 | Utils.getBrightness = function (hexColor) { 383 | // http://www.w3.org/TR/AERT#color-contrast 384 | if (typeof hexColor === 'string' && hexColor.charAt(0) === '#') { 385 | var rgb = Utils.hexToRgb(hexColor); 386 | return (rgb.r * 299 + rgb.g * 587 + rgb.b * 114) / 1000; 387 | } 388 | return 0; 389 | }; 390 | 391 | Utils.readJson = function (file, callback) { 392 | var req = new XMLHttpRequest(); 393 | req.onerror = function (e) { 394 | // Utils.log(`[Utils][readJson] Error while trying to read ${file}`, e); 395 | }; 396 | req.overrideMimeType('application/json'); 397 | req.open('GET', file, true); 398 | req.onreadystatechange = function () { 399 | if (req.readyState === 4) { 400 | // && req.status == "200") { 401 | if (callback) callback(req.responseText); 402 | } 403 | }; 404 | req.send(null); 405 | }; 406 | 407 | Utils.readFile = function(url) { 408 | return new Promise(function(resolve, reject) { 409 | var xhr = new XMLHttpRequest(); 410 | xhr.onload = function() { 411 | //resolve(new Response(xhr.responseText, {status: xhr.status})) 412 | resolve(xhr.responseText); 413 | }; 414 | xhr.onerror = function() { 415 | reject(new TypeError('Local request failed')); 416 | }; 417 | xhr.open('GET', url); 418 | xhr.send(null); 419 | }); 420 | }; 421 | 422 | Utils.loadScript = function (url, callback) { 423 | const el = document.createElement('script'); 424 | el.src = url; 425 | el.onload = function () { 426 | callback(url, true); 427 | }; 428 | el.onerror = function () { 429 | console.error('Failed to load file: ' + url); 430 | callback(url, false); 431 | }; 432 | document.body.appendChild(el); 433 | }; 434 | 435 | Utils.createInlineWorker = (fn) => { 436 | const fnAsString = fn.toString().replace(/^[^{]*{\s*/, '').replace(/\s*}[^}]*$/, ''); 437 | return new Worker(URL.createObjectURL( 438 | new Blob([fnAsString], {type: 'text/javascript'}) 439 | )); 440 | }; 441 | 442 | Utils.parseJson = function (jsonString) { 443 | if (typeof jsonString === 'object') return jsonString; 444 | try { 445 | const o = JSON.parse(jsonString); 446 | 447 | // Handle non-exception-throwing cases: 448 | // Neither JSON.parse(false) or JSON.parse(1234) throw errors, hence the type-checking, 449 | // but... JSON.parse(null) returns null, and typeof null === "object", 450 | // so we must check for that, too. Thankfully, null is falsey, so this suffices: 451 | if (o && typeof o === 'object') { 452 | return o; 453 | } 454 | } catch (e) {} 455 | 456 | return false; 457 | }; 458 | 459 | Utils.parseJSONPromise = function (jsonString) { 460 | // fetch('/my-json-doc-as-string') 461 | // .then(Utils.parseJSONPromise) 462 | // .then(heresYourValidJSON) 463 | // .catch(error - or return default JSON) 464 | 465 | return new Promise((resolve, reject) => { 466 | try { 467 | const o = JSON.parse(jsonString); 468 | if(o && typeof o === 'object') { 469 | resolve(o); 470 | } else { 471 | resolve({}); 472 | } 473 | } catch (e) { 474 | reject(e); 475 | } 476 | }); 477 | }; 478 | 479 | 480 | Utils.getProperty = function (obj, dotSeparatedKeys, defaultValue) { 481 | if(arguments.length > 1 && typeof dotSeparatedKeys !== 'string') return undefined; 482 | if (typeof obj !== 'undefined' && typeof dotSeparatedKeys === 'string') { 483 | const pathArr = dotSeparatedKeys.split('.'); 484 | pathArr.forEach((key, idx, arr) => { 485 | if (typeof key === 'string' && key.includes('[')) { 486 | try { 487 | // extract the array index as string 488 | const pos = /\[([^)]+)\]/.exec(key)[1]; 489 | // get the index string length (i.e. '21'.length === 2) 490 | const posLen = pos.length; 491 | arr.splice(idx + 1, 0, Number(pos)); 492 | 493 | // keep the key (array name) without the index comprehension: 494 | // (i.e. key without [] (string of length 2) 495 | // and the length of the index (posLen)) 496 | arr[idx] = key.slice(0, -2 - posLen); // eslint-disable-line no-param-reassign 497 | } catch (e) { 498 | // do nothing 499 | } 500 | } 501 | }); 502 | // eslint-disable-next-line no-param-reassign, no-confusing-arrow 503 | obj = pathArr.reduce((o, key) => (o && o[key] !== 'undefined' ? o[key] : undefined), obj); 504 | } 505 | return obj === undefined ? defaultValue : obj; 506 | }; 507 | 508 | Utils.getProp = (jsn, str, defaultValue = {}, sep = '.') => { 509 | const arr = str.split(sep); 510 | return arr.reduce((obj, key) => (obj && obj.hasOwnProperty(key) ? obj[key] : defaultValue), jsn); 511 | }; 512 | 513 | Utils.setProp = function (jsonObj, path, value) { 514 | const names = path.split('.'); 515 | let jsn = jsonObj; 516 | 517 | // createNestedObject(jsn, names, values); 518 | // If a value is given, remove the last name and keep it for later: 519 | var targetProperty = arguments.length === 3 ? names.pop() : false; 520 | 521 | // Walk the hierarchy, creating new objects where needed. 522 | // If the lastName was removed, then the last object is not set yet: 523 | for (var i = 0; i < names.length; i++) { 524 | jsn = jsn[names[i]] = jsn[names[i]] || {}; 525 | } 526 | 527 | // If a value was given, set it to the target property (the last one): 528 | if (targetProperty) jsn = jsn[targetProperty] = value; 529 | 530 | // Return the last object in the hierarchy: 531 | return jsn; 532 | }; 533 | 534 | Utils.stringToHTML = function(html, all = false) { 535 | var template = document.createElement('template'); 536 | template.innerHTML = html.trim(); 537 | return all ? template.content : template.content.firstChild; 538 | }; 539 | 540 | Utils.stringToHTMLDiv = function(html, className) { 541 | var template = document.createElement('div'); 542 | if(className) template.classList.add(className); 543 | template.innerHTML = html.trim(); 544 | return template; 545 | }; 546 | 547 | 548 | Utils.getDataUri = function(url, callback, inCanvas, inFillcolor, w, h, clearCtx) { 549 | var image = new Image(); 550 | 551 | image.onload = function () { 552 | const canvas = inCanvas && Utils.isCanvas(inCanvas) ? inCanvas : document.createElement('canvas'); 553 | 554 | canvas.width = w || this.naturalWidth; // or 'width' if you want a special/scaled size 555 | canvas.height = h || this.naturalHeight; // or 'height' if you want a special/scaled size 556 | 557 | const ctx = canvas.getContext('2d'); 558 | if(clearCtx) { 559 | ctx.clearRect(0, 0, canvas.width, canvas.height); 560 | } 561 | if (inFillcolor) { 562 | ctx.fillStyle = inFillcolor; 563 | ctx.fillRect(0, 0, canvas.width, canvas.height); 564 | } 565 | ctx.drawImage(image, 0, 0); 566 | // Get raw image data 567 | // callback && callback(canvas.toDataURL('image/png').replace(/^data:image\/(png|jpg);base64,/, '')); 568 | 569 | // ... or get as Data URI 570 | callback(canvas.toDataURL('image/png')); 571 | }; 572 | 573 | image.src = url; 574 | }; 575 | 576 | Utils.s2c = function(svg, inCanvas, inContext, w, h, cb) { 577 | var img = new Image(); 578 | img.src = svg; 579 | img.onload = function() { 580 | let cnv = inCanvas || document.createElement('canvas'); 581 | let ctx = inContext || cnv.getContext('2d'); 582 | cnv.width = w || image.naturalWidth; 583 | cnv.height = h || image.naturalHeight; 584 | ctx.clearRect(0, 0, cnv.width, cnv.height); 585 | ctx.drawImage(img, 0, 0); 586 | if(cb) cb(this, cnv.toDataURL('image/png')); 587 | }; 588 | }; 589 | 590 | Utils.drawStringToCanvas = (text, size = 400, inCanvas) => { 591 | const canvas = inCanvas ? inCanvas : document.createElement('canvas'); 592 | const tempCtx = canvas.getContext('2d'); 593 | tempCtx.clearRect(0, 0, canvas.width, canvas.height); 594 | tempCtx.font = `${size}px Helvetica`; 595 | tempCtx.fontWeight = 'bold'; 596 | tempCtx.lineHeight = size; 597 | tempCtx.fillStyle = '#d8d8d8'; 598 | tempCtx.textAlign = 'center'; 599 | tempCtx.textBaseline = 'middle'; 600 | // tempCtx.fillText(`${text}`, canvas.width / 2, canvas.height / 2); 601 | 602 | // var text = 'All the world \'s a stage, and all the men and women merely players. They have their exits and their entrances; And one man in his time plays many parts.'; 603 | // wrapText(tempCtx, text, 0, 0, 144); 604 | Utils.wrapText(tempCtx, text, canvas.width / 2, canvas.height / 2, canvas.width); 605 | 606 | 607 | }; 608 | 609 | Utils.wrapText = (ctx, text, x = 0, y = 0, maxWidth = 144) => { 610 | const words = text.split(' '); 611 | let line = ''; 612 | let metrics; 613 | 614 | for(let i = 0;i < words.length;i++) { 615 | let tmpStr = words[i]; 616 | metrics = ctx.measureText(tmpStr); 617 | while(metrics.width > maxWidth) { 618 | tmpStr = tmpStr.substring(0, tmpStr.length - 1); 619 | metrics = ctx.measureText(tmpStr); 620 | } 621 | 622 | if(words[i] != tmpStr) { 623 | words.splice(i + 1, 0, words[i].substr(tmpStr.length)); 624 | words[i] = tmpStr; 625 | } 626 | 627 | tmpStr = words.length > 1 ? `${line}${words[i]} ` : `${line}${words[i]}`; 628 | metrics = ctx.measureText(tmpStr); 629 | // console.log("len:", words.length, metrics); 630 | 631 | if(metrics.width > maxWidth && i > 0) { 632 | y -= ctx.lineHeight / 2; 633 | ctx.fillText(line, x, y); 634 | line = `${words[i]} `; 635 | y += ctx.lineHeight; 636 | } 637 | else { 638 | // console.log(tmpStr, metrics.width, x, maxWidth); 639 | // x = (metrics.width + x) / 2; 640 | line = tmpStr; 641 | } 642 | } 643 | ctx.fillText(line, x, y); 644 | }; 645 | 646 | Utils.getFontSize = (options = {}) => { //thx gifshot! :) 647 | // var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; 648 | /* needs { 649 | text: options.title, // the text to measure 650 | width: options.width || window.innerWidth, // maximum available width 651 | minFontSize: options.minFontSize, //minimum acceptable font size 652 | } 653 | */ 654 | if(!document.body || options.resizeFont === false) { 655 | return options.fontSize; 656 | } 657 | 658 | var fontSize = parseInt(options.fontSize, 10) || 13; 659 | var minFontSize = parseInt(options.minFontSize, 10) || 6; 660 | var div = document.createElement('div'); 661 | var span = document.createElement('span'); 662 | 663 | div.setAttribute('width', options.width); 664 | div.appendChild(span); 665 | 666 | span.innerHTML = options.title || options.text || ''; 667 | span.style.fontSize = fontSize + 'px'; 668 | span.style.textIndent = '-9999px'; 669 | span.style.visibility = 'hidden'; 670 | 671 | document.body.appendChild(span); 672 | 673 | while((span.offsetWidth > options.width) && (fontSize >= minFontSize)) { 674 | span.style.fontSize = --fontSize + 'px'; 675 | } 676 | 677 | document.body.removeChild(span); 678 | 679 | return fontSize; //+ 'px'; 680 | }; 681 | 682 | 683 | // const greenKey = Utils.createColoredKeyAsDataUrl(null, "#00AA33"); 684 | 685 | Utils.createColoredKeyAsDataUrl = (inCanvas, inFillcolor, inWidth = 144, inHeight = 144) => { 686 | let canvas = inCanvas; 687 | if(!inCanvas) { 688 | canvas = document.createElement('canvas'); 689 | canvas.width = inWidth; 690 | canvas.height = inHeight; 691 | } 692 | const tempCtx = canvas.getContext('2d'); 693 | tempCtx.clearRect(0, 0, canvas.width, canvas.height); 694 | tempCtx.fillStyle = inFillcolor; 695 | tempCtx.fillRect(0, 0, canvas.width, canvas.height); 696 | return canvas.toDataURL('image/png'); 697 | }; 698 | 699 | /** Quick utility to inject a style to the DOM 700 | * e.g. injectStyle('.localbody { background-color: green;}') 701 | */ 702 | Utils.injectStyle = function (styles, styleId) { 703 | const node = document.createElement('style'); 704 | const tempID = styleId || Utils.randomString(8); 705 | node.setAttribute('id', tempID); 706 | node.innerHTML = styles; 707 | document.body.appendChild(node); 708 | return node; 709 | }; 710 | 711 | Utils.loadImageData = function(inUrl, callback) { 712 | let image = new Image(); 713 | image.onload = function() { 714 | callback(image); 715 | // or to get raw image data 716 | // callback && callback(canvas.toDataURL('image/png').replace(/^data:image\/(png|jpg);base64,/, '')); 717 | }; 718 | image.src = inUrl; 719 | }; 720 | 721 | Utils.loadImagePromise = url => 722 | new Promise(resolve => { 723 | const img = new Image(); 724 | img.onload = () => resolve({url, status: 'ok'}); 725 | img.onerror = () => resolve({url, status: 'error'}); 726 | img.src = url; 727 | }); 728 | 729 | Utils.loadImages = arrayOfUrls => Promise.all(arrayOfUrls.map(Utils.loadImagePromise)); 730 | 731 | Utils.loadImageWithOptions = (url, w, h, inCanvas, clearCtx, inFillcolor) => 732 | new Promise(resolve => { 733 | const img = new Image(); 734 | img.onload = () => { 735 | const canvas = inCanvas && Utils.isCanvas(inCanvas) ? inCanvas : document.createElement('canvas'); 736 | canvas.width = w || img.naturalWidth; // or 'width' if you want a special/scaled size 737 | canvas.height = h || img.naturalHeight; // or 'height' if you want a special/scaled size 738 | console.log('IMG', img, img.naturalWidth); 739 | const ctx = canvas.getContext('2d'); 740 | if(clearCtx) { 741 | ctx.clearRect(0, 0, canvas.width, canvas.height); 742 | } 743 | if(inFillcolor) { 744 | ctx.fillStyle = inFillcolor; 745 | ctx.fillRect(0, 0, canvas.width, canvas.height); 746 | } 747 | ctx.drawImage(img, 0, 0, canvas.width, canvas.height); 748 | window.bbb = canvas.toDataURL('image/png'); 749 | resolve({url, status: 'ok', image: canvas.toDataURL('image/png')}); // raw image with: canvas.toDataURL('image/png').replace(/^data:image\/(png|jpg);base64,/, ''); 750 | }; 751 | img.onerror = () => resolve({url, status: 'error'}); 752 | img.src = url; 753 | }); 754 | 755 | Utils.loadImage = function (inUrl, callback, inCanvas, inFillcolor) { 756 | /** Convert to array, so we may load multiple images at once */ 757 | const aUrl = !Array.isArray(inUrl) ? [inUrl] : inUrl; 758 | const canvas = inCanvas && inCanvas instanceof HTMLCanvasElement ? inCanvas : document.createElement('canvas'); 759 | var imgCount = aUrl.length - 1; 760 | const imgCache = {}; 761 | 762 | var ctx = canvas.getContext('2d'); 763 | ctx.globalCompositeOperation = 'source-over'; 764 | 765 | for (let url of aUrl) { 766 | let image = new Image(); 767 | let cnt = imgCount; 768 | let w = 144, h = 144; 769 | 770 | image.onload = function () { 771 | imgCache[url] = this; 772 | // look at the size of the first image 773 | if (url === aUrl[0]) { 774 | canvas.width = this.naturalWidth; // or 'width' if you want a special/scaled size 775 | canvas.height = this.naturalHeight; // or 'height' if you want a special/scaled size 776 | } 777 | // if (Object.keys(imgCache).length == aUrl.length) { 778 | if (cnt < 1) { 779 | if (inFillcolor) { 780 | ctx.fillStyle = inFillcolor; 781 | ctx.fillRect(0, 0, canvas.width, canvas.height); 782 | } 783 | // draw in the proper sequence FIFO 784 | aUrl.forEach(e => { 785 | if (!imgCache[e]) { 786 | console.warn(imgCache[e], imgCache); 787 | } 788 | 789 | if (imgCache[e]) { 790 | ctx.drawImage(imgCache[e], 0, 0); 791 | ctx.save(); 792 | } 793 | }); 794 | 795 | callback(canvas.toDataURL('image/png'), canvas.width, canvas.height, this); 796 | // or to get raw image data 797 | // callback && callback(canvas.toDataURL('image/png').replace(/^data:image\/(png|jpg);base64,/, '')); 798 | } 799 | }; 800 | 801 | imgCount--; 802 | image.src = url; 803 | } 804 | }; 805 | 806 | Utils.crop = function(canvas, offsetX, offsetY, width, height, callback, inCanvas) { 807 | var buffer = inCanvas && inCanvas instanceof HTMLCanvasElement ? inCanvas : document.createElement('canvas'); 808 | var ctx = buffer.getContext('2d'); 809 | buffer.width = width; 810 | buffer.height = height; 811 | 812 | // drawImage(source, source_X, source_Y, source_Width, source_Height, dest_X, dest_Y, dest_Width, dest_Height) 813 | ctx.drawImage(canvas, offsetX, offsetY, width, height, 0, 0, buffer.width, buffer.height); 814 | 815 | if(callback) callback(buffer.toDataURL('image/png')); 816 | }; 817 | 818 | Utils.getData = function (url) { 819 | // Return a new promise. 820 | return new Promise(function (resolve, reject) { 821 | // Do the usual XHR stuff 822 | var req = new XMLHttpRequest(); 823 | // Make sure to call .open asynchronously 824 | req.open('GET', url, true); 825 | 826 | req.onload = function () { 827 | // This is called even on 404 etc 828 | // so check the status 829 | if (req.status === 200) { 830 | // Resolve the promise with the response text 831 | resolve(req.response); 832 | } else { 833 | // Otherwise reject with the status text 834 | // which will hopefully be a meaningful error 835 | reject(Error(req.statusText)); 836 | } 837 | }; 838 | 839 | // Handle network errors 840 | req.onerror = function () { 841 | reject(Error('Network Error')); 842 | }; 843 | 844 | // Make the request 845 | req.send(); 846 | }); 847 | }; 848 | 849 | Utils.negArray = function (arr) { 850 | /** http://h3manth.com/new/blog/2013/negative-array-index-in-javascript/ */ 851 | return Proxy.create({ 852 | set: function (proxy, index, value) { 853 | index = parseInt(index); 854 | return index < 0 ? (arr[arr.length + index] = value) : (arr[index] = value); 855 | }, 856 | get: function (proxy, index) { 857 | index = parseInt(index); 858 | return index < 0 ? arr[arr.length + index] : arr[index]; 859 | } 860 | }); 861 | }; 862 | 863 | Utils.onChange = function(object, changedCallback, callback) { 864 | /** https://github.com/sindresorhus/on-change */ 865 | 'use strict'; 866 | const handler = { 867 | get (target, property, receiver) { 868 | try { 869 | return new Proxy(target[property], handler); 870 | } catch (err) { 871 | return Reflect.get(target, property, receiver); 872 | } 873 | }, 874 | set (target, property, value, receiver) { 875 | try { 876 | if(callback && !callback(target, property, value)) { 877 | throw new Error(`${value} is not a valid ${property}`); 878 | }; 879 | 880 | const oldValue = Reflect.get(target, property, value, receiver); 881 | const success = Reflect.set(target, property, value); 882 | 883 | if(oldValue !== value && typeof changedCallback === 'function') { 884 | changedCallback(target, property, value, oldValue); 885 | } 886 | return success; 887 | } catch(err) { 888 | console.warn(`proxy:property was not SAVED: ${err}`); 889 | return Reflect.get(target, property, receiver) || {}; 890 | } 891 | }, 892 | defineProperty (target, property, descriptor) { 893 | console.log('Utils.onChange:defineProperty:', target, property, descriptor); 894 | callback(target, property, descriptor); 895 | return Reflect.defineProperty(target, property, descriptor); 896 | }, 897 | deleteProperty (target, property) { 898 | console.log('Utils.onChange:deleteProperty:', target, property); 899 | callback(target, property); 900 | return Reflect.deleteProperty(target, property); 901 | } 902 | }; 903 | 904 | return new Proxy(object, handler); 905 | }; 906 | 907 | Utils.observeArray = function (object, callback) { 908 | 'use strict'; 909 | const array = []; 910 | const handler = { 911 | get (target, property, receiver) { 912 | try { 913 | return new Proxy(target[property], handler); 914 | } catch (err) { 915 | return Reflect.get(target, property, receiver); 916 | } 917 | }, 918 | set (target, property, value, receiver) { 919 | console.log('XXXUtils.observeArray:set1:', target, property, value, array); 920 | target[property] = value; 921 | console.log('XXXUtils.observeArray:set2:', target, property, value, array); 922 | }, 923 | defineProperty (target, property, descriptor) { 924 | callback(target, property, descriptor); 925 | return Reflect.defineProperty(target, property, descriptor); 926 | }, 927 | deleteProperty (target, property) { 928 | callback(target, property, descriptor); 929 | return Reflect.deleteProperty(target, property); 930 | } 931 | }; 932 | 933 | return new Proxy(object, handler); 934 | }; 935 | 936 | Utils.noop = function() {}; 937 | 938 | window['_'] = Utils; 939 | 940 | /* 941 | * connectElgatoStreamDeckSocket 942 | * This is the first function StreamDeck Software calls, when 943 | * establishing the connection to the plugin or the Property Inspector 944 | * @param {string} inPort - The socket's port to communicate with StreamDeck software. 945 | * @param {string} inUUID - A unique identifier, which StreamDeck uses to communicate with the plugin 946 | * @param {string} inMessageType - Identifies, if the event is meant for the property inspector or the plugin. 947 | * @param {string} inApplicationInfo - Information about the host (StreamDeck) application 948 | * @param {string} inActionInfo - Context is an internal identifier used to communicate to the host application. 949 | */ 950 | 951 | 952 | // eslint-disable-next-line no-unused-vars 953 | function connectElgatoStreamDeckSocket(inPort, inUUID, inMessageType, inApplicationInfo, inActionInfo) { 954 | StreamDeck.getInstance().connect(arguments); 955 | window.$SD.api = Object.assign({ send: SDApi.send }, SDApi.common, SDApi[inMessageType]); 956 | } 957 | 958 | /* legacy support */ 959 | 960 | function connectSocket(inPort, inUUID, inMessageType, inApplicationInfo, inActionInfo) { 961 | connectElgatoStreamDeckSocket(inPort, inUUID, inMessageType, inApplicationInfo, inActionInfo); 962 | } 963 | 964 | /** 965 | * StreamDeck object containing all required code to establish 966 | * communication with SD-Software and the Property Inspector 967 | */ 968 | 969 | const StreamDeck = (function () { 970 | // Hello it's me 971 | var instance; 972 | /* 973 | Populate and initialize internally used properties 974 | */ 975 | 976 | function init () { 977 | // *** PRIVATE *** 978 | 979 | var inPort, 980 | inUUID, 981 | inMessageType, 982 | inApplicationInfo, 983 | inActionInfo, 984 | websocket = null, 985 | filterPIMessages = ['didReceiveSettings', 'didReceiveGlobalSettings', 'propertyInspectorDidAppear', 'propertyInspectorDidDisappear']; 986 | // filterPIMessages = ['didReceiveGlobalSettings', 'propertyInspectorDidAppear', 'propertyInspectorDidDisappear']; 987 | 988 | var events = ELGEvents.eventEmitter(); 989 | 990 | function connect (args) { 991 | inPort = args[0]; 992 | inUUID = args[1]; 993 | inMessageType = args[2]; 994 | inApplicationInfo = Utils.parseJson(args[3]); 995 | inActionInfo = args[4] !== 'undefined' ? Utils.parseJson(args[4]) : args[4]; 996 | 997 | const lang = Utils.getProp(inApplicationInfo,'application.language', false); 998 | if(lang) { 999 | loadLocalization(lang, inMessageType === 'registerPropertyInspector' ? '../' : './', function() { 1000 | events.emit('localizationLoaded', {language: lang}); 1001 | }); 1002 | } 1003 | 1004 | /** restrict the API to what's possible 1005 | * within Plugin or Property Inspector 1006 | * 1007 | */ 1008 | // $SD.api = SDApi[inMessageType]; 1009 | 1010 | if (websocket) { 1011 | websocket.close(); 1012 | websocket = null; 1013 | } 1014 | 1015 | websocket = new WebSocket('ws://127.0.0.1:' + inPort); 1016 | 1017 | websocket.onopen = function () { 1018 | var json = { 1019 | event: inMessageType, 1020 | uuid: inUUID 1021 | }; 1022 | 1023 | // console.log('***************', inMessageType + " websocket:onopen", inUUID, json); 1024 | 1025 | websocket.sendJSON(json); 1026 | $SD.uuid = inUUID; 1027 | $SD.actionInfo = inActionInfo; 1028 | $SD.applicationInfo = inApplicationInfo; 1029 | $SD.messageType = inMessageType; 1030 | $SD.connection = websocket; 1031 | 1032 | instance.emit('connected', { 1033 | connection: websocket, 1034 | port: inPort, 1035 | uuid: inUUID, 1036 | actionInfo: inActionInfo, 1037 | applicationInfo: inApplicationInfo, 1038 | messageType: inMessageType 1039 | }); 1040 | }; 1041 | 1042 | websocket.onerror = function (evt) { 1043 | console.warn('WEBOCKET ERROR', evt, evt.data); 1044 | }; 1045 | 1046 | websocket.onclose = function (evt) { 1047 | // Websocket is closed 1048 | var reason = WEBSOCKETERROR(evt); 1049 | console.warn('[STREAMDECK]***** WEBOCKET CLOSED **** reason:', reason); 1050 | }; 1051 | 1052 | websocket.onmessage = function (evt) { 1053 | var jsonObj = Utils.parseJson(evt.data), 1054 | m; 1055 | 1056 | // console.log('[STREAMDECK] websocket.onmessage ... ', jsonObj.event, jsonObj); 1057 | 1058 | if (!jsonObj.hasOwnProperty('action')) { 1059 | m = jsonObj.event; 1060 | // console.log('%c%s', 'color: white; background: red; font-size: 12px;', '[common.js]onmessage:', m); 1061 | } else { 1062 | const e = jsonObj['event']; 1063 | if(filterPIMessages.includes(e)) { 1064 | // console.log('%c%s', 'color: white; background: red; font-size: 13px;', 'EMIITING GLOBAL PIMESSAGE ', e, jsonObj); 1065 | events.emit(e, jsonObj); 1066 | // m = jsonObj['action'] + '.' + jsonObj['event']; 1067 | } 1068 | switch (inMessageType) { 1069 | case 'registerPlugin': 1070 | m = jsonObj['action'] + '.' + jsonObj['event']; 1071 | break; 1072 | case 'registerPropertyInspector': 1073 | m = 'sendToPropertyInspector'; 1074 | break; 1075 | default: 1076 | console.log('%c%s', 'color: white; background: red; font-size: 12px;', '[STREAMDECK] websocket.onmessage +++++++++ PROBLEM ++++++++'); 1077 | console.warn('UNREGISTERED MESSAGETYPE:', inMessageType); 1078 | } 1079 | } 1080 | 1081 | if(m && m !== '') events.emit(m, jsonObj); 1082 | }; 1083 | 1084 | instance.connection = websocket; 1085 | } 1086 | 1087 | return { 1088 | // *** PUBLIC *** 1089 | 1090 | uuid: inUUID, 1091 | on: events.on, 1092 | emit: events.emit, 1093 | connection: websocket, 1094 | connect: connect, 1095 | api: null, 1096 | }; 1097 | } 1098 | 1099 | return { 1100 | getInstance: function () { 1101 | if (!instance) { 1102 | instance = init(); 1103 | } 1104 | return instance; 1105 | } 1106 | }; 1107 | })(); 1108 | 1109 | // eslint-disable-next-line no-unused-vars 1110 | function initializeControlCenterClient () { 1111 | const settings = Object.assign(REMOTESETTINGS || {}, { debug: false }); 1112 | var $CC = new ControlCenterClient(settings); 1113 | window['$CC'] = $CC; 1114 | return $CC; 1115 | } 1116 | 1117 | /** ELGEvents 1118 | * Publish/Subscribe pattern to quickly signal events to 1119 | * the plugin, property inspector and data. 1120 | */ 1121 | 1122 | const ELGEvents = { 1123 | eventEmitter: function (name, fn) { 1124 | const eventList = new Map(); 1125 | 1126 | const on = (name, fn) => { 1127 | if (!eventList.has(name)) eventList.set(name, ELGEvents.pubSub()); 1128 | 1129 | return eventList.get(name).sub(fn); 1130 | }; 1131 | 1132 | const has = name => eventList.has(name); 1133 | 1134 | const emit = (name, data) => eventList.has(name) && eventList.get(name).pub(data); 1135 | 1136 | return Object.freeze({ on, has, emit, eventList }); 1137 | }, 1138 | 1139 | pubSub: function pubSub () { 1140 | const subscribers = new Set(); 1141 | 1142 | const sub = fn => { 1143 | subscribers.add(fn); 1144 | return () => { 1145 | subscribers.delete(fn); 1146 | }; 1147 | }; 1148 | 1149 | const pub = data => subscribers.forEach(fn => fn(data)); 1150 | return Object.freeze({ pub, sub }); 1151 | } 1152 | }; 1153 | 1154 | /** SDApi 1155 | * This ist the main API to communicate between plugin, property inspector and 1156 | * application host. 1157 | * Internal functions: 1158 | * - setContext: sets the context of the current plugin 1159 | * - exec: prepare the correct JSON structure and send 1160 | * 1161 | * Methods exposed in the $SD.api alias 1162 | * Messages send from the plugin 1163 | * ----------------------------- 1164 | * - showAlert 1165 | * - showOK 1166 | * - setSettings 1167 | * - setTitle 1168 | * - setImage 1169 | * - sendToPropertyInspector 1170 | * 1171 | * Messages send from Property Inspector 1172 | * ------------------------------------- 1173 | * - sendToPlugin 1174 | * 1175 | * Messages received in the plugin 1176 | * ------------------------------- 1177 | * willAppear 1178 | * willDisappear 1179 | * keyDown 1180 | * keyUp 1181 | */ 1182 | 1183 | const SDApi = { 1184 | send: function (context, fn, payload, debug) { 1185 | /** Combine the passed JSON with the name of the event and it's context 1186 | * If the payload contains 'event' or 'context' keys, it will overwrite existing 'event' or 'context'. 1187 | * This function is non-mutating and thereby creates a new object containing 1188 | * all keys of the original JSON objects. 1189 | */ 1190 | const pl = Object.assign({}, { event: fn, context: context }, payload); 1191 | 1192 | /** Check, if we have a connection, and if, send the JSON payload */ 1193 | if (debug) { 1194 | console.log('-----SDApi.send-----'); 1195 | console.log('context', context); 1196 | console.log(pl); 1197 | console.log(payload.payload); 1198 | console.log(JSON.stringify(payload.payload)); 1199 | console.log('-------'); 1200 | } 1201 | $SD.connection && $SD.connection.sendJSON(pl); 1202 | 1203 | /** 1204 | * DEBUG-Utility to quickly show the current payload in the Property Inspector. 1205 | */ 1206 | 1207 | if($SD.connection && ['sendToPropertyInspector', 'showOK', 'showAlert', 'setSettings'].indexOf(fn) === -1) { 1208 | // console.log("send.sendToPropertyInspector", payload); 1209 | // this.sendToPropertyInspector(context, typeof payload.payload==='object' ? JSON.stringify(payload.payload) : JSON.stringify({'payload':payload.payload}), pl['action']); 1210 | } 1211 | }, 1212 | 1213 | registerPlugin: { 1214 | 1215 | /** Messages send from the plugin */ 1216 | showAlert: function (context) { 1217 | SDApi.send(context, 'showAlert', {}); 1218 | }, 1219 | 1220 | showOk: function (context) { 1221 | SDApi.send(context, 'showOk', {}); 1222 | }, 1223 | 1224 | 1225 | setState: function (context, payload) { 1226 | SDApi.send(context, 'setState', { 1227 | payload: { 1228 | state: 1 - Number(payload === 0) 1229 | } 1230 | }); 1231 | }, 1232 | 1233 | setTitle: function (context, title, target) { 1234 | SDApi.send(context, 'setTitle', { 1235 | payload: { 1236 | title: '' + title || '', 1237 | target: target || DestinationEnum.HARDWARE_AND_SOFTWARE 1238 | } 1239 | }); 1240 | }, 1241 | 1242 | clearTitle: function(context, title, target) { 1243 | SDApi.send(context, 'setTitle', { 1244 | payload: { 1245 | target: target || DestinationEnum.HARDWARE_AND_SOFTWARE 1246 | } 1247 | }); 1248 | }, 1249 | 1250 | setImage: function (context, img, target) { 1251 | SDApi.send(context, 'setImage', { 1252 | payload: { 1253 | image: img || '', 1254 | target: target || DestinationEnum.HARDWARE_AND_SOFTWARE 1255 | } 1256 | }); 1257 | }, 1258 | 1259 | sendToPropertyInspector: function (context, payload, action) { 1260 | SDApi.send(context, 'sendToPropertyInspector', { 1261 | action: action, 1262 | payload: payload 1263 | }); 1264 | }, 1265 | 1266 | showUrl2: function (context, urlToOpen) { 1267 | SDApi.send(context, 'openUrl', { 1268 | payload: { 1269 | url: urlToOpen 1270 | } 1271 | }); 1272 | } 1273 | }, 1274 | 1275 | /** Messages send from Property Inspector */ 1276 | 1277 | registerPropertyInspector: { 1278 | 1279 | sendToPlugin: function (piUUID, action, payload) { 1280 | SDApi.send( 1281 | piUUID, 1282 | 'sendToPlugin', 1283 | { 1284 | action: action, 1285 | payload: payload || {} 1286 | }, 1287 | false 1288 | ); 1289 | } 1290 | }, 1291 | 1292 | /** COMMON */ 1293 | 1294 | common: { 1295 | 1296 | getSettings: function(context, payload) { 1297 | const uuid = context ? context : $SD.uuid; 1298 | SDApi.send(uuid, 'getSettings', {}); 1299 | }, 1300 | 1301 | setSettings: function(context, payload) { 1302 | if(!context) context = $SD.uuid; 1303 | SDApi.send(context, 'setSettings', { 1304 | payload: payload 1305 | }); 1306 | }, 1307 | 1308 | getGlobalSettings: function(context) { 1309 | const uuid = context ? context : $SD.uuid; 1310 | SDApi.send(uuid, 'getGlobalSettings', {}); 1311 | }, 1312 | 1313 | setGlobalSettings: function(context, payload) { 1314 | const uuid = context ? context : $SD.uuid; 1315 | SDApi.send(uuid, 'setGlobalSettings', { 1316 | payload: payload 1317 | }); 1318 | }, 1319 | 1320 | switchToProfile: function(inContext, inDeviceID, inProfileName = null) { 1321 | if(inDeviceID && inDeviceID.length !== 0) { 1322 | const context = inContext ? inContext : $SD.uuid; 1323 | const device = inDeviceID; 1324 | const event = 'switchToProfile'; 1325 | // if (inProfileName && inProfileName.length !== 0) { 1326 | const payload = { 1327 | profile: inProfileName 1328 | }; 1329 | const pl = Object.assign({}, {event, context, device}, {payload: payload}); 1330 | console.log("$SD.switchToProfile", inProfileName, pl); 1331 | $SD.connection && $SD.connection.sendJSON(pl); 1332 | // } 1333 | } 1334 | }, 1335 | 1336 | logMessage: function() { 1337 | /** 1338 | * for logMessage we don't need a context, so we allow both 1339 | * logMessage(unneededContext, 'message') 1340 | * and 1341 | * logMessage('message') 1342 | */ 1343 | 1344 | let payload = arguments.length > 1 ? arguments[1] : arguments[0]; 1345 | 1346 | SDApi.send(null, 'logMessage', { 1347 | payload: { 1348 | message: payload 1349 | } 1350 | }); 1351 | }, 1352 | 1353 | openUrl: function (context, urlToOpen) { 1354 | SDApi.send(context, 'openUrl', { 1355 | payload: { 1356 | url: urlToOpen 1357 | } 1358 | }); 1359 | }, 1360 | 1361 | test: function () { 1362 | console.log(this); 1363 | console.log(SDApi); 1364 | }, 1365 | 1366 | debugPrint: function (context, inString) { 1367 | // console.log("------------ DEBUGPRINT"); 1368 | // console.log([].slice.apply(arguments).join()); 1369 | // console.log("------------ DEBUGPRINT"); 1370 | SDApi.send(context, 'debugPrint', { 1371 | payload: [].slice.apply(arguments).join('.') || '' 1372 | }); 1373 | }, 1374 | 1375 | dbgSend: function (fn, context) { 1376 | /** lookup if an appropriate function exists */ 1377 | if ($SD.connection && this[fn] && typeof this[fn] === 'function') { 1378 | /** verify if type of payload is an object/json */ 1379 | const payload = this[fn](); 1380 | if (typeof payload === 'object') { 1381 | Object.assign({ event: fn, context: context }, payload); 1382 | $SD.connection && $SD.connection.sendJSON(payload); 1383 | } 1384 | } 1385 | console.log(this, fn, typeof this[fn], this[fn]()); 1386 | } 1387 | 1388 | } 1389 | }; 1390 | 1391 | /** 1392 | * This is the instance of the StreamDeck object. 1393 | * There's only one StreamDeck object, which carries 1394 | * connection parameters and handles communication 1395 | * to/from the software's PluginManager. 1396 | */ 1397 | 1398 | window.$SD = StreamDeck.getInstance(); 1399 | window.$SD.api = SDApi; 1400 | 1401 | function WEBSOCKETERROR (evt) { 1402 | // Websocket is closed 1403 | var reason = ''; 1404 | if (evt.code === 1000) { 1405 | reason = 'Normal Closure. The purpose for which the connection was established has been fulfilled.'; 1406 | } else if (evt.code === 1001) { 1407 | reason = 'Going Away. An endpoint is "going away", such as a server going down or a browser having navigated away from a page.'; 1408 | } else if (evt.code === 1002) { 1409 | reason = 'Protocol error. An endpoint is terminating the connection due to a protocol error'; 1410 | } else if (evt.code === 1003) { 1411 | reason = 'Unsupported Data. An endpoint received a type of data it doesn\'t support.'; 1412 | } else if (evt.code === 1004) { 1413 | reason = '--Reserved--. The specific meaning might be defined in the future.'; 1414 | } else if (evt.code === 1005) { 1415 | reason = 'No Status. No status code was actually present.'; 1416 | } else if (evt.code === 1006) { 1417 | reason = 'Abnormal Closure. The connection was closed abnormally, e.g., without sending or receiving a Close control frame'; 1418 | } else if (evt.code === 1007) { 1419 | reason = 'Invalid frame payload data. The connection was closed, because the received data was not consistent with the type of the message (e.g., non-UTF-8 [http://tools.ietf.org/html/rfc3629]).'; 1420 | } else if (evt.code === 1008) { 1421 | reason = 'Policy Violation. The connection was closed, because current message data "violates its policy". This reason is given either if there is no other suitable reason, or if there is a need to hide specific details about the policy.'; 1422 | } else if (evt.code === 1009) { 1423 | reason = 'Message Too Big. Connection closed because the message is too big for it to process.'; 1424 | } else if(evt.code === 1010) { 1425 | // Note that this status code is not used by the server, because it can fail the WebSocket handshake instead. 1426 | reason = 1427 | 'Mandatory Ext. Connection is terminated the connection because the server didn\'t negotiate one or more extensions in the WebSocket handshake.
Mandatory extensions were: ' + 1428 | evt.reason; 1429 | } else if (evt.code === 1011) { 1430 | reason = 'Internal Server Error. Connection closed because it encountered an unexpected condition that prevented it from fulfilling the request.'; 1431 | } else if (evt.code === 1015) { 1432 | reason = 'TLS Handshake. The connection was closed due to a failure to perform a TLS handshake (e.g., the server certificate can\'t be verified).'; 1433 | } else { 1434 | reason = 'Unknown reason'; 1435 | } 1436 | 1437 | return reason; 1438 | } 1439 | 1440 | const SOCKETERRORS = { 1441 | '0': 'The connection has not yet been established', 1442 | '1': 'The connection is established and communication is possible', 1443 | '2': 'The connection is going through the closing handshake', 1444 | '3': 'The connection has been closed or could not be opened' 1445 | }; 1446 | --------------------------------------------------------------------------------