diff --git a/src/classes/IntervalClient.ts b/src/classes/IntervalClient.ts index f79a7b1..502f686 100644 --- a/src/classes/IntervalClient.ts +++ b/src/classes/IntervalClient.ts @@ -24,7 +24,7 @@ import { ActionResultSchema, IOFunctionReturnType, IO_RESPONSE, - LinkProps, + LegacyLinkProps, T_IO_RENDER_INPUT, T_IO_RESPONSE, } from '../ioSchema' @@ -1041,7 +1041,7 @@ export default class IntervalClient { }) }, }), - redirect: (props: LinkProps) => + redirect: (props: LegacyLinkProps) => this.#sendRedirect(transactionId, props), } @@ -1301,7 +1301,7 @@ export default class IntervalClient { }) } - async #sendRedirect(transactionId: string, props: LinkProps) { + async #sendRedirect(transactionId: string, props: LegacyLinkProps) { const response = await this.#send('SEND_REDIRECT', { transactionId, ...props, diff --git a/src/components/displayLink.ts b/src/components/displayLink.ts index c342905..70313eb 100644 --- a/src/components/displayLink.ts +++ b/src/components/displayLink.ts @@ -7,6 +7,12 @@ export default function displayLink( | { url: string } + | { + route: string + params?: SerializableRecord + } + // deprecated in favor of `route` + // TODO: Add TS deprecated flag soon | { action: string params?: SerializableRecord diff --git a/src/examples/app/index.ts b/src/examples/app/index.ts index 0c975dc..1eb4b84 100644 --- a/src/examples/app/index.ts +++ b/src/examples/app/index.ts @@ -35,7 +35,7 @@ const hello_app = new Page({ }, { label: 'Action link', - action: 'hello_app/hello_world', + route: 'hello_app/hello_world', }, // { // label: 'Inline action', @@ -59,7 +59,7 @@ const hello_app = new Page({ rowMenuItems: () => [ { label: 'Hello', - action: 'hello_app/hello_world', + route: 'hello_app/hello_world', }, ], }), @@ -122,11 +122,11 @@ const users = new Page({ menuItems: [ { label: 'View funnel', - action: 'users/view_funnel', + route: 'users/view_funnel', }, { label: 'Create user', - action: 'users/create', + route: 'users/create', }, ], children: [ @@ -135,7 +135,7 @@ const users = new Page({ rowMenuItems: row => [ { label: 'Edit', - action: 'users/edit', + route: 'users/edit', params: { id: row.id }, }, ], @@ -210,11 +210,11 @@ const interval = new Interval({ menuItems: [ { label: 'Reload', - action: 'info', + route: 'info', }, { label: 'Add timestamp param', - action: 'info', + route: 'info', params: { timestamp: new Date().valueOf() }, }, ], diff --git a/src/examples/basic/index.ts b/src/examples/basic/index.ts index 3b8ad00..a5b2aeb 100644 --- a/src/examples/basic/index.ts +++ b/src/examples/basic/index.ts @@ -45,13 +45,13 @@ const actionLinks: IntervalActionHandler = async () => { url: 'https://example.com', }), io.display.link('Action link', { - action: 'helloCurrentUser', + route: 'helloCurrentUser', params: { message: 'From a button!', }, }), io.display.link('This same action', { - action: 'actionLinks', + route: 'actionLinks', params: { prevActionAt: new Date().toISOString(), }, @@ -124,7 +124,7 @@ const prod = new Interval({ const { workDone = false } = ctx.params if (!workDone) { await ctx.redirect({ - action: 'perform_common_work', + route: 'perform_common_work', }) startedWork = true } @@ -142,7 +142,7 @@ const prod = new Interval({ ) await sleep(2000) await ctx.redirect({ - action: 'perform_redirect_flow', + route: 'perform_redirect_flow', params: { workDone: true, }, @@ -405,6 +405,11 @@ const interval = new Interval({ action: 'helloCurrentUser', params: { message: 'Hello from metadata!' }, }, + { + label: 'External link', + value: 'Click me', + url: 'https://interval.com', + }, { label: 'Image', value: 'Optional caption', @@ -1046,7 +1051,7 @@ const interval = new Interval({ return { url: url.href } }, redirect: async () => { - const [url, , action, paramsStr] = await io.group([ + const [url, , route, paramsStr] = await io.group([ io.input.url('Enter a URL').optional(), io.display.markdown('--- or ---'), io.input.text('Enter an action slug').optional(), @@ -1060,7 +1065,7 @@ const interval = new Interval({ let params = undefined if (url) { await ctx.redirect({ url: url.toString() }) - } else if (action) { + } else if (route) { if (paramsStr) { try { params = JSON.parse(paramsStr) @@ -1069,20 +1074,20 @@ const interval = new Interval({ } } - await ctx.redirect({ action, params }) + await ctx.redirect({ route, params }) } else { throw new Error('Must enter either a URL or an action slug') } console.log({ url, - action, + route, params, }) return { url: url?.toString(), - action, + route, paramsStr, } }, diff --git a/src/examples/basic/selectFromTable.ts b/src/examples/basic/selectFromTable.ts index 3787a4f..9b1f2e4 100644 --- a/src/examples/basic/selectFromTable.ts +++ b/src/examples/basic/selectFromTable.ts @@ -82,7 +82,7 @@ export const table_actions: IntervalActionHandler = async io => { rowMenuItems: row => [ { label: 'Edit', - action: 'edit_user', + route: 'edit_user', params: { email: row.email }, }, ], diff --git a/src/examples/basic/table.ts b/src/examples/basic/table.ts index 46df738..ab987ef 100644 --- a/src/examples/basic/table.ts +++ b/src/examples/basic/table.ts @@ -98,24 +98,24 @@ export const display_table: IntervalActionHandler = async io => { rowMenuItems: row => [ { label: 'Edit', - action: 'edit_user', + route: 'edit_user', params: { email: row.email }, }, { label: 'Edit', - action: 'edit_user', + route: 'edit_user', params: { email: row.email }, disabled: true, }, { label: 'Delete', - action: 'delete_user', + route: 'delete_user', params: { email: row.email }, theme: 'danger', }, { label: 'Delete', - action: 'delete_user', + route: 'delete_user', params: { email: row.email }, theme: 'danger', disabled: true, @@ -223,7 +223,7 @@ export const async_table: IntervalActionHandler = async io => { rowMenuItems: row => [ { label: 'Edit', - action: 'edit_user', + route: 'edit_user', params: { email: row.email }, }, ], @@ -261,7 +261,7 @@ export const select_table: IntervalActionHandler = async io => { rowMenuItems: row => [ { label: 'Edit', - action: 'edit_user', + route: 'edit_user', params: { email: row.email }, }, ], diff --git a/src/examples/structure/index.ts b/src/examples/structure/index.ts index 4a4842b..d9c30a7 100644 --- a/src/examples/structure/index.ts +++ b/src/examples/structure/index.ts @@ -47,7 +47,7 @@ const routes: IntervalRouteDefinitions = { menuItems: [ { label: 'Create user', - action: 'users/create', + route: 'users/create', }, ], children: [ @@ -56,7 +56,7 @@ const routes: IntervalRouteDefinitions = { rowMenuItems: row => [ { label: 'Edit', - action: 'users/edit', + route: 'users/edit', params: { id: row.id }, }, ], @@ -99,12 +99,12 @@ const routes: IntervalRouteDefinitions = { rowMenuItems: row => [ { label: 'Edit', - action: 'users/subscriptions/edit', + route: 'users/subscriptions/edit', params: { id: row.id }, }, { label: 'Cancel', - action: 'users/subscriptions/cancel', + route: 'users/subscriptions/cancel', theme: 'danger', params: { id: row.id }, }, @@ -140,7 +140,7 @@ const routes: IntervalRouteDefinitions = { menuItems: [ { label: 'Create comment', - action: 'users/comments/create', + route: 'users/comments/create', }, ], children: [ @@ -149,7 +149,7 @@ const routes: IntervalRouteDefinitions = { rowMenuItems: row => [ { label: 'Edit', - action: 'users/comments/edit', + route: 'users/comments/edit', params: { id: row.id }, }, ], diff --git a/src/internalRpcSchema.ts b/src/internalRpcSchema.ts index 864938b..3356d1b 100644 --- a/src/internalRpcSchema.ts +++ b/src/internalRpcSchema.ts @@ -1,6 +1,7 @@ import { z } from 'zod' import { deserializableRecord, + legacyLinkSchema, linkSchema, serializableRecord, } from './ioSchema' @@ -257,7 +258,7 @@ export const wsServerSchema = { z.object({ transactionId: z.string(), }), - linkSchema + legacyLinkSchema ), returns: z.boolean(), }, diff --git a/src/ioSchema.ts b/src/ioSchema.ts index 8d55d7a..19a680b 100644 --- a/src/ioSchema.ts +++ b/src/ioSchema.ts @@ -191,7 +191,9 @@ export const tableRowValue = z.union([ url: z.string(), }) .optional(), + // Deprecated in favor of route action: z.string().optional(), + route: z.string().optional(), params: serializableRecord.optional(), }), ]) @@ -209,11 +211,21 @@ export const menuItem = z.intersection( theme: z.enum(['default', 'danger']).optional(), }), z.union([ - z.object({ - action: z.string(), - params: serializableRecord.optional(), - disabled: z.boolean().optional(), - }), + z.intersection( + z.object({ + params: serializableRecord.optional(), + disabled: z.boolean().optional(), + }), + z.union([ + z.object({ + route: z.string(), + }), + z.object({ + // deprecated in favor of `route` + action: z.string(), + }), + ]) + ), z.object({ url: z.string(), disabled: z.boolean().optional(), @@ -230,11 +242,21 @@ export const buttonItem = z.intersection( theme: buttonTheme.optional(), }), z.union([ - z.object({ - action: z.string(), - params: serializableRecord.optional(), - disabled: z.boolean().optional(), - }), + z.intersection( + z.object({ + params: serializableRecord.optional(), + disabled: z.boolean().optional(), + }), + z.union([ + z.object({ + route: z.string(), + }), + z.object({ + // deprecated in favor of `route` + action: z.string(), + }), + ]) + ), z.object({ url: z.string(), disabled: z.boolean().optional(), @@ -250,13 +272,24 @@ export const linkSchema = z.union([ url: z.string(), }), z.object({ - action: z.string(), + route: z.string(), params: serializableRecord.optional(), }), ]) export type LinkProps = z.infer +// TODO: Remove soon +export const legacyLinkSchema = z.union([ + linkSchema, + z.object({ + action: z.string(), + params: serializableRecord.optional(), + }), +]) + +export type LegacyLinkProps = z.infer + export const internalTableRow = z.object({ key: z.string(), data: tableRow, @@ -318,6 +351,8 @@ export const metaItemSchema = z.object({ url: z.string(), }) .optional(), + route: z.string().optional(), + // Deprecated in favor of `route` above action: z.string().optional(), params: serializableRecord.optional(), error: z.string().nullish(), @@ -373,7 +408,7 @@ const DISPLAY_SCHEMA = { z.object({ href: z.string(), }), - linkSchema, + legacyLinkSchema, ]) ), state: z.null(), diff --git a/src/types.ts b/src/types.ts index bce8a2a..971b078 100644 --- a/src/types.ts +++ b/src/types.ts @@ -16,6 +16,8 @@ import type { ButtonTheme, serializableRecord, ImageSize, + SerializableRecord, + LegacyLinkProps, } from './ioSchema' import type { HostSchema } from './internalRpcSchema' import type { IOClient, IOClientRenderValidator } from './classes/IOClient' @@ -217,7 +219,7 @@ export type ActionLogFn = (...args: any[]) => Promise export type NotifyFn = (config: NotifyConfig) => Promise -export type RedirectFn = (props: LinkProps) => Promise +export type RedirectFn = (props: LegacyLinkProps) => Promise export type ResponseHandlerFn = (fn: T_IO_RESPONSE) => void @@ -293,25 +295,42 @@ export type IOComponentDefinition< ) => Promise> } -type DistributiveOmit = T extends any - ? Omit - : never - export type InternalMenuItem = z.input -export type MenuItem = DistributiveOmit & { +export type MenuItem = { + label: string theme?: 'danger' -} +} & ( + | { + route: string + params?: SerializableRecord + disabled?: boolean + } + // Deprecated in favor of `route` + // TODO: Add TS deprecation soon + | { + action: string + params?: SerializableRecord + disabled?: boolean + } + | { + url: string + disabled?: boolean + } + | { disabled: true } +) export type InternalButtonItem = z.input -export type ButtonItem = DistributiveOmit & { +export type ButtonItem = { + label: string theme?: 'primary' | 'secondary' | 'danger' -} -// | { -// label: InternalMenuItem['label'] -// theme?: InternalMenuItem['theme'] -// action: IntervalActionHandler -// disabled?: boolean -// } +} & ( + | { route: string; params?: SerializableRecord; disabled?: boolean } + // Deprecated in favor of `route` + // TODO: Add TS deprecation soon + | { action: string; params?: SerializableRecord; disabled?: boolean } + | { url: string; disabled?: boolean } + | { disabled: true } +) export type ButtonConfig = { label?: string @@ -335,6 +354,9 @@ export type TableColumnResult = height?: ImageSize } & ({ url: string } | { buffer: Buffer }) url?: string + route?: string + // Deprecated in favor of `route` + // TODO: Add TS deprecation soon action?: string params?: z.infer }