Added Panel.py
This commit is contained in:
@@ -14,6 +14,7 @@ from myfasthtml.controls.TreeView import TreeView, TreeNode
|
|||||||
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
|
||||||
from myfasthtml.icons.carbon import volume_object_storage
|
from myfasthtml.icons.carbon import volume_object_storage
|
||||||
|
from myfasthtml.icons.fluent_p2 import key_command16_regular
|
||||||
from myfasthtml.icons.fluent_p3 import folder_open20_regular
|
from myfasthtml.icons.fluent_p3 import folder_open20_regular
|
||||||
from myfasthtml.myfastapp import create_app
|
from myfasthtml.myfastapp import create_app
|
||||||
|
|
||||||
@@ -73,7 +74,7 @@ def create_sample_treeview(parent):
|
|||||||
tree_view.add_node(todo, parent_id=documents.id)
|
tree_view.add_node(todo, parent_id=documents.id)
|
||||||
|
|
||||||
# Expand all nodes to show the full structure
|
# Expand all nodes to show the full structure
|
||||||
tree_view.expand_all()
|
#tree_view.expand_all()
|
||||||
|
|
||||||
return tree_view
|
return tree_view
|
||||||
|
|
||||||
@@ -98,7 +99,7 @@ def index(session):
|
|||||||
|
|
||||||
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=key_command16_regular,
|
||||||
command=add_tab("Commands", commands_debugger),
|
command=add_tab("Commands", commands_debugger),
|
||||||
id=commands_debugger.get_id())
|
id=commands_debugger.get_id())
|
||||||
|
|
||||||
|
|||||||
@@ -492,6 +492,7 @@
|
|||||||
transition: background-color 0.15s ease;
|
transition: background-color 0.15s ease;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Input for Editing */
|
/* Input for Editing */
|
||||||
.mf-treenode-input {
|
.mf-treenode-input {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@@ -503,7 +504,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.mf-treenode:hover {
|
.mf-treenode:hover {
|
||||||
background-color: var(--color-base-200);
|
background-color: var(--color-base-200);
|
||||||
}
|
}
|
||||||
@@ -544,4 +544,131 @@
|
|||||||
|
|
||||||
.mf-treenode:hover .mf-treenode-actions {
|
.mf-treenode:hover .mf-treenode-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* *********************************************** */
|
||||||
|
/* ********** Generic Resizer Classes ************ */
|
||||||
|
/* *********************************************** */
|
||||||
|
|
||||||
|
/* Generic resizer - used by both Layout and Panel */
|
||||||
|
.mf-resizer {
|
||||||
|
position: absolute;
|
||||||
|
width: 4px;
|
||||||
|
cursor: col-resize;
|
||||||
|
background-color: transparent;
|
||||||
|
transition: background-color 0.2s ease;
|
||||||
|
z-index: 100;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mf-resizer:hover {
|
||||||
|
background-color: rgba(59, 130, 246, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Active state during resize */
|
||||||
|
.mf-resizing .mf-resizer {
|
||||||
|
background-color: rgba(59, 130, 246, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Prevent text selection during resize */
|
||||||
|
.mf-resizing {
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-moz-user-select: none;
|
||||||
|
-ms-user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Cursor override for entire body during resize */
|
||||||
|
.mf-resizing * {
|
||||||
|
cursor: col-resize !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Visual indicator for resizer on hover - subtle border */
|
||||||
|
.mf-resizer::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 2px;
|
||||||
|
height: 40px;
|
||||||
|
background-color: rgba(156, 163, 175, 0.4);
|
||||||
|
border-radius: 2px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mf-resizer:hover::before,
|
||||||
|
.mf-resizing .mf-resizer::before {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Resizer positioning */
|
||||||
|
/* Left resizer is on the right side of the left panel */
|
||||||
|
.mf-resizer-left {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Right resizer is on the left side of the right panel */
|
||||||
|
.mf-resizer-right {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Position indicator for resizer */
|
||||||
|
.mf-resizer-left::before {
|
||||||
|
right: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mf-resizer-right::before {
|
||||||
|
left: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Disable transitions during resize for smooth dragging */
|
||||||
|
.mf-item-resizing {
|
||||||
|
transition: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* *********************************************** */
|
||||||
|
/* *************** Panel Component *************** */
|
||||||
|
/* *********************************************** */
|
||||||
|
|
||||||
|
/* Container principal du panel */
|
||||||
|
.mf-panel {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Panel gauche */
|
||||||
|
.mf-panel-left {
|
||||||
|
position: relative;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 250px;
|
||||||
|
min-width: 150px;
|
||||||
|
max-width: 400px;
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
border-right: 1px solid var(--color-border-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Panel principal (centre) */
|
||||||
|
.mf-panel-main {
|
||||||
|
flex: 1;
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
min-width: 0; /* Important pour permettre le shrink du flexbox */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Panel droit */
|
||||||
|
.mf-panel-right {
|
||||||
|
position: relative;
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: 300px;
|
||||||
|
min-width: 150px;
|
||||||
|
max-width: 500px;
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
border-left: 1px solid var(--color-border-primary);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,40 +1,43 @@
|
|||||||
/**
|
/**
|
||||||
* Layout Drawer Resizer
|
* Generic Resizer
|
||||||
*
|
*
|
||||||
* Handles resizing of left and right drawers with drag functionality.
|
* Handles resizing of elements with drag functionality.
|
||||||
* Communicates with server via HTMX to persist width changes.
|
* Communicates with server via HTMX to persist width changes.
|
||||||
|
* Works for both Layout drawers and Panel sides.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize drawer resizer functionality for a specific layout instance
|
* Initialize resizer functionality for a specific container
|
||||||
*
|
*
|
||||||
* @param {string} layoutId - The ID of the layout instance to initialize
|
* @param {string} containerId - The ID of the container instance to initialize
|
||||||
|
* @param {Object} options - Configuration options
|
||||||
|
* @param {number} options.minWidth - Minimum width in pixels (default: 150)
|
||||||
|
* @param {number} options.maxWidth - Maximum width in pixels (default: 600)
|
||||||
*/
|
*/
|
||||||
function initLayoutResizer(layoutId) {
|
function initResizer(containerId, options = {}) {
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const MIN_WIDTH = 150;
|
const MIN_WIDTH = options.minWidth || 150;
|
||||||
const MAX_WIDTH = 600;
|
const MAX_WIDTH = options.maxWidth || 600;
|
||||||
|
|
||||||
let isResizing = false;
|
let isResizing = false;
|
||||||
let currentResizer = null;
|
let currentResizer = null;
|
||||||
let currentDrawer = null;
|
let currentItem = null;
|
||||||
let startX = 0;
|
let startX = 0;
|
||||||
let startWidth = 0;
|
let startWidth = 0;
|
||||||
let side = null;
|
let side = null;
|
||||||
|
|
||||||
const layoutElement = document.getElementById(layoutId);
|
const containerElement = document.getElementById(containerId);
|
||||||
|
|
||||||
if (!layoutElement) {
|
if (!containerElement) {
|
||||||
console.error(`Layout element with ID "${layoutId}" not found`);
|
console.error(`Container element with ID "${containerId}" not found`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize resizer functionality for this layout instance
|
* Initialize resizer functionality for this container instance
|
||||||
*/
|
*/
|
||||||
function initResizers() {
|
function initResizers() {
|
||||||
const resizers = layoutElement.querySelectorAll('.mf-layout-resizer');
|
const resizers = containerElement.querySelectorAll('.mf-resizer');
|
||||||
|
|
||||||
resizers.forEach(resizer => {
|
resizers.forEach(resizer => {
|
||||||
// Remove existing listener if any to avoid duplicates
|
// Remove existing listener if any to avoid duplicates
|
||||||
@@ -51,24 +54,24 @@ function initLayoutResizer(layoutId) {
|
|||||||
|
|
||||||
currentResizer = e.target;
|
currentResizer = e.target;
|
||||||
side = currentResizer.dataset.side;
|
side = currentResizer.dataset.side;
|
||||||
currentDrawer = currentResizer.closest('.mf-layout-drawer');
|
currentItem = currentResizer.parentElement;
|
||||||
|
|
||||||
if (!currentDrawer) {
|
if (!currentItem) {
|
||||||
console.error('Could not find drawer element');
|
console.error('Could not find item element');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
isResizing = true;
|
isResizing = true;
|
||||||
startX = e.clientX;
|
startX = e.clientX;
|
||||||
startWidth = currentDrawer.offsetWidth;
|
startWidth = currentItem.offsetWidth;
|
||||||
|
|
||||||
// Add event listeners for mouse move and up
|
// Add event listeners for mouse move and up
|
||||||
document.addEventListener('mousemove', handleMouseMove);
|
document.addEventListener('mousemove', handleMouseMove);
|
||||||
document.addEventListener('mouseup', handleMouseUp);
|
document.addEventListener('mouseup', handleMouseUp);
|
||||||
|
|
||||||
// Add resizing class for visual feedback
|
// Add resizing class for visual feedback
|
||||||
document.body.classList.add('mf-layout-resizing');
|
document.body.classList.add('mf-resizing');
|
||||||
currentDrawer.classList.add('mf-layout-drawer-resizing');
|
currentItem.classList.add('mf-item-resizing');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -92,8 +95,8 @@ function initLayoutResizer(layoutId) {
|
|||||||
// Constrain width between min and max
|
// Constrain width between min and max
|
||||||
newWidth = Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, newWidth));
|
newWidth = Math.max(MIN_WIDTH, Math.min(MAX_WIDTH, newWidth));
|
||||||
|
|
||||||
// Update drawer width visually
|
// Update item width visually
|
||||||
currentDrawer.style.width = `${newWidth}px`;
|
currentItem.style.width = `${newWidth}px`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -109,11 +112,11 @@ function initLayoutResizer(layoutId) {
|
|||||||
document.removeEventListener('mouseup', handleMouseUp);
|
document.removeEventListener('mouseup', handleMouseUp);
|
||||||
|
|
||||||
// Remove resizing classes
|
// Remove resizing classes
|
||||||
document.body.classList.remove('mf-layout-resizing');
|
document.body.classList.remove('mf-resizing');
|
||||||
currentDrawer.classList.remove('mf-layout-drawer-resizing');
|
currentItem.classList.remove('mf-item-resizing');
|
||||||
|
|
||||||
// Get final width
|
// Get final width
|
||||||
const finalWidth = currentDrawer.offsetWidth;
|
const finalWidth = currentItem.offsetWidth;
|
||||||
const commandId = currentResizer.dataset.commandId;
|
const commandId = currentResizer.dataset.commandId;
|
||||||
|
|
||||||
if (!commandId) {
|
if (!commandId) {
|
||||||
@@ -122,24 +125,24 @@ function initLayoutResizer(layoutId) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Send width update to server
|
// Send width update to server
|
||||||
saveDrawerWidth(commandId, finalWidth);
|
saveWidth(commandId, finalWidth);
|
||||||
|
|
||||||
// Reset state
|
// Reset state
|
||||||
currentResizer = null;
|
currentResizer = null;
|
||||||
currentDrawer = null;
|
currentItem = null;
|
||||||
side = null;
|
side = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save drawer width to server via HTMX
|
* Save width to server via HTMX
|
||||||
*/
|
*/
|
||||||
function saveDrawerWidth(commandId, width) {
|
function saveWidth(commandId, width) {
|
||||||
htmx.ajax('POST', '/myfasthtml/commands', {
|
htmx.ajax('POST', '/myfasthtml/commands', {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/x-www-form-urlencoded"
|
"Content-Type": "application/x-www-form-urlencoded"
|
||||||
},
|
},
|
||||||
swap: "outerHTML",
|
swap: "outerHTML",
|
||||||
target: `#${currentDrawer.id}`,
|
target: `#${currentItem.id}`,
|
||||||
values: {
|
values: {
|
||||||
c_id: commandId,
|
c_id: commandId,
|
||||||
width: width
|
width: width
|
||||||
@@ -150,8 +153,8 @@ function initLayoutResizer(layoutId) {
|
|||||||
// Initialize resizers
|
// Initialize resizers
|
||||||
initResizers();
|
initResizers();
|
||||||
|
|
||||||
// Re-initialize after HTMX swaps within this layout
|
// Re-initialize after HTMX swaps within this container
|
||||||
layoutElement.addEventListener('htmx:afterSwap', function (event) {
|
containerElement.addEventListener('htmx:afterSwap', function (event) {
|
||||||
initResizers();
|
initResizers();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from myfasthtml.controls.Panel import Panel
|
||||||
from myfasthtml.controls.VisNetwork import VisNetwork
|
from myfasthtml.controls.VisNetwork import VisNetwork
|
||||||
from myfasthtml.core.instances import SingleInstance, InstancesManager
|
from myfasthtml.core.instances import SingleInstance, InstancesManager
|
||||||
from myfasthtml.core.network_utils import from_parent_child_list
|
from myfasthtml.core.network_utils import from_parent_child_list
|
||||||
@@ -6,9 +7,14 @@ from myfasthtml.core.network_utils import from_parent_child_list
|
|||||||
class InstancesDebugger(SingleInstance):
|
class InstancesDebugger(SingleInstance):
|
||||||
def __init__(self, parent, _id=None):
|
def __init__(self, parent, _id=None):
|
||||||
super().__init__(parent, _id=_id)
|
super().__init__(parent, _id=_id)
|
||||||
|
self._panel = Panel(self, _id="-panel")
|
||||||
|
|
||||||
def render(self):
|
def render(self):
|
||||||
s_name = InstancesManager.get_session_user_name
|
nodes, edges = self._get_nodes_and_edges()
|
||||||
|
vis_network = VisNetwork(self, nodes=nodes, edges=edges, _id="-vis")
|
||||||
|
return self._panel.set_main(vis_network)
|
||||||
|
|
||||||
|
def _get_nodes_and_edges(self):
|
||||||
instances = self._get_instances()
|
instances = self._get_instances()
|
||||||
nodes, edges = from_parent_child_list(
|
nodes, edges = from_parent_child_list(
|
||||||
instances,
|
instances,
|
||||||
@@ -23,9 +29,7 @@ class InstancesDebugger(SingleInstance):
|
|||||||
for node in nodes:
|
for node in nodes:
|
||||||
node["shape"] = "box"
|
node["shape"] = "box"
|
||||||
|
|
||||||
vis_network = VisNetwork(self, nodes=nodes, edges=edges, _id="-vis")
|
return nodes, edges
|
||||||
# vis_network.add_to_options(physics={"wind": {"x": 0, "y": 1}})
|
|
||||||
return vis_network
|
|
||||||
|
|
||||||
def _get_instances(self):
|
def _get_instances(self):
|
||||||
return list(InstancesManager.instances.values())
|
return list(InstancesManager.instances.values())
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ class Layout(SingleInstance):
|
|||||||
self.header_right = self.Content(self)
|
self.header_right = self.Content(self)
|
||||||
self.footer_left = self.Content(self)
|
self.footer_left = self.Content(self)
|
||||||
self.footer_right = self.Content(self)
|
self.footer_right = self.Content(self)
|
||||||
|
self._footer_content = None
|
||||||
|
|
||||||
def set_footer(self, content):
|
def set_footer(self, content):
|
||||||
"""
|
"""
|
||||||
@@ -141,6 +142,7 @@ class Layout(SingleInstance):
|
|||||||
content: FastHTML component(s) or content for the main area
|
content: FastHTML component(s) or content for the main area
|
||||||
"""
|
"""
|
||||||
self._main_content = content
|
self._main_content = content
|
||||||
|
return self
|
||||||
|
|
||||||
def toggle_drawer(self, side: Literal["left", "right"]):
|
def toggle_drawer(self, side: Literal["left", "right"]):
|
||||||
logger.debug(f"Toggle drawer: {side=}, {self._state.left_drawer_open=}")
|
logger.debug(f"Toggle drawer: {side=}, {self._state.left_drawer_open=}")
|
||||||
@@ -233,7 +235,7 @@ class Layout(SingleInstance):
|
|||||||
Div: FastHTML Div component for left drawer
|
Div: FastHTML Div component for left drawer
|
||||||
"""
|
"""
|
||||||
resizer = Div(
|
resizer = Div(
|
||||||
cls="mf-layout-resizer mf-layout-resizer-right",
|
cls="mf-resizer mf-resizer-left",
|
||||||
data_command_id=self.commands.update_drawer_width("left").id,
|
data_command_id=self.commands.update_drawer_width("left").id,
|
||||||
data_side="left"
|
data_side="left"
|
||||||
)
|
)
|
||||||
@@ -266,8 +268,9 @@ class Layout(SingleInstance):
|
|||||||
Returns:
|
Returns:
|
||||||
Div: FastHTML Div component for right drawer
|
Div: FastHTML Div component for right drawer
|
||||||
"""
|
"""
|
||||||
|
|
||||||
resizer = Div(
|
resizer = Div(
|
||||||
cls="mf-layout-resizer mf-layout-resizer-left",
|
cls="mf-resizer mf-resizer-right",
|
||||||
data_command_id=self.commands.update_drawer_width("right").id,
|
data_command_id=self.commands.update_drawer_width("right").id,
|
||||||
data_side="right"
|
data_side="right"
|
||||||
)
|
)
|
||||||
@@ -311,7 +314,7 @@ class Layout(SingleInstance):
|
|||||||
self._mk_main(),
|
self._mk_main(),
|
||||||
self._mk_right_drawer(),
|
self._mk_right_drawer(),
|
||||||
self._mk_footer(),
|
self._mk_footer(),
|
||||||
Script(f"initLayoutResizer('{self._id}');"),
|
Script(f"initResizer('{self._id}');"),
|
||||||
id=self._id,
|
id=self._id,
|
||||||
cls="mf-layout",
|
cls="mf-layout",
|
||||||
)
|
)
|
||||||
|
|||||||
102
src/myfasthtml/controls/Panel.py
Normal file
102
src/myfasthtml/controls/Panel.py
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Literal
|
||||||
|
|
||||||
|
from fasthtml.components import Div
|
||||||
|
from fasthtml.xtend import Script
|
||||||
|
|
||||||
|
from myfasthtml.controls.BaseCommands import BaseCommands
|
||||||
|
from myfasthtml.core.commands import Command
|
||||||
|
from myfasthtml.core.instances import MultipleInstance
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PanelConf:
|
||||||
|
left: bool = False
|
||||||
|
right: bool = True
|
||||||
|
|
||||||
|
|
||||||
|
class Commands(BaseCommands):
|
||||||
|
def toggle_side(self, side: Literal["left", "right"]):
|
||||||
|
return Command("TogglePanelSide", f"Toggle {side} side panel", self._owner.toggle_side, side)
|
||||||
|
|
||||||
|
def update_side_width(self, side: Literal["left", "right"]):
|
||||||
|
"""
|
||||||
|
Create a command to update panel's side width.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
side: Which panel's side to update ("left" or "right")
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Command: Command object for updating panel's side width
|
||||||
|
"""
|
||||||
|
return Command(
|
||||||
|
f"UpdatePanelSideWidth_{side}",
|
||||||
|
f"Update {side} side panel width",
|
||||||
|
self._owner.update_side_width,
|
||||||
|
side
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Panel(MultipleInstance):
|
||||||
|
def __init__(self, parent, conf=None, _id=None):
|
||||||
|
super().__init__(parent, _id=_id)
|
||||||
|
self.conf = conf or PanelConf()
|
||||||
|
self.commands = Commands(self)
|
||||||
|
self._main = None
|
||||||
|
self._right = None
|
||||||
|
self._left = None
|
||||||
|
|
||||||
|
def update_side_width(self, side, width):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def toggle_side(self, side):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def set_main(self, main):
|
||||||
|
self._main = main
|
||||||
|
return self
|
||||||
|
|
||||||
|
def set_right(self, right):
|
||||||
|
self._right = right
|
||||||
|
return self
|
||||||
|
|
||||||
|
def set_left(self, left):
|
||||||
|
self._left = left
|
||||||
|
return self
|
||||||
|
|
||||||
|
def _mk_right(self):
|
||||||
|
if not self.conf.right:
|
||||||
|
return None
|
||||||
|
|
||||||
|
resizer = Div(
|
||||||
|
cls="mf-resizer mf-resizer-right",
|
||||||
|
data_command_id=self.commands.update_side_width("right").id,
|
||||||
|
data_side="right"
|
||||||
|
)
|
||||||
|
|
||||||
|
return Div(resizer, self._right, cls="mf-panel-right")
|
||||||
|
|
||||||
|
def _mk_left(self):
|
||||||
|
if not self.conf.left:
|
||||||
|
return None
|
||||||
|
|
||||||
|
resizer = Div(
|
||||||
|
cls="mf-resizer mf-resizer-left",
|
||||||
|
data_command_id=self.commands.update_side_width("left").id,
|
||||||
|
data_side="left"
|
||||||
|
)
|
||||||
|
|
||||||
|
return Div(self._left, resizer, cls="mf-panel-left")
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
return Div(
|
||||||
|
self._mk_left(),
|
||||||
|
Div(self._main, cls="mf-panel-main"),
|
||||||
|
self._mk_right(),
|
||||||
|
Script(f"initResizer('{self._id}');"),
|
||||||
|
cls="mf-panel",
|
||||||
|
id=self._id,
|
||||||
|
)
|
||||||
|
|
||||||
|
def __ft__(self):
|
||||||
|
return self.render()
|
||||||
@@ -409,11 +409,25 @@ def matches(actual, expected, path=""):
|
|||||||
assert hasattr(actual, attr), _error_msg(f"'{attr}' is not found in Actual.",
|
assert hasattr(actual, attr), _error_msg(f"'{attr}' is not found in Actual.",
|
||||||
_actual=actual,
|
_actual=actual,
|
||||||
_expected=expected)
|
_expected=expected)
|
||||||
assert matches(getattr(actual, attr), value), _error_msg(f"The values are different for '{attr}': ",
|
try:
|
||||||
_actual=getattr(actual, attr),
|
matches(getattr(actual, attr), value)
|
||||||
_expected=value)
|
except AssertionError as e:
|
||||||
|
match = re.search(r"Error : (.+?)\n", str(e))
|
||||||
|
if match:
|
||||||
|
assert False, _error_msg(f"{match.group(1)} for '{attr}':",
|
||||||
|
_actual=getattr(actual, attr),
|
||||||
|
_expected=value)
|
||||||
|
assert False, _error_msg(f"The values are different for '{attr}': ",
|
||||||
|
_actual=getattr(actual, attr),
|
||||||
|
_expected=value)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
if isinstance(expected, Predicate):
|
||||||
|
assert expected.validate(actual), \
|
||||||
|
_error_msg(f"The condition '{expected}' is not satisfied.",
|
||||||
|
_actual=actual,
|
||||||
|
_expected=expected)
|
||||||
|
|
||||||
assert _type(actual) == _type(expected) or (hasattr(actual, "tag") and hasattr(expected, "tag")), \
|
assert _type(actual) == _type(expected) or (hasattr(actual, "tag") and hasattr(expected, "tag")), \
|
||||||
_error_msg("The types are different: ", _actual=actual, _expected=expected)
|
_error_msg("The types are different: ", _actual=actual, _expected=expected)
|
||||||
|
|
||||||
@@ -481,7 +495,7 @@ def matches(actual, expected, path=""):
|
|||||||
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
assert actual == expected, _error_msg("The values are different: ",
|
assert actual == expected, _error_msg("The values are different",
|
||||||
_actual=actual,
|
_actual=actual,
|
||||||
_expected=expected)
|
_expected=expected)
|
||||||
|
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ class TestMatches:
|
|||||||
(Div(Dummy(123, "value")), Div(TestObject(Dummy, attr1=123, attr3="value3")), "'attr3' is not found in Actual"),
|
(Div(Dummy(123, "value")), Div(TestObject(Dummy, attr1=123, attr3="value3")), "'attr3' is not found in Actual"),
|
||||||
(Div(Dummy(123, "value")), Div(TestObject(Dummy, attr1=123, attr2="value2")), "are different for 'attr2'"),
|
(Div(Dummy(123, "value")), Div(TestObject(Dummy, attr1=123, attr2="value2")), "are different for 'attr2'"),
|
||||||
(Div(123, "value"), TestObject("Dummy", attr1=123, attr2="value2"), "The types are different:"),
|
(Div(123, "value"), TestObject("Dummy", attr1=123, attr2="value2"), "The types are different:"),
|
||||||
|
(Dummy(123, "value"), TestObject("Dummy", attr1=123, attr2=Contains("value2")), "The condition 'Contains(value2)' is not satisfied"),
|
||||||
|
|
||||||
])
|
])
|
||||||
def test_i_can_detect_errors(self, actual, expected, error_message):
|
def test_i_can_detect_errors(self, actual, expected, error_message):
|
||||||
|
|||||||
Reference in New Issue
Block a user