From 7678954aab3f34e9f5c9902b0920cfe99ba50e94 Mon Sep 17 00:00:00 2001 From: Teodor Morfeldt Gadler Date: Mon, 4 Mar 2024 09:43:21 +0100 Subject: [PATCH 1/3] wip --- companion/lib/Instance/Variable.js | 60 +++++++++++++++++++++++ shared-lib/lib/SocketIO.ts | 25 +++++----- webui/src/Buttons/CustomVariablesList.tsx | 53 +++++++++++++++++++- 3 files changed, 125 insertions(+), 13 deletions(-) diff --git a/companion/lib/Instance/Variable.js b/companion/lib/Instance/Variable.js index a4305edcd..5e2246e4e 100644 --- a/companion/lib/Instance/Variable.js +++ b/companion/lib/Instance/Variable.js @@ -22,6 +22,7 @@ import jsonPatch from 'fast-json-patch' import { ResolveExpression } from '@companion-app/shared/Expression/ExpressionResolve.js' import { ParseExpression } from '@companion-app/shared/Expression/ExpressionParse.js' import { ExpressionFunctions } from '@companion-app/shared/Expression/ExpressionFunctions.js' +import { ParseControlId } from '@companion-app/shared/ControlId.js' const logger = LogController.createLogger('Instance/Variable') @@ -117,6 +118,8 @@ class InstanceVariable extends CoreBase { /** * @param {import('../Registry.js').default} registry */ + + constructor(registry) { super(registry, 'Instance/Variable') @@ -313,6 +316,63 @@ class InstanceVariable extends CoreBase { client.onPromise('variables:instance-values', (label) => { return this.#variableValues[label] }) + + client.onPromise('variables:get-instances', (name) => { + const result = new Set() + + // name -> style + // feedback -> feedbacks + // action -> steps + + for (let [controlId, control] of this.registry.controls.getAllControls()) { + const parsedControl = control.toJSON(); + + if (typeof parsedControl != "string") { + for (const propName in parsedControl) { + if (JSON.stringify(parsedControl[propName]).includes(name)) { + result.add({ "button": controlId, "property": propName }) + } + } + } + } + + return Array.from(result) + }) + } + + /** + * @param {any} obj + * @param {string} str + */ + objectContainsString(obj, str) { + if (typeof obj !== 'object' || obj === null) { + return false; + } + + const seenObjects = new Set(); // To handle circular references + + /** + * @param {any} obj + */ + function checkObject(obj) { + if (seenObjects.has(obj)) { + return false; // Circular reference detected + } + + seenObjects.add(obj); + + for (const key in obj) { + if (typeof obj[key] === 'string' && obj[key].includes(str)) { + return true; + } else if (typeof obj[key] === 'object' && checkObject(obj[key])) { + return true; + } + } + + return false; + } + + return checkObject(obj); } /** diff --git a/shared-lib/lib/SocketIO.ts b/shared-lib/lib/SocketIO.ts index 33b6119c8..170138d98 100644 --- a/shared-lib/lib/SocketIO.ts +++ b/shared-lib/lib/SocketIO.ts @@ -267,6 +267,7 @@ export interface ClientToBackendEventsMap { 'connections:get-help': (id: string) => [err: string, result: null] | [err: null, result: HelpDescription] 'variables:instance-values': (label: string) => CompanionVariableValues | undefined + 'variables:get-instances': (name: string) => { buttonName: string; usageType: string }[] 'presets:subscribe': () => Record | undefined> 'presets:unsubscribe': () => void @@ -336,10 +337,10 @@ type ChangeSignatureToHaveCallback any> = ( export type AddCallbackParamToEvents = { [K in keyof T]: T[K] extends (...args: any[]) => any - ? ReturnType extends never - ? T[K] - : ChangeSignatureToHaveCallback - : never + ? ReturnType extends never + ? T[K] + : ChangeSignatureToHaveCallback + : never } export type StripNever = { @@ -348,8 +349,8 @@ export type StripNever = { export type ClientToBackendEventsWithNoResponse = { [K in keyof ClientToBackendEventsListenMap as ReturnType extends void - ? K - : never]: true + ? K + : never]: true } // { // [K in keyof ClientToBackendEventsMap as ClientToBackendEventsMap[K] extends (...args: any[]) => never ? never : K]: ( @@ -359,8 +360,8 @@ export type ClientToBackendEventsWithNoResponse = { export type ClientToBackendEventsWithPromiseResponse = { [K in keyof ClientToBackendEventsListenMap as ReturnType extends void - ? never - : K]: true + ? never + : K]: true } // StripNever<{ // [K in keyof ClientToBackendEventsMap]: ClientToBackendEventsMap[K] extends (...args: any[]) => any @@ -372,8 +373,8 @@ export type ClientToBackendEventsWithPromiseResponse = { export type ClientToBackendEventsListenMap = { [K in keyof ClientToBackendEventsMap]: ClientToBackendEventsMap[K] extends (...args: any[]) => never - ? (...args: Parameters) => void - : ( - ...args: Parameters - ) => Promise> | ReturnType + ? (...args: Parameters) => void + : ( + ...args: Parameters + ) => Promise> | ReturnType } diff --git a/webui/src/Buttons/CustomVariablesList.tsx b/webui/src/Buttons/CustomVariablesList.tsx index 74e17e9c2..a02718a28 100644 --- a/webui/src/Buttons/CustomVariablesList.tsx +++ b/webui/src/Buttons/CustomVariablesList.tsx @@ -76,6 +76,24 @@ export function CustomVariablesList({ setShowCustom }: CustomVariablesListProps) const [newName, setNewName] = useState('') + const getInstances = useCallback( + (variableName: string) => { + console.log("Running getInstances in CustomVariableList.tsx!"); + + return socketEmitPromise(socket, 'variables:get-instances', [variableName]) + .then((res) => { + console.log(res) + }) + .catch((e) => { + console.log("Error", e); + console.log("Error", e.name); + console.log("Error", e.message); + console.error('Failed to retrieve instances'); + }); + }, + [socket] + ); + const doCreateNew = useCallback( (e: FormEvent) => { e?.preventDefault() @@ -211,13 +229,30 @@ export function CustomVariablesList({ setShowCustom }: CustomVariablesListProps) } }, [variableDefinitions, filter]) + const [usageData, setUsageData] = useState>({}); + + useEffect(() => { + const fetchUsageData = async () => { + const data: Record = {}; + + for (const name of allVariableNames) { + const instances = await getInstances(name); + data[name] = instances; + } + + setUsageData(data); + }; + + fetchUsageData(); + }, [allVariableNames]); + return (
Custom Variables {!hasNoVariables && canExpandAll && ( - + )} @@ -229,6 +264,10 @@ export function CustomVariablesList({ setShowCustom }: CustomVariablesListProps) Back + + + Get Instances +
@@ -283,6 +322,7 @@ export function CustomVariablesList({ setShowCustom }: CustomVariablesListProps) moveRow={moveRow} setCollapsed={setPanelCollapsed} isCollapsed={isPanelCollapsed(info.name)} + usage={usageData[info.name]} /> ) })} @@ -334,6 +374,7 @@ interface CustomVariableRowProps { moveRow: (itemName: string, targetName: string) => void isCollapsed: boolean setCollapsed: (name: string, collapsed: boolean) => void + usage: Promise<{ buttonName: string; usageType: string }[]> } function CustomVariableRow({ @@ -350,6 +391,7 @@ function CustomVariableRow({ moveRow, isCollapsed, setCollapsed, + usage }: CustomVariableRowProps) { const fullname = `internal:${shortname}` @@ -455,6 +497,15 @@ function CustomVariableRow({
+ +
+ {Array.from(usage).map((item: { buttonName: string; usageType: string }, index: number) => ( +
+

Button Name: {item.buttonName}

+

Usage Type: {item.usageType}

+
+ ))} +
)} From a118e5d09381dc07a68e17396404d59d81e67f88 Mon Sep 17 00:00:00 2001 From: Teodor Morfeldt Gadler Date: Mon, 4 Mar 2024 14:12:53 +0100 Subject: [PATCH 2/3] feat: issue 2581 implemented for custom variables --- companion/lib/Instance/Variable.js | 14 +++- webui/src/Buttons/CustomVariablesList.tsx | 84 +++++++++++------------ 2 files changed, 51 insertions(+), 47 deletions(-) diff --git a/companion/lib/Instance/Variable.js b/companion/lib/Instance/Variable.js index 5e2246e4e..184367599 100644 --- a/companion/lib/Instance/Variable.js +++ b/companion/lib/Instance/Variable.js @@ -22,7 +22,6 @@ import jsonPatch from 'fast-json-patch' import { ResolveExpression } from '@companion-app/shared/Expression/ExpressionResolve.js' import { ParseExpression } from '@companion-app/shared/Expression/ExpressionParse.js' import { ExpressionFunctions } from '@companion-app/shared/Expression/ExpressionFunctions.js' -import { ParseControlId } from '@companion-app/shared/ControlId.js' const logger = LogController.createLogger('Instance/Variable') @@ -320,6 +319,11 @@ class InstanceVariable extends CoreBase { client.onPromise('variables:get-instances', (name) => { const result = new Set() + const usageTypes = new Map([ + ["style", "name"], + ["feedbacks", "feedbacks"], + ["steps", "action"] + ]); // name -> style // feedback -> feedbacks // action -> steps @@ -330,7 +334,13 @@ class InstanceVariable extends CoreBase { if (typeof parsedControl != "string") { for (const propName in parsedControl) { if (JSON.stringify(parsedControl[propName]).includes(name)) { - result.add({ "button": controlId, "property": propName }) + const location = this.registry.page.getLocationOfControlId(controlId) + const formattedLocation = location?.pageNumber + "/" + location?.row + "/" + location?.column + const formattedUsageType = usageTypes.get(propName) == undefined ? "unknown" : usageTypes.get(propName) + + result.add({ + "buttonName": formattedLocation, "usageType": formattedUsageType + }) } } } diff --git a/webui/src/Buttons/CustomVariablesList.tsx b/webui/src/Buttons/CustomVariablesList.tsx index a02718a28..056d70741 100644 --- a/webui/src/Buttons/CustomVariablesList.tsx +++ b/webui/src/Buttons/CustomVariablesList.tsx @@ -76,23 +76,35 @@ export function CustomVariablesList({ setShowCustom }: CustomVariablesListProps) const [newName, setNewName] = useState('') - const getInstances = useCallback( - (variableName: string) => { - console.log("Running getInstances in CustomVariableList.tsx!"); + const [variableUsage, setVariableUsage] = useState>(new Map()); - return socketEmitPromise(socket, 'variables:get-instances', [variableName]) + useEffect(() => { + const doPoll = (variableName: string) => { + return socketEmitPromise(socket, 'variables:get-instances', ["$(internal:custom_" + variableName + ")"]) .then((res) => { - console.log(res) + setVariableUsage((prevMap) => { + return new Map(prevMap.set(variableName, res)); + }); }) .catch((e) => { - console.log("Error", e); - console.log("Error", e.name); - console.log("Error", e.message); console.error('Failed to retrieve instances'); }); - }, - [socket] - ); + } + + + const doPollAll = () => { + for (let name in allVariableNames) { + doPoll(allVariableNames[name]) + } + } + + doPollAll() + const interval = setInterval(doPollAll, 1000) + + return () => { + clearInterval(interval) + } + }, [socket]) const doCreateNew = useCallback( (e: FormEvent) => { @@ -229,30 +241,13 @@ export function CustomVariablesList({ setShowCustom }: CustomVariablesListProps) } }, [variableDefinitions, filter]) - const [usageData, setUsageData] = useState>({}); - - useEffect(() => { - const fetchUsageData = async () => { - const data: Record = {}; - - for (const name of allVariableNames) { - const instances = await getInstances(name); - data[name] = instances; - } - - setUsageData(data); - }; - - fetchUsageData(); - }, [allVariableNames]); - return (
Custom Variables {!hasNoVariables && canExpandAll && ( - + )} @@ -264,10 +259,6 @@ export function CustomVariablesList({ setShowCustom }: CustomVariablesListProps) Back - - - Get Instances -
@@ -305,7 +296,6 @@ export function CustomVariablesList({ setShowCustom }: CustomVariablesListProps) {candidates && candidates.map((info, index) => { const shortname = `custom_${info.name}` - return ( ) })} @@ -374,7 +364,7 @@ interface CustomVariableRowProps { moveRow: (itemName: string, targetName: string) => void isCollapsed: boolean setCollapsed: (name: string, collapsed: boolean) => void - usage: Promise<{ buttonName: string; usageType: string }[]> + usage: { buttonName: string; usageType: string }[] | undefined } function CustomVariableRow({ @@ -497,18 +487,22 @@ function CustomVariableRow({
- -
- {Array.from(usage).map((item: { buttonName: string; usageType: string }, index: number) => ( -
-

Button Name: {item.buttonName}

-

Usage Type: {item.usageType}

-
- ))} -
)} + {!isCollapsed && ( + <> +
+ {usage !== undefined && ( + Array.from(usage).map((item: { buttonName: string; usageType: string }, index: number) => ( +
+

Used by {item.buttonName} for {item.usageType}

+
+ )) + )} +
+ + )} ) From 79cc4e5bb7edad7e78b32fa9af0548be8e6285ee Mon Sep 17 00:00:00 2001 From: Teodor Morfeldt Gadler Date: Mon, 4 Mar 2024 15:02:44 +0100 Subject: [PATCH 3/3] chore: Clean up patch --- shared-lib/lib/SocketIO.ts | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/shared-lib/lib/SocketIO.ts b/shared-lib/lib/SocketIO.ts index 170138d98..3e4bfc5c4 100644 --- a/shared-lib/lib/SocketIO.ts +++ b/shared-lib/lib/SocketIO.ts @@ -269,6 +269,7 @@ export interface ClientToBackendEventsMap { 'variables:instance-values': (label: string) => CompanionVariableValues | undefined 'variables:get-instances': (name: string) => { buttonName: string; usageType: string }[] + 'presets:subscribe': () => Record | undefined> 'presets:unsubscribe': () => void 'presets:preview_render': (connectionId: string, presetId: string) => string | null @@ -337,10 +338,10 @@ type ChangeSignatureToHaveCallback any> = ( export type AddCallbackParamToEvents = { [K in keyof T]: T[K] extends (...args: any[]) => any - ? ReturnType extends never - ? T[K] - : ChangeSignatureToHaveCallback - : never + ? ReturnType extends never + ? T[K] + : ChangeSignatureToHaveCallback + : never } export type StripNever = { @@ -349,8 +350,8 @@ export type StripNever = { export type ClientToBackendEventsWithNoResponse = { [K in keyof ClientToBackendEventsListenMap as ReturnType extends void - ? K - : never]: true + ? K + : never]: true } // { // [K in keyof ClientToBackendEventsMap as ClientToBackendEventsMap[K] extends (...args: any[]) => never ? never : K]: ( @@ -360,8 +361,8 @@ export type ClientToBackendEventsWithNoResponse = { export type ClientToBackendEventsWithPromiseResponse = { [K in keyof ClientToBackendEventsListenMap as ReturnType extends void - ? never - : K]: true + ? never + : K]: true } // StripNever<{ // [K in keyof ClientToBackendEventsMap]: ClientToBackendEventsMap[K] extends (...args: any[]) => any @@ -373,8 +374,8 @@ export type ClientToBackendEventsWithPromiseResponse = { export type ClientToBackendEventsListenMap = { [K in keyof ClientToBackendEventsMap]: ClientToBackendEventsMap[K] extends (...args: any[]) => never - ? (...args: Parameters) => void - : ( - ...args: Parameters - ) => Promise> | ReturnType -} + ? (...args: Parameters) => void + : ( + ...args: Parameters + ) => Promise> | ReturnType +} \ No newline at end of file