Commit 1143e063 authored by Lyza Danger Gardner's avatar Lyza Danger Gardner Committed by Lyza Gardner

Add basic Notebook class, supporting modules

parent 9fdd66b0
/**
* Create the JSON-serializable subset of annotator configuration that should
* be passed to the sidebar application.
*/
export function createSidebarConfig(config) {
const sidebarConfig = { ...config };
// Some config settings are not JSON-stringifiable (e.g. JavaScript
// functions) and will be omitted when the config is JSON-stringified.
// Add a JSON-stringifiable option for each of these so that the sidebar can
// at least know whether the callback functions were provided or not.
if (sidebarConfig.services?.length > 0) {
const service = sidebarConfig.services[0];
if (service.onLoginRequest) {
service.onLoginRequestProvided = true;
}
if (service.onLogoutRequest) {
service.onLogoutRequestProvided = true;
}
if (service.onSignupRequest) {
service.onSignupRequestProvided = true;
}
if (service.onProfileRequest) {
service.onProfileRequestProvided = true;
}
if (service.onHelpRequest) {
service.onHelpRequestProvided = true;
}
}
// Remove several annotator-only properties.
//
// nb. We don't currently strip all the annotator-only properties here.
// That's OK because validation / filtering happens in the sidebar app itself.
// It just results in unnecessary content in the sidebar iframe's URL string.
['notebookAppUrl', 'sidebarAppUrl', 'pluginClasses'].forEach(
key => delete sidebarConfig[key]
);
return sidebarConfig;
}
import Delegator from './delegator';
import { createSidebarConfig } from './config/sidebar';
/**
* Create the iframe that will load the notebook application.
*
* @return {HTMLIFrameElement}
*/
function createNotebookFrame(config) {
const sidebarConfig = createSidebarConfig(config);
const configParam =
'config=' + encodeURIComponent(JSON.stringify(sidebarConfig));
const notebookAppSrc = config.notebookAppUrl + '#' + configParam;
const notebookFrame = document.createElement('iframe');
// Enable media in annotations to be shown fullscreen
notebookFrame.setAttribute('allowfullscreen', '');
notebookFrame.src = notebookAppSrc;
notebookFrame.title = 'Hypothesis annotation notebook';
notebookFrame.className = 'notebook-inner';
return notebookFrame;
}
export default class Notebook extends Delegator {
constructor(element, config) {
super(element, config);
this.frame = null;
this.container = document.createElement('div');
this.container.style.display = 'none';
this.container.className = 'notebook-outer';
this.subscribe('showNotebook', () => this.show());
this.subscribe('hideNotebook', () => this.hide());
// If the sidebar has opened, get out of the way
this.subscribe('sidebarOpened', () => this.hide());
}
init() {
if (!this.frame) {
this.frame = createNotebookFrame(this.options);
this.container.appendChild(this.frame);
this.element.appendChild(this.container);
}
}
show() {
this.init();
this.container.classList.add('is-open');
this.container.style.display = '';
}
hide() {
this.container.classList.remove('is-open');
this.container.style.display = 'none';
}
destroy() {
this.frame?.remove();
}
}
import Notebook from '../notebook';
describe('Notebook', () => {
// `Notebook` instances created by current test
let notebooks;
const createNotebook = (config = {}) => {
config = { notebookAppUrl: '/base/annotator/test/empty.html', ...config };
const element = document.createElement('div');
const notebook = new Notebook(element, config);
notebooks.push(notebook);
return notebook;
};
beforeEach(() => {
notebooks = [];
});
afterEach(() => {
notebooks.forEach(n => n.destroy());
});
describe('notebook container frame', () => {
it('starts hidden', () => {
const notebook = createNotebook();
assert.equal(notebook.container.style.display, 'none');
});
it('displays when opened', () => {
const notebook = createNotebook();
notebook.show();
assert.equal(notebook.container.style.display, '');
assert.isTrue(notebook.container.classList.contains('is-open'));
});
it('hides when closed', () => {
const notebook = createNotebook();
notebook.show();
notebook.hide();
assert.equal(notebook.container.style.display, 'none');
assert.isFalse(notebook.container.classList.contains('is-open'));
});
});
describe('creating the notebook iframe', () => {
it('creates the iframe when the notebook is shown for the first time', () => {
const notebook = createNotebook();
assert.isNull(notebook.frame);
notebook.show();
assert.isTrue(notebook.frame instanceof Element);
});
it('sets the iframe source to the configured `notebookAppUrl`', () => {
const notebook = createNotebook({
notebookAppUrl: 'http://www.example.com/foo/bar',
});
notebook.show();
// The rest of the config gets added as a hash to the end of the src,
// so split that off and look at the string before it
assert.equal(
notebook.frame.src.split('#')[0],
'http://www.example.com/foo/bar'
);
});
});
describe('responding to events', () => {
it('shows on `showNotebook`', () => {
const notebook = createNotebook();
notebook.publish('showNotebook');
assert.equal(notebook.container.style.display, '');
});
it('hides on `hideNotebook`', () => {
const notebook = createNotebook();
notebook.show();
notebook.publish('hideNotebook');
assert.equal(notebook.container.style.display, 'none');
});
it('hides on "sidebarOpened"', () => {
const notebook = createNotebook();
notebook.show();
notebook.publish('sidebarOpened');
assert.equal(notebook.container.style.display, 'none');
});
});
describe('destruction', () => {
it('should remove the frame', () => {
const notebook = createNotebook();
// Make sure the frame is created
notebook.init();
notebook.destroy();
assert.equal(notebook.frame.parentElement, null);
});
});
});
......@@ -15,6 +15,7 @@
@use './components/toolbar';
@use './bucket-bar';
@use './highlights';
@use './notebook';
// Sidebar
.annotator-frame {
......
@use '../variables' as var;
@use '../mixins/molecules';
.notebook-outer {
// TODO: CSS namespace conflicts?
box-sizing: border-box;
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
padding: var.$layout-space;
// Leave explicitly the right amount of room for closed-sidebar affordances
padding-right: var.$annotator-toolbar-width + 5px;
&.is-open {
// TBD: Actual opacity/overlay we'd like to use
background-color: rgba(0, 0, 0, 0.5);
}
}
.notebook-inner {
box-sizing: border-box;
@include molecules.panel;
padding: 0;
width: 100%;
height: 100%;
}
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