From 261f466c6532230b640fec2ffa2a3be9fb89e964 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Tue, 17 Dec 2024 21:52:06 +0000 Subject: [PATCH] wip: 'Conditionalise existing feedbacks' feedback --- .../Fragments/FeedbackStyleBuilder.ts | 58 ++++++++ .../Fragments/FragmentFeedbackInstance.ts | 14 +- .../Fragments/FragmentFeedbackList.ts | 62 +++------ .../Controls/Fragments/FragmentFeedbacks.ts | 5 +- companion/lib/Internal/BuildingBlocks.ts | 7 +- webui/src/Buttons/EditButton.tsx | 2 +- webui/src/Controls/AddFeedbackDropdown.tsx | 36 ++--- webui/src/Controls/AddModal.tsx | 16 ++- webui/src/Controls/FeedbackEditor.tsx | 130 +++++++++++------- .../Controls/ControlFeedbacksService.ts | 25 ++-- webui/src/Triggers/EditPanel.tsx | 2 +- 11 files changed, 219 insertions(+), 138 deletions(-) create mode 100644 companion/lib/Controls/Fragments/FeedbackStyleBuilder.ts diff --git a/companion/lib/Controls/Fragments/FeedbackStyleBuilder.ts b/companion/lib/Controls/Fragments/FeedbackStyleBuilder.ts new file mode 100644 index 000000000..bec1a82ab --- /dev/null +++ b/companion/lib/Controls/Fragments/FeedbackStyleBuilder.ts @@ -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 | 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, + }) + } + } + } +} diff --git a/companion/lib/Controls/Fragments/FragmentFeedbackInstance.ts b/companion/lib/Controls/Fragments/FragmentFeedbackInstance.ts index 5d1aa43e8..ab5433920 100644 --- a/companion/lib/Controls/Fragments/FragmentFeedbackInstance.ts +++ b/companion/lib/Controls/Fragments/FragmentFeedbackInstance.ts @@ -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 */ @@ -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_')) { @@ -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 @@ -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 */ diff --git a/companion/lib/Controls/Fragments/FragmentFeedbackList.ts b/companion/lib/Controls/Fragments/FragmentFeedbackList.ts index e76449eae..b021ede61 100644 --- a/companion/lib/Controls/Fragments/FragmentFeedbackList.ts +++ b/companion/lib/Controls/Fragments/FragmentFeedbackList.ts @@ -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 @@ -45,6 +45,13 @@ export class FragmentFeedbackList { this.#onlyType = onlyType } + /** + * Get the feedbacks + */ + getFeedbacks(): FragmentFeedbackInstance[] { + return this.#feedbacks + } + /** * Get all the feedbacks */ @@ -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) @@ -354,14 +363,9 @@ 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) { @@ -369,45 +373,19 @@ export class FragmentFeedbackList { 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 } /** diff --git a/companion/lib/Controls/Fragments/FragmentFeedbacks.ts b/companion/lib/Controls/Fragments/FragmentFeedbacks.ts index 7ca541c11..cb92b200c 100644 --- a/companion/lib/Controls/Fragments/FragmentFeedbacks.ts +++ b/companion/lib/Controls/Fragments/FragmentFeedbacks.ts @@ -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 @@ -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 } /** diff --git a/companion/lib/Internal/BuildingBlocks.ts b/companion/lib/Internal/BuildingBlocks.ts index 317a9c86f..012033533 100644 --- a/companion/lib/Internal/BuildingBlocks.ts +++ b/companion/lib/Internal/BuildingBlocks.ts @@ -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", @@ -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 @@ -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 diff --git a/webui/src/Buttons/EditButton.tsx b/webui/src/Buttons/EditButton.tsx index fc2e9b2a6..4eedf201a 100644 --- a/webui/src/Buttons/EditButton.tsx +++ b/webui/src/Buttons/EditButton.tsx @@ -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" /> diff --git a/webui/src/Controls/AddFeedbackDropdown.tsx b/webui/src/Controls/AddFeedbackDropdown.tsx index 45ef021de..eb526c118 100644 --- a/webui/src/Controls/AddFeedbackDropdown.tsx +++ b/webui/src/Controls/AddFeedbackDropdown.tsx @@ -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) @@ -49,7 +49,7 @@ export const AddFeedbackDropdown = observer(function AddFeedbackDropdown({ const options: Array = [] 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({ @@ -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), + }) } } @@ -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) => { diff --git a/webui/src/Controls/AddModal.tsx b/webui/src/Controls/AddModal.tsx index 4e15b834a..b97ef25d2 100644 --- a/webui/src/Controls/AddModal.tsx +++ b/webui/src/Controls/AddModal.tsx @@ -80,6 +80,7 @@ export const AddActionsModal = observer( key={connectionId} connectionId={connectionId} items={actions} + onlyType={null} itemName="actions" expanded={!!filter || expanded[connectionId]} filter={filter} @@ -100,7 +101,7 @@ export const AddActionsModal = observer( interface AddFeedbacksModalProps { addFeedback: (feedbackType: string) => void - booleanOnly: boolean + onlyType: 'boolean' | 'advanced' | null entityType: string } export interface AddFeedbacksModalRef { @@ -109,7 +110,7 @@ export interface AddFeedbacksModalRef { export const AddFeedbacksModal = observer( forwardRef(function AddFeedbacksModal( - { addFeedback, booleanOnly, entityType }, + { addFeedback, onlyType, entityType }, ref ) { const { feedbackDefinitions, recentlyAddedFeedbacks } = useContext(RootAppStoreContext) @@ -175,7 +176,7 @@ export const AddFeedbacksModal = observer( itemName="feedbacks" expanded={!!filter || expanded[connectionId]} filter={filter} - booleanOnly={booleanOnly} + onlyType={onlyType} doToggle={toggleExpanded} doAdd={addFeedback2} /> @@ -205,7 +206,7 @@ interface ConnectionCollapseProps { itemName: string expanded: boolean filter: string - booleanOnly?: boolean + onlyType: string | null doToggle: (connectionId: string) => void doAdd: (itemId: string) => void } @@ -216,7 +217,7 @@ const ConnectionCollapse = observer(function ConnectionCollapse) { @@ -231,7 +232,8 @@ const ConnectionCollapse = observer(function ConnectionCollapse { - if (!info || !info.label || (booleanOnly && (!('type' in info) || info.type !== 'boolean'))) return null + if (!info || !info.label) return null + if (onlyType && (!('type' in info) || info.type !== onlyType)) return null return { fullId: `${connectionId}:${id}`, @@ -240,7 +242,7 @@ const ConnectionCollapse = observer(function ConnectionCollapse !!v) - }, [items, booleanOnly]) + }, [items, onlyType]) const searchResults = filter ? fuzzySearch(filter, allValues, { diff --git a/webui/src/Controls/FeedbackEditor.tsx b/webui/src/Controls/FeedbackEditor.tsx index 8d7653cbb..3acafb380 100644 --- a/webui/src/Controls/FeedbackEditor.tsx +++ b/webui/src/Controls/FeedbackEditor.tsx @@ -20,7 +20,7 @@ import { AddFeedbacksModal, AddFeedbacksModalRef } from './AddModal.js' import { PanelCollapseHelper, usePanelCollapseHelper } from '../Helpers/CollapseHelper.js' import { OptionButtonPreview } from './OptionButtonPreview.js' import { ButtonStyleProperties } from '@companion-app/shared/Style.js' -import { FeedbackInstance } from '@companion-app/shared/Model/FeedbackModel.js' +import { FeedbackInstance, FeedbackOwner } from '@companion-app/shared/Model/FeedbackModel.js' import { ClientFeedbackDefinition } from '@companion-app/shared/Model/FeedbackDefinitionModel.js' import { DropdownChoiceId } from '@companion-module/base' import { ControlLocation } from '@companion-app/shared/Model/Common.js' @@ -36,12 +36,13 @@ import { observer } from 'mobx-react-lite' import { RootAppStoreContext } from '../Stores/RootAppStore.js' import classNames from 'classnames' import { InlineHelp } from '../Components/InlineHelp.js' +import { isEqual } from 'lodash-es' interface ControlFeedbacksEditorProps { controlId: string feedbacks: FeedbackInstance[] heading: JSX.Element | string entityType: string - booleanOnly: boolean + onlyType: 'boolean' | 'advanced' | null location: ControlLocation | undefined addPlaceholder: string } @@ -53,6 +54,9 @@ function findAllFeedbackIdsDeep(feedbacks: FeedbackInstance[]): string[] { if (feedback.children) { result.push(...findAllFeedbackIdsDeep(feedback.children)) } + if (feedback.advancedChildren) { + result.push(...findAllFeedbackIdsDeep(feedback.advancedChildren)) + } } return result @@ -63,7 +67,7 @@ export function ControlFeedbacksEditor({ feedbacks, heading, entityType, - booleanOnly, + onlyType, location, addPlaceholder, }: ControlFeedbacksEditorProps) { @@ -84,11 +88,11 @@ export function ControlFeedbacksEditor({ heading={heading} feedbacks={feedbacks} entityType={entityType} - booleanOnly={booleanOnly} + onlyType={onlyType} location={location} addPlaceholder={addPlaceholder} feedbacksService={feedbacksService} - parentId={null} + ownerId={null} panelCollapseHelper={panelCollapseHelper} /> @@ -100,11 +104,11 @@ interface InlineFeedbacksEditorProps { heading: JSX.Element | string | null feedbacks: FeedbackInstance[] entityType: string - booleanOnly: boolean + onlyType: 'boolean' | 'advanced' | null location: ControlLocation | undefined addPlaceholder: string feedbacksService: IFeedbackEditorService - parentId: string | null + ownerId: FeedbackOwner | null panelCollapseHelper: PanelCollapseHelper } @@ -113,30 +117,32 @@ const InlineFeedbacksEditor = observer(function InlineFeedbacksEditor({ heading, feedbacks, entityType, - booleanOnly, + onlyType, location, addPlaceholder, feedbacksService, - parentId, + ownerId, panelCollapseHelper, }: InlineFeedbacksEditorProps) { const addFeedbacksRef = useRef(null) const showAddModal = useCallback(() => addFeedbacksRef.current?.show(), []) const addFeedback = useCallback( - (feedbackType: string) => feedbacksService.addFeedback(feedbackType, parentId), - [feedbacksService, parentId] + (feedbackType: string) => feedbacksService.addFeedback(feedbackType, ownerId), + [feedbacksService, ownerId] ) const childFeedbackIds = feedbacks.map((f) => f.id) + const expandGroupId = feedbackOwnerString(ownerId) + return ( <> @@ -147,19 +153,19 @@ const InlineFeedbacksEditor = observer(function InlineFeedbacksEditor({ {feedbacks.length >= 1 && ( - {panelCollapseHelper.canExpandAll(parentId, childFeedbackIds) && ( + {panelCollapseHelper.canExpandAll(expandGroupId, childFeedbackIds) && ( panelCollapseHelper.setAllExpanded(parentId, childFeedbackIds)} + onClick={() => panelCollapseHelper.setAllExpanded(expandGroupId, childFeedbackIds)} title="Expand all feedbacks" > )} - {panelCollapseHelper.canCollapseAll(parentId, childFeedbackIds) && ( + {panelCollapseHelper.canCollapseAll(expandGroupId, childFeedbackIds) && ( panelCollapseHelper.setAllCollapsed(parentId, childFeedbackIds)} + onClick={() => panelCollapseHelper.setAllCollapsed(expandGroupId, childFeedbackIds)} title="Collapse all feedbacks" > @@ -177,22 +183,22 @@ const InlineFeedbacksEditor = observer(function InlineFeedbacksEditor({ ))} - {!!parentId && ( + {!!ownerId && ( @@ -201,7 +207,7 @@ const InlineFeedbacksEditor = observer(function InlineFeedbacksEditor({
- + (null) @@ -263,31 +269,31 @@ function FeedbackTableRow({ // Ensure the hover targets this element, and not a child element if (!monitor.isOver({ shallow: true })) return - const dragParentId = item.parentId + const dragOwnerId = item.ownerId const dragIndex = item.index - const hoverParentId = parentId + const hoverOwnerId = ownerId const hoverIndex = index const hoverId = feedback.id if (!checkDragState(item, monitor, hoverId)) return // Don't replace items with themselves - if (item.feedbackId === hoverId || (dragIndex === hoverIndex && dragParentId === hoverParentId)) { + if (item.feedbackId === hoverId || (dragIndex === hoverIndex && isEqual(dragOwnerId, hoverOwnerId))) { return } // Can't move into itself - if (item.feedbackId === hoverParentId) return + if (hoverOwnerId && item.feedbackId === hoverOwnerId.parentFeedbackId) return // Time to actually perform the action - serviceFactory.moveCard(item.feedbackId, hoverParentId, hoverIndex) + serviceFactory.moveCard(item.feedbackId, hoverOwnerId, hoverIndex) // Note: we're mutating the monitor item here! // Generally it's better to avoid mutations, // but it's good here for the sake of performance // to avoid expensive index searches. item.index = hoverIndex - item.parentId = hoverParentId + item.ownerId = hoverOwnerId }, drop(item, _monitor) { item.dragState = null @@ -298,7 +304,7 @@ function FeedbackTableRow({ item: { feedbackId: feedback.id, index: index, - parentId: parentId, + ownerId: ownerId, dragState: null, }, collect: (monitor) => ({ @@ -320,13 +326,13 @@ function FeedbackTableRow({ @@ -335,24 +341,24 @@ function FeedbackTableRow({ interface FeedbackEditorProps { controlId: string - parentId: string | null + ownerId: FeedbackOwner | null entityType: string feedback: FeedbackInstance location: ControlLocation | undefined serviceFactory: IFeedbackEditorService panelCollapseHelper: PanelCollapseHelper - booleanOnly: boolean + onlyType: 'boolean' | 'advanced' | null } const FeedbackEditor = observer(function FeedbackEditor({ controlId, - parentId, + ownerId, entityType, feedback, location, serviceFactory, panelCollapseHelper, - booleanOnly, + onlyType, }: FeedbackEditorProps) { const service = useControlFeedbackService(serviceFactory, feedback) @@ -390,7 +396,10 @@ const FeedbackEditor = observer(function FeedbackEditor({ () => panelCollapseHelper.setPanelCollapsed(feedback.id, false), [panelCollapseHelper, feedback.id] ) - const isCollapsed = panelCollapseHelper.isPanelCollapsed(parentId, feedback.id) + const isCollapsed = panelCollapseHelper.isPanelCollapsed(feedbackOwnerString(ownerId), feedback.id) + + const childrenGroupId: FeedbackOwner = { parentFeedbackId: feedback.id, childGroup: 'children' } + const advancedChildrenGroupId: FeedbackOwner = { parentFeedbackId: feedback.id, childGroup: 'advancedChildren' } return ( <> @@ -498,17 +507,36 @@ const FeedbackEditor = observer(function FeedbackEditor({ + + {feedbackSpec.supportsAdvancedChildFeedbacks && ( + <> + + + + + )}
)} @@ -548,7 +576,7 @@ const FeedbackEditor = observer(function FeedbackEditor({ )} - {!booleanOnly && ( + {onlyType === null && ( <> void + moveCard: (dragFeedbackId: string, hoverOwnerId: FeedbackOwner | null, hoverIndex: number) => void } -function FeedbackRowDropPlaceholder({ dragId, parentId, feedbackCount, moveCard }: FeedbackRowDropPlaceholderProps) { +function FeedbackRowDropPlaceholder({ dragId, ownerId, feedbackCount, moveCard }: FeedbackRowDropPlaceholderProps) { const [isDragging, drop] = useDrop({ accept: dragId, collect: (monitor) => { @@ -665,9 +693,9 @@ function FeedbackRowDropPlaceholder({ dragId, parentId, feedbackCount, moveCard }, hover(item, _monitor) { // Can't move into itself - if (item.feedbackId === parentId) return + if (isEqual(item.feedbackId, ownerId)) return - moveCard(item.feedbackId, parentId, 0) + moveCard(item.feedbackId, ownerId, 0) }, }) @@ -681,3 +709,7 @@ function FeedbackRowDropPlaceholder({ dragId, parentId, feedbackCount, moveCard ) } + +function feedbackOwnerString(ownerId: FeedbackOwner | null): string | null { + return ownerId ? `${ownerId.parentFeedbackId}_${ownerId.childGroup}` : null +} diff --git a/webui/src/Services/Controls/ControlFeedbacksService.ts b/webui/src/Services/Controls/ControlFeedbacksService.ts index 193273f60..23aa55641 100644 --- a/webui/src/Services/Controls/ControlFeedbacksService.ts +++ b/webui/src/Services/Controls/ControlFeedbacksService.ts @@ -1,11 +1,11 @@ import { useContext, useMemo, useRef } from 'react' import { SocketContext, socketEmitPromise } from '../../util.js' -import { FeedbackInstance } from '@companion-app/shared/Model/FeedbackModel.js' +import { FeedbackInstance, FeedbackOwner } from '@companion-app/shared/Model/FeedbackModel.js' import { GenericConfirmModalRef } from '../../Components/GenericConfirmModal.js' export interface IFeedbackEditorService { - addFeedback: (feedbackType: string, parentId: string | null) => void - moveCard: (dragId: string, hoverParentId: string | null, hoverIndex: number) => void + addFeedback: (feedbackType: string, ownerId: FeedbackOwner | null) => void + moveCard: (dragId: string, hoverOwnerId: FeedbackOwner | null, hoverIndex: number) => void setValue: (feedbackId: string, feedback: FeedbackInstance | undefined, key: string, value: any) => void setConnection: (feedbackId: string, connectionId: string | number) => void @@ -41,22 +41,19 @@ export function useControlFeedbacksEditorService( return useMemo( () => ({ - addFeedback: (feedbackType: string, parentId: string | null) => { + addFeedback: (feedbackType: string, ownerId: FeedbackOwner | null) => { const [connectionId, feedbackId] = feedbackType.split(':', 2) - socketEmitPromise(socket, 'controls:feedback:add', [ - controlId, - parentId ? { parentFeedbackId: parentId, childGroup: 'children' } : null, - connectionId, - feedbackId, - ]).catch((e) => { - console.error('Failed to add control feedback', e) - }) + socketEmitPromise(socket, 'controls:feedback:add', [controlId, ownerId, connectionId, feedbackId]).catch( + (e) => { + console.error('Failed to add control feedback', e) + } + ) }, - moveCard: (dragFeedbackId: string, hoverParentId: string | null, hoverIndex: number) => { + moveCard: (dragFeedbackId: string, hoverOwnerId: FeedbackOwner | null, hoverIndex: number) => { socketEmitPromise(socket, 'controls:feedback:move', [ controlId, dragFeedbackId, - hoverParentId ? { parentFeedbackId: hoverParentId, childGroup: 'children' } : null, + hoverOwnerId, hoverIndex, ]).catch((e) => { console.error(`Move failed: ${e}`) diff --git a/webui/src/Triggers/EditPanel.tsx b/webui/src/Triggers/EditPanel.tsx index 652d004fe..f04366107 100644 --- a/webui/src/Triggers/EditPanel.tsx +++ b/webui/src/Triggers/EditPanel.tsx @@ -142,7 +142,7 @@ export function EditTriggerPanel({ controlId }: EditTriggerPanelProps) { entityType="condition" controlId={controlId} feedbacks={config.condition} - booleanOnly={true} + onlyType={'boolean'} location={undefined} addPlaceholder="+ Add condition" />