Add pages abstraction
This commit is contained in:
@@ -1,5 +1,11 @@
|
|||||||
# Soul Diary
|
# Soul Diary
|
||||||
|
|
||||||
|
## ToDo
|
||||||
|
|
||||||
|
1. Refactoring: create separate pages and user controls
|
||||||
|
2. Implement S3 backend client
|
||||||
|
3. Implement FTP backend client
|
||||||
|
|
||||||
## User Flow
|
## User Flow
|
||||||
|
|
||||||
### Soul Diary Server
|
### Soul Diary Server
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
|
|||||||
|
|
||||||
|
|
||||||
class APISettings(BaseSettings):
|
class APISettings(BaseSettings):
|
||||||
model_config = SettingsConfigDict(prefix="backend_api_")
|
model_config = SettingsConfigDict(env_prefix="backend_api_")
|
||||||
|
|
||||||
port: conint(ge=1, le=65535) = 8001
|
port: conint(ge=1, le=65535) = 8001
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,6 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
|
|||||||
|
|
||||||
|
|
||||||
class DatabaseSettings(BaseSettings):
|
class DatabaseSettings(BaseSettings):
|
||||||
model_config = SettingsConfigDict(prefix="backend_database_")
|
model_config = SettingsConfigDict(env_prefix="backend_database_")
|
||||||
|
|
||||||
dsn: AnyUrl = "sqlite+aiosqlite:///soul_diary.sqlite3"
|
dsn: AnyUrl = "sqlite+aiosqlite:///soul_diary.sqlite3"
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ from soul_diary.ui.app.routes import AUTH, SENSE_LIST
|
|||||||
async def middleware(page: flet.Page, params: Params, basket: Basket):
|
async def middleware(page: flet.Page, params: Params, basket: Basket):
|
||||||
local_storage = LocalStorage(client_storage=page.client_storage)
|
local_storage = LocalStorage(client_storage=page.client_storage)
|
||||||
auth_data = await local_storage.get_auth_data()
|
auth_data = await local_storage.get_auth_data()
|
||||||
# await local_storage._client_storage.clear_async()
|
|
||||||
if auth_data is None:
|
if auth_data is None:
|
||||||
await page.go_async(AUTH)
|
await page.go_async(AUTH)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
from typing import Callable
|
|
||||||
|
|
||||||
import flet
|
|
||||||
from flet_route import Basket, Params
|
|
||||||
|
|
||||||
|
|
||||||
class BaseMiddleware:
|
|
||||||
async def __call__(
|
|
||||||
self,
|
|
||||||
page: flet.Page,
|
|
||||||
params: Params,
|
|
||||||
basket: Basket,
|
|
||||||
next_handler: Callable,
|
|
||||||
):
|
|
||||||
raise NotImplementedError
|
|
||||||
61
soul_diary/ui/app/pages/auth/choose_backend.py
Normal file
61
soul_diary/ui/app/pages/auth/choose_backend.py
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
from functools import partial
|
||||||
|
|
||||||
|
import flet
|
||||||
|
|
||||||
|
from soul_diary.ui.app.models import BackendType
|
||||||
|
from soul_diary.ui.app.pages.base import BasePage
|
||||||
|
|
||||||
|
|
||||||
|
class ChooseBackendPage(BasePage):
|
||||||
|
BACKENDS = {
|
||||||
|
BackendType.LOCAL: "Локально",
|
||||||
|
BackendType.SOUL: "Soul Diary сервер",
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, backend: BackendType | None = None):
|
||||||
|
self.backend: BackendType | None = backend
|
||||||
|
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
def build(self) -> flet.Container:
|
||||||
|
label = flet.Container(
|
||||||
|
content=flet.Text("Выберите сервер"),
|
||||||
|
alignment=flet.alignment.center,
|
||||||
|
)
|
||||||
|
dropdown = flet.Dropdown(
|
||||||
|
label="Бэкенд",
|
||||||
|
options=[
|
||||||
|
flet.dropdown.Option(text=text, key=key.value)
|
||||||
|
for key, text in self.BACKENDS.items()
|
||||||
|
],
|
||||||
|
value=None if self.backend is None else self.backend.value,
|
||||||
|
on_change=self.callback_change_backend,
|
||||||
|
)
|
||||||
|
connect_button = flet.ElevatedButton(
|
||||||
|
"Выбрать",
|
||||||
|
width=300,
|
||||||
|
height=50,
|
||||||
|
on_click=partial(self.callback_choose_backend, dropdown=dropdown),
|
||||||
|
)
|
||||||
|
|
||||||
|
return flet.Container(
|
||||||
|
content=flet.Column(
|
||||||
|
controls=[label, dropdown, connect_button],
|
||||||
|
width=300,
|
||||||
|
),
|
||||||
|
alignment=flet.alignment.center,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def callback_change_backend(self, event: flet.ControlEvent):
|
||||||
|
self.backend = BackendType(event.control.value)
|
||||||
|
event.control.error_text = None
|
||||||
|
await event.control.update_async()
|
||||||
|
|
||||||
|
async def callback_choose_backend(self, event: flet.ControlEvent, dropdown: flet.Dropdown):
|
||||||
|
if self.backend == BackendType.LOCAL:
|
||||||
|
await self.credentials_view(page=event.page)
|
||||||
|
elif self.backend == BackendType.SOUL:
|
||||||
|
await self.soul_server_data_view(page=event.page)
|
||||||
|
else:
|
||||||
|
dropdown.error_text = "Выберите тип бекенда"
|
||||||
|
await self.update_async()
|
||||||
8
soul_diary/ui/app/pages/auth/soul_backend_data.py
Normal file
8
soul_diary/ui/app/pages/auth/soul_backend_data.py
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import flet
|
||||||
|
|
||||||
|
from soul_diary.ui.app.pages.base import BasePage
|
||||||
|
|
||||||
|
|
||||||
|
class SoulBackendDataPage(BasePage):
|
||||||
|
def build(self) -> flet.Container:
|
||||||
|
pass
|
||||||
7
soul_diary/ui/app/pages/base.py
Normal file
7
soul_diary/ui/app/pages/base.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import flet
|
||||||
|
|
||||||
|
|
||||||
|
class BasePage(flet.UserControl):
|
||||||
|
async def apply(self, page: flet.Page):
|
||||||
|
await page.clean_async()
|
||||||
|
await page.add_async(self)
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import Any, Callable, Sequence
|
from typing import Any
|
||||||
|
|
||||||
import flet
|
import flet
|
||||||
from pydantic import AnyHttpUrl
|
from pydantic import AnyHttpUrl
|
||||||
@@ -8,7 +8,6 @@ from pydantic import AnyHttpUrl
|
|||||||
from soul_diary.ui.app.backend.exceptions import IncorrectCredentialsException, UserAlreadyExistsException
|
from soul_diary.ui.app.backend.exceptions import IncorrectCredentialsException, UserAlreadyExistsException
|
||||||
from soul_diary.ui.app.backend.soul import SoulBackend
|
from soul_diary.ui.app.backend.soul import SoulBackend
|
||||||
from soul_diary.ui.app.local_storage import LocalStorage
|
from soul_diary.ui.app.local_storage import LocalStorage
|
||||||
from soul_diary.ui.app.middlewares.base import BaseMiddleware
|
|
||||||
from soul_diary.ui.app.models import BackendType, Options
|
from soul_diary.ui.app.models import BackendType, Options
|
||||||
from soul_diary.ui.app.routes import AUTH, SENSE_LIST
|
from soul_diary.ui.app.routes import AUTH, SENSE_LIST
|
||||||
from soul_diary.ui.app.views.exceptions import SoulServerIncorrectURL
|
from soul_diary.ui.app.views.exceptions import SoulServerIncorrectURL
|
||||||
@@ -21,7 +20,6 @@ class AuthView(BaseView):
|
|||||||
local_storage: LocalStorage,
|
local_storage: LocalStorage,
|
||||||
backend: BackendType | None = None,
|
backend: BackendType | None = None,
|
||||||
backend_data: dict[str, Any] | None = None,
|
backend_data: dict[str, Any] | None = None,
|
||||||
middlewares: Sequence[BaseMiddleware | Callable] = (),
|
|
||||||
):
|
):
|
||||||
self.top_container: flet.Container
|
self.top_container: flet.Container
|
||||||
self.center_container: flet.Container
|
self.center_container: flet.Container
|
||||||
@@ -33,7 +31,7 @@ class AuthView(BaseView):
|
|||||||
self.username: str | None = None
|
self.username: str | None = None
|
||||||
self.password: str | None = None
|
self.password: str | None = None
|
||||||
|
|
||||||
super().__init__(local_storage=local_storage, middlewares=middlewares)
|
super().__init__(local_storage=local_storage)
|
||||||
|
|
||||||
async def clear(self):
|
async def clear(self):
|
||||||
self.top_container.content = None
|
self.top_container.content = None
|
||||||
@@ -81,7 +79,6 @@ class AuthView(BaseView):
|
|||||||
label = flet.Text("Выберите сервер")
|
label = flet.Text("Выберите сервер")
|
||||||
self.top_container.content = label
|
self.top_container.content = label
|
||||||
|
|
||||||
backend_controls = flet.Column()
|
|
||||||
backend_dropdown = flet.Dropdown(
|
backend_dropdown = flet.Dropdown(
|
||||||
label="Бэкенд",
|
label="Бэкенд",
|
||||||
options=[
|
options=[
|
||||||
@@ -91,14 +88,7 @@ class AuthView(BaseView):
|
|||||||
value=None if self.backend is None else self.backend.value,
|
value=None if self.backend is None else self.backend.value,
|
||||||
on_change=self.callback_change_backend,
|
on_change=self.callback_change_backend,
|
||||||
)
|
)
|
||||||
|
self.center_container.content = backend_dropdown
|
||||||
container = flet.Container(
|
|
||||||
content=flet.Column(
|
|
||||||
controls=[backend_dropdown, backend_controls],
|
|
||||||
width=300,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
self.center_container.content = container
|
|
||||||
|
|
||||||
connect_button = flet.ElevatedButton(
|
connect_button = flet.ElevatedButton(
|
||||||
"Выбрать",
|
"Выбрать",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import asyncio
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from functools import partial, reduce
|
from typing import Any, Callable
|
||||||
from typing import Any, Callable, Sequence
|
|
||||||
|
|
||||||
import flet
|
import flet
|
||||||
from flet_route import Basket, Params
|
from flet_route import Basket, Params
|
||||||
@@ -10,7 +10,6 @@ from soul_diary.ui.app.backend.exceptions import NonAuthenticatedException
|
|||||||
from soul_diary.ui.app.backend.local import LocalBackend
|
from soul_diary.ui.app.backend.local import LocalBackend
|
||||||
from soul_diary.ui.app.backend.soul import SoulBackend
|
from soul_diary.ui.app.backend.soul import SoulBackend
|
||||||
from soul_diary.ui.app.local_storage import LocalStorage
|
from soul_diary.ui.app.local_storage import LocalStorage
|
||||||
from soul_diary.ui.app.middlewares.base import BaseMiddleware
|
|
||||||
from soul_diary.ui.app.models import BackendType
|
from soul_diary.ui.app.models import BackendType
|
||||||
|
|
||||||
|
|
||||||
@@ -29,21 +28,6 @@ def view(initial: bool = False, disabled: bool = False):
|
|||||||
return decorator
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
def setup_middlewares(function: Callable):
|
|
||||||
async def wrapper(self, page: flet.Page, params: Params, basket: Basket) -> flet.View:
|
|
||||||
entrypoint = partial(function, self)
|
|
||||||
if self.middlewares:
|
|
||||||
entrypoint = partial(self.middlewares[-1], next_handler=entrypoint)
|
|
||||||
entrypoint = reduce(
|
|
||||||
lambda entrypoint, function: partial(entrypoint, next_handler=function),
|
|
||||||
self.middlewares[-2::-1],
|
|
||||||
entrypoint,
|
|
||||||
)
|
|
||||||
return await entrypoint(page=page, params=params, basket=basket)
|
|
||||||
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
|
|
||||||
class MetaView(type):
|
class MetaView(type):
|
||||||
def __init__(cls, name: str, bases: tuple[type], attrs: dict[str, Any]):
|
def __init__(cls, name: str, bases: tuple[type], attrs: dict[str, Any]):
|
||||||
super().__init__(name, bases, attrs)
|
super().__init__(name, bases, attrs)
|
||||||
@@ -76,28 +60,23 @@ class BaseView(metaclass=MetaView):
|
|||||||
is_abstract = True
|
is_abstract = True
|
||||||
_initial_view: Callable | None
|
_initial_view: Callable | None
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, local_storage: LocalStorage):
|
||||||
self,
|
|
||||||
local_storage: LocalStorage,
|
|
||||||
middlewares: Sequence[BaseMiddleware | Callable] = (),
|
|
||||||
):
|
|
||||||
self.local_storage = local_storage
|
self.local_storage = local_storage
|
||||||
self.middlewares = middlewares
|
|
||||||
|
|
||||||
self.container: flet.Container
|
self.container: flet.Container
|
||||||
self.stack: flet.Stack
|
|
||||||
self.view: flet.View
|
self.view: flet.View
|
||||||
|
|
||||||
@setup_middlewares
|
|
||||||
async def entrypoint(self, page: flet.Page, params: Params, basket: Basket) -> flet.View:
|
async def entrypoint(self, page: flet.Page, params: Params, basket: Basket) -> flet.View:
|
||||||
self.container = flet.Container()
|
self.container = flet.Container()
|
||||||
self.stack = flet.Stack(controls=[self.container])
|
self.view = flet.View(controls=[self.container])
|
||||||
self.view = flet.View(controls=[self.stack], route="/test")
|
|
||||||
|
|
||||||
await self.setup()
|
await self.setup()
|
||||||
await self.clear()
|
await self.clear()
|
||||||
self.clear_data()
|
self.clear_data()
|
||||||
await self.run_initial_view(page=page)
|
|
||||||
|
loop = asyncio.get_running_loop()
|
||||||
|
loop.create_task(self.run_initial_view(page=page))
|
||||||
|
|
||||||
return self.view
|
return self.view
|
||||||
|
|
||||||
async def setup(self):
|
async def setup(self):
|
||||||
@@ -111,21 +90,20 @@ class BaseView(metaclass=MetaView):
|
|||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def in_progress(self, page: flet.Page, tooltip: str | None = None):
|
async def in_progress(self, page: flet.Page, tooltip: str | None = None):
|
||||||
for control in self.stack.controls:
|
self.container.disabled = True
|
||||||
control.disabled = True
|
page.splash = flet.Column(
|
||||||
self.stack.controls.append(
|
controls=[flet.Container(
|
||||||
flet.Container(
|
|
||||||
content=flet.ProgressRing(tooltip=tooltip),
|
content=flet.ProgressRing(tooltip=tooltip),
|
||||||
alignment=flet.alignment.center,
|
alignment=flet.alignment.center,
|
||||||
),
|
)],
|
||||||
|
alignment=flet.MainAxisAlignment.CENTER,
|
||||||
)
|
)
|
||||||
await page.update_async()
|
await page.update_async()
|
||||||
|
|
||||||
yield
|
yield
|
||||||
|
|
||||||
self.stack.controls.pop()
|
self.container.disabled = False
|
||||||
for control in self.stack.controls:
|
page.splash = None
|
||||||
control.disabled = False
|
|
||||||
await page.update_async()
|
await page.update_async()
|
||||||
|
|
||||||
async def run_initial_view(self, page: flet.Page):
|
async def run_initial_view(self, page: flet.Page):
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
from functools import partial
|
from functools import partial
|
||||||
from typing import Awaitable, Callable, Sequence
|
|
||||||
|
|
||||||
import flet
|
import flet
|
||||||
|
|
||||||
from soul_diary.ui.app.local_storage import LocalStorage
|
from soul_diary.ui.app.local_storage import LocalStorage
|
||||||
from soul_diary.ui.app.middlewares.base import BaseMiddleware
|
|
||||||
from soul_diary.ui.app.models import Emotion
|
from soul_diary.ui.app.models import Emotion
|
||||||
from soul_diary.ui.app.routes import SENSE_ADD, SENSE_LIST
|
from soul_diary.ui.app.routes import SENSE_ADD, SENSE_LIST
|
||||||
from .base import BaseView, view
|
from .base import BaseView, view
|
||||||
@@ -14,7 +12,6 @@ class SenseAddView(BaseView):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
local_storage: LocalStorage,
|
local_storage: LocalStorage,
|
||||||
middlewares: Sequence[BaseMiddleware | Callable[[flet.Page], Awaitable]] = (),
|
|
||||||
):
|
):
|
||||||
self.title: flet.Text
|
self.title: flet.Text
|
||||||
self.content_container: flet.Container
|
self.content_container: flet.Container
|
||||||
@@ -25,7 +22,7 @@ class SenseAddView(BaseView):
|
|||||||
self.body: str | None = None
|
self.body: str | None = None
|
||||||
self.desires: str | None = None
|
self.desires: str | None = None
|
||||||
|
|
||||||
super().__init__(local_storage=local_storage, middlewares=middlewares)
|
super().__init__(local_storage=local_storage)
|
||||||
|
|
||||||
async def clear(self):
|
async def clear(self):
|
||||||
self.title.value = ""
|
self.title.value = ""
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from typing import Awaitable, Callable, Sequence
|
|
||||||
|
|
||||||
import flet
|
import flet
|
||||||
from soul_diary.ui.app.backend.exceptions import NonAuthenticatedException
|
from soul_diary.ui.app.backend.exceptions import NonAuthenticatedException
|
||||||
|
|
||||||
from soul_diary.ui.app.local_storage import LocalStorage
|
from soul_diary.ui.app.local_storage import LocalStorage
|
||||||
from soul_diary.ui.app.middlewares.base import BaseMiddleware
|
from soul_diary.ui.app.models import Sense
|
||||||
from soul_diary.ui.app.models import BackendType, Sense
|
|
||||||
from soul_diary.ui.app.routes import AUTH, SENSE_ADD, SENSE_LIST
|
from soul_diary.ui.app.routes import AUTH, SENSE_ADD, SENSE_LIST
|
||||||
from .base import BaseView, view
|
from .base import BaseView, view
|
||||||
|
|
||||||
@@ -15,13 +13,12 @@ class SenseListView(BaseView):
|
|||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
local_storage: LocalStorage,
|
local_storage: LocalStorage,
|
||||||
middlewares: Sequence[BaseMiddleware | Callable[[flet.Page], Awaitable]] = (),
|
|
||||||
):
|
):
|
||||||
self.cards: flet.Column
|
self.cards: flet.Column
|
||||||
|
|
||||||
self.local_storage = local_storage
|
self.local_storage = local_storage
|
||||||
|
|
||||||
super().__init__(local_storage=local_storage, middlewares=middlewares)
|
super().__init__(local_storage=local_storage)
|
||||||
|
|
||||||
async def setup(self):
|
async def setup(self):
|
||||||
self.cards = flet.Column(alignment=flet.alignment.center, width=400)
|
self.cards = flet.Column(alignment=flet.alignment.center, width=400)
|
||||||
@@ -97,5 +94,6 @@ class SenseListView(BaseView):
|
|||||||
|
|
||||||
async def callback_logout(self, event: flet.ControlEvent):
|
async def callback_logout(self, event: flet.ControlEvent):
|
||||||
backend_client = await self.get_backend_client()
|
backend_client = await self.get_backend_client()
|
||||||
await backend_client.logout()
|
async with self.in_progress(page=event.page):
|
||||||
|
await backend_client.logout()
|
||||||
await event.page.go_async(AUTH)
|
await event.page.go_async(AUTH)
|
||||||
|
|||||||
@@ -25,8 +25,9 @@ class WebService(ServiceMixin):
|
|||||||
|
|
||||||
async def start(self):
|
async def start(self):
|
||||||
app = flet_fastapi.app(SoulDiaryApp(
|
app = flet_fastapi.app(SoulDiaryApp(
|
||||||
backend=BackendType.SOUL,
|
# backend=BackendType.SOUL,
|
||||||
backend_data=self._backend_data,
|
# backend_data=self._backend_data,
|
||||||
|
# backend=BackendType.LOCAL,
|
||||||
).run)
|
).run)
|
||||||
config = uvicorn.Config(app=app, host="0.0.0.0", port=self._port)
|
config = uvicorn.Config(app=app, host="0.0.0.0", port=self._port)
|
||||||
server = UvicornServer(config)
|
server = UvicornServer(config)
|
||||||
|
|||||||
Reference in New Issue
Block a user