Commit 570ce476 authored by Robert Knight's avatar Robert Knight

Convert AnnotationUI, AnnotationUISync from CoffeeScript to JS

parent acd4eb9a
# Watch the UI state and update scope properties.
module.exports = class AnnotationUIController
this.$inject = ['$rootScope', '$scope', 'annotationUI']
constructor: ( $rootScope, $scope, annotationUI ) ->
$rootScope.$watch (-> annotationUI.selectedAnnotationMap), (map={}) ->
count = Object.keys(map).length
$scope.selectedAnnotationsCount = count
if count
$scope.selectedAnnotations = map
else
$scope.selectedAnnotations = null
$rootScope.$watch (-> annotationUI.focusedAnnotationMap), (map={}) ->
$scope.focusedAnnotations = map
$rootScope.$on 'annotationDeleted', (event, annotation) ->
annotationUI.removeSelectedAnnotation(annotation)
'use strict';
/** Watch the UI state and update scope properties. */
// @ngInject
function AnnotationUIController($rootScope, $scope, annotationUI) {
$rootScope.$watch(function () {
return annotationUI.selectedAnnotationMap;
}, function (map) {
map = map || {};
var count = Object.keys(map).length;
$scope.selectedAnnotationsCount = count;
if (count) {
$scope.selectedAnnotations = map;
} else {
$scope.selectedAnnotations = null;
}
});
$rootScope.$watch(function () {
return annotationUI.focusedAnnotationMap;
}, function (map) {
map = map || {};
$scope.focusedAnnotations = map;
});
$rootScope.$on('annotationDeleted', function (event, annotation) {
annotationUI.removeSelectedAnnotation(annotation);
});
}
module.exports = AnnotationUIController;
# Uses a channel between the sidebar and the attached frames to ensure
# the interface remains in sync.
module.exports = class AnnotationUISync
###*
# @name AnnotationUISync
# @param {$window} $window An Angular window service.
# @param {Bridge} bridge
# @param {AnnotationSync} annotationSync
# @param {AnnotationUI} annotationUI An instance of the AnnotatonUI service
# @description
# Listens for incoming events over the bridge concerning the annotation
# interface and updates the applications internal state. It also ensures
# that the messages are broadcast out to other frames.
###
constructor: ($rootScope, $window, bridge, annotationSync, annotationUI) ->
# Retrieves annotations from the annotationSync cache.
getAnnotationsByTags = (tags) ->
tags.map(annotationSync.getAnnotationForTag, annotationSync)
channelListeners =
showAnnotations: (tags=[]) ->
annotations = getAnnotationsByTags(tags)
annotationUI.selectAnnotations(annotations)
focusAnnotations: (tags=[]) ->
annotations = getAnnotationsByTags(tags)
annotationUI.focusAnnotations(annotations)
toggleAnnotationSelection: (tags=[]) ->
annotations = getAnnotationsByTags(tags)
annotationUI.xorSelectedAnnotations(annotations)
setVisibleHighlights: (state=true) ->
if annotationUI.visibleHighlights != state
annotationUI.visibleHighlights = state
bridge.call('setVisibleHighlights', state)
# Because the channel events are all outside of the angular framework we
# need to inform Angular that it needs to re-check it's state and re-draw
# any UI that may have been affected by the handlers.
ensureDigest = (fn) ->
->
fn.apply(this, arguments)
$rootScope.$digest()
for own channel, listener of channelListeners
bridge.on(channel, ensureDigest(listener))
onConnect = (channel, source) ->
if source is $window.parent
# The host initializes its own state
return
else
# Synchronize the state of guests
channel.call('setVisibleHighlights', annotationUI.visibleHighlights)
bridge.onConnect(onConnect)
'use strict';
/**
* Uses a channel between the sidebar and the attached frames to ensure
* the interface remains in sync.
*
* @name AnnotationUISync
* @param {$window} $window An Angular window service.
* @param {Bridge} bridge
* @param {AnnotationSync} annotationSync
* @param {AnnotationUI} annotationUI An instance of the AnnotatonUI service
* @description
* Listens for incoming events over the bridge concerning the annotation
* interface and updates the applications internal state. It also ensures
* that the messages are broadcast out to other frames.
*/
// @ngInject
function AnnotationUISync($rootScope, $window, bridge, annotationSync,
annotationUI) {
// Retrieves annotations from the annotationSync cache.
var getAnnotationsByTags = function (tags) {
return tags.map(annotationSync.getAnnotationForTag, annotationSync);
};
var channelListeners = {
showAnnotations: function (tags) {
tags = tags || [];
var annotations = getAnnotationsByTags(tags);
annotationUI.selectAnnotations(annotations);
},
focusAnnotations: function (tags) {
tags = tags || [];
var annotations = getAnnotationsByTags(tags);
annotationUI.focusAnnotations(annotations);
},
toggleAnnotationSelection: function (tags) {
tags = tags || [];
var annotations = getAnnotationsByTags(tags);
annotationUI.xorSelectedAnnotations(annotations);
},
setVisibleHighlights: function (state) {
if (typeof state !== 'boolean') {
state = true;
}
if (annotationUI.visibleHighlights !== state) {
annotationUI.visibleHighlights = state;
bridge.call('setVisibleHighlights', state);
}
}
};
// Because the channel events are all outside of the angular framework we
// need to inform Angular that it needs to re-check it's state and re-draw
// any UI that may have been affected by the handlers.
var ensureDigest = function (fn) {
return function () {
fn.apply(this, arguments);
$rootScope.$digest();
};
};
for (var channel in channelListeners) {
if (Object.prototype.hasOwnProperty.call(channelListeners, channel)) {
var listener = channelListeners[channel];
bridge.on(channel, ensureDigest(listener));
}
}
var onConnect = function (channel, source) {
if (source === $window.parent) {
// The host initializes its own state
return;
} else {
// Synchronize the state of guests
channel.call('setVisibleHighlights', annotationUI.visibleHighlights);
}
};
bridge.onConnect(onConnect);
}
module.exports = AnnotationUISync;
{module, inject} = angular.mock
describe 'AnnotationUIController', ->
$scope = null
$rootScope = null
annotationUI = null
sandbox = null
before ->
angular.module('h', [])
.controller('AnnotationUIController', require('../annotation-ui-controller'))
beforeEach module('h')
beforeEach inject ($controller, _$rootScope_) ->
sandbox = sinon.sandbox.create()
$rootScope = _$rootScope_
$scope = $rootScope.$new()
$scope.search = {}
annotationUI =
tool: 'comment'
selectedAnnotationMap: null
focusedAnnotationsMap: null
removeSelectedAnnotation: sandbox.stub()
$controller 'AnnotationUIController', {$scope, annotationUI}
afterEach ->
sandbox.restore()
it 'updates the view when the selection changes', ->
annotationUI.selectedAnnotationMap = {1: true, 2: true}
$rootScope.$digest()
assert.deepEqual($scope.selectedAnnotations, {1: true, 2: true})
it 'updates the selection counter when the selection changes', ->
annotationUI.selectedAnnotationMap = {1: true, 2: true}
$rootScope.$digest()
assert.deepEqual($scope.selectedAnnotationsCount, 2)
it 'clears the selection when no annotations are selected', ->
annotationUI.selectedAnnotationMap = {}
$rootScope.$digest()
assert.deepEqual($scope.selectedAnnotations, null)
assert.deepEqual($scope.selectedAnnotationsCount, 0)
it 'updates the focused annotations when the focus map changes', ->
annotationUI.focusedAnnotationMap = {1: true, 2: true}
$rootScope.$digest()
assert.deepEqual($scope.focusedAnnotations, {1: true, 2: true})
describe 'on annotationDeleted', ->
it 'removes the deleted annotation from the selection', ->
$rootScope.$emit('annotationDeleted', {id: 1})
assert.calledWith(annotationUI.removeSelectedAnnotation, {id: 1})
'use strict';
var angular = require('angular');
describe('AnnotationUIController', function () {
var $scope;
var $rootScope;
var annotationUI;
var sandbox;
before(function () {
angular.module('h', [])
.controller('AnnotationUIController',
require('../annotation-ui-controller'));
});
beforeEach(angular.mock.module('h'));
beforeEach(angular.mock.inject(function ($controller, _$rootScope_) {
sandbox = sinon.sandbox.create();
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
$scope.search = {};
annotationUI = {
tool: 'comment',
selectedAnnotationMap: null,
focusedAnnotationsMap: null,
removeSelectedAnnotation: sandbox.stub()
};
$controller('AnnotationUIController', {
$scope: $scope,
annotationUI: annotationUI,
});
}));
afterEach(function () {
sandbox.restore();
});
it('updates the view when the selection changes', function () {
annotationUI.selectedAnnotationMap = { 1: true, 2: true };
$rootScope.$digest();
assert.deepEqual($scope.selectedAnnotations, { 1: true, 2: true });
});
it('updates the selection counter when the selection changes', function () {
annotationUI.selectedAnnotationMap = { 1: true, 2: true };
$rootScope.$digest();
assert.deepEqual($scope.selectedAnnotationsCount, 2);
});
it('clears the selection when no annotations are selected', function () {
annotationUI.selectedAnnotationMap = {};
$rootScope.$digest();
assert.deepEqual($scope.selectedAnnotations, null);
assert.deepEqual($scope.selectedAnnotationsCount, 0);
});
it('updates the focused annotations when the focus map changes', function () {
annotationUI.focusedAnnotationMap = { 1: true, 2: true };
$rootScope.$digest();
assert.deepEqual($scope.focusedAnnotations, { 1: true, 2: true });
});
describe('on annotationDeleted', function () {
it('removes the deleted annotation from the selection', function () {
$rootScope.$emit('annotationDeleted', { id: 1 });
assert.calledWith(annotationUI.removeSelectedAnnotation, { id: 1 });
});
});
});
{module, inject} = angular.mock
describe 'AnnotationUISync', ->
sandbox = sinon.sandbox.create()
$digest = null
uiSync = null
publish = null
fakeBridge = null
fakeAnnotationUI = null
fakeAnnotationSync = null
createAnnotationUISync = null
createChannel = -> {call: sandbox.stub()}
PARENT_WINDOW = 'PARENT_WINDOW'
before ->
angular.module('h', [])
.value('AnnotationUISync', require('../annotation-ui-sync'))
beforeEach module('h')
beforeEach inject (AnnotationUISync, $rootScope) ->
$digest = sandbox.stub($rootScope, '$digest')
listeners = {}
publish = (method, args...) -> listeners[method](args...)
fakeWindow = parent: PARENT_WINDOW
fakeBridge =
on: sandbox.spy((method, fn) -> listeners[method] = fn)
call: sandbox.stub()
onConnect: sandbox.stub()
links: [
{window: PARENT_WINDOW, channel: createChannel()}
{window: 'ANOTHER_WINDOW', channel: createChannel()}
{window: 'THIRD_WINDOW', channel: createChannel()}
]
fakeAnnotationSync =
getAnnotationForTag: (tag) -> {id: Number(tag.replace('tag', ''))}
fakeAnnotationUI =
focusAnnotations: sandbox.stub()
selectAnnotations: sandbox.stub()
xorSelectedAnnotations: sandbox.stub()
visibleHighlights: false
createAnnotationUISync = ->
new AnnotationUISync(
$rootScope, fakeWindow, fakeBridge, fakeAnnotationSync, fakeAnnotationUI)
afterEach: -> sandbox.restore()
describe 'on bridge connection', ->
describe 'when the source is not the parent window', ->
it 'broadcasts the visibility settings to the channel', ->
channel = createChannel()
fakeBridge.onConnect.callsArgWith(0, channel, {})
createAnnotationUISync()
assert.calledWith(channel.call, 'setVisibleHighlights', false)
describe 'when the source is the parent window', ->
it 'does nothing', ->
channel = call: sandbox.stub()
fakeBridge.onConnect.callsArgWith(0, channel, PARENT_WINDOW)
createAnnotationUISync()
assert.notCalled(channel.call)
describe 'on "showAnnotations" event', ->
it 'updates the annotationUI to include the shown annotations', ->
createAnnotationUISync()
publish('showAnnotations', ['tag1', 'tag2', 'tag3'])
assert.called(fakeAnnotationUI.selectAnnotations)
assert.calledWith(fakeAnnotationUI.selectAnnotations, [
{id: 1}, {id: 2}, {id: 3}
])
it 'triggers a digest', ->
createAnnotationUISync()
publish('showAnnotations', ['tag1', 'tag2', 'tag3'])
assert.called($digest)
describe 'on "focusAnnotations" event', ->
it 'updates the annotationUI to show the provided annotations', ->
createAnnotationUISync()
publish('focusAnnotations', ['tag1', 'tag2', 'tag3'])
assert.called(fakeAnnotationUI.focusAnnotations)
assert.calledWith(fakeAnnotationUI.focusAnnotations, [
{id: 1}, {id: 2}, {id: 3}
])
it 'triggers a digest', ->
createAnnotationUISync()
publish('focusAnnotations', ['tag1', 'tag2', 'tag3'])
assert.called($digest)
describe 'on "toggleAnnotationSelection" event', ->
it 'updates the annotationUI to show the provided annotations', ->
createAnnotationUISync()
publish('toggleAnnotationSelection', ['tag1', 'tag2', 'tag3'])
assert.called(fakeAnnotationUI.xorSelectedAnnotations)
assert.calledWith(fakeAnnotationUI.xorSelectedAnnotations, [
{id: 1}, {id: 2}, {id: 3}
])
it 'triggers a digest', ->
createAnnotationUISync()
publish('toggleAnnotationSelection', ['tag1', 'tag2', 'tag3'])
assert.called($digest)
describe 'on "setVisibleHighlights" event', ->
it 'updates the annotationUI with the new value', ->
createAnnotationUISync()
publish('setVisibleHighlights', true)
assert.equal(fakeAnnotationUI.visibleHighlights, true)
it 'notifies the other frames of the change', ->
createAnnotationUISync()
publish('setVisibleHighlights', true)
assert.calledWith(fakeBridge.call, 'setVisibleHighlights', true)
it 'triggers a digest of the application state', ->
createAnnotationUISync()
publish('setVisibleHighlights', true)
assert.called($digest)
'use strict';
var angular = require('angular');
describe('AnnotationUISync', function () {
var sandbox = sinon.sandbox.create();
var $digest;
var publish;
var fakeBridge;
var fakeAnnotationUI;
var fakeAnnotationSync;
var createAnnotationUISync;
var createChannel = function () {
return { call: sandbox.stub() };
};
var PARENT_WINDOW = 'PARENT_WINDOW';
before(function () {
angular.module('h', [])
.value('AnnotationUISync', require('../annotation-ui-sync'));
});
beforeEach(angular.mock.module('h'));
beforeEach(angular.mock.inject(function (AnnotationUISync, $rootScope) {
$digest = sandbox.stub($rootScope, '$digest');
var listeners = {};
publish = function (method) {
var args = [].slice.apply(arguments);
return listeners[method].apply(null, args.slice(1));
};
var fakeWindow = { parent: PARENT_WINDOW };
fakeBridge = {
on: sandbox.spy(function (method, fn) { listeners[method] = fn; }),
call: sandbox.stub(),
onConnect: sandbox.stub(),
links: [
{ window: PARENT_WINDOW, channel: createChannel() },
{ window: 'ANOTHER_WINDOW', channel: createChannel() },
{ window: 'THIRD_WINDOW', channel: createChannel() }
]
};
fakeAnnotationSync = {
getAnnotationForTag: function (tag) {
return { id: Number(tag.replace('tag', '')) };
}
};
fakeAnnotationUI = {
focusAnnotations: sandbox.stub(),
selectAnnotations: sandbox.stub(),
xorSelectedAnnotations: sandbox.stub(),
visibleHighlights: false,
};
createAnnotationUISync = function () {
new AnnotationUISync(
$rootScope, fakeWindow, fakeBridge, fakeAnnotationSync,
fakeAnnotationUI
);
};
}));
afterEach(function () {
sandbox.restore();
});
describe('on bridge connection', function () {
describe('when the source is not the parent window', function () {
it('broadcasts the visibility settings to the channel', function () {
var channel = createChannel();
fakeBridge.onConnect.callsArgWith(0, channel, {});
createAnnotationUISync();
assert.calledWith(channel.call, 'setVisibleHighlights', false);
});
});
describe('when the source is the parent window', function () {
it('does nothing', function () {
var channel = { call: sandbox.stub() };
fakeBridge.onConnect.callsArgWith(0, channel, PARENT_WINDOW);
createAnnotationUISync();
assert.notCalled(channel.call);
});
});
});
describe('on "showAnnotations" event', function () {
it('updates the annotationUI to include the shown annotations', function () {
createAnnotationUISync();
publish('showAnnotations', ['tag1', 'tag2', 'tag3']);
assert.called(fakeAnnotationUI.selectAnnotations);
assert.calledWith(fakeAnnotationUI.selectAnnotations, [
{ id: 1 }, { id: 2 }, { id: 3 }
]);
});
it('triggers a digest', function () {
createAnnotationUISync();
publish('showAnnotations', ['tag1', 'tag2', 'tag3']);
assert.called($digest);
});
});
describe('on "focusAnnotations" event', function () {
it('updates the annotationUI to show the provided annotations', function () {
createAnnotationUISync();
publish('focusAnnotations', ['tag1', 'tag2', 'tag3']);
assert.called(fakeAnnotationUI.focusAnnotations);
assert.calledWith(fakeAnnotationUI.focusAnnotations, [
{ id: 1 }, { id: 2 }, { id: 3 }
]);
});
it('triggers a digest', function () {
createAnnotationUISync();
publish('focusAnnotations', ['tag1', 'tag2', 'tag3']);
assert.called($digest);
});
});
describe('on "toggleAnnotationSelection" event', function () {
it('updates the annotationUI to show the provided annotations', function () {
createAnnotationUISync();
publish('toggleAnnotationSelection', ['tag1', 'tag2', 'tag3']);
assert.called(fakeAnnotationUI.xorSelectedAnnotations);
assert.calledWith(fakeAnnotationUI.xorSelectedAnnotations, [
{ id: 1 }, { id: 2 }, { id: 3 }
]);
});
it('triggers a digest', function () {
createAnnotationUISync();
publish('toggleAnnotationSelection', ['tag1', 'tag2', 'tag3']);
assert.called($digest);
});
});
describe('on "setVisibleHighlights" event', function () {
it('updates the annotationUI with the new value', function () {
createAnnotationUISync();
publish('setVisibleHighlights', true);
assert.equal(fakeAnnotationUI.visibleHighlights, true);
});
it('notifies the other frames of the change', function () {
createAnnotationUISync();
publish('setVisibleHighlights', true);
assert.calledWith(fakeBridge.call, 'setVisibleHighlights', true);
});
it('triggers a digest of the application state', function () {
createAnnotationUISync();
publish('setVisibleHighlights', true);
assert.called($digest);
});
});
});
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