Compare commits

...

17 Commits

Author SHA1 Message Date
victor-meng
35dcc17768 Fix styling issues with tabs manager (#84) 2020-07-10 15:08:38 -07:00
Tanuj Mittal
dcd6e0361c Add hideInputs query param in notebookViewer and other minor updates (#82)
* Add hideInputs query param in notebookViewer

* Fix test and other minor changes

* Make GalleryHeaderComponent more functional
2020-07-10 14:23:53 -07:00
Steve Faulkner
9bdf7e0cce Further decrease runner frequency 2020-07-09 20:39:35 -05:00
Steve Faulkner
a033bcb360 Decrease runner frequency 2020-07-09 16:21:34 -05:00
victor-meng
4068a9fbaa Create tabs manager and refactor tab related logic (#66)
Co-authored-by: Steve Faulkner <southpolesteve@gmail.com>
2020-07-09 13:53:37 -07:00
Steve Faulkner
326bd4f494 Initial pass at removing old spark code (#80)
Co-authored-by: Steve Faulkner <stfaul@microsoft.com>
2020-07-09 10:49:27 -05:00
Tanuj Mittal
5a1dd42395 Add top bar in standalone gallery and notebook viewer (#76)
* Add top bar in standalone gallery

* Address review comments
2020-07-08 12:40:47 -07:00
vchske
955d08e4d0 Added a different message if the account is free tier (#69)
* Added a different upsell message if the account is free tier

* Fixing prettier and unit tests
2020-07-08 10:02:47 -07:00
Steve Faulkner
512f56c28a Update to Boostrap 3.4.1 (#78) 2020-07-08 10:23:30 -05:00
Steve Faulkner
604f87f318 Upgrade to TypeScript 3.9 (#75) 2020-07-08 10:12:49 -05:00
Steve Faulkner
00bb39deb4 Upgrade to jQuery 3.5.1 (#77) 2020-07-08 09:48:15 -05:00
Steve Faulkner
42bee92e7a Remove old static JS and image files (#79)
* Remove old static JS and image files

* Add back images that may be used

Co-authored-by: Steve Faulkner <stfaul@microsoft.com>
2020-07-08 09:47:57 -05:00
Vignesh Rangaishenvi
a08890aadf Ability to skip resource validation with RP calls (#73)
* Support ability to skip resource validation

* Use request options
2020-07-07 14:31:19 -07:00
Tanuj Mittal
84ea3796ec Remove enableGallery feature flag (#68)
* Remove enableGallery feature flag

* Fix bugs

* Add tests to increase coverage

* Move favorites functionality behind feature.enableGalleryPublish flag

* Show code cells in NotebookViewer

* Use cosmos db logo as persona image for sample notebook gallery cards

* Update gallery card snapshot to fix test
2020-07-06 12:10:26 -07:00
Laurent Nguyen
27024ef75c Initial implementation of a generic UI component (#61)
* Add generic component

* Add validation. Rename to widgetRenderer

* Remove test code from splash screen

* Clean up infobox

* Fix styling/layout

* Move test code into unit test

* Replace <input> and <labe> by <TextField> and <Text> respectively. Fix style.

* Replace InfoBoxComponent with UI fabric MessageBar. Fix styling for TextField

* Use MessageBar for error message

* Rename WdigetRendererComponent to SmartUiComponent
2020-07-06 17:16:43 +02:00
Laurent Nguyen
3f34936acd Add more files to strict compile. Update CONTRIBUTING.md (#63)
* Add more files to strict compile. Update CONTRIBUTING.md to recommend FluentUI use

* Remove eslint-disable and use non-null assertion
2020-07-06 17:16:24 +02:00
Steve Faulkner
c51a55013c Upload screenshot for runner failures (#72) 2020-07-02 09:58:36 -05:00
133 changed files with 5682 additions and 38541 deletions

View File

@@ -147,7 +147,6 @@ src/Explorer/Panes/ExecuteSprocParamsPane.ts
src/Explorer/Panes/GraphStylingPane.ts
src/Explorer/Panes/LibraryManagePane.ts
src/Explorer/Panes/LoadQueryPane.ts
src/Explorer/Panes/ManageSparkClusterPane.ts
src/Explorer/Panes/NewVertexPane.ts
src/Explorer/Panes/PaneComponents.ts
src/Explorer/Panes/RenewAdHocAccessPane.ts
@@ -155,7 +154,6 @@ src/Explorer/Panes/SaveQueryPane.ts
src/Explorer/Panes/SettingsPane.test.ts
src/Explorer/Panes/SettingsPane.ts
src/Explorer/Panes/SetupNotebooksPane.ts
src/Explorer/Panes/SetupSparkClusterPane.ts
src/Explorer/Panes/StringInputPane.ts
src/Explorer/Panes/SwitchDirectoryPane.ts
src/Explorer/Panes/Tables/AddTableEntityPane.ts
@@ -411,7 +409,6 @@ src/Explorer/Tabs/NotebookViewerTab.tsx
src/Explorer/Tabs/TerminalTab.tsx
src/Explorer/Tree/ResourceTreeAdapter.tsx
src/Explorer/Tree/ResourceTreeAdapterForResourceToken.tsx
src/GalleryViewer/Cards/CardStyleConstants.tsx
src/GalleryViewer/Cards/GalleryCardComponent.tsx
src/GalleryViewer/GalleryViewer.tsx
src/GalleryViewer/GalleryViewerComponent.tsx

View File

@@ -175,6 +175,7 @@ jobs:
- run: nuget pack -Version "2.0.0-github-${GITHUB_SHA}"
- run: nuget push -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg
- uses: actions/upload-artifact@v2
name: packages
with:
path: "*.nupkg"
nugetmpac:
@@ -198,5 +199,6 @@ jobs:
- run: nuget pack -Version "2.0.0-github-${GITHUB_SHA}"
- run: nuget push -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg
- uses: actions/upload-artifact@v2
name: packages
with:
path: "*.nupkg"

View File

@@ -1,7 +1,7 @@
name: Runners
on:
schedule:
- cron: "*/10 * * * *"
- cron: "0 * 1 * *"
jobs:
sqlcreatecollection:
runs-on: ubuntu-latest
@@ -18,3 +18,8 @@ jobs:
PORTAL_RUNNER_SUBSCRIPTION: 69e02f2d-f059-4409-9eac-97e8a276ae2c
PORTAL_RUNNER_RESOURCE_GROUP: runners
PORTAL_RUNNER_DATABASE_ACCOUNT: portal-sql-runner
- uses: actions/upload-artifact@v2
if: failure()
with:
name: screenshots
path: failure.png

3
.gitignore vendored
View File

@@ -16,4 +16,5 @@ notebookapp/*
Contracts/*
.DS_Store
.cache/
.env
.env
failure.png

View File

@@ -30,12 +30,13 @@ For IE support, polyfill is preferred over new usage of lodash or underscore. We
### Typescript
* Follow this [typescript style guide](https://github.com/excelmicro/typescript) which is based on [airbnb's style guide](https://github.com/airbnb/javascript).
* Conventions speficic to this project:
* Use double-quotes for string
* Don't use null, use undefined
* Pascal case for private static readonly fields
* Camel case for classnames in markup
- Use double-quotes for string
- Don't use `null`, use `undefined`
- Pascal case for private static readonly fields
- Camel case for classnames in markup
* Don't use class unless necessary
* Code related to notebooks should be dynamically imported so that it is loaded from a separate bundle only if the account is notebook-enabled. There are already top-level notebook components which are dynamically imported and their dependencies can be statically imported from these files.
* Prefer using [Fluent UI controls](https://developer.microsoft.com/en-us/fluentui#/controls/web) over creating your own, in order to maintain consistency and support a11y.
### React
* Prefer using React class components over function components and hooks unless you have a simple component and require no nested functions:

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

595
externals/bops.js vendored
View File

@@ -1,595 +0,0 @@
"use strict";
(function(e){if("function"==typeof bootstrap)bootstrap("bops",e);else if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else if("undefined"!=typeof ses){if(!ses.ok())return;ses.makeBops=e}else"undefined"!=typeof window?window.bops=e():global.bops=e()})(function(){var define,ses,bootstrap,module,exports;
return (function(e,t,n){function i(n,s){if(!t[n]){if(!e[n]){var o=typeof require=="function"&&require;if(!s&&o)return o(n,!0);if(r)return r(n,!0);throw new Error("Cannot find module '"+n+"'")}var u=t[n]={exports:{}};e[n][0].call(u.exports,function(t){var r=e[n][1][t];return i(r?r:t)},u,u.exports)}return t[n].exports}var r=typeof require=="function"&&require;for(var s=0;s<n.length;s++)i(n[s]);return i})({1:[function(require,module,exports){
var proto = {}
module.exports = proto
proto.from = require('./from.js')
proto.to = require('./to.js')
proto.is = require('./is.js')
proto.subarray = require('./subarray.js')
proto.join = require('./join.js')
proto.copy = require('./copy.js')
proto.create = require('./create.js')
mix(require('./read.js'), proto)
mix(require('./write.js'), proto)
function mix(from, into) {
for(var key in from) {
into[key] = from[key]
}
}
},{"./from.js":2,"./to.js":3,"./is.js":4,"./subarray.js":5,"./join.js":6,"./copy.js":7,"./create.js":8,"./read.js":9,"./write.js":10}],4:[function(require,module,exports){
module.exports = function(buffer) {
return buffer instanceof Uint8Array;
}
},{}],5:[function(require,module,exports){
module.exports = subarray
function subarray(buf, from, to) {
return buf.subarray(from || 0, to || buf.length)
}
},{}],6:[function(require,module,exports){
module.exports = join
function join(targets, hint) {
if(!targets.length) {
return new Uint8Array(0)
}
var len = hint !== undefined ? hint : get_length(targets)
, out = new Uint8Array(len)
, cur = targets[0]
, curlen = cur.length
, curidx = 0
, curoff = 0
, i = 0
while(i < len) {
if(curoff === curlen) {
curoff = 0
++curidx
cur = targets[curidx]
curlen = cur && cur.length
continue
}
out[i++] = cur[curoff++]
}
return out
}
function get_length(targets) {
var size = 0
for(var i = 0, len = targets.length; i < len; ++i) {
size += targets[i].byteLength
}
return size
}
},{}],7:[function(require,module,exports){
module.exports = copy
var slice = [].slice
function copy(source, target, target_start, source_start, source_end) {
target_start = arguments.length < 3 ? 0 : target_start
source_start = arguments.length < 4 ? 0 : source_start
source_end = arguments.length < 5 ? source.length : source_end
if(source_end === source_start) {
return
}
if(target.length === 0 || source.length === 0) {
return
}
if(source_end > source.length) {
source_end = source.length
}
if(target.length - target_start < source_end - source_start) {
source_end = target.length - target_start + start
}
if(source.buffer !== target.buffer) {
return fast_copy(source, target, target_start, source_start, source_end)
}
return slow_copy(source, target, target_start, source_start, source_end)
}
function fast_copy(source, target, target_start, source_start, source_end) {
var len = (source_end - source_start) + target_start
for(var i = target_start, j = source_start;
i < len;
++i,
++j) {
target[i] = source[j]
}
}
function slow_copy(from, to, j, i, jend) {
// the buffers could overlap.
var iend = jend + i
, tmp = new Uint8Array(slice.call(from, i, iend))
, x = 0
for(; i < iend; ++i, ++x) {
to[j++] = tmp[x]
}
}
},{}],8:[function(require,module,exports){
module.exports = function(size) {
return new Uint8Array(size)
}
},{}],9:[function(require,module,exports){
module.exports = {
readUInt8: read_uint8
, readInt8: read_int8
, readUInt16LE: read_uint16_le
, readUInt32LE: read_uint32_le
, readInt16LE: read_int16_le
, readInt32LE: read_int32_le
, readFloatLE: read_float_le
, readDoubleLE: read_double_le
, readUInt16BE: read_uint16_be
, readUInt32BE: read_uint32_be
, readInt16BE: read_int16_be
, readInt32BE: read_int32_be
, readFloatBE: read_float_be
, readDoubleBE: read_double_be
}
var map = require('./mapped.js')
function read_uint8(target, at) {
return target[at]
}
function read_int8(target, at) {
var v = target[at];
return v < 0x80 ? v : v - 0x100
}
function read_uint16_le(target, at) {
var dv = map.get(target);
return dv.getUint16(at + target.byteOffset, true)
}
function read_uint32_le(target, at) {
var dv = map.get(target);
return dv.getUint32(at + target.byteOffset, true)
}
function read_int16_le(target, at) {
var dv = map.get(target);
return dv.getInt16(at + target.byteOffset, true)
}
function read_int32_le(target, at) {
var dv = map.get(target);
return dv.getInt32(at + target.byteOffset, true)
}
function read_float_le(target, at) {
var dv = map.get(target);
return dv.getFloat32(at + target.byteOffset, true)
}
function read_double_le(target, at) {
var dv = map.get(target);
return dv.getFloat64(at + target.byteOffset, true)
}
function read_uint16_be(target, at) {
var dv = map.get(target);
return dv.getUint16(at + target.byteOffset, false)
}
function read_uint32_be(target, at) {
var dv = map.get(target);
return dv.getUint32(at + target.byteOffset, false)
}
function read_int16_be(target, at) {
var dv = map.get(target);
return dv.getInt16(at + target.byteOffset, false)
}
function read_int32_be(target, at) {
var dv = map.get(target);
return dv.getInt32(at + target.byteOffset, false)
}
function read_float_be(target, at) {
var dv = map.get(target);
return dv.getFloat32(at + target.byteOffset, false)
}
function read_double_be(target, at) {
var dv = map.get(target);
return dv.getFloat64(at + target.byteOffset, false)
}
},{"./mapped.js":11}],10:[function(require,module,exports){
module.exports = {
writeUInt8: write_uint8
, writeInt8: write_int8
, writeUInt16LE: write_uint16_le
, writeUInt32LE: write_uint32_le
, writeInt16LE: write_int16_le
, writeInt32LE: write_int32_le
, writeFloatLE: write_float_le
, writeDoubleLE: write_double_le
, writeUInt16BE: write_uint16_be
, writeUInt32BE: write_uint32_be
, writeInt16BE: write_int16_be
, writeInt32BE: write_int32_be
, writeFloatBE: write_float_be
, writeDoubleBE: write_double_be
}
var map = require('./mapped.js')
function write_uint8(target, value, at) {
return target[at] = value
}
function write_int8(target, value, at) {
return target[at] = value < 0 ? value + 0x100 : value
}
function write_uint16_le(target, value, at) {
var dv = map.get(target);
return dv.setUint16(at + target.byteOffset, value, true)
}
function write_uint32_le(target, value, at) {
var dv = map.get(target);
return dv.setUint32(at + target.byteOffset, value, true)
}
function write_int16_le(target, value, at) {
var dv = map.get(target);
return dv.setInt16(at + target.byteOffset, value, true)
}
function write_int32_le(target, value, at) {
var dv = map.get(target);
return dv.setInt32(at + target.byteOffset, value, true)
}
function write_float_le(target, value, at) {
var dv = map.get(target);
return dv.setFloat32(at + target.byteOffset, value, true)
}
function write_double_le(target, value, at) {
var dv = map.get(target);
return dv.setFloat64(at + target.byteOffset, value, true)
}
function write_uint16_be(target, value, at) {
var dv = map.get(target);
return dv.setUint16(at + target.byteOffset, value, false)
}
function write_uint32_be(target, value, at) {
var dv = map.get(target);
return dv.setUint32(at + target.byteOffset, value, false)
}
function write_int16_be(target, value, at) {
var dv = map.get(target);
return dv.setInt16(at + target.byteOffset, value, false)
}
function write_int32_be(target, value, at) {
var dv = map.get(target);
return dv.setInt32(at + target.byteOffset, value, false)
}
function write_float_be(target, value, at) {
var dv = map.get(target);
return dv.setFloat32(at + target.byteOffset, value, false)
}
function write_double_be(target, value, at) {
var dv = map.get(target);
return dv.setFloat64(at + target.byteOffset, value, false)
}
},{"./mapped.js":11}],11:[function(require,module,exports){
var proto
, map
module.exports = proto = {}
map = typeof WeakMap === 'undefined' ? null : new WeakMap
proto.get = !map ? no_weakmap_get : get
function no_weakmap_get(target) {
return new DataView(target.buffer, 0)
}
function get(target) {
var out = map.get(target.buffer)
if(!out) {
map.set(target.buffer, out = new DataView(target.buffer, 0))
}
return out
}
},{}],2:[function(require,module,exports){
module.exports = from
var base64 = require('base64-js')
var decoders = {
hex: from_hex
, utf8: from_utf
, base64: from_base64
}
function from(source, encoding) {
if(Array.isArray(source)) {
return new Uint8Array(source)
}
return decoders[encoding || 'utf8'](source)
}
function from_hex(str) {
var size = str.length / 2
, buf = new Uint8Array(size)
, character = ''
for(var i = 0, len = str.length; i < len; ++i) {
character += str.charAt(i)
if(i > 0 && (i % 2) === 1) {
buf[i>>>1] = parseInt(character, 16)
character = ''
}
}
return buf
}
function from_utf(str) {
var bytes = []
, tmp
, ch
for(var i = 0, len = str.length; i < len; ++i) {
ch = str.charCodeAt(i)
if(ch & 0x80) {
tmp = encodeURIComponent(str.charAt(i)).substr(1).split('%')
for(var j = 0, jlen = tmp.length; j < jlen; ++j) {
bytes[bytes.length] = parseInt(tmp[j], 16)
}
} else {
bytes[bytes.length] = ch
}
}
return new Uint8Array(bytes)
}
function from_base64(str) {
return new Uint8Array(base64.toByteArray(str))
}
},{"base64-js":12}],3:[function(require,module,exports){
module.exports = to
var base64 = require('base64-js')
, toutf8 = require('to-utf8')
var encoders = {
hex: to_hex
, utf8: to_utf
, base64: to_base64
}
function to(buf, encoding) {
return encoders[encoding || 'utf8'](buf)
}
function to_hex(buf) {
var str = ''
, byt
for(var i = 0, len = buf.length; i < len; ++i) {
byt = buf[i]
str += ((byt & 0xF0) >>> 4).toString(16)
str += (byt & 0x0F).toString(16)
}
return str
}
function to_utf(buf) {
return toutf8(buf)
}
function to_base64(buf) {
return base64.fromByteArray(buf)
}
},{"base64-js":12,"to-utf8":13}],12:[function(require,module,exports){
(function (exports) {
'use strict';
var lookup = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
function b64ToByteArray(b64) {
var i, j, l, tmp, placeHolders, arr;
if (b64.length % 4 > 0) {
throw 'Invalid string. Length must be a multiple of 4';
}
// the number of equal signs (place holders)
// if there are two placeholders, than the two characters before it
// represent one byte
// if there is only one, then the three characters before it represent 2 bytes
// this is just a cheap hack to not do indexOf twice
placeHolders = b64.indexOf('=');
placeHolders = placeHolders > 0 ? b64.length - placeHolders : 0;
// base64 is 4/3 + up to two characters of the original data
arr = [];//new Uint8Array(b64.length * 3 / 4 - placeHolders);
// if there are placeholders, only get up to the last complete 4 chars
l = placeHolders > 0 ? b64.length - 4 : b64.length;
for (i = 0, j = 0; i < l; i += 4, j += 3) {
tmp = (lookup.indexOf(b64[i]) << 18) | (lookup.indexOf(b64[i + 1]) << 12) | (lookup.indexOf(b64[i + 2]) << 6) | lookup.indexOf(b64[i + 3]);
arr.push((tmp & 0xFF0000) >> 16);
arr.push((tmp & 0xFF00) >> 8);
arr.push(tmp & 0xFF);
}
if (placeHolders === 2) {
tmp = (lookup.indexOf(b64[i]) << 2) | (lookup.indexOf(b64[i + 1]) >> 4);
arr.push(tmp & 0xFF);
} else if (placeHolders === 1) {
tmp = (lookup.indexOf(b64[i]) << 10) | (lookup.indexOf(b64[i + 1]) << 4) | (lookup.indexOf(b64[i + 2]) >> 2);
arr.push((tmp >> 8) & 0xFF);
arr.push(tmp & 0xFF);
}
return arr;
}
function uint8ToBase64(uint8) {
var i,
extraBytes = uint8.length % 3, // if we have 1 byte left, pad 2 bytes
output = "",
temp, length;
function tripletToBase64 (num) {
return lookup[num >> 18 & 0x3F] + lookup[num >> 12 & 0x3F] + lookup[num >> 6 & 0x3F] + lookup[num & 0x3F];
};
// go through the array every three bytes, we'll deal with trailing stuff later
for (i = 0, length = uint8.length - extraBytes; i < length; i += 3) {
temp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2]);
output += tripletToBase64(temp);
}
// pad the end with zeros, but make sure to not forget the extra bytes
switch (extraBytes) {
case 1:
temp = uint8[uint8.length - 1];
output += lookup[temp >> 2];
output += lookup[(temp << 4) & 0x3F];
output += '==';
break;
case 2:
temp = (uint8[uint8.length - 2] << 8) + (uint8[uint8.length - 1]);
output += lookup[temp >> 10];
output += lookup[(temp >> 4) & 0x3F];
output += lookup[(temp << 2) & 0x3F];
output += '=';
break;
}
return output;
}
module.exports.toByteArray = b64ToByteArray;
module.exports.fromByteArray = uint8ToBase64;
}());
},{}],13:[function(require,module,exports){
module.exports = to_utf8
var out = []
, col = []
, fcc = String.fromCharCode
, mask = [0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01]
, unmask = [
0x00
, 0x01
, 0x02 | 0x01
, 0x04 | 0x02 | 0x01
, 0x08 | 0x04 | 0x02 | 0x01
, 0x10 | 0x08 | 0x04 | 0x02 | 0x01
, 0x20 | 0x10 | 0x08 | 0x04 | 0x02 | 0x01
, 0x40 | 0x20 | 0x10 | 0x08 | 0x04 | 0x02 | 0x01
]
function to_utf8(bytes, start, end) {
start = start === undefined ? 0 : start
end = end === undefined ? bytes.length : end
var idx = 0
, hi = 0x80
, collecting = 0
, pos
, by
col.length =
out.length = 0
while(idx < bytes.length) {
by = bytes[idx]
if(!collecting && by & hi) {
pos = find_pad_position(by)
collecting += pos
if(pos < 8) {
col[col.length] = by & unmask[6 - pos]
}
} else if(collecting) {
col[col.length] = by & unmask[6]
--collecting
if(!collecting && col.length) {
out[out.length] = fcc(reduced(col, pos))
col.length = 0
}
} else {
out[out.length] = fcc(by)
}
++idx
}
if(col.length && !collecting) {
out[out.length] = fcc(reduced(col, pos))
col.length = 0
}
return out.join('')
}
function find_pad_position(byt) {
for(var i = 0; i < 7; ++i) {
if(!(byt & mask[i])) {
break
}
}
return i
}
function reduced(list) {
var out = 0
for(var i = 0, len = list.length; i < len; ++i) {
out |= list[i] << ((len - i - 1) * 6)
}
return out
}
},{}]},{},[1])(1)
});
;

View File

@@ -1,723 +0,0 @@
/** @license
* crossroads <http://millermedeiros.github.com/crossroads.js/>
* Author: Miller Medeiros | MIT License
* v0.12.2 (2015/07/31 18:37)
*/
(function () {
var factory = function (signals) {
var crossroads,
_hasOptionalGroupBug,
UNDEF;
// Helpers -----------
//====================
// IE 7-8 capture optional groups as empty strings while other browsers
// capture as `undefined`
_hasOptionalGroupBug = (/t(.+)?/).exec('t')[1] === '';
function arrayIndexOf(arr, val) {
if (arr.indexOf) {
return arr.indexOf(val);
} else {
//Array.indexOf doesn't work on IE 6-7
var n = arr.length;
while (n--) {
if (arr[n] === val) {
return n;
}
}
return -1;
}
}
function arrayRemove(arr, item) {
var i = arrayIndexOf(arr, item);
if (i !== -1) {
arr.splice(i, 1);
}
}
function isKind(val, kind) {
return '[object '+ kind +']' === Object.prototype.toString.call(val);
}
function isRegExp(val) {
return isKind(val, 'RegExp');
}
function isArray(val) {
return isKind(val, 'Array');
}
function isFunction(val) {
return typeof val === 'function';
}
//borrowed from AMD-utils
function typecastValue(val) {
var r;
if (val === null || val === 'null') {
r = null;
} else if (val === 'true') {
r = true;
} else if (val === 'false') {
r = false;
} else if (val === UNDEF || val === 'undefined') {
r = UNDEF;
} else if (val === '' || isNaN(val)) {
r = val;
} else {
r = parseFloat(val);
}
return r;
}
function typecastArrayValues(values) {
var n = values.length,
result = [];
while (n--) {
result[n] = typecastValue(values[n]);
}
return result;
}
// borrowed from MOUT
function decodeQueryString(queryStr, shouldTypecast) {
var queryArr = (queryStr || '').replace('?', '').split('&'),
reg = /([^=]+)=(.+)/,
i = -1,
obj = {},
equalIndex, cur, pValue, pName;
while ((cur = queryArr[++i])) {
equalIndex = cur.indexOf('=');
pName = cur.substring(0, equalIndex);
pValue = decodeURIComponent(cur.substring(equalIndex + 1));
if (shouldTypecast !== false) {
pValue = typecastValue(pValue);
}
if (pName in obj){
if(isArray(obj[pName])){
obj[pName].push(pValue);
} else {
obj[pName] = [obj[pName], pValue];
}
} else {
obj[pName] = pValue;
}
}
return obj;
}
// Crossroads --------
//====================
/**
* @constructor
*/
function Crossroads() {
this.bypassed = new signals.Signal();
this.routed = new signals.Signal();
this._routes = [];
this._prevRoutes = [];
this._piped = [];
this.resetState();
}
Crossroads.prototype = {
greedy : false,
greedyEnabled : true,
ignoreCase : true,
ignoreState : false,
shouldTypecast : false,
normalizeFn : null,
resetState : function(){
this._prevRoutes.length = 0;
this._prevMatchedRequest = null;
this._prevBypassedRequest = null;
},
create : function () {
return new Crossroads();
},
addRoute : function (pattern, callback, priority) {
var route = new Route(pattern, callback, priority, this);
this._sortedInsert(route);
return route;
},
removeRoute : function (route) {
arrayRemove(this._routes, route);
route._destroy();
},
removeAllRoutes : function () {
var n = this.getNumRoutes();
while (n--) {
this._routes[n]._destroy();
}
this._routes.length = 0;
},
parse : function (request, defaultArgs) {
request = request || '';
defaultArgs = defaultArgs || [];
// should only care about different requests if ignoreState isn't true
if ( !this.ignoreState &&
(request === this._prevMatchedRequest ||
request === this._prevBypassedRequest) ) {
return;
}
var routes = this._getMatchedRoutes(request),
i = 0,
n = routes.length,
cur;
if (n) {
this._prevMatchedRequest = request;
this._notifyPrevRoutes(routes, request);
this._prevRoutes = routes;
//should be incremental loop, execute routes in order
while (i < n) {
cur = routes[i];
cur.route.matched.dispatch.apply(cur.route.matched, defaultArgs.concat(cur.params));
cur.isFirst = !i;
this.routed.dispatch.apply(this.routed, defaultArgs.concat([request, cur]));
i += 1;
}
} else {
this._prevBypassedRequest = request;
this.bypassed.dispatch.apply(this.bypassed, defaultArgs.concat([request]));
}
this._pipeParse(request, defaultArgs);
},
_notifyPrevRoutes : function(matchedRoutes, request) {
var i = 0, prev;
while (prev = this._prevRoutes[i++]) {
//check if switched exist since route may be disposed
if(prev.route.switched && this._didSwitch(prev.route, matchedRoutes)) {
prev.route.switched.dispatch(request);
}
}
},
_didSwitch : function (route, matchedRoutes){
var matched,
i = 0;
while (matched = matchedRoutes[i++]) {
// only dispatch switched if it is going to a different route
if (matched.route === route) {
return false;
}
}
return true;
},
_pipeParse : function(request, defaultArgs) {
var i = 0, route;
while (route = this._piped[i++]) {
route.parse(request, defaultArgs);
}
},
getNumRoutes : function () {
return this._routes.length;
},
_sortedInsert : function (route) {
//simplified insertion sort
var routes = this._routes,
n = routes.length;
do { --n; } while (routes[n] && route._priority <= routes[n]._priority);
routes.splice(n+1, 0, route);
},
_getMatchedRoutes : function (request) {
var res = [],
routes = this._routes,
n = routes.length,
route;
//should be decrement loop since higher priorities are added at the end of array
while (route = routes[--n]) {
if ((!res.length || this.greedy || route.greedy) && route.match(request)) {
res.push({
route : route,
params : route._getParamsArray(request)
});
}
if (!this.greedyEnabled && res.length) {
break;
}
}
return res;
},
pipe : function (otherRouter) {
this._piped.push(otherRouter);
},
unpipe : function (otherRouter) {
arrayRemove(this._piped, otherRouter);
},
toString : function () {
return '[crossroads numRoutes:'+ this.getNumRoutes() +']';
}
};
//"static" instance
crossroads = new Crossroads();
crossroads.VERSION = '0.12.2';
crossroads.NORM_AS_ARRAY = function (req, vals) {
return [vals.vals_];
};
crossroads.NORM_AS_OBJECT = function (req, vals) {
return [vals];
};
// Route --------------
//=====================
/**
* @constructor
*/
function Route(pattern, callback, priority, router) {
var isRegexPattern = isRegExp(pattern),
patternLexer = router.patternLexer;
this._router = router;
this._pattern = pattern;
this._paramsIds = isRegexPattern? null : patternLexer.getParamIds(pattern);
this._optionalParamsIds = isRegexPattern? null : patternLexer.getOptionalParamsIds(pattern);
this._matchRegexp = isRegexPattern? pattern : patternLexer.compilePattern(pattern, router.ignoreCase);
this.matched = new signals.Signal();
this.switched = new signals.Signal();
if (callback) {
this.matched.add(callback);
}
this._priority = priority || 0;
}
Route.prototype = {
greedy : false,
rules : void(0),
match : function (request) {
request = request || '';
return this._matchRegexp.test(request) && this._validateParams(request); //validate params even if regexp because of `request_` rule.
},
_validateParams : function (request) {
var rules = this.rules,
values = this._getParamsObject(request),
key;
for (key in rules) {
// normalize_ isn't a validation rule... (#39)
if(key !== 'normalize_' && rules.hasOwnProperty(key) && ! this._isValidParam(request, key, values)){
return false;
}
}
return true;
},
_isValidParam : function (request, prop, values) {
var validationRule = this.rules[prop],
val = values[prop],
isValid = false,
isQuery = (prop.indexOf('?') === 0);
if (val == null && this._optionalParamsIds && arrayIndexOf(this._optionalParamsIds, prop) !== -1) {
isValid = true;
}
else if (isRegExp(validationRule)) {
if (isQuery) {
val = values[prop +'_']; //use raw string
}
isValid = validationRule.test(val);
}
else if (isArray(validationRule)) {
if (isQuery) {
val = values[prop +'_']; //use raw string
}
isValid = this._isValidArrayRule(validationRule, val);
}
else if (isFunction(validationRule)) {
isValid = validationRule(val, request, values);
}
return isValid; //fail silently if validationRule is from an unsupported type
},
_isValidArrayRule : function (arr, val) {
if (! this._router.ignoreCase) {
return arrayIndexOf(arr, val) !== -1;
}
if (typeof val === 'string') {
val = val.toLowerCase();
}
var n = arr.length,
item,
compareVal;
while (n--) {
item = arr[n];
compareVal = (typeof item === 'string')? item.toLowerCase() : item;
if (compareVal === val) {
return true;
}
}
return false;
},
_getParamsObject : function (request) {
var shouldTypecast = this._router.shouldTypecast,
values = this._router.patternLexer.getParamValues(request, this._matchRegexp, shouldTypecast),
o = {},
n = values.length,
param, val;
while (n--) {
val = values[n];
if (this._paramsIds) {
param = this._paramsIds[n];
if (param.indexOf('?') === 0 && val) {
//make a copy of the original string so array and
//RegExp validation can be applied properly
o[param +'_'] = val;
//update vals_ array as well since it will be used
//during dispatch
val = decodeQueryString(val, shouldTypecast);
values[n] = val;
}
// IE will capture optional groups as empty strings while other
// browsers will capture `undefined` so normalize behavior.
// see: #gh-58, #gh-59, #gh-60
if ( _hasOptionalGroupBug && val === '' && arrayIndexOf(this._optionalParamsIds, param) !== -1 ) {
val = void(0);
values[n] = val;
}
o[param] = val;
}
//alias to paths and for RegExp pattern
o[n] = val;
}
o.request_ = shouldTypecast? typecastValue(request) : request;
o.vals_ = values;
return o;
},
_getParamsArray : function (request) {
var norm = this.rules? this.rules.normalize_ : null,
params;
norm = norm || this._router.normalizeFn; // default normalize
if (norm && isFunction(norm)) {
params = norm(request, this._getParamsObject(request));
} else {
params = this._getParamsObject(request).vals_;
}
return params;
},
interpolate : function(replacements) {
var str = this._router.patternLexer.interpolate(this._pattern, replacements);
if (! this._validateParams(str) ) {
throw new Error('Generated string doesn\'t validate against `Route.rules`.');
}
return str;
},
dispose : function () {
this._router.removeRoute(this);
},
_destroy : function () {
this.matched.dispose();
this.switched.dispose();
this.matched = this.switched = this._pattern = this._matchRegexp = null;
},
toString : function () {
return '[Route pattern:"'+ this._pattern +'", numListeners:'+ this.matched.getNumListeners() +']';
}
};
// Pattern Lexer ------
//=====================
Crossroads.prototype.patternLexer = (function () {
var
//match chars that should be escaped on string regexp
ESCAPE_CHARS_REGEXP = /[\\.+*?\^$\[\](){}\/'#]/g,
//trailing slashes (begin/end of string)
LOOSE_SLASHES_REGEXP = /^\/|\/$/g,
LEGACY_SLASHES_REGEXP = /\/$/g,
//params - everything between `{ }` or `: :`
PARAMS_REGEXP = /(?:\{|:)([^}:]+)(?:\}|:)/g,
//used to save params during compile (avoid escaping things that
//shouldn't be escaped).
TOKENS = {
'OS' : {
//optional slashes
//slash between `::` or `}:` or `\w:` or `:{?` or `}{?` or `\w{?`
rgx : /([:}]|\w(?=\/))\/?(:|(?:\{\?))/g,
save : '$1{{id}}$2',
res : '\\/?'
},
'RS' : {
//required slashes
//used to insert slash between `:{` and `}{`
rgx : /([:}])\/?(\{)/g,
save : '$1{{id}}$2',
res : '\\/'
},
'RQ' : {
//required query string - everything in between `{? }`
rgx : /\{\?([^}]+)\}/g,
//everything from `?` till `#` or end of string
res : '\\?([^#]+)'
},
'OQ' : {
//optional query string - everything in between `:? :`
rgx : /:\?([^:]+):/g,
//everything from `?` till `#` or end of string
res : '(?:\\?([^#]*))?'
},
'OR' : {
//optional rest - everything in between `: *:`
rgx : /:([^:]+)\*:/g,
res : '(.*)?' // optional group to avoid passing empty string as captured
},
'RR' : {
//rest param - everything in between `{ *}`
rgx : /\{([^}]+)\*\}/g,
res : '(.+)'
},
// required/optional params should come after rest segments
'RP' : {
//required params - everything between `{ }`
rgx : /\{([^}]+)\}/g,
res : '([^\\/?]+)'
},
'OP' : {
//optional params - everything between `: :`
rgx : /:([^:]+):/g,
res : '([^\\/?]+)?\/?'
}
},
LOOSE_SLASH = 1,
STRICT_SLASH = 2,
LEGACY_SLASH = 3,
_slashMode = LOOSE_SLASH;
function precompileTokens(){
var key, cur;
for (key in TOKENS) {
if (TOKENS.hasOwnProperty(key)) {
cur = TOKENS[key];
cur.id = '__CR_'+ key +'__';
cur.save = ('save' in cur)? cur.save.replace('{{id}}', cur.id) : cur.id;
cur.rRestore = new RegExp(cur.id, 'g');
}
}
}
precompileTokens();
function captureVals(regex, pattern) {
var vals = [], match;
// very important to reset lastIndex since RegExp can have "g" flag
// and multiple runs might affect the result, specially if matching
// same string multiple times on IE 7-8
regex.lastIndex = 0;
while (match = regex.exec(pattern)) {
vals.push(match[1]);
}
return vals;
}
function getParamIds(pattern) {
return captureVals(PARAMS_REGEXP, pattern);
}
function getOptionalParamsIds(pattern) {
return captureVals(TOKENS.OP.rgx, pattern);
}
function compilePattern(pattern, ignoreCase) {
pattern = pattern || '';
if(pattern){
if (_slashMode === LOOSE_SLASH) {
pattern = pattern.replace(LOOSE_SLASHES_REGEXP, '');
}
else if (_slashMode === LEGACY_SLASH) {
pattern = pattern.replace(LEGACY_SLASHES_REGEXP, '');
}
//save tokens
pattern = replaceTokens(pattern, 'rgx', 'save');
//regexp escape
pattern = pattern.replace(ESCAPE_CHARS_REGEXP, '\\$&');
//restore tokens
pattern = replaceTokens(pattern, 'rRestore', 'res');
if (_slashMode === LOOSE_SLASH) {
pattern = '\\/?'+ pattern;
}
}
if (_slashMode !== STRICT_SLASH) {
//single slash is treated as empty and end slash is optional
pattern += '\\/?';
}
return new RegExp('^'+ pattern + '$', ignoreCase? 'i' : '');
}
function replaceTokens(pattern, regexpName, replaceName) {
var cur, key;
for (key in TOKENS) {
if (TOKENS.hasOwnProperty(key)) {
cur = TOKENS[key];
pattern = pattern.replace(cur[regexpName], cur[replaceName]);
}
}
return pattern;
}
function getParamValues(request, regexp, shouldTypecast) {
var vals = regexp.exec(request);
if (vals) {
vals.shift();
if (shouldTypecast) {
vals = typecastArrayValues(vals);
}
}
return vals;
}
function interpolate(pattern, replacements) {
// default to an empty object because pattern might have just
// optional arguments
replacements = replacements || {};
if (typeof pattern !== 'string') {
throw new Error('Route pattern should be a string.');
}
var replaceFn = function(match, prop){
var val;
prop = (prop.substr(0, 1) === '?')? prop.substr(1) : prop;
if (replacements[prop] != null) {
if (typeof replacements[prop] === 'object') {
var queryParts = [], rep;
for(var key in replacements[prop]) {
rep = replacements[prop][key];
if (isArray(rep)) {
for (var k in rep) {
if ( key.slice(-2) == '[]' ) {
queryParts.push(encodeURI(key.slice(0, -2)) + '[]=' + encodeURI(rep[k]));
} else {
queryParts.push(encodeURI(key + '=' + rep[k]));
}
}
}
else {
queryParts.push(encodeURI(key + '=' + rep));
}
}
val = '?' + queryParts.join('&');
} else {
// make sure value is a string see #gh-54
val = String(replacements[prop]);
}
if (match.indexOf('*') === -1 && val.indexOf('/') !== -1) {
throw new Error('Invalid value "'+ val +'" for segment "'+ match +'".');
}
}
else if (match.indexOf('{') !== -1) {
throw new Error('The segment '+ match +' is required.');
}
else {
val = '';
}
return val;
};
if (! TOKENS.OS.trail) {
TOKENS.OS.trail = new RegExp('(?:'+ TOKENS.OS.id +')+$');
}
return pattern
.replace(TOKENS.OS.rgx, TOKENS.OS.save)
.replace(PARAMS_REGEXP, replaceFn)
.replace(TOKENS.OS.trail, '') // remove trailing
.replace(TOKENS.OS.rRestore, '/'); // add slash between segments
}
//API
return {
strict : function(){
_slashMode = STRICT_SLASH;
},
loose : function(){
_slashMode = LOOSE_SLASH;
},
legacy : function(){
_slashMode = LEGACY_SLASH;
},
getParamIds : getParamIds,
getOptionalParamsIds : getOptionalParamsIds,
getParamValues : getParamValues,
compilePattern : compilePattern,
interpolate : interpolate
};
}());
return crossroads;
};
if (typeof define === 'function' && define.amd) {
define(['signals'], factory);
} else if (typeof module !== 'undefined' && module.exports) { //Node
module.exports = factory(require('signals'));
} else {
/*jshint sub:true */
window['crossroads'] = factory(window['signals']);
}
}());

File diff suppressed because it is too large Load Diff

View File

@@ -1,846 +0,0 @@
/*! ColResize 0.0.10
*/
/**
* @summary ColResize
* @description Provide the ability to resize columns in a DataTable
* @version 0.0.10
* @file dataTables.colResize.js
* @author Silvacom Ltd.
*
* For details please refer to: http://www.datatables.net
*
* Special thank to everyone who has contributed to this plug in
* - dykstrad
* - tdillan (for 0.0.3 and 0.0.5 bug fixes)
* - kylealonius (for 0.0.8 bug fix)
* - the86freak (for 0.0.9 bug fix)
*/
(function (window, document, undefined) {
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* DataTables plug-in API functions test
*
* This are required by ColResize in order to perform the tasks required, and also keep this
* code portable, to be used for other column resize projects with DataTables, if needed.
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
var factory = function ($, DataTable) {
"use strict";
/**
* Plug-in for DataTables which will resize the columns depending on the handle clicked
* @method $.fn.dataTableExt.oApi.fnColResize
* @param object oSettings DataTables settings object - automatically added by DataTables!
* @param int iCol Take the column to be resized
* @returns void
*/
$.fn.dataTableExt.oApi.fnColResize = function (oSettings, iCol) {
var v110 = $.fn.dataTable.Api ? true : false;
/*
* Update DataTables' event handlers
*/
/* Fire an event so other plug-ins can update */
$(oSettings.oInstance).trigger('column-resize', [ oSettings, {
"iCol": iCol
} ]);
};
/**
* ColResize provides column resize control for DataTables
* @class ColResize
* @constructor
* @param {object} dt DataTables settings object
* @param {object} opts ColResize options
*/
var ColResize = function (dt, opts) {
var oDTSettings;
if ($.fn.dataTable.Api) {
oDTSettings = new $.fn.dataTable.Api(dt).settings()[0];
}
// 1.9 compatibility
else if (dt.fnSettings) {
// DataTables object, convert to the settings object
oDTSettings = dt.fnSettings();
}
else if (typeof dt === 'string') {
// jQuery selector
if ($.fn.dataTable.fnIsDataTable($(dt)[0])) {
oDTSettings = $(dt).eq(0).dataTable().fnSettings();
}
}
else if (dt.nodeName && dt.nodeName.toLowerCase() === 'table') {
// Table node
if ($.fn.dataTable.fnIsDataTable(dt.nodeName)) {
oDTSettings = $(dt.nodeName).dataTable().fnSettings();
}
}
else if (dt instanceof jQuery) {
// jQuery object
if ($.fn.dataTable.fnIsDataTable(dt[0])) {
oDTSettings = dt.eq(0).dataTable().fnSettings();
}
}
else {
// DataTables settings object
oDTSettings = dt;
}
// Convert from camelCase to Hungarian, just as DataTables does
if ($.fn.dataTable.camelToHungarian) {
$.fn.dataTable.camelToHungarian(ColResize.defaults, opts || {});
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Public class variables
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/**
* @namespace Settings object which contains customizable information for ColResize instance
*/
this.s = {
/**
* DataTables settings object
* @property dt
* @type Object
* @default null
*/
"dt": null,
/**
* Initialisation object used for this instance
* @property init
* @type object
* @default {}
*/
"init": $.extend(true, {}, ColResize.defaults, opts),
/**
* @namespace Information used for the mouse drag
*/
"mouse": {
"startX": -1,
"startY": -1,
"targetIndex": -1,
"targetColumn": -1,
"neighbourIndex": -1,
"neighbourColumn": -1
},
/**
* Status variable keeping track of mouse down status
* @property isMousedown
* @type boolean
* @default false
*/
"isMousedown": false
};
/**
* @namespace Common and useful DOM elements for the class instance
*/
this.dom = {
/**
* Resizing element (the one the mouse is resizing)
* @property resize
* @type element
* @default null
*/
"resizeCol": null,
/**
* Resizing element neighbour (the column next to the one the mouse is resizing)
* This is for fixed table resizing.
* @property resize
* @type element
* @default null
*/
"resizeColNeighbour": null,
/**
* Array of events to be restored, used for overriding existing events from other plugins for a time.
* @property restoreEvents
* @type array
* @default []
*/
"restoreEvents": []
};
/* Constructor logic */
this.s.dt = oDTSettings.oInstance.fnSettings();
this.s.dt._colResize = this;
this._fnConstruct();
/* Add destroy callback */
oDTSettings.oApi._fnCallbackReg(oDTSettings, 'aoDestroyCallback', $.proxy(this._fnDestroy, this), 'ColResize');
return this;
};
ColResize.prototype = {
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Public methods
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/**
* Reset the column widths to the original widths that was detected on
* start up.
* @return {this} Returns `this` for chaining.
*
* @example
* // DataTables initialisation with ColResize
* var table = $('#example').dataTable( {
* "sDom": 'Zlfrtip'
* } );
*
* // Add click event to a button to reset the ordering
* $('#resetOrdering').click( function (e) {
* e.preventDefault();
* $.fn.dataTable.ColResize( table ).fnReset();
* } );
*/
"fnReset": function () {
var a = [];
for (var i = 0, iLen = this.s.dt.aoColumns.length; i < iLen; i++) {
this.s.dt.aoColumns[i].width = this.s.dt.aoColumns[i]._ColResize_iOrigWidth;
}
this.s.dt.adjust().draw();
return this;
},
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Private methods (they are of course public in JS, but recommended as private)
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/**
* Constructor logic
* @method _fnConstruct
* @returns void
* @private
*/
"_fnConstruct": function () {
var that = this;
var iLen = that.s.dt.aoColumns.length;
var i;
that._fnSetupMouseListeners();
/* Add event handlers for the resize handles */
for (i = 0; i < iLen; i++) {
/* Mark the original column width for later reference */
this.s.dt.aoColumns[i]._ColResize_iOrigWidth = this.s.dt.aoColumns[i].width;
}
this._fnSetColumnIndexes();
/* State saving */
this.s.dt.oApi._fnCallbackReg( this.s.dt, 'aoStateSaveParams', function (oS, oData) {
that._fnStateSave.call(that, oData);
}, "ColResize_State" );
// State loading
this._fnStateLoad();
},
/**
* @method _fnStateSave
* @param object oState DataTables state
* @private
*/
"_fnStateSave": function (oState) {
this.s.dt.aoColumns.forEach(function(col, index) {
oState.columns[index].width = col.sWidthOrig;
});
},
/**
* If state has been loaded, apply the saved widths to the columns
* @method _fnStateLoad
* @private
*/
"_fnStateLoad": function() {
var that = this,
loadedState = this.s.dt.oLoadedState;
if (loadedState && loadedState.columns) {
var colStates = loadedState.columns,
currCols = this.s.dt.aoColumns;
// Only apply the saved widths if the number of columns is the same.
// Otherwise, we don't know if we're applying the width to the correct column.
if (colStates.length > 0 && colStates.length === currCols.length) {
colStates.forEach(function(state, index) {
var col = that.s.dt.aoColumns[index];
if (state.width) {
col.sWidthOrig = col.sWidth = state.width;
}
});
}
}
},
/**
* Remove events of type from obj add it to restoreEvents array to be restored at a later time
* @param until string flag when to restore the event
* @param obj Object to remove events from
* @param type type of event to remove
* @param namespace namespace of the event being removed
*/
"_fnDelayEvents": function (until, obj, type, namespace) {
var that = this;
//Get the events for the object
var events = $._data($(obj).get(0), 'events');
$.each(events, function (i, o) {
//If the event type matches
if (i == type) {
//Loop through the possible many events with that type
$.each(o, function (k, v) {
//Somehow it is possible for the event to be undefined make sure it is defined first
if (v) {
if (namespace) {
//Add the event to the array of events to be restored later
that.dom.restoreEvents.push({"until": until, "obj": obj, "type": v.type, "namespace": v.namespace, "handler": v.handler});
//If the namespace matches
if (v.namespace == namespace) {
//Turn off/unbind the event
$(obj).off(type + "." + namespace);
}
} else {
//Add the event to the array of events to be restored later
that.dom.restoreEvents.push({"until": until, "obj": obj, "type": v.type, "namespace": null, "handler": v.handler});
//Turn off/unbind the event
$(obj).off(type);
}
}
});
}
});
},
/**
* Loop through restoreEvents array and restore the events on the elements provided
*/
"_fnRestoreEvents": function (until) {
var that = this;
//Loop through the events to be restored
var i;
for (i = that.dom.restoreEvents.length; i--;) {
if (that.dom.restoreEvents[i].until == undefined || that.dom.restoreEvents[i].until == null || that.dom.restoreEvents[i].until == until) {
if (that.dom.restoreEvents[i].namespace) {
//Turn on the event for the object provided
$(that.dom.restoreEvents[i].obj).off(that.dom.restoreEvents[i].type + "." + that.dom.restoreEvents[i].namespace).on(that.dom.restoreEvents[i].type + "." + that.dom.restoreEvents[i].namespace, that.dom.restoreEvents[i].handler);
that.dom.restoreEvents.splice(i, 1);
} else {
//Turn on the event for the object provided
$(that.dom.restoreEvents[i].obj).off(that.dom.restoreEvents[i].type).on(that.dom.restoreEvents[i].type, that.dom.restoreEvents[i].handler);
that.dom.restoreEvents.splice(i, 1);
}
}
}
},
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Mouse drop and drag
*/
"_fnSetupMouseListeners":function() {
var that = this;
$(that.s.dt.nTableWrapper).off("mouseenter.ColResize").on("mouseenter.ColResize","th",function(e) {
e.preventDefault();
that._fnMouseEnter.call(that, e, this);
});
$(that.s.dt.nTableWrapper).off("mouseleave.ColResize").on("mouseleave.ColResize","th",function(e) {
e.preventDefault();
that._fnMouseLeave.call(that, e, this);
});
},
/**
* Add mouse listeners to the resize handle on TH element
* @method _fnMouseListener
* @param i Column index
* @param nTh TH resize handle element clicked on
* @returns void
* @private
*/
"_fnMouseListener": function (i, nTh) {
var that = this;
$(nTh).off('mouseenter.ColResize').on('mouseenter.ColResize', function (e) {
e.preventDefault();
that._fnMouseEnter.call(that, e, nTh);
});
$(nTh).off('mouseleave.ColResize').on('mouseleave.ColResize', function (e) {
e.preventDefault();
that._fnMouseLeave.call(that, e, nTh);
});
},
/**
*
* @param e Mouse event
* @param nTh TH element that the mouse is over
*/
"_fnMouseEnter": function (e, nTh) {
var that = this;
if(!that.s.isMousedown) {
//Once the mouse has entered the cell add mouse move event to see if the mouse is over resize handle
$(nTh).off('mousemove.ColResizeHandle').on('mousemove.ColResizeHandle', function (e) {
e.preventDefault();
that._fnResizeHandleCheck.call(that, e, nTh);
});
}
},
/**
* Clear mouse events when the mouse has left the th
* @param e Mouse event
* @param nTh TH element that the mouse has just left
*/
"_fnMouseLeave": function (e, nTh) {
//Once the mouse has left the TH make suure to remove the mouse move listener
$(nTh).off('mousemove.ColResizeHandle');
},
/**
* Mouse down on a TH element in the table header
* @method _fnMouseDown
* @param event e Mouse event
* @param element nTh TH element to be resized
* @returns void
* @private
*/
"_fnMouseDown": function (e, nTh) {
var that = this;
that.s.isMousedown = true;
/* Store information about the mouse position */
var target = $(e.target).closest('th, td');
var offset = target.offset();
/* Store information about the mouse position for resize calculations in mouse move function */
this.s.mouse.startX = e.pageX;
this.s.mouse.startY = e.pageY;
//Store the indexes of the columns the mouse is down on
var idx = that.dom.resizeCol[0].cellIndex;
// the last column has no 'right-side' neighbour
// with fixed this can make the table smaller
if (that.dom.resizeColNeighbour[0] === undefined){
var idxNeighbour = 0;
} else {
var idxNeighbour = that.dom.resizeColNeighbour[0].cellIndex;
}
if (idx === undefined) {
return;
}
this.s.mouse.targetIndex = idx;
this.s.mouse.targetColumn = this.s.dt.aoColumns[ idx ];
this.s.mouse.neighbourIndex = idxNeighbour;
this.s.mouse.neighbourColumn = this.s.dt.aoColumns[ idxNeighbour ];
/* Add event handlers to the document */
$(document)
.off('mousemove.ColResize').on('mousemove.ColResize', function (e) {
that._fnMouseMove.call(that, e);
})
.off('mouseup.ColResize').on('mouseup.ColResize', function (e) {
that._fnMouseUp.call(that, e);
});
},
/**
* Deal with a mouse move event while dragging to resize a column
* @method _fnMouseMove
* @param e Mouse event
* @returns void
* @private
*/
"_fnMouseMove": function (e) {
var that = this;
var offset = $(that.s.mouse.targetColumn.nTh).offset();
var relativeX = (e.pageX - offset.left);
var distFromLeft = relativeX;
var distFromRight = $(that.s.mouse.targetColumn.nTh).outerWidth() - relativeX - 1;
//Change in mouse x position
var dx = e.pageX - that.s.mouse.startX;
//Get the minimum width of the column (default minimum 10px)
var minColumnWidth = Math.max(parseInt($(that.s.mouse.targetColumn.nTh).css('min-width')), 10);
//Store the previous width of the column
var prevWidth = $(that.s.mouse.targetColumn.nTh).width();
//As long as the cursor is past the handle, resize the columns
if ((dx > 0 && distFromRight <= 0) || (dx < 0 && distFromRight >= 0)) {
if (!that.s.init.tableWidthFixed) {
//As long as the width is larger than the minimum
var newColWidth = Math.max(minColumnWidth, prevWidth + dx);
//Get the width difference (take into account the columns minimum width)
var widthDiff = newColWidth - prevWidth;
var colResizeIdx = parseInt(that.dom.resizeCol.attr("data-column-index"));
//Set datatable column widths
that.s.mouse.targetColumn.sWidthOrig = that.s.mouse.targetColumn.sWidth = that.s.mouse.targetColumn.width = newColWidth + "px";
var domCols = $(that.s.dt.nTableWrapper).find("th[data-column-index='"+colResizeIdx+"']");
//For each table expand the width by the same amount as the column
//This accounts for other datatable plugins like FixedColumns
domCols.parents("table").each(function() {
if(!$(this).parent().hasClass("DTFC_LeftBodyLiner")) {
var newWidth = $(this).width() + widthDiff;
$(this).width(newWidth);
} else {
var newWidth =$(that.s.dt.nTableWrapper).find(".DTFC_LeftHeadWrapper").children("table").width();
$(this).parents(".DTFC_LeftWrapper").width(newWidth);
$(this).parent().width(newWidth+15);
$(this).width(newWidth);
}
});
//Apply the new width to the columns after the table has been resized
domCols.width(that.s.mouse.targetColumn.width);
} else {
//A neighbour column must exist in order to resize a column in a table with a fixed width
if (that.s.mouse.neighbourColumn) {
//Get the minimum width of the neighbor column (default minimum 10px)
var minColumnNeighbourWidth = Math.max(parseInt($(that.s.mouse.neighbourColumn.nTh).css('min-width')), 10);
//Store the previous width of the neighbour column
var prevNeighbourWidth = $(that.s.mouse.neighbourColumn.nTh).width();
//As long as the width is larger than the minimum
var newColWidth = Math.max(minColumnWidth, prevWidth + dx);
var newColNeighbourWidth = Math.max(minColumnNeighbourWidth, prevNeighbourWidth - dx);
//Get the width difference (take into account the columns minimum width)
var widthDiff = newColWidth - prevWidth;
var widthDiffNeighbour = newColNeighbourWidth - prevNeighbourWidth;
//Get the column index for the column being changed
var colResizeIdx = parseInt(that.dom.resizeCol.attr("data-column-index"));
var neighbourColResizeIdx = parseInt(that.dom.resizeColNeighbour.attr("data-column-index"));
//Set datatable column widths
that.s.mouse.neighbourColumn.sWidthOrig = that.s.mouse.neighbourColumn.sWidth = that.s.mouse.neighbourColumn.width = newColNeighbourWidth + "px";
that.s.mouse.targetColumn.sWidthOrig = that.s.mouse.targetColumn.sWidth = that.s.mouse.targetColumn.width = newColWidth + "px";
//Get list of columns based on column index in all affected tables tables. This accounts for other plugins like FixedColumns
var domNeighbourCols = $(that.s.dt.nTableWrapper).find("th[data-column-index='" + neighbourColResizeIdx + "']");
var domCols = $(that.s.dt.nTableWrapper).find("th[data-column-index='" + colResizeIdx + "']");
//If dx if positive (the width is getting larger) shrink the neighbour columns first
if(dx>0) {
domNeighbourCols.width(that.s.mouse.neighbourColumn.width);
domCols.width(that.s.mouse.targetColumn.width);
} else {
//Apply the new width to the columns then to the neighbour columns
domCols.width(that.s.mouse.targetColumn.width);
domNeighbourCols.width(that.s.mouse.neighbourColumn.width);
}
}
}
}
that.s.mouse.startX = e.pageX;
},
/**
* Check to see if the mouse is over the resize handle area
* @param e
* @param nTh
*/
"_fnResizeHandleCheck": function (e, nTh) {
var that = this;
var offset = $(nTh).offset();
var relativeX = (e.pageX - offset.left);
var relativeY = (e.pageY - offset.top);
var distFromLeft = relativeX;
var distFromRight = $(nTh).outerWidth() - relativeX - 1;
var handleBuffer = this.s.init.handleWidth / 2;
var leftHandleOn = distFromLeft < handleBuffer;
var rightHandleOn = distFromRight < handleBuffer;
//If this is the first table cell
if ($(nTh).prev("th").length == 0) {
if(this.s.init.rtl)
rightHandleOn = false;
else
leftHandleOn = false;
}
//If this is the last cell and the table is fixed width don't let them expand the last cell directly
if ($(nTh).next("th").length == 0 && this.s.init.tableWidthFixed) {
if(this.s.init.rtl)
leftHandleOn = false;
else
rightHandleOn = false;
}
var resizeAvailable = leftHandleOn||rightHandleOn;
//If table is in right to left mode flip which TH is being resized
if (that.s.init.rtl) {
//Handle is to the left
if (leftHandleOn) {
that.dom.resizeCol = $(nTh);
that.dom.resizeColNeighbour = $(nTh).next();
} else if (rightHandleOn) {
that.dom.resizeCol = $(nTh).prev();
that.dom.resizeColNeighbour = $(nTh);
}
} else {
//Handle is to the right
if (rightHandleOn) {
that.dom.resizeCol = $(nTh);
that.dom.resizeColNeighbour = $(nTh).next();
} else if (leftHandleOn) {
that.dom.resizeCol = $(nTh).prev();
that.dom.resizeColNeighbour = $(nTh);
}
}
//If table width is fixed make sure both columns are resizable else just check the one.
if(this.s.init.tableWidthFixed)
resizeAvailable &= this.s.init.exclude.indexOf(parseInt($(that.dom.resizeCol).attr("data-column-index"))) == -1 && this.s.init.exclude.indexOf(parseInt($(that.dom.resizeColNeighbour).attr("data-column-index"))) == -1;
else
resizeAvailable &= this.s.init.exclude.indexOf(parseInt($(that.dom.resizeCol).attr("data-column-index"))) == -1;
$(nTh).off('mousedown.ColResize');
if (resizeAvailable) {
$(nTh).css("cursor", "ew-resize");
//Delay other mousedown events from the Reorder plugin
that._fnDelayEvents(null, nTh, "mousedown", "ColReorder");
that._fnDelayEvents("click", nTh, "click", "DT");
$(nTh).off('mousedown.ColResize').on('mousedown.ColResize', function (e) {
e.preventDefault();
that._fnMouseDown.call(that, e, nTh);
})
.off('click.ColResize').on('click.ColResize', function (e) {
that._fnClick.call(that, e);
});
} else {
$(nTh).css("cursor", "pointer");
$(nTh).off('mousedown.ColResize click.ColResize');
//Restore any events that were removed
that._fnRestoreEvents();
//This is to restore column sorting on click functionality
if (!that.s.isMousedown)
//Restore click event if mouse is not down
this._fnRestoreEvents("click");
}
},
"_fnClick": function (e) {
var that = this;
that.s.isMousedown = false;
e.stopImmediatePropagation();
},
/**
* Finish off the mouse drag
* @method _fnMouseUp
* @param e Mouse event
* @returns void
* @private
*/
"_fnMouseUp": function (e) {
var that = this;
that.s.isMousedown = false;
//Fix width of column to be the size the dom is limited to (for when user sets min-width on a column)
that.s.mouse.targetColumn.width = that.dom.resizeCol.width();
$(document).off('mousemove.ColResize mouseup.ColResize');
this.s.dt.oInstance.fnAdjustColumnSizing();
//Table width fix, prevents extra gaps between tables
var LeftWrapper = $(that.s.dt.nTableWrapper).find(".DTFC_LeftWrapper");
var DTFC_LeftWidth = LeftWrapper.width();
LeftWrapper.children(".DTFC_LeftHeadWrapper").children("table").width(DTFC_LeftWidth);
if (that.s.init.resizeCallback) {
that.s.init.resizeCallback.call(that, that.s.mouse.targetColumn);
}
},
/**
* Clean up ColResize memory references and event handlers
* @method _fnDestroy
* @returns void
* @private
*/
"_fnDestroy": function () {
var i, iLen;
for (i = 0, iLen = this.s.dt.aoDrawCallback.length; i < iLen; i++) {
if (this.s.dt.aoDrawCallback[i].sName === 'ColResize_Pre') {
this.s.dt.aoDrawCallback.splice(i, 1);
break;
}
}
$(this.s.dt.nTHead).find('*').off('.ColResize');
$.each(this.s.dt.aoColumns, function (i, column) {
$(column.nTh).removeAttr('data-column-index');
});
this.s.dt._colResize = null;
this.s = null;
},
/**
* Add a data attribute to the column headers, so we know the index of
* the row to be reordered. This allows fast detection of the index, and
* for this plug-in to work with FixedHeader which clones the nodes.
* @private
*/
"_fnSetColumnIndexes": function () {
$.each(this.s.dt.aoColumns, function (i, column) {
$(column.nTh).attr('data-column-index', i);
});
}
};
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Static parameters
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/**
* ColResize default settings for initialisation
* @namespace
* @static
*/
ColResize.defaults = {
/**
* Callback function that is fired when columns are resized
* @type function():void
* @default null
* @static
*/
"resizeCallback": null,
/**
* Exclude array for columns that are not resizable
* @property exclude
* @type array of indexes that are excluded from resizing
* @default []
*/
"exclude": [],
/**
* Check to see if user is using a fixed table width or dynamic
* if true:
* -Columns will resize themselves and their neighbour
* -If neighbour is excluded resize will not occur
* if false:
* -Columns will resize themselves and increase or decrease the width of the table accordingly
*/
"tableWidthFixed": true,
/**
* Width of the resize handle in pixels
* @property handleWidth
* @type int (pixels)
* @default 10
*/
"handleWidth": 10,
/**
* Right to left support, when true flips which column they are resizing on mouse down
* @property rtl
* @type bool
* @default false
*/
"rtl": false
};
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Constants
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/**
* ColResize version
* @constant version
* @type String
* @default As code
*/
ColResize.version = "0.0.10";
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* DataTables interfaces
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
// Expose
$.fn.dataTable.ColResize = ColResize;
$.fn.DataTable.ColResize = ColResize;
// Register a new feature with DataTables
if (typeof $.fn.dataTable == "function" &&
typeof $.fn.dataTableExt.fnVersionCheck == "function" &&
$.fn.dataTableExt.fnVersionCheck('1.9.3')) {
$.fn.dataTableExt.aoFeatures.push({
"fnInit": function (settings) {
var table = settings.oInstance;
if (!settings._colResize) {
var dtInit = settings.oInit;
var opts = dtInit.colResize || dtInit.oColResize || {};
new ColResize(settings, opts);
}
else {
table.oApi._fnLog(settings, 1, "ColResize attempted to initialise twice. Ignoring second");
}
return null;
/* No node for DataTables to insert */
},
"cFeature": "Z",
"sFeature": "ColResize"
});
} else {
alert("Warning: ColResize requires DataTables 1.9.3 or greater - www.datatables.net/download");
}
// API augmentation
if ($.fn.dataTable.Api) {
$.fn.dataTable.Api.register('colResize.reset()', function () {
return this.iterator('table', function (ctx) {
ctx._colResize.fnReset();
});
});
}
return ColResize;
}; // /factory
// Define as an AMD module if possible
if ( typeof define === 'function' && define.amd ) {
define( ['jquery', 'datatables'], factory );
}
else if ( typeof exports === 'object' ) {
// Node/CommonJS
factory( require('jquery'), require('datatables') );
}
else if (jQuery && !jQuery.fn.dataTable.ColResize) {
// Otherwise simply initialise as normal, stopping multiple evaluation
factory(jQuery, jQuery.fn.dataTable);
}
})(window, document);

15321
externals/datatables.js vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,160 +0,0 @@
/*! DataTables 1.10.7
* ©2008-2015 SpryMedia Ltd - datatables.net/license
*/
(function(Ea,Q,k){var P=function(h){function W(a){var b,c,e={};h.each(a,function(d){if((b=d.match(/^([^A-Z]+?)([A-Z])/))&&-1!=="a aa ai ao as b fn i m o s ".indexOf(b[1]+" "))c=d.replace(b[0],b[2].toLowerCase()),e[c]=d,"o"===b[1]&&W(a[d])});a._hungarianMap=e}function H(a,b,c){a._hungarianMap||W(a);var e;h.each(b,function(d){e=a._hungarianMap[d];if(e!==k&&(c||b[e]===k))"o"===e.charAt(0)?(b[e]||(b[e]={}),h.extend(!0,b[e],b[d]),H(a[e],b[e],c)):b[e]=b[d]})}function P(a){var b=m.defaults.oLanguage,c=a.sZeroRecords;
!a.sEmptyTable&&(c&&"No data available in table"===b.sEmptyTable)&&E(a,a,"sZeroRecords","sEmptyTable");!a.sLoadingRecords&&(c&&"Loading..."===b.sLoadingRecords)&&E(a,a,"sZeroRecords","sLoadingRecords");a.sInfoThousands&&(a.sThousands=a.sInfoThousands);(a=a.sDecimal)&&db(a)}function eb(a){A(a,"ordering","bSort");A(a,"orderMulti","bSortMulti");A(a,"orderClasses","bSortClasses");A(a,"orderCellsTop","bSortCellsTop");A(a,"order","aaSorting");A(a,"orderFixed","aaSortingFixed");A(a,"paging","bPaginate");
A(a,"pagingType","sPaginationType");A(a,"pageLength","iDisplayLength");A(a,"searching","bFilter");if(a=a.aoSearchCols)for(var b=0,c=a.length;b<c;b++)a[b]&&H(m.models.oSearch,a[b])}function fb(a){A(a,"orderable","bSortable");A(a,"orderData","aDataSort");A(a,"orderSequence","asSorting");A(a,"orderDataType","sortDataType");var b=a.aDataSort;b&&!h.isArray(b)&&(a.aDataSort=[b])}function gb(a){var a=a.oBrowser,b=h("<div/>").css({position:"absolute",top:0,left:0,height:1,width:1,overflow:"hidden"}).append(h("<div/>").css({position:"absolute",
top:1,left:1,width:100,overflow:"scroll"}).append(h('<div class="test"/>').css({width:"100%",height:10}))).appendTo("body"),c=b.find(".test");a.bScrollOversize=100===c[0].offsetWidth;a.bScrollbarLeft=1!==Math.round(c.offset().left);b.remove()}function hb(a,b,c,e,d,f){var g,j=!1;c!==k&&(g=c,j=!0);for(;e!==d;)a.hasOwnProperty(e)&&(g=j?b(g,a[e],e,a):a[e],j=!0,e+=f);return g}function Fa(a,b){var c=m.defaults.column,e=a.aoColumns.length,c=h.extend({},m.models.oColumn,c,{nTh:b?b:Q.createElement("th"),sTitle:c.sTitle?
c.sTitle:b?b.innerHTML:"",aDataSort:c.aDataSort?c.aDataSort:[e],mData:c.mData?c.mData:e,idx:e});a.aoColumns.push(c);c=a.aoPreSearchCols;c[e]=h.extend({},m.models.oSearch,c[e]);ka(a,e,h(b).data())}function ka(a,b,c){var b=a.aoColumns[b],e=a.oClasses,d=h(b.nTh);if(!b.sWidthOrig){b.sWidthOrig=d.attr("width")||null;var f=(d.attr("style")||"").match(/width:\s*(\d+[pxem%]+)/);f&&(b.sWidthOrig=f[1])}c!==k&&null!==c&&(fb(c),H(m.defaults.column,c),c.mDataProp!==k&&!c.mData&&(c.mData=c.mDataProp),c.sType&&
(b._sManualType=c.sType),c.className&&!c.sClass&&(c.sClass=c.className),h.extend(b,c),E(b,c,"sWidth","sWidthOrig"),c.iDataSort!==k&&(b.aDataSort=[c.iDataSort]),E(b,c,"aDataSort"));var g=b.mData,j=R(g),i=b.mRender?R(b.mRender):null,c=function(a){return"string"===typeof a&&-1!==a.indexOf("@")};b._bAttrSrc=h.isPlainObject(g)&&(c(g.sort)||c(g.type)||c(g.filter));b.fnGetData=function(a,b,c){var e=j(a,b,k,c);return i&&b?i(e,b,a,c):e};b.fnSetData=function(a,b,c){return S(g)(a,b,c)};"number"!==typeof g&&
(a._rowReadObject=!0);a.oFeatures.bSort||(b.bSortable=!1,d.addClass(e.sSortableNone));a=-1!==h.inArray("asc",b.asSorting);c=-1!==h.inArray("desc",b.asSorting);!b.bSortable||!a&&!c?(b.sSortingClass=e.sSortableNone,b.sSortingClassJUI=""):a&&!c?(b.sSortingClass=e.sSortableAsc,b.sSortingClassJUI=e.sSortJUIAscAllowed):!a&&c?(b.sSortingClass=e.sSortableDesc,b.sSortingClassJUI=e.sSortJUIDescAllowed):(b.sSortingClass=e.sSortable,b.sSortingClassJUI=e.sSortJUI)}function X(a){if(!1!==a.oFeatures.bAutoWidth){var b=
a.aoColumns;Ga(a);for(var c=0,e=b.length;c<e;c++)b[c].nTh.style.width=b[c].sWidth}b=a.oScroll;(""!==b.sY||""!==b.sX)&&Y(a);w(a,null,"column-sizing",[a])}function la(a,b){var c=Z(a,"bVisible");return"number"===typeof c[b]?c[b]:null}function $(a,b){var c=Z(a,"bVisible"),c=h.inArray(b,c);return-1!==c?c:null}function aa(a){return Z(a,"bVisible").length}function Z(a,b){var c=[];h.map(a.aoColumns,function(a,d){a[b]&&c.push(d)});return c}function Ha(a){var b=a.aoColumns,c=a.aoData,e=m.ext.type.detect,d,
f,g,j,i,h,l,q,n;d=0;for(f=b.length;d<f;d++)if(l=b[d],n=[],!l.sType&&l._sManualType)l.sType=l._sManualType;else if(!l.sType){g=0;for(j=e.length;g<j;g++){i=0;for(h=c.length;i<h;i++){n[i]===k&&(n[i]=x(a,i,d,"type"));q=e[g](n[i],a);if(!q&&g!==e.length-1)break;if("html"===q)break}if(q){l.sType=q;break}}l.sType||(l.sType="string")}}function ib(a,b,c,e){var d,f,g,j,i,o,l=a.aoColumns;if(b)for(d=b.length-1;0<=d;d--){o=b[d];var q=o.targets!==k?o.targets:o.aTargets;h.isArray(q)||(q=[q]);f=0;for(g=q.length;f<
g;f++)if("number"===typeof q[f]&&0<=q[f]){for(;l.length<=q[f];)Fa(a);e(q[f],o)}else if("number"===typeof q[f]&&0>q[f])e(l.length+q[f],o);else if("string"===typeof q[f]){j=0;for(i=l.length;j<i;j++)("_all"==q[f]||h(l[j].nTh).hasClass(q[f]))&&e(j,o)}}if(c){d=0;for(a=c.length;d<a;d++)e(d,c[d])}}function K(a,b,c,e){var d=a.aoData.length,f=h.extend(!0,{},m.models.oRow,{src:c?"dom":"data"});f._aData=b;a.aoData.push(f);for(var b=a.aoColumns,f=0,g=b.length;f<g;f++)c&&Ia(a,d,f,x(a,d,f)),b[f].sType=null;a.aiDisplayMaster.push(d);
(c||!a.oFeatures.bDeferRender)&&Ja(a,d,c,e);return d}function ma(a,b){var c;b instanceof h||(b=h(b));return b.map(function(b,d){c=na(a,d);return K(a,c.data,d,c.cells)})}function x(a,b,c,e){var d=a.iDraw,f=a.aoColumns[c],g=a.aoData[b]._aData,j=f.sDefaultContent,c=f.fnGetData(g,e,{settings:a,row:b,col:c});if(c===k)return a.iDrawError!=d&&null===j&&(I(a,0,"Requested unknown parameter "+("function"==typeof f.mData?"{function}":"'"+f.mData+"'")+" for row "+b,4),a.iDrawError=d),j;if((c===g||null===c)&&
null!==j)c=j;else if("function"===typeof c)return c.call(g);return null===c&&"display"==e?"":c}function Ia(a,b,c,e){a.aoColumns[c].fnSetData(a.aoData[b]._aData,e,{settings:a,row:b,col:c})}function Ka(a){return h.map(a.match(/(\\.|[^\.])+/g),function(a){return a.replace(/\\./g,".")})}function R(a){if(h.isPlainObject(a)){var b={};h.each(a,function(a,c){c&&(b[a]=R(c))});return function(a,c,f,g){var j=b[c]||b._;return j!==k?j(a,c,f,g):a}}if(null===a)return function(a){return a};if("function"===typeof a)return function(b,
c,f,g){return a(b,c,f,g)};if("string"===typeof a&&(-1!==a.indexOf(".")||-1!==a.indexOf("[")||-1!==a.indexOf("("))){var c=function(a,b,f){var g,j;if(""!==f){j=Ka(f);for(var i=0,h=j.length;i<h;i++){f=j[i].match(ba);g=j[i].match(T);if(f){j[i]=j[i].replace(ba,"");""!==j[i]&&(a=a[j[i]]);g=[];j.splice(0,i+1);j=j.join(".");i=0;for(h=a.length;i<h;i++)g.push(c(a[i],b,j));a=f[0].substring(1,f[0].length-1);a=""===a?g:g.join(a);break}else if(g){j[i]=j[i].replace(T,"");a=a[j[i]]();continue}if(null===a||a[j[i]]===
k)return k;a=a[j[i]]}}return a};return function(b,d){return c(b,d,a)}}return function(b){return b[a]}}function S(a){if(h.isPlainObject(a))return S(a._);if(null===a)return function(){};if("function"===typeof a)return function(b,e,d){a(b,"set",e,d)};if("string"===typeof a&&(-1!==a.indexOf(".")||-1!==a.indexOf("[")||-1!==a.indexOf("("))){var b=function(a,e,d){var d=Ka(d),f;f=d[d.length-1];for(var g,j,i=0,h=d.length-1;i<h;i++){g=d[i].match(ba);j=d[i].match(T);if(g){d[i]=d[i].replace(ba,"");a[d[i]]=[];
f=d.slice();f.splice(0,i+1);g=f.join(".");j=0;for(h=e.length;j<h;j++)f={},b(f,e[j],g),a[d[i]].push(f);return}j&&(d[i]=d[i].replace(T,""),a=a[d[i]](e));if(null===a[d[i]]||a[d[i]]===k)a[d[i]]={};a=a[d[i]]}if(f.match(T))a[f.replace(T,"")](e);else a[f.replace(ba,"")]=e};return function(c,e){return b(c,e,a)}}return function(b,e){b[a]=e}}function La(a){return D(a.aoData,"_aData")}function oa(a){a.aoData.length=0;a.aiDisplayMaster.length=0;a.aiDisplay.length=0}function pa(a,b,c){for(var e=-1,d=0,f=a.length;d<
f;d++)a[d]==b?e=d:a[d]>b&&a[d]--; -1!=e&&c===k&&a.splice(e,1)}function ca(a,b,c,e){var d=a.aoData[b],f,g=function(c,f){for(;c.childNodes.length;)c.removeChild(c.firstChild);c.innerHTML=x(a,b,f,"display")};if("dom"===c||(!c||"auto"===c)&&"dom"===d.src)d._aData=na(a,d,e,e===k?k:d._aData).data;else{var j=d.anCells;if(j)if(e!==k)g(j[e],e);else{c=0;for(f=j.length;c<f;c++)g(j[c],c)}}d._aSortData=null;d._aFilterData=null;g=a.aoColumns;if(e!==k)g[e].sType=null;else{c=0;for(f=g.length;c<f;c++)g[c].sType=null;
Ma(d)}}function na(a,b,c,e){var d=[],f=b.firstChild,g,j=0,i,o=a.aoColumns,l=a._rowReadObject,e=e||l?{}:[],q=function(a,b){if("string"===typeof a){var c=a.indexOf("@");-1!==c&&(c=a.substring(c+1),S(a)(e,b.getAttribute(c)))}},a=function(a){if(c===k||c===j)g=o[j],i=h.trim(a.innerHTML),g&&g._bAttrSrc?(S(g.mData._)(e,i),q(g.mData.sort,a),q(g.mData.type,a),q(g.mData.filter,a)):l?(g._setter||(g._setter=S(g.mData)),g._setter(e,i)):e[j]=i;j++};if(f)for(;f;){b=f.nodeName.toUpperCase();if("TD"==b||"TH"==b)a(f),
d.push(f);f=f.nextSibling}else{d=b.anCells;f=0;for(b=d.length;f<b;f++)a(d[f])}return{data:e,cells:d}}function Ja(a,b,c,e){var d=a.aoData[b],f=d._aData,g=[],j,i,h,l,q;if(null===d.nTr){j=c||Q.createElement("tr");d.nTr=j;d.anCells=g;j._DT_RowIndex=b;Ma(d);l=0;for(q=a.aoColumns.length;l<q;l++){h=a.aoColumns[l];i=c?e[l]:Q.createElement(h.sCellType);g.push(i);if(!c||h.mRender||h.mData!==l)i.innerHTML=x(a,b,l,"display");h.sClass&&(i.className+=" "+h.sClass);h.bVisible&&!c?j.appendChild(i):!h.bVisible&&c&&
i.parentNode.removeChild(i);h.fnCreatedCell&&h.fnCreatedCell.call(a.oInstance,i,x(a,b,l),f,b,l)}w(a,"aoRowCreatedCallback",null,[j,f,b])}d.nTr.setAttribute("role","row")}function Ma(a){var b=a.nTr,c=a._aData;if(b){c.DT_RowId&&(b.id=c.DT_RowId);if(c.DT_RowClass){var e=c.DT_RowClass.split(" ");a.__rowc=a.__rowc?Na(a.__rowc.concat(e)):e;h(b).removeClass(a.__rowc.join(" ")).addClass(c.DT_RowClass)}c.DT_RowAttr&&h(b).attr(c.DT_RowAttr);c.DT_RowData&&h(b).data(c.DT_RowData)}}function jb(a){var b,c,e,d,
f,g=a.nTHead,j=a.nTFoot,i=0===h("th, td",g).length,o=a.oClasses,l=a.aoColumns;i&&(d=h("<tr/>").appendTo(g));b=0;for(c=l.length;b<c;b++)f=l[b],e=h(f.nTh).addClass(f.sClass),i&&e.appendTo(d),a.oFeatures.bSort&&(e.addClass(f.sSortingClass),!1!==f.bSortable&&(e.attr("tabindex",a.iTabIndex).attr("aria-controls",a.sTableId),Oa(a,f.nTh,b))),f.sTitle!=e.html()&&e.html(f.sTitle),Pa(a,"header")(a,e,f,o);i&&da(a.aoHeader,g);h(g).find(">tr").attr("role","row");h(g).find(">tr>th, >tr>td").addClass(o.sHeaderTH);
h(j).find(">tr>th, >tr>td").addClass(o.sFooterTH);if(null!==j){a=a.aoFooter[0];b=0;for(c=a.length;b<c;b++)f=l[b],f.nTf=a[b].cell,f.sClass&&h(f.nTf).addClass(f.sClass)}}function ea(a,b,c){var e,d,f,g=[],j=[],i=a.aoColumns.length,o;if(b){c===k&&(c=!1);e=0;for(d=b.length;e<d;e++){g[e]=b[e].slice();g[e].nTr=b[e].nTr;for(f=i-1;0<=f;f--)!a.aoColumns[f].bVisible&&!c&&g[e].splice(f,1);j.push([])}e=0;for(d=g.length;e<d;e++){if(a=g[e].nTr)for(;f=a.firstChild;)a.removeChild(f);f=0;for(b=g[e].length;f<b;f++)if(o=
i=1,j[e][f]===k){a.appendChild(g[e][f].cell);for(j[e][f]=1;g[e+i]!==k&&g[e][f].cell==g[e+i][f].cell;)j[e+i][f]=1,i++;for(;g[e][f+o]!==k&&g[e][f].cell==g[e][f+o].cell;){for(c=0;c<i;c++)j[e+c][f+o]=1;o++}h(g[e][f].cell).attr("rowspan",i).attr("colspan",o)}}}}function M(a){var b=w(a,"aoPreDrawCallback","preDraw",[a]);if(-1!==h.inArray(!1,b))C(a,!1);else{var b=[],c=0,e=a.asStripeClasses,d=e.length,f=a.oLanguage,g=a.iInitDisplayStart,j="ssp"==B(a),i=a.aiDisplay;a.bDrawing=!0;g!==k&&-1!==g&&(a._iDisplayStart=
j?g:g>=a.fnRecordsDisplay()?0:g,a.iInitDisplayStart=-1);var g=a._iDisplayStart,o=a.fnDisplayEnd();if(a.bDeferLoading)a.bDeferLoading=!1,a.iDraw++,C(a,!1);else if(j){if(!a.bDestroying&&!kb(a))return}else a.iDraw++;if(0!==i.length){f=j?a.aoData.length:o;for(j=j?0:g;j<f;j++){var l=i[j],q=a.aoData[l];null===q.nTr&&Ja(a,l);l=q.nTr;if(0!==d){var n=e[c%d];q._sRowStripe!=n&&(h(l).removeClass(q._sRowStripe).addClass(n),q._sRowStripe=n)}w(a,"aoRowCallback",null,[l,q._aData,c,j]);b.push(l);c++}}else c=f.sZeroRecords,
1==a.iDraw&&"ajax"==B(a)?c=f.sLoadingRecords:f.sEmptyTable&&0===a.fnRecordsTotal()&&(c=f.sEmptyTable),b[0]=h("<tr/>",{"class":d?e[0]:""}).append(h("<td />",{valign:"top",colSpan:aa(a),"class":a.oClasses.sRowEmpty}).html(c))[0];w(a,"aoHeaderCallback","header",[h(a.nTHead).children("tr")[0],La(a),g,o,i]);w(a,"aoFooterCallback","footer",[h(a.nTFoot).children("tr")[0],La(a),g,o,i]);e=h(a.nTBody);e.children().detach();e.append(h(b));w(a,"aoDrawCallback","draw",[a]);a.bSorted=!1;a.bFiltered=!1;a.bDrawing=
!1}}function N(a,b){var c=a.oFeatures,e=c.bFilter;c.bSort&&lb(a);e?fa(a,a.oPreviousSearch):a.aiDisplay=a.aiDisplayMaster.slice();!0!==b&&(a._iDisplayStart=0);a._drawHold=b;M(a);a._drawHold=!1}function mb(a){var b=a.oClasses,c=h(a.nTable),c=h("<div/>").insertBefore(c),e=a.oFeatures,d=h("<div/>",{id:a.sTableId+"_wrapper","class":b.sWrapper+(a.nTFoot?"":" "+b.sNoFooter)});a.nHolding=c[0];a.nTableWrapper=d[0];a.nTableReinsertBefore=a.nTable.nextSibling;for(var f=a.sDom.split(""),g,j,i,o,l,q,n=0;n<f.length;n++){g=
null;j=f[n];if("<"==j){i=h("<div/>")[0];o=f[n+1];if("'"==o||'"'==o){l="";for(q=2;f[n+q]!=o;)l+=f[n+q],q++;"H"==l?l=b.sJUIHeader:"F"==l&&(l=b.sJUIFooter);-1!=l.indexOf(".")?(o=l.split("."),i.id=o[0].substr(1,o[0].length-1),i.className=o[1]):"#"==l.charAt(0)?i.id=l.substr(1,l.length-1):i.className=l;n+=q}d.append(i);d=h(i)}else if(">"==j)d=d.parent();else if("l"==j&&e.bPaginate&&e.bLengthChange)g=nb(a);else if("f"==j&&e.bFilter)g=ob(a);else if("r"==j&&e.bProcessing)g=pb(a);else if("t"==j)g=qb(a);else if("i"==
j&&e.bInfo)g=rb(a);else if("p"==j&&e.bPaginate)g=sb(a);else if(0!==m.ext.feature.length){i=m.ext.feature;q=0;for(o=i.length;q<o;q++)if(j==i[q].cFeature){g=i[q].fnInit(a);break}}g&&(i=a.aanFeatures,i[j]||(i[j]=[]),i[j].push(g),d.append(g))}c.replaceWith(d)}function da(a,b){var c=h(b).children("tr"),e,d,f,g,j,i,o,l,q,n;a.splice(0,a.length);f=0;for(i=c.length;f<i;f++)a.push([]);f=0;for(i=c.length;f<i;f++){e=c[f];for(d=e.firstChild;d;){if("TD"==d.nodeName.toUpperCase()||"TH"==d.nodeName.toUpperCase()){l=
1*d.getAttribute("colspan");q=1*d.getAttribute("rowspan");l=!l||0===l||1===l?1:l;q=!q||0===q||1===q?1:q;g=0;for(j=a[f];j[g];)g++;o=g;n=1===l?!0:!1;for(j=0;j<l;j++)for(g=0;g<q;g++)a[f+g][o+j]={cell:d,unique:n},a[f+g].nTr=e}d=d.nextSibling}}}function qa(a,b,c){var e=[];c||(c=a.aoHeader,b&&(c=[],da(c,b)));for(var b=0,d=c.length;b<d;b++)for(var f=0,g=c[b].length;f<g;f++)if(c[b][f].unique&&(!e[f]||!a.bSortCellsTop))e[f]=c[b][f].cell;return e}function ra(a,b,c){w(a,"aoServerParams","serverParams",[b]);
if(b&&h.isArray(b)){var e={},d=/(.*?)\[\]$/;h.each(b,function(a,b){var c=b.name.match(d);c?(c=c[0],e[c]||(e[c]=[]),e[c].push(b.value)):e[b.name]=b.value});b=e}var f,g=a.ajax,j=a.oInstance,i=function(b){w(a,null,"xhr",[a,b,a.jqXHR]);c(b)};if(h.isPlainObject(g)&&g.data){f=g.data;var o=h.isFunction(f)?f(b,a):f,b=h.isFunction(f)&&o?o:h.extend(!0,b,o);delete g.data}o={data:b,success:function(b){var c=b.error||b.sError;c&&I(a,0,c);a.json=b;i(b)},dataType:"json",cache:!1,type:a.sServerMethod,error:function(b,
c){var f=w(a,null,"xhr",[a,null,a.jqXHR]);-1===h.inArray(!0,f)&&("parsererror"==c?I(a,0,"Invalid JSON response",1):4===b.readyState&&I(a,0,"Ajax error",7));C(a,!1)}};a.oAjaxData=b;w(a,null,"preXhr",[a,b]);a.fnServerData?a.fnServerData.call(j,a.sAjaxSource,h.map(b,function(a,b){return{name:b,value:a}}),i,a):a.sAjaxSource||"string"===typeof g?a.jqXHR=h.ajax(h.extend(o,{url:g||a.sAjaxSource})):h.isFunction(g)?a.jqXHR=g.call(j,b,i,a):(a.jqXHR=h.ajax(h.extend(o,g)),g.data=f)}function kb(a){return a.bAjaxDataGet?
(a.iDraw++,C(a,!0),ra(a,tb(a),function(b){ub(a,b)}),!1):!0}function tb(a){var b=a.aoColumns,c=b.length,e=a.oFeatures,d=a.oPreviousSearch,f=a.aoPreSearchCols,g,j=[],i,o,l,q=U(a);g=a._iDisplayStart;i=!1!==e.bPaginate?a._iDisplayLength:-1;var n=function(a,b){j.push({name:a,value:b})};n("sEcho",a.iDraw);n("iColumns",c);n("sColumns",D(b,"sName").join(","));n("iDisplayStart",g);n("iDisplayLength",i);var k={draw:a.iDraw,columns:[],order:[],start:g,length:i,search:{value:d.sSearch,regex:d.bRegex}};for(g=
0;g<c;g++)o=b[g],l=f[g],i="function"==typeof o.mData?"function":o.mData,k.columns.push({data:i,name:o.sName,searchable:o.bSearchable,orderable:o.bSortable,search:{value:l.sSearch,regex:l.bRegex}}),n("mDataProp_"+g,i),e.bFilter&&(n("sSearch_"+g,l.sSearch),n("bRegex_"+g,l.bRegex),n("bSearchable_"+g,o.bSearchable)),e.bSort&&n("bSortable_"+g,o.bSortable);e.bFilter&&(n("sSearch",d.sSearch),n("bRegex",d.bRegex));e.bSort&&(h.each(q,function(a,b){k.order.push({column:b.col,dir:b.dir});n("iSortCol_"+a,b.col);
n("sSortDir_"+a,b.dir)}),n("iSortingCols",q.length));b=m.ext.legacy.ajax;return null===b?a.sAjaxSource?j:k:b?j:k}function ub(a,b){var c=sa(a,b),e=b.sEcho!==k?b.sEcho:b.draw,d=b.iTotalRecords!==k?b.iTotalRecords:b.recordsTotal,f=b.iTotalDisplayRecords!==k?b.iTotalDisplayRecords:b.recordsFiltered;if(e){if(1*e<a.iDraw)return;a.iDraw=1*e}oa(a);a._iRecordsTotal=parseInt(d,10);a._iRecordsDisplay=parseInt(f,10);e=0;for(d=c.length;e<d;e++)K(a,c[e]);a.aiDisplay=a.aiDisplayMaster.slice();a.bAjaxDataGet=!1;
M(a);a._bInitComplete||ta(a,b);a.bAjaxDataGet=!0;C(a,!1)}function sa(a,b){var c=h.isPlainObject(a.ajax)&&a.ajax.dataSrc!==k?a.ajax.dataSrc:a.sAjaxDataProp;return"data"===c?b.aaData||b[c]:""!==c?R(c)(b):b}function ob(a){var b=a.oClasses,c=a.sTableId,e=a.oLanguage,d=a.oPreviousSearch,f=a.aanFeatures,g='<input type="search" class="'+b.sFilterInput+'"/>',j=e.sSearch,j=j.match(/_INPUT_/)?j.replace("_INPUT_",g):j+g,b=h("<div/>",{id:!f.f?c+"_filter":null,"class":b.sFilter}).append(h("<label/>").append(j)),
f=function(){var b=!this.value?"":this.value;b!=d.sSearch&&(fa(a,{sSearch:b,bRegex:d.bRegex,bSmart:d.bSmart,bCaseInsensitive:d.bCaseInsensitive}),a._iDisplayStart=0,M(a))},g=null!==a.searchDelay?a.searchDelay:"ssp"===B(a)?400:0,i=h("input",b).val(d.sSearch).attr("placeholder",e.sSearchPlaceholder).bind("keyup.DT search.DT input.DT paste.DT cut.DT",g?ua(f,g):f).bind("keypress.DT",function(a){if(13==a.keyCode)return!1}).attr("aria-controls",c);h(a.nTable).on("search.dt.DT",function(b,c){if(a===c)try{i[0]!==
Q.activeElement&&i.val(d.sSearch)}catch(f){}});return b[0]}function fa(a,b,c){var e=a.oPreviousSearch,d=a.aoPreSearchCols,f=function(a){e.sSearch=a.sSearch;e.bRegex=a.bRegex;e.bSmart=a.bSmart;e.bCaseInsensitive=a.bCaseInsensitive};Ha(a);if("ssp"!=B(a)){vb(a,b.sSearch,c,b.bEscapeRegex!==k?!b.bEscapeRegex:b.bRegex,b.bSmart,b.bCaseInsensitive);f(b);for(b=0;b<d.length;b++)wb(a,d[b].sSearch,b,d[b].bEscapeRegex!==k?!d[b].bEscapeRegex:d[b].bRegex,d[b].bSmart,d[b].bCaseInsensitive);xb(a)}else f(b);a.bFiltered=
!0;w(a,null,"search",[a])}function xb(a){for(var b=m.ext.search,c=a.aiDisplay,e,d,f=0,g=b.length;f<g;f++){for(var j=[],i=0,h=c.length;i<h;i++)d=c[i],e=a.aoData[d],b[f](a,e._aFilterData,d,e._aData,i)&&j.push(d);c.length=0;c.push.apply(c,j)}}function wb(a,b,c,e,d,f){if(""!==b)for(var g=a.aiDisplay,e=Qa(b,e,d,f),d=g.length-1;0<=d;d--)b=a.aoData[g[d]]._aFilterData[c],e.test(b)||g.splice(d,1)}function vb(a,b,c,e,d,f){var e=Qa(b,e,d,f),d=a.oPreviousSearch.sSearch,f=a.aiDisplayMaster,g;0!==m.ext.search.length&&
(c=!0);g=yb(a);if(0>=b.length)a.aiDisplay=f.slice();else{if(g||c||d.length>b.length||0!==b.indexOf(d)||a.bSorted)a.aiDisplay=f.slice();b=a.aiDisplay;for(c=b.length-1;0<=c;c--)e.test(a.aoData[b[c]]._sFilterRow)||b.splice(c,1)}}function Qa(a,b,c,e){a=b?a:va(a);c&&(a="^(?=.*?"+h.map(a.match(/"[^"]+"|[^ ]+/g)||[""],function(a){if('"'===a.charAt(0))var b=a.match(/^"(.*)"$/),a=b?b[1]:a;return a.replace('"',"")}).join(")(?=.*?")+").*$");return RegExp(a,e?"i":"")}function va(a){return a.replace(Yb,"\\$1")}
function yb(a){var b=a.aoColumns,c,e,d,f,g,j,i,h,l=m.ext.type.search;c=!1;e=0;for(f=a.aoData.length;e<f;e++)if(h=a.aoData[e],!h._aFilterData){j=[];d=0;for(g=b.length;d<g;d++)c=b[d],c.bSearchable?(i=x(a,e,d,"filter"),l[c.sType]&&(i=l[c.sType](i)),null===i&&(i=""),"string"!==typeof i&&i.toString&&(i=i.toString())):i="",i.indexOf&&-1!==i.indexOf("&")&&(wa.innerHTML=i,i=Zb?wa.textContent:wa.innerText),i.replace&&(i=i.replace(/[\r\n]/g,"")),j.push(i);h._aFilterData=j;h._sFilterRow=j.join(" ");c=!0}return c}
function zb(a){return{search:a.sSearch,smart:a.bSmart,regex:a.bRegex,caseInsensitive:a.bCaseInsensitive}}function Ab(a){return{sSearch:a.search,bSmart:a.smart,bRegex:a.regex,bCaseInsensitive:a.caseInsensitive}}function rb(a){var b=a.sTableId,c=a.aanFeatures.i,e=h("<div/>",{"class":a.oClasses.sInfo,id:!c?b+"_info":null});c||(a.aoDrawCallback.push({fn:Bb,sName:"information"}),e.attr("role","status").attr("aria-live","polite"),h(a.nTable).attr("aria-describedby",b+"_info"));return e[0]}function Bb(a){var b=
a.aanFeatures.i;if(0!==b.length){var c=a.oLanguage,e=a._iDisplayStart+1,d=a.fnDisplayEnd(),f=a.fnRecordsTotal(),g=a.fnRecordsDisplay(),j=g?c.sInfo:c.sInfoEmpty;g!==f&&(j+=" "+c.sInfoFiltered);j+=c.sInfoPostFix;j=Cb(a,j);c=c.fnInfoCallback;null!==c&&(j=c.call(a.oInstance,a,e,d,f,g,j));h(b).html(j)}}function Cb(a,b){var c=a.fnFormatNumber,e=a._iDisplayStart+1,d=a._iDisplayLength,f=a.fnRecordsDisplay(),g=-1===d;return b.replace(/_START_/g,c.call(a,e)).replace(/_END_/g,c.call(a,a.fnDisplayEnd())).replace(/_MAX_/g,
c.call(a,a.fnRecordsTotal())).replace(/_TOTAL_/g,c.call(a,f)).replace(/_PAGE_/g,c.call(a,g?1:Math.ceil(e/d))).replace(/_PAGES_/g,c.call(a,g?1:Math.ceil(f/d)))}function ga(a){var b,c,e=a.iInitDisplayStart,d=a.aoColumns,f;c=a.oFeatures;if(a.bInitialised){mb(a);jb(a);ea(a,a.aoHeader);ea(a,a.aoFooter);C(a,!0);c.bAutoWidth&&Ga(a);b=0;for(c=d.length;b<c;b++)f=d[b],f.sWidth&&(f.nTh.style.width=s(f.sWidth));N(a);d=B(a);"ssp"!=d&&("ajax"==d?ra(a,[],function(c){var f=sa(a,c);for(b=0;b<f.length;b++)K(a,f[b]);
a.iInitDisplayStart=e;N(a);C(a,!1);ta(a,c)},a):(C(a,!1),ta(a)))}else setTimeout(function(){ga(a)},200)}function ta(a,b){a._bInitComplete=!0;b&&X(a);w(a,"aoInitComplete","init",[a,b])}function Ra(a,b){var c=parseInt(b,10);a._iDisplayLength=c;Sa(a);w(a,null,"length",[a,c])}function nb(a){for(var b=a.oClasses,c=a.sTableId,e=a.aLengthMenu,d=h.isArray(e[0]),f=d?e[0]:e,e=d?e[1]:e,d=h("<select/>",{name:c+"_length","aria-controls":c,"class":b.sLengthSelect}),g=0,j=f.length;g<j;g++)d[0][g]=new Option(e[g],
f[g]);var i=h("<div><label/></div>").addClass(b.sLength);a.aanFeatures.l||(i[0].id=c+"_length");i.children().append(a.oLanguage.sLengthMenu.replace("_MENU_",d[0].outerHTML));h("select",i).val(a._iDisplayLength).bind("change.DT",function(){Ra(a,h(this).val());M(a)});h(a.nTable).bind("length.dt.DT",function(b,c,f){a===c&&h("select",i).val(f)});return i[0]}function sb(a){var b=a.sPaginationType,c=m.ext.pager[b],e="function"===typeof c,d=function(a){M(a)},b=h("<div/>").addClass(a.oClasses.sPaging+b)[0],
f=a.aanFeatures;e||c.fnInit(a,b,d);f.p||(b.id=a.sTableId+"_paginate",a.aoDrawCallback.push({fn:function(a){if(e){var b=a._iDisplayStart,i=a._iDisplayLength,h=a.fnRecordsDisplay(),l=-1===i,b=l?0:Math.ceil(b/i),i=l?1:Math.ceil(h/i),h=c(b,i),q,l=0;for(q=f.p.length;l<q;l++)Pa(a,"pageButton")(a,f.p[l],l,h,b,i)}else c.fnUpdate(a,d)},sName:"pagination"}));return b}function Ta(a,b,c){var e=a._iDisplayStart,d=a._iDisplayLength,f=a.fnRecordsDisplay();0===f||-1===d?e=0:"number"===typeof b?(e=b*d,e>f&&(e=0)):
"first"==b?e=0:"previous"==b?(e=0<=d?e-d:0,0>e&&(e=0)):"next"==b?e+d<f&&(e+=d):"last"==b?e=Math.floor((f-1)/d)*d:I(a,0,"Unknown paging action: "+b,5);b=a._iDisplayStart!==e;a._iDisplayStart=e;b&&(w(a,null,"page",[a]),c&&M(a));return b}function pb(a){return h("<div/>",{id:!a.aanFeatures.r?a.sTableId+"_processing":null,"class":a.oClasses.sProcessing}).html(a.oLanguage.sProcessing).insertBefore(a.nTable)[0]}function C(a,b){a.oFeatures.bProcessing&&h(a.aanFeatures.r).css("display",b?"block":"none");w(a,
null,"processing",[a,b])}function qb(a){var b=h(a.nTable);b.attr("role","grid");var c=a.oScroll;if(""===c.sX&&""===c.sY)return a.nTable;var e=c.sX,d=c.sY,f=a.oClasses,g=b.children("caption"),j=g.length?g[0]._captionSide:null,i=h(b[0].cloneNode(!1)),o=h(b[0].cloneNode(!1)),l=b.children("tfoot");c.sX&&"100%"===b.attr("width")&&b.removeAttr("width");l.length||(l=null);c=h("<div/>",{"class":f.sScrollWrapper}).append(h("<div/>",{"class":f.sScrollHead}).css({overflow:"hidden",position:"relative",border:0,
width:e?!e?null:s(e):"100%"}).append(h("<div/>",{"class":f.sScrollHeadInner}).css({"box-sizing":"content-box",width:c.sXInner||"100%"}).append(i.removeAttr("id").css("margin-left",0).append("top"===j?g:null).append(b.children("thead"))))).append(h("<div/>",{"class":f.sScrollBody}).css({overflow:"auto",height:!d?null:s(d),width:!e?null:s(e)}).append(b));l&&c.append(h("<div/>",{"class":f.sScrollFoot}).css({overflow:"hidden",border:0,width:e?!e?null:s(e):"100%"}).append(h("<div/>",{"class":f.sScrollFootInner}).append(o.removeAttr("id").css("margin-left",
0).append("bottom"===j?g:null).append(b.children("tfoot")))));var b=c.children(),q=b[0],f=b[1],n=l?b[2]:null;if(e)h(f).on("scroll.DT",function(){var a=this.scrollLeft;q.scrollLeft=a;l&&(n.scrollLeft=a)});a.nScrollHead=q;a.nScrollBody=f;a.nScrollFoot=n;a.aoDrawCallback.push({fn:Y,sName:"scrolling"});return c[0]}function Y(a){var b=a.oScroll,c=b.sX,e=b.sXInner,d=b.sY,f=b.iBarWidth,g=h(a.nScrollHead),j=g[0].style,i=g.children("div"),o=i[0].style,l=i.children("table"),i=a.nScrollBody,q=h(i),n=i.style,
k=h(a.nScrollFoot).children("div"),p=k.children("table"),m=h(a.nTHead),r=h(a.nTable),t=r[0],O=t.style,L=a.nTFoot?h(a.nTFoot):null,ha=a.oBrowser,w=ha.bScrollOversize,v,u,y,x,z,A=[],B=[],C=[],D,E=function(a){a=a.style;a.paddingTop="0";a.paddingBottom="0";a.borderTopWidth="0";a.borderBottomWidth="0";a.height=0};r.children("thead, tfoot").remove();z=m.clone().prependTo(r);v=m.find("tr");y=z.find("tr");z.find("th, td").removeAttr("tabindex");L&&(x=L.clone().prependTo(r),u=L.find("tr"),x=x.find("tr"));
c||(n.width="100%",g[0].style.width="100%");h.each(qa(a,z),function(b,c){D=la(a,b);c.style.width=a.aoColumns[D].sWidth});L&&G(function(a){a.style.width=""},x);b.bCollapse&&""!==d&&(n.height=q[0].offsetHeight+m[0].offsetHeight+"px");g=r.outerWidth();if(""===c){if(O.width="100%",w&&(r.find("tbody").height()>i.offsetHeight||"scroll"==q.css("overflow-y")))O.width=s(r.outerWidth()-f)}else""!==e?O.width=s(e):g==q.width()&&q.height()<r.height()?(O.width=s(g-f),r.outerWidth()>g-f&&(O.width=s(g))):O.width=
s(g);g=r.outerWidth();G(E,y);G(function(a){C.push(a.innerHTML);A.push(s(h(a).css("width")))},y);G(function(a,b){a.style.width=A[b]},v);h(y).height(0);L&&(G(E,x),G(function(a){B.push(s(h(a).css("width")))},x),G(function(a,b){a.style.width=B[b]},u),h(x).height(0));G(function(a,b){a.innerHTML='<div class="dataTables_sizing" style="height:0;overflow:hidden;">'+C[b]+"</div>";a.style.width=A[b]},y);L&&G(function(a,b){a.innerHTML="";a.style.width=B[b]},x);if(r.outerWidth()<g){u=i.scrollHeight>i.offsetHeight||
"scroll"==q.css("overflow-y")?g+f:g;if(w&&(i.scrollHeight>i.offsetHeight||"scroll"==q.css("overflow-y")))O.width=s(u-f);(""===c||""!==e)&&I(a,1,"Possible column misalignment",6)}else u="100%";n.width=s(u);j.width=s(u);L&&(a.nScrollFoot.style.width=s(u));!d&&w&&(n.height=s(t.offsetHeight+f));d&&b.bCollapse&&(n.height=s(d),b=c&&t.offsetWidth>i.offsetWidth?f:0,t.offsetHeight<i.offsetHeight&&(n.height=s(t.offsetHeight+b)));b=r.outerWidth();l[0].style.width=s(b);o.width=s(b);l=r.height()>i.clientHeight||
"scroll"==q.css("overflow-y");ha="padding"+(ha.bScrollbarLeft?"Left":"Right");o[ha]=l?f+"px":"0px";L&&(p[0].style.width=s(b),k[0].style.width=s(b),k[0].style[ha]=l?f+"px":"0px");q.scroll();if((a.bSorted||a.bFiltered)&&!a._drawHold)i.scrollTop=0}function G(a,b,c){for(var e=0,d=0,f=b.length,g,j;d<f;){g=b[d].firstChild;for(j=c?c[d].firstChild:null;g;)1===g.nodeType&&(c?a(g,j,e):a(g,e),e++),g=g.nextSibling,j=c?j.nextSibling:null;d++}}function Ga(a){var b=a.nTable,c=a.aoColumns,e=a.oScroll,d=e.sY,f=e.sX,
g=e.sXInner,j=c.length,e=Z(a,"bVisible"),i=h("th",a.nTHead),o=b.getAttribute("width"),l=b.parentNode,k=!1,n,m;(n=b.style.width)&&-1!==n.indexOf("%")&&(o=n);for(n=0;n<e.length;n++)m=c[e[n]],null!==m.sWidth&&(m.sWidth=Db(m.sWidthOrig,l),k=!0);if(!k&&!f&&!d&&j==aa(a)&&j==i.length)for(n=0;n<j;n++)c[n].sWidth=s(i.eq(n).width());else{j=h(b).clone().css("visibility","hidden").removeAttr("id");j.find("tbody tr").remove();var p=h("<tr/>").appendTo(j.find("tbody"));j.find("tfoot th, tfoot td").css("width",
"");i=qa(a,j.find("thead")[0]);for(n=0;n<e.length;n++)m=c[e[n]],i[n].style.width=null!==m.sWidthOrig&&""!==m.sWidthOrig?s(m.sWidthOrig):"";if(a.aoData.length)for(n=0;n<e.length;n++)k=e[n],m=c[k],h(Eb(a,k)).clone(!1).append(m.sContentPadding).appendTo(p);j.appendTo(l);f&&g?j.width(g):f?(j.css("width","auto"),j.width()<l.offsetWidth&&j.width(l.offsetWidth)):d?j.width(l.offsetWidth):o&&j.width(o);Fb(a,j[0]);if(f){for(n=g=0;n<e.length;n++)m=c[e[n]],d=h(i[n]).outerWidth(),g+=null===m.sWidthOrig?d:parseInt(m.sWidth,
10)+d-h(i[n]).width();j.width(s(g));b.style.width=s(g)}for(n=0;n<e.length;n++)if(m=c[e[n]],d=h(i[n]).width())m.sWidth=s(d);b.style.width=s(j.css("width"));j.remove()}o&&(b.style.width=s(o));if((o||f)&&!a._reszEvt)b=function(){h(Ea).bind("resize.DT-"+a.sInstance,ua(function(){X(a)}))},a.oBrowser.bScrollOversize?setTimeout(b,1E3):b(),a._reszEvt=!0}function ua(a,b){var c=b!==k?b:200,e,d;return function(){var b=this,g=+new Date,j=arguments;e&&g<e+c?(clearTimeout(d),d=setTimeout(function(){e=k;a.apply(b,
j)},c)):(e=g,a.apply(b,j))}}function Db(a,b){if(!a)return 0;var c=h("<div/>").css("width",s(a)).appendTo(b||Q.body),e=c[0].offsetWidth;c.remove();return e}function Fb(a,b){var c=a.oScroll;if(c.sX||c.sY)c=!c.sX?c.iBarWidth:0,b.style.width=s(h(b).outerWidth()-c)}function Eb(a,b){var c=Gb(a,b);if(0>c)return null;var e=a.aoData[c];return!e.nTr?h("<td/>").html(x(a,c,b,"display"))[0]:e.anCells[b]}function Gb(a,b){for(var c,e=-1,d=-1,f=0,g=a.aoData.length;f<g;f++)c=x(a,f,b,"display")+"",c=c.replace($b,""),
c.length>e&&(e=c.length,d=f);return d}function s(a){return null===a?"0px":"number"==typeof a?0>a?"0px":a+"px":a.match(/\d$/)?a+"px":a}function Hb(){var a=m.__scrollbarWidth;if(a===k){var b=h("<p/>").css({position:"absolute",top:0,left:0,width:"100%",height:150,padding:0,overflow:"scroll",visibility:"hidden"}).appendTo("body"),a=b[0].offsetWidth-b[0].clientWidth;m.__scrollbarWidth=a;b.remove()}return a}function U(a){var b,c,e=[],d=a.aoColumns,f,g,j,i;b=a.aaSortingFixed;c=h.isPlainObject(b);var o=[];
f=function(a){a.length&&!h.isArray(a[0])?o.push(a):o.push.apply(o,a)};h.isArray(b)&&f(b);c&&b.pre&&f(b.pre);f(a.aaSorting);c&&b.post&&f(b.post);for(a=0;a<o.length;a++){i=o[a][0];f=d[i].aDataSort;b=0;for(c=f.length;b<c;b++)g=f[b],j=d[g].sType||"string",o[a]._idx===k&&(o[a]._idx=h.inArray(o[a][1],d[g].asSorting)),e.push({src:i,col:g,dir:o[a][1],index:o[a]._idx,type:j,formatter:m.ext.type.order[j+"-pre"]})}return e}function lb(a){var b,c,e=[],d=m.ext.type.order,f=a.aoData,g=0,j,i=a.aiDisplayMaster,h;
Ha(a);h=U(a);b=0;for(c=h.length;b<c;b++)j=h[b],j.formatter&&g++,Ib(a,j.col);if("ssp"!=B(a)&&0!==h.length){b=0;for(c=i.length;b<c;b++)e[i[b]]=b;g===h.length?i.sort(function(a,b){var c,d,g,j,i=h.length,k=f[a]._aSortData,m=f[b]._aSortData;for(g=0;g<i;g++)if(j=h[g],c=k[j.col],d=m[j.col],c=c<d?-1:c>d?1:0,0!==c)return"asc"===j.dir?c:-c;c=e[a];d=e[b];return c<d?-1:c>d?1:0}):i.sort(function(a,b){var c,g,j,i,k=h.length,m=f[a]._aSortData,r=f[b]._aSortData;for(j=0;j<k;j++)if(i=h[j],c=m[i.col],g=r[i.col],i=d[i.type+
"-"+i.dir]||d["string-"+i.dir],c=i(c,g),0!==c)return c;c=e[a];g=e[b];return c<g?-1:c>g?1:0})}a.bSorted=!0}function Jb(a){for(var b,c,e=a.aoColumns,d=U(a),a=a.oLanguage.oAria,f=0,g=e.length;f<g;f++){c=e[f];var j=c.asSorting;b=c.sTitle.replace(/<.*?>/g,"");var i=c.nTh;i.removeAttribute("aria-sort");c.bSortable&&(0<d.length&&d[0].col==f?(i.setAttribute("aria-sort","asc"==d[0].dir?"ascending":"descending"),c=j[d[0].index+1]||j[0]):c=j[0],b+="asc"===c?a.sSortAscending:a.sSortDescending);i.setAttribute("aria-label",
b)}}function Ua(a,b,c,e){var d=a.aaSorting,f=a.aoColumns[b].asSorting,g=function(a,b){var c=a._idx;c===k&&(c=h.inArray(a[1],f));return c+1<f.length?c+1:b?null:0};"number"===typeof d[0]&&(d=a.aaSorting=[d]);c&&a.oFeatures.bSortMulti?(c=h.inArray(b,D(d,"0")),-1!==c?(b=g(d[c],!0),null===b&&1===d.length&&(b=0),null===b?d.splice(c,1):(d[c][1]=f[b],d[c]._idx=b)):(d.push([b,f[0],0]),d[d.length-1]._idx=0)):d.length&&d[0][0]==b?(b=g(d[0]),d.length=1,d[0][1]=f[b],d[0]._idx=b):(d.length=0,d.push([b,f[0]]),d[0]._idx=
0);N(a);"function"==typeof e&&e(a)}function Oa(a,b,c,e){var d=a.aoColumns[c];Va(b,{},function(b){!1!==d.bSortable&&(a.oFeatures.bProcessing?(C(a,!0),setTimeout(function(){Ua(a,c,b.shiftKey,e);"ssp"!==B(a)&&C(a,!1)},0)):Ua(a,c,b.shiftKey,e))})}function xa(a){var b=a.aLastSort,c=a.oClasses.sSortColumn,e=U(a),d=a.oFeatures,f,g;if(d.bSort&&d.bSortClasses){d=0;for(f=b.length;d<f;d++)g=b[d].src,h(D(a.aoData,"anCells",g)).removeClass(c+(2>d?d+1:3));d=0;for(f=e.length;d<f;d++)g=e[d].src,h(D(a.aoData,"anCells",
g)).addClass(c+(2>d?d+1:3))}a.aLastSort=e}function Ib(a,b){var c=a.aoColumns[b],e=m.ext.order[c.sSortDataType],d;e&&(d=e.call(a.oInstance,a,b,$(a,b)));for(var f,g=m.ext.type.order[c.sType+"-pre"],j=0,i=a.aoData.length;j<i;j++)if(c=a.aoData[j],c._aSortData||(c._aSortData=[]),!c._aSortData[b]||e)f=e?d[j]:x(a,j,b,"sort"),c._aSortData[b]=g?g(f):f}function ya(a){if(a.oFeatures.bStateSave&&!a.bDestroying){var b={time:+new Date,start:a._iDisplayStart,length:a._iDisplayLength,order:h.extend(!0,[],a.aaSorting),
search:zb(a.oPreviousSearch),columns:h.map(a.aoColumns,function(b,e){return{visible:b.bVisible,search:zb(a.aoPreSearchCols[e])}})};w(a,"aoStateSaveParams","stateSaveParams",[a,b]);a.oSavedState=b;a.fnStateSaveCallback.call(a.oInstance,a,b)}}function Kb(a){var b,c,e=a.aoColumns;if(a.oFeatures.bStateSave){var d=a.fnStateLoadCallback.call(a.oInstance,a);if(d&&d.time&&(b=w(a,"aoStateLoadParams","stateLoadParams",[a,d]),-1===h.inArray(!1,b)&&(b=a.iStateDuration,!(0<b&&d.time<+new Date-1E3*b)&&e.length===
d.columns.length))){a.oLoadedState=h.extend(!0,{},d);d.start!==k&&(a._iDisplayStart=d.start,a.iInitDisplayStart=d.start);d.length!==k&&(a._iDisplayLength=d.length);d.order!==k&&(a.aaSorting=[],h.each(d.order,function(b,c){a.aaSorting.push(c[0]>=e.length?[0,c[1]]:c)}));d.search!==k&&h.extend(a.oPreviousSearch,Ab(d.search));b=0;for(c=d.columns.length;b<c;b++){var f=d.columns[b];f.visible!==k&&(e[b].bVisible=f.visible);f.search!==k&&h.extend(a.aoPreSearchCols[b],Ab(f.search))}w(a,"aoStateLoaded","stateLoaded",
[a,d])}}}function za(a){var b=m.settings,a=h.inArray(a,D(b,"nTable"));return-1!==a?b[a]:null}function I(a,b,c,e){c="DataTables warning: "+(null!==a?"table id="+a.sTableId+" - ":"")+c;e&&(c+=". For more information about this error, please see http://datatables.net/tn/"+e);if(b)Ea.console&&console.log&&console.log(c);else if(b=m.ext,b=b.sErrMode||b.errMode,w(a,null,"error",[a,e,c]),"alert"==b)alert(c);else{if("throw"==b)throw Error(c);"function"==typeof b&&b(a,e,c)}}function E(a,b,c,e){h.isArray(c)?
h.each(c,function(c,f){h.isArray(f)?E(a,b,f[0],f[1]):E(a,b,f)}):(e===k&&(e=c),b[c]!==k&&(a[e]=b[c]))}function Lb(a,b,c){var e,d;for(d in b)b.hasOwnProperty(d)&&(e=b[d],h.isPlainObject(e)?(h.isPlainObject(a[d])||(a[d]={}),h.extend(!0,a[d],e)):a[d]=c&&"data"!==d&&"aaData"!==d&&h.isArray(e)?e.slice():e);return a}function Va(a,b,c){h(a).bind("click.DT",b,function(b){a.blur();c(b)}).bind("keypress.DT",b,function(a){13===a.which&&(a.preventDefault(),c(a))}).bind("selectstart.DT",function(){return!1})}function z(a,
b,c,e){c&&a[b].push({fn:c,sName:e})}function w(a,b,c,e){var d=[];b&&(d=h.map(a[b].slice().reverse(),function(b){return b.fn.apply(a.oInstance,e)}));null!==c&&(b=h.Event(c+".dt"),h(a.nTable).trigger(b,e),d.push(b.result));return d}function Sa(a){var b=a._iDisplayStart,c=a.fnDisplayEnd(),e=a._iDisplayLength;b>=c&&(b=c-e);b-=b%e;if(-1===e||0>b)b=0;a._iDisplayStart=b}function Pa(a,b){var c=a.renderer,e=m.ext.renderer[b];return h.isPlainObject(c)&&c[b]?e[c[b]]||e._:"string"===typeof c?e[c]||e._:e._}function B(a){return a.oFeatures.bServerSide?
"ssp":a.ajax||a.sAjaxSource?"ajax":"dom"}function Wa(a,b){var c=[],c=Mb.numbers_length,e=Math.floor(c/2);b<=c?c=V(0,b):a<=e?(c=V(0,c-2),c.push("ellipsis"),c.push(b-1)):(a>=b-1-e?c=V(b-(c-2),b):(c=V(a-e+2,a+e-1),c.push("ellipsis"),c.push(b-1)),c.splice(0,0,"ellipsis"),c.splice(0,0,0));c.DT_el="span";return c}function db(a){h.each({num:function(b){return Aa(b,a)},"num-fmt":function(b){return Aa(b,a,Xa)},"html-num":function(b){return Aa(b,a,Ba)},"html-num-fmt":function(b){return Aa(b,a,Ba,Xa)}},function(b,
c){u.type.order[b+a+"-pre"]=c;b.match(/^html\-/)&&(u.type.search[b+a]=u.type.search.html)})}function Nb(a){return function(){var b=[za(this[m.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return m.ext.internal[a].apply(this,b)}}var m,u,t,r,v,Ya={},Ob=/[\r\n]/g,Ba=/<.*?>/g,ac=/^[\w\+\-]/,bc=/[\w\+\-]$/,Yb=RegExp("(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\|\\$|\\^|\\-)","g"),Xa=/[',$\u00a3\u20ac\u00a5%\u2009\u202F\u20BD\u20a9\u20BArfk]/gi,J=function(a){return!a||!0===a||
"-"===a?!0:!1},Pb=function(a){var b=parseInt(a,10);return!isNaN(b)&&isFinite(a)?b:null},Qb=function(a,b){Ya[b]||(Ya[b]=RegExp(va(b),"g"));return"string"===typeof a&&"."!==b?a.replace(/\./g,"").replace(Ya[b],"."):a},Za=function(a,b,c){var e="string"===typeof a;if(J(a))return!0;b&&e&&(a=Qb(a,b));c&&e&&(a=a.replace(Xa,""));return!isNaN(parseFloat(a))&&isFinite(a)},Rb=function(a,b,c){return J(a)?!0:!(J(a)||"string"===typeof a)?null:Za(a.replace(Ba,""),b,c)?!0:null},D=function(a,b,c){var e=[],d=0,f=a.length;
if(c!==k)for(;d<f;d++)a[d]&&a[d][b]&&e.push(a[d][b][c]);else for(;d<f;d++)a[d]&&e.push(a[d][b]);return e},ia=function(a,b,c,e){var d=[],f=0,g=b.length;if(e!==k)for(;f<g;f++)a[b[f]][c]&&d.push(a[b[f]][c][e]);else for(;f<g;f++)d.push(a[b[f]][c]);return d},V=function(a,b){var c=[],e;b===k?(b=0,e=a):(e=b,b=a);for(var d=b;d<e;d++)c.push(d);return c},Sb=function(a){for(var b=[],c=0,e=a.length;c<e;c++)a[c]&&b.push(a[c]);return b},Na=function(a){var b=[],c,e,d=a.length,f,g=0;e=0;a:for(;e<d;e++){c=a[e];for(f=
0;f<g;f++)if(b[f]===c)continue a;b.push(c);g++}return b},A=function(a,b,c){a[b]!==k&&(a[c]=a[b])},ba=/\[.*?\]$/,T=/\(\)$/,wa=h("<div>")[0],Zb=wa.textContent!==k,$b=/<.*?>/g;m=function(a){this.$=function(a,b){return this.api(!0).$(a,b)};this._=function(a,b){return this.api(!0).rows(a,b).data()};this.api=function(a){return a?new t(za(this[u.iApiIndex])):new t(this)};this.fnAddData=function(a,b){var c=this.api(!0),e=h.isArray(a)&&(h.isArray(a[0])||h.isPlainObject(a[0]))?c.rows.add(a):c.row.add(a);(b===
k||b)&&c.draw();return e.flatten().toArray()};this.fnAdjustColumnSizing=function(a){var b=this.api(!0).columns.adjust(),c=b.settings()[0],e=c.oScroll;a===k||a?b.draw(!1):(""!==e.sX||""!==e.sY)&&Y(c)};this.fnClearTable=function(a){var b=this.api(!0).clear();(a===k||a)&&b.draw()};this.fnClose=function(a){this.api(!0).row(a).child.hide()};this.fnDeleteRow=function(a,b,c){var e=this.api(!0),a=e.rows(a),d=a.settings()[0],h=d.aoData[a[0][0]];a.remove();b&&b.call(this,d,h);(c===k||c)&&e.draw();return h};
this.fnDestroy=function(a){this.api(!0).destroy(a)};this.fnDraw=function(a){this.api(!0).draw(a)};this.fnFilter=function(a,b,c,e,d,h){d=this.api(!0);null===b||b===k?d.search(a,c,e,h):d.column(b).search(a,c,e,h);d.draw()};this.fnGetData=function(a,b){var c=this.api(!0);if(a!==k){var e=a.nodeName?a.nodeName.toLowerCase():"";return b!==k||"td"==e||"th"==e?c.cell(a,b).data():c.row(a).data()||null}return c.data().toArray()};this.fnGetNodes=function(a){var b=this.api(!0);return a!==k?b.row(a).node():b.rows().nodes().flatten().toArray()};
this.fnGetPosition=function(a){var b=this.api(!0),c=a.nodeName.toUpperCase();return"TR"==c?b.row(a).index():"TD"==c||"TH"==c?(a=b.cell(a).index(),[a.row,a.columnVisible,a.column]):null};this.fnIsOpen=function(a){return this.api(!0).row(a).child.isShown()};this.fnOpen=function(a,b,c){return this.api(!0).row(a).child(b,c).show().child()[0]};this.fnPageChange=function(a,b){var c=this.api(!0).page(a);(b===k||b)&&c.draw(!1)};this.fnSetColumnVis=function(a,b,c){a=this.api(!0).column(a).visible(b);(c===
k||c)&&a.columns.adjust().draw()};this.fnSettings=function(){return za(this[u.iApiIndex])};this.fnSort=function(a){this.api(!0).order(a).draw()};this.fnSortListener=function(a,b,c){this.api(!0).order.listener(a,b,c)};this.fnUpdate=function(a,b,c,e,d){var h=this.api(!0);c===k||null===c?h.row(b).data(a):h.cell(b,c).data(a);(d===k||d)&&h.columns.adjust();(e===k||e)&&h.draw();return 0};this.fnVersionCheck=u.fnVersionCheck;var b=this,c=a===k,e=this.length;c&&(a={});this.oApi=this.internal=u.internal;for(var d in m.ext.internal)d&&
(this[d]=Nb(d));this.each(function(){var d={},d=1<e?Lb(d,a,!0):a,g=0,j,i=this.getAttribute("id"),o=!1,l=m.defaults,q=h(this);if("table"!=this.nodeName.toLowerCase())I(null,0,"Non-table node initialisation ("+this.nodeName+")",2);else{eb(l);fb(l.column);H(l,l,!0);H(l.column,l.column,!0);H(l,h.extend(d,q.data()));var n=m.settings,g=0;for(j=n.length;g<j;g++){var r=n[g];if(r.nTable==this||r.nTHead.parentNode==this||r.nTFoot&&r.nTFoot.parentNode==this){g=d.bRetrieve!==k?d.bRetrieve:l.bRetrieve;if(c||g)return r.oInstance;
if(d.bDestroy!==k?d.bDestroy:l.bDestroy){r.oInstance.fnDestroy();break}else{I(r,0,"Cannot reinitialise DataTable",3);return}}if(r.sTableId==this.id){n.splice(g,1);break}}if(null===i||""===i)this.id=i="DataTables_Table_"+m.ext._unique++;var p=h.extend(!0,{},m.models.oSettings,{sDestroyWidth:q[0].style.width,sInstance:i,sTableId:i});p.nTable=this;p.oApi=b.internal;p.oInit=d;n.push(p);p.oInstance=1===b.length?b:q.dataTable();eb(d);d.oLanguage&&P(d.oLanguage);d.aLengthMenu&&!d.iDisplayLength&&(d.iDisplayLength=
h.isArray(d.aLengthMenu[0])?d.aLengthMenu[0][0]:d.aLengthMenu[0]);d=Lb(h.extend(!0,{},l),d);E(p.oFeatures,d,"bPaginate bLengthChange bFilter bSort bSortMulti bInfo bProcessing bAutoWidth bSortClasses bServerSide bDeferRender".split(" "));E(p,d,["asStripeClasses","ajax","fnServerData","fnFormatNumber","sServerMethod","aaSorting","aaSortingFixed","aLengthMenu","sPaginationType","sAjaxSource","sAjaxDataProp","iStateDuration","sDom","bSortCellsTop","iTabIndex","fnStateLoadCallback","fnStateSaveCallback",
"renderer","searchDelay",["iCookieDuration","iStateDuration"],["oSearch","oPreviousSearch"],["aoSearchCols","aoPreSearchCols"],["iDisplayLength","_iDisplayLength"],["bJQueryUI","bJUI"]]);E(p.oScroll,d,[["sScrollX","sX"],["sScrollXInner","sXInner"],["sScrollY","sY"],["bScrollCollapse","bCollapse"]]);E(p.oLanguage,d,"fnInfoCallback");z(p,"aoDrawCallback",d.fnDrawCallback,"user");z(p,"aoServerParams",d.fnServerParams,"user");z(p,"aoStateSaveParams",d.fnStateSaveParams,"user");z(p,"aoStateLoadParams",
d.fnStateLoadParams,"user");z(p,"aoStateLoaded",d.fnStateLoaded,"user");z(p,"aoRowCallback",d.fnRowCallback,"user");z(p,"aoRowCreatedCallback",d.fnCreatedRow,"user");z(p,"aoHeaderCallback",d.fnHeaderCallback,"user");z(p,"aoFooterCallback",d.fnFooterCallback,"user");z(p,"aoInitComplete",d.fnInitComplete,"user");z(p,"aoPreDrawCallback",d.fnPreDrawCallback,"user");i=p.oClasses;d.bJQueryUI?(h.extend(i,m.ext.oJUIClasses,d.oClasses),d.sDom===l.sDom&&"lfrtip"===l.sDom&&(p.sDom='<"H"lfr>t<"F"ip>'),p.renderer)?
h.isPlainObject(p.renderer)&&!p.renderer.header&&(p.renderer.header="jqueryui"):p.renderer="jqueryui":h.extend(i,m.ext.classes,d.oClasses);q.addClass(i.sTable);if(""!==p.oScroll.sX||""!==p.oScroll.sY)p.oScroll.iBarWidth=Hb();!0===p.oScroll.sX&&(p.oScroll.sX="100%");p.iInitDisplayStart===k&&(p.iInitDisplayStart=d.iDisplayStart,p._iDisplayStart=d.iDisplayStart);null!==d.iDeferLoading&&(p.bDeferLoading=!0,g=h.isArray(d.iDeferLoading),p._iRecordsDisplay=g?d.iDeferLoading[0]:d.iDeferLoading,p._iRecordsTotal=
g?d.iDeferLoading[1]:d.iDeferLoading);var t=p.oLanguage;h.extend(!0,t,d.oLanguage);""!==t.sUrl&&(h.ajax({dataType:"json",url:t.sUrl,success:function(a){P(a);H(l.oLanguage,a);h.extend(true,t,a);ga(p)},error:function(){ga(p)}}),o=!0);null===d.asStripeClasses&&(p.asStripeClasses=[i.sStripeOdd,i.sStripeEven]);var g=p.asStripeClasses,s=q.children("tbody").find("tr").eq(0);-1!==h.inArray(!0,h.map(g,function(a){return s.hasClass(a)}))&&(h("tbody tr",this).removeClass(g.join(" ")),p.asDestroyStripes=g.slice());
n=[];g=this.getElementsByTagName("thead");0!==g.length&&(da(p.aoHeader,g[0]),n=qa(p));if(null===d.aoColumns){r=[];g=0;for(j=n.length;g<j;g++)r.push(null)}else r=d.aoColumns;g=0;for(j=r.length;g<j;g++)Fa(p,n?n[g]:null);ib(p,d.aoColumnDefs,r,function(a,b){ka(p,a,b)});if(s.length){var u=function(a,b){return a.getAttribute("data-"+b)!==null?b:null};h.each(na(p,s[0]).cells,function(a,b){var c=p.aoColumns[a];if(c.mData===a){var d=u(b,"sort")||u(b,"order"),e=u(b,"filter")||u(b,"search");if(d!==null||e!==
null){c.mData={_:a+".display",sort:d!==null?a+".@data-"+d:k,type:d!==null?a+".@data-"+d:k,filter:e!==null?a+".@data-"+e:k};ka(p,a)}}})}var v=p.oFeatures;d.bStateSave&&(v.bStateSave=!0,Kb(p,d),z(p,"aoDrawCallback",ya,"state_save"));if(d.aaSorting===k){n=p.aaSorting;g=0;for(j=n.length;g<j;g++)n[g][1]=p.aoColumns[g].asSorting[0]}xa(p);v.bSort&&z(p,"aoDrawCallback",function(){if(p.bSorted){var a=U(p),b={};h.each(a,function(a,c){b[c.src]=c.dir});w(p,null,"order",[p,a,b]);Jb(p)}});z(p,"aoDrawCallback",
function(){(p.bSorted||B(p)==="ssp"||v.bDeferRender)&&xa(p)},"sc");gb(p);g=q.children("caption").each(function(){this._captionSide=q.css("caption-side")});j=q.children("thead");0===j.length&&(j=h("<thead/>").appendTo(this));p.nTHead=j[0];j=q.children("tbody");0===j.length&&(j=h("<tbody/>").appendTo(this));p.nTBody=j[0];j=q.children("tfoot");if(0===j.length&&0<g.length&&(""!==p.oScroll.sX||""!==p.oScroll.sY))j=h("<tfoot/>").appendTo(this);0===j.length||0===j.children().length?q.addClass(i.sNoFooter):
0<j.length&&(p.nTFoot=j[0],da(p.aoFooter,p.nTFoot));if(d.aaData)for(g=0;g<d.aaData.length;g++)K(p,d.aaData[g]);else(p.bDeferLoading||"dom"==B(p))&&ma(p,h(p.nTBody).children("tr"));p.aiDisplay=p.aiDisplayMaster.slice();p.bInitialised=!0;!1===o&&ga(p)}});b=null;return this};var Tb=[],y=Array.prototype,cc=function(a){var b,c,e=m.settings,d=h.map(e,function(a){return a.nTable});if(a){if(a.nTable&&a.oApi)return[a];if(a.nodeName&&"table"===a.nodeName.toLowerCase())return b=h.inArray(a,d),-1!==b?[e[b]]:
null;if(a&&"function"===typeof a.settings)return a.settings().toArray();"string"===typeof a?c=h(a):a instanceof h&&(c=a)}else return[];if(c)return c.map(function(){b=h.inArray(this,d);return-1!==b?e[b]:null}).toArray()};t=function(a,b){if(!(this instanceof t))return new t(a,b);var c=[],e=function(a){(a=cc(a))&&c.push.apply(c,a)};if(h.isArray(a))for(var d=0,f=a.length;d<f;d++)e(a[d]);else e(a);this.context=Na(c);b&&this.push.apply(this,b.toArray?b.toArray():b);this.selector={rows:null,cols:null,opts:null};
t.extend(this,this,Tb)};m.Api=t;t.prototype={any:function(){return 0!==this.flatten().length},concat:y.concat,context:[],each:function(a){for(var b=0,c=this.length;b<c;b++)a.call(this,this[b],b,this);return this},eq:function(a){var b=this.context;return b.length>a?new t(b[a],this[a]):null},filter:function(a){var b=[];if(y.filter)b=y.filter.call(this,a,this);else for(var c=0,e=this.length;c<e;c++)a.call(this,this[c],c,this)&&b.push(this[c]);return new t(this.context,b)},flatten:function(){var a=[];
return new t(this.context,a.concat.apply(a,this.toArray()))},join:y.join,indexOf:y.indexOf||function(a,b){for(var c=b||0,e=this.length;c<e;c++)if(this[c]===a)return c;return-1},iterator:function(a,b,c,e){var d=[],f,g,h,i,o,l=this.context,q,n,m=this.selector;"string"===typeof a&&(e=c,c=b,b=a,a=!1);g=0;for(h=l.length;g<h;g++){var p=new t(l[g]);if("table"===b)f=c.call(p,l[g],g),f!==k&&d.push(f);else if("columns"===b||"rows"===b)f=c.call(p,l[g],this[g],g),f!==k&&d.push(f);else if("column"===b||"column-rows"===
b||"row"===b||"cell"===b){n=this[g];"column-rows"===b&&(q=Ca(l[g],m.opts));i=0;for(o=n.length;i<o;i++)f=n[i],f="cell"===b?c.call(p,l[g],f.row,f.column,g,i):c.call(p,l[g],f,g,i,q),f!==k&&d.push(f)}}return d.length||e?(a=new t(l,a?d.concat.apply([],d):d),b=a.selector,b.rows=m.rows,b.cols=m.cols,b.opts=m.opts,a):this},lastIndexOf:y.lastIndexOf||function(a,b){return this.indexOf.apply(this.toArray.reverse(),arguments)},length:0,map:function(a){var b=[];if(y.map)b=y.map.call(this,a,this);else for(var c=
0,e=this.length;c<e;c++)b.push(a.call(this,this[c],c));return new t(this.context,b)},pluck:function(a){return this.map(function(b){return b[a]})},pop:y.pop,push:y.push,reduce:y.reduce||function(a,b){return hb(this,a,b,0,this.length,1)},reduceRight:y.reduceRight||function(a,b){return hb(this,a,b,this.length-1,-1,-1)},reverse:y.reverse,selector:null,shift:y.shift,sort:y.sort,splice:y.splice,toArray:function(){return y.slice.call(this)},to$:function(){return h(this)},toJQuery:function(){return h(this)},
unique:function(){return new t(this.context,Na(this))},unshift:y.unshift};t.extend=function(a,b,c){if(c.length&&b&&(b instanceof t||b.__dt_wrapper)){var e,d,f,g=function(a,b,c){return function(){var d=b.apply(a,arguments);t.extend(d,d,c.methodExt);return d}};e=0;for(d=c.length;e<d;e++)f=c[e],b[f.name]="function"===typeof f.val?g(a,f.val,f):h.isPlainObject(f.val)?{}:f.val,b[f.name].__dt_wrapper=!0,t.extend(a,b[f.name],f.propExt)}};t.register=r=function(a,b){if(h.isArray(a))for(var c=0,e=a.length;c<
e;c++)t.register(a[c],b);else for(var d=a.split("."),f=Tb,g,j,c=0,e=d.length;c<e;c++){g=(j=-1!==d[c].indexOf("()"))?d[c].replace("()",""):d[c];var i;a:{i=0;for(var o=f.length;i<o;i++)if(f[i].name===g){i=f[i];break a}i=null}i||(i={name:g,val:{},methodExt:[],propExt:[]},f.push(i));c===e-1?i.val=b:f=j?i.methodExt:i.propExt}};t.registerPlural=v=function(a,b,c){t.register(a,c);t.register(b,function(){var a=c.apply(this,arguments);return a===this?this:a instanceof t?a.length?h.isArray(a[0])?new t(a.context,
a[0]):a[0]:k:a})};r("tables()",function(a){var b;if(a){b=t;var c=this.context;if("number"===typeof a)a=[c[a]];else var e=h.map(c,function(a){return a.nTable}),a=h(e).filter(a).map(function(){var a=h.inArray(this,e);return c[a]}).toArray();b=new b(a)}else b=this;return b});r("table()",function(a){var a=this.tables(a),b=a.context;return b.length?new t(b[0]):a});v("tables().nodes()","table().node()",function(){return this.iterator("table",function(a){return a.nTable},1)});v("tables().body()","table().body()",
function(){return this.iterator("table",function(a){return a.nTBody},1)});v("tables().header()","table().header()",function(){return this.iterator("table",function(a){return a.nTHead},1)});v("tables().footer()","table().footer()",function(){return this.iterator("table",function(a){return a.nTFoot},1)});v("tables().containers()","table().container()",function(){return this.iterator("table",function(a){return a.nTableWrapper},1)});r("draw()",function(a){return this.iterator("table",function(b){N(b,
!1===a)})});r("page()",function(a){return a===k?this.page.info().page:this.iterator("table",function(b){Ta(b,a)})});r("page.info()",function(){if(0===this.context.length)return k;var a=this.context[0],b=a._iDisplayStart,c=a._iDisplayLength,e=a.fnRecordsDisplay(),d=-1===c;return{page:d?0:Math.floor(b/c),pages:d?1:Math.ceil(e/c),start:b,end:a.fnDisplayEnd(),length:c,recordsTotal:a.fnRecordsTotal(),recordsDisplay:e}});r("page.len()",function(a){return a===k?0!==this.context.length?this.context[0]._iDisplayLength:
k:this.iterator("table",function(b){Ra(b,a)})});var Ub=function(a,b,c){if(c){var e=new t(a);e.one("draw",function(){c(e.ajax.json())})}"ssp"==B(a)?N(a,b):(C(a,!0),ra(a,[],function(c){oa(a);for(var c=sa(a,c),e=0,g=c.length;e<g;e++)K(a,c[e]);N(a,b);C(a,!1)}))};r("ajax.json()",function(){var a=this.context;if(0<a.length)return a[0].json});r("ajax.params()",function(){var a=this.context;if(0<a.length)return a[0].oAjaxData});r("ajax.reload()",function(a,b){return this.iterator("table",function(c){Ub(c,
!1===b,a)})});r("ajax.url()",function(a){var b=this.context;if(a===k){if(0===b.length)return k;b=b[0];return b.ajax?h.isPlainObject(b.ajax)?b.ajax.url:b.ajax:b.sAjaxSource}return this.iterator("table",function(b){h.isPlainObject(b.ajax)?b.ajax.url=a:b.ajax=a})});r("ajax.url().load()",function(a,b){return this.iterator("table",function(c){Ub(c,!1===b,a)})});var $a=function(a,b,c,e,d){var f=[],g,j,i,o,l,q;i=typeof b;if(!b||"string"===i||"function"===i||b.length===k)b=[b];i=0;for(o=b.length;i<o;i++){j=
b[i]&&b[i].split?b[i].split(","):[b[i]];l=0;for(q=j.length;l<q;l++)(g=c("string"===typeof j[l]?h.trim(j[l]):j[l]))&&g.length&&f.push.apply(f,g)}a=u.selector[a];if(a.length){i=0;for(o=a.length;i<o;i++)f=a[i](e,d,f)}return f},ab=function(a){a||(a={});a.filter&&a.search===k&&(a.search=a.filter);return h.extend({search:"none",order:"current",page:"all"},a)},bb=function(a){for(var b=0,c=a.length;b<c;b++)if(0<a[b].length)return a[0]=a[b],a[0].length=1,a.length=1,a.context=[a.context[b]],a;a.length=0;return a},
Ca=function(a,b){var c,e,d,f=[],g=a.aiDisplay;c=a.aiDisplayMaster;var j=b.search;e=b.order;d=b.page;if("ssp"==B(a))return"removed"===j?[]:V(0,c.length);if("current"==d){c=a._iDisplayStart;for(e=a.fnDisplayEnd();c<e;c++)f.push(g[c])}else if("current"==e||"applied"==e)f="none"==j?c.slice():"applied"==j?g.slice():h.map(c,function(a){return-1===h.inArray(a,g)?a:null});else if("index"==e||"original"==e){c=0;for(e=a.aoData.length;c<e;c++)"none"==j?f.push(c):(d=h.inArray(c,g),(-1===d&&"removed"==j||0<=d&&
"applied"==j)&&f.push(c))}return f};r("rows()",function(a,b){a===k?a="":h.isPlainObject(a)&&(b=a,a="");var b=ab(b),c=this.iterator("table",function(c){var d=b;return $a("row",a,function(a){var b=Pb(a);if(b!==null&&!d)return[b];var j=Ca(c,d);if(b!==null&&h.inArray(b,j)!==-1)return[b];if(!a)return j;if(typeof a==="function")return h.map(j,function(b){var d=c.aoData[b];return a(b,d._aData,d.nTr)?b:null});b=Sb(ia(c.aoData,j,"nTr"));return a.nodeName&&h.inArray(a,b)!==-1?[a._DT_RowIndex]:h(b).filter(a).map(function(){return this._DT_RowIndex}).toArray()},
c,d)},1);c.selector.rows=a;c.selector.opts=b;return c});r("rows().nodes()",function(){return this.iterator("row",function(a,b){return a.aoData[b].nTr||k},1)});r("rows().data()",function(){return this.iterator(!0,"rows",function(a,b){return ia(a.aoData,b,"_aData")},1)});v("rows().cache()","row().cache()",function(a){return this.iterator("row",function(b,c){var e=b.aoData[c];return"search"===a?e._aFilterData:e._aSortData},1)});v("rows().invalidate()","row().invalidate()",function(a){return this.iterator("row",
function(b,c){ca(b,c,a)})});v("rows().indexes()","row().index()",function(){return this.iterator("row",function(a,b){return b},1)});v("rows().remove()","row().remove()",function(){var a=this;return this.iterator("row",function(b,c,e){var d=b.aoData;d.splice(c,1);for(var f=0,g=d.length;f<g;f++)null!==d[f].nTr&&(d[f].nTr._DT_RowIndex=f);h.inArray(c,b.aiDisplay);pa(b.aiDisplayMaster,c);pa(b.aiDisplay,c);pa(a[e],c,!1);Sa(b)})});r("rows.add()",function(a){var b=this.iterator("table",function(b){var c,
f,g,h=[];f=0;for(g=a.length;f<g;f++)c=a[f],c.nodeName&&"TR"===c.nodeName.toUpperCase()?h.push(ma(b,c)[0]):h.push(K(b,c));return h},1),c=this.rows(-1);c.pop();c.push.apply(c,b.toArray());return c});r("row()",function(a,b){return bb(this.rows(a,b))});r("row().data()",function(a){var b=this.context;if(a===k)return b.length&&this.length?b[0].aoData[this[0]]._aData:k;b[0].aoData[this[0]]._aData=a;ca(b[0],this[0],"data");return this});r("row().node()",function(){var a=this.context;return a.length&&this.length?
a[0].aoData[this[0]].nTr||null:null});r("row.add()",function(a){a instanceof h&&a.length&&(a=a[0]);var b=this.iterator("table",function(b){return a.nodeName&&"TR"===a.nodeName.toUpperCase()?ma(b,a)[0]:K(b,a)});return this.row(b[0])});var cb=function(a,b){var c=a.context;c.length&&(c=c[0].aoData[b!==k?b:a[0]],c._details&&(c._details.remove(),c._detailsShow=k,c._details=k))},Vb=function(a,b){var c=a.context;if(c.length&&a.length){var e=c[0].aoData[a[0]];if(e._details){(e._detailsShow=b)?e._details.insertAfter(e.nTr):
e._details.detach();var d=c[0],f=new t(d),g=d.aoData;f.off("draw.dt.DT_details column-visibility.dt.DT_details destroy.dt.DT_details");0<D(g,"_details").length&&(f.on("draw.dt.DT_details",function(a,b){d===b&&f.rows({page:"current"}).eq(0).each(function(a){a=g[a];a._detailsShow&&a._details.insertAfter(a.nTr)})}),f.on("column-visibility.dt.DT_details",function(a,b){if(d===b)for(var c,e=aa(b),f=0,h=g.length;f<h;f++)c=g[f],c._details&&c._details.children("td[colspan]").attr("colspan",e)}),f.on("destroy.dt.DT_details",
function(a,b){if(d===b)for(var c=0,e=g.length;c<e;c++)g[c]._details&&cb(f,c)}))}}};r("row().child()",function(a,b){var c=this.context;if(a===k)return c.length&&this.length?c[0].aoData[this[0]]._details:k;if(!0===a)this.child.show();else if(!1===a)cb(this);else if(c.length&&this.length){var e=c[0],c=c[0].aoData[this[0]],d=[],f=function(a,b){if(h.isArray(a)||a instanceof h)for(var c=0,k=a.length;c<k;c++)f(a[c],b);else a.nodeName&&"tr"===a.nodeName.toLowerCase()?d.push(a):(c=h("<tr><td/></tr>").addClass(b),
h("td",c).addClass(b).html(a)[0].colSpan=aa(e),d.push(c[0]))};f(a,b);c._details&&c._details.remove();c._details=h(d);c._detailsShow&&c._details.insertAfter(c.nTr)}return this});r(["row().child.show()","row().child().show()"],function(){Vb(this,!0);return this});r(["row().child.hide()","row().child().hide()"],function(){Vb(this,!1);return this});r(["row().child.remove()","row().child().remove()"],function(){cb(this);return this});r("row().child.isShown()",function(){var a=this.context;return a.length&&
this.length?a[0].aoData[this[0]]._detailsShow||!1:!1});var dc=/^(.+):(name|visIdx|visible)$/,Wb=function(a,b,c,e,d){for(var c=[],e=0,f=d.length;e<f;e++)c.push(x(a,d[e],b));return c};r("columns()",function(a,b){a===k?a="":h.isPlainObject(a)&&(b=a,a="");var b=ab(b),c=this.iterator("table",function(c){var d=a,f=b,g=c.aoColumns,j=D(g,"sName"),i=D(g,"nTh");return $a("column",d,function(a){var b=Pb(a);if(a==="")return V(g.length);if(b!==null)return[b>=0?b:g.length+b];if(typeof a==="function"){var d=Ca(c,
f);return h.map(g,function(b,f){return a(f,Wb(c,f,0,0,d),i[f])?f:null})}var k=typeof a==="string"?a.match(dc):"";if(k)switch(k[2]){case "visIdx":case "visible":b=parseInt(k[1],10);if(b<0){var m=h.map(g,function(a,b){return a.bVisible?b:null});return[m[m.length+b]]}return[la(c,b)];case "name":return h.map(j,function(a,b){return a===k[1]?b:null})}else return h(i).filter(a).map(function(){return h.inArray(this,i)}).toArray()},c,f)},1);c.selector.cols=a;c.selector.opts=b;return c});v("columns().header()",
"column().header()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTh},1)});v("columns().footer()","column().footer()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTf},1)});v("columns().data()","column().data()",function(){return this.iterator("column-rows",Wb,1)});v("columns().dataSrc()","column().dataSrc()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].mData},1)});v("columns().cache()","column().cache()",
function(a){return this.iterator("column-rows",function(b,c,e,d,f){return ia(b.aoData,f,"search"===a?"_aFilterData":"_aSortData",c)},1)});v("columns().nodes()","column().nodes()",function(){return this.iterator("column-rows",function(a,b,c,e,d){return ia(a.aoData,d,"anCells",b)},1)});v("columns().visible()","column().visible()",function(a,b){return this.iterator("column",function(c,e){if(a===k)return c.aoColumns[e].bVisible;var d=c.aoColumns,f=d[e],g=c.aoData,j,i,m;if(a!==k&&f.bVisible!==a){if(a){var l=
h.inArray(!0,D(d,"bVisible"),e+1);j=0;for(i=g.length;j<i;j++)m=g[j].nTr,d=g[j].anCells,m&&m.insertBefore(d[e],d[l]||null)}else h(D(c.aoData,"anCells",e)).detach();f.bVisible=a;ea(c,c.aoHeader);ea(c,c.aoFooter);if(b===k||b)X(c),(c.oScroll.sX||c.oScroll.sY)&&Y(c);w(c,null,"column-visibility",[c,e,a]);ya(c)}})});v("columns().indexes()","column().index()",function(a){return this.iterator("column",function(b,c){return"visible"===a?$(b,c):c},1)});r("columns.adjust()",function(){return this.iterator("table",
function(a){X(a)},1)});r("column.index()",function(a,b){if(0!==this.context.length){var c=this.context[0];if("fromVisible"===a||"toData"===a)return la(c,b);if("fromData"===a||"toVisible"===a)return $(c,b)}});r("column()",function(a,b){return bb(this.columns(a,b))});r("cells()",function(a,b,c){h.isPlainObject(a)&&(a.row===k?(c=a,a=null):(c=b,b=null));h.isPlainObject(b)&&(c=b,b=null);if(null===b||b===k)return this.iterator("table",function(b){var d=a,e=ab(c),f=b.aoData,g=Ca(b,e),i=Sb(ia(f,g,"anCells")),
j=h([].concat.apply([],i)),l,m=b.aoColumns.length,o,r,t,s,u,v;return $a("cell",d,function(a){var c=typeof a==="function";if(a===null||a===k||c){o=[];r=0;for(t=g.length;r<t;r++){l=g[r];for(s=0;s<m;s++){u={row:l,column:s};if(c){v=b.aoData[l];a(u,x(b,l,s),v.anCells?v.anCells[s]:null)&&o.push(u)}else o.push(u)}}return o}return h.isPlainObject(a)?[a]:j.filter(a).map(function(a,b){l=b.parentNode._DT_RowIndex;return{row:l,column:h.inArray(b,f[l].anCells)}}).toArray()},b,e)});var e=this.columns(b,c),d=this.rows(a,
c),f,g,j,i,m,l=this.iterator("table",function(a,b){f=[];g=0;for(j=d[b].length;g<j;g++){i=0;for(m=e[b].length;i<m;i++)f.push({row:d[b][g],column:e[b][i]})}return f},1);h.extend(l.selector,{cols:b,rows:a,opts:c});return l});v("cells().nodes()","cell().node()",function(){return this.iterator("cell",function(a,b,c){return(a=a.aoData[b].anCells)?a[c]:k},1)});r("cells().data()",function(){return this.iterator("cell",function(a,b,c){return x(a,b,c)},1)});v("cells().cache()","cell().cache()",function(a){a=
"search"===a?"_aFilterData":"_aSortData";return this.iterator("cell",function(b,c,e){return b.aoData[c][a][e]},1)});v("cells().render()","cell().render()",function(a){return this.iterator("cell",function(b,c,e){return x(b,c,e,a)},1)});v("cells().indexes()","cell().index()",function(){return this.iterator("cell",function(a,b,c){return{row:b,column:c,columnVisible:$(a,c)}},1)});v("cells().invalidate()","cell().invalidate()",function(a){return this.iterator("cell",function(b,c,e){ca(b,c,a,e)})});r("cell()",
function(a,b,c){return bb(this.cells(a,b,c))});r("cell().data()",function(a){var b=this.context,c=this[0];if(a===k)return b.length&&c.length?x(b[0],c[0].row,c[0].column):k;Ia(b[0],c[0].row,c[0].column,a);ca(b[0],c[0].row,"data",c[0].column);return this});r("order()",function(a,b){var c=this.context;if(a===k)return 0!==c.length?c[0].aaSorting:k;"number"===typeof a?a=[[a,b]]:h.isArray(a[0])||(a=Array.prototype.slice.call(arguments));return this.iterator("table",function(b){b.aaSorting=a.slice()})});
r("order.listener()",function(a,b,c){return this.iterator("table",function(e){Oa(e,a,b,c)})});r(["columns().order()","column().order()"],function(a){var b=this;return this.iterator("table",function(c,e){var d=[];h.each(b[e],function(b,c){d.push([c,a])});c.aaSorting=d})});r("search()",function(a,b,c,e){var d=this.context;return a===k?0!==d.length?d[0].oPreviousSearch.sSearch:k:this.iterator("table",function(d){d.oFeatures.bFilter&&fa(d,h.extend({},d.oPreviousSearch,{sSearch:a+"",bRegex:null===b?!1:
b,bSmart:null===c?!0:c,bCaseInsensitive:null===e?!0:e}),1)})});v("columns().search()","column().search()",function(a,b,c,e){return this.iterator("column",function(d,f){var g=d.aoPreSearchCols;if(a===k)return g[f].sSearch;d.oFeatures.bFilter&&(h.extend(g[f],{sSearch:a+"",bRegex:null===b?!1:b,bSmart:null===c?!0:c,bCaseInsensitive:null===e?!0:e}),fa(d,d.oPreviousSearch,1))})});r("state()",function(){return this.context.length?this.context[0].oSavedState:null});r("state.clear()",function(){return this.iterator("table",
function(a){a.fnStateSaveCallback.call(a.oInstance,a,{})})});r("state.loaded()",function(){return this.context.length?this.context[0].oLoadedState:null});r("state.save()",function(){return this.iterator("table",function(a){ya(a)})});m.versionCheck=m.fnVersionCheck=function(a){for(var b=m.version.split("."),a=a.split("."),c,e,d=0,f=a.length;d<f;d++)if(c=parseInt(b[d],10)||0,e=parseInt(a[d],10)||0,c!==e)return c>e;return!0};m.isDataTable=m.fnIsDataTable=function(a){var b=h(a).get(0),c=!1;h.each(m.settings,
function(a,d){var f=d.nScrollHead?h("table",d.nScrollHead)[0]:null,g=d.nScrollFoot?h("table",d.nScrollFoot)[0]:null;if(d.nTable===b||f===b||g===b)c=!0});return c};m.tables=m.fnTables=function(a){return h.map(m.settings,function(b){if(!a||a&&h(b.nTable).is(":visible"))return b.nTable})};m.util={throttle:ua,escapeRegex:va};m.camelToHungarian=H;r("$()",function(a,b){var c=this.rows(b).nodes(),c=h(c);return h([].concat(c.filter(a).toArray(),c.find(a).toArray()))});h.each(["on","one","off"],function(a,
b){r(b+"()",function(){var a=Array.prototype.slice.call(arguments);a[0].match(/\.dt\b/)||(a[0]+=".dt");var e=h(this.tables().nodes());e[b].apply(e,a);return this})});r("clear()",function(){return this.iterator("table",function(a){oa(a)})});r("settings()",function(){return new t(this.context,this.context)});r("init()",function(){var a=this.context;return a.length?a[0].oInit:null});r("data()",function(){return this.iterator("table",function(a){return D(a.aoData,"_aData")}).flatten()});r("destroy()",
function(a){a=a||!1;return this.iterator("table",function(b){var c=b.nTableWrapper.parentNode,e=b.oClasses,d=b.nTable,f=b.nTBody,g=b.nTHead,j=b.nTFoot,i=h(d),f=h(f),k=h(b.nTableWrapper),l=h.map(b.aoData,function(a){return a.nTr}),q;b.bDestroying=!0;w(b,"aoDestroyCallback","destroy",[b]);a||(new t(b)).columns().visible(!0);k.unbind(".DT").find(":not(tbody *)").unbind(".DT");h(Ea).unbind(".DT-"+b.sInstance);d!=g.parentNode&&(i.children("thead").detach(),i.append(g));j&&d!=j.parentNode&&(i.children("tfoot").detach(),
i.append(j));i.detach();k.detach();b.aaSorting=[];b.aaSortingFixed=[];xa(b);h(l).removeClass(b.asStripeClasses.join(" "));h("th, td",g).removeClass(e.sSortable+" "+e.sSortableAsc+" "+e.sSortableDesc+" "+e.sSortableNone);b.bJUI&&(h("th span."+e.sSortIcon+", td span."+e.sSortIcon,g).detach(),h("th, td",g).each(function(){var a=h("div."+e.sSortJUIWrapper,this);h(this).append(a.contents());a.detach()}));!a&&c&&c.insertBefore(d,b.nTableReinsertBefore);f.children().detach();f.append(l);i.css("width",b.sDestroyWidth).removeClass(e.sTable);
(q=b.asDestroyStripes.length)&&f.children().each(function(a){h(this).addClass(b.asDestroyStripes[a%q])});c=h.inArray(b,m.settings);-1!==c&&m.settings.splice(c,1)})});h.each(["column","row","cell"],function(a,b){r(b+"s().every()",function(a){return this.iterator(b,function(e,d,f){a.call((new t(e))[b](d,f))})})});r("i18n()",function(a,b,c){var e=this.context[0],a=R(a)(e.oLanguage);a===k&&(a=b);c!==k&&h.isPlainObject(a)&&(a=a[c]!==k?a[c]:a._);return a.replace("%d",c)});m.version="1.10.7";m.settings=
[];m.models={};m.models.oSearch={bCaseInsensitive:!0,sSearch:"",bRegex:!1,bSmart:!0};m.models.oRow={nTr:null,anCells:null,_aData:[],_aSortData:null,_aFilterData:null,_sFilterRow:null,_sRowStripe:"",src:null};m.models.oColumn={idx:null,aDataSort:null,asSorting:null,bSearchable:null,bSortable:null,bVisible:null,_sManualType:null,_bAttrSrc:!1,fnCreatedCell:null,fnGetData:null,fnSetData:null,mData:null,mRender:null,nTh:null,nTf:null,sClass:null,sContentPadding:null,sDefaultContent:null,sName:null,sSortDataType:"std",
sSortingClass:null,sSortingClassJUI:null,sTitle:null,sType:null,sWidth:null,sWidthOrig:null};m.defaults={aaData:null,aaSorting:[[0,"asc"]],aaSortingFixed:[],ajax:null,aLengthMenu:[10,25,50,100],aoColumns:null,aoColumnDefs:null,aoSearchCols:[],asStripeClasses:null,bAutoWidth:!0,bDeferRender:!1,bDestroy:!1,bFilter:!0,bInfo:!0,bJQueryUI:!1,bLengthChange:!0,bPaginate:!0,bProcessing:!1,bRetrieve:!1,bScrollCollapse:!1,bServerSide:!1,bSort:!0,bSortMulti:!0,bSortCellsTop:!1,bSortClasses:!0,bStateSave:!1,
fnCreatedRow:null,fnDrawCallback:null,fnFooterCallback:null,fnFormatNumber:function(a){return a.toString().replace(/\B(?=(\d{3})+(?!\d))/g,this.oLanguage.sThousands)},fnHeaderCallback:null,fnInfoCallback:null,fnInitComplete:null,fnPreDrawCallback:null,fnRowCallback:null,fnServerData:null,fnServerParams:null,fnStateLoadCallback:function(a){try{return JSON.parse((-1===a.iStateDuration?sessionStorage:localStorage).getItem("DataTables_"+a.sInstance+"_"+location.pathname))}catch(b){}},fnStateLoadParams:null,
fnStateLoaded:null,fnStateSaveCallback:function(a,b){try{(-1===a.iStateDuration?sessionStorage:localStorage).setItem("DataTables_"+a.sInstance+"_"+location.pathname,JSON.stringify(b))}catch(c){}},fnStateSaveParams:null,iStateDuration:7200,iDeferLoading:null,iDisplayLength:10,iDisplayStart:0,iTabIndex:0,oClasses:{},oLanguage:{oAria:{sSortAscending:": activate to sort column ascending",sSortDescending:": activate to sort column descending"},oPaginate:{sFirst:"First",sLast:"Last",sNext:"Next",sPrevious:"Previous"},
sEmptyTable:"No data available in table",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries",sInfoEmpty:"Showing 0 to 0 of 0 entries",sInfoFiltered:"(filtered from _MAX_ total entries)",sInfoPostFix:"",sDecimal:"",sThousands:",",sLengthMenu:"Show _MENU_ entries",sLoadingRecords:"Loading...",sProcessing:"Processing...",sSearch:"Search:",sSearchPlaceholder:"",sUrl:"",sZeroRecords:"No matching records found"},oSearch:h.extend({},m.models.oSearch),sAjaxDataProp:"data",sAjaxSource:null,sDom:"lfrtip",searchDelay:null,
sPaginationType:"simple_numbers",sScrollX:"",sScrollXInner:"",sScrollY:"",sServerMethod:"GET",renderer:null};W(m.defaults);m.defaults.column={aDataSort:null,iDataSort:-1,asSorting:["asc","desc"],bSearchable:!0,bSortable:!0,bVisible:!0,fnCreatedCell:null,mData:null,mRender:null,sCellType:"td",sClass:"",sContentPadding:"",sDefaultContent:null,sName:"",sSortDataType:"std",sTitle:null,sType:null,sWidth:null};W(m.defaults.column);m.models.oSettings={oFeatures:{bAutoWidth:null,bDeferRender:null,bFilter:null,
bInfo:null,bLengthChange:null,bPaginate:null,bProcessing:null,bServerSide:null,bSort:null,bSortMulti:null,bSortClasses:null,bStateSave:null},oScroll:{bCollapse:null,iBarWidth:0,sX:null,sXInner:null,sY:null},oLanguage:{fnInfoCallback:null},oBrowser:{bScrollOversize:!1,bScrollbarLeft:!1},ajax:null,aanFeatures:[],aoData:[],aiDisplay:[],aiDisplayMaster:[],aoColumns:[],aoHeader:[],aoFooter:[],oPreviousSearch:{},aoPreSearchCols:[],aaSorting:null,aaSortingFixed:[],asStripeClasses:null,asDestroyStripes:[],
sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[],aoFooterCallback:[],aoDrawCallback:[],aoRowCreatedCallback:[],aoPreDrawCallback:[],aoInitComplete:[],aoStateSaveParams:[],aoStateLoadParams:[],aoStateLoaded:[],sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:!1,bInitialised:!1,aoOpenRows:[],sDom:null,searchDelay:null,sPaginationType:"two_button",iStateDuration:0,aoStateSave:[],aoStateLoad:[],oSavedState:null,oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null,
bAjaxDataGet:!0,jqXHR:null,json:k,oAjaxData:k,fnServerData:null,aoServerParams:[],sServerMethod:null,fnFormatNumber:null,aLengthMenu:null,iDraw:0,bDrawing:!1,iDrawError:-1,_iDisplayLength:10,_iDisplayStart:0,_iRecordsTotal:0,_iRecordsDisplay:0,bJUI:null,oClasses:{},bFiltered:!1,bSorted:!1,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],fnRecordsTotal:function(){return"ssp"==B(this)?1*this._iRecordsTotal:this.aiDisplayMaster.length},fnRecordsDisplay:function(){return"ssp"==B(this)?1*this._iRecordsDisplay:
this.aiDisplay.length},fnDisplayEnd:function(){var a=this._iDisplayLength,b=this._iDisplayStart,c=b+a,e=this.aiDisplay.length,d=this.oFeatures,f=d.bPaginate;return d.bServerSide?!1===f||-1===a?b+e:Math.min(b+a,this._iRecordsDisplay):!f||c>e||-1===a?e:c},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null,aLastSort:[],oPlugins:{}};m.ext=u={buttons:{},classes:{},errMode:"alert",feature:[],search:[],selector:{cell:[],column:[],row:[]},internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{},
header:{}},order:{},type:{detect:[],search:{},order:{}},_unique:0,fnVersionCheck:m.fnVersionCheck,iApiIndex:0,oJUIClasses:{},sVersion:m.version};h.extend(u,{afnFiltering:u.search,aTypes:u.type.detect,ofnSearch:u.type.search,oSort:u.type.order,afnSortData:u.order,aoFeatures:u.feature,oApi:u.internal,oStdClasses:u.classes,oPagination:u.pager});h.extend(m.ext.classes,{sTable:"dataTable",sNoFooter:"no-footer",sPageButton:"paginate_button",sPageButtonActive:"current",sPageButtonDisabled:"disabled",sStripeOdd:"odd",
sStripeEven:"even",sRowEmpty:"dataTables_empty",sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_asc_disabled",sSortableDesc:"sorting_desc_disabled",sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sFilterInput:"",sLengthSelect:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",
sScrollHeadInner:"dataTables_scrollHeadInner",sScrollBody:"dataTables_scrollBody",sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sHeaderTH:"",sFooterTH:"",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",sJUIHeader:"",sJUIFooter:""});var Da="",Da="",F=Da+"ui-state-default",ja=Da+"css_right ui-icon ui-icon-",Xb=Da+"fg-toolbar ui-toolbar ui-widget-header ui-helper-clearfix";h.extend(m.ext.oJUIClasses,
m.ext.classes,{sPageButton:"fg-button ui-button "+F,sPageButtonActive:"ui-state-disabled",sPageButtonDisabled:"ui-state-disabled",sPaging:"dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi ui-buttonset-multi paging_",sSortAsc:F+" sorting_asc",sSortDesc:F+" sorting_desc",sSortable:F+" sorting",sSortableAsc:F+" sorting_asc_disabled",sSortableDesc:F+" sorting_desc_disabled",sSortableNone:F+" sorting_disabled",sSortJUIAsc:ja+"triangle-1-n",sSortJUIDesc:ja+"triangle-1-s",sSortJUI:ja+"carat-2-n-s",
sSortJUIAscAllowed:ja+"carat-1-n",sSortJUIDescAllowed:ja+"carat-1-s",sSortJUIWrapper:"DataTables_sort_wrapper",sSortIcon:"DataTables_sort_icon",sScrollHead:"dataTables_scrollHead "+F,sScrollFoot:"dataTables_scrollFoot "+F,sHeaderTH:F,sFooterTH:F,sJUIHeader:Xb+" ui-corner-tl ui-corner-tr",sJUIFooter:Xb+" ui-corner-bl ui-corner-br"});var Mb=m.ext.pager;h.extend(Mb,{simple:function(){return["previous","next"]},full:function(){return["first","previous","next","last"]},simple_numbers:function(a,b){return["previous",
Wa(a,b),"next"]},full_numbers:function(a,b){return["first","previous",Wa(a,b),"next","last"]},_numbers:Wa,numbers_length:7});h.extend(!0,m.ext.renderer,{pageButton:{_:function(a,b,c,e,d,f){var g=a.oClasses,j=a.oLanguage.oPaginate,i,k,l=0,m=function(b,e){var n,r,t,s,u=function(b){Ta(a,b.data.action,true)};n=0;for(r=e.length;n<r;n++){s=e[n];if(h.isArray(s)){t=h("<"+(s.DT_el||"div")+"/>").appendTo(b);m(t,s)}else{k=i="";switch(s){case "ellipsis":b.append('<span class="ellipsis">&#x2026;</span>');break;
case "first":i=j.sFirst;k=s+(d>0?"":" "+g.sPageButtonDisabled);break;case "previous":i=j.sPrevious;k=s+(d>0?"":" "+g.sPageButtonDisabled);break;case "next":i=j.sNext;k=s+(d<f-1?"":" "+g.sPageButtonDisabled);break;case "last":i=j.sLast;k=s+(d<f-1?"":" "+g.sPageButtonDisabled);break;default:i=s+1;k=d===s?g.sPageButtonActive:""}if(i){t=h("<a>",{"class":g.sPageButton+" "+k,"aria-controls":a.sTableId,"data-dt-idx":l,tabindex:a.iTabIndex,id:c===0&&typeof s==="string"?a.sTableId+"_"+s:null}).html(i).appendTo(b);
Va(t,{action:s},u);l++}}}},n;try{n=h(Q.activeElement).data("dt-idx")}catch(r){}m(h(b).empty(),e);n&&h(b).find("[data-dt-idx="+n+"]").focus()}}});h.extend(m.ext.type.detect,[function(a,b){var c=b.oLanguage.sDecimal;return Za(a,c)?"num"+c:null},function(a){if(a&&!(a instanceof Date)&&(!ac.test(a)||!bc.test(a)))return null;var b=Date.parse(a);return null!==b&&!isNaN(b)||J(a)?"date":null},function(a,b){var c=b.oLanguage.sDecimal;return Za(a,c,!0)?"num-fmt"+c:null},function(a,b){var c=b.oLanguage.sDecimal;
return Rb(a,c)?"html-num"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Rb(a,c,!0)?"html-num-fmt"+c:null},function(a){return J(a)||"string"===typeof a&&-1!==a.indexOf("<")?"html":null}]);h.extend(m.ext.type.search,{html:function(a){return J(a)?a:"string"===typeof a?a.replace(Ob," ").replace(Ba,""):""},string:function(a){return J(a)?a:"string"===typeof a?a.replace(Ob," "):a}});var Aa=function(a,b,c,e){if(0!==a&&(!a||"-"===a))return-Infinity;b&&(a=Qb(a,b));a.replace&&(c&&(a=a.replace(c,"")),
e&&(a=a.replace(e,"")));return 1*a};h.extend(u.type.order,{"date-pre":function(a){return Date.parse(a)||0},"html-pre":function(a){return J(a)?"":a.replace?a.replace(/<.*?>/g,"").toLowerCase():a+""},"string-pre":function(a){return J(a)?"":"string"===typeof a?a.toLowerCase():!a.toString?"":a.toString()},"string-asc":function(a,b){return a<b?-1:a>b?1:0},"string-desc":function(a,b){return a<b?1:a>b?-1:0}});db("");h.extend(!0,m.ext.renderer,{header:{_:function(a,b,c,e){h(a.nTable).on("order.dt.DT",function(d,
f,g,h){if(a===f){d=c.idx;b.removeClass(c.sSortingClass+" "+e.sSortAsc+" "+e.sSortDesc).addClass(h[d]=="asc"?e.sSortAsc:h[d]=="desc"?e.sSortDesc:c.sSortingClass)}})},jqueryui:function(a,b,c,e){h("<div/>").addClass(e.sSortJUIWrapper).append(b.contents()).append(h("<span/>").addClass(e.sSortIcon+" "+c.sSortingClassJUI)).appendTo(b);h(a.nTable).on("order.dt.DT",function(d,f,g,h){if(a===f){d=c.idx;b.removeClass(e.sSortAsc+" "+e.sSortDesc).addClass(h[d]=="asc"?e.sSortAsc:h[d]=="desc"?e.sSortDesc:c.sSortingClass);
b.find("span."+e.sSortIcon).removeClass(e.sSortJUIAsc+" "+e.sSortJUIDesc+" "+e.sSortJUI+" "+e.sSortJUIAscAllowed+" "+e.sSortJUIDescAllowed).addClass(h[d]=="asc"?e.sSortJUIAsc:h[d]=="desc"?e.sSortJUIDesc:c.sSortingClassJUI)}})}}});m.render={number:function(a,b,c,e){return{display:function(d){if("number"!==typeof d&&"string"!==typeof d)return d;var f=0>d?"-":"",d=Math.abs(parseFloat(d)),g=parseInt(d,10),d=c?b+(d-g).toFixed(c).substring(2):"";return f+(e||"")+g.toString().replace(/\B(?=(\d{3})+(?!\d))/g,
a)+d}}}};h.extend(m.ext.internal,{_fnExternApiFunc:Nb,_fnBuildAjax:ra,_fnAjaxUpdate:kb,_fnAjaxParameters:tb,_fnAjaxUpdateDraw:ub,_fnAjaxDataSrc:sa,_fnAddColumn:Fa,_fnColumnOptions:ka,_fnAdjustColumnSizing:X,_fnVisibleToColumnIndex:la,_fnColumnIndexToVisible:$,_fnVisbleColumns:aa,_fnGetColumns:Z,_fnColumnTypes:Ha,_fnApplyColumnDefs:ib,_fnHungarianMap:W,_fnCamelToHungarian:H,_fnLanguageCompat:P,_fnBrowserDetect:gb,_fnAddData:K,_fnAddTr:ma,_fnNodeToDataIndex:function(a,b){return b._DT_RowIndex!==k?b._DT_RowIndex:
null},_fnNodeToColumnIndex:function(a,b,c){return h.inArray(c,a.aoData[b].anCells)},_fnGetCellData:x,_fnSetCellData:Ia,_fnSplitObjNotation:Ka,_fnGetObjectDataFn:R,_fnSetObjectDataFn:S,_fnGetDataMaster:La,_fnClearTable:oa,_fnDeleteIndex:pa,_fnInvalidate:ca,_fnGetRowElements:na,_fnCreateTr:Ja,_fnBuildHead:jb,_fnDrawHead:ea,_fnDraw:M,_fnReDraw:N,_fnAddOptionsHtml:mb,_fnDetectHeader:da,_fnGetUniqueThs:qa,_fnFeatureHtmlFilter:ob,_fnFilterComplete:fa,_fnFilterCustom:xb,_fnFilterColumn:wb,_fnFilter:vb,_fnFilterCreateSearch:Qa,
_fnEscapeRegex:va,_fnFilterData:yb,_fnFeatureHtmlInfo:rb,_fnUpdateInfo:Bb,_fnInfoMacros:Cb,_fnInitialise:ga,_fnInitComplete:ta,_fnLengthChange:Ra,_fnFeatureHtmlLength:nb,_fnFeatureHtmlPaginate:sb,_fnPageChange:Ta,_fnFeatureHtmlProcessing:pb,_fnProcessingDisplay:C,_fnFeatureHtmlTable:qb,_fnScrollDraw:Y,_fnApplyToChildren:G,_fnCalculateColumnWidths:Ga,_fnThrottle:ua,_fnConvertToWidth:Db,_fnScrollingWidthAdjust:Fb,_fnGetWidestNode:Eb,_fnGetMaxLenString:Gb,_fnStringToCss:s,_fnScrollBarWidth:Hb,_fnSortFlatten:U,
_fnSort:lb,_fnSortAria:Jb,_fnSortListener:Ua,_fnSortAttachListener:Oa,_fnSortingClasses:xa,_fnSortData:Ib,_fnSaveState:ya,_fnLoadState:Kb,_fnSettingsFromNode:za,_fnLog:I,_fnMap:E,_fnBindAction:Va,_fnCallbackReg:z,_fnCallbackFire:w,_fnLengthOverflow:Sa,_fnRenderer:Pa,_fnDataSource:B,_fnRowAttributes:Ma,_fnCalculateEnd:function(){}});h.fn.dataTable=m;h.fn.dataTableSettings=m.settings;h.fn.dataTableExt=m.ext;h.fn.DataTable=function(a){return h(this).dataTable(a).api()};h.each(m,function(a,b){h.fn.DataTable[a]=
b});return h.fn.dataTable};"function"===typeof define&&define.amd?define("datatables",["jquery"],P):"object"===typeof exports?module.exports=P(require("jquery")):jQuery&&!jQuery.fn.dataTable&&P(jQuery)})(window,document);

File diff suppressed because it is too large Load Diff

11
externals/jquery.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

22
images/CosmosDB-logo.svg Normal file
View File

@@ -0,0 +1,22 @@
<svg id="b089cfca-0de1-451c-a1ca-6680ea50cb4f" xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 18 18">
<defs>
<radialGradient id="b25d0836-964a-4c84-8c20-855f66e8345e" cx="-105.006" cy="-10.409" r="5.954" gradientTransform="translate(117.739 19.644) scale(1.036 1.027)" gradientUnits="userSpaceOnUse">
<stop offset="0.183" stop-color="#5ea0ef"/>
<stop offset="1" stop-color="#0078d4"/>
</radialGradient>
<clipPath id="b36c7f5d-2ef1-4760-8a25-eeb9661f4e47">
<path d="M14.969,7.53A6.137,6.137,0,1,1,7.574,2.987,6.137,6.137,0,0,1,14.969,7.53Z" fill="none"/>
</clipPath>
</defs>
<title>Icon-databases-121</title>
<path d="M2.954,5.266a.175.175,0,0,1-.176-.176h0A2.012,2.012,0,0,0,.769,3.081a.176.176,0,0,1-.176-.175h0a.176.176,0,0,1,.176-.176A2.012,2.012,0,0,0,2.778.72.175.175,0,0,1,2.954.544h0A.175.175,0,0,1,3.13.72h0A2.012,2.012,0,0,0,5.139,2.729a.175.175,0,0,1,.176.176h0a.175.175,0,0,1-.176.176h0A2.011,2.011,0,0,0,3.13,5.09.177.177,0,0,1,2.954,5.266Z" fill="#50e6ff"/>
<path d="M15.611,17.456a.141.141,0,0,1-.141-.141h0a1.609,1.609,0,0,0-1.607-1.607.141.141,0,0,1-.141-.14h0a.141.141,0,0,1,.141-.141h0a1.608,1.608,0,0,0,1.607-1.607.141.141,0,0,1,.141-.141h0a.141.141,0,0,1,.141.141h0a1.608,1.608,0,0,0,1.607,1.607.141.141,0,1,1,0,.282h0a1.609,1.609,0,0,0-1.607,1.607A.141.141,0,0,1,15.611,17.456Z" fill="#50e6ff"/>
<g>
<path d="M14.969,7.53A6.137,6.137,0,1,1,7.574,2.987,6.137,6.137,0,0,1,14.969,7.53Z" fill="url(#b25d0836-964a-4c84-8c20-855f66e8345e)"/>
<g clip-path="url(#b36c7f5d-2ef1-4760-8a25-eeb9661f4e47)">
<path d="M5.709,13.115A1.638,1.638,0,1,0,5.714,9.84,1.307,1.307,0,0,0,5.721,9.7,1.651,1.651,0,0,0,4.06,8.064H2.832a6.251,6.251,0,0,0,1.595,5.051Z" fill="#f2f2f2"/>
<path d="M15.045,7.815c0-.015,0-.03-.007-.044a5.978,5.978,0,0,0-1.406-2.88,1.825,1.825,0,0,0-.289-.09,1.806,1.806,0,0,0-2.3,1.663,2,2,0,0,0-.2-.013,1.737,1.737,0,0,0-.581,3.374,1.451,1.451,0,0,0,.541.1h2.03A13.453,13.453,0,0,0,15.045,7.815Z" fill="#f2f2f2"/>
</g>
</g>
<path d="M17.191,3.832c-.629-1.047-2.1-1.455-4.155-1.149a14.606,14.606,0,0,0-2.082.452,6.456,6.456,0,0,1,1.528.767c.241-.053.483-.116.715-.151A7.49,7.49,0,0,1,14.3,3.662a2.188,2.188,0,0,1,1.959.725h0c.383.638.06,1.729-.886,3a16.723,16.723,0,0,1-4.749,4.051A16.758,16.758,0,0,1,4.8,13.7c-1.564.234-2.682,0-3.065-.636s-.06-1.73.886-2.995c.117-.157.146-.234.279-.392a6.252,6.252,0,0,1,.026-1.63A11.552,11.552,0,0,0,1.756,9.419C.517,11.076.181,12.566.809,13.613a3.165,3.165,0,0,0,2.9,1.249,8.434,8.434,0,0,0,1.251-.1,17.855,17.855,0,0,0,6.219-2.4,17.808,17.808,0,0,0,5.061-4.332C17.483,6.369,17.819,4.88,17.191,3.832Z" fill="#50e6ff"/>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -2349,6 +2349,12 @@ a:link {
text-decoration: none;
}
.tabsContainer {
height: 100%;
width: 100%;
overflow: hidden;
}
.tabs {
position: relative;
margin: 15px 0 25px 0;

View File

@@ -1,17 +0,0 @@
@import "./Common/Constants";
@import "./forms";
.formTree {
border: 1px solid inherit;
color: inherit;
padding: 0px 12px 1px 8px;
}
.formTree:hover {
border: 1px solid inherit;
background-color: inherit;
}
.formTree:active {
border: 1px solid inherit;
}

View File

@@ -15,7 +15,7 @@
.infoBoxMessage {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
white-space: normal;
width: 320px;
padding-top: 2px;
color: @BaseHigh;

3521
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -34,12 +34,12 @@
"@nteract/transform-vega": "7.0.6",
"@octokit/rest": "17.9.2",
"@phosphor/widgets": "1.9.3",
"@uifabric/react-cards": "0.109.53",
"@uifabric/styling": "7.11.2",
"@uifabric/react-cards": "0.109.110",
"@uifabric/styling": "7.13.7",
"abort-controller": "3.0.0",
"applicationinsights": "1.8.0",
"babel-polyfill": "6.26.0",
"bootstrap": "3.3.7",
"bootstrap": "3.4.1",
"canvas": "2.6.0",
"clean-webpack-plugin": "0.1.19",
"copy-webpack-plugin": "6.0.2",
@@ -56,13 +56,13 @@
"hasher": "1.2.0",
"immutable": "4.0.0-rc.12",
"is-ci": "2.0.0",
"jquery": "3.4.0",
"jquery": "3.5.1",
"jquery-typeahead": "2.10.6",
"jquery-ui-dist": "1.12.1",
"knockout": "3.5.1",
"monaco-editor": "0.15.6",
"object.entries": "1.1.0",
"office-ui-fabric-react": "7.115.0",
"office-ui-fabric-react": "7.121.10",
"plotly.js-cartesian-dist-min": "1.52.3",
"promise-polyfill": "8.1.0",
"promise.prototype.finally": "3.1.0",
@@ -109,7 +109,7 @@
"@types/react-notification-system": "0.2.39",
"@types/react-redux": "7.1.7",
"@types/sinon": "2.3.3",
"@types/styled-components": "4.1.8",
"@types/styled-components": "5.1.1",
"@types/text-encoding": "0.0.33",
"@types/underscore": "1.7.36",
"@types/webfontloader": "1.6.29",
@@ -156,7 +156,7 @@
"ts-loader": "6.2.2",
"tslint": "5.11.0",
"tslint-microsoft-contrib": "6.0.0",
"typescript": "3.8.3",
"typescript": "3.9.6",
"url-loader": "1.1.1",
"webpack": "4.43.0",
"webpack-bundle-analyzer": "3.6.1",

View File

@@ -1,11 +1,6 @@
import * as ko from "knockout";
import * as ReactBindingHandler from "./ReactBindingHandler";
interface RestorePoint {
readonly element: JQuery;
readonly width: number;
}
export class BindingHandlersRegisterer {
public static registerBindingHandlers() {
ko.bindingHandlers.setTemplateReady = {
@@ -17,7 +12,7 @@ export class BindingHandlersRegisterer {
bindingContext?: ko.BindingContext
) {
const value = ko.unwrap(wrappedValueAccessor());
bindingContext.$data.isTemplateReady(value);
bindingContext?.$data.isTemplateReady(value);
}
} as ko.BindingHandler;

View File

@@ -111,7 +111,6 @@ export class Features {
public static readonly hostedDataExplorer = "hosteddataexplorerenabled";
public static readonly enableTtl = "enablettl";
public static readonly enableNotebooks = "enablenotebooks";
public static readonly enableGallery = "enablegallery";
public static readonly enableGalleryPublish = "enablegallerypublish";
public static readonly enableSpark = "enablespark";
public static readonly livyEndpoint = "livyendpoint";
@@ -375,6 +374,8 @@ export class HttpStatusCodes {
export class Urls {
public static feedbackEmail = "https://aka.ms/cosmosdbfeedback?subject=Cosmos%20DB%20Data%20Explorer%20Feedback";
public static autoscaleMigration = "https://aka.ms/cosmos-autoscale-migration";
public static freeTierInformation = "https://aka.ms/cosmos-free-tier";
public static cosmosPricing = "https://aka.ms/azure-cosmos-db-pricing";
}
export class HashRoutePrefixes {
@@ -444,6 +445,17 @@ export class KeyCodes {
public static Tab: number = 9;
}
// Normalized per: https://www.w3.org/TR/uievents-key/#named-key-attribute-values
export class NormalizedEventKey {
public static readonly Space = " ";
public static readonly Enter = "Enter";
public static readonly Escape = "Escape";
public static readonly UpArrow = "ArrowUp";
public static readonly DownArrow = "ArrowDown";
public static readonly LeftArrow = "ArrowLeft";
public static readonly RightArrow = "ArrowRight";
}
export class TryCosmosExperience {
public static extendUrl: string = "https://trycosmosdb.azure.com/api/resource/extendportal?userId={0}";
public static deleteUrl: string = "https://trycosmosdb.azure.com/api/resource/deleteportal?userId={0}";

View File

@@ -447,7 +447,7 @@ export abstract class DataAccessUtilityBase {
.database(databaseId)
.container(collectionId)
.read()
.then(response => response.resource)
.then(response => response.resource as DataModels.Collection)
);
}
@@ -577,7 +577,7 @@ export abstract class DataAccessUtilityBase {
}
);
})
.then(containerResponse => containerResponse.resource)
.then(containerResponse => containerResponse.resource as DataModels.Collection)
.finally(() => this.refreshCachedResources(options))
);
}

View File

@@ -59,13 +59,13 @@ function _generateLogEntry(
level: Diagnostics.LogEntryLevel,
message: string,
area: string,
code: number
code?: number
): Diagnostics.LogEntry {
return {
timestamp: new Date().getUTCSeconds(),
level: level,
message: message,
area: area,
code: code
level,
message,
area,
code
};
}

View File

@@ -16,7 +16,6 @@ interface Config {
ARM_API_VERSION: string;
GRAPH_ENDPOINT: string;
GRAPH_API_VERSION: string;
AZURESAMPLESCOSMOSDBPAT: string;
ARCADIA_ENDPOINT: string;
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: string;
BACKEND_ENDPOINT?: string;
@@ -44,8 +43,7 @@ let config: Config = {
ARCADIA_ENDPOINT: "https://workspaceartifacts.projectarcadia.net",
ARCADIA_LIVY_ENDPOINT_DNS_ZONE: "dev.azuresynapse.net",
GITHUB_CLIENT_ID: "6cb2f63cf6f7b5cbdeca", // Registered OAuth app: https://github.com/settings/applications/1189306
JUNO_ENDPOINT: "https://tools.cosmos.azure.com",
AZURESAMPLESCOSMOSDBPAT: "99e38770e29b4a61d7c49f188780504efd35cc86" //[SuppressMessage("Microsoft.Security", "CS001:SecretInline", Justification=" // this PAT is a "no scopes" PAT with zero access to any projects, this is just used to get around the dev.github.com rate limit when accessing public samples repo.")]
JUNO_ENDPOINT: "https://tools.cosmos.azure.com"
};
// Injected for local develpment. These will be removed in the production bundle by webpack

View File

@@ -46,7 +46,7 @@ export interface LogEntry {
/**
* The message code.
*/
code: number;
code?: number;
/**
* Any additional data to be logged.
*/

View File

@@ -22,6 +22,7 @@ import { QueryMetrics } from "@azure/cosmos";
import { SetupNotebooksPane } from "../Explorer/Panes/SetupNotebooksPane";
import { Splitter } from "../Common/Splitter";
import { StringInputPane } from "../Explorer/Panes/StringInputPane";
import { TabsManager } from "../Explorer/Tabs/TabsManager";
import { TextFieldProps } from "../Explorer/Controls/DialogReactComponent/DialogComponent";
import { UploadDetails } from "../workers/upload/definitions";
import { UploadItemsPaneAdapter } from "../Explorer/Panes/UploadItemsPaneAdapter";
@@ -83,7 +84,6 @@ export interface Explorer {
extensionEndpoint: ko.Observable<string>;
armEndpoint: ko.Observable<string>;
isFeatureEnabled: (feature: string) => boolean;
isGalleryEnabled: ko.Computed<boolean>;
isGalleryPublishEnabled: ko.Computed<boolean>;
isGitHubPaneEnabled: ko.Observable<boolean>;
isPublishNotebookPaneEnabled: ko.Observable<boolean>;
@@ -122,9 +122,8 @@ export interface Explorer {
isResourceTokenCollectionNodeSelected: ko.Computed<boolean>;
// Tabs
openedTabs: ko.ObservableArray<Tab>;
activeTab: ko.Observable<Tab>;
isTabsContentExpanded: ko.Observable<boolean>;
tabsManager: TabsManager;
// Contextual Panes
addDatabasePane: AddDatabasePane;
@@ -149,8 +148,6 @@ export interface Explorer {
uploadFilePane: UploadFilePane;
stringInputPane: StringInputPane;
setupNotebooksPane: SetupNotebooksPane;
setupSparkClusterPane: ContextualPane;
manageSparkClusterPane: ContextualPane;
libraryManagePane: ContextualPane;
clusterLibraryPane: ContextualPane;
gitHubReposPane: ContextualPane;
@@ -164,8 +161,6 @@ export interface Explorer {
refreshDatabaseForResourceToken(): Q.Promise<void>;
refreshAllDatabases(isInitialLoad?: boolean): Q.Promise<any>;
closeAllPanes(): void;
closeAllTabsForResource(resourceId: string): void;
findActiveTab(): Tab; // TODO Deprecate in favor activeTab
findSelectedDatabase(): Database;
findDatabaseWithId(databaseRid: string): Database;
isLastDatabase(): boolean;
@@ -224,13 +219,12 @@ export interface Explorer {
sparkClusterConnectionInfo: ko.Observable<DataModels.SparkClusterConnectionInfo>;
arcadiaToken: ko.Observable<string>;
arcadiaWorkspaces: ko.ObservableArray<ArcadiaWorkspaceItem>;
isNotebookTabActive: ko.Computed<boolean>;
memoryUsageInfo: ko.Observable<DataModels.MemoryUsageInfo>;
notebookManager?: any; // This is dynamically loaded
openNotebook(notebookContentItem: NotebookContentItem): Promise<boolean>; // True if it was opened, false otherwise
resetNotebookWorkspace(): void;
importAndOpen: (path: string) => Promise<boolean>;
importAndOpenFromGallery: (name: string, content: string) => Promise<boolean>;
importAndOpenContent: (name: string, content: string) => Promise<boolean>;
publishNotebook: (name: string, content: string) => void;
openNotebookTerminal: (kind: TerminalKind) => void;
openGallery: (notebookUrl?: string, galleryItem?: IGalleryItem, isFavorite?: boolean) => void;
@@ -253,7 +247,6 @@ export interface Explorer {
readFile: (notebookFile: NotebookContentItem) => Promise<string>;
downloadFile: (notebookFile: NotebookContentItem) => Promise<void>;
createNotebookContentItemFile: (name: string, filepath: string) => NotebookContentItem;
closeNotebookTab: (filepath: string) => void;
refreshContentItem(item: NotebookContentItem): Promise<void>;
getNotebookBasePath(): string;
@@ -384,7 +377,6 @@ export interface Database extends TreeNode {
findCollectionWithId(collectionRid: string): Collection;
openAddCollection(database: Database, event: MouseEvent): void;
onDeleteDatabaseContextMenuClick(source: Database, event: MouseEvent | KeyboardEvent): void;
refreshTabSelectedState(): void;
readSettings(): void;
onSettingsClick: () => void;
}
@@ -406,7 +398,6 @@ export interface CollectionBase extends TreeNode {
onNewQueryClick(source: any, event: MouseEvent, queryText?: string): void;
expandCollection(): Q.Promise<any>;
collapseCollection(): void;
refreshActiveTab(): void;
getDatabase(): Database;
}
@@ -840,7 +831,6 @@ export interface TabOptions {
onUpdateTabsButtons: (buttons: NavbarButtonConfig[]) => void;
isTabsContentExpanded?: ko.Observable<boolean>;
onLoadStartKey?: number;
openedTabs: Tab[];
// TODO Remove the flag and use a context to handle this
// TODO: 145357 Remove dependency on collection/database and add abstraction
@@ -934,14 +924,12 @@ export interface Tab {
tabTitle: ko.Observable<string>;
hashLocation: ko.Observable<string>;
closeTabButton: Button;
onCloseTabButtonClick(): Q.Promise<any>;
onCloseTabButtonClick(): void;
onTabClick(): Q.Promise<any>;
onKeyPressActivate(source: any, event: KeyboardEvent): void;
onKeyPressClose(source: any, event: KeyboardEvent): void;
onActivate(): Q.Promise<any>;
refresh(): void;
nextTab: ko.Observable<Tab>;
previousTab: ko.Observable<Tab>;
closeButtonTabIndex: ko.Computed<number>;
isExecutionError: ko.Observable<boolean>;
isExecuting: ko.Observable<boolean>;

View File

@@ -198,7 +198,7 @@ export class Heatmap {
let timeSelected: string = data.points[0].x;
timeSelected = timeSelected.replace(" ", "T");
timeSelected = `${timeSelected}Z`;
let xAxisIndex;
let xAxisIndex = 0;
for (let i = 0; i < this._chartData.xAxisPoints.length; i++) {
if (this._chartData.xAxisPoints[i] === timeSelected) {
xAxisIndex = i;
@@ -234,7 +234,8 @@ export function handleMessage(event: MessageEvent) {
return;
}
Plotly.purge(Heatmap.elementId);
document.getElementById(Heatmap.elementId).innerHTML = "";
document.getElementById(Heatmap.elementId)!.innerHTML = "";
const data = event.data.data;
const chartData: DataPayload = data.chartData;
const chartSettings: HeatmapCaptions = data.chartSettings;
@@ -259,8 +260,8 @@ export function handleMessage(event: MessageEvent) {
noDataMessageContent.classList.add("dark-theme");
}
document.getElementById(Heatmap.elementId).appendChild(chartTitleElement);
document.getElementById(Heatmap.elementId).appendChild(noDataMessageElement);
document.getElementById(Heatmap.elementId)!.appendChild(chartTitleElement);
document.getElementById(Heatmap.elementId)!.appendChild(noDataMessageElement);
}
}

View File

@@ -116,14 +116,6 @@ describe("Component Registerer", () => {
expect(ko.components.isRegistered("setup-notebooks-pane")).toBe(true);
});
it("should register setup-spark-cluster-pane component", () => {
expect(ko.components.isRegistered("setup-spark-cluster-pane")).toBe(true);
});
it("should register manage-spark-cluster-pane component", () => {
expect(ko.components.isRegistered("manage-spark-cluster-pane")).toBe(true);
});
it("should register dynamic-list component", () => {
expect(ko.components.isRegistered("dynamic-list")).toBe(true);
});

View File

@@ -10,6 +10,7 @@ import { GraphStyleComponent } from "./Graph/GraphStyleComponent/GraphStyleCompo
import { InputTypeaheadComponent } from "./Controls/InputTypeahead/InputTypeahead";
import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent";
import { NewVertexComponent } from "./Graph/NewVertexComponent/NewVertexComponent";
import { TabsManagerKOComponent } from "./Tabs/TabsManager";
import { ThroughputInputComponent } from "./Controls/ThroughputInput/ThroughputInputComponent";
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
import { ToolbarComponent } from "./Controls/Toolbar/Toolbar";
@@ -26,6 +27,7 @@ ko.components.register("diff-editor", new DiffEditorComponent());
ko.components.register("dynamic-list", DynamicListComponent);
ko.components.register("throughput-input", ThroughputInputComponent);
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
ko.components.register("tabs-manager", TabsManagerKOComponent());
// Collection Tabs
ko.components.register("documents-tab", new TabComponents.DocumentsTab());
@@ -76,8 +78,6 @@ ko.components.register("browse-queries-pane", new PaneComponents.BrowseQueriesPa
ko.components.register("upload-file-pane", new PaneComponents.UploadFilePaneComponent());
ko.components.register("string-input-pane", new PaneComponents.StringInputPaneComponent());
ko.components.register("setup-notebooks-pane", new PaneComponents.SetupNotebooksPaneComponent());
ko.components.register("setup-spark-cluster-pane", new PaneComponents.SetupSparkClusterPaneComponent());
ko.components.register("manage-spark-cluster-pane", new PaneComponents.ManageSparkClusterPaneComponent());
ko.components.register("library-manage-pane", new PaneComponents.LibraryManagePaneComponent());
ko.components.register("cluster-library-pane", new PaneComponents.ClusterLibraryPaneComponent());
ko.components.register("github-repos-pane", new PaneComponents.GitHubReposPaneComponent());

View File

@@ -31,6 +31,8 @@ export interface DialogProps {
onSecondaryButtonClick: () => void;
primaryButtonDisabled?: boolean;
type?: DialogType;
showCloseButton?: boolean;
onDismiss?: () => void;
}
const DIALOG_MIN_WIDTH = "400px";
@@ -55,7 +57,8 @@ export class DialogComponent extends React.Component<DialogProps, {}> {
title: { fontSize: DIALOG_TITLE_FONT_SIZE, fontWeight: DIALOG_TITLE_FONT_WEIGHT },
subText: { fontSize: DIALOG_SUBTEXT_FONT_SIZE }
},
showCloseButton: false
showCloseButton: this.props.showCloseButton || false,
onDismiss: this.props.onDismiss
},
modalProps: { isBlocking: this.props.isModal },
minWidth: DIALOG_MIN_WIDTH,

View File

@@ -84,6 +84,8 @@ exports[`test render renders with filters 1`] = `
"elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)",
"elevation8": "0 3.2px 7.2px 0 rgba(0, 0, 0, 0.132), 0 0.6px 1.8px 0 rgba(0, 0, 0, 0.108)",
"roundedCorner2": "2px",
"roundedCorner4": "4px",
"roundedCorner6": "6px",
},
"fonts": Object {
"large": Object {
@@ -421,6 +423,8 @@ exports[`test render renders with filters 1`] = `
"elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)",
"elevation8": "0 3.2px 7.2px 0 rgba(0, 0, 0, 0.132), 0 0.6px 1.8px 0 rgba(0, 0, 0, 0.108)",
"roundedCorner2": "2px",
"roundedCorner4": "4px",
"roundedCorner6": "6px",
},
"fonts": Object {
"large": Object {
@@ -812,6 +816,8 @@ exports[`test render renders with filters 1`] = `
"elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)",
"elevation8": "0 3.2px 7.2px 0 rgba(0, 0, 0, 0.132), 0 0.6px 1.8px 0 rgba(0, 0, 0, 0.108)",
"roundedCorner2": "2px",
"roundedCorner4": "4px",
"roundedCorner6": "6px",
},
"fonts": Object {
"large": Object {
@@ -1588,6 +1594,8 @@ exports[`test render renders with filters 1`] = `
"elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)",
"elevation8": "0 3.2px 7.2px 0 rgba(0, 0, 0, 0.132), 0 0.6px 1.8px 0 rgba(0, 0, 0, 0.108)",
"roundedCorner2": "2px",
"roundedCorner4": "4px",
"roundedCorner6": "6px",
},
"fonts": Object {
"large": Object {

View File

@@ -48,7 +48,6 @@ export const FeaturePanelComponent: React.FunctionComponent = () => {
{ key: "feature.dataexplorerexecutesproc", label: "Execute stored procedure", value: "true" },
{ key: "feature.hosteddataexplorerenabled", label: "Hosted Data Explorer (deprecated?)", value: "true" },
{ key: "feature.enablettl", label: "Enable TTL", value: "true" },
{ key: "feature.enablegallery", label: "Enable Notebook Gallery", value: "true" },
{ key: "feature.enablegallerypublish", label: "Enable Notebook Gallery Publishing", value: "true" },
{ key: "feature.canexceedmaximumvalue", label: "Can exceed max value", value: "true" },
{

View File

@@ -157,14 +157,14 @@ exports[`Feature panel renders all flags 1`] = `
/>
<StyledCheckboxBase
checked={false}
key="feature.enablegallery"
label="Enable Notebook Gallery"
key="feature.enablegallerypublish"
label="Enable Notebook Gallery Publishing"
onChange={[Function]}
/>
<StyledCheckboxBase
checked={false}
key="feature.enablegallerypublish"
label="Enable Notebook Gallery Publishing"
key="feature.canexceedmaximumvalue"
label="Can exceed max value"
onChange={[Function]}
/>
</Stack>
@@ -172,12 +172,6 @@ exports[`Feature panel renders all flags 1`] = `
className="checkboxRow"
horizontalAlign="space-between"
>
<StyledCheckboxBase
checked={false}
key="feature.canexceedmaximumvalue"
label="Can exceed max value"
onChange={[Function]}
/>
<StyledCheckboxBase
checked={false}
key="feature.enablefixedcollectionwithsharedthroughput"

View File

@@ -0,0 +1,83 @@
import * as React from "react";
import { Stack, Text, Separator, FontIcon, CommandButton, FontWeights, ITextProps } from "office-ui-fabric-react";
export class GalleryHeaderComponent extends React.Component {
private static readonly azureText = "Microsoft Azure";
private static readonly cosmosdbText = "Cosmos DB";
private static readonly galleryText = "Gallery";
private static readonly loginText = "Log in";
private static readonly openPortal = () => window.open("https://portal.azure.com", "_blank");
private static readonly openDataExplorer = () => (window.location.href = new URL("./", window.location.href).href);
private static readonly openGallery = () =>
(window.location.href = new URL("./galleryViewer.html", window.location.href).href);
private static readonly headerItemStyle: React.CSSProperties = {
color: "white"
};
private static readonly mainHeaderTextProps: ITextProps = {
style: GalleryHeaderComponent.headerItemStyle,
variant: "mediumPlus",
styles: {
root: {
fontWeight: FontWeights.semibold
}
}
};
private static readonly headerItemTextProps: ITextProps = { style: GalleryHeaderComponent.headerItemStyle };
private renderHeaderItem = (text: string, onClick: () => void, textProps: ITextProps): JSX.Element => {
return (
<CommandButton onClick={onClick} ariaLabel={text}>
<Text {...textProps}>{text}</Text>
</CommandButton>
);
};
public render(): JSX.Element {
return (
<Stack
tokens={{ childrenGap: 10 }}
horizontal
styles={{ root: { background: "#0078d4", paddingLeft: 20, paddingRight: 20 } }}
verticalAlign="center"
>
<Stack.Item>
{this.renderHeaderItem(
GalleryHeaderComponent.azureText,
GalleryHeaderComponent.openPortal,
GalleryHeaderComponent.mainHeaderTextProps
)}
</Stack.Item>
<Stack.Item>
<Separator vertical />
</Stack.Item>
<Stack.Item>
{this.renderHeaderItem(
GalleryHeaderComponent.cosmosdbText,
GalleryHeaderComponent.openDataExplorer,
GalleryHeaderComponent.headerItemTextProps
)}
</Stack.Item>
<Stack.Item>
<FontIcon style={GalleryHeaderComponent.headerItemStyle} iconName="ChevronRight" />
</Stack.Item>
<Stack.Item>
{this.renderHeaderItem(
GalleryHeaderComponent.galleryText,
GalleryHeaderComponent.openGallery,
GalleryHeaderComponent.headerItemTextProps
)}
</Stack.Item>
<Stack.Item grow>
<></>
</Stack.Item>
<Stack.Item>
{this.renderHeaderItem(
GalleryHeaderComponent.loginText,
GalleryHeaderComponent.openDataExplorer,
GalleryHeaderComponent.headerItemTextProps
)}
</Stack.Item>
</Stack>
);
}
}

View File

@@ -1,72 +0,0 @@
import { FontWeights } from "@uifabric/styling";
import { IIconStyles, ITextStyles } from "office-ui-fabric-react";
export const siteTextStyles: ITextStyles = {
root: {
color: "#025F52",
fontWeight: FontWeights.semibold
}
};
export const descriptionTextStyles: ITextStyles = {
root: {
color: "#333333",
fontWeight: FontWeights.semibold
}
};
export const subtleHelpfulTextStyles: ITextStyles = {
root: {
color: "#ccc",
fontWeight: FontWeights.regular
}
};
export const iconButtonStyles: IIconStyles = {
root: {
marginLeft: "10px",
color: "#0078D4",
backgroundColor: "#FFF",
fontSize: 16,
fontWeight: FontWeights.regular,
display: "inline-block",
selectors: {
":hover .ms-Button-icon": {
color: "#ccc"
}
}
}
};
export const iconStyles: IIconStyles = {
root: {
marginLeft: "10px",
color: "#0078D4",
backgroundColor: "#FFF",
fontSize: 16,
fontWeight: FontWeights.regular,
display: "inline-block"
}
};
export const mainHelpfulTextStyles: ITextStyles = {
root: {
color: "#000",
fontWeight: FontWeights.regular
}
};
export const subtleIconStyles: IIconStyles = {
root: {
color: "#ddd",
fontSize: 12,
fontWeight: FontWeights.regular
}
};
export const helpfulTextStyles: ITextStyles = {
root: {
color: "#333333",
fontWeight: FontWeights.regular
}
};

View File

@@ -17,6 +17,7 @@ import {
import * as React from "react";
import { IGalleryItem } from "../../../../Juno/JunoClient";
import { FileSystemUtil } from "../../../Notebook/FileSystemUtil";
import CosmosDBLogo from "../../../../../images/CosmosDB-logo.svg";
export interface GalleryCardComponentProps {
data: IGalleryItem;
@@ -55,7 +56,11 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
return (
<Card aria-label="Notebook Card" tokens={GalleryCardComponent.cardTokens} onClick={this.props.onClick}>
<Card.Item>
<Persona text={this.props.data.author} secondaryText={dateString} />
<Persona
imageUrl={this.props.data.isSample && CosmosDBLogo}
text={this.props.data.author}
secondaryText={dateString}
/>
</Card.Item>
<Card.Item fill>
@@ -89,15 +94,9 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
</Card.Section>
<Card.Section horizontal styles={{ root: { alignItems: "flex-end" } }}>
<Text variant="tiny" styles={{ root: { color: "#ccc" } }}>
<Icon iconName="RedEye" styles={{ root: { verticalAlign: "middle" } }} /> {this.props.data.views}
</Text>
<Text variant="tiny" styles={{ root: { color: "#ccc" } }}>
<Icon iconName="Download" styles={{ root: { verticalAlign: "middle" } }} /> {this.props.data.downloads}
</Text>
<Text variant="tiny" styles={{ root: { color: "#ccc" } }}>
<Icon iconName="Heart" styles={{ root: { verticalAlign: "middle" } }} /> {this.props.data.favorites}
</Text>
{this.generateIconText("RedEye", this.props.data.views.toString())}
{this.generateIconText("Download", this.props.data.downloads.toString())}
{this.props.isFavorite !== undefined && this.generateIconText("Heart", this.props.data.favorites.toString())}
</Card.Section>
<Card.Item>
@@ -105,17 +104,18 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
</Card.Item>
<Card.Section horizontal styles={{ root: { marginTop: 0 } }}>
{this.generateIconButtonWithTooltip(
this.props.isFavorite ? "HeartFill" : "Heart",
this.props.isFavorite ? "Unlike" : "Like",
this.props.isFavorite ? this.onUnfavoriteClick : this.onFavoriteClick
)}
{this.props.isFavorite !== undefined &&
this.generateIconButtonWithTooltip(
this.props.isFavorite ? "HeartFill" : "Heart",
this.props.isFavorite ? "Unlike" : "Like",
this.props.isFavorite ? this.onUnfavoriteClick : this.onFavoriteClick
)}
{this.generateIconButtonWithTooltip("Download", "Download", this.onDownloadClick)}
{this.props.showDelete && (
<div style={{ width: "100%", textAlign: "right" }}>
{this.generateIconButtonWithTooltip("Delete", "Remove", this.props.onDeleteClick)}
{this.generateIconButtonWithTooltip("Delete", "Remove", this.onDeleteClick)}
</div>
)}
</Card.Section>
@@ -123,6 +123,14 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
);
}
private generateIconText = (iconName: string, text: string): JSX.Element => {
return (
<Text variant="tiny" styles={{ root: { color: "#ccc" } }}>
<Icon iconName={iconName} styles={{ root: { verticalAlign: "middle" } }} /> {text}
</Text>
);
};
/*
* Fluent UI doesn't support tooltips on IconButtons out of the box. In the meantime the recommendation is
* to do the following (from https://developer.microsoft.com/en-us/fluentui#/controls/web/button)

View File

@@ -14,6 +14,7 @@ exports[`GalleryCardComponent renders 1`] = `
>
<CardItem>
<StyledPersonaBase
imageUrl={false}
secondaryText="Invalid Date"
text="author"
/>
@@ -21,7 +22,7 @@ exports[`GalleryCardComponent renders 1`] = `
<CardItem
fill={true}
>
<StyledImageBase
<Memo(StyledImageBase)
alt="Notebook cover image"
height={144}
imageFit={2}
@@ -89,7 +90,7 @@ exports[`GalleryCardComponent renders 1`] = `
}
variant="tiny"
>
<StyledIconBase
<Memo(StyledIconBase)
iconName="RedEye"
styles={
Object {
@@ -112,7 +113,7 @@ exports[`GalleryCardComponent renders 1`] = `
}
variant="tiny"
>
<StyledIconBase
<Memo(StyledIconBase)
iconName="Download"
styles={
Object {
@@ -135,7 +136,7 @@ exports[`GalleryCardComponent renders 1`] = `
}
variant="tiny"
>
<StyledIconBase
<Memo(StyledIconBase)
iconName="Heart"
styles={
Object {
@@ -256,6 +257,7 @@ exports[`GalleryCardComponent renders 1`] = `
"iconName": "Delete",
}
}
onClick={[Function]}
title="Remove"
/>
</StyledTooltipHostBase>

View File

@@ -75,27 +75,10 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
private static readonly mostViewedText = "Most viewed";
private static readonly mostDownloadedText = "Most downloaded";
private static readonly mostFavoritedText = "Most favorited";
private static readonly mostFavoritedText = "Most liked";
private static readonly mostRecentText = "Most recent";
private static readonly sortingOptions: IDropdownOption[] = [
{
key: SortBy.MostViewed,
text: GalleryViewerComponent.mostViewedText
},
{
key: SortBy.MostDownloaded,
text: GalleryViewerComponent.mostDownloadedText
},
{
key: SortBy.MostFavorited,
text: GalleryViewerComponent.mostFavoritedText
},
{
key: SortBy.MostRecent,
text: GalleryViewerComponent.mostRecentText
}
];
private readonly sortingOptions: IDropdownOption[];
private sampleNotebooks: IGalleryItem[];
private publicNotebooks: IGalleryItem[];
@@ -118,8 +101,29 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
dialogProps: undefined
};
this.sortingOptions = [
{
key: SortBy.MostViewed,
text: GalleryViewerComponent.mostViewedText
},
{
key: SortBy.MostDownloaded,
text: GalleryViewerComponent.mostDownloadedText
},
{
key: SortBy.MostRecent,
text: GalleryViewerComponent.mostRecentText
}
];
if (this.props.container?.isGalleryPublishEnabled()) {
this.sortingOptions.push({
key: SortBy.MostFavorited,
text: GalleryViewerComponent.mostFavoritedText
});
}
this.loadTabContent(this.state.selectedTab, this.state.searchText, this.state.sortBy, false);
if (this.props.container) {
if (this.props.container?.isGalleryPublishEnabled()) {
this.loadFavoriteNotebooks(this.state.searchText, this.state.sortBy, false); // Need this to show correct favorite button state
}
}
@@ -131,16 +135,10 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
public render(): JSX.Element {
const tabs: GalleryTabInfo[] = [this.createTab(GalleryTab.OfficialSamples, this.state.sampleNotebooks)];
if (this.props.container) {
if (this.props.container.isGalleryPublishEnabled()) {
tabs.push(this.createTab(GalleryTab.PublicGallery, this.state.publicNotebooks));
}
if (this.props.container?.isGalleryPublishEnabled()) {
tabs.push(this.createTab(GalleryTab.PublicGallery, this.state.publicNotebooks));
tabs.push(this.createTab(GalleryTab.Favorites, this.state.favoriteNotebooks));
if (this.props.container.isGalleryPublishEnabled()) {
tabs.push(this.createTab(GalleryTab.Published, this.state.publishedNotebooks));
}
tabs.push(this.createTab(GalleryTab.Published, this.state.publishedNotebooks));
}
const pivotProps: IPivotProps = {
@@ -189,11 +187,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
<Label>Sort by</Label>
</Stack.Item>
<Stack.Item styles={{ root: { minWidth: 200 } }}>
<Dropdown
options={GalleryViewerComponent.sortingOptions}
selectedKey={this.state.sortBy}
onChange={this.onDropdownChange}
/>
<Dropdown options={this.sortingOptions} selectedKey={this.state.sortBy} onChange={this.onDropdownChange} />
</Stack.Item>
</Stack>
@@ -405,7 +399,10 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
};
private onRenderCell = (data?: IGalleryItem): JSX.Element => {
const isFavorite = this.favoriteNotebooks?.find(item => item.id === data.id) !== undefined;
let isFavorite: boolean;
if (this.props.container?.isGalleryPublishEnabled()) {
isFavorite = this.favoriteNotebooks?.find(item => item.id === data.id) !== undefined;
}
const props: GalleryCardComponentProps = {
data,
isFavorite,
@@ -434,7 +431,8 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
[GalleryUtils.NotebookViewerParams.GalleryItemId]: data.id
});
window.open(`/notebookViewer.html?${params.toString()}`);
const location = new URL("./notebookViewer.html", window.location.href).href;
window.open(`${location}?${params.toString()}`);
}
};

View File

@@ -67,10 +67,6 @@ exports[`GalleryViewerComponent renders 1`] = `
"key": 1,
"text": "Most downloaded",
},
Object {
"key": 2,
"text": "Most favorited",
},
Object {
"key": 3,
"text": "Most recent",

View File

@@ -44,11 +44,15 @@ export class NotebookMetadataComponent extends React.Component<NotebookMetadataC
{FileSystemUtil.stripExtension(this.props.data.name, "ipynb")}
</Text>
<Text>
<IconButton
iconProps={{ iconName: this.props.isFavorite ? "HeartFill" : "Heart" }}
onClick={this.props.isFavorite ? this.props.onUnfavoriteClick : this.props.onFavoriteClick}
/>
{this.props.data.favorites} likes
{this.props.isFavorite !== undefined && (
<>
<IconButton
iconProps={{ iconName: this.props.isFavorite ? "HeartFill" : "Heart" }}
onClick={this.props.isFavorite ? this.props.onUnfavoriteClick : this.props.onFavoriteClick}
/>
{this.props.data.favorites} likes
</>
)}
</Text>
<PrimaryButton text={this.props.downloadButtonText} onClick={this.props.onDownloadClick} />
</Stack>

View File

@@ -6,21 +6,3 @@
width: 100%;
overflow-y: auto;
}
.downloadButton {
background-color: #0078D4;
color: @BaseLight;
cursor: pointer;
padding: 5px;
border: none;
text-align: left;
outline: none;
font-size: @mediumFontSize;
border-radius: 5px;
display: "inline-block";
margin: 10px;
}
.active, .downloadButton:hover {
color: @BaseMedium;
}

View File

@@ -26,6 +26,7 @@ export interface NotebookViewerComponentProps {
galleryItem?: IGalleryItem;
isFavorite?: boolean;
backNavigationText: string;
hideInputs?: boolean;
onBackClick: () => void;
onTagClick: (tag: string) => void;
}
@@ -129,7 +130,9 @@ export class NotebookViewerComponent extends React.Component<NotebookViewerCompo
<></>
)}
{this.notebookComponentBootstrapper.renderComponent(NotebookReadOnlyRenderer, { hideInputs: true })}
{this.notebookComponentBootstrapper.renderComponent(NotebookReadOnlyRenderer, {
hideInputs: this.props.hideInputs
})}
{this.state.dialogProps && <DialogComponent {...this.state.dialogProps} />}
</div>

View File

@@ -55,14 +55,14 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
Invalid Date
</Text>
<Text>
<StyledIconBase
<Memo(StyledIconBase)
iconName="RedEye"
/>
0
</Text>
<Text>
<StyledIconBase
<Memo(StyledIconBase)
iconName="Download"
/>
0
@@ -154,14 +154,14 @@ exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
Invalid Date
</Text>
<Text>
<StyledIconBase
<Memo(StyledIconBase)
iconName="RedEye"
/>
0
</Text>
<Text>
<StyledIconBase
<Memo(StyledIconBase)
iconName="Download"
/>
0

View File

@@ -0,0 +1,16 @@
@import "../../../../less/Common/Constants.less";
.radioSwitchComponent {
cursor: pointer;
display: flex;
&>span:nth-child(n+2) {
margin-left: 10px;
}
.caption {
color: @BaseDark;
padding-left: @SmallSpace;
vertical-align: top;
}
}

View File

@@ -0,0 +1,51 @@
/**
* Horizontal switch component
*/
import * as React from "react";
import "./RadioSwitchComponent.less";
import { Icon } from "office-ui-fabric-react/lib/Icon";
import { NormalizedEventKey } from "../../../Common/Constants";
export interface Choice {
key: string;
onSelect: () => void;
label: string;
}
export interface RadioSwitchComponentProps {
choices: Choice[];
selectedKey: string;
onSelectionKeyChange?: (newValue: string) => void;
}
export class RadioSwitchComponent extends React.Component<RadioSwitchComponentProps> {
public render(): JSX.Element {
return (
<div className="radioSwitchComponent">
{this.props.choices.map((choice: Choice) => (
<span
tabIndex={0}
key={choice.key}
onClick={() => this.onSelect(choice)}
onKeyPress={event => this.onKeyPress(event, choice)}
>
<Icon iconName={this.props.selectedKey === choice.key ? "RadioBtnOn" : "RadioBtnOff"} />
<span className="caption">{choice.label}</span>
</span>
))}
</div>
);
}
private onSelect(choice: Choice): void {
this.props.onSelectionKeyChange && this.props.onSelectionKeyChange(choice.key);
choice.onSelect();
}
private onKeyPress(event: React.KeyboardEvent<HTMLSpanElement>, choice: Choice): void {
if (event.key === NormalizedEventKey.Enter || event.key === NormalizedEventKey.Space) {
this.onSelect(choice);
}
}
}

View File

@@ -0,0 +1,35 @@
/* Utilities for validation */
export const onValidateValueChange = (newValue: string, minValue?: number, maxValue?: number): number => {
let numericValue = parseInt(newValue);
if (!isNaN(numericValue) && isFinite(numericValue)) {
if (minValue !== undefined && numericValue < minValue) {
numericValue = minValue;
}
if (maxValue !== undefined && numericValue > maxValue) {
numericValue = maxValue;
}
return Math.floor(numericValue);
}
return undefined;
};
export const onIncrementValue = (newValue: string, step: number, max?: number): number => {
const numericValue = parseInt(newValue);
if (!isNaN(numericValue) && isFinite(numericValue)) {
const newValue = numericValue + step;
return max !== undefined ? Math.min(max, newValue) : newValue;
}
return undefined;
};
export const onDecrementValue = (newValue: string, step: number, min?: number): number => {
const numericValue = parseInt(newValue);
if (!isNaN(numericValue) && isFinite(numericValue)) {
const newValue = numericValue - step;
return min !== undefined ? Math.max(min, newValue) : newValue;
}
return undefined;
};

View File

@@ -0,0 +1,14 @@
@import "../../../../less/Common/Constants.less";
.widgetRendererContainer {
text-align: left;
.inputLabelContainer {
margin-bottom: 4px;
.inputLabel {
color: #393939;
font-weight: 600;
}
}
}

View File

@@ -0,0 +1,88 @@
import React from "react";
import { shallow } from "enzyme";
import { SmartUiComponent, Descriptor, InputType } from "./SmartUiComponent";
describe("SmartUiComponent", () => {
const exampleData: Descriptor = {
root: {
id: "root",
info: {
message: "Start at $24/mo per database",
link: {
href: "https://aka.ms/azure-cosmos-db-pricing",
text: "More Details"
}
},
children: [
{
id: "throughput",
input: {
label: "Throughput (input)",
dataFieldName: "throughput",
type: "number",
min: 400,
max: 500,
step: 10,
defaultValue: 400,
inputType: "spin"
}
},
{
id: "throughput2",
input: {
label: "Throughput (Slider)",
dataFieldName: "throughput2",
type: "number",
min: 400,
max: 500,
step: 10,
defaultValue: 400,
inputType: "slider"
}
},
{
id: "containerId",
input: {
label: "Container id",
dataFieldName: "containerId",
type: "string"
}
},
{
id: "analyticalStore",
input: {
label: "Analytical Store",
trueLabel: "Enabled",
falseLabel: "Disabled",
defaultValue: true,
dataFieldName: "analyticalStore",
type: "boolean"
}
},
{
id: "database",
input: {
label: "Database",
dataFieldName: "database",
type: "enum",
choices: [
{ label: "Database 1", key: "db1", value: "database1" },
{ label: "Database 2", key: "db2", value: "database2" },
{ label: "Database 3", key: "db3", value: "database3" }
],
defaultKey: "db2"
}
}
]
}
};
const exampleCallbacks = (newValues: Map<string, InputType>): void => {
console.log("New values:", newValues);
};
it("should render", () => {
const wrapper = shallow(<SmartUiComponent descriptor={exampleData} onChange={exampleCallbacks} />);
expect(wrapper).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,335 @@
import * as React from "react";
import { Position } from "office-ui-fabric-react/lib/utilities/positioning";
import { Slider } from "office-ui-fabric-react/lib/Slider";
import { SpinButton } from "office-ui-fabric-react/lib/SpinButton";
import { Dropdown, IDropdownOption } from "office-ui-fabric-react/lib/Dropdown";
import { TextField } from "office-ui-fabric-react/lib/TextField";
import { Text } from "office-ui-fabric-react/lib/Text";
import { InputType } from "../../Tables/Constants";
import { RadioSwitchComponent } from "../RadioSwitchComponent/RadioSwitchComponent";
import { Stack, IStackTokens } from "office-ui-fabric-react/lib/Stack";
import { Link, MessageBar, MessageBarType } from "office-ui-fabric-react";
import * as InputUtils from "./InputUtils";
import "./SmartUiComponent.less";
/**
* Generic UX renderer
* It takes:
* - a JSON object as data
* - a Map of callbacks
* - a descriptor of the UX.
*/
export type InputTypeValue = "number" | "string" | "boolean" | "enum";
/* eslint-disable-next-line @typescript-eslint/no-explicit-any */
export type EnumItem = { label: string; key: string; value: any };
export type InputType = number | string | boolean | EnumItem;
interface BaseInput {
label: string;
dataFieldName: string;
type: InputTypeValue;
placeholder?: string;
}
/**
* For now, this only supports integers
*/
export interface NumberInput extends BaseInput {
min?: number;
max?: number;
step: number;
defaultValue: number;
inputType: "spin" | "slider";
}
export interface BooleanInput extends BaseInput {
trueLabel: string;
falseLabel: string;
defaultValue: boolean;
}
export interface StringInput extends BaseInput {
defaultValue?: string;
}
export interface EnumInput extends BaseInput {
choices: EnumItem[];
defaultKey: string;
}
export interface Info {
message: string;
link?: {
href: string;
text: string;
};
}
export type AnyInput = NumberInput | BooleanInput | StringInput | EnumInput;
export interface Node {
id: string;
info?: Info;
input?: AnyInput;
children?: Node[];
}
export interface Descriptor {
root: Node;
}
/************************** Component implementation starts here ************************************* */
export interface SmartUiComponentProps {
descriptor: Descriptor;
onChange: (newValues: Map<string, InputType>) => void;
}
interface SmartUiComponentState {
currentValues: Map<string, InputType>;
errors: Map<string, string>;
}
export class SmartUiComponent extends React.Component<SmartUiComponentProps, SmartUiComponentState> {
private static readonly labelStyle = {
color: "#393939",
fontFamily: "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
fontSize: 12
};
constructor(props: SmartUiComponentProps) {
super(props);
this.state = {
currentValues: new Map(),
errors: new Map()
};
}
private renderInfo(info: Info): JSX.Element {
return (
<MessageBar>
{info.message}
<Link href={info.link.href} target="_blank">
{info.link.text}
</Link>
</MessageBar>
);
}
private onInputChange = (newValue: string | number | boolean, dataFieldName: string) => {
const { currentValues } = this.state;
currentValues.set(dataFieldName, newValue);
this.setState({ currentValues }, () => this.props.onChange(this.state.currentValues));
};
private renderStringInput(input: StringInput): JSX.Element {
return (
<div className="stringInputContainer">
<div>
<TextField
id={`${input.dataFieldName}-input`}
label={input.label}
type="text"
value={input.defaultValue}
placeholder={input.placeholder}
onChange={(_, newValue) => this.onInputChange(newValue, input.dataFieldName)}
styles={{
subComponentStyles: {
label: {
root: {
...SmartUiComponent.labelStyle,
fontWeight: 600
}
}
}
}}
/>
</div>
</div>
);
}
private clearError(dataFieldName: string): void {
const { errors } = this.state;
errors.delete(dataFieldName);
this.setState({ errors });
}
private onValidate = (value: string, min: number, max: number, dataFieldName: string): string => {
const newValue = InputUtils.onValidateValueChange(value, min, max);
if (newValue) {
this.onInputChange(newValue, dataFieldName);
this.clearError(dataFieldName);
return newValue.toString();
} else {
const { errors } = this.state;
errors.set(dataFieldName, `Invalid value ${value}: must be between ${min} and ${max}`);
this.setState({ errors });
}
return undefined;
};
private onIncrement = (value: string, step: number, max: number, dataFieldName: string): string => {
const newValue = InputUtils.onIncrementValue(value, step, max);
if (newValue) {
this.onInputChange(newValue, dataFieldName);
this.clearError(dataFieldName);
return newValue.toString();
}
return undefined;
};
private onDecrement = (value: string, step: number, min: number, dataFieldName: string): string => {
const newValue = InputUtils.onDecrementValue(value, step, min);
if (newValue) {
this.onInputChange(newValue, dataFieldName);
this.clearError(dataFieldName);
return newValue.toString();
}
return undefined;
};
private renderNumberInput(input: NumberInput): JSX.Element {
const { label, min, max, defaultValue, dataFieldName, step } = input;
const props = { label, min, max, ariaLabel: label, step };
if (input.inputType === "spin") {
return (
<div>
<SpinButton
{...props}
defaultValue={defaultValue.toString()}
onValidate={newValue => this.onValidate(newValue, min, max, dataFieldName)}
onIncrement={newValue => this.onIncrement(newValue, step, max, dataFieldName)}
onDecrement={newValue => this.onDecrement(newValue, step, min, dataFieldName)}
labelPosition={Position.top}
styles={{
label: {
...SmartUiComponent.labelStyle,
fontWeight: 600
}
}}
/>
{this.state.errors.has(dataFieldName) && (
<MessageBar messageBarType={MessageBarType.error}>Error: {this.state.errors.get(dataFieldName)}</MessageBar>
)}
</div>
);
} else if (input.inputType === "slider") {
return (
<Slider
// showValue={true}
// valueFormat={}
{...props}
defaultValue={defaultValue}
onChange={newValue => this.onInputChange(newValue, dataFieldName)}
styles={{
titleLabel: {
...SmartUiComponent.labelStyle,
fontWeight: 600
},
valueLabel: SmartUiComponent.labelStyle
}}
/>
);
} else {
return <>Unsupported number input type {input.inputType}</>;
}
}
private renderBooleanInput(input: BooleanInput): JSX.Element {
const { dataFieldName } = input;
return (
<div>
<div className="inputLabelContainer">
<Text variant="small" nowrap className="inputLabel">
{input.label}
</Text>
</div>
<RadioSwitchComponent
choices={[
{
label: input.falseLabel,
key: "false",
onSelect: () => this.onInputChange(false, dataFieldName)
},
{
label: input.trueLabel,
key: "true",
onSelect: () => this.onInputChange(true, dataFieldName)
}
]}
selectedKey={
(this.state.currentValues.has(dataFieldName)
? (this.state.currentValues.get(dataFieldName) as boolean)
: input.defaultValue)
? "true"
: "false"
}
/>
</div>
);
}
private renderEnumInput(input: EnumInput): JSX.Element {
const { label, defaultKey, dataFieldName, choices, placeholder } = input;
return (
<Dropdown
label={label}
selectedKey={
this.state.currentValues.has(dataFieldName)
? (this.state.currentValues.get(dataFieldName) as string)
: defaultKey
}
onChange={(_, item: IDropdownOption) => this.onInputChange(item.key.toString(), dataFieldName)}
placeholder={placeholder}
options={choices.map(c => ({
key: c.key,
text: c.value
}))}
styles={{
label: {
...SmartUiComponent.labelStyle,
fontWeight: 600
},
dropdown: SmartUiComponent.labelStyle
}}
/>
);
}
private renderInput(input: AnyInput): JSX.Element {
switch (input.type) {
case "string":
return this.renderStringInput(input as StringInput);
case "number":
return this.renderNumberInput(input as NumberInput);
case "boolean":
return this.renderBooleanInput(input as BooleanInput);
case "enum":
return this.renderEnumInput(input as EnumInput);
default:
throw new Error(`Unknown input type: ${input.type}`);
}
}
private renderNode(node: Node): JSX.Element {
const containerStackTokens: IStackTokens = { childrenGap: 10 };
return (
<Stack tokens={containerStackTokens} className="widgetRendererContainer">
{node.info && this.renderInfo(node.info)}
{node.input && this.renderInput(node.input)}
{node.children && node.children.map(child => <div key={child.id}>{this.renderNode(child)}</div>)}
</Stack>
);
}
render(): JSX.Element {
return <>{this.renderNode(this.props.descriptor.root)}</>;
}
}

View File

@@ -0,0 +1,240 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SmartUiComponent should render 1`] = `
<Fragment>
<Stack
className="widgetRendererContainer"
tokens={
Object {
"childrenGap": 10,
}
}
>
<StyledMessageBarBase>
Start at $24/mo per database
<StyledLinkBase
href="https://aka.ms/azure-cosmos-db-pricing"
target="_blank"
>
More Details
</StyledLinkBase>
</StyledMessageBarBase>
<div
key="throughput"
>
<Stack
className="widgetRendererContainer"
tokens={
Object {
"childrenGap": 10,
}
}
>
<div>
<CustomizedSpinButton
ariaLabel="Throughput (input)"
decrementButtonIcon={
Object {
"iconName": "ChevronDownSmall",
}
}
defaultValue="400"
disabled={false}
incrementButtonIcon={
Object {
"iconName": "ChevronUpSmall",
}
}
label="Throughput (input)"
labelPosition={0}
max={500}
min={400}
onDecrement={[Function]}
onIncrement={[Function]}
onValidate={[Function]}
step={10}
styles={
Object {
"label": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
"fontWeight": 600,
},
}
}
/>
</div>
</Stack>
</div>
<div
key="throughput2"
>
<Stack
className="widgetRendererContainer"
tokens={
Object {
"childrenGap": 10,
}
}
>
<StyledSliderBase
ariaLabel="Throughput (Slider)"
defaultValue={400}
label="Throughput (Slider)"
max={500}
min={400}
onChange={[Function]}
step={10}
styles={
Object {
"titleLabel": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
"fontWeight": 600,
},
"valueLabel": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
},
}
}
/>
</Stack>
</div>
<div
key="containerId"
>
<Stack
className="widgetRendererContainer"
tokens={
Object {
"childrenGap": 10,
}
}
>
<div
className="stringInputContainer"
>
<div>
<StyledTextFieldBase
id="containerId-input"
label="Container id"
onChange={[Function]}
styles={
Object {
"subComponentStyles": Object {
"label": Object {
"root": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
"fontWeight": 600,
},
},
},
}
}
type="text"
/>
</div>
</div>
</Stack>
</div>
<div
key="analyticalStore"
>
<Stack
className="widgetRendererContainer"
tokens={
Object {
"childrenGap": 10,
}
}
>
<div>
<div
className="inputLabelContainer"
>
<Text
className="inputLabel"
nowrap={true}
variant="small"
>
Analytical Store
</Text>
</div>
<RadioSwitchComponent
choices={
Array [
Object {
"key": "false",
"label": "Disabled",
"onSelect": [Function],
},
Object {
"key": "true",
"label": "Enabled",
"onSelect": [Function],
},
]
}
selectedKey="true"
/>
</div>
</Stack>
</div>
<div
key="database"
>
<Stack
className="widgetRendererContainer"
tokens={
Object {
"childrenGap": 10,
}
}
>
<StyledWithResponsiveMode
label="Database"
onChange={[Function]}
options={
Array [
Object {
"key": "db1",
"text": "database1",
},
Object {
"key": "db2",
"text": "database2",
},
Object {
"key": "db3",
"text": "database3",
},
]
}
selectedKey="db2"
styles={
Object {
"dropdown": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
},
"label": Object {
"color": "#393939",
"fontFamily": "wf_segoe-ui_normal, 'Segoe UI', 'Segoe WP', Tahoma, Arial, sans-serif",
"fontSize": 12,
"fontWeight": 600,
},
}
}
/>
</Stack>
</div>
</Stack>
</Fragment>
`;

View File

@@ -55,7 +55,6 @@ import { IGalleryItem } from "../Juno/JunoClient";
import { LibraryManagePane } from "./Panes/LibraryManagePane";
import { LoadQueryPane } from "./Panes/LoadQueryPane";
import * as Logger from "../Common/Logger";
import { ManageSparkClusterPane } from "./Panes/ManageSparkClusterPane";
import { MessageHandler } from "../Common/MessageHandler";
import { NotebookContentItem, NotebookContentItemType } from "./Notebook/NotebookContentItem";
import { NotebookUtil } from "./Notebook/NotebookUtil";
@@ -73,16 +72,17 @@ import { RouteHandler } from "../RouteHandlers/RouteHandler";
import { SaveQueryPane } from "./Panes/SaveQueryPane";
import { SettingsPane } from "./Panes/SettingsPane";
import { SetupNotebooksPane } from "./Panes/SetupNotebooksPane";
import { SetupSparkClusterPane } from "./Panes/SetupSparkClusterPane";
import { SparkClusterManager } from "../SparkClusterManager/SparkClusterManager";
import { SplashScreenComponentAdapter } from "./SplashScreen/SplashScreenComponentApdapter";
import { Splitter, SplitterBounds, SplitterDirection } from "../Common/Splitter";
import { StringInputPane } from "./Panes/StringInputPane";
import { TableColumnOptionsPane } from "./Panes/Tables/TableColumnOptionsPane";
import { TabsManager } from "./Tabs/TabsManager";
import { UploadFilePane } from "./Panes/UploadFilePane";
import { UploadItemsPane } from "./Panes/UploadItemsPane";
import { UploadItemsPaneAdapter } from "./Panes/UploadItemsPaneAdapter";
import { ReactAdapter } from "../Bindings/ReactBindingHandler";
import { toRawContentUri, fromContentUri } from "../Utils/GitHubUtils";
BindingHandlersRegisterer.registerBindingHandlers();
// Hold a reference to ComponentRegisterer to prevent transpiler to ignore import
@@ -162,11 +162,10 @@ export default class Explorer implements ViewModels.Explorer {
private resourceTreeForResourceToken: ResourceTreeAdapterForResourceToken;
// Tabs
public openedTabs: ko.ObservableArray<ViewModels.Tab>;
public activeTab: ko.Observable<ViewModels.Tab>;
public isTabsContentExpanded: ko.Observable<boolean>;
public galleryTab: any;
public notebookViewerTab: any;
public tabsManager: TabsManager;
// Contextual panes
public addDatabasePane: ViewModels.AddDatabasePane;
@@ -191,15 +190,12 @@ export default class Explorer implements ViewModels.Explorer {
public uploadFilePane: UploadFilePane;
public stringInputPane: StringInputPane;
public setupNotebooksPane: SetupNotebooksPane;
public setupSparkClusterPane: ViewModels.ContextualPane;
public manageSparkClusterPane: ViewModels.ContextualPane;
public libraryManagePane: ViewModels.ContextualPane;
public clusterLibraryPane: ViewModels.ContextualPane;
public gitHubReposPane: ViewModels.ContextualPane;
public publishNotebookPaneAdapter: ReactAdapter;
// features
public isGalleryEnabled: ko.Computed<boolean>;
public isGalleryPublishEnabled: ko.Computed<boolean>;
public isGitHubPaneEnabled: ko.Observable<boolean>;
public isPublishNotebookPaneEnabled: ko.Observable<boolean>;
@@ -233,7 +229,6 @@ export default class Explorer implements ViewModels.Explorer {
public arcadiaWorkspaces: ko.ObservableArray<ArcadiaWorkspaceItem>;
public hasStorageAnalyticsAfecFeature: ko.Observable<boolean>;
public isSynapseLinkUpdating: ko.Observable<boolean>;
public isNotebookTabActive: ko.Computed<boolean>;
public memoryUsageInfo: ko.Observable<DataModels.MemoryUsageInfo>;
public notebookManager?: any; // This is dynamically loaded
@@ -244,7 +239,10 @@ export default class Explorer implements ViewModels.Explorer {
private _isInitializingSparkConnectionInfo: boolean;
private notebookBasePath: ko.Observable<string>;
private _arcadiaManager: ViewModels.ArcadiaResourceManager;
private _filePathToImportAndOpen: string;
private notebookToImport: {
name: string;
content: string;
};
// React adapters
private commandBarComponentAdapter: CommandBarComponentAdapter;
@@ -297,14 +295,10 @@ export default class Explorer implements ViewModels.Explorer {
this.arcadiaToken = ko.observable<string>();
this.arcadiaToken.subscribe((token: string) => {
if (token) {
this.openedTabs &&
this.openedTabs().forEach(tab => {
if (tab.tabKind === ViewModels.CollectionTabKind.Notebook) {
throw new Error("NotebookTab is deprecated. Use NotebookV2Tab");
} else if (tab.tabKind === ViewModels.CollectionTabKind.NotebookV2) {
(tab as NotebookV2Tab).reconfigureServiceEndpoints();
}
});
const notebookTabs = this.tabsManager.getTabs(ViewModels.CollectionTabKind.NotebookV2);
(notebookTabs || []).forEach((tab: NotebookV2Tab) => {
tab.reconfigureServiceEndpoints();
});
}
});
this.isNotebooksEnabledForAccount = ko.observable(false);
@@ -344,7 +338,7 @@ export default class Explorer implements ViewModels.Explorer {
await this.initNotebooks(this.databaseAccount());
const workspaces = await this._getArcadiaWorkspaces();
this.arcadiaWorkspaces(workspaces);
} else if (this._filePathToImportAndOpen) {
} else if (this.notebookToImport) {
// if notebooks is not enabled but the user is trying to do a quickstart setup with notebooks, open the SetupNotebooksPane
this._openSetupNotebooksPaneForQuickstart();
}
@@ -405,7 +399,6 @@ export default class Explorer implements ViewModels.Explorer {
this.shouldShowShareDialogContents = ko.observable<boolean>(false);
this.shouldShowDataAccessExpiryDialog = ko.observable<boolean>(false);
this.shouldShowContextSwitchPrompt = ko.observable<boolean>(false);
this.isGalleryEnabled = ko.computed<boolean>(() => this.isFeatureEnabled(Constants.Features.enableGallery));
this.isGalleryPublishEnabled = ko.computed<boolean>(() =>
this.isFeatureEnabled(Constants.Features.enableGalleryPublish)
);
@@ -757,22 +750,6 @@ export default class Explorer implements ViewModels.Explorer {
container: this
});
this.setupSparkClusterPane = new SetupSparkClusterPane({
documentClientUtility: this.documentClientUtility,
id: "setupsparkclusterpane",
visible: ko.observable<boolean>(false),
container: this
});
this.manageSparkClusterPane = new ManageSparkClusterPane({
documentClientUtility: this.documentClientUtility,
id: "managesparkclusterpane",
visible: ko.observable<boolean>(false),
container: this
});
this.libraryManagePane = new LibraryManagePane({
documentClientUtility: this.documentClientUtility,
id: "libraryManagePane",
@@ -789,12 +766,8 @@ export default class Explorer implements ViewModels.Explorer {
container: this
});
this.openedTabs = ko.observableArray<ViewModels.Tab>([]);
this.activeTab = ko.observable(null);
this.isNotebookTabActive = ko.computed<boolean>(() => {
// only show memory tracker if the current active tab is a notebook
return this.activeTab() && this.activeTab().tabKind === ViewModels.CollectionTabKind.NotebookV2;
});
this.tabsManager = new TabsManager();
this._panes = [
this.addDatabasePane,
this.addCollectionPane,
@@ -816,9 +789,7 @@ export default class Explorer implements ViewModels.Explorer {
this.browseQueriesPane,
this.uploadFilePane,
this.stringInputPane,
this.setupNotebooksPane,
this.setupSparkClusterPane,
this.manageSparkClusterPane
this.setupNotebooksPane
];
this.addDatabaseText.subscribe((addDatabaseText: string) => this.addDatabasePane.title(addDatabaseText));
this.rebindDocumentClientUtility.bind(this);
@@ -1282,7 +1253,7 @@ export default class Explorer implements ViewModels.Explorer {
class: "connectDialogButtons okBtn connectOkBtns",
click: () => {
$("#contextSwitchPrompt").dialog("close");
this.openedTabs([]); // clear all tabs so we dont leave any tabs from previous session open
this.tabsManager.closeTabs(); // clear all tabs so we dont leave any tabs from previous session open
this.renewShareAccess(connectionString);
}
};
@@ -2110,10 +2081,6 @@ export default class Explorer implements ViewModels.Explorer {
return Q();
}
public findActiveTab(): ViewModels.Tab {
return this.activeTab();
}
public findSelectedCollection(): ViewModels.Collection {
if (this.selectedNode().nodeKind === "Collection") {
return this.findSelectedCollectionForSelectedNode();
@@ -2126,11 +2093,9 @@ export default class Explorer implements ViewModels.Explorer {
public findSelectedStoredProcedure(): ViewModels.StoredProcedure {
const selectedCollection: ViewModels.Collection = this.findSelectedCollection();
return _.find(selectedCollection.storedProcedures(), (storedProcedure: ViewModels.StoredProcedure) => {
const openedSprocTab = this.openedTabs().filter(
(tab: ViewModels.Tab) =>
tab.node &&
tab.node.rid === storedProcedure.rid &&
tab.tabKind === ViewModels.CollectionTabKind.StoredProcedures
const openedSprocTab = this.tabsManager.getTabs(
ViewModels.CollectionTabKind.StoredProcedures,
(tab: ViewModels.Tab) => tab.node && tab.node.rid === storedProcedure.rid
);
return (
storedProcedure.rid === this.selectedNode().rid ||
@@ -2142,11 +2107,9 @@ export default class Explorer implements ViewModels.Explorer {
public findSelectedUDF(): ViewModels.UserDefinedFunction {
const selectedCollection: ViewModels.Collection = this.findSelectedCollection();
return _.find(selectedCollection.userDefinedFunctions(), (userDefinedFunction: ViewModels.UserDefinedFunction) => {
const openedUdfTab = this.openedTabs().filter(
(tab: ViewModels.Tab) =>
tab.node &&
tab.node.rid === userDefinedFunction.rid &&
tab.tabKind === ViewModels.CollectionTabKind.UserDefinedFunctions
const openedUdfTab = this.tabsManager.getTabs(
ViewModels.CollectionTabKind.UserDefinedFunctions,
(tab: ViewModels.Tab) => tab.node && tab.node.rid === userDefinedFunction.rid
);
return (
userDefinedFunction.rid === this.selectedNode().rid ||
@@ -2158,9 +2121,9 @@ export default class Explorer implements ViewModels.Explorer {
public findSelectedTrigger(): ViewModels.Trigger {
const selectedCollection: ViewModels.Collection = this.findSelectedCollection();
return _.find(selectedCollection.triggers(), (trigger: ViewModels.Trigger) => {
const openedTriggerTab = this.openedTabs().filter(
(tab: ViewModels.Tab) =>
tab.node && tab.node.rid === trigger.rid && tab.tabKind === ViewModels.CollectionTabKind.Triggers
const openedTriggerTab = this.tabsManager.getTabs(
ViewModels.CollectionTabKind.Triggers,
(tab: ViewModels.Tab) => tab.node && tab.node.rid === trigger.rid
);
return (
trigger.rid === this.selectedNode().rid ||
@@ -2169,16 +2132,6 @@ export default class Explorer implements ViewModels.Explorer {
});
}
public closeAllTabsForResource(resourceId: string): void {
const currentlyActiveTabs = this.openedTabs().filter((tab: ViewModels.Tab) => tab.isActive && tab.isActive());
currentlyActiveTabs.forEach((tab: ViewModels.Tab) => tab.isActive(false));
this.activeTab(null);
const openedTabsForResource = this.openedTabs().filter(
(tab: ViewModels.Tab) => tab.node && tab.node.rid === resourceId
);
openedTabsForResource.forEach((tab: ViewModels.Tab) => tab.onCloseTabButtonClick());
}
public closeAllPanes(): void {
this._panes.forEach((pane: ViewModels.ContextualPane) => pane.close());
}
@@ -2255,7 +2208,9 @@ export default class Explorer implements ViewModels.Explorer {
if (isNewDatabase) {
database.expandDatabase();
}
database.refreshTabSelectedState();
this.tabsManager.refreshActiveTab(
(tab: ViewModels.Tab) => tab.collection && tab.collection.getDatabase().rid === database.rid
);
})
);
});
@@ -2358,7 +2313,7 @@ export default class Explorer implements ViewModels.Explorer {
}
const urlPrefixWithKeyParam: string = `${config.hostedExplorerURL}?key=`;
const currentActiveTab: ViewModels.Tab = this.findActiveTab();
const currentActiveTab: ViewModels.Tab = this.tabsManager.activeTab();
return `${urlPrefixWithKeyParam}${token}#/${(currentActiveTab && currentActiveTab.hashLocation()) || ""}`;
}
@@ -2490,10 +2445,6 @@ export default class Explorer implements ViewModels.Explorer {
const parent = this.resourceTree.myNotebooksContentRoot;
if (parent && parent.children && this.isNotebookEnabled() && this.notebookManager?.notebookClient) {
if (this._filePathToImportAndOpen === path) {
this._filePathToImportAndOpen = null; // we don't want to try opening this path again
}
const existingItem = _.find(parent.children, node => node.name === name);
if (existingItem) {
return this.openNotebook(existingItem);
@@ -2504,24 +2455,27 @@ export default class Explorer implements ViewModels.Explorer {
return this.openNotebook(uploadedItem);
}
this._filePathToImportAndOpen = path; // we'll try opening this path later on
return Promise.resolve(false);
}
public async importAndOpenFromGallery(name: string, content: string): Promise<boolean> {
public async importAndOpenContent(name: string, content: string): Promise<boolean> {
const parent = this.resourceTree.myNotebooksContentRoot;
if (parent && parent.children && this.isNotebookEnabled() && this.notebookManager?.notebookClient) {
if (this.notebookToImport && this.notebookToImport.name === name && this.notebookToImport.content === content) {
this.notebookToImport = undefined; // we don't want to try opening this notebook again
}
const existingItem = _.find(parent.children, node => node.name === name);
if (existingItem) {
this.showOkModalDialog("Download failed", "Notebook with the same name already exists.");
return Promise.reject(false);
return this.openNotebook(existingItem);
}
const uploadedItem = await this.uploadFile(name, content, parent);
return this.openNotebook(uploadedItem);
}
this.notebookToImport = { name, content }; // we'll try opening this notebook later on
return Promise.resolve(false);
}
@@ -2621,46 +2575,48 @@ export default class Explorer implements ViewModels.Explorer {
if (!notebookContentItem || !notebookContentItem.path) {
throw new Error(`Invalid notebookContentItem: ${notebookContentItem}`);
}
const openedNotebookTabs = this.getNotebookTabsForFilepath(notebookContentItem.path);
if (openedNotebookTabs.length > 0) {
openedNotebookTabs[0].onTabClick();
openedNotebookTabs[0].onActivate();
return true;
const notebookTabs: NotebookV2Tab[] = this.tabsManager.getTabs(
ViewModels.CollectionTabKind.NotebookV2,
(tab: ViewModels.Tab) =>
(tab as NotebookV2Tab).notebookPath &&
FileSystemUtil.isPathEqual((tab as NotebookV2Tab).notebookPath(), notebookContentItem.path)
) as NotebookV2Tab[];
let notebookTab: NotebookV2Tab = notebookTabs && notebookTabs[0];
if (notebookTab) {
this.tabsManager.activateTab(notebookTab);
} else {
const options: ViewModels.NotebookTabOptions = {
account: CosmosClient.databaseAccount(),
tabKind: ViewModels.CollectionTabKind.NotebookV2,
node: null,
title: notebookContentItem.name,
tabPath: notebookContentItem.path,
documentClientUtility: null,
collection: null,
selfLink: null,
masterKey: CosmosClient.masterKey() || "",
hashLocation: "notebooks",
isActive: ko.observable(false),
isTabsContentExpanded: ko.observable(true),
onLoadStartKey: null,
onUpdateTabsButtons: this.onUpdateTabsButtons,
container: this,
notebookContentItem
};
try {
const NotebookTabV2 = await import(/* webpackChunkName: "NotebookV2Tab" */ "./Tabs/NotebookV2Tab");
notebookTab = new NotebookTabV2.default(options);
this.tabsManager.activateNewTab(notebookTab);
} catch (reason) {
console.error("Import NotebookV2Tab failed!", reason);
return false;
}
}
const options: ViewModels.NotebookTabOptions = {
account: CosmosClient.databaseAccount(),
tabKind: ViewModels.CollectionTabKind.NotebookV2,
node: null,
title: notebookContentItem.name,
tabPath: notebookContentItem.path,
documentClientUtility: null,
collection: null,
selfLink: null,
masterKey: CosmosClient.masterKey() || "",
hashLocation: "notebooks",
isActive: ko.observable(false),
isTabsContentExpanded: ko.observable(true),
onLoadStartKey: null,
onUpdateTabsButtons: this.onUpdateTabsButtons,
container: this,
notebookContentItem,
openedTabs: this.openedTabs()
};
try {
const NotebookTabV2 = await import(/* webpackChunkName: "NotebookV2Tab" */ "./Tabs/NotebookV2Tab");
const notebookTab = new NotebookTabV2.default(options);
this.openedTabs.push(notebookTab);
// Activate
notebookTab.onTabClick();
} catch (reason) {
console.error("Import NotebookV2Tab failed!", reason);
return false;
}
return true;
}
@@ -2673,10 +2629,11 @@ export default class Explorer implements ViewModels.Explorer {
}
// Don't delete if tab is open to avoid accidental deletion
const openedNotebookTabs = this.openedTabs().filter(
(tab: ViewModels.Tab) =>
tab.tabKind === ViewModels.CollectionTabKind.NotebookV2 &&
(tab as NotebookV2Tab).notebookPath() === notebookFile.path
const openedNotebookTabs = this.tabsManager.getTabs(
ViewModels.CollectionTabKind.NotebookV2,
(tab: NotebookV2Tab) => {
return tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), notebookFile.path);
}
);
if (openedNotebookTabs.length > 0) {
this.showOkModalDialog("Unable to rename file", "This file is being edited. Please close the tab and try again.");
@@ -2696,17 +2653,15 @@ export default class Explorer implements ViewModels.Explorer {
onSubmit: (input: string) => this.notebookManager?.notebookContentClient.renameNotebook(notebookFile, input)
})
.then(newNotebookFile => {
this.openedTabs()
.filter(
(tab: ViewModels.Tab) =>
tab.tabKind === ViewModels.CollectionTabKind.NotebookV2 &&
FileSystemUtil.isPathEqual((tab as NotebookV2Tab).notebookPath(), originalPath)
)
.forEach(tab => {
tab.tabTitle(newNotebookFile.name);
tab.tabPath(newNotebookFile.path);
(tab as NotebookV2Tab).notebookPath(newNotebookFile.path);
});
const notebookTabs: ViewModels.Tab[] = this.tabsManager.getTabs(
ViewModels.CollectionTabKind.NotebookV2,
(tab: NotebookV2Tab) => tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), originalPath)
);
notebookTabs.forEach(tab => {
tab.tabTitle(newNotebookFile.name);
tab.tabPath(newNotebookFile.path);
(tab as NotebookV2Tab).notebookPath(newNotebookFile.path);
});
return newNotebookFile;
});
@@ -2886,39 +2841,34 @@ export default class Explorer implements ViewModels.Explorer {
await this.initSparkConnectionInfo(this.databaseAccount());
}
const openedSparkMasterTabs = this.openedTabs().filter(
(tab: ViewModels.Tab) => tab.tabKind === ViewModels.CollectionTabKind.SparkMasterTab
);
if (openedSparkMasterTabs.length > 0) {
openedSparkMasterTabs[0].onTabClick();
openedSparkMasterTabs[0].onActivate();
return;
const sparkMasterTabs: SparkMasterTab[] = this.tabsManager.getTabs(
ViewModels.CollectionTabKind.SparkMasterTab
) as SparkMasterTab[];
let sparkMasterTab: SparkMasterTab = sparkMasterTabs && sparkMasterTabs[0];
if (sparkMasterTab) {
this.tabsManager.activateTab(sparkMasterTab);
} else {
sparkMasterTab = new SparkMasterTab({
clusterConnectionInfo: this.sparkClusterConnectionInfo(),
tabKind: ViewModels.CollectionTabKind.SparkMasterTab,
node: null,
title: "Apache Spark",
tabPath: "",
documentClientUtility: null,
collection: null,
selfLink: null,
hashLocation: "sparkmaster",
isActive: ko.observable(false),
isTabsContentExpanded: ko.observable(true),
onLoadStartKey: null,
onUpdateTabsButtons: this.onUpdateTabsButtons,
container: this
});
this.tabsManager.activateNewTab(sparkMasterTab);
}
const sparkMasterTab = new SparkMasterTab({
clusterConnectionInfo: this.sparkClusterConnectionInfo(),
tabKind: ViewModels.CollectionTabKind.SparkMasterTab,
node: null,
title: "Apache Spark",
tabPath: "",
documentClientUtility: null,
collection: null,
selfLink: null,
hashLocation: "sparkmaster",
isActive: ko.observable(false),
isTabsContentExpanded: ko.observable(true),
onLoadStartKey: null,
onUpdateTabsButtons: this.onUpdateTabsButtons,
openedTabs: this.openedTabs(),
container: this
});
this.openedTabs.push(sparkMasterTab);
// Activate
sparkMasterTab.onTabClick();
return;
}
private refreshNotebookList = async (): Promise<void> => {
@@ -2927,8 +2877,8 @@ export default class Explorer implements ViewModels.Explorer {
}
await this.resourceTree.initialize();
if (this._filePathToImportAndOpen) {
this.importAndOpen(this._filePathToImportAndOpen);
if (this.notebookToImport) {
this.importAndOpenContent(this.notebookToImport.name, this.notebookToImport.content);
}
};
@@ -2941,9 +2891,11 @@ export default class Explorer implements ViewModels.Explorer {
}
// Don't delete if tab is open to avoid accidental deletion
const openedNotebookTabs = this.openedTabs().filter(
(tab: ViewModels.Tab) =>
tab.tabKind === ViewModels.CollectionTabKind.NotebookV2 && (tab as NotebookV2Tab).notebookPath() === item.path
const openedNotebookTabs = this.tabsManager.getTabs(
ViewModels.CollectionTabKind.NotebookV2,
(tab: NotebookV2Tab) => {
return tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), item.path);
}
);
if (openedNotebookTabs.length > 0) {
this.showOkModalDialog("Unable to delete file", "This file is being edited. Please close the tab and try again.");
@@ -3105,94 +3057,79 @@ export default class Explorer implements ViewModels.Explorer {
throw new Error("Terminal kind: ${kind} not supported");
}
const openedTabs = this.openedTabs().filter(
(tab: ViewModels.Tab) => tab.tabKind === ViewModels.CollectionTabKind.Terminal
);
const terminalTabs: TerminalTab[] = this.tabsManager.getTabs(
ViewModels.CollectionTabKind.Terminal,
(tab: ViewModels.Tab) => tab.hashLocation() == hashLocation
) as TerminalTab[];
let terminalTab: TerminalTab = terminalTabs && terminalTabs[0];
for (let i = 0; i < openedTabs.length; ++i) {
if (openedTabs[i].hashLocation() == hashLocation) {
openedTabs[i].onTabClick();
openedTabs[i].onActivate();
return;
}
if (terminalTab) {
this.tabsManager.activateTab(terminalTab);
} else {
const newTab = new TerminalTab({
account: CosmosClient.databaseAccount(),
tabKind: ViewModels.CollectionTabKind.Terminal,
node: null,
title: title,
tabPath: title,
documentClientUtility: null,
collection: null,
selfLink: null,
hashLocation: hashLocation,
isActive: ko.observable(false),
isTabsContentExpanded: ko.observable(true),
onLoadStartKey: null,
onUpdateTabsButtons: this.onUpdateTabsButtons,
container: this,
kind: kind
});
this.tabsManager.activateNewTab(newTab);
}
const newTab = new TerminalTab({
account: CosmosClient.databaseAccount(),
tabKind: ViewModels.CollectionTabKind.Terminal,
node: null,
title: title,
tabPath: title,
documentClientUtility: null,
collection: null,
selfLink: null,
hashLocation: hashLocation,
isActive: ko.observable(false),
isTabsContentExpanded: ko.observable(true),
onLoadStartKey: null,
onUpdateTabsButtons: this.onUpdateTabsButtons,
container: this,
openedTabs: this.openedTabs(),
kind: kind
});
this.openedTabs.push(newTab);
// Activate
newTab.onTabClick();
}
public async openGallery(notebookUrl?: string, galleryItem?: IGalleryItem, isFavorite?: boolean) {
let title: string;
let hashLocation: string;
let title: string = "Gallery";
let hashLocation: string = "gallery";
title = "Gallery";
hashLocation = "gallery";
const openedTabs = this.openedTabs().filter(
(tab: ViewModels.Tab) => tab.tabKind === ViewModels.CollectionTabKind.Gallery
const galleryTabs = this.tabsManager.getTabs(
ViewModels.CollectionTabKind.Gallery,
(tab: ViewModels.Tab) => tab.hashLocation() == hashLocation
);
let galleryTab = galleryTabs && galleryTabs[0];
for (let i = 0; i < openedTabs.length; ++i) {
if (openedTabs[i].hashLocation() == hashLocation) {
openedTabs[i].onTabClick();
openedTabs[i].onActivate();
(openedTabs[i] as any).updateGalleryParams(notebookUrl, galleryItem, isFavorite);
return;
if (galleryTab) {
this.tabsManager.activateTab(galleryTab);
(galleryTab as any).updateGalleryParams(notebookUrl, galleryItem, isFavorite);
} else {
if (!this.galleryTab) {
this.galleryTab = await import(/* webpackChunkName: "GalleryTab" */ "./Tabs/GalleryTab");
}
const newTab = new this.galleryTab.default({
// GalleryTabOptions
account: CosmosClient.databaseAccount(),
container: this,
junoClient: this.notebookManager?.junoClient,
notebookUrl,
galleryItem,
isFavorite,
// TabOptions
tabKind: ViewModels.CollectionTabKind.Gallery,
title: title,
tabPath: title,
documentClientUtility: null,
selfLink: null,
isActive: ko.observable(false),
hashLocation: hashLocation,
onUpdateTabsButtons: this.onUpdateTabsButtons,
isTabsContentExpanded: ko.observable(true),
onLoadStartKey: null
});
this.tabsManager.activateNewTab(newTab);
}
if (!this.galleryTab) {
this.galleryTab = await import(/* webpackChunkName: "GalleryTab" */ "./Tabs/GalleryTab");
}
const newTab = new this.galleryTab.default({
// GalleryTabOptions
account: CosmosClient.databaseAccount(),
container: this,
junoClient: this.notebookManager?.junoClient,
notebookUrl,
galleryItem,
isFavorite,
// TabOptions
tabKind: ViewModels.CollectionTabKind.Gallery,
title: title,
tabPath: title,
documentClientUtility: null,
selfLink: null,
isActive: ko.observable(false),
hashLocation: hashLocation,
onUpdateTabsButtons: this.onUpdateTabsButtons,
isTabsContentExpanded: ko.observable(true),
onLoadStartKey: null,
openedTabs: this.openedTabs()
});
this.openedTabs.push(newTab);
// Activate
newTab.onTabClick();
}
public async openNotebookViewer(notebookUrl: string) {
@@ -3210,41 +3147,37 @@ export default class Explorer implements ViewModels.Explorer {
return notebookViewerTab.notebookUrl === notebookUrl;
};
const openedTabs = this.openedTabs().filter(
(tab: ViewModels.Tab) => tab.tabKind === ViewModels.CollectionTabKind.NotebookViewer && isNotebookViewerOpen(tab)
);
for (let i = 0; i < openedTabs.length; ++i) {
if (openedTabs[i].hashLocation() == hashLocation) {
openedTabs[i].onTabClick();
openedTabs[i].onActivate();
return;
const notebookViewerTabs = this.tabsManager.getTabs(
ViewModels.CollectionTabKind.NotebookV2,
(tab: ViewModels.Tab) => {
return tab.hashLocation() == hashLocation && isNotebookViewerOpen(tab);
}
);
let notebookViewerTab = notebookViewerTabs && notebookViewerTabs[0];
if (notebookViewerTab) {
this.tabsManager.activateNewTab(notebookViewerTab);
} else {
notebookViewerTab = new this.notebookViewerTab.default({
account: CosmosClient.databaseAccount(),
tabKind: ViewModels.CollectionTabKind.NotebookViewer,
node: null,
title: title,
tabPath: title,
documentClientUtility: null,
collection: null,
selfLink: null,
hashLocation: hashLocation,
isActive: ko.observable(false),
isTabsContentExpanded: ko.observable(true),
onLoadStartKey: null,
onUpdateTabsButtons: this.onUpdateTabsButtons,
container: this,
notebookUrl
});
this.tabsManager.activateNewTab(notebookViewerTab);
}
const newTab = new this.notebookViewerTab.default({
account: CosmosClient.databaseAccount(),
tabKind: ViewModels.CollectionTabKind.NotebookViewer,
node: null,
title: title,
tabPath: title,
documentClientUtility: null,
collection: null,
selfLink: null,
hashLocation: hashLocation,
isActive: ko.observable(false),
isTabsContentExpanded: ko.observable(true),
onLoadStartKey: null,
onUpdateTabsButtons: this.onUpdateTabsButtons,
container: this,
openedTabs: this.openedTabs(),
notebookUrl
});
this.openedTabs.push(newTab);
// Activate
newTab.onTabClick();
}
public onNewCollectionClicked(): void {
@@ -3256,24 +3189,8 @@ export default class Explorer implements ViewModels.Explorer {
document.getElementById("linkAddCollection").focus();
}
private getNotebookTabsForFilepath(filepath: string): ViewModels.Tab[] {
return this.openedTabs().filter(
(tab: ViewModels.Tab) =>
tab.tabKind === ViewModels.CollectionTabKind.NotebookV2 &&
(tab as any).notebookPath &&
FileSystemUtil.isPathEqual((tab as any).notebookPath(), filepath)
);
}
public closeNotebookTab(filepath: string): void {
if (!filepath) {
return;
}
this.getNotebookTabsForFilepath(filepath).forEach(tab => tab.onCloseTabButtonClick());
}
private refreshCommandBarButtons(): void {
const activeTab = this.findActiveTab();
const activeTab = this.tabsManager.activeTab();
if (activeTab) {
activeTab.onActivate(); // TODO only update tabs buttons?
} else {
@@ -3351,6 +3268,21 @@ export default class Explorer implements ViewModels.Explorer {
this._openSetupNotebooksPaneForQuickstart();
}
this.importAndOpen(path);
// We still use github urls like https://github.com/Azure-Samples/cosmos-notebooks/blob/master/CSharp_quickstarts/GettingStarted_CSharp.ipynb
// when launching a notebook quickstart from Portal. In future we should just use gallery id and use Juno to fetch instead of directly
// calling GitHub. For now convert this url to a raw url and download content.
const gitHubInfo = fromContentUri(path);
if (gitHubInfo) {
const rawUrl = toRawContentUri(gitHubInfo.owner, gitHubInfo.repo, gitHubInfo.branch, gitHubInfo.path);
const response = await fetch(rawUrl);
if (response.status === Constants.HttpStatusCodes.OK) {
this.notebookToImport = {
name: NotebookUtil.getName(path),
content: await response.text()
};
this.importAndOpenContent(this.notebookToImport.name, this.notebookToImport.content);
}
}
}
}

View File

@@ -16,10 +16,14 @@ export class CommandBarComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<number>;
public container: ViewModels.Explorer;
private tabsButtons: ViewModels.NavbarButtonConfig[];
private isNotebookTabActive: ko.Computed<boolean>;
constructor(container: ViewModels.Explorer) {
this.container = container;
this.tabsButtons = [];
this.isNotebookTabActive = ko.computed(() =>
container.tabsManager.isTabActive(ViewModels.CollectionTabKind.NotebookV2)
);
// These are the parameters watched by the react binding that will trigger a renderComponent() if one of the ko mutates
const toWatch = [
@@ -39,7 +43,7 @@ export class CommandBarComponentAdapter implements ReactAdapter {
container.isHostedDataExplorerEnabled,
container.isSynapseLinkUpdating,
container.databaseAccount,
container.isNotebookTabActive
this.isNotebookTabActive
];
ko.computed(() => ko.toJSON(toWatch)).subscribe(() => this.triggerRender());
@@ -74,7 +78,7 @@ export class CommandBarComponentAdapter implements ReactAdapter {
const uiFabricControlButtons = CommandBarUtil.convertButton(controlButtons, backgroundColor);
uiFabricControlButtons.forEach((btn: ICommandBarItemProps) => (btn.iconOnly = true));
if (this.container && this.container.isNotebookTabActive()) {
if (this.isNotebookTabActive()) {
uiFabricControlButtons.unshift(
CommandBarUtil.createMemoryTracker("memoryTracker", this.container.memoryUsageInfo)
);

View File

@@ -19,7 +19,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false);
mockExplorer.isSparkEnabled = ko.observable(true);
mockExplorer.isGalleryEnabled = ko.computed<boolean>(() => false);
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
@@ -82,7 +81,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isPreferredApiTable = ko.computed(() => true);
mockExplorer.isPreferredApiCassandra = ko.computed<boolean>(() => false);
mockExplorer.isSparkEnabled = ko.observable(true);
mockExplorer.isGalleryEnabled = ko.computed<boolean>(() => false);
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
@@ -163,7 +161,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isPreferredApiTable = ko.computed(() => true);
mockExplorer.isPreferredApiMongoDB = ko.computed<boolean>(() => false);
mockExplorer.isSparkEnabled = ko.observable(true);
mockExplorer.isGalleryEnabled = ko.computed<boolean>(() => false);
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
mockExplorer.hasAutoPilotV2FeatureFlag = ko.computed<boolean>(() => true);
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
@@ -250,7 +247,6 @@ describe("CommandBarComponentButtonFactory tests", () => {
mockExplorer.isDatabaseNodeOrNoneSelected = () => true;
mockExplorer.isNotebooksEnabledForAccount = ko.observable(false);
mockExplorer.isRunningOnNationalCloud = ko.observable(false);
mockExplorer.isGalleryEnabled = ko.computed<boolean>(() => false);
mockExplorer.isGalleryPublishEnabled = ko.computed<boolean>(() => false);
mockExplorer.notebookManager = new NotebookManager();
mockExplorer.notebookManager.gitHubOAuthService = new GitHubOAuthService(undefined);

View File

@@ -4,14 +4,11 @@ import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryCons
import { Areas } from "../../../Common/Constants";
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import ApacheSparkIcon from "../../../../images/notebook/Apache-spark.svg";
import AddDatabaseIcon from "../../../../images/AddDatabase.svg";
import AddCollectionIcon from "../../../../images/AddCollection.svg";
import AddSqlQueryIcon from "../../../../images/AddSqlQuery_16x16.svg";
import BrowseQueriesIcon from "../../../../images/BrowseQuery.svg";
import * as Constants from "../../../Common/Constants";
import DeleteIcon from "../../../../images/delete.svg";
import EditIcon from "../../../../images/edit.svg";
import OpenInTabIcon from "../../../../images/open-in-tab.svg";
import OpenQueryFromDiskIcon from "../../../../images/OpenQueryFromDisk.svg";
import CosmosTerminalIcon from "../../../../images/Cosmos-Terminal.svg";
@@ -25,7 +22,6 @@ import FeedbackIcon from "../../../../images/Feedback-Command.svg";
import EnableNotebooksIcon from "../../../../images/notebook/Notebook-enable.svg";
import NewNotebookIcon from "../../../../images/notebook/Notebook-new.svg";
import ResetWorkspaceIcon from "../../../../images/notebook/Notebook-reset-workspace.svg";
import LibraryManageIcon from "../../../../images/notebook/Spark-library-manage.svg";
import GitHubIcon from "../../../../images/github.svg";
import SynapseIcon from "../../../../images/synapse-link.svg";
import { config, Platform } from "../../../Config";
@@ -88,25 +84,6 @@ export class CommandBarComponentButtonFactory {
buttons.push(CommandBarComponentButtonFactory.createNotebookWorkspaceResetButton(container));
}
// TODO: Should be replaced with the create arcadia spark pool button
// if (!container.isSparkEnabled() && container.isSparkEnabledForAccount()) {
// const createSparkClusterButton = CommandBarComponentButtonFactory.createSparkClusterButton(container);
// buttons.push(createSparkClusterButton);
// }
// TODO: Should be replaced with the edit/manage/delete arcadia spark pool button
// if (container.isSparkEnabled()) {
// const manageSparkClusterButton = CommandBarComponentButtonFactory.createMonitorClusterButton(container);
// manageSparkClusterButton.children = [
// CommandBarComponentButtonFactory.createMonitorClusterButton(container),
// CommandBarComponentButtonFactory.createEditClusterButton(container),
// CommandBarComponentButtonFactory.createDeleteClusterButton(container),
// CommandBarComponentButtonFactory.createLibraryManageButton(container),
// CommandBarComponentButtonFactory.createClusterLibraryButton(container)
// ];
// buttons.push(manageSparkClusterButton);
// }
if (!container.isDatabaseNodeOrNoneSelected()) {
if (container.isNotebookEnabled()) {
buttons.push(CommandBarComponentButtonFactory.createDivider());
@@ -505,58 +482,6 @@ export class CommandBarComponentButtonFactory {
};
}
private static createSparkClusterButton(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig {
const label = "Enable Spark";
return {
iconSrc: ApacheSparkIcon,
iconAlt: "Enable spark icon",
onCommandClick: () => container.setupSparkClusterPane.open(),
commandButtonLabel: label,
hasPopup: false,
disabled: false,
ariaLabel: label
};
}
private static createEditClusterButton(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig {
const label = "Edit Cluster";
return {
iconSrc: EditIcon,
iconAlt: "Edit cluster icon",
onCommandClick: () => container.manageSparkClusterPane.open(),
commandButtonLabel: label,
hasPopup: false,
disabled: false,
ariaLabel: label
};
}
private static createDeleteClusterButton(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig {
const label = "Delete Cluster";
return {
iconSrc: DeleteIcon,
iconAlt: "Delete cluster icon",
onCommandClick: () => container.deleteCluster(),
commandButtonLabel: label,
hasPopup: false,
disabled: false,
ariaLabel: label
};
}
private static createMonitorClusterButton(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig {
const label = "Monitor Cluster";
return {
iconSrc: ApacheSparkIcon,
iconAlt: "Monitor cluster icon",
onCommandClick: () => container.openSparkMasterTab(),
commandButtonLabel: label,
hasPopup: false,
disabled: false,
ariaLabel: label
};
}
private static createOpenTerminalButton(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig {
const label = "Open Terminal";
return {
@@ -658,32 +583,6 @@ export class CommandBarComponentButtonFactory {
};
}
private static createLibraryManageButton(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig {
const label = "Manage Libraries";
return {
iconSrc: LibraryManageIcon,
iconAlt: label,
onCommandClick: () => container.libraryManagePane.open(),
commandButtonLabel: label,
hasPopup: false,
disabled: false,
ariaLabel: label
};
}
private static createClusterLibraryButton(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig {
const label = "Manage Cluster Libraries";
return {
iconSrc: LibraryManageIcon,
iconAlt: label,
onCommandClick: () => container.clusterLibraryPane.open(),
commandButtonLabel: label,
hasPopup: false,
disabled: false,
ariaLabel: label
};
}
private static createStaticCommandBarButtonsForResourceToken(
container: ViewModels.Explorer
): ViewModels.NavbarButtonConfig[] {

View File

@@ -28,7 +28,7 @@ exports[`test render renders signed in with full info 1`] = `
<FocusZone
direction={2}
isCircularNavigation={false}
preventDefaultWhenHandled={true}
shouldRaiseClicks={true}
>
<CustomizedDefaultButton
className="mecontrolHeaderButton"

View File

@@ -4,7 +4,7 @@ import { NotebookContentRecordProps, selectors } from "@nteract/core";
* A bunch of utilities to interact with nteract
*/
export default class NTeractUtil {
public static getCurrentCellType(content: NotebookContentRecordProps): "markdown" | "code" | "raw" {
public static getCurrentCellType(content: NotebookContentRecordProps): "markdown" | "code" | "raw" | undefined {
if (!content) {
return undefined;
}

View File

@@ -43,6 +43,7 @@ import { CdbAppState } from "./types";
import { decryptJWTToken } from "../../../Utils/AuthorizationUtils";
import * as TextFile from "./contents/file/text-file";
import { NotebookUtil } from "../NotebookUtil";
import { FileSystemUtil } from "../FileSystemUtil";
interface NotebookServiceConfig extends JupyterServerConfig {
userPuid?: string;
@@ -806,7 +807,9 @@ const closeUnsupportedMimetypesEpic = (
if (explorer && !TextFile.handles(mimetype)) {
const filepath = action.payload.filepath;
// Close tab and show error message
explorer.closeNotebookTab(filepath);
explorer.tabsManager.closeTabsByComparator(
tab => (tab as any).notebookPath && FileSystemUtil.isPathEqual((tab as any).notebookPath(), filepath)
);
const msg = `${filepath} cannot be rendered. Please download the file, in order to view it outside of Data Explorer.`;
explorer.showOkModalDialog("File cannot be rendered", msg);
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
@@ -832,7 +835,9 @@ const closeContentFailedToFetchEpic = (
if (explorer) {
const filepath = action.payload.filepath;
// Close tab and show error message
explorer.closeNotebookTab(filepath);
explorer.tabsManager.closeTabsByComparator(
tab => (tab as any).notebookPath && FileSystemUtil.isPathEqual((tab as any).notebookPath(), filepath)
);
const msg = `Failed to load file: ${filepath}.`;
explorer.showOkModalDialog("Failure to load", msg);
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);

View File

@@ -5,10 +5,10 @@ import { Notebook } from "../../../Common/Constants";
import { CellId } from "@nteract/commutable";
export interface CdbRecordProps {
databaseAccountName: string;
defaultExperience: string;
databaseAccountName: string | undefined;
defaultExperience: string | undefined;
kernelRestartDelayMs: number;
hoveredCellId: CellId;
hoveredCellId: CellId | undefined;
}
export type CdbRecord = Immutable.RecordOf<CdbRecordProps>;

View File

@@ -6,7 +6,6 @@ import { JunoClient } from "../../Juno/JunoClient";
import * as ViewModels from "../../Contracts/ViewModels";
import { GitHubOAuthService } from "../../GitHub/GitHubOAuthService";
import { GitHubClient } from "../../GitHub/GitHubClient";
import { config } from "../../Config";
import * as Logger from "../../Common/Logger";
import { HttpStatusCodes, Areas } from "../../Common/Constants";
import { GitHubReposPane } from "../Panes/GitHubReposPane";
@@ -54,7 +53,7 @@ export default class NotebookManager {
this.junoClient = new JunoClient(this.params.container.databaseAccount);
this.gitHubOAuthService = new GitHubOAuthService(this.junoClient);
this.gitHubClient = new GitHubClient(config.AZURESAMPLESCOSMOSDBPAT, this.onGitHubClientError);
this.gitHubClient = new GitHubClient(this.onGitHubClientError);
this.gitHubReposPane = new GitHubReposPane({
documentClientUtility: this.params.container.documentClientUtility,
id: "gitHubReposPane",
@@ -91,7 +90,7 @@ export default class NotebookManager {
}
this.gitHubOAuthService.getTokenObservable().subscribe(token => {
this.gitHubClient.setToken(token?.access_token ? token.access_token : config.AZURESAMPLESCOSMOSDBPAT);
this.gitHubClient.setToken(token?.access_token);
if (this.gitHubReposPane.visible()) {
this.gitHubReposPane.open();

View File

@@ -1,127 +0,0 @@
import { IGitHubRepo, IGitHubBranch } from "../../GitHub/GitHubClient";
export const SamplesRepo: IGitHubRepo = {
name: "cosmos-notebooks",
owner: "Azure-Samples",
private: false
};
export const SamplesBranch: IGitHubBranch = {
name: "master"
};
export const isSamplesCall = (owner: string, repo: string, branch?: string): boolean => {
return owner === SamplesRepo.owner && repo === SamplesRepo.name && (!branch || branch === SamplesBranch.name);
};
// GitHub API calls have a rate limit of 5000 requests per hour. So if we get high traffic on Data Explorer
// loading samples exceed that limit. Using this hard coded response for samples until we fix that.
export const SamplesContentsQueryResponse = {
repository: {
owner: {
login: "Azure-Samples"
},
name: "cosmos-notebooks",
isPrivate: false,
ref: {
name: "master",
target: {
history: {
nodes: [
{
oid: "cda7facb9e039b173f3376200c26c859896e7974",
message:
"Merge pull request #45 from Azure-Samples/users/deborahc/pythonSampleUpdates\n\nAdd bokeh version to notebook",
committer: {
date: "2020-05-28T11:28:01-07:00"
}
}
]
}
}
},
object: {
entries: [
{
name: ".github",
type: "tree",
object: {}
},
{
name: ".gitignore",
type: "blob",
object: {
oid: "3e759b75bf455ac809d0987d369aab89137b5689",
byteSize: 5582
}
},
{
name: "1. GettingStarted.ipynb",
type: "blob",
object: {
oid: "0732ff5366e4aefdc4c378c61cbd968664f0acec",
byteSize: 3933
}
},
{
name: "2. Visualization.ipynb",
type: "blob",
object: {
oid: "6b16b0740a77afdd38a95bc6c3ebd0f2f17d9465",
byteSize: 820317
}
},
{
name: "3. RequestUnits.ipynb",
type: "blob",
object: {
oid: "252b79a4adc81e9f2ffde453231b695d75e270e8",
byteSize: 9490
}
},
{
name: "4. Indexing.ipynb",
type: "blob",
object: {
oid: "e10dd67bd1c55c345226769e4f80e43659ef9cd5",
byteSize: 10394
}
},
{
name: "5. StoredProcedures.ipynb",
type: "blob",
object: {
oid: "949941949920de4d2d111149e2182e9657cc8134",
byteSize: 11818
}
},
{
name: "6. GlobalDistribution.ipynb",
type: "blob",
object: {
oid: "b91c31dacacbc9e35750d9054063dda4a5309f3b",
byteSize: 11375
}
},
{
name: "7. IoTAnomalyDetection.ipynb",
type: "blob",
object: {
oid: "82057ae52a67721a5966e2361317f5dfbd0ee595",
byteSize: 377939
}
},
{
name: "All_API_quickstarts",
type: "tree",
object: {}
},
{
name: "CSharp_quickstarts",
type: "tree",
object: {}
}
]
}
}
};

View File

@@ -22,6 +22,7 @@ import { Versions } from "../../src/Contracts/ExplorerContracts";
import { CollectionCreationDefaults } from "../Shared/Constants";
import { IGalleryItem } from "../Juno/JunoClient";
import { ReactAdapter } from "../Bindings/ReactBindingHandler";
import { TabsManager } from "./Tabs/TabsManager";
export class ExplorerStub implements ViewModels.Explorer {
public flight: ko.Observable<string>;
@@ -69,7 +70,6 @@ export class ExplorerStub implements ViewModels.Explorer {
public isLeftPaneExpanded: ko.Observable<boolean>;
public selectedNode: ko.Observable<ViewModels.TreeNode>;
public isRefreshingExplorer: ko.Observable<boolean>;
public openedTabs: ko.ObservableArray<ViewModels.Tab>;
public isTabsContentExpanded: ko.Observable<boolean>;
public addCollectionPane: ViewModels.AddCollectionPane;
public addDatabasePane: ViewModels.AddDatabasePane;
@@ -94,9 +94,6 @@ export class ExplorerStub implements ViewModels.Explorer {
public uploadFilePane: UploadFilePane;
public stringInputPane: StringInputPane;
public setupNotebooksPane: SetupNotebooksPane;
public setupSparkClusterPane: ViewModels.ContextualPane;
public manageSparkClusterPane: ViewModels.ContextualPane;
public isGalleryEnabled: ko.Computed<boolean>;
public isGalleryPublishEnabled: ko.Computed<boolean>;
public isGitHubPaneEnabled: ko.Observable<boolean>;
public isPublishNotebookPaneEnabled: ko.Observable<boolean>;
@@ -104,7 +101,6 @@ export class ExplorerStub implements ViewModels.Explorer {
public canExceedMaximumValue: ko.Computed<boolean>;
public isHostedDataExplorerEnabled: ko.Computed<boolean>;
public parentFrameDataExplorerVersion: ko.Observable<string> = ko.observable<string>(Versions.DataExplorer);
public activeTab: ko.Observable<ViewModels.Tab>;
public mostRecentActivity: MostRecentActivity;
public isNotebookEnabled: ko.Observable<boolean>;
public isSparkEnabled: ko.Observable<boolean>;
@@ -122,7 +118,6 @@ export class ExplorerStub implements ViewModels.Explorer {
public arcadiaWorkspaces: ko.ObservableArray<ArcadiaWorkspaceItem>;
public hasStorageAnalyticsAfecFeature: ko.Observable<boolean>;
public isSynapseLinkUpdating: ko.Observable<boolean>;
public isNotebookTabActive: ko.Computed<boolean>;
public memoryUsageInfo: ko.Observable<DataModels.MemoryUsageInfo>;
public notebookManager?: any;
public openGallery: (notebookUrl?: string, galleryItem?: IGalleryItem, isFavorite?: boolean) => void;
@@ -133,6 +128,7 @@ export class ExplorerStub implements ViewModels.Explorer {
public resourceTokenPartitionKey: ko.Observable<string>;
public isAuthWithResourceToken: ko.Observable<boolean>;
public isResourceTokenCollectionNodeSelected: ko.Computed<boolean>;
public tabsManager: TabsManager;
private _featureEnabledReturnValue: boolean;
@@ -251,10 +247,6 @@ export class ExplorerStub implements ViewModels.Explorer {
throw new Error("Not implemented");
}
public findActiveTab(): ViewModels.Tab {
throw new Error("Not implemented");
}
public findSelectedStoredProcedure(): ViewModels.StoredProcedure {
throw new Error("Not implemented");
}
@@ -303,10 +295,6 @@ export class ExplorerStub implements ViewModels.Explorer {
throw new Error("Not implemented");
}
public closeAllTabsForResource(resourceId: string): void {
throw new Error("Not implemented");
}
public getPlatformType(): PlatformType {
throw new Error("Not implemented");
}
@@ -333,7 +321,7 @@ export class ExplorerStub implements ViewModels.Explorer {
throw new Error("Not implemented");
}
public importAndOpenFromGallery(name: string, content: string): Promise<boolean> {
public importAndOpenContent(name: string, content: string): Promise<boolean> {
throw new Error("Not implemented");
}
@@ -429,10 +417,6 @@ export class ExplorerStub implements ViewModels.Explorer {
throw new Error("Not implemented");
}
public closeNotebookTab(filepath: string): void {
throw new Error("Not implemented");
}
public refreshContentItem(item: NotebookContentItem): Promise<void> {
throw new Error("Not implemented");
}
@@ -514,10 +498,6 @@ export class DatabaseStub implements ViewModels.Database {
throw new Error("Not implemented");
}
public refreshTabSelectedState(): void {
throw new Error("Not implemented");
}
public readSettings() {
throw new Error("Not implemented");
}
@@ -782,10 +762,6 @@ export class CollectionStub implements ViewModels.Collection {
throw new Error("Not implemented");
};
public refreshActiveTab = (): void => {
throw new Error("Not implemented");
};
public getLabel(): string {
throw new Error("Not implemented");
}

View File

@@ -61,9 +61,8 @@
<span><img class="infoBoxIcon" src="/info_color.svg" alt="Promo"></span>
<span class="infoBoxDetails">
<span class="infoBoxMessage" data-bind="text: upsellMessage, attr: { title: upsellMessage }"></span>
<a class="underlinedLink" id="linkAddCollection" data-bind="attr: { 'aria-label': upsellMessageAriaLabel }"
target="_blank" href="https://aka.ms/azure-cosmos-db-pricing" tabindex="0">
More details</a>
<a class="underlinedLink" id="linkAddCollection" data-bind="text: upsellAnchorText, attr: { 'href': upsellAnchorUrl, 'aria-label': upsellMessageAriaLabel }"
target="_blank" href="" tabindex="0"></a>
</span>
</div>
</div>

View File

@@ -13,7 +13,29 @@ describe("Add Collection Pane", () => {
kind: "DocumentDB",
location: "",
name: "mock",
properties: undefined,
properties: {
documentEndpoint: "",
cassandraEndpoint: "",
gremlinEndpoint: "",
tableEndpoint: "",
enableFreeTier: false
},
type: undefined,
tags: []
};
const mockFreeTierDatabaseAccount: ViewModels.DatabaseAccount = {
id: "mock",
kind: "DocumentDB",
location: "",
name: "mock",
properties: {
documentEndpoint: "",
cassandraEndpoint: "",
gremlinEndpoint: "",
tableEndpoint: "",
enableFreeTier: true
},
type: undefined,
tags: []
};
@@ -68,5 +90,23 @@ describe("Add Collection Pane", () => {
addCollectionPane.partitionKey("/label");
expect(addCollectionPane.isValid()).toBe(true);
});
it("should display free tier text in upsell messaging", () => {
explorer.databaseAccount(mockFreeTierDatabaseAccount);
const addCollectionPane = explorer.addCollectionPane as AddCollectionPane;
expect(addCollectionPane.isFreeTierAccount()).toBe(true);
expect(addCollectionPane.upsellMessage()).toContain("With free tier discount");
expect(addCollectionPane.upsellAnchorUrl()).toBe(Constants.Urls.freeTierInformation);
expect(addCollectionPane.upsellAnchorText()).toBe("Learn more");
});
it("should display standard texr in upsell messaging", () => {
explorer.databaseAccount(mockDatabaseAccount);
const addCollectionPane = explorer.addCollectionPane as AddCollectionPane;
expect(addCollectionPane.isFreeTierAccount()).toBe(false);
expect(addCollectionPane.upsellMessage()).toContain("Start at");
expect(addCollectionPane.upsellAnchorUrl()).toBe(Constants.Urls.cosmosPricing);
expect(addCollectionPane.upsellAnchorText()).toBe("More details");
});
});
});

View File

@@ -69,6 +69,8 @@ export default class AddCollectionPane extends ContextualPaneBase implements Vie
public uniqueKeysPlaceholder: ko.Computed<string>;
public upsellMessage: ko.PureComputed<string>;
public upsellMessageAriaLabel: ko.PureComputed<string>;
public upsellAnchorUrl: ko.PureComputed<string>;
public upsellAnchorText: ko.PureComputed<string>;
public debugstring: ko.Computed<string>;
public displayCollectionThroughput: ko.Computed<boolean>;
public isAutoPilotSelected: ko.Observable<boolean>;
@@ -508,11 +510,19 @@ export default class AddCollectionPane extends ContextualPaneBase implements Vie
});
this.upsellMessage = ko.pureComputed<string>(() => {
return PricingUtils.getUpsellMessage(this.container.serverId());
return PricingUtils.getUpsellMessage(this.container.serverId(), this.isFreeTierAccount());
});
this.upsellMessageAriaLabel = ko.pureComputed<string>(() => {
return `${this.upsellMessage()}. Click for more details`;
return `${this.upsellMessage()}. Click ${this.isFreeTierAccount() ? "to learn more" : "for more details"}`;
});
this.upsellAnchorUrl = ko.pureComputed<string>(() => {
return this.isFreeTierAccount() ? Constants.Urls.freeTierInformation : Constants.Urls.cosmosPricing;
});
this.upsellAnchorText = ko.pureComputed<string>(() => {
return this.isFreeTierAccount() ? "Learn more" : "More details";
});
this.displayCollectionThroughput = ko.computed<boolean>(() => {

View File

@@ -53,9 +53,8 @@
<span><img class="infoBoxIcon" src="/info_color.svg" alt="Promo"></span>
<span class="infoBoxDetails">
<span class="infoBoxMessage" data-bind="text: upsellMessage, attr: { title: upsellMessage }"></span>
<a class="underlinedLink" id="linkAddDatabase" data-bind="attr: { 'aria-label': upsellMessageAriaLabel }"
target="_blank" href="https://aka.ms/azure-cosmos-db-pricing" tabindex="0">
More details</a>
<a class="underlinedLink" id="linkAddDatabase" data-bind="text: upsellAnchorText, attr: { 'href': upsellAnchorUrl, 'aria-label': upsellMessageAriaLabel }"
target="_blank" href="" tabindex="0"></a>
</span>
</div>
</div>

View File

@@ -1,3 +1,4 @@
import * as Constants from "../../Common/Constants";
import * as ViewModels from "../../Contracts/ViewModels";
import Explorer from "../Explorer";
import AddDatabasePane from "./AddDatabasePane";
@@ -5,6 +6,37 @@ import AddDatabasePane from "./AddDatabasePane";
describe("Add Database Pane", () => {
describe("getSharedThroughputDefault()", () => {
let explorer: ViewModels.Explorer;
const mockDatabaseAccount: ViewModels.DatabaseAccount = {
id: "mock",
kind: "DocumentDB",
location: "",
name: "mock",
properties: {
documentEndpoint: "",
cassandraEndpoint: "",
gremlinEndpoint: "",
tableEndpoint: "",
enableFreeTier: false
},
type: undefined,
tags: []
};
const mockFreeTierDatabaseAccount: ViewModels.DatabaseAccount = {
id: "mock",
kind: "DocumentDB",
location: "",
name: "mock",
properties: {
documentEndpoint: "",
cassandraEndpoint: "",
gremlinEndpoint: "",
tableEndpoint: "",
enableFreeTier: true
},
type: undefined,
tags: []
};
beforeEach(() => {
explorer = new Explorer({
@@ -43,5 +75,23 @@ describe("Add Database Pane", () => {
const addDatabasePane = explorer.addDatabasePane as AddDatabasePane;
expect(addDatabasePane.getSharedThroughputDefault()).toBe(true);
});
it("should display free tier text in upsell messaging", () => {
explorer.databaseAccount(mockFreeTierDatabaseAccount);
const addDatabasePane = explorer.addDatabasePane as AddDatabasePane;
expect(addDatabasePane.isFreeTierAccount()).toBe(true);
expect(addDatabasePane.upsellMessage()).toContain("With free tier discount");
expect(addDatabasePane.upsellAnchorUrl()).toBe(Constants.Urls.freeTierInformation);
expect(addDatabasePane.upsellAnchorText()).toBe("Learn more");
});
it("should display standard texr in upsell messaging", () => {
explorer.databaseAccount(mockDatabaseAccount);
const addDatabasePane = explorer.addDatabasePane as AddDatabasePane;
expect(addDatabasePane.isFreeTierAccount()).toBe(false);
expect(addDatabasePane.upsellMessage()).toContain("Start at");
expect(addDatabasePane.upsellAnchorUrl()).toBe(Constants.Urls.cosmosPricing);
expect(addDatabasePane.upsellAnchorText()).toBe("More details");
});
});
});

View File

@@ -38,6 +38,8 @@ export default class AddDatabasePane extends ContextualPaneBase implements ViewM
public costsVisible: ko.PureComputed<boolean>;
public upsellMessage: ko.PureComputed<string>;
public upsellMessageAriaLabel: ko.PureComputed<string>;
public upsellAnchorUrl: ko.PureComputed<string>;
public upsellAnchorText: ko.PureComputed<string>;
public isAutoPilotSelected: ko.Observable<boolean>;
public selectedAutoPilotTier: ko.Observable<DataModels.AutopilotTier>;
public autoPilotTiersList: ko.ObservableArray<ViewModels.DropdownOption<DataModels.AutopilotTier>>;
@@ -228,11 +230,19 @@ export default class AddDatabasePane extends ContextualPaneBase implements ViewM
});
this.upsellMessage = ko.pureComputed<string>(() => {
return PricingUtils.getUpsellMessage(this.container.serverId());
return PricingUtils.getUpsellMessage(this.container.serverId(), this.isFreeTierAccount());
});
this.upsellMessageAriaLabel = ko.pureComputed<string>(() => {
return `${this.upsellMessage()}. Click for more details`;
return `${this.upsellMessage()}. Click ${this.isFreeTierAccount() ? "to learn more" : "for more details"}`;
});
this.upsellAnchorUrl = ko.pureComputed<string>(() => {
return this.isFreeTierAccount() ? Constants.Urls.freeTierInformation : Constants.Urls.cosmosPricing;
});
this.upsellAnchorText = ko.pureComputed<string>(() => {
return this.isFreeTierAccount() ? "Learn more" : "More details";
});
}

View File

@@ -87,7 +87,7 @@ export class BrowseQueriesPane extends ContextualPaneBase implements ViewModels.
} else {
selectedCollection.onNewQueryClick(selectedCollection, null);
}
const queryTab: ViewModels.QueryTab = this.container.findActiveTab() as ViewModels.QueryTab;
const queryTab: ViewModels.QueryTab = this.container.tabsManager.activeTab() as ViewModels.QueryTab;
queryTab.tabTitle(savedQuery.queryName);
queryTab.tabPath(`${selectedCollection.databaseId}>${selectedCollection.id()}>${savedQuery.queryName}`);
queryTab.initialEditorContent(savedQuery.query);

View File

@@ -66,7 +66,9 @@ export default class DeleteCollectionConfirmationPane extends ContextualPaneBase
this.isExecuting(false);
this.close();
this.container.selectedNode(selectedCollection.database);
this.container.closeAllTabsForResource(selectedCollection.rid);
this.container.tabsManager?.closeTabsByComparator(
(tab: ViewModels.Tab) => tab.node && tab.node.rid === selectedCollection.rid
);
this.container.refreshAllDatabases();
this.resetData();
TelemetryProcessor.traceSuccess(

View File

@@ -11,6 +11,7 @@ import Explorer from "../Explorer";
import { CollectionStub, DatabaseStub, ExplorerStub } from "../OpenActionsStubs";
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import { TreeNode } from "../../Contracts/ViewModels";
import { TabsManager } from "../Tabs/TabsManager";
describe("Delete Database Confirmation Pane", () => {
describe("Explorer.isLastDatabase() and Explorer.isLastNonEmptyDatabase()", () => {
@@ -123,6 +124,7 @@ describe("Delete Database Confirmation Pane", () => {
);
sinon.stub(fakeExplorer, "documentClientUtility").value(fakeDocumentClientUtility);
sinon.stub(fakeExplorer, "selectedNode").value(ko.observable<TreeNode>());
sinon.stub(fakeExplorer, "tabsManager").value(new TabsManager());
fakeExplorer.isLastNonEmptyDatabase.returns(true);
let pane = new DeleteDatabaseConfirmationPane({

View File

@@ -67,11 +67,17 @@ export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase
this.isExecuting(false);
this.close();
this.container.refreshAllDatabases();
this.container.closeAllTabsForResource(selectedDatabase.rid);
this.container.tabsManager.closeTabsByComparator(
(tab: ViewModels.Tab) => tab.node && tab.node.rid === selectedDatabase.rid
);
this.container.selectedNode(null);
selectedDatabase
.collections()
.forEach((collection: ViewModels.Collection) => this.container.closeAllTabsForResource(collection.rid));
.forEach((collection: ViewModels.Collection) =>
this.container.tabsManager.closeTabsByComparator(
(tab: ViewModels.Tab) => tab.node && tab.node.rid === collection.rid
)
);
this.resetData();
TelemetryProcessor.traceSuccess(
Action.DeleteDatabase,

View File

@@ -111,7 +111,7 @@ export class LoadQueryPane extends ContextualPaneBase implements ViewModels.Load
const reader = new FileReader();
reader.onload = (evt: any): void => {
const fileData: string = evt.target.result;
const queryTab: ViewModels.QueryTab = this.container.findActiveTab() as ViewModels.QueryTab;
const queryTab: ViewModels.QueryTab = this.container.tabsManager.activeTab() as ViewModels.QueryTab;
queryTab.initialEditorContent(fileData);
queryTab.sqlQueryEditorContent(fileData);
deferred.resolve();

View File

@@ -1,37 +0,0 @@
<div data-bind="visible: visible, event: { keydown: onPaneKeyDown }">
<div class="contextual-pane-out" data-bind="click: cancel, clickBubble: false"></div>
<div class="contextual-pane" id="managesparkclusterpane">
<!-- Setup spark form -- Start -->
<div class="contextual-pane-in">
<div class="paneContentContainer">
<form class="paneContentContainer" data-bind="submit: submit">
<!-- Setup spark header - Start -->
<div class="firstdivbg headerline">
<span data-bind="text: title"></span>
<div
class="closeImg"
role="button"
aria-label="Close pane"
tabindex="0"
data-bind="click: cancel, event: { keypress: onCloseKeyPress }"
>
<img src="../../../images/close-black.svg" title="Close" alt="Close" />
</div>
</div>
<!-- Setup spark header - End -->
<div class="paneMainContent"><div data-bind="react: clusterSettingsComponentAdapter"></div></div>
<div class="paneFooter">
<div class="leftpanel-okbut"><input type="submit" value="Save" class="btncreatecoll1" /></div>
</div>
</form>
</div>
</div>
<!-- Setup spark form - Start -->
<!-- Loader - Start -->
<div class="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer" data-bind="visible: isExecuting">
<img class="dataExplorerLoader" alt="loading indicator image" src="/LoadingIndicator_3Squares.gif" />
</div>
<!-- Loader - End -->
</div>
</div>

View File

@@ -1,153 +0,0 @@
import * as ko from "knockout";
import * as ViewModels from "../../Contracts/ViewModels";
import * as DataModels from "../../Contracts/DataModels";
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import { Areas } from "../../Common/Constants";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import { ContextualPaneBase } from "./ContextualPaneBase";
import { ClusterSettingsComponentAdapter } from "../Controls/Spark/ClusterSettingsComponentAdapter";
import { ClusterSettingsComponentProps } from "../Controls/Spark/ClusterSettingsComponent";
import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
import { Spark } from "../../Common/Constants";
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
const MAX_NUM_WORKERS = 10;
export class ManageSparkClusterPane extends ContextualPaneBase {
public readonly maxWorkerCount = Spark.MaxWorkerCount;
public workerCount: ko.Observable<number>;
public clusterSettingsComponentAdapter: ClusterSettingsComponentAdapter;
private _settingsComponentAdapterProps: ko.Observable<ClusterSettingsComponentProps>;
private _defaultCluster: ko.Observable<DataModels.SparkCluster>;
constructor(options: ViewModels.PaneOptions) {
super(options);
this.title("Manage spark cluster");
this.workerCount = ko.observable<number>();
this._defaultCluster = ko.observable<DataModels.SparkCluster>({
id: undefined,
name: undefined,
type: undefined,
properties: {
kind: undefined,
creationTime: undefined,
driverSize: undefined,
status: undefined,
workerInstanceCount: undefined,
workerSize: undefined
}
});
this._settingsComponentAdapterProps = ko.observable<ClusterSettingsComponentProps>({
cluster: this._defaultCluster(),
onClusterSettingsChanged: this.onClusterSettingsChange
});
this._defaultCluster.subscribe(cluster => {
this._settingsComponentAdapterProps().cluster = cluster;
this._settingsComponentAdapterProps.valueHasMutated(); // trigger component re-render
});
this.clusterSettingsComponentAdapter = new ClusterSettingsComponentAdapter();
this.clusterSettingsComponentAdapter.parameters = this._settingsComponentAdapterProps;
this.resetData();
}
public async submit() {
if (!this.workerCount() || this.workerCount() > MAX_NUM_WORKERS) {
this.formErrors("Invalid worker count specified");
this.formErrorsDetails(`The number of workers should be between 0 and ${MAX_NUM_WORKERS}`);
return;
}
if (!this._defaultCluster()) {
this.formErrors("No default cluster found");
this.formErrorsDetails("No default cluster found to be associated with this account");
return;
}
const startKey = TelemetryProcessor.traceStart(Action.UpdateSparkCluster, {
databaseAccountName: this.container && this.container.databaseAccount().name,
defaultExperience: this.container && this.container.defaultExperience(),
dataExplorerArea: Areas.ContextualPane,
paneTitle: this.title()
});
const workerCount = this.workerCount();
const id = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
`Updating default cluster worker count to ${workerCount} nodes`
);
this.isExecuting(true);
try {
const databaseAccount = this.container && this.container.databaseAccount();
const cluster = this._defaultCluster();
cluster.properties.workerInstanceCount = workerCount;
const updatedCluster =
this.container &&
(await this.container.sparkClusterManager.updateClusterAsync(
databaseAccount && databaseAccount.id,
cluster.name,
cluster
));
this._defaultCluster(updatedCluster);
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Info,
`Successfully updated default cluster worker count to ${workerCount} nodes`
);
TelemetryProcessor.traceSuccess(
Action.UpdateSparkCluster,
{
databaseAccountName: this.container && this.container.databaseAccount().name,
defaultExperience: this.container && this.container.defaultExperience(),
dataExplorerArea: Areas.ContextualPane,
paneTitle: this.title()
},
startKey
);
} catch (error) {
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Failed to update default cluster worker count to ${workerCount} nodes: ${JSON.stringify(error)}`
);
TelemetryProcessor.traceFailure(
Action.UpdateSparkCluster,
{
databaseAccountName: this.container && this.container.databaseAccount().name,
defaultExperience: this.container && this.container.defaultExperience(),
dataExplorerArea: Areas.ContextualPane,
paneTitle: this.title(),
error: JSON.stringify(error)
},
startKey
);
} finally {
this.isExecuting(false);
NotificationConsoleUtils.clearInProgressMessageWithId(id);
}
}
public onClusterSettingsChange = (cluster: DataModels.SparkCluster) => {
this._defaultCluster(cluster);
this.workerCount(
(cluster &&
cluster.properties &&
cluster.properties.workerInstanceCount !== undefined &&
cluster.properties.workerInstanceCount) ||
0
);
};
public async open() {
const defaultCluster = await this.container.sparkClusterManager.getClusterAsync(
this.container.databaseAccount().id,
"default"
);
this._defaultCluster(defaultCluster);
this.workerCount(
(defaultCluster &&
defaultCluster.properties &&
defaultCluster.properties.workerInstanceCount !== undefined &&
defaultCluster.properties.workerInstanceCount) ||
0
);
super.open();
}
}

View File

@@ -19,8 +19,6 @@ import BrowseQueriesPaneTemplate from "./BrowseQueriesPane.html";
import UploadFilePaneTemplate from "./UploadFilePane.html";
import StringInputPaneTemplate from "./StringInputPane.html";
import SetupNotebooksPaneTemplate from "./SetupNotebooksPane.html";
import SetupSparkClusterPaneTemplate from "./SetupSparkClusterPane.html";
import ManageSparkClusterPaneTemplate from "./ManageSparkClusterPane.html";
import LibraryManagePaneTemplate from "./LibraryManagePane.html";
import ClusterLibraryPaneTemplate from "./ClusterLibraryPane.html";
import GitHubReposPaneTemplate from "./GitHubReposPane.html";
@@ -220,24 +218,6 @@ export class SetupNotebooksPaneComponent {
}
}
export class SetupSparkClusterPaneComponent {
constructor() {
return {
viewModel: PaneComponent,
template: SetupSparkClusterPaneTemplate
};
}
}
export class ManageSparkClusterPaneComponent {
constructor() {
return {
viewModel: PaneComponent,
template: ManageSparkClusterPaneTemplate
};
}
}
export class LibraryManagePaneComponent {
constructor() {
return {

View File

@@ -53,7 +53,7 @@ export class RenewAdHocAccessPane extends ContextualPaneBase implements ViewMode
const apiKind: DataModels.ApiKind =
this.container && DefaultExperienceUtility.getApiKindFromDefaultExperience(this.container.defaultExperience());
const hasOpenedTabs: boolean =
(this.container && this.container.openedTabs() && this.container.openedTabs().length > 0) || false;
(this.container && this.container.tabsManager && this.container.tabsManager.openedTabs().length > 0) || false;
if (!inputMetadata || inputMetadata.apiKind == null || !inputMetadata.accountName) {
this.formErrors("Invalid connection string input");

View File

@@ -34,7 +34,8 @@ export class SaveQueryPane extends ContextualPaneBase {
}
const queryName: string = this.queryName();
const queryTab: ViewModels.QueryTab = this.container && (this.container.findActiveTab() as ViewModels.QueryTab);
const queryTab: ViewModels.QueryTab =
this.container && (this.container.tabsManager.activeTab() as ViewModels.QueryTab);
const query: string = queryTab && queryTab.sqlQueryEditorContent();
if (!queryName || queryName.length === 0) {
this.formErrors("No query name specified");

View File

@@ -1,52 +0,0 @@
<div data-bind="visible: visible, event: { keydown: onPaneKeyDown }">
<div class="contextual-pane-out" data-bind="click: cancel, clickBubble: false"></div>
<div class="contextual-pane" id="setupsparkclusterpane">
<!-- Setup spark form -- Start -->
<div class="contextual-pane-in">
<form class="paneContentContainer" data-bind="submit: submit">
<div class="paneContentContainer">
<!-- Setup spark header - Start -->
<div class="firstdivbg headerline">
<span data-bind="text: title"></span>
<div
class="closeImg"
role="button"
aria-label="Close pane"
tabindex="0"
data-bind="click: cancel, event: { keypress: onCloseKeyPress }"
>
<img src="../../../images/close-black.svg" title="Close" alt="Close" />
</div>
</div>
<!-- Setup spark header - End -->
<div class="paneMainContent">
<div class="pkPadding">
<div data-bind="text: setupSparkClusterText"></div>
<div class="seconddivpadding">
<div>Worker Count</div>
<input
class="sparkWorkerCountInput"
type="number"
min="1"
aria-label="worker count input"
required
data-bind="textInput: workerCount, attr: { max: maxWorkerNodes }"
/>
</div>
</div>
</div>
<div class="paneFooter">
<div class="leftpanel-okbut"><input type="submit" value="Complete Setup" class="btncreatecoll1" /></div>
</div>
</div>
</form>
</div>
<!-- Setup spark form - Start -->
<!-- Loader - Start -->
<div class="dataExplorerLoaderContainer dataExplorerPaneLoaderContainer" data-bind="visible: isExecuting">
<img class="dataExplorerLoader" alt="loading indicator image" src="/LoadingIndicator_3Squares.gif" />
</div>
<!-- Loader - End -->
</div>
</div>

View File

@@ -1,99 +0,0 @@
import * as ko from "knockout";
import * as ViewModels from "../../Contracts/ViewModels";
import { Action } from "../../Shared/Telemetry/TelemetryConstants";
import { Areas } from "../../Common/Constants";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import { ContextualPaneBase } from "./ContextualPaneBase";
import { NotificationConsoleUtils } from "../../Utils/NotificationConsoleUtils";
import { Spark } from "../../Common/Constants";
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
export class SetupSparkClusterPane extends ContextualPaneBase {
public setupSparkClusterText: string =
"Looks like you have not yet created a default spark cluster for this account. To proceed and start using notebooks with spark, we'll need to create a default spark cluster for this account.";
public readonly maxWorkerNodes: number = Spark.MaxWorkerCount;
public workerCount: ko.Observable<number>;
constructor(options: ViewModels.PaneOptions) {
super(options);
this.title("Enable spark");
this.workerCount = ko.observable<number>(1);
this.resetData();
}
public async submit() {
await this.setupSparkCluster();
}
public async setupSparkCluster(): Promise<void> {
if (!this.container) {
return;
}
const startKey: number = TelemetryProcessor.traceStart(Action.CreateSparkCluster, {
databaseAccountName: this.container && this.container.databaseAccount().name,
defaultExperience: this.container && this.container.defaultExperience(),
dataExplorerArea: Areas.ContextualPane,
paneTitle: this.title()
});
const id = NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.InProgress,
"Creating a new default spark cluster"
);
try {
this.isExecuting(true);
await this.container.sparkClusterManager.createClusterAsync(
this.container.databaseAccount() && this.container.databaseAccount().id,
{
name: "default",
properties: {
kind: "Spark",
workerInstanceCount: this.workerCount(),
driverSize: "Cosmos.Spark.D4s",
workerSize: "Cosmos.Spark.D4s",
creationTime: undefined,
status: undefined
}
}
);
this.container.isAccountReady.valueHasMutated(); // refresh internal state
this.close();
TelemetryProcessor.traceSuccess(
Action.CreateSparkCluster,
{
databaseAccountName: this.container && this.container.databaseAccount().name,
defaultExperience: this.container && this.container.defaultExperience(),
dataExplorerArea: Areas.ContextualPane,
paneTitle: this.title()
},
startKey
);
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Info,
"Successfully created a default spark cluster for the account"
);
} catch (error) {
const errorMessage = typeof error == "string" ? error : JSON.stringify(error);
TelemetryProcessor.traceFailure(
Action.CreateSparkCluster,
{
databaseAccountName: this.container && this.container.databaseAccount().name,
defaultExperience: this.container && this.container.defaultExperience(),
dataExplorerArea: Areas.ContextualPane,
paneTitle: this.title(),
error: errorMessage
},
startKey
);
this.formErrors("Failed to setup a default spark cluster");
this.formErrorsDetails(`Failed to setup a default spark cluster: ${errorMessage}`);
NotificationConsoleUtils.logConsoleMessage(
ConsoleDataType.Error,
`Failed to create a default spark cluster: ${errorMessage}`
);
} finally {
this.isExecuting(false);
NotificationConsoleUtils.clearInProgressMessageWithId(id);
}
}
}

View File

@@ -1,15 +1,16 @@
import * as ko from "knockout";
import { DataSamplesUtil } from "../DataSamples/DataSamplesUtil";
import { SplashScreenComponentAdapter } from "./SplashScreenComponentApdapter";
import { TabsManager } from "../Tabs/TabsManager";
import Explorer from "../Explorer";
jest.mock("../Explorer");
const createExplorer = () => {
const mock = new Explorer({} as any);
mock.openedTabs = ko.observableArray([]);
mock.selectedNode = ko.observable();
mock.isNotebookEnabled = ko.observable(false);
mock.addCollectionText = ko.observable("add collection");
mock.tabsManager = new TabsManager();
return mock as jest.Mocked<Explorer>;
};

View File

@@ -31,7 +31,7 @@ export class SplashScreenComponentAdapter implements ReactAdapter {
constructor(private container: ViewModels.Explorer) {
this.parameters = ko.observable<number>(Date.now());
this.container.openedTabs.subscribe(tabs => {
this.container.tabsManager.openedTabs.subscribe((tabs: ViewModels.Tab[]) => {
if (tabs.length === 0) {
this.forceRender();
}

View File

@@ -21,7 +21,7 @@ import DeleteIcon from "../../../images/delete.svg";
import { QueryIterator, ItemDefinition, Resource, ConflictDefinition } from "@azure/cosmos";
import { MinimalQueryIterator } from "../../Common/IteratorUtilities";
export class ConflictsTab extends TabsBase implements ViewModels.ConflictsTab {
export default class ConflictsTab extends TabsBase implements ViewModels.ConflictsTab {
public selectedConflictId: ko.Observable<ViewModels.ConflictId>;
public selectedConflictContent: ViewModels.Editable<string>;
public selectedConflictCurrent: ViewModels.Editable<string>;

View File

@@ -20,8 +20,7 @@ describe("Documents tab", () => {
hashLocation: "",
isActive: ko.observable<boolean>(false),
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {},
openedTabs: []
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
});
expect(documentsTab.buildQuery("")).toContain("select");
@@ -104,8 +103,7 @@ describe("Documents tab", () => {
hashLocation: "",
isActive: ko.observable<boolean>(false),
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {},
openedTabs: []
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
});
expect(documentsTab.showPartitionKey).toBe(false);
@@ -124,8 +122,7 @@ describe("Documents tab", () => {
hashLocation: "",
isActive: ko.observable<boolean>(false),
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {},
openedTabs: []
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
});
expect(documentsTab.showPartitionKey).toBe(false);
@@ -144,8 +141,7 @@ describe("Documents tab", () => {
hashLocation: "",
isActive: ko.observable<boolean>(false),
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {},
openedTabs: []
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
});
expect(documentsTab.showPartitionKey).toBe(true);
@@ -164,8 +160,7 @@ describe("Documents tab", () => {
hashLocation: "",
isActive: ko.observable<boolean>(false),
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {},
openedTabs: []
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
});
expect(documentsTab.showPartitionKey).toBe(false);
@@ -184,8 +179,7 @@ describe("Documents tab", () => {
hashLocation: "",
isActive: ko.observable<boolean>(false),
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {},
openedTabs: []
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
});
expect(documentsTab.showPartitionKey).toBe(true);

View File

@@ -28,8 +28,7 @@ describe("Query Tab", () => {
selfLink: "",
isActive: ko.observable<boolean>(false),
hashLocation: "",
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {},
openedTabs: []
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
});
}

View File

@@ -76,8 +76,7 @@ describe("Settings tab", () => {
quotaInfo,
null
),
onUpdateTabsButtons: undefined,
openedTabs: []
onUpdateTabsButtons: undefined
});
};
@@ -196,8 +195,7 @@ describe("Settings tab", () => {
hashLocation: "",
isActive: ko.observable(false),
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {},
openedTabs: []
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
});
expect(settingsTab.shouldUpdateCollection()).toBe(false);
@@ -221,8 +219,7 @@ describe("Settings tab", () => {
hashLocation: "",
isActive: ko.observable(false),
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {},
openedTabs: []
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
});
expect(settingsTab.shouldUpdateCollection()).toBe(false);
@@ -241,8 +238,7 @@ describe("Settings tab", () => {
hashLocation: "",
isActive: ko.observable(false),
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {},
openedTabs: []
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
});
expect(settingsTab.shouldUpdateCollection()).toBe(false);
@@ -281,8 +277,7 @@ describe("Settings tab", () => {
hashLocation: "",
isActive: ko.observable(false),
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {},
openedTabs: []
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
});
expect(settingsTab.getUpdatedConflictResolutionPolicy()).toBe(null);
@@ -299,8 +294,7 @@ describe("Settings tab", () => {
hashLocation: "",
isActive: ko.observable(false),
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {},
openedTabs: []
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
});
expect(settingsTab.getUpdatedConflictResolutionPolicy()).toBe(null);
@@ -326,8 +320,7 @@ describe("Settings tab", () => {
hashLocation: "",
isActive: ko.observable(false),
collection: new Collection(explorer, "mydb", baseCollection, quotaInfo, null),
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {},
openedTabs: []
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
});
expect(settingsTab.getUpdatedConflictResolutionPolicy()).toBe(null);
@@ -408,8 +401,7 @@ describe("Settings tab", () => {
hashLocation: "",
isActive: ko.observable(false),
collection: getCollection(defaultApi, partitionKeyOption),
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {},
openedTabs: []
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
});
}
@@ -555,8 +547,7 @@ describe("Settings tab", () => {
hashLocation: "",
isActive: ko.observable(false),
collection: getCollection(autoPilotTier),
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {},
openedTabs: []
onUpdateTabsButtons: (buttons: ViewModels.NavbarButtonConfig[]): void => {}
});
}
describe("Visible", () => {

View File

@@ -252,7 +252,7 @@ export default class SettingsTab extends TabsBase implements ViewModels.Settings
this.throughputModeRadioName = `throughputModeRadio${this.tabId}`;
this.changeFeedPolicyToggled = editable.observable<ChangeFeedPolicyToggledState>(
this.collection.rawDataModel.changeFeedPolicy != null
this.collection.rawDataModel?.changeFeedPolicy != null
? ChangeFeedPolicyToggledState.On
: ChangeFeedPolicyToggledState.Off
);

View File

@@ -16,6 +16,7 @@ import TriggerTabTemplate from "./TriggerTab.html";
import UserDefinedFunctionTabTemplate from "./UserDefinedFunctionTab.html";
import GalleryTabTemplate from "./GalleryTab.html";
import NotebookViewerTabTemplate from "./NotebookViewerTab.html";
import TabsManagerTemplate from "./TabsManager.html";
export class TabComponent {
constructor(data: any) {
@@ -23,6 +24,15 @@ export class TabComponent {
}
}
export class TabsManager {
constructor() {
return {
viewModel: TabComponent,
template: TabsManagerTemplate
};
}
}
export class DocumentsTab {
constructor() {
return {

View File

@@ -24,8 +24,6 @@ export default class TabsBase extends WaitsForTemplateViewModel implements ViewM
public tabKind: ViewModels.CollectionTabKind;
public tabTitle: ko.Observable<string>;
public tabPath: ko.Observable<string>;
public nextTab: ko.Observable<ViewModels.Tab>;
public previousTab: ko.Observable<ViewModels.Tab>;
public closeButtonTabIndex: ko.Computed<number>;
public errorDetailsTabIndex: ko.Computed<number>;
public hashLocation: ko.Observable<string>;
@@ -55,8 +53,6 @@ export default class TabsBase extends WaitsForTemplateViewModel implements ViewM
(options.tabPath && ko.observable<string>(options.tabPath)) ||
(this.collection &&
ko.observable<string>(`${this.collection.databaseId}>${this.collection.id()}>${this.tabTitle()}`));
this.nextTab = ko.observable<ViewModels.Tab>();
this.previousTab = ko.observable<ViewModels.Tab>();
this.closeButtonTabIndex = ko.computed<number>(() => (this.isActive() ? 0 : null));
this.errorDetailsTabIndex = ko.computed<number>(() => (this.isActive() ? 0 : null));
this.isExecutionError = ko.observable<boolean>(false);
@@ -80,34 +76,11 @@ export default class TabsBase extends WaitsForTemplateViewModel implements ViewM
return true;
})
};
const openedTabs = options.openedTabs;
if (openedTabs && openedTabs.length && openedTabs.length > 0) {
const lastTab = openedTabs[openedTabs.length - 1];
lastTab && lastTab.nextTab(this);
this.previousTab(lastTab);
}
}
public onCloseTabButtonClick(): Q.Promise<any> {
const previousTab = this.previousTab();
const nextTab = this.nextTab();
previousTab && previousTab.nextTab(nextTab);
nextTab && nextTab.previousTab(previousTab);
this.getContainer().openedTabs.remove(tab => tab.tabId === this.tabId);
const tabToActivate = nextTab || previousTab;
if (!tabToActivate) {
this.getContainer().selectedNode(null);
this.getContainer().onUpdateTabsButtons([]);
this.getContainer().activeTab(null);
} else {
tabToActivate.isActive(true);
this.getContainer().activeTab(tabToActivate);
}
public onCloseTabButtonClick(): void {
const explorer: ViewModels.Explorer = this.getContainer();
explorer.tabsManager.closeTab(this.tabId, explorer);
TelemetryProcessor.trace(Action.Tab, ActionModifiers.Close, {
databaseAccountName: this.getContainer().databaseAccount().name,
@@ -115,16 +88,10 @@ export default class TabsBase extends WaitsForTemplateViewModel implements ViewM
dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle()
});
return Q();
}
public onTabClick(): Q.Promise<any> {
for (let i = 0; i < this.getContainer().openedTabs().length; i++) {
const tab = this.getContainer().openedTabs()[i];
tab.isActive(false);
}
this.isActive(true);
this.getContainer().activeTab(this);
this.getContainer().tabsManager.activateTab(this);
return Q();
}

View File

@@ -0,0 +1,148 @@
<div id="content" class="flexContainer hideOverflows" data-bind="visible: openedTabs && openedTabs().length > 0">
<!-- Tabs - Start -->
<div class="nav-tabs-margin">
<ul class="nav nav-tabs level navTabHeight" id="navTabs" role="tablist">
<!-- ko foreach: openedTabs -->
<li
class="tabList"
data-bind="
attr: {
title: $data.tabPath,
'aria-selected': $data.isActive,
'aria-expanded': $data.isActive,
'aria-controls': $data.tabId
},
css:{
active: $data.isActive
},
hasFocus: $data.hasFocus,
event: { keypress: onKeyPressActivate },
click: $data.onTabClick,"
tabindex="0"
role="tab"
>
<span class="tabNavContentContainer">
<a data-toggle="tab" data-bind="attr: { href: '#' + $data.tabId }" tabindex="-1">
<div class="tab_Content">
<span class="statusIconContainer">
<div
class="errorIconContainer"
id="errorStatusIcon"
title="Click to view more details"
role="button"
data-bind="
click: onErrorDetailsClick,
event: { keypress: onErrorDetailsKeyPress },
attr: { tabindex: errorDetailsTabIndex },
css: { actionsEnabled: isActive },
visible: isExecutionError"
>
<span class="errorIcon"></span>
</div>
<img
class="loadingIcon"
title="Loading"
src="/circular_loader_black_16x16.gif"
data-bind="visible: $data.isExecuting"
alt="Loading"
/>
</span>
<span class="tabNavText" data-bind="text: $data.tabTitle"></span>
<span class="tabIconSection">
<span
aria-label="Close Tab"
role="button"
class="cancelButton"
data-bind="
click: $data.onCloseTabButtonClick,
event: { keypress: onKeyPressClose },
attr: { tabindex: $data.closeButtonTabIndex },
visible: $data.isActive() || $data.isMouseOver()"
title="Close"
>
<span class="tabIcon close-Icon" data-bind="visible: $data.isActive() || $data.isMouseOver()">
<img src="../../../images/close-black.svg" title="Close" alt="Close" />
</span>
</span>
</span>
</div>
</a>
</span>
</li>
<!-- /ko -->
</ul>
</div>
<!-- Tabs -- End -->
<!-- Tabs Panes -- Start -->
<div class="tabPanesContainer">
<!-- ko foreach: openedTabs -->
<div class="tabs-container" data-bind="visible: $data.isActive">
<!-- ko if: $data.tabKind === 0 -->
<documents-tab params="{data: $data}"></documents-tab>
<!-- /ko -->
<!-- ko if: $data.tabKind === 1 -->
<settings-tab params="{data: $data}"></settings-tab>
<!-- /ko -->
<!-- ko if: $data.tabKind === 2 -->
<stored-procedure-tab params="{data: $data}"></stored-procedure-tab>
<!-- /ko -->
<!-- ko if: $data.tabKind === 3 -->
<user-defined-function-tab params="{data: $data}"></user-defined-function-tab>
<!-- /ko -->
<!-- ko if: $data.tabKind === 4 -->
<trigger-tab params="{data: $data}"></trigger-tab>
<!-- /ko -->
<!-- ko if: $data.tabKind === 5 -->
<query-tab params="{data: $data}"></query-tab>
<!-- /ko -->
<!-- ko if: $data.tabKind === 6 -->
<graph-tab params="{data: $data}"></graph-tab>
<!-- /ko -->
<!-- ko if: $data.tabKind === 9 -->
<tables-query-tab class="flexContainer" params="{data: $data}"></tables-query-tab>
<!-- /ko -->
<!-- ko if: $data.tabKind === 10 -->
<mongo-shell-tab params="{data: $data}"></mongo-shell-tab>
<!-- /ko -->
<!-- ko if: $data.tabKind === 11 -->
<database-settings-tab params="{data: $data}"></database-settings-tab>
<!-- /ko -->
<!-- ko if: $data.tabKind === 12 -->
<conflicts-tab params="{data: $data}"></conflicts-tab>
<!-- /ko -->
<!-- ko if: $data.tabKind === 14 -->
<terminal-tab params="{data: $data}"></terminal-tab>
<!-- /ko -->
<!-- ko if: $data.tabKind === 15 -->
<notebookv2-tab params="{data: $data}"></notebookv2-tab>
<!-- /ko -->
<!-- ko if: $data.tabKind === 16 -->
<spark-master-tab params="{data: $data}"></spark-master-tab>
<!-- /ko -->
<!-- ko if: $data.tabKind === 17 -->
<gallery-tab params="{data: $data}"></gallery-tab>
<!-- /ko -->
<!-- ko if: $data.tabKind === 18 -->
<notebook-viewer-tab params="{data: $data}"></notebook-viewer-tab>
<!-- /ko -->
</div>
<!-- /ko -->
</div>
<!-- Tabs Panes - End -->
</div>

View File

@@ -0,0 +1,138 @@
import * as ko from "knockout";
import * as ViewModels from "../../Contracts/ViewModels";
import { CollectionStub, DatabaseStub } from "../../Explorer/OpenActionsStubs";
import { DataAccessUtility } from "../../Platform/Portal/DataAccessUtility";
import { TabsManager } from "./TabsManager";
import DocumentClientUtilityBase from "../../Common/DocumentClientUtilityBase";
import DocumentsTab from "./DocumentsTab";
import Explorer from "../Explorer";
import QueryTab from "./QueryTab";
describe("Tabs manager tests", () => {
let tabsManager: TabsManager;
let explorer: ViewModels.Explorer;
let database: ViewModels.Database;
let collection: ViewModels.Collection;
let queryTab: QueryTab;
let documentsTab: DocumentsTab;
beforeAll(() => {
explorer = new Explorer({ documentClientUtility: undefined, notificationsClient: undefined, isEmulator: false });
explorer.databaseAccount = ko.observable<ViewModels.DatabaseAccount>({
id: "test",
name: "test",
location: "",
type: "",
kind: "",
tags: "",
properties: undefined
});
database = new DatabaseStub({
container: explorer,
id: ko.observable<string>("test"),
isDatabaseShared: () => false
});
database.isDatabaseExpanded = ko.observable<boolean>(true);
database.selectedSubnodeKind = ko.observable<ViewModels.CollectionTabKind>();
collection = new CollectionStub({
container: explorer,
databaseId: "test",
id: ko.observable<string>("test")
});
collection.getDatabase = (): ViewModels.Database => database;
collection.isCollectionExpanded = ko.observable<boolean>(true);
collection.selectedSubnodeKind = ko.observable<ViewModels.CollectionTabKind>();
queryTab = new QueryTab({
tabKind: ViewModels.CollectionTabKind.Query,
collection,
database,
title: "",
tabPath: "",
documentClientUtility: explorer.documentClientUtility,
selfLink: "",
isActive: ko.observable<boolean>(false),
hashLocation: "",
onUpdateTabsButtons: undefined
});
documentsTab = new DocumentsTab({
partitionKey: undefined,
documentIds: ko.observableArray<ViewModels.DocumentId>(),
tabKind: ViewModels.CollectionTabKind.Documents,
collection,
title: "",
tabPath: "",
documentClientUtility: new DocumentClientUtilityBase(new DataAccessUtility()),
selfLink: "",
hashLocation: "",
isActive: ko.observable<boolean>(false),
onUpdateTabsButtons: undefined
});
});
beforeEach(() => {
tabsManager = new TabsManager();
explorer.tabsManager = tabsManager;
});
it("open new tabs", () => {
tabsManager.activateNewTab(queryTab);
expect(tabsManager.openedTabs().length).toBe(1);
expect(tabsManager.openedTabs()[0]).toEqual(queryTab);
expect(tabsManager.activeTab()).toEqual(queryTab);
expect(queryTab.isActive()).toBe(true);
tabsManager.activateNewTab(documentsTab);
expect(tabsManager.openedTabs().length).toBe(2);
expect(tabsManager.openedTabs()[1]).toEqual(documentsTab);
expect(tabsManager.activeTab()).toEqual(documentsTab);
expect(queryTab.isActive()).toBe(false);
expect(documentsTab.isActive()).toBe(true);
});
it("open existing tabs", () => {
tabsManager.activateNewTab(queryTab);
tabsManager.activateNewTab(documentsTab);
tabsManager.activateTab(queryTab);
expect(tabsManager.openedTabs().length).toBe(2);
expect(tabsManager.activeTab()).toEqual(queryTab);
expect(queryTab.isActive()).toBe(true);
expect(documentsTab.isActive()).toBe(false);
});
it("get tabs", () => {
tabsManager.activateNewTab(queryTab);
tabsManager.activateNewTab(documentsTab);
const queryTabs: ViewModels.Tab[] = tabsManager.getTabs(ViewModels.CollectionTabKind.Query);
expect(queryTabs.length).toBe(1);
expect(queryTabs[0]).toEqual(queryTab);
const documentsTabs: ViewModels.Tab[] = tabsManager.getTabs(
ViewModels.CollectionTabKind.Documents,
(tab: ViewModels.Tab) => tab.tabId === documentsTab.tabId
);
expect(documentsTabs.length).toBe(1);
expect(documentsTabs[0]).toEqual(documentsTab);
});
it("close tabs", () => {
tabsManager.activateNewTab(queryTab);
tabsManager.activateNewTab(documentsTab);
tabsManager.closeTab(documentsTab.tabId, explorer);
expect(tabsManager.openedTabs().length).toBe(1);
expect(tabsManager.openedTabs()[0]).toEqual(queryTab);
expect(tabsManager.activeTab()).toEqual(queryTab);
expect(queryTab.isActive()).toBe(true);
expect(documentsTab.isActive()).toBe(false);
tabsManager.closeTabsByComparator((tab: ViewModels.Tab) => tab.tabId === queryTab.tabId);
expect(tabsManager.openedTabs().length).toBe(0);
expect(tabsManager.activeTab()).toEqual(undefined);
expect(queryTab.isActive()).toBe(false);
});
});

View File

@@ -0,0 +1,96 @@
import * as ko from "knockout";
import * as ViewModels from "../../Contracts/ViewModels";
import TabsManagerTemplate from "./TabsManager.html";
export class TabsManager {
public openedTabs: ko.ObservableArray<ViewModels.Tab>;
public activeTab: ko.Observable<ViewModels.Tab>;
constructor() {
this.openedTabs = ko.observableArray<ViewModels.Tab>([]);
this.activeTab = ko.observable<ViewModels.Tab>();
}
public activateNewTab(tab: ViewModels.Tab): void {
this.openedTabs.push(tab);
this.activateTab(tab);
}
public activateTab(tab: ViewModels.Tab): void {
this.activeTab() && this.activeTab().isActive(false);
tab.isActive(true);
this.activeTab(tab);
}
public getTabs(
tabKind: ViewModels.CollectionTabKind,
comparator?: (tab: ViewModels.Tab) => boolean
): ViewModels.Tab[] {
return this.openedTabs().filter((openedTab: ViewModels.Tab) => {
return openedTab.tabKind === tabKind && (!comparator || comparator(openedTab));
});
}
public refreshActiveTab(comparator: (tab: ViewModels.Tab) => boolean): void {
// ensures that the tab selects/highlights the right node based on resource tree expand/collapse state
this.openedTabs().forEach((tab: ViewModels.Tab) => {
if (comparator(tab) && tab.isActive()) {
tab.onActivate();
}
});
}
public removeTabById(tabId: string): void {
this.openedTabs.remove((tab: ViewModels.Tab) => tab.tabId === tabId);
}
public removeTabByComparator(comparator: (tab: ViewModels.Tab) => boolean): void {
this.openedTabs.remove((tab: ViewModels.Tab) => comparator(tab));
}
public closeTabsByComparator(comparator: (tab: ViewModels.Tab) => boolean): void {
this.activeTab() && this.activeTab().isActive(false);
this.activeTab(undefined);
this.openedTabs().forEach((tab: ViewModels.Tab) => {
if (comparator(tab)) {
tab.onCloseTabButtonClick();
}
});
}
public closeTabs(): void {
this.openedTabs([]);
}
public closeTab(tabId: string, explorer: ViewModels.Explorer): void {
const tabIndex: number = this.openedTabs().findIndex((tab: ViewModels.Tab) => tab.tabId === tabId);
if (tabIndex !== -1) {
const tabToActive: ViewModels.Tab = this.openedTabs()[tabIndex + 1] || this.openedTabs()[tabIndex - 1];
this.openedTabs()[tabIndex].isActive(false);
this.removeTabById(tabId);
if (tabToActive) {
tabToActive.isActive(true);
this.activeTab(tabToActive);
} else {
explorer.selectedNode(undefined);
explorer.onUpdateTabsButtons([]);
this.activeTab(undefined);
}
}
}
public isTabActive(tabKind: ViewModels.CollectionTabKind): boolean {
return this.activeTab() && this.activeTab().tabKind === tabKind;
}
}
function TabsManagerWrapperViewModel(params: { data: TabsManager }) {
return params.data;
}
export function TabsManagerKOComponent(): unknown {
return {
viewModel: TabsManagerWrapperViewModel,
template: TabsManagerTemplate
};
}

View File

@@ -16,7 +16,10 @@ import { OfferUtils } from "../../Utils/OfferUtils";
import { StartUploadMessageParams, UploadDetails, UploadDetailsRecord } from "../../workers/upload/definitions";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import { CassandraAPIDataClient, CassandraTableKey, CassandraTableKeys } from "../Tables/TableDataClient";
import { ConflictsTab } from "../Tabs/ConflictsTab";
import ConflictId from "./ConflictId";
import DocumentId from "./DocumentId";
import ConflictsTab from "../Tabs/ConflictsTab";
import DocumentsTab from "../Tabs/DocumentsTab";
import GraphTab from "../Tabs/GraphTab";
import MongoDocumentsTab from "../Tabs/MongoDocumentsTab";
@@ -25,8 +28,6 @@ import MongoShellTab from "../Tabs/MongoShellTab";
import QueryTab from "../Tabs/QueryTab";
import QueryTablesTab from "../Tabs/QueryTablesTab";
import SettingsTab from "../Tabs/SettingsTab";
import ConflictId from "./ConflictId";
import DocumentId from "./DocumentId";
import StoredProcedure from "./StoredProcedure";
import Trigger from "./Trigger";
import UserDefinedFunction from "./UserDefinedFunction";
@@ -229,7 +230,9 @@ export default class Collection implements ViewModels.Collection {
this.expandCollection();
}
this.container.onUpdateTabsButtons([]);
this.refreshActiveTab();
this.container.tabsManager.refreshActiveTab(
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === this.rid
);
}
public collapseCollection() {
@@ -278,14 +281,15 @@ export default class Collection implements ViewModels.Collection {
dataExplorerArea: Constants.Areas.ResourceTree
});
// create documents tab if not created yet
const openedTabs = this.container.openedTabs();
const documentsTabs: DocumentsTab[] = this.container.tabsManager.getTabs(
ViewModels.CollectionTabKind.Documents,
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === this.rid
) as DocumentsTab[];
let documentsTab: DocumentsTab = documentsTabs && documentsTabs[0];
let documentsTab: ViewModels.Tab = openedTabs
.filter(tab => tab.collection && tab.collection.rid === this.rid)
.filter(tab => tab.tabKind === ViewModels.CollectionTabKind.Documents)[0];
if (!documentsTab) {
if (documentsTab) {
this.container.tabsManager.activateTab(documentsTab);
} else {
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseAccountName: this.container.databaseAccount().name,
databaseName: this.databaseId,
@@ -295,6 +299,7 @@ export default class Collection implements ViewModels.Collection {
tabTitle: "Items"
});
this.documentIds([]);
documentsTab = new DocumentsTab({
partitionKey: this.partitionKey,
documentIds: ko.observableArray<DocumentId>([]),
@@ -309,14 +314,11 @@ export default class Collection implements ViewModels.Collection {
tabPath: `${this.databaseId}>${this.id()}>Documents`,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/documents`,
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
openedTabs: this.container.openedTabs()
onUpdateTabsButtons: this.container.onUpdateTabsButtons
});
this.container.openedTabs.push(documentsTab);
}
// Activate
documentsTab.onTabClick();
this.container.tabsManager.activateNewTab(documentsTab);
}
}
public onConflictsClick() {
@@ -331,14 +333,15 @@ export default class Collection implements ViewModels.Collection {
dataExplorerArea: Constants.Areas.ResourceTree
});
// create documents tab if not created yet
const openedTabs = this.container.openedTabs();
const conflictsTabs: ConflictsTab[] = this.container.tabsManager.getTabs(
ViewModels.CollectionTabKind.Conflicts,
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === this.rid
) as ConflictsTab[];
let conflictsTab: ConflictsTab = conflictsTabs && conflictsTabs[0];
let conflictsTab: ViewModels.Tab = openedTabs
.filter(tab => tab.collection && tab.collection.rid === this.rid)
.filter(tab => tab.tabKind === ViewModels.CollectionTabKind.Conflicts)[0];
if (!conflictsTab) {
if (conflictsTab) {
this.container.tabsManager.activateTab(conflictsTab);
} else {
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseAccountName: this.container.databaseAccount().name,
databaseName: this.databaseId,
@@ -348,7 +351,8 @@ export default class Collection implements ViewModels.Collection {
tabTitle: "Conflicts"
});
this.documentIds([]);
conflictsTab = new ConflictsTab({
const conflictsTab: ConflictsTab = new ConflictsTab({
partitionKey: this.partitionKey,
conflictIds: ko.observableArray<ConflictId>([]),
tabKind: ViewModels.CollectionTabKind.Conflicts,
@@ -362,14 +366,11 @@ export default class Collection implements ViewModels.Collection {
tabPath: `${this.databaseId}>${this.id()}>Conflicts`,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/conflicts`,
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
openedTabs: this.container.openedTabs()
onUpdateTabsButtons: this.container.onUpdateTabsButtons
});
this.container.openedTabs.push(conflictsTab);
}
// Activate
conflictsTab.onTabClick();
this.container.tabsManager.activateNewTab(conflictsTab);
}
}
public onTableEntitiesClick() {
@@ -390,14 +391,15 @@ export default class Collection implements ViewModels.Collection {
});
}
// create entities tab if not created yet
const openedTabs = this.container.openedTabs();
const queryTablesTabs: QueryTablesTab[] = this.container.tabsManager.getTabs(
ViewModels.CollectionTabKind.QueryTables,
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === this.rid
) as QueryTablesTab[];
let queryTablesTab: QueryTablesTab = queryTablesTabs && queryTablesTabs[0];
let documentsTab: ViewModels.Tab = openedTabs
.filter(tab => tab.collection && tab.collection.rid === this.rid)
.filter(tab => tab.tabKind === ViewModels.CollectionTabKind.QueryTables)[0];
if (!documentsTab) {
if (queryTablesTab) {
this.container.tabsManager.activateTab(queryTablesTab);
} else {
this.documentIds([]);
let title = `Entities`;
if (this.container.isPreferredApiCassandra()) {
@@ -411,7 +413,8 @@ export default class Collection implements ViewModels.Collection {
dataExplorerArea: Constants.Areas.Tab,
tabTitle: title
});
documentsTab = new QueryTablesTab({
queryTablesTab = new QueryTablesTab({
tabKind: ViewModels.CollectionTabKind.QueryTables,
title: title,
tabPath: "",
@@ -423,14 +426,11 @@ export default class Collection implements ViewModels.Collection {
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/entities`,
isActive: ko.observable(false),
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
openedTabs: this.container.openedTabs()
onUpdateTabsButtons: this.container.onUpdateTabsButtons
});
this.container.openedTabs.push(documentsTab);
}
// Activate
documentsTab.onTabClick();
this.container.tabsManager.activateNewTab(queryTablesTab);
}
}
public onGraphDocumentsClick() {
@@ -445,55 +445,50 @@ export default class Collection implements ViewModels.Collection {
dataExplorerArea: Constants.Areas.ResourceTree
});
// create documents tab if not created yet
const openedTabs = this.container.openedTabs();
const graphTabs: GraphTab[] = this.container.tabsManager.getTabs(
ViewModels.CollectionTabKind.Graph,
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === this.rid
) as GraphTab[];
let graphTab: GraphTab = graphTabs && graphTabs[0];
let documentsTab: ViewModels.Tab = openedTabs
.filter(tab => tab.collection && tab.collection.rid === this.rid)
.filter(tab => tab.tabKind === ViewModels.CollectionTabKind.Graph)[0];
if (!documentsTab) {
if (graphTab) {
this.container.tabsManager.activateTab(graphTab);
} else {
this.documentIds([]);
documentsTab = this._createGraphTab("Graph");
this.container.openedTabs.push(documentsTab);
const title = "Graph";
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseAccountName: this.container.databaseAccount().name,
databaseName: this.databaseId,
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: title
});
graphTab = new GraphTab({
account: CosmosClient.databaseAccount(),
tabKind: ViewModels.CollectionTabKind.Graph,
node: this,
title: title,
tabPath: "",
documentClientUtility: this.container.documentClientUtility,
collection: this,
selfLink: this.self,
masterKey: CosmosClient.masterKey() || "",
collectionPartitionKeyProperty: this.partitionKeyProperty,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/graphs`,
collectionId: this.id(),
isActive: ko.observable(false),
databaseId: this.databaseId,
isTabsContentExpanded: this.container.isTabsContentExpanded,
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons
});
this.container.tabsManager.activateNewTab(graphTab);
}
// Activate
documentsTab.onTabClick();
}
private _createGraphTab = (title: string): ViewModels.GraphTab => {
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseAccountName: this.container.databaseAccount().name,
databaseName: this.databaseId,
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: title
});
// TODO where does the success/failure trace for this tab go?
return new GraphTab({
account: CosmosClient.databaseAccount(),
tabKind: ViewModels.CollectionTabKind.Graph,
node: this,
title: title,
tabPath: "",
documentClientUtility: this.container.documentClientUtility,
collection: this,
selfLink: this.self,
masterKey: CosmosClient.masterKey() || "",
collectionPartitionKeyProperty: this.partitionKeyProperty,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/graphs`,
collectionId: this.id(),
isActive: ko.observable(false),
databaseId: this.databaseId,
isTabsContentExpanded: this.container.isTabsContentExpanded,
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
openedTabs: this.container.openedTabs()
});
};
public onMongoDBDocumentsClick = () => {
this.container.selectedNode(this);
this.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents);
@@ -506,14 +501,15 @@ export default class Collection implements ViewModels.Collection {
dataExplorerArea: Constants.Areas.ResourceTree
});
// create documents tab if not created yet
const openedTabs = this.container.openedTabs();
const mongoDocumentsTabs: MongoDocumentsTab[] = this.container.tabsManager.getTabs(
ViewModels.CollectionTabKind.Documents,
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === this.rid
) as MongoDocumentsTab[];
let mongoDocumentsTab: MongoDocumentsTab = mongoDocumentsTabs && mongoDocumentsTabs[0];
let documentsTab: ViewModels.Tab = openedTabs
.filter(tab => tab.collection && tab.collection && tab.collection.rid === this.rid)
.filter(tab => tab.tabKind === ViewModels.CollectionTabKind.Documents)[0];
if (!documentsTab) {
if (mongoDocumentsTab) {
this.container.tabsManager.activateTab(mongoDocumentsTab);
} else {
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseAccountName: this.container.databaseAccount().name,
databaseName: this.databaseId,
@@ -523,7 +519,8 @@ export default class Collection implements ViewModels.Collection {
tabTitle: "Documents"
});
this.documentIds([]);
documentsTab = new MongoDocumentsTab({
mongoDocumentsTab = new MongoDocumentsTab({
partitionKey: this.partitionKey,
documentIds: this.documentIds,
tabKind: ViewModels.CollectionTabKind.Documents,
@@ -537,14 +534,10 @@ export default class Collection implements ViewModels.Collection {
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoDocuments`,
isActive: ko.observable(false),
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
openedTabs: this.container.openedTabs()
onUpdateTabsButtons: this.container.onUpdateTabsButtons
});
this.container.openedTabs.push(documentsTab);
this.container.tabsManager.activateNewTab(mongoDocumentsTab);
}
// Activate
documentsTab.onTabClick();
};
public onSettingsClick = () => {
@@ -559,14 +552,15 @@ export default class Collection implements ViewModels.Collection {
dataExplorerArea: Constants.Areas.ResourceTree
});
// create settings tab if not created yet
const openedTabs = this.container.openedTabs();
let settingsTab: ViewModels.Tab = openedTabs
.filter(tab => tab.collection && tab.collection.rid === this.rid)
.filter(tab => tab.tabKind === ViewModels.CollectionTabKind.Settings)[0];
const tabTitle = "Scale & Settings";
const pendingNotificationsPromise: Q.Promise<DataModels.Notification> = this._getPendingThroughputSplitNotification();
const matchingTabs: ViewModels.Tab[] = this.container.tabsManager.getTabs(
ViewModels.CollectionTabKind.Settings,
(tab: ViewModels.Tab) => {
return tab.collection && tab.collection.rid === this.rid;
}
);
let settingsTab: SettingsTab = matchingTabs && (matchingTabs[0] as SettingsTab);
if (!settingsTab) {
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseAccountName: this.container.databaseAccount().name,
@@ -592,12 +586,10 @@ export default class Collection implements ViewModels.Collection {
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/settings`,
isActive: ko.observable(false),
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
openedTabs: this.container.openedTabs()
onUpdateTabsButtons: this.container.onUpdateTabsButtons
});
(settingsTab as ViewModels.SettingsTab).pendingNotification(pendingNotification);
this.container.openedTabs.push(settingsTab);
settingsTab.onTabClick(); // Activate
this.container.tabsManager.activateNewTab(settingsTab);
settingsTab.pendingNotification(pendingNotification);
},
(error: any) => {
TelemetryProcessor.traceFailure(
@@ -623,12 +615,12 @@ export default class Collection implements ViewModels.Collection {
} else {
pendingNotificationsPromise.then(
(pendingNotification: DataModels.Notification) => {
(settingsTab as ViewModels.SettingsTab).pendingNotification(pendingNotification);
settingsTab.onTabClick();
settingsTab.pendingNotification(pendingNotification);
this.container.tabsManager.activateTab(settingsTab);
},
(error: any) => {
(settingsTab as ViewModels.SettingsTab).pendingNotification(undefined);
settingsTab.onTabClick();
settingsTab.pendingNotification(undefined);
this.container.tabsManager.activateTab(settingsTab);
}
);
}
@@ -738,9 +730,7 @@ export default class Collection implements ViewModels.Collection {
public onNewQueryClick(source: any, event: MouseEvent, queryText?: string) {
const collection: ViewModels.Collection = source.collection || source;
const explorer: ViewModels.Explorer = source.container;
const openedTabs = explorer.openedTabs();
const id = openedTabs.filter(t => t.tabKind === ViewModels.CollectionTabKind.Query).length + 1;
const id = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Query).length + 1;
const title = "Query " + id;
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseAccountName: this.container.databaseAccount().name,
@@ -751,7 +741,7 @@ export default class Collection implements ViewModels.Collection {
tabTitle: title
});
let queryTab: ViewModels.Tab = new QueryTab({
const queryTab: QueryTab = new QueryTab({
tabKind: ViewModels.CollectionTabKind.Query,
title: title,
tabPath: "",
@@ -764,20 +754,15 @@ export default class Collection implements ViewModels.Collection {
queryText: queryText,
partitionKey: collection.partitionKey,
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
openedTabs: this.container.openedTabs()
onUpdateTabsButtons: this.container.onUpdateTabsButtons
});
this.container.openedTabs.push(queryTab);
// Activate
queryTab.onTabClick();
this.container.tabsManager.activateNewTab(queryTab);
}
public onNewMongoQueryClick(source: any, event: MouseEvent, queryText?: string) {
const collection: ViewModels.Collection = source.collection || source;
const explorer: ViewModels.Explorer = source.container;
const openedTabs = explorer.openedTabs();
const id = openedTabs.filter(t => t.tabKind === ViewModels.CollectionTabKind.Query).length + 1;
const id = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Query).length + 1;
const title = "Query " + id;
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
@@ -789,7 +774,7 @@ export default class Collection implements ViewModels.Collection {
tabTitle: title
});
let queryTab: ViewModels.Tab = new MongoQueryTab({
const mongoQueryTab: MongoQueryTab = new MongoQueryTab({
tabKind: ViewModels.CollectionTabKind.Query,
title: title,
tabPath: "",
@@ -801,26 +786,51 @@ export default class Collection implements ViewModels.Collection {
isActive: ko.observable(false),
partitionKey: collection.partitionKey,
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
openedTabs: this.container.openedTabs()
onUpdateTabsButtons: this.container.onUpdateTabsButtons
});
this.container.openedTabs.push(queryTab);
// Activate
queryTab.onTabClick();
this.container.tabsManager.activateNewTab(mongoQueryTab);
}
public onNewGraphClick() {
var id = this.container.openedTabs().filter(t => t.tabKind === ViewModels.CollectionTabKind.Graph).length + 1;
var graphTab = this._createGraphTab("Graph Query " + id);
this.container.openedTabs.push(graphTab);
// Activate
graphTab.onTabClick();
const id: number = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Graph).length + 1;
const title: string = "Graph Query " + id;
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseAccountName: this.container.databaseAccount().name,
databaseName: this.databaseId,
collectionName: this.id(),
defaultExperience: this.container.defaultExperience(),
dataExplorerArea: Constants.Areas.Tab,
tabTitle: title
});
const graphTab: GraphTab = new GraphTab({
account: CosmosClient.databaseAccount(),
tabKind: ViewModels.CollectionTabKind.Graph,
node: this,
title: title,
tabPath: "",
documentClientUtility: this.container.documentClientUtility,
collection: this,
selfLink: this.self,
masterKey: CosmosClient.masterKey() || "",
collectionPartitionKeyProperty: this.partitionKeyProperty,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/graphs`,
collectionId: this.id(),
isActive: ko.observable(false),
databaseId: this.databaseId,
isTabsContentExpanded: this.container.isTabsContentExpanded,
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons
});
this.container.tabsManager.activateNewTab(graphTab);
}
public onNewMongoShellClick() {
var id = this.container.openedTabs().filter(t => t.tabKind === ViewModels.CollectionTabKind.MongoShell).length + 1;
var mongoShellTab = new MongoShellTab({
const id = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.MongoShell).length + 1;
const mongoShellTab: MongoShellTab = new MongoShellTab({
tabKind: ViewModels.CollectionTabKind.MongoShell,
title: "Shell " + id,
tabPath: "",
@@ -830,14 +840,10 @@ export default class Collection implements ViewModels.Collection {
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoShell`,
selfLink: this.self,
isActive: ko.observable(false),
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
openedTabs: this.container.openedTabs()
onUpdateTabsButtons: this.container.onUpdateTabsButtons
});
this.container.openedTabs.push(mongoShellTab);
// Activate
mongoShellTab.onTabClick();
this.container.tabsManager.activateNewTab(mongoShellTab);
}
public onNewStoredProcedureClick(source: ViewModels.Collection, event: MouseEvent) {
@@ -898,7 +904,9 @@ export default class Collection implements ViewModels.Collection {
} else {
this.expandStoredProcedures();
}
this.refreshActiveTab();
this.container.tabsManager.refreshActiveTab(
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === this.rid
);
}
public expandStoredProcedures() {
@@ -955,7 +963,9 @@ export default class Collection implements ViewModels.Collection {
} else {
this.expandUserDefinedFunctions();
}
this.refreshActiveTab();
this.container.tabsManager.refreshActiveTab(
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === this.rid
);
}
public expandUserDefinedFunctions() {
@@ -1012,7 +1022,9 @@ export default class Collection implements ViewModels.Collection {
} else {
this.expandTriggers();
}
this.refreshActiveTab();
this.container.tabsManager.refreshActiveTab(
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === this.rid
);
}
public expandTriggers() {
@@ -1366,19 +1378,6 @@ export default class Collection implements ViewModels.Collection {
});
}
public refreshActiveTab(): void {
// ensures that the tab selects/highlights the right node based on resource tree expand/collapse state
const openedRelevantTabs: ViewModels.Tab[] = this.container
.openedTabs()
.filter((tab: ViewModels.Tab) => tab && tab.collection && tab.collection.rid === this.rid);
openedRelevantTabs.forEach((tab: ViewModels.Tab) => {
if (tab.isActive()) {
tab.onActivate();
}
});
}
protected _getOfferForCollection(offers: DataModels.Offer[], collection: DataModels.Collection): DataModels.Offer {
return _.find(offers, (offer: DataModels.Offer) => offer.resource.indexOf(collection._rid) >= 0);
}

View File

@@ -49,12 +49,12 @@ export default class Database implements ViewModels.Database {
dataExplorerArea: Constants.Areas.ResourceTree
});
// create settings tab if not created yet
const openedTabs = this.container.openedTabs();
let settingsTab: ViewModels.Tab = openedTabs
.filter(tab => tab.rid === this.rid)
.filter(tab => tab.tabKind === ViewModels.CollectionTabKind.DatabaseSettings)[0];
const pendingNotificationsPromise: Q.Promise<DataModels.Notification> = this._getPendingThroughputSplitNotification();
const matchingTabs: ViewModels.Tab[] = this.container.tabsManager.getTabs(
ViewModels.CollectionTabKind.DatabaseSettings,
(tab: ViewModels.Tab) => tab.rid === this.rid
);
let settingsTab: DatabaseSettingsTab = matchingTabs && (matchingTabs[0] as DatabaseSettingsTab);
if (!settingsTab) {
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseAccountName: this.container.databaseAccount().name,
@@ -78,12 +78,11 @@ export default class Database implements ViewModels.Database {
selfLink: this.self,
isActive: ko.observable(false),
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
openedTabs: this.container.openedTabs()
onUpdateTabsButtons: this.container.onUpdateTabsButtons
});
(settingsTab as ViewModels.DatabaseSettingsTab).pendingNotification(pendingNotification);
this.container.openedTabs.push(settingsTab);
settingsTab.onTabClick(); // Activate
settingsTab.pendingNotification(pendingNotification);
this.container.tabsManager.activateNewTab(settingsTab);
},
(error: any) => {
TelemetryProcessor.traceFailure(
@@ -109,12 +108,12 @@ export default class Database implements ViewModels.Database {
} else {
pendingNotificationsPromise.then(
(pendingNotification: DataModels.Notification) => {
(settingsTab as ViewModels.DatabaseSettingsTab).pendingNotification(pendingNotification);
settingsTab.onTabClick();
settingsTab.pendingNotification(pendingNotification);
this.container.tabsManager.activateTab(settingsTab);
},
(error: any) => {
(settingsTab as ViewModels.DatabaseSettingsTab).pendingNotification(undefined);
settingsTab.onTabClick();
settingsTab.pendingNotification(undefined);
this.container.tabsManager.activateTab(settingsTab);
}
);
}
@@ -221,7 +220,9 @@ export default class Database implements ViewModels.Database {
this.expandDatabase();
}
this.container.onUpdateTabsButtons([]);
this.refreshTabSelectedState();
this.container.tabsManager.refreshActiveTab(
(tab: ViewModels.Tab) => tab.collection && tab.collection.getDatabase().rid === this.rid
);
}
public expandDatabase() {
@@ -286,18 +287,6 @@ export default class Database implements ViewModels.Database {
database.container.addCollectionPane.open();
}
public refreshTabSelectedState(): void {
const openedRelevantTabs: ViewModels.Tab[] = this.container
.openedTabs()
.filter((tab: ViewModels.Tab) => tab && tab.collection && tab.collection.getDatabase().rid === this.rid);
openedRelevantTabs.forEach((tab: ViewModels.Tab) => {
if (tab.isActive()) {
tab.onTabClick(); // this ensures the next (deepest) item in the resource tree is highlighted
}
});
}
public findCollectionWithId(collectionId: string): ViewModels.Collection {
return _.find(this.collections(), (collection: ViewModels.Collection) => collection.id() === collectionId);
}

View File

@@ -73,24 +73,9 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
});
}
public refreshActiveTab(): void {
// ensures that the tab selects/highlights the right node based on resource tree expand/collapse state
const openedRelevantTabs: ViewModels.Tab[] = this.container
.openedTabs()
.filter((tab: ViewModels.Tab) => tab && tab.collection && tab.collection.rid === this.rid);
openedRelevantTabs.forEach((tab: ViewModels.Tab) => {
if (tab.isActive()) {
tab.onActivate();
}
});
}
public onNewQueryClick(source: any, event: MouseEvent, queryText?: string) {
const collection: ViewModels.Collection = source.collection || source;
const explorer: ViewModels.Explorer = source.container;
const openedTabs = explorer.openedTabs();
const id = openedTabs.filter(t => t.tabKind === ViewModels.CollectionTabKind.Query).length + 1;
const id = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Query).length + 1;
const title = "Query " + id;
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseAccountName: this.container.databaseAccount().name,
@@ -101,7 +86,7 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
tabTitle: title
});
let queryTab: ViewModels.Tab = new QueryTab({
const queryTab: QueryTab = new QueryTab({
tabKind: ViewModels.CollectionTabKind.Query,
title: title,
tabPath: "",
@@ -115,13 +100,10 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
partitionKey: collection.partitionKey,
resourceTokenPartitionKey: this.container.resourceTokenPartitionKey(),
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
openedTabs: this.container.openedTabs()
onUpdateTabsButtons: this.container.onUpdateTabsButtons
});
this.container.openedTabs.push(queryTab);
// Activate
queryTab.onTabClick();
this.container.tabsManager.activateNewTab(queryTab);
}
public onDocumentDBDocumentsClick() {
@@ -136,14 +118,15 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
dataExplorerArea: Constants.Areas.ResourceTree
});
// create documents tab if not created yet
const openedTabs = this.container.openedTabs();
const documentsTabs: DocumentsTab[] = this.container.tabsManager.getTabs(
ViewModels.CollectionTabKind.Documents,
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === this.rid
) as DocumentsTab[];
let documentsTab: DocumentsTab = documentsTabs && documentsTabs[0];
let documentsTab: ViewModels.Tab = openedTabs
.filter(tab => tab.collection && tab.collection.rid === this.rid)
.filter(tab => tab.tabKind === ViewModels.CollectionTabKind.Documents)[0];
if (!documentsTab) {
if (documentsTab) {
this.container.tabsManager.activateTab(documentsTab);
} else {
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseAccountName: this.container.databaseAccount() && this.container.databaseAccount().name,
databaseName: this.databaseId,
@@ -152,6 +135,7 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
dataExplorerArea: Constants.Areas.Tab,
tabTitle: "Items"
});
documentsTab = new DocumentsTab({
partitionKey: this.partitionKey,
resourceTokenPartitionKey: this.container.resourceTokenPartitionKey(),
@@ -167,14 +151,11 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
tabPath: `${this.databaseId}>${this.id()}>Documents`,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/documents`,
onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons,
openedTabs: this.container.openedTabs()
onUpdateTabsButtons: this.container.onUpdateTabsButtons
});
this.container.openedTabs.push(documentsTab);
}
// Activate
documentsTab.onTabClick();
this.container.tabsManager.activateNewTab(documentsTab);
}
}
public getDatabase(): ViewModels.Database {

Some files were not shown because too many files have changed in this diff Show More