Skip to content

Commit

Permalink
Merge branch 'main' into async-table-data
Browse files Browse the repository at this point in the history
  • Loading branch information
jacobmischka committed Oct 13, 2022
2 parents e496e39 + a28a0ee commit 5c8a65c
Show file tree
Hide file tree
Showing 8 changed files with 237 additions and 84 deletions.
202 changes: 139 additions & 63 deletions src/classes/IntervalClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import type {
IntervalActionStore,
IntervalPageStore,
InternalMenuItem,
InternalButtonItem,
PageError,
} from '../types'
import TransactionLoadingState from '../classes/TransactionLoadingState'
import localConfig from '../localConfig'
Expand All @@ -49,6 +51,7 @@ import {
LayoutSchemaInput,
MetaItemSchema,
MetaItemsSchema,
BasicLayoutConfig,
} from './Layout'

export const DEFAULT_WEBSOCKET_ENDPOINT = 'wss://interval.com/websocket'
Expand Down Expand Up @@ -646,8 +649,9 @@ export default class IntervalClient {
}

let page: Layout
let menuItems: InternalMenuItem[] | undefined = undefined
let menuItems: InternalButtonItem[] | undefined = undefined
let renderInstruction: T_IO_RENDER_INPUT | undefined = undefined
let errors: PageError[] = []

const MAX_PAGE_RETRIES = 5

Expand All @@ -669,16 +673,17 @@ export default class IntervalClient {
: null,
menuItems,
children: renderInstruction,
errors,
}

if (page.metadata) {
const items: MetaItemSchema[] = []
for (const pageItem of page.metadata) {
let { label, value } = pageItem
let { label, value, error } = pageItem
if (typeof value === 'function' || value instanceof Promise) {
items.push({ label })
} else {
items.push({ label, value })
items.push({ label, value, error })
}
}

Expand Down Expand Up @@ -774,82 +779,153 @@ export default class IntervalClient {
this.#pageIOClients.set(pageKey, client)
this.#ioResponseHandlers.set(pageKey, client.onResponse.bind(client))

pageLocalStorage.run({ display, ctx }, () => {
appHandler(display, ctx).then(res => {
page = res

if (typeof page.title === 'function') {
page.title = page.title()
const pageError = (
error: unknown,
layoutKey?: keyof BasicLayoutConfig
) => {
if (error instanceof Error) {
return {
layoutKey,
error: error.name,
message: error.message,
}

if (page.title instanceof Promise) {
page.title.then(title => {
page.title = title
scheduleSendPage()
})
} else {
return {
layoutKey,
error: 'Unknown error',
message: String(error),
}
}
}

if (page.description) {
if (typeof page.description === 'function') {
page.description = page.description()
pageLocalStorage.run({ display, ctx }, () => {
appHandler(display, ctx)
.then(res => {
page = res

if (typeof page.title === 'function') {
try {
page.title = page.title()
} catch (err) {
this.#logger.error(err)
errors.push(pageError(err, 'title'))
}
}

if (page.description instanceof Promise) {
page.description.then(description => {
page.description = description
scheduleSendPage()
})
if (page.title instanceof Promise) {
page.title
.then(title => {
page.title = title
scheduleSendPage()
})
.catch(err => {
this.#logger.error(err)
errors.push(pageError(err, 'title'))
scheduleSendPage()
})
}
}

if (page.menuItems) {
menuItems = page.menuItems.map(menuItem => {
// if (
// 'action' in menuItem &&
// typeof menuItem['action'] === 'function'
// ) {
// const inlineAction = client.addInlineAction(menuItem.action)
// return {
// ...menuItem,
// inlineAction,
// }
// }

return menuItem
})
}

if (page instanceof Basic) {
const { metadata } = page
if (metadata) {
for (let i = 0; i < metadata.length; i++) {
let { value } = metadata[i]
if (typeof value === 'function') {
value = value()
metadata[i].value = value
if (page.description) {
if (typeof page.description === 'function') {
try {
page.description = page.description()
} catch (err) {
this.#logger.error(err)
errors.push(pageError(err, 'description'))
}
}

if (value instanceof Promise) {
value.then(resolved => {
metadata[i].value = resolved
if (page.description instanceof Promise) {
page.description
.then(description => {
page.description = description
scheduleSendPage()
})
.catch(err => {
this.#logger.error(err)
errors.push(pageError(err, 'description'))
scheduleSendPage()
})
}
}

if (page.menuItems) {
menuItems = page.menuItems.map(menuItem => {
// if (
// 'action' in menuItem &&
// typeof menuItem['action'] === 'function'
// ) {
// const inlineAction = client.addInlineAction(menuItem.action)
// return {
// ...menuItem,
// inlineAction,
// }
// }

return menuItem
})
}

if (page instanceof Basic) {
const { metadata } = page
if (metadata) {
for (let i = 0; i < metadata.length; i++) {
let { value } = metadata[i]
if (typeof value === 'function') {
try {
value = value()
metadata[i].value = value
} catch (err) {
this.#logger.error(err)
const error = pageError(err, 'metadata')
errors.push(error)
metadata[i].value = null
metadata[i].error = error.message
}
}

if (value instanceof Promise) {
value
.then(resolved => {
metadata[i].value = resolved
scheduleSendPage()
})
.catch(err => {
this.#logger.error(err)
const error = pageError(err, 'metadata')
errors.push(error)
metadata[i].value = null
metadata[i].error = error.message
scheduleSendPage()
})
}
}
}
}
}

if (page.children) {
group(page.children).then(() => {
this.#logger.debug(
'Initial children render complete for pageKey',
pageKey
)
if (page.children) {
group(page.children).then(() => {
this.#logger.debug(
'Initial children render complete for pageKey',
pageKey
)
})
} else {
scheduleSendPage()
}
})
.catch(async err => {
this.#logger.error(err)
errors.push(pageError(err))
const pageLayout: LayoutSchemaInput = {
kind: 'BASIC',
errors,
}
await this.#send('SEND_PAGE', {
pageKey,
page: JSON.stringify(pageLayout),
})
} else {
scheduleSendPage()
}
})
})
})

return {
Expand Down
34 changes: 27 additions & 7 deletions src/classes/Layout.ts
Original file line number Diff line number Diff line change
@@ -1,42 +1,51 @@
import { z } from 'zod'
import { primitiveValue, Literal, IO_RENDER, menuItem } from '../ioSchema'
import { AnyDisplayIOPromise, MenuItem } from '../types'
import {
primitiveValue,
Literal,
IO_RENDER,
menuItem,
buttonItem,
} from '../ioSchema'
import { AnyDisplayIOPromise, ButtonItem, PageError } from '../types'

type EventualString =
| string
| Promise<string>
| (() => string)
| (() => Promise<string>)

interface BasicLayoutConfig {
export interface BasicLayoutConfig {
title?: EventualString
description?: EventualString
children?: AnyDisplayIOPromise[]
menuItems?: MenuItem[]
menuItems?: ButtonItem[]
metadata?: MetaItem[]
}

export interface Layout {
title?: EventualString
description?: EventualString
children?: AnyDisplayIOPromise[]
menuItems?: MenuItem[]
menuItems?: ButtonItem[]
errors?: PageError[]
}

// Base class
export class Basic implements Layout {
title?: EventualString
description?: EventualString
children?: AnyDisplayIOPromise[]
menuItems?: MenuItem[]
menuItems?: ButtonItem[]
metadata?: MetaItem[]
errors?: PageError[]

constructor(config: BasicLayoutConfig) {
this.title = config.title
this.description = config.description
this.children = config.children
this.menuItems = config.menuItems
this.metadata = config.metadata
this.errors = []
}
}

Expand All @@ -49,11 +58,13 @@ export interface MetaItem {
| Promise<MetaItemValue>
| (() => MetaItemValue)
| (() => Promise<MetaItemValue>)
error?: string
}

export const META_ITEM_SCHEMA = z.object({
label: z.string(),
value: primitiveValue.or(z.bigint()).nullish(),
error: z.string().nullish(),
})

export type MetaItemSchema = z.infer<typeof META_ITEM_SCHEMA>
Expand All @@ -72,7 +83,16 @@ export const BASIC_LAYOUT_SCHEMA = z.object({
description: z.string().nullish(),
children: IO_RENDER.optional(),
metadata: META_ITEMS_SCHEMA.optional(),
menuItems: z.array(menuItem).optional(),
menuItems: z.array(buttonItem).optional(),
errors: z
.array(
z.object({
layoutKey: z.string().optional(),
error: z.string(),
message: z.string(),
})
)
.optional(),
})

// To be extended with z.discriminatedUnion when adding different pages
Expand Down
12 changes: 3 additions & 9 deletions src/components/displayTable.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import { z } from 'zod'
import Logger from '../classes/Logger'
import {
tableRow,
T_IO_PROPS,
menuItem,
T_IO_STATE,
internalTableRow,
} from '../ioSchema'
import { TableColumn } from '../types'
import { tableRow, T_IO_PROPS, T_IO_STATE, internalTableRow } from '../ioSchema'
import { MenuItem, TableColumn } from '../types'
import {
columnsBuilder,
tableRowSerializer,
Expand All @@ -24,7 +18,7 @@ type PublicProps<Row> = Omit<
'data' | 'columns' | 'totalRecords' | 'isAsync'
> & {
columns?: (TableColumn<Row> | string)[]
rowMenuItems?: (row: Row) => z.infer<typeof menuItem>[]
rowMenuItems?: (row: Row) => MenuItem[]
} & (
| {
data: Row[]
Expand Down
4 changes: 2 additions & 2 deletions src/components/selectTable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
T_IO_RETURNS,
T_IO_STATE,
} from '../ioSchema'
import { TableColumn } from '../types'
import { MenuItem, TableColumn } from '../types'
import {
columnsBuilder,
filterRows,
Expand All @@ -20,7 +20,7 @@ import {
type PublicProps<Row> = Omit<T_IO_PROPS<'SELECT_TABLE'>, 'data' | 'columns'> & {
data: Row[]
columns?: (TableColumn<Row> | string)[]
rowMenuItems?: (row: Row) => z.infer<typeof menuItem>[]
rowMenuItems?: (row: Row) => MenuItem[]
}

export default function selectTable(logger: Logger) {
Expand Down
Loading

0 comments on commit 5c8a65c

Please sign in to comment.