Open
Description
import dash
from dash import html, dcc, Input, Output, State
import dash_excalidraw
from dash.exceptions import PreventUpdate
import uuid
from datetime import datetime
import random
# Initialize the Dash app
app = dash.Dash(__name__, suppress_callback_exceptions=True)
# Initial empty canvas data (can be simplified further)
def get_initial_canvas_data(content="Initial"):
return {
'type': 'excalidraw',
'version': 2,
'source': 'Minimal-Bug-Reproducer',
'elements': [
{
"id": str(uuid.uuid4()),
"type": "text",
"x": 50 + random.randint(-20, 20), # Slightly change position
"y": 50 + random.randint(-20, 20),
"width": 300,
"height": 50,
"angle": 0,
"strokeColor": "#000000",
"backgroundColor": "transparent",
"fillStyle": "hachure",
"strokeWidth": 1,
"strokeStyle": "solid",
"roughness": 1,
"opacity": 100,
"text": f"{content} - {datetime.now().time()}", # Change text content
"fontSize": 20,
"fontFamily": 1,
"textAlign": "left",
"verticalAlign": "top",
"baseline": 18,
"seed": random.randint(1, 1000000), # Change seed
"version": 1,
"versionNonce": random.randint(1, 1000000), # Change versionNonce
"isDeleted": False,
"updated": int(datetime.now().timestamp() * 1000)
}
],
'appState': {
'gridSize': 20,
'viewBackgroundColor': '#ffffff',
"theme": "light" # Ensure theme is present, as it's often in appState
},
'files': {}
}
initial_data_for_load = get_initial_canvas_data("Loaded at Start")
# Define the app layout
app.layout = html.Div([
html.H1("Dash Excalidraw Bug Reproducer"),
html.Button("Update Excalidraw with New Data", id="update-button", n_clicks=0),
html.Div(id="status-output", style={"marginTop": "10px", "marginBottom": "10px"}),
html.Div(
id="excalidraw-wrapper-container", # This div's children will be updated
children=html.Div( # The re-keyed div
key='excalidraw-initial-key',
children=dash_excalidraw.DashExcalidraw(
id='excalidraw-instance',
width='100%',
height='500px',
initialData=initial_data_for_load,
),
style={"border": "1px solid lightgrey", "height": "500px"}
)
),
html.P("Open your browser's developer console (usually F12) to check for 'Maximum update depth exceeded' errors after clicking the button multiple times.")
])
@app.callback(
Output("excalidraw-wrapper-container", "children"),
Output("status-output", "children"),
Input("update-button", "n_clicks"),
prevent_initial_call=True
)
def update_excalidraw(n_clicks):
if n_clicks == 0:
raise PreventUpdate
print(f"Button clicked: {n_clicks}")
new_content = f"Update #{n_clicks}"
new_excalidraw_data = get_initial_canvas_data(new_content)
# Create the new DashExcalidraw component instance
new_excalidraw_component = dash_excalidraw.DashExcalidraw(
id='excalidraw-instance', # ID can remain the same as React handles identity by key
width='100%',
height='500px',
initialData=new_excalidraw_data,
)
# Create a new wrapper div with a new unique key to force remount
# Using a timestamp for the key is a reliable way to make it unique
new_key = f'excalidraw-key-{datetime.now().timestamp()}'
print(f"Generated new key: {new_key}")
new_wrapper_div = html.Div(
key=new_key,
children=new_excalidraw_component,
style={"border": "1px solid lightblue", "height": "500px"} # Visual cue for change
)
status_message = f"Excalidraw updated with new data at {datetime.now().strftime('%H:%M:%S')}. New key: {new_key}"
print(status_message)
return new_wrapper_div, status_message
if __name__ == '__main__':
print("Starting Dash app on http://127.0.0.1:8050/")
print("After clicking the button, check the browser's developer console for errors.")
app.run(debug=True, port=8250)
Metadata
Metadata
Assignees
Labels
No labels