From f9681b04110f99b413356da62d2bc291ab6e4bce Mon Sep 17 00:00:00 2001 From: Axel Boberg Date: Sat, 28 Feb 2026 02:04:22 +0100 Subject: [PATCH 1/2] Update README and CHANGELOG Signed-off-by: Axel Boberg --- CHANGELOG.md | 1 + README.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 228a74e..b7b99bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - The shortcut for opening settings now works as intented - Settings no longer collide with other modals - An issue where children weren't removed when their parent group was removed +- Updated dependencies ### Changed - Keyboard shortcuts now use hotkeys-js for better stability - The item.apply event has been changed to item.change diff --git a/README.md b/README.md index 0097eec..3532e21 100644 --- a/README.md +++ b/README.md @@ -45,6 +45,7 @@ The roadmap is available on Notion - HTTP triggers - CasparCG library, playout and templates - LTC timecode triggers +- Keyboard triggers ## Community plugins - [CRON - triggers based on the time of day](https://github.com/axelboberg/bridge-plugin-cron) From 3e419baa5e5e2dcc435970e9753df9175bca4553 Mon Sep 17 00:00:00 2001 From: Axel Boberg Date: Sat, 28 Feb 2026 02:43:37 +0100 Subject: [PATCH 2/2] Improve ux on drag-n-drop and allow dropping items om references and triggers to set target id Signed-off-by: Axel Boberg --- CHANGELOG.md | 2 + .../app/components/RundownGroupItem/index.jsx | 18 ++++++- .../app/components/RundownGroupItem/style.css | 12 +++++ .../app/components/RundownItem/index.jsx | 51 ++++++++++++++++++- .../app/components/RundownItem/style.css | 21 ++++++++ .../app/components/RundownList/index.jsx | 2 + .../components/RundownReferenceItem/index.jsx | 21 ++++++++ .../components/RundownReferenceItem/style.css | 0 .../components/RundownTriggerItem/index.jsx | 15 +++++- plugins/types/lib/types.js | 3 +- 10 files changed, 140 insertions(+), 5 deletions(-) create mode 100644 plugins/rundown/app/components/RundownReferenceItem/index.jsx create mode 100644 plugins/rundown/app/components/RundownReferenceItem/style.css diff --git a/CHANGELOG.md b/CHANGELOG.md index b7b99bb..6ccdc4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Free wheel for LTC inputs - Reference items now show a warning if they're missing a target id - Keyboard triggers +- Drag and drop on references and triggers to automatically set target id ### Fixed - An issue where invalid types could crash the rundown widget - An issue where 0 (or falsy values) could not be used as ids for options in preferences of the select type @@ -18,6 +19,7 @@ ### Changed - Keyboard shortcuts now use hotkeys-js for better stability - The item.apply event has been changed to item.change +- Improved ux when dropping items on references, triggers and groups ## 1.0.0-beta.9 ### Added diff --git a/plugins/rundown/app/components/RundownGroupItem/index.jsx b/plugins/rundown/app/components/RundownGroupItem/index.jsx index 7c663c4..44537f2 100644 --- a/plugins/rundown/app/components/RundownGroupItem/index.jsx +++ b/plugins/rundown/app/components/RundownGroupItem/index.jsx @@ -13,9 +13,18 @@ import { Icon } from '../Icon' export function RundownGroupItem ({ index, item }) { const [shared] = React.useContext(SharedContext) + const [isDraggedOver, setIsDraggedOver] = React.useState(false) const elRef = React.useRef() + React.useEffect(() => { + function onDragEnd () { + setIsDraggedOver(false) + } + window.addEventListener('dragend', onDragEnd) + return () => window.removeEventListener('dragend', onDragEnd) + }, []) + /** * Set this group to be * collapsed or expanded @@ -69,6 +78,7 @@ export function RundownGroupItem ({ index, item }) { async function handleDrop (e) { e.stopPropagation() + setIsDraggedOver(false) let itemId = e.dataTransfer.getData('text/plain') const itemSpec = e.dataTransfer.getData('bridge/item') @@ -104,6 +114,11 @@ export function RundownGroupItem ({ index, item }) { function handleDragOver (e) { e.preventDefault() e.stopPropagation() + setIsDraggedOver(true) + } + + function handleDragLeave (e) { + setIsDraggedOver(false) } function handleToggleCollapsed (e) { @@ -121,7 +136,7 @@ export function RundownGroupItem ({ index, item }) { const isCollapsed = item?.['rundown.ui.collapsed'] return ( -
+
handleToggleCollapsed(e)}> @@ -153,6 +168,7 @@ export function RundownGroupItem ({ index, item }) {
handleDragOver(e)} + onDragLeave={e => handleDragLeave(e)} > { (itemIds || []).length === 0 || isCollapsed diff --git a/plugins/rundown/app/components/RundownGroupItem/style.css b/plugins/rundown/app/components/RundownGroupItem/style.css index 8bf539e..2bda799 100644 --- a/plugins/rundown/app/components/RundownGroupItem/style.css +++ b/plugins/rundown/app/components/RundownGroupItem/style.css @@ -9,6 +9,18 @@ width: 100%; } +.RundownGroupItem.is-draggedOver::before { + position: absolute; + content: ''; + + top: 0; + left: 0; + right: 0; + bottom: 0; + + border: 2px dashed var(--base-color); +} + .RundownGroupItem-header { position: relative; display: flex; diff --git a/plugins/rundown/app/components/RundownItem/index.jsx b/plugins/rundown/app/components/RundownItem/index.jsx index 002cc93..6d15c7a 100644 --- a/plugins/rundown/app/components/RundownItem/index.jsx +++ b/plugins/rundown/app/components/RundownItem/index.jsx @@ -78,10 +78,20 @@ async function getReadablePropertiesForType (typeName) { } } -export function RundownItem ({ index, icon, item }) { +export function RundownItem ({ index, icon, item, dropzone, onDrop = () => {} }) { const [shared] = React.useContext(SharedContext) const [typeProperties, setTypeProperties] = React.useState([]) + const [isDraggedOver, setIsDraggedOver] = React.useState(false) + + React.useEffect(() => { + function onDragEnd () { + setIsDraggedOver(false) + } + window.addEventListener('dragend', onDragEnd) + return () => window.removeEventListener('dragend', onDragEnd) + }, []) + const [name] = useAsyncValue(() => { /* Make sure to check if there @@ -119,8 +129,34 @@ export function RundownItem ({ index, icon, item }) { loadProperties() }, [item?.type]) + /** + * Prevent the event from propagating + * so that we don't trigger the parent + * rundown's listeners + */ + function handleDragOver (e) { + e.preventDefault() + e.stopPropagation() + setIsDraggedOver(true) + } + + function handleDragLeave (e) { + setIsDraggedOver(false) + } + + async function handleDrop (e) { + e.stopPropagation() + setIsDraggedOver(false) + + const itemId = e.dataTransfer.getData('text/plain') + + if (itemId) { + onDrop(itemId) + } + } + return ( -
+
@@ -180,6 +216,17 @@ export function RundownItem ({ index, icon, item }) {
+ { + dropzone && + ( +
handleDragOver(e)} + onDragLeave={e => handleDragLeave(e)} + onDrop={e => handleDrop(e)} + /> + ) + }
) diff --git a/plugins/rundown/app/components/RundownItem/style.css b/plugins/rundown/app/components/RundownItem/style.css index 9d0e0ca..c909151 100644 --- a/plugins/rundown/app/components/RundownItem/style.css +++ b/plugins/rundown/app/components/RundownItem/style.css @@ -19,6 +19,18 @@ margin: 0.4em 1em; } +.RundownItem.is-draggedOver::before { + position: absolute; + content: ''; + + top: 0; + left: 0; + right: 0; + bottom: 0; + + border: 2px dashed var(--base-color); +} + .RundownItem-section { display: flex; width: 100%; @@ -92,3 +104,12 @@ opacity: 0.3; z-index: -1; } + +.RundownItem-dropZone { + position: absolute; + width: 100%; + height: 50%; + bottom: 0; + + z-index: 1; +} diff --git a/plugins/rundown/app/components/RundownList/index.jsx b/plugins/rundown/app/components/RundownList/index.jsx index f7da48d..d99deee 100644 --- a/plugins/rundown/app/components/RundownList/index.jsx +++ b/plugins/rundown/app/components/RundownList/index.jsx @@ -5,6 +5,7 @@ import './style.css' import { SharedContext } from '../../sharedContext' +import { RundownReferenceItem } from '../RundownReferenceItem' import { RundownVariableItem } from '../RundownVariableItem' import { RundownTriggerItem } from '../RundownTriggerItem' import { RundownDividerItem } from '../RundownDividerItem' @@ -24,6 +25,7 @@ import * as keyboard from '../../utils/keyboard' */ const TYPE_COMPONENTS = { 'bridge.variables.variable': { item: RundownVariableItem }, + 'bridge.types.reference': { item: RundownReferenceItem }, 'bridge.types.trigger': { item: RundownTriggerItem }, 'bridge.types.divider': { item: RundownDividerItem }, 'bridge.types.group': { diff --git a/plugins/rundown/app/components/RundownReferenceItem/index.jsx b/plugins/rundown/app/components/RundownReferenceItem/index.jsx new file mode 100644 index 0000000..0537dd9 --- /dev/null +++ b/plugins/rundown/app/components/RundownReferenceItem/index.jsx @@ -0,0 +1,21 @@ +import React from 'react' +import './style.css' + +import bridge from 'bridge' + +import { RundownItem } from '../RundownItem' + +export function RundownReferenceItem ({ index, item }) { + function handleDrop (itemId) { + if (typeof itemId !== 'string') { + return + } + bridge.items.applyItem(item.id, { + data: { + targetId: itemId + } + }, true) + } + + return handleDrop(itemId)} dropzone /> +} diff --git a/plugins/rundown/app/components/RundownReferenceItem/style.css b/plugins/rundown/app/components/RundownReferenceItem/style.css new file mode 100644 index 0000000..e69de29 diff --git a/plugins/rundown/app/components/RundownTriggerItem/index.jsx b/plugins/rundown/app/components/RundownTriggerItem/index.jsx index 561b57a..91b0bdc 100644 --- a/plugins/rundown/app/components/RundownTriggerItem/index.jsx +++ b/plugins/rundown/app/components/RundownTriggerItem/index.jsx @@ -1,8 +1,21 @@ import React from 'react' import './style.css' +import bridge from 'bridge' + import { RundownItem } from '../RundownItem' export function RundownTriggerItem ({ index, item }) { - return + function handleDrop (itemId) { + if (typeof itemId !== 'string') { + return + } + bridge.items.applyItem(item.id, { + data: { + targetId: itemId + } + }, true) + } + + return handleDrop(itemId)} dropzone /> } diff --git a/plugins/types/lib/types.js b/plugins/types/lib/types.js index a208064..c5a9707 100644 --- a/plugins/types/lib/types.js +++ b/plugins/types/lib/types.js @@ -39,7 +39,8 @@ async function init (htmlPath) { targetId: { name: 'Target id', type: 'string', - 'ui.group': 'Reference' + 'ui.group': 'Reference', + 'ui.readable': true }, targetButton: { name: '',