Working on Dropdown. Removed dynamic_get

This commit is contained in:
2025-11-25 21:28:48 +01:00
parent 52b4e6a8b6
commit 53253278b2
6 changed files with 78 additions and 51 deletions

View File

@@ -4,11 +4,11 @@ import yaml
from fasthtml import serve from fasthtml import serve
from myfasthtml.controls.CommandsDebugger import CommandsDebugger from myfasthtml.controls.CommandsDebugger import CommandsDebugger
from myfasthtml.controls.Dropdown import Dropdown
from myfasthtml.controls.FileUpload import FileUpload from myfasthtml.controls.FileUpload import FileUpload
from myfasthtml.controls.InstancesDebugger import InstancesDebugger from myfasthtml.controls.InstancesDebugger import InstancesDebugger
from myfasthtml.controls.Keyboard import Keyboard from myfasthtml.controls.Keyboard import Keyboard
from myfasthtml.controls.Layout import Layout from myfasthtml.controls.Layout import Layout
from myfasthtml.controls.Dropdown import Dropdown
from myfasthtml.controls.TabsManager import TabsManager from myfasthtml.controls.TabsManager import TabsManager
from myfasthtml.controls.helpers import Ids, mk from myfasthtml.controls.helpers import Ids, mk
from myfasthtml.core.instances import UniqueInstance from myfasthtml.core.instances import UniqueInstance
@@ -38,6 +38,7 @@ def index(session):
layout.set_footer("Goodbye World") layout.set_footer("Goodbye World")
tabs_manager = TabsManager(layout, _id=f"-tabs_manager") tabs_manager = TabsManager(layout, _id=f"-tabs_manager")
add_tab = tabs_manager.commands.add_tab
btn_show_right_drawer = mk.button("show", btn_show_right_drawer = mk.button("show",
command=layout.commands.toggle_drawer("right"), command=layout.commands.toggle_drawer("right"),
id="btn_show_right_drawer_id") id="btn_show_right_drawer_id")
@@ -45,22 +46,22 @@ def index(session):
instances_debugger = InstancesDebugger(layout) instances_debugger = InstancesDebugger(layout)
btn_show_instances_debugger = mk.label("Instances", btn_show_instances_debugger = mk.label("Instances",
icon=volume_object_storage, icon=volume_object_storage,
command=tabs_manager.commands.add_tab("Instances", instances_debugger), command=add_tab("Instances", instances_debugger),
id=instances_debugger.get_id()) id=instances_debugger.get_id())
commands_debugger = CommandsDebugger(layout) commands_debugger = CommandsDebugger(layout)
btn_show_commands_debugger = mk.label("Commands", btn_show_commands_debugger = mk.label("Commands",
icon=None, icon=None,
command=tabs_manager.commands.add_tab("Commands", commands_debugger), command=add_tab("Commands", commands_debugger),
id=commands_debugger.get_id()) id=commands_debugger.get_id())
btn_file_upload = mk.label("Upload", btn_file_upload = mk.label("Upload",
icon=folder_open20_regular, icon=folder_open20_regular,
command=tabs_manager.commands.add_tab("File Open", FileUpload(layout, _id="-file_upload")), command=add_tab("File Open", FileUpload(layout, _id="-file_upload")),
id="file_upload_id") id="file_upload_id")
btn_popup = mk.label("Popup", btn_popup = mk.label("Popup",
command=tabs_manager.commands.add_tab("Popup", Dropdown(layout, "Content", button="button", _id="-popup"))) command=add_tab("Popup", Dropdown(layout, "Content", button="button", _id="-dropdown")))
layout.header_left.add(tabs_manager.add_tab_btn()) layout.header_left.add(tabs_manager.add_tab_btn())
layout.header_right.add(btn_show_right_drawer) layout.header_right.add(btn_show_right_drawer)
@@ -70,10 +71,8 @@ def index(session):
layout.left_drawer.add(btn_popup, "Test") layout.left_drawer.add(btn_popup, "Test")
layout.set_main(tabs_manager) layout.set_main(tabs_manager)
keyboard = Keyboard(layout, _id="-keyboard").add("ctrl+o", keyboard = Keyboard(layout, _id="-keyboard").add("ctrl+o",
tabs_manager.commands.add_tab("File Open", add_tab("File Open", FileUpload(layout, _id="-file_upload")))
FileUpload(layout, keyboard.add("ctrl+n", add_tab("File Open", FileUpload(layout, _id="-file_upload")))
_id="-file_upload")))
keyboard.add("ctrl+n", tabs_manager.commands.add_tab("File Open", FileUpload(layout, _id="-file_upload")))
return layout, keyboard return layout, keyboard

View File

@@ -446,9 +446,6 @@
display: inline-block; display: inline-block;
} }
.mf-dropdown-wrapper:hover .mf-dropdown {
display: block;
}
.mf-dropdown { .mf-dropdown {
display: none; display: none;

View File

@@ -342,7 +342,7 @@ function updateTabs(controllerId) {
* Create a new trie node * Create a new trie node
* @returns {Object} - New trie node * @returns {Object} - New trie node
*/ */
function createTrieNode() { function createTreeNode() {
return { return {
config: null, config: null,
combinationStr: null, combinationStr: null,
@@ -355,18 +355,19 @@ function updateTabs(controllerId) {
* @param {Object} combinations - Map of combination strings to HTMX config objects * @param {Object} combinations - Map of combination strings to HTMX config objects
* @returns {Object} - Root trie node * @returns {Object} - Root trie node
*/ */
function buildTrie(combinations) { function buildTree(combinations) {
const root = createTrieNode(); const root = createTreeNode();
for (const [combinationStr, config] of Object.entries(combinations)) { for (const [combinationStr, config] of Object.entries(combinations)) {
const sequence = parseCombination(combinationStr); const sequence = parseCombination(combinationStr);
console.log("Parsing combination", combinationStr, "=>", sequence);
let currentNode = root; let currentNode = root;
for (const keySet of sequence) { for (const keySet of sequence) {
const key = setToKey(keySet); const key = setToKey(keySet);
if (!currentNode.children.has(key)) { if (!currentNode.children.has(key)) {
currentNode.children.set(key, createTrieNode()); currentNode.children.set(key, createTreeNode());
} }
currentNode = currentNode.children.get(key); currentNode = currentNode.children.get(key);
@@ -381,12 +382,12 @@ function updateTabs(controllerId) {
} }
/** /**
* Traverse the trie with the current snapshot history * Traverse the tree with the current snapshot history
* @param {Object} trieRoot - Root of the trie * @param {Object} trieRoot - Root of the tree
* @param {Array} snapshotHistory - Array of Sets representing pressed keys * @param {Array} snapshotHistory - Array of Sets representing pressed keys
* @returns {Object|null} - Current node or null if no match * @returns {Object|null} - Current node or null if no match
*/ */
function traverseTrie(trieRoot, snapshotHistory) { function traverseTree(trieRoot, snapshotHistory) {
let currentNode = trieRoot; let currentNode = trieRoot;
for (const snapshot of snapshotHistory) { for (const snapshot of snapshotHistory) {
@@ -506,6 +507,7 @@ function updateTabs(controllerId) {
// Add key to current pressed keys // Add key to current pressed keys
KeyboardRegistry.currentKeys.add(key); KeyboardRegistry.currentKeys.add(key);
console.debug("Received key", key);
// Create a snapshot of current keyboard state // Create a snapshot of current keyboard state
const snapshot = new Set(KeyboardRegistry.currentKeys); const snapshot = new Set(KeyboardRegistry.currentKeys);
@@ -530,13 +532,14 @@ function updateTabs(controllerId) {
const element = document.getElementById(elementId); const element = document.getElementById(elementId);
if (!element) continue; if (!element) continue;
const trieRoot = data.trie; const treeRoot = data.tree;
// Traverse the trie with current snapshot history // Traverse the tree with current snapshot history
const currentNode = traverseTrie(trieRoot, KeyboardRegistry.snapshotHistory); const currentNode = traverseTree(treeRoot, KeyboardRegistry.snapshotHistory);
if (!currentNode) { if (!currentNode) {
// No match in this trie, continue to next element // No match in this tree, continue to next element
console.debug("No match in tree for event", key);
continue; continue;
} }
@@ -649,15 +652,19 @@ function updateTabs(controllerId) {
// Parse the combinations JSON // Parse the combinations JSON
const combinations = JSON.parse(combinationsJson); const combinations = JSON.parse(combinationsJson);
// Build trie for this element // Build tree for this element
const trie = buildTrie(combinations); const tree = buildTree(combinations);
// Get element reference // Get element reference
const element = document.getElementById(elementId); const element = document.getElementById(elementId);
if (!element) {
console.error("Element with ID", elementId, "not found!");
return;
}
// Add to registry // Add to registry
KeyboardRegistry.elements.set(elementId, { KeyboardRegistry.elements.set(elementId, {
trie: trie, tree: tree,
element: element element: element
}); });

View File

@@ -1,19 +1,59 @@
from fastcore.xml import FT
from fasthtml.components import Div from fasthtml.components import Div
from myfasthtml.controls.BaseCommands import BaseCommands
from myfasthtml.controls.Keyboard import Keyboard
from myfasthtml.core.commands import Command
from myfasthtml.core.instances import MultipleInstance from myfasthtml.core.instances import MultipleInstance
class Commands(BaseCommands):
def toggle(self):
return Command("Toggle", "Toggle Dropdown", self._owner.toggle).htmx(target=f"#{self._owner.get_id()}-content")
def close(self):
return Command("Close", "Close Dropdown", self._owner.close).htmx(target=f"#{self._owner.get_id()}-content")
class DropdownState:
def __init__(self):
self.opened = False
class Dropdown(MultipleInstance): class Dropdown(MultipleInstance):
def __init__(self, parent, content, button=None, _id=None): def __init__(self, parent, content=None, button=None, _id=None):
super().__init__(parent, _id=_id) super().__init__(parent, _id=_id)
self.button = button self.button = Div(button) if not isinstance(button, FT) else button
self.content = content self.content = content
self.commands = Commands(self)
self._state = DropdownState()
self._toggle_command = self.commands.toggle()
# attach the command to the button
self._toggle_command.bind_ft(self.button)
def toggle(self):
self._state.opened = not self._state.opened
return self._mk_content()
def close(self):
self._state.opened = False
return self._mk_content()
def _mk_content(self):
return Div(self.content,
cls=f"mf-dropdown {'is-visible' if self._state.opened else ''}",
id=f"{self._id}-content"),
def render(self): def render(self):
return Div( return Div(
Div(
Div(self.button) if self.button else Div("None"), Div(self.button) if self.button else Div("None"),
Div(self.content, cls="mf-dropdown"), self._mk_content(),
cls="mf-dropdown-wrapper" cls="mf-dropdown-wrapper"
),
Keyboard(self, "_keyboard").add("esc", self.commands.close()),
id=self._id
) )
def __ft__(self): def __ft__(self):

View File

@@ -102,7 +102,7 @@ class TabsManager(MultipleInstance):
tab_config = self._state.tabs[tab_id] tab_config = self._state.tabs[tab_id]
if tab_config["component_type"] is None: if tab_config["component_type"] is None:
return None return None
return InstancesManager.dynamic_get(self, tab_config["component_type"], tab_config["component_id"]) return InstancesManager.get(self._session, tab_config["component_id"])
@staticmethod @staticmethod
def _get_tab_count(): def _get_tab_count():

View File

@@ -2,10 +2,8 @@ import logging
import uuid import uuid
from typing import Optional from typing import Optional
from dbengine.utils import get_class
from myfasthtml.controls.helpers import Ids from myfasthtml.controls.helpers import Ids
from myfasthtml.core.utils import pascal_to_snake, snake_to_pascal from myfasthtml.core.utils import pascal_to_snake
logger = logging.getLogger("InstancesManager") logger = logging.getLogger("InstancesManager")
@@ -216,19 +214,5 @@ class InstancesManager:
if key[0] != session_id if key[0] != session_id
} }
@staticmethod
def dynamic_get(parent: BaseInstance, component_type: str, instance_id: str):
logger.debug(f"Dynamic get: {component_type=} {instance_id=}")
cls = InstancesManager._get_class_name(component_type)
fully_qualified_name = f"myfasthtml.controls.{cls}.{cls}"
cls = get_class(fully_qualified_name)
return cls(parent, instance_id)
@staticmethod
def _get_class_name(component_type: str) -> str:
component_type = component_type.replace("mf-", "")
component_type = snake_to_pascal(component_type)
return component_type
RootInstance = SingleInstance(None, special_session, Ids.Root) RootInstance = SingleInstance(None, special_session, Ids.Root)