Skip to content
Merged
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
27 changes: 27 additions & 0 deletions packages/blueprints-integration/src/userEditing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,35 @@ import { DefaultUserOperationsTypes } from './ingest.js'
* Description of a user performed editing operation allowed on an document
*/
export type UserEditingDefinition =
| UserEditingDefinitionState
| UserEditingDefinitionAction
| UserEditingDefinitionForm
| UserEditingDefinitionSofieDefault

/**
* A simple 'state' that can be signlaled to the user, but has no associated action. This is useful for indicating
* things like "This piece is being held" or "This piece is being affected by a global action"
*/
export interface UserEditingDefinitionState {
type: UserEditingType.STATE
/** Id of this operation */
id: string
/** Label to show to the user for this operation */
label: ITranslatableMessage
/** Icon to show when this action is 'active'
*
* This can either be a relative URL to an image in the Blueprints assets or a `data:` URL
*/
icon?: string
/** Icon to show when this action is 'disabled'
*
* This can either be a relative URL to an image in the Blueprints assets or a `data:` URL
*/
iconInactive?: string
/** Whether this action should be indicated as being active */
isActive?: boolean
}

/**
* A simple 'action' that can be performed
*/
Expand Down Expand Up @@ -65,6 +90,8 @@ export interface UserEditingDefinitionSofieDefault {
}

export enum UserEditingType {
/** State */
STATE = 'state',
Comment thread
coderabbitai[bot] marked this conversation as resolved.
/** Action */
ACTION = 'action',
/** Form */
Expand Down
19 changes: 19 additions & 0 deletions packages/corelib/src/dataModel/UserEditingDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,29 @@ import type {
import type { ITranslatableMessage } from '../TranslatableMessage.js'

export type CoreUserEditingDefinition =
| CoreUserEditingDefinitionState
| CoreUserEditingDefinitionAction
| CoreUserEditingDefinitionForm
| CoreUserEditingDefinitionSofie

export interface CoreUserEditingDefinitionState {
type: UserEditingType.STATE
/** Id of this operation */
id: string
/** Label to show to the user for this operation */
label: ITranslatableMessage
/** Icon to show when this state is 'active'.
*
* This can either be a relative URL to an image in the Blueprints assets or a `data:` URL */
icon?: string
/** Icon to show when this state is 'disabled'.
*
* This can either be a relative URL to an image in the Blueprints assets or a `data:` URL */
iconInactive?: string
/** Whether this state should be indicated as being active */
isActive?: boolean
}

export interface CoreUserEditingDefinitionAction {
type: UserEditingType.ACTION
/** Id of this operation */
Expand Down
20 changes: 20 additions & 0 deletions packages/job-worker/src/blueprints/context/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import { DBRundown, Rundown } from '@sofie-automation/corelib/dist/dataModel/Rundown'
import {
CoreUserEditingDefinition,
CoreUserEditingDefinitionState,
CoreUserEditingDefinitionAction,
CoreUserEditingDefinitionForm,
CoreUserEditingProperties,
Expand Down Expand Up @@ -71,6 +72,7 @@ import {
UserEditingProperties,
UserEditingDefinitionSofieDefault,
UserEditingType,
UserEditingDefinitionState,
} from '@sofie-automation/blueprints-integration/dist/userEditing'
import type { PlayoutMutatablePart } from '../../playout/model/PlayoutPartInstanceModel.js'
import { BlueprintQuickLookInfo } from '@sofie-automation/blueprints-integration/dist/context/quickLoopInfo'
Expand Down Expand Up @@ -568,6 +570,15 @@ function translateUserEditsToBlueprint(
return _.compact(
userEdits.map((userEdit) => {
switch (userEdit.type) {
case UserEditingType.STATE:
return literal<UserEditingDefinitionState>({
type: UserEditingType.STATE,
id: userEdit.id,
label: omit(userEdit.label, 'namespaces'),
icon: userEdit.icon,
iconInactive: userEdit.iconInactive,
isActive: userEdit.isActive,
})
case UserEditingType.ACTION:
return literal<UserEditingDefinitionAction>({
type: UserEditingType.ACTION,
Expand Down Expand Up @@ -631,6 +642,15 @@ export function translateUserEditsFromBlueprint(
return _.compact(
userEdits.map((userEdit) => {
switch (userEdit.type) {
case UserEditingType.STATE:
return literal<CoreUserEditingDefinitionState>({
type: UserEditingType.STATE,
id: userEdit.id,
label: wrapTranslatableMessageFromBlueprints(userEdit.label, blueprintIds),
icon: userEdit.icon,
iconInactive: userEdit.iconInactive,
isActive: userEdit.isActive,
})
case UserEditingType.ACTION:
return literal<CoreUserEditingDefinitionAction>({
type: UserEditingType.ACTION,
Expand Down
12 changes: 6 additions & 6 deletions packages/playout-gateway/src/tsrHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,20 +289,20 @@ export class TSRHandler {

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

if (!e || !('message' in e)) {
if (!e || typeof e !== 'object' || !('message' in e)) {
return {
message: name + ': ' + 'Unknown error: ' + JSON.stringify(e),
message: name + ': ' + 'Unknown error: ' + stringifyError(e, true),
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

return {
message: e.message && name + ': ' + e.message,
name: e.name && name + ': ' + e.name,
stack: e.stack && e.stack + '\nAt device' + name,
message: e.message !== undefined ? name + ': ' + e.message : undefined,
name: e.name !== undefined ? name + ': ' + e.name : undefined,
stack: e.stack !== undefined ? e.stack + '\nAt device' + name : undefined,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix stack suffix formatting in device context.

Line 275 currently builds ...'\nAt device' + name, which produces At deviceDevice ... without a separator.

🔧 Proposed fix
-				stack: e.stack !== undefined ? e.stack + '\nAt device' + name : undefined,
+				stack: e.stack !== undefined ? `${e.stack}\nAt device ${name}` : undefined,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
stack: e.stack !== undefined ? e.stack + '\nAt device' + name : undefined,
stack: e.stack !== undefined ? `${e.stack}\nAt device ${name}` : undefined,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/playout-gateway/src/tsrHandler.ts` at line 275, The stack suffix is
concatenated without a separator, causing outputs like "At deviceDevice"; update
the concatenation in tsrHandler.ts where the stack is built (the expression
using e.stack and name) to include a separator (e.g., a space or colon) before
the device name so it reads "At device <name>" (ensure the updated concatenation
still only runs when e.stack is defined).

}
}
const fixContext = (...context: any[]): any => {
Expand Down
24 changes: 20 additions & 4 deletions packages/shared-lib/src/lib/JSONSchemaUtil.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ export enum SchemaFormUIField {
* Title of the property
*/
Title = 'ui:title',
/**
* Icon to use for the property, for widgets that support them: `oneOfButtons` in `oneOf` array members
*/
Icon = 'ui:icon',
/**
* Description/hint for the property
*/
Expand All @@ -24,15 +28,27 @@ export enum SchemaFormUIField {
* If an integer property, whether to treat it as zero-based
*/
ZeroBased = 'ui:zeroBased',
/**
* Whether the property is read-only. This will disable the input and hide any buttons for modifying the value.
*/
ReadOnly = 'ui:readOnly',
/**
* Override the presentation with a special mode.
* Currently only valid for:
* - object properties. Valid values are 'json'.
* - string properties. Valid values are 'base64-image'.
* - boolean properties. Valid values are 'switch'.
* - array properties with items.type string. Valid values are 'bread-crumbs'.
* - object properties. Valid values are `json`, `oneOfButtons`.
* - `oneOfButtons` uses a `oneOf` list of possible variants of the object, with a `ui:oneOf:discriminant` field
* to determine which variant is selected.
* - string properties. Valid values are `base64-image`.
* - boolean properties. Valid values are `switch`.
* - number properties. Valid values are `timeMs`.
* - array properties with items.type string. Valid values are `bread-crumbs`.
*/
DisplayType = 'ui:displayType',
/**
* When using `oneOf` for an object, the discriminant field is used to determine which variant is selected.
* The value of this field must be a property that has a unique const value for each variant in the oneOf
*/
OneOfDiscriminant = 'ui:oneOf:discriminant',
/**
* Name of the enum values as generated for the typescript enum.
* Future: a new field should probably be added for the UI to use.
Expand Down
Empty file.
28 changes: 22 additions & 6 deletions packages/webui/src/client/lib/Components/FloatInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ interface IFloatInputControlProps {
classNames?: string
modifiedClassName?: string
disabled?: boolean
readOnly?: boolean
placeholder?: string

/** Call handleUpdate on every change, before focus is lost */
Expand All @@ -24,6 +25,7 @@ export function FloatInputControl({
modifiedClassName,
value,
disabled,
readOnly,
placeholder,
handleUpdate,
updateOnKey,
Expand All @@ -36,31 +38,44 @@ export function FloatInputControl({

const handleChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
if (readOnly) return

const number = parseFloat(event.target.value.replace(',', '.'))
setEditingValue(number)

if (updateOnKey && !isNaN(number)) {
handleUpdate(zeroBased ? number - 1 : number)
}
},
[handleUpdate, updateOnKey, zeroBased]
[handleUpdate, updateOnKey, zeroBased, readOnly]
)
const handleBlur = useCallback(
(event: React.FocusEvent<HTMLInputElement>) => {
if (readOnly) {
setEditingValue(null)
return
}

const number = parseFloat(event.currentTarget.value.replace(',', '.'))
if (!isNaN(number)) {
handleUpdate(zeroBased ? number - 1 : number)
}

setEditingValue(null)
},
[handleUpdate, zeroBased]
[handleUpdate, zeroBased, readOnly]
)
const handleFocus = useCallback(
(event: React.FocusEvent<HTMLInputElement>) => {
if (readOnly) return
setEditingValue(parseFloat(event.currentTarget.value.replace(',', '.')))
},
[readOnly]
)
const handleFocus = useCallback((event: React.FocusEvent<HTMLInputElement>) => {
setEditingValue(parseFloat(event.currentTarget.value.replace(',', '.')))
}, [])
const handleKeyUp = useCallback(
(event: React.KeyboardEvent<HTMLInputElement>) => {
if (readOnly) return

if (event.key === 'Escape') {
setEditingValue(null)
} else if (event.key === 'Enter') {
Expand All @@ -70,7 +85,7 @@ export function FloatInputControl({
}
}
},
[handleUpdate, zeroBased]
[handleUpdate, zeroBased, readOnly]
)

let showValue: string | number | undefined = editingValue ?? undefined
Expand All @@ -93,6 +108,7 @@ export function FloatInputControl({
onFocus={handleFocus}
onKeyUp={handleKeyUp}
disabled={disabled}
readOnly={readOnly}
/>
)
}
15 changes: 12 additions & 3 deletions packages/webui/src/client/lib/Components/IntInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ interface IIntInputControlProps {
classNames?: string
modifiedClassName?: string
disabled?: boolean
readOnly?: boolean
placeholder?: string

/** Call handleUpdate on every change, before focus is lost */
Expand All @@ -24,6 +25,7 @@ export function IntInputControl({
modifiedClassName,
value,
disabled,
readOnly,
placeholder,
handleUpdate,
updateOnKey,
Expand All @@ -36,31 +38,37 @@ export function IntInputControl({

const handleChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
if (readOnly) return

const number = parseInt(event.target.value, 10)
setEditingValue(number)

if (updateOnKey && !isNaN(number)) {
handleUpdate(zeroBased ? number - 1 : number)
}
},
[handleUpdate, updateOnKey, zeroBased]
[handleUpdate, updateOnKey, zeroBased, readOnly]
)
const handleBlur = useCallback(
(event: React.FocusEvent<HTMLInputElement>) => {
if (readOnly) return

const number = parseInt(event.currentTarget.value, 10)
if (!isNaN(number)) {
handleUpdate(zeroBased ? number - 1 : number)
}

setEditingValue(null)
},
[handleUpdate, zeroBased]
[handleUpdate, zeroBased, readOnly]
)
const handleFocus = useCallback((event: React.FocusEvent<HTMLInputElement>) => {
setEditingValue(parseInt(event.currentTarget.value, 10))
}, [])
const handleKeyUp = useCallback(
(event: React.KeyboardEvent<HTMLInputElement>) => {
if (readOnly) return

if (event.key === 'Escape') {
setEditingValue(null)
} else if (event.key === 'Enter') {
Expand All @@ -70,7 +78,7 @@ export function IntInputControl({
}
}
},
[handleUpdate, zeroBased]
[handleUpdate, zeroBased, readOnly]
)

let showValue: string | number | undefined = editingValue ?? undefined
Expand All @@ -93,6 +101,7 @@ export function IntInputControl({
onFocus={handleFocus}
onKeyUp={handleKeyUp}
disabled={disabled}
readOnly={readOnly}
/>
)
}
11 changes: 9 additions & 2 deletions packages/webui/src/client/lib/Components/MultiLineTextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ interface IMultiLineTextInputControlProps {
classNames?: string
modifiedClassName?: string
disabled?: boolean
readOnly?: boolean
placeholder?: string

/** Call handleUpdate on every change, before focus is lost */
Expand All @@ -33,6 +34,7 @@ export function MultiLineTextInputControl({
modifiedClassName,
value,
disabled,
readOnly,
placeholder,
handleUpdate,
updateOnKey,
Expand All @@ -41,20 +43,24 @@ export function MultiLineTextInputControl({

const handleChange = useCallback(
(event: React.ChangeEvent<HTMLTextAreaElement>) => {
if (readOnly) return

setEditingValue(event.target.value)

if (updateOnKey) {
handleUpdate(splitValueIntoLines(event.target.value))
}
},
[handleUpdate, updateOnKey]
[handleUpdate, updateOnKey, readOnly]
)
const handleBlur = useCallback(
(event: React.FocusEvent<HTMLTextAreaElement>) => {
if (readOnly) return

handleUpdate(splitValueIntoLines(event.target.value))
setEditingValue(null)
},
[handleUpdate]
[handleUpdate, readOnly]
)
const handleFocus = useCallback((event: React.FocusEvent<HTMLTextAreaElement>) => {
setEditingValue(event.currentTarget.value)
Expand Down Expand Up @@ -82,6 +88,7 @@ export function MultiLineTextInputControl({
onKeyUp={handleKeyUp}
onKeyPress={handleKeyPress}
disabled={disabled}
readOnly={readOnly}
/>
)
}
Expand Down
Loading
Loading