Skip to content

Commit

Permalink
Add InputIOPromise and DisplayIOPromise classes to partition behavior
Browse files Browse the repository at this point in the history
DisplayIOPromises can't be optional or validated.

There are still some internal methods that are exposed via TypeScript
that we need to override externally somehow. I know that's possible but
I don't know how to do it at the moment.
  • Loading branch information
jacobmischka committed Jul 15, 2022
1 parent f6b6dfb commit 3b6d9bf
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 78 deletions.
160 changes: 107 additions & 53 deletions src/classes/IOClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@ import type {
T_IO_PROPS,
T_IO_RETURNS,
T_IO_METHOD_NAMES,
T_IO_DISPLAY_METHOD_NAMES,
T_IO_INPUT_METHOD_NAMES,
} from '../ioSchema'
import Logger from './Logger'
import { AnyIOComponent } from './IOComponent'
import {
IOPromise,
ExclusiveIOPromise,
IOGroupPromise,
IOPromiseValidator,
DisplayIOPromise,
InputIOPromise,
} from './IOPromise'
import IOError from './IOError'
import spreadsheet from '../components/spreadsheet'
Expand All @@ -28,12 +31,14 @@ import {
IORenderSender,
ResponseHandlerFn,
MaybeOptionalGroupIOPromise,
IOComponentFunction,
ExclusiveIOComponentFunction,
ComponentRenderer,
IOComponentDefinition,
RequiredPropsIOComponentFunction,
RequiredPropsExclusiveIOComponentFunction,
DisplayIOComponentFunction,
RequiredPropsDisplayIOComponentFunction,
InputIOComponentFunction,
RequiredPropsInputIOComponentFunction,
} from '../types'
import { stripUndefined } from '../utils/deserialize'
import { IntervalError } from '..'
Expand Down Expand Up @@ -265,34 +270,62 @@ export class IOClient {
}

createIOMethod<
MethodName extends T_IO_METHOD_NAMES,
MethodName extends T_IO_DISPLAY_METHOD_NAMES,
Props extends object = T_IO_PROPS<MethodName>,
Output = T_IO_RETURNS<MethodName>
>(
methodName: MethodName,
propsRequired?: false,
componentDef?: IOComponentDefinition<MethodName, Props, Output>
): IOComponentFunction<MethodName, Props, Output>
config?: {
propsRequired?: false
componentDef?: IOComponentDefinition<MethodName, Props, Output>
}
): DisplayIOComponentFunction<MethodName, Props, Output>
createIOMethod<
MethodName extends T_IO_METHOD_NAMES,
MethodName extends T_IO_DISPLAY_METHOD_NAMES,
Props extends object = T_IO_PROPS<MethodName>,
Output = T_IO_RETURNS<MethodName>
>(
methodName: MethodName,
propsRequired?: true,
componentDef?: IOComponentDefinition<MethodName, Props, Output>
): RequiredPropsIOComponentFunction<MethodName, Props, Output>
config: {
propsRequired?: true
componentDef?: IOComponentDefinition<MethodName, Props, Output>
}
): RequiredPropsDisplayIOComponentFunction<MethodName, Props, Output>
createIOMethod<
MethodName extends T_IO_INPUT_METHOD_NAMES,
Props extends object = T_IO_PROPS<MethodName>,
Output = T_IO_RETURNS<MethodName>
>(
methodName: MethodName,
config?: {
propsRequired?: false
componentDef?: IOComponentDefinition<MethodName, Props, Output>
}
): InputIOComponentFunction<MethodName, Props, Output>
createIOMethod<
MethodName extends T_IO_INPUT_METHOD_NAMES,
Props extends object = T_IO_PROPS<MethodName>,
Output = T_IO_RETURNS<MethodName>
>(
methodName: MethodName,
config: {
propsRequired?: true
componentDef?: IOComponentDefinition<MethodName, Props, Output>
}
): RequiredPropsInputIOComponentFunction<MethodName, Props, Output>
createIOMethod<
MethodName extends T_IO_METHOD_NAMES,
Props extends object = T_IO_PROPS<MethodName>,
Output = T_IO_RETURNS<MethodName>
>(
methodName: MethodName,
_propsRequired = false,
componentDef?: IOComponentDefinition<MethodName, Props, Output>
):
| IOComponentFunction<MethodName, Props, Output>
| RequiredPropsIOComponentFunction<MethodName, Props, Output> {
{
componentDef,
}: {
propsRequired?: boolean
componentDef?: IOComponentDefinition<MethodName, Props, Output>
} = {}
) {
return (label: string, props?: Props) => {
let internalProps = props ? (props as T_IO_PROPS<MethodName>) : {}
let getValue = (r: T_IO_RETURNS<MethodName>) => r as unknown as Output
Expand All @@ -316,33 +349,46 @@ export class IOClient {
}
}

return new IOPromise<MethodName, T_IO_PROPS<MethodName>, Output>({
methodName,
renderer: this.renderComponents.bind(
this
) as ComponentRenderer<MethodName>,
label,
props: internalProps,
valueGetter: getValue,
onStateChange,
})
const isDisplay = methodName.startsWith('DISPLAY_')

return isDisplay
? new DisplayIOPromise({
methodName: methodName as T_IO_DISPLAY_METHOD_NAMES,
renderer: this.renderComponents.bind(
this
) as ComponentRenderer<T_IO_DISPLAY_METHOD_NAMES>,
label,
props: internalProps,
valueGetter: getValue,
onStateChange,
})
: new InputIOPromise({
methodName: methodName as T_IO_INPUT_METHOD_NAMES,
renderer: this.renderComponents.bind(
this
) as ComponentRenderer<T_IO_INPUT_METHOD_NAMES>,
label,
props: internalProps,
valueGetter: getValue,
onStateChange,
})
}
}

/**
* A very thin wrapper function that converts an IOPromise to an
* ExclusiveIOPromise, which cannot be rendered in a group.
*/
makeExclusive<MethodName extends T_IO_METHOD_NAMES, Props, Output>(
inner: IOComponentFunction<MethodName, Props, Output>,
makeExclusive<MethodName extends T_IO_INPUT_METHOD_NAMES, Props, Output>(
inner: InputIOComponentFunction<MethodName, Props, Output>,
propsRequired?: false
): ExclusiveIOComponentFunction<MethodName, Props, Output>
makeExclusive<MethodName extends T_IO_METHOD_NAMES, Props, Output>(
inner: IOComponentFunction<MethodName, Props, Output>,
makeExclusive<MethodName extends T_IO_INPUT_METHOD_NAMES, Props, Output>(
inner: InputIOComponentFunction<MethodName, Props, Output>,
propsRequired?: true
): RequiredPropsExclusiveIOComponentFunction<MethodName, Props, Output>
makeExclusive<MethodName extends T_IO_METHOD_NAMES, Props, Output>(
inner: IOComponentFunction<MethodName, Props, Output>,
makeExclusive<MethodName extends T_IO_INPUT_METHOD_NAMES, Props, Output>(
inner: InputIOComponentFunction<MethodName, Props, Output>,
_propsRequired = false
): ExclusiveIOComponentFunction<MethodName, Props, Output> {
return (label: string, props?: Props) => {
Expand All @@ -359,7 +405,10 @@ export class IOClient {

confirm: this.makeExclusive(this.createIOMethod('CONFIRM')),

search: this.createIOMethod('SEARCH', true, search),
search: this.createIOMethod('SEARCH', {
propsRequired: true,
componentDef: search,
}),

input: {
text: this.createIOMethod('INPUT_TEXT'),
Expand All @@ -369,36 +418,41 @@ export class IOClient {
richText: this.createIOMethod('INPUT_RICH_TEXT'),
},
select: {
single: this.createIOMethod('SELECT_SINGLE', true, selectSingle),
multiple: this.createIOMethod('SELECT_MULTIPLE', true, selectMultiple),
table: this.createIOMethod(
'SELECT_TABLE',
true,
selectTable(this.logger)
),
single: this.createIOMethod('SELECT_SINGLE', {
propsRequired: true,
componentDef: selectSingle,
}),
multiple: this.createIOMethod('SELECT_MULTIPLE', {
propsRequired: true,
componentDef: selectMultiple,
}),
table: this.createIOMethod('SELECT_TABLE', {
propsRequired: true,
componentDef: selectTable(this.logger),
}),
},
display: {
heading: this.createIOMethod('DISPLAY_HEADING'),
markdown: this.createIOMethod('DISPLAY_MARKDOWN'),
link: this.createIOMethod('DISPLAY_LINK'),
object: this.createIOMethod('DISPLAY_OBJECT'),
table: this.createIOMethod(
'DISPLAY_TABLE',
true,
displayTable(this.logger)
),
table: this.createIOMethod('DISPLAY_TABLE', {
propsRequired: true,
componentDef: displayTable(this.logger),
}),
},
experimental: {
spreadsheet: this.createIOMethod(
'INPUT_SPREADSHEET',
true,
spreadsheet
),
date: this.createIOMethod('INPUT_DATE', false, date),
spreadsheet: this.createIOMethod('INPUT_SPREADSHEET', {
propsRequired: true,
componentDef: spreadsheet,
}),
date: this.createIOMethod('INPUT_DATE', { componentDef: date }),
time: this.createIOMethod('INPUT_TIME'),
datetime: this.createIOMethod('INPUT_DATETIME', false, datetime),
datetime: this.createIOMethod('INPUT_DATETIME', {
componentDef: datetime,
}),
input: {
file: this.createIOMethod('UPLOAD_FILE', false, file),
file: this.createIOMethod('UPLOAD_FILE', { componentDef: file }),
},
},
}
Expand Down
53 changes: 37 additions & 16 deletions src/classes/IOPromise.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { T_IO_METHOD_NAMES, T_IO_PROPS, T_IO_STATE } from '../ioSchema'
import {
T_IO_DISPLAY_METHOD_NAMES,
T_IO_INPUT_METHOD_NAMES,
T_IO_METHOD_NAMES,
T_IO_PROPS,
T_IO_STATE,
} from '../ioSchema'
import IOComponent, {
AnyIOComponent,
ComponentReturnValue,
Expand Down Expand Up @@ -82,6 +88,32 @@ export class IOPromise<
return result as unknown as Output
}

get component() {
return new IOComponent(
this.methodName,
this.label,
this.props,
this.onStateChange,
false
)
}
}

/**
* A thin subtype of IOPromise that does nothing but mark the component
* as "display" for display-only components.
*/
export class DisplayIOPromise<
MethodName extends T_IO_DISPLAY_METHOD_NAMES,
Props = T_IO_PROPS<MethodName>,
Output = ComponentReturnValue<MethodName>
> extends IOPromise<MethodName, Props, Output> {}

export class InputIOPromise<
MethodName extends T_IO_INPUT_METHOD_NAMES,
Props = T_IO_PROPS<MethodName>,
Output = ComponentReturnValue<MethodName>
> extends IOPromise<MethodName, Props, Output> {
get component() {
return new IOComponent(
this.methodName,
Expand Down Expand Up @@ -153,10 +185,10 @@ export class IOPromise<
* "optional" and returns `undefined` if not provided by the action runner.
*/
export class OptionalIOPromise<
MethodName extends T_IO_METHOD_NAMES,
MethodName extends T_IO_INPUT_METHOD_NAMES,
Props = T_IO_PROPS<MethodName>,
Output = ComponentReturnValue<MethodName>
> extends IOPromise<MethodName, Props, Output | undefined> {
> extends InputIOPromise<MethodName, Props, Output | undefined> {
then(
resolve: (output: Output | undefined) => void,
reject?: (err: IOError) => void
Expand Down Expand Up @@ -205,21 +237,10 @@ export class OptionalIOPromise<
* as "exclusive" for components that cannot be rendered in a group.
*/
export class ExclusiveIOPromise<
MethodName extends T_IO_METHOD_NAMES,
MethodName extends T_IO_INPUT_METHOD_NAMES,
Props = T_IO_PROPS<MethodName>,
Output = ComponentReturnValue<MethodName>
> extends IOPromise<MethodName, Props, Output> {}

/**
* A thin subtype of IOPromise that does nothing but mark the component
* as "display" for display-only components.
*/
export type DisplayIOPromise =
| IOPromise<'DISPLAY_HEADING', any, any>
| IOPromise<'DISPLAY_MARKDOWN', any, any>
| IOPromise<'DISPLAY_LINK', any, any>
| IOPromise<'DISPLAY_OBJECT', any, any>
| IOPromise<'DISPLAY_TABLE', any, any>
> extends InputIOPromise<MethodName, Props, Output> {}

export type IOGroupReturnValues<
IOPromises extends [
Expand Down
12 changes: 12 additions & 0 deletions src/ioSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,18 @@ export type IoMethod = {
export type T_IO_Schema = typeof ioSchema
export type T_IO_METHOD_NAMES = keyof T_IO_Schema

export type T_IO_DISPLAY_METHOD_NAMES =
| 'DISPLAY_HEADING'
| 'DISPLAY_MARKDOWN'
| 'DISPLAY_LINK'
| 'DISPLAY_OBJECT'
| 'DISPLAY_TABLE'

export type T_IO_INPUT_METHOD_NAMES = Exclude<
T_IO_METHOD_NAMES,
T_IO_DISPLAY_METHOD_NAMES
>

type T_Fields = 'props' | 'state' | 'returns'

// prettier-ignore
Expand Down
Loading

0 comments on commit 3b6d9bf

Please sign in to comment.