Skip to content

Commit

Permalink
wip: 'Conditionalise existing feedbacks' feedback
Browse files Browse the repository at this point in the history
  • Loading branch information
Julusian committed Dec 17, 2024
1 parent 7d73769 commit 261f466
Show file tree
Hide file tree
Showing 11 changed files with 219 additions and 138 deletions.
58 changes: 58 additions & 0 deletions companion/lib/Controls/Fragments/FeedbackStyleBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import type { ButtonStyleProperties, UnparsedButtonStyle } from '@companion-app/shared/Model/StyleModel.js'

/**
* A simple class to help combine multiple styles into one that can be drawn
*/
export class FeedbackStyleBuilder {
#combinedStyle: UnparsedButtonStyle

get style(): UnparsedButtonStyle {
return this.#combinedStyle
}

constructor(baseStyle: ButtonStyleProperties) {
this.#combinedStyle = {
...baseStyle,
imageBuffers: [],
}
}

/**
* Apply a simple layer of style
*/
applySimpleStyle(style: Partial<ButtonStyleProperties> | undefined) {
this.#combinedStyle = {
...this.#combinedStyle,
...style,
}
}

applyComplexStyle(rawValue: any) {
if (typeof rawValue === 'object' && rawValue !== undefined) {
// Prune off some special properties
const prunedValue = { ...rawValue }
delete prunedValue.imageBuffer
delete prunedValue.imageBufferPosition

// Ensure `textExpression` is set at the same times as `text`
delete prunedValue.textExpression
if ('text' in prunedValue) {
prunedValue.textExpression = rawValue.textExpression || false
}

this.#combinedStyle = {
...this.#combinedStyle,
...prunedValue,
}

// Push the imageBuffer into an array
if (rawValue.imageBuffer) {
this.#combinedStyle.imageBuffers.push({
...rawValue.imageBufferPosition,
...rawValue.imageBufferEncoding,
buffer: rawValue.imageBuffer,
})
}
}
}
}
14 changes: 13 additions & 1 deletion companion/lib/Controls/Fragments/FragmentFeedbackInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ export class FragmentFeedbackInstance {
return !!this.#data.disabled
}

get feedbackId(): string {
return this.#data.type
}

/**
* Get the id of the connection this feedback belongs to
*/
Expand Down Expand Up @@ -183,7 +187,6 @@ export class FragmentFeedbackInstance {
if (this.#data.disabled) return false

const definition = this.getDefinition()
if (!definition || definition.type !== 'boolean') return false

// Special case to handle the internal 'logic' operators, which need to be executed live
if (this.connectionId === 'internal' && this.#data.type.startsWith('logic_')) {
Expand All @@ -193,6 +196,8 @@ export class FragmentFeedbackInstance {
return this.#internalModule.executeLogicFeedback(this.asFeedbackInstance(), childValues)
}

if (!definition || definition.type !== 'boolean') return false

if (typeof this.#cachedValue === 'boolean') {
if (definition.showInvert && this.#data.isInverted) return !this.#cachedValue

Expand Down Expand Up @@ -552,6 +557,13 @@ export class FragmentFeedbackInstance {
return feedbacks
}

/**
* Recursively get all the feedbacks
*/
getChildrenOfGroup(groupId: FeedbackChildGroup): FragmentFeedbackInstance[] {
return this.#children.get(groupId)?.getFeedbacks() ?? []
}

/**
* Cleanup and forget any children belonging to the given connection
*/
Expand Down
62 changes: 20 additions & 42 deletions companion/lib/Controls/Fragments/FragmentFeedbackList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { InstanceDefinitions } from '../../Instance/Definitions.js'
import type { InternalController } from '../../Internal/Controller.js'
import type { ModuleHost } from '../../Instance/Host.js'
import type { FeedbackInstance, FeedbackOwner } from '@companion-app/shared/Model/FeedbackModel.js'
import type { ButtonStyleProperties, UnparsedButtonStyle } from '@companion-app/shared/Model/StyleModel.js'
import type { FeedbackStyleBuilder } from './FeedbackStyleBuilder.js'

export class FragmentFeedbackList {
readonly #instanceDefinitions: InstanceDefinitions
Expand Down Expand Up @@ -45,6 +45,13 @@ export class FragmentFeedbackList {
this.#onlyType = onlyType
}

/**
* Get the feedbacks
*/
getFeedbacks(): FragmentFeedbackInstance[] {
return this.#feedbacks
}

/**
* Get all the feedbacks
*/
Expand Down Expand Up @@ -206,7 +213,9 @@ export class FragmentFeedbackList {
!!isCloned
)

// TODO - verify that the feedback matches this.#booleanOnly?
const feedbackDefinition = newFeedback.getDefinition()
if (this.#onlyType && feedbackDefinition?.type !== this.#onlyType)
throw new Error('FragmentFeedbacks cannot accept this type of feedback')

this.#feedbacks.push(newFeedback)

Expand Down Expand Up @@ -354,60 +363,29 @@ export class FragmentFeedbackList {
* @param baseStyle Style of the button without feedbacks applied
* @returns the unprocessed style
*/
getUnparsedStyle(baseStyle: ButtonStyleProperties): UnparsedButtonStyle {
buildStyle(styleBuilder: FeedbackStyleBuilder): void {
if (this.#onlyType == 'boolean') throw new Error('FragmentFeedbacks not setup to use styles')

let style: UnparsedButtonStyle = {
...baseStyle,
imageBuffers: [],
}

// Note: We don't need to consider children of the feedbacks here, as that can only be from boolean feedbacks which are handled by the `getBooleanValue`

for (const feedback of this.#feedbacks) {
if (feedback.disabled) continue

const definition = feedback.getDefinition()
if (definition?.type === 'boolean') {
const booleanValue = feedback.getBooleanValue()
if (booleanValue) {
style = {
...style,
...feedback.asFeedbackInstance().style,
}
}
if (feedback.getBooleanValue()) styleBuilder.applySimpleStyle(feedback.asFeedbackInstance().style)
} else if (definition?.type === 'advanced') {
const rawValue = feedback.cachedValue
if (typeof rawValue === 'object' && rawValue !== undefined) {
// Prune off some special properties
const prunedValue = { ...rawValue }
delete prunedValue.imageBuffer
delete prunedValue.imageBufferPosition

// Ensure `textExpression` is set at the same times as `text`
delete prunedValue.textExpression
if ('text' in prunedValue) {
prunedValue.textExpression = rawValue.textExpression || false
}

style = {
...style,
...prunedValue,
}

// Push the imageBuffer into an array
if (rawValue.imageBuffer) {
style.imageBuffers.push({
...rawValue.imageBufferPosition,
...rawValue.imageBufferEncoding,
buffer: rawValue.imageBuffer,
})
if (feedback.connectionId === 'internal' && feedback.feedbackId === 'logic_conditionalise_advanced') {
if (feedback.getBooleanValue()) {
for (const child of feedback.getChildrenOfGroup('advancedChildren')) {
styleBuilder.applyComplexStyle(child.cachedValue)
}
}
} else {
styleBuilder.applyComplexStyle(feedback.cachedValue)
}
}
}

return style
}

/**
Expand Down
5 changes: 4 additions & 1 deletion companion/lib/Controls/Fragments/FragmentFeedbacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { InstanceDefinitions } from '../../Instance/Definitions.js'
import type { InternalController } from '../../Internal/Controller.js'
import type { ModuleHost } from '../../Instance/Host.js'
import type { FeedbackInstance, FeedbackOwner } from '@companion-app/shared/Model/FeedbackModel.js'
import { FeedbackStyleBuilder } from './FeedbackStyleBuilder.js'

/**
* Helper for ControlTypes with feedbacks
Expand Down Expand Up @@ -451,7 +452,9 @@ export class FragmentFeedbacks {
* Note: Does not clone the style
*/
getUnparsedStyle(): UnparsedButtonStyle {
return this.#feedbacks.getUnparsedStyle(this.baseStyle)
const styleBuilder = new FeedbackStyleBuilder(this.baseStyle)
this.#feedbacks.buildStyle(styleBuilder)
return styleBuilder.style
}

/**
Expand Down
7 changes: 2 additions & 5 deletions companion/lib/Internal/BuildingBlocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export class InternalBuildingBlocks implements InternalModuleFragment {
learnTimeout: undefined,
supportsChildFeedbacks: true,
},
conditionalise_advanced: {
logic_conditionalise_advanced: {
type: 'advanced',
label: 'Conditionalise existing feedbacks',
description: "Make 'advanced' feedbacks conditional",
Expand Down Expand Up @@ -147,7 +147,7 @@ export class InternalBuildingBlocks implements InternalModuleFragment {
* Execute a logic feedback
*/
executeLogicFeedback(feedback: FeedbackInstance, childValues: boolean[]): boolean {
if (feedback.type === 'logic_and') {
if (feedback.type === 'logic_and' || feedback.type === 'logic_conditionalise_advanced') {
if (childValues.length === 0) return !!feedback.isInverted

return childValues.reduce((acc, val) => acc && val, true) === !feedback.isInverted
Expand All @@ -156,9 +156,6 @@ export class InternalBuildingBlocks implements InternalModuleFragment {
} else if (feedback.type === 'logic_xor') {
const isSingleTrue = childValues.reduce((acc, val) => acc + (val ? 1 : 0), 0) === 1
return isSingleTrue === !feedback.isInverted
} else if (feedback.type === 'conditionalise_advanced') {
console.log('TODO', feedback)
return false
} else {
this.#logger.warn(`Unexpected logic feedback type "${feedback.type}"`)
return false
Expand Down
2 changes: 1 addition & 1 deletion webui/src/Buttons/EditButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -559,7 +559,7 @@ function TabsSection({ style, controlId, location, steps, runtimeProps, rotaryAc
controlId={controlId}
feedbacks={feedbacks}
location={location}
booleanOnly={false}
onlyType={null}
addPlaceholder="+ Add feedback"
/>
</MyErrorBoundary>
Expand Down
36 changes: 19 additions & 17 deletions webui/src/Controls/AddFeedbackDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@ interface AddFeedbackGroup {
}
interface AddFeedbackDropdownProps {
onSelect: (feedbackType: string) => void
booleanOnly: boolean
onlyType: 'boolean' | 'advanced' | null
addPlaceholder: string
}
export const AddFeedbackDropdown = observer(function AddFeedbackDropdown({
onSelect,
booleanOnly,
onlyType,
addPlaceholder,
}: AddFeedbackDropdownProps) {
const { connections, feedbackDefinitions, recentlyAddedFeedbacks } = useContext(RootAppStoreContext)
Expand All @@ -49,7 +49,7 @@ export const AddFeedbackDropdown = observer(function AddFeedbackDropdown({
const options: Array<AddFeedbackOption | AddFeedbackGroup> = []
for (const [connectionId, instanceFeedbacks] of feedbackDefinitions.connections.entries()) {
for (const [feedbackId, feedback] of instanceFeedbacks.entries()) {
if (!booleanOnly || feedback.type === 'boolean') {
if (!onlyType || feedback.type === onlyType) {
const connectionLabel = connections.getLabel(connectionId) ?? connectionId
const optionLabel = `${connectionLabel}: ${feedback.label}`
options.push({
Expand All @@ -64,19 +64,21 @@ export const AddFeedbackDropdown = observer(function AddFeedbackDropdown({

const recents: AddFeedbackOption[] = []
for (const feedbackType of recentlyAddedFeedbacks.recentIds) {
if (feedbackType) {
const [connectionId, feedbackId] = feedbackType.split(':', 2)
const feedbackInfo = feedbackDefinitions.connections.get(connectionId)?.get(feedbackId)
if (feedbackInfo) {
const connectionLabel = connections.getLabel(connectionId) ?? connectionId
const optionLabel = `${connectionLabel}: ${feedbackInfo.label}`
recents.push({
isRecent: true,
value: `${connectionId}:${feedbackId}`,
label: optionLabel,
fuzzy: fuzzyPrepare(optionLabel),
})
}
if (!feedbackType) continue

const [connectionId, feedbackId] = feedbackType.split(':', 2)
const feedbackInfo = feedbackDefinitions.connections.get(connectionId)?.get(feedbackId)
if (!feedbackInfo) continue

if (!onlyType || feedbackInfo.type === onlyType) {
const connectionLabel = connections.getLabel(connectionId) ?? connectionId
const optionLabel = `${connectionLabel}: ${feedbackInfo.label}`
recents.push({
isRecent: true,
value: `${connectionId}:${feedbackId}`,
label: optionLabel,
fuzzy: fuzzyPrepare(optionLabel),
})
}
}

Expand All @@ -86,7 +88,7 @@ export const AddFeedbackDropdown = observer(function AddFeedbackDropdown({
})

return options
}, [feedbackDefinitions, connections, booleanOnly, recentlyAddedFeedbacks.recentIds])
}, [feedbackDefinitions, connections, onlyType, recentlyAddedFeedbacks.recentIds])

const innerChange = useCallback(
(e: AddFeedbackOption | null) => {
Expand Down
Loading

0 comments on commit 261f466

Please sign in to comment.