First Working version. I can add table

This commit is contained in:
2025-05-10 16:55:52 +02:00
parent b708ef2c46
commit 2daff83e67
157 changed files with 17282 additions and 12 deletions

View File

@@ -0,0 +1,22 @@
import logging
from fasthtml.fastapp import fast_app
from components.tabs.constants import Routes
from core.instance_manager import InstanceManager
logger = logging.getLogger("TabsApp")
tabs_app, rt = fast_app()
@rt(Routes.SelectTab)
def post(session, _id: str, tab_id: str):
instance = InstanceManager.get(session, _id)
instance.select_tab_by_id(tab_id)
return instance
@rt(Routes.RemoveTab)
def post(session, _id: str, tab_id: str):
instance = InstanceManager.get(session, _id)
instance.remove_tab(tab_id)
return instance

View File

View File

View File

@@ -0,0 +1,45 @@
.tabs {
background-color: var(--color-base-200);
color: color-mix(in oklab, var(--color-base-content) 50%, transparent);
border-radius: .5rem;
width: 100%;
height: 100%;
}
.tabs-content {
display: block;
width: 100%;
height: 100%;
background-color: var(--color-base-100);
padding: 4px;
}
.tabs-tab {
cursor: pointer;
appearance: none;
text-align: center;
user-select: none;
font-size: .875rem;
display: flex;
margin: 4px 6px 0 6px;
padding: 0 6px;
align-items: center;
border-radius: .25rem;
}
.tabs-tab:hover {
color: var(--color-base-content); /* Change text color on hover */
}
.tabs-label {
max-width: 150px;
}
.tabs-active {
--depth: 1;
background-color: var(--color-base-100);
color: var(--color-base-content);
border-radius: .25rem;
box-shadow: 0 1px oklch(100% 0 0/calc(var(--depth) * .1)) inset, 0 1px 1px -1px color-mix(in oklab, var(--color-neutral) calc(var(--depth) * 50%), #0000), 0 1px 6px -4px color-mix(in oklab, var(--color-neutral) calc(var(--depth) * 100%), #0000);
}

View File

@@ -0,0 +1,3 @@
function bindTabs(tabsId) {
bindTooltipsWithDelegation(tabsId)
}

View File

@@ -0,0 +1,192 @@
import dataclasses
import logging
from fasthtml.components import *
from fasthtml.xtend import Script
from assets.icons import icon_dismiss_regular
from components.BaseComponent import BaseComponent
from components.tabs.constants import MY_TABS_INSTANCE_ID, Routes, ROUTE_ROOT
from components_helpers import mk_ellipsis, mk_tooltip_container
from core.instance_manager import InstanceManager
from core.utils import get_unique_id
logger = logging.getLogger("MyTabs")
@dataclasses.dataclass
class Tab:
id: str # unique id for the table
title: str # title to display
content: str #
key: str | tuple = None # another way to retrieve the tab, based on a key
icon: str = None
active: bool = False
class MyTabs(BaseComponent):
def __init__(self, session: dict, _id: str):
super().__init__(session, _id)
self.tabs = [
]
self.tabs_by_key = {}
def request_new_tab_id(self):
return get_unique_id(self._id)
def select_tab_by_id(self, tab_id):
"""
Sets the active state of a tab given its unique identifier. This allows toggling
the currently active tab in a group of tabs. Only one tab is active at a time.
:param tab_id: A unique identifier for the tab to be activated. Must correspond
to the id of one of the tabs present in the `self.tabs` list.
:type tab_id: Any
:return: None
"""
for tab in self.tabs:
tab.active = tab.id == tab_id
def select_tab_by_key(self, key):
"""
Selects a tab using the specified key.
:param key: The unique key identifying the tab to be selected.
:type key: Any
:return: None
:rtype: NoneType
"""
if key in self.tabs_by_key:
self.select_tab_by_id(self.tabs_by_key[key].id)
def remove_tab(self, tab_id):
"""
Removes a tab with the specified ID from the current list of tabs.
:param tab_id: The unique identifier of the tab to be removed.
:type tab_id: Any
:return: None
"""
logger.debug(f"'remove_tab' {tab_id=}, {self.tabs=}")
to_remove = next(filter(lambda t: t.id == tab_id, self.tabs), None)
if to_remove is None:
logger.warning(f" No tab found with id {tab_id=}")
return
# dispose the content if required
if hasattr(to_remove.content, "dispose") and callable(to_remove.content.dispose):
to_remove.content.dispose()
InstanceManager.remove(self._session, to_remove.content)
# remove the tab
self.tabs = [tab for tab in self.tabs if tab.id != tab_id]
# clean the tab by key
if to_remove.key in self.tabs_by_key:
del self.tabs_by_key[to_remove.key]
# Check if there is no active tab; if so, call select_tab for the first tab (if available)
if self.tabs and not any(tab.active for tab in self.tabs):
self.select_tab_by_id(self.tabs[0].id)
def add_tab(self, title, content, key: str | tuple = None, tab_id: str = None, icon=None):
"""
Adds a new tab with the specified title, content, and optional icon, then selects
the newly created tab by its unique identifier.
:param title: The title of the new tab.
:param content: The content to be displayed within the tab.
:param key:
:param tab_id:
:param icon: An optional icon to represent the tab visually.
:return: The unique identifier of the newly created tab.
"""
logger.debug(f"'add_tab' {title=}, {content=}, {key=}")
if key in self.tabs_by_key:
# deal with potentially an already known entry
older_tab = self.tabs_by_key[key]
if older_tab.content != content:
self.remove_tab(older_tab.id)
id_to_use = older_tab.id
new_tab = Tab(id_to_use, title, content, key=key, icon=icon)
self.tabs.append(new_tab)
self.tabs_by_key[key] = new_tab
self.select_tab_by_key(key)
return self.tabs_by_key[key].id
# else create a new tab
id_to_use = tab_id or get_unique_id(self._id)
new_tab = Tab(id_to_use, title, content, key=key, icon=icon)
self.tabs.append(new_tab)
if key is not None:
self.tabs_by_key[key] = new_tab
self.select_tab_by_id(new_tab.id)
return new_tab.id
def set_tab_content(self, tab_id, content, title=None, key: str | tuple = None, active=None):
logger.debug(f"'set_tab_content' {tab_id=}, {content=}, {active=}")
to_modify = next(filter(lambda t: t.id == tab_id, self.tabs), None)
if to_modify is None:
logger.warning(f" No tab found with id {tab_id=}")
return
to_modify.content = content
if to_modify.key is not None:
del self.tabs_by_key[to_modify.key]
if title is not None:
to_modify.title = title
if key is not None:
self.tabs_by_key[key] = to_modify
if active is not None:
to_modify.active = active
def refresh(self):
return self.render(oob=True)
def __ft__(self):
return mk_tooltip_container(self._id), self.render(), Script(f"bindTabs('{self._id}')")
def render(self, oob=False):
if not self.tabs:
return Div(id=self._id, hx_swap_oob="true" if oob else None)
active_content = self.get_active_tab_content()
if hasattr(active_content, "on_htmx_after_settle"):
extra_params = {"hx-on::after-settle": active_content.on_htmx_after_settle()}
else:
extra_params = {}
return Div(
*[self._mk_tab(tab) for tab in self.tabs], # headers
Div(active_content, cls="tabs-content"),
cls="tabs",
id=self._id,
hx_swap_oob="true" if oob else None,
**extra_params,
)
def _mk_tab(self, tab: Tab):
return Span(
Label(mk_ellipsis(tab.title), hx_post=f"{ROUTE_ROOT}{Routes.SelectTab}", cls="tabs-label truncate"),
Div(icon_dismiss_regular, cls="icon-16 ml-2", hx_post=f"{ROUTE_ROOT}{Routes.RemoveTab}"),
cls=f"tabs-tab {'tabs-active' if tab.active else ''}",
hx_vals=f'{{"_id": "{self._id}", "tab_id":"{tab.id}"}}',
hx_target=f"#{self._id}",
hx_swap="outerHTML",
)
def get_active_tab_content(self):
active_tab = next(filter(lambda t: t.active, self.tabs), None)
return active_tab.content if active_tab else None
@staticmethod
def create_component_id(session):
prefix = f"{MY_TABS_INSTANCE_ID}{session['user_id']}"
return get_unique_id(prefix)

View File

@@ -0,0 +1,8 @@
MY_TABS_INSTANCE_ID = "__MyTabs__"
ROUTE_ROOT = "/tabs"
class Routes:
SelectTab = "/select"
AddTab = "/add"
RemoveTab = "/remove"
MoveTab = "/move"