From ee5991c4d47bf2ee7d05b70469b001993f7bfc2c Mon Sep 17 00:00:00 2001 From: Jacob Mischka Date: Thu, 6 Apr 2023 13:27:17 -0500 Subject: [PATCH] Allow returning undefined/None to opt out of custom page layout Closes T-820 --- src/classes/IntervalClient.ts | 70 ++++++++++++++++++++--------------- src/examples/basic/index.ts | 28 ++++++++++++++ src/internalRpcSchema.ts | 8 ++-- src/types.ts | 2 +- 4 files changed, 74 insertions(+), 34 deletions(-) diff --git a/src/classes/IntervalClient.ts b/src/classes/IntervalClient.ts index 4867948..bb0fc34 100644 --- a/src/classes/IntervalClient.ts +++ b/src/classes/IntervalClient.ts @@ -1366,7 +1366,7 @@ export default class IntervalClient { page: inputs.page, } - let page: Layout + let page: Layout | undefined = undefined let menuItems: InternalButtonItem[] | undefined = undefined let renderInstruction: T_IO_RENDER_INPUT | undefined = undefined let errors: PageError[] = [] @@ -1374,8 +1374,9 @@ export default class IntervalClient { const MAX_PAGE_RETRIES = 5 const sendPage = async () => { + let pageLayout: LayoutSchemaInput | undefined if (page instanceof BasicLayout) { - const pageLayout: LayoutSchemaInput = { + pageLayout = { kind: 'BASIC', title: page.title === undefined @@ -1399,33 +1400,35 @@ export default class IntervalClient { 'The `metadata` property on `Layout` is deprecated. Please use `io.display.metadata` in the `children` array instead.' ) } + } - if (this.#config.getClientHandlers) { - await this.#config.getClientHandlers()?.RENDER_PAGE({ - pageKey, - page: JSON.stringify(pageLayout), - hostInstanceId: 'demo', - }) - } else { - for (let i = 0; i < MAX_PAGE_RETRIES; i++) { - try { - const page = JSON.stringify(pageLayout) + if (this.#config.getClientHandlers) { + await this.#config.getClientHandlers()?.RENDER_PAGE({ + pageKey, + page: pageLayout ? JSON.stringify(pageLayout) : undefined, + hostInstanceId: 'demo', + }) + } else { + for (let i = 0; i < MAX_PAGE_RETRIES; i++) { + try { + const page = pageLayout ? JSON.stringify(pageLayout) : undefined + if (page) { this.#pendingPageLayouts.set(pageKey, page) - await this.#send('SEND_PAGE', { - pageKey, - page, - }) - return - } catch (err) { - this.#logger.debug('Failed sending page', err) - this.#logger.debug('Retrying in', this.#retryIntervalMs) - await sleep(this.#retryIntervalMs) } + await this.#send('SEND_PAGE', { + pageKey, + page, + }) + return + } catch (err) { + this.#logger.debug('Failed sending page', err) + this.#logger.debug('Retrying in', this.#retryIntervalMs) + await sleep(this.#retryIntervalMs) } - throw new IntervalError( - 'Unsuccessful sending page, max retries exceeded.' - ) } + throw new IntervalError( + 'Unsuccessful sending page, max retries exceeded.' + ) } } @@ -1519,6 +1522,11 @@ export default class IntervalClient { .then(res => { page = res + if (!page) { + scheduleSendPage() + return + } + if (typeof page.title === 'function') { try { page.title = page.title() @@ -1531,8 +1539,10 @@ export default class IntervalClient { if (page.title instanceof Promise) { page.title .then(title => { - page.title = title - scheduleSendPage() + if (page) { + page.title = title + scheduleSendPage() + } }) .catch(err => { this.#logger.error(err) @@ -1554,8 +1564,10 @@ export default class IntervalClient { if (page.description instanceof Promise) { page.description .then(description => { - page.description = description - scheduleSendPage() + if (page) { + page.description = description + scheduleSendPage() + } }) .catch(err => { this.#logger.error(err) @@ -1589,7 +1601,7 @@ export default class IntervalClient { ) } - if (page.children) { + if (page.children?.length) { group(page.children).then( () => { this.#logger.debug( diff --git a/src/examples/basic/index.ts b/src/examples/basic/index.ts index 969df5f..6ad2c82 100644 --- a/src/examples/basic/index.ts +++ b/src/examples/basic/index.ts @@ -96,6 +96,32 @@ const sidebar_depth = new Page({ }, }) +const empty_page = new Page({ + name: 'Empty page', + handler: async () => { + if (ctx.params.show_layout) { + return new Layout({ + title: 'Contents!', + children: [io.display.markdown('Children!')], + menuItems: [ + { + label: 'Hide layout', + route: 'empty_page', + }, + ], + }) + } + }, + routes: { + child_action: new Action(async () => { + return 'Hello!' + }), + show_layout: new Action(async () => { + ctx.redirect({ route: 'empty_page', params: { show_layout: 1 } }) + }), + }, +}) + const confirmIdentity = new Action({ name: 'Confirm identity', handler: async () => { @@ -335,6 +361,7 @@ const prod = new Interval({ }, }) }, + empty_page, grids: gridsPage, tables: new Page({ name: 'Tables', @@ -421,6 +448,7 @@ const interval = new Interval({ routes: { sidebar_depth, echoContext, + empty_page, inputRightAfterDisplay: async () => { await io.display.link('Display', { url: '', diff --git a/src/internalRpcSchema.ts b/src/internalRpcSchema.ts index ede1987..7839ec0 100644 --- a/src/internalRpcSchema.ts +++ b/src/internalRpcSchema.ts @@ -319,8 +319,8 @@ export const wsServerSchema = { SEND_PAGE: { inputs: z.object({ pageKey: z.string(), - // stringified PAGE_SCHEMA - page: z.string(), + // stringified LAYOUT_SCHEMA + page: z.string().nullish(), }), returns: z.boolean(), }, @@ -479,8 +479,8 @@ export const clientSchema = { RENDER_PAGE: { inputs: z.object({ pageKey: z.string(), - // stringified PAGE_SCHEMA - page: z.string(), + // stringified LAYOUT_SCHEMA + page: z.string().nullish(), hostInstanceId: z.string(), }), returns: z.boolean(), diff --git a/src/types.ts b/src/types.ts index a45b802..962b932 100644 --- a/src/types.ts +++ b/src/types.ts @@ -209,7 +209,7 @@ export type IntervalRouteDefinitions = Record< export type IntervalPageHandler = ( display: IO['display'], ctx: PageCtx -) => Promise +) => Promise export type RequiredPropsIOComponentFunction< MethodName extends T_IO_METHOD_NAMES,