Skip to content

Commit

Permalink
Add a button component
Browse files Browse the repository at this point in the history
Signed-off-by: Axel Boberg <[email protected]>
  • Loading branch information
axelboberg committed Apr 24, 2024
1 parent 9194c35 commit 08a0290
Show file tree
Hide file tree
Showing 10 changed files with 301 additions and 0 deletions.
70 changes: 70 additions & 0 deletions plugins/button/app/App.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React from 'react'
import bridge from 'bridge'

import { QueryPath } from './components/QueryPath'
import { ItemButton } from './components/ItemButton'
import { ItemDropArea } from './components/ItemDropArea'

export default function App () {
const [itemId, setItemId] = React.useState()
const [item, setItem] = React.useState()

React.useEffect(() => {
const itemId = window.WIDGET_DATA?.['itemId']
setItemId(itemId)
}, [])

React.useEffect(() => {
async function getItem (itemId) {
const item = await bridge.items.getItem(itemId)
console.log('Got item', item)
setItem(item)
}
getItem(itemId)
}, [itemId])

React.useEffect(() => {
bridge.events.on('item.change', newItem => {
if (newItem?.id !== item?.id) {
return
}
setItem(newItem)
})
}, [item])

function handleItemChange (itemId) {
window.WIDGET_UPDATE({
'itemId': itemId
})
setItemId(itemId)
}

function handlePlayItem () {
if (!itemId || !item) {
return
}
bridge.items.playItem(itemId)
}

function handleStopItem () {
if (!itemId || !item) {
return
}
bridge.items.stopItem(itemId)
}

return (
<>
<QueryPath path='play'>
<ItemDropArea onDrop={itemId => handleItemChange(itemId)}>
<ItemButton label={item?.data?.name || 'Drop an item here'} color={item?.data?.color} onClick={() => handlePlayItem()} />
</ItemDropArea>
</QueryPath>
<QueryPath path='stop'>
<ItemDropArea onDrop={itemId => handleItemChange(itemId)}>
<ItemButton label={item?.data?.name || 'Drop an item here'} color={item?.data?.color} onClick={() => handleStopItem()} />
</ItemDropArea>
</QueryPath>
</>
)
}
10 changes: 10 additions & 0 deletions plugins/button/app/components/ItemButton/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react'
import './style.css'

export const ItemButton = ({ label, color = 'transparent', onClick = () => {} }) => {
return (
<button className='ItemButton' onClick={() => onClick()} style={{ backgroundColor: color }}>
{ label }
</button>
)
}
30 changes: 30 additions & 0 deletions plugins/button/app/components/ItemButton/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
.ItemButton {
display: flex;
width: calc(100% - 10px);
height: calc(100% - 5px);

margin: 0 5px 5px;
padding: 10px;

color: var(--base-color);
font-size: 1em;
font-family: var(--base-fontFamily--primary);

border: none;
border-radius: 6px;

background: none;

box-sizing: border-box;

align-items: center;
justify-content: center;
}

.ItemButton:hover {
box-shadow: inset 0 0 0 2px var(--base-color--shade1);
}

.ItemButton:active {
opacity: 0.7;
}
48 changes: 48 additions & 0 deletions plugins/button/app/components/ItemDropArea/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React from 'react'
import './style.css'

export const ItemDropArea = ({ children, onDrop = () => {} }) => {
const [isDraggedOver, setIsDraggedOver] = React.useState()

function handleDragOver (e) {
e.preventDefault()
setIsDraggedOver(true)
}

function handleDragLeave (e) {
e.stopPropagation()
setIsDraggedOver(false)
}

function handleDrop (e) {
e.stopPropagation()
setIsDraggedOver(false)

const itemId = e.dataTransfer.getData('text/plain')
const item = e.dataTransfer.getData('bridge/item')
if (!itemId && !item?.id) {
return
}

onDrop(itemId || item?.id)
}

return (
<div
className='ItemDropArea'
onDragOver={handleDragOver}
>
{
isDraggedOver &&
(
<div
className='ItemDropArea-dropArea'
onDrop={handleDrop}
onDragLeave={handleDragLeave}
/>
)
}
{children}
</div>
)
}
18 changes: 18 additions & 0 deletions plugins/button/app/components/ItemDropArea/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.ItemDropArea {
position: relative;
width: 100%;
height: 100%;
}

.ItemDropArea-dropArea {
position: absolute;
width: 100%;
height: 100%;

background: var(--base-color--shade1);
border: 2px solid var(--base-color--shade2);

border-radius: 6px;

z-index: 1;
}
31 changes: 31 additions & 0 deletions plugins/button/app/components/QueryPath/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react'

function getQueryParam (path, param) {
const params = new URLSearchParams(path)
return params.get(param)
}

export const QueryPath = ({ path, children }) => {
const [currentPath, setCurrentPath] = React.useState()

React.useEffect(() => {
function onPopState () {
setCurrentPath(getQueryParam(window.location.search, 'path'))
}

onPopState()

window.addEventListener('popstate', onPopState)
return window.removeEventListener('popstate', onPopState)
}, [])

if (currentPath !== path) {
return <></>
}

return (
<>
{children}
</>
)
}
13 changes: 13 additions & 0 deletions plugins/button/app/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react'
import ReactDOM from 'react-dom'

import App from './App'

import './style.css'

ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
)
10 changes: 10 additions & 0 deletions plugins/button/app/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
html, body, #root {
position: relative;
width: 100%;
height: 100%;

padding: 0;
margin: 0;

font-size: min(10vw, 30vh);
}
53 changes: 53 additions & 0 deletions plugins/button/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// SPDX-FileCopyrightText: 2024 Sveriges Television AB
//
// SPDX-License-Identifier: MIT

/**
* @type { import('../../api').Api }
*/
const bridge = require('bridge')

const assets = require('../../assets.json')
const manifest = require('./package.json')

async function initWidget () {
const cssPath = `${assets.hash}.${manifest.name}.bundle.css`
const jsPath = `${assets.hash}.${manifest.name}.bundle.js`

const html = `
<!DOCTYPE html>
<html>
<head>
<title>Button</title>
<base href="/"></base>
<link rel="stylesheet" href="${bridge.server.uris.STYLE_RESET}" />
<link rel="stylesheet" href="${cssPath}" />
<script src="${jsPath}" defer></script>
</head>
<body>
<div id="root"></div>
</body>
</html>
`

const htmlPath = await bridge.server.serveString(html)

bridge.widgets.registerWidget({
id: 'bridge.plugins.button.play',
name: 'Play button',
uri: `${htmlPath}?path=play`,
description: 'A button playing an item'
})

bridge.widgets.registerWidget({
id: 'bridge.plugins.button.stop',
name: 'Stop button',
uri: `${htmlPath}?path=stop`,
description: 'A button stopping an item'
})
}

exports.activate = async () => {
bridge.commands.registerCommand('bridge.plugins.clock.time', echo => ({ echo, time: Date.now() }))
initWidget()
}
18 changes: 18 additions & 0 deletions plugins/button/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "bridge-plugin-button",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"engines": {
"bridge": "^0.0.1"
},
"keywords": [
"bridge",
"plugin"
],
"author": "Axel Boberg ([email protected])",
"license": "UNLICENSED"
}

0 comments on commit 08a0290

Please sign in to comment.