Skip to content

Commit 4cf32fa

Browse files
authored
fix: Various UI bugs (#15)
1 parent b9861de commit 4cf32fa

10 files changed

Lines changed: 184 additions & 78 deletions

File tree

fastapi_forge/frontend/components/model_row.py

Lines changed: 33 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,20 @@ def __init__(
1212
):
1313
super().__init__(wrap=False)
1414
self.model = model
15-
self.is_editing = False
15+
self.is_selected_row = model == state.selected_model
1616
self.color = color
17+
self.is_editing = False
1718
self._build()
1819

1920
def _build(self) -> None:
20-
with self.classes("w-full flex items-center justify-between cursor-pointer"):
21+
self.on("click", lambda: state.select_model(self.model))
22+
base_classes = "w-full flex items-center justify-between cursor-pointer p-2 rounded transition-all"
23+
if self.is_selected_row:
24+
base_classes += " bg-blue-100 dark:bg-blue-900 border-l-4 border-blue-500"
25+
else:
26+
base_classes += " hover:bg-gray-100 dark:hover:bg-gray-800"
27+
28+
with self.classes(base_classes):
2129
self.name_label = ui.label(text=self.model.name).classes("self-center")
2230
if self.color:
2331
self.name_label.classes(add=self.color)
@@ -28,18 +36,30 @@ def _build(self) -> None:
2836
)
2937
self.name_label.bind_visibility_from(self, "is_editing", lambda x: not x)
3038

31-
self.on("click", lambda: state.select_model(self.model))
39+
with ui.row().classes("flex-nowrap gap-2 min-w-fit"):
40+
self.edit_button = (
41+
ui.button(
42+
icon="edit",
43+
)
44+
.on("click.stop", self._toggle_edit)
45+
.bind_visibility_from(self, "is_editing", lambda x: not x)
46+
.classes("min-w-fit")
47+
)
3248

33-
with ui.row().classes("gap-2"):
34-
self.edit_button = ui.button(
35-
icon="edit",
36-
on_click=self._toggle_edit,
37-
).bind_visibility_from(self, "is_editing", lambda x: not x)
38-
self.save_button = ui.button(
39-
icon="save",
40-
on_click=self._save_model,
41-
).bind_visibility_from(self, "is_editing")
42-
ui.button(icon="delete", on_click=self._delete_model)
49+
self.save_button = (
50+
ui.button(
51+
icon="save",
52+
)
53+
.on("click.stop", self._save_model)
54+
.bind_visibility_from(self, "is_editing")
55+
.classes("min-w-fit")
56+
)
57+
58+
ui.button(
59+
icon="delete",
60+
).on("click.stop", lambda: state.delete_model(self.model)).classes(
61+
"min-w-fit"
62+
)
4363

4464
def _toggle_edit(self) -> None:
4565
self.is_editing = not self.is_editing
@@ -49,6 +69,3 @@ def _save_model(self) -> None:
4969
if new_name:
5070
state.update_model_name(self.model, new_name)
5171
self.is_editing = False
52-
53-
def _delete_model(self) -> None:
54-
state.delete_model(self.model)

fastapi_forge/frontend/constants.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
from typing import Any
22

3+
SELECTED_MODEL_TEXT_COLOR = "text-black-500 dark:text-amber-300"
4+
35
FIELD_COLUMNS: list[dict[str, Any]] = [
46
{
57
"name": "name",

fastapi_forge/frontend/main.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,12 @@ def load_initial_project(path: Path) -> p.ProjectSpec:
3838
def create_ui_components() -> None:
3939
"""Create all UI components"""
4040
with ui.column().classes("w-full h-full items-center justify-center mt-4"):
41-
ModelEditorPanel().classes("no-shadow min-w-[600px]")
41+
ModelEditorPanel().classes(
42+
"shadow-2xl dark:shadow-none min-w-[700px] max-w-[800px]"
43+
)
4244

43-
ModelPanel()
44-
ProjectConfigPanel()
45+
ModelPanel().classes("shadow-xl dark:shadow-none")
46+
ProjectConfigPanel().classes("shadow-xl dark:shadow-none")
4547

4648

4749
def run_ui(reload: bool) -> None:

fastapi_forge/frontend/panels/model_editor_panel.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ def __init__(self):
2323
self.visible = False
2424

2525
state.select_model_fn = self.set_selected_model
26+
state.deselect_model_fn = self.deselect_model
27+
state.render_model_editor_fn = self.refresh
2628

2729
self.add_field_modal: AddFieldModal = AddFieldModal(
2830
on_add_field=self._handle_modal_add_field,
@@ -298,6 +300,10 @@ def _validate_field_input(
298300
return False
299301
return True
300302

303+
def refresh(self) -> None:
304+
self._refresh_table(state.selected_model.fields)
305+
self._refresh_relationship_table(state.selected_model.relationships)
306+
301307
def _refresh_table(self, fields: list[ModelField]) -> None:
302308
if state.selected_model is None:
303309
return
@@ -513,3 +519,6 @@ def set_selected_model(self, model: Model) -> None:
513519
self._refresh_table(model.fields)
514520
self._refresh_relationship_table(model.relationships)
515521
self.visible = True
522+
523+
def deselect_model(self) -> None:
524+
self.visible = False

fastapi_forge/frontend/panels/model_panel.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from pydantic import ValidationError
55

66
from fastapi_forge.frontend import ModelCreate, ModelRow
7+
from fastapi_forge.frontend.constants import SELECTED_MODEL_TEXT_COLOR
78
from fastapi_forge.frontend.notifications import notify_validation_error
89
from fastapi_forge.frontend.state import state
910
from fastapi_forge.project_io import ProjectExporter
@@ -12,7 +13,6 @@
1213
class ModelPanel(ui.left_drawer):
1314
def __init__(self):
1415
super().__init__(value=True, elevated=False, bottom_corner=True)
15-
self.classes("border-right[1px]")
1616

1717
state.render_models_fn = self._render_models
1818

@@ -58,9 +58,11 @@ def _render_models(self) -> None:
5858

5959
with self.model_list:
6060
for model in state.models:
61-
is_auth_user = model.name == "auth_user"
62-
color = "text-green-500" if is_auth_user else None
6361
ModelRow(
6462
model,
65-
color=color,
63+
color=(
64+
SELECTED_MODEL_TEXT_COLOR
65+
if model == state.selected_model
66+
else None
67+
),
6668
)

fastapi_forge/frontend/panels/project_config_panel.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,11 +200,41 @@ def _handle_builtin_auth_change(self, event: ValueChangeEventArguments) -> None:
200200
state.render_models_fn()
201201
ui.notify("The 'auth_user' model has been deleted.", type="positive")
202202

203+
async def _warn_override(self) -> bool:
204+
"""Show a confirmation dialog if the project already exists."""
205+
dialog = ui.dialog()
206+
with dialog, ui.card().classes("w-full max-w-md p-6 text-center"):
207+
ui.icon("warning", color="orange-500").classes("text-4xl self-center")
208+
ui.markdown(
209+
f"Project '{state.project_name}' already exists!\n\n"
210+
"This will **permanently overwrite** the existing project directory.\n"
211+
"Are you sure you want to continue?"
212+
).classes("text-center")
213+
214+
with ui.row().classes("w-full justify-center gap-4 mt-4"):
215+
ui.button("Cancel", color="primary", on_click=dialog.close)
216+
ui.button(
217+
"Overwrite", color="negative", on_click=lambda: dialog.submit(True)
218+
)
219+
220+
return await dialog
221+
203222
async def _create_project(self) -> None:
204-
"""Generate the project based on current state"""
223+
"""Generate the project based on the current state."""
224+
project_path = Path(state.project_name)
225+
226+
if project_path.exists():
227+
try:
228+
override = await self._warn_override()
229+
if not override:
230+
ui.notify("Project generation cancelled.", type="warning")
231+
return
232+
except Exception as e:
233+
ui.notify(f"Error displaying confirmation: {e}", type="negative")
234+
return
235+
205236
self.create_button.classes("hidden")
206237
self.loading_spinner.classes(remove="hidden")
207-
208238
ongoing_notification = ui.notification("Generating project...")
209239

210240
try:

0 commit comments

Comments
 (0)