Compare commits

...

20 Commits

Author SHA1 Message Date
Tanuj Mittal
b1e20796c2 Updates to standalone gallery (#91)
* Add hyperlink icon for links in DialogComponent

* Use a Cosmos DB image for sample Persona

* Fix tests

* Update standalone gallery behavior to match DE gallery

* Use /gallery endpoint for gallery

* Update Gallery Card style to remove buttons in standalone mode

* Address comments

* Add text banner
2020-07-15 13:58:43 -07:00
Srinath Narayanan
543ae9fe4a Added infinite progress bar when gallery read-only notebook viewer loads (#90)
* Added infinite progress bar when gallery notebook content loads

- reused infinite progress bar from magic commands

* made single quotes to double quotes

* formatting changes

* updated state

* changed to ProgressIndicator

* undo packgae.json change
2020-07-15 09:53:06 -07:00
Tanuj Mittal
db0b478eb0 Fix gallery card list bug (#87) 2020-07-15 09:27:27 -07:00
Steve Faulkner
f6938f5ec5 Add deployment status CLI utility (#86) 2020-07-15 07:49:06 -05:00
Steve Faulkner
9affc34301 Initial Pass at Accessibility Checks in CI (#88) 2020-07-14 23:01:28 -05:00
victor-meng
15953da51e Fix indexing off not working for SQL and graph free tier accounts (#85) 2020-07-13 10:54:59 -07:00
Steve Faulkner
15f9146ac9 Remove correlation iframe (#83)
Co-authored-by: Steve Faulkner <stfaul@microsoft.com>
2020-07-13 09:41:41 -05:00
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
117 changed files with 4937 additions and 38645 deletions

View File

@@ -147,7 +147,6 @@ src/Explorer/Panes/ExecuteSprocParamsPane.ts
src/Explorer/Panes/GraphStylingPane.ts src/Explorer/Panes/GraphStylingPane.ts
src/Explorer/Panes/LibraryManagePane.ts src/Explorer/Panes/LibraryManagePane.ts
src/Explorer/Panes/LoadQueryPane.ts src/Explorer/Panes/LoadQueryPane.ts
src/Explorer/Panes/ManageSparkClusterPane.ts
src/Explorer/Panes/NewVertexPane.ts src/Explorer/Panes/NewVertexPane.ts
src/Explorer/Panes/PaneComponents.ts src/Explorer/Panes/PaneComponents.ts
src/Explorer/Panes/RenewAdHocAccessPane.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.test.ts
src/Explorer/Panes/SettingsPane.ts src/Explorer/Panes/SettingsPane.ts
src/Explorer/Panes/SetupNotebooksPane.ts src/Explorer/Panes/SetupNotebooksPane.ts
src/Explorer/Panes/SetupSparkClusterPane.ts
src/Explorer/Panes/StringInputPane.ts src/Explorer/Panes/StringInputPane.ts
src/Explorer/Panes/SwitchDirectoryPane.ts src/Explorer/Panes/SwitchDirectoryPane.ts
src/Explorer/Panes/Tables/AddTableEntityPane.ts src/Explorer/Panes/Tables/AddTableEntityPane.ts
@@ -273,7 +271,6 @@ src/Shared/AddCollectionUtility.test.ts
src/Shared/AddCollectionUtility.ts src/Shared/AddCollectionUtility.ts
src/Shared/AddDatabaseUtility.test.ts src/Shared/AddDatabaseUtility.test.ts
src/Shared/AddDatabaseUtility.ts src/Shared/AddDatabaseUtility.ts
src/Shared/Ajax.ts
src/Shared/Constants.ts src/Shared/Constants.ts
src/Shared/DefaultExperienceUtility.test.ts src/Shared/DefaultExperienceUtility.test.ts
src/Shared/DefaultExperienceUtility.ts src/Shared/DefaultExperienceUtility.ts
@@ -411,7 +408,6 @@ src/Explorer/Tabs/NotebookViewerTab.tsx
src/Explorer/Tabs/TerminalTab.tsx src/Explorer/Tabs/TerminalTab.tsx
src/Explorer/Tree/ResourceTreeAdapter.tsx src/Explorer/Tree/ResourceTreeAdapter.tsx
src/Explorer/Tree/ResourceTreeAdapterForResourceToken.tsx src/Explorer/Tree/ResourceTreeAdapterForResourceToken.tsx
src/GalleryViewer/Cards/CardStyleConstants.tsx
src/GalleryViewer/Cards/GalleryCardComponent.tsx src/GalleryViewer/Cards/GalleryCardComponent.tsx
src/GalleryViewer/GalleryViewer.tsx src/GalleryViewer/GalleryViewer.tsx
src/GalleryViewer/GalleryViewerComponent.tsx src/GalleryViewer/GalleryViewerComponent.tsx

View File

@@ -155,8 +155,31 @@ jobs:
NODE_TLS_REJECT_UNAUTHORIZED: 0 NODE_TLS_REJECT_UNAUTHORIZED: 0
CYPRESS_CACHE_FOLDER: ~/.cache/Cypress CYPRESS_CACHE_FOLDER: ~/.cache/Cypress
CYPRESS_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_MONGO }} CYPRESS_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_MONGO }}
accessibility:
name: "Accessibility | Hosted"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js 12.x
uses: actions/setup-node@v1
with:
node-version: 12.x
- name: Accessibility Check
run: |
# Ubuntu gets mad when webpack runs too many files watchers
cat /proc/sys/fs/inotify/max_user_watches
sudo sysctl fs.inotify.max_user_watches=524288
sudo sysctl -p
npm ci
npm start &
npx wait-on -i 5000 https-get://0.0.0.0:1234/
node utils/accesibilityCheck.js
shell: bash
env:
NODE_TLS_REJECT_UNAUTHORIZED: 0
nuget: nuget:
name: Publish Nuget name: Publish Nuget
if: github.ref == 'refs/heads/master'
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendsql, endtoendmongo] needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendsql, endtoendmongo]
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
@@ -180,6 +203,7 @@ jobs:
path: "*.nupkg" path: "*.nupkg"
nugetmpac: nugetmpac:
name: Publish Nuget MPAC name: Publish Nuget MPAC
if: github.ref == 'refs/heads/master'
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendsql, endtoendmongo] needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendsql, endtoendmongo]
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:

View File

@@ -1,7 +1,7 @@
name: Runners name: Runners
on: on:
schedule: schedule:
- cron: "*/5 * * * *" - cron: "0 * 1 * *"
jobs: jobs:
sqlcreatecollection: sqlcreatecollection:
runs-on: ubuntu-latest runs-on: ubuntu-latest

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

File diff suppressed because it is too large Load Diff

View File

@@ -2349,6 +2349,12 @@ a:link {
text-decoration: none; text-decoration: none;
} }
.tabsContainer {
height: 100%;
width: 100%;
overflow: hidden;
}
.tabs { .tabs {
position: relative; position: relative;
margin: 15px 0 25px 0; 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 { .infoBoxMessage {
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: normal;
width: 320px; width: 320px;
padding-top: 2px; padding-top: 2px;
color: @BaseHigh; color: @BaseHigh;

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

View File

@@ -374,6 +374,8 @@ export class HttpStatusCodes {
export class Urls { export class Urls {
public static feedbackEmail = "https://aka.ms/cosmosdbfeedback?subject=Cosmos%20DB%20Data%20Explorer%20Feedback"; public static feedbackEmail = "https://aka.ms/cosmosdbfeedback?subject=Cosmos%20DB%20Data%20Explorer%20Feedback";
public static autoscaleMigration = "https://aka.ms/cosmos-autoscale-migration"; 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 { export class HashRoutePrefixes {

View File

@@ -447,7 +447,7 @@ export abstract class DataAccessUtilityBase {
.database(databaseId) .database(databaseId)
.container(collectionId) .container(collectionId)
.read() .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)) .finally(() => this.refreshCachedResources(options))
); );
} }

View File

@@ -553,6 +553,7 @@ export interface GraphParameters extends RpParameters {
pk: string; pk: string;
coll: string; coll: string;
cd: Boolean; cd: Boolean;
indexingPolicy?: IndexingPolicy;
} }
export interface CreationRequest { export interface CreationRequest {
@@ -570,6 +571,7 @@ export interface SqlCollectionParameters extends RpParameters {
coll: string; coll: string;
cd: Boolean; cd: Boolean;
analyticalStorageTtl?: number; analyticalStorageTtl?: number;
indexingPolicy?: IndexingPolicy;
} }
export interface MongoCreationRequest extends CreationRequest { export interface MongoCreationRequest extends CreationRequest {
@@ -588,6 +590,7 @@ export interface GraphCreationRequest extends CreationRequest {
resource: { resource: {
id: string; id: string;
partitionKey: {}; partitionKey: {};
indexingPolicy?: IndexingPolicy;
}; };
options: RpOptions; options: RpOptions;
}; };
@@ -613,6 +616,7 @@ export interface SqlCollectionCreationRequest extends CreationRequest {
id: string; id: string;
partitionKey: {}; partitionKey: {};
analyticalStorageTtl?: number; analyticalStorageTtl?: number;
indexingPolicy?: IndexingPolicy;
}; };
options: RpOptions; options: RpOptions;
}; };

View File

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

View File

@@ -116,14 +116,6 @@ describe("Component Registerer", () => {
expect(ko.components.isRegistered("setup-notebooks-pane")).toBe(true); 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", () => { it("should register dynamic-list component", () => {
expect(ko.components.isRegistered("dynamic-list")).toBe(true); 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 { InputTypeaheadComponent } from "./Controls/InputTypeahead/InputTypeahead";
import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent"; import { JsonEditorComponent } from "./Controls/JsonEditor/JsonEditorComponent";
import { NewVertexComponent } from "./Graph/NewVertexComponent/NewVertexComponent"; import { NewVertexComponent } from "./Graph/NewVertexComponent/NewVertexComponent";
import { TabsManagerKOComponent } from "./Tabs/TabsManager";
import { ThroughputInputComponent } from "./Controls/ThroughputInput/ThroughputInputComponent"; import { ThroughputInputComponent } from "./Controls/ThroughputInput/ThroughputInputComponent";
import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3"; import { ThroughputInputComponentAutoPilotV3 } from "./Controls/ThroughputInput/ThroughputInputComponentAutoPilotV3";
import { ToolbarComponent } from "./Controls/Toolbar/Toolbar"; 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("dynamic-list", DynamicListComponent);
ko.components.register("throughput-input", ThroughputInputComponent); ko.components.register("throughput-input", ThroughputInputComponent);
ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3); ko.components.register("throughput-input-autopilot-v3", ThroughputInputComponentAutoPilotV3);
ko.components.register("tabs-manager", TabsManagerKOComponent());
// Collection Tabs // Collection Tabs
ko.components.register("documents-tab", new TabComponents.DocumentsTab()); 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("upload-file-pane", new PaneComponents.UploadFilePaneComponent());
ko.components.register("string-input-pane", new PaneComponents.StringInputPaneComponent()); ko.components.register("string-input-pane", new PaneComponents.StringInputPaneComponent());
ko.components.register("setup-notebooks-pane", new PaneComponents.SetupNotebooksPaneComponent()); 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("library-manage-pane", new PaneComponents.LibraryManagePaneComponent());
ko.components.register("cluster-library-pane", new PaneComponents.ClusterLibraryPaneComponent()); ko.components.register("cluster-library-pane", new PaneComponents.ClusterLibraryPaneComponent());
ko.components.register("github-repos-pane", new PaneComponents.GitHubReposPaneComponent()); ko.components.register("github-repos-pane", new PaneComponents.GitHubReposPaneComponent());

View File

@@ -3,6 +3,7 @@ import { Dialog, DialogType, DialogFooter, IDialogProps } from "office-ui-fabric
import { IButtonProps, PrimaryButton, DefaultButton } from "office-ui-fabric-react/lib/Button"; import { IButtonProps, PrimaryButton, DefaultButton } from "office-ui-fabric-react/lib/Button";
import { ITextFieldProps, TextField } from "office-ui-fabric-react/lib/TextField"; import { ITextFieldProps, TextField } from "office-ui-fabric-react/lib/TextField";
import { Link } from "office-ui-fabric-react/lib/Link"; import { Link } from "office-ui-fabric-react/lib/Link";
import { FontIcon } from "office-ui-fabric-react";
export interface TextFieldProps extends ITextFieldProps { export interface TextFieldProps extends ITextFieldProps {
label: string; label: string;
@@ -31,6 +32,8 @@ export interface DialogProps {
onSecondaryButtonClick: () => void; onSecondaryButtonClick: () => void;
primaryButtonDisabled?: boolean; primaryButtonDisabled?: boolean;
type?: DialogType; type?: DialogType;
showCloseButton?: boolean;
onDismiss?: () => void;
} }
const DIALOG_MIN_WIDTH = "400px"; const DIALOG_MIN_WIDTH = "400px";
@@ -55,7 +58,8 @@ export class DialogComponent extends React.Component<DialogProps, {}> {
title: { fontSize: DIALOG_TITLE_FONT_SIZE, fontWeight: DIALOG_TITLE_FONT_WEIGHT }, title: { fontSize: DIALOG_TITLE_FONT_SIZE, fontWeight: DIALOG_TITLE_FONT_WEIGHT },
subText: { fontSize: DIALOG_SUBTEXT_FONT_SIZE } subText: { fontSize: DIALOG_SUBTEXT_FONT_SIZE }
}, },
showCloseButton: false showCloseButton: this.props.showCloseButton || false,
onDismiss: this.props.onDismiss
}, },
modalProps: { isBlocking: this.props.isModal }, modalProps: { isBlocking: this.props.isModal },
minWidth: DIALOG_MIN_WIDTH, minWidth: DIALOG_MIN_WIDTH,
@@ -81,7 +85,7 @@ export class DialogComponent extends React.Component<DialogProps, {}> {
{textFieldProps && <TextField {...textFieldProps} />} {textFieldProps && <TextField {...textFieldProps} />}
{linkProps && ( {linkProps && (
<Link href={linkProps.linkUrl} target="_blank"> <Link href={linkProps.linkUrl} target="_blank">
{linkProps.linkText} {linkProps.linkText} <FontIcon iconName="NavigateExternalInline" />
</Link> </Link>
)} )}
<DialogFooter> <DialogFooter>

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)", "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)", "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", "roundedCorner2": "2px",
"roundedCorner4": "4px",
"roundedCorner6": "6px",
}, },
"fonts": Object { "fonts": Object {
"large": 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)", "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)", "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", "roundedCorner2": "2px",
"roundedCorner4": "4px",
"roundedCorner6": "6px",
}, },
"fonts": Object { "fonts": Object {
"large": 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)", "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)", "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", "roundedCorner2": "2px",
"roundedCorner4": "4px",
"roundedCorner6": "6px",
}, },
"fonts": Object { "fonts": Object {
"large": 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)", "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)", "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", "roundedCorner2": "2px",
"roundedCorner4": "4px",
"roundedCorner6": "6px",
}, },
"fonts": Object { "fonts": Object {
"large": Object { "large": Object {

View File

@@ -0,0 +1,81 @@
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 = "Sign 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 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,
undefined,
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

@@ -20,6 +20,7 @@ describe("GalleryCardComponent", () => {
views: 0 views: 0
}, },
isFavorite: false, isFavorite: false,
showDownload: true,
showDelete: true, showDelete: true,
onClick: undefined, onClick: undefined,
onTagClick: undefined, onTagClick: undefined,

View File

@@ -1,4 +1,4 @@
import { Card, ICardTokens } from "@uifabric/react-cards"; import { Card } from "@uifabric/react-cards";
import { import {
FontWeights, FontWeights,
Icon, Icon,
@@ -18,10 +18,12 @@ import * as React from "react";
import { IGalleryItem } from "../../../../Juno/JunoClient"; import { IGalleryItem } from "../../../../Juno/JunoClient";
import { FileSystemUtil } from "../../../Notebook/FileSystemUtil"; import { FileSystemUtil } from "../../../Notebook/FileSystemUtil";
import CosmosDBLogo from "../../../../../images/CosmosDB-logo.svg"; import CosmosDBLogo from "../../../../../images/CosmosDB-logo.svg";
import { StyleConstants } from "../../../../Common/Constants";
export interface GalleryCardComponentProps { export interface GalleryCardComponentProps {
data: IGalleryItem; data: IGalleryItem;
isFavorite: boolean; isFavorite: boolean;
showDownload: boolean;
showDelete: boolean; showDelete: boolean;
onClick: () => void; onClick: () => void;
onTagClick: (tag: string) => void; onTagClick: (tag: string) => void;
@@ -32,30 +34,30 @@ export interface GalleryCardComponentProps {
} }
export class GalleryCardComponent extends React.Component<GalleryCardComponentProps> { export class GalleryCardComponent extends React.Component<GalleryCardComponentProps> {
public static readonly CARD_HEIGHT = 384;
public static readonly CARD_WIDTH = 256; public static readonly CARD_WIDTH = 256;
private static readonly cardImageHeight = 144; private static readonly cardImageHeight = 144;
private static readonly cardDescriptionMaxChars = 88; private static readonly cardDescriptionMaxChars = 88;
private static readonly cardTokens: ICardTokens = { private static readonly cardItemGapBig = 10;
width: GalleryCardComponent.CARD_WIDTH, private static readonly cardItemGapSmall = 8;
height: GalleryCardComponent.CARD_HEIGHT,
childrenGap: 8,
childrenMargin: 10
};
public render(): JSX.Element { public render(): JSX.Element {
const cardButtonsVisible = this.props.isFavorite !== undefined || this.props.showDownload || this.props.showDelete;
const options: Intl.DateTimeFormatOptions = { const options: Intl.DateTimeFormatOptions = {
year: "numeric", year: "numeric",
month: "short", month: "short",
day: "numeric" day: "numeric"
}; };
const dateString = new Date(this.props.data.created).toLocaleString("default", options); const dateString = new Date(this.props.data.created).toLocaleString("default", options);
const cardTitle = FileSystemUtil.stripExtension(this.props.data.name, "ipynb");
return ( return (
<Card aria-label="Notebook Card" tokens={GalleryCardComponent.cardTokens} onClick={this.props.onClick}> <Card
<Card.Item> aria-label={cardTitle}
data-is-focusable="true"
tokens={{ width: GalleryCardComponent.CARD_WIDTH, childrenGap: 0 }}
onClick={event => this.onClick(event, this.props.onClick)}
>
<Card.Item tokens={{ padding: GalleryCardComponent.cardItemGapBig }}>
<Persona <Persona
imageUrl={this.props.data.isSample && CosmosDBLogo} imageUrl={this.props.data.isSample && CosmosDBLogo}
text={this.props.data.author} text={this.props.data.author}
@@ -63,69 +65,89 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
/> />
</Card.Item> </Card.Item>
<Card.Item fill> <Card.Item>
<Image <Image
src={ src={this.props.data.thumbnailUrl}
this.props.data.thumbnailUrl ||
`https://placehold.it/${GalleryCardComponent.CARD_WIDTH}x${GalleryCardComponent.cardImageHeight}`
}
width={GalleryCardComponent.CARD_WIDTH} width={GalleryCardComponent.CARD_WIDTH}
height={GalleryCardComponent.cardImageHeight} height={GalleryCardComponent.cardImageHeight}
imageFit={ImageFit.cover} imageFit={ImageFit.cover}
alt="Notebook cover image" alt={`${cardTitle} cover image`}
/> />
</Card.Item> </Card.Item>
<Card.Section> <Card.Section styles={{ root: { padding: GalleryCardComponent.cardItemGapBig } }}>
<Text variant="small" nowrap> <Text variant="small" nowrap>
{this.props.data.tags?.map((tag, index, array) => ( {this.props.data.tags?.map((tag, index, array) => (
<span key={tag}> <span key={tag}>
<Link onClick={(event): void => this.onTagClick(event, tag)}>{tag}</Link> <Link onClick={event => this.onClick(event, () => this.props.onTagClick(tag))}>{tag}</Link>
{index === array.length - 1 ? <></> : ", "} {index === array.length - 1 ? <></> : ", "}
</span> </span>
))} ))}
</Text> </Text>
<Text styles={{ root: { fontWeight: FontWeights.semibold } }} nowrap>
{FileSystemUtil.stripExtension(this.props.data.name, "ipynb")} <Text
styles={{
root: {
fontWeight: FontWeights.semibold,
paddingTop: GalleryCardComponent.cardItemGapSmall,
paddingBottom: GalleryCardComponent.cardItemGapSmall
}
}}
nowrap
>
{cardTitle}
</Text> </Text>
<Text variant="small" styles={{ root: { height: 36 } }}> <Text variant="small" styles={{ root: { height: 36 } }}>
{this.props.data.description.substr(0, GalleryCardComponent.cardDescriptionMaxChars)} {this.props.data.description.substr(0, GalleryCardComponent.cardDescriptionMaxChars)}
</Text> </Text>
<span>
{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())}
</span>
</Card.Section> </Card.Section>
<Card.Section horizontal styles={{ root: { alignItems: "flex-end" } }}> {cardButtonsVisible && (
{this.generateIconText("RedEye", this.props.data.views.toString())} <Card.Section
{this.generateIconText("Download", this.props.data.downloads.toString())} styles={{
{this.props.isFavorite !== undefined && this.generateIconText("Heart", this.props.data.favorites.toString())} root: {
</Card.Section> marginLeft: GalleryCardComponent.cardItemGapBig,
marginRight: GalleryCardComponent.cardItemGapBig
}
}}
>
<Separator styles={{ root: { padding: 0, height: 1 } }} />
<Card.Item> <span>
<Separator styles={{ root: { padding: 0, height: 1 } }} /> {this.props.isFavorite !== undefined &&
</Card.Item> this.generateIconButtonWithTooltip(
this.props.isFavorite ? "HeartFill" : "Heart",
this.props.isFavorite ? "Unlike" : "Like",
"left",
this.props.isFavorite ? this.props.onUnfavoriteClick : this.props.onFavoriteClick
)}
<Card.Section horizontal styles={{ root: { marginTop: 0 } }}> {this.props.showDownload &&
{this.props.isFavorite !== undefined && this.generateIconButtonWithTooltip("Download", "Download", "left", this.props.onDownloadClick)}
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 &&
this.generateIconButtonWithTooltip("Delete", "Remove", "right", this.props.onDeleteClick)}
{this.props.showDelete && ( </span>
<div style={{ width: "100%", textAlign: "right" }}> </Card.Section>
{this.generateIconButtonWithTooltip("Delete", "Remove", this.onDeleteClick)} )}
</div>
)}
</Card.Section>
</Card> </Card>
); );
} }
private generateIconText = (iconName: string, text: string): JSX.Element => { private generateIconText = (iconName: string, text: string): JSX.Element => {
return ( return (
<Text variant="tiny" styles={{ root: { color: "#ccc" } }}> <Text
variant="tiny"
styles={{ root: { color: StyleConstants.BaseMedium, paddingRight: GalleryCardComponent.cardItemGapSmall } }}
>
<Icon iconName={iconName} styles={{ root: { verticalAlign: "middle" } }} /> {text} <Icon iconName={iconName} styles={{ root: { verticalAlign: "middle" } }} /> {text}
</Text> </Text>
); );
@@ -138,70 +160,37 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
private generateIconButtonWithTooltip = ( private generateIconButtonWithTooltip = (
iconName: string, iconName: string,
title: string, title: string,
onClick: ( horizontalAlign: "right" | "left",
event: React.MouseEvent< activate: () => void
HTMLAnchorElement | HTMLButtonElement | HTMLDivElement | BaseButton | Button | HTMLSpanElement,
MouseEvent
>
) => void
): JSX.Element => { ): JSX.Element => {
return ( return (
<TooltipHost <TooltipHost
content={title} content={title}
id={`TooltipHost-IconButton-${iconName}`} id={`TooltipHost-IconButton-${iconName}`}
calloutProps={{ gapSpace: 0 }} calloutProps={{ gapSpace: 0 }}
styles={{ root: { display: "inline-block" } }} styles={{ root: { display: "inline-block", float: horizontalAlign } }}
> >
<IconButton iconProps={{ iconName }} title={title} ariaLabel={title} onClick={onClick} /> <IconButton
iconProps={{ iconName }}
title={title}
ariaLabel={title}
onClick={event => this.onClick(event, activate)}
/>
</TooltipHost> </TooltipHost>
); );
}; };
private onTagClick = ( private onClick = (
event: React.MouseEvent<HTMLElement | HTMLAnchorElement | HTMLButtonElement | LinkBase, MouseEvent>, event:
tag: string | React.MouseEvent<HTMLElement | HTMLAnchorElement | HTMLButtonElement | LinkBase, MouseEvent>
| React.MouseEvent<
HTMLAnchorElement | HTMLButtonElement | HTMLDivElement | BaseButton | Button | HTMLSpanElement,
MouseEvent
>,
activate: () => void
): void => { ): void => {
event.stopPropagation(); event.stopPropagation();
this.props.onTagClick(tag); event.preventDefault();
}; activate();
private onFavoriteClick = (
event: React.MouseEvent<
HTMLAnchorElement | HTMLButtonElement | HTMLDivElement | BaseButton | Button | HTMLSpanElement,
MouseEvent
>
): void => {
event.stopPropagation();
this.props.onFavoriteClick();
};
private onUnfavoriteClick = (
event: React.MouseEvent<
HTMLAnchorElement | HTMLButtonElement | HTMLDivElement | BaseButton | Button | HTMLSpanElement,
MouseEvent
>
): void => {
event.stopPropagation();
this.props.onUnfavoriteClick();
};
private onDownloadClick = (
event: React.MouseEvent<
HTMLAnchorElement | HTMLButtonElement | HTMLDivElement | BaseButton | Button | HTMLSpanElement,
MouseEvent
>
): void => {
event.stopPropagation();
this.props.onDownloadClick();
};
private onDeleteClick = (
event: React.MouseEvent<
HTMLAnchorElement | HTMLButtonElement | HTMLDivElement | BaseButton | Button | HTMLSpanElement,
MouseEvent
>
): void => {
event.stopPropagation();
this.props.onDeleteClick();
}; };
} }

View File

@@ -2,35 +2,47 @@
exports[`GalleryCardComponent renders 1`] = ` exports[`GalleryCardComponent renders 1`] = `
<Card <Card
aria-label="Notebook Card" aria-label="name"
data-is-focusable="true"
onClick={[Function]}
tokens={ tokens={
Object { Object {
"childrenGap": 8, "childrenGap": 0,
"childrenMargin": 10,
"height": 384,
"width": 256, "width": 256,
} }
} }
> >
<CardItem> <CardItem
tokens={
Object {
"padding": 10,
}
}
>
<StyledPersonaBase <StyledPersonaBase
imageUrl={false} imageUrl={false}
secondaryText="Invalid Date" secondaryText="Invalid Date"
text="author" text="author"
/> />
</CardItem> </CardItem>
<CardItem <CardItem>
fill={true} <Memo(StyledImageBase)
> alt="name cover image"
<StyledImageBase
alt="Notebook cover image"
height={144} height={144}
imageFit={2} imageFit={2}
src="thumbnailUrl" src="thumbnailUrl"
width={256} width={256}
/> />
</CardItem> </CardItem>
<CardSection> <CardSection
styles={
Object {
"root": Object {
"padding": 10,
},
}
}
>
<Text <Text
nowrap={true} nowrap={true}
variant="small" variant="small"
@@ -51,6 +63,8 @@ exports[`GalleryCardComponent renders 1`] = `
Object { Object {
"root": Object { "root": Object {
"fontWeight": 600, "fontWeight": 600,
"paddingBottom": 8,
"paddingTop": 8,
}, },
} }
} }
@@ -69,88 +83,91 @@ exports[`GalleryCardComponent renders 1`] = `
> >
description description
</Text> </Text>
<span>
<Text
styles={
Object {
"root": Object {
"color": undefined,
"paddingRight": 8,
},
}
}
variant="tiny"
>
<Memo(StyledIconBase)
iconName="RedEye"
styles={
Object {
"root": Object {
"verticalAlign": "middle",
},
}
}
/>
0
</Text>
<Text
styles={
Object {
"root": Object {
"color": undefined,
"paddingRight": 8,
},
}
}
variant="tiny"
>
<Memo(StyledIconBase)
iconName="Download"
styles={
Object {
"root": Object {
"verticalAlign": "middle",
},
}
}
/>
0
</Text>
<Text
styles={
Object {
"root": Object {
"color": undefined,
"paddingRight": 8,
},
}
}
variant="tiny"
>
<Memo(StyledIconBase)
iconName="Heart"
styles={
Object {
"root": Object {
"verticalAlign": "middle",
},
}
}
/>
0
</Text>
</span>
</CardSection> </CardSection>
<CardSection <CardSection
horizontal={true}
styles={ styles={
Object { Object {
"root": Object { "root": Object {
"alignItems": "flex-end", "marginLeft": 10,
"marginRight": 10,
}, },
} }
} }
> >
<Text
styles={
Object {
"root": Object {
"color": "#ccc",
},
}
}
variant="tiny"
>
<StyledIconBase
iconName="RedEye"
styles={
Object {
"root": Object {
"verticalAlign": "middle",
},
}
}
/>
0
</Text>
<Text
styles={
Object {
"root": Object {
"color": "#ccc",
},
}
}
variant="tiny"
>
<StyledIconBase
iconName="Download"
styles={
Object {
"root": Object {
"verticalAlign": "middle",
},
}
}
/>
0
</Text>
<Text
styles={
Object {
"root": Object {
"color": "#ccc",
},
}
}
variant="tiny"
>
<StyledIconBase
iconName="Heart"
styles={
Object {
"root": Object {
"verticalAlign": "middle",
},
}
}
/>
0
</Text>
</CardSection>
<CardItem>
<Styled <Styled
styles={ styles={
Object { Object {
@@ -161,79 +178,63 @@ exports[`GalleryCardComponent renders 1`] = `
} }
} }
/> />
</CardItem> <span>
<CardSection <StyledTooltipHostBase
horizontal={true} calloutProps={
styles={
Object {
"root": Object {
"marginTop": 0,
},
}
}
>
<StyledTooltipHostBase
calloutProps={
Object {
"gapSpace": 0,
}
}
content="Like"
id="TooltipHost-IconButton-Heart"
styles={
Object {
"root": Object {
"display": "inline-block",
},
}
}
>
<CustomizedIconButton
ariaLabel="Like"
iconProps={
Object { Object {
"iconName": "Heart", "gapSpace": 0,
} }
} }
onClick={[Function]} content="Like"
title="Like" id="TooltipHost-IconButton-Heart"
/> styles={
</StyledTooltipHostBase>
<StyledTooltipHostBase
calloutProps={
Object {
"gapSpace": 0,
}
}
content="Download"
id="TooltipHost-IconButton-Download"
styles={
Object {
"root": Object {
"display": "inline-block",
},
}
}
>
<CustomizedIconButton
ariaLabel="Download"
iconProps={
Object { Object {
"iconName": "Download", "root": Object {
"display": "inline-block",
"float": "left",
},
} }
} }
onClick={[Function]} >
title="Download" <CustomizedIconButton
/> ariaLabel="Like"
</StyledTooltipHostBase> iconProps={
<div Object {
style={ "iconName": "Heart",
Object { }
"textAlign": "right", }
"width": "100%", onClick={[Function]}
title="Like"
/>
</StyledTooltipHostBase>
<StyledTooltipHostBase
calloutProps={
Object {
"gapSpace": 0,
}
} }
} content="Download"
> id="TooltipHost-IconButton-Download"
styles={
Object {
"root": Object {
"display": "inline-block",
"float": "left",
},
}
}
>
<CustomizedIconButton
ariaLabel="Download"
iconProps={
Object {
"iconName": "Download",
}
}
onClick={[Function]}
title="Download"
/>
</StyledTooltipHostBase>
<StyledTooltipHostBase <StyledTooltipHostBase
calloutProps={ calloutProps={
Object { Object {
@@ -246,6 +247,7 @@ exports[`GalleryCardComponent renders 1`] = `
Object { Object {
"root": Object { "root": Object {
"display": "inline-block", "display": "inline-block",
"float": "right",
}, },
} }
} }
@@ -261,7 +263,7 @@ exports[`GalleryCardComponent renders 1`] = `
title="Remove" title="Remove"
/> />
</StyledTooltipHostBase> </StyledTooltipHostBase>
</div> </span>
</CardSection> </CardSection>
</Card> </Card>
`; `;

View File

@@ -0,0 +1,114 @@
import * as React from "react";
import * as ViewModels from "../../../Contracts/ViewModels";
import { JunoClient, IGalleryItem } from "../../../Juno/JunoClient";
import { GalleryTab, SortBy, GalleryViewerComponentProps, GalleryViewerComponent } from "./GalleryViewerComponent";
import { NotebookViewerComponentProps, NotebookViewerComponent } from "../NotebookViewer/NotebookViewerComponent";
import * as GalleryUtils from "../../../Utils/GalleryUtils";
export interface GalleryAndNotebookViewerComponentProps {
container?: ViewModels.Explorer;
junoClient: JunoClient;
notebookUrl?: string;
galleryItem?: IGalleryItem;
isFavorite?: boolean;
selectedTab: GalleryTab;
sortBy: SortBy;
searchText: string;
}
interface GalleryAndNotebookViewerComponentState {
notebookUrl: string;
galleryItem: IGalleryItem;
isFavorite: boolean;
selectedTab: GalleryTab;
sortBy: SortBy;
searchText: string;
}
export class GalleryAndNotebookViewerComponent extends React.Component<
GalleryAndNotebookViewerComponentProps,
GalleryAndNotebookViewerComponentState
> {
constructor(props: GalleryAndNotebookViewerComponentProps) {
super(props);
this.state = {
notebookUrl: props.notebookUrl,
galleryItem: props.galleryItem,
isFavorite: props.isFavorite,
selectedTab: props.selectedTab,
sortBy: props.sortBy,
searchText: props.searchText
};
}
public render(): JSX.Element {
if (this.state.notebookUrl) {
const props: NotebookViewerComponentProps = {
container: this.props.container,
junoClient: this.props.junoClient,
notebookUrl: this.state.notebookUrl,
galleryItem: this.state.galleryItem,
isFavorite: this.state.isFavorite,
backNavigationText: GalleryUtils.getTabTitle(this.state.selectedTab),
onBackClick: this.onBackClick,
onTagClick: this.loadTaggedItems
};
return <NotebookViewerComponent {...props} />;
}
const props: GalleryViewerComponentProps = {
container: this.props.container,
junoClient: this.props.junoClient,
selectedTab: this.state.selectedTab,
sortBy: this.state.sortBy,
searchText: this.state.searchText,
openNotebook: this.openNotebook,
onSelectedTabChange: this.onSelectedTabChange,
onSortByChange: this.onSortByChange,
onSearchTextChange: this.onSearchTextChange
};
return <GalleryViewerComponent {...props} />;
}
private onBackClick = (): void => {
this.setState({
notebookUrl: undefined
});
};
private loadTaggedItems = (tag: string): void => {
this.setState({
notebookUrl: undefined,
searchText: tag
});
};
private openNotebook = (data: IGalleryItem, isFavorite: boolean): void => {
this.setState({
notebookUrl: this.props.junoClient.getNotebookContentUrl(data.id),
galleryItem: data,
isFavorite
});
};
private onSelectedTabChange = (selectedTab: GalleryTab): void => {
this.setState({
selectedTab
});
};
private onSortByChange = (sortBy: SortBy): void => {
this.setState({
sortBy
});
};
private onSearchTextChange = (searchText: string): void => {
this.setState({
searchText
});
};
}

View File

@@ -0,0 +1,23 @@
import ko from "knockout";
import * as React from "react";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import {
GalleryAndNotebookViewerComponentProps,
GalleryAndNotebookViewerComponent
} from "./GalleryAndNotebookViewerComponent";
export class GalleryAndNotebookViewerComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<number>;
constructor(private props: GalleryAndNotebookViewerComponentProps) {
this.parameters = ko.observable<number>(Date.now());
}
public renderComponent(): JSX.Element {
return <GalleryAndNotebookViewerComponent {...this.props} />;
}
public triggerRender(): void {
window.requestAnimationFrame(() => this.parameters(Date.now()));
}
}

View File

@@ -9,6 +9,7 @@ describe("GalleryViewerComponent", () => {
selectedTab: GalleryTab.OfficialSamples, selectedTab: GalleryTab.OfficialSamples,
sortBy: SortBy.MostViewed, sortBy: SortBy.MostViewed,
searchText: undefined, searchText: undefined,
openNotebook: undefined,
onSelectedTabChange: undefined, onSelectedTabChange: undefined,
onSortByChange: undefined, onSortByChange: undefined,
onSearchTextChange: undefined onSearchTextChange: undefined

View File

@@ -31,6 +31,7 @@ export interface GalleryViewerComponentProps {
selectedTab: GalleryTab; selectedTab: GalleryTab;
sortBy: SortBy; sortBy: SortBy;
searchText: string; searchText: string;
openNotebook: (data: IGalleryItem, isFavorite: boolean) => void;
onSelectedTabChange: (newTab: GalleryTab) => void; onSelectedTabChange: (newTab: GalleryTab) => void;
onSortByChange: (sortBy: SortBy) => void; onSortByChange: (sortBy: SortBy) => void;
onSearchTextChange: (searchText: string) => void; onSearchTextChange: (searchText: string) => void;
@@ -66,13 +67,14 @@ interface GalleryTabInfo {
content: JSX.Element; content: JSX.Element;
} }
export class GalleryViewerComponent extends React.Component<GalleryViewerComponentProps, GalleryViewerComponentState> export class GalleryViewerComponent extends React.Component<GalleryViewerComponentProps, GalleryViewerComponentState> {
implements GalleryUtils.DialogEnabledComponent {
public static readonly OfficialSamplesTitle = "Official samples"; public static readonly OfficialSamplesTitle = "Official samples";
public static readonly PublicGalleryTitle = "Public gallery"; public static readonly PublicGalleryTitle = "Public gallery";
public static readonly FavoritesTitle = "Liked"; public static readonly FavoritesTitle = "Liked";
public static readonly PublishedTitle = "Your published work"; public static readonly PublishedTitle = "Your published work";
private static readonly rowsPerPage = 5;
private static readonly mostViewedText = "Most viewed"; private static readonly mostViewedText = "Most viewed";
private static readonly mostDownloadedText = "Most downloaded"; private static readonly mostDownloadedText = "Most downloaded";
private static readonly mostFavoritedText = "Most liked"; private static readonly mostFavoritedText = "Most liked";
@@ -128,10 +130,6 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
} }
} }
setDialogProps = (dialogProps: DialogProps): void => {
this.setState({ dialogProps });
};
public render(): JSX.Element { public render(): JSX.Element {
const tabs: GalleryTabInfo[] = [this.createTab(GalleryTab.OfficialSamples, this.state.sampleNotebooks)]; const tabs: GalleryTabInfo[] = [this.createTab(GalleryTab.OfficialSamples, this.state.sampleNotebooks)];
@@ -178,8 +176,8 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
private createTabContent(data: IGalleryItem[]): JSX.Element { private createTabContent(data: IGalleryItem[]): JSX.Element {
return ( return (
<Stack tokens={{ childrenGap: 20 }}> <Stack tokens={{ childrenGap: 10 }}>
<Stack horizontal tokens={{ childrenGap: 20 }}> <Stack horizontal tokens={{ childrenGap: 20, padding: 10 }}>
<Stack.Item grow> <Stack.Item grow>
<SearchBox value={this.state.searchText} placeholder="Search" onChange={this.onSearchBoxChange} /> <SearchBox value={this.state.searchText} placeholder="Search" onChange={this.onSearchBoxChange} />
</Stack.Item> </Stack.Item>
@@ -389,8 +387,10 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
} }
private getPageSpecification = (itemIndex?: number, visibleRect?: IRectangle): IPageSpecification => { private getPageSpecification = (itemIndex?: number, visibleRect?: IRectangle): IPageSpecification => {
this.columnCount = Math.floor(visibleRect.width / GalleryCardComponent.CARD_WIDTH); if (itemIndex === 0) {
this.rowCount = Math.floor(visibleRect.height / GalleryCardComponent.CARD_HEIGHT); this.columnCount = Math.floor(visibleRect.width / GalleryCardComponent.CARD_WIDTH) || this.columnCount;
this.rowCount = GalleryViewerComponent.rowsPerPage;
}
return { return {
height: visibleRect.height, height: visibleRect.height,
@@ -406,8 +406,9 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
const props: GalleryCardComponentProps = { const props: GalleryCardComponentProps = {
data, data,
isFavorite, isFavorite,
showDownload: !!this.props.container,
showDelete: this.state.selectedTab === GalleryTab.Published, showDelete: this.state.selectedTab === GalleryTab.Published,
onClick: () => this.openNotebook(data, isFavorite), onClick: () => this.props.openNotebook(data, isFavorite),
onTagClick: this.loadTaggedItems, onTagClick: this.loadTaggedItems,
onFavoriteClick: () => this.favoriteItem(data), onFavoriteClick: () => this.favoriteItem(data),
onUnfavoriteClick: () => this.unfavoriteItem(data), onUnfavoriteClick: () => this.unfavoriteItem(data),
@@ -422,20 +423,6 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
); );
}; };
private openNotebook = (data: IGalleryItem, isFavorite: boolean): void => {
if (this.props.container && this.props.junoClient) {
this.props.container.openGallery(this.props.junoClient.getNotebookContentUrl(data.id), data, isFavorite);
} else {
const params = new URLSearchParams({
[GalleryUtils.NotebookViewerParams.NotebookUrl]: this.props.junoClient.getNotebookContentUrl(data.id),
[GalleryUtils.NotebookViewerParams.GalleryItemId]: data.id
});
const location = new URL("./notebookViewer.html", window.location.href).href;
window.open(`${location}?${params.toString()}`);
}
};
private loadTaggedItems = (tag: string): void => { private loadTaggedItems = (tag: string): void => {
const searchText = tag; const searchText = tag;
this.setState({ this.setState({
@@ -465,9 +452,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
}; };
private downloadItem = async (data: IGalleryItem): Promise<void> => { private downloadItem = async (data: IGalleryItem): Promise<void> => {
GalleryUtils.downloadItem(this, this.props.container, this.props.junoClient, data, item => GalleryUtils.downloadItem(this.props.container, this.props.junoClient, data, item => this.refreshSelectedTab(item));
this.refreshSelectedTab(item)
);
}; };
private deleteItem = async (data: IGalleryItem): Promise<void> => { private deleteItem = async (data: IGalleryItem): Promise<void> => {

View File

@@ -21,7 +21,7 @@ exports[`GalleryViewerComponent renders 1`] = `
<Stack <Stack
tokens={ tokens={
Object { Object {
"childrenGap": 20, "childrenGap": 10,
} }
} }
> >
@@ -30,6 +30,7 @@ exports[`GalleryViewerComponent renders 1`] = `
tokens={ tokens={
Object { Object {
"childrenGap": 20, "childrenGap": 20,
"padding": 10,
} }
} }
> >

View File

@@ -16,11 +16,12 @@ import * as React from "react";
import { IGalleryItem } from "../../../Juno/JunoClient"; import { IGalleryItem } from "../../../Juno/JunoClient";
import { FileSystemUtil } from "../../Notebook/FileSystemUtil"; import { FileSystemUtil } from "../../Notebook/FileSystemUtil";
import "./NotebookViewerComponent.less"; import "./NotebookViewerComponent.less";
import CosmosDBLogo from "../../../../images/CosmosDB-logo.svg";
export interface NotebookMetadataComponentProps { export interface NotebookMetadataComponentProps {
data: IGalleryItem; data: IGalleryItem;
isFavorite: boolean; isFavorite: boolean;
downloadButtonText: string; downloadButtonText?: string;
onTagClick: (tag: string) => void; onTagClick: (tag: string) => void;
onFavoriteClick: () => void; onFavoriteClick: () => void;
onUnfavoriteClick: () => void; onUnfavoriteClick: () => void;
@@ -54,11 +55,18 @@ export class NotebookMetadataComponent extends React.Component<NotebookMetadataC
</> </>
)} )}
</Text> </Text>
<PrimaryButton text={this.props.downloadButtonText} onClick={this.props.onDownloadClick} />
{this.props.downloadButtonText && (
<PrimaryButton text={this.props.downloadButtonText} onClick={this.props.onDownloadClick} />
)}
</Stack> </Stack>
<Stack horizontal verticalAlign="center" tokens={{ childrenGap: 10 }}> <Stack horizontal verticalAlign="center" tokens={{ childrenGap: 10 }}>
<Persona text={this.props.data.author} size={PersonaSize.size32} /> <Persona
imageUrl={this.props.data.isSample && CosmosDBLogo}
text={this.props.data.author}
size={PersonaSize.size32}
/>
<Text>{dateString}</Text> <Text>{dateString}</Text>
<Text> <Text>
<Icon iconName="RedEye" /> {this.props.data.views} <Icon iconName="RedEye" /> {this.props.data.views}

View File

@@ -6,21 +6,3 @@
width: 100%; width: 100%;
overflow-y: auto; 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

@@ -3,7 +3,7 @@
*/ */
import { Notebook } from "@nteract/commutable"; import { Notebook } from "@nteract/commutable";
import { createContentRef } from "@nteract/core"; import { createContentRef } from "@nteract/core";
import { Icon, Link } from "office-ui-fabric-react"; import { Icon, Link, ProgressIndicator } from "office-ui-fabric-react";
import * as React from "react"; import * as React from "react";
import { contents } from "rx-jupyter"; import { contents } from "rx-jupyter";
import * as Logger from "../../../Common/Logger"; import * as Logger from "../../../Common/Logger";
@@ -26,6 +26,7 @@ export interface NotebookViewerComponentProps {
galleryItem?: IGalleryItem; galleryItem?: IGalleryItem;
isFavorite?: boolean; isFavorite?: boolean;
backNavigationText: string; backNavigationText: string;
hideInputs?: boolean;
onBackClick: () => void; onBackClick: () => void;
onTagClick: (tag: string) => void; onTagClick: (tag: string) => void;
} }
@@ -35,10 +36,13 @@ interface NotebookViewerComponentState {
galleryItem?: IGalleryItem; galleryItem?: IGalleryItem;
isFavorite?: boolean; isFavorite?: boolean;
dialogProps: DialogProps; dialogProps: DialogProps;
showProgressBar: boolean;
} }
export class NotebookViewerComponent extends React.Component<NotebookViewerComponentProps, NotebookViewerComponentState> export class NotebookViewerComponent extends React.Component<
implements GalleryUtils.DialogEnabledComponent { NotebookViewerComponentProps,
NotebookViewerComponentState
> {
private clientManager: NotebookClientV2; private clientManager: NotebookClientV2;
private notebookComponentBootstrapper: NotebookComponentBootstrapper; private notebookComponentBootstrapper: NotebookComponentBootstrapper;
@@ -64,26 +68,24 @@ export class NotebookViewerComponent extends React.Component<NotebookViewerCompo
content: undefined, content: undefined,
galleryItem: props.galleryItem, galleryItem: props.galleryItem,
isFavorite: props.isFavorite, isFavorite: props.isFavorite,
dialogProps: undefined dialogProps: undefined,
showProgressBar: true
}; };
this.loadNotebookContent(); this.loadNotebookContent();
} }
setDialogProps = (dialogProps: DialogProps): void => {
this.setState({ dialogProps });
};
private async loadNotebookContent(): Promise<void> { private async loadNotebookContent(): Promise<void> {
try { try {
const response = await fetch(this.props.notebookUrl); const response = await fetch(this.props.notebookUrl);
if (!response.ok) { if (!response.ok) {
this.setState({ showProgressBar: false });
throw new Error(`Received HTTP ${response.status} while fetching ${this.props.notebookUrl}`); throw new Error(`Received HTTP ${response.status} while fetching ${this.props.notebookUrl}`);
} }
const notebook: Notebook = await response.json(); const notebook: Notebook = await response.json();
this.notebookComponentBootstrapper.setContent("json", notebook); this.notebookComponentBootstrapper.setContent("json", notebook);
this.setState({ content: notebook }); this.setState({ content: notebook, showProgressBar: false });
if (this.props.galleryItem) { if (this.props.galleryItem) {
const response = await this.props.junoClient.increaseNotebookViews(this.props.galleryItem.id); const response = await this.props.junoClient.increaseNotebookViews(this.props.galleryItem.id);
@@ -94,6 +96,7 @@ export class NotebookViewerComponent extends React.Component<NotebookViewerCompo
this.setState({ galleryItem: response.data }); this.setState({ galleryItem: response.data });
} }
} catch (error) { } catch (error) {
this.setState({ showProgressBar: false });
const message = `Failed to load notebook content: ${error}`; const message = `Failed to load notebook content: ${error}`;
Logger.logError(message, "NotebookViewerComponent/loadNotebookContent"); Logger.logError(message, "NotebookViewerComponent/loadNotebookContent");
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message); NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
@@ -116,9 +119,7 @@ export class NotebookViewerComponent extends React.Component<NotebookViewerCompo
<NotebookMetadataComponent <NotebookMetadataComponent
data={this.state.galleryItem} data={this.state.galleryItem}
isFavorite={this.state.isFavorite} isFavorite={this.state.isFavorite}
downloadButtonText={ downloadButtonText={this.props.container && "Download to my notebooks"}
this.props.container ? "Download to my notebooks" : "Edit/Run in Cosmos DB data explorer"
}
onTagClick={this.props.onTagClick} onTagClick={this.props.onTagClick}
onFavoriteClick={this.favoriteItem} onFavoriteClick={this.favoriteItem}
onUnfavoriteClick={this.unfavoriteItem} onUnfavoriteClick={this.unfavoriteItem}
@@ -129,7 +130,11 @@ export class NotebookViewerComponent extends React.Component<NotebookViewerCompo
<></> <></>
)} )}
{this.notebookComponentBootstrapper.renderComponent(NotebookReadOnlyRenderer, { hideInputs: false })} {this.state.showProgressBar && <ProgressIndicator />}
{this.notebookComponentBootstrapper.renderComponent(NotebookReadOnlyRenderer, {
hideInputs: this.props.hideInputs
})}
{this.state.dialogProps && <DialogComponent {...this.state.dialogProps} />} {this.state.dialogProps && <DialogComponent {...this.state.dialogProps} />}
</div> </div>
@@ -170,7 +175,7 @@ export class NotebookViewerComponent extends React.Component<NotebookViewerCompo
}; };
private downloadItem = async (): Promise<void> => { private downloadItem = async (): Promise<void> => {
GalleryUtils.downloadItem(this, this.props.container, this.props.junoClient, this.state.galleryItem, item => GalleryUtils.downloadItem(this.props.container, this.props.junoClient, this.state.galleryItem, item =>
this.setState({ galleryItem: item }) this.setState({ galleryItem: item })
); );
}; };

View File

@@ -48,6 +48,7 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
verticalAlign="center" verticalAlign="center"
> >
<StyledPersonaBase <StyledPersonaBase
imageUrl={false}
size={11} size={11}
text="author" text="author"
/> />
@@ -55,14 +56,14 @@ exports[`NotebookMetadataComponent renders liked notebook 1`] = `
Invalid Date Invalid Date
</Text> </Text>
<Text> <Text>
<StyledIconBase <Memo(StyledIconBase)
iconName="RedEye" iconName="RedEye"
/> />
0 0
</Text> </Text>
<Text> <Text>
<StyledIconBase <Memo(StyledIconBase)
iconName="Download" iconName="Download"
/> />
0 0
@@ -147,6 +148,7 @@ exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
verticalAlign="center" verticalAlign="center"
> >
<StyledPersonaBase <StyledPersonaBase
imageUrl={false}
size={11} size={11}
text="author" text="author"
/> />
@@ -154,14 +156,14 @@ exports[`NotebookMetadataComponent renders un-liked notebook 1`] = `
Invalid Date Invalid Date
</Text> </Text>
<Text> <Text>
<StyledIconBase <Memo(StyledIconBase)
iconName="RedEye" iconName="RedEye"
/> />
0 0
</Text> </Text>
<Text> <Text>
<StyledIconBase <Memo(StyledIconBase)
iconName="Download" iconName="Download"
/> />
0 0

View File

@@ -55,7 +55,6 @@ import { IGalleryItem } from "../Juno/JunoClient";
import { LibraryManagePane } from "./Panes/LibraryManagePane"; import { LibraryManagePane } from "./Panes/LibraryManagePane";
import { LoadQueryPane } from "./Panes/LoadQueryPane"; import { LoadQueryPane } from "./Panes/LoadQueryPane";
import * as Logger from "../Common/Logger"; import * as Logger from "../Common/Logger";
import { ManageSparkClusterPane } from "./Panes/ManageSparkClusterPane";
import { MessageHandler } from "../Common/MessageHandler"; import { MessageHandler } from "../Common/MessageHandler";
import { NotebookContentItem, NotebookContentItemType } from "./Notebook/NotebookContentItem"; import { NotebookContentItem, NotebookContentItemType } from "./Notebook/NotebookContentItem";
import { NotebookUtil } from "./Notebook/NotebookUtil"; import { NotebookUtil } from "./Notebook/NotebookUtil";
@@ -73,12 +72,12 @@ import { RouteHandler } from "../RouteHandlers/RouteHandler";
import { SaveQueryPane } from "./Panes/SaveQueryPane"; import { SaveQueryPane } from "./Panes/SaveQueryPane";
import { SettingsPane } from "./Panes/SettingsPane"; import { SettingsPane } from "./Panes/SettingsPane";
import { SetupNotebooksPane } from "./Panes/SetupNotebooksPane"; import { SetupNotebooksPane } from "./Panes/SetupNotebooksPane";
import { SetupSparkClusterPane } from "./Panes/SetupSparkClusterPane";
import { SparkClusterManager } from "../SparkClusterManager/SparkClusterManager"; import { SparkClusterManager } from "../SparkClusterManager/SparkClusterManager";
import { SplashScreenComponentAdapter } from "./SplashScreen/SplashScreenComponentApdapter"; import { SplashScreenComponentAdapter } from "./SplashScreen/SplashScreenComponentApdapter";
import { Splitter, SplitterBounds, SplitterDirection } from "../Common/Splitter"; import { Splitter, SplitterBounds, SplitterDirection } from "../Common/Splitter";
import { StringInputPane } from "./Panes/StringInputPane"; import { StringInputPane } from "./Panes/StringInputPane";
import { TableColumnOptionsPane } from "./Panes/Tables/TableColumnOptionsPane"; import { TableColumnOptionsPane } from "./Panes/Tables/TableColumnOptionsPane";
import { TabsManager } from "./Tabs/TabsManager";
import { UploadFilePane } from "./Panes/UploadFilePane"; import { UploadFilePane } from "./Panes/UploadFilePane";
import { UploadItemsPane } from "./Panes/UploadItemsPane"; import { UploadItemsPane } from "./Panes/UploadItemsPane";
import { UploadItemsPaneAdapter } from "./Panes/UploadItemsPaneAdapter"; import { UploadItemsPaneAdapter } from "./Panes/UploadItemsPaneAdapter";
@@ -163,11 +162,10 @@ export default class Explorer implements ViewModels.Explorer {
private resourceTreeForResourceToken: ResourceTreeAdapterForResourceToken; private resourceTreeForResourceToken: ResourceTreeAdapterForResourceToken;
// Tabs // Tabs
public openedTabs: ko.ObservableArray<ViewModels.Tab>;
public activeTab: ko.Observable<ViewModels.Tab>;
public isTabsContentExpanded: ko.Observable<boolean>; public isTabsContentExpanded: ko.Observable<boolean>;
public galleryTab: any; public galleryTab: any;
public notebookViewerTab: any; public notebookViewerTab: any;
public tabsManager: TabsManager;
// Contextual panes // Contextual panes
public addDatabasePane: ViewModels.AddDatabasePane; public addDatabasePane: ViewModels.AddDatabasePane;
@@ -192,8 +190,6 @@ export default class Explorer implements ViewModels.Explorer {
public uploadFilePane: UploadFilePane; public uploadFilePane: UploadFilePane;
public stringInputPane: StringInputPane; public stringInputPane: StringInputPane;
public setupNotebooksPane: SetupNotebooksPane; public setupNotebooksPane: SetupNotebooksPane;
public setupSparkClusterPane: ViewModels.ContextualPane;
public manageSparkClusterPane: ViewModels.ContextualPane;
public libraryManagePane: ViewModels.ContextualPane; public libraryManagePane: ViewModels.ContextualPane;
public clusterLibraryPane: ViewModels.ContextualPane; public clusterLibraryPane: ViewModels.ContextualPane;
public gitHubReposPane: ViewModels.ContextualPane; public gitHubReposPane: ViewModels.ContextualPane;
@@ -233,7 +229,6 @@ export default class Explorer implements ViewModels.Explorer {
public arcadiaWorkspaces: ko.ObservableArray<ArcadiaWorkspaceItem>; public arcadiaWorkspaces: ko.ObservableArray<ArcadiaWorkspaceItem>;
public hasStorageAnalyticsAfecFeature: ko.Observable<boolean>; public hasStorageAnalyticsAfecFeature: ko.Observable<boolean>;
public isSynapseLinkUpdating: ko.Observable<boolean>; public isSynapseLinkUpdating: ko.Observable<boolean>;
public isNotebookTabActive: ko.Computed<boolean>;
public memoryUsageInfo: ko.Observable<DataModels.MemoryUsageInfo>; public memoryUsageInfo: ko.Observable<DataModels.MemoryUsageInfo>;
public notebookManager?: any; // This is dynamically loaded public notebookManager?: any; // This is dynamically loaded
@@ -300,14 +295,10 @@ export default class Explorer implements ViewModels.Explorer {
this.arcadiaToken = ko.observable<string>(); this.arcadiaToken = ko.observable<string>();
this.arcadiaToken.subscribe((token: string) => { this.arcadiaToken.subscribe((token: string) => {
if (token) { if (token) {
this.openedTabs && const notebookTabs = this.tabsManager.getTabs(ViewModels.CollectionTabKind.NotebookV2);
this.openedTabs().forEach(tab => { (notebookTabs || []).forEach((tab: NotebookV2Tab) => {
if (tab.tabKind === ViewModels.CollectionTabKind.Notebook) { tab.reconfigureServiceEndpoints();
throw new Error("NotebookTab is deprecated. Use NotebookV2Tab"); });
} else if (tab.tabKind === ViewModels.CollectionTabKind.NotebookV2) {
(tab as NotebookV2Tab).reconfigureServiceEndpoints();
}
});
} }
}); });
this.isNotebooksEnabledForAccount = ko.observable(false); this.isNotebooksEnabledForAccount = ko.observable(false);
@@ -759,22 +750,6 @@ export default class Explorer implements ViewModels.Explorer {
container: this 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({ this.libraryManagePane = new LibraryManagePane({
documentClientUtility: this.documentClientUtility, documentClientUtility: this.documentClientUtility,
id: "libraryManagePane", id: "libraryManagePane",
@@ -791,12 +766,8 @@ export default class Explorer implements ViewModels.Explorer {
container: this container: this
}); });
this.openedTabs = ko.observableArray<ViewModels.Tab>([]); this.tabsManager = new TabsManager();
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._panes = [ this._panes = [
this.addDatabasePane, this.addDatabasePane,
this.addCollectionPane, this.addCollectionPane,
@@ -818,9 +789,7 @@ export default class Explorer implements ViewModels.Explorer {
this.browseQueriesPane, this.browseQueriesPane,
this.uploadFilePane, this.uploadFilePane,
this.stringInputPane, this.stringInputPane,
this.setupNotebooksPane, this.setupNotebooksPane
this.setupSparkClusterPane,
this.manageSparkClusterPane
]; ];
this.addDatabaseText.subscribe((addDatabaseText: string) => this.addDatabasePane.title(addDatabaseText)); this.addDatabaseText.subscribe((addDatabaseText: string) => this.addDatabasePane.title(addDatabaseText));
this.rebindDocumentClientUtility.bind(this); this.rebindDocumentClientUtility.bind(this);
@@ -1284,7 +1253,7 @@ export default class Explorer implements ViewModels.Explorer {
class: "connectDialogButtons okBtn connectOkBtns", class: "connectDialogButtons okBtn connectOkBtns",
click: () => { click: () => {
$("#contextSwitchPrompt").dialog("close"); $("#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); this.renewShareAccess(connectionString);
} }
}; };
@@ -2112,10 +2081,6 @@ export default class Explorer implements ViewModels.Explorer {
return Q(); return Q();
} }
public findActiveTab(): ViewModels.Tab {
return this.activeTab();
}
public findSelectedCollection(): ViewModels.Collection { public findSelectedCollection(): ViewModels.Collection {
if (this.selectedNode().nodeKind === "Collection") { if (this.selectedNode().nodeKind === "Collection") {
return this.findSelectedCollectionForSelectedNode(); return this.findSelectedCollectionForSelectedNode();
@@ -2128,11 +2093,9 @@ export default class Explorer implements ViewModels.Explorer {
public findSelectedStoredProcedure(): ViewModels.StoredProcedure { public findSelectedStoredProcedure(): ViewModels.StoredProcedure {
const selectedCollection: ViewModels.Collection = this.findSelectedCollection(); const selectedCollection: ViewModels.Collection = this.findSelectedCollection();
return _.find(selectedCollection.storedProcedures(), (storedProcedure: ViewModels.StoredProcedure) => { return _.find(selectedCollection.storedProcedures(), (storedProcedure: ViewModels.StoredProcedure) => {
const openedSprocTab = this.openedTabs().filter( const openedSprocTab = this.tabsManager.getTabs(
(tab: ViewModels.Tab) => ViewModels.CollectionTabKind.StoredProcedures,
tab.node && (tab: ViewModels.Tab) => tab.node && tab.node.rid === storedProcedure.rid
tab.node.rid === storedProcedure.rid &&
tab.tabKind === ViewModels.CollectionTabKind.StoredProcedures
); );
return ( return (
storedProcedure.rid === this.selectedNode().rid || storedProcedure.rid === this.selectedNode().rid ||
@@ -2144,11 +2107,9 @@ export default class Explorer implements ViewModels.Explorer {
public findSelectedUDF(): ViewModels.UserDefinedFunction { public findSelectedUDF(): ViewModels.UserDefinedFunction {
const selectedCollection: ViewModels.Collection = this.findSelectedCollection(); const selectedCollection: ViewModels.Collection = this.findSelectedCollection();
return _.find(selectedCollection.userDefinedFunctions(), (userDefinedFunction: ViewModels.UserDefinedFunction) => { return _.find(selectedCollection.userDefinedFunctions(), (userDefinedFunction: ViewModels.UserDefinedFunction) => {
const openedUdfTab = this.openedTabs().filter( const openedUdfTab = this.tabsManager.getTabs(
(tab: ViewModels.Tab) => ViewModels.CollectionTabKind.UserDefinedFunctions,
tab.node && (tab: ViewModels.Tab) => tab.node && tab.node.rid === userDefinedFunction.rid
tab.node.rid === userDefinedFunction.rid &&
tab.tabKind === ViewModels.CollectionTabKind.UserDefinedFunctions
); );
return ( return (
userDefinedFunction.rid === this.selectedNode().rid || userDefinedFunction.rid === this.selectedNode().rid ||
@@ -2160,9 +2121,9 @@ export default class Explorer implements ViewModels.Explorer {
public findSelectedTrigger(): ViewModels.Trigger { public findSelectedTrigger(): ViewModels.Trigger {
const selectedCollection: ViewModels.Collection = this.findSelectedCollection(); const selectedCollection: ViewModels.Collection = this.findSelectedCollection();
return _.find(selectedCollection.triggers(), (trigger: ViewModels.Trigger) => { return _.find(selectedCollection.triggers(), (trigger: ViewModels.Trigger) => {
const openedTriggerTab = this.openedTabs().filter( const openedTriggerTab = this.tabsManager.getTabs(
(tab: ViewModels.Tab) => ViewModels.CollectionTabKind.Triggers,
tab.node && tab.node.rid === trigger.rid && tab.tabKind === ViewModels.CollectionTabKind.Triggers (tab: ViewModels.Tab) => tab.node && tab.node.rid === trigger.rid
); );
return ( return (
trigger.rid === this.selectedNode().rid || trigger.rid === this.selectedNode().rid ||
@@ -2171,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 { public closeAllPanes(): void {
this._panes.forEach((pane: ViewModels.ContextualPane) => pane.close()); this._panes.forEach((pane: ViewModels.ContextualPane) => pane.close());
} }
@@ -2257,7 +2208,9 @@ export default class Explorer implements ViewModels.Explorer {
if (isNewDatabase) { if (isNewDatabase) {
database.expandDatabase(); database.expandDatabase();
} }
database.refreshTabSelectedState(); this.tabsManager.refreshActiveTab(
(tab: ViewModels.Tab) => tab.collection && tab.collection.getDatabase().rid === database.rid
);
}) })
); );
}); });
@@ -2360,7 +2313,7 @@ export default class Explorer implements ViewModels.Explorer {
} }
const urlPrefixWithKeyParam: string = `${config.hostedExplorerURL}?key=`; 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()) || ""}`; return `${urlPrefixWithKeyParam}${token}#/${(currentActiveTab && currentActiveTab.hashLocation()) || ""}`;
} }
@@ -2622,46 +2575,48 @@ export default class Explorer implements ViewModels.Explorer {
if (!notebookContentItem || !notebookContentItem.path) { if (!notebookContentItem || !notebookContentItem.path) {
throw new Error(`Invalid notebookContentItem: ${notebookContentItem}`); throw new Error(`Invalid notebookContentItem: ${notebookContentItem}`);
} }
const openedNotebookTabs = this.getNotebookTabsForFilepath(notebookContentItem.path);
if (openedNotebookTabs.length > 0) { const notebookTabs: NotebookV2Tab[] = this.tabsManager.getTabs(
openedNotebookTabs[0].onTabClick(); ViewModels.CollectionTabKind.NotebookV2,
openedNotebookTabs[0].onActivate(); (tab: ViewModels.Tab) =>
return true; (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; return true;
} }
@@ -2674,10 +2629,11 @@ export default class Explorer implements ViewModels.Explorer {
} }
// Don't delete if tab is open to avoid accidental deletion // Don't delete if tab is open to avoid accidental deletion
const openedNotebookTabs = this.openedTabs().filter( const openedNotebookTabs = this.tabsManager.getTabs(
(tab: ViewModels.Tab) => ViewModels.CollectionTabKind.NotebookV2,
tab.tabKind === ViewModels.CollectionTabKind.NotebookV2 && (tab: NotebookV2Tab) => {
(tab as NotebookV2Tab).notebookPath() === notebookFile.path return tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), notebookFile.path);
}
); );
if (openedNotebookTabs.length > 0) { if (openedNotebookTabs.length > 0) {
this.showOkModalDialog("Unable to rename file", "This file is being edited. Please close the tab and try again."); this.showOkModalDialog("Unable to rename file", "This file is being edited. Please close the tab and try again.");
@@ -2697,17 +2653,15 @@ export default class Explorer implements ViewModels.Explorer {
onSubmit: (input: string) => this.notebookManager?.notebookContentClient.renameNotebook(notebookFile, input) onSubmit: (input: string) => this.notebookManager?.notebookContentClient.renameNotebook(notebookFile, input)
}) })
.then(newNotebookFile => { .then(newNotebookFile => {
this.openedTabs() const notebookTabs: ViewModels.Tab[] = this.tabsManager.getTabs(
.filter( ViewModels.CollectionTabKind.NotebookV2,
(tab: ViewModels.Tab) => (tab: NotebookV2Tab) => tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), originalPath)
tab.tabKind === ViewModels.CollectionTabKind.NotebookV2 && );
FileSystemUtil.isPathEqual((tab as NotebookV2Tab).notebookPath(), originalPath) notebookTabs.forEach(tab => {
) tab.tabTitle(newNotebookFile.name);
.forEach(tab => { tab.tabPath(newNotebookFile.path);
tab.tabTitle(newNotebookFile.name); (tab as NotebookV2Tab).notebookPath(newNotebookFile.path);
tab.tabPath(newNotebookFile.path); });
(tab as NotebookV2Tab).notebookPath(newNotebookFile.path);
});
return newNotebookFile; return newNotebookFile;
}); });
@@ -2887,39 +2841,34 @@ export default class Explorer implements ViewModels.Explorer {
await this.initSparkConnectionInfo(this.databaseAccount()); await this.initSparkConnectionInfo(this.databaseAccount());
} }
const openedSparkMasterTabs = this.openedTabs().filter( const sparkMasterTabs: SparkMasterTab[] = this.tabsManager.getTabs(
(tab: ViewModels.Tab) => tab.tabKind === ViewModels.CollectionTabKind.SparkMasterTab ViewModels.CollectionTabKind.SparkMasterTab
); ) as SparkMasterTab[];
if (openedSparkMasterTabs.length > 0) { let sparkMasterTab: SparkMasterTab = sparkMasterTabs && sparkMasterTabs[0];
openedSparkMasterTabs[0].onTabClick();
openedSparkMasterTabs[0].onActivate(); if (sparkMasterTab) {
return; 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> => { private refreshNotebookList = async (): Promise<void> => {
@@ -2942,9 +2891,11 @@ export default class Explorer implements ViewModels.Explorer {
} }
// Don't delete if tab is open to avoid accidental deletion // Don't delete if tab is open to avoid accidental deletion
const openedNotebookTabs = this.openedTabs().filter( const openedNotebookTabs = this.tabsManager.getTabs(
(tab: ViewModels.Tab) => ViewModels.CollectionTabKind.NotebookV2,
tab.tabKind === ViewModels.CollectionTabKind.NotebookV2 && (tab as NotebookV2Tab).notebookPath() === item.path (tab: NotebookV2Tab) => {
return tab.notebookPath && FileSystemUtil.isPathEqual(tab.notebookPath(), item.path);
}
); );
if (openedNotebookTabs.length > 0) { if (openedNotebookTabs.length > 0) {
this.showOkModalDialog("Unable to delete file", "This file is being edited. Please close the tab and try again."); this.showOkModalDialog("Unable to delete file", "This file is being edited. Please close the tab and try again.");
@@ -3106,94 +3057,78 @@ export default class Explorer implements ViewModels.Explorer {
throw new Error("Terminal kind: ${kind} not supported"); throw new Error("Terminal kind: ${kind} not supported");
} }
const openedTabs = this.openedTabs().filter( const terminalTabs: TerminalTab[] = this.tabsManager.getTabs(
(tab: ViewModels.Tab) => tab.tabKind === ViewModels.CollectionTabKind.Terminal 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 (terminalTab) {
if (openedTabs[i].hashLocation() == hashLocation) { this.tabsManager.activateTab(terminalTab);
openedTabs[i].onTabClick(); } else {
openedTabs[i].onActivate(); const newTab = new TerminalTab({
return; 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) { public async openGallery(notebookUrl?: string, galleryItem?: IGalleryItem, isFavorite?: boolean) {
let title: string; let title: string = "Gallery";
let hashLocation: string; let hashLocation: string = "gallery";
title = "Gallery"; const galleryTabs = this.tabsManager.getTabs(
hashLocation = "gallery"; ViewModels.CollectionTabKind.Gallery,
(tab: ViewModels.Tab) => tab.hashLocation() == hashLocation
const openedTabs = this.openedTabs().filter(
(tab: ViewModels.Tab) => tab.tabKind === ViewModels.CollectionTabKind.Gallery
); );
let galleryTab = galleryTabs && galleryTabs[0];
for (let i = 0; i < openedTabs.length; ++i) { if (galleryTab) {
if (openedTabs[i].hashLocation() == hashLocation) { this.tabsManager.activateTab(galleryTab);
openedTabs[i].onTabClick(); } else {
openedTabs[i].onActivate(); if (!this.galleryTab) {
(openedTabs[i] as any).updateGalleryParams(notebookUrl, galleryItem, isFavorite); this.galleryTab = await import(/* webpackChunkName: "GalleryTab" */ "./Tabs/GalleryTab");
return;
} }
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) { public async openNotebookViewer(notebookUrl: string) {
@@ -3211,41 +3146,37 @@ export default class Explorer implements ViewModels.Explorer {
return notebookViewerTab.notebookUrl === notebookUrl; return notebookViewerTab.notebookUrl === notebookUrl;
}; };
const openedTabs = this.openedTabs().filter( const notebookViewerTabs = this.tabsManager.getTabs(
(tab: ViewModels.Tab) => tab.tabKind === ViewModels.CollectionTabKind.NotebookViewer && isNotebookViewerOpen(tab) ViewModels.CollectionTabKind.NotebookV2,
); (tab: ViewModels.Tab) => {
return tab.hashLocation() == hashLocation && isNotebookViewerOpen(tab);
for (let i = 0; i < openedTabs.length; ++i) {
if (openedTabs[i].hashLocation() == hashLocation) {
openedTabs[i].onTabClick();
openedTabs[i].onActivate();
return;
} }
);
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 { public onNewCollectionClicked(): void {
@@ -3257,24 +3188,8 @@ export default class Explorer implements ViewModels.Explorer {
document.getElementById("linkAddCollection").focus(); 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 { private refreshCommandBarButtons(): void {
const activeTab = this.findActiveTab(); const activeTab = this.tabsManager.activeTab();
if (activeTab) { if (activeTab) {
activeTab.onActivate(); // TODO only update tabs buttons? activeTab.onActivate(); // TODO only update tabs buttons?
} else { } else {

View File

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

View File

@@ -4,14 +4,11 @@ import { Action, ActionModifiers } from "../../../Shared/Telemetry/TelemetryCons
import { Areas } from "../../../Common/Constants"; import { Areas } from "../../../Common/Constants";
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor"; import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import ApacheSparkIcon from "../../../../images/notebook/Apache-spark.svg";
import AddDatabaseIcon from "../../../../images/AddDatabase.svg"; import AddDatabaseIcon from "../../../../images/AddDatabase.svg";
import AddCollectionIcon from "../../../../images/AddCollection.svg"; import AddCollectionIcon from "../../../../images/AddCollection.svg";
import AddSqlQueryIcon from "../../../../images/AddSqlQuery_16x16.svg"; import AddSqlQueryIcon from "../../../../images/AddSqlQuery_16x16.svg";
import BrowseQueriesIcon from "../../../../images/BrowseQuery.svg"; import BrowseQueriesIcon from "../../../../images/BrowseQuery.svg";
import * as Constants from "../../../Common/Constants"; 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 OpenInTabIcon from "../../../../images/open-in-tab.svg";
import OpenQueryFromDiskIcon from "../../../../images/OpenQueryFromDisk.svg"; import OpenQueryFromDiskIcon from "../../../../images/OpenQueryFromDisk.svg";
import CosmosTerminalIcon from "../../../../images/Cosmos-Terminal.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 EnableNotebooksIcon from "../../../../images/notebook/Notebook-enable.svg";
import NewNotebookIcon from "../../../../images/notebook/Notebook-new.svg"; import NewNotebookIcon from "../../../../images/notebook/Notebook-new.svg";
import ResetWorkspaceIcon from "../../../../images/notebook/Notebook-reset-workspace.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 GitHubIcon from "../../../../images/github.svg";
import SynapseIcon from "../../../../images/synapse-link.svg"; import SynapseIcon from "../../../../images/synapse-link.svg";
import { config, Platform } from "../../../Config"; import { config, Platform } from "../../../Config";
@@ -88,25 +84,6 @@ export class CommandBarComponentButtonFactory {
buttons.push(CommandBarComponentButtonFactory.createNotebookWorkspaceResetButton(container)); 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.isDatabaseNodeOrNoneSelected()) {
if (container.isNotebookEnabled()) { if (container.isNotebookEnabled()) {
buttons.push(CommandBarComponentButtonFactory.createDivider()); 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 { private static createOpenTerminalButton(container: ViewModels.Explorer): ViewModels.NavbarButtonConfig {
const label = "Open Terminal"; const label = "Open Terminal";
return { 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( private static createStaticCommandBarButtonsForResourceToken(
container: ViewModels.Explorer container: ViewModels.Explorer
): ViewModels.NavbarButtonConfig[] { ): ViewModels.NavbarButtonConfig[] {

View File

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

View File

@@ -43,6 +43,7 @@ import { CdbAppState } from "./types";
import { decryptJWTToken } from "../../../Utils/AuthorizationUtils"; import { decryptJWTToken } from "../../../Utils/AuthorizationUtils";
import * as TextFile from "./contents/file/text-file"; import * as TextFile from "./contents/file/text-file";
import { NotebookUtil } from "../NotebookUtil"; import { NotebookUtil } from "../NotebookUtil";
import { FileSystemUtil } from "../FileSystemUtil";
interface NotebookServiceConfig extends JupyterServerConfig { interface NotebookServiceConfig extends JupyterServerConfig {
userPuid?: string; userPuid?: string;
@@ -806,7 +807,9 @@ const closeUnsupportedMimetypesEpic = (
if (explorer && !TextFile.handles(mimetype)) { if (explorer && !TextFile.handles(mimetype)) {
const filepath = action.payload.filepath; const filepath = action.payload.filepath;
// Close tab and show error message // 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.`; 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); explorer.showOkModalDialog("File cannot be rendered", msg);
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg); NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);
@@ -832,7 +835,9 @@ const closeContentFailedToFetchEpic = (
if (explorer) { if (explorer) {
const filepath = action.payload.filepath; const filepath = action.payload.filepath;
// Close tab and show error message // 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}.`; const msg = `Failed to load file: ${filepath}.`;
explorer.showOkModalDialog("Failure to load", msg); explorer.showOkModalDialog("Failure to load", msg);
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg); NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, msg);

View File

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

View File

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

View File

@@ -13,7 +13,29 @@ describe("Add Collection Pane", () => {
kind: "DocumentDB", kind: "DocumentDB",
location: "", location: "",
name: "mock", 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, type: undefined,
tags: [] tags: []
}; };
@@ -68,5 +90,23 @@ describe("Add Collection Pane", () => {
addCollectionPane.partitionKey("/label"); addCollectionPane.partitionKey("/label");
expect(addCollectionPane.isValid()).toBe(true); 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 uniqueKeysPlaceholder: ko.Computed<string>;
public upsellMessage: ko.PureComputed<string>; public upsellMessage: ko.PureComputed<string>;
public upsellMessageAriaLabel: ko.PureComputed<string>; public upsellMessageAriaLabel: ko.PureComputed<string>;
public upsellAnchorUrl: ko.PureComputed<string>;
public upsellAnchorText: ko.PureComputed<string>;
public debugstring: ko.Computed<string>; public debugstring: ko.Computed<string>;
public displayCollectionThroughput: ko.Computed<boolean>; public displayCollectionThroughput: ko.Computed<boolean>;
public isAutoPilotSelected: ko.Observable<boolean>; public isAutoPilotSelected: ko.Observable<boolean>;
@@ -508,11 +510,19 @@ export default class AddCollectionPane extends ContextualPaneBase implements Vie
}); });
this.upsellMessage = ko.pureComputed<string>(() => { this.upsellMessage = ko.pureComputed<string>(() => {
return PricingUtils.getUpsellMessage(this.container.serverId()); return PricingUtils.getUpsellMessage(this.container.serverId(), this.isFreeTierAccount());
}); });
this.upsellMessageAriaLabel = ko.pureComputed<string>(() => { 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>(() => { this.displayCollectionThroughput = ko.computed<boolean>(() => {
@@ -879,6 +889,7 @@ export default class AddCollectionPane extends ContextualPaneBase implements Vie
this.container.armEndpoint(), this.container.armEndpoint(),
databaseId, databaseId,
collectionId, collectionId,
indexingPolicy,
offerThroughput, offerThroughput,
partitionKeyPath, partitionKeyPath,
partitionKey.version, partitionKey.version,
@@ -898,6 +909,7 @@ export default class AddCollectionPane extends ContextualPaneBase implements Vie
databaseId, databaseId,
this._getAnalyticalStorageTtl(), this._getAnalyticalStorageTtl(),
collectionId, collectionId,
indexingPolicy,
offerThroughput, offerThroughput,
partitionKeyPath, partitionKeyPath,
partitionKey.version, partitionKey.version,

View File

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

View File

@@ -1,3 +1,4 @@
import * as Constants from "../../Common/Constants";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
import AddDatabasePane from "./AddDatabasePane"; import AddDatabasePane from "./AddDatabasePane";
@@ -5,6 +6,37 @@ import AddDatabasePane from "./AddDatabasePane";
describe("Add Database Pane", () => { describe("Add Database Pane", () => {
describe("getSharedThroughputDefault()", () => { describe("getSharedThroughputDefault()", () => {
let explorer: ViewModels.Explorer; 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(() => { beforeEach(() => {
explorer = new Explorer({ explorer = new Explorer({
@@ -43,5 +75,23 @@ describe("Add Database Pane", () => {
const addDatabasePane = explorer.addDatabasePane as AddDatabasePane; const addDatabasePane = explorer.addDatabasePane as AddDatabasePane;
expect(addDatabasePane.getSharedThroughputDefault()).toBe(true); 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 costsVisible: ko.PureComputed<boolean>;
public upsellMessage: ko.PureComputed<string>; public upsellMessage: ko.PureComputed<string>;
public upsellMessageAriaLabel: ko.PureComputed<string>; public upsellMessageAriaLabel: ko.PureComputed<string>;
public upsellAnchorUrl: ko.PureComputed<string>;
public upsellAnchorText: ko.PureComputed<string>;
public isAutoPilotSelected: ko.Observable<boolean>; public isAutoPilotSelected: ko.Observable<boolean>;
public selectedAutoPilotTier: ko.Observable<DataModels.AutopilotTier>; public selectedAutoPilotTier: ko.Observable<DataModels.AutopilotTier>;
public autoPilotTiersList: ko.ObservableArray<ViewModels.DropdownOption<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>(() => { this.upsellMessage = ko.pureComputed<string>(() => {
return PricingUtils.getUpsellMessage(this.container.serverId()); return PricingUtils.getUpsellMessage(this.container.serverId(), this.isFreeTierAccount());
}); });
this.upsellMessageAriaLabel = ko.pureComputed<string>(() => { 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 { } else {
selectedCollection.onNewQueryClick(selectedCollection, null); 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.tabTitle(savedQuery.queryName);
queryTab.tabPath(`${selectedCollection.databaseId}>${selectedCollection.id()}>${savedQuery.queryName}`); queryTab.tabPath(`${selectedCollection.databaseId}>${selectedCollection.id()}>${savedQuery.queryName}`);
queryTab.initialEditorContent(savedQuery.query); queryTab.initialEditorContent(savedQuery.query);

View File

@@ -66,7 +66,9 @@ export default class DeleteCollectionConfirmationPane extends ContextualPaneBase
this.isExecuting(false); this.isExecuting(false);
this.close(); this.close();
this.container.selectedNode(selectedCollection.database); 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.container.refreshAllDatabases();
this.resetData(); this.resetData();
TelemetryProcessor.traceSuccess( TelemetryProcessor.traceSuccess(

View File

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

View File

@@ -67,11 +67,17 @@ export default class DeleteDatabaseConfirmationPane extends ContextualPaneBase
this.isExecuting(false); this.isExecuting(false);
this.close(); this.close();
this.container.refreshAllDatabases(); 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); this.container.selectedNode(null);
selectedDatabase selectedDatabase
.collections() .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(); this.resetData();
TelemetryProcessor.traceSuccess( TelemetryProcessor.traceSuccess(
Action.DeleteDatabase, Action.DeleteDatabase,

View File

@@ -111,7 +111,7 @@ export class LoadQueryPane extends ContextualPaneBase implements ViewModels.Load
const reader = new FileReader(); const reader = new FileReader();
reader.onload = (evt: any): void => { reader.onload = (evt: any): void => {
const fileData: string = evt.target.result; 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.initialEditorContent(fileData);
queryTab.sqlQueryEditorContent(fileData); queryTab.sqlQueryEditorContent(fileData);
deferred.resolve(); 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 UploadFilePaneTemplate from "./UploadFilePane.html";
import StringInputPaneTemplate from "./StringInputPane.html"; import StringInputPaneTemplate from "./StringInputPane.html";
import SetupNotebooksPaneTemplate from "./SetupNotebooksPane.html"; import SetupNotebooksPaneTemplate from "./SetupNotebooksPane.html";
import SetupSparkClusterPaneTemplate from "./SetupSparkClusterPane.html";
import ManageSparkClusterPaneTemplate from "./ManageSparkClusterPane.html";
import LibraryManagePaneTemplate from "./LibraryManagePane.html"; import LibraryManagePaneTemplate from "./LibraryManagePane.html";
import ClusterLibraryPaneTemplate from "./ClusterLibraryPane.html"; import ClusterLibraryPaneTemplate from "./ClusterLibraryPane.html";
import GitHubReposPaneTemplate from "./GitHubReposPane.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 { export class LibraryManagePaneComponent {
constructor() { constructor() {
return { return {

View File

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

View File

@@ -34,7 +34,8 @@ export class SaveQueryPane extends ContextualPaneBase {
} }
const queryName: string = this.queryName(); 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(); const query: string = queryTab && queryTab.sqlQueryEditorContent();
if (!queryName || queryName.length === 0) { if (!queryName || queryName.length === 0) {
this.formErrors("No query name specified"); 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 * as ko from "knockout";
import { DataSamplesUtil } from "../DataSamples/DataSamplesUtil"; import { DataSamplesUtil } from "../DataSamples/DataSamplesUtil";
import { SplashScreenComponentAdapter } from "./SplashScreenComponentApdapter"; import { SplashScreenComponentAdapter } from "./SplashScreenComponentApdapter";
import { TabsManager } from "../Tabs/TabsManager";
import Explorer from "../Explorer"; import Explorer from "../Explorer";
jest.mock("../Explorer"); jest.mock("../Explorer");
const createExplorer = () => { const createExplorer = () => {
const mock = new Explorer({} as any); const mock = new Explorer({} as any);
mock.openedTabs = ko.observableArray([]);
mock.selectedNode = ko.observable(); mock.selectedNode = ko.observable();
mock.isNotebookEnabled = ko.observable(false); mock.isNotebookEnabled = ko.observable(false);
mock.addCollectionText = ko.observable("add collection"); mock.addCollectionText = ko.observable("add collection");
mock.tabsManager = new TabsManager();
return mock as jest.Mocked<Explorer>; return mock as jest.Mocked<Explorer>;
}; };

View File

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

View File

@@ -21,7 +21,7 @@ import DeleteIcon from "../../../images/delete.svg";
import { QueryIterator, ItemDefinition, Resource, ConflictDefinition } from "@azure/cosmos"; import { QueryIterator, ItemDefinition, Resource, ConflictDefinition } from "@azure/cosmos";
import { MinimalQueryIterator } from "../../Common/IteratorUtilities"; 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 selectedConflictId: ko.Observable<ViewModels.ConflictId>;
public selectedConflictContent: ViewModels.Editable<string>; public selectedConflictContent: ViewModels.Editable<string>;
public selectedConflictCurrent: ViewModels.Editable<string>; public selectedConflictCurrent: ViewModels.Editable<string>;

View File

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

View File

@@ -1 +1 @@
<div style="height: 100%" data-bind="react:galleryComponentAdapter, setTemplateReady: true"></div> <div style="height: 100%" data-bind="react:galleryAndNotebookViewerComponentAdapter, setTemplateReady: true"></div>

View File

@@ -1,129 +1,21 @@
import * as ko from "knockout";
import * as React from "react";
import { ReactAdapter } from "../../Bindings/ReactBindingHandler";
import * as ViewModels from "../../Contracts/ViewModels"; import * as ViewModels from "../../Contracts/ViewModels";
import { IGalleryItem, JunoClient } from "../../Juno/JunoClient"; import { GalleryAndNotebookViewerComponentProps } from "../Controls/NotebookGallery/GalleryAndNotebookViewerComponent";
import * as GalleryUtils from "../../Utils/GalleryUtils"; import { GalleryAndNotebookViewerComponentAdapter } from "../Controls/NotebookGallery/GalleryAndNotebookViewerComponentAdapter";
import { import { GalleryTab as GalleryViewerTab, SortBy } from "../Controls/NotebookGallery/GalleryViewerComponent";
GalleryTab as GalleryViewerTab,
GalleryViewerComponent,
GalleryViewerComponentProps,
SortBy
} from "../Controls/NotebookGallery/GalleryViewerComponent";
import {
NotebookViewerComponent,
NotebookViewerComponentProps
} from "../Controls/NotebookViewer/NotebookViewerComponent";
import TabsBase from "./TabsBase"; import TabsBase from "./TabsBase";
/** /**
* Notebook gallery tab * Notebook gallery tab
*/ */
interface GalleryComponentAdapterProps {
container: ViewModels.Explorer;
junoClient: JunoClient;
notebookUrl: string;
galleryItem: IGalleryItem;
isFavorite: boolean;
selectedTab: GalleryViewerTab;
sortBy: SortBy;
searchText: string;
}
interface GalleryComponentAdapterState {
notebookUrl: string;
galleryItem: IGalleryItem;
isFavorite: boolean;
selectedTab: GalleryViewerTab;
sortBy: SortBy;
searchText: string;
}
class GalleryComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<number>;
private state: GalleryComponentAdapterState;
constructor(private props: GalleryComponentAdapterProps) {
this.parameters = ko.observable<number>(Date.now());
this.state = {
notebookUrl: props.notebookUrl,
galleryItem: props.galleryItem,
isFavorite: props.isFavorite,
selectedTab: props.selectedTab,
sortBy: props.sortBy,
searchText: props.searchText
};
}
public renderComponent(): JSX.Element {
if (this.state.notebookUrl) {
const props: NotebookViewerComponentProps = {
container: this.props.container,
junoClient: this.props.junoClient,
notebookUrl: this.state.notebookUrl,
galleryItem: this.state.galleryItem,
isFavorite: this.state.isFavorite,
backNavigationText: GalleryUtils.getTabTitle(this.state.selectedTab),
onBackClick: this.onBackClick,
onTagClick: this.loadTaggedItems
};
return <NotebookViewerComponent {...props} />;
}
const props: GalleryViewerComponentProps = {
container: this.props.container,
junoClient: this.props.junoClient,
selectedTab: this.state.selectedTab,
sortBy: this.state.sortBy,
searchText: this.state.searchText,
onSelectedTabChange: this.onSelectedTabChange,
onSortByChange: this.onSortByChange,
onSearchTextChange: this.onSearchTextChange
};
return <GalleryViewerComponent {...props} />;
}
public setState(state: Partial<GalleryComponentAdapterState>): void {
this.state = Object.assign(this.state, state);
window.requestAnimationFrame(() => this.parameters(Date.now()));
}
private onBackClick = (): void => {
this.props.container.openGallery();
};
private loadTaggedItems = (tag: string): void => {
this.setState({
notebookUrl: undefined,
searchText: tag
});
};
private onSelectedTabChange = (selectedTab: GalleryViewerTab): void => {
this.state.selectedTab = selectedTab;
};
private onSortByChange = (sortBy: SortBy): void => {
this.state.sortBy = sortBy;
};
private onSearchTextChange = (searchText: string): void => {
this.state.searchText = searchText;
};
}
export default class GalleryTab extends TabsBase implements ViewModels.Tab { export default class GalleryTab extends TabsBase implements ViewModels.Tab {
private container: ViewModels.Explorer; private container: ViewModels.Explorer;
private galleryComponentAdapterProps: GalleryComponentAdapterProps; public galleryAndNotebookViewerComponentAdapter: GalleryAndNotebookViewerComponentAdapter;
private galleryComponentAdapter: GalleryComponentAdapter;
constructor(options: ViewModels.GalleryTabOptions) { constructor(options: ViewModels.GalleryTabOptions) {
super(options); super(options);
this.container = options.container; this.container = options.container;
this.galleryComponentAdapterProps = { const props: GalleryAndNotebookViewerComponentProps = {
container: options.container, container: options.container,
junoClient: options.junoClient, junoClient: options.junoClient,
notebookUrl: options.notebookUrl, notebookUrl: options.notebookUrl,
@@ -134,18 +26,10 @@ export default class GalleryTab extends TabsBase implements ViewModels.Tab {
searchText: undefined searchText: undefined
}; };
this.galleryComponentAdapter = new GalleryComponentAdapter(this.galleryComponentAdapterProps); this.galleryAndNotebookViewerComponentAdapter = new GalleryAndNotebookViewerComponentAdapter(props);
} }
protected getContainer(): ViewModels.Explorer { protected getContainer(): ViewModels.Explorer {
return this.container; return this.container;
} }
public updateGalleryParams(notebookUrl?: string, galleryItem?: IGalleryItem, isFavorite?: boolean): void {
this.galleryComponentAdapter.setState({
notebookUrl,
galleryItem,
isFavorite
});
}
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -24,8 +24,6 @@ export default class TabsBase extends WaitsForTemplateViewModel implements ViewM
public tabKind: ViewModels.CollectionTabKind; public tabKind: ViewModels.CollectionTabKind;
public tabTitle: ko.Observable<string>; public tabTitle: ko.Observable<string>;
public tabPath: 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 closeButtonTabIndex: ko.Computed<number>;
public errorDetailsTabIndex: ko.Computed<number>; public errorDetailsTabIndex: ko.Computed<number>;
public hashLocation: ko.Observable<string>; public hashLocation: ko.Observable<string>;
@@ -55,8 +53,6 @@ export default class TabsBase extends WaitsForTemplateViewModel implements ViewM
(options.tabPath && ko.observable<string>(options.tabPath)) || (options.tabPath && ko.observable<string>(options.tabPath)) ||
(this.collection && (this.collection &&
ko.observable<string>(`${this.collection.databaseId}>${this.collection.id()}>${this.tabTitle()}`)); 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.closeButtonTabIndex = ko.computed<number>(() => (this.isActive() ? 0 : null));
this.errorDetailsTabIndex = ko.computed<number>(() => (this.isActive() ? 0 : null)); this.errorDetailsTabIndex = ko.computed<number>(() => (this.isActive() ? 0 : null));
this.isExecutionError = ko.observable<boolean>(false); this.isExecutionError = ko.observable<boolean>(false);
@@ -80,34 +76,11 @@ export default class TabsBase extends WaitsForTemplateViewModel implements ViewM
return true; 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> { public onCloseTabButtonClick(): void {
const previousTab = this.previousTab(); const explorer: ViewModels.Explorer = this.getContainer();
const nextTab = this.nextTab(); explorer.tabsManager.closeTab(this.tabId, explorer);
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);
}
TelemetryProcessor.trace(Action.Tab, ActionModifiers.Close, { TelemetryProcessor.trace(Action.Tab, ActionModifiers.Close, {
databaseAccountName: this.getContainer().databaseAccount().name, databaseAccountName: this.getContainer().databaseAccount().name,
@@ -115,16 +88,10 @@ export default class TabsBase extends WaitsForTemplateViewModel implements ViewM
dataExplorerArea: Constants.Areas.Tab, dataExplorerArea: Constants.Areas.Tab,
tabTitle: this.tabTitle() tabTitle: this.tabTitle()
}); });
return Q();
} }
public onTabClick(): Q.Promise<any> { public onTabClick(): Q.Promise<any> {
for (let i = 0; i < this.getContainer().openedTabs().length; i++) { this.getContainer().tabsManager.activateTab(this);
const tab = this.getContainer().openedTabs()[i];
tab.isActive(false);
}
this.isActive(true);
this.getContainer().activeTab(this);
return Q(); 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 { StartUploadMessageParams, UploadDetails, UploadDetailsRecord } from "../../workers/upload/definitions";
import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent"; import { ConsoleDataType } from "../Menus/NotificationConsole/NotificationConsoleComponent";
import { CassandraAPIDataClient, CassandraTableKey, CassandraTableKeys } from "../Tables/TableDataClient"; 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 DocumentsTab from "../Tabs/DocumentsTab";
import GraphTab from "../Tabs/GraphTab"; import GraphTab from "../Tabs/GraphTab";
import MongoDocumentsTab from "../Tabs/MongoDocumentsTab"; import MongoDocumentsTab from "../Tabs/MongoDocumentsTab";
@@ -25,8 +28,6 @@ import MongoShellTab from "../Tabs/MongoShellTab";
import QueryTab from "../Tabs/QueryTab"; import QueryTab from "../Tabs/QueryTab";
import QueryTablesTab from "../Tabs/QueryTablesTab"; import QueryTablesTab from "../Tabs/QueryTablesTab";
import SettingsTab from "../Tabs/SettingsTab"; import SettingsTab from "../Tabs/SettingsTab";
import ConflictId from "./ConflictId";
import DocumentId from "./DocumentId";
import StoredProcedure from "./StoredProcedure"; import StoredProcedure from "./StoredProcedure";
import Trigger from "./Trigger"; import Trigger from "./Trigger";
import UserDefinedFunction from "./UserDefinedFunction"; import UserDefinedFunction from "./UserDefinedFunction";
@@ -229,7 +230,9 @@ export default class Collection implements ViewModels.Collection {
this.expandCollection(); this.expandCollection();
} }
this.container.onUpdateTabsButtons([]); this.container.onUpdateTabsButtons([]);
this.refreshActiveTab(); this.container.tabsManager.refreshActiveTab(
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === this.rid
);
} }
public collapseCollection() { public collapseCollection() {
@@ -278,14 +281,15 @@ export default class Collection implements ViewModels.Collection {
dataExplorerArea: Constants.Areas.ResourceTree dataExplorerArea: Constants.Areas.ResourceTree
}); });
// create documents tab if not created yet const documentsTabs: DocumentsTab[] = this.container.tabsManager.getTabs(
const openedTabs = this.container.openedTabs(); 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 if (documentsTab) {
.filter(tab => tab.collection && tab.collection.rid === this.rid) this.container.tabsManager.activateTab(documentsTab);
.filter(tab => tab.tabKind === ViewModels.CollectionTabKind.Documents)[0]; } else {
if (!documentsTab) {
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, { const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseAccountName: this.container.databaseAccount().name, databaseAccountName: this.container.databaseAccount().name,
databaseName: this.databaseId, databaseName: this.databaseId,
@@ -295,6 +299,7 @@ export default class Collection implements ViewModels.Collection {
tabTitle: "Items" tabTitle: "Items"
}); });
this.documentIds([]); this.documentIds([]);
documentsTab = new DocumentsTab({ documentsTab = new DocumentsTab({
partitionKey: this.partitionKey, partitionKey: this.partitionKey,
documentIds: ko.observableArray<DocumentId>([]), documentIds: ko.observableArray<DocumentId>([]),
@@ -309,14 +314,11 @@ export default class Collection implements ViewModels.Collection {
tabPath: `${this.databaseId}>${this.id()}>Documents`, tabPath: `${this.databaseId}>${this.id()}>Documents`,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/documents`, hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/documents`,
onLoadStartKey: startKey, onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons, onUpdateTabsButtons: this.container.onUpdateTabsButtons
openedTabs: this.container.openedTabs()
}); });
this.container.openedTabs.push(documentsTab);
}
// Activate this.container.tabsManager.activateNewTab(documentsTab);
documentsTab.onTabClick(); }
} }
public onConflictsClick() { public onConflictsClick() {
@@ -331,14 +333,15 @@ export default class Collection implements ViewModels.Collection {
dataExplorerArea: Constants.Areas.ResourceTree dataExplorerArea: Constants.Areas.ResourceTree
}); });
// create documents tab if not created yet const conflictsTabs: ConflictsTab[] = this.container.tabsManager.getTabs(
const openedTabs = this.container.openedTabs(); 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 if (conflictsTab) {
.filter(tab => tab.collection && tab.collection.rid === this.rid) this.container.tabsManager.activateTab(conflictsTab);
.filter(tab => tab.tabKind === ViewModels.CollectionTabKind.Conflicts)[0]; } else {
if (!conflictsTab) {
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, { const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseAccountName: this.container.databaseAccount().name, databaseAccountName: this.container.databaseAccount().name,
databaseName: this.databaseId, databaseName: this.databaseId,
@@ -348,7 +351,8 @@ export default class Collection implements ViewModels.Collection {
tabTitle: "Conflicts" tabTitle: "Conflicts"
}); });
this.documentIds([]); this.documentIds([]);
conflictsTab = new ConflictsTab({
const conflictsTab: ConflictsTab = new ConflictsTab({
partitionKey: this.partitionKey, partitionKey: this.partitionKey,
conflictIds: ko.observableArray<ConflictId>([]), conflictIds: ko.observableArray<ConflictId>([]),
tabKind: ViewModels.CollectionTabKind.Conflicts, tabKind: ViewModels.CollectionTabKind.Conflicts,
@@ -362,14 +366,11 @@ export default class Collection implements ViewModels.Collection {
tabPath: `${this.databaseId}>${this.id()}>Conflicts`, tabPath: `${this.databaseId}>${this.id()}>Conflicts`,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/conflicts`, hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/conflicts`,
onLoadStartKey: startKey, onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons, onUpdateTabsButtons: this.container.onUpdateTabsButtons
openedTabs: this.container.openedTabs()
}); });
this.container.openedTabs.push(conflictsTab);
}
// Activate this.container.tabsManager.activateNewTab(conflictsTab);
conflictsTab.onTabClick(); }
} }
public onTableEntitiesClick() { public onTableEntitiesClick() {
@@ -390,14 +391,15 @@ export default class Collection implements ViewModels.Collection {
}); });
} }
// create entities tab if not created yet const queryTablesTabs: QueryTablesTab[] = this.container.tabsManager.getTabs(
const openedTabs = this.container.openedTabs(); 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 if (queryTablesTab) {
.filter(tab => tab.collection && tab.collection.rid === this.rid) this.container.tabsManager.activateTab(queryTablesTab);
.filter(tab => tab.tabKind === ViewModels.CollectionTabKind.QueryTables)[0]; } else {
if (!documentsTab) {
this.documentIds([]); this.documentIds([]);
let title = `Entities`; let title = `Entities`;
if (this.container.isPreferredApiCassandra()) { if (this.container.isPreferredApiCassandra()) {
@@ -411,7 +413,8 @@ export default class Collection implements ViewModels.Collection {
dataExplorerArea: Constants.Areas.Tab, dataExplorerArea: Constants.Areas.Tab,
tabTitle: title tabTitle: title
}); });
documentsTab = new QueryTablesTab({
queryTablesTab = new QueryTablesTab({
tabKind: ViewModels.CollectionTabKind.QueryTables, tabKind: ViewModels.CollectionTabKind.QueryTables,
title: title, title: title,
tabPath: "", tabPath: "",
@@ -423,14 +426,11 @@ export default class Collection implements ViewModels.Collection {
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/entities`, hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/entities`,
isActive: ko.observable(false), isActive: ko.observable(false),
onLoadStartKey: startKey, onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons, onUpdateTabsButtons: this.container.onUpdateTabsButtons
openedTabs: this.container.openedTabs()
}); });
this.container.openedTabs.push(documentsTab);
}
// Activate this.container.tabsManager.activateNewTab(queryTablesTab);
documentsTab.onTabClick(); }
} }
public onGraphDocumentsClick() { public onGraphDocumentsClick() {
@@ -445,55 +445,50 @@ export default class Collection implements ViewModels.Collection {
dataExplorerArea: Constants.Areas.ResourceTree dataExplorerArea: Constants.Areas.ResourceTree
}); });
// create documents tab if not created yet const graphTabs: GraphTab[] = this.container.tabsManager.getTabs(
const openedTabs = this.container.openedTabs(); 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 if (graphTab) {
.filter(tab => tab.collection && tab.collection.rid === this.rid) this.container.tabsManager.activateTab(graphTab);
.filter(tab => tab.tabKind === ViewModels.CollectionTabKind.Graph)[0]; } else {
if (!documentsTab) {
this.documentIds([]); this.documentIds([]);
documentsTab = this._createGraphTab("Graph"); const title = "Graph";
this.container.openedTabs.push(documentsTab); 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 = () => { public onMongoDBDocumentsClick = () => {
this.container.selectedNode(this); this.container.selectedNode(this);
this.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents); this.selectedSubnodeKind(ViewModels.CollectionTabKind.Documents);
@@ -506,14 +501,15 @@ export default class Collection implements ViewModels.Collection {
dataExplorerArea: Constants.Areas.ResourceTree dataExplorerArea: Constants.Areas.ResourceTree
}); });
// create documents tab if not created yet const mongoDocumentsTabs: MongoDocumentsTab[] = this.container.tabsManager.getTabs(
const openedTabs = this.container.openedTabs(); 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 if (mongoDocumentsTab) {
.filter(tab => tab.collection && tab.collection && tab.collection.rid === this.rid) this.container.tabsManager.activateTab(mongoDocumentsTab);
.filter(tab => tab.tabKind === ViewModels.CollectionTabKind.Documents)[0]; } else {
if (!documentsTab) {
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, { const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseAccountName: this.container.databaseAccount().name, databaseAccountName: this.container.databaseAccount().name,
databaseName: this.databaseId, databaseName: this.databaseId,
@@ -523,7 +519,8 @@ export default class Collection implements ViewModels.Collection {
tabTitle: "Documents" tabTitle: "Documents"
}); });
this.documentIds([]); this.documentIds([]);
documentsTab = new MongoDocumentsTab({
mongoDocumentsTab = new MongoDocumentsTab({
partitionKey: this.partitionKey, partitionKey: this.partitionKey,
documentIds: this.documentIds, documentIds: this.documentIds,
tabKind: ViewModels.CollectionTabKind.Documents, tabKind: ViewModels.CollectionTabKind.Documents,
@@ -537,14 +534,10 @@ export default class Collection implements ViewModels.Collection {
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoDocuments`, hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoDocuments`,
isActive: ko.observable(false), isActive: ko.observable(false),
onLoadStartKey: startKey, onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons, onUpdateTabsButtons: this.container.onUpdateTabsButtons
openedTabs: this.container.openedTabs()
}); });
this.container.openedTabs.push(documentsTab); this.container.tabsManager.activateNewTab(mongoDocumentsTab);
} }
// Activate
documentsTab.onTabClick();
}; };
public onSettingsClick = () => { public onSettingsClick = () => {
@@ -559,14 +552,15 @@ export default class Collection implements ViewModels.Collection {
dataExplorerArea: Constants.Areas.ResourceTree 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 tabTitle = "Scale & Settings";
const pendingNotificationsPromise: Q.Promise<DataModels.Notification> = this._getPendingThroughputSplitNotification(); 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) { if (!settingsTab) {
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, { const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseAccountName: this.container.databaseAccount().name, 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`, hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/settings`,
isActive: ko.observable(false), isActive: ko.observable(false),
onLoadStartKey: startKey, onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons, onUpdateTabsButtons: this.container.onUpdateTabsButtons
openedTabs: this.container.openedTabs()
}); });
(settingsTab as ViewModels.SettingsTab).pendingNotification(pendingNotification); this.container.tabsManager.activateNewTab(settingsTab);
this.container.openedTabs.push(settingsTab); settingsTab.pendingNotification(pendingNotification);
settingsTab.onTabClick(); // Activate
}, },
(error: any) => { (error: any) => {
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
@@ -623,12 +615,12 @@ export default class Collection implements ViewModels.Collection {
} else { } else {
pendingNotificationsPromise.then( pendingNotificationsPromise.then(
(pendingNotification: DataModels.Notification) => { (pendingNotification: DataModels.Notification) => {
(settingsTab as ViewModels.SettingsTab).pendingNotification(pendingNotification); settingsTab.pendingNotification(pendingNotification);
settingsTab.onTabClick(); this.container.tabsManager.activateTab(settingsTab);
}, },
(error: any) => { (error: any) => {
(settingsTab as ViewModels.SettingsTab).pendingNotification(undefined); settingsTab.pendingNotification(undefined);
settingsTab.onTabClick(); this.container.tabsManager.activateTab(settingsTab);
} }
); );
} }
@@ -738,9 +730,7 @@ export default class Collection implements ViewModels.Collection {
public onNewQueryClick(source: any, event: MouseEvent, queryText?: string) { public onNewQueryClick(source: any, event: MouseEvent, queryText?: string) {
const collection: ViewModels.Collection = source.collection || source; const collection: ViewModels.Collection = source.collection || source;
const explorer: ViewModels.Explorer = source.container; const id = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Query).length + 1;
const openedTabs = explorer.openedTabs();
const id = openedTabs.filter(t => t.tabKind === ViewModels.CollectionTabKind.Query).length + 1;
const title = "Query " + id; const title = "Query " + id;
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, { const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseAccountName: this.container.databaseAccount().name, databaseAccountName: this.container.databaseAccount().name,
@@ -751,7 +741,7 @@ export default class Collection implements ViewModels.Collection {
tabTitle: title tabTitle: title
}); });
let queryTab: ViewModels.Tab = new QueryTab({ const queryTab: QueryTab = new QueryTab({
tabKind: ViewModels.CollectionTabKind.Query, tabKind: ViewModels.CollectionTabKind.Query,
title: title, title: title,
tabPath: "", tabPath: "",
@@ -764,20 +754,15 @@ export default class Collection implements ViewModels.Collection {
queryText: queryText, queryText: queryText,
partitionKey: collection.partitionKey, partitionKey: collection.partitionKey,
onLoadStartKey: startKey, onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons, onUpdateTabsButtons: this.container.onUpdateTabsButtons
openedTabs: this.container.openedTabs()
}); });
this.container.openedTabs.push(queryTab);
// Activate this.container.tabsManager.activateNewTab(queryTab);
queryTab.onTabClick();
} }
public onNewMongoQueryClick(source: any, event: MouseEvent, queryText?: string) { public onNewMongoQueryClick(source: any, event: MouseEvent, queryText?: string) {
const collection: ViewModels.Collection = source.collection || source; const collection: ViewModels.Collection = source.collection || source;
const explorer: ViewModels.Explorer = source.container; const id = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Query).length + 1;
const openedTabs = explorer.openedTabs();
const id = openedTabs.filter(t => t.tabKind === ViewModels.CollectionTabKind.Query).length + 1;
const title = "Query " + id; const title = "Query " + id;
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, { const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
@@ -789,7 +774,7 @@ export default class Collection implements ViewModels.Collection {
tabTitle: title tabTitle: title
}); });
let queryTab: ViewModels.Tab = new MongoQueryTab({ const mongoQueryTab: MongoQueryTab = new MongoQueryTab({
tabKind: ViewModels.CollectionTabKind.Query, tabKind: ViewModels.CollectionTabKind.Query,
title: title, title: title,
tabPath: "", tabPath: "",
@@ -801,26 +786,51 @@ export default class Collection implements ViewModels.Collection {
isActive: ko.observable(false), isActive: ko.observable(false),
partitionKey: collection.partitionKey, partitionKey: collection.partitionKey,
onLoadStartKey: startKey, onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons, onUpdateTabsButtons: this.container.onUpdateTabsButtons
openedTabs: this.container.openedTabs()
}); });
this.container.openedTabs.push(queryTab);
// Activate this.container.tabsManager.activateNewTab(mongoQueryTab);
queryTab.onTabClick();
} }
public onNewGraphClick() { public onNewGraphClick() {
var id = this.container.openedTabs().filter(t => t.tabKind === ViewModels.CollectionTabKind.Graph).length + 1; const id: number = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Graph).length + 1;
var graphTab = this._createGraphTab("Graph Query " + id); const title: string = "Graph Query " + id;
this.container.openedTabs.push(graphTab);
// Activate const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
graphTab.onTabClick(); 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() { public onNewMongoShellClick() {
var id = this.container.openedTabs().filter(t => t.tabKind === ViewModels.CollectionTabKind.MongoShell).length + 1; const id = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.MongoShell).length + 1;
var mongoShellTab = new MongoShellTab({ const mongoShellTab: MongoShellTab = new MongoShellTab({
tabKind: ViewModels.CollectionTabKind.MongoShell, tabKind: ViewModels.CollectionTabKind.MongoShell,
title: "Shell " + id, title: "Shell " + id,
tabPath: "", tabPath: "",
@@ -830,14 +840,10 @@ export default class Collection implements ViewModels.Collection {
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoShell`, hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/mongoShell`,
selfLink: this.self, selfLink: this.self,
isActive: ko.observable(false), isActive: ko.observable(false),
onUpdateTabsButtons: this.container.onUpdateTabsButtons, onUpdateTabsButtons: this.container.onUpdateTabsButtons
openedTabs: this.container.openedTabs()
}); });
this.container.openedTabs.push(mongoShellTab); this.container.tabsManager.activateNewTab(mongoShellTab);
// Activate
mongoShellTab.onTabClick();
} }
public onNewStoredProcedureClick(source: ViewModels.Collection, event: MouseEvent) { public onNewStoredProcedureClick(source: ViewModels.Collection, event: MouseEvent) {
@@ -898,7 +904,9 @@ export default class Collection implements ViewModels.Collection {
} else { } else {
this.expandStoredProcedures(); this.expandStoredProcedures();
} }
this.refreshActiveTab(); this.container.tabsManager.refreshActiveTab(
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === this.rid
);
} }
public expandStoredProcedures() { public expandStoredProcedures() {
@@ -955,7 +963,9 @@ export default class Collection implements ViewModels.Collection {
} else { } else {
this.expandUserDefinedFunctions(); this.expandUserDefinedFunctions();
} }
this.refreshActiveTab(); this.container.tabsManager.refreshActiveTab(
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === this.rid
);
} }
public expandUserDefinedFunctions() { public expandUserDefinedFunctions() {
@@ -1012,7 +1022,9 @@ export default class Collection implements ViewModels.Collection {
} else { } else {
this.expandTriggers(); this.expandTriggers();
} }
this.refreshActiveTab(); this.container.tabsManager.refreshActiveTab(
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === this.rid
);
} }
public expandTriggers() { 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 { protected _getOfferForCollection(offers: DataModels.Offer[], collection: DataModels.Collection): DataModels.Offer {
return _.find(offers, (offer: DataModels.Offer) => offer.resource.indexOf(collection._rid) >= 0); 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 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 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) { if (!settingsTab) {
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, { const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseAccountName: this.container.databaseAccount().name, databaseAccountName: this.container.databaseAccount().name,
@@ -78,12 +78,11 @@ export default class Database implements ViewModels.Database {
selfLink: this.self, selfLink: this.self,
isActive: ko.observable(false), isActive: ko.observable(false),
onLoadStartKey: startKey, onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons, onUpdateTabsButtons: this.container.onUpdateTabsButtons
openedTabs: this.container.openedTabs()
}); });
(settingsTab as ViewModels.DatabaseSettingsTab).pendingNotification(pendingNotification);
this.container.openedTabs.push(settingsTab); settingsTab.pendingNotification(pendingNotification);
settingsTab.onTabClick(); // Activate this.container.tabsManager.activateNewTab(settingsTab);
}, },
(error: any) => { (error: any) => {
TelemetryProcessor.traceFailure( TelemetryProcessor.traceFailure(
@@ -109,12 +108,12 @@ export default class Database implements ViewModels.Database {
} else { } else {
pendingNotificationsPromise.then( pendingNotificationsPromise.then(
(pendingNotification: DataModels.Notification) => { (pendingNotification: DataModels.Notification) => {
(settingsTab as ViewModels.DatabaseSettingsTab).pendingNotification(pendingNotification); settingsTab.pendingNotification(pendingNotification);
settingsTab.onTabClick(); this.container.tabsManager.activateTab(settingsTab);
}, },
(error: any) => { (error: any) => {
(settingsTab as ViewModels.DatabaseSettingsTab).pendingNotification(undefined); settingsTab.pendingNotification(undefined);
settingsTab.onTabClick(); this.container.tabsManager.activateTab(settingsTab);
} }
); );
} }
@@ -221,7 +220,9 @@ export default class Database implements ViewModels.Database {
this.expandDatabase(); this.expandDatabase();
} }
this.container.onUpdateTabsButtons([]); this.container.onUpdateTabsButtons([]);
this.refreshTabSelectedState(); this.container.tabsManager.refreshActiveTab(
(tab: ViewModels.Tab) => tab.collection && tab.collection.getDatabase().rid === this.rid
);
} }
public expandDatabase() { public expandDatabase() {
@@ -286,18 +287,6 @@ export default class Database implements ViewModels.Database {
database.container.addCollectionPane.open(); 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 { public findCollectionWithId(collectionId: string): ViewModels.Collection {
return _.find(this.collections(), (collection: ViewModels.Collection) => collection.id() === collectionId); 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) { public onNewQueryClick(source: any, event: MouseEvent, queryText?: string) {
const collection: ViewModels.Collection = source.collection || source; const collection: ViewModels.Collection = source.collection || source;
const explorer: ViewModels.Explorer = source.container; const id = this.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Query).length + 1;
const openedTabs = explorer.openedTabs();
const id = openedTabs.filter(t => t.tabKind === ViewModels.CollectionTabKind.Query).length + 1;
const title = "Query " + id; const title = "Query " + id;
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, { const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseAccountName: this.container.databaseAccount().name, databaseAccountName: this.container.databaseAccount().name,
@@ -101,7 +86,7 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
tabTitle: title tabTitle: title
}); });
let queryTab: ViewModels.Tab = new QueryTab({ const queryTab: QueryTab = new QueryTab({
tabKind: ViewModels.CollectionTabKind.Query, tabKind: ViewModels.CollectionTabKind.Query,
title: title, title: title,
tabPath: "", tabPath: "",
@@ -115,13 +100,10 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
partitionKey: collection.partitionKey, partitionKey: collection.partitionKey,
resourceTokenPartitionKey: this.container.resourceTokenPartitionKey(), resourceTokenPartitionKey: this.container.resourceTokenPartitionKey(),
onLoadStartKey: startKey, onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons, onUpdateTabsButtons: this.container.onUpdateTabsButtons
openedTabs: this.container.openedTabs()
}); });
this.container.openedTabs.push(queryTab);
// Activate this.container.tabsManager.activateNewTab(queryTab);
queryTab.onTabClick();
} }
public onDocumentDBDocumentsClick() { public onDocumentDBDocumentsClick() {
@@ -136,14 +118,15 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
dataExplorerArea: Constants.Areas.ResourceTree dataExplorerArea: Constants.Areas.ResourceTree
}); });
// create documents tab if not created yet const documentsTabs: DocumentsTab[] = this.container.tabsManager.getTabs(
const openedTabs = this.container.openedTabs(); 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 if (documentsTab) {
.filter(tab => tab.collection && tab.collection.rid === this.rid) this.container.tabsManager.activateTab(documentsTab);
.filter(tab => tab.tabKind === ViewModels.CollectionTabKind.Documents)[0]; } else {
if (!documentsTab) {
const startKey: number = TelemetryProcessor.traceStart(Action.Tab, { const startKey: number = TelemetryProcessor.traceStart(Action.Tab, {
databaseAccountName: this.container.databaseAccount() && this.container.databaseAccount().name, databaseAccountName: this.container.databaseAccount() && this.container.databaseAccount().name,
databaseName: this.databaseId, databaseName: this.databaseId,
@@ -152,6 +135,7 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
dataExplorerArea: Constants.Areas.Tab, dataExplorerArea: Constants.Areas.Tab,
tabTitle: "Items" tabTitle: "Items"
}); });
documentsTab = new DocumentsTab({ documentsTab = new DocumentsTab({
partitionKey: this.partitionKey, partitionKey: this.partitionKey,
resourceTokenPartitionKey: this.container.resourceTokenPartitionKey(), resourceTokenPartitionKey: this.container.resourceTokenPartitionKey(),
@@ -167,14 +151,11 @@ export default class ResourceTokenCollection implements ViewModels.CollectionBas
tabPath: `${this.databaseId}>${this.id()}>Documents`, tabPath: `${this.databaseId}>${this.id()}>Documents`,
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/documents`, hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(this.databaseId, this.id())}/documents`,
onLoadStartKey: startKey, onLoadStartKey: startKey,
onUpdateTabsButtons: this.container.onUpdateTabsButtons, onUpdateTabsButtons: this.container.onUpdateTabsButtons
openedTabs: this.container.openedTabs()
}); });
this.container.openedTabs.push(documentsTab);
}
// Activate this.container.tabsManager.activateNewTab(documentsTab);
documentsTab.onTabClick(); }
} }
public getDatabase(): ViewModels.Database { public getDatabase(): ViewModels.Database {

View File

@@ -46,7 +46,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
this.parameters = ko.observable(Date.now()); this.parameters = ko.observable(Date.now());
this.container.selectedNode.subscribe((newValue: any) => this.triggerRender()); this.container.selectedNode.subscribe((newValue: any) => this.triggerRender());
this.container.activeTab.subscribe((newValue: ViewModels.Tab) => this.triggerRender()); this.container.tabsManager.activeTab.subscribe((newValue: ViewModels.Tab) => this.triggerRender());
this.container.isNotebookEnabled.subscribe(newValue => this.triggerRender()); this.container.isNotebookEnabled.subscribe(newValue => this.triggerRender());
this.koSubsDatabaseIdMap = new ArrayHashMap(); this.koSubsDatabaseIdMap = new ArrayHashMap();
@@ -171,7 +171,9 @@ export class ResourceTreeAdapter implements ReactAdapter {
} }
database.selectDatabase(); database.selectDatabase();
this.container.onUpdateTabsButtons([]); this.container.onUpdateTabsButtons([]);
database.refreshTabSelectedState(); this.container.tabsManager.refreshActiveTab(
(tab: ViewModels.Tab) => tab.collection && tab.collection.getDatabase().rid === database.rid
);
}, },
onContextMenuOpen: () => this.container.selectedNode(database) onContextMenuOpen: () => this.container.selectedNode(database)
}; };
@@ -268,7 +270,9 @@ export class ResourceTreeAdapter implements ReactAdapter {
// Rewritten version of expandCollapseCollection // Rewritten version of expandCollapseCollection
this.container.selectedNode(collection); this.container.selectedNode(collection);
this.container.onUpdateTabsButtons([]); this.container.onUpdateTabsButtons([]);
collection.refreshActiveTab(); this.container.tabsManager.refreshActiveTab(
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === collection.rid
);
}, },
onExpanded: () => { onExpanded: () => {
if (ResourceTreeAdapter.showScriptNodes(this.container)) { if (ResourceTreeAdapter.showScriptNodes(this.container)) {
@@ -294,7 +298,9 @@ export class ResourceTreeAdapter implements ReactAdapter {
})), })),
onClick: () => { onClick: () => {
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.StoredProcedures); collection.selectedSubnodeKind(ViewModels.CollectionTabKind.StoredProcedures);
collection.refreshActiveTab(); this.container.tabsManager.refreshActiveTab(
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === collection.rid
);
} }
}; };
} }
@@ -311,7 +317,9 @@ export class ResourceTreeAdapter implements ReactAdapter {
})), })),
onClick: () => { onClick: () => {
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.UserDefinedFunctions); collection.selectedSubnodeKind(ViewModels.CollectionTabKind.UserDefinedFunctions);
collection.refreshActiveTab(); this.container.tabsManager.refreshActiveTab(
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === collection.rid
);
} }
}; };
} }
@@ -327,7 +335,9 @@ export class ResourceTreeAdapter implements ReactAdapter {
})), })),
onClick: () => { onClick: () => {
collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Triggers); collection.selectedSubnodeKind(ViewModels.CollectionTabKind.Triggers);
collection.refreshActiveTab(); this.container.tabsManager.refreshActiveTab(
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === collection.rid
);
} }
}; };
} }
@@ -409,7 +419,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
className: "notebookHeader galleryHeader", className: "notebookHeader galleryHeader",
onClick: () => this.container.openGallery(), onClick: () => this.container.openGallery(),
isSelected: () => { isSelected: () => {
const activeTab = this.container.findActiveTab(); const activeTab = this.container.tabsManager.activeTab();
return activeTab && activeTab.tabKind === ViewModels.CollectionTabKind.Gallery; return activeTab && activeTab.tabKind === ViewModels.CollectionTabKind.Gallery;
} }
}; };
@@ -517,7 +527,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
className: "notebookHeader", className: "notebookHeader",
onClick: () => onFileClick(item), onClick: () => onFileClick(item),
isSelected: () => { isSelected: () => {
const activeTab = this.container.findActiveTab(); const activeTab = this.container.tabsManager.activeTab();
return ( return (
activeTab && activeTab &&
activeTab.tabKind === ViewModels.CollectionTabKind.NotebookV2 && activeTab.tabKind === ViewModels.CollectionTabKind.NotebookV2 &&
@@ -634,7 +644,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
} }
}, },
isSelected: () => { isSelected: () => {
const activeTab = this.container.findActiveTab(); const activeTab = this.container.tabsManager.activeTab();
return ( return (
activeTab && activeTab &&
activeTab.tabKind === ViewModels.CollectionTabKind.NotebookV2 && activeTab.tabKind === ViewModels.CollectionTabKind.NotebookV2 &&
@@ -657,14 +667,6 @@ export class ResourceTreeAdapter implements ReactAdapter {
window.requestAnimationFrame(() => this.parameters(Date.now())); window.requestAnimationFrame(() => this.parameters(Date.now()));
} }
private getActiveTab(): ViewModels.Tab {
const activeTabs: ViewModels.Tab[] = this.container.openedTabs().filter((tab: ViewModels.Tab) => tab.isActive());
if (activeTabs.length) {
return activeTabs[0];
}
return undefined;
}
private isDataNodeSelected(rid: string, nodeKind: string, subnodeKind: ViewModels.CollectionTabKind): boolean { private isDataNodeSelected(rid: string, nodeKind: string, subnodeKind: ViewModels.CollectionTabKind): boolean {
if (!this.container.selectedNode || !this.container.selectedNode()) { if (!this.container.selectedNode || !this.container.selectedNode()) {
return false; return false;
@@ -674,7 +676,7 @@ export class ResourceTreeAdapter implements ReactAdapter {
if (subnodeKind === undefined) { if (subnodeKind === undefined) {
return selectedNode.rid === rid && selectedNode.nodeKind === nodeKind; return selectedNode.rid === rid && selectedNode.nodeKind === nodeKind;
} else { } else {
const activeTab = this.getActiveTab(); const activeTab = this.container.tabsManager.activeTab();
let selectedSubnodeKind; let selectedSubnodeKind;
if (nodeKind === "Database" && (selectedNode as ViewModels.Database).selectedSubnodeKind) { if (nodeKind === "Database" && (selectedNode as ViewModels.Database).selectedSubnodeKind) {
selectedSubnodeKind = (selectedNode as ViewModels.Database).selectedSubnodeKind(); selectedSubnodeKind = (selectedNode as ViewModels.Database).selectedSubnodeKind();

View File

@@ -12,9 +12,7 @@ const createMockContainer = (): ViewModels.Explorer => {
let mockContainer = {} as ViewModels.Explorer; let mockContainer = {} as ViewModels.Explorer;
mockContainer.resourceTokenCollection = createMockCollection(mockContainer); mockContainer.resourceTokenCollection = createMockCollection(mockContainer);
mockContainer.selectedNode = ko.observable<ViewModels.TreeNode>(); mockContainer.selectedNode = ko.observable<ViewModels.TreeNode>();
mockContainer.activeTab = ko.observable<ViewModels.Tab>();
mockContainer.mostRecentActivity = new MostRecentActivity.MostRecentActivity(mockContainer); mockContainer.mostRecentActivity = new MostRecentActivity.MostRecentActivity(mockContainer);
mockContainer.openedTabs = ko.observableArray<ViewModels.Tab>([]);
mockContainer.onUpdateTabsButtons = () => {}; mockContainer.onUpdateTabsButtons = () => {};
return mockContainer; return mockContainer;

View File

@@ -17,7 +17,8 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
this.container.resourceTokenCollection.subscribe((collection: ViewModels.CollectionBase) => this.triggerRender()); this.container.resourceTokenCollection.subscribe((collection: ViewModels.CollectionBase) => this.triggerRender());
this.container.selectedNode.subscribe((newValue: any) => this.triggerRender()); this.container.selectedNode.subscribe((newValue: any) => this.triggerRender());
this.container.activeTab.subscribe((newValue: ViewModels.Tab) => this.triggerRender()); this.container.tabsManager &&
this.container.tabsManager.activeTab.subscribe((newValue: ViewModels.Tab) => this.triggerRender());
this.triggerRender(); this.triggerRender();
} }
@@ -63,7 +64,9 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
// Rewritten version of expandCollapseCollection // Rewritten version of expandCollapseCollection
this.container.selectedNode(collection); this.container.selectedNode(collection);
this.container.onUpdateTabsButtons([]); this.container.onUpdateTabsButtons([]);
collection.refreshActiveTab(); this.container.tabsManager.refreshActiveTab(
(tab: ViewModels.Tab) => tab.collection && tab.collection.rid === collection.rid
);
}, },
isSelected: () => this.isDataNodeSelected(collection.rid, "Collection", undefined) isSelected: () => this.isDataNodeSelected(collection.rid, "Collection", undefined)
}; };
@@ -75,14 +78,6 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
}; };
} }
private getActiveTab(): ViewModels.Tab {
const activeTabs: ViewModels.Tab[] = this.container.openedTabs().filter((tab: ViewModels.Tab) => tab.isActive());
if (activeTabs.length) {
return activeTabs[0];
}
return undefined;
}
private isDataNodeSelected(rid: string, nodeKind: string, subnodeKind: ViewModels.CollectionTabKind): boolean { private isDataNodeSelected(rid: string, nodeKind: string, subnodeKind: ViewModels.CollectionTabKind): boolean {
if (!this.container.selectedNode || !this.container.selectedNode()) { if (!this.container.selectedNode || !this.container.selectedNode()) {
return false; return false;
@@ -92,7 +87,7 @@ export class ResourceTreeAdapterForResourceToken implements ReactAdapter {
if (subnodeKind) { if (subnodeKind) {
return selectedNode.rid === rid && selectedNode.nodeKind === nodeKind; return selectedNode.rid === rid && selectedNode.nodeKind === nodeKind;
} else { } else {
const activeTab = this.getActiveTab(); const activeTab = this.container.tabsManager.activeTab();
let selectedSubnodeKind; let selectedSubnodeKind;
if (nodeKind === "Database" && (selectedNode as ViewModels.Database).selectedSubnodeKind) { if (nodeKind === "Database" && (selectedNode as ViewModels.Database).selectedSubnodeKind) {
selectedSubnodeKind = (selectedNode as ViewModels.Database).selectedSubnodeKind(); selectedSubnodeKind = (selectedNode as ViewModels.Database).selectedSubnodeKind();

View File

@@ -56,15 +56,13 @@ export default class StoredProcedure implements ViewModels.StoredProcedure {
} }
public static create(source: ViewModels.Collection, event: MouseEvent) { public static create(source: ViewModels.Collection, event: MouseEvent) {
const openedTabs = source.container.openedTabs(); const id = source.container.tabsManager.getTabs(ViewModels.CollectionTabKind.StoredProcedures).length + 1;
const id =
openedTabs.filter((tab: ViewModels.Tab) => tab.tabKind === ViewModels.CollectionTabKind.StoredProcedures).length +
1;
const storedProcedure = <DataModels.StoredProcedure>{ const storedProcedure = <DataModels.StoredProcedure>{
id: "", id: "",
body: sampleStoredProcedureBody body: sampleStoredProcedureBody
}; };
let storedProcedureTab: ViewModels.Tab = new StoredProcedureTab({
const storedProcedureTab: StoredProcedureTab = new StoredProcedureTab({
resource: storedProcedure, resource: storedProcedure,
isNew: true, isNew: true,
tabKind: ViewModels.CollectionTabKind.StoredProcedures, tabKind: ViewModels.CollectionTabKind.StoredProcedures,
@@ -76,13 +74,10 @@ export default class StoredProcedure implements ViewModels.StoredProcedure {
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/sproc`, hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/sproc`,
selfLink: "", selfLink: "",
isActive: ko.observable(false), isActive: ko.observable(false),
onUpdateTabsButtons: source.container.onUpdateTabsButtons, onUpdateTabsButtons: source.container.onUpdateTabsButtons
openedTabs: source.container.openedTabs()
}); });
source.container.openedTabs.push(storedProcedureTab);
// Activate source.container.tabsManager.activateNewTab(storedProcedureTab);
storedProcedureTab.onTabClick();
} }
public select() { public select() {
@@ -98,15 +93,15 @@ export default class StoredProcedure implements ViewModels.StoredProcedure {
public open = () => { public open = () => {
this.select(); this.select();
const openedTabs = this.container.openedTabs(); const storedProcedureTabs: StoredProcedureTab[] = this.container.tabsManager.getTabs(
const storedProcedureTabsOpen: ViewModels.Tab[] = ViewModels.CollectionTabKind.StoredProcedures,
openedTabs && (tab: ViewModels.Tab) => tab.node && tab.node.rid === this.rid
openedTabs.filter( ) as StoredProcedureTab[];
tab => tab.node && tab.node.rid === this.rid && tab.tabKind === ViewModels.CollectionTabKind.StoredProcedures let storedProcedureTab: StoredProcedureTab = storedProcedureTabs && storedProcedureTabs[0];
);
let storedProcedureTab: ViewModels.Tab = if (storedProcedureTab) {
storedProcedureTabsOpen && storedProcedureTabsOpen.length > 0 && storedProcedureTabsOpen[0]; this.container.tabsManager.activateTab(storedProcedureTab);
if (!storedProcedureTab) { } else {
const storedProcedureData = <DataModels.StoredProcedure>{ const storedProcedureData = <DataModels.StoredProcedure>{
_rid: this.rid, _rid: this.rid,
_self: this.self, _self: this.self,
@@ -129,14 +124,11 @@ export default class StoredProcedure implements ViewModels.StoredProcedure {
)}/sprocs/${this.id()}`, )}/sprocs/${this.id()}`,
selfLink: this.self, selfLink: this.self,
isActive: ko.observable(false), isActive: ko.observable(false),
onUpdateTabsButtons: this.container.onUpdateTabsButtons, onUpdateTabsButtons: this.container.onUpdateTabsButtons
openedTabs: this.container.openedTabs()
}); });
this.container.openedTabs.push(storedProcedureTab);
}
// Activate this.container.tabsManager.activateNewTab(storedProcedureTab);
storedProcedureTab.onTabClick(); }
}; };
public delete() { public delete() {
@@ -153,7 +145,9 @@ export default class StoredProcedure implements ViewModels.StoredProcedure {
this.container.documentClientUtility.deleteStoredProcedure(this.collection, storedProcedureData).then( this.container.documentClientUtility.deleteStoredProcedure(this.collection, storedProcedureData).then(
() => { () => {
this.container.openedTabs.remove((tab: ViewModels.Tab) => tab.node && tab.node.rid === this.rid); this.container.tabsManager.removeTabByComparator(
(tab: ViewModels.Tab) => tab.node && tab.node.rid === this.rid
);
this.collection.children.remove(this); this.collection.children.remove(this);
}, },
reason => {} reason => {}
@@ -161,7 +155,11 @@ export default class StoredProcedure implements ViewModels.StoredProcedure {
} }
public execute(params: string[], partitionKeyValue?: string): void { public execute(params: string[], partitionKeyValue?: string): void {
const sprocTab: ViewModels.StoredProcedureTab = this._getCurrentStoredProcedureTab(); const sprocTabs: ViewModels.StoredProcedureTab[] = this.container.tabsManager.getTabs(
ViewModels.CollectionTabKind.StoredProcedures,
(tab: ViewModels.Tab) => tab.node && tab.node.rid === this.rid
) as ViewModels.StoredProcedureTab[];
const sprocTab: ViewModels.StoredProcedureTab = sprocTabs && sprocTabs.length > 0 && sprocTabs[0];
sprocTab.isExecuting(true); sprocTab.isExecuting(true);
this.container && this.container &&
this.container.documentClientUtility this.container.documentClientUtility
@@ -184,17 +182,4 @@ export default class StoredProcedure implements ViewModels.StoredProcedure {
const focusElement = document.getElementById("execute-storedproc-toggles"); const focusElement = document.getElementById("execute-storedproc-toggles");
focusElement && focusElement.focus(); focusElement && focusElement.focus();
} }
private _getCurrentStoredProcedureTab(): ViewModels.StoredProcedureTab {
const openedTabs = this.container.openedTabs();
const storedProcedureTabsOpen: ViewModels.Tab[] =
openedTabs &&
openedTabs.filter(
tab => tab.node && tab.node.rid === this.rid && tab.tabKind === ViewModels.CollectionTabKind.StoredProcedures
);
return (storedProcedureTabsOpen &&
storedProcedureTabsOpen.length > 0 &&
storedProcedureTabsOpen[0]) as ViewModels.StoredProcedureTab;
}
} }

View File

@@ -3,7 +3,6 @@ import * as ViewModels from "../../Contracts/ViewModels";
import * as Constants from "../../Common/Constants"; import * as Constants from "../../Common/Constants";
import * as DataModels from "../../Contracts/DataModels"; import * as DataModels from "../../Contracts/DataModels";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import Collection from "./Collection";
import TriggerTab from "../Tabs/TriggerTab"; import TriggerTab from "../Tabs/TriggerTab";
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
@@ -41,10 +40,7 @@ export default class Trigger implements ViewModels.Trigger {
} }
public static create(source: ViewModels.Collection, event: MouseEvent) { public static create(source: ViewModels.Collection, event: MouseEvent) {
const id = const id = source.container.tabsManager.getTabs(ViewModels.CollectionTabKind.Triggers).length + 1;
source.container
.openedTabs()
.filter((tab: ViewModels.Tab) => tab.tabKind === ViewModels.CollectionTabKind.Triggers).length + 1;
const trigger = <DataModels.Trigger>{ const trigger = <DataModels.Trigger>{
id: "", id: "",
body: "function trigger(){}", body: "function trigger(){}",
@@ -52,7 +48,7 @@ export default class Trigger implements ViewModels.Trigger {
triggerType: "Pre" triggerType: "Pre"
}; };
let triggerTab: ViewModels.Tab = new TriggerTab({ const triggerTab: TriggerTab = new TriggerTab({
resource: trigger, resource: trigger,
isNew: true, isNew: true,
tabKind: ViewModels.CollectionTabKind.Triggers, tabKind: ViewModels.CollectionTabKind.Triggers,
@@ -64,23 +60,24 @@ export default class Trigger implements ViewModels.Trigger {
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/trigger`, hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/trigger`,
selfLink: "", selfLink: "",
isActive: ko.observable(false), isActive: ko.observable(false),
onUpdateTabsButtons: source.container.onUpdateTabsButtons, onUpdateTabsButtons: source.container.onUpdateTabsButtons
openedTabs: source.container.openedTabs()
}); });
source.container.openedTabs.push(triggerTab); source.container.tabsManager.activateNewTab(triggerTab);
// Activate
triggerTab.onTabClick();
} }
public open = () => { public open = () => {
this.select(); this.select();
let triggerTab: ViewModels.Tab = this.container const triggerTabs: TriggerTab[] = this.container.tabsManager.getTabs(
.openedTabs() ViewModels.CollectionTabKind.Triggers,
.filter(tab => tab.node && tab.node.rid === this.rid)[0]; (tab: ViewModels.Tab) => tab.node && tab.node.rid === this.rid
if (!triggerTab) { ) as TriggerTab[];
let triggerTab: TriggerTab = triggerTabs && triggerTabs[0];
if (triggerTab) {
this.container.tabsManager.activateTab(triggerTab);
} else {
const triggerData = <DataModels.Trigger>{ const triggerData = <DataModels.Trigger>{
_rid: this.rid, _rid: this.rid,
_self: this.self, _self: this.self,
@@ -105,15 +102,11 @@ export default class Trigger implements ViewModels.Trigger {
)}/triggers/${this.id()}`, )}/triggers/${this.id()}`,
selfLink: "", selfLink: "",
isActive: ko.observable(false), isActive: ko.observable(false),
onUpdateTabsButtons: this.container.onUpdateTabsButtons, onUpdateTabsButtons: this.container.onUpdateTabsButtons
openedTabs: this.container.openedTabs()
}); });
this.container.openedTabs.push(triggerTab); this.container.tabsManager.activateNewTab(triggerTab);
} }
// Activate
triggerTab.onTabClick();
}; };
public delete() { public delete() {
@@ -132,7 +125,9 @@ export default class Trigger implements ViewModels.Trigger {
this.container.documentClientUtility.deleteTrigger(this.collection, triggerData).then( this.container.documentClientUtility.deleteTrigger(this.collection, triggerData).then(
() => { () => {
this.container.openedTabs.remove((tab: ViewModels.Tab) => tab.node && tab.node.rid === this.rid); this.container.tabsManager.removeTabByComparator(
(tab: ViewModels.Tab) => tab.node && tab.node.rid === this.rid
);
this.collection.children.remove(this); this.collection.children.remove(this);
}, },
reason => {} reason => {}

View File

@@ -5,7 +5,6 @@ import * as DataModels from "../../Contracts/DataModels";
import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants"; import { Action, ActionModifiers } from "../../Shared/Telemetry/TelemetryConstants";
import UserDefinedFunctionTab from "../Tabs/UserDefinedFunctionTab"; import UserDefinedFunctionTab from "../Tabs/UserDefinedFunctionTab";
import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor"; import TelemetryProcessor from "../../Shared/Telemetry/TelemetryProcessor";
import Collection from "./Collection";
export default class UserDefinedFunction implements ViewModels.UserDefinedFunction { export default class UserDefinedFunction implements ViewModels.UserDefinedFunction {
public nodeKind: string; public nodeKind: string;
@@ -28,15 +27,13 @@ export default class UserDefinedFunction implements ViewModels.UserDefinedFuncti
} }
public static create(source: ViewModels.Collection, event: MouseEvent) { public static create(source: ViewModels.Collection, event: MouseEvent) {
const id = const id = source.container.tabsManager.getTabs(ViewModels.CollectionTabKind.UserDefinedFunctions).length + 1;
source.container
.openedTabs()
.filter((tab: ViewModels.Tab) => tab.tabKind === ViewModels.CollectionTabKind.UserDefinedFunctions).length + 1;
const userDefinedFunction = <DataModels.UserDefinedFunction>{ const userDefinedFunction = <DataModels.UserDefinedFunction>{
id: "", id: "",
body: "function userDefinedFunction(){}" body: "function userDefinedFunction(){}"
}; };
let userDefinedFunctionTab: ViewModels.Tab = new UserDefinedFunctionTab({
const userDefinedFunctionTab: UserDefinedFunctionTab = new UserDefinedFunctionTab({
resource: userDefinedFunction, resource: userDefinedFunction,
isNew: true, isNew: true,
tabKind: ViewModels.CollectionTabKind.UserDefinedFunctions, tabKind: ViewModels.CollectionTabKind.UserDefinedFunctions,
@@ -48,22 +45,24 @@ export default class UserDefinedFunction implements ViewModels.UserDefinedFuncti
hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/udf`, hashLocation: `${Constants.HashRoutePrefixes.collectionsWithIds(source.databaseId, source.id())}/udf`,
selfLink: "", selfLink: "",
isActive: ko.observable(false), isActive: ko.observable(false),
onUpdateTabsButtons: source.container.onUpdateTabsButtons, onUpdateTabsButtons: source.container.onUpdateTabsButtons
openedTabs: source.container.openedTabs()
}); });
source.container.openedTabs.push(userDefinedFunctionTab);
// Activate source.container.tabsManager.activateNewTab(userDefinedFunctionTab);
userDefinedFunctionTab.onTabClick();
} }
public open = () => { public open = () => {
this.select(); this.select();
let userDefinedFunctionTab: ViewModels.Tab = this.container const userDefinedFunctionTabs: UserDefinedFunctionTab[] = this.container.tabsManager.getTabs(
.openedTabs() ViewModels.CollectionTabKind.UserDefinedFunctions,
.filter(tab => tab.node && tab.node.rid === this.rid)[0]; (tab: ViewModels.Tab) => tab.collection && tab.collection.rid === this.rid
if (!userDefinedFunctionTab) { ) as UserDefinedFunctionTab[];
let userDefinedFunctionTab: UserDefinedFunctionTab = userDefinedFunctionTabs && userDefinedFunctionTabs[0];
if (userDefinedFunctionTab) {
this.container.tabsManager.activateTab(userDefinedFunctionTab);
} else {
const userDefinedFunctionData = <DataModels.UserDefinedFunction>{ const userDefinedFunctionData = <DataModels.UserDefinedFunction>{
_rid: this.rid, _rid: this.rid,
_self: this.self, _self: this.self,
@@ -86,14 +85,11 @@ export default class UserDefinedFunction implements ViewModels.UserDefinedFuncti
)}/udfs/${this.id()}`, )}/udfs/${this.id()}`,
selfLink: "", selfLink: "",
isActive: ko.observable(false), isActive: ko.observable(false),
onUpdateTabsButtons: this.container.onUpdateTabsButtons, onUpdateTabsButtons: this.container.onUpdateTabsButtons
openedTabs: this.container.openedTabs()
}); });
this.container.openedTabs.push(userDefinedFunctionTab);
}
// Activate this.container.tabsManager.activateNewTab(userDefinedFunctionTab);
userDefinedFunctionTab.onTabClick(); }
}; };
public select() { public select() {
@@ -119,7 +115,9 @@ export default class UserDefinedFunction implements ViewModels.UserDefinedFuncti
}; };
this.container.documentClientUtility.deleteUserDefinedFunction(this.collection, userDefinedFunctionData).then( this.container.documentClientUtility.deleteUserDefinedFunction(this.collection, userDefinedFunctionData).then(
() => { () => {
this.container.openedTabs.remove((tab: ViewModels.Tab) => tab.node && tab.node.rid === this.rid); this.container.tabsManager.removeTabByComparator(
(tab: ViewModels.Tab) => tab.node && tab.node.rid === this.rid
);
this.collection.children.remove(this); this.collection.children.remove(this);
}, },
reason => {} reason => {}

View File

@@ -1,33 +1,61 @@
import "bootstrap/dist/css/bootstrap.css"; import "bootstrap/dist/css/bootstrap.css";
import { initializeIcons } from "office-ui-fabric-react/lib/Icons"; import { initializeIcons } from "office-ui-fabric-react/lib/Icons";
import { Text, Link } from "office-ui-fabric-react";
import * as React from "react"; import * as React from "react";
import * as ReactDOM from "react-dom"; import * as ReactDOM from "react-dom";
import { initializeConfiguration } from "../Config"; import { initializeConfiguration } from "../Config";
import { GalleryHeaderComponent } from "../Explorer/Controls/Header/GalleryHeaderComponent";
import { import {
GalleryTab, GalleryAndNotebookViewerComponent,
GalleryViewerComponent, GalleryAndNotebookViewerComponentProps
GalleryViewerComponentProps, } from "../Explorer/Controls/NotebookGallery/GalleryAndNotebookViewerComponent";
SortBy import { GalleryTab, SortBy } from "../Explorer/Controls/NotebookGallery/GalleryViewerComponent";
} from "../Explorer/Controls/NotebookGallery/GalleryViewerComponent";
import { JunoClient } from "../Juno/JunoClient"; import { JunoClient } from "../Juno/JunoClient";
import * as GalleryUtils from "../Utils/GalleryUtils"; import * as GalleryUtils from "../Utils/GalleryUtils";
const enableNotebooksUrl = "https://aka.ms/cosmos-enable-notebooks";
const createAccountUrl = "https://aka.ms/cosmos-create-account-portal";
const onInit = async () => { const onInit = async () => {
const dataExplorerUrl = new URL("./", window.location.href).href;
initializeIcons(); initializeIcons();
await initializeConfiguration(); await initializeConfiguration();
const galleryViewerProps = GalleryUtils.getGalleryViewerProps(window.location.search); const galleryViewerProps = GalleryUtils.getGalleryViewerProps(window.location.search);
const props: GalleryViewerComponentProps = { const props: GalleryAndNotebookViewerComponentProps = {
junoClient: new JunoClient(), junoClient: new JunoClient(),
selectedTab: galleryViewerProps.selectedTab || GalleryTab.OfficialSamples, selectedTab: galleryViewerProps.selectedTab || GalleryTab.OfficialSamples,
sortBy: galleryViewerProps.sortBy || SortBy.MostViewed, sortBy: galleryViewerProps.sortBy || SortBy.MostViewed,
searchText: galleryViewerProps.searchText, searchText: galleryViewerProps.searchText
onSelectedTabChange: undefined,
onSortByChange: undefined,
onSearchTextChange: undefined
}; };
ReactDOM.render(<GalleryViewerComponent {...props} />, document.getElementById("galleryContent")); const element = (
<>
<header>
<GalleryHeaderComponent />
</header>
<div style={{ marginLeft: 138, marginRight: 138 }}>
<div style={{ paddingLeft: 26, paddingRight: 26, paddingTop: 20 }}>
<Text block>
Welcome to the Azure Cosmos DB notebooks gallery! View the sample notebooks to learn about use cases, best
practices, and how to get started with Azure Cosmos DB.
</Text>
<Text styles={{ root: { marginTop: 10 } }} block>
If you'd like to run or edit the notebook in your own Azure Cosmos DB account,{" "}
<Link href={dataExplorerUrl}>sign in</Link> and select an account with{" "}
<Link href={enableNotebooksUrl}>notebooks enabled</Link>. From there, you can download the sample to your
account. If you don't have an account yet, you can{" "}
<Link href={createAccountUrl}>create one from the Azure portal</Link>.
</Text>
</div>
<GalleryAndNotebookViewerComponent {...props} />
</div>
</>
);
ReactDOM.render(element, document.getElementById("galleryContent"));
}; };
// Entry point // Entry point

View File

@@ -3,11 +3,11 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="height=device-height, width=device-width, initial-scale=1.0" /> <meta name="viewport" content="height=device-height, width=device-width, initial-scale=1.0" />
<title>Gallery Viewer</title> <title>Gallery Viewer</title>
<link rel="shortcut icon" href="images/CosmosDB_rgb_ui_lighttheme.ico" type="image/x-icon" />
</head> </head>
<body> <body style="overflow-y:scroll">
<div class="galleryContent" id="galleryContent"></div> <div class="galleryContent" id="galleryContent"></div>
</body> </body>
</html> </html>

View File

@@ -68,6 +68,7 @@ class HostedExplorer {
this._controlbarCommands = ko.observableArray([ this._controlbarCommands = ko.observableArray([
{ {
id: "commandbutton-connect",
iconSrc: ConnectIcon, iconSrc: ConnectIcon,
iconAlt: "connect button", iconAlt: "connect button",
onCommandClick: () => this.openConnectPane(), onCommandClick: () => this.openConnectPane(),
@@ -78,6 +79,7 @@ class HostedExplorer {
disabled: false disabled: false
}, },
{ {
id: "commandbutton-settings",
iconSrc: SettingsIcon, iconSrc: SettingsIcon,
iconAlt: "setting button", iconAlt: "setting button",
onCommandClick: () => this.openSettingsPane(), onCommandClick: () => this.openSettingsPane(),
@@ -88,6 +90,7 @@ class HostedExplorer {
disabled: false disabled: false
}, },
{ {
id: "commandbutton-feedback",
iconSrc: FeedbackIcon, iconSrc: FeedbackIcon,
iconAlt: "feeback button", iconAlt: "feeback button",
onCommandClick: () => onCommandClick: () =>

View File

@@ -2,22 +2,12 @@ import "../less/index.less";
import "./Libs/jquery"; import "./Libs/jquery";
import * as ko from "knockout"; import * as ko from "knockout";
import { CorrelationBackend } from "./Common/Constants";
import Ajax from "./Shared/Ajax";
class Index { class Index {
public navigationSelection: ko.Observable<string>; public navigationSelection: ko.Observable<string>;
public correlationSrc: ko.Observable<string>;
constructor() { constructor() {
this.navigationSelection = ko.observable("quickstart"); this.navigationSelection = ko.observable("quickstart");
this.correlationSrc = ko.observable("");
Ajax.get("/_explorer/installation_id.txt").then(result => {
// TODO: Detect correct URL for each environment automatically.
const url: string = `${CorrelationBackend.Url}?emulator_id=${result}`;
this.correlationSrc(url);
});
} }
public quickstart_click() { public quickstart_click() {

View File

@@ -1,11 +0,0 @@
@import "../../../../less/Common/Constants";
.notebookComponentContainer {
height: 100vh;
width: 100vw;
margin: 0px;
overflow-x: hidden;
font-family: @DataExplorerFont;
padding: 20px;
}

View File

@@ -9,6 +9,8 @@ import {
} from "../Explorer/Controls/NotebookViewer/NotebookViewerComponent"; } from "../Explorer/Controls/NotebookViewer/NotebookViewerComponent";
import { IGalleryItem, JunoClient } from "../Juno/JunoClient"; import { IGalleryItem, JunoClient } from "../Juno/JunoClient";
import * as GalleryUtils from "../Utils/GalleryUtils"; import * as GalleryUtils from "../Utils/GalleryUtils";
import { GalleryHeaderComponent } from "../Explorer/Controls/Header/GalleryHeaderComponent";
import { FileSystemUtil } from "../Explorer/Notebook/FileSystemUtil";
const onInit = async () => { const onInit = async () => {
initializeIcons(); initializeIcons();
@@ -16,29 +18,46 @@ const onInit = async () => {
const galleryViewerProps = GalleryUtils.getGalleryViewerProps(window.location.search); const galleryViewerProps = GalleryUtils.getGalleryViewerProps(window.location.search);
const notebookViewerProps = GalleryUtils.getNotebookViewerProps(window.location.search); const notebookViewerProps = GalleryUtils.getNotebookViewerProps(window.location.search);
const backNavigationText = galleryViewerProps.selectedTab && GalleryUtils.getTabTitle(galleryViewerProps.selectedTab); const backNavigationText = galleryViewerProps.selectedTab && GalleryUtils.getTabTitle(galleryViewerProps.selectedTab);
const hideInputs = notebookViewerProps.hideInputs;
const notebookUrl = decodeURIComponent(notebookViewerProps.notebookUrl); const notebookUrl = decodeURIComponent(notebookViewerProps.notebookUrl);
render(notebookUrl, backNavigationText); render(notebookUrl, backNavigationText, hideInputs);
const galleryItemId = notebookViewerProps.galleryItemId; const galleryItemId = notebookViewerProps.galleryItemId;
if (galleryItemId) { if (galleryItemId) {
const junoClient = new JunoClient(); const junoClient = new JunoClient();
const notebook = await junoClient.getNotebook(galleryItemId); const notebook = await junoClient.getNotebook(galleryItemId);
render(notebookUrl, backNavigationText, notebook.data); render(notebookUrl, backNavigationText, hideInputs, notebook.data);
} }
}; };
const render = (notebookUrl: string, backNavigationText: string, galleryItem?: IGalleryItem) => { const render = (notebookUrl: string, backNavigationText: string, hideInputs: boolean, galleryItem?: IGalleryItem) => {
const props: NotebookViewerComponentProps = { const props: NotebookViewerComponentProps = {
junoClient: galleryItem ? new JunoClient() : undefined, junoClient: galleryItem ? new JunoClient() : undefined,
notebookUrl, notebookUrl,
galleryItem, galleryItem,
backNavigationText, backNavigationText,
hideInputs,
onBackClick: undefined, onBackClick: undefined,
onTagClick: undefined onTagClick: undefined
}; };
ReactDOM.render(<NotebookViewerComponent {...props} />, document.getElementById("notebookContent")); if (galleryItem) {
document.title = FileSystemUtil.stripExtension(galleryItem.name, "ipynb");
}
const element = (
<>
<header>
<GalleryHeaderComponent />
</header>
<div style={{ marginLeft: 120, marginRight: 120 }}>
<NotebookViewerComponent {...props} />
</div>
</>
);
ReactDOM.render(element, document.getElementById("notebookContent"));
}; };
// Entry point // Entry point

View File

@@ -3,8 +3,8 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="height=device-height, width=device-width, initial-scale=1.0" /> <meta name="viewport" content="height=device-height, width=device-width, initial-scale=1.0" />
<title>Notebook Viewer</title> <title>Notebook Viewer</title>
<link rel="shortcut icon" href="images/CosmosDB_rgb_ui_lighttheme.ico" type="image/x-icon" />
</head> </head>
<body> <body>

View File

@@ -83,7 +83,9 @@ export class NotebookWorkspaceManager implements ViewModels.NotebookWorkspaceMan
public async startNotebookWorkspaceAsync(cosmosdbResourceId: string, notebookWorkspaceId: string): Promise<void> { public async startNotebookWorkspaceAsync(cosmosdbResourceId: string, notebookWorkspaceId: string): Promise<void> {
const uri = `${cosmosdbResourceId}/notebookWorkspaces/${notebookWorkspaceId}/start`; const uri = `${cosmosdbResourceId}/notebookWorkspaces/${notebookWorkspaceId}/start`;
try { try {
return await this.rpClient(uri).postAsync(uri, ArmApiVersions.documentDB, undefined); return await this.rpClient(uri).postAsync(uri, ArmApiVersions.documentDB, undefined, {
skipResourceValidation: true
});
} catch (error) { } catch (error) {
Logger.logError(error, "NotebookWorkspaceManager/startNotebookWorkspaceAsync"); Logger.logError(error, "NotebookWorkspaceManager/startNotebookWorkspaceAsync");
throw error; throw error;

View File

@@ -1,9 +1,28 @@
export interface IResourceProviderClient<TResource> { export interface IResourceProviderClient<TResource> {
deleteAsync(url: string, apiVersion: string): Promise<void>; deleteAsync(url: string, apiVersion: string, requestOptions?: IResourceProviderRequestOptions): Promise<void>;
getAsync(url: string, apiVersion: string, queryString?: string): Promise<TResource | TResource[]>; getAsync(
postAsync(url: string, apiVersion: string, body: any): Promise<any>; url: string,
putAsync(url: string, apiVersion: string, body: any): Promise<TResource>; apiVersion: string,
patchAsync(url: string, apiVersion: string, body: any): Promise<TResource>; queryString?: string,
requestOptions?: IResourceProviderRequestOptions
): Promise<TResource | TResource[]>;
postAsync(url: string, apiVersion: string, body: any, requestOptions?: IResourceProviderRequestOptions): Promise<any>;
putAsync(
url: string,
apiVersion: string,
body: any,
requestOptions?: IResourceProviderRequestOptions
): Promise<TResource>;
patchAsync(
url: string,
apiVersion: string,
body: any,
requestOptions?: IResourceProviderRequestOptions
): Promise<TResource>;
}
export interface IResourceProviderRequestOptions {
skipResourceValidation: boolean;
} }
export interface IResourceProviderClientFactory<TResult> { export interface IResourceProviderClientFactory<TResult> {

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