Commit 0bb2f970 authored by Eduardo Sanz García's avatar Eduardo Sanz García Committed by Eduardo

Added close button to the notebook

The sidebar component directs the opening of the notebook. This is for two reasons:

* it's a little bit easier to save the UI state of the current sidebar
  (immediately before the notebook is opened), in case in the future we
  want to restored it.

* to avoid adding additional events, like `show/hideSidebarControls`,
  that probably would be needed for granular control of the sidebar, in
  case the notebook would be directing the process of closing/re-opening
  the sidebar.
parent f0a2ae18
...@@ -3,6 +3,9 @@ import { createSidebarConfig } from './config/sidebar'; ...@@ -3,6 +3,9 @@ import { createSidebarConfig } from './config/sidebar';
import { createShadowRoot } from './util/shadow-root'; import { createShadowRoot } from './util/shadow-root';
import { render } from 'preact'; import { render } from 'preact';
// FIXME: use the button from the frontend shared package once this is stable.
import Button from '../sidebar/components/Button';
/** /**
* Create the iframe that will load the notebook application. * Create the iframe that will load the notebook application.
* *
...@@ -33,7 +36,9 @@ export default class Notebook extends Delegator { ...@@ -33,7 +36,9 @@ export default class Notebook extends Delegator {
super(element, config); super(element, config);
this.frame = null; this.frame = null;
/** @type {null|string} */
this._groupId = null; this._groupId = null;
/** @type {null|string} */
this._prevGroupId = null; this._prevGroupId = null;
/** /**
...@@ -56,8 +61,7 @@ export default class Notebook extends Delegator { ...@@ -56,8 +61,7 @@ export default class Notebook extends Delegator {
this._groupId = groupId; this._groupId = groupId;
this.open(); this.open();
}); });
this.subscribe('closeNotebook', () => this.close()); // Defensive programming: if the sidebar has opened, get out of the way
// If the sidebar has opened, get out of the way
this.subscribe('sidebarOpened', () => this.close()); this.subscribe('sidebarOpened', () => this.close());
} }
...@@ -106,6 +110,25 @@ export default class Notebook extends Delegator { ...@@ -106,6 +110,25 @@ export default class Notebook extends Delegator {
this.container.className = 'notebook-outer'; this.container.className = 'notebook-outer';
shadowRoot.appendChild(this.container); shadowRoot.appendChild(this.container);
render(
<div className="Notebook__controller-bar">
<Button
icon="cancel"
className="Notebook__close-button"
buttonText="Close"
title="Close the Notebook"
onClick={event => {
// Guest 'component' captures all click events in the host page and opens the sidebar.
// We stop the propagation of the event to prevent the sidebar to be opened.
event.stopPropagation();
this.close();
this.publish('closeNotebook');
}}
/>
</div>,
this.container
);
return this.container; return this.container;
} }
} }
...@@ -237,13 +237,14 @@ export default class Sidebar extends Delegator { ...@@ -237,13 +237,14 @@ export default class Sidebar extends Delegator {
// Re-publish the crossframe event so that anything extending Delegator // Re-publish the crossframe event so that anything extending Delegator
// can subscribe to it (without need for crossframe) // can subscribe to it (without need for crossframe)
this.guest.crossframe.on('openNotebook', groupId => { this.guest.crossframe.on('openNotebook', (
this.close(); /** @type {string} */ groupId
) => {
this.hide();
this.publish('openNotebook', [groupId]); this.publish('openNotebook', [groupId]);
}); });
this.guest.crossframe.on('closeNotebook', () => { this.subscribe('closeNotebook', () => {
this.open(); this.show();
this.publish('closeNotebook');
}); });
const eventHandlers = [ const eventHandlers = [
...@@ -472,4 +473,22 @@ export default class Sidebar extends Delegator { ...@@ -472,4 +473,22 @@ export default class Sidebar extends Delegator {
setAllVisibleHighlights(shouldShowHighlights) { setAllVisibleHighlights(shouldShowHighlights) {
this.guest.crossframe.call('setVisibleHighlights', shouldShowHighlights); this.guest.crossframe.call('setVisibleHighlights', shouldShowHighlights);
} }
/**
* Shows the sidebar's controls
*/
show() {
if (this.iframeContainer) {
this.iframeContainer.classList.remove('is-hidden');
}
}
/**
* Hides the sidebar's controls
*/
hide() {
if (this.iframeContainer) {
this.iframeContainer.classList.add('is-hidden');
}
}
} }
...@@ -114,22 +114,27 @@ describe('Notebook', () => { ...@@ -114,22 +114,27 @@ describe('Notebook', () => {
}); });
}); });
describe('responding to events', () => { describe('responding to user input', () => {
it('opens on `openNotebook`', () => { it('closes the notebook when close button clicked', () => {
const notebook = createNotebook(); const notebook = createNotebook();
notebook.publish('openNotebook'); notebook.open();
assert.equal(notebook.container.style.display, ''); const button = notebook.container.getElementsByClassName(
'Notebook__close-button'
)[0];
button.click();
assert.equal(notebook.container.style.display, 'none');
}); });
});
it('closes on `closeNotebook`', () => { describe('responding to events', () => {
it('opens on `openNotebook`', () => {
const notebook = createNotebook(); const notebook = createNotebook();
notebook.open(); notebook.publish('openNotebook');
notebook.publish('closeNotebook');
assert.equal(notebook.container.style.display, 'none'); assert.equal(notebook.container.style.display, '');
}); });
it('closes on "sidebarOpened"', () => { it('closes on "sidebarOpened"', () => {
......
...@@ -278,29 +278,29 @@ describe('Sidebar', () => { ...@@ -278,29 +278,29 @@ describe('Sidebar', () => {
assert.called(target); assert.called(target);
})); }));
describe('on "openNotebook" event', () => { describe('on "openNotebook" crossframe event', () => {
it('hides itself and republishes the event', () => { it('hides the sidebar', () => {
const sidebar = createSidebar(); const sidebar = createSidebar();
sinon.stub(sidebar, 'hide').callThrough();
sinon.stub(sidebar, 'publish'); sinon.stub(sidebar, 'publish');
sinon.stub(sidebar, 'close');
emitEvent('openNotebook', 'mygroup'); emitEvent('openNotebook', 'mygroup');
assert.calledWith( assert.calledWith(
sidebar.publish, sidebar.publish,
'openNotebook', 'openNotebook',
sinon.match(['mygroup']) sinon.match(['mygroup'])
); );
assert.calledOnce(sidebar.close); assert.calledOnce(sidebar.hide);
assert.notEqual(sidebar.iframeContainer.style.visibility, 'hidden');
}); });
}); });
describe('on "closeNotebook" event', () => { describe('on "closeNotebook" internal event', () => {
it('opens itself and republishes the event', () => { it('shows the sidebar', () => {
const sidebar = createSidebar(); const sidebar = createSidebar();
sinon.stub(sidebar, 'publish'); sinon.stub(sidebar, 'show').callThrough();
sinon.stub(sidebar, 'open'); sidebar.publish('closeNotebook');
emitEvent('closeNotebook'); assert.calledOnce(sidebar.show);
assert.calledWith(sidebar.publish, 'closeNotebook'); assert.equal(sidebar.iframeContainer.style.visibility, '');
assert.calledOnce(sidebar.open);
}); });
}); });
......
...@@ -47,6 +47,11 @@ ...@@ -47,6 +47,11 @@
user-select: none; user-select: none;
-webkit-tap-highlight-color: rgba(255, 255, 255, 0); -webkit-tap-highlight-color: rgba(255, 255, 255, 0);
&.is-hidden {
visibility: hidden;
transition: visibility var.$annotator-timing--sidebar-collapse-transition;
}
&.annotator-collapsed { &.annotator-collapsed {
margin-left: 0; margin-left: 0;
......
@use '../variables' as var; @use '../variables' as var;
@use '../mixins/molecules'; @use '../mixins/molecules';
@use "../mixins/buttons";
.notebook-outer { .notebook-outer {
box-sizing: border-box; box-sizing: border-box;
position: fixed; position: fixed;
// This large zIndex is used to bring the notebook to the front, so it is not
// hidden by other elements from the host page. It is the same value used by
// the sidebar
z-index: 2147483647;
top: 0; top: 0;
left: 0; left: 0;
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
padding: var.$layout-space; padding: var.$layout-space;
// Leave explicitly the right amount of room for closed-sidebar affordances
padding-right: var.$annotator-toolbar-width + 5px;
&.is-open { &.is-open {
// TBD: Actual opacity/overlay we'd like to use // TBD: Actual opacity/overlay we'd like to use
...@@ -24,4 +27,30 @@ ...@@ -24,4 +27,30 @@
padding: 0; padding: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
// FIXME: these properties produce a visual break between the Notebook__controller-bar and the iframe.
// A better approach would be if the Notebook__controller-bar and iframe could be children of notebook-inner.
border: none;
border-top-left-radius: 0;
border-top-right-radius: 0;
}
// This container element has the purpose of pushing children to the right side.
.Notebook__controller-bar {
font-size: var.$font-size--large;
display: flex;
justify-content: flex-end;
background-color: var.$grey-2;
padding: var.$layout-space--xsmall;
// FIXME: these properties emulates as if the Notebook__controller-bar would be part of the iframe.
border-top-left-radius: var.$border-radius;
border-top-right-radius: var.$border-radius;
}
.Notebook__close-button {
@include buttons.button--labeled(
$background-color: var.$grey-2,
$active-background-color: var.$grey-3
);
} }
...@@ -24,9 +24,9 @@ ...@@ -24,9 +24,9 @@
// FIXME: Temporary affordance for the Notebook view to override some // FIXME: Temporary affordance for the Notebook view to override some
// layout spacing that assumes the presence of the top bar // layout spacing that assumes the presence of the top bar
&--notebook { &--notebook {
padding: var.$layout-space; padding: 0 var.$layout-space;
@include responsive.respond-to(tablets desktops) { @include responsive.respond-to(tablets desktops) {
padding: var.$layout-space--xlarge; padding: 0 var.$layout-space--xlarge;
} }
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment