Commit b5e68c35 authored by Sean Hammond's avatar Sean Hammond Committed by GitHub

Merge pull request #313 from hypothesis/sidebar-content-component

Convert `WidgetController` into a `<sidebar-content>` component
parents 8fed502d 7d0122d4
......@@ -5,7 +5,6 @@ var scrollIntoView = require('scroll-into-view');
var events = require('./events');
var parseAccountID = require('./filter/persona').parseAccountID;
var scopeTimeout = require('./util/scope-timeout');
var uiConstants = require('./ui-constants');
var serviceConfig = require('./service-config');
var bridgeEvents = require('../shared/bridge-events');
......@@ -146,16 +145,6 @@ module.exports = function AppController(
session.logout();
};
$scope.clearSelection = function () {
var selectedTab = annotationUI.getState().selectedTab;
if (!annotationUI.getState().selectedTab || annotationUI.getState().selectedTab === uiConstants.TAB_ORPHANS) {
selectedTab = uiConstants.TAB_ANNOTATIONS;
}
annotationUI.clearSelectedAnnotations();
annotationUI.selectTab(selectedTab);
};
$scope.search = {
query: function () {
return annotationUI.getState().filterQuery;
......
......@@ -75,8 +75,11 @@ function configureRoutes($routeProvider) {
resolve: resolve,
});
$routeProvider.otherwise({
controller: 'WidgetController',
template: require('./templates/sidebar_content.html'),
// Trivial template for use until the other controllers are also converted
// to components and we can remove the router entirely.
//
// The "search" and "auth" properties are provided by "AppController".
template: '<sidebar-content search="search" auth="auth"></sidebar-content>',
reloadOnSearch: false,
resolve: resolve,
});
......@@ -128,7 +131,6 @@ module.exports = angular.module('h', [
.controller('AnnotationViewerController', require('./annotation-viewer-controller'))
.controller('StreamController', require('./stream-controller'))
.controller('WidgetController', require('./widget-controller'))
// The root component for the application
.directive('hypothesisApp', require('./directive/app'))
......@@ -150,6 +152,7 @@ module.exports = angular.module('h', [
.component('searchInput', require('./components/search-input'))
.component('searchStatusBar', require('./components/search-status-bar'))
.component('selectionTabs', require('./components/selection-tabs'))
.component('sidebarContent', require('./components/sidebar-content'))
.component('sidebarTutorial', require('./components/sidebar-tutorial').component)
.component('shareDialog', require('./components/share-dialog'))
.component('sortDropdown', require('./components/sort-dropdown'))
......
'use strict';
var SearchClient = require('./search-client');
var events = require('./events');
var memoize = require('./util/memoize');
var tabs = require('./tabs');
var SearchClient = require('../search-client');
var events = require('../events');
var memoize = require('../util/memoize');
var tabs = require('../tabs');
var uiConstants = require('../ui-constants');
function firstKey(object) {
for (var k in object) {
......@@ -31,10 +32,12 @@ function groupIDFromSelection(selection, results) {
}
// @ngInject
module.exports = function WidgetController(
function SidebarContentController(
$scope, analytics, annotationUI, annotationMapper, drafts, features, frameSync,
groups, rootThread, settings, streamer, streamFilter, store
) {
var self = this;
function thread() {
return rootThread.thread(annotationUI.getState());
}
......@@ -42,13 +45,13 @@ module.exports = function WidgetController(
var unsubscribeAnnotationUI = annotationUI.subscribe(function () {
var state = annotationUI.getState();
$scope.rootThread = thread();
$scope.selectedTab = state.selectedTab;
self.rootThread = thread();
self.selectedTab = state.selectedTab;
var separateOrphans = tabs.shouldSeparateOrphans(state);
var counts = tabs.counts(state.annotations, separateOrphans);
Object.assign($scope, {
Object.assign(self, {
totalNotes: counts.notes,
totalAnnotations: counts.annotations,
totalOrphans: counts.orphans,
......@@ -210,7 +213,7 @@ module.exports = function WidgetController(
});
// If the user is logged in, we connect nevertheless
if ($scope.auth.status === 'logged-in') {
if (this.auth.status === 'logged-in') {
streamer.connect();
}
......@@ -257,21 +260,21 @@ module.exports = function WidgetController(
});
}, loadAnnotations, true);
$scope.setCollapsed = function (id, collapsed) {
this.setCollapsed = function (id, collapsed) {
annotationUI.setCollapsed(id, collapsed);
};
$scope.forceVisible = function (thread) {
this.forceVisible = function (thread) {
annotationUI.setForceVisible(thread.id, true);
if (thread.parent) {
annotationUI.setCollapsed(thread.parent.id, false);
}
};
$scope.focus = focusAnnotation;
$scope.scrollTo = scrollToAnnotation;
this.focus = focusAnnotation;
this.scrollTo = scrollToAnnotation;
$scope.selectedAnnotationCount = function () {
this.selectedAnnotationCount = function () {
var selection = annotationUI.getState().selectedAnnotationMap;
if (!selection) {
return 0;
......@@ -279,16 +282,16 @@ module.exports = function WidgetController(
return Object.keys(selection).length;
};
$scope.selectedAnnotationUnavailable = function () {
this.selectedAnnotationUnavailable = function () {
var selectedID = firstKey(annotationUI.getState().selectedAnnotationMap);
return !isLoading() &&
!!selectedID &&
!annotationUI.annotationExists(selectedID);
};
$scope.shouldShowLoggedOutMessage = function () {
this.shouldShowLoggedOutMessage = function () {
// If user is not logged out, don't show CTA.
if ($scope.auth.status !== 'logged-out') {
if (self.auth.status !== 'logged-out') {
return false;
}
......@@ -307,7 +310,7 @@ module.exports = function WidgetController(
annotationUI.annotationExists(selectedID);
};
$scope.isLoading = isLoading;
this.isLoading = isLoading;
var visibleCount = memoize(function (thread) {
return thread.children.reduce(function (count, child) {
......@@ -315,11 +318,31 @@ module.exports = function WidgetController(
}, thread.visible ? 1 : 0);
});
$scope.visibleCount = function () {
this.visibleCount = function () {
return visibleCount(thread());
};
$scope.topLevelThreadCount = function () {
this.topLevelThreadCount = function () {
return thread().totalChildren;
};
this.clearSelection = function () {
var selectedTab = annotationUI.getState().selectedTab;
if (!annotationUI.getState().selectedTab || annotationUI.getState().selectedTab === uiConstants.TAB_ORPHANS) {
selectedTab = uiConstants.TAB_ANNOTATIONS;
}
annotationUI.clearSelectedAnnotations();
annotationUI.selectTab(selectedTab);
};
}
module.exports = {
controller: SidebarContentController,
controllerAs: 'vm',
bindings: {
auth: '<',
search: '<',
},
template: require('../templates/sidebar_content.html'),
};
......@@ -5,8 +5,8 @@ var inherits = require('inherits');
var proxyquire = require('proxyquire');
var EventEmitter = require('tiny-emitter');
var events = require('../events');
var noCallThru = require('../../shared/test/util').noCallThru;
var events = require('../../events');
var noCallThru = require('../../../shared/test/util').noCallThru;
var searchClients;
function FakeSearchClient(searchFn, opts) {
......@@ -36,10 +36,11 @@ function FakeRootThread() {
}
inherits(FakeRootThread, EventEmitter);
describe('WidgetController', function () {
describe('SidebarContentController', function () {
var $rootScope;
var $scope;
var annotationUI;
var ctrl;
var fakeAnalytics;
var fakeAnnotationMapper;
var fakeDrafts;
......@@ -55,11 +56,11 @@ describe('WidgetController', function () {
before(function () {
angular.module('h', [])
.service('annotationUI', require('../annotation-ui'))
.controller('WidgetController', proxyquire('../widget-controller',
.service('annotationUI', require('../../annotation-ui'))
.component('sidebarContent', proxyquire('../sidebar-content',
noCallThru({
angular: angular,
'./search-client': FakeSearchClient,
'../search-client': FakeSearchClient,
})
));
});
......@@ -135,14 +136,15 @@ describe('WidgetController', function () {
annotationUI.frames.returns(frames);
}
beforeEach(angular.mock.inject(function ($controller, _annotationUI_, _$rootScope_) {
beforeEach(angular.mock.inject(function ($componentController, _annotationUI_, _$rootScope_) {
$rootScope = _$rootScope_;
$scope = $rootScope.$new();
$scope.auth = {'status': 'unknown'};
annotationUI = _annotationUI_;
annotationUI.frames = sinon.stub().returns([]);
annotationUI.updateFrameAnnotationFetchStatus = sinon.stub();
$controller('WidgetController', {$scope: $scope});
ctrl = $componentController('sidebarContent', { $scope: $scope }, {
auth: { status: 'unknown' },
});
}));
afterEach(function () {
......@@ -223,7 +225,7 @@ describe('WidgetController', function () {
});
it('selectedAnnotationCount is > 0', function () {
assert.equal($scope.selectedAnnotationCount(), 1);
assert.equal(ctrl.selectedAnnotationCount(), 1);
});
it('switches to the selected annotation\'s group', function () {
......@@ -253,7 +255,7 @@ describe('WidgetController', function () {
});
it('selectedAnnotationCount is 0', function () {
assert.equal($scope.selectedAnnotationCount(), 0);
assert.equal(ctrl.selectedAnnotationCount(), 0);
});
it('fetches annotations for the current group', function () {
......@@ -338,20 +340,20 @@ describe('WidgetController', function () {
it('displays a message if the selection is unavailable', function () {
annotationUI.selectAnnotations(['missing']);
$scope.$digest();
assert.isTrue($scope.selectedAnnotationUnavailable());
assert.isTrue(ctrl.selectedAnnotationUnavailable());
});
it('does not show a message if the selection is available', function () {
annotationUI.addAnnotations([{id: '123'}]);
annotationUI.selectAnnotations(['123']);
$scope.$digest();
assert.isFalse($scope.selectedAnnotationUnavailable());
assert.isFalse(ctrl.selectedAnnotationUnavailable());
});
it('does not a show a message if there is no selection', function () {
annotationUI.selectAnnotations([]);
$scope.$digest();
assert.isFalse($scope.selectedAnnotationUnavailable());
assert.isFalse(ctrl.selectedAnnotationUnavailable());
});
it('doesn\'t show a message if the document isn\'t loaded yet', function () {
......@@ -363,56 +365,56 @@ describe('WidgetController', function () {
setFrames([]);
$scope.$digest();
assert.isFalse($scope.selectedAnnotationUnavailable());
assert.isFalse(ctrl.selectedAnnotationUnavailable());
});
it('shows logged out message if selection is available', function () {
$scope.auth = {
ctrl.auth = {
status: 'logged-out',
};
annotationUI.addAnnotations([{id: '123'}]);
annotationUI.selectAnnotations(['123']);
$scope.$digest();
assert.isTrue($scope.shouldShowLoggedOutMessage());
assert.isTrue(ctrl.shouldShowLoggedOutMessage());
});
it('does not show loggedout message if selection is unavailable', function () {
$scope.auth = {
ctrl.auth = {
status: 'logged-out',
};
annotationUI.selectAnnotations(['missing']);
$scope.$digest();
assert.isFalse($scope.shouldShowLoggedOutMessage());
assert.isFalse(ctrl.shouldShowLoggedOutMessage());
});
it('does not show loggedout message if there is no selection', function () {
$scope.auth = {
ctrl.auth = {
status: 'logged-out',
};
annotationUI.selectAnnotations([]);
$scope.$digest();
assert.isFalse($scope.shouldShowLoggedOutMessage());
assert.isFalse(ctrl.shouldShowLoggedOutMessage());
});
it('does not show loggedout message if user is not logged out', function () {
$scope.auth = {
ctrl.auth = {
status: 'logged-in',
};
annotationUI.addAnnotations([{id: '123'}]);
annotationUI.selectAnnotations(['123']);
$scope.$digest();
assert.isFalse($scope.shouldShowLoggedOutMessage());
assert.isFalse(ctrl.shouldShowLoggedOutMessage());
});
it('does not show loggedout message if not a direct link', function () {
$scope.auth = {
ctrl.auth = {
status: 'logged-out',
};
delete fakeSettings.annotations;
annotationUI.addAnnotations([{id: '123'}]);
annotationUI.selectAnnotations(['123']);
$scope.$digest();
assert.isFalse($scope.shouldShowLoggedOutMessage());
assert.isFalse(ctrl.shouldShowLoggedOutMessage());
});
});
......@@ -440,7 +442,7 @@ describe('WidgetController', function () {
describe('#forceVisible', function () {
it('shows the thread', function () {
var thread = {id: '1'};
$scope.forceVisible(thread);
ctrl.forceVisible(thread);
assert.deepEqual(annotationUI.getState().forceVisible, {1: true});
});
......@@ -450,7 +452,7 @@ describe('WidgetController', function () {
parent: {id: '3'},
};
assert.equal(annotationUI.getState().expanded[thread.parent.id], undefined);
$scope.forceVisible(thread);
ctrl.forceVisible(thread);
assert.equal(annotationUI.getState().expanded[thread.parent.id], true);
});
});
......@@ -469,7 +471,7 @@ describe('WidgetController', function () {
}],
});
$scope.$digest();
assert.equal($scope.visibleCount(), 2);
assert.equal(ctrl.visibleCount(), 2);
});
});
});
<selection-tabs
ng-if="!search.query() && selectedAnnotationCount() === 0"
is-waiting-to-anchor-annotations="waitingToAnchorAnnotations"
is-loading="isLoading"
selected-tab="selectedTab"
total-annotations="totalAnnotations"
total-notes="totalNotes"
total-orphans="totalOrphans">
ng-if="!vm.search.query() && vm.selectedAnnotationCount() === 0"
is-waiting-to-anchor-annotations="vm.waitingToAnchorAnnotations"
is-loading="vm.isLoading"
selected-tab="vm.selectedTab"
total-annotations="vm.totalAnnotations"
total-notes="vm.totalNotes"
total-orphans="vm.totalOrphans">
</selection-tabs>
<search-status-bar
ng-show="!isLoading()"
filter-active="!!search.query()"
filter-match-count="visibleCount()"
on-clear-selection="clearSelection()"
search-query="search ? search.query : ''"
selection-count="selectedAnnotationCount()"
total-count="topLevelThreadCount()"
selected-tab="selectedTab"
total-annotations="totalAnnotations"
total-notes="totalNotes">
ng-show="!vm.isLoading()"
filter-active="!!vm.search.query()"
filter-match-count="vm.visibleCount()"
on-clear-selection="vm.clearSelection()"
search-query="vm.search ? vm.search.query : ''"
selection-count="vm.selectedAnnotationCount()"
total-count="vm.topLevelThreadCount()"
selected-tab="vm.selectedTab"
total-annotations="vm.totalAnnotations"
total-notes="vm.totalNotes">
</search-status-bar>
<div class="annotation-unavailable-message"
ng-if="selectedAnnotationUnavailable()">
ng-if="vm.selectedAnnotationUnavailable()">
<div class="annotation-unavailable-message__icon"></div>
<p class="annotation-unavailable-message__label">
<span ng-if="auth.status === 'logged-out'">
<span ng-if="vm.auth.status === 'logged-out'">
This annotation is not available.
<br>
You may need to
<a class="loggedout-message__link" href="" ng-click="login()">log in</a>
<a class="loggedout-message__link" href="" ng-click="vm.login()">log in</a>
to see it.
</span>
<span ng-if="auth.status === 'logged-in'">
<span ng-if="vm.auth.status === 'logged-in'">
You do not have permission to view this annotation.
</span>
</p>
</div>
<thread-list
on-change-collapsed="setCollapsed(id, collapsed)"
on-clear-selection="clearSelection()"
on-focus="focus(annotation)"
on-force-visible="forceVisible(thread)"
on-select="scrollTo(annotation)"
on-change-collapsed="vm.setCollapsed(id, collapsed)"
on-clear-selection="vm.clearSelection()"
on-focus="vm.focus(annotation)"
on-force-visible="vm.forceVisible(thread)"
on-select="vm.scrollTo(annotation)"
show-document-info="false"
thread="rootThread">
thread="vm.rootThread">
</thread-list>
<loggedout-message ng-if="shouldShowLoggedOutMessage()" on-login="login()">
<loggedout-message ng-if="vm.shouldShowLoggedOutMessage()" on-login="vm.login()">
</loggedout-message>
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