bitfocus / companion

Bitfocus Companion enables the reasonably priced Elgato Stream Deck and other controllers to be a professional shotbox surface for an increasing amount of different presentation switchers, video playback software and broadcast equipment.
http://bitfocus.io/companion
Other
1.54k stars 501 forks source link

Actions exposing some boolean properties for feedbacks #1825

Open Julusian opened 2 years ago

Julusian commented 2 years ago

Describe the feature Inspired by a discussion in slack about how to avoid repetition of properties across an action and feedback.

This may fit better (or not) into my pitch for #1187, but for now this shall be written about actions.

When a button contains an action, it is very common for a feedback to be used on the same button with some of the same properties. eg, action will set output 1 to input 1, and feedback turns button red when input 1 is on output 1 To do this today requires the user to find the appropriate feedback and add it to the button. Then remember to update its properties if you change the action properties.

If we had a way of an action exposing some related feedback fragments (needs some unique terminology?), then we could easily define the relationship. It could be tied to the action by id, and as part of this we could hide the 'inherited' properties.

For example, the action which sets output 1 to input 1, could expose some feedbacks fragments:

Implementation notes Data model for this sounds like it will become rather complicated. Handling feedback invalidations will require more jumping around in the code, but maybe the rest would be fairly straightforward?

Propsed action typings:

export interface CompanionAction {
    label: string
    description?: string
    options: SomeCompanionInputField[]
    callback?: (action: CompanionActionEvent, info: CompanionActionEventInfo | null) => void
    subscribe?: (action: CompanionActionEvent) => void
    unsubscribe?: (action: CompanionActionEvent) => void
    /** New property: **/
    feedbackFragments?: CompanionFeedbackFragment[]
}

export interface CompanionFeedbackFragment {
    /** This should be the type of a normal feedback **/
    type: string
    /** Object of which feedback options should be inherited from the action **/
    inheritedOptions: { [feedbackKey: string]: string | undefined }
}

When the action gets removed from the button, the related feedbacks should probably be removed too.

In the ui, we will want to somehow clearly show which feedbacks belong to which actions (it could get confusing if you have two similar actions on the same button), and some nice and easy way to add feedbacks from an action.
The ui should also make sure to hide the input boxes for properties that are inherited.

estilles commented 2 years ago

I love this idea (definitely needs beer ... can we add a "Needs Vodka" tag?). In my head they're called "Action Feedbacks". Like you described, since they're attached to/inherit from actions, they would be deleted along with their related action. I've been wanting to work on something similar, but I can't seem to see the UI.

estilles commented 2 years ago

Ooops.

Julusian commented 2 years ago

yeah I dont have any ideas on showing the relationship in the ui currently. Perhaps it will help if we give each action a user editable 'name' field? But should the linked feedbacks be added from the same add dropdown, another one just above/below, a dropdown shown with each action, or something else?

@estilles If you do want to start working on this, I don't think it will be merged before 2.3 so take your time

hadphild commented 2 years ago

It would be cool to have a Node visual tool like https://github.com/retejs/rete to enable this. So Button Press And or Time Variable.

estilles commented 2 years ago

If you do want to start working on this, I don't think it will be merged before 2.3 so take your time

Gotcha! I have some ideas. I'll definitely start working on it.

It would be cool to have a Node visual tool like https://github.com/retejs/rete to enable this. So Button Press And or Time Variable.

I love Rete.js and I've used it in other projects. Visually it's such a departure from the current UI that I'm not exactly sure how to make it fit so it's visually appealing. It's definitely worth considering. Thanks @hadphild.

hadphild commented 2 years ago

When you are in Buttons then having a Node View to do complex actions would be great. for the right-click action then it could be your Variables list. And just build your boolean tasks. Also, you could easily do feedbacks this way.

estilles commented 2 years ago

When you are in Buttons then having a Node View to do complex actions would be great.

I get that. However, my statement stands. It's such a departure from the current UI that I'm not exactly sure how to make it fit so it's visually appealing.

hadphild commented 2 years ago

Presentation1 How about using the Button Icons as the Nodes

estilles commented 2 years ago

@hadphild If you believe you have a good way to implement this, by all means submit a PR.

hadphild commented 2 years ago

Will do @estilles My ideas came from drinking whiskey not Vodka. Really wish I could program but more of an Idea guy.

estilles commented 2 years ago

Will do @estilles My ideas came from drinking whiskey not Vodka. Really wish I could program but more of an Idea guy.

I guess we need a Needs Whiskey tag too. :-)

hadphild commented 2 years ago

Also, the admins like to keep every feature request in the one place and it's your Idea/feature request as well.

Julusian commented 2 years ago

If the graph is composed of actions and feedbacks, it doesnt currently make sense, as there is currently no relationship between any of them, other than order. But the main aim of this task will add some lines between actions and feedbacks, so a graph may help visualise it. And once we have logical operators in the feedbacks, that would also benefit from being shown in this way

So I think I can see the benefit of a node graph approach. but I do still question whether the page layout gives us enough space to work with this graph, and whether it will be too confusing for new users

krocheck commented 2 years ago

@hadphild I'm honestly not following the node graph concept. But its the morning and I've barely had my coffee, so I'm not at your level. I'll revisit later when I've had some whiskey and a 2nd look :)

@Julusian a "quick" way would be to watch the action being added to the bank and do a popup like: "This action has associated feedback(s). Would you like to add and link them?" [if there's multiple maybe a checkbox listing] In the feedback we could have a parent_action field that stores the [short]id of the action. That field having a value would grey out / disable (instead of hiding) the fields in the feedback and have a message saying its linked to a parent action. This way it appears in the table and can be ordered with other feedbacks as desired (and can be deleted). Then we 'just' need to have add some hooks for update and delete of the parent action to propagate down to the feedback.

I see your point about having a "name" field, and there have been many requests for a "note" field for banks and actions over time, so could kill 2 birds with 1 stone.

Here's an action definition example from shure-scm820. Admittedly it may not show this well because in that module all the action and feedback keys are the same (when associated) and likewise the options use common names, but it shows a way the developers could activate this functionality.

actions['always_on_enable'] = {
    label: 'Set channel always on in mix',
    options: [this.MIXER_FIELD, this.CHANNELS_FIELD('I'), this.ONOFFTOGGLE_FIELD],
    callback: ({ options }) => {
        let cmd = `SET ${options.channel} ALWAYS_ON_ENABLE_${options.mix} ${options.choice}`
        this.sendCommand(cmd)
    },
    child_feedback: {
        'always_on_enable': { 
            [feedback_field]: [action_field] (these could be reversed)
            'mix': 'mix',
            'channel': 'channel',
        },
    },
}

It seems like we're on almost the same page with the data structure.

Although I've had a 2nd thought that maybe those could/should be callbacks for some added flexibility:

    child_feedback: {
        'always_on_enable': { 
            [feedback_field]: (action options) => { return value }
            'mix': (options) => { return options.mix },
            'channel': (options) => { return options.channel },
        },
    },

This provide quick and set processing for propagating the action values down.