Add drag interaction to duplicate stream ids in control page

This commit is contained in:
Max Goodhart
2020-07-05 22:52:27 -07:00
parent 645220ac00
commit 904049646d
4 changed files with 48 additions and 158 deletions

View File

@@ -5,7 +5,7 @@ import { BrowserView, BrowserWindow, ipcMain } from 'electron'
import { interpret } from 'xstate'
import viewStateMachine from './viewStateMachine'
import { boxesFromViewContentMap } from './geometry'
import { boxesFromViewContentMap } from '../geometry'
export default class StreamWindow extends EventEmitter {
constructor(config) {

View File

@@ -1,61 +0,0 @@
import isEqual from 'lodash/isEqual'
export function boxesFromViewContentMap(width, height, viewContentMap) {
const boxes = []
const visited = new Set()
function isPosContent(x, y, content) {
const checkIdx = width * y + x
return (
!visited.has(checkIdx) && isEqual(viewContentMap.get(checkIdx), content)
)
}
function findLargestBox(x, y) {
const idx = width * y + x
const spaces = [idx]
const content = viewContentMap.get(idx)
let maxY
for (maxY = y + 1; maxY < height; maxY++) {
if (!isPosContent(x, maxY, content)) {
break
}
spaces.push(width * maxY + x)
}
let cx = x
let cy = y
scan: for (cx = x + 1; cx < width; cx++) {
for (cy = y; cy < maxY; cy++) {
if (!isPosContent(cx, cy, content)) {
break scan
}
}
for (let cy = y; cy < maxY; cy++) {
spaces.push(width * cy + cx)
}
}
const w = cx - x
const h = maxY - y
spaces.sort()
return { content, x, y, w, h, spaces }
}
for (let y = 0; y < width; y++) {
for (let x = 0; x < height; x++) {
const idx = width * y + x
if (visited.has(idx) || viewContentMap.get(idx) === undefined) {
continue
}
const box = findLargestBox(x, y)
boxes.push(box)
for (const boxIdx of box.spaces) {
visited.add(boxIdx)
}
}
}
return boxes
}

View File

@@ -1,91 +0,0 @@
import { boxesFromViewContentMap } from './geometry'
function example([text]) {
return text
.replace(/\s/g, '')
.split('')
.map((c) => (c === '.' ? undefined : { url: c }))
}
const box1 = example`
ab
ab
`
const box2 = example`
aa
bb
`
const box3 = example`
aac
aaa
dae
`
const box4 = example`
...
.aa
.aa
`
const box5 = example`
..a
..a
.aa
`
describe.each([
[
2,
2,
box1,
[
{ content: { url: 'a' }, x: 0, y: 0, w: 1, h: 2, spaces: [0, 2] },
{ content: { url: 'b' }, x: 1, y: 0, w: 1, h: 2, spaces: [1, 3] },
],
],
[
2,
2,
box2,
[
{ content: { url: 'a' }, x: 0, y: 0, w: 2, h: 1, spaces: [0, 1] },
{ content: { url: 'b' }, x: 0, y: 1, w: 2, h: 1, spaces: [2, 3] },
],
],
[
3,
3,
box3,
[
{ content: { url: 'a' }, x: 0, y: 0, w: 2, h: 2, spaces: [0, 1, 3, 4] },
{ content: { url: 'c' }, x: 2, y: 0, w: 1, h: 1, spaces: [2] },
{ content: { url: 'a' }, x: 2, y: 1, w: 1, h: 1, spaces: [5] },
{ content: { url: 'd' }, x: 0, y: 2, w: 1, h: 1, spaces: [6] },
{ content: { url: 'a' }, x: 1, y: 2, w: 1, h: 1, spaces: [7] },
{ content: { url: 'e' }, x: 2, y: 2, w: 1, h: 1, spaces: [8] },
],
],
[
3,
3,
box4,
[{ content: { url: 'a' }, x: 1, y: 1, w: 2, h: 2, spaces: [4, 5, 7, 8] }],
],
[
3,
3,
box5,
[
{ content: { url: 'a' }, x: 2, y: 0, w: 1, h: 3, spaces: [2, 5, 8] },
{ content: { url: 'a' }, x: 1, y: 2, w: 1, h: 1, spaces: [7] },
],
],
])('boxesFromViewContentMap(%i, %i, %j)', (width, height, data, expected) => {
test(`returns expected ${expected.length} boxes`, () => {
const stateURLMap = new Map(data.map((v, idx) => [idx, v]))
const result = boxesFromViewContentMap(width, height, stateURLMap)
expect(result).toStrictEqual(expected)
})
})

View File

@@ -11,6 +11,7 @@ import styled, { css } from 'styled-components'
import { useHotkeys } from 'react-hotkeys-hook'
import '../index.css'
import { idxInBox } from '../geometry'
import SoundIcon from '../static/volume-up-solid.svg'
import NoVideoIcon from '../static/video-slash-solid.svg'
import ReloadIcon from '../static/redo-alt-solid.svg'
@@ -185,9 +186,33 @@ function App({ wsEndpoint }) {
stateIdxMap,
delayState,
} = useStreamwallConnection(wsEndpoint)
const { gridCount } = config
const [dragStart, setDragStart] = useState()
const handleDragStart = useCallback((idx, ev) => {
setDragStart(idx)
ev.preventDefault()
}, [])
const [dragEnd, setDragEnd] = useState()
useEffect(() => {
function endDrag() {
if (dragStart !== undefined) {
stateDoc.transact(() => {
const viewsState = stateDoc.getMap('views')
const streamId = viewsState.get(String(dragStart)).get('streamId')
for (let idx = 0; idx < gridCount ** 2; idx++) {
if (idxInBox(gridCount, dragStart, dragEnd, idx)) {
viewsState.get(String(idx)).set('streamId', streamId)
}
}
})
setDragStart()
}
}
window.addEventListener('mouseup', endDrag)
return () => window.removeEventListener('mouseup', endDrag)
}, [stateDoc, dragStart, dragEnd])
const handleSetView = useCallback(
(idx, streamId) => {
const stream = streams.find((d) => d._id === streamId)
@@ -344,6 +369,9 @@ function App({ wsEndpoint }) {
const { isListening = false, isBlurred = false, state } =
stateIdxMap.get(idx) || {}
const { streamId } = sharedState.views?.[idx] || ''
const isDragHighlighted =
dragStart !== undefined &&
idxInBox(gridCount, dragStart, dragEnd, idx)
return (
<GridInput
idx={idx}
@@ -352,6 +380,9 @@ function App({ wsEndpoint }) {
isDisplaying={state && state.matches('displaying')}
isListening={isListening}
isBlurred={isBlurred}
isHighlighted={isDragHighlighted}
onMouseDown={handleDragStart}
onMouseEnter={setDragEnd}
onChangeSpace={handleSetView}
onSetListening={handleSetListening}
onSetBlurred={handleSetBlurred}
@@ -472,6 +503,9 @@ function GridInput({
isError,
isListening,
isBlurred,
isHighlighted,
onMouseDown,
onMouseEnter,
onSetListening,
onSetBlurred,
onReloadView,
@@ -514,9 +548,14 @@ function GridInput({
idx,
onDevTools,
])
const handleClick = useCallback((ev) => {
ev.target.select()
})
const handleMouseDown = useCallback(
(ev) => {
ev.target.select()
onMouseDown(idx, ev)
},
[onMouseDown],
)
const handleMouseEnter = useCallback(() => onMouseEnter(idx), [onMouseEnter])
return (
<StyledGridContainer>
{isDisplaying && (
@@ -552,9 +591,11 @@ function GridInput({
name={idx}
value={editingValue || spaceValue || ''}
isError={isError}
isHighlighted={isHighlighted}
onFocus={handleFocus}
onBlur={handleBlur}
onClick={handleClick}
onMouseDown={handleMouseDown}
onMouseEnter={handleMouseEnter}
onChange={handleChange}
/>
</StyledGridContainer>
@@ -675,6 +716,7 @@ const StyledGridInput = styled.input`
height: 50px;
padding: 20px;
border: 2px solid ${({ isError }) => (isError ? 'red' : 'black')};
background: ${({ isHighlighted }) => (isHighlighted ? '#dfd' : 'white')};
font-size: 20px;
text-align: center;