diff --git a/__tests__/integration/mirador/companion_windows.test.js b/__tests__/integration/mirador/companion_windows.test.js index f17e1d6261ee598090e14dc001b74196db7eff47..19eb7b1679f53ed15d29e1191f7f6d1f695b666f 100644 --- a/__tests__/integration/mirador/companion_windows.test.js +++ b/__tests__/integration/mirador/companion_windows.test.js @@ -8,7 +8,8 @@ describe('Companion Windows', () => { await expect(page).toClick('button[aria-label="Toggle window sidebar"]'); await page.waitFor(1000); - await expect(page).toClick('button[aria-label="Open information companion window"]'); + await expect(page).toMatchElement('.mirador-companion-window-left.mirador-window-sidebar-info-panel'); + await expect(page).toMatchElement('button[aria-label="Open information companion window"][aria-selected="true"]'); await expect(page).not.toMatchElement('.mirador-companion-window-right.mirador-window-sidebar-info-panel'); diff --git a/__tests__/src/actions/canvas.test.js b/__tests__/src/actions/canvas.test.js index da7344e256c27d6d604272c0fe27493df9fed5e6..571c3c6b40ee5c24dfd6f3010c7d41abc91a9c1c 100644 --- a/__tests__/src/actions/canvas.test.js +++ b/__tests__/src/actions/canvas.test.js @@ -1,7 +1,7 @@ import * as actions from '../../../src/state/actions'; import ActionTypes from '../../../src/state/actions/action-types'; -const debounceTime = 300; +const debounceTime = 100; describe('canvas actions', () => { describe('setCanvas', () => { diff --git a/__tests__/src/actions/window.test.js b/__tests__/src/actions/window.test.js index ba983c88c09d7677974b24a3b83fe2e69a6bf527..c144d7d2d94b9bdef18563b8e30ba7ccc04fd26c 100644 --- a/__tests__/src/actions/window.test.js +++ b/__tests__/src/actions/window.test.js @@ -15,20 +15,24 @@ describe('window actions', () => { id: 'helloworld', canvasIndex: 1, collectionIndex: 0, - companionWindowIds: [], manifestId: null, maximized: false, rangeId: null, thumbnailNavigationPosition: 'bottom', x: 2700, y: 2700, + sideBarPanel: 'info', width: 400, height: 400, rotation: null, view: 'single', }, + companionWindows: [{ position: 'left', content: 'info' }], }; - expect(actions.addWindow(options)).toEqual(expectedAction); + const action = actions.addWindow(options); + expect(action).toMatchObject(expectedAction); + expect(action.window.companionWindowIds.length).toEqual(1); + expect(action.window.companionWindowIds[0]).toEqual(action.companionWindows[0].id); }); }); diff --git a/__tests__/src/components/OpenSeadragonViewer.test.js b/__tests__/src/components/OpenSeadragonViewer.test.js index 6368e2e050d1ea528cf051d5649d4c9359955943..8113ca8e9bfbda73b9526d85e757ff14710bd62f 100644 --- a/__tests__/src/components/OpenSeadragonViewer.test.js +++ b/__tests__/src/components/OpenSeadragonViewer.test.js @@ -2,15 +2,20 @@ import React from 'react'; import { shallow } from 'enzyme'; import OpenSeadragon from 'openseadragon'; import { OpenSeadragonViewer } from '../../../src/components/OpenSeadragonViewer'; +import OpenSeadragonCanvasOverlay from '../../../src/lib/OpenSeadragonCanvasOverlay'; +import Annotation from '../../../src/lib/Annotation'; import ZoomControls from '../../../src/containers/ZoomControls'; jest.mock('openseadragon'); +jest.mock('../../../src/lib/OpenSeadragonCanvasOverlay'); + describe('OpenSeadragonViewer', () => { let wrapper; let updateViewport; beforeEach(() => { OpenSeadragon.mockClear(); + OpenSeadragonCanvasOverlay.mockClear(); updateViewport = jest.fn(); @@ -126,6 +131,16 @@ describe('OpenSeadragonViewer', () => { 0.5, { x: 1, y: 0, zoom: 0.5 }, false, ); }); + + it('sets up a OpenSeadragonCanvasOverlay', () => { + wrapper.instance().componentDidMount(); + expect(OpenSeadragonCanvasOverlay).toHaveBeenCalledTimes(1); + }); + + it('sets up a listener on update-viewport', () => { + wrapper.instance().componentDidMount(); + expect(addHandler).toHaveBeenCalledWith('update-viewport', expect.anything()); + }); }); describe('componentDidUpdate', () => { @@ -153,6 +168,49 @@ describe('OpenSeadragonViewer', () => { 0.5, { x: 1, y: 0, zoom: 0.5 }, false, ); }); + + it('sets up canvasUpdate to add annotations to the canvas', () => { + const clear = jest.fn(); + const resize = jest.fn(); + const canvasUpdate = jest.fn(); + wrapper.instance().osdCanvasOverlay = { + clear, + resize, + canvasUpdate, + }; + + wrapper.setProps( + { + annotations: [ + new Annotation( + { '@id': 'foo', resources: [{ foo: 'bar' }] }, + ), + ], + }, + ); + wrapper.setProps( + { + annotations: [ + new Annotation( + { '@id': 'foo', resources: [{ foo: 'bar' }] }, + ), + ], + }, + ); + wrapper.setProps( + { + annotations: [ + new Annotation( + { '@id': 'bar', resources: [{ foo: 'bar' }] }, + ), + ], + }, + ); + wrapper.instance().updateCanvas(); + expect(clear).toHaveBeenCalledTimes(1); + expect(resize).toHaveBeenCalledTimes(1); + expect(canvasUpdate).toHaveBeenCalledTimes(1); + }); }); describe('onViewportChange', () => { @@ -173,4 +231,35 @@ describe('OpenSeadragonViewer', () => { ); }); }); + + describe('onUpdateViewport', () => { + it('fires updateCanvas', () => { + const updateCanvas = jest.fn(); + wrapper.instance().updateCanvas = updateCanvas; + wrapper.instance().onUpdateViewport(); + expect(updateCanvas).toHaveBeenCalledTimes(1); + }); + }); + + describe('annotationsToContext', () => { + it('converts the annotations to canvas', () => { + const strokeRect = jest.fn(); + wrapper.instance().osdCanvasOverlay = { + context2d: { + strokeRect, + }, + }; + + const annotations = [ + new Annotation( + { '@id': 'foo', resources: [{ on: 'www.example.com/#xywh=10,10,100,200' }] }, + ), + ]; + wrapper.instance().annotationsToContext(annotations); + const context = wrapper.instance().osdCanvasOverlay.context2d; + expect(context.strokeStyle).toEqual('yellow'); + expect(context.lineWidth).toEqual(10); + expect(strokeRect).toHaveBeenCalledWith(10, 10, 100, 200); + }); + }); }); diff --git a/__tests__/src/lib/AnnotationResource.test.js b/__tests__/src/lib/AnnotationResource.test.js index fcf2eb5b5cc091169dce49e1659ab0c1156631ab..64d7777efb21228a8aa5593c01fc4a7b059e846d 100644 --- a/__tests__/src/lib/AnnotationResource.test.js +++ b/__tests__/src/lib/AnnotationResource.test.js @@ -53,11 +53,11 @@ describe('AnnotationResource', () => { describe('fragmentSelector', () => { it('simple string', () => { expect(new AnnotationResource({ on: 'www.example.com/#xywh=10,10,100,200' }) - .fragmentSelector).toEqual(['10', '10', '100', '200']); + .fragmentSelector).toEqual([10, 10, 100, 200]); }); it('more complex selector', () => { expect(new AnnotationResource({ on: { selector: { value: 'www.example.com/#xywh=10,10,100,200' } } }) - .fragmentSelector).toEqual(['10', '10', '100', '200']); + .fragmentSelector).toEqual([10, 10, 100, 200]); }); }); }); diff --git a/__tests__/src/lib/OpenSeadragonCanvasOverlay.test.js b/__tests__/src/lib/OpenSeadragonCanvasOverlay.test.js new file mode 100644 index 0000000000000000000000000000000000000000..8663b652be436724cd7a52bb9191aa530e688a4e --- /dev/null +++ b/__tests__/src/lib/OpenSeadragonCanvasOverlay.test.js @@ -0,0 +1,122 @@ +import OpenSeadragon from 'openseadragon'; +import OpenSeadragonCanvasOverlay from '../../../src/lib/OpenSeadragonCanvasOverlay'; + +jest.mock('openseadragon'); + +describe('OpenSeadragonCanvasOverlay', () => { + let canvasOverlay; + beforeEach(() => { + document.body.innerHTML = '<div id="canvas"></div>'; + OpenSeadragon.mockClear(); + OpenSeadragon.mockImplementation(() => ({ + canvas: document.getElementById('canvas'), + container: { + clientHeight: 100, + clientWidth: 200, + }, + viewport: { + getBounds: jest.fn(() => ({ + x: 40, y: 80, width: 200, height: 300, + })), + getZoom: jest.fn(() => (0.75)), + }, + world: { + getItemAt: jest.fn(() => ({ + source: { + dimensions: { + x: 1000, + y: 2000, + }, + }, + viewportToImageZoom: jest.fn(() => (0.075)), + })), + }, + })); + canvasOverlay = new OpenSeadragonCanvasOverlay(new OpenSeadragon()); + }); + describe('constructor', () => { + it('sets up initial values and canvas', () => { + expect(canvasOverlay.containerHeight).toEqual(0); + expect(canvasOverlay.containerWidth).toEqual(0); + expect(canvasOverlay.canvasDiv.outerHTML).toEqual( + '<div style="position: absolute; left: 0px; top: 0px; width: 100%; height: 100%;"><canvas></canvas></div>', + ); + }); + }); + describe('context2d', () => { + it('calls getContext on canvas', () => { + const contextMock = jest.fn(); + canvasOverlay.canvas = { + getContext: contextMock, + }; + canvasOverlay.context2d; // eslint-disable-line no-unused-expressions + expect(contextMock).toHaveBeenCalledTimes(1); + }); + }); + describe('clear', () => { + it('calls getContext and clearRect on canvas', () => { + const clearRect = jest.fn(); + const contextMock = jest.fn(() => ({ + clearRect, + })); + canvasOverlay.canvas = { + getContext: contextMock, + }; + canvasOverlay.clear(); + expect(contextMock).toHaveBeenCalledTimes(1); + expect(clearRect).toHaveBeenCalledTimes(1); + }); + }); + describe('resize', () => { + it('sets various values based off of image and container sizes', () => { + canvasOverlay.resize(); + expect(canvasOverlay.containerHeight).toEqual(100); + expect(canvasOverlay.containerWidth).toEqual(200); + expect(canvasOverlay.imgAspectRatio).toEqual(0.5); + }); + it('when image is undefined returns early', () => { + OpenSeadragon.mockClear(); + OpenSeadragon.mockImplementation(() => ({ + canvas: document.getElementById('canvas'), + container: { + clientHeight: 100, + clientWidth: 200, + }, + viewport: { + getBounds: jest.fn(() => (new OpenSeadragon.Rect(0, 0, 200, 200))), + }, + world: { + getItemAt: jest.fn(), + }, + })); + canvasOverlay = new OpenSeadragonCanvasOverlay(new OpenSeadragon()); + canvasOverlay.resize(); + expect(canvasOverlay.imgHeight).toEqual(undefined); + expect(canvasOverlay.imgWidth).toEqual(undefined); + }); + }); + describe('canvasUpdate', () => { + it('sets appropriate sizes and calls update argument', () => { + const scale = jest.fn(); + const setAttribute = jest.fn(); + const setTransform = jest.fn(); + const translate = jest.fn(); + const contextMock = jest.fn(() => ({ + scale, + setTransform, + translate, + })); + canvasOverlay.canvas = { + getContext: contextMock, + setAttribute, + }; + const update = jest.fn(); + canvasOverlay.resize(); + canvasOverlay.canvasUpdate(update); + expect(update).toHaveBeenCalledTimes(1); + expect(scale).toHaveBeenCalledWith(0.075, 0.075); + expect(translate).toHaveBeenCalledWith(-39.96, -26.65333333333333); + expect(setTransform).toHaveBeenCalledWith(1, 0, 0, 1, 0, 0); + }); + }); +}); diff --git a/__tests__/src/reducers/companionWindows.test.js b/__tests__/src/reducers/companionWindows.test.js index 68775833910068ef0252fdf95084980790c37c1a..9f33e9ecf64c6c1aa05470a9149c470e913678af 100644 --- a/__tests__/src/reducers/companionWindows.test.js +++ b/__tests__/src/reducers/companionWindows.test.js @@ -20,6 +20,22 @@ describe('companionWindowsReducer', () => { }); }); + describe('ADD_WINDOW', () => { + it('adds default companion window(s)', () => { + const action = { + type: ActionTypes.ADD_WINDOW, + companionWindows: [{ id: 'banana', position: 'left', content: 'info' }, { id: 'Banane', position: 'right', content: 'canvas_navigation' }], + }; + const beforeState = {}; + const expectedState = { + banana: { id: 'banana', position: 'left', content: 'info' }, + Banane: { id: 'Banane', position: 'right', content: 'canvas_navigation' }, + }; + expect(companionWindowsReducer(beforeState, action)).toEqual(expectedState); + }); + }); + + describe('UPDATE_COMPANION_WINDOW', () => { it('updates an existing companion window', () => { const action = { diff --git a/src/components/CompanionWindow.js b/src/components/CompanionWindow.js index 2615be8ed46326fffa644f43f205af8f933bb3b3..585009d68f2a16b48366b3dc2c0bf90a8255b5c0 100644 --- a/src/components/CompanionWindow.js +++ b/src/components/CompanionWindow.js @@ -35,7 +35,7 @@ export class CompanionWindow extends Component { component="aside" aria-label={title} > - <Toolbar variant="dense" className={ns('companion-window-header')}> + <Toolbar variant="dense" className={[position === 'left' ? classes.leftPadding : undefined, ns('companion-window-header')].join(' ')} disableGutters> <Typography variant="h3" className={classes.windowSideBarTitle}> {title} </Typography> diff --git a/src/components/OpenSeadragonViewer.js b/src/components/OpenSeadragonViewer.js index b78231c44454bee293467e6eb46c8f07a46b76b6..50708b5ac3df3f776f1e861e68fa4e0498b30ca0 100644 --- a/src/components/OpenSeadragonViewer.js +++ b/src/components/OpenSeadragonViewer.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import OpenSeadragon from 'openseadragon'; import ns from '../config/css-ns'; import ZoomControls from '../containers/ZoomControls'; +import OpenSeadragonCanvasOverlay from '../lib/OpenSeadragonCanvasOverlay'; /** * Represents a OpenSeadragonViewer in the mirador workspace. Responsible for mounting @@ -16,7 +17,11 @@ export class OpenSeadragonViewer extends Component { super(props); this.viewer = null; + this.osdCanvasOverlay = null; + // An initial value for the updateCanvas method + this.updateCanvas = () => {}; this.ref = React.createRef(); + this.onUpdateViewport = this.onUpdateViewport.bind(this); this.onViewportChange = this.onViewportChange.bind(this); } @@ -36,6 +41,9 @@ export class OpenSeadragonViewer extends Component { showNavigationControl: false, preserveImageSizeOnResize: true, }); + + this.osdCanvasOverlay = new OpenSeadragonCanvasOverlay(this.viewer); + this.viewer.addHandler('update-viewport', this.onUpdateViewport); this.viewer.addHandler('viewport-change', this.onViewportChange); if (viewer) { @@ -48,10 +56,21 @@ export class OpenSeadragonViewer extends Component { /** * When the tileSources change, make sure to close the OSD viewer. + * When the annotations change, reset the updateCanvas method to make sure + * they are added. * When the viewport state changes, pan or zoom the OSD viewer as appropriate */ componentDidUpdate(prevProps) { - const { tileSources, viewer } = this.props; + const { tileSources, viewer, annotations } = this.props; + if (!this.annotationsMatch(prevProps.annotations)) { + this.updateCanvas = () => { + this.osdCanvasOverlay.clear(); + this.osdCanvasOverlay.resize(); + this.osdCanvasOverlay.canvasUpdate(() => { + this.annotationsToContext(annotations); + }); + }; + } if (!this.tileSourcesMatch(prevProps.tileSources)) { this.viewer.close(); Promise.all( @@ -81,6 +100,13 @@ export class OpenSeadragonViewer extends Component { this.viewer.removeAllHandlers(); } + /** + * onUpdateViewport - fires during OpenSeadragon render method. + */ + onUpdateViewport(event) { + this.updateCanvas(); + } + /** * Forward OSD state to redux */ @@ -96,6 +122,20 @@ export class OpenSeadragonViewer extends Component { }); } + /** + * annotationsToContext - converts anontations to a canvas context + */ + annotationsToContext(annotations) { + const context = this.osdCanvasOverlay.context2d; + annotations.forEach((annotation) => { + annotation.resources.forEach((resource) => { + context.strokeStyle = 'yellow'; + context.lineWidth = 10; + context.strokeRect(...resource.fragmentSelector); + }); + }); + } + /** * boundsFromTileSources - calculates the overall width/height * based on 0 -> n tileSources @@ -195,6 +235,25 @@ export class OpenSeadragonViewer extends Component { }); } + /** + * annotationsMatch - compares previous annotations to current to determine + * whether to add a new updateCanvas method to draw annotations + * @param {Array} prevAnnotations + * @return {Boolean} + */ + annotationsMatch(prevAnnotations) { + const { annotations } = this.props; + return annotations.some((annotation, index) => { + if (!prevAnnotations[index]) { + return false; + } + if (annotation.id === prevAnnotations[index].id) { + return true; + } + return false; + }); + } + /** * Renders things */ @@ -220,6 +279,7 @@ export class OpenSeadragonViewer extends Component { } OpenSeadragonViewer.defaultProps = { + annotations: [], children: null, tileSources: [], viewer: null, @@ -227,6 +287,7 @@ OpenSeadragonViewer.defaultProps = { }; OpenSeadragonViewer.propTypes = { + annotations: PropTypes.arrayOf(PropTypes.object), children: PropTypes.element, tileSources: PropTypes.arrayOf(PropTypes.object), viewer: PropTypes.object, // eslint-disable-line react/forbid-prop-types diff --git a/src/components/WindowViewer.js b/src/components/WindowViewer.js index 9f103823ffff31dfbae01d24b8a8d01267eb0d4b..78b5354560789a897ae934acd703cdb380deb9db 100644 --- a/src/components/WindowViewer.js +++ b/src/components/WindowViewer.js @@ -126,6 +126,7 @@ export class WindowViewer extends Component { <> <OSDViewer tileSources={this.tileInfoFetchedFromStore()} + currentCanvases={this.currentCanvases()} windowId={window.id} > <ViewerNavigation window={window} canvases={this.canvases} /> diff --git a/src/containers/CompanionWindow.js b/src/containers/CompanionWindow.js index e2a9615f679cdca7e66489189d21fd19dee07b31..3d7e6724df9b27c38e139c943d1770ec067c1d78 100644 --- a/src/containers/CompanionWindow.js +++ b/src/containers/CompanionWindow.js @@ -59,6 +59,10 @@ const styles = theme => ({ positionButton: { order: -100, }, + leftPadding: { + ...theme.mixins.gutters(), + paddingRight: 0, + }, content: { ...theme.mixins.gutters(), overflowY: 'auto', diff --git a/src/containers/OpenSeadragonViewer.js b/src/containers/OpenSeadragonViewer.js index eb0e1422d496870e33c228ab101a5bd0ffdc238e..7099f30dc6460276366607398747635cf8499a66 100644 --- a/src/containers/OpenSeadragonViewer.js +++ b/src/containers/OpenSeadragonViewer.js @@ -7,6 +7,7 @@ import * as actions from '../state/actions'; import { getCanvasLabel, getSelectedCanvas, + getSelectedCanvasAnnotations, } from '../state/selectors'; /** @@ -14,12 +15,18 @@ import { * @memberof Window * @private */ -const mapStateToProps = ({ viewers, windows, manifests }, { windowId }) => ({ +const mapStateToProps = ({ + viewers, windows, manifests, annotations, +}, { windowId, currentCanvases }) => ({ viewer: viewers[windowId], label: getCanvasLabel( getSelectedCanvas({ windows, manifests }, windowId), windows[windowId].canvasIndex, ), + annotations: getSelectedCanvasAnnotations( + { annotations }, + currentCanvases.map(canvas => canvas.id), + ), }); /** diff --git a/src/lib/AnnotationResource.js b/src/lib/AnnotationResource.js index 88e38be87073a32e118c63d2d724fe8c99bf6f9c..3e3091a559a22f99c1eebd18ae55e55c08c78c3e 100644 --- a/src/lib/AnnotationResource.js +++ b/src/lib/AnnotationResource.js @@ -37,9 +37,9 @@ export default class AnnotationResource { const { on } = this.resource; switch (typeof on) { case 'string': - return on.match(/xywh=(.*)$/)[1].split(','); + return on.match(/xywh=(.*)$/)[1].split(',').map(str => parseInt(str, 10)); case 'object': - return on.selector.value.match(/xywh=(.*)$/)[1].split(','); + return on.selector.value.match(/xywh=(.*)$/)[1].split(',').map(str => parseInt(str, 10)); default: return null; } diff --git a/src/lib/OpenSeadragonCanvasOverlay.js b/src/lib/OpenSeadragonCanvasOverlay.js new file mode 100644 index 0000000000000000000000000000000000000000..e306f4579b84557503324fb6d9c33676ca56c5b1 --- /dev/null +++ b/src/lib/OpenSeadragonCanvasOverlay.js @@ -0,0 +1,100 @@ +import OpenSeadragon from 'openseadragon'; + +/** + * OpenSeadragonCanvasOverlay - adapted from https://github.com/altert/OpenSeadragonCanvasOverlay + * used rather than an "onRedraw" function we tap into our own method. Existing + * repository is not published as an npm package. + * Code ported from https://github.com/altert/OpenSeadragonCanvasOverlay + * carries a BSD 3-Clause license originally authored by @altert from + * https://github.com/altert/OpenseadragonFabricjsOverlay + */ +export default class OpenSeadragonCanvasOverlay { + /** + * constructor - sets up the Canvas overlay container + */ + constructor(viewer) { + this.viewer = viewer; + + this.containerWidth = 0; + this.containerHeight = 0; + + this.canvasDiv = document.createElement('div'); + this.canvasDiv.style.position = 'absolute'; + this.canvasDiv.style.left = 0; + this.canvasDiv.style.top = 0; + this.canvasDiv.style.width = '100%'; + this.canvasDiv.style.height = '100%'; + this.viewer.canvas.appendChild(this.canvasDiv); + + this.canvas = document.createElement('canvas'); + this.canvasDiv.appendChild(this.canvas); + this.imgAspectRatio = 1; + } + + /** */ + get context2d() { + return this.canvas.getContext('2d'); + } + + /** */ + clear() { + this.canvas.getContext('2d').clearRect(0, 0, this.containerWidth, this.containerHeight); + } + + /** + * resize - resizes the added Canvas overlay. + */ + resize() { + if (this.containerWidth !== this.viewer.container.clientWidth) { + this.containerWidth = this.viewer.container.clientWidth; + this.canvasDiv.setAttribute('width', this.containerWidth); + this.canvas.setAttribute('width', this.containerWidth); + } + + if (this.containerHeight !== this.viewer.container.clientHeight) { + this.containerHeight = this.viewer.container.clientHeight; + this.canvasDiv.setAttribute('height', this.containerHeight); + this.canvas.setAttribute('height', this.containerHeight); + } + + this.viewportOrigin = new OpenSeadragon.Point(0, 0); + const boundsRect = this.viewer.viewport.getBounds(true); + this.viewportOrigin.x = boundsRect.x; + this.viewportOrigin.y = boundsRect.y * this.imgAspectRatio; + + this.viewportWidth = boundsRect.width; + this.viewportHeight = boundsRect.height * this.imgAspectRatio; + const image1 = this.viewer.world.getItemAt(0); + if (!image1) return; + this.imgWidth = image1.source.dimensions.x; + this.imgHeight = image1.source.dimensions.y; + this.imgAspectRatio = this.imgWidth / this.imgHeight; + } + + /** + * canvasUpdate - sets up the dimensions for the canvas update to mimick image + * 0 dimensions. Then call provided update function. + * @param {Function} update + */ + canvasUpdate(update) { + const viewportZoom = this.viewer.viewport.getZoom(true); + const image1 = this.viewer.world.getItemAt(0); + if (!image1) return; + const zoom = image1.viewportToImageZoom(viewportZoom); + + const x = ( + (this.viewportOrigin.x / this.imgWidth - this.viewportOrigin.x) / this.viewportWidth + ) * this.containerWidth; + const y = ( + (this.viewportOrigin.y / this.imgHeight - this.viewportOrigin.y) / this.viewportHeight + ) * this.containerHeight; + + if (this.clearBeforeRedraw) this.clear(); + this.canvas.getContext('2d').translate(x, y); + this.canvas.getContext('2d').scale(zoom, zoom); + + update(); + + this.canvas.getContext('2d').setTransform(1, 0, 0, 1, 0, 0); + } +} diff --git a/src/state/actions/canvas.js b/src/state/actions/canvas.js index 68e0e8408d6a4a5d03e7d986b9385c63e17f88f6..5fc9c5f9da83c7fe7f83ef4a3969a93c0f81fba2 100644 --- a/src/state/actions/canvas.js +++ b/src/state/actions/canvas.js @@ -22,7 +22,7 @@ export function updateViewport(windowId, payload) { meta: { debounce: { // TODO : set this value in a registry - time: 300, + time: 100, }, }, windowId, diff --git a/src/state/actions/window.js b/src/state/actions/window.js index 85a1ecf0f5526f3e8ff045fb6ea126a7370e309c..b966c2dca801ab5818894cba3d3a57618c536a46 100644 --- a/src/state/actions/window.js +++ b/src/state/actions/window.js @@ -19,6 +19,7 @@ export function focusWindow(windowId) { * @memberof ActionCreators */ export function addWindow(options) { + const cwDefault = `cw-${uuid()}`; const defaultOptions = { id: `window-${uuid()}`, canvasIndex: 0, @@ -30,12 +31,13 @@ export function addWindow(options) { height: 400, x: 2700, y: 2700, - companionWindowIds: [], + companionWindowIds: [cwDefault], + sideBarPanel: 'info', rotation: null, view: 'single', maximized: false, }; - return { type: ActionTypes.ADD_WINDOW, window: { ...defaultOptions, ...options } }; + return { type: ActionTypes.ADD_WINDOW, window: { ...defaultOptions, ...options }, companionWindows: [{ id: cwDefault, position: 'left', content: 'info' }] }; } /** diff --git a/src/state/reducers/companionWindows.js b/src/state/reducers/companionWindows.js index c5cfc2dcc685b5570d1563974c423124591fcaf9..e3d43b9809e8dbb417b9e4eef182e5ca464f21ca 100644 --- a/src/state/reducers/companionWindows.js +++ b/src/state/reducers/companionWindows.js @@ -9,6 +9,12 @@ export function companionWindowsReducer(state = {}, action) { case ActionTypes.ADD_COMPANION_WINDOW: return setIn(state, [action.id], action.payload); + case ActionTypes.ADD_WINDOW: + return action.companionWindows.reduce((newState, cw) => { + newState[cw.id] = cw; // eslint-disable-line no-param-reassign + return newState; + }, state); + case ActionTypes.UPDATE_COMPANION_WINDOW: return updateIn(state, [action.id], orig => merge(orig, action.payload));