diff --git a/CHANGELOG.md b/CHANGELOG.md
index 228a74ed..6ccdc4c4 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
@@ -14,9 +15,11 @@
- 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
+- Improved ux when dropping items on references, triggers and groups
## 1.0.0-beta.9
### Added
diff --git a/README.md b/README.md
index 0097eec5..3532e21e 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)
diff --git a/plugins/rundown/app/components/RundownGroupItem/index.jsx b/plugins/rundown/app/components/RundownGroupItem/index.jsx
index 7c663c4c..44537f2e 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 8bf539e8..2bda7993 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 002cc935..6d15c7a4 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 9d0e0ca4..c9091514 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 f7da48d8..d99deeed 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 00000000..0537dd9f
--- /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 00000000..e69de29b
diff --git a/plugins/rundown/app/components/RundownTriggerItem/index.jsx b/plugins/rundown/app/components/RundownTriggerItem/index.jsx
index 561b57a2..91b0bdc3 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 a2080641..c5a97077 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: '',