diff --git a/app/src/index.tsx b/app/src/index.tsx index cd01472..f46bf77 100644 --- a/app/src/index.tsx +++ b/app/src/index.tsx @@ -22,7 +22,7 @@ import { Preview, ChartPreview } from './components/preview'; import UploadSpecModal from "./components/uploadSpecModal" import UploadChartModal from './components/uploadChartModal'; import InitModal from './components/initModal'; -import { getSaveTool, hidePreview } from './tools/saveTool'; +import { getSaveTool } from './tools/saveTool'; import { getExportTool } from './tools/exportTool'; import { getExportDataframeTool } from './tools/exportDataframe'; import { formatExportedChartDatas } from "./utils/save"; @@ -64,7 +64,6 @@ const initChart = async (gwRef: React.MutableRefObject, total curIndex: chart.index + 1, total: chart.total, }); - hidePreview(props.id); } } commonStore.setInitModalOpen(false); @@ -308,7 +307,6 @@ const initOnJupyter = async(props: IAppProps) => { comm.sendMsgAsync("request_data", {}, null); } await initDslParser(); - hidePreview(props.id); } const initOnHttpCommunication = async(props: IAppProps) => { @@ -382,14 +380,19 @@ function GWalker(props: IAppProps, id: string) { }) } -function PreviewApp(props: IPreviewProps, id: string) { +function PreviewApp(props: IPreviewProps, containerId: string) { props.charts = FormatSpec(props.charts.map(chart => chart.visSpec), []) .map((visSpec, index) => { return {...props.charts[index], visSpec} }); + + if (window.document.getElementById(`gwalker-${props.gid}`)) { + window.document.getElementById(containerId)?.remove(); + } + ReactDOM.render( , - document.getElementById(id) + document.getElementById(containerId) ); } diff --git a/app/src/tools/saveTool.tsx b/app/src/tools/saveTool.tsx index 6d866c2..7f1e1af 100644 --- a/app/src/tools/saveTool.tsx +++ b/app/src/tools/saveTool.tsx @@ -15,12 +15,6 @@ import type { IGWHandler } from '@kanaries/graphic-walker/interfaces'; import type { ToolbarButtonItem } from "@kanaries/graphic-walker/components/toolbar/toolbar-button" import type { VizSpecStore } from '@kanaries/graphic-walker/store/visualSpecStore' -function saveJupyterNotebook() { - const rootDocument = window.parent.document; - rootDocument.body.dispatchEvent(new KeyboardEvent('keydown', {key:'s', keyCode: 83, metaKey: true})); - rootDocument.body.dispatchEvent(new KeyboardEvent('keydown', {key:'s', keyCode: 83, ctrlKey: true})); -} - function DocumentTextIconWithRedPoint(iconProps) { return (
@@ -30,12 +24,6 @@ function DocumentTextIconWithRedPoint(iconProps) { ) } -export function hidePreview(id: string) { - setTimeout(() => { - window.parent.document.getElementById(`pygwalker-preview-${id}`)?.remove(); - }, 500) -} - export function getSaveTool( props: IAppProps, gwRef: React.MutableRefObject, @@ -89,10 +77,8 @@ export function getSaveTool( "chartData": await formatExportedChartDatas(chartData), "workflowList": visSpec.map((spec) => chartToWorkflow(spec)) }); - saveJupyterNotebook(); } finally { setSaving(false); - hidePreview(props.id); } if (["json_file", "json_ksf"].indexOf(props.specType) === -1) { diff --git a/pygwalker/api/pygwalker.py b/pygwalker/api/pygwalker.py index c5d4add..15d6128 100644 --- a/pygwalker/api/pygwalker.py +++ b/pygwalker/api/pygwalker.py @@ -254,7 +254,7 @@ def display_on_jupyter_use_widgets(self, iframe_width: Optional[str] = None, ifr display_html(html_widgets) preview_tool.init_display() - preview_tool.render_gw_review(self._get_gw_preview_html()) + preview_tool.async_render_gw_review(self._get_gw_preview_html()) def display_preview_on_jupyter(self): """ @@ -356,7 +356,7 @@ def update_spec(data: Dict[str, Any]): self.workflow_list = data.get("workflowList", []) if self.use_preview: - preview_tool.render_gw_review(self._get_gw_preview_html()) + preview_tool.async_render_gw_review(self._get_gw_preview_html()) save_chart_endpoint(data["chartData"]) diff --git a/pygwalker/services/preview_image.py b/pygwalker/services/preview_image.py index ed1a57d..dcd31b3 100644 --- a/pygwalker/services/preview_image.py +++ b/pygwalker/services/preview_image.py @@ -1,9 +1,11 @@ from typing import Optional, List, Dict, Any +from concurrent.futures.thread import ThreadPoolExecutor import base64 import zlib import json from pydantic import BaseModel, Field +from ipylab import JupyterFrontEnd from pygwalker.utils.encode import DataFrameEncoder from pygwalker.utils.display import display_html @@ -106,7 +108,7 @@ def render_gw_preview_html( "data": data_base64_str }) - props = {"charts": charts, "themeKey": theme_key, "dark": appearance} + props = {"charts": charts, "themeKey": theme_key, "dark": appearance, "gid": gid} container_id = f"pygwalker-preview-{gid}" template = jinja_env.get_template("index.html") @@ -161,6 +163,11 @@ class PreviewImageTool: def __init__(self, gid: str): self.gid = gid self.image_slot_id = f"pygwalker-preview-{gid}" + self.t_pool = ThreadPoolExecutor(1) + try: + self.command_app = JupyterFrontEnd() + except Exception: + self.command_app = None def init_display(self): display_html("", slot_id=self.image_slot_id) @@ -171,3 +178,12 @@ def render(self, charts_map: Dict[str, ChartData]): def render_gw_review(self, html: str): display_html(html, slot_id=self.image_slot_id) + + if self.command_app: + try: + self.command_app.commands.execute('docmanager:save') + except Exception: + pass + + def async_render_gw_review(self, html: str): + self.t_pool.submit(self.render_gw_review, html) diff --git a/pyproject.toml b/pyproject.toml index 02e1f50..3bc59b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,8 @@ dependencies = [ "kanaries_track==0.0.5", "cachetools", "packaging", - "numpy<2.0.0" + "numpy<2.0.0", + "ipylab<=1.0.0" ] [project.urls] homepage = "https://github.com/Kanaries/pygwalker"