Skip to content
Merged

1.0.2 #140

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
# Changelog

## 1.0.2
### Added
- Support for using the whole state in variables
- Auto save functionality
### Changed
- Context menus of rundown items are now searchable
- Updated dependencies
- "Divider" cues have been renamed to "Comments"
### Fixed
- Select options now render in the system default color (#137)
- Context menus now align themselvses with respect to the top and bottom of the viewport
- An issue where the role popover had the wrong text size
- The playhead of timelines now change color with the theme
### Removed
- Support for days in LTC

## 1.0.1
### Added
Expand All @@ -15,7 +27,7 @@
### Changed
- Added 'Stop' as an option when setting default on end actions in settings
- Electron now bypasses proxies on requests to localhost
- Update dependencies
- Updated dependencies
### Fixed
- Added a check for undefined items in item.change in the type plugin
- Regular Electron accelerators now work even though a widget isn't focused (CMD+Q e.t.c.)
Expand Down
5 changes: 3 additions & 2 deletions api/items.js
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,8 @@ class Items {

const type = await this.#props.Types.getType(itemObj.type)
const vars = await this.#props.Variables.getAllVariables()
const clone = this.populateVariablesMutable(deepClone(itemObj), type, vars)
const clone = this.populateVariablesMutable(deepClone(itemObj), type, { ...vars, state: this.#props.State.getLocalState() })

return clone
}

Expand Down Expand Up @@ -530,7 +531,7 @@ class Items {
async renderValue (itemId, path) {
const item = await this.getItem(itemId)
const currentValue = objectPath.get(item || {}, path)
return this.#props.Variables.substituteInString(currentValue, undefined, { this: item })
return this.#props.Variables.substituteInString(currentValue, undefined, { this: item, state: this.#props.State.getLocalState() })
}
}

Expand Down
2 changes: 1 addition & 1 deletion api/variables.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class Variables {
* default data rather than replace
* @returns { String }
*/
substituteInString (str, data = (this.#props.State.getLocalState()?.variables || {}), overrideData = {}) {
substituteInString (str, data = { ...(this.#props.State.getLocalState()?.variables || {}), state: this.#props.State.getLocalState() }, overrideData = {}) {
if (!str) {
return ''
}
Expand Down
2 changes: 2 additions & 0 deletions app/assets/icons/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import add from './add.svg'
import edit from './edit.svg'
import play from './play.svg'
import save from './save.svg'
import stop from './stop.svg'
import close from './close.svg'
import float from './float.svg'
Expand Down Expand Up @@ -39,6 +40,7 @@ export default {
add,
edit,
play,
save,
stop,
close,
float,
Expand Down
5 changes: 5 additions & 0 deletions app/assets/icons/save.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
61 changes: 55 additions & 6 deletions app/components/ContextMenu/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ import { createPortal } from 'react-dom'

import './style.css'

const DIRECTION = Object.freeze({
UP: 'up',
DOWN: 'down'
})

const VERTICAL_MARGIN_TO_WINDOW_EDGE_PX = 5
const VERTICAL_DIRECTION_SHIFT_PX = 20

/**
* A threshold for how long the context menu has
* to have been open before an event can close it
Expand All @@ -26,16 +34,55 @@ const OPEN_THRESHOLD_MS = 100
*/
const DEFAULT_WIDTH_PX = 150

export const ContextMenu = ({ x, y, width = DEFAULT_WIDTH_PX, children, onClose = () => {} }) => {
export const ContextMenu = ({ x: _x, y: _y, width = DEFAULT_WIDTH_PX, children, onClose = () => {} }) => {
const elRef = React.useRef()
const openTimestampRef = React.useRef()

const [direction, setDirection] = React.useState('down')
const [direction, setDirection] = React.useState(DIRECTION.DOWN)
const [x, setX] = React.useState(_x)
const [y, setY] = React.useState(_y)

React.useEffect(() => {
openTimestampRef.current = Date.now()
}, [x, y])

/*
Avoid clipping the context menu as much as possible
by shifting it based on the window size and the menus
direction
*/
React.useEffect(() => {
const observer = new ResizeObserver(entries => {
if (!entries[0]) {
return
}

if (!elRef.current) {
return
}
const bounds = elRef.current.getBoundingClientRect()

/*
Move it down from the top
*/
if (direction === DIRECTION.UP && y - bounds.height < 0) {
setY(bounds.height + VERTICAL_MARGIN_TO_WINDOW_EDGE_PX)
return
}

/*
Move it up from the bottom
*/
if (direction === DIRECTION.DOWN && y + bounds.height > window.innerHeight) {
setY(window.innerHeight - bounds.height - VERTICAL_MARGIN_TO_WINDOW_EDGE_PX)
return
}
})

observer.observe(elRef.current)
return () => observer.disconnect(elRef.current)
}, [x, y, direction])

React.useEffect(() => {
function closeContext () {
/*
Expand Down Expand Up @@ -77,12 +124,14 @@ export const ContextMenu = ({ x, y, width = DEFAULT_WIDTH_PX, children, onClose
*/
React.useEffect(() => {
const viewportHeight = window.innerHeight
if (y > viewportHeight / 2) {
setDirection('up')
if (_y > viewportHeight / 2) {
setDirection(DIRECTION.UP)
setY(current => current + VERTICAL_DIRECTION_SHIFT_PX)
} else {
setDirection('down')
setDirection(DIRECTION.DOWN)
setY(current => current - VERTICAL_DIRECTION_SHIFT_PX)
}
}, [y])
}, [_y])

return (
<>
Expand Down
11 changes: 9 additions & 2 deletions app/components/Footer/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Preferences } from '../Preferences'
import { Icon } from '../Icon'

import * as api from '../../api'
import * as dateUtils from '../../utils/date'
import * as windowUtils from '../../utils/window'

import './style.css'
Expand Down Expand Up @@ -58,7 +59,7 @@ export function Footer ({ title = DEFAULT_TITLE, features }) {
{
featureShown('role') &&
(
<div className='Footer-actionSection'>
<div className='Footer-section'>
<button className={`Footer-button Footer-roleBtn ${role === 1 ? 'is-main' : ''}`} onClick={() => setRoleOpen(true)}>
{role === 1 ? 'Main' : 'Satellite'}
</button>
Expand All @@ -69,7 +70,7 @@ export function Footer ({ title = DEFAULT_TITLE, features }) {
{
featureShown('sharing') &&
(
<div className='Footer-actionSection'>
<div className='Footer-section'>
<button className='Footer-button Footer-sharingBtn' onClick={() => setSharingOpen(true)}>
<Icon name='person' />
{connectionCount || 0}
Expand All @@ -78,6 +79,12 @@ export function Footer ({ title = DEFAULT_TITLE, features }) {
</div>
)
}
{
shared?._lastSavedAt &&
<div className='Footer-section Footer-section--hint' title='Last saved at'>
<Icon name='save' /> {dateUtils.format(new Date(shared?._lastSavedAt), 'h:m:s')}
</div>
}
</div>
<div className='Footer-block'>
{
Expand Down
14 changes: 13 additions & 1 deletion app/components/Footer/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,20 @@
margin-right: 10px;
}

.Footer-actionSection {
.Footer-section {
display: flex;
margin-right: 10px;

align-items: center;
}

.Footer-section--hint {
opacity: 0.5;
}

.Footer-section .Icon {
transform: scale(0.75);
transform-origin: 50% 50%;
}

.Footer-button.Footer-sharingBtn {
Expand Down
8 changes: 4 additions & 4 deletions app/components/Preferences/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import appearance from './sections/appearance.json'
import shortcuts from './sections/shortcuts.json'
import general from './sections/general.json'

import advanced from './sections/project-advanced.json'
import sharing from './sections/project-sharing.json'
import projectGeneral from './sections/project-general.json'
import projectAdvanced from './sections/project-advanced.json'

import './style.css'

Expand All @@ -36,8 +36,8 @@ const INTERNAL_SETTINGS = [
{
title: 'Project',
items: [
{ title: 'Sharing', items: sharing },
{ title: 'Advanced', items: advanced }
{ title: 'General', items: projectGeneral },
{ title: 'Advanced', items: projectAdvanced }
]
}
]
Expand Down
16 changes: 16 additions & 0 deletions app/components/Preferences/sections/project-general.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[
{
"title": "Named url",
"description": "This workspace will become available as /named/<custom name> and persistent across restarts if a custom name is specified",
"inputs": [
{ "type": "string", "bind": "shared.url", "label": "Custom name" }
]
},
{
"title": "Auto save",
"inputs": [
{ "type": "boolean", "bind": "shared.core.internals.enableAutoSave", "label": "Enable auto save" },
{ "type": "number", "bind": "shared.core.internals.autoSaveInterval", "label": "Save interval in seconds", "max": 86400, "min": 1 }
]
}
]
9 changes: 0 additions & 9 deletions app/components/Preferences/sections/project-sharing.json

This file was deleted.

2 changes: 1 addition & 1 deletion app/components/Role/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
width: 300px;

text-align: left;
font-size: 1em;
font-size: 1rem;
color: var(--base-color);
}

Expand Down
33 changes: 33 additions & 0 deletions app/utils/date.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
function zeroPad (n) {
if (n < 10) {
return `0${n}`
}
return `${n}`
}

/**
* Format a date as a string according to a pattern
* @param { Date } d
* @param { string } pattern
* @returns { string }
*/
export function format (d, pattern = 'Y-M-D h:m:s') {
const parts = {
Y: d.getFullYear(),
M: d.getMonth() + 1,
D: d.getDate(),
h: d.getHours(),
m: d.getMinutes(),
s: d.getSeconds()
}

return pattern
.split('')
.map(char => {
if (Object.prototype.hasOwnProperty.call(parts, char)) {
return zeroPad(parts[char])
}
return char
})
.join('')
}
13 changes: 13 additions & 0 deletions app/utils/date.unit.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { format } from './date.js'

test('formats dates with the default pattern', () => {
const date = new Date(2026, 6, 16, 9, 5, 3)

expect(format(date)).toBe('2026-07-16 09:05:03')
})

test('formats dates with custom patterns and preserves non-token characters', () => {
const date = new Date(2026, 0, 2, 13, 4, 59)

expect(format(date, 'D/M/Y @ h:m')).toBe('02/01/2026 @ 13:04')
})
8 changes: 7 additions & 1 deletion lib/api/SWindow.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class SWindow extends DIBase {
this.props.SCommands.registerCommand('window.toggleMaximize', this.toggleMaximize.bind(this))
this.props.SCommands.registerCommand('window.minimize', this.minimize.bind(this))
this.props.SCommands.registerCommand('window.close', this.close.bind(this))
this.props.SCommands.registerCommand('window.save', this.save.bind(this))
}

#buildAppMenu (itemList) {
Expand Down Expand Up @@ -132,6 +133,10 @@ class SWindow extends DIBase {
}
}

async save () {
this.props.Workspace.save()
}

async setStayOnTop (id, newValue) {
if (!id) {
return
Expand Down Expand Up @@ -202,5 +207,6 @@ class SWindow extends DIBase {
DIController.main.register('SWindow', SWindow, [
'Workspace',
'SCommands',
'SEvents'
'SEvents',
'SState'
])
Loading
Loading