├── .dockerignore ├── public └── robots.txt ├── .npmrc ├── media ├── ui.png ├── appicon.icns ├── appicon.ico ├── appicon.png ├── screenshot.png └── docs │ ├── plugins │ ├── loader.png │ ├── directory.png │ └── manage-plugins.png │ └── architecture │ ├── client-server.png │ └── methodology.png ├── plugins ├── caspar │ ├── app │ │ ├── components │ │ │ ├── Select │ │ │ │ ├── style.css │ │ │ │ └── index.jsx │ │ │ ├── Monaco │ │ │ │ └── style.css │ │ │ ├── LibraryList │ │ │ │ ├── style.css │ │ │ │ └── index.jsx │ │ │ ├── ServerSelector │ │ │ │ └── style.css │ │ │ ├── EasingPreview │ │ │ │ └── style.css │ │ │ ├── ServerStatus │ │ │ │ ├── style.css │ │ │ │ └── index.jsx │ │ │ ├── TemplateDataHeader │ │ │ │ ├── style.css │ │ │ │ └── index.jsx │ │ │ ├── ThumbnailImage │ │ │ │ ├── style.css │ │ │ │ └── index.jsx │ │ │ ├── ServerStatusBadge │ │ │ │ └── style.css │ │ │ ├── LiveSwitchControl │ │ │ │ ├── index.jsx │ │ │ │ └── style.css │ │ │ ├── LibraryHeader │ │ │ │ └── style.css │ │ │ ├── LibraryListItem │ │ │ │ └── style.css │ │ │ └── ServerInput │ │ │ │ └── style.css │ │ ├── index.jsx │ │ ├── assets │ │ │ └── icons │ │ │ │ ├── disconnected.svg │ │ │ │ ├── connected.svg │ │ │ │ └── error.svg │ │ ├── views │ │ │ ├── Status.jsx │ │ │ ├── LiveSwitch.jsx │ │ │ └── Settings.jsx │ │ └── style.css │ ├── lib │ │ ├── paths.js │ │ ├── error │ │ │ ├── CasparError.js │ │ │ ├── CommandError.js │ │ │ └── TcpSocketError.js │ │ └── CasparManager.js │ ├── webpack.config.js │ └── package.json ├── rundown │ ├── app │ │ ├── config.js │ │ ├── components │ │ │ ├── Layout │ │ │ │ ├── style.css │ │ │ │ └── index.jsx │ │ │ ├── Icon │ │ │ │ ├── style.css │ │ │ │ └── index.jsx │ │ │ ├── RundownItemTimeSection │ │ │ │ └── style.css │ │ │ ├── RundownItemIndicatorsSection │ │ │ │ └── style.css │ │ │ ├── RundownList │ │ │ │ └── style.css │ │ │ ├── RundownItemProgress │ │ │ │ └── style.css │ │ │ ├── RundownDividerItem │ │ │ │ ├── index.jsx │ │ │ │ └── style.css │ │ │ ├── RundownVariableItem │ │ │ │ ├── style.css │ │ │ │ └── index.jsx │ │ │ └── Header │ │ │ │ └── style.css │ │ ├── index.jsx │ │ ├── index.css │ │ ├── hooks │ │ │ └── useAsyncValue.js │ │ ├── assets │ │ │ └── icons │ │ │ │ ├── index.js │ │ │ │ ├── arrow-down.svg │ │ │ │ ├── arrow-down-secondary.svg │ │ │ │ ├── warning.svg │ │ │ │ └── arrow-down-play.svg │ │ ├── utils │ │ │ └── keyboard.js │ │ └── App.jsx │ ├── package-lock.json │ └── lib │ │ └── commands.js ├── types │ ├── app │ │ ├── components │ │ │ └── ReferenceButton │ │ │ │ ├── style.css │ │ │ │ └── index.jsx │ │ ├── style.css │ │ ├── index.jsx │ │ ├── App.jsx │ │ └── views │ │ │ └── InspectorReferenceButton.jsx │ ├── package-lock.json │ └── lib │ │ └── utils.js ├── inspector │ ├── app │ │ ├── components │ │ │ ├── BooleanInput │ │ │ │ ├── style.css │ │ │ │ └── index.jsx │ │ │ ├── SelectInput │ │ │ │ ├── style.css │ │ │ │ └── index.jsx │ │ │ ├── Icon │ │ │ │ ├── style.css │ │ │ │ └── index.jsx │ │ │ ├── VariableHint │ │ │ │ ├── style.css │ │ │ │ └── index.jsx │ │ │ ├── NoSelection │ │ │ │ ├── style.css │ │ │ │ └── index.jsx │ │ │ ├── TextInput │ │ │ │ ├── style.css │ │ │ │ └── index.jsx │ │ │ ├── VariableStringInput │ │ │ │ └── style.css │ │ │ ├── Accordion │ │ │ │ ├── style.css │ │ │ │ └── index.jsx │ │ │ └── StringInput │ │ │ │ ├── style.css │ │ │ │ └── index.jsx │ │ ├── storeContext.js │ │ ├── index.jsx │ │ ├── assets │ │ │ └── icons │ │ │ │ ├── index.js │ │ │ │ └── arrow-down.svg │ │ ├── sharedContext.js │ │ └── index.css │ ├── README.md │ ├── package-lock.json │ └── package.json ├── clock │ ├── app │ │ ├── components │ │ │ └── CurrentTime │ │ │ │ └── style.css │ │ ├── index.jsx │ │ └── style.css │ ├── package-lock.json │ └── package.json ├── button │ ├── app │ │ ├── style.css │ │ ├── index.jsx │ │ └── components │ │ │ ├── ItemButton │ │ │ ├── index.jsx │ │ │ └── style.css │ │ │ ├── ItemDropArea │ │ │ └── style.css │ │ │ └── QueryPath │ │ │ └── index.jsx │ ├── package-lock.json │ └── package.json ├── osc │ ├── app │ │ ├── components │ │ │ ├── TargetSelector │ │ │ │ ├── style.css │ │ │ │ └── index.jsx │ │ │ ├── LogHeader │ │ │ │ ├── style.css │ │ │ │ └── index.jsx │ │ │ ├── LogItem │ │ │ │ ├── style.css │ │ │ │ └── index.jsx │ │ │ └── TargetInput │ │ │ │ └── style.css │ │ ├── index.jsx │ │ ├── style.css │ │ ├── App.jsx │ │ └── views │ │ │ └── Settings.jsx │ └── lib │ │ ├── paths.js │ │ ├── Transport.js │ │ ├── UDPClient.js │ │ ├── UDPTransport.js │ │ └── Server.js ├── state │ ├── README.md │ ├── app │ │ ├── index.jsx │ │ ├── components │ │ │ └── TreeView │ │ │ │ └── style.css │ │ ├── index.css │ │ └── App.jsx │ ├── package.json │ └── package-lock.json ├── README.md ├── http │ ├── package-lock.json │ ├── README.md │ ├── package.json │ └── lib │ │ └── random.js ├── scheduler │ ├── package-lock.json │ └── package.json ├── shortcuts │ └── package-lock.json └── variables │ ├── package-lock.json │ ├── index.js │ └── package.json ├── .husky ├── pre-commit └── commit-msg ├── .env.example ├── app ├── fonts.css ├── components │ ├── PreferencesFrameInput │ │ ├── style.css │ │ └── index.jsx │ ├── PreferencesBooleanInput │ │ ├── style.css │ │ └── index.jsx │ ├── PreferencesClearStateInput │ │ ├── style.css │ │ └── index.jsx │ ├── PreferencesSelectInput │ │ ├── style.css │ │ └── index.jsx │ ├── PreferencesSegmentedInput │ │ ├── style.css │ │ └── index.jsx │ ├── AppMenu │ │ ├── style.css │ │ ├── AppMenuRootItem.css │ │ └── AppMenuRootItem.jsx │ ├── MessageContainer │ │ └── style.css │ ├── ContextMenuDivider │ │ ├── style.css │ │ └── index.jsx │ ├── PreferencesVersionInput │ │ ├── style.css │ │ └── index.jsx │ ├── PreferencesStringInput │ │ ├── style.css │ │ └── index.jsx │ ├── Popup │ │ ├── confirm.css │ │ ├── index.jsx │ │ ├── confirm.jsx │ │ ├── shortcut.css │ │ └── style.css │ ├── Frame │ │ └── style.css │ ├── Preferences │ │ ├── sections │ │ │ ├── shortcuts.json │ │ │ ├── state.json │ │ │ ├── sharing.json │ │ │ ├── appearance.json │ │ │ └── general.json │ │ ├── preference.css │ │ └── style.css │ ├── ContextSearchItem │ │ └── style.css │ ├── EmptyComponent │ │ ├── style.css │ │ └── index.jsx │ ├── MissingComponent │ │ ├── style.css │ │ └── index.jsx │ ├── Role │ │ └── style.css │ ├── GridEmptyContent │ │ ├── style.css │ │ └── index.jsx │ ├── Icon │ │ ├── style.css │ │ └── index.jsx │ ├── Palette │ │ └── integrations │ │ │ └── index.js │ ├── Layout │ │ ├── index.jsx │ │ └── style.css │ ├── Sharing │ │ └── style.css │ ├── Grid │ │ ├── background.css │ │ ├── background.jsx │ │ └── style.css │ ├── Onboarding │ │ ├── style.css │ │ └── onboarding.json │ ├── Popover │ │ └── style.css │ ├── SegmentedControl │ │ ├── style.css │ │ └── index.jsx │ ├── PreferencesThemeInput │ │ ├── style.css │ │ └── index.jsx │ ├── ContextMenu │ │ └── style.css │ ├── Router │ │ ├── router.js │ │ └── router.unit.test.js │ ├── GridItem │ │ ├── style.css │ │ └── index.jsx │ ├── VerticalNavigation │ │ └── style.css │ ├── PreferencesNumberInput │ │ ├── style.css │ │ └── index.jsx │ ├── Transparency │ │ └── index.jsx │ ├── ContextMenuItem │ │ └── style.css │ └── PreferencesShortcutsInput │ │ └── style.css ├── assets │ ├── fonts │ │ ├── InterVariable.woff2 │ │ ├── bridge-glyphs.woff │ │ ├── InterVariable-Italic.woff2 │ │ └── bridge-glyphs.css │ └── icons │ │ ├── add.svg │ │ ├── window-maximize.svg │ │ ├── window-minimize.svg │ │ ├── placeholder.svg │ │ ├── spinner.svg │ │ ├── edit.svg │ │ ├── window-restore.svg │ │ ├── arrow-down.svg │ │ ├── window-close.svg │ │ ├── selector.svg │ │ ├── selector-white.svg │ │ ├── close.svg │ │ ├── color-success.svg │ │ ├── person.svg │ │ ├── edit-detail.svg │ │ ├── arrow-right.svg │ │ ├── warning.svg │ │ ├── reload.svg │ │ ├── preferences.svg │ │ ├── inspector.svg │ │ ├── search.svg │ │ ├── widget.svg │ │ └── float.svg ├── utils │ ├── console.js │ ├── random.js │ ├── fetch.js │ ├── clipboard.js │ ├── LazyValue.js │ └── browser.js ├── views │ └── Start.jsx ├── index.jsx ├── sharedContext.js ├── localContext.js ├── socketContext.js ├── hooks │ └── useJson.js ├── api.js ├── auth.js └── index.css ├── api ├── README.md ├── error │ ├── ApiError.js │ ├── InvalidArgumentError.js │ ├── MissingArgumentError.js │ ├── NoLocalHandlerError.js │ └── MissingIdentityError.js ├── dummy │ └── transport.js ├── ui.js ├── client.js ├── node │ ├── ui.js │ └── transport.js ├── browser │ └── ui │ │ └── index.js ├── transport.js ├── classes │ └── Cache.unit.test.js ├── system.unit.test.js ├── widgets.js ├── settings.js ├── events.unit.test.js └── random.js ├── .env.test ├── .gitignore ├── lib ├── config.js ├── security │ ├── EC.unit.test.js │ ├── JWT.unit.test.js │ ├── random.js │ ├── PolicyAgent.js │ ├── EC.js │ └── JWT.js ├── error │ ├── WorkerError.js │ ├── ApiError.js │ ├── PolicyError.js │ ├── InvalidArgumentError.js │ ├── HttpError.js │ ├── MissingTypeError.js │ ├── ContextError.js │ ├── ValidationError.js │ └── PluginMissingMainScriptError.js ├── api │ ├── rules │ │ ├── deny.js │ │ └── scope.js │ ├── CommandHandler.js │ └── SSystem.js ├── UserDefaults.js ├── utils.js ├── platform.js ├── network.js ├── routes │ └── index.js ├── plugin │ ├── PluginManifest.js │ └── PluginLoader.unit.test.js ├── workspace │ └── WorkspaceCrypto.js ├── StaticFileRegistry.unit.test.js └── init-electron.js ├── shared ├── DIControllerError.js ├── index.js ├── DIBase.js ├── DIController.unit.test.js └── LazyValue.unit.test.js ├── scripts ├── clean-build-folder.js └── sign-macos.js ├── examples ├── plugin-custom-types │ ├── README.md │ ├── package.json │ └── index.js └── plugin-hello-world │ ├── package.json │ └── README.md ├── index.js ├── docker-compose.yml ├── .fleet └── run.json ├── Dockerfile ├── .eslintrc.json ├── webpack.dev.js ├── .github └── workflows │ └── test.yml ├── docs ├── plugins │ └── installation.md ├── types.md ├── build.md └── structure.md ├── extra.plist ├── .vscode └── launch.json ├── LICENSE.md └── LICENSES └── MIT.txt /.dockerignore: -------------------------------------------------------------------------------- 1 | .git -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org 2 | proxy=null -------------------------------------------------------------------------------- /media/ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svt/bridge/HEAD/media/ui.png -------------------------------------------------------------------------------- /media/appicon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svt/bridge/HEAD/media/appicon.icns -------------------------------------------------------------------------------- /media/appicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svt/bridge/HEAD/media/appicon.ico -------------------------------------------------------------------------------- /media/appicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svt/bridge/HEAD/media/appicon.png -------------------------------------------------------------------------------- /plugins/caspar/app/components/Select/style.css: -------------------------------------------------------------------------------- 1 | .Select { 2 | width: 100%; 3 | } -------------------------------------------------------------------------------- /plugins/rundown/app/config.js: -------------------------------------------------------------------------------- 1 | export const DEFAULT_RUNDOWN_ID = 'RUNDOWN_ROOT' 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm test 5 | -------------------------------------------------------------------------------- /media/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svt/bridge/HEAD/media/screenshot.png -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | PORT= 2 | NODE_ENV= 3 | LOG_LEVEL=debug|info|warn|error 4 | APP_DATA_BASE_PATH= -------------------------------------------------------------------------------- /app/fonts.css: -------------------------------------------------------------------------------- 1 | @import "./assets/fonts/inter.css"; 2 | @import "./assets/fonts/bridge-glyphs.css"; -------------------------------------------------------------------------------- /app/components/PreferencesFrameInput/style.css: -------------------------------------------------------------------------------- 1 | .PreferencesFrameInput { 2 | padding: 15px 0; 3 | } -------------------------------------------------------------------------------- /plugins/caspar/app/components/Monaco/style.css: -------------------------------------------------------------------------------- 1 | .Monaco { 2 | width: 100%; 3 | height: 350px; 4 | } -------------------------------------------------------------------------------- /media/docs/plugins/loader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svt/bridge/HEAD/media/docs/plugins/loader.png -------------------------------------------------------------------------------- /plugins/types/app/components/ReferenceButton/style.css: -------------------------------------------------------------------------------- 1 | .ReferenceButton-button { 2 | width: 100%; 3 | } -------------------------------------------------------------------------------- /app/components/PreferencesBooleanInput/style.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | .PreferencesBooleanInput { 4 | margin: 5px 0; 5 | } -------------------------------------------------------------------------------- /app/components/PreferencesClearStateInput/style.css: -------------------------------------------------------------------------------- 1 | .PreferencesClearStateInput { 2 | margin: 10px 0 0; 3 | } -------------------------------------------------------------------------------- /app/components/PreferencesSelectInput/style.css: -------------------------------------------------------------------------------- 1 | .PreferencesSelectInput select { 2 | margin-top: 10px; 3 | } -------------------------------------------------------------------------------- /media/docs/plugins/directory.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svt/bridge/HEAD/media/docs/plugins/directory.png -------------------------------------------------------------------------------- /api/README.md: -------------------------------------------------------------------------------- 1 | # API 2 | The extension api available for plugins 3 | 4 | [Full api documentation](/docs/api/README.md) -------------------------------------------------------------------------------- /app/assets/fonts/InterVariable.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svt/bridge/HEAD/app/assets/fonts/InterVariable.woff2 -------------------------------------------------------------------------------- /app/assets/fonts/bridge-glyphs.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svt/bridge/HEAD/app/assets/fonts/bridge-glyphs.woff -------------------------------------------------------------------------------- /app/components/PreferencesSegmentedInput/style.css: -------------------------------------------------------------------------------- 1 | .PreferencesSegmentedInput-controlWrapper { 2 | margin-top: 10px; 3 | } -------------------------------------------------------------------------------- /media/docs/plugins/manage-plugins.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svt/bridge/HEAD/media/docs/plugins/manage-plugins.png -------------------------------------------------------------------------------- /media/docs/architecture/client-server.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svt/bridge/HEAD/media/docs/architecture/client-server.png -------------------------------------------------------------------------------- /media/docs/architecture/methodology.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svt/bridge/HEAD/media/docs/architecture/methodology.png -------------------------------------------------------------------------------- /app/assets/fonts/InterVariable-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/svt/bridge/HEAD/app/assets/fonts/InterVariable-Italic.woff2 -------------------------------------------------------------------------------- /app/components/AppMenu/style.css: -------------------------------------------------------------------------------- 1 | .AppMenu { 2 | display: flex; 3 | font-size: 0.9em; 4 | 5 | -webkit-app-region: no-drag; 6 | } -------------------------------------------------------------------------------- /app/assets/fonts/bridge-glyphs.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: bridge-glyphs; 3 | src: url("./bridge-glyphs.woff") format("woff"); 4 | } -------------------------------------------------------------------------------- /app/components/MessageContainer/style.css: -------------------------------------------------------------------------------- 1 | .MessageContainer { 2 | position: fixed; 3 | bottom: 10px; 4 | right: 10px; 5 | z-index: 1; 6 | } -------------------------------------------------------------------------------- /plugins/rundown/app/components/Layout/style.css: -------------------------------------------------------------------------------- 1 | .Layout--spread { 2 | display: flex; 3 | width: 100%; 4 | justify-content: space-between; 5 | } 6 | -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | # Environment variables 2 | # used for testing 3 | LOG_LEVEL=info 4 | NODE_OPTIONS="$NODE_OPTIONS --experimental-vm-modules" 5 | APP_DATA_BASE_PATH=- -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # macos 2 | *.DS_Store 3 | 4 | # npm 5 | node_modules 6 | 7 | # build 8 | assets.json 9 | dist 10 | bin 11 | 12 | # temporary files 13 | data -------------------------------------------------------------------------------- /app/components/ContextMenuDivider/style.css: -------------------------------------------------------------------------------- 1 | .ContextMenuDivider { 2 | width: 100%; 3 | height: 1px; 4 | 5 | background: black; 6 | opacity: 0.1; 7 | } 8 | -------------------------------------------------------------------------------- /plugins/inspector/app/components/BooleanInput/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022 Sveriges Television AB 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ -------------------------------------------------------------------------------- /lib/config.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | exports.defaults = { 6 | HTTP_PORT: 5544 7 | } 8 | -------------------------------------------------------------------------------- /app/components/PreferencesVersionInput/style.css: -------------------------------------------------------------------------------- 1 | .PreferencesVersionInput { 2 | padding: 1em; 3 | 4 | background: var(--base-color--shade1); 5 | border-radius: 6px; 6 | } -------------------------------------------------------------------------------- /app/components/PreferencesStringInput/style.css: -------------------------------------------------------------------------------- 1 | .PreferencesStringInput { 2 | margin-top: 10px; 3 | } 4 | 5 | .PreferencesStringInput-input { 6 | display: block; 7 | margin-top: 5px; 8 | } -------------------------------------------------------------------------------- /plugins/clock/app/components/CurrentTime/style.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | .CurrentTime-char { 4 | display: inline-block; 5 | width: 0.6em; 6 | } 7 | 8 | .CurrentTime-faded { 9 | opacity: 0.4; 10 | } -------------------------------------------------------------------------------- /app/components/Popup/confirm.css: -------------------------------------------------------------------------------- 1 | .PopupConfirm-actions { 2 | display: flex; 3 | margin-top: 20px; 4 | justify-content: space-around; 5 | } 6 | 7 | .PopupConfirm-confirmAction { 8 | font-weight: 500; 9 | } -------------------------------------------------------------------------------- /plugins/button/app/style.css: -------------------------------------------------------------------------------- 1 | html, body, #root { 2 | position: relative; 3 | width: 100%; 4 | height: 100%; 5 | 6 | padding: 0; 7 | margin: 0; 8 | 9 | font-size: min(10vw, 30vh); 10 | } 11 | -------------------------------------------------------------------------------- /shared/DIControllerError.js: -------------------------------------------------------------------------------- 1 | class DIControllerError extends Error { 2 | constructor (msg) { 3 | super(msg) 4 | this.name = DIControllerError 5 | } 6 | } 7 | 8 | module.exports = DIControllerError 9 | -------------------------------------------------------------------------------- /plugins/inspector/README.md: -------------------------------------------------------------------------------- 1 | # Inspector plugin 2 | Bridge's default inspector plugin 3 | 4 | ## Description 5 | A widget allowing item data to be edited 6 | 7 | ## Table of contents 8 | - [Description](#description) -------------------------------------------------------------------------------- /plugins/inspector/app/components/SelectInput/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022 Sveriges Television AB 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | .SelectInput { 8 | width: 100%; 9 | } -------------------------------------------------------------------------------- /plugins/inspector/app/components/Icon/style.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | .Icon svg [stroke]:not([stroke='none']) { 4 | stroke: var(--Icon-color); 5 | } 6 | 7 | .Icon svg [fill]:not([fill='none']) { 8 | fill: var(--Icon-color); 9 | } -------------------------------------------------------------------------------- /plugins/osc/app/components/TargetSelector/style.css: -------------------------------------------------------------------------------- 1 | .TargetSelector { 2 | width: 100%; 3 | padding: 0 5px 5px; 4 | box-sizing: border-box; 5 | } 6 | 7 | .TargetSelector select { 8 | width: 100%; 9 | } 10 | -------------------------------------------------------------------------------- /plugins/rundown/app/components/Icon/style.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | .Icon svg [stroke]:not([stroke='none']) { 4 | stroke: var(--base-color); 5 | } 6 | 7 | .Icon svg [fill]:not([fill='none']) { 8 | fill: var(--base-color); 9 | } -------------------------------------------------------------------------------- /app/components/ContextMenuDivider/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import './style.css' 3 | 4 | export const ContextMenuDivider = () => { 5 | return ( 6 |
7 | ) 8 | } 9 | -------------------------------------------------------------------------------- /app/components/AppMenu/AppMenuRootItem.css: -------------------------------------------------------------------------------- 1 | .AppMenuRootItem { 2 | padding: 0.3em 0.8em; 3 | border-radius: 7px; 4 | } 5 | 6 | .AppMenuRootItem:hover, 7 | .AppMenuRootItem:focus { 8 | background: var(--base-color--shade); 9 | } -------------------------------------------------------------------------------- /app/utils/console.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Init the development console 3 | * with a welcome message 4 | */ 5 | export function init () { 6 | console.log('%c[APP] Bridge development console', 'font-weight: 600; color: #E543FF') 7 | } 8 | -------------------------------------------------------------------------------- /app/components/Frame/style.css: -------------------------------------------------------------------------------- 1 | .Frame { 2 | position: relative; 3 | width: 100%; 4 | height: 100%; 5 | } 6 | 7 | .Frame-frame { 8 | position: relative; 9 | width: 100%; 10 | height: 0; 11 | border: none; 12 | } 13 | -------------------------------------------------------------------------------- /plugins/caspar/lib/paths.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | const manifest = require('../package.json') 6 | 7 | exports.STATE_SETTINGS_PATH = `plugins.${manifest.name}` 8 | -------------------------------------------------------------------------------- /plugins/osc/lib/paths.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | const manifest = require('../package.json') 6 | 7 | exports.STATE_SETTINGS_PATH = `plugins.${manifest.name}` 8 | -------------------------------------------------------------------------------- /plugins/types/app/style.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | position: relative; 3 | width: 100%; 4 | height: 100%; 5 | 6 | padding: 0; 7 | margin: 0; 8 | 9 | overflow: hidden; 10 | } 11 | 12 | #root { 13 | display: contents; 14 | } 15 | -------------------------------------------------------------------------------- /plugins/caspar/app/components/LibraryList/style.css: -------------------------------------------------------------------------------- 1 | .LibraryList { 2 | position: relative; 3 | width: 100%; 4 | height: 100%; 5 | padding: 0; 6 | margin: 0; 7 | 8 | list-style: none; 9 | overflow-y: scroll; 10 | overflow-x: hidden; 11 | } -------------------------------------------------------------------------------- /plugins/state/README.md: -------------------------------------------------------------------------------- 1 | # State plugin 2 | Browse the state as a widget 3 | 4 | ## Description 5 | This plugin allows the state to be viewed directly in a workspace, primarily for debugging purposes 6 | 7 | ## Table of contents 8 | - [Description](#description) -------------------------------------------------------------------------------- /app/components/Preferences/sections/shortcuts.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "Shortcuts", 4 | "description": "Customize registered keyboard shortcuts", 5 | "inputs": [ 6 | { 7 | "type": "shortcuts" 8 | } 9 | ] 10 | } 11 | ] -------------------------------------------------------------------------------- /lib/security/EC.unit.test.js: -------------------------------------------------------------------------------- 1 | const EC = require('./EC') 2 | 3 | test('generate key pair', async () => { 4 | const pair = await EC.generateKeyPair() 5 | expect(typeof pair.publicKey).toBe('string') 6 | expect(typeof pair.privateKey).toBe('string') 7 | }) 8 | -------------------------------------------------------------------------------- /plugins/inspector/app/components/VariableHint/style.css: -------------------------------------------------------------------------------- 1 | .VariableHint { 2 | margin-bottom: 2px; 3 | padding: 2px 5px; 4 | 5 | border-radius: 7px; 6 | background: var(--base-color--shade); 7 | font-size: 0.8em; 8 | 9 | box-sizing: border-box; 10 | } -------------------------------------------------------------------------------- /api/error/ApiError.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | class ApiError extends Error { 6 | constructor (msg = 'Api error') { 7 | super(msg) 8 | } 9 | } 10 | module.exports = ApiError 11 | -------------------------------------------------------------------------------- /app/components/ContextSearchItem/style.css: -------------------------------------------------------------------------------- 1 | .ContextMenuSearchItem { 2 | padding: 0; 3 | } 4 | 5 | .ContextMenuSearchItem input.ContextMenuSearchItem-input{ 6 | width: 100%; 7 | 8 | background: none; 9 | 10 | border-radius: 0; 11 | box-shadow: none; 12 | } -------------------------------------------------------------------------------- /plugins/rundown/app/components/Layout/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import './style.css' 4 | 5 | export function Spread ({ children }) { 6 | return ( 7 |
8 | {children} 9 |
10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /scripts/clean-build-folder.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | const fs = require('fs') 6 | const path = require('path') 7 | 8 | fs.rmSync(path.join(__dirname, '../dist'), { recursive: true, force: true }) 9 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | 5 | # Does the commit have a signoff? 6 | if [ "1" != "$(grep -c '^Signed-off-by: ' "$1")" ]; then 7 | printf >&2 "%sMissing Signed-off-by line, commit with --signoff or -s. %s\n" 8 | exit 1 9 | fi 10 | -------------------------------------------------------------------------------- /api/dummy/transport.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | /** 6 | * @type { import('../transport').Communicator } 7 | */ 8 | module.exports = { 9 | onMessage: handler => {}, 10 | send: msg => {} 11 | } 12 | -------------------------------------------------------------------------------- /plugins/inspector/app/storeContext.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | /** 4 | * A context for being shared 5 | * across active clients 6 | * 7 | * @see {@link ./App.js} 8 | * 9 | * @type { React.Context } 10 | */ 11 | export const StoreContext = React.createContext() 12 | -------------------------------------------------------------------------------- /plugins/README.md: -------------------------------------------------------------------------------- 1 | # Internal plugins 2 | This directory holds the plugins that are bundled with Bridge and should not include external plugins. 3 | 4 | ## Building 5 | These plugins are built together with Bridge and triggered with the main `build` command defined in [package.json](/package.json). -------------------------------------------------------------------------------- /plugins/clock/app/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | 4 | import App from './App' 5 | 6 | import './style.css' 7 | 8 | ReactDOM.render( 9 | 10 | 11 | , 12 | document.getElementById('root') 13 | ) -------------------------------------------------------------------------------- /plugins/osc/app/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | 4 | import App from './App' 5 | 6 | import './style.css' 7 | 8 | ReactDOM.render( 9 | 10 | 11 | , 12 | document.getElementById('root') 13 | ) -------------------------------------------------------------------------------- /plugins/state/app/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | 4 | import App from './App' 5 | 6 | import './index.css' 7 | 8 | ReactDOM.render( 9 | 10 | 11 | , 12 | document.getElementById('root') 13 | ) -------------------------------------------------------------------------------- /plugins/types/app/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | 4 | import App from './App' 5 | 6 | import './style.css' 7 | 8 | ReactDOM.render( 9 | 10 | 11 | , 12 | document.getElementById('root') 13 | ) -------------------------------------------------------------------------------- /examples/plugin-custom-types/README.md: -------------------------------------------------------------------------------- 1 | # Custom types example plugin 2 | This is an example plugin for showcasing how to create custom types that can be run within Bridge. 3 | The type definition is located within [package.json](./package.json) while the type-specific logic resides in [index.js](./index.js). -------------------------------------------------------------------------------- /plugins/button/app/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | 4 | import App from './App' 5 | 6 | import './style.css' 7 | 8 | ReactDOM.render( 9 | 10 | 11 | , 12 | document.getElementById('root') 13 | ) -------------------------------------------------------------------------------- /plugins/rundown/app/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | 4 | import App from './App' 5 | 6 | import './index.css' 7 | 8 | ReactDOM.render( 9 | 10 | 11 | , 12 | document.getElementById('root') 13 | ) -------------------------------------------------------------------------------- /plugins/caspar/app/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | 4 | import App from './App' 5 | 6 | import './style.css' 7 | 8 | ReactDOM.render( 9 | 10 | 11 | , 12 | document.getElementById('root') 13 | ) 14 | -------------------------------------------------------------------------------- /plugins/inspector/app/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | 4 | import App from './App' 5 | 6 | import './index.css' 7 | 8 | ReactDOM.render( 9 | 10 | 11 | , 12 | document.getElementById('root') 13 | ) 14 | -------------------------------------------------------------------------------- /plugins/osc/app/components/LogHeader/style.css: -------------------------------------------------------------------------------- 1 | .LogHeader { 2 | width: 100%; 3 | padding: 0 10px 10px; 4 | 5 | border-bottom: 1px solid var(--base-color--shade1); 6 | } 7 | 8 | .LogHeader-setting { 9 | display: flex; 10 | } 11 | 12 | .LogHeader-settingLabel { 13 | margin-left: 10px; 14 | } -------------------------------------------------------------------------------- /api/ui.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | ;(function () { 6 | if (module.parent) { 7 | require('./node/ui') 8 | return 9 | } 10 | if (typeof window !== 'undefined') { 11 | require('./browser/ui') 12 | } 13 | })() 14 | -------------------------------------------------------------------------------- /app/components/Preferences/preference.css: -------------------------------------------------------------------------------- 1 | .Preferences-preference { 2 | margin-bottom: 20px; 3 | text-align: left; 4 | } 5 | 6 | .Preferences-preferenceTitle, 7 | .Preferences-preferenceDescription { 8 | display: block; 9 | } 10 | 11 | .Preferences-preferenceDescription { 12 | max-width: 500px; 13 | } -------------------------------------------------------------------------------- /app/components/Preferences/sections/state.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "Clear items", 4 | "description": "Will remove all items from the current workspace without emitting the remove event", 5 | "inputs": [ 6 | { "type": "clear", "bind": "shared.items", "label": "Clear all items" } 7 | ] 8 | } 9 | ] -------------------------------------------------------------------------------- /lib/security/JWT.unit.test.js: -------------------------------------------------------------------------------- 1 | const EC = require('./EC') 2 | const JWT = require('./JWT') 3 | 4 | test('sign with EC', async () => { 5 | const { publicKey, privateKey } = await EC.generateKeyPair() 6 | const jwt = await JWT.sign({ foo: 'bar' }, privateKey, 'ES256') 7 | expect(typeof jwt).toBe('string') 8 | }) 9 | -------------------------------------------------------------------------------- /plugins/inspector/app/assets/icons/index.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | import arrowDown from './arrow-down.svg' 6 | import bucket from './bucket.svg' 7 | 8 | export default { 9 | 'arrow-down': arrowDown, 10 | bucket 11 | } 12 | -------------------------------------------------------------------------------- /plugins/rundown/app/components/RundownItemTimeSection/style.css: -------------------------------------------------------------------------------- 1 | .RundownItemTimeSection { 2 | display: flex; 3 | padding: 5px 6px; 4 | margin: -5px -5px -5px 10px; 5 | 6 | background: var(--base-color--grey1); 7 | border-radius: 5px; 8 | 9 | align-items: center; 10 | box-sizing: border-box; 11 | } -------------------------------------------------------------------------------- /api/client.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | ;(function () { 6 | if (module.parent) { 7 | require('./node/client') 8 | return 9 | } 10 | if (typeof window !== 'undefined') { 11 | require('./browser/client') 12 | } 13 | })() 14 | -------------------------------------------------------------------------------- /app/views/Start.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Axel Boberg 3 | */ 4 | 5 | import React from 'react' 6 | 7 | export const Start = () => { 8 | return ( 9 | <> 10 |
11 |

Welcome

12 | New workspace 13 |
14 | 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /lib/error/WorkerError.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 Axel Boberg 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | class WorkerError extends Error { 6 | constructor (msg) { 7 | super(msg) 8 | this.name = 'WorkerError' 9 | this.code = 'ERR_WORKER' 10 | } 11 | } 12 | 13 | module.exports = WorkerError 14 | -------------------------------------------------------------------------------- /plugins/inspector/app/components/VariableHint/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import './style.css' 3 | 4 | export function VariableHint ({ onClick = () => {} }) { 5 | return ( 6 |
onClick()}>VAR
7 | ) 8 | } 9 | -------------------------------------------------------------------------------- /lib/error/ApiError.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | class ApiError extends Error { 6 | constructor (message, code) { 7 | super(message) 8 | this.name = 'ApiError' 9 | this.code = code 10 | } 11 | } 12 | 13 | module.exports = ApiError 14 | -------------------------------------------------------------------------------- /plugins/caspar/app/components/ServerSelector/style.css: -------------------------------------------------------------------------------- 1 | .ServerSelector { 2 | width: 100%; 3 | box-sizing: border-box; 4 | } 5 | 6 | .ServerSelector select { 7 | width: 100%; 8 | } 9 | 10 | .ServerSelector-noServers { 11 | padding: 0.5em 0; 12 | width: 100%; 13 | text-align: center; 14 | 15 | opacity: 0.5; 16 | } -------------------------------------------------------------------------------- /shared/index.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | /* 6 | Expose the shared utils as window.bridge 7 | if we're running in a browser 8 | */ 9 | if (typeof window !== 'undefined') { 10 | window.shared = { 11 | merge: require('./merge') 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /api/node/ui.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 Axel Boberg 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | const DIController = require('../../shared/DIController') 6 | 7 | /** 8 | * No-op class as this API 9 | * is only available 10 | * in browser processes 11 | */ 12 | class UI {} 13 | DIController.main.register('UI', UI) 14 | -------------------------------------------------------------------------------- /lib/error/PolicyError.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 Axel Boberg 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | class PolicyError extends Error { 6 | constructor (message, reason) { 7 | super(message) 8 | this.name = 'PolicyError' 9 | this.reason = reason 10 | } 11 | } 12 | 13 | module.exports = PolicyError 14 | -------------------------------------------------------------------------------- /lib/api/rules/deny.js: -------------------------------------------------------------------------------- 1 | const PolicyError = require('../../error/PolicyError') 2 | 3 | module.exports = (code, message) => { 4 | return () => { 5 | throw new PolicyError('Policy denied', code, message) 6 | } 7 | } 8 | 9 | module.exports.CODE = { 10 | INCORRECT_CONTEXT: 'INCORRECT_CONTEXT', 11 | BAD_DATA: 'BAD_DATA' 12 | } 13 | -------------------------------------------------------------------------------- /lib/security/random.js: -------------------------------------------------------------------------------- 1 | const crypto = require('node:crypto') 2 | 3 | /** 4 | * Get secure random string 5 | * @param { number } bytes The length of the string in bytes 6 | * @returns { string } 7 | */ 8 | function string (bytes) { 9 | return Buffer.from(crypto.randomBytes(bytes)).toString('hex') 10 | } 11 | exports.string = string 12 | -------------------------------------------------------------------------------- /plugins/rundown/app/components/Icon/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import icons from '../../assets/icons' 3 | 4 | import './style.css' 5 | 6 | export function Icon ({ name = 'placeholder' }) { 7 | return ( 8 | 9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /app/components/Popup/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import './style.css' 4 | 5 | export function Popup ({ children, open }) { 6 | return ( 7 |
8 |
9 | {children} 10 |
11 |
12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /app/components/Preferences/sections/sharing.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "Named url", 4 | "description": "This workspace will become available as /named/ and persistent across restarts if a custom name is specified", 5 | "inputs": [ 6 | { "type": "string", "bind": "shared.url", "label": "Custom name" } 7 | ] 8 | } 9 | ] -------------------------------------------------------------------------------- /plugins/caspar/lib/error/CasparError.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | class CasparError extends Error { 6 | constructor (msg, code) { 7 | super(msg) 8 | this.name = 'CasparError' 9 | this.code = code 10 | } 11 | } 12 | module.exports = CasparError 13 | -------------------------------------------------------------------------------- /plugins/caspar/lib/error/CommandError.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | class CommandError extends Error { 6 | constructor (msg, code) { 7 | super(msg) 8 | this.name = 'CommandError' 9 | this.code = code 10 | } 11 | } 12 | module.exports = CommandError 13 | -------------------------------------------------------------------------------- /plugins/osc/app/style.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | position: relative; 3 | width: 100%; 4 | height: 100%; 5 | 6 | padding: 0; 7 | margin: 0; 8 | 9 | overflow: hidden; 10 | } 11 | 12 | #root { 13 | display: contents; 14 | } 15 | 16 | .LogList { 17 | height: calc(100% - 30px); 18 | overflow-y: scroll; 19 | overflow-x: hidden; 20 | } -------------------------------------------------------------------------------- /app/components/EmptyComponent/style.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | .EmptyComponent { 4 | display: flex; 5 | width: 100%; 6 | height: 100%; 7 | 8 | text-align: center; 9 | align-items: center; 10 | } 11 | 12 | .EmptyComponent-container { 13 | width: 100%; 14 | } 15 | 16 | .EmptyComponent-heading { 17 | margin: 10px 0 3px; 18 | font-weight: 600; 19 | } -------------------------------------------------------------------------------- /plugins/caspar/app/components/EasingPreview/style.css: -------------------------------------------------------------------------------- 1 | .EasingPreview { 2 | width: 50px; 3 | height: 50px; 4 | padding: 5px; 5 | margin-right: 10px; 6 | 7 | border: 1px solid var(--base-color--shade); 8 | border-radius: 10px; 9 | } 10 | 11 | .EasingPreview canvas { 12 | transform: scale(0.5); 13 | transform-origin: top left; 14 | } -------------------------------------------------------------------------------- /app/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | 4 | import App from './App' 5 | 6 | import * as _console from './utils/console' 7 | _console.init() 8 | 9 | import './index.css' 10 | 11 | ReactDOM.render( 12 | 13 | 14 | , 15 | document.getElementById('root') 16 | ) -------------------------------------------------------------------------------- /examples/plugin-hello-world/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plugin-hello-world", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "engines": { 10 | "bridge": "^0.0.1" 11 | }, 12 | "author": "", 13 | "license": "ISC" 14 | } 15 | -------------------------------------------------------------------------------- /plugins/caspar/lib/error/TcpSocketError.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | class TcpSocketError extends Error { 6 | constructor (msg, code) { 7 | super(msg) 8 | this.code = code 9 | this.name = 'TcpSocketError' 10 | } 11 | } 12 | module.exports = TcpSocketError 13 | -------------------------------------------------------------------------------- /api/error/InvalidArgumentError.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | const ApiError = require('./ApiError') 6 | 7 | class InvalidArgumentError extends ApiError { 8 | constructor (msg = 'Invalid argument') { 9 | super(msg) 10 | } 11 | } 12 | module.exports = InvalidArgumentError 13 | -------------------------------------------------------------------------------- /api/error/MissingArgumentError.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | const ApiError = require('./ApiError') 6 | 7 | class MissingArgumentError extends ApiError { 8 | constructor (msg = 'Missing argument') { 9 | super(msg) 10 | } 11 | } 12 | module.exports = MissingArgumentError 13 | -------------------------------------------------------------------------------- /app/components/MissingComponent/style.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | .MissingComponent { 4 | display: flex; 5 | width: 100%; 6 | height: 100%; 7 | 8 | text-align: center; 9 | align-items: center; 10 | } 11 | 12 | .MissingComponent-container { 13 | width: 100%; 14 | } 15 | 16 | .MissingComponent-heading { 17 | margin: 10px 0 3px; 18 | font-weight: 600; 19 | } -------------------------------------------------------------------------------- /app/components/Role/style.css: -------------------------------------------------------------------------------- 1 | .Role { 2 | width: 300px; 3 | 4 | text-align: left; 5 | font-size: 1em; 6 | color: var(--base-color); 7 | } 8 | 9 | .Role-content { 10 | padding: 10px; 11 | box-sizing: border-box; 12 | } 13 | 14 | .Role-info { 15 | margin: 5px; 16 | } 17 | 18 | .Role-status { 19 | margin-top: 10px; 20 | opacity: 0.7; 21 | } -------------------------------------------------------------------------------- /lib/error/InvalidArgumentError.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | const ApiError = require('./ApiError') 6 | 7 | class InvalidArgumentError extends ApiError { 8 | constructor (msg = 'Invalid argument') { 9 | super(msg) 10 | } 11 | } 12 | module.exports = InvalidArgumentError 13 | -------------------------------------------------------------------------------- /plugins/inspector/app/components/NoSelection/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022 Sveriges Television AB 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | .NoSelection { 8 | position: relative; 9 | display: flex; 10 | 11 | width: 100%; 12 | height: 100%; 13 | 14 | align-items: center; 15 | justify-content: center; 16 | } 17 | -------------------------------------------------------------------------------- /plugins/state/app/components/TreeView/style.css: -------------------------------------------------------------------------------- 1 | .TreeView .TreeView-tree * { 2 | font-family: var(--base-fontFamily--primary); 3 | background-color: unset; 4 | } 5 | 6 | .TreeView .TreeView-tree .json-key { 7 | color: var(--base-color); 8 | } 9 | 10 | .TreeView .TreeView-tree .json-value.json-number { 11 | color: var(--base-color--accent1); 12 | } 13 | -------------------------------------------------------------------------------- /api/error/NoLocalHandlerError.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | const ApiError = require('./ApiError') 6 | 7 | class NoLocalHandlerError extends ApiError { 8 | constructor (msg = 'Missing local handler') { 9 | super(msg) 10 | } 11 | } 12 | module.exports = NoLocalHandlerError 13 | -------------------------------------------------------------------------------- /plugins/inspector/app/components/TextInput/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022 Sveriges Television AB 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | .TextInput { 8 | width: 100%; 9 | padding: 0.5em 0.75em; 10 | 11 | box-sizing: border-box; 12 | resize: vertical; 13 | } 14 | 15 | .TextInput--large { 16 | font-size: 1.1em; 17 | } -------------------------------------------------------------------------------- /api/error/MissingIdentityError.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | const ApiError = require('./ApiError') 6 | 7 | class MissingIdentityError extends ApiError { 8 | constructor (msg = 'Unknown client identity') { 9 | super(msg) 10 | } 11 | } 12 | module.exports = MissingIdentityError 13 | -------------------------------------------------------------------------------- /lib/error/HttpError.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | class HttpError extends Error { 6 | constructor (message, code, status) { 7 | super(message) 8 | this.name = 'HttpError' 9 | this.code = code 10 | this.status = status 11 | } 12 | } 13 | 14 | module.exports = HttpError 15 | -------------------------------------------------------------------------------- /plugins/button/app/components/ItemButton/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import './style.css' 3 | 4 | export const ItemButton = ({ label, color = 'transparent', onClick = () => {} }) => { 5 | return ( 6 | 9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /app/sharedContext.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | import React from 'react' 6 | 7 | /** 8 | * A context for being shared 9 | * across active clients 10 | * 11 | * @see {@link ./App.js} 12 | * 13 | * @type { React.Context } 14 | */ 15 | export const SharedContext = React.createContext() 16 | -------------------------------------------------------------------------------- /plugins/state/app/index.css: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022 Sveriges Television AB 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | .App { 8 | position: relative; 9 | width: 100%; 10 | height: 100%; 11 | } 12 | 13 | *, .App * { 14 | -webkit-user-select: auto; 15 | -moz-user-select: auto; 16 | -ms-user-select: auto; 17 | user-select: auto; 18 | } -------------------------------------------------------------------------------- /plugins/types/app/components/ReferenceButton/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import './style.css' 3 | 4 | export const ReferenceButton = ({ onClick = () => {} }) => { 5 | return ( 6 |
7 | 8 |
9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /app/components/GridEmptyContent/style.css: -------------------------------------------------------------------------------- 1 | .GridEmptyContent { 2 | position: absolute; 3 | display: flex; 4 | 5 | top: 0; 6 | right: 0; 7 | bottom: 0; 8 | left: 0; 9 | 10 | text-align: center; 11 | 12 | align-items: center; 13 | justify-content: center; 14 | 15 | z-index: 1; 16 | } 17 | 18 | .GridEmptyContent-actions { 19 | margin-top: 20px; 20 | } -------------------------------------------------------------------------------- /app/utils/random.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | /** 6 | * Generate a random number 7 | * @param { Number } length The length of the number to generate 8 | * @returns { Number } 9 | */ 10 | export function number (length = 5) { 11 | return Math.floor(Math.random() * Math.pow(10, length)) 12 | } 13 | -------------------------------------------------------------------------------- /lib/error/MissingTypeError.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | class MissingTypeError extends Error { 6 | constructor () { 7 | super('No such type exists') 8 | this.name = 'MissingTypeError' 9 | this.code = 'ERR_TYPE_MISSING_TYPE' 10 | } 11 | } 12 | 13 | module.exports = MissingTypeError 14 | -------------------------------------------------------------------------------- /app/localContext.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | import React from 'react' 6 | 7 | /** 8 | * A context for holding data meant 9 | * to only be accessible locally 10 | * 11 | * @see {@link ./App.js} 12 | * 13 | * @type { React.Context } 14 | */ 15 | export const LocalContext = React.createContext() 16 | -------------------------------------------------------------------------------- /app/socketContext.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | import React from 'react' 6 | 7 | /** 8 | * A context for exposing the 9 | * websocket responsible for ipc 10 | * 11 | * @see {@link ./App.js} 12 | * 13 | * @type { React.Context } 14 | */ 15 | export const SocketContext = React.createContext() 16 | -------------------------------------------------------------------------------- /lib/UserDefaults.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | const State = require('./state/State') 6 | 7 | /** 8 | * A state representing user defaults, 9 | * that is settings for the current local 10 | * machine that is shared between workspaces 11 | * @type { State } 12 | */ 13 | module.exports = new State() 14 | -------------------------------------------------------------------------------- /lib/error/ContextError.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | class ContextError extends Error { 6 | constructor (message, code, status) { 7 | super(message) 8 | this.name = 'ContextError' 9 | this.code = code 10 | this.status = status 11 | } 12 | } 13 | 14 | module.exports = ContextError 15 | -------------------------------------------------------------------------------- /plugins/http/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bridge-plugin-http", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "bridge-plugin-http", 9 | "version": "1.0.0", 10 | "license": "UNLICENSED", 11 | "engines": { 12 | "bridge": "^0.0.1" 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /plugins/button/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bridge-plugin-button", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "bridge-plugin-button", 9 | "version": "1.0.0", 10 | "license": "UNLICENSED", 11 | "engines": { 12 | "bridge": "^0.0.1" 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /plugins/clock/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bridge-plugin-clock", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "bridge-plugin-clock", 9 | "version": "1.0.0", 10 | "license": "UNLICENSED", 11 | "engines": { 12 | "bridge": "^0.0.1" 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /plugins/inspector/app/components/NoSelection/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022 Sveriges Television AB 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | import React from 'react' 8 | import './style.css' 9 | 10 | export function NoSelection () { 11 | return ( 12 |
13 | No item selected 14 |
15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /plugins/types/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bridge-plugin-types", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "bridge-plugin-types", 9 | "version": "1.0.0", 10 | "license": "UNLICENSED", 11 | "engines": { 12 | "bridge": "^0.0.1" 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/components/Icon/style.css: -------------------------------------------------------------------------------- 1 | .Icon { 2 | display: flex; 3 | align-items: center; 4 | justify-content: center; 5 | 6 | --Icon-color: var(--base-color); 7 | } 8 | 9 | .Icon:not(.Icon--originalColors) svg [stroke]:not([stroke='none']) { 10 | stroke: var(--Icon-color); 11 | } 12 | 13 | .Icon:not(.Icon--originalColors) svg [fill]:not([fill='none']) { 14 | fill: var(--Icon-color); 15 | } -------------------------------------------------------------------------------- /app/utils/fetch.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | const API_HOST = (function () { 6 | if (window.APP.apiHost) { 7 | return window.APP.apiHost 8 | } 9 | return '' 10 | })() 11 | 12 | export const host = API_HOST 13 | 14 | export function api (path) { 15 | return window.fetch(`${API_HOST}${path}`) 16 | } 17 | -------------------------------------------------------------------------------- /lib/error/ValidationError.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | class ValidationError extends Error { 6 | constructor (message, code, params) { 7 | super(message) 8 | this.name = 'ValidationError' 9 | this.code = code 10 | this.params = params 11 | } 12 | } 13 | 14 | module.exports = ValidationError 15 | -------------------------------------------------------------------------------- /plugins/rundown/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bridge-plugin-rundown", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "bridge-plugin-rundown", 9 | "version": "1.0.0", 10 | "license": "UNLICENSED", 11 | "engines": { 12 | "bridge": "^0.0.1" 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /examples/plugin-hello-world/README.md: -------------------------------------------------------------------------------- 1 | # Hello World example plugin 2 | This is an example plugin registering a 'Hello World widget' in the Bridge UI 3 | Copy the entire directory to Bridge's plugin path and restart Bridge to make it appear in the widget selection menu in the edit mode. 4 | 5 | **Tip: find the plugin path via the application's menu bar after you've started Bridge. Plugins > Manage Plugins...** -------------------------------------------------------------------------------- /plugins/caspar/app/components/ServerStatus/style.css: -------------------------------------------------------------------------------- 1 | .ServerStatus { 2 | display: flex; 3 | padding: 5px 10px; 4 | width: 100%; 5 | 6 | box-sizing: border-box; 7 | } 8 | 9 | .ServerStatus:nth-child(odd) { 10 | background: var(--base-color--shade); 11 | } 12 | 13 | .ServerStatus > div { 14 | width: 100%; 15 | } 16 | 17 | .ServerStatus > div:last-child { 18 | text-align: right; 19 | } -------------------------------------------------------------------------------- /plugins/inspector/app/sharedContext.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @copyright Copyright © 2021 SVT Design 3 | * @author Axel Boberg 4 | */ 5 | 6 | import React from 'react' 7 | 8 | /** 9 | * A context for being shared 10 | * across active clients 11 | * 12 | * @see {@link ./App.js} 13 | * 14 | * @type { React.Context } 15 | */ 16 | export const SharedContext = React.createContext() 17 | -------------------------------------------------------------------------------- /plugins/inspector/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bridge-plugin-inspector", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "bridge-plugin-inspector", 9 | "version": "1.0.0", 10 | "license": "UNLICENSED", 11 | "engines": { 12 | "bridge": "^0.0.1" 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /plugins/scheduler/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bridge-plugin-scheduler", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "bridge-plugin-scheduler", 9 | "version": "1.0.0", 10 | "license": "UNLICENSED", 11 | "engines": { 12 | "bridge": "^0.0.1" 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /plugins/shortcuts/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bridge-plugin-shortcuts", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "bridge-plugin-shortcuts", 9 | "version": "1.0.0", 10 | "license": "UNLICENSED", 11 | "engines": { 12 | "bridge": "^0.0.1" 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /plugins/variables/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bridge-plugin-variables", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "bridge-plugin-variables", 9 | "version": "1.0.0", 10 | "license": "UNLICENSED", 11 | "engines": { 12 | "bridge": "^0.0.1" 13 | } 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app/components/Palette/integrations/index.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | import items from './items' 6 | 7 | /** 8 | * Export all available integrations 9 | * 10 | * All integrations must export a definition object, 11 | * see the 'items' integration for an example 12 | */ 13 | export default [ 14 | items 15 | ] 16 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | const platform = require('./lib/platform') 6 | 7 | /* 8 | Do required initialization 9 | */ 10 | require('./lib/init-common') 11 | require('./lib/server') 12 | 13 | if (platform.isElectron()) { 14 | require('./lib/init-electron') 15 | } else { 16 | require('./lib/init-node') 17 | } 18 | -------------------------------------------------------------------------------- /plugins/button/app/components/ItemDropArea/style.css: -------------------------------------------------------------------------------- 1 | .ItemDropArea { 2 | position: relative; 3 | width: 100%; 4 | height: 100%; 5 | } 6 | 7 | .ItemDropArea-dropArea { 8 | position: absolute; 9 | width: 100%; 10 | height: 100%; 11 | 12 | background: var(--base-color--shade1); 13 | border: 2px solid var(--base-color--shade2); 14 | 15 | border-radius: 6px; 16 | 17 | z-index: 1; 18 | } -------------------------------------------------------------------------------- /api/browser/ui/index.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 Axel Boberg 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | const DIController = require('../../../shared/DIController') 6 | 7 | require('./contextMenu') 8 | 9 | class UI { 10 | constructor (props) { 11 | this.contextMenu = props.UIContextMenu 12 | } 13 | } 14 | 15 | DIController.main.register('UI', UI, [ 16 | 'UIContextMenu' 17 | ]) 18 | -------------------------------------------------------------------------------- /plugins/caspar/app/components/TemplateDataHeader/style.css: -------------------------------------------------------------------------------- 1 | .TemplateDataHeader { 2 | display: flex; 3 | 4 | width: 100%; 5 | padding: 5px 5px 5px 10px; 6 | 7 | border-radius: 6px; 8 | background: var(--base-color--accent5); 9 | 10 | align-items: center; 11 | justify-content: space-between; 12 | } 13 | 14 | .TemplateDataHeader-section { 15 | display: flex; 16 | align-items: center; 17 | } -------------------------------------------------------------------------------- /plugins/caspar/app/components/ThumbnailImage/style.css: -------------------------------------------------------------------------------- 1 | .ThumbnailImage { 2 | display: flex; 3 | position: relative; 4 | 5 | width: 100%; 6 | height: 100%; 7 | 8 | align-items: center; 9 | justify-content: center; 10 | } 11 | 12 | .ThumbnailImage-img { 13 | width: 100%; 14 | height: 100%; 15 | 16 | object-fit: contain; 17 | } 18 | 19 | .ThumbnailImage-text { 20 | text-align: center; 21 | } -------------------------------------------------------------------------------- /app/components/Layout/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import './style.css' 4 | 5 | export function Master ({ children, sidebar }) { 6 | return ( 7 |
8 |
9 | {sidebar} 10 |
11 |
12 | {children} 13 |
14 |
15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /app/components/Sharing/style.css: -------------------------------------------------------------------------------- 1 | .Sharing { 2 | width: 300px; 3 | 4 | text-align: left; 5 | font-size: 1em; 6 | color: var(--base-color); 7 | } 8 | 9 | .Sharing-content { 10 | padding: 10px; 11 | box-sizing: border-box; 12 | } 13 | 14 | .Sharing-copyBtn { 15 | margin-top: 15px; 16 | } 17 | 18 | .Sharing-icon { 19 | margin: 5px 0 10px; 20 | } 21 | 22 | .Sharing-notification { 23 | padding: 5px; 24 | } -------------------------------------------------------------------------------- /plugins/rundown/app/index.css: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022 Sveriges Television AB 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | html, body, #root { 8 | height: 100%; 9 | min-height: 100vh; 10 | } 11 | 12 | .App { 13 | display: flex; 14 | height: 100%; 15 | 16 | flex-direction: column; 17 | overflow: hidden; 18 | } 19 | 20 | .View { 21 | height: 100%; 22 | overflow-y: scroll; 23 | } -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2022 Sveriges Television AB 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | version: '3.9' 6 | services: 7 | app: 8 | build: . 9 | command: npm run nodemon 10 | volumes: 11 | - .:/app 12 | environment: 13 | - NODE_ENV=development 14 | - PORT=3000 15 | - APP_DATA_BASE_PATH=../data 16 | ports: 17 | - 3000:3000 18 | - 9229:9229 -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | /** 6 | * Returns a promise that resolves 7 | * after the given delay 8 | * @param { Number } delayMs 9 | * @returns { Promise. } 10 | */ 11 | function wait (delayMs) { 12 | return new Promise(resolve => { 13 | setTimeout(() => resolve(), delayMs) 14 | }) 15 | } 16 | exports.wait = wait 17 | -------------------------------------------------------------------------------- /plugins/clock/app/style.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | html, body, #root { 4 | position: relative; 5 | width: 100%; 6 | height: 100%; 7 | 8 | padding: 0; 9 | margin: 0; 10 | 11 | font-size: min(8vw, 30vh); 12 | } 13 | 14 | .Clock-wrapper { 15 | display: flex; 16 | width: 100%; 17 | height: 100%; 18 | 19 | align-items: center; 20 | justify-content: center; 21 | } 22 | 23 | .Clock-digits { 24 | font-size: 2em; 25 | } 26 | -------------------------------------------------------------------------------- /plugins/rundown/app/components/RundownItemIndicatorsSection/style.css: -------------------------------------------------------------------------------- 1 | .RundownItemIndicatorsSection { 2 | display: flex; 3 | height: 1.2em; 4 | width: 100px; 5 | 6 | align-items: center; 7 | justify-content: flex-end; 8 | } 9 | 10 | .RundownItemIndicatorsSection-icon { 11 | display: inline-block; 12 | width: 1.2em; 13 | } 14 | 15 | .RundownItemIndicatorsSection-icon:not(:last-child) { 16 | margin-right: 5px; 17 | } -------------------------------------------------------------------------------- /lib/api/rules/scope.js: -------------------------------------------------------------------------------- 1 | const deny = require('./deny') 2 | 3 | exports.require = scope => { 4 | return input => { 5 | if (typeof input?.context?.scope !== 'string') { 6 | deny(deny.CODE.BAD_DATA)() 7 | } 8 | const scopes = input?.context?.scope?.split(' ') 9 | if (scopes.indexOf(scope) === -1) { 10 | deny('MISSING_SCOPE', `Missing required scope: ${scope}`)() 11 | } 12 | return true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /plugins/caspar/webpack.config.js: -------------------------------------------------------------------------------- 1 | const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin') 2 | 3 | module.exports = { 4 | plugins: [ 5 | new MonacoWebpackPlugin({ 6 | languages: ['json'], 7 | features: [ 8 | 'comment', 9 | 'colorPicker', 10 | 'smartSelect', 11 | 'multicursor', 12 | 'inlineCompletions', 13 | 'clipboard' 14 | ] 15 | }) 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /plugins/rundown/app/hooks/useAsyncValue.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export function useAsyncValue (provider = () => {}, dependencies = []) { 4 | const [value, setValue] = React.useState() 5 | 6 | React.useEffect(() => { 7 | async function get () { 8 | const newValue = await provider() 9 | setValue(newValue) 10 | } 11 | get() 12 | }, [provider, ...dependencies]) 13 | 14 | return [value] 15 | } 16 | -------------------------------------------------------------------------------- /lib/error/PluginMissingMainScriptError.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | class PluginMissingMainScriptError extends Error { 6 | constructor () { 7 | super('Plugin has no main script') 8 | this.name = 'PluginMissingMainScriptError' 9 | this.code = 'ERR_PLUGIN_MISSING_MAIN_SCRIPT' 10 | } 11 | } 12 | 13 | module.exports = PluginMissingMainScriptError 14 | -------------------------------------------------------------------------------- /plugins/clock/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bridge-plugin-clock", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "engines": { 10 | "bridge": "^0.0.1" 11 | }, 12 | "keywords": [ 13 | "bridge", 14 | "plugin" 15 | ], 16 | "author": "Axel Boberg (axel.boberg@svt.se)", 17 | "license": "UNLICENSED" 18 | } 19 | -------------------------------------------------------------------------------- /plugins/inspector/app/components/Icon/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import icons from '../../assets/icons' 3 | 4 | import './style.css' 5 | 6 | export function Icon ({ name = 'placeholder', color = 'var(--base-color)' }) { 7 | return ( 8 | 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /plugins/inspector/app/components/VariableStringInput/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022 Sveriges Television AB 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | .VariableStringInput { 8 | position: relative; 9 | width: 100%; 10 | } 11 | 12 | .VariableStringInput-suggestion { 13 | position: absolute; 14 | padding: 0.75em 0.6em; 15 | pointer-events: none; 16 | opacity: 0.5; 17 | 18 | white-space: nowrap; 19 | } -------------------------------------------------------------------------------- /.fleet/run.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "type": "docker-compose-up", 5 | "name": "Run with Docker compose", 6 | "files": [] 7 | }, 8 | { 9 | "name": "start electron", 10 | "type": "command", 11 | "program": "$WORKSPACE_DIR$/node_modules/.bin/electron", 12 | "args": [ 13 | "$WORKSPACE_DIR$/index.js", 14 | "--remote-debugging-port=9222" 15 | ] 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # The first stage will 2 | # build the app into /app 3 | FROM node:14.16.0-alpine3.10 4 | WORKDIR /app 5 | 6 | COPY package*.json ./ 7 | 8 | RUN npm ci 9 | 10 | COPY . ./ 11 | RUN npm run build 12 | 13 | CMD ["npm", "start"] 14 | 15 | # Create a second image 16 | # to force-squash the history 17 | # and prevent any tokens 18 | # from leaking out 19 | FROM node:14.16.0-alpine3.10 20 | WORKDIR /app 21 | 22 | COPY --from=0 /app /app 23 | -------------------------------------------------------------------------------- /plugins/button/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bridge-plugin-button", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "engines": { 10 | "bridge": "^0.0.1" 11 | }, 12 | "keywords": [ 13 | "bridge", 14 | "plugin" 15 | ], 16 | "author": "Axel Boberg (axel.boberg@svt.se)", 17 | "license": "UNLICENSED" 18 | } 19 | -------------------------------------------------------------------------------- /plugins/caspar/app/components/LibraryList/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import './style.css' 3 | 4 | import { LibraryListItem } from '../LibraryListItem' 5 | 6 | export const LibraryList = ({ items = [] }) => { 7 | return ( 8 |
    9 | { 10 | (items || []).map((item, i) => { 11 | return 12 | }) 13 | } 14 |
15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /plugins/scheduler/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bridge-plugin-scheduler", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "engines": { 10 | "bridge": "^0.0.1" 11 | }, 12 | "keywords": [ 13 | "bridge", 14 | "plugin" 15 | ], 16 | "author": "Axel Boberg (axel.boberg@svt.se)", 17 | "license": "UNLICENSED" 18 | } -------------------------------------------------------------------------------- /app/components/Icon/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import icons from '../../assets/icons' 3 | 4 | import './style.css' 5 | 6 | export function Icon ({ name = 'placeholder', color = 'var(--base-color)', originalColors = false }) { 7 | return ( 8 | 9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /plugins/inspector/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bridge-plugin-inspector", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "engines": { 10 | "bridge": "^0.0.1" 11 | }, 12 | "keywords": [ 13 | "bridge", 14 | "plugin" 15 | ], 16 | "author": "Axel Boberg (axel.boberg@svt.se)", 17 | "license": "UNLICENSED" 18 | } 19 | -------------------------------------------------------------------------------- /app/assets/icons/add.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | icons/add 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /plugins/http/README.md: -------------------------------------------------------------------------------- 1 | # HTTP plugin 2 | This plugin brings basic HTTP functionality to Bridge, such as items that make requests. 3 | 4 | Stopping an HTTP item immediately aborts any requests started by playing it. Playing an item multiple times while requests are still running will result in multiple requests being made. 5 | 6 | ## User agent 7 | All requests will be tagged by Bridge's user agent string, formatted as `Bridge/` for easier identification. 8 | -------------------------------------------------------------------------------- /app/components/Layout/style.css: -------------------------------------------------------------------------------- 1 | .Layout--master { 2 | display: flex; 3 | width: 100%; 4 | } 5 | 6 | .Layout--master-main, 7 | .Layout--master-sidebar { 8 | overflow-y: scroll; 9 | } 10 | 11 | .Layout--master-sidebar { 12 | width: 250px; 13 | padding: 20px; 14 | 15 | border-right: 1px solid var(--base-color--shade); 16 | 17 | text-align: left; 18 | } 19 | 20 | .Layout--master-main { 21 | width: 100%; 22 | padding: 20px; 23 | 24 | flex: 1; 25 | } -------------------------------------------------------------------------------- /lib/platform.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | /** 6 | * Check if the process is running 7 | * in an environment that is 8 | * compatible with Electron 9 | * @returns { Boolean } True if the process is 10 | * compatible with Electron 11 | */ 12 | function isElectron () { 13 | return process.versions.electron != null 14 | } 15 | exports.isElectron = isElectron 16 | -------------------------------------------------------------------------------- /plugins/caspar/app/components/ServerStatus/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import './style.css' 3 | 4 | import { ServerStatusBadge } from '../ServerStatusBadge' 5 | 6 | export const ServerStatus = ({ server = {} }) => { 7 | return ( 8 |
9 |
10 | {server?.name} 11 |
12 |
13 | 14 |
15 |
16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /shared/DIBase.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @class DIBase 3 | * @description 4 | * A base class for objects registered 5 | * to the DIController, this will setup 6 | * props as the private #prop member 7 | */ 8 | class DIBase { 9 | props 10 | 11 | /** 12 | * Create a new instance of the DIBase class 13 | * and set props to the props member variable 14 | */ 15 | constructor (props) { 16 | this.props = props 17 | } 18 | } 19 | 20 | module.exports = DIBase 21 | -------------------------------------------------------------------------------- /app/assets/icons/window-maximize.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | icons/window-maximize 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /plugins/osc/lib/Transport.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2024 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | const EventEmitter = require('events') 6 | 7 | /** 8 | * A base class for transports 9 | * for the Server class 10 | */ 11 | class Transport extends EventEmitter { 12 | /** 13 | * Tear down this transport 14 | */ 15 | teardown () { 16 | this.removeAllListeners() 17 | } 18 | } 19 | 20 | module.exports = Transport 21 | -------------------------------------------------------------------------------- /app/assets/icons/window-minimize.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | icons/window-minimize 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/components/Grid/background.css: -------------------------------------------------------------------------------- 1 | .Grid-background { 2 | position: absolute; 3 | display: flex; 4 | top: 0; 5 | left: 0; 6 | width: 100%; 7 | height: 100%; 8 | } 9 | 10 | .Grid-backgroundCol { 11 | position: relative; 12 | display: flex; 13 | width: 100%; 14 | height: 100%; 15 | 16 | flex-direction: column; 17 | } 18 | 19 | .Grid-backgroundRow { 20 | width: 100%; 21 | height: 100%; 22 | 23 | border: 0.5px solid var(--base-color--shade-3pct); 24 | } -------------------------------------------------------------------------------- /app/components/PreferencesVersionInput/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import './style.css' 3 | 4 | function getEnvironment () { 5 | if (window.navigator.userAgent.includes('Bridge')) { 6 | return 'electron' 7 | } 8 | return 'web' 9 | } 10 | 11 | export function PreferencesVersionInput () { 12 | return ( 13 |
14 | {window.APP.version || 'Unknown'} ({getEnvironment()}) 15 |
16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /plugins/caspar/app/components/ThumbnailImage/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import './style.css' 3 | 4 | export const ThumbnailImage = ({ src, alt = 'Thumbnail image' }) => { 5 | return ( 6 |
7 | { 8 | src 9 | ? {alt} 10 | :
Thumbnail is not available
11 | } 12 |
13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /api/node/transport.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2025 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | const { parentPort } = require('worker_threads') 6 | 7 | const DIController = require('../../shared/DIController') 8 | 9 | class Transport { 10 | onMessage (handler) { 11 | parentPort.on('message', handler) 12 | } 13 | 14 | send (msg) { 15 | parentPort.postMessage(msg) 16 | } 17 | } 18 | 19 | DIController.main.register('Transport', Transport) 20 | -------------------------------------------------------------------------------- /app/components/Onboarding/style.css: -------------------------------------------------------------------------------- 1 | .Onboarding { 2 | display: flex; 3 | padding: 30px; 4 | height: 100%; 5 | 6 | flex-direction: column; 7 | box-sizing: border-box; 8 | } 9 | 10 | .Onboarding-paragraphs { 11 | height: 100%; 12 | margin: 15px 20px 0; 13 | text-align: left; 14 | } 15 | 16 | .Onboarding-paragraph { 17 | display: flex; 18 | margin-bottom: 15px; 19 | 20 | align-items: center; 21 | } 22 | 23 | .Onboarding-paragraphIcon { 24 | margin-right: 15px; 25 | } -------------------------------------------------------------------------------- /plugins/inspector/app/index.css: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022 Sveriges Television AB 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | html, body, #root { 8 | height: 100%; 9 | min-height: 100vh; 10 | } 11 | 12 | .App { 13 | display: flex; 14 | height: 100%; 15 | 16 | flex-direction: column; 17 | overflow: hidden; 18 | } 19 | 20 | .View { 21 | height: 100%; 22 | overflow-y: hidden; 23 | } 24 | 25 | .u-textTransform--none { 26 | text-transform: none; 27 | } -------------------------------------------------------------------------------- /plugins/inspector/app/components/Accordion/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022 Sveriges Television AB 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | .Accordion-header { 8 | display: flex; 9 | padding: 3px; 10 | 11 | align-items: center; 12 | box-sizing: border-box; 13 | } 14 | 15 | .Accordion .Accordion-icon { 16 | margin-right: 5px; 17 | transform: rotate(-90deg); 18 | } 19 | 20 | .Accordion.is-open .Accordion-icon { 21 | transform: rotate(0deg); 22 | } 23 | -------------------------------------------------------------------------------- /app/assets/icons/placeholder.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | icons/placeholder 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /plugins/caspar/app/components/ServerStatusBadge/style.css: -------------------------------------------------------------------------------- 1 | .ServerStatus { 2 | display: flex; 3 | padding: 5px 10px; 4 | width: 100%; 5 | 6 | box-sizing: border-box; 7 | } 8 | 9 | .ServerStatus:nth-child(odd) { 10 | background: var(--base-color--shade); 11 | } 12 | 13 | .ServerStatus > div { 14 | width: 100%; 15 | } 16 | 17 | .ServerStatus > div:last-child { 18 | text-align: right; 19 | } 20 | 21 | .ServerStatusBadge-icon { 22 | vertical-align: middle; 23 | margin-right: 7px; 24 | } -------------------------------------------------------------------------------- /plugins/variables/index.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2023 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | /** 6 | * @type { import('../../api').Api } 7 | */ 8 | const bridge = require('bridge') 9 | 10 | exports.activate = async () => { 11 | bridge.events.on('item.play', item => { 12 | if (item.type !== 'bridge.variables.variable') { 13 | return 14 | } 15 | bridge.variables.setVariable(item.data.variable.key, item.data.variable.value) 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /plugins/rundown/app/assets/icons/index.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | import arrowDownSecondary from './arrow-down-secondary.svg' 6 | import arrowDownPlay from './arrow-down-play.svg' 7 | import arrowDown from './arrow-down.svg' 8 | import warning from './warning.svg' 9 | import empty from './empty.svg' 10 | 11 | export default { 12 | arrowDownSecondary, 13 | arrowDownPlay, 14 | arrowDown, 15 | warning, 16 | empty 17 | } 18 | -------------------------------------------------------------------------------- /app/components/Popover/style.css: -------------------------------------------------------------------------------- 1 | .Popover, 2 | .Popover-trigger { 3 | position: relative; 4 | } 5 | 6 | .Popover-content { 7 | display: block; 8 | position: absolute; 9 | 10 | left: 50%; 11 | bottom: 0; 12 | 13 | text-align: center; 14 | 15 | border-radius: 15px; 16 | background: var(--base-color--background); 17 | 18 | box-sizing: border-box; 19 | box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); 20 | 21 | transform: translate(-50%, 100%); 22 | 23 | overflow: hidden; 24 | z-index: 1; 25 | } 26 | -------------------------------------------------------------------------------- /plugins/state/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bridge-plugin-state", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "engines": { 10 | "bridge": "^0.0.1" 11 | }, 12 | "keywords": [ 13 | "bridge", 14 | "plugin" 15 | ], 16 | "author": "Axel Boberg (axel.boberg@svt.se)", 17 | "license": "UNLICENSED", 18 | "devDependencies": { 19 | "@pgrabovets/json-view": "^2.7.6" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "ignorePatterns": [ 3 | "**/*.test.js", 4 | "**/node_modules/**/*.js", 5 | "/bin/**/*.js", 6 | "/dist/**/*.js" 7 | ], 8 | "env": { 9 | "browser": true, 10 | "es2021": true, 11 | "node": true 12 | }, 13 | "extends": [ 14 | "standard" 15 | ], 16 | "overrides": [ 17 | ], 18 | "parserOptions": { 19 | "ecmaVersion": "latest" 20 | }, 21 | "plugins": [ 22 | ], 23 | "rules": { 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/assets/icons/spinner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | icons/spinner 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/components/SegmentedControl/style.css: -------------------------------------------------------------------------------- 1 | .SegmentedControl { 2 | display: inline-block; 3 | padding: 4px; 4 | 5 | border-radius: 8px; 6 | background: var(--base-color--shade1); 7 | white-space: nowrap; 8 | } 9 | 10 | .SegmentedControl-segment { 11 | display: inline-block; 12 | padding: 0.75em 1em; 13 | color: var(--base-color); 14 | 15 | border-radius: 5px; 16 | } 17 | 18 | .SegmentedControl-segment.is-active { 19 | background: var(--base-color--background); 20 | box-shadow: 0 2px 2px rgba(0, 0, 0, 0.1); 21 | } -------------------------------------------------------------------------------- /plugins/caspar/app/assets/icons/disconnected.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | icons/caspar/disconnected 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /plugins/rundown/app/components/RundownList/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022 Sveriges Television AB 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | .RundownList { 8 | padding: 0 2px; 9 | min-height: 100%; 10 | overflow-x: hidden; 11 | } 12 | 13 | .RundownList-empty { 14 | display: flex; 15 | position: absolute; 16 | 17 | top: 0; 18 | right: 0; 19 | bottom: 0; 20 | left: 0; 21 | 22 | text-align: center; 23 | 24 | align-items: center; 25 | justify-content: center; 26 | 27 | z-index: -1; 28 | } -------------------------------------------------------------------------------- /app/components/PreferencesThemeInput/style.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | .PreferencesThemeInput-item { 4 | display: inline-block; 5 | margin: 15px 15px 10px 0; 6 | width: 80px; 7 | text-align: center; 8 | } 9 | 10 | .PreferencesThemeInput-itemColor { 11 | width: 100%; 12 | height: 50px; 13 | 14 | border-radius: 8px; 15 | } 16 | 17 | .PreferencesThemeInput-item.is-active .PreferencesThemeInput-itemColor { 18 | box-shadow: 0 0 0 3px var(--base-color--shade); 19 | } 20 | 21 | .PreferencesThemeInput-itemLabel { 22 | margin-top: 5px; 23 | } -------------------------------------------------------------------------------- /app/hooks/useJson.js: -------------------------------------------------------------------------------- 1 | // SPDX-FileCopyrightText: 2022 Sveriges Television AB 2 | // 3 | // SPDX-License-Identifier: MIT 4 | 5 | import React from 'react' 6 | 7 | export const useJson = url => { 8 | const [data, setData] = React.useState({}) 9 | 10 | React.useEffect(() => { 11 | async function get (url) { 12 | const res = await window.fetch(url) 13 | .then(res => res.json()) 14 | setData(res) 15 | } 16 | get(url) 17 | 18 | return () => setData({}) 19 | }, [url]) 20 | 21 | return [data] 22 | } 23 | -------------------------------------------------------------------------------- /plugins/osc/app/components/LogItem/style.css: -------------------------------------------------------------------------------- 1 | .LogItem { 2 | display: flex; 3 | width: 100%; 4 | 5 | font-size: 0.9em; 6 | 7 | opacity: 0.8; 8 | 9 | white-space: nowrap; 10 | } 11 | 12 | .LogItem > div { 13 | margin-left: 10px; 14 | 15 | text-overflow: ellipsis; 16 | overflow: hidden; 17 | } 18 | 19 | .LogItem-timestamp { 20 | width: 90px; 21 | flex-shrink: 0; 22 | } 23 | 24 | .LogItem-direction { 25 | flex-shrink: 0; 26 | } 27 | 28 | .LogItem:nth-child(odd) { 29 | background: var(--base-color--shade1); 30 | } -------------------------------------------------------------------------------- /app/components/MissingComponent/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import './style.css' 3 | 4 | import icon from '../../assets/icons/warning.svg' 5 | 6 | export const MissingComponent = ({ data = {} }) => { 7 | return ( 8 |
9 |
10 |
11 |
Missing or crashing widget
12 | {data.component} 13 |
14 |
15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /plugins/inspector/app/components/StringInput/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022 Sveriges Television AB 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | .StringInput { 8 | width: 100%; 9 | padding: 0.5em; 10 | 11 | font-size: 1em; 12 | font-family: var(--base-fontFamily--primary); 13 | 14 | border: none; 15 | border-radius: 8px; 16 | 17 | color: var(--base-color); 18 | background: var(--base-color--shade2); 19 | 20 | box-sizing: border-box; 21 | } 22 | 23 | .StringInput--large { 24 | font-size: 1.1em; 25 | } -------------------------------------------------------------------------------- /plugins/types/app/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import { InspectorReferenceButton } from './views/InspectorReferenceButton' 4 | 5 | export default function App () { 6 | const [view, setView] = React.useState() 7 | 8 | React.useEffect(() => { 9 | const params = new URLSearchParams(window.location.search) 10 | setView(params.get('path')) 11 | }, []) 12 | 13 | switch (view) { 14 | case 'inspector/reference/button': 15 | return 16 | default: 17 | return <> 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/components/PreferencesBooleanInput/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import './style.css' 3 | 4 | import * as random from '../../utils/random' 5 | 6 | export function PreferencesBooleanInput ({ label, value = false, onChange = () => {} }) { 7 | const [id] = React.useState(`number-${random.number()}`) 8 | return ( 9 |
10 | onChange(e.target.checked)} /> 11 | 12 |
13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /plugins/inspector/app/components/TextInput/index.jsx: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022 Sveriges Television AB 3 | * 4 | * SPDX-License-Identifier: MIT 5 | */ 6 | 7 | import React from 'react' 8 | import './style.css' 9 | 10 | export function TextInput ({ 11 | htmlFor, 12 | value = '', 13 | onChange = () => {} 14 | }) { 15 | return ( 16 |