mirror of
https://github.com/samuelcolvin/FastUI.git
synced 2023-12-01 22:22:11 +03:00
server load (#6)
This commit is contained in:
55
packages/fastui/src/components/ServerLoad.tsx
Normal file
55
packages/fastui/src/components/ServerLoad.tsx
Normal file
@@ -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} />
|
||||
}
|
||||
}
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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'
|
||||
@@ -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
|
||||
@@ -35,6 +36,7 @@ export type FastProps =
|
||||
| LinkProps
|
||||
| AllDisplayProps
|
||||
| JsonProps
|
||||
| ServerLoadProps
|
||||
|
||||
export const AnyComp: FC<FastProps> = (props) => {
|
||||
const { DisplayError } = useContext(ErrorContext)
|
||||
@@ -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}"`} />
|
||||
|
||||
@@ -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'
|
||||
import { ServerLoadComp } from './components/ServerLoad'
|
||||
|
||||
type Props = Omit<FastUIProps, 'defaultClassName' | 'OnError' | 'customRender'>
|
||||
|
||||
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
packages/fastui/src/hooks/config.ts
Normal file
23
packages/fastui/src/hooks/config.ts
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
import { createContext, FC, useContext } from 'react'
|
||||
|
||||
import { FastProps } from '../components'
|
||||
|
||||
export type CustomRender = (props: FastProps) => FC | void
|
||||
|
||||
export const CustomRenderContext = createContext<CustomRender | null>(null)
|
||||
|
||||
export const useCustomRender = (props: FastProps): FC | void => {
|
||||
const customRender = useContext(CustomRenderContext)
|
||||
|
||||
if (customRender) {
|
||||
return customRender(props)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
@@ -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'),
|
||||
),
|
||||
@@ -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(
|
||||
@@ -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](
|
||||
@@ -111,8 +118,6 @@ def form_view() -> AnyComponent:
|
||||
),
|
||||
]
|
||||
)
|
||||
debug(f)
|
||||
return f
|
||||
|
||||
|
||||
@app.post('/api/form')
|
||||
|
||||
@@ -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'),
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user