blob: 4e9133a194829c8587dca0d094ed1eb16e9049ef [file] [log] [blame]
Simon Hunt72e44bf2015-07-21 21:34:20 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2015-present Open Networking Foundation
Simon Hunt72e44bf2015-07-21 21:34:20 -07003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 */
17
18/*
19 ONOS GUI -- Topology Overlay Module.
20
21 Provides overlay capabilities, allowing ONOS apps to provide additional
22 custom data/behavior for the topology view.
23
24 */
25
26(function () {
27 'use strict';
28
29 // constants
30 var tos = 'TopoOverlayService: ';
31
32 // injected refs
Andrea Campanella2dc91dc2015-12-07 12:17:02 -080033 var $log, $timeout, fs, gs, wss, ns, tss, tps, api;
Simon Hunt72e44bf2015-07-21 21:34:20 -070034
35 // internal state
Simon Hunte05cae42015-07-23 17:35:24 -070036 var overlays = {},
Simon Hunt4a6b54b2015-10-27 22:08:25 -070037 current = null,
38 reset = true;
Simon Hunt72e44bf2015-07-21 21:34:20 -070039
Simon Hunt1603c692017-08-10 19:53:35 -070040 // function to be replaced by the localization bundle function
41 var topoLion = function (x) {
42 return '#tov#' + x + '#';
43 };
44
Simon Hunt72e44bf2015-07-21 21:34:20 -070045 function error(fn, msg) {
46 $log.error(tos + fn + '(): ' + msg);
47 }
48
49 function warn(fn, msg) {
50 $log.warn(tos + fn + '(): ' + msg);
51 }
52
Simon Huntfb940112015-07-29 18:36:35 -070053 function mkGlyphId(oid, gid) {
54 return (gid[0] === '*') ? oid + '-' + gid.slice(1) : gid;
55 }
Simon Hunt72e44bf2015-07-21 21:34:20 -070056
Simon Huntfb940112015-07-29 18:36:35 -070057 function handleGlyphs(o) {
58 var gdata = fs.isO(o.glyphs),
59 oid = o.overlayId,
60 gid = o.glyphId || 'unknown',
61 data = {},
62 note = [];
63
64 o._glyphId = mkGlyphId(oid, gid);
65
66 o.mkGid = function (g) {
67 return mkGlyphId(oid, g);
68 };
69 o.mkId = function (s) {
70 return oid + '-' + s;
71 };
72
73 // process glyphs if defined
74 if (gdata) {
75 angular.forEach(gdata, function (value, key) {
76 var fullkey = oid + '-' + key;
77 data['_' + fullkey] = value.vb;
78 data[fullkey] = value.d;
79 note.push('*' + key);
80 });
81 gs.registerGlyphs(data);
82 $log.debug('registered overlay glyphs:', oid, note);
Simon Hunt72e44bf2015-07-21 21:34:20 -070083 }
84 }
85
86 function register(overlay) {
87 var r = 'register',
88 over = fs.isO(overlay),
Simon Hunt8d22c4b2015-08-06 16:24:43 -070089 kb = over ? fs.isO(overlay.keyBindings) : null,
Simon Hunt72e44bf2015-07-21 21:34:20 -070090 id = over ? over.overlayId : '';
91
92 if (!id) {
93 return error(r, 'not a recognized overlay');
94 }
95 if (overlays[id]) {
96 return warn(r, 'already registered: "' + id + '"');
97 }
98 overlays[id] = overlay;
Simon Huntfb940112015-07-29 18:36:35 -070099 handleGlyphs(overlay);
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700100
101 if (kb) {
102 if (!fs.isA(kb._keyOrder)) {
103 warn(r, 'no _keyOrder array defined on keyBindings');
104 } else {
105 kb._keyOrder.forEach(function (k) {
106 if (k !== '-' && !kb[k]) {
107 warn(r, 'no "' + k + '" property defined on keyBindings');
108 }
109 });
110 }
111 }
112
Simon Hunt72e44bf2015-07-21 21:34:20 -0700113 $log.debug(tos + 'registered overlay: ' + id, overlay);
114 }
115
Simon Huntfc5c5842017-02-01 23:32:18 -0800116 // Returns the list of overlay identifiers.
117 function list() {
118 return d3.map(overlays).keys();
119 }
Simon Huntc5489c92017-01-30 17:50:48 -0800120
Simon Hunt441c9ae2017-02-03 18:22:31 -0800121 // Returns an array containing overlays that implement the showIntent and
122 // acceptIntent callbacks, and that accept the given intent type
123 function overlaysAcceptingIntents(intentType) {
Simon Huntfc5c5842017-02-01 23:32:18 -0800124 var result = [];
125 angular.forEach(overlays, function (ov) {
Simon Hunt441c9ae2017-02-03 18:22:31 -0800126 var ovid = ov.overlayId,
127 hooks = fs.isO(ov.hooks) || {},
128 aicb = fs.isF(hooks.acceptIntent),
129 sicb = fs.isF(hooks.showIntent);
Simon Huntc5489c92017-01-30 17:50:48 -0800130
Simon Hunt441c9ae2017-02-03 18:22:31 -0800131 if (sicb && aicb && aicb(intentType)) {
Simon Huntfc5c5842017-02-01 23:32:18 -0800132 result.push({
Simon Hunt441c9ae2017-02-03 18:22:31 -0800133 id: ovid,
Steven Burrows1c2a9682017-07-14 16:52:46 +0100134 tt: ov.tooltip || '%' + ovid + '%',
Simon Huntfc5c5842017-02-01 23:32:18 -0800135 });
136 }
Simon Huntc5489c92017-01-30 17:50:48 -0800137 });
Simon Huntfc5c5842017-02-01 23:32:18 -0800138 return result;
Simon Hunt72e44bf2015-07-21 21:34:20 -0700139 }
140
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700141 // add a radio button for each registered overlay
Simon Hunta5b53af2015-10-12 15:56:40 -0700142 // return an overlay id to index map
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700143 function augmentRbset(rset, switchFn) {
Simon Hunta5b53af2015-10-12 15:56:40 -0700144 var map = {},
145 idx = 1;
146
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700147 angular.forEach(overlays, function (ov) {
148 rset.push({
149 gid: ov._glyphId,
Simon Huntcaed0412017-08-12 13:49:17 -0700150 tooltip: (ov.tooltip || ''),
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700151 cb: function () {
152 tbSelection(ov.overlayId, switchFn);
Steven Burrows1c2a9682017-07-14 16:52:46 +0100153 },
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700154 });
Simon Hunta5b53af2015-10-12 15:56:40 -0700155 map[ov.overlayId] = idx++;
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700156 });
Simon Hunta5b53af2015-10-12 15:56:40 -0700157 return map;
Simon Hunt72e44bf2015-07-21 21:34:20 -0700158 }
159
Simon Hunte05cae42015-07-23 17:35:24 -0700160 // an overlay was selected via toolbar radio button press from user
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700161 function tbSelection(id, switchFn) {
Simon Hunte05cae42015-07-23 17:35:24 -0700162 var same = current && current.overlayId === id,
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700163 payload = {},
164 actions;
Simon Hunte05cae42015-07-23 17:35:24 -0700165
166 function doop(op) {
167 var oid = current.overlayId;
168 $log.debug('Overlay:', op, oid);
169 current[op]();
170 payload[op] = oid;
171 }
172
Simon Hunt4a6b54b2015-10-27 22:08:25 -0700173 if (reset || !same) {
174 reset = false;
Simon Hunte05cae42015-07-23 17:35:24 -0700175 current && doop('deactivate');
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700176 current = overlays[id];
Simon Hunte05cae42015-07-23 17:35:24 -0700177 current && doop('activate');
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700178 actions = current && fs.isO(current.keyBindings);
179 switchFn(id, actions);
180
Simon Hunte05cae42015-07-23 17:35:24 -0700181 wss.sendEvent('topoSelectOverlay', payload);
Simon Hunt0af1ec32015-07-24 12:17:55 -0700182
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700183 // Ensure summary and details panels are updated immediately..
Simon Hunt0af1ec32015-07-24 12:17:55 -0700184 wss.sendEvent('requestSummary');
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700185 tss.updateDetail();
Simon Hunte05cae42015-07-23 17:35:24 -0700186 }
187 }
188
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700189 var coreButtons = {
190 showDeviceView: {
191 gid: 'switch',
Simon Hunt1603c692017-08-10 19:53:35 -0700192 tt: function () { return topoLion('btn_show_view_device'); },
Steven Burrows1c2a9682017-07-14 16:52:46 +0100193 path: 'device',
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700194 },
195 showFlowView: {
196 gid: 'flowTable',
Simon Hunt1603c692017-08-10 19:53:35 -0700197 tt: function () { return topoLion('btn_show_view_flow'); },
Steven Burrows1c2a9682017-07-14 16:52:46 +0100198 path: 'flow',
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700199 },
200 showPortView: {
201 gid: 'portTable',
Simon Hunt1603c692017-08-10 19:53:35 -0700202 tt: function () { return topoLion('btn_show_view_port'); },
Steven Burrows1c2a9682017-07-14 16:52:46 +0100203 path: 'port',
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700204 },
205 showGroupView: {
206 gid: 'groupTable',
Simon Hunt1603c692017-08-10 19:53:35 -0700207 tt: function () { return topoLion('btn_show_view_group'); },
Steven Burrows1c2a9682017-07-14 16:52:46 +0100208 path: 'group',
Jian Li79f67322016-01-06 18:22:37 -0800209 },
210 showMeterView: {
211 gid: 'meterTable',
Simon Hunt1603c692017-08-10 19:53:35 -0700212 tt: function () { return topoLion('btn_show_view_meter'); },
Steven Burrows1c2a9682017-07-14 16:52:46 +0100213 path: 'meter',
214 },
Simon Hunt3a0598f2015-08-04 19:59:04 -0700215 };
Simon Huntfb940112015-07-29 18:36:35 -0700216
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700217 // retrieves a button definition from the current overlay and generates
218 // a button descriptor to be added to the panel, with the data baked in
219 function _getButtonDef(id, data) {
220 var btns = current && current.buttons,
221 b = btns && btns[id],
222 cb = fs.isF(b.cb),
223 f = cb ? function () { cb(data); } : function () {};
224
225 return b ? {
226 id: current.mkId(id),
227 gid: current.mkGid(b.gid),
228 tt: b.tt,
Steven Burrows1c2a9682017-07-14 16:52:46 +0100229 cb: f,
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700230 } : null;
231 }
232
Simon Hunt3a0598f2015-08-04 19:59:04 -0700233 // install core buttons, and include any additional from the current overlay
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700234 function installButtons(buttons, data, devId) {
235 buttons.forEach(function (id) {
236 var btn = coreButtons[id],
237 gid = btn && btn.gid,
238 tt = btn && btn.tt,
239 path = btn && btn.path;
Simon Hunt3a0598f2015-08-04 19:59:04 -0700240
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700241 if (btn) {
242 tps.addAction({
243 id: 'core-' + id,
244 gid: gid,
245 tt: tt,
Steven Burrows1c2a9682017-07-14 16:52:46 +0100246 cb: function () { ns.navTo(path, { devId: devId }); },
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700247 });
248 } else if (btn = _getButtonDef(id, data)) {
249 tps.addAction(btn);
Simon Hunt3a0598f2015-08-04 19:59:04 -0700250 }
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700251 });
252 }
Simon Hunt3a0598f2015-08-04 19:59:04 -0700253
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700254 function addDetailButton(id) {
255 var b = _getButtonDef(id);
256 if (b) {
257 tps.addAction({
258 id: current.mkId(id),
259 gid: current.mkGid(b.gid),
260 cb: b.cb,
Steven Burrows1c2a9682017-07-14 16:52:46 +0100261 tt: b.tt,
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700262 });
263 }
264 }
Simon Hunt3a0598f2015-08-04 19:59:04 -0700265
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700266
267 // === -----------------------------------------------------
268 // Hooks for overlays
269
270 function _hook(x) {
271 var h = current && current.hooks;
272 return h && fs.isF(h[x]);
273 }
274
275 function escapeHook() {
276 var eh = _hook('escape');
277 return eh ? eh() : false;
278 }
279
280 function emptySelectHook() {
281 var cb = _hook('empty');
282 cb && cb();
283 }
284
285 function singleSelectHook(data) {
286 var cb = _hook('single');
287 cb && cb(data);
288 }
289
290 function multiSelectHook(selectOrder) {
291 var cb = _hook('multi');
292 cb && cb(selectOrder);
293 }
294
Simon Hunt584e92d2015-08-24 11:27:22 -0700295 function mouseOverHook(what) {
296 var cb = _hook('mouseover');
297 cb && cb(what);
298 }
299
300 function mouseOutHook() {
301 var cb = _hook('mouseout');
302 cb && cb();
303 }
304
Simon Hunt8419efd2017-01-12 12:36:28 -0800305 // Request from Intent View to visualize an intent on the topo view
306 function showIntentHook(intentData) {
Simon Hunt441c9ae2017-02-03 18:22:31 -0800307 var cb = _hook('showIntent');
Simon Hunt8419efd2017-01-12 12:36:28 -0800308 return cb && cb(intentData);
309 }
310
Simon Huntcaed0412017-08-12 13:49:17 -0700311 // 'core.view.Topo' lion bundle will be injected here.
312 // NOTE: if an overlay wants additional bundles, it should use the
313 // LionService to request them at this time.
314 function injectLion(topoBundle) {
315 var cb = _hook('injectLion');
316 return cb && cb(topoBundle);
317 }
318
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700319 // === -----------------------------------------------------
320 // Event (from server) Handlers
321
322 function setApi(_api_, _tss_) {
323 api = _api_;
324 tss = _tss_;
325 }
326
Steven Burrows1c2a9682017-07-14 16:52:46 +0100327 // process highlight event with optional delay
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700328 function showHighlights(data) {
Andrea Campanella2dc91dc2015-12-07 12:17:02 -0800329 function doHighlight() {
330 _showHighlights(data);
331 }
332
333 if (data.delay) {
334 $timeout(doHighlight, data.delay);
335 } else {
336 doHighlight();
337 }
338 }
339
340 function _showHighlights(data) {
Simon Hunt743a8492015-08-25 16:18:19 -0700341 var less;
342
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700343 /*
344 API to topoForce
345 clearLinkTrafficStyle()
346 removeLinkLabels()
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700347 findLinkById( id )
Simon Hunt94f7dae2015-08-26 17:40:59 -0700348 findNodeById( id )
Simon Hunt743a8492015-08-25 16:18:19 -0700349 updateLinks()
350 updateNodes()
351 supLayers( bool, [less] )
352 unsupNode( id, [less] )
Simon Hunt94f7dae2015-08-26 17:40:59 -0700353 unsupLink( key, [less] )
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700354 */
355
Simon Hunte9343f32015-10-21 18:07:46 -0700356 api.clearNodeDeco();
357 api.removeNodeBadges();
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700358 api.clearLinkTrafficStyle();
359 api.removeLinkLabels();
360
Simon Hunt743a8492015-08-25 16:18:19 -0700361 // handle element suppression
362 if (data.subdue) {
363 less = data.subdue === 'min';
364 api.supLayers(true, less);
365
366 } else {
367 api.supLayers(false);
368 api.supLayers(false, true);
369 }
370
Simon Hunt94f7dae2015-08-26 17:40:59 -0700371 data.hosts.forEach(function (host) {
Andrea Campanella52125412015-12-03 14:50:40 -0800372 var hdata = api.findNodeById(host.id),
373 badgeData = host.badge || null;
374
Simon Hunta1f1c022016-03-03 15:54:57 -0800375 if (hdata && hdata.el && !hdata.el.empty()) {
Andrea Campanella52125412015-12-03 14:50:40 -0800376 hdata.badge = badgeData;
Simon Hunt5b3ff902015-08-27 09:46:27 -0700377 if (!host.subdue) {
378 api.unsupNode(hdata.id, less);
379 }
Simon Hunt94f7dae2015-08-26 17:40:59 -0700380 // TODO: further highlighting?
Simon Huntb3442482016-03-03 17:30:07 -0800381 } else {
382 $log.warn('HILITE: no host element:', host.id);
Simon Hunt94f7dae2015-08-26 17:40:59 -0700383 }
384 });
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700385
Simon Hunt94f7dae2015-08-26 17:40:59 -0700386 data.devices.forEach(function (device) {
Simon Hunte9343f32015-10-21 18:07:46 -0700387 var ddata = api.findNodeById(device.id),
388 badgeData = device.badge || null;
389
Simon Hunta1f1c022016-03-03 15:54:57 -0800390 if (ddata && ddata.el && !ddata.el.empty()) {
Simon Hunte9343f32015-10-21 18:07:46 -0700391 ddata.badge = badgeData;
Simon Hunt5b3ff902015-08-27 09:46:27 -0700392 if (!device.subdue) {
393 api.unsupNode(ddata.id, less);
394 }
Simon Hunt94f7dae2015-08-26 17:40:59 -0700395 // TODO: further highlighting?
Simon Huntb3442482016-03-03 17:30:07 -0800396 } else {
397 $log.warn('HILITE: no device element:', device.id);
Simon Hunt94f7dae2015-08-26 17:40:59 -0700398 }
399 });
400
401 data.links.forEach(function (link) {
Simon Hunt69855172017-03-31 09:48:25 -0700402 var ldata = api.findLinkById(link.id);
Simon Hunta1f1c022016-03-03 15:54:57 -0800403
404 if (ldata && ldata.el && !ldata.el.empty()) {
Simon Hunt5b3ff902015-08-27 09:46:27 -0700405 if (!link.subdue) {
406 api.unsupLink(ldata.key, less);
407 }
Simon Hunt94f7dae2015-08-26 17:40:59 -0700408 ldata.el.classed(link.css, true);
Simon Hunt69855172017-03-31 09:48:25 -0700409 ldata.label = link.label;
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700410
Simon Huntb3442482016-03-03 17:30:07 -0800411 } else {
412 $log.warn('HILITE: no link element:', link.id);
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700413 }
Simon Hunt3a0598f2015-08-04 19:59:04 -0700414 });
Simon Huntfb940112015-07-29 18:36:35 -0700415
Simon Hunt743a8492015-08-25 16:18:19 -0700416 api.updateNodes();
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700417 api.updateLinks();
Simon Huntfb940112015-07-29 18:36:35 -0700418 }
419
Simon Hunt1603c692017-08-10 19:53:35 -0700420 // invoked after the localization bundle has been received from the server
421 function setLionBundle(bundle) {
422 topoLion = bundle;
Simon Huntcaed0412017-08-12 13:49:17 -0700423 // also inject the topo lion bundle to all overlays that request it
424 angular.forEach(overlays, function (ov) {
425 var hooks = fs.isO(ov.hooks) || {},
426 inj = fs.isF(hooks.injectLion);
427 inj && inj(bundle);
428 });
Simon Hunt1603c692017-08-10 19:53:35 -0700429 }
430
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700431 // ========================================================================
432
Simon Hunt72e44bf2015-07-21 21:34:20 -0700433 angular.module('ovTopo')
434 .factory('TopoOverlayService',
Andrea Campanella2dc91dc2015-12-07 12:17:02 -0800435 ['$log', '$timeout', 'FnService', 'GlyphService', 'WebSocketService',
436 'NavService', 'TopoPanelService',
Simon Hunt72e44bf2015-07-21 21:34:20 -0700437
Andrea Campanella2dc91dc2015-12-07 12:17:02 -0800438 function (_$log_, _$timeout_, _fs_, _gs_, _wss_, _ns_, _tps_) {
Simon Hunt72e44bf2015-07-21 21:34:20 -0700439 $log = _$log_;
Andrea Campanella2dc91dc2015-12-07 12:17:02 -0800440 $timeout = _$timeout_;
Simon Hunt72e44bf2015-07-21 21:34:20 -0700441 fs = _fs_;
442 gs = _gs_;
Simon Hunte05cae42015-07-23 17:35:24 -0700443 wss = _wss_;
Simon Hunt3a0598f2015-08-04 19:59:04 -0700444 ns = _ns_;
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700445 tps = _tps_;
Simon Hunt72e44bf2015-07-21 21:34:20 -0700446
447 return {
448 register: register,
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700449 setApi: setApi,
Simon Hunt72e44bf2015-07-21 21:34:20 -0700450 list: list,
Simon Hunt441c9ae2017-02-03 18:22:31 -0800451 overlaysAcceptingIntents: overlaysAcceptingIntents,
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700452 augmentRbset: augmentRbset,
453 mkGlyphId: mkGlyphId,
Simon Huntfb940112015-07-29 18:36:35 -0700454 tbSelection: tbSelection,
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700455 installButtons: installButtons,
456 addDetailButton: addDetailButton,
Simon Hunt4a6b54b2015-10-27 22:08:25 -0700457 resetOnToolbarDestroy: function () { reset = true; },
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700458 hooks: {
459 escape: escapeHook,
460 emptySelect: emptySelectHook,
461 singleSelect: singleSelectHook,
Simon Hunt584e92d2015-08-24 11:27:22 -0700462 multiSelect: multiSelectHook,
463 mouseOver: mouseOverHook,
Simon Hunt5c1a9382016-06-01 19:35:35 -0700464 mouseOut: mouseOutHook,
Steven Burrows1c2a9682017-07-14 16:52:46 +0100465 showIntent: showIntentHook,
Simon Hunt8d22c4b2015-08-06 16:24:43 -0700466 },
467
Steven Burrows1c2a9682017-07-14 16:52:46 +0100468 showHighlights: showHighlights,
Simon Hunt1603c692017-08-10 19:53:35 -0700469 setLionBundle: setLionBundle,
Steven Burrows1c2a9682017-07-14 16:52:46 +0100470 };
Simon Hunt72e44bf2015-07-21 21:34:20 -0700471 }]);
472
Steven Burrows1c2a9682017-07-14 16:52:46 +0100473}());