mirror of
https://github.com/samuelcolvin/FastUI.git
synced 2023-12-01 22:22:11 +03:00
@@ -6,8 +6,9 @@ module.exports = {
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:react-hooks/recommended',
|
||||
'prettier',
|
||||
],
|
||||
ignorePatterns: ['dist', '.eslintrc.cjs', 'vite.config.ts'],
|
||||
ignorePatterns: ['dist', '.eslintrc.cjs', 'demo/vite.config.ts'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['react', '@typescript-eslint', 'react-refresh', 'simple-import-sort'],
|
||||
rules: {
|
||||
79
.github/workflows/ci.yml
vendored
Normal file
79
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,79 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- '**'
|
||||
pull_request:
|
||||
types: [opened, synchronize]
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- run: pip install -r python/requirements/all.txt
|
||||
|
||||
- run: npm install
|
||||
|
||||
- uses: pre-commit/action@v3.0.0
|
||||
with:
|
||||
extra_args: --all-files
|
||||
env:
|
||||
SKIP: no-commit-to-branch
|
||||
|
||||
check: # This job does nothing and is only used for the branch protection
|
||||
if: always()
|
||||
needs: [lint]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Decide whether the needed jobs succeeded or failed
|
||||
uses: re-actors/alls-green@release/v1
|
||||
id: all-green
|
||||
with:
|
||||
jobs: ${{ toJSON(needs) }}
|
||||
|
||||
release:
|
||||
needs: [check]
|
||||
if: "success() && startsWith(github.ref, 'refs/tags/')"
|
||||
runs-on: ubuntu-latest
|
||||
environment: release
|
||||
|
||||
permissions:
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: set up python
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: install
|
||||
run: pip install -U build
|
||||
|
||||
- name: check version
|
||||
id: check-version
|
||||
uses: samuelcolvin/check-python-version@v4.1
|
||||
with:
|
||||
version_file_path: 'python/fastui/__init__.py'
|
||||
|
||||
- name: build
|
||||
run: python -m build
|
||||
|
||||
- name: Upload package to PyPI
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -28,3 +28,5 @@ dist-ssr
|
||||
__pycache__/
|
||||
|
||||
/.logfire/
|
||||
/frontend-dist/
|
||||
/scratch/
|
||||
|
||||
41
.pre-commit-config.yaml
Normal file
41
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,41 @@
|
||||
repos:
|
||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||
rev: v4.3.0
|
||||
hooks:
|
||||
- id: no-commit-to-branch
|
||||
- id: check-yaml
|
||||
- id: check-toml
|
||||
- id: end-of-file-fixer
|
||||
- id: trailing-whitespace
|
||||
|
||||
- repo: local
|
||||
hooks:
|
||||
- id: python-format
|
||||
name: python-format
|
||||
types_or: [python]
|
||||
entry: make format
|
||||
language: system
|
||||
pass_filenames: false
|
||||
- id: python-typecheck
|
||||
name: python-typecheck
|
||||
types_or: [python]
|
||||
entry: make typecheck
|
||||
language: system
|
||||
pass_filenames: false
|
||||
- id: react-prettier
|
||||
name: react-prettier
|
||||
types_or: [javascript, jsx, ts, tsx, css, json, markdown]
|
||||
entry: npm run prettier
|
||||
language: system
|
||||
- id: react-lint
|
||||
name: react-lint
|
||||
types_or: [ts, tsx]
|
||||
entry: npm run lint-fix
|
||||
language: system
|
||||
pass_filenames: false
|
||||
- id: react-typecheck
|
||||
name: react-typecheck
|
||||
types_or: [ts, tsx]
|
||||
entry: npm run typecheck
|
||||
language: system
|
||||
pass_filenames: false
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2023 to present Samuel Colvin
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
44
Makefile
Normal file
44
Makefile
Normal file
@@ -0,0 +1,44 @@
|
||||
.DEFAULT_GOAL:=all
|
||||
paths = python
|
||||
|
||||
.PHONY: install
|
||||
install:
|
||||
pip install -U pip pre-commit pip-tools
|
||||
pip install -r python/requirements/all.txt
|
||||
pre-commit install
|
||||
|
||||
.PHONY: update-lockfiles
|
||||
update-lockfiles:
|
||||
@echo "Updating requirements files using pip-compile"
|
||||
pip-compile -q --strip-extras -o python/requirements/lint.txt python/requirements/lint.in
|
||||
pip-compile -q --strip-extras -o python/requirements/pyproject.txt pyproject.toml
|
||||
pip install --dry-run -r python/requirements/all.txt
|
||||
|
||||
.PHONY: format
|
||||
format:
|
||||
ruff check --fix-only $(paths)
|
||||
ruff format $(paths)
|
||||
|
||||
.PHONY: lint
|
||||
lint:
|
||||
ruff check $(paths)
|
||||
ruff format --check $(paths)
|
||||
|
||||
.PHONY: typecheck
|
||||
typecheck:
|
||||
pyright python/fastui
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
coverage run -m pytest tests
|
||||
|
||||
.PHONY: testcov
|
||||
testcov: test
|
||||
coverage html
|
||||
|
||||
.PHONY: dev
|
||||
dev:
|
||||
uvicorn demo.server:app --reload
|
||||
|
||||
.PHONY: all
|
||||
all: testcov lint
|
||||
8
README.md
Normal file
8
README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
# FastUI
|
||||
|
||||
[](https://github.com/samuelcolvin/FastUI/actions?query=event%3Apush+branch%3Amain+workflow%3ACI)
|
||||
[](https://pypi.python.org/pypi/fastui)
|
||||
[](https://github.com/samuelcolvin/FastUI)
|
||||
[](https://github.com/samuelcolvin/FastUI/blob/main/LICENSE)
|
||||
|
||||
WIP
|
||||
@@ -3,20 +3,15 @@ from __future__ import annotations as _annotations
|
||||
from datetime import date
|
||||
|
||||
from fastapi import FastAPI
|
||||
from pydantic import RootModel, BaseModel, Field
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
import components as c
|
||||
from components import AnyComponent
|
||||
from components.events import PageEvent, GoToEvent
|
||||
from fastui import components as c
|
||||
from fastui import FastUI, PageEvent, GoToEvent, Display, AnyComponent
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
class FastUi(RootModel):
|
||||
root: AnyComponent
|
||||
|
||||
|
||||
@app.get('/api/', response_model=FastUi, response_model_exclude_none=True)
|
||||
@app.get('/api/', response_model=FastUI, response_model_exclude_none=True)
|
||||
def read_root() -> AnyComponent:
|
||||
return c.Page(
|
||||
children=[
|
||||
@@ -44,7 +39,7 @@ class MyTableRow(BaseModel):
|
||||
enabled: bool | None = None
|
||||
|
||||
|
||||
@app.get('/api/table', response_model=FastUi, response_model_exclude_none=True)
|
||||
@app.get('/api/table', response_model=FastUI, response_model_exclude_none=True)
|
||||
def read_foo() -> AnyComponent:
|
||||
return c.Page(
|
||||
children=[
|
||||
@@ -57,7 +52,7 @@ def read_foo() -> AnyComponent:
|
||||
],
|
||||
columns=[
|
||||
c.Column(field='name', on_click=GoToEvent(url='/api/more/{id}/')),
|
||||
c.Column(field='dob', display=c.Display.date),
|
||||
c.Column(field='dob', display=Display.date),
|
||||
c.Column(field='enabled'),
|
||||
]
|
||||
)
|
||||
@@ -1,6 +1,6 @@
|
||||
import { FastUI, ClassNameGenerator, CustomRender } from './FastUI'
|
||||
import { FastUI, ClassNameGenerator, CustomRender } from 'fastui'
|
||||
|
||||
export default function App () {
|
||||
export default function App() {
|
||||
return (
|
||||
<div className="app">
|
||||
<FastUI rootUrl="/api" classNameGenerator={bootstrapClassName} customRender={customRender} />
|
||||
|
||||
@@ -7,5 +7,5 @@ import './main.scss'
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
</React.StrictMode>,
|
||||
)
|
||||
|
||||
@@ -18,7 +18,10 @@
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"paths": {
|
||||
"fastui": ["../react/fastui"]
|
||||
}
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
|
||||
@@ -17,6 +17,7 @@ export default () => {
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
fastui: path.resolve(__dirname, '../react/fastui'),
|
||||
},
|
||||
},
|
||||
server: serverConfig,
|
||||
|
||||
25
demo/package-lock.json → package-lock.json
generated
25
demo/package-lock.json → package-lock.json
generated
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "fastui-demo",
|
||||
"name": "fastui",
|
||||
"version": "0.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "fastui-demo",
|
||||
"name": "fastui",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"react": "^18.2.0",
|
||||
@@ -26,7 +26,6 @@
|
||||
"eslint-plugin-react-refresh": "^0.4.4",
|
||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||
"prettier": "^3.0.3",
|
||||
"sass": "^1.67.0",
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^4.4.5"
|
||||
}
|
||||
@@ -1054,6 +1053,8 @@
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"normalize-path": "^3.0.0",
|
||||
"picomatch": "^2.0.4"
|
||||
@@ -1231,6 +1232,8 @@
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
@@ -1349,6 +1352,8 @@
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
],
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"anymatch": "~3.1.2",
|
||||
"braces": "~3.0.2",
|
||||
@@ -1370,6 +1375,8 @@
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"is-glob": "^4.0.1"
|
||||
},
|
||||
@@ -2538,7 +2545,9 @@
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz",
|
||||
"integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/import-fresh": {
|
||||
"version": "3.3.0",
|
||||
@@ -2641,6 +2650,8 @@
|
||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"binary-extensions": "^2.0.0"
|
||||
},
|
||||
@@ -3170,6 +3181,8 @@
|
||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -3547,6 +3560,8 @@
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"picomatch": "^2.2.1"
|
||||
},
|
||||
@@ -3729,6 +3744,8 @@
|
||||
"resolved": "https://registry.npmjs.org/sass/-/sass-1.67.0.tgz",
|
||||
"integrity": "sha512-SVrO9ZeX/QQyEGtuZYCVxoeAL5vGlYjJ9p4i4HFuekWl8y/LtJ7tJc10Z+ck1c8xOuoBm2MYzcLfTAffD0pl/A==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"chokidar": ">=3.0.0 <4.0.0",
|
||||
"immutable": "^4.0.0",
|
||||
@@ -1,16 +1,18 @@
|
||||
{
|
||||
"name": "fastui-demo",
|
||||
"name": "fastui",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"entry": "src/index.tsx",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"dev": "vite demo",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"build": "tsc",
|
||||
"typewatch": "tsc --noEmit --watch",
|
||||
"build": "tsc && vite build",
|
||||
"lint": "eslint . --ext .ts,.tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"format": "prettier . --write -- '**/*.{ts,tsx,js,css,json,md}' && npm run lint -- --fix",
|
||||
"preview": "vite preview"
|
||||
"lint": "eslint react --ext .ts,.tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"lint-fix": "npm run lint -- --fix",
|
||||
"prettier": "prettier --write",
|
||||
"format": "npm run prettier -- . && npm run lint-fix"
|
||||
},
|
||||
"prettier": {
|
||||
"singleQuote": true,
|
||||
@@ -39,7 +41,6 @@
|
||||
"eslint-plugin-react-refresh": "^0.4.4",
|
||||
"eslint-plugin-simple-import-sort": "^10.0.0",
|
||||
"prettier": "^3.0.3",
|
||||
"sass": "^1.67.0",
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^4.4.5"
|
||||
}
|
||||
50
pyproject.toml
Normal file
50
pyproject.toml
Normal file
@@ -0,0 +1,50 @@
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.build.targets.sdist]
|
||||
include = ["python"]
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["python/fastui"]
|
||||
|
||||
[tool.hatch.version]
|
||||
path = "python/fastui/__init__.py"
|
||||
|
||||
[project]
|
||||
name = "fastui"
|
||||
description = "Build UIs fast."
|
||||
authors = [{ name = "Samuel Colvin", email = "s@muelcolvin.com" }]
|
||||
license = "MIT"
|
||||
readme = "README.md"
|
||||
classifiers = [
|
||||
"Development Status :: 4 - Beta",
|
||||
"Topic :: Internet",
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Programming Language :: Python :: 3",
|
||||
'Programming Language :: Python :: 3 :: Only',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3.10',
|
||||
'Programming Language :: Python :: 3.11',
|
||||
'Programming Language :: Python :: 3.12',
|
||||
"Intended Audience :: Developers",
|
||||
"Intended Audience :: Information Technology",
|
||||
]
|
||||
requires-python = ">=3.8"
|
||||
dependencies = [
|
||||
"pydantic>=2.5.0b1",
|
||||
"fastapi>=0.104.0",
|
||||
]
|
||||
dynamic = ["version"]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/samuelcolvin/FastUI"
|
||||
|
||||
|
||||
[tool.ruff]
|
||||
line-length = 120
|
||||
extend-select = ["Q", "RUF100", "UP", "I"]
|
||||
flake8-quotes = {inline-quotes = "single", multiline-quotes = "double"}
|
||||
format.quote-style="single"
|
||||
target-version = "py38"
|
||||
9
python/fastui/__init__.py
Normal file
9
python/fastui/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
||||
__version__ = '0.0.1'
|
||||
|
||||
import pydantic
|
||||
|
||||
from .components import AnyComponent
|
||||
|
||||
|
||||
class FastUI(pydantic.RootModel):
|
||||
root: AnyComponent
|
||||
@@ -8,10 +8,12 @@ All CamelCase names in the namespace should be components.
|
||||
from __future__ import annotations as _annotations
|
||||
|
||||
import typing
|
||||
|
||||
import pydantic
|
||||
|
||||
from . import extra, events
|
||||
from .table import Table, Column, Display
|
||||
from .. import events
|
||||
from . import extra
|
||||
from .table import Table
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
import pydantic.fields
|
||||
@@ -32,6 +34,7 @@ class Page(pydantic.BaseModel):
|
||||
"""
|
||||
Similar to `container` in many UI frameworks, this should be a reasonable root component for most pages.
|
||||
"""
|
||||
|
||||
children: list[AnyComponent]
|
||||
class_name: extra.ClassName | None = None
|
||||
type: typing.Literal['Page'] = 'Page'
|
||||
@@ -77,6 +80,5 @@ PydanticModel = typing.TypeVar('PydanticModel', bound=pydantic.BaseModel)
|
||||
|
||||
|
||||
AnyComponent = typing.Annotated[
|
||||
Text | Div | Page | Heading | Row | Col | Button | Modal | Table,
|
||||
pydantic.Field(discriminator='type')
|
||||
Text | Div | Page | Heading | Row | Col | Button | Modal | Table, pydantic.Field(discriminator='type')
|
||||
]
|
||||
@@ -1,4 +1,5 @@
|
||||
from typing import Annotated
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
ClassName = Annotated[str | list[str] | dict[str, bool | None], Field(serialization_alias='className')]
|
||||
@@ -1,39 +1,26 @@
|
||||
from __future__ import annotations as _annotations
|
||||
|
||||
import typing
|
||||
from enum import StrEnum
|
||||
|
||||
import pydantic
|
||||
|
||||
from . import extra, events
|
||||
from .. import events
|
||||
from ..display import Display
|
||||
from . import extra
|
||||
|
||||
# TODO allow dataclasses and dicts here too
|
||||
DataModel = typing.TypeVar('DataModel', bound=pydantic.BaseModel)
|
||||
|
||||
|
||||
class Display(StrEnum):
|
||||
"""
|
||||
How to a value.
|
||||
"""
|
||||
auto = 'auto' # default, same as None below
|
||||
plain = 'plain'
|
||||
datetime = 'datetime'
|
||||
date = 'date'
|
||||
duration = 'duration'
|
||||
as_title = 'as_title'
|
||||
markdown = 'markdown'
|
||||
json = 'json'
|
||||
inline_code = 'inline_code'
|
||||
|
||||
|
||||
class Column(pydantic.BaseModel):
|
||||
"""
|
||||
Description of a table column.
|
||||
"""
|
||||
|
||||
field: str
|
||||
display: Display | None = None
|
||||
title: str | None = None
|
||||
on_click: events.Event | None = pydantic.Field(None, serialization_alias='onClick')
|
||||
on_click: typing.Annotated[events.Event | None, pydantic.Field(serialization_alias='onClick')] = None
|
||||
class_name: extra.ClassName | None = None
|
||||
|
||||
|
||||
17
python/fastui/display.py
Normal file
17
python/fastui/display.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from enum import StrEnum
|
||||
|
||||
|
||||
class Display(StrEnum):
|
||||
"""
|
||||
How to a value.
|
||||
"""
|
||||
|
||||
auto = 'auto' # default, same as None below
|
||||
plain = 'plain'
|
||||
datetime = 'datetime'
|
||||
date = 'date'
|
||||
duration = 'duration'
|
||||
as_title = 'as_title'
|
||||
markdown = 'markdown'
|
||||
json = 'json'
|
||||
inline_code = 'inline_code'
|
||||
@@ -1,5 +1,6 @@
|
||||
from typing import Annotated, Literal
|
||||
from pydantic import Field, BaseModel
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class PageEvent(BaseModel):
|
||||
3
python/requirements/all.txt
Normal file
3
python/requirements/all.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
-r ./lint.txt
|
||||
-r ./pyproject.txt
|
||||
uvicorn[standard]
|
||||
2
python/requirements/lint.in
Normal file
2
python/requirements/lint.in
Normal file
@@ -0,0 +1,2 @@
|
||||
ruff
|
||||
pyright
|
||||
15
python/requirements/lint.txt
Normal file
15
python/requirements/lint.txt
Normal file
@@ -0,0 +1,15 @@
|
||||
#
|
||||
# This file is autogenerated by pip-compile with Python 3.11
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile --output-file=python/requirements/lint.txt --strip-extras python/requirements/lint.in
|
||||
#
|
||||
nodeenv==1.8.0
|
||||
# via pyright
|
||||
pyright==1.1.335
|
||||
# via -r python/requirements/lint.in
|
||||
ruff==0.1.5
|
||||
# via -r python/requirements/lint.in
|
||||
|
||||
# The following packages are considered to be unsafe in a requirements file:
|
||||
# setuptools
|
||||
31
python/requirements/pyproject.txt
Normal file
31
python/requirements/pyproject.txt
Normal file
@@ -0,0 +1,31 @@
|
||||
#
|
||||
# This file is autogenerated by pip-compile with Python 3.11
|
||||
# by the following command:
|
||||
#
|
||||
# pip-compile --output-file=python/requirements/pyproject.txt --strip-extras pyproject.toml
|
||||
#
|
||||
annotated-types==0.6.0
|
||||
# via pydantic
|
||||
anyio==3.7.1
|
||||
# via
|
||||
# fastapi
|
||||
# starlette
|
||||
fastapi==0.104.1
|
||||
# via fastui (pyproject.toml)
|
||||
idna==3.4
|
||||
# via anyio
|
||||
pydantic==2.5.0b1
|
||||
# via
|
||||
# fastapi
|
||||
# fastui (pyproject.toml)
|
||||
pydantic-core==2.14.1
|
||||
# via pydantic
|
||||
sniffio==1.3.0
|
||||
# via anyio
|
||||
starlette==0.27.0
|
||||
# via fastapi
|
||||
typing-extensions==4.8.0
|
||||
# via
|
||||
# fastapi
|
||||
# pydantic
|
||||
# pydantic-core
|
||||
@@ -15,7 +15,7 @@ export const HeadingComp: FC<HeadingProps> = (props) => {
|
||||
return <HeadingComponent text={text} className={useClassNameGenerator(className, props)} />
|
||||
}
|
||||
|
||||
function getComponent (level: 1 | 2 | 3 | 4 | 5 | 6): FC<{ text: string; className: string }> {
|
||||
function getComponent(level: 1 | 2 | 3 | 4 | 5 | 6): FC<{ text: string; className: string }> {
|
||||
switch (level) {
|
||||
case 1:
|
||||
return ({ text, className }) => <h1 className={className}>{text}</h1>
|
||||
@@ -81,7 +81,7 @@ interface WithChildren {
|
||||
children: FastProps[]
|
||||
}
|
||||
|
||||
function renderWithChildren<T extends WithChildren> (Component: FC<T>, props: T) {
|
||||
function renderWithChildren<T extends WithChildren>(Component: FC<T>, props: T) {
|
||||
const { children, ...rest } = props
|
||||
// TODO is there a way to make this type safe?
|
||||
return <Component {...(rest as any)}>{children}</Component>
|
||||
@@ -68,7 +68,7 @@ const request = async ({ url, method, headers, body }: Request): Promise<FastPro
|
||||
return data as FastProps
|
||||
}
|
||||
|
||||
export function FastUIController ({ rootUrl, pathSendMode, loading }: Props) {
|
||||
export function FastUIController({ rootUrl, pathSendMode, loading }: Props) {
|
||||
const [componentProps, setComponentProps] = useState<FastProps | null>(null)
|
||||
const { fullPath } = useContext(LocationContext)
|
||||
|
||||
@@ -14,7 +14,7 @@ export const ClassNameContext = createContext<ClassNameGenerator | null>(null)
|
||||
* @param props The full props object sent from the backend, this is passed to the class name generator.
|
||||
* @param dft default className to use if the class name generator is not set or returns undefined.
|
||||
*/
|
||||
export function useClassNameGenerator (classNameProp: ClassName, props: FastProps, dft?: ClassName): string {
|
||||
export function useClassNameGenerator(classNameProp: ClassName, props: FastProps, dft?: ClassName): string {
|
||||
const classNameGenerator = useContext(ClassNameContext)
|
||||
if (combineClassNameProp(classNameProp)) {
|
||||
if (!dft && classNameGenerator) {
|
||||
@@ -33,7 +33,7 @@ export function useClassNameGenerator (classNameProp: ClassName, props: FastProp
|
||||
* then we generate the default className and append the user's className to it.
|
||||
* @param classNameProp
|
||||
*/
|
||||
function combineClassNameProp (classNameProp: ClassName): boolean {
|
||||
function combineClassNameProp(classNameProp: ClassName): boolean {
|
||||
if (Array.isArray(classNameProp)) {
|
||||
// classNameProp is an array, check if it contains `+`
|
||||
return classNameProp.some((c) => c === '+')
|
||||
@@ -49,7 +49,7 @@ function combineClassNameProp (classNameProp: ClassName): boolean {
|
||||
}
|
||||
}
|
||||
|
||||
function combine (cn1: ClassName, cn2: ClassName): string {
|
||||
function combine(cn1: ClassName, cn2: ClassName): string {
|
||||
if (!cn1) {
|
||||
return renderClassName(cn2)
|
||||
} else if (!cn2) {
|
||||
@@ -63,7 +63,7 @@ function combine (cn1: ClassName, cn2: ClassName): string {
|
||||
* Renders the className to a string, removing plus signs.
|
||||
* @param className
|
||||
*/
|
||||
export function renderClassName (className: ClassName): string {
|
||||
export function renderClassName(className: ClassName): string {
|
||||
if (typeof className === 'string') {
|
||||
return className.replace(/^\+ /, '')
|
||||
} else if (Array.isArray(className)) {
|
||||
@@ -30,7 +30,7 @@ const DefaultErrorDisplay: ErrorDisplayType = ({ title, description, children })
|
||||
export const ErrorContext = createContext<ErrorContextType>({
|
||||
error: null,
|
||||
setError: () => null,
|
||||
DisplayError: DefaultErrorDisplay
|
||||
DisplayError: DefaultErrorDisplay,
|
||||
})
|
||||
|
||||
const MaybeError: FC<{ children: ReactNode }> = ({ children }) => {
|
||||
@@ -55,7 +55,7 @@ export const ErrorContextProvider: FC<Props> = ({ DisplayError, children }) => {
|
||||
console.warn('setting error:', error)
|
||||
setErrorState(error)
|
||||
},
|
||||
[setErrorState]
|
||||
[setErrorState],
|
||||
)
|
||||
const contextValue: ErrorContextType = { error, setError, DisplayError: DisplayError ?? DefaultErrorDisplay }
|
||||
|
||||
@@ -12,14 +12,14 @@ export interface GoToEvent {
|
||||
url: string
|
||||
}
|
||||
|
||||
function pageEventType (event: PageEvent): string {
|
||||
function pageEventType(event: PageEvent): string {
|
||||
return `fastui:${event.name}`
|
||||
}
|
||||
|
||||
export function useFireEvent (): { fireEvent: (event?: PageEvent | GoToEvent) => void } {
|
||||
export function useFireEvent(): { fireEvent: (event?: PageEvent | GoToEvent) => void } {
|
||||
const location = useContext(LocationContext)
|
||||
|
||||
function fireEvent (event?: PageEvent | GoToEvent) {
|
||||
function fireEvent(event?: PageEvent | GoToEvent) {
|
||||
if (!event) {
|
||||
return
|
||||
}
|
||||
@@ -38,7 +38,7 @@ export function useFireEvent (): { fireEvent: (event?: PageEvent | GoToEvent) =>
|
||||
return { fireEvent }
|
||||
}
|
||||
|
||||
export function useEventListenerToggle (event?: PageEvent, initialState = false): [boolean, () => void] {
|
||||
export function useEventListenerToggle(event?: PageEvent, initialState = false): [boolean, () => void] {
|
||||
const [state, setState] = useState(initialState)
|
||||
|
||||
const toggle = useCallback(() => setState((state) => !state), [])
|
||||
@@ -2,7 +2,7 @@ import { createContext, ReactNode, useEffect, useState, useCallback, useContext
|
||||
|
||||
import { ErrorContext } from './error'
|
||||
|
||||
function parseLocation (): string {
|
||||
function parseLocation(): string {
|
||||
const { href, origin } = window.location
|
||||
// remove origin from the beginning of href
|
||||
return href.slice(origin.length)
|
||||
@@ -17,12 +17,12 @@ const initialPath = parseLocation()
|
||||
|
||||
const initialState = {
|
||||
fullPath: initialPath,
|
||||
goto: () => null
|
||||
goto: () => null,
|
||||
}
|
||||
|
||||
export const LocationContext = createContext<LocationState>(initialState)
|
||||
|
||||
export function LocationProvider ({ children }: { children: ReactNode }) {
|
||||
export function LocationProvider({ children }: { children: ReactNode }) {
|
||||
const [fullPath, setFullPath] = useState(initialPath)
|
||||
const { setError } = useContext(ErrorContext)
|
||||
|
||||
@@ -66,8 +66,8 @@ export function LocationProvider ({ children }: { children: ReactNode }) {
|
||||
setError(null)
|
||||
setFullPath(newPath)
|
||||
},
|
||||
[setError]
|
||||
)
|
||||
[setError],
|
||||
),
|
||||
}
|
||||
|
||||
return <LocationContext.Provider value={value}>{children}</LocationContext.Provider>
|
||||
@@ -20,7 +20,7 @@ export interface FastUIProps {
|
||||
customRender?: CustomRender
|
||||
}
|
||||
|
||||
export function FastUI (props: FastUIProps) {
|
||||
export function FastUI(props: FastUIProps) {
|
||||
const { classNameGenerator, DisplayError, customRender, ...rest } = props
|
||||
return (
|
||||
<div className="fastui">
|
||||
24
tsconfig.json
Normal file
24
tsconfig.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "./react-dist",
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": false,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": ["react"]
|
||||
}
|
||||
Reference in New Issue
Block a user