Skip to content

Commit 3826017

Browse files
authored
Merge pull request Sofie-Automation#1712 from nrkno/contrib/feat/UserEditingTypeSTATE
feat: expand Properties panel capabilities
2 parents 83f876c + 6bf3302 commit 3826017

28 files changed

Lines changed: 920 additions & 68 deletions

packages/blueprints-integration/src/userEditing.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,35 @@ import { DefaultUserOperationsTypes } from './ingest.js'
88
* Description of a user performed editing operation allowed on an document
99
*/
1010
export type UserEditingDefinition =
11+
| UserEditingDefinitionState
1112
| UserEditingDefinitionAction
1213
| UserEditingDefinitionForm
1314
| UserEditingDefinitionSofieDefault
1415

16+
/**
17+
* A simple 'state' that can be signlaled to the user, but has no associated action. This is useful for indicating
18+
* things like "This piece is being held" or "This piece is being affected by a global action"
19+
*/
20+
export interface UserEditingDefinitionState {
21+
type: UserEditingType.STATE
22+
/** Id of this operation */
23+
id: string
24+
/** Label to show to the user for this operation */
25+
label: ITranslatableMessage
26+
/** Icon to show when this action is 'active'
27+
*
28+
* This can either be a relative URL to an image in the Blueprints assets or a `data:` URL
29+
*/
30+
icon?: string
31+
/** Icon to show when this action is 'disabled'
32+
*
33+
* This can either be a relative URL to an image in the Blueprints assets or a `data:` URL
34+
*/
35+
iconInactive?: string
36+
/** Whether this action should be indicated as being active */
37+
isActive?: boolean
38+
}
39+
1540
/**
1641
* A simple 'action' that can be performed
1742
*/
@@ -65,6 +90,8 @@ export interface UserEditingDefinitionSofieDefault {
6590
}
6691

6792
export enum UserEditingType {
93+
/** State */
94+
STATE = 'state',
6895
/** Action */
6996
ACTION = 'action',
7097
/** Form */

packages/corelib/src/dataModel/UserEditingDefinitions.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,29 @@ import type {
88
import type { ITranslatableMessage } from '../TranslatableMessage.js'
99

1010
export type CoreUserEditingDefinition =
11+
| CoreUserEditingDefinitionState
1112
| CoreUserEditingDefinitionAction
1213
| CoreUserEditingDefinitionForm
1314
| CoreUserEditingDefinitionSofie
1415

16+
export interface CoreUserEditingDefinitionState {
17+
type: UserEditingType.STATE
18+
/** Id of this operation */
19+
id: string
20+
/** Label to show to the user for this operation */
21+
label: ITranslatableMessage
22+
/** Icon to show when this state is 'active'.
23+
*
24+
* This can either be a relative URL to an image in the Blueprints assets or a `data:` URL */
25+
icon?: string
26+
/** Icon to show when this state is 'disabled'.
27+
*
28+
* This can either be a relative URL to an image in the Blueprints assets or a `data:` URL */
29+
iconInactive?: string
30+
/** Whether this state should be indicated as being active */
31+
isActive?: boolean
32+
}
33+
1534
export interface CoreUserEditingDefinitionAction {
1635
type: UserEditingType.ACTION
1736
/** Id of this operation */

packages/job-worker/src/blueprints/context/lib.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
import { DBRundown, Rundown } from '@sofie-automation/corelib/dist/dataModel/Rundown'
1616
import {
1717
CoreUserEditingDefinition,
18+
CoreUserEditingDefinitionState,
1819
CoreUserEditingDefinitionAction,
1920
CoreUserEditingDefinitionForm,
2021
CoreUserEditingProperties,
@@ -71,6 +72,7 @@ import {
7172
UserEditingProperties,
7273
UserEditingDefinitionSofieDefault,
7374
UserEditingType,
75+
UserEditingDefinitionState,
7476
} from '@sofie-automation/blueprints-integration/dist/userEditing'
7577
import type { PlayoutMutatablePart } from '../../playout/model/PlayoutPartInstanceModel.js'
7678
import { BlueprintQuickLookInfo } from '@sofie-automation/blueprints-integration/dist/context/quickLoopInfo'
@@ -568,6 +570,15 @@ function translateUserEditsToBlueprint(
568570
return _.compact(
569571
userEdits.map((userEdit) => {
570572
switch (userEdit.type) {
573+
case UserEditingType.STATE:
574+
return literal<UserEditingDefinitionState>({
575+
type: UserEditingType.STATE,
576+
id: userEdit.id,
577+
label: omit(userEdit.label, 'namespaces'),
578+
icon: userEdit.icon,
579+
iconInactive: userEdit.iconInactive,
580+
isActive: userEdit.isActive,
581+
})
571582
case UserEditingType.ACTION:
572583
return literal<UserEditingDefinitionAction>({
573584
type: UserEditingType.ACTION,
@@ -631,6 +642,15 @@ export function translateUserEditsFromBlueprint(
631642
return _.compact(
632643
userEdits.map((userEdit) => {
633644
switch (userEdit.type) {
645+
case UserEditingType.STATE:
646+
return literal<CoreUserEditingDefinitionState>({
647+
type: UserEditingType.STATE,
648+
id: userEdit.id,
649+
label: wrapTranslatableMessageFromBlueprints(userEdit.label, blueprintIds),
650+
icon: userEdit.icon,
651+
iconInactive: userEdit.iconInactive,
652+
isActive: userEdit.isActive,
653+
})
634654
case UserEditingType.ACTION:
635655
return literal<CoreUserEditingDefinitionAction>({
636656
type: UserEditingType.ACTION,

packages/playout-gateway/src/tsrHandler.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -289,20 +289,20 @@ export class TSRHandler {
289289

290290
return `Device "${device?.deviceName ?? id}" (${device?.instanceId ?? 'instance unknown'}): ` + e
291291
}
292-
const fixError = (id: string, e: Error): any => {
292+
const fixError = (id: string, e: any): any => {
293293
const device = this._coreTsrHandlers[id]?._device
294294
const name = `Device "${device?.deviceName ?? id}" (${device?.instanceId ?? 'instance unknown'})`
295295

296-
if (!e || !('message' in e)) {
296+
if (!e || typeof e !== 'object' || !('message' in e)) {
297297
return {
298-
message: name + ': ' + 'Unknown error: ' + JSON.stringify(e),
298+
message: name + ': ' + 'Unknown error: ' + stringifyError(e, true),
299299
}
300300
}
301301

302302
return {
303-
message: e.message && name + ': ' + e.message,
304-
name: e.name && name + ': ' + e.name,
305-
stack: e.stack && e.stack + '\nAt device' + name,
303+
message: e.message !== undefined ? name + ': ' + e.message : undefined,
304+
name: e.name !== undefined ? name + ': ' + e.name : undefined,
305+
stack: e.stack !== undefined ? e.stack + '\nAt device' + name : undefined,
306306
}
307307
}
308308
const fixContext = (...context: any[]): any => {

packages/shared-lib/src/lib/JSONSchemaUtil.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ export enum SchemaFormUIField {
1212
* Title of the property
1313
*/
1414
Title = 'ui:title',
15+
/**
16+
* Icon to use for the property, for widgets that support them: `oneOfButtons` in `oneOf` array members
17+
*/
18+
Icon = 'ui:icon',
1519
/**
1620
* Description/hint for the property
1721
*/
@@ -24,15 +28,27 @@ export enum SchemaFormUIField {
2428
* If an integer property, whether to treat it as zero-based
2529
*/
2630
ZeroBased = 'ui:zeroBased',
31+
/**
32+
* Whether the property is read-only. This will disable the input and hide any buttons for modifying the value.
33+
*/
34+
ReadOnly = 'ui:readOnly',
2735
/**
2836
* Override the presentation with a special mode.
2937
* Currently only valid for:
30-
* - object properties. Valid values are 'json'.
31-
* - string properties. Valid values are 'base64-image'.
32-
* - boolean properties. Valid values are 'switch'.
33-
* - array properties with items.type string. Valid values are 'bread-crumbs'.
38+
* - object properties. Valid values are `json`, `oneOfButtons`.
39+
* - `oneOfButtons` uses a `oneOf` list of possible variants of the object, with a `ui:oneOf:discriminant` field
40+
* to determine which variant is selected.
41+
* - string properties. Valid values are `base64-image`.
42+
* - boolean properties. Valid values are `switch`.
43+
* - number properties. Valid values are `timeMs`.
44+
* - array properties with items.type string. Valid values are `bread-crumbs`.
3445
*/
3546
DisplayType = 'ui:displayType',
47+
/**
48+
* When using `oneOf` for an object, the discriminant field is used to determine which variant is selected.
49+
* The value of this field must be a property that has a unique const value for each variant in the oneOf
50+
*/
51+
OneOfDiscriminant = 'ui:oneOf:discriminant',
3652
/**
3753
* Name of the enum values as generated for the typescript enum.
3854
* Future: a new field should probably be added for the UI to use.

packages/webui/src/client/lib/Components/Button.tsx

Whitespace-only changes.

packages/webui/src/client/lib/Components/FloatInput.tsx

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ interface IFloatInputControlProps {
66
classNames?: string
77
modifiedClassName?: string
88
disabled?: boolean
9+
readOnly?: boolean
910
placeholder?: string
1011

1112
/** Call handleUpdate on every change, before focus is lost */
@@ -24,6 +25,7 @@ export function FloatInputControl({
2425
modifiedClassName,
2526
value,
2627
disabled,
28+
readOnly,
2729
placeholder,
2830
handleUpdate,
2931
updateOnKey,
@@ -36,31 +38,44 @@ export function FloatInputControl({
3638

3739
const handleChange = useCallback(
3840
(event: React.ChangeEvent<HTMLInputElement>) => {
41+
if (readOnly) return
42+
3943
const number = parseFloat(event.target.value.replace(',', '.'))
4044
setEditingValue(number)
4145

4246
if (updateOnKey && !isNaN(number)) {
4347
handleUpdate(zeroBased ? number - 1 : number)
4448
}
4549
},
46-
[handleUpdate, updateOnKey, zeroBased]
50+
[handleUpdate, updateOnKey, zeroBased, readOnly]
4751
)
4852
const handleBlur = useCallback(
4953
(event: React.FocusEvent<HTMLInputElement>) => {
54+
if (readOnly) {
55+
setEditingValue(null)
56+
return
57+
}
58+
5059
const number = parseFloat(event.currentTarget.value.replace(',', '.'))
5160
if (!isNaN(number)) {
5261
handleUpdate(zeroBased ? number - 1 : number)
5362
}
5463

5564
setEditingValue(null)
5665
},
57-
[handleUpdate, zeroBased]
66+
[handleUpdate, zeroBased, readOnly]
67+
)
68+
const handleFocus = useCallback(
69+
(event: React.FocusEvent<HTMLInputElement>) => {
70+
if (readOnly) return
71+
setEditingValue(parseFloat(event.currentTarget.value.replace(',', '.')))
72+
},
73+
[readOnly]
5874
)
59-
const handleFocus = useCallback((event: React.FocusEvent<HTMLInputElement>) => {
60-
setEditingValue(parseFloat(event.currentTarget.value.replace(',', '.')))
61-
}, [])
6275
const handleKeyUp = useCallback(
6376
(event: React.KeyboardEvent<HTMLInputElement>) => {
77+
if (readOnly) return
78+
6479
if (event.key === 'Escape') {
6580
setEditingValue(null)
6681
} else if (event.key === 'Enter') {
@@ -70,7 +85,7 @@ export function FloatInputControl({
7085
}
7186
}
7287
},
73-
[handleUpdate, zeroBased]
88+
[handleUpdate, zeroBased, readOnly]
7489
)
7590

7691
let showValue: string | number | undefined = editingValue ?? undefined
@@ -93,6 +108,7 @@ export function FloatInputControl({
93108
onFocus={handleFocus}
94109
onKeyUp={handleKeyUp}
95110
disabled={disabled}
111+
readOnly={readOnly}
96112
/>
97113
)
98114
}

packages/webui/src/client/lib/Components/IntInput.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ interface IIntInputControlProps {
66
classNames?: string
77
modifiedClassName?: string
88
disabled?: boolean
9+
readOnly?: boolean
910
placeholder?: string
1011

1112
/** Call handleUpdate on every change, before focus is lost */
@@ -24,6 +25,7 @@ export function IntInputControl({
2425
modifiedClassName,
2526
value,
2627
disabled,
28+
readOnly,
2729
placeholder,
2830
handleUpdate,
2931
updateOnKey,
@@ -36,31 +38,37 @@ export function IntInputControl({
3638

3739
const handleChange = useCallback(
3840
(event: React.ChangeEvent<HTMLInputElement>) => {
41+
if (readOnly) return
42+
3943
const number = parseInt(event.target.value, 10)
4044
setEditingValue(number)
4145

4246
if (updateOnKey && !isNaN(number)) {
4347
handleUpdate(zeroBased ? number - 1 : number)
4448
}
4549
},
46-
[handleUpdate, updateOnKey, zeroBased]
50+
[handleUpdate, updateOnKey, zeroBased, readOnly]
4751
)
4852
const handleBlur = useCallback(
4953
(event: React.FocusEvent<HTMLInputElement>) => {
54+
if (readOnly) return
55+
5056
const number = parseInt(event.currentTarget.value, 10)
5157
if (!isNaN(number)) {
5258
handleUpdate(zeroBased ? number - 1 : number)
5359
}
5460

5561
setEditingValue(null)
5662
},
57-
[handleUpdate, zeroBased]
63+
[handleUpdate, zeroBased, readOnly]
5864
)
5965
const handleFocus = useCallback((event: React.FocusEvent<HTMLInputElement>) => {
6066
setEditingValue(parseInt(event.currentTarget.value, 10))
6167
}, [])
6268
const handleKeyUp = useCallback(
6369
(event: React.KeyboardEvent<HTMLInputElement>) => {
70+
if (readOnly) return
71+
6472
if (event.key === 'Escape') {
6573
setEditingValue(null)
6674
} else if (event.key === 'Enter') {
@@ -70,7 +78,7 @@ export function IntInputControl({
7078
}
7179
}
7280
},
73-
[handleUpdate, zeroBased]
81+
[handleUpdate, zeroBased, readOnly]
7482
)
7583

7684
let showValue: string | number | undefined = editingValue ?? undefined
@@ -93,6 +101,7 @@ export function IntInputControl({
93101
onFocus={handleFocus}
94102
onKeyUp={handleKeyUp}
95103
disabled={disabled}
104+
readOnly={readOnly}
96105
/>
97106
)
98107
}

packages/webui/src/client/lib/Components/MultiLineTextInput.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ interface IMultiLineTextInputControlProps {
2020
classNames?: string
2121
modifiedClassName?: string
2222
disabled?: boolean
23+
readOnly?: boolean
2324
placeholder?: string
2425

2526
/** Call handleUpdate on every change, before focus is lost */
@@ -33,6 +34,7 @@ export function MultiLineTextInputControl({
3334
modifiedClassName,
3435
value,
3536
disabled,
37+
readOnly,
3638
placeholder,
3739
handleUpdate,
3840
updateOnKey,
@@ -41,20 +43,24 @@ export function MultiLineTextInputControl({
4143

4244
const handleChange = useCallback(
4345
(event: React.ChangeEvent<HTMLTextAreaElement>) => {
46+
if (readOnly) return
47+
4448
setEditingValue(event.target.value)
4549

4650
if (updateOnKey) {
4751
handleUpdate(splitValueIntoLines(event.target.value))
4852
}
4953
},
50-
[handleUpdate, updateOnKey]
54+
[handleUpdate, updateOnKey, readOnly]
5155
)
5256
const handleBlur = useCallback(
5357
(event: React.FocusEvent<HTMLTextAreaElement>) => {
58+
if (readOnly) return
59+
5460
handleUpdate(splitValueIntoLines(event.target.value))
5561
setEditingValue(null)
5662
},
57-
[handleUpdate]
63+
[handleUpdate, readOnly]
5864
)
5965
const handleFocus = useCallback((event: React.FocusEvent<HTMLTextAreaElement>) => {
6066
setEditingValue(event.currentTarget.value)
@@ -82,6 +88,7 @@ export function MultiLineTextInputControl({
8288
onKeyUp={handleKeyUp}
8389
onKeyPress={handleKeyPress}
8490
disabled={disabled}
91+
readOnly={readOnly}
8592
/>
8693
)
8794
}

0 commit comments

Comments
 (0)