From 23bbe1e79baa9fcb0de3dd27925625ab8d6e0e08 Mon Sep 17 00:00:00 2001 From: Carlos Herrero Date: Tue, 20 Dec 2022 19:43:37 +0100 Subject: [PATCH 1/3] Allow opening multiple element --- src/model.ts | 1 - src/panelview/objecttree.tsx | 63 ++++++++++++++++-------------------- 2 files changed, 28 insertions(+), 36 deletions(-) diff --git a/src/model.ts b/src/model.ts index ce308ebf..81d73326 100644 --- a/src/model.ts +++ b/src/model.ts @@ -30,7 +30,6 @@ export class JupyterCadModel implements IJupyterCadModel { this.modelDB = modelDB || new ModelDB(); this.sharedModel.changed.connect(this._onSharedModelChanged); this.sharedModel.awareness.on('change', this._onClientStateChanged); - this.sharedModel; } get isDisposed(): boolean { diff --git a/src/panelview/objecttree.tsx b/src/panelview/objecttree.tsx index da477e79..ec1c9ddd 100644 --- a/src/panelview/objecttree.tsx +++ b/src/panelview/objecttree.tsx @@ -81,7 +81,7 @@ interface IStates { clientId: number | null; // ID of the yjs client id: string; // ID of the component, it is used to identify which component //is the source of awareness updates. - openNodes: (string | number)[]; + openNodes: (number | string)[]; } interface IProps { @@ -107,24 +107,24 @@ class ObjectTreeReact extends React.Component { this.props.cpModel.jcadModel?.sharedModelChanged.connect( this._sharedJcadModelChanged ); - this.props.cpModel.documentChanged.connect((_, changed) => { - if (changed) { + this.props.cpModel.documentChanged.connect((_, document) => { + if (document) { this.props.cpModel.disconnect(this._sharedJcadModelChanged); this.props.cpModel.disconnect(this._handleThemeChange); this.props.cpModel.disconnect(this._onClientSharedStateChanged); - changed.context.model.sharedModelChanged.connect( + document.context.model.sharedModelChanged.connect( this._sharedJcadModelChanged ); - changed.context.model.themeChanged.connect(this._handleThemeChange); - changed.context.model.clientStateChanged.connect( + document.context.model.themeChanged.connect(this._handleThemeChange); + document.context.model.clientStateChanged.connect( this._onClientSharedStateChanged ); this.setState(old => ({ ...old, - filePath: changed.context.localPath, + filePath: document.context.localPath, jcadObject: this.props.cpModel.jcadModel?.getAllObject(), - clientId: changed.context.model.getClientId() + clientId: document.context.model.getClientId() })); } else { this.setState({ @@ -183,13 +183,15 @@ class ObjectTreeReact extends React.Component { }; private _sharedJcadModelChanged = ( - _, - changed: IJupyterCadDocChange + sender: IJupyterCadModel, + change: IJupyterCadDocChange ): void => { - this.setState(old => ({ - ...old, - jcadObject: this.props.cpModel.jcadModel?.getAllObject() - })); + if (change.objectChange) { + this.setState(old => ({ + ...old, + jcadObject: this.props.cpModel.jcadModel?.getAllObject() + })); + } }; private _onClientSharedStateChanged = ( @@ -204,38 +206,32 @@ class ObjectTreeReact extends React.Component { // Update from other components of current client const localState = clientId ? clients.get(clientId) : null; if (localState) { - if ( - localState.selected?.emitter && - localState.selected.emitter !== this.state.id - ) { - const selectedNode = localState.selected.value!; - this.setState(old => ({ - ...old, - selectedNode, - openNodes: [...old.openNodes, selectedNode] - })); + // This is also triggered when an object is selected and the pointer changes + // re-rendering the tree multiple times + if (localState.selected?.emitter && localState.selected?.value) { + const selectedNode = localState.selected.value; + this.setState(old => ({ ...old, selectedNode })); } } } }; render(): React.ReactNode { + const { selectedNode } = this.state; const data = this.stateToTree(); - let selectedNode: (number | string)[] = []; - if (this.state.selectedNode) { - const parentNode = data.filter( - node => node.id === this.state.selectedNode - ); + let selectedNodes: (number | string)[] = []; + if (selectedNode) { + const parentNode = data.filter(node => node.id === selectedNode); if (parentNode.length > 0 && parentNode[0].items.length > 0) { - selectedNode = [parentNode[0].items[0].id]; + selectedNodes = [parentNode[0].items[0].id]; } } + return (
{ this.props.cpModel.jcadModel?.syncSelectedObject(undefined); } }} - onToggleOpenNodes={nodes => - this.setState(old => ({ ...old, openNodes: nodes })) - } RenderNode={options => { // const paddingLeft = 25 * (options.level + 1); const jcadObj = this.getObjectFromName( From d23286bd95c68899b8d7e411b0b2f49152c79fe5 Mon Sep 17 00:00:00 2001 From: Carlos Herrero Date: Tue, 20 Dec 2022 20:18:31 +0100 Subject: [PATCH 2/3] Sync selection from 3D view to tree --- src/panelview/objecttree.tsx | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/src/panelview/objecttree.tsx b/src/panelview/objecttree.tsx index ec1c9ddd..6ff8473a 100644 --- a/src/panelview/objecttree.tsx +++ b/src/panelview/objecttree.tsx @@ -206,18 +206,27 @@ class ObjectTreeReact extends React.Component { // Update from other components of current client const localState = clientId ? clients.get(clientId) : null; if (localState) { - // This is also triggered when an object is selected and the pointer changes - // re-rendering the tree multiple times - if (localState.selected?.emitter && localState.selected?.value) { - const selectedNode = localState.selected.value; - this.setState(old => ({ ...old, selectedNode })); + if ( + localState.selected?.emitter && + localState.selected.emitter !== this.state.id + ) { + const selectedNode = localState.selected.value!; + + const openNodes = [...this.state.openNodes]; + const index = openNodes.indexOf(selectedNode); + + if (index === -1) { + openNodes.push(selectedNode); + } + + this.setState(old => ({ ...old, selectedNode, openNodes })); } } } }; render(): React.ReactNode { - const { selectedNode } = this.state; + const { selectedNode, openNodes } = this.state; const data = this.stateToTree(); let selectedNodes: (number | string)[] = []; if (selectedNode) { @@ -231,6 +240,7 @@ class ObjectTreeReact extends React.Component {
{ onToggleSelectedNodes={id => { if (id && id.length > 0) { let name = id[0] as string; + if (name.includes('#')) { name = name.split('#')[0]; - this.props.cpModel.jcadModel?.syncSelectedObject( name, this.state.id ); + return; + } + + const openNodes = [...this.state.openNodes]; + const index = openNodes.indexOf(name); + + if (index !== -1) { + openNodes.splice(index, 1); + } else { + openNodes.push(name); } + this.setState(old => ({ ...old, openNodes })); } else { this.props.cpModel.jcadModel?.syncSelectedObject(undefined); } From 2d9428e5fb5134c810bb556b05ac734f0b6108fa Mon Sep 17 00:00:00 2001 From: Duc Trung Le Date: Wed, 21 Dec 2022 17:58:49 +0100 Subject: [PATCH 3/3] Add UI test --- ui-tests/package.json | 3 +- ui-tests/tests/tree.spec.ts | 66 ++++++++++++++++++ .../Tree-Display-example1-FCStd-linux.png | Bin 0 -> 10414 bytes 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 ui-tests/tests/tree.spec.ts create mode 100644 ui-tests/tests/tree.spec.ts-snapshots/Tree-Display-example1-FCStd-linux.png diff --git a/ui-tests/package.json b/ui-tests/package.json index e8b71afb..9032ba0d 100644 --- a/ui-tests/package.json +++ b/ui-tests/package.json @@ -6,7 +6,8 @@ "scripts": { "start": "jupyter lab --config jupyter_server_test_config.py", "test": "jlpm playwright test", - "test:update": "jlpm playwright test --update-snapshots" + "test:update": "jlpm playwright test --update-snapshots", + "test:debug": "PWDEBUG=1 jlpm playwright test" }, "devDependencies": { "@jupyterlab/galata": "^5.0.0-alpha.14" diff --git a/ui-tests/tests/tree.spec.ts b/ui-tests/tests/tree.spec.ts new file mode 100644 index 00000000..022bc912 --- /dev/null +++ b/ui-tests/tests/tree.spec.ts @@ -0,0 +1,66 @@ +import { expect, test, galata } from '@jupyterlab/galata'; +import path from 'path'; + +test.use({ autoGoto: false }); + +test.describe('Tree UI test', () => { + let errors = 0; + test.beforeEach(async ({ page, request }) => { + page.setViewportSize({ width: 1920, height: 1080 }); + page.on('console', message => { + if (message.type() === 'error') { + errors += 1; + } + }); + + const content = galata.newContentsHelper(request); + await content.deleteDirectory('/examples'); + await content.uploadDirectory( + path.resolve(__dirname, '../../examples'), + '/examples' + ); + }); + + test.afterEach(async ({ page }) => { + errors = 0; + }); + + test(`Should display the object tree`, async ({ page }) => { + await page.goto(); + + const fileName = 'example1.FCStd'; + const fullPath = `examples/${fileName}`; + await page.notebook.openByPath(fullPath); + await page.notebook.activate(fullPath); + await page.locator('div.jpcad-Spinner').waitFor({ state: 'hidden' }); + + // Close the property panel + await page + .getByRole('tablist', { name: 'alternate sidebar' }) + .getByRole('tab', { name: 'JupyterCad Control Panel' }) + .click(); + + await page + .locator('[data-test-id="react-tree-root"] div.jpcad-control-panel-tree') + .nth(0) + .click(); + await page + .locator('[data-test-id="react-tree-root"] div.jpcad-control-panel-tree') + .nth(2) + .click(); + await page + .locator('[data-test-id="react-tree-root"] div.jpcad-control-panel-tree') + .nth(4) + .click(); + + expect(errors).toBe(0); + const tree = await page.getByRole('region', { + name: 'Objects tree Section' + }); + if (tree) { + expect(await tree.screenshot()).toMatchSnapshot({ + name: `Tree-Display-${fileName}.png` + }); + } + }); +}); diff --git a/ui-tests/tests/tree.spec.ts-snapshots/Tree-Display-example1-FCStd-linux.png b/ui-tests/tests/tree.spec.ts-snapshots/Tree-Display-example1-FCStd-linux.png new file mode 100644 index 0000000000000000000000000000000000000000..84202a99909a717b7d6cb8ad8eea857b42f1b565 GIT binary patch literal 10414 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 RUqBSl(=j|-sD173e*hW>>E{3d literal 0 HcmV?d00001