From cebd15ec558ae8cac68cfc437183ab996f62bfae Mon Sep 17 00:00:00 2001 From: Duc Trung Le Date: Fri, 20 Jan 2023 14:05:07 +0100 Subject: [PATCH] Refactor widget --- .gitignore | 3 + examples/api.ipynb | 39 ++- jupytercad/__init__.py | 1 + jupytercad/notebook/__init__.py | 2 + jupytercad/{widget => notebook}/_frontend.py | 0 .../document.py => notebook/cad_document.py} | 22 +- jupytercad/notebook/objects/__init__.py | 3 + jupytercad/notebook/objects/base.py | 11 + jupytercad/notebook/objects/box.py | 30 +++ jupytercad/notebook/objects/placement.py | 19 ++ jupytercad/notebook/utils.py | 5 + jupytercad/widget/__init__.py | 1 - package.json | 250 +++++++++--------- src/notebookrenderer/model.ts | 90 +++++++ src/notebookrenderer/renderer.ts | 61 ----- src/notebookrenderer/types.ts | 15 +- src/notebookrenderer/view.ts | 52 ++++ src/notebookrenderer/widget.ts | 37 ++- yarn.lock | 10 +- 19 files changed, 417 insertions(+), 234 deletions(-) create mode 100644 jupytercad/notebook/__init__.py rename jupytercad/{widget => notebook}/_frontend.py (100%) rename jupytercad/{widget/document.py => notebook/cad_document.py} (59%) create mode 100644 jupytercad/notebook/objects/__init__.py create mode 100644 jupytercad/notebook/objects/base.py create mode 100644 jupytercad/notebook/objects/box.py create mode 100644 jupytercad/notebook/objects/placement.py create mode 100644 jupytercad/notebook/utils.py delete mode 100644 jupytercad/widget/__init__.py create mode 100644 src/notebookrenderer/model.ts delete mode 100644 src/notebookrenderer/renderer.ts create mode 100644 src/notebookrenderer/view.ts diff --git a/.gitignore b/.gitignore index 3969f5f2..c06f7ff3 100644 --- a/.gitignore +++ b/.gitignore @@ -118,3 +118,6 @@ src/_interface/ # Integration tests ui-tests/test-results/ ui-tests/playwright-report/ + +# Hatchling +jupytercad/_version.py \ No newline at end of file diff --git a/examples/api.ipynb b/examples/api.ipynb index 0abe18ff..1b3c848d 100644 --- a/examples/api.ipynb +++ b/examples/api.ipynb @@ -2,47 +2,42 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "from jupytercad.widget import Document" + "from jupytercad.notebook import CadDocument" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "a = Document(\"examples/common.FCStd\")" + "a = CadDocument(\"examples/common.FCStd\")" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "5a02cb81887e48e294805571aaa81169", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": [ - "Document(path='examples/common.FCStd')" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "a" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from jupytercad.notebook import Box\n", + "box = Box(10,2,2)\n", + "a.add_object(box)" + ] + }, { "cell_type": "code", "execution_count": null, diff --git a/jupytercad/__init__.py b/jupytercad/__init__.py index 2566541b..0b95d36c 100644 --- a/jupytercad/__init__.py +++ b/jupytercad/__init__.py @@ -1,4 +1,5 @@ from ._version import __version__ +from .notebook import CadDocument def _jupyter_labextension_paths(): return [{'src': 'labextension', 'dest': 'jupytercad'}] diff --git a/jupytercad/notebook/__init__.py b/jupytercad/notebook/__init__.py new file mode 100644 index 00000000..4becca2a --- /dev/null +++ b/jupytercad/notebook/__init__.py @@ -0,0 +1,2 @@ +from .cad_document import CadDocument +from .objects import * \ No newline at end of file diff --git a/jupytercad/widget/_frontend.py b/jupytercad/notebook/_frontend.py similarity index 100% rename from jupytercad/widget/_frontend.py rename to jupytercad/notebook/_frontend.py diff --git a/jupytercad/widget/document.py b/jupytercad/notebook/cad_document.py similarity index 59% rename from jupytercad/widget/document.py rename to jupytercad/notebook/cad_document.py index 6947929e..1113e375 100644 --- a/jupytercad/widget/document.py +++ b/jupytercad/notebook/cad_document.py @@ -1,12 +1,16 @@ -from cmath import log -from typing import Dict +from typing import Dict, List from ipywidgets import DOMWidget from traitlets import Unicode + +from .objects import BaseObject + +from .utils import MESSAGE_ACTION from ._frontend import module_name, module_version -class Document(DOMWidget): + +class CadDocument(DOMWidget): _model_name = Unicode('JupyterCadWidgetModel').tag(sync=True) _model_module = Unicode(module_name).tag(sync=True) @@ -20,4 +24,14 @@ class Document(DOMWidget): def __init__(self, path: str, **kwargs) -> None: super().__init__(**kwargs) self.path = path - \ No newline at end of file + self.on_msg(self._handle_frontend_msg) + + def _handle_frontend_msg( + self, model: 'CadDocument', msg: Dict, buffer: List + ) -> None: + pass + + def add_object(self, object: BaseObject): + self.send( + {'action': MESSAGE_ACTION.ADD_OBJECT, 'payload': object.to_dict()} + ) diff --git a/jupytercad/notebook/objects/__init__.py b/jupytercad/notebook/objects/__init__.py new file mode 100644 index 00000000..83e3d9a4 --- /dev/null +++ b/jupytercad/notebook/objects/__init__.py @@ -0,0 +1,3 @@ +from .box import Box +from .placement import Placement +from .base import BaseObject \ No newline at end of file diff --git a/jupytercad/notebook/objects/base.py b/jupytercad/notebook/objects/base.py new file mode 100644 index 00000000..e8eea297 --- /dev/null +++ b/jupytercad/notebook/objects/base.py @@ -0,0 +1,11 @@ +from abc import ABC, abstractmethod +from typing import Dict + +class BaseObject(ABC): + @abstractmethod + def to_dict(self) -> Dict: + pass + + @abstractmethod + def from_dict(self, value: Dict) -> None: + pass diff --git a/jupytercad/notebook/objects/box.py b/jupytercad/notebook/objects/box.py new file mode 100644 index 00000000..6926de65 --- /dev/null +++ b/jupytercad/notebook/objects/box.py @@ -0,0 +1,30 @@ +from typing import Dict + +from .placement import Placement +from .base import BaseObject + + +class Box(BaseObject): + def __init__( + self, Length=1, Width=1, Height=1, Placement=Placement() + ) -> None: + super().__init__() + self.Length = Length + self.Width = Width + self.Height = Height + self.Placement = Placement + + def from_dict(self, value: Dict) -> None: + self.Length = value.get('Length', None) + self.Width = value.get('Width', None) + self.Height = value.get('Height', None) + newPlacement = Placement() + self.Placement = newPlacement.from_dict(value.get('Placement', None)) + + def to_dict(self) -> Dict: + return dict( + Length=self.Length, + Width=self.Width, + Height=self.Height, + Placement=self.Placement.to_dict(), + ) diff --git a/jupytercad/notebook/objects/placement.py b/jupytercad/notebook/objects/placement.py new file mode 100644 index 00000000..456fdd1b --- /dev/null +++ b/jupytercad/notebook/objects/placement.py @@ -0,0 +1,19 @@ +from typing import Dict +from .base import BaseObject + + +class Placement(BaseObject): + + def __init__(self, Position=[0,0,0], Axis=[1,0,0], Angle= 0) -> None: + super().__init__() + self.Position = Position + self.Axis = Axis + self.Angle = Angle + + def to_dict(self) -> Dict: + return dict(Position=self.Position, Axis=self.Axis, Angle=self.Angle) + + def from_dict(self, value: Dict) -> None: + self.Position = value.get('Position', None) + self.Axis = value.get('Axis', None) + self.Angle = value.get('Angle', None) \ No newline at end of file diff --git a/jupytercad/notebook/utils.py b/jupytercad/notebook/utils.py new file mode 100644 index 00000000..fd5396f5 --- /dev/null +++ b/jupytercad/notebook/utils.py @@ -0,0 +1,5 @@ +from enum import Enum + + +class MESSAGE_ACTION(str, Enum): + ADD_OBJECT = 'add_object' diff --git a/jupytercad/widget/__init__.py b/jupytercad/widget/__init__.py deleted file mode 100644 index 1396bb55..00000000 --- a/jupytercad/widget/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .document import Document \ No newline at end of file diff --git a/package.json b/package.json index 97239a44..e3f3793e 100644 --- a/package.json +++ b/package.json @@ -1,128 +1,128 @@ { - "name": "jupytercad", - "version": "0.1.0", - "description": "A JupyterLab extension for 3D modelling.", - "keywords": [ - "jupyter", - "jupyterlab", - "jupyterlab-extension" - ], - "homepage": "https://github.com/QuantStack/jupytercad", - "bugs": { - "url": "https://github.com/QuantStack/jupytercad/issues" - }, - "license": "BSD-3-Clause", - "author": { - "name": "Trung Le", - "email": "leductrungxf@gmail.com" - }, - "files": [ - "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}", - "style/**/*.{css,js,eot,gif,html,jpg,json,png,svg,woff2,ttf}" - ], - "main": "lib/index.js", - "types": "lib/index.d.ts", - "style": "style/index.css", - "repository": { - "type": "git", - "url": "https://github.com/QuantStack/jupytercad.git" - }, - "scripts": { - "build": "jlpm build:schema && jlpm run build:lib && jlpm run build:worker && jlpm run build:labextension:dev", - "build:schema": "json2ts -i src/schema -o src/_interface --no-unknownAny --unreachableDefinitions --cwd ./src/schema && cd src/schema && node ../../schema.js", - "build:prod": "jlpm run clean && jlpm build:schema && jlpm run build:worker:prod && jlpm run build:lib && jlpm run build:labextension", - "build:labextension": "jupyter labextension build .", - "build:labextension:dev": "jupyter labextension build --development True .", - "build:lib": "tsc", - "build:worker": "webpack --config worker.webpack.config.js --mode=development", - "build:worker:prod": "webpack --config worker.webpack.config.js --mode=production", - "clean": "jlpm run clean:lib", - "clean:lib": "rimraf lib tsconfig.tsbuildinfo", - "clean:labextension": "rimraf jupytercad/labextension", - "clean:all": "jlpm run clean:lib && jlpm run clean:labextension", - "eslint": "eslint . --ext .ts,.tsx --fix", - "eslint:check": "eslint . --ext .ts,.tsx", - "install:extension": "jlpm run build", - "lint": "jlpm prettier && jlpm eslint", - "lint:check": "jlpm prettier:check && jlpm eslint:check", - "prettier": "jlpm prettier:base --write --list-different", - "prettier:base": "prettier \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"", - "prettier:check": "jlpm prettier:base --check", - "watch": "run-p watch:worker watch:src watch:labextension", - "watch:src": "tsc -w", - "watch:worker": "webpack --config worker.webpack.config.js --watch --mode=development", - "watch:labextension": "jupyter labextension watch ." - }, - "dependencies": { - "@deathbeds/jupyterlab-rjsf": "^1.1.0", - "@jupyter-widgets/base": "^6.0.2", - "@jupyter/ydoc": "~0.2.2", - "@jupyterlab/application": "~4.0.0-alpha.17", - "@jupyterlab/apputils": "~4.0.0-alpha.17", - "@jupyterlab/collaboration": "~4.0.0-alpha.17", - "@jupyterlab/coreutils": "~6.0.0-alpha.17", - "@jupyterlab/docregistry": "~4.0.0-alpha.17", - "@jupyterlab/mainmenu": "~4.0.0-alpha.17", - "@jupyterlab/observables": "~5.0.0-alpha.17", - "@jupyterlab/services": "~7.0.0-alpha.17", - "@jupyterlab/translation": "~4.0.0-alpha.17", - "@jupyterlab/ui-components": "~4.0.0-alpha.32", - "@lumino/commands": "~2.0.0-alpha.6", - "@lumino/coreutils": "~2.0.0-alpha.6", - "@lumino/signaling": "~2.0.0-alpha.6", - "@lumino/widgets": "~2.0.0-alpha.6", - "@naisutech/react-tree": "^3.0.1", - "@rjsf/core": "^4.2.0", - "@types/d3-color": "^3.1.0", - "@types/three": "^0.134.0", - "d3-color": "^3.1.0", - "jupytercad-opencascade": "^0.1.0", - "styled-components": "^5.3.6", - "three": "^0.135.0", - "three-mesh-bvh": "^0.5.17", - "uuid": "^8.3.2", - "react": "^17.0.1" - }, - "resolutions": { - "@jupyterlab/apputils": "~4.0.0-alpha.17", - "@lumino/coreutils": "~2.0.0-alpha.6" - }, - "devDependencies": { - "@apidevtools/json-schema-ref-parser": "^9.0.9", - "@jupyterlab/builder": "~4.0.0-alpha.17", - "@types/node": "^16.11.10", - "@typescript-eslint/eslint-plugin": "^4.8.1", - "@typescript-eslint/parser": "^4.8.1", - "copy-webpack-plugin": "^10.0.0", - "eslint": "^7.14.0", - "eslint-config-prettier": "^6.15.0", - "eslint-plugin-prettier": "^3.1.4", - "file-loader": "^6.2.0", - "json-schema-to-typescript": "^10.1.5", - "npm-run-all": "^4.1.5", - "prettier": "^2.1.1", - "rimraf": "^3.0.2", - "source-map-loader": "^3.0.0", - "ts-loader": "^9.2.6", - "typescript": "~4.1.3" - }, - "sideEffects": [ - "style/*.css", - "style/index.js" - ], - "styleModule": "style/index.js", - "publishConfig": { - "access": "public" - }, - "jupyterlab": { - "extension": true, - "outputDir": "jupytercad/labextension", - "webpackConfig": "./extension.webpack.config.js", - "sharedPackages": { - "@jupyter-widgets/base": { - "bundled": false, - "singleton": true - } - } + "name": "jupytercad", + "version": "0.1.0", + "description": "A JupyterLab extension for 3D modelling.", + "keywords": [ + "jupyter", + "jupyterlab", + "jupyterlab-extension" + ], + "homepage": "https://github.com/QuantStack/jupytercad", + "bugs": { + "url": "https://github.com/QuantStack/jupytercad/issues" + }, + "license": "BSD-3-Clause", + "author": { + "name": "Trung Le", + "email": "leductrungxf@gmail.com" + }, + "files": [ + "lib/**/*.{d.ts,eot,gif,html,jpg,js,js.map,json,png,svg,woff2,ttf}", + "style/**/*.{css,js,eot,gif,html,jpg,json,png,svg,woff2,ttf}" + ], + "main": "lib/index.js", + "types": "lib/index.d.ts", + "style": "style/index.css", + "repository": { + "type": "git", + "url": "https://github.com/QuantStack/jupytercad.git" + }, + "scripts": { + "build": "jlpm build:schema && jlpm run build:lib && jlpm run build:worker && jlpm run build:labextension:dev", + "build:schema": "json2ts -i src/schema -o src/_interface --no-unknownAny --unreachableDefinitions --cwd ./src/schema && cd src/schema && node ../../schema.js", + "build:prod": "jlpm run clean && jlpm build:schema && jlpm run build:worker:prod && jlpm run build:lib && jlpm run build:labextension", + "build:labextension": "jupyter labextension build .", + "build:labextension:dev": "jupyter labextension build --development True .", + "build:lib": "tsc", + "build:worker": "webpack --config worker.webpack.config.js --mode=development", + "build:worker:prod": "webpack --config worker.webpack.config.js --mode=production", + "clean": "jlpm run clean:lib", + "clean:lib": "rimraf lib tsconfig.tsbuildinfo", + "clean:labextension": "rimraf jupytercad/labextension", + "clean:all": "jlpm run clean:lib && jlpm run clean:labextension", + "eslint": "eslint . --ext .ts,.tsx --fix", + "eslint:check": "eslint . --ext .ts,.tsx", + "install:extension": "jlpm run build", + "lint": "jlpm prettier && jlpm eslint", + "lint:check": "jlpm prettier:check && jlpm eslint:check", + "prettier": "jlpm prettier:base --write --list-different", + "prettier:base": "prettier \"**/*{.ts,.tsx,.js,.jsx,.css,.json,.md}\"", + "prettier:check": "jlpm prettier:base --check", + "watch": "run-p watch:worker watch:src watch:labextension", + "watch:src": "tsc -w", + "watch:worker": "webpack --config worker.webpack.config.js --watch --mode=development", + "watch:labextension": "jupyter labextension watch ." + }, + "dependencies": { + "@deathbeds/jupyterlab-rjsf": "^1.1.0", + "@jupyter-widgets/base": "^6.0.2", + "@jupyter/ydoc": "~0.2.2", + "@jupyterlab/application": "~4.0.0-alpha.17", + "@jupyterlab/apputils": "~4.0.0-alpha.17", + "@jupyterlab/collaboration": "~4.0.0-alpha.17", + "@jupyterlab/coreutils": "~6.0.0-alpha.17", + "@jupyterlab/docregistry": "~4.0.0-alpha.17", + "@jupyterlab/mainmenu": "~4.0.0-alpha.17", + "@jupyterlab/observables": "~5.0.0-alpha.17", + "@jupyterlab/services": "~7.0.0-alpha.17", + "@jupyterlab/translation": "~4.0.0-alpha.17", + "@jupyterlab/ui-components": "~4.0.0-alpha.32", + "@lumino/commands": "~2.0.0-alpha.6", + "@lumino/coreutils": "~2.0.0-alpha.6", + "@lumino/signaling": "~2.0.0-alpha.6", + "@lumino/widgets": "~2.0.0-alpha.6", + "@naisutech/react-tree": "^3.0.1", + "@rjsf/core": "^4.2.0", + "d3-color": "^3.1.0", + "jupytercad-opencascade": "^0.1.0", + "react": "^17.0.1", + "styled-components": "^5.3.6", + "three": "^0.135.0", + "three-mesh-bvh": "^0.5.17", + "uuid": "^8.3.2" + }, + "resolutions": { + "@jupyterlab/apputils": "~4.0.0-alpha.17", + "@lumino/coreutils": "~2.0.0-alpha.6" + }, + "devDependencies": { + "@apidevtools/json-schema-ref-parser": "^9.0.9", + "@jupyterlab/builder": "~4.0.0-alpha.17", + "@types/d3-color": "^3.1.0", + "@types/node": "^16.11.10", + "@types/three": "^0.134.0", + "@typescript-eslint/eslint-plugin": "^4.8.1", + "@typescript-eslint/parser": "^4.8.1", + "copy-webpack-plugin": "^10.0.0", + "eslint": "^7.14.0", + "eslint-config-prettier": "^6.15.0", + "eslint-plugin-prettier": "^3.1.4", + "file-loader": "^6.2.0", + "json-schema-to-typescript": "^10.1.5", + "npm-run-all": "^4.1.5", + "prettier": "^2.1.1", + "rimraf": "^3.0.2", + "source-map-loader": "^3.0.0", + "ts-loader": "^9.2.6", + "typescript": "~4.1.3" + }, + "sideEffects": [ + "style/*.css", + "style/index.js" + ], + "styleModule": "style/index.js", + "publishConfig": { + "access": "public" + }, + "jupyterlab": { + "extension": true, + "outputDir": "jupytercad/labextension", + "webpackConfig": "./extension.webpack.config.js", + "sharedPackages": { + "@jupyter-widgets/base": { + "bundled": false, + "singleton": true + } } + } } diff --git a/src/notebookrenderer/model.ts b/src/notebookrenderer/model.ts new file mode 100644 index 00000000..95daae2d --- /dev/null +++ b/src/notebookrenderer/model.ts @@ -0,0 +1,90 @@ +import { IDisposable } from '@lumino/disposable'; +import { IDocumentProviderFactory } from '@jupyterlab/docprovider'; +import { Context, DocumentRegistry } from '@jupyterlab/docregistry'; +import { ServiceManager } from '@jupyterlab/services'; +import { IJupyterCadModel } from '../types'; +import { IWidgetMessage } from './types'; +import { ISignal, Signal } from '@lumino/signaling'; + +export class NotebookWidgetModel implements IDisposable { + constructor(options: NotebookWidgetModel.IOptions) { + this._docProviderFactory = options.docProviderFactory; + this._manager = options.manager; + this._docModelFactory = options.docModelFactory; + this._path = options.path; + } + + get isDisposed(): boolean { + return this._isDisposed; + } + + get messageSent(): ISignal { + return this._messageSent; + } + + dispose(): void { + if (this._isDisposed) { + return; + } + this._context?.dispose(); + this._isDisposed = true; + } + + async createContext(): Promise> { + if (this._context) { + return this._context; + } + this._context = new Context({ + path: this._path, + manager: this._manager, + factory: this._docModelFactory, + docProviderFactory: this._docProviderFactory + }); + await this._context.initialize(false); + const model = this._context.model; + model.sharedObjectsChanged.connect(() => { + // this.sendMsg({action:'updateAllObjects', payload: model.getAllObject()}) + }); + return this._context; + } + + handleMessage(msg: IWidgetMessage): void { + const { action, payload } = msg; + switch (action) { + case 'add_object': + this._context?.model.sharedModel.addObject({ + name: 'name', + visible: true, + shape: 'Part::Box', + parameters: payload + }); + break; + case 'remove_object': + break; + + default: + break; + } + } + + protected sendMsg(msg: IWidgetMessage): void { + this._messageSent.emit(msg); + } + + private _context: Context | undefined; + private _isDisposed = false; + private _path: string; + private _manager: ServiceManager.IManager; + private _docProviderFactory: IDocumentProviderFactory; + private _docModelFactory: DocumentRegistry.IModelFactory; + private _messageSent = new Signal(this); +} + +export namespace NotebookWidgetModel { + export interface IOptions { + path: string; + manager: ServiceManager.IManager; + docProviderFactory: IDocumentProviderFactory; + docModelFactory: DocumentRegistry.IModelFactory; + } +} diff --git a/src/notebookrenderer/renderer.ts b/src/notebookrenderer/renderer.ts deleted file mode 100644 index 9602f9f6..00000000 --- a/src/notebookrenderer/renderer.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { IDocumentProviderFactory } from '@jupyterlab/docprovider'; -import { Context, DocumentRegistry } from '@jupyterlab/docregistry'; -import { ServiceManager } from '@jupyterlab/services'; -import { MessageLoop } from '@lumino/messaging'; -import { Widget } from '@lumino/widgets'; - -import { ToolbarModel } from '../toolbar/model'; -import { ToolbarWidget } from '../toolbar/widget'; -import { IJupyterCadModel } from '../types'; -import { JupyterCadPanel, JupyterCadWidget } from '../widget'; -import { CLASS_NAME, INotebookRendererOptions } from './types'; - -export class NotebookRenderer extends Widget { - /** - * Construct a new output widget. - */ - constructor(options: INotebookRendererOptions) { - super(); - this._docProviderFactory = options.docProviderFactory; - this._manager = options.manager; - this._modelFactory = options.modelFactory; - this.addClass(CLASS_NAME); - } - - dispose(): void { - this._jcadWidget.context.dispose(); - super.dispose(); - } - renderModel(path: string): Promise { - const context = new Context({ - manager: this._manager, - path, - factory: this._modelFactory, - docProviderFactory: this._docProviderFactory - }); - return context.initialize(false).then(() => { - const content = new JupyterCadPanel(context); - const toolbarModel = new ToolbarModel({ panel: content, context }); - const toolbar = new ToolbarWidget({ - model: toolbarModel - }); - this._jcadWidget = new JupyterCadWidget({ context, content, toolbar }); - MessageLoop.sendMessage(this._jcadWidget, Widget.Msg.BeforeAttach); - this.node.appendChild(this._jcadWidget.node); - MessageLoop.sendMessage(this._jcadWidget, Widget.Msg.AfterAttach); - }); - } - - onResize = (): void => { - if (this._jcadWidget) { - MessageLoop.sendMessage( - this._jcadWidget, - Widget.ResizeMessage.UnknownSize - ); - } - }; - private _jcadWidget: JupyterCadWidget; - private _manager: ServiceManager.IManager; - private _docProviderFactory: IDocumentProviderFactory; - private _modelFactory: DocumentRegistry.IModelFactory; -} diff --git a/src/notebookrenderer/types.ts b/src/notebookrenderer/types.ts index 323cd9f4..18ebbf48 100644 --- a/src/notebookrenderer/types.ts +++ b/src/notebookrenderer/types.ts @@ -1,13 +1,8 @@ -import { IDocumentProviderFactory } from '@jupyterlab/docprovider'; -import { DocumentRegistry } from '@jupyterlab/docregistry'; -import { ServiceManager } from '@jupyterlab/services'; +import { IDict } from '../types'; -import { IJupyterCadModel } from '../types'; +export type IWidgetMessageAction = 'add_object' | 'remove_object'; -export const CLASS_NAME = 'mimerenderer-jupytercad'; - -export interface INotebookRendererOptions { - manager: ServiceManager.IManager; - docProviderFactory: IDocumentProviderFactory; - modelFactory: DocumentRegistry.IModelFactory; +export interface IWidgetMessage { + action: IWidgetMessageAction; + payload: IDict; } diff --git a/src/notebookrenderer/view.ts b/src/notebookrenderer/view.ts new file mode 100644 index 00000000..38064e9b --- /dev/null +++ b/src/notebookrenderer/view.ts @@ -0,0 +1,52 @@ +import { MessageLoop } from '@lumino/messaging'; +import { Widget } from '@lumino/widgets'; + +import { ToolbarModel } from '../toolbar/model'; +import { ToolbarWidget } from '../toolbar/widget'; +import { JupyterCadPanel, JupyterCadWidget } from '../widget'; +import { NotebookWidgetModel } from './model'; + +export const CLASS_NAME = 'mimerenderer-jupytercad'; + +export class NotebookRenderer extends Widget { + /** + * Construct a new output widget. + */ + constructor(options: { model: NotebookWidgetModel }) { + super(); + this._model = options.model; + this.addClass(CLASS_NAME); + } + + dispose(): void { + if (this.isDisposed) { + return; + } + this._model.dispose(); + super.dispose(); + } + async renderModel(): Promise { + const context = await this._model.createContext(); + + const content = new JupyterCadPanel(context); + const toolbar = new ToolbarWidget({ + model: new ToolbarModel({ panel: content, context }) + }); + this._jcadWidget = new JupyterCadWidget({ context, content, toolbar }); + + MessageLoop.sendMessage(this._jcadWidget, Widget.Msg.BeforeAttach); + this.node.appendChild(this._jcadWidget.node); + MessageLoop.sendMessage(this._jcadWidget, Widget.Msg.AfterAttach); + } + + onResize = (): void => { + if (this._jcadWidget) { + MessageLoop.sendMessage( + this._jcadWidget, + Widget.ResizeMessage.UnknownSize + ); + } + }; + private _jcadWidget: JupyterCadWidget; + private _model: NotebookWidgetModel; +} diff --git a/src/notebookrenderer/widget.ts b/src/notebookrenderer/widget.ts index f09bd23c..474154d8 100644 --- a/src/notebookrenderer/widget.ts +++ b/src/notebookrenderer/widget.ts @@ -1,6 +1,7 @@ import { DOMWidgetModel, DOMWidgetView, + IBackboneModelOptions, ISerializers, WidgetModel, WidgetView @@ -9,13 +10,17 @@ import { IDocumentProviderFactory } from '@jupyterlab/docprovider'; import { DocumentRegistry } from '@jupyterlab/docregistry'; import { ServiceManager } from '@jupyterlab/services'; import { Message, MessageLoop } from '@lumino/messaging'; +import { ISignal, Signal } from '@lumino/signaling'; import { Widget } from '@lumino/widgets'; +import { ObjectHash } from 'backbone'; import { JupyterCadFCModelFactory } from '../fcplugin/modelfactory'; import { JupyterCadJcadModelFactory } from '../jcadplugin/modelfactory'; import { IAnnotationModel, IJupyterCadModel } from '../types'; -import { NotebookRenderer } from './renderer'; +import { NotebookWidgetModel } from './model'; +import { IWidgetMessage } from './types'; import { MODULE_NAME, MODULE_VERSION } from './version'; +import { NotebookRenderer } from './view'; export class JupyterCadWidgetModel extends DOMWidgetModel { defaults() { @@ -31,6 +36,17 @@ export class JupyterCadWidgetModel extends DOMWidgetModel { }; } + get messageReceived(): ISignal { + return this._messageReceived; + } + + initialize(attributes: ObjectHash, options: IBackboneModelOptions): void { + super.initialize(attributes, options); + this.listenTo(this, 'msg:custom', msg => { + this._messageReceived.emit(msg as IWidgetMessage); + }); + } + docModelFactory(): | DocumentRegistry.IModelFactory | undefined { @@ -61,6 +77,8 @@ export class JupyterCadWidgetModel extends DOMWidgetModel { static view_name = 'JupyterCadWidgetView'; static view_module = MODULE_NAME; static view_module_version = MODULE_VERSION; + + private _messageReceived = new Signal(this); } export class JupyterCadWidgetView extends DOMWidgetView { @@ -84,18 +102,24 @@ export class JupyterCadWidgetView extends DOMWidgetView { const model = this.model as JupyterCadWidgetModel; const modelFactory = model.docModelFactory(); if (modelFactory) { - this._view = new NotebookRenderer({ + const notebookWidgetModel = new NotebookWidgetModel({ + path: this.model.get('path'), manager: JupyterCadWidgetModel.serviceManager, docProviderFactory: JupyterCadWidgetModel.docProviderFactory, - modelFactory: modelFactory + docModelFactory: modelFactory }); + model.messageReceived.connect((_, msg) => + notebookWidgetModel.handleMessage(msg) + ); + notebookWidgetModel.messageSent.connect((_, msg) => this.send(msg)); + this._notebookWidgetModel = notebookWidgetModel; } } async render() { super.render(); - if (this._view) { - await this._view.renderModel(this.model.get('path')); - + if (this._notebookWidgetModel) { + this._view = new NotebookRenderer({ model: this._notebookWidgetModel }); + await this._view.renderModel(); MessageLoop.sendMessage(this._view, Widget.Msg.BeforeAttach); this.el.appendChild(this._view.node); MessageLoop.sendMessage(this._view, Widget.Msg.AfterAttach); @@ -103,4 +127,5 @@ export class JupyterCadWidgetView extends DOMWidgetView { } private _view?: NotebookRenderer; + private _notebookWidgetModel?: NotebookWidgetModel; } diff --git a/yarn.lock b/yarn.lock index fcfa6bd6..914f75f6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -928,7 +928,7 @@ "@lumino/coreutils" "^1.11.0" "@lumino/widgets" "^1.33.0" -"@jupyterlab/rendermime-interfaces@^3.8.0-alpha.17", "@jupyterlab/rendermime-interfaces@~3.8.0-alpha.17": +"@jupyterlab/rendermime-interfaces@^3.8.0-alpha.17": version "3.8.0-alpha.17" resolved "https://registry.yarnpkg.com/@jupyterlab/rendermime-interfaces/-/rendermime-interfaces-3.8.0-alpha.17.tgz#79943eae7f4eb2fc773138f4f2ac99e8ef5dfd22" integrity sha512-WU6MTvrnRgoxrN5ZnT63s/Ag1n3Q/DeFpq8sBcoglzckCcmVIi0OwNHZZMFQWw1jQgDQfr0SXQgXKDxtg6pgzQ== @@ -3586,9 +3586,9 @@ functions-have-names@^1.2.2: integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.3.tgz#063c84329ad93e83893c7f4f243ef63ffa351385" - integrity sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A== + version "1.2.0" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.0.tgz#7ad1dc0535f3a2904bba075772763e5051f6d05f" + integrity sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q== dependencies: function-bind "^1.1.1" has "^1.0.3" @@ -6781,4 +6781,4 @@ yjs@^13.5.17, yjs@^13.5.40: resolved "https://registry.yarnpkg.com/yjs/-/yjs-13.5.44.tgz#1c79ec7407963e07f44174cffcfde5b58a62b0da" integrity sha512-UL+abIh2lQonqXfaJ+en7z9eGshpY11j1zNLc2kDYs0vrTjee4gZJUXC3ZsuhP6geQt0IRU04epCGRaVPQAVCA== dependencies: - lib0 "^0.2.49" \ No newline at end of file + lib0 "^0.2.49"