diff --git a/src/annotator/config/index.js b/src/annotator/config/index.js index d1a47669158..2a39f590bef 100644 --- a/src/annotator/config/index.js +++ b/src/annotator/config/index.js @@ -10,29 +10,26 @@ var settingsFrom = require('./settings'); function configFrom(window_) { var settings = settingsFrom(window_); return { + annotations: settings.annotations, // URL where client assets are served from. Used when injecting the client // into child iframes. assetRoot: settings.hostPageSetting('assetRoot', {allowInBrowserExt: true}), + branding: settings.hostPageSetting('branding'), // URL of the client's boot script. Used when injecting the client into // child iframes. clientUrl: settings.clientUrl, - - annotations: settings.annotations, - branding: settings.hostPageSetting('branding'), + // Temporary feature flag override for 1st-party OAuth + oauthEnabled: settings.hostPageSetting('oauthEnabled'), + onLayoutChange: settings.hostPageSetting('onLayoutChange'), + openLoginForm: settings.hostPageSetting('openLoginForm', {allowInBrowserExt: true}), + openSidebar: settings.hostPageSetting('openSidebar', {allowInBrowserExt: true}), + query: settings.query, services: settings.hostPageSetting('services'), showHighlights: settings.showHighlights, sidebarAppUrl: settings.sidebarAppUrl, - // Subframe identifier given when a frame is being embedded into // by a top level client subFrameIdentifier: settings.hostPageSetting('subFrameIdentifier', {allowInBrowserExt: true}), - - openLoginForm: settings.hostPageSetting('openLoginForm', {allowInBrowserExt: true}), - openSidebar: settings.hostPageSetting('openSidebar', {allowInBrowserExt: true}), - query: settings.query, - - // Temporary feature flag override for 1st-party OAuth - oauthEnabled: settings.hostPageSetting('oauthEnabled'), }; } diff --git a/src/annotator/sidebar.coffee b/src/annotator/sidebar.coffee index 223a90f02f0..b26b75cda6f 100644 --- a/src/annotator/sidebar.coffee +++ b/src/annotator/sidebar.coffee @@ -5,8 +5,8 @@ Hammer = require('hammerjs') Host = require('./host') annotationCounts = require('./annotation-counts') sidebarTrigger = require('./sidebar-trigger') -events = require('../shared/bridge-events'); -features = require('./features'); +events = require('../shared/bridge-events') +features = require('./features') # Minimum width to which the frame can be resized. MIN_RESIZE = 280 @@ -35,6 +35,7 @@ module.exports = class Sidebar extends Host @plugins.BucketBar.element.on 'click', (event) => this.show() if @plugins.Toolbar? + @toolbarWidth = parseInt(window.getComputedStyle(this.plugins.Toolbar.toolbar[0]).width) this._setupGestures() # The partner-provided callback functions. @@ -46,6 +47,11 @@ module.exports = class Sidebar extends Host @onProfileRequest = serviceConfig.onProfileRequest @onHelpRequest = serviceConfig.onHelpRequest + @onLayoutChange = config.onLayoutChange + + # initial layout notification + this._notifyOfLayoutChange(false) + this._setupSidebarEvents() _setupSidebarEvents: -> @@ -58,23 +64,23 @@ module.exports = class Sidebar extends Host @crossframe.on(events.LOGIN_REQUESTED, => if @onLoginRequest @onLoginRequest() - ); + ) @crossframe.on(events.LOGOUT_REQUESTED, => if @onLogoutRequest @onLogoutRequest() - ); + ) @crossframe.on(events.SIGNUP_REQUESTED, => if @onSignupRequest @onSignupRequest() - ); + ) @crossframe.on(events.PROFILE_REQUESTED, => if @onProfileRequest @onProfileRequest() - ); + ) @crossframe.on(events.HELP_REQUESTED, => if @onHelpRequest @onHelpRequest() - ); + ) # Return this for chaining this @@ -120,6 +126,62 @@ module.exports = class Sidebar extends Host w = -m @frame.css('margin-left', "#{m}px") if w >= MIN_RESIZE then @frame.css('width', "#{w}px") + this._notifyOfLayoutChange() + + ###* + # Notify integrator when sidebar layout changes via `onLayoutChange` callback. + # + # @param [boolean] explicitExpandedState - `true` or `false` if the sidebar + # is being directly opened or closed, as opposed to being resized via + # the sidebar's drag handles. + ### + _notifyOfLayoutChange: (explicitExpandedState) => + toolbarWidth = @toolbarWidth || 0 + + # The sidebar structure is: + # + # [ Toolbar ][ ] + # [ ---------- ][ Sidebar iframe container (@frame) ] + # [ Bucket Bar ][ ] + # + # The sidebar iframe is hidden or shown by adjusting the left margin of its + # container. + + if @onLayoutChange + rect = @frame[0].getBoundingClientRect() + computedStyle = window.getComputedStyle(@frame[0]) + width = parseInt(computedStyle.width) + leftMargin = parseInt(computedStyle.marginLeft) + + # The width of the sidebar that is visible on screen, including the + # toolbar, which is always visible. + frameVisibleWidth = toolbarWidth + + if explicitExpandedState? + # When we are explicitly saying to open or close, jump + # straight to the upper and lower bounding widths. + if explicitExpandedState + frameVisibleWidth += width + else + if leftMargin < MIN_RESIZE + # When the width hits its threshold of MIN_RESIZE, + # the left margin continues to push the sidebar off screen. + # So it's the best indicator of width when we get below that threshold. + # Note: when we hit the right edge, it will be -0 + frameVisibleWidth += -leftMargin + else + frameVisibleWidth += width + + # Since we have added logic on if this is an explicit show/hide + # and applied proper width to the visible value above, we can infer + # expanded state on that width value vs the lower bound + expanded = frameVisibleWidth > toolbarWidth + + @onLayoutChange({ + expanded: expanded, + width: if expanded then frameVisibleWidth else toolbarWidth, + height: rect.height, + }) onPan: (event) => switch event.type @@ -176,6 +238,8 @@ module.exports = class Sidebar extends Host if @options.showHighlights == 'whenSidebarOpen' @setVisibleHighlights(true) + this._notifyOfLayoutChange(true) + hide: -> @frame.css 'margin-left': '' @frame.addClass 'annotator-collapsed' @@ -188,6 +252,8 @@ module.exports = class Sidebar extends Host if @options.showHighlights == 'whenSidebarOpen' @setVisibleHighlights(false) + this._notifyOfLayoutChange(false) + isOpen: -> !@frame.hasClass('annotator-collapsed') diff --git a/src/annotator/test/sidebar-test.coffee b/src/annotator/test/sidebar-test.coffee index 00414791d54..34ed0477bdd 100644 --- a/src/annotator/test/sidebar-test.coffee +++ b/src/annotator/test/sidebar-test.coffee @@ -1,7 +1,11 @@ events = require('../../shared/bridge-events') proxyquire = require('proxyquire') -Sidebar = proxyquire('../sidebar', {}) + +rafStub = (fn) -> + fn() + +Sidebar = proxyquire('../sidebar', { raf: rafStub }) describe 'Sidebar', -> sandbox = sinon.sandbox.create() @@ -253,3 +257,67 @@ describe 'Sidebar', -> assert.calledWith(fakeCrossFrame.call, 'setVisibleHighlights', true) assert.calledWith(sidebar.publish, 'setVisibleHighlights', true) + describe 'layout change notifier', -> + + layoutChangeHandlerSpy = null + sidebar = null + frame = null + DEFAULT_WIDTH = 350 + DEFAULT_HEIGHT = 600 + + assertLayoutValues = (args, expectations) -> + expected = Object.assign { + width: DEFAULT_WIDTH, + height: DEFAULT_HEIGHT, + expanded: true + }, expectations + + assert.deepEqual args, expected + + beforeEach -> + layoutChangeHandlerSpy = sandbox.spy() + sidebar = createSidebar { onLayoutChange: layoutChangeHandlerSpy, sidebarAppUrl: '/' } + + # remove info about call that happens on creation of sidebar + layoutChangeHandlerSpy.reset() + + frame = sidebar.frame[0] + Object.assign frame.style, { + display: 'block', + width: DEFAULT_WIDTH + 'px', + height: DEFAULT_HEIGHT + 'px', + + # width is based on left position of the window, + # we need to apply the css that puts the frame in the + # correct position + position: 'fixed', + top: 0, + left: '100%', + } + + document.body.appendChild frame + + afterEach -> + frame.remove() + + it 'notifies when sidebar changes expanded state', -> + sidebar.show() + assert.calledOnce layoutChangeHandlerSpy + assertLayoutValues layoutChangeHandlerSpy.lastCall.args[0], {expanded: true} + + sidebar.hide() + assert.calledTwice layoutChangeHandlerSpy + assertLayoutValues layoutChangeHandlerSpy.lastCall.args[0], { + expanded: false, + width: 0, + } + + it 'notifies when sidebar is panned left', -> + sidebar.gestureState = { initial: -DEFAULT_WIDTH } + sidebar.onPan({type: 'panleft', deltaX: -50}) + assertLayoutValues layoutChangeHandlerSpy.lastCall.args[0], { width: 400 } + + it 'notifies when sidebar is panned right', -> + sidebar.gestureState = { initial: -DEFAULT_WIDTH } + sidebar.onPan({type: 'panright', deltaX: 50}) + assertLayoutValues layoutChangeHandlerSpy.lastCall.args[0], { width: 300 }