From 171d847e7dee9dac9f79cc1867946d536ffed415 Mon Sep 17 00:00:00 2001 From: Paul Elliott Date: Mon, 15 Jan 2024 23:16:18 -0500 Subject: [PATCH] feat(imjoy-api): create service, move dispatchSpawn to render --- .github/workflows/test.yml | 16 +--- package.json | 2 - packages/element/examples/view-3d.ts | 2 - packages/element/src/itk-view-2d-vtkjs.ts | 16 ++-- packages/element/src/itk-view-2d.ts | 12 +-- packages/element/src/itk-view-3d-vtkjs.ts | 17 ++-- packages/element/src/itk-view-3d.ts | 16 ++-- packages/element/src/itk-viewer-3d.ts | 50 +++++++++++ packages/element/src/itk-viewer-element.ts | 6 +- packages/element/src/itk-viewport.ts | 24 ++---- packages/element/src/spawn-controller.ts | 59 +++++-------- packages/imjoy-api/index.html | 86 +++++++++++++++++++ packages/imjoy-api/package.json | 10 ++- packages/imjoy-api/src/remote-control.html | 63 ++++++++++++++ packages/imjoy-api/src/viewer.cy.ts | 64 ++------------ packages/imjoy-api/src/viewer.ts | 34 +++++--- packages/imjoy-api/vite.config.ts | 28 ++++++ .../remote-viewport/src/remote-machine.ts | 1 - packages/viewer/src/view-2d.cy.ts | 7 +- packages/viewer/src/viewer.cy.ts | 7 +- packages/viewer/src/viewer.ts | 15 +++- packages/viewer/src/viewport.cy.ts | 25 ++---- packages/viewer/src/viewport.ts | 3 - packages/vtkjs/src/view-2d-vtkjs.cy.ts | 6 +- pnpm-lock.yaml | 26 +++--- 25 files changed, 380 insertions(+), 215 deletions(-) create mode 100644 packages/element/src/itk-viewer-3d.ts create mode 100644 packages/imjoy-api/index.html create mode 100644 packages/imjoy-api/src/remote-control.html create mode 100644 packages/imjoy-api/vite.config.ts diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 30f825c1..8de832bf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -27,8 +27,7 @@ jobs: - name: Test with Chrome uses: cypress-io/github-action@v5 with: - start: pnpm run test:ci - record: true + start: pnpm run test component: true browser: chrome env: @@ -38,20 +37,9 @@ jobs: - name: Test with Firefox uses: cypress-io/github-action@v5 with: - start: pnpm run test:ci - record: true + start: pnpm run cy:component component: true browser: firefox env: CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - # Not quite read for automated publication - #- name: Publish - #env: - #GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - #NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - #run: | - #git config --global user.name "GitHub Actions" - #git config --global user.email "itk+community@discourse.itk.org" - #npx semantic-release diff --git a/package.json b/package.json index 9f384788..3867053f 100644 --- a/package.json +++ b/package.json @@ -13,11 +13,9 @@ "setup-micromamba": "setup-micromamba --micromamba-binary-path ./micromamba/micromamba --micromamba-root-path ./micromamba --init-shell none --create-environment true --environment-file environment.yml --log-level info --run-command \"clean -fya\"", "start-hypha": "pnpm setup-micromamba && ./micromamba/micromamba run -r micromamba -n itk-viewer hypha --host=127.0.0.1 --port=37580", "test": "pnpm test:downloadData && concurrently -n \"hypha,cypress\" --success first -k \"pnpm start-hypha\" \"pnpm cy:component\"", - "test:ci": "pnpm test:downloadData && pnpm cy:component:ci", "test:downloadData": "node test/downloadData.mjs", "cy:watch": "cypress open --component -b chrome", "cy:component": "cypress run --component", - "cy:component:ci": "cypress run --component --record", "cy:component:chrome": "cypress run --component --browser chrome", "clean": "git clean -fdx", "ci:publish": "pnpm publish -r --access public" diff --git a/packages/element/examples/view-3d.ts b/packages/element/examples/view-3d.ts index a423ce66..d9e0825f 100644 --- a/packages/element/examples/view-3d.ts +++ b/packages/element/examples/view-3d.ts @@ -8,8 +8,6 @@ setPipelinesBaseUrl(pipelineBaseUrl); document.addEventListener('DOMContentLoaded', async function () { const imagePath = '/ome-ngff-prototypes/single_image/v0.4/zyx.ome.zarr'; - // const imagePath = '/ome-ngff-prototypes/single_image/v0.4/tczyx.ome.zarr'; - // const imagePath = '/astronaut.zarr'; const url = new URL(imagePath, document.location.origin); const image = await ZarrMultiscaleSpatialImage.fromUrl(url); diff --git a/packages/element/src/itk-view-2d-vtkjs.ts b/packages/element/src/itk-view-2d-vtkjs.ts index cd2a030e..7155cead 100644 --- a/packages/element/src/itk-view-2d-vtkjs.ts +++ b/packages/element/src/itk-view-2d-vtkjs.ts @@ -5,7 +5,7 @@ import { SelectorController } from 'xstate-lit'; import { Actor } from 'xstate'; import { createLogic } from '@itk-viewer/vtkjs/view-2d-vtkjs.js'; -import { SpawnController } from './spawn-controller.js'; +import { dispatchSpawn } from './spawn-controller.js'; import { Camera } from '@itk-viewer/viewer/camera.js'; import './itk-camera.js'; @@ -15,13 +15,7 @@ type ComponentActor = Actor>; export class ItkView2dVtkjs extends LitElement { actor: ComponentActor | undefined; container: HTMLElement | undefined; - - spawner = new SpawnController( - this, - 'renderer', - createLogic(), - (actor: ComponentActor) => this.setActor(actor), - ); + dispatched = false; cameraActor: | SelectorController @@ -54,6 +48,12 @@ export class ItkView2dVtkjs extends LitElement { } render() { + if (!this.dispatched) { + dispatchSpawn(this, 'renderer', createLogic(), (actor) => + this.setActor(actor), + ); + this.dispatched = true; + } return html`
diff --git a/packages/element/src/itk-view-2d.ts b/packages/element/src/itk-view-2d.ts index 92c909c0..50d7c9dd 100644 --- a/packages/element/src/itk-view-2d.ts +++ b/packages/element/src/itk-view-2d.ts @@ -2,7 +2,7 @@ import { View2dActor, view2d } from '@itk-viewer/viewer/view-2d.js'; import { LitElement, css, html, nothing } from 'lit'; import { customElement } from 'lit/decorators.js'; import { SelectorController } from 'xstate-lit'; -import { SpawnController, handleLogic } from './spawn-controller.js'; +import { dispatchSpawn, handleLogic } from './spawn-controller.js'; @customElement('itk-view-2d') export class ItkView2d extends LitElement { @@ -10,10 +10,7 @@ export class ItkView2d extends LitElement { scale: SelectorController | undefined; scaleCount: SelectorController | undefined; slice: SelectorController | undefined; - - spawner = new SpawnController(this, 'view', view2d, (actor: View2dActor) => - this.setActor(actor), - ); + dispatched = false; constructor() { super(); @@ -58,6 +55,11 @@ export class ItkView2d extends LitElement { } render() { + if (!this.dispatched) { + dispatchSpawn(this, 'view', view2d, (actor) => this.setActor(actor)); + this.dispatched = true; + } + const slice = this.slice?.value ?? 0; const scale = this.scale?.value ?? 0; const scaleCount = this.scaleCount?.value ?? 1; diff --git a/packages/element/src/itk-view-3d-vtkjs.ts b/packages/element/src/itk-view-3d-vtkjs.ts index 7cebee65..157fd236 100644 --- a/packages/element/src/itk-view-3d-vtkjs.ts +++ b/packages/element/src/itk-view-3d-vtkjs.ts @@ -4,7 +4,7 @@ import { ref } from 'lit/directives/ref.js'; import { Actor } from 'xstate'; import { createLogic } from '@itk-viewer/vtkjs/view-3d-vtkjs.js'; -import { SpawnController } from './spawn-controller.js'; +import { dispatchSpawn } from './spawn-controller.js'; import { SelectorController } from 'xstate-lit'; import { Camera } from '@itk-viewer/viewer/camera.js'; import './itk-camera.js'; @@ -15,13 +15,7 @@ type ComponentActor = Actor>; export class ItkView3dVtkjs extends LitElement { actor: ComponentActor | undefined; container: HTMLElement | undefined; - - spawner = new SpawnController( - this, - 'renderer', - createLogic(), - (actor: ComponentActor) => this.setActor(actor), - ); + dispatched = false; cameraActor: | SelectorController @@ -54,6 +48,13 @@ export class ItkView3dVtkjs extends LitElement { } render() { + if (!this.dispatched) { + dispatchSpawn(this, 'renderer', createLogic(), (actor) => + this.setActor(actor), + ); + this.dispatched = true; + } + return html`
diff --git a/packages/element/src/itk-view-3d.ts b/packages/element/src/itk-view-3d.ts index 65694f6d..cccc2aaa 100644 --- a/packages/element/src/itk-view-3d.ts +++ b/packages/element/src/itk-view-3d.ts @@ -2,7 +2,7 @@ import { View3dActor, view3d } from '@itk-viewer/viewer/view-3d.js'; import { LitElement, css, html, nothing } from 'lit'; import { customElement } from 'lit/decorators.js'; import { SelectorController } from 'xstate-lit'; -import { SpawnController, handleLogic } from './spawn-controller.js'; +import { dispatchSpawn, handleLogic } from './spawn-controller.js'; type Actor = View3dActor; @@ -11,14 +11,7 @@ export class ItkView3d extends LitElement { actor: Actor | undefined; scale: SelectorController | undefined; scaleCount: SelectorController | undefined; - - spawner = new SpawnController(this, 'view', view3d, (actor: Actor) => - this.setActor(actor), - ); - - constructor() { - super(); - } + dispatched = false; setActor(actor: Actor) { this.actor = actor; @@ -46,6 +39,11 @@ export class ItkView3d extends LitElement { } render() { + if (!this.dispatched) { + dispatchSpawn(this, 'view', view3d, (actor) => this.setActor(actor)); + this.dispatched = true; + } + const scale = this.scale?.value ?? 0; const scaleCount = this.scaleCount?.value ?? 1; const scaleOptions = Array.from( diff --git a/packages/element/src/itk-viewer-3d.ts b/packages/element/src/itk-viewer-3d.ts new file mode 100644 index 00000000..b099d40b --- /dev/null +++ b/packages/element/src/itk-viewer-3d.ts @@ -0,0 +1,50 @@ +import { LitElement, css, html } from 'lit'; +import { customElement } from 'lit/decorators.js'; +import { Ref, createRef, ref } from 'lit/directives/ref.js'; +import { ItkViewer } from './itk-viewer-element.js'; +import './itk-viewport.js'; +import './itk-view-3d.js'; +import './itk-view-3d-vtkjs.js'; + +@customElement('itk-viewer-3d') +export class ItkViewer3d extends LitElement { + viewer: Ref = createRef(); + + getActor() { + return this.viewer.value?.getActor(); + } + + render() { + return html` + + + + + + + + `; + } + + static styles = css` + :host { + flex: 1; + display: flex; + flex-direction: column; + } + + .fill { + flex: 1; + min-height: 0; + overflow: hidden; + display: flex; + flex-direction: column; + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + 'itk-viewer-3d': ItkViewer3d; + } +} diff --git a/packages/element/src/itk-viewer-element.ts b/packages/element/src/itk-viewer-element.ts index 9ffb8d81..719de1ca 100644 --- a/packages/element/src/itk-viewer-element.ts +++ b/packages/element/src/itk-viewer-element.ts @@ -7,20 +7,20 @@ import { handleLogic } from './spawn-controller.js'; @customElement('itk-viewer') export class ItkViewer extends LitElement { - viewer = createActor(viewerMachine).start(); + actor = createActor(viewerMachine).start(); constructor() { super(); } getActor() { - return this.viewer; + return this.actor; } render() { return html`

Viewer

- + `; } } diff --git a/packages/element/src/itk-viewport.ts b/packages/element/src/itk-viewport.ts index 34d818bf..6b32ae47 100644 --- a/packages/element/src/itk-viewport.ts +++ b/packages/element/src/itk-viewport.ts @@ -1,22 +1,12 @@ import { LitElement, html } from 'lit'; import { customElement } from 'lit/decorators.js'; import { viewportMachine, ViewportActor } from '@itk-viewer/viewer/viewport.js'; -import { SpawnController, handleLogic } from './spawn-controller.js'; +import { dispatchSpawn, handleLogic } from './spawn-controller.js'; @customElement('itk-viewport') export class ItkViewport extends LitElement { actor: ViewportActor | undefined; - - spawner = new SpawnController( - this, - 'viewport', - viewportMachine, - (actor) => (this.actor = actor), - ); - - constructor() { - super(); - } + dispatched = false; getActor() { return this.actor; @@ -26,11 +16,13 @@ export class ItkViewport extends LitElement { this.actor = actor; } - connectedCallback(): void { - super.connectedCallback(); - } - render() { + if (!this.dispatched) { + dispatchSpawn(this, 'viewport', viewportMachine, (actor) => + this.setActor(actor), + ); + this.dispatched = true; + } return html`

Viewport

diff --git a/packages/element/src/spawn-controller.ts b/packages/element/src/spawn-controller.ts index 2110a80c..f97629a3 100644 --- a/packages/element/src/spawn-controller.ts +++ b/packages/element/src/spawn-controller.ts @@ -1,43 +1,27 @@ -import { LitElement, ReactiveController } from 'lit'; +import { LitElement } from 'lit'; import { Actor, AnyActor, AnyActorLogic } from 'xstate'; -export class SpawnController - implements ReactiveController -{ - private host: HTMLElement; - type: string; - logic: TLogic; - onActor: (actor: Actor) => void; +export const dispatchSpawn = ( + host: LitElement, + eventType: string, + logic: TLogic, + onActor: (actor: Actor) => void, +) => { + const event = new CustomEvent(eventType, { + bubbles: true, + composed: true, + detail: { + logic: logic, + onActor: onActor, + }, + }); + host.dispatchEvent(event); +}; - constructor( - host: LitElement, - type: string, - logic: TLogic, - onActor: (actor: Actor) => void, - ) { - (this.host = host).addController(this); - this.type = type; - this.logic = logic; - this.onActor = onActor; - } - - hostConnected() { - const event = new CustomEvent(this.type, { - bubbles: true, - composed: true, - detail: { - logic: this.logic, - onActor: this.onActor, - }, - }); - - this.host.dispatchEvent(event); - } -} - -export const handleLogic = - (parentActor: TActor | undefined) => - (e: Event) => { +export const handleLogic = ( + parentActor: TActor | undefined, +) => { + return (e: Event) => { if (!parentActor) throw new Error('Parent actor not available'); e.stopPropagation(); const logic = (e as CustomEvent).detail.logic; @@ -49,3 +33,4 @@ export const handleLogic = onActor: (e as CustomEvent).detail.onActor, }); }; +}; diff --git a/packages/imjoy-api/index.html b/packages/imjoy-api/index.html new file mode 100644 index 00000000..6b0a708a --- /dev/null +++ b/packages/imjoy-api/index.html @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + +
+ + + + diff --git a/packages/imjoy-api/package.json b/packages/imjoy-api/package.json index eb0b4e62..f210f822 100644 --- a/packages/imjoy-api/package.json +++ b/packages/imjoy-api/package.json @@ -7,7 +7,7 @@ "./*": "./dist/*" }, "scripts": { - "dev": "tsc --watch --outDir dist --project tsconfig.build.json", + "dev": "tsc --watch --outDir dist --project tsconfig.build.json & vite", "build": "tsc --outDir dist --project tsconfig.build.json" }, "repository": { @@ -21,11 +21,15 @@ }, "homepage": "https://github.com/InsightSoftwareConsortium/itk-viewer#readme", "devDependencies": { - "typescript": "^5.2.2" + "typescript": "^5.3.3", + "vite": "^4.4.11", + "vite-plugin-static-copy": "^0.17.0" }, "dependencies": { + "@itk-viewer/element": "workspace:^", "@itk-viewer/io": "workspace:^", "@itk-viewer/viewer": "workspace:^", - "imjoy-rpc": "^0.5.44" + "imjoy-rpc": "^0.5.44", + "itk-wasm": "1.0.0-b.160" } } diff --git a/packages/imjoy-api/src/remote-control.html b/packages/imjoy-api/src/remote-control.html new file mode 100644 index 00000000..d6af6cde --- /dev/null +++ b/packages/imjoy-api/src/remote-control.html @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + +
+ + + + diff --git a/packages/imjoy-api/src/viewer.cy.ts b/packages/imjoy-api/src/viewer.cy.ts index 158d29cb..64b5153b 100644 --- a/packages/imjoy-api/src/viewer.cy.ts +++ b/packages/imjoy-api/src/viewer.cy.ts @@ -1,72 +1,22 @@ import { hyphaWebsocketClient } from 'imjoy-rpc'; import { setup } from './viewer.js'; import { createViewer } from '@itk-viewer/viewer/viewer.js'; -import { createViewport } from '@itk-viewer/viewer/viewport.js'; -import { ZarrMultiscaleSpatialImage } from '@itk-viewer/io/ZarrMultiscaleSpatialImage.js'; -const serverUrl = 'http://localhost:37580'; +const server_url = 'https://hypha.website'; describe('imjoy-viewer', () => { - it('registers service with Hypha server', () => { - cy.wrap(hyphaWebsocketClient.connectToServer({ - name: 'test-client', - server_url: serverUrl, - })).then((server) => { - const viewer = createViewer(); - cy.wrap(setup(server, viewer)).should('be.ok') - .and('have.all.keys', - 'addViewport', - 'config', - 'description', - 'id', - 'name', - 'setImage', - 'type', - 'viewer'); - }); - }); - - it('returns the viewer', () => { - const viewer = createViewer(); - - cy.wrap(hyphaWebsocketClient.connectToServer({ - name: 'test-client', - server_url: serverUrl, - })).then((server) => { - return cy.wrap(setup(server, viewer)) - }) - .then((service) => { - cy.wrap(service.viewer()).should('equal', viewer); - });; - }); - - it('accepts a viewport and set image', () => { + it('starts and takes set image', async () => { const viewer = createViewer(); - const viewport = createViewport(); const storeURL = new URL( '/ome-ngff-prototypes/single_image/v0.4/yx.ome.zarr', document.location.origin, ); - cy.wrap(hyphaWebsocketClient.connectToServer({ + const server = await hyphaWebsocketClient.connectToServer({ name: 'test-client', - server_url: serverUrl, - })) - .then((server) => { - cy.wrap(setup(server, viewer)); - }).then((service) => { - cy.wrap(service.addViewport(viewport, 'first')).then(() => { - cy.wrap(viewer.getSnapshot().context.viewports) - .should('have.property', 'first', viewport); - }); - return service - }).then((service) => { - cy.wrap(ZarrMultiscaleSpatialImage.fromUrl(storeURL)) - .then((image) => { - cy.wrap(service.setImage(image, 'zarr')).then(() => { - cy.wrap(viewport.getSnapshot().context.image).should('equal', image); - }); - }) + server_url, }); + const viewerService = await setup(server, viewer); + await viewerService.setImage(storeURL.href); }); -}) +}); diff --git a/packages/imjoy-api/src/viewer.ts b/packages/imjoy-api/src/viewer.ts index fd5acce9..2e93e6b2 100644 --- a/packages/imjoy-api/src/viewer.ts +++ b/packages/imjoy-api/src/viewer.ts @@ -1,22 +1,30 @@ import { Viewer } from '@itk-viewer/viewer/viewer.js'; -import { Viewport } from '@itk-viewer/viewer/viewport.js'; -import MultiscaleSpatialImage from '@itk-viewer/io/MultiscaleSpatialImage.js'; +import { ZarrMultiscaleSpatialImage } from '@itk-viewer/io/ZarrMultiscaleSpatialImage.js'; + +type HyphaService = { + id: string; +}; + +type ViewerService = { + setImage: (storeHref: string) => void; +}; type HyphaServer = { - registerService: (api: object) => any, - getService: (name: string) => any, - id: string, + registerService: (api: object) => Promise; + getService: (id: string) => Promise; + id: string; }; export async function setup(server: HyphaServer, viewer: Viewer) { const service = await server.registerService({ - name: "itk_viewer", - id: "itk-viewer", - description: "Retrieve the viewer API", - viewer: () => viewer, - addViewport: (viewport: Viewport, name: string) => viewer.send({ type: 'addViewport', viewport: viewport, name: name }), - setImage: (image: MultiscaleSpatialImage, name?: string) => viewer.send({ type: 'setImage', image, name: name ?? 'image' }), + name: 'itk-viewer', + id: 'itk-viewer', + description: 'Retrieve the viewer API', + setImage: async (imagePath: string, name?: string) => { + const url = new URL(imagePath, document.location.origin); + const image = await ZarrMultiscaleSpatialImage.fromUrl(url); + viewer.send({ type: 'setImage', image, name: name ?? 'image' }); + }, }); - - return await server.getService(service.id) + return server.getService(service.id); } diff --git a/packages/imjoy-api/vite.config.ts b/packages/imjoy-api/vite.config.ts new file mode 100644 index 00000000..5b7127cb --- /dev/null +++ b/packages/imjoy-api/vite.config.ts @@ -0,0 +1,28 @@ +import { defineConfig } from 'vite'; +import { viteStaticCopy } from 'vite-plugin-static-copy'; + +export default defineConfig({ + publicDir: '../../test/data/input', + optimizeDeps: { + exclude: ['@itk-wasm/htj2k', 'itk-wasm'], + }, + plugins: [ + // collect lazy loaded JavaScript and Wasm bundles in public directory + viteStaticCopy({ + targets: [ + { + src: './node_modules/itk-wasm/dist/pipeline/web-workers/bundles/itk-wasm-pipeline.min.worker.js', + dest: 'itk/web-workers', + }, + { + src: '../io/node_modules/itk-image-io/*', + dest: 'itk/image-io', + }, + { + src: '../blosc-zarr/emscripten-build/*', + dest: 'itk/pipelines', + }, + ], + }), + ], +}); diff --git a/packages/remote-viewport/src/remote-machine.ts b/packages/remote-viewport/src/remote-machine.ts index 17791804..b6640f8d 100644 --- a/packages/remote-viewport/src/remote-machine.ts +++ b/packages/remote-viewport/src/remote-machine.ts @@ -23,7 +23,6 @@ import { createBounds, ensuredDims, } from '@itk-viewer/io/dimensionUtils.js'; -import { state } from 'lit/decorators.js'; const MAX_IMAGE_BYTES_DEFAULT = 4000 * 1000 * 1000; // 4000 MB diff --git a/packages/viewer/src/view-2d.cy.ts b/packages/viewer/src/view-2d.cy.ts index 5e76f9c0..5864c851 100644 --- a/packages/viewer/src/view-2d.cy.ts +++ b/packages/viewer/src/view-2d.cy.ts @@ -25,7 +25,12 @@ describe('view 2d', () => { }, }); const view = createActor(view2d).start(); - view.send({ type: 'createRenderer', logic: renderer }); + view.send({ + type: 'createChild', + childType: 'renderer', + logic: renderer, + onActor: () => {}, + }); expect(childStarted).to.be.true; const image = await ZarrMultiscaleSpatialImage.fromUrl( diff --git a/packages/viewer/src/viewer.cy.ts b/packages/viewer/src/viewer.cy.ts index 51f800da..888cae72 100644 --- a/packages/viewer/src/viewer.cy.ts +++ b/packages/viewer/src/viewer.cy.ts @@ -28,7 +28,12 @@ describe('Viewer', () => { }, }); const viewer = createViewer(); - viewer.send({ type: 'createViewport', logic }); + viewer.send({ + type: 'createChild', + childType: 'viewport', + logic, + onActor: () => {}, + }); expect(childStarted).to.be.true; const image = await ZarrMultiscaleSpatialImage.fromUrl( diff --git a/packages/viewer/src/viewer.ts b/packages/viewer/src/viewer.ts index a05407e9..2eecb233 100644 --- a/packages/viewer/src/viewer.ts +++ b/packages/viewer/src/viewer.ts @@ -1,4 +1,11 @@ -import { AnyActorRef, assign, raise, setup } from 'xstate'; +import { + ActorRefFrom, + AnyActorRef, + assign, + createActor, + raise, + setup, +} from 'xstate'; import MultiscaleSpatialImage from '@itk-viewer/io/MultiscaleSpatialImage.js'; import { CreateChild } from './children.js'; @@ -82,3 +89,9 @@ export const viewerMachine = setup({ }, }, }); + +export const createViewer = () => { + return createActor(viewerMachine).start(); +}; + +export type Viewer = ActorRefFrom; diff --git a/packages/viewer/src/viewport.cy.ts b/packages/viewer/src/viewport.cy.ts index f0bf1e5d..38503249 100644 --- a/packages/viewer/src/viewport.cy.ts +++ b/packages/viewer/src/viewport.cy.ts @@ -1,4 +1,3 @@ -import { mat4 } from 'gl-matrix'; import { createActor, createMachine } from 'xstate'; import { MultiscaleSpatialImage } from '@itk-viewer/io/MultiscaleSpatialImage.js'; import { ZarrMultiscaleSpatialImage } from '@itk-viewer/io/ZarrMultiscaleSpatialImage.js'; @@ -35,29 +34,14 @@ describe('Viewport', () => { cy.wrap(viewport.getSnapshot().context.image).should('equal', image); }); - it('updates observers when camera updates', () => { + it('adopts a camera actor', () => { const viewport = createViewport(); - const camera = createCamera(); - viewport.send({ type: 'setCamera', camera }); cy.wrap( viewport.getSnapshot().context.camera?.getSnapshot().context.pose, ).should('deep.equal', camera.getSnapshot().context.pose); - - let cameraPose = undefined; - viewport.subscribe((state) => { - cameraPose = state.context.camera?.getSnapshot().context.pose; - }); - - const targetCameraPose = mat4.fromTranslation(mat4.create(), [1, 2, 3]); - camera.send({ - type: 'setPose', - pose: targetCameraPose, - }); - - cy.wrap(cameraPose).should('deep.equal', targetCameraPose); }); it('spawns view actors', async () => { @@ -76,7 +60,12 @@ describe('Viewport', () => { }, }); const viewport = createViewport(); - viewport.send({ type: 'createView', logic: view }); + viewport.send({ + type: 'createChild', + childType: 'viewport', + logic: view, + onActor: () => {}, + }); expect(childStarted).to.be.true; const image = await ZarrMultiscaleSpatialImage.fromUrl( diff --git a/packages/viewer/src/viewport.ts b/packages/viewer/src/viewport.ts index f1783060..05ef152d 100644 --- a/packages/viewer/src/viewport.ts +++ b/packages/viewer/src/viewport.ts @@ -154,9 +154,6 @@ export const viewportMachine = setup({ 'resetCameraPose', ], }, - cameraPoseUpdated: { - actions: ['forwardToParent'], - }, setResolution: { actions: [ assign({ diff --git a/packages/vtkjs/src/view-2d-vtkjs.cy.ts b/packages/vtkjs/src/view-2d-vtkjs.cy.ts index 68e71fbd..b3ebc2ff 100644 --- a/packages/vtkjs/src/view-2d-vtkjs.cy.ts +++ b/packages/vtkjs/src/view-2d-vtkjs.cy.ts @@ -1,6 +1,6 @@ import { createActor } from 'xstate'; import { setPipelineWorkerUrl, setPipelinesBaseUrl } from 'itk-wasm'; -import { createRenderer } from './view-2d-vtkjs.js'; +import { createLogic } from './view-2d-vtkjs.js'; import { ZarrMultiscaleSpatialImage } from '@itk-viewer/io/ZarrMultiscaleSpatialImage.js'; before(() => { @@ -12,11 +12,11 @@ before(() => { describe('View 2D vtk.js', () => { it('constructs', () => { - expect(createRenderer()).to.be.ok; + expect(createLogic()).to.be.ok; }); it('takes imageBuild event', async () => { - const render = createActor(createRenderer()).start(); + const render = createActor(createLogic()).start(); const multiscale = await ZarrMultiscaleSpatialImage.fromUrl( new URL('/astronaut.zarr', document.location.origin), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4295f6ad..c7f3c4fc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -133,6 +133,9 @@ importers: packages/imjoy-api: dependencies: + '@itk-viewer/element': + specifier: workspace:^ + version: link:../element '@itk-viewer/io': specifier: workspace:^ version: link:../io @@ -141,11 +144,20 @@ importers: version: link:../viewer imjoy-rpc: specifier: ^0.5.44 - version: 0.5.44 + version: 0.5.46 + itk-wasm: + specifier: 1.0.0-b.160 + version: 1.0.0-b.160 devDependencies: typescript: - specifier: ^5.2.2 - version: 5.2.2 + specifier: ^5.3.3 + version: 5.3.3 + vite: + specifier: ^4.4.11 + version: 4.4.11 + vite-plugin-static-copy: + specifier: ^0.17.0 + version: 0.17.0(vite@4.4.11) packages/io: dependencies: @@ -5466,12 +5478,6 @@ packages: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} dev: true - /nanoid@3.3.6: - resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - dev: true - /nanoid@3.3.7: resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -5739,7 +5745,7 @@ packages: resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} engines: {node: ^10 || ^12 || >=14} dependencies: - nanoid: 3.3.6 + nanoid: 3.3.7 picocolors: 1.0.0 source-map-js: 1.0.2 dev: true