├── release
└── .gitignore
├── .gitignore
├── com.dflydev.streamdeck.restreamio.sdPlugin
├── public
│ ├── dist
│ │ └── .gitignore
│ ├── icons
│ │ └── caret.svg
│ ├── plugin.html
│ ├── property-inspector.html
│ └── sdpi.css
├── icons
│ ├── action.png
│ ├── action@2x.png
│ ├── actionimage.png
│ ├── channel-off.png
│ ├── channel-on.png
│ ├── plugin-icon.png
│ ├── category-icon.png
│ ├── channel-on@2x.png
│ ├── actionimage@2x.png
│ ├── category-icon@2x.png
│ ├── channel-off@2x.png
│ ├── channel-unknown.png
│ ├── plugin-icon@2x.png
│ ├── channel-unknown@2x.png
│ ├── channel-action-image.png
│ └── channel-action-image@2x.png
└── manifest.json
├── .github
├── FUNDING.yml
└── workflows
│ └── pre-release.yml
├── tsconfig.eslint.json
├── .eslintignore
├── .prettierignore
├── .env.example
├── .prettierrc.json
├── .editorconfig
├── src
├── settings
│ ├── global-settings.ts
│ ├── settings.ts
│ ├── managed-settings.ts
│ ├── managed-global-settings.ts
│ └── deferred-global-settings-requests.ts
├── actions
│ ├── action.ts
│ └── channel-action.ts
├── restreamio-plugin.ts
├── restreamio
│ ├── types.ts
│ └── restreamio-client.ts
└── restreamio-property-inspector.ts
├── tsconfig.json
├── LICENSE.txt
├── package.json
├── .eslintrc.json
└── README.md
/release/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 | .env
3 | package-lock.json
4 |
--------------------------------------------------------------------------------
/com.dflydev.streamdeck.restreamio.sdPlugin/public/dist/.gitignore:
--------------------------------------------------------------------------------
1 | *
2 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: [simensen, dflydev]
2 | open_collective: dflydev
3 |
--------------------------------------------------------------------------------
/tsconfig.eslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "exclude": []
4 | }
5 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | /build/
2 | /com.dflydev.streamdeck.restreamio.sdPlugin/public/dist/
3 | /node_modules/
4 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | /build/
2 | /com.dflydev.streamdeck.restreamio.sdPlugin/public/dist/
3 | /node_modules/
4 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | STREAMDECK_RESTREAMIO_LOGIN_URL=https://example.test/login
2 | STREAMDECK_RESTREAMIO_REFRESH_URL=https://example.test/refresh
3 |
--------------------------------------------------------------------------------
/com.dflydev.streamdeck.restreamio.sdPlugin/icons/action.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dflydev/streamdeck-restreamio/main/com.dflydev.streamdeck.restreamio.sdPlugin/icons/action.png
--------------------------------------------------------------------------------
/com.dflydev.streamdeck.restreamio.sdPlugin/icons/action@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dflydev/streamdeck-restreamio/main/com.dflydev.streamdeck.restreamio.sdPlugin/icons/action@2x.png
--------------------------------------------------------------------------------
/com.dflydev.streamdeck.restreamio.sdPlugin/icons/actionimage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dflydev/streamdeck-restreamio/main/com.dflydev.streamdeck.restreamio.sdPlugin/icons/actionimage.png
--------------------------------------------------------------------------------
/com.dflydev.streamdeck.restreamio.sdPlugin/icons/channel-off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dflydev/streamdeck-restreamio/main/com.dflydev.streamdeck.restreamio.sdPlugin/icons/channel-off.png
--------------------------------------------------------------------------------
/com.dflydev.streamdeck.restreamio.sdPlugin/icons/channel-on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dflydev/streamdeck-restreamio/main/com.dflydev.streamdeck.restreamio.sdPlugin/icons/channel-on.png
--------------------------------------------------------------------------------
/com.dflydev.streamdeck.restreamio.sdPlugin/icons/plugin-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dflydev/streamdeck-restreamio/main/com.dflydev.streamdeck.restreamio.sdPlugin/icons/plugin-icon.png
--------------------------------------------------------------------------------
/com.dflydev.streamdeck.restreamio.sdPlugin/icons/category-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dflydev/streamdeck-restreamio/main/com.dflydev.streamdeck.restreamio.sdPlugin/icons/category-icon.png
--------------------------------------------------------------------------------
/com.dflydev.streamdeck.restreamio.sdPlugin/icons/channel-on@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dflydev/streamdeck-restreamio/main/com.dflydev.streamdeck.restreamio.sdPlugin/icons/channel-on@2x.png
--------------------------------------------------------------------------------
/com.dflydev.streamdeck.restreamio.sdPlugin/icons/actionimage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dflydev/streamdeck-restreamio/main/com.dflydev.streamdeck.restreamio.sdPlugin/icons/actionimage@2x.png
--------------------------------------------------------------------------------
/com.dflydev.streamdeck.restreamio.sdPlugin/icons/category-icon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dflydev/streamdeck-restreamio/main/com.dflydev.streamdeck.restreamio.sdPlugin/icons/category-icon@2x.png
--------------------------------------------------------------------------------
/com.dflydev.streamdeck.restreamio.sdPlugin/icons/channel-off@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dflydev/streamdeck-restreamio/main/com.dflydev.streamdeck.restreamio.sdPlugin/icons/channel-off@2x.png
--------------------------------------------------------------------------------
/com.dflydev.streamdeck.restreamio.sdPlugin/icons/channel-unknown.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dflydev/streamdeck-restreamio/main/com.dflydev.streamdeck.restreamio.sdPlugin/icons/channel-unknown.png
--------------------------------------------------------------------------------
/com.dflydev.streamdeck.restreamio.sdPlugin/icons/plugin-icon@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dflydev/streamdeck-restreamio/main/com.dflydev.streamdeck.restreamio.sdPlugin/icons/plugin-icon@2x.png
--------------------------------------------------------------------------------
/com.dflydev.streamdeck.restreamio.sdPlugin/icons/channel-unknown@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dflydev/streamdeck-restreamio/main/com.dflydev.streamdeck.restreamio.sdPlugin/icons/channel-unknown@2x.png
--------------------------------------------------------------------------------
/com.dflydev.streamdeck.restreamio.sdPlugin/icons/channel-action-image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dflydev/streamdeck-restreamio/main/com.dflydev.streamdeck.restreamio.sdPlugin/icons/channel-action-image.png
--------------------------------------------------------------------------------
/com.dflydev.streamdeck.restreamio.sdPlugin/icons/channel-action-image@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dflydev/streamdeck-restreamio/main/com.dflydev.streamdeck.restreamio.sdPlugin/icons/channel-action-image@2x.png
--------------------------------------------------------------------------------
/com.dflydev.streamdeck.restreamio.sdPlugin/public/icons/caret.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 80,
3 | "tabWidth": 2,
4 | "useTabs": false,
5 | "semi": false,
6 | "singleQuote": true,
7 | "trailingComma": "all",
8 | "bracketSpacing": false,
9 | "arrowParens": "avoid",
10 | "parser": "typescript"
11 | }
12 |
--------------------------------------------------------------------------------
/com.dflydev.streamdeck.restreamio.sdPlugin/public/plugin.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | com.dflydev.streamdeck.restreamio
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | insert_final_newline = true
7 | indent_style = space
8 | indent_size = 4
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
13 |
14 | [*.yml]
15 | indent_size = 2
16 |
17 | [*.{js,ts,json}]
18 | indent_size = 2
19 |
20 | [*.vue]
21 | indent_size = 2
22 |
--------------------------------------------------------------------------------
/src/settings/global-settings.ts:
--------------------------------------------------------------------------------
1 | import {
2 | RestreamioAccountCollection,
3 | RestreamioStreamingPlatform,
4 | } from '../restreamio/types'
5 |
6 | export function createEmptyGlobalSettings(): GlobalSettings {
7 | return {
8 | restreamioAccounts: {},
9 | restreamioStreamingPlatforms: [],
10 | }
11 | }
12 |
13 | export interface GlobalSettings {
14 | restreamioAccounts: RestreamioAccountCollection
15 | restreamioStreamingPlatforms: RestreamioStreamingPlatform[]
16 | }
17 |
--------------------------------------------------------------------------------
/src/settings/settings.ts:
--------------------------------------------------------------------------------
1 | import {RestreamioAccountId, RestreamioChannelId} from '../restreamio/types'
2 |
3 | export function createEmptySettings(): Settings {
4 | return new (class implements Settings {
5 | restreamioAccountId: null
6 | restreamioChannelId: null
7 | name: null
8 | platform: null
9 | })()
10 | }
11 |
12 | export interface Settings {
13 | restreamioAccountId: RestreamioAccountId | null
14 | restreamioChannelId: RestreamioChannelId | null
15 | name: string | null
16 | platform: string | null
17 | }
18 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "CommonJS",
4 | "target": "ES6",
5 | "noImplicitAny": true,
6 | "removeComments": true,
7 | "preserveConstEnums": true,
8 | "declaration": true,
9 | "sourceMap": false,
10 | "forceConsistentCasingInFileNames": true,
11 | "strictPropertyInitialization": false,
12 | "skipLibCheck": true,
13 | "outDir": "./build",
14 | "esModuleInterop": true,
15 | "experimentalDecorators": true,
16 | "lib": [
17 | "dom",
18 | "ESNext"
19 | ]
20 | },
21 | "include": [
22 | "src/**/*"
23 | ],
24 | "exclude": [
25 | "node_modules",
26 | "**/*.spec.ts"
27 | ],
28 | "extends": "@tsconfig/recommended/tsconfig.json"
29 | }
30 |
--------------------------------------------------------------------------------
/src/actions/action.ts:
--------------------------------------------------------------------------------
1 | import {RestreamioPlugin} from '../restreamio-plugin'
2 | import {ManagedGlobalSettings} from '../settings/managed-global-settings'
3 | import {ManagedSettings} from '../settings/managed-settings'
4 | import {RestreamioClient} from '../restreamio/restreamio-client'
5 | import {StreamDeckAction} from 'streamdeck-typescript'
6 |
7 | export abstract class Action extends StreamDeckAction<
8 | RestreamioPlugin,
9 | TheAction
10 | > {
11 | protected restreamioClient(context?: string): RestreamioClient {
12 | return RestreamioClient.fromSettingsManager(
13 | this.plugin.settingsManager,
14 | context,
15 | )
16 | }
17 |
18 | protected get globalSettings(): ManagedGlobalSettings {
19 | return new ManagedGlobalSettings(this.plugin.settingsManager)
20 | }
21 |
22 | protected settings(context: string): ManagedSettings {
23 | return new ManagedSettings(this.plugin.settingsManager, context)
24 | }
25 |
26 | constructor(protected plugin: RestreamioPlugin, actionName: string) {
27 | super(plugin, actionName)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2021 dflydev
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is furnished
8 | to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19 | THE SOFTWARE.
20 |
--------------------------------------------------------------------------------
/com.dflydev.streamdeck.restreamio.sdPlugin/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "Actions": [
3 | {
4 | "Icon": "icons/channel-action-image",
5 | "Name": "Channel",
6 | "States": [
7 | {
8 | "Name": "On",
9 | "Image": "icons/channel-on",
10 | "TitleAlignment": "bottom",
11 | "FontSize": "11"
12 | },
13 | {
14 | "Name": "Off",
15 | "Image": "icons/channel-off",
16 | "TitleAlignment": "bottom",
17 | "FontSize": "11"
18 | }
19 | ],
20 | "Tooltip": "Toggle channel off/on",
21 | "UUID": "com.dflydev.streamdeck.restreamio.channel"
22 | }
23 | ],
24 | "SDKVersion": 2,
25 | "Author": "dflydev",
26 | "Category": "Restream.io",
27 | "CategoryIcon": "icons/category-icon",
28 | "CodePath": "public/plugin.html",
29 | "PropertyInspectorPath": "public/property-inspector.html",
30 | "Description": "Control and view stats from Restream.io",
31 | "Name": "Restream.io plugin",
32 | "Icon": "icons/category-icon",
33 | "URL": "https://www.elgato.com/en/gaming/stream-deck",
34 | "Version": "1.0.0",
35 | "OS": [
36 | {
37 | "Platform": "mac",
38 | "MinimumVersion" : "10.11"
39 | },
40 | {
41 | "Platform": "windows",
42 | "MinimumVersion" : "10"
43 | }
44 | ],
45 | "Software":
46 | {
47 | "MinimumVersion" : "4.1"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/com.dflydev.streamdeck.restreamio.sdPlugin/public/property-inspector.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | com.dflydev.streamdeck.restreamio.pi
5 |
6 |
7 |
8 |
9 |
10 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@dflydev/streamdeck-restreamio",
3 | "version": "0.0.0",
4 | "description": "Restream.io plugin for Stream Deck",
5 | "repository": "https://github.com/dflydev/streamdeck-restreamio",
6 | "scripts": {
7 | "browserify": "browserify -t @sethvincent/dotenvify build/restreamio-plugin.js > com.dflydev.streamdeck.restreamio.sdPlugin/public/dist/restreamio-plugin-bundle.js && browserify -t @sethvincent/dotenvify build/restreamio-property-inspector.js > com.dflydev.streamdeck.restreamio.sdPlugin/public/dist/restreamio-property-inspector-bundle.js",
8 | "build": "npm run tsc && npm run browserify",
9 | "lint": "eslint src/**/*.ts",
10 | "fix": "eslint --fix src/**/*.ts",
11 | "test": "echo \"Error: no test specified\" && exit 1",
12 | "tsc": "tsc -p tsconfig.json",
13 | "watch": "tsc-watch --onSuccess \"npm run browserify\""
14 | },
15 | "author": "Beau Simensen (https://beausimensen.com)",
16 | "license": "MIT",
17 | "devDependencies": {
18 | "@sethvincent/dotenvify": "^1.0.4",
19 | "@tsconfig/recommended": "^1.0.1",
20 | "@types/node": "^14.14.31",
21 | "@typescript-eslint/eslint-plugin": "^4.15.2",
22 | "@typescript-eslint/parser": "^4.15.2",
23 | "axios": "^0.21.1",
24 | "axios-auth-refresh": "^3.1.0",
25 | "browserify": "^17.0.0",
26 | "dotenv": "^8.2.0",
27 | "eslint": "^7.21.0",
28 | "eslint-plugin-github": "^4.1.1",
29 | "prettier": "^2.2.1",
30 | "retry-axios": "^2.4.0",
31 | "streamdeck-typescript": "^3.0.0",
32 | "tsc-watch": "^4.2.9",
33 | "typescript": "^4.2.2",
34 | "watchify": "^4.0.0"
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/restreamio-plugin.ts:
--------------------------------------------------------------------------------
1 | import {SDOnActionEvent, StreamDeckPluginHandler} from 'streamdeck-typescript'
2 | import {ChannelAction} from './actions/channel-action'
3 | import {RestreamioClient} from './restreamio/restreamio-client'
4 | import {ManagedGlobalSettings} from './settings/managed-global-settings'
5 | import {
6 | DeferredFunction,
7 | DeferredGlobalSettingsRequests,
8 | } from './settings/deferred-global-settings-requests'
9 |
10 | export class RestreamioPlugin extends StreamDeckPluginHandler {
11 | protected deferredGlobalSettingsRequests: DeferredGlobalSettingsRequests
12 |
13 | constructor() {
14 | super()
15 | this.deferredGlobalSettingsRequests = new DeferredGlobalSettingsRequests(
16 | this.settingsManager,
17 | )
18 | new ChannelAction(this, 'com.dflydev.streamdeck.restreamio.channel')
19 | }
20 |
21 | get managedGlobalSettings(): ManagedGlobalSettings {
22 | return new ManagedGlobalSettings(this.settingsManager)
23 | }
24 |
25 | @SDOnActionEvent('setupReady')
26 | private async onSetupReady(): Promise {
27 | const restreamioStreamingPlatforms = await RestreamioClient.fromSettingsManager(
28 | this.settingsManager,
29 | ).getStreamingPlatforms()
30 |
31 | this.managedGlobalSettings.setRestreamioStreamingPlatforms(
32 | restreamioStreamingPlatforms,
33 | )
34 |
35 | this.deferredGlobalSettingsRequests.handle()
36 | }
37 |
38 | withGlobalSettings(context: string, fnc: DeferredFunction): void {
39 | this.deferredGlobalSettingsRequests.deferOrNow(
40 | this.globalSettingsReady,
41 | fnc,
42 | context,
43 | )
44 | }
45 | }
46 |
47 | new RestreamioPlugin()
48 |
--------------------------------------------------------------------------------
/src/restreamio/types.ts:
--------------------------------------------------------------------------------
1 | export interface RestreamioAccountCollection {
2 | [key: string]: RestreamioAccount
3 | }
4 |
5 | export interface RestreamioAccount {
6 | profile: RestreamioProfile
7 | token: RestreamioToken
8 | }
9 |
10 | export interface RestreamioProfile {
11 | id: RestreamioAccountId
12 | email: RestreamioEmail
13 | username: RestreamioUsername
14 | }
15 |
16 | export type RestreamioRefreshedTokenCallback = (
17 | restreamioToken: RestreamioToken,
18 | ) => void
19 | export type RestreamioChannelId = number
20 |
21 | export type RestreamioAccountId = number
22 | export type RestreamioEmail = string
23 | export type RestreamioUsername = string
24 |
25 | export type RestreamioStreamingPlatformId = number
26 |
27 | export type RestreamioStreamingPlatformImageUrl = string
28 |
29 | export interface RestreamioStreamingPlatformImage {
30 | svg: RestreamioStreamingPlatformImageUrl
31 | png: RestreamioStreamingPlatformImageUrl
32 | }
33 |
34 | export interface RestreamioStreamingPlatform {
35 | id: RestreamioStreamingPlatformId
36 | name: string
37 | url: string
38 | image: RestreamioStreamingPlatformImage
39 | altImage: RestreamioStreamingPlatformImage
40 | }
41 |
42 | export interface RestreamioToken {
43 | tokenType: RestreamioTokenType
44 | tokenScope: RestreamioTokenScope
45 | accessToken: RestreamioAccessToken
46 | accessTokenEpochExpirationTime: RestreamioAccessTokenEpochExpirationTime
47 | refreshToken: RestreamioRefreshToken
48 | refreshTokenEpochExpirationTime: RestreamioRefreshTokenEpochExpirationTime
49 | }
50 |
51 | export type RestreamioEpochExpirationTime = bigint
52 |
53 | export type RestreamioTokenType = string
54 | export type RestreamioTokenScope = string
55 |
56 | export type RestreamioAccessToken = string
57 | export type RestreamioAccessTokenEpochExpirationTime = RestreamioEpochExpirationTime
58 |
59 | export type RestreamioRefreshToken = string
60 | export type RestreamioRefreshTokenEpochExpirationTime = RestreamioEpochExpirationTime
61 |
--------------------------------------------------------------------------------
/.github/workflows/pre-release.yml:
--------------------------------------------------------------------------------
1 | name: "pre-release"
2 |
3 | on:
4 | push:
5 | branches:
6 | - "main"
7 |
8 | jobs:
9 | pre-release:
10 | name: "Pre Release"
11 | runs-on: "macos-latest"
12 |
13 | steps:
14 | - uses: actions/checkout@v2
15 | with:
16 | fetch-depth: 0
17 |
18 | - uses: actions/setup-node@v2
19 |
20 | - name: Get npm cache directory
21 | id: npm_cache
22 | run: |
23 | echo "::set-output name=dir::$(npm config get cache)"
24 |
25 | - name: Cache npm cache
26 | uses: actions/cache@v1
27 | with:
28 | path: ${{ steps.npm_cache.outputs.dir }}
29 | key: ${{ runner.os }}-npm-cache-${{ hashFiles('**/package-lock.json') }}
30 | restore-keys: |
31 | ${{ runner.os }}-npm-cache-
32 |
33 | - name: Cache npm dependencies
34 | uses: actions/cache@v1
35 | with:
36 | path: node_modules
37 | key: ${{ runner.os }}-npm-node-modules-${{ hashFiles('**/package-lock.json') }}
38 | restore-keys: |
39 | ${{ runner.os }}-npm-node-modules-
40 |
41 | - run: npm install
42 |
43 | - run: npm run lint
44 |
45 | - name: Prepare the environment
46 | run: cp .env.example .env
47 |
48 | - run: npm run build
49 | env:
50 | STREAMDECK_RESTREAMIO_LOGIN_URL: ${{ secrets.STREAMDECK_RESTREAMIO_LOGIN_URL }}
51 | STREAMDECK_RESTREAMIO_REFRESH_URL: ${{ secrets.STREAMDECK_RESTREAMIO_REFRESH_URL }}
52 |
53 | - name: Copy additional assets
54 | run: |
55 | cp LICENSE.txt README.md com.dflydev.streamdeck.restreamio.sdPlugin
56 |
57 | - name: StreamDeck Distribution Tool
58 | uses: AdamCarballo/streamdeck-distribution-tool@v1
59 | with:
60 | input: com.dflydev.streamdeck.restreamio.sdPlugin
61 | output: release
62 |
63 | - uses: "marvinpinto/action-automatic-releases@latest"
64 | with:
65 | repo_token: "${{ secrets.GITHUB_TOKEN }}"
66 | automatic_release_tag: "latest"
67 | prerelease: true
68 | title: "Latest Build"
69 | files: |
70 | release/com.dflydev.streamdeck.restreamio.streamDeckPlugin
71 |
--------------------------------------------------------------------------------
/src/settings/managed-settings.ts:
--------------------------------------------------------------------------------
1 | import {RestreamioAccountId, RestreamioChannelId} from '../restreamio/types'
2 | import {SettingsManager} from 'streamdeck-typescript'
3 | import {createEmptySettings, Settings} from './settings'
4 |
5 | export class ManagedSettings implements Settings {
6 | private context: string
7 | private settingsManager: SettingsManager
8 |
9 | private get rawSettings(): Settings {
10 | const rawSettings = this.settingsManager.getContextSettings(
11 | this.context,
12 | )
13 |
14 | if (!rawSettings) {
15 | return createEmptySettings()
16 | }
17 |
18 | return rawSettings
19 | }
20 |
21 | get restreamioAccountId(): RestreamioAccountId | null {
22 | return this.rawSettings.restreamioAccountId
23 | }
24 |
25 | get restreamioChannelId(): RestreamioChannelId | null {
26 | return this.rawSettings.restreamioChannelId
27 | }
28 |
29 | get name(): string | null {
30 | return this.rawSettings.name
31 | }
32 |
33 | get platform(): string | null {
34 | return this.rawSettings.platform
35 | }
36 |
37 | constructor(settingsManager: SettingsManager, context: string) {
38 | this.settingsManager = settingsManager
39 | this.context = context
40 | }
41 |
42 | assignRestreamioAccountId(restreamioAccountId: RestreamioAccountId): void {
43 | this.settingsManager.setContextSettingsAttributes(this.context, {
44 | restreamioAccountId,
45 | })
46 | }
47 |
48 | assignRestreamioChannelId(
49 | restreamioChannelId: RestreamioChannelId,
50 | platform: string,
51 | name: string,
52 | ): void {
53 | this.settingsManager.setContextSettingsAttributes(this.context, {
54 | restreamioChannelId,
55 | platform,
56 | name,
57 | })
58 | }
59 |
60 | assignRestreamioChannelPlatform(platform: string): void {
61 | this.settingsManager.setContextSettingsAttributes(this.context, {
62 | platform,
63 | })
64 | }
65 |
66 | assignRestreamioChannelName(name: string): void {
67 | this.settingsManager.setContextSettingsAttributes(this.context, {
68 | name,
69 | })
70 | }
71 |
72 | unassignRestreamioAccount(): void {
73 | this.settingsManager.setContextSettingsAttributes(this.context, {
74 | restreamioAccountId: null,
75 | restreamioChannelId: null,
76 | platform: null,
77 | name: null,
78 | })
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/settings/managed-global-settings.ts:
--------------------------------------------------------------------------------
1 | import {
2 | RestreamioAccount,
3 | RestreamioAccountCollection,
4 | RestreamioAccountId,
5 | RestreamioStreamingPlatform,
6 | RestreamioStreamingPlatformId,
7 | } from '../restreamio/types'
8 | import {SettingsManager} from 'streamdeck-typescript'
9 | import {createEmptyGlobalSettings, GlobalSettings} from './global-settings'
10 |
11 | export class ManagedGlobalSettings implements GlobalSettings {
12 | private settingsManager: SettingsManager
13 |
14 | get rawGlobalSettings(): GlobalSettings {
15 | const rawGlobalSettings = this.settingsManager.getGlobalSettings() as GlobalSettings
16 |
17 | if (!rawGlobalSettings) {
18 | return createEmptyGlobalSettings()
19 | }
20 |
21 | return rawGlobalSettings
22 | }
23 |
24 | get restreamioAccounts(): RestreamioAccountCollection {
25 | return this.rawGlobalSettings.restreamioAccounts
26 | }
27 |
28 | get restreamioStreamingPlatforms(): RestreamioStreamingPlatform[] {
29 | return this.rawGlobalSettings.restreamioStreamingPlatforms
30 | }
31 |
32 | constructor(settingsManager: SettingsManager) {
33 | this.settingsManager = settingsManager
34 | }
35 |
36 | setRestreamioStreamingPlatforms(
37 | restreamioStreamingPlatforms: RestreamioStreamingPlatform[],
38 | ): void {
39 | this.settingsManager.setGlobalSettingsAttributes({
40 | restreamioStreamingPlatforms,
41 | })
42 | }
43 |
44 | registerRestreamioAccount(
45 | restreamioAccount: RestreamioAccount,
46 | ): RestreamioAccountId {
47 | this.settingsManager.setGlobalSettings({
48 | ...this.rawGlobalSettings,
49 | restreamioAccounts: {
50 | ...this.rawGlobalSettings.restreamioAccounts,
51 | [restreamioAccount.profile.id]: restreamioAccount,
52 | },
53 | })
54 |
55 | return restreamioAccount.profile.id
56 | }
57 |
58 | getRestreamioAccounts(): RestreamioAccount[] {
59 | if (!this.rawGlobalSettings.restreamioAccounts) {
60 | return []
61 | }
62 |
63 | return Object.keys(this.rawGlobalSettings.restreamioAccounts).map(
64 | k => this.rawGlobalSettings.restreamioAccounts[k],
65 | )
66 | }
67 |
68 | getRestreamioStreamingPlatformById(
69 | restreamStreamingPlatformId: RestreamioStreamingPlatformId,
70 | ): RestreamioStreamingPlatform | undefined {
71 | return this.rawGlobalSettings.restreamioStreamingPlatforms.find(
72 | platform => platform.id === restreamStreamingPlatformId,
73 | )
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/settings/deferred-global-settings-requests.ts:
--------------------------------------------------------------------------------
1 | import {ManagedGlobalSettings} from './managed-global-settings'
2 | import {ManagedSettings} from './managed-settings'
3 | import {SettingsManager} from 'streamdeck-typescript'
4 |
5 | export type DeferredFunctionWithoutContext = (
6 | managedGlobalSettings: ManagedGlobalSettings,
7 | ) => void
8 |
9 | export type DeferredFunctionWithContext = (
10 | managedGlobalSettings: ManagedGlobalSettings,
11 | managedSeetings: ManagedSettings,
12 | ) => void
13 |
14 | export type DeferredFunction =
15 | | DeferredFunctionWithoutContext
16 | | DeferredFunctionWithContext
17 |
18 | interface DeferredRequest {
19 | fnc: DeferredFunction
20 | context?: string
21 | }
22 |
23 | export class DeferredGlobalSettingsRequests {
24 | private deferredGlobalSettingsRequests: DeferredRequest[] = []
25 | private settingsManager: SettingsManager
26 |
27 | constructor(settingsManager: SettingsManager) {
28 | this.settingsManager = settingsManager
29 | }
30 |
31 | handle(): void {
32 | while (this.deferredGlobalSettingsRequests.length > 0) {
33 | const deferredRequest = this.deferredGlobalSettingsRequests.shift()
34 |
35 | if (!deferredRequest) {
36 | continue
37 | }
38 |
39 | if (deferredRequest.context) {
40 | this.withGlobalSettingsResolvedForContext(
41 | deferredRequest.fnc,
42 | deferredRequest.context,
43 | )
44 | } else {
45 | this.withGlobalSettingsResolved(
46 | deferredRequest.fnc as DeferredFunctionWithoutContext,
47 | )
48 | }
49 | }
50 | }
51 |
52 | deferOrNow(isReady: boolean, fnc: DeferredFunction, context?: string): void {
53 | if (isReady) {
54 | if (context) {
55 | this.withGlobalSettingsResolvedForContext(fnc, context)
56 | } else {
57 | this.withGlobalSettingsResolved(fnc as DeferredFunctionWithoutContext)
58 | }
59 | } else {
60 | this.defer(fnc, context)
61 | }
62 | }
63 |
64 | defer(fnc: DeferredFunction, context?: string): void {
65 | this.deferredGlobalSettingsRequests.push({fnc, context})
66 | }
67 |
68 | private withGlobalSettingsResolved(
69 | fnc: DeferredFunctionWithoutContext,
70 | ): void {
71 | fnc(new ManagedGlobalSettings(this.settingsManager))
72 | }
73 |
74 | private withGlobalSettingsResolvedForContext(
75 | fnc: DeferredFunctionWithContext,
76 | context: string,
77 | ): void {
78 | fnc(
79 | new ManagedGlobalSettings(this.settingsManager),
80 | new ManagedSettings(this.settingsManager, context ?? ''),
81 | )
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["@typescript-eslint"],
3 | "extends": [
4 | "plugin:github/browser",
5 | "plugin:@typescript-eslint/recommended",
6 | "prettier"
7 | ],
8 | "parser": "@typescript-eslint/parser",
9 | "parserOptions": {
10 | "ecmaVersion": 9,
11 | "sourceType": "module",
12 | "project": "./tsconfig.eslint.json"
13 | },
14 | "rules": {
15 | "eslint-comments/no-use": "off",
16 | "import/no-namespace": "off",
17 | "no-unused-vars": "off",
18 | "@typescript-eslint/no-unused-vars": "error",
19 | "@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}],
20 | "@typescript-eslint/no-require-imports": "error",
21 | "@typescript-eslint/array-type": "error",
22 | "@typescript-eslint/await-thenable": "error",
23 | //"@typescript-eslint/ban-ts-ignore": "error",
24 | "camelcase": "off",
25 | "@typescript-eslint/naming-convention": "error",
26 | "@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}],
27 | "@typescript-eslint/func-call-spacing": ["error", "never"],
28 | "@typescript-eslint/no-array-constructor": "error",
29 | "@typescript-eslint/no-empty-interface": "error",
30 | "@typescript-eslint/no-explicit-any": "error",
31 | "@typescript-eslint/no-extraneous-class": "error",
32 | "@typescript-eslint/no-for-in-array": "error",
33 | "@typescript-eslint/no-inferrable-types": "error",
34 | "@typescript-eslint/no-misused-new": "error",
35 | "@typescript-eslint/no-namespace": "error",
36 | "@typescript-eslint/no-non-null-assertion": "warn",
37 | //"@typescript-eslint/no-object-literal-type-assertion": "error",
38 | "@typescript-eslint/no-unnecessary-qualifier": "error",
39 | "@typescript-eslint/no-unnecessary-type-assertion": "error",
40 | "@typescript-eslint/no-useless-constructor": "error",
41 | "@typescript-eslint/no-var-requires": "error",
42 | "@typescript-eslint/prefer-for-of": "warn",
43 | "@typescript-eslint/prefer-function-type": "warn",
44 | "@typescript-eslint/prefer-includes": "error",
45 | //"@typescript-eslint/prefer-interface": "error",
46 | "@typescript-eslint/prefer-string-starts-ends-with": "error",
47 | "@typescript-eslint/promise-function-async": "error",
48 | "@typescript-eslint/require-array-sort-compare": "error",
49 | "@typescript-eslint/restrict-plus-operands": "error",
50 | "semi": "off",
51 | "@typescript-eslint/semi": ["error", "never"],
52 | "@typescript-eslint/type-annotation-spacing": "error",
53 | "@typescript-eslint/unbound-method": "error"
54 | },
55 | "env": {
56 | "node": true,
57 | "es6": true
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 🔌 Stream Deck Restream.io plugin
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 | > Integrate [Elgato Stream Deck](https://www.elgato.com/en/gaming/stream-deck) with [Restream.io](https://restream.io).
21 |
22 | ## 🚀 Quick start
23 |
24 | [Download the latest release](https://github.com/dflydev/streamdeck-restreamio/releases/download/latest/com.dflydev.streamdeck.restreamio.streamDeckPlugin) from GitHub.
25 |
26 | ## Author
27 |
28 | 👥 **dflydev**
29 |
30 | * Website: [dflydev.com](https://dflydev.com?utm_source=streamdeck-restreamio&utm_medium=readme-author)
31 | * Twitter: [@dflydev](https://twitter.com/dflydev)
32 | * Github: [@dflydev](https://github.com/dflydev)
33 |
34 | 👤 **Beau Simensen**
35 |
36 | * Website: [beausimensen.com](https://beausimensen.com?utm_source=streamdeck-restreamio&utm_medium=readme-author)
37 | * Twitter: [@dflydev](https://twitter.com/beausimensen)
38 | * Github: [@simensen](https://github.com/simensen)
39 |
40 | ## 🤝 Contributing
41 |
42 | Contributions, issues and feature requests are welcome!
Feel free to check [issues page](https://github.com/dflydev/streamdeck-restreamio/issues).
43 |
44 |
45 | ## ❤️ Support the development
46 |
47 | Give a ⭐️ if this project helped you!
48 |
49 | Did this project save you time? Did this project increase your productivity? Did this project solve a problem for you? Did this project make your life easier? Please also consider donating or buying a license!
50 |
51 | * [Sponsor Beau Simensen on GitHub](https://github.com/sponsors/simensen)
52 | * [Sponsor dflydev on GitHub](https://github.com/sponsors/dflydev)
53 | * [Sponsor dflydev on OpenCollecctive](https://opencollective.com/dflydev)
54 |
55 |
56 | ## 📝 License
57 |
58 | Copyright © 2021 [dflydev](https://github.com/dflydev).
59 | This project is [MIT](https://github.com/dflydev/streamdeck-restreamio/blob/master/LICENSE) licensed.
60 |
--------------------------------------------------------------------------------
/src/actions/channel-action.ts:
--------------------------------------------------------------------------------
1 | import {
2 | DidReceiveSettingsEvent,
3 | KeyUpEvent,
4 | SDOnActionEvent,
5 | StateType,
6 | WillAppearEvent,
7 | WillDisappearEvent,
8 | } from 'streamdeck-typescript'
9 | import {RestreamioPlugin} from '../restreamio-plugin'
10 | import {Action} from './action'
11 | import {Settings} from '../settings/settings'
12 |
13 | export class ChannelAction extends Action {
14 | private channels: Set
15 |
16 | constructor(plugin: RestreamioPlugin, actionName: string) {
17 | super(plugin, actionName)
18 | this.channels = new Set()
19 | }
20 |
21 | @SDOnActionEvent('willAppear')
22 | private onWillAppear({
23 | context,
24 | payload: {isInMultiAction},
25 | }: WillAppearEvent): void {
26 | if (!isInMultiAction) {
27 | this.channels.add(context)
28 | }
29 |
30 | this.plugin.setState(StateType.OFF, context)
31 | this.updateUi(context)
32 |
33 | this.plugin.withGlobalSettings(context, () => this.getChannelState(context))
34 | }
35 |
36 | @SDOnActionEvent('willDisappear')
37 | private onWillDisappear({
38 | context,
39 | payload: {isInMultiAction},
40 | }: WillDisappearEvent): void {
41 | if (!isInMultiAction) {
42 | this.channels.delete(context)
43 | }
44 | }
45 |
46 | private getChannelState(context: string): void {
47 | this.restreamioClient(context)
48 | .getChannel()
49 | .then(channelData => {
50 | const channel = channelData as {active: boolean}
51 | this.plugin.setState(
52 | channel.active ? StateType.ON : StateType.OFF,
53 | context,
54 | )
55 | })
56 | }
57 |
58 | @SDOnActionEvent('didReceiveSettings')
59 | private onDidReceiveSettings({context}: DidReceiveSettingsEvent): void {
60 | this.updateUi(context)
61 | }
62 |
63 | @SDOnActionEvent('keyUp')
64 | private async onKeyUp({
65 | context,
66 | payload: {settings, isInMultiAction, userDesiredState, state},
67 | }: KeyUpEvent): Promise {
68 | if (isInMultiAction) {
69 | if (userDesiredState === StateType.ON) {
70 | await this.restreamioClient(context).enableChannel()
71 | this.setAllChannelActionsTo(context, settings, StateType.ON)
72 | } else {
73 | await this.restreamioClient(context).disableChannel()
74 | this.setAllChannelActionsTo(context, settings, StateType.OFF)
75 | }
76 | } else {
77 | if (state === StateType.OFF) {
78 | try {
79 | await this.restreamioClient(context).enableChannel()
80 | this.plugin.showOk(context)
81 | this.setAllChannelActionsTo(context, settings, StateType.ON)
82 | } catch (e) {
83 | console.log('Could not enable channel', e)
84 | this.plugin.setState(StateType.OFF, context)
85 | this.plugin.showAlert(context)
86 | }
87 | }
88 | if (state === StateType.ON) {
89 | try {
90 | await this.restreamioClient(context).disableChannel()
91 | this.plugin.showOk(context)
92 | this.setAllChannelActionsTo(context, settings, StateType.OFF)
93 | } catch (e) {
94 | console.log('Could not disable channel', e)
95 | this.plugin.setState(StateType.ON, context)
96 | this.plugin.showAlert(context)
97 | }
98 | }
99 | }
100 |
101 | return Promise.resolve()
102 | }
103 |
104 | private setAllChannelActionsTo(
105 | context: string,
106 | settings: Settings,
107 | desiredState: StateType,
108 | ): void {
109 | const ourContext = context
110 | this.channels.forEach((context: string) => {
111 | if (ourContext === context) {
112 | return
113 | }
114 |
115 | const theirSettings = this.settings(context)
116 |
117 | if (theirSettings.restreamioAccountId !== settings.restreamioAccountId) {
118 | return
119 | }
120 |
121 | if (theirSettings.restreamioChannelId !== settings.restreamioChannelId) {
122 | return
123 | }
124 |
125 | this.plugin.setState(desiredState, context)
126 | })
127 | }
128 |
129 | private async updateUi(context: string): Promise {
130 | if (
131 | !this.settings(context).restreamioAccountId ||
132 | !this.settings(context).restreamioChannelId
133 | ) {
134 | return Promise.resolve()
135 | }
136 |
137 | const platform = this.settings(context).platform
138 | const name = this.settings(context).name
139 |
140 | this.plugin.setTitle(`${name}\n${platform}`, context)
141 |
142 | return Promise.resolve()
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/src/restreamio/restreamio-client.ts:
--------------------------------------------------------------------------------
1 | import axios, {AxiosInstance, AxiosRequestConfig} from 'axios'
2 | import {ManagedGlobalSettings} from '../settings/managed-global-settings'
3 | import {ManagedSettings} from '../settings/managed-settings'
4 | import {RestreamioStreamingPlatform, RestreamioToken} from './types'
5 | import {SettingsManager} from 'streamdeck-typescript'
6 | import createAuthRefreshInterceptor from 'axios-auth-refresh'
7 | import * as rax from 'retry-axios'
8 |
9 | export class RestreamioClient {
10 | private managedGlobalSettings: ManagedGlobalSettings
11 | private managedSettings?: ManagedSettings
12 | private authenticatedAxios: AxiosInstance
13 | private unAuthenticatedAxios: AxiosInstance
14 |
15 | static fromSettingsManager(
16 | settingsManager: SettingsManager,
17 | context?: string,
18 | ): RestreamioClient {
19 | return new RestreamioClient(
20 | new ManagedGlobalSettings(settingsManager),
21 | context ? new ManagedSettings(settingsManager, context) : undefined,
22 | )
23 | }
24 |
25 | constructor(
26 | managedGlobalSettings: ManagedGlobalSettings,
27 | managedSettings?: ManagedSettings,
28 | ) {
29 | this.managedGlobalSettings = managedGlobalSettings
30 | this.managedSettings = managedSettings
31 | this.authenticatedAxios = this.createAuthenticatedAxios()
32 | this.unAuthenticatedAxios = RestreamioClient.createUnauthenticatedAxios()
33 | }
34 |
35 | private static createUnauthenticatedAxios(): AxiosInstance {
36 | const unauthenticatedAxios = axios.create()
37 |
38 | unauthenticatedAxios.defaults.raxConfig = {
39 | instance: unauthenticatedAxios,
40 | }
41 |
42 | rax.attach(unauthenticatedAxios)
43 |
44 | return unauthenticatedAxios
45 | }
46 |
47 | private createAuthenticatedAxios(): AxiosInstance {
48 | const authenticatedAxios = axios.create()
49 |
50 | authenticatedAxios.interceptors.request.use(request => {
51 | const token = this.getToken()
52 |
53 | if (!token) {
54 | return request
55 | }
56 |
57 | request.headers['Authorization'] = `Bearer ${token.accessToken}`
58 |
59 | return request
60 | })
61 |
62 | createAuthRefreshInterceptor(authenticatedAxios, async failedRequest => {
63 | const formData = new FormData()
64 | formData.append('refresh_token', this.getToken()?.refreshToken ?? '')
65 | const refreshUrl = process.env.STREAMDECK_RESTREAMIO_REFRESH_URL
66 |
67 | if (!refreshUrl) {
68 | throw new Error('Refresh URL is not defined')
69 | }
70 |
71 | return axios
72 | .post(refreshUrl, formData)
73 | .then(async tokenRefreshResponse => {
74 | this.managedGlobalSettings.registerRestreamioAccount(
75 | tokenRefreshResponse.data,
76 | )
77 | failedRequest.response.config.headers[
78 | 'Authorization'
79 | ] = `Bearer ${tokenRefreshResponse.data.token.accessToken}`
80 |
81 | return Promise.resolve()
82 | })
83 | })
84 |
85 | authenticatedAxios.defaults.raxConfig = {
86 | instance: authenticatedAxios,
87 | }
88 |
89 | rax.attach(authenticatedAxios)
90 |
91 | return authenticatedAxios
92 | }
93 |
94 | async getChannel(): Promise {
95 | if (!this.managedSettings?.restreamioChannelId) {
96 | return {active: false}
97 | }
98 |
99 | return this.get(
100 | `https://api.restream.io/v2/user/channel/${this.managedSettings?.restreamioChannelId}`,
101 | )
102 | }
103 |
104 | async enableChannel(): Promise {
105 | if (!this.managedSettings?.restreamioChannelId) {
106 | return
107 | }
108 |
109 | return await this.patch(
110 | `https://api.restream.io/v2/user/channel/${this.managedSettings?.restreamioChannelId}`,
111 | {
112 | active: true,
113 | },
114 | )
115 | }
116 |
117 | async disableChannel(): Promise {
118 | if (!this.managedSettings?.restreamioChannelId) {
119 | return
120 | }
121 |
122 | return await this.patch(
123 | `https://api.restream.io/v2/user/channel/${this.managedSettings?.restreamioChannelId}`,
124 | {
125 | active: false,
126 | },
127 | )
128 | }
129 |
130 | async getChannels(): Promise {
131 | return this.get('https://api.restream.io/v2/user/channel/all')
132 | }
133 |
134 | private getToken(): RestreamioToken | null {
135 | if (!this.managedSettings?.restreamioAccountId) {
136 | return null
137 | }
138 |
139 | return this.managedGlobalSettings.restreamioAccounts[
140 | this.managedSettings.restreamioAccountId
141 | ].token
142 | }
143 |
144 | async getStreamingPlatforms(): Promise {
145 | const options = {
146 | method: 'GET',
147 | url: 'https://api.restream.io/v2/platform/all',
148 | } as AxiosRequestConfig
149 |
150 | return await axios.request(options).then(response => {
151 | return response.data
152 | })
153 | }
154 |
155 | private async get(url: string): Promise {
156 | const options = {
157 | method: 'GET',
158 | url,
159 | } as AxiosRequestConfig
160 |
161 | return await this.authenticatedAxios
162 | .request(options)
163 | .then(function (response) {
164 | return response.data
165 | })
166 | }
167 |
168 | private async patch(url: string, payload: unknown): Promise {
169 | const options = {
170 | method: 'PATCH',
171 | url,
172 | data: payload,
173 | } as AxiosRequestConfig
174 |
175 | return await this.authenticatedAxios.request(options)
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/src/restreamio-property-inspector.ts:
--------------------------------------------------------------------------------
1 | import {
2 | DidReceiveSettingsEvent,
3 | SDOnPiEvent,
4 | StreamDeckPropertyInspectorHandler,
5 | } from 'streamdeck-typescript'
6 | import {GlobalSettings} from './settings/global-settings'
7 | import {ManagedGlobalSettings} from './settings/managed-global-settings'
8 | import {ManagedSettings} from './settings/managed-settings'
9 | import {RestreamioAccount} from './restreamio/types'
10 | import {Settings} from './settings/settings'
11 | import {RestreamioClient} from './restreamio/restreamio-client'
12 | import {
13 | DeferredFunction,
14 | DeferredGlobalSettingsRequests,
15 | } from './settings/deferred-global-settings-requests'
16 | import Timeout = NodeJS.Timeout
17 |
18 | class RestreamioPropertyInspector extends StreamDeckPropertyInspectorHandler<
19 | Settings,
20 | GlobalSettings
21 | > {
22 | private deferredGlobalSettingsRequests: DeferredGlobalSettingsRequests
23 | private restreamioAccountIdElement: HTMLSelectElement
24 | private restreamioChannelIdElement: HTMLSelectElement
25 | private restreamioChannelElement: HTMLDivElement
26 | private restreamioChannelPlatformWrapperElement: HTMLDivElement
27 | private restreamioChannelNameWrapperElement: HTMLDivElement
28 | private restreamioChannelPlatformFieldElement: HTMLInputElement
29 | private restreamioChannelNameFieldElement: HTMLInputElement
30 |
31 | private authWindow: Window | null
32 |
33 | private updateUiTimeoutId: Timeout
34 |
35 | constructor() {
36 | super()
37 | this.deferredGlobalSettingsRequests = new DeferredGlobalSettingsRequests(
38 | this.settingsManager,
39 | )
40 | }
41 |
42 | @SDOnPiEvent('documentLoaded')
43 | private onDocumentLoaded(): void {
44 | window.addEventListener('message', e => {
45 | this.withGlobalSettings(
46 | (globalSettings: ManagedGlobalSettings, settings: ManagedSettings) => {
47 | const restreamioAccountId = globalSettings.registerRestreamioAccount(
48 | e.data,
49 | )
50 | settings.assignRestreamioAccountId(restreamioAccountId)
51 |
52 | if (this.authWindow) {
53 | this.authWindow.close()
54 |
55 | this.authWindow = null
56 | }
57 |
58 | this.updateUi()
59 | },
60 | )
61 | })
62 |
63 | this.restreamioAccountIdElement = document.getElementById(
64 | 'restreamio_account_id',
65 | ) as HTMLSelectElement
66 | this.restreamioAccountIdElement.onchange = () => {
67 | const selectedValue = this.restreamioAccountIdElement.value
68 |
69 | if (selectedValue === '__NEW__') {
70 | return
71 | }
72 |
73 | this.withGlobalSettings(
74 | (globalSettings: ManagedGlobalSettings, settings: ManagedSettings) => {
75 | if (selectedValue) {
76 | settings.assignRestreamioAccountId(Number(selectedValue))
77 | } else {
78 | settings.unassignRestreamioAccount()
79 | }
80 |
81 | this.updateUi()
82 | },
83 | )
84 | }
85 |
86 | this.restreamioAccountIdElement.onclick = () => {
87 | if (this.restreamioAccountIdElement.value !== '__NEW__') {
88 | return
89 | }
90 |
91 | const externalUrl = process.env.STREAMDECK_RESTREAMIO_LOGIN_URL
92 |
93 | this.authWindow = window.open(externalUrl, 'Reastream.io')
94 | }
95 |
96 | this.restreamioChannelElement = document.getElementById(
97 | 'restreamio_channel',
98 | ) as HTMLDivElement
99 | this.restreamioChannelPlatformWrapperElement = document.getElementById(
100 | 'restreamio_channel_platform_wrapper',
101 | ) as HTMLDivElement
102 | this.restreamioChannelNameWrapperElement = document.getElementById(
103 | 'restreamio_channel_name_wrapper',
104 | ) as HTMLDivElement
105 | this.restreamioChannelIdElement = document.getElementById(
106 | 'restreamio_channel_id',
107 | ) as HTMLSelectElement
108 | this.restreamioChannelPlatformFieldElement = document.getElementById(
109 | 'restreamio_channel_platform',
110 | ) as HTMLInputElement
111 | this.restreamioChannelNameFieldElement = document.getElementById(
112 | 'restreamio_channel_name',
113 | ) as HTMLInputElement
114 | this.restreamioChannelIdElement.onchange = () => {
115 | const selectedValue = this.restreamioChannelIdElement.value
116 |
117 | this.withGlobalSettings(
118 | (globalSettings: ManagedGlobalSettings, settings: ManagedSettings) => {
119 | const label =
120 | this.restreamioChannelIdElement.selectedOptions.item(0)?.text ||
121 | 'Unknown – Unknown'
122 | const splitLabel = label.split(' – ')
123 | const platform = splitLabel.shift()
124 | const name = splitLabel.shift()
125 | settings.assignRestreamioChannelId(
126 | Number(selectedValue),
127 | platform ?? 'Unknown',
128 | name ?? 'Unknown',
129 | )
130 |
131 | this.updateUi()
132 | },
133 | )
134 | }
135 |
136 | this.restreamioChannelNameFieldElement.onchange = () => {
137 | this.withGlobalSettings(
138 | (globalSettings: ManagedGlobalSettings, settings: ManagedSettings) => {
139 | settings.assignRestreamioChannelName(
140 | this.restreamioChannelNameFieldElement.value,
141 | )
142 | },
143 | )
144 | }
145 |
146 | this.restreamioChannelPlatformFieldElement.onchange = () => {
147 | this.withGlobalSettings(
148 | (globalSettings: ManagedGlobalSettings, settings: ManagedSettings) => {
149 | settings.assignRestreamioChannelPlatform(
150 | this.restreamioChannelPlatformFieldElement.value,
151 | )
152 | },
153 | )
154 | }
155 |
156 | this.updateUi()
157 | }
158 |
159 | @SDOnPiEvent('globalSettingsAvailable')
160 | private async onGlobalSettingsAvailable(): Promise {
161 | this.deferredGlobalSettingsRequests.handle()
162 | }
163 |
164 | @SDOnPiEvent('didReceiveSettings')
165 | private async onDidReceiveSettings({
166 | payload: {settings},
167 | }: DidReceiveSettingsEvent): Promise {
168 | this.updateUi()
169 | }
170 |
171 | @SDOnPiEvent('didReceiveGlobalSettings')
172 | private onDidReceiveGlobalSettings(): void {
173 | this.updateUi()
174 | }
175 |
176 | withGlobalSettings(fnc: DeferredFunction): void {
177 | this.deferredGlobalSettingsRequests.deferOrNow(
178 | this.globalSettingsReady,
179 | fnc,
180 | this.actionInfo.context,
181 | )
182 | }
183 |
184 | private updateUi(): void {
185 | this.withGlobalSettings(
186 | async (
187 | globalSettings: ManagedGlobalSettings,
188 | settings: ManagedSettings,
189 | ) => {
190 | clearTimeout(this.updateUiTimeoutId)
191 | this.updateUiTimeoutId = setTimeout(async () => {
192 | this.updateRestreamioAccountUi(globalSettings, settings)
193 | await this.updateRestreamioChannelUi(globalSettings, settings)
194 | }, 100)
195 | },
196 | )
197 | }
198 |
199 | private async updateRestreamioAccountUi(
200 | globalSettings: ManagedGlobalSettings,
201 | settings: ManagedSettings,
202 | ): Promise {
203 | while (this.restreamioAccountIdElement.options.length > 0) {
204 | this.restreamioAccountIdElement.options.remove(0)
205 | }
206 |
207 | let anAccountIsSelected = false
208 |
209 | globalSettings
210 | .getRestreamioAccounts()
211 | .forEach((restreamioAccount: RestreamioAccount) => {
212 | const option = document.createElement('option')
213 | option.value = restreamioAccount.profile.id.toString()
214 | option.text = restreamioAccount.profile.username
215 | if (restreamioAccount.profile.id === settings.restreamioAccountId) {
216 | option.selected = true
217 |
218 | anAccountIsSelected = true
219 | }
220 | this.restreamioAccountIdElement.options.add(option)
221 | })
222 |
223 | const blankOption = document.createElement('option')
224 |
225 | if (!anAccountIsSelected) {
226 | blankOption.selected = !anAccountIsSelected
227 | }
228 |
229 | this.restreamioAccountIdElement.options.add(blankOption)
230 |
231 | const newOption = document.createElement('option')
232 | newOption.value = '__NEW__'
233 | newOption.text = 'Add new account'
234 |
235 | this.restreamioAccountIdElement.options.add(newOption)
236 | }
237 |
238 | private async updateRestreamioChannelUi(
239 | globalSettings: ManagedGlobalSettings,
240 | settings: ManagedSettings,
241 | ): Promise {
242 | const restreamioClient = new RestreamioClient(globalSettings, settings)
243 |
244 | let channels: any[] = []
245 |
246 | if (settings.restreamioAccountId) {
247 | try {
248 | channels = (await restreamioClient.getChannels()) as any[]
249 | } catch (e) {
250 | channels = []
251 | }
252 | }
253 |
254 | while (this.restreamioChannelIdElement.options.length > 0) {
255 | this.restreamioChannelIdElement.options.remove(0)
256 | }
257 |
258 | let aChannelIsSelected = false
259 |
260 | channels.forEach((channel: any) => {
261 | const option = document.createElement('option')
262 | const platform = globalSettings.getRestreamioStreamingPlatformById(
263 | channel.streamingPlatformId,
264 | )
265 | option.value = channel.id
266 | option.text = `${platform ? platform.name : 'Unknown'} – ${
267 | channel.displayName
268 | }`
269 | if (channel.id === settings.restreamioChannelId) {
270 | option.selected = true
271 |
272 | aChannelIsSelected = true
273 | }
274 | this.restreamioChannelIdElement.options.add(option)
275 | })
276 |
277 | if (!aChannelIsSelected) {
278 | const blankOption = document.createElement('option')
279 | blankOption.selected = true
280 | this.restreamioChannelIdElement.options.add(blankOption)
281 | }
282 |
283 | if (this.restreamioChannelIdElement.options.length === 1) {
284 | this.restreamioChannelElement.style.display = 'none'
285 | } else {
286 | this.restreamioChannelElement.style.display = 'flex'
287 | }
288 |
289 | if (aChannelIsSelected) {
290 | this.restreamioChannelPlatformFieldElement.value = settings.platform ?? ''
291 | this.restreamioChannelNameFieldElement.value = settings.name ?? ''
292 |
293 | this.restreamioChannelPlatformWrapperElement.style.display = 'flex'
294 | this.restreamioChannelNameWrapperElement.style.display = 'flex'
295 | } else {
296 | this.restreamioChannelPlatformWrapperElement.style.display = 'none'
297 | this.restreamioChannelNameWrapperElement.style.display = 'none'
298 | }
299 | }
300 | }
301 |
302 | new RestreamioPropertyInspector()
303 |
--------------------------------------------------------------------------------
/com.dflydev.streamdeck.restreamio.sdPlugin/public/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(./icons/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 | */
--------------------------------------------------------------------------------