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 myfasthtml.controls.CommandsDebugger import CommandsDebugger
from myfasthtml.controls.Dropdown import Dropdown
from myfasthtml.controls.FileUpload import FileUpload
from myfasthtml.controls.InstancesDebugger import InstancesDebugger
from myfasthtml.controls.Keyboard import Keyboard
from myfasthtml.controls.Layout import Layout
from myfasthtml.controls.Dropdown import Dropdown
from myfasthtml.controls.TabsManager import TabsManager
from myfasthtml.controls.helpers import Ids, mk
from myfasthtml.core.instances import UniqueInstance
@@ -38,6 +38,7 @@ def index(session):
layout.set_footer("Goodbye World")
tabs_manager = TabsManager(layout, _id=f"-tabs_manager")
add_tab = tabs_manager.commands.add_tab
btn_show_right_drawer = mk.button("show",
command=layout.commands.toggle_drawer("right"),
id="btn_show_right_drawer_id")
@@ -45,22 +46,22 @@ def index(session):
instances_debugger = InstancesDebugger(layout)
btn_show_instances_debugger = mk.label("Instances",
icon=volume_object_storage,
command=tabs_manager.commands.add_tab("Instances", instances_debugger),
command=add_tab("Instances", instances_debugger),
id=instances_debugger.get_id())
commands_debugger = CommandsDebugger(layout)
btn_show_commands_debugger = mk.label("Commands",
icon=None,
command=tabs_manager.commands.add_tab("Commands", commands_debugger),
command=add_tab("Commands", commands_debugger),
id=commands_debugger.get_id())
btn_file_upload = mk.label("Upload",
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")
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_right.add(btn_show_right_drawer)
@@ -70,10 +71,8 @@ def index(session):
layout.left_drawer.add(btn_popup, "Test")
layout.set_main(tabs_manager)
keyboard = Keyboard(layout, _id="-keyboard").add("ctrl+o",
tabs_manager.commands.add_tab("File Open",
FileUpload(layout,
_id="-file_upload")))
keyboard.add("ctrl+n", tabs_manager.commands.add_tab("File Open", FileUpload(layout, _id="-file_upload")))
add_tab("File Open", FileUpload(layout, _id="-file_upload")))
keyboard.add("ctrl+n", add_tab("File Open", FileUpload(layout, _id="-file_upload")))
return layout, keyboard

View File

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

View File

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

View File

@@ -1,19 +1,59 @@
from fastcore.xml import FT
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
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):
def __init__(self, parent, content, button=None, _id=None):
def __init__(self, parent, content=None, button=None, _id=None):
super().__init__(parent, _id=_id)
self.button = button
self.button = Div(button) if not isinstance(button, FT) else button
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):
return Div(
Div(self.button) if self.button else Div("None"),
Div(self.content, cls="mf-dropdown"),
cls="mf-dropdown-wrapper"
Div(
Div(self.button) if self.button else Div("None"),
self._mk_content(),
cls="mf-dropdown-wrapper"
),
Keyboard(self, "_keyboard").add("esc", self.commands.close()),
id=self._id
)
def __ft__(self):

View File

@@ -102,7 +102,7 @@ class TabsManager(MultipleInstance):
tab_config = self._state.tabs[tab_id]
if tab_config["component_type"] is 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
def _get_tab_count():

View File

@@ -2,10 +2,8 @@ import logging
import uuid
from typing import Optional
from dbengine.utils import get_class
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")
@@ -215,20 +213,6 @@ class InstancesManager:
for key, instance in InstancesManager.instances.items()
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)