Skip to content

Commit

Permalink
server load (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelcolvin authored Nov 21, 2023
1 parent 95cf655 commit a2dacd0
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 75 deletions.
55 changes: 55 additions & 0 deletions packages/fastui/src/components/ServerLoad.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { FC, useContext, useEffect, useState } from 'react'

import { ErrorContext } from '../hooks/error'
import { ReloadContext } from '../hooks/dev'
import { request } from '../tools'
import { DefaultLoading } from '../DefaultLoading'
import { ConfigContext } from '../hooks/config'

import { AnyComp, FastProps } from './index'

export interface ServerLoadProps {
type: 'ServerLoad'
url: string
}

export const ServerLoadComp: FC<ServerLoadProps> = ({ url }) => {
const [componentProps, setComponentProps] = useState<FastProps | null>(null)

const { error, setError } = useContext(ErrorContext)
const reloadValue = useContext(ReloadContext)
const { rootUrl, pathSendMode, Loading } = useContext(ConfigContext)

useEffect(() => {
// setViewData(null)
let fetchUrl = rootUrl
if (pathSendMode === 'query') {
fetchUrl += `?path=${encodeURIComponent(url)}`
} else {
fetchUrl += url
}

const promise = request({ url: fetchUrl })

promise
.then(([, data]) => setComponentProps(data as FastProps))
.catch((e) => {
setError({ title: 'Request Error', description: e.message })
})
return () => {
promise.then(() => null)
}
}, [rootUrl, pathSendMode, url, setError, reloadValue])

if (componentProps === null) {
if (error) {
return <></>
} else if (Loading) {
return <Loading />
} else {
return <DefaultLoading />
}
} else {
return <AnyComp {...componentProps} />
}
}
2 changes: 1 addition & 1 deletion packages/fastui/src/components/display.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { FC } from 'react'

import { useCustomRender } from '../hooks/customRender'
import { useCustomRender } from '../hooks/config'
import { DisplayChoices, asTitle } from '../display'
import { unreachable } from '../tools'

Expand Down
6 changes: 5 additions & 1 deletion packages/fastui/src/components/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useContext, FC } from 'react'

import { ErrorContext } from '../hooks/error'
import { useCustomRender } from '../hooks/customRender'
import { useCustomRender } from '../hooks/config'
import { unreachable } from '../tools'

import { AllDivProps, DivComp } from './div'
Expand All @@ -21,6 +21,7 @@ import { ModalComp, ModalProps } from './modal'
import { TableComp, TableProps } from './table'
import { AllDisplayProps, DisplayArray, DisplayComp, DisplayObject, DisplayPrimitive } from './display'
import { JsonComp, JsonProps } from './Json'
import { ServerLoadComp, ServerLoadProps } from './ServerLoad'

export type FastProps =
| TextProps
Expand All @@ -35,6 +36,7 @@ export type FastProps =
| LinkProps
| AllDisplayProps
| JsonProps
| ServerLoadProps

export const AnyComp: FC<FastProps> = (props) => {
const { DisplayError } = useContext(ErrorContext)
Expand Down Expand Up @@ -85,6 +87,8 @@ export const AnyComp: FC<FastProps> = (props) => {
return <DisplayPrimitive {...props} />
case 'JSON':
return <JsonComp {...props} />
case 'ServerLoad':
return <ServerLoadComp {...props} />
default:
unreachable('Unexpected component type', type, props)
return <DisplayError title="Invalid Server Response" description={`Unknown component type: "${type}"`} />
Expand Down
49 changes: 4 additions & 45 deletions packages/fastui/src/controller.tsx
Original file line number Diff line number Diff line change
@@ -1,51 +1,10 @@
import { useContext, useEffect, useState } from 'react'
import { useContext } from 'react'

import type { FastUIProps } from './index'

import { FastProps, AnyComp } from './components'
import { DefaultLoading } from './DefaultLoading'
import { LocationContext } from './hooks/locationContext'
import { ErrorContext } from './hooks/error'
import { request } from './tools'
import { ReloadContext } from './hooks/dev'

type Props = Omit<FastUIProps, 'defaultClassName' | 'OnError' | 'customRender'>
import { ServerLoadComp } from './components/ServerLoad'

export function FastUIController({ rootUrl, pathSendMode, loading }: Props) {
const [componentProps, setComponentProps] = useState<FastProps | null>(null)
export function FastUIController() {
const { fullPath } = useContext(LocationContext)

const { error, setError } = useContext(ErrorContext)
const reloadValue = useContext(ReloadContext)

useEffect(() => {
// setViewData(null)
let url = rootUrl
if (pathSendMode === 'query') {
url += `?path=${encodeURIComponent(fullPath)}`
} else {
url += fullPath
}

const promise = request({ url })

promise
.then(([, data]) => setComponentProps(data as FastProps))
.catch((e) => {
setError({ title: 'Request Error', description: e.message })
})
return () => {
promise.then(() => null).catch(() => null)
}
}, [rootUrl, pathSendMode, fullPath, setError, reloadValue])

if (componentProps === null) {
if (error) {
return <></>
} else {
return <>{loading ? loading() : <DefaultLoading />}</>
}
} else {
return <AnyComp {...componentProps} />
}
return <ServerLoadComp type="ServerLoad" url={fullPath} />
}
23 changes: 23 additions & 0 deletions packages/fastui/src/hooks/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { createContext, FC, useContext } from 'react'

import type { CustomRender } from '../index'

import { FastProps } from '../components'

interface Config {
rootUrl: string
// defaults to 'append'
pathSendMode?: 'append' | 'query'
customRender?: CustomRender
Loading?: FC
}

export const ConfigContext = createContext<Config>({ rootUrl: '' })

export const useCustomRender = (props: FastProps): FC | void => {
const { customRender } = useContext(ConfigContext)

if (customRender) {
return customRender(props)
}
}
15 changes: 0 additions & 15 deletions packages/fastui/src/hooks/customRender.ts

This file was deleted.

18 changes: 10 additions & 8 deletions packages/fastui/src/index.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import { ReactNode } from 'react'
import { FC } from 'react'

import { LocationProvider } from './hooks/locationContext'
import { FastUIController } from './controller'
import { ClassNameContext, ClassNameGenerator } from './hooks/className'
import { ErrorContextProvider, ErrorDisplayType } from './hooks/error'
import { CustomRender, CustomRenderContext } from './hooks/customRender'
import { ConfigContext } from './hooks/config'
import { FastProps } from './components'
import { DisplayChoices } from './display'
import { DevReloadProvider } from './hooks/dev'

export type { ClassNameGenerator, CustomRender, ErrorDisplayType, FastProps, DisplayChoices }
export type { ClassNameGenerator, ErrorDisplayType, FastProps, DisplayChoices }

export type CustomRender = (props: FastProps) => FC | void

export interface FastUIProps {
rootUrl: string
// defaults to 'append'
pathSendMode?: 'append' | 'query'
loading?: () => ReactNode
Loading?: FC
DisplayError?: ErrorDisplayType
classNameGenerator?: ClassNameGenerator
customRender?: CustomRender
Expand All @@ -24,16 +26,16 @@ export interface FastUIProps {
}

export function FastUI(props: FastUIProps) {
const { classNameGenerator, DisplayError, customRender, devMode, ...rest } = props
const { classNameGenerator, DisplayError, devMode, ...rest } = props
return (
<div className="fastui">
<ErrorContextProvider DisplayError={DisplayError}>
<DevReloadProvider enabled={devMode}>
<LocationProvider>
<ClassNameContext.Provider value={classNameGenerator ?? null}>
<CustomRenderContext.Provider value={customRender ?? null}>
<FastUIController {...rest} />
</CustomRenderContext.Provider>
<ConfigContext.Provider value={rest}>
<FastUIController />
</ConfigContext.Provider>
</ClassNameContext.Provider>
</LocationProvider>
</DevReloadProvider>
Expand Down
13 changes: 9 additions & 4 deletions python/demo/main.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from __future__ import annotations as _annotations

import asyncio
from datetime import date
from enum import StrEnum
from typing import Annotated, Literal
Expand Down Expand Up @@ -31,7 +32,7 @@ def read_root() -> AnyComponent:
),
c.Modal(
title='Modal Title',
body=[c.Text(text='Modal Content')],
body=[c.ServerLoad(url='/modal')],
footer=[c.Button(text='Close', on_click=PageEvent(name='modal'))],
open_trigger=PageEvent(name='modal'),
),
Expand All @@ -47,6 +48,12 @@ class MyTableRow(BaseModel):
enabled: bool | None = None


@app.get('/api/modal', response_model=FastUI, response_model_exclude_none=True)
async def modal_view() -> AnyComponent:
await asyncio.sleep(2)
return c.Text(text='Modal Content Dynamic')


@app.get('/api/table', response_model=FastUI, response_model_exclude_none=True)
def table_view() -> AnyComponent:
return c.Page(
Expand Down Expand Up @@ -98,7 +105,7 @@ class MyFormModel(BaseModel):

@app.get('/api/form', response_model=FastUI, response_model_exclude_none=True)
def form_view() -> AnyComponent:
f = c.Page(
return c.Page(
children=[
c.Heading(text='Form'),
c.ModelForm[MyFormModel](
Expand All @@ -111,8 +118,6 @@ def form_view() -> AnyComponent:
),
]
)
debug(f)
return f


@app.post('/api/form')
Expand Down
12 changes: 11 additions & 1 deletion python/fastui/components/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,17 @@ class Modal(pydantic.BaseModel):
type: typing.Literal['Modal'] = 'Modal'


class ServerLoad(pydantic.BaseModel):
"""
A component that will be replaced by the server with the component returned by the given URL.
"""

url: str
class_name: extra.ClassName | None = None
type: typing.Literal['ServerLoad'] = 'ServerLoad'


AnyComponent = typing.Annotated[
Text | Div | Page | Heading | Row | Col | Button | Modal | Table | Form | ModelForm | FormField,
Text | Div | Page | Heading | Row | Col | Button | Modal | ServerLoad | Table | Form | ModelForm | FormField,
pydantic.Field(discriminator='type'),
]

0 comments on commit a2dacd0

Please sign in to comment.