Working on Dropdown. Removed dynamic_get
This commit is contained in:
17
src/app.py
17
src/app.py
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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():
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user