First Working version. I can add table
This commit is contained in:
22
src/components/tabs/TabsApp.py
Normal file
22
src/components/tabs/TabsApp.py
Normal 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
|
||||
0
src/components/tabs/__init__.py
Normal file
0
src/components/tabs/__init__.py
Normal file
0
src/components/tabs/assets/__init__.py
Normal file
0
src/components/tabs/assets/__init__.py
Normal file
45
src/components/tabs/assets/tabs.css
Normal file
45
src/components/tabs/assets/tabs.css
Normal 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);
|
||||
}
|
||||
3
src/components/tabs/assets/tabs.js
Normal file
3
src/components/tabs/assets/tabs.js
Normal file
@@ -0,0 +1,3 @@
|
||||
function bindTabs(tabsId) {
|
||||
bindTooltipsWithDelegation(tabsId)
|
||||
}
|
||||
192
src/components/tabs/components/MyTabs.py
Normal file
192
src/components/tabs/components/MyTabs.py
Normal 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)
|
||||
0
src/components/tabs/components/__init__.py
Normal file
0
src/components/tabs/components/__init__.py
Normal file
8
src/components/tabs/constants.py
Normal file
8
src/components/tabs/constants.py
Normal file
@@ -0,0 +1,8 @@
|
||||
MY_TABS_INSTANCE_ID = "__MyTabs__"
|
||||
ROUTE_ROOT = "/tabs"
|
||||
|
||||
class Routes:
|
||||
SelectTab = "/select"
|
||||
AddTab = "/add"
|
||||
RemoveTab = "/remove"
|
||||
MoveTab = "/move"
|
||||
Reference in New Issue
Block a user