From c67c3b6147ae20dba2ea20197c8b53add9b3b638 Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 8 Apr 2024 21:15:01 +0100 Subject: [PATCH 1/2] wip: prototype typings --- package.json | 3 +- src/internal/strict-options.ts | 72 +++++++++++++++++++++++++ src/module-api/action.ts | 60 ++++++++++++++++++++- src/module-api/common.ts | 21 ++++++++ src/module-api/feedback.ts | 96 +++++++++++++++++++++++++++++++++- yarn.lock | 5 ++ 6 files changed, 254 insertions(+), 3 deletions(-) create mode 100644 src/internal/strict-options.ts diff --git a/package.json b/package.json index d480134..2e42937 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,8 @@ "nanoid": "^3.3.4", "p-queue": "^6.6.2", "p-timeout": "^4.1.0", - "tslib": "^2.6.2" + "tslib": "^2.6.2", + "type-fest": "^4.15.0" }, "devDependencies": { "@companion-module/tools": "^1.5.0", diff --git a/src/internal/strict-options.ts b/src/internal/strict-options.ts new file mode 100644 index 0000000..8727c4e --- /dev/null +++ b/src/internal/strict-options.ts @@ -0,0 +1,72 @@ +import type { ConditionalKeys } from 'type-fest' +import type { CompanionOptionValues, CompanionActionContext } from '../module-api/index.js' +import type { CompanionCommonCallbackContext, StrictOptions, StrictOptionsObject } from '../module-api/common.js' + +export class StrictOptionsImpl implements StrictOptions { + readonly #options: any + readonly #context: CompanionCommonCallbackContext + readonly #fields: StrictOptionsObject + + constructor( + options: CompanionOptionValues, + context: CompanionActionContext, + fields: StrictOptionsObject + ) { + this.#options = options + this.#context = context + this.#fields = fields + } + + getRawJson(): any { + return { ...this.#options } + } + getRaw(fieldName: Key): any { + // TODO - should this populate defaults? + return this.#options[fieldName] + } + + getPlainString>(fieldName: Key): TOptions[Key] { + const fieldSpec = this.#fields[fieldName] + const defaultValue = fieldSpec && 'default' in fieldSpec ? fieldSpec.default : undefined + + const rawValue = this.#options[fieldName] + if (defaultValue !== undefined && rawValue === undefined) return String(defaultValue) as any + + return String(rawValue) as any + } + + getPlainNumber>(fieldName: Key): TOptions[Key] { + const fieldSpec = this.#fields[fieldName] + const defaultValue = fieldSpec && 'default' in fieldSpec ? fieldSpec.default : undefined + + const rawValue = this.#options[fieldName] + if (defaultValue !== undefined && rawValue === undefined) return Number(defaultValue) as any + + const value = Number(rawValue) + if (isNaN(value)) { + throw new Error(`Invalid option '${String(fieldName)}'`) + } + return value as any + } + + getPlainBoolean>(fieldName: Key): boolean { + const fieldSpec = this.#fields[fieldName] + const defaultValue = fieldSpec && 'default' in fieldSpec ? fieldSpec.default : undefined + + const rawValue = this.#options[fieldName] + if (defaultValue !== undefined && rawValue === undefined) return Boolean(defaultValue) + + return Boolean(rawValue) + } + + async getParsedString>(fieldName: Key): Promise { + const rawValue = this.#options[fieldName] + + return this.#context.parseVariablesInString(rawValue) + } + async getParsedNumber>(fieldName: Key): Promise { + const str = await this.getParsedString(fieldName) + + return Number(str) + } +} diff --git a/src/module-api/action.ts b/src/module-api/action.ts index 0a9a0d0..d4c9ba8 100644 --- a/src/module-api/action.ts +++ b/src/module-api/action.ts @@ -1,4 +1,4 @@ -import type { CompanionCommonCallbackContext } from './common.js' +import type { CompanionCommonCallbackContext, StrictOptions, StrictOptionsObject } from './common.js' import type { CompanionOptionValues, CompanionInputFieldCheckbox, @@ -30,6 +30,8 @@ export type CompanionActionContext = CompanionCommonCallbackContext * The definition of an action */ export interface CompanionActionDefinition { + type?: 'loose' + /** Name to show in the actions list */ name: string /** Additional description of the action */ @@ -93,3 +95,59 @@ export interface CompanionActionEvent extends CompanionActionInfo { /** Identifier of the surface which triggered this action */ readonly surfaceId: string | undefined } + +/** + * Basic information about an instance of an action + */ +export interface StrictActionInfo { + /** The unique id for this action */ + readonly id: string + /** The unique id for the location of this action */ + readonly controlId: string + /** The id of the action definition */ + readonly actionId: string + /** The user selected options for the action */ + readonly options: StrictOptions +} +/** + * Extended information for execution of an action + */ +export interface StrictActionEvent extends StrictActionInfo { + /** Identifier of the surface which triggered this action */ + readonly surfaceId: string | undefined +} + +export interface StrictActionDefinition { + type: 'strict' + + /** Name to show in the actions list */ + name: string + /** Additional description of the action */ + description?: string + /** The input fields for the action */ + options: StrictOptionsObject + + /** Called to execute the action */ + callback: (action: StrictActionEvent, context: CompanionActionContext) => Promise | void + /** + * Called to report the existence of an action + * Useful to ensure necessary data is loaded + */ + subscribe?: (action: StrictActionInfo, context: CompanionActionContext) => Promise | void + /** + * Called to report an action has been edited/removed + * Useful to cleanup subscriptions setup in subscribe + */ + unsubscribe?: (action: StrictActionInfo, context: CompanionActionContext) => Promise | void + /** + * The user requested to 'learn' the values for this action. + */ + learn?: ( + action: StrictActionEvent, + context: CompanionActionContext + ) => TOptions | undefined | Promise +} + +export type StrictActionDefinitions = { + [Key in keyof TTypes]: StrictActionDefinition | undefined +} diff --git a/src/module-api/common.ts b/src/module-api/common.ts index 65cd97e..2c7caee 100644 --- a/src/module-api/common.ts +++ b/src/module-api/common.ts @@ -1,3 +1,6 @@ +import type { ConditionalKeys } from 'type-fest' +import type { CompanionInputFieldBase } from './input.js' + /** * Utility functions available in the context of an action/feedback */ @@ -10,3 +13,21 @@ export interface CompanionCommonCallbackContext { */ parseVariablesInString(text: string): Promise } + +export type StrictOptionsObject = { + [K in keyof TOptions]: undefined extends TOptions[K] ? TFields | undefined : TFields +} + +/** + * + */ +export interface StrictOptions { + getRawJson(): any + getRaw(fieldName: Key): TOptions[Key] | undefined + getPlainString>(fieldName: Key): TOptions[Key] + getPlainNumber>(fieldName: Key): TOptions[Key] + getPlainBoolean>(fieldName: Key): boolean + + getParsedString>(fieldName: Key): Promise + getParsedNumber>(fieldName: Key): Promise +} diff --git a/src/module-api/feedback.ts b/src/module-api/feedback.ts index 2fa2312..8567094 100644 --- a/src/module-api/feedback.ts +++ b/src/module-api/feedback.ts @@ -1,4 +1,4 @@ -import type { CompanionCommonCallbackContext } from './common.js' +import type { CompanionCommonCallbackContext, StrictOptions, StrictOptionsObject } from './common.js' import type { CompanionOptionValues, CompanionInputFieldStaticText, @@ -161,3 +161,97 @@ export type CompanionFeedbackDefinition = CompanionBooleanFeedbackDefinition | C export interface CompanionFeedbackDefinitions { [id: string]: CompanionFeedbackDefinition | undefined } + +/** + * Basic information about an instance of an Feedback + */ +export interface StrictFeedbackInfo { + /** The type of the feedback */ + readonly type: 'boolean-strict' | 'advanced-strict' + /** The unique id for this feedback */ + readonly id: string + /** The unique id for the location of this feedback */ + readonly controlId: string + /** The id of the feedback definition */ + readonly feedbackId: string + /** The user selected options for the feedback */ + readonly options: StrictOptions +} +/** + * Extended information for execution of an Feedback + */ +export type StrictBooleanFeedbackEvent = StrictFeedbackInfo + +/** + * Extended information for execution of an advanced feedback + */ +export interface StrictAdvancedFeedbackEvent extends StrictFeedbackInfo { + /** If control supports an imageBuffer, the dimensions the buffer should be */ + readonly image?: { + readonly width: number + readonly height: number + } +} + +export interface StrictFeedbackDefinitionBase { + /** Name to show in the Feedbacks list */ + name: string + /** Additional description of the Feedback */ + description?: string + /** The input fields for the Feedback */ + options: StrictOptionsObject + + /** + * Called to report the existence of an Feedback + * Useful to ensure necessary data is loaded + */ + subscribe?: (Feedback: StrictFeedbackInfo, context: CompanionFeedbackContext) => Promise | void + /** + * Called to report an Feedback has been edited/removed + * Useful to cleanup subscriptions setup in subscribe + */ + unsubscribe?: (Feedback: StrictFeedbackInfo, context: CompanionFeedbackContext) => Promise | void + /** + * The user requested to 'learn' the values for this Feedback. + */ + learn?: ( + Feedback: StrictFeedbackInfo, + context: CompanionFeedbackContext + ) => TOptions | undefined | Promise +} + +export interface StrictBooleanFeedbackDefinition extends StrictFeedbackDefinitionBase { + type: 'boolean-strict' + + /** The default style properties for this feedback */ + defaultStyle: Partial + + /** + * If `undefined` or true, Companion will add an 'Inverted' checkbox for your feedback, and handle the logic for you. + * By setting this to false, you can disable this for your feedback. You should do this if it does not make sense for your feedback. + */ + showInvert?: boolean + + /** Called to execute the Feedback */ + callback: ( + Feedback: StrictBooleanFeedbackEvent, + context: CompanionFeedbackContext + ) => Promise | boolean +} +export interface StrictAdvancedFeedbackDefinition extends StrictFeedbackDefinitionBase { + type: 'advanced-strict' + + /** Called to execute the Feedback */ + callback: ( + Feedback: StrictAdvancedFeedbackEvent, + context: CompanionFeedbackContext + ) => Promise | CompanionAdvancedFeedbackResult +} + +export declare type StrictFeedbackDefinition = + | StrictBooleanFeedbackDefinition + | StrictAdvancedFeedbackDefinition + +export type StrictFeedbackDefinitions = { + [Key in keyof TTypes]: StrictFeedbackDefinition | undefined +} diff --git a/yarn.lock b/yarn.lock index 7253957..5bc603c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3932,6 +3932,11 @@ type-fest@^0.21.3: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== +type-fest@^4.15.0: + version "4.15.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-4.15.0.tgz#21da206b89c15774cc718c4f2d693e13a1a14a43" + integrity sha512-tB9lu0pQpX5KJq54g+oHOLumOx+pMep4RaM6liXh2PKmVRFF+/vAtUP0ZaJ0kOySfVNjF6doBWPHhBhISKdlIA== + type@^2.7.2: version "2.7.2" resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0" From 41288d91a6b2cf8b1c086fab86802275661f1d0a Mon Sep 17 00:00:00 2001 From: Julian Waller Date: Mon, 8 Apr 2024 21:27:51 +0100 Subject: [PATCH 2/2] wip: add preset typings --- src/module-api/preset.ts | 71 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/src/module-api/preset.ts b/src/module-api/preset.ts index 2fff663..b48458d 100644 --- a/src/module-api/preset.ts +++ b/src/module-api/preset.ts @@ -90,3 +90,74 @@ export interface CompanionButtonStepActions { export interface CompanionPresetDefinitions { [id: string]: CompanionButtonPresetDefinition | undefined } + +export type StrictPresetDefinitions = StrictPresetDefinitionCategory[] +export type StrictPresetDefinitionCategory = { + name: string + presets: Record> +} + +export interface StrictButtonPresetDefinition { + /** The type of this preset */ + type: 'button-strict' + /** The category of this preset, for grouping */ + // category: string + /** The name of this preset */ + name: string + /** The base style of this preset, this will be copied to the button */ + style: CompanionButtonStyleProps + /** Preview style for preset, will be used in GUI for preview */ + previewStyle?: CompanionButtonStyleProps + /** Options for this preset */ + options?: CompanionButtonPresetOptions + /** The feedbacks on the button */ + feedbacks: StrictPresetFeedback[] + steps: StrictButtonStepActions[] +} + +type StrictPresetFeedbackInner = Id extends any + ? { + /** The id of the feedback definition */ + feedbackId: Id + /** The option values for the feedback */ + options: TTypes[Id] + /** + * If a boolean feedback, the style effect of the feedback + */ + style?: CompanionFeedbackButtonStyleResult + /** + * If a boolean feedback, invert the value of the feedback + */ + isInverted?: boolean + } + : never + +export type StrictPresetFeedback = StrictPresetFeedbackInner + +type StrictPresetActionInner = Id extends any + ? { + /** The id of the action definition */ + actionId: Id + /** The option values for the action */ + options: TTypes[Id] + /** The execution delay of the action */ + delay?: number + } + : never + +export type StrictPresetAction = StrictPresetActionInner + +export interface StrictButtonStepActions { + /** The button down actions */ + down: StrictPresetAction[] + /** The button up actions */ + up: StrictPresetAction[] + rotateLeft?: StrictPresetAction[] + rotateRight?: StrictPresetAction[] + [delay: number]: StrictPresetActionsWithOptions | StrictPresetAction[] +} + +export interface StrictPresetActionsWithOptions { + options?: CompanionActionSetOptions + actions: StrictPresetAction[] +}