Compare commits

...

55 Commits

Author SHA1 Message Date
vchske
d45af21996 Updating error message on mongo collection create (#118)
* 1) Updated mongo collection create pane to display a better error when a shard key is ill formed.
2) Updated an error message to use double quotes since no string interpolation is used. I thought it was included in a previoue PR but I guess the change didn't get staged.
2020-08-06 12:56:40 -05:00
Steve Faulkner
a64109ebaa Fix Generated ARM types (#131) 2020-08-06 09:27:41 -05:00
victor-meng
70f9b28499 Fix tabs manager test (#130) 2020-08-05 18:01:13 -07:00
Steve Faulkner
78e70cc7cc Refresh Caches only in Portal (#129) 2020-08-05 15:54:17 -05:00
Steve Faulkner
f132a8546c Move SQL database deletion to ARM (#126) 2020-08-04 18:03:14 -05:00
Tanuj Mittal
e6acf6686f Update NotebookReadOnlyRenderer.tsx (#127) 2020-08-04 10:24:25 -07:00
Steve Faulkner
2904a1a60d Move Delete Container call to use ARM when logged in with AAD (#110) 2020-08-03 17:11:07 -05:00
Tanuj Mittal
8c792fd147 Hide Azure Synapse Link button for Serverless accounts (#121) 2020-07-31 15:31:21 -07:00
Steve Faulkner
2a53dfabb5 Fix refresh resources button and remove portal specific notebooks call (#123) 2020-07-31 16:45:36 -05:00
Srinath Narayanan
14ef40029d Added session based view updation for gallery notebooks (#120) 2020-07-31 12:31:05 -07:00
Steve Faulkner
dab6e43d0d Hotfix: Remove extra JSON.stringify in Monogo update code path (#119) 2020-07-28 15:13:48 -05:00
Steve Faulkner
aea168c893 Add lint rule to prefer arror function (#114) 2020-07-27 16:40:04 -05:00
Steve Faulkner
fea321cd68 More ViewModel cleanup (#116) 2020-07-27 16:05:25 -05:00
Steve Faulkner
2e49ed45c3 Refactor DocumentClientUtilityBase to not be a class (#115) 2020-07-27 12:58:27 -05:00
Steve Faulkner
6d142f16f9 Refactor Data Access Utility (#112) 2020-07-24 16:45:48 -05:00
Steve Faulkner
6dcdacc8c4 Update CODEOWNERS 2020-07-24 15:45:42 -05:00
Tanuj Mittal
33969581ac Support serverless accounts (#109)
* Changes for serverless accounts

* Dont show upsell message for serverless accounts

* Update CassandraAddCollectionPane to support serverless
2020-07-24 13:13:54 -07:00
Srinath Narayanan
dc67c5f40b Added support for taking screenshot during Notebook publish to Gallery (#108)
* Added support for taking screenshot

- Screenshot is taken using html2canvas package
- Converted to base 64 and uploaded to metadata
- For Using first display output
  - Notebok object is passed instead of string, to publish pane
  - The first cell with output present is parsed out
  - The dom is also parsed to get corresponding div element to take screenshot of the first output

* fixed bug

* Addressed PR comments

- FIxed bug that didn't capture screenshot when mutiple notebook tabs are opened

* removed unnecessary dependencies

* fixed compile issues

* more edits
2020-07-23 00:43:53 -07:00
Steve Faulkner
acc65c9588 Update README.md 2020-07-22 13:52:47 -05:00
Steve Faulkner
6860d8db1c Remove unused Data Access methods (#107) 2020-07-22 12:03:51 -05:00
Steve Faulkner
3187756d03 Update README.md 2020-07-21 15:03:18 -05:00
Vignesh Rangaishenvi
0f4ff0e49f Support readers (#105) 2020-07-21 11:57:23 -07:00
Steve Faulkner
4f86015be7 Remove OpenActionsStubs (#106) 2020-07-21 13:50:51 -05:00
Steve Faulkner
46cca859e3 Add more files to strict mode (#93) 2020-07-21 12:14:55 -05:00
Steve Faulkner
574fdcaf37 Remove Pane Stubs (#102) 2020-07-21 09:23:51 -05:00
Steve Faulkner
eab6506940 Remove Explorer Stub and ViewModel.Explorer (#101) 2020-07-20 12:59:40 -05:00
Srinath Narayanan
050da28d6e Added support for custom image upload during publish to Gallery (#99)
* Added support for custom image upload

- Dropdown gives an option for URL or image upload
- Preview shows how the card will be displayed in the gallery
- base64 converted image stored in metadata document
- Max limit is 1.5MiB for the image

* fixed lint errors

* addressed PR comments

- Added test

* added snapshot

* fixed failing test
2020-07-17 15:32:39 -07:00
Steve Faulkner
ffae9baca2 Tabs CSS Hotfix and hash CSS file contents (#98)
Co-authored-by: Victor Meng <vimeng@microsoft.com>
2020-07-16 20:35:18 -05:00
Tanuj Mittal
e491c1a042 Use gallery.html instead of /gallery/index.html as endpoint (#94) 2020-07-15 23:41:05 -07:00
Steve Faulkner
444e25c086 More Spark UI Cleanup (#89)
Co-authored-by: Steve Faulkner <stfaul@microsoft.com>
2020-07-15 16:59:04 -05:00
Steve Faulkner
99c6a7ebcc Make explicit any an error (#81)
Co-authored-by: Steve Faulkner <stfaul@microsoft.com>
2020-07-15 16:21:50 -05:00
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
Tanuj Mittal
84ea3796ec Remove enableGallery feature flag (#68)
* Remove enableGallery feature flag

* Fix bugs

* Add tests to increase coverage

* Move favorites functionality behind feature.enableGalleryPublish flag

* Show code cells in NotebookViewer

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

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

* Add validation. Rename to widgetRenderer

* Remove test code from splash screen

* Clean up infobox

* Fix styling/layout

* Move test code into unit test

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

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

* Use MessageBar for error message

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

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

View File

@@ -1,5 +1,6 @@
**/node_modules/
dist/
Contracts/
src/Api/Apis.ts
src/AuthType.ts
src/Bindings/BindingHandlersRegisterer.ts
@@ -137,7 +138,6 @@ src/Explorer/Panes/AddDatabasePane.test.ts
src/Explorer/Panes/AddDatabasePane.ts
src/Explorer/Panes/BrowseQueriesPane.ts
src/Explorer/Panes/CassandraAddCollectionPane.ts
src/Explorer/Panes/ClusterLibraryPane.ts
src/Explorer/Panes/ContextualPaneBase.ts
src/Explorer/Panes/DeleteCollectionConfirmationPane.test.ts
src/Explorer/Panes/DeleteCollectionConfirmationPane.ts
@@ -145,9 +145,7 @@ src/Explorer/Panes/DeleteDatabaseConfirmationPane.test.ts
src/Explorer/Panes/DeleteDatabaseConfirmationPane.ts
src/Explorer/Panes/ExecuteSprocParamsPane.ts
src/Explorer/Panes/GraphStylingPane.ts
src/Explorer/Panes/LibraryManagePane.ts
src/Explorer/Panes/LoadQueryPane.ts
src/Explorer/Panes/ManageSparkClusterPane.ts
src/Explorer/Panes/NewVertexPane.ts
src/Explorer/Panes/PaneComponents.ts
src/Explorer/Panes/RenewAdHocAccessPane.ts
@@ -155,7 +153,6 @@ src/Explorer/Panes/SaveQueryPane.ts
src/Explorer/Panes/SettingsPane.test.ts
src/Explorer/Panes/SettingsPane.ts
src/Explorer/Panes/SetupNotebooksPane.ts
src/Explorer/Panes/SetupSparkClusterPane.ts
src/Explorer/Panes/StringInputPane.ts
src/Explorer/Panes/SwitchDirectoryPane.ts
src/Explorer/Panes/Tables/AddTableEntityPane.ts
@@ -273,7 +270,6 @@ src/Shared/AddCollectionUtility.test.ts
src/Shared/AddCollectionUtility.ts
src/Shared/AddDatabaseUtility.test.ts
src/Shared/AddDatabaseUtility.ts
src/Shared/Ajax.ts
src/Shared/Constants.ts
src/Shared/DefaultExperienceUtility.test.ts
src/Shared/DefaultExperienceUtility.ts
@@ -302,11 +298,9 @@ src/Utils/DatabaseAccountUtils.ts
src/Utils/JunoUtils.ts
src/Utils/MessageValidation.ts
src/Utils/NotebookConfigurationUtils.ts
src/Utils/NotificationConsoleUtils.ts
src/Utils/OfferUtils.test.ts
src/Utils/OfferUtils.ts
src/Utils/PricingUtils.test.ts
src/Utils/PricingUtils.ts
src/Utils/QueryUtils.test.ts
src/Utils/QueryUtils.ts
src/Utils/StringUtils.test.ts
@@ -334,10 +328,6 @@ src/Explorer/Controls/Directory/DirectoryListComponent.test.tsx
src/Explorer/Controls/Directory/DirectoryListComponent.tsx
src/Explorer/Controls/Editor/EditorReact.tsx
src/Explorer/Controls/InputTypeahead/InputTypeaheadComponent.tsx
src/Explorer/Controls/LibraryManagement/ClusterLibraryGrid.tsx
src/Explorer/Controls/LibraryManagement/ClusterLibraryGridAdapter.tsx
src/Explorer/Controls/LibraryManagement/LibraryManage.tsx
src/Explorer/Controls/LibraryManagement/LibraryManageComponentAdapter.tsx
src/Explorer/Controls/Notebook/NotebookTerminalComponent.test.tsx
src/Explorer/Controls/Notebook/NotebookTerminalComponent.tsx
src/Explorer/Controls/NotebookViewer/NotebookMetadataComponent.tsx
@@ -411,7 +401,6 @@ src/Explorer/Tabs/NotebookViewerTab.tsx
src/Explorer/Tabs/TerminalTab.tsx
src/Explorer/Tree/ResourceTreeAdapter.tsx
src/Explorer/Tree/ResourceTreeAdapterForResourceToken.tsx
src/GalleryViewer/Cards/CardStyleConstants.tsx
src/GalleryViewer/Cards/GalleryCardComponent.tsx
src/GalleryViewer/GalleryViewer.tsx
src/GalleryViewer/GalleryViewerComponent.tsx

View File

@@ -3,7 +3,7 @@ module.exports = {
browser: true,
es6: true
},
plugins: ["@typescript-eslint", "no-null"],
plugins: ["@typescript-eslint", "no-null", "prefer-arrow"],
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
globals: {
Atomics: "readonly",
@@ -39,6 +39,8 @@ module.exports = {
curly: "error",
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/no-extraneous-class": "error",
"no-null/no-null": "error"
"no-null/no-null": "error",
"@typescript-eslint/no-explicit-any": "error",
"prefer-arrow/prefer-arrow-functions": ["error", { allowStandaloneDeclarations: true }]
}
};

2
.github/CODEOWNERS vendored
View File

@@ -1 +1 @@
* @Azure/cosmos-explorer-owners
* @Azure/cosmos-explorer-owners @Azure/azure-cosmos-explorer-developers

View File

@@ -1,9 +1,13 @@
name: CI
on:
push:
branches: [master]
branches:
- master
- hotfix/*
- release/*
pull_request:
branches: [master]
branches:
- master
jobs:
compile:
runs-on: ubuntu-latest
@@ -52,6 +56,7 @@ jobs:
- run: npm run test
build:
runs-on: ubuntu-latest
needs: [lint, format, compile, unittest]
name: "Build"
steps:
- uses: actions/checkout@v2
@@ -75,6 +80,7 @@ jobs:
path: dist/
endtoendemulator:
name: "End To End Tests | Emulator | SQL"
needs: [lint, format, compile, unittest]
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
@@ -101,6 +107,7 @@ jobs:
CYPRESS_CACHE_FOLDER: ~/.cache/Cypress
endtoendsql:
name: "End To End Tests | SQL"
needs: [lint, format, compile, unittest]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@@ -129,6 +136,7 @@ jobs:
CYPRESS_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_SQL }}
endtoendmongo:
name: "End To End Tests | Mongo"
needs: [lint, format, compile, unittest]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
@@ -155,8 +163,32 @@ jobs:
NODE_TLS_REJECT_UNAUTHORIZED: 0
CYPRESS_CACHE_FOLDER: ~/.cache/Cypress
CYPRESS_CONNECTION_STRING: ${{ secrets.CONNECTION_STRING_MONGO }}
accessibility:
name: "Accessibility | Hosted"
needs: [lint, format, compile, unittest]
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:
name: Publish Nuget
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendsql, endtoendmongo]
runs-on: ubuntu-latest
env:
@@ -175,10 +207,12 @@ jobs:
- run: nuget pack -Version "2.0.0-github-${GITHUB_SHA}"
- run: nuget push -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg
- uses: actions/upload-artifact@v2
name: packages
with:
path: "*.nupkg"
nugetmpac:
name: Publish Nuget MPAC
if: github.ref == 'refs/heads/master' || contains(github.ref, 'hotfix/') || contains(github.ref, 'release/')
needs: [lint, format, compile, build, unittest, endtoendemulator, endtoendsql, endtoendmongo]
runs-on: ubuntu-latest
env:
@@ -198,5 +232,6 @@ jobs:
- run: nuget pack -Version "2.0.0-github-${GITHUB_SHA}"
- run: nuget push -Source "$NUGET_SOURCE" -ApiKey Az *.nupkg
- uses: actions/upload-artifact@v2
name: packages
with:
path: "*.nupkg"

View File

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

3
.gitignore vendored
View File

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

View File

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

View File

@@ -1,5 +1,9 @@
# CosmosDB Explorer
UI for Azure Cosmos DB. Powers the [Azure Portal](https://portal.azure.com/), https://cosmos.azure.com/, and the [Cosmos DB Emulator](https://docs.microsoft.com/en-us/azure/cosmos-db/local-emulator)
![](https://sdkctlstore.blob.core.windows.net/exe/dataexplorer.gif)
## Getting Started
- `npm install`
@@ -87,6 +91,10 @@ Jest and Puppeteer are used for end to end production runners and are contained
1. Copy .env.example to .env and fill in all variables
2. Run `npm run test:e2e`
### Releasing
We generally adhear to the release strategy [documented by the Azure SDK Guidelines](https://azure.github.io/azure-sdk/policies_repobranching.html#release-branches). Most releases should happen from the master branch. If master contains commits that cannot be released, you may create a release from a `release/` or `hotfix/` branch. See linked documentation for more details.
# Contributing
Please read the [contribution guidelines](./CONTRIBUTING.md).

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

595
externals/bops.js vendored
View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

15321
externals/datatables.js vendored

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

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

File diff suppressed because it is too large Load Diff

11
externals/jquery.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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

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

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -39,10 +39,10 @@ module.exports = {
// An object that configures minimum threshold enforcement for coverage results
coverageThreshold: {
global: {
branches: 18,
functions: 22,
lines: 28,
statements: 27
branches: 20,
functions: 24,
lines: 30,
statements: 29.0
}
},

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

3755
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -34,12 +34,14 @@
"@nteract/transform-vega": "7.0.6",
"@octokit/rest": "17.9.2",
"@phosphor/widgets": "1.9.3",
"@uifabric/react-cards": "0.109.53",
"@uifabric/styling": "7.11.2",
"@types/mkdirp": "1.0.1",
"@types/node-fetch": "2.5.7",
"@uifabric/react-cards": "0.109.110",
"@uifabric/styling": "7.13.7",
"abort-controller": "3.0.0",
"applicationinsights": "1.8.0",
"babel-polyfill": "6.26.0",
"bootstrap": "3.3.7",
"bootstrap": "3.4.1",
"canvas": "2.6.0",
"clean-webpack-plugin": "0.1.19",
"copy-webpack-plugin": "6.0.2",
@@ -54,15 +56,18 @@
"es6-symbol": "3.1.3",
"eslint-plugin-jest": "23.13.2",
"hasher": "1.2.0",
"html2canvas": "1.0.0-rc.5",
"immutable": "4.0.0-rc.12",
"is-ci": "2.0.0",
"jquery": "3.4.0",
"jquery": "3.5.1",
"jquery-typeahead": "2.10.6",
"jquery-ui-dist": "1.12.1",
"knockout": "3.5.1",
"mkdirp": "1.0.4",
"monaco-editor": "0.15.6",
"object.entries": "1.1.0",
"office-ui-fabric-react": "7.115.0",
"office-ui-fabric-react": "7.121.10",
"p-retry": "4.2.0",
"plotly.js-cartesian-dist-min": "1.52.3",
"promise-polyfill": "8.1.0",
"promise.prototype.finally": "3.1.0",
@@ -109,13 +114,14 @@
"@types/react-notification-system": "0.2.39",
"@types/react-redux": "7.1.7",
"@types/sinon": "2.3.3",
"@types/styled-components": "4.1.8",
"@types/styled-components": "5.1.1",
"@types/text-encoding": "0.0.33",
"@types/underscore": "1.7.36",
"@types/webfontloader": "1.6.29",
"@typescript-eslint/eslint-plugin": "3.2.0",
"@typescript-eslint/parser": "3.2.0",
"adal-angular": "1.0.15",
"axe-puppeteer": "1.1.0",
"babel-jest": "24.9.0",
"babel-loader": "8.1.0",
"buffer": "5.1.0",
@@ -129,6 +135,7 @@
"eslint": "7.3.1",
"eslint-cli": "1.1.1",
"eslint-plugin-no-null": "1.0.2",
"eslint-plugin-prefer-arrow": "1.2.2",
"eslint-plugin-react": "7.20.0",
"expose-loader": "0.7.5",
"file-loader": "2.0.0",
@@ -146,6 +153,7 @@
"less-vars-loader": "1.1.0",
"mini-css-extract-plugin": "0.4.3",
"monaco-editor-webpack-plugin": "1.7.0",
"node-fetch": "2.6.0",
"prettier": "1.19.1",
"puppeteer": "4.0.0",
"raw-loader": "0.5.1",
@@ -156,7 +164,7 @@
"ts-loader": "6.2.2",
"tslint": "5.11.0",
"tslint-microsoft-contrib": "6.0.0",
"typescript": "3.8.3",
"typescript": "3.9.6",
"url-loader": "1.1.1",
"webpack": "4.43.0",
"webpack-bundle-analyzer": "3.6.1",
@@ -186,7 +194,8 @@
"build:contracts": "npm run compile:contracts",
"strictEligibleFiles": "node ./strict-migration-tools/index.js",
"autoAddStrictEligibleFiles": "node ./strict-migration-tools/autoAdd.js",
"compile:fullStrict": "tsc -p ./tsconfig.json --strictNullChecks"
"compile:fullStrict": "tsc -p ./tsconfig.json --strictNullChecks",
"generateARMClients": "ts-node --compiler-options '{\"module\":\"commonjs\"}' utils/armClientGenerator/generator.ts"
},
"repository": {
"type": "git",

View File

@@ -1,13 +0,0 @@
import * as ViewModels from "../Contracts/ViewModels";
export class DefaultApi implements ViewModels.CosmosDbApi {
public isSystemDatabasePredicate = (database: ViewModels.Database): boolean => {
return false;
};
}
export class CassandraApi implements ViewModels.CosmosDbApi {
public isSystemDatabasePredicate = (database: ViewModels.Database): boolean => {
return database.id() === "system";
};
}

View File

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

View File

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

View File

@@ -2,7 +2,7 @@ import * as Cosmos from "@azure/cosmos";
import { RequestInfo, setAuthorizationTokenHeaderUsingMasterKey } from "@azure/cosmos";
import { DatabaseAccount } from "../Contracts/DataModels";
import { HttpHeaders, EmulatorMasterKey } from "./Constants";
import { NotificationConsoleUtils } from "../Utils/NotificationConsoleUtils";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import { config, Platform } from "../Config";

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,7 @@ import * as Constants from "../Common/Constants";
import * as ViewModels from "../Contracts/ViewModels";
import { AuthType } from "../AuthType";
import { StringUtils } from "../Utils/StringUtils";
import Explorer from "../Explorer/Explorer";
export default class EnvironmentUtility {
public static getMongoBackendEndpoint(serverId: string, location: string, extensionEndpoint: string = ""): string {
@@ -26,7 +27,7 @@ export default class EnvironmentUtility {
return window.authType === AuthType.AAD;
}
public static getCassandraBackendEndpoint(explorer: ViewModels.Explorer): string {
public static getCassandraBackendEndpoint(explorer: Explorer): string {
const defaultLocation: string = "default";
const location: string = EnvironmentUtility.normalizeRegionName(explorer.databaseAccount().location);
return (

View File

@@ -1,67 +1,69 @@
import * as DataModels from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels";
export function replaceKnownError(err: string): string {
if (
window.dataExplorer.subscriptionType() === ViewModels.SubscriptionType.Internal &&
err.indexOf("SharedOffer is Disabled for your account") >= 0
) {
return "Database throughput is not supported for internal subscriptions.";
}
return err;
}
export function parse(err: any): DataModels.ErrorDataModel[] {
try {
return _parse(err);
} catch (e) {
return [<DataModels.ErrorDataModel>{ message: JSON.stringify(err) }];
}
}
function _parse(err: any): DataModels.ErrorDataModel[] {
var normalizedErrors: DataModels.ErrorDataModel[] = [];
if (err.message && !err.code) {
normalizedErrors.push(err);
} else {
const innerErrors: any[] = _getInnerErrors(err.message);
normalizedErrors = innerErrors.map(innerError =>
typeof innerError === "string" ? { message: innerError } : innerError
);
}
return normalizedErrors;
}
function _getInnerErrors(message: string): any[] {
/*
The backend error message has an inner-message which is a stringified object.
For SQL errors, the "errors" property is an array of SqlErrorDataModel.
Example:
"Message: {"Errors":["Resource with specified id or name already exists"]}\r\nActivityId: 80005000008d40b6a, Request URI: /apps/19000c000c0a0005/services/mctestdocdbprod-MasterService-0-00066ab9937/partitions/900005f9000e676fb8/replicas/13000000000955p"
For non-SQL errors the "Errors" propery is an array of string.
Example:
"Message: {"errors":[{"severity":"Error","location":{"start":7,"end":8},"code":"SC1001","message":"Syntax error, incorrect syntax near '.'."}]}\r\nActivityId: d3300016d4084e310a, Request URI: /apps/12401f9e1df77/services/dc100232b1f44545/partitions/f86f3bc0001a2f78/replicas/13085003638s"
*/
let innerMessage: any = null;
const singleLineMessage = message.replace(/[\r\n]|\r|\n/g, "");
try {
// Multi-Partition error flavor
const regExp = /^(.*)ActivityId: (.*)/g;
const regString = regExp.exec(singleLineMessage);
const innerMessageString = regString[1];
innerMessage = JSON.parse(innerMessageString);
} catch (e) {
// Single-partition error flavor
const regExp = /^Message: (.*)ActivityId: (.*), Request URI: (.*)/g;
const regString = regExp.exec(singleLineMessage);
const innerMessageString = regString[1];
innerMessage = JSON.parse(innerMessageString);
}
return innerMessage.errors ? innerMessage.errors : innerMessage.Errors;
}
import * as DataModels from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels";
export function replaceKnownError(err: string): string {
if (
window.dataExplorer.subscriptionType() === ViewModels.SubscriptionType.Internal &&
err.indexOf("SharedOffer is Disabled for your account") >= 0
) {
return "Database throughput is not supported for internal subscriptions.";
} else if (err.indexOf("Partition key paths must contain only valid") >= 0) {
return "Partition key paths must contain only valid characters and not contain a trailing slash or wildcard character.";
}
return err;
}
export function parse(err: any): DataModels.ErrorDataModel[] {
try {
return _parse(err);
} catch (e) {
return [<DataModels.ErrorDataModel>{ message: JSON.stringify(err) }];
}
}
function _parse(err: any): DataModels.ErrorDataModel[] {
var normalizedErrors: DataModels.ErrorDataModel[] = [];
if (err.message && !err.code) {
normalizedErrors.push(err);
} else {
const innerErrors: any[] = _getInnerErrors(err.message);
normalizedErrors = innerErrors.map(innerError =>
typeof innerError === "string" ? { message: innerError } : innerError
);
}
return normalizedErrors;
}
function _getInnerErrors(message: string): any[] {
/*
The backend error message has an inner-message which is a stringified object.
For SQL errors, the "errors" property is an array of SqlErrorDataModel.
Example:
"Message: {"Errors":["Resource with specified id or name already exists"]}\r\nActivityId: 80005000008d40b6a, Request URI: /apps/19000c000c0a0005/services/mctestdocdbprod-MasterService-0-00066ab9937/partitions/900005f9000e676fb8/replicas/13000000000955p"
For non-SQL errors the "Errors" propery is an array of string.
Example:
"Message: {"errors":[{"severity":"Error","location":{"start":7,"end":8},"code":"SC1001","message":"Syntax error, incorrect syntax near '.'."}]}\r\nActivityId: d3300016d4084e310a, Request URI: /apps/12401f9e1df77/services/dc100232b1f44545/partitions/f86f3bc0001a2f78/replicas/13085003638s"
*/
let innerMessage: any = null;
const singleLineMessage = message.replace(/[\r\n]|\r|\n/g, "");
try {
// Multi-Partition error flavor
const regExp = /^(.*)ActivityId: (.*)/g;
const regString = regExp.exec(singleLineMessage);
const innerMessageString = regString[1];
innerMessage = JSON.parse(innerMessageString);
} catch (e) {
// Single-partition error flavor
const regExp = /^Message: (.*)ActivityId: (.*), Request URI: (.*)/g;
const regString = regExp.exec(singleLineMessage);
const innerMessageString = regString[1];
innerMessage = JSON.parse(innerMessageString);
}
return innerMessage.errors ? innerMessage.errors : innerMessage.Errors;
}

View File

@@ -1,46 +1,26 @@
jest.mock("./MessageHandler");
import { LogEntryLevel } from "../Contracts/Diagnostics";
import * as Logger from "./Logger";
import { MessageHandler } from "./MessageHandler";
import { MessageTypes } from "../Contracts/ExplorerContracts";
import { sendMessage } from "./MessageHandler";
describe("Logger", () => {
let sendMessageSpy: jasmine.Spy;
beforeEach(() => {
sendMessageSpy = spyOn(MessageHandler, "sendMessage");
});
afterEach(() => {
sendMessageSpy = null;
jest.resetAllMocks();
});
it("should log info messages", () => {
Logger.logInfo("Test info", "DocDB");
const spyArgs = sendMessageSpy.calls.mostRecent().args[0];
expect(spyArgs.type).toBe(MessageTypes.LogInfo);
expect(spyArgs.data).toContain(LogEntryLevel.Verbose);
expect(spyArgs.data).toContain("DocDB");
expect(spyArgs.data).toContain("Test info");
expect(sendMessage).toBeCalled();
});
it("should log error messages", () => {
Logger.logError("Test error", "DocDB");
const spyArgs = sendMessageSpy.calls.mostRecent().args[0];
expect(spyArgs.type).toBe(MessageTypes.LogInfo);
expect(spyArgs.data).toContain(LogEntryLevel.Error);
expect(spyArgs.data).toContain("DocDB");
expect(spyArgs.data).toContain("Test error");
expect(sendMessage).toBeCalled();
});
it("should log warnings", () => {
Logger.logWarning("Test warning", "DocDB");
const spyArgs = sendMessageSpy.calls.mostRecent().args[0];
expect(spyArgs.type).toBe(MessageTypes.LogInfo);
expect(spyArgs.data).toContain(LogEntryLevel.Warning);
expect(spyArgs.data).toContain("DocDB");
expect(spyArgs.data).toContain("Test warning");
expect(sendMessage).toBeCalled();
});
});

View File

@@ -1,4 +1,4 @@
import { MessageHandler } from "./MessageHandler";
import { sendMessage } from "./MessageHandler";
import { Diagnostics, MessageTypes } from "../Contracts/ExplorerContracts";
import { appInsights } from "../Shared/appInsights";
import { SeverityLevel } from "@microsoft/applicationinsights-web";
@@ -33,7 +33,7 @@ export function logError(message: string | Error, area: string, code?: number):
}
function _logEntry(entry: Diagnostics.LogEntry): void {
MessageHandler.sendMessage({
sendMessage({
type: MessageTypes.LogInfo,
data: JSON.stringify(entry)
});
@@ -59,13 +59,13 @@ function _generateLogEntry(
level: Diagnostics.LogEntryLevel,
message: string,
area: string,
code: number
code?: number
): Diagnostics.LogEntry {
return {
timestamp: new Date().getUTCSeconds(),
level: level,
message: message,
area: area,
code: code
level,
message,
area,
code
};
}

View File

@@ -1,65 +1,29 @@
import Q from "q";
import { CachedDataPromise, MessageHandler } from "./MessageHandler";
import * as MessageHandler from "./MessageHandler";
import { MessageTypes } from "../Contracts/ExplorerContracts";
class MockMessageHandler extends MessageHandler {
public static addToMap(key: string, value: CachedDataPromise<any>): void {
MessageHandler.RequestMap[key] = value;
}
public static mapContainsKey(key: string): boolean {
return MessageHandler.RequestMap[key] != null;
}
public static clearAllEntries(): void {
MessageHandler.RequestMap = {};
}
public static runGarbageCollector(): void {
MessageHandler.runGarbageCollector();
}
}
describe("Message Handler", () => {
beforeEach(() => {
MockMessageHandler.clearAllEntries();
});
xit("should send cached data message", (done: any) => {
const testValidationCallback = (e: MessageEvent) => {
expect(e.data.data).toEqual(
jasmine.objectContaining({ type: MessageTypes.AllDatabases, params: ["some param"] })
);
e.currentTarget.removeEventListener(e.type, testValidationCallback);
done();
};
window.parent.addEventListener("message", testValidationCallback);
MockMessageHandler.sendCachedDataMessage(MessageTypes.AllDatabases, ["some param"]);
});
it("should handle cached message", () => {
let mockPromise: CachedDataPromise<any> = {
it("should handle cached message", async () => {
let mockPromise = {
id: "123",
startTime: new Date(),
deferred: Q.defer<any>()
};
let mockMessage = { message: { id: "123", data: "{}" } };
MockMessageHandler.addToMap(mockPromise.id, mockPromise);
MockMessageHandler.handleCachedDataMessage(mockMessage);
MessageHandler.RequestMap[mockPromise.id] = mockPromise;
MessageHandler.handleCachedDataMessage(mockMessage);
expect(mockPromise.deferred.promise.isFulfilled()).toBe(true);
});
it("should delete fulfilled promises on running the garbage collector", () => {
let mockPromise: CachedDataPromise<any> = {
it("should delete fulfilled promises on running the garbage collector", async () => {
let message = {
id: "123",
startTime: new Date(),
deferred: Q.defer<any>()
};
MockMessageHandler.addToMap(mockPromise.id, mockPromise);
mockPromise.deferred.reject("some error");
MockMessageHandler.runGarbageCollector();
expect(MockMessageHandler.mapContainsKey(mockPromise.id)).toBe(false);
MessageHandler.handleCachedDataMessage(message);
MessageHandler.runGarbageCollector();
expect(MessageHandler.RequestMap["123"]).toBeUndefined();
});
});

View File

@@ -9,77 +9,65 @@ export interface CachedDataPromise<T> {
id: string;
}
/**
* For some reason, typescript emits a Map() in the compiled js output(despite the target being set to ES5) forcing us to define our own polyfill,
* so we define our own custom implementation of the ES6 Map to work around it.
*/
type Map = { [key: string]: CachedDataPromise<any> };
export const RequestMap: Record<string, CachedDataPromise<any>> = {};
export class MessageHandler {
protected static RequestMap: Map = {};
public static handleCachedDataMessage(message: any): void {
const messageContent = message && message.message;
if (
message == null ||
messageContent == null ||
messageContent.id == null ||
!MessageHandler.RequestMap[messageContent.id]
) {
return;
}
const cachedDataPromise = MessageHandler.RequestMap[messageContent.id];
if (messageContent.error != null) {
cachedDataPromise.deferred.reject(messageContent.error);
} else {
cachedDataPromise.deferred.resolve(JSON.parse(messageContent.data));
}
MessageHandler.runGarbageCollector();
export function handleCachedDataMessage(message: any): void {
const messageContent = message && message.message;
if (message == null || messageContent == null || messageContent.id == null || !RequestMap[messageContent.id]) {
return;
}
public static sendCachedDataMessage<TResponseDataModel>(
messageType: MessageTypes,
params: Object[],
timeoutInMs?: number
): Q.Promise<TResponseDataModel> {
let cachedDataPromise: CachedDataPromise<TResponseDataModel> = {
deferred: Q.defer<TResponseDataModel>(),
startTime: new Date(),
id: _.uniqueId()
};
MessageHandler.RequestMap[cachedDataPromise.id] = cachedDataPromise;
MessageHandler.sendMessage({ type: messageType, params: params, id: cachedDataPromise.id });
const cachedDataPromise = RequestMap[messageContent.id];
if (messageContent.error != null) {
cachedDataPromise.deferred.reject(messageContent.error);
} else {
cachedDataPromise.deferred.resolve(JSON.parse(messageContent.data));
}
runGarbageCollector();
}
//TODO: Use telemetry to measure optimal time to resolve/reject promises
return cachedDataPromise.deferred.promise.timeout(
timeoutInMs || Constants.ClientDefaults.requestTimeoutMs,
"Timed out while waiting for response from portal"
export function sendCachedDataMessage<TResponseDataModel>(
messageType: MessageTypes,
params: Object[],
timeoutInMs?: number
): Q.Promise<TResponseDataModel> {
let cachedDataPromise: CachedDataPromise<TResponseDataModel> = {
deferred: Q.defer<TResponseDataModel>(),
startTime: new Date(),
id: _.uniqueId()
};
RequestMap[cachedDataPromise.id] = cachedDataPromise;
sendMessage({ type: messageType, params: params, id: cachedDataPromise.id });
//TODO: Use telemetry to measure optimal time to resolve/reject promises
return cachedDataPromise.deferred.promise.timeout(
timeoutInMs || Constants.ClientDefaults.requestTimeoutMs,
"Timed out while waiting for response from portal"
);
}
export function sendMessage(data: any): void {
if (canSendMessage()) {
window.parent.postMessage(
{
signature: "pcIframe",
data: data
},
window.document.referrer
);
}
public static sendMessage(data: any): void {
if (MessageHandler.canSendMessage()) {
window.parent.postMessage(
{
signature: "pcIframe",
data: data
},
window.document.referrer
);
}
}
public static canSendMessage(): boolean {
return window.parent !== window;
}
protected static runGarbageCollector() {
Object.keys(MessageHandler.RequestMap).forEach((key: string) => {
const promise: Q.Promise<any> = MessageHandler.RequestMap[key].deferred.promise;
if (promise.isFulfilled() || promise.isRejected()) {
delete MessageHandler.RequestMap[key];
}
});
}
}
export function canSendMessage(): boolean {
return window.parent !== window;
}
// TODO: This is exported just for testing. It should not be.
export function runGarbageCollector() {
Object.keys(RequestMap).forEach((key: string) => {
const promise: Q.Promise<any> = RequestMap[key].deferred.promise;
if (promise.isFulfilled() || promise.isRejected()) {
delete RequestMap[key];
}
});
}

View File

@@ -7,10 +7,12 @@ import {
updateDocument
} from "./MongoProxyClient";
import { AuthType } from "../AuthType";
import { Collection, DatabaseAccount, DocumentId } from "../Contracts/ViewModels";
import { Collection } from "../Contracts/ViewModels";
import { config } from "../Config";
import { CosmosClient } from "./CosmosClient";
import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient";
import DocumentId from "../Explorer/Tree/DocumentId";
import { DatabaseAccount } from "../Contracts/DataModels";
jest.mock("../ResourceProvider/ResourceProviderClient.ts");
const databaseId = "testDB";
@@ -171,7 +173,7 @@ describe("MongoProxyClient", () => {
});
it("builds the correct URL", () => {
updateDocument(databaseId, collection, documentId, {});
updateDocument(databaseId, collection, documentId, "{}");
expect(window.fetch).toHaveBeenCalledWith(
"https://main.documentdb.ext.azure.com/api/mongo/explorer?db=testDB&coll=testCollection&resourceUrl=bardb%2FtestDB%2Fdb%2FtestCollection%2Fdocs%2FtestId&rid=testId&rtype=docs&sid=&rg=&dba=foo&pk=pk",
expect.any(Object)
@@ -180,7 +182,7 @@ describe("MongoProxyClient", () => {
it("builds the correct proxy URL in development", () => {
config.MONGO_BACKEND_ENDPOINT = "https://localhost:1234";
updateDocument(databaseId, collection, documentId, {});
updateDocument(databaseId, collection, documentId, "{}");
expect(window.fetch).toHaveBeenCalledWith(
"https://localhost:1234/api/mongo/explorer?db=testDB&coll=testCollection&resourceUrl=bardb%2FtestDB%2Fdb%2FtestCollection%2Fdocs%2FtestId&rid=testId&rtype=docs&sid=&rg=&dba=foo&pk=pk",
expect.any(Object)

View File

@@ -1,7 +1,6 @@
import * as Constants from "../Common/Constants";
import * as DataExplorerConstants from "../Common/Constants";
import * as DataModels from "../Contracts/DataModels";
import * as ViewModels from "../Contracts/ViewModels";
import EnvironmentUtility from "./EnvironmentUtility";
import queryString from "querystring";
import { AddDbUtilities } from "../Shared/AddDatabaseUtility";
@@ -12,10 +11,12 @@ import { config } from "../Config";
import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/NotificationConsoleComponent";
import { Constants as CosmosSDKConstants } from "@azure/cosmos";
import { CosmosClient } from "./CosmosClient";
import { MessageHandler } from "./MessageHandler";
import { sendMessage } from "./MessageHandler";
import { MessageTypes } from "../Contracts/ExplorerContracts";
import { NotificationConsoleUtils } from "../Utils/NotificationConsoleUtils";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import { ResourceProviderClient } from "../ResourceProvider/ResourceProviderClient";
import { MinimalQueryIterator } from "./IteratorUtilities";
import DocumentId from "../Explorer/Tree/DocumentId";
const defaultHeaders = {
[HttpHeaders.apiType]: ApiType.MongoDB.toString(),
@@ -23,7 +24,7 @@ const defaultHeaders = {
[CosmosSDKConstants.HttpHeaders.Version]: "2017-11-15"
};
function authHeaders(): any {
function authHeaders() {
if (window.authType === AuthType.EncryptedToken) {
return { [HttpHeaders.guestAccessToken]: CosmosClient.accessToken() };
} else {
@@ -31,21 +32,21 @@ function authHeaders(): any {
}
}
export function queryIterator(databaseId: string, collection: Collection, query: string): any {
export function queryIterator(databaseId: string, collection: Collection, query: string): MinimalQueryIterator {
let continuationToken: string;
return {
fetchNext: () => {
return queryDocuments(databaseId, collection, false, query).then(response => {
continuationToken = response.continuationToken;
const headers = {} as any;
response.headers.forEach((value: any, key: any) => {
const headers: { [key: string]: string | number } = {};
response.headers.forEach((value, key) => {
headers[key] = value;
});
return {
resources: response.documents,
headers,
requestCharge: headers[CosmosSDKConstants.HttpHeaders.RequestCharge],
activityId: headers[CosmosSDKConstants.HttpHeaders.ActivityId],
requestCharge: Number(headers[CosmosSDKConstants.HttpHeaders.RequestCharge]),
activityId: String(headers[CosmosSDKConstants.HttpHeaders.ActivityId]),
hasMoreResults: !!continuationToken
};
});
@@ -114,14 +115,15 @@ export function queryDocuments(
headers: response.headers
};
}
return errorHandling(response, "querying documents", params);
errorHandling(response, "querying documents", params);
return undefined;
});
}
export function readDocument(
databaseId: string,
collection: Collection,
documentId: ViewModels.DocumentId
documentId: DocumentId
): Promise<DataModels.DocumentId> {
const databaseAccount = CosmosClient.databaseAccount();
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
@@ -165,7 +167,7 @@ export function createDocument(
databaseId: string,
collection: Collection,
partitionKeyProperty: string,
documentContent: any
documentContent: unknown
): Promise<DataModels.DocumentId> {
const databaseAccount = CosmosClient.databaseAccount();
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
@@ -203,8 +205,8 @@ export function createDocument(
export function updateDocument(
databaseId: string,
collection: Collection,
documentId: ViewModels.DocumentId,
documentContent: any
documentId: DocumentId,
documentContent: string
): Promise<DataModels.DocumentId> {
const databaseAccount = CosmosClient.databaseAccount();
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
@@ -244,11 +246,7 @@ export function updateDocument(
});
}
export function deleteDocument(
databaseId: string,
collection: Collection,
documentId: ViewModels.DocumentId
): Promise<any> {
export function deleteDocument(databaseId: string, collection: Collection, documentId: DocumentId): Promise<void> {
const databaseAccount = CosmosClient.databaseAccount();
const resourceEndpoint = databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint;
const idComponents = documentId.self.split("/");
@@ -295,7 +293,7 @@ export function createMongoCollectionWithProxy(
sharedThroughput: boolean,
isSharded: boolean,
autopilotOptions?: DataModels.RpOptions
): Promise<any> {
): Promise<DataModels.Collection> {
const databaseAccount = CosmosClient.databaseAccount();
const params: DataModels.MongoParameters = {
resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint,
@@ -335,7 +333,7 @@ export function createMongoCollectionWithProxy(
)
.then(response => {
if (response.ok) {
return undefined;
return response.json();
}
return errorHandling(response, "creating collection", params);
});
@@ -352,7 +350,7 @@ export function createMongoCollectionWithARM(
sharedThroughput: boolean,
isSharded: boolean,
additionalOptions?: DataModels.RpOptions
): Promise<any> {
): Promise<DataModels.CreateCollectionWithRpResponse> {
const databaseAccount = CosmosClient.databaseAccount();
const params: DataModels.MongoParameters = {
resourceUrl: databaseAccount.properties.mongoEndpoint || databaseAccount.properties.documentEndpoint,
@@ -383,7 +381,7 @@ export function createMongoCollectionWithARM(
return _createMongoCollectionWithARM(armEndpoint, params, additionalOptions);
}
export function getEndpoint(databaseAccount: ViewModels.DatabaseAccount): string {
export function getEndpoint(databaseAccount: DataModels.DatabaseAccount): string {
const serverId = window.dataExplorer.serverId();
const extensionEndpoint = window.dataExplorer.extensionEndpoint();
let url = config.MONGO_BACKEND_ENDPOINT
@@ -396,7 +394,9 @@ export function getEndpoint(databaseAccount: ViewModels.DatabaseAccount): string
return url;
}
async function errorHandling(response: any, action: string, params: any): Promise<any> {
// TODO: This function throws most of the time except on Forbidden which is a bit strange
// It causes problems for TypeScript understanding the types
async function errorHandling(response: Response, action: string, params: unknown): Promise<void> {
const errorMessage = await response.text();
// Log the error where the user can see it
NotificationConsoleUtils.logConsoleMessage(
@@ -404,7 +404,7 @@ async function errorHandling(response: any, action: string, params: any): Promis
`Error ${action}: ${errorMessage}, Payload: ${JSON.stringify(params)}`
);
if (response.status === HttpStatusCodes.Forbidden) {
MessageHandler.sendMessage({ type: MessageTypes.ForbiddenError, reason: errorMessage });
sendMessage({ type: MessageTypes.ForbiddenError, reason: errorMessage });
return;
}
throw new Error(errorMessage);
@@ -420,7 +420,7 @@ export async function _createMongoCollectionWithARM(
armEndpoint: string,
params: DataModels.MongoParameters,
rpOptions: DataModels.RpOptions
): Promise<any> {
): Promise<DataModels.CreateCollectionWithRpResponse> {
const rpPayloadToCreateCollection: DataModels.MongoCreationRequest = {
properties: {
resource: {
@@ -448,12 +448,13 @@ export async function _createMongoCollectionWithARM(
}
try {
await new ResourceProviderClient(armEndpoint).putAsync(
return new ResourceProviderClient<DataModels.CreateCollectionWithRpResponse>(armEndpoint).putAsync(
getARMCreateCollectionEndpoint(params),
DataExplorerConstants.ArmApiVersions.publicVersion,
rpPayloadToCreateCollection
);
} catch (response) {
return errorHandling(response, "creating collection", undefined);
errorHandling(response, "creating collection", undefined);
return undefined;
}
}

View File

@@ -6,7 +6,7 @@ import * as ViewModels from "../Contracts/ViewModels";
import { getAuthorizationHeader } from "../Utils/AuthorizationUtils";
import { CosmosClient } from "./CosmosClient";
export class NotificationsClientBase implements ViewModels.NotificationsClient {
export class NotificationsClientBase {
private _extensionEndpoint: string;
private _notificationsApiSuffix: string;
@@ -16,7 +16,7 @@ export class NotificationsClientBase implements ViewModels.NotificationsClient {
public fetchNotifications(): Q.Promise<DataModels.Notification[]> {
const deferred: Q.Deferred<DataModels.Notification[]> = Q.defer<DataModels.Notification[]>();
const databaseAccount: ViewModels.DatabaseAccount = CosmosClient.databaseAccount();
const databaseAccount = CosmosClient.databaseAccount();
const subscriptionId: string = CosmosClient.subscriptionId();
const resourceGroup: string = CosmosClient.resourceGroup();
const url: string = `${this._extensionEndpoint}${this._notificationsApiSuffix}?accountName=${databaseAccount.name}&subscriptionId=${subscriptionId}&resourceGroup=${resourceGroup}`;

View File

@@ -8,10 +8,19 @@ import { ConsoleDataType } from "../Explorer/Menus/NotificationConsole/Notificat
import { CosmosClient } from "./CosmosClient";
import { ItemDefinition, QueryIterator, Resource } from "@azure/cosmos";
import * as Logger from "./Logger";
import { NotificationConsoleUtils } from "../Utils/NotificationConsoleUtils";
import * as NotificationConsoleUtils from "../Utils/NotificationConsoleUtils";
import { QueryUtils } from "../Utils/QueryUtils";
import Explorer from "../Explorer/Explorer";
import {
getOrCreateDatabaseAndCollection,
createDocument,
queryDocuments,
queryDocumentsPage,
deleteDocument
} from "./DocumentClientUtilityBase";
import DocumentsTab from "../Explorer/Tabs/DocumentsTab";
export class QueriesClient implements ViewModels.QueriesClient {
export class QueriesClient {
private static readonly PartitionKey: DataModels.PartitionKey = {
paths: [`/${SavedQueries.PartitionKeyProperty}`],
kind: BackendDefaults.partitionKeyKind,
@@ -20,7 +29,7 @@ export class QueriesClient implements ViewModels.QueriesClient {
private static readonly FetchQuery: string = "SELECT * FROM c";
private static readonly FetchMongoQuery: string = "{}";
public constructor(private container: ViewModels.Explorer) {}
public constructor(private container: Explorer) {}
public async setupQueriesCollection(): Promise<DataModels.Collection> {
const queriesCollection: ViewModels.Collection = this.findQueriesCollection();
@@ -32,14 +41,13 @@ export class QueriesClient implements ViewModels.QueriesClient {
ConsoleDataType.InProgress,
"Setting up account for saving queries"
);
return this.container.documentClientUtility
.getOrCreateDatabaseAndCollection({
collectionId: SavedQueries.CollectionName,
databaseId: SavedQueries.DatabaseName,
partitionKey: QueriesClient.PartitionKey,
offerThroughput: SavedQueries.OfferThroughput,
databaseLevelThroughput: undefined
})
return getOrCreateDatabaseAndCollection({
collectionId: SavedQueries.CollectionName,
databaseId: SavedQueries.DatabaseName,
partitionKey: QueriesClient.PartitionKey,
offerThroughput: SavedQueries.OfferThroughput,
databaseLevelThroughput: undefined
})
.then(
(collection: DataModels.Collection) => {
NotificationConsoleUtils.logConsoleMessage(
@@ -88,8 +96,7 @@ export class QueriesClient implements ViewModels.QueriesClient {
`Saving query ${query.queryName}`
);
query.id = query.queryName;
return this.container.documentClientUtility
.createDocument(queriesCollection, query)
return createDocument(queriesCollection, query)
.then(
(savedQuery: DataModels.Query) => {
NotificationConsoleUtils.logConsoleMessage(
@@ -130,17 +137,11 @@ export class QueriesClient implements ViewModels.QueriesClient {
const options: any = { enableCrossPartitionQuery: true };
const id = NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.InProgress, "Fetching saved queries");
return this.container.documentClientUtility
.queryDocuments(SavedQueries.DatabaseName, SavedQueries.CollectionName, this.fetchQueriesQuery(), options)
return queryDocuments(SavedQueries.DatabaseName, SavedQueries.CollectionName, this.fetchQueriesQuery(), options)
.then(
(queryIterator: QueryIterator<ItemDefinition & Resource>) => {
const fetchQueries = (firstItemIndex: number): Q.Promise<ViewModels.QueryResults> =>
this.container.documentClientUtility.queryDocumentsPage(
queriesCollection.id(),
queryIterator,
firstItemIndex,
options
);
queryDocumentsPage(queriesCollection.id(), queryIterator, firstItemIndex, options);
return QueryUtils.queryAllPages(fetchQueries).then(
(results: ViewModels.QueryResults) => {
let queries: DataModels.Query[] = _.map(results.documents, (document: DataModels.Query) => {
@@ -216,17 +217,16 @@ export class QueriesClient implements ViewModels.QueriesClient {
`Deleting query ${query.queryName}`
);
query.id = query.queryName;
const documentId: ViewModels.DocumentId = new DocumentId(
const documentId = new DocumentId(
{
partitionKey: QueriesClient.PartitionKey,
partitionKeyProperty: "id"
} as ViewModels.DocumentsTab,
} as DocumentsTab,
query,
query.queryName
); // TODO: Remove DocumentId's dependency on DocumentsTab
const options: any = { partitionKey: query.resourceId };
return this.container.documentClientUtility
.deleteDocument(queriesCollection, documentId)
return deleteDocument(queriesCollection, documentId)
.then(
() => {
NotificationConsoleUtils.logConsoleMessage(
@@ -249,7 +249,7 @@ export class QueriesClient implements ViewModels.QueriesClient {
}
public getResourceId(): string {
const databaseAccount: ViewModels.DatabaseAccount = CosmosClient.databaseAccount();
const databaseAccount = CosmosClient.databaseAccount();
const databaseAccountName: string = (databaseAccount && databaseAccount.name) || "";
const subscriptionId: string = CosmosClient.subscriptionId() || "";
const resourceGroup: string = CosmosClient.resourceGroup() || "";

View File

@@ -0,0 +1,16 @@
jest.mock("../../Utils/arm/request");
jest.mock("../MessageHandler");
import { deleteCollection } from "./deleteCollection";
import { armRequest } from "../../Utils/arm/request";
import { AuthType } from "../../AuthType";
import { sendCachedDataMessage } from "../MessageHandler";
describe("deleteCollection", () => {
it("should call ARM if logged in with AAD", async () => {
window.authType = AuthType.AAD;
(sendCachedDataMessage as jest.Mock).mockResolvedValue(undefined);
await deleteCollection("database", "collection");
expect(armRequest).toHaveBeenCalled();
});
// TODO: Test non-AAD case
});

View File

@@ -0,0 +1,31 @@
import { CosmosClient } from "../CosmosClient";
import { refreshCachedResources } from "../DataAccessUtilityBase";
import { logConsoleProgress, logConsoleInfo, logConsoleError } from "../../Utils/NotificationConsoleUtils";
import { AuthType } from "../../AuthType";
import { deleteSqlContainer } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
export async function deleteCollection(databaseId: string, collectionId: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting container ${collectionId}`);
try {
if (window.authType === AuthType.AAD) {
await deleteSqlContainer(
CosmosClient.subscriptionId(),
CosmosClient.resourceGroup(),
CosmosClient.databaseAccount().name,
databaseId,
collectionId
);
} else {
await CosmosClient.client()
.database(databaseId)
.container(collectionId)
.delete();
}
} catch (error) {
logConsoleError(`Error while deleting container ${collectionId}:\n ${JSON.stringify(error)}`);
throw error;
}
logConsoleInfo(`Successfully deleted container ${collectionId}`);
clearMessage();
await refreshCachedResources();
}

View File

@@ -0,0 +1,16 @@
jest.mock("../../Utils/arm/request");
jest.mock("../MessageHandler");
import { deleteDatabase } from "./deleteDatabase";
import { armRequest } from "../../Utils/arm/request";
import { AuthType } from "../../AuthType";
import { sendCachedDataMessage } from "../MessageHandler";
describe("deleteDatabase", () => {
it("should call ARM if logged in with AAD", async () => {
window.authType = AuthType.AAD;
(sendCachedDataMessage as jest.Mock).mockResolvedValue(undefined);
await deleteDatabase("database");
expect(armRequest).toHaveBeenCalled();
});
// TODO: Test non-AAD case
});

View File

@@ -0,0 +1,34 @@
import { CosmosClient } from "../CosmosClient";
import { refreshCachedResources } from "../DataAccessUtilityBase";
import { logConsoleProgress, logConsoleError, logConsoleInfo } from "../../Utils/NotificationConsoleUtils";
import { deleteSqlDatabase } from "../../Utils/arm/generatedClients/2020-04-01/sqlResources";
import { AuthType } from "../../AuthType";
import { logError } from "../Logger";
import { sendNotificationForError } from "./sendNotificationForError";
export async function deleteDatabase(databaseId: string): Promise<void> {
const clearMessage = logConsoleProgress(`Deleting database ${databaseId}`);
try {
if (window.authType === AuthType.AAD) {
await deleteSqlDatabase(
CosmosClient.subscriptionId(),
CosmosClient.resourceGroup(),
CosmosClient.databaseAccount().name,
databaseId
);
} else {
await CosmosClient.client()
.database(databaseId)
.delete();
}
} catch (error) {
logConsoleError(`Error while deleting database ${databaseId}:\n ${JSON.stringify(error)}`);
logError(JSON.stringify(error), "DeleteDatabase", error.code);
sendNotificationForError(error);
throw error;
}
logConsoleInfo(`Successfully deleted database ${databaseId}`);
clearMessage();
await refreshCachedResources();
}

View File

@@ -0,0 +1,20 @@
import * as Constants from "../Constants";
import { sendMessage } from "../MessageHandler";
import { MessageTypes } from "../../Contracts/ExplorerContracts";
interface CosmosError {
code: number;
message?: string;
}
export function sendNotificationForError(error: CosmosError): void {
if (error && error.code === Constants.HttpStatusCodes.Forbidden) {
if (error.message && error.message.toLowerCase().indexOf("sharedoffer is disabled for your account") > 0) {
return;
}
sendMessage({
type: MessageTypes.ForbiddenError,
reason: error && error.message ? error.message : error
});
}
}

View File

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

View File

@@ -437,10 +437,8 @@ export interface Tenant {
export interface AccountKeys {
primaryMasterKey: string;
secondaryMasterKey: string;
properties: {
primaryReadonlyMasterKey: string;
secondaryReadonlyMasterKey: string;
};
primaryReadonlyMasterKey: string;
secondaryReadonlyMasterKey: string;
}
export interface AfecFeature {
@@ -553,6 +551,7 @@ export interface GraphParameters extends RpParameters {
pk: string;
coll: string;
cd: Boolean;
indexingPolicy?: IndexingPolicy;
}
export interface CreationRequest {
@@ -570,6 +569,7 @@ export interface SqlCollectionParameters extends RpParameters {
coll: string;
cd: Boolean;
analyticalStorageTtl?: number;
indexingPolicy?: IndexingPolicy;
}
export interface MongoCreationRequest extends CreationRequest {
@@ -588,6 +588,7 @@ export interface GraphCreationRequest extends CreationRequest {
resource: {
id: string;
partitionKey: {};
indexingPolicy?: IndexingPolicy;
};
options: RpOptions;
};
@@ -613,6 +614,7 @@ export interface SqlCollectionCreationRequest extends CreationRequest {
id: string;
partitionKey: {};
analyticalStorageTtl?: number;
indexingPolicy?: IndexingPolicy;
};
options: RpOptions;
};

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -12,7 +12,7 @@ import {
PortalTheme
} from "./HeatmapDatatypes";
import { isInvalidParentFrameOrigin } from "../../Utils/MessageValidation";
import { MessageHandler } from "../../Common/MessageHandler";
import { sendCachedDataMessage, sendMessage } from "../../Common/MessageHandler";
import { MessageTypes } from "../../Contracts/ExplorerContracts";
import { StyleConstants } from "../../Common/Constants";
import "./Heatmap.less";
@@ -198,7 +198,7 @@ export class Heatmap {
let timeSelected: string = data.points[0].x;
timeSelected = timeSelected.replace(" ", "T");
timeSelected = `${timeSelected}Z`;
let xAxisIndex;
let xAxisIndex = 0;
for (let i = 0; i < this._chartData.xAxisPoints.length; i++) {
if (this._chartData.xAxisPoints[i] === timeSelected) {
xAxisIndex = i;
@@ -209,7 +209,7 @@ export class Heatmap {
for (let i = 0; i < this._chartData.dataPoints.length; i++) {
output.push(this._chartData.dataPoints[i][xAxisIndex]);
}
MessageHandler.sendCachedDataMessage(MessageTypes.LogInfo, output);
sendCachedDataMessage(MessageTypes.LogInfo, output);
});
}
}
@@ -234,7 +234,8 @@ export function handleMessage(event: MessageEvent) {
return;
}
Plotly.purge(Heatmap.elementId);
document.getElementById(Heatmap.elementId).innerHTML = "";
document.getElementById(Heatmap.elementId)!.innerHTML = "";
const data = event.data.data;
const chartData: DataPayload = data.chartData;
const chartSettings: HeatmapCaptions = data.chartSettings;
@@ -259,10 +260,10 @@ export function handleMessage(event: MessageEvent) {
noDataMessageContent.classList.add("dark-theme");
}
document.getElementById(Heatmap.elementId).appendChild(chartTitleElement);
document.getElementById(Heatmap.elementId).appendChild(noDataMessageElement);
document.getElementById(Heatmap.elementId)!.appendChild(chartTitleElement);
document.getElementById(Heatmap.elementId)!.appendChild(noDataMessageElement);
}
}
window.addEventListener("message", handleMessage, false);
MessageHandler.sendMessage("ready");
sendMessage("ready");

View File

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

View File

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

View File

@@ -12,6 +12,10 @@ import AddTriggerIcon from "../../images/AddTrigger.svg";
import DeleteTriggerIcon from "../../images/DeleteTrigger.svg";
import DeleteUDFIcon from "../../images/DeleteUDF.svg";
import DeleteSprocIcon from "../../images/DeleteSproc.svg";
import Explorer from "./Explorer";
import UserDefinedFunction from "./Tree/UserDefinedFunction";
import StoredProcedure from "./Tree/StoredProcedure";
import Trigger from "./Tree/Trigger";
export interface CollectionContextMenuButtonParams {
databaseId: string;
@@ -26,7 +30,7 @@ export interface DatabaseContextMenuButtonParams {
*/
export class ResourceTreeContextMenuButtonFactory {
public static createDatabaseContextMenu(
container: ViewModels.Explorer,
container: Explorer,
selectedDatabase: ViewModels.Database
): TreeNodeMenuItem[] {
const newCollectionMenuItem: TreeNodeMenuItem = {
@@ -44,7 +48,7 @@ export class ResourceTreeContextMenuButtonFactory {
}
public static createCollectionContextMenuButton(
container: ViewModels.Explorer,
container: Explorer,
selectedCollection: ViewModels.Collection
): TreeNodeMenuItem[] {
const items: TreeNodeMenuItem[] = [];
@@ -115,8 +119,8 @@ export class ResourceTreeContextMenuButtonFactory {
}
public static createStoreProcedureContextMenuItems(
container: ViewModels.Explorer,
storedProcedure: ViewModels.StoredProcedure
container: Explorer,
storedProcedure: StoredProcedure
): TreeNodeMenuItem[] {
if (container.isPreferredApiCassandra()) {
return [];
@@ -131,10 +135,7 @@ export class ResourceTreeContextMenuButtonFactory {
];
}
public static createTriggerContextMenuItems(
container: ViewModels.Explorer,
trigger: ViewModels.Trigger
): TreeNodeMenuItem[] {
public static createTriggerContextMenuItems(container: Explorer, trigger: Trigger): TreeNodeMenuItem[] {
if (container.isPreferredApiCassandra()) {
return [];
}
@@ -149,8 +150,8 @@ export class ResourceTreeContextMenuButtonFactory {
}
public static createUserDefinedFunctionContextMenuItems(
container: ViewModels.Explorer,
userDefinedFunction: ViewModels.UserDefinedFunction
container: Explorer,
userDefinedFunction: UserDefinedFunction
): TreeNodeMenuItem[] {
if (container.isPreferredApiCassandra()) {
return [];

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 { ITextFieldProps, TextField } from "office-ui-fabric-react/lib/TextField";
import { Link } from "office-ui-fabric-react/lib/Link";
import { FontIcon } from "office-ui-fabric-react";
export interface TextFieldProps extends ITextFieldProps {
label: string;
@@ -31,6 +32,8 @@ export interface DialogProps {
onSecondaryButtonClick: () => void;
primaryButtonDisabled?: boolean;
type?: DialogType;
showCloseButton?: boolean;
onDismiss?: () => void;
}
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 },
subText: { fontSize: DIALOG_SUBTEXT_FONT_SIZE }
},
showCloseButton: false
showCloseButton: this.props.showCloseButton || false,
onDismiss: this.props.onDismiss
},
modalProps: { isBlocking: this.props.isModal },
minWidth: DIALOG_MIN_WIDTH,
@@ -81,7 +85,7 @@ export class DialogComponent extends React.Component<DialogProps, {}> {
{textFieldProps && <TextField {...textFieldProps} />}
{linkProps && (
<Link href={linkProps.linkUrl} target="_blank">
{linkProps.linkText}
{linkProps.linkText} <FontIcon iconName="NavigateExternalInline" />
</Link>
)}
<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)",
"elevation8": "0 3.2px 7.2px 0 rgba(0, 0, 0, 0.132), 0 0.6px 1.8px 0 rgba(0, 0, 0, 0.108)",
"roundedCorner2": "2px",
"roundedCorner4": "4px",
"roundedCorner6": "6px",
},
"fonts": Object {
"large": Object {
@@ -421,6 +423,8 @@ exports[`test render renders with filters 1`] = `
"elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)",
"elevation8": "0 3.2px 7.2px 0 rgba(0, 0, 0, 0.132), 0 0.6px 1.8px 0 rgba(0, 0, 0, 0.108)",
"roundedCorner2": "2px",
"roundedCorner4": "4px",
"roundedCorner6": "6px",
},
"fonts": Object {
"large": Object {
@@ -812,6 +816,8 @@ exports[`test render renders with filters 1`] = `
"elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)",
"elevation8": "0 3.2px 7.2px 0 rgba(0, 0, 0, 0.132), 0 0.6px 1.8px 0 rgba(0, 0, 0, 0.108)",
"roundedCorner2": "2px",
"roundedCorner4": "4px",
"roundedCorner6": "6px",
},
"fonts": Object {
"large": Object {
@@ -1588,6 +1594,8 @@ exports[`test render renders with filters 1`] = `
"elevation64": "0 25.6px 57.6px 0 rgba(0, 0, 0, 0.22), 0 4.8px 14.4px 0 rgba(0, 0, 0, 0.18)",
"elevation8": "0 3.2px 7.2px 0 rgba(0, 0, 0, 0.132), 0 0.6px 1.8px 0 rgba(0, 0, 0, 0.108)",
"roundedCorner2": "2px",
"roundedCorner4": "4px",
"roundedCorner6": "6px",
},
"fonts": Object {
"large": Object {

View File

@@ -23,8 +23,5 @@ interface ErrorDisplayParams {
}
class ErrorDisplayViewModel {
private params: ErrorDisplayParams;
public constructor(params: ErrorDisplayParams) {
this.params = params;
}
public constructor(public params: ErrorDisplayParams) {}
}

View File

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

View File

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

View File

@@ -1,6 +1,5 @@
import { DefaultButton, IButtonProps, ITextFieldProps, TextField } from "office-ui-fabric-react";
import * as React from "react";
import * as ViewModels from "../../../Contracts/ViewModels";
import * as Constants from "../../../Common/Constants";
import { Action } from "../../../Shared/Telemetry/TelemetryConstants";
import { RepoListItem } from "./GitHubReposComponent";
@@ -9,9 +8,10 @@ import * as GitHubUtils from "../../../Utils/GitHubUtils";
import { IGitHubRepo } from "../../../GitHub/GitHubClient";
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import UrlUtility from "../../../Common/UrlUtility";
import Explorer from "../../Explorer";
export interface AddRepoComponentProps {
container: ViewModels.Explorer;
container: Explorer;
getRepo: (owner: string, repo: string) => Promise<IGitHubRepo>;
pinRepo: (item: RepoListItem) => void;
}

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,40 +0,0 @@
import * as React from "react";
import { DetailsList, IColumn, SelectionMode } from "office-ui-fabric-react/lib/DetailsList";
import { Library } from "../../../Contracts/DataModels";
export interface ClusterLibraryItem extends Library {
installed: boolean;
}
export interface ClusterLibraryGridProps {
libraryItems: ClusterLibraryItem[];
onInstalledChanged: (libraryName: string, installed: boolean) => void;
}
export function ClusterLibraryGrid(props: ClusterLibraryGridProps): JSX.Element {
const onInstalledChanged = (e: React.FormEvent<HTMLInputElement>) => {
const target = e.target;
const libraryName = (target as any).dataset.name;
const checked = (target as any).checked;
return props.onInstalledChanged(libraryName, checked);
};
const columns: IColumn[] = [
{
key: "name",
name: "Name",
fieldName: "name",
minWidth: 150
},
{
key: "installed",
name: "Installed",
minWidth: 100,
onRender: (item: ClusterLibraryItem) => {
return <input type="checkbox" checked={item.installed} onChange={onInstalledChanged} data-name={item.name} />;
}
}
];
return <DetailsList columns={columns} items={props.libraryItems} selectionMode={SelectionMode.none} />;
}

View File

@@ -1,11 +0,0 @@
import * as React from "react";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import { ClusterLibraryGrid, ClusterLibraryGridProps } from "./ClusterLibraryGrid";
export class ClusterLibraryGridAdapter implements ReactAdapter {
public parameters: ko.Observable<ClusterLibraryGridProps>;
public renderComponent(): JSX.Element {
return <ClusterLibraryGrid {...this.parameters()} />;
}
}

View File

@@ -1,156 +0,0 @@
import * as React from "react";
import DeleteIcon from "../../../../images/delete.svg";
import { Button } from "office-ui-fabric-react/lib/Button";
import { DetailsList, IColumn, SelectionMode } from "office-ui-fabric-react/lib/DetailsList";
import { Library } from "../../../Contracts/DataModels";
import { Label } from "office-ui-fabric-react/lib/Label";
import { SparkLibrary } from "../../../Common/Constants";
import { TextField } from "office-ui-fabric-react/lib/TextField";
export interface LibraryManageComponentProps {
addProps: {
nameProps: LibraryAddNameTextFieldProps;
urlProps: LibraryAddUrlTextFieldProps;
buttonProps: LibraryAddButtonProps;
};
gridProps: LibraryManageGridProps;
}
export function LibraryManageComponent(props: LibraryManageComponentProps): JSX.Element {
const {
addProps: { nameProps, urlProps, buttonProps },
gridProps
} = props;
return (
<div>
<div className="library-add-container">
<LibraryAddNameTextField {...nameProps} />
<LibraryAddUrlTextField {...urlProps} />
<LibraryAddButton {...buttonProps} />
</div>
<div className="library-grid-container">
<Label>All Libraries</Label>
<LibraryManageGrid {...gridProps} />
</div>
</div>
);
}
export interface LibraryManageGridProps {
items: Library[];
onLibraryDeleteClick: (libraryName: string) => void;
}
function LibraryManageGrid(props: LibraryManageGridProps): JSX.Element {
const columns: IColumn[] = [
{
key: "name",
name: "Name",
fieldName: "name",
minWidth: 200
},
{
key: "delete",
name: "Delete",
minWidth: 60,
onRender: (item: Library) => {
const onDelete = () => {
props.onLibraryDeleteClick(item.name);
};
return (
<span className="library-delete">
<img src={DeleteIcon} alt="Delete" onClick={onDelete} />
</span>
);
}
}
];
return <DetailsList columns={columns} items={props.items} selectionMode={SelectionMode.none} />;
}
export interface LibraryAddButtonProps {
disabled: boolean;
onLibraryAddClick: (event: React.FormEvent<any>) => void;
}
function LibraryAddButton(props: LibraryAddButtonProps): JSX.Element {
return (
<Button text="Add" className="library-add-button" onClick={props.onLibraryAddClick} disabled={props.disabled} />
);
}
export interface LibraryAddUrlTextFieldProps {
libraryAddress: string;
onLibraryAddressChange: (libraryAddress: string) => void;
onLibraryAddressValidated: (errorMessage: string, value: string) => void;
}
function LibraryAddUrlTextField(props: LibraryAddUrlTextFieldProps): JSX.Element {
const handleTextChange = (e: React.FormEvent<any>, libraryAddress: string) => {
props.onLibraryAddressChange(libraryAddress);
};
const validateText = (text: string): string => {
if (!text) {
return "";
}
const libraryUrlRegex = /^(https:\/\/.+\/)(.+)\.(jar)$/gi;
const isValidUrl = libraryUrlRegex.test(text);
if (isValidUrl) {
return "";
}
return "Need to be a valid https uri";
};
return (
<TextField
value={props.libraryAddress}
label="Url"
type="url"
className="library-add-textfield"
onChange={handleTextChange}
onGetErrorMessage={validateText}
onNotifyValidationResult={props.onLibraryAddressValidated}
placeholder="https://myrepo/myjar.jar"
autoComplete="off"
/>
);
}
export interface LibraryAddNameTextFieldProps {
libraryName: string;
onLibraryNameChange: (libraryName: string) => void;
onLibraryNameValidated: (errorMessage: string, value: string) => void;
}
function LibraryAddNameTextField(props: LibraryAddNameTextFieldProps): JSX.Element {
const handleTextChange = (e: React.FormEvent<any>, libraryName: string) => {
props.onLibraryNameChange(libraryName);
};
const validateText = (text: string): string => {
if (!text) {
return "";
}
const length = text.length;
if (length < SparkLibrary.nameMinLength || length > SparkLibrary.nameMaxLength) {
return "Library name length need to be between 3 and 63.";
}
const nameRegex = /^[a-z0-9][-a-z0-9]*[a-z0-9]$/gi;
const isValidUrl = nameRegex.test(text);
if (isValidUrl) {
return "";
}
return "Need to be a valid name. Letters, numbers and - are allowed";
};
return (
<TextField
value={props.libraryName}
label="Name"
type="text"
className="library-add-textfield"
onChange={handleTextChange}
onGetErrorMessage={validateText}
onNotifyValidationResult={props.onLibraryNameValidated}
placeholder="myjar"
autoComplete="off"
/>
);
}

View File

@@ -1,11 +0,0 @@
import * as React from "react";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import { LibraryManageComponent, LibraryManageComponentProps } from "./LibraryManage";
export class LibraryManageComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<LibraryManageComponentProps>;
public renderComponent(): JSX.Element {
return <LibraryManageComponent {...this.parameters()} />;
}
}

View File

@@ -5,7 +5,7 @@
import * as React from "react";
import * as DataModels from "../../../Contracts/DataModels";
import * as Logger from "../../../Common/Logger";
import { NotificationConsoleUtils } from "../../../Utils/NotificationConsoleUtils";
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
import { StringUtils } from "../../../Utils/StringUtils";

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
},
isFavorite: false,
showDownload: true,
showDelete: true,
onClick: undefined,
onTagClick: undefined,

View File

@@ -1,4 +1,4 @@
import { Card, ICardTokens } from "@uifabric/react-cards";
import { Card } from "@uifabric/react-cards";
import {
FontWeights,
Icon,
@@ -17,10 +17,13 @@ import {
import * as React from "react";
import { IGalleryItem } from "../../../../Juno/JunoClient";
import { FileSystemUtil } from "../../../Notebook/FileSystemUtil";
import CosmosDBLogo from "../../../../../images/CosmosDB-logo.svg";
import { StyleConstants } from "../../../../Common/Constants";
export interface GalleryCardComponentProps {
data: IGalleryItem;
isFavorite: boolean;
showDownload: boolean;
showDelete: boolean;
onClick: () => void;
onTagClick: (tag: string) => void;
@@ -31,98 +34,127 @@ export interface GalleryCardComponentProps {
}
export class GalleryCardComponent extends React.Component<GalleryCardComponentProps> {
public static readonly CARD_HEIGHT = 384;
public static readonly CARD_WIDTH = 256;
private static readonly cardImageHeight = 144;
public static readonly cardHeightToWidthRatio =
GalleryCardComponent.cardImageHeight / GalleryCardComponent.CARD_WIDTH;
private static readonly cardDescriptionMaxChars = 88;
private static readonly cardTokens: ICardTokens = {
width: GalleryCardComponent.CARD_WIDTH,
height: GalleryCardComponent.CARD_HEIGHT,
childrenGap: 8,
childrenMargin: 10
};
private static readonly cardItemGapBig = 10;
private static readonly cardItemGapSmall = 8;
public render(): JSX.Element {
const cardButtonsVisible = this.props.isFavorite !== undefined || this.props.showDownload || this.props.showDelete;
const options: Intl.DateTimeFormatOptions = {
year: "numeric",
month: "short",
day: "numeric"
};
const dateString = new Date(this.props.data.created).toLocaleString("default", options);
const cardTitle = FileSystemUtil.stripExtension(this.props.data.name, "ipynb");
return (
<Card aria-label="Notebook Card" tokens={GalleryCardComponent.cardTokens} onClick={this.props.onClick}>
<Card.Item>
<Persona text={this.props.data.author} secondaryText={dateString} />
</Card.Item>
<Card.Item fill>
<Image
src={
this.props.data.thumbnailUrl ||
`https://placehold.it/${GalleryCardComponent.CARD_WIDTH}x${GalleryCardComponent.cardImageHeight}`
}
width={GalleryCardComponent.CARD_WIDTH}
height={GalleryCardComponent.cardImageHeight}
imageFit={ImageFit.cover}
alt="Notebook cover image"
<Card
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
imageUrl={this.props.data.isSample && CosmosDBLogo}
text={this.props.data.author}
secondaryText={dateString}
/>
</Card.Item>
<Card.Section>
<Card.Item>
<Image
src={this.props.data.thumbnailUrl}
width={GalleryCardComponent.CARD_WIDTH}
height={GalleryCardComponent.cardImageHeight}
imageFit={ImageFit.cover}
alt={`${cardTitle} cover image`}
/>
</Card.Item>
<Card.Section styles={{ root: { padding: GalleryCardComponent.cardItemGapBig } }}>
<Text variant="small" nowrap>
{this.props.data.tags?.map((tag, index, array) => (
<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 ? <></> : ", "}
</span>
))}
</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 variant="small" styles={{ root: { height: 36 } }}>
{this.props.data.description.substr(0, GalleryCardComponent.cardDescriptionMaxChars)}
</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 horizontal styles={{ root: { alignItems: "flex-end" } }}>
<Text variant="tiny" styles={{ root: { color: "#ccc" } }}>
<Icon iconName="RedEye" styles={{ root: { verticalAlign: "middle" } }} /> {this.props.data.views}
</Text>
<Text variant="tiny" styles={{ root: { color: "#ccc" } }}>
<Icon iconName="Download" styles={{ root: { verticalAlign: "middle" } }} /> {this.props.data.downloads}
</Text>
<Text variant="tiny" styles={{ root: { color: "#ccc" } }}>
<Icon iconName="Heart" styles={{ root: { verticalAlign: "middle" } }} /> {this.props.data.favorites}
</Text>
</Card.Section>
{cardButtonsVisible && (
<Card.Section
styles={{
root: {
marginLeft: GalleryCardComponent.cardItemGapBig,
marginRight: GalleryCardComponent.cardItemGapBig
}
}}
>
<Separator styles={{ root: { padding: 0, height: 1 } }} />
<Card.Item>
<Separator styles={{ root: { padding: 0, height: 1 } }} />
</Card.Item>
<span>
{this.props.isFavorite !== undefined &&
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.generateIconButtonWithTooltip(
this.props.isFavorite ? "HeartFill" : "Heart",
this.props.isFavorite ? "Unlike" : "Like",
this.props.isFavorite ? this.onUnfavoriteClick : this.onFavoriteClick
)}
{this.props.showDownload &&
this.generateIconButtonWithTooltip("Download", "Download", "left", this.props.onDownloadClick)}
{this.generateIconButtonWithTooltip("Download", "Download", this.onDownloadClick)}
{this.props.showDelete && (
<div style={{ width: "100%", textAlign: "right" }}>
{this.generateIconButtonWithTooltip("Delete", "Remove", this.props.onDeleteClick)}
</div>
)}
</Card.Section>
{this.props.showDelete &&
this.generateIconButtonWithTooltip("Delete", "Remove", "right", this.props.onDeleteClick)}
</span>
</Card.Section>
)}
</Card>
);
}
private generateIconText = (iconName: string, text: string): JSX.Element => {
return (
<Text
variant="tiny"
styles={{ root: { color: StyleConstants.BaseMedium, paddingRight: GalleryCardComponent.cardItemGapSmall } }}
>
<Icon iconName={iconName} styles={{ root: { verticalAlign: "middle" } }} /> {text}
</Text>
);
};
/*
* Fluent UI doesn't support tooltips on IconButtons out of the box. In the meantime the recommendation is
* to do the following (from https://developer.microsoft.com/en-us/fluentui#/controls/web/button)
@@ -130,70 +162,37 @@ export class GalleryCardComponent extends React.Component<GalleryCardComponentPr
private generateIconButtonWithTooltip = (
iconName: string,
title: string,
onClick: (
event: React.MouseEvent<
HTMLAnchorElement | HTMLButtonElement | HTMLDivElement | BaseButton | Button | HTMLSpanElement,
MouseEvent
>
) => void
horizontalAlign: "right" | "left",
activate: () => void
): JSX.Element => {
return (
<TooltipHost
content={title}
id={`TooltipHost-IconButton-${iconName}`}
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>
);
};
private onTagClick = (
event: React.MouseEvent<HTMLElement | HTMLAnchorElement | HTMLButtonElement | LinkBase, MouseEvent>,
tag: string
private onClick = (
event:
| React.MouseEvent<HTMLElement | HTMLAnchorElement | HTMLButtonElement | LinkBase, MouseEvent>
| React.MouseEvent<
HTMLAnchorElement | HTMLButtonElement | HTMLDivElement | BaseButton | Button | HTMLSpanElement,
MouseEvent
>,
activate: () => void
): void => {
event.stopPropagation();
this.props.onTagClick(tag);
};
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();
event.preventDefault();
activate();
};
}

View File

@@ -2,34 +2,47 @@
exports[`GalleryCardComponent renders 1`] = `
<Card
aria-label="Notebook Card"
aria-label="name"
data-is-focusable="true"
onClick={[Function]}
tokens={
Object {
"childrenGap": 8,
"childrenMargin": 10,
"height": 384,
"childrenGap": 0,
"width": 256,
}
}
>
<CardItem>
<CardItem
tokens={
Object {
"padding": 10,
}
}
>
<StyledPersonaBase
imageUrl={false}
secondaryText="Invalid Date"
text="author"
/>
</CardItem>
<CardItem
fill={true}
>
<StyledImageBase
alt="Notebook cover image"
<CardItem>
<Memo(StyledImageBase)
alt="name cover image"
height={144}
imageFit={2}
src="thumbnailUrl"
width={256}
/>
</CardItem>
<CardSection>
<CardSection
styles={
Object {
"root": Object {
"padding": 10,
},
}
}
>
<Text
nowrap={true}
variant="small"
@@ -50,6 +63,8 @@ exports[`GalleryCardComponent renders 1`] = `
Object {
"root": Object {
"fontWeight": 600,
"paddingBottom": 8,
"paddingTop": 8,
},
}
}
@@ -68,88 +83,91 @@ exports[`GalleryCardComponent renders 1`] = `
>
description
</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
horizontal={true}
styles={
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
styles={
Object {
@@ -160,79 +178,63 @@ exports[`GalleryCardComponent renders 1`] = `
}
}
/>
</CardItem>
<CardSection
horizontal={true}
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={
<span>
<StyledTooltipHostBase
calloutProps={
Object {
"iconName": "Heart",
"gapSpace": 0,
}
}
onClick={[Function]}
title="Like"
/>
</StyledTooltipHostBase>
<StyledTooltipHostBase
calloutProps={
Object {
"gapSpace": 0,
}
}
content="Download"
id="TooltipHost-IconButton-Download"
styles={
Object {
"root": Object {
"display": "inline-block",
},
}
}
>
<CustomizedIconButton
ariaLabel="Download"
iconProps={
content="Like"
id="TooltipHost-IconButton-Heart"
styles={
Object {
"iconName": "Download",
"root": Object {
"display": "inline-block",
"float": "left",
},
}
}
onClick={[Function]}
title="Download"
/>
</StyledTooltipHostBase>
<div
style={
Object {
"textAlign": "right",
"width": "100%",
>
<CustomizedIconButton
ariaLabel="Like"
iconProps={
Object {
"iconName": "Heart",
}
}
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
calloutProps={
Object {
@@ -245,6 +247,7 @@ exports[`GalleryCardComponent renders 1`] = `
Object {
"root": Object {
"display": "inline-block",
"float": "right",
},
}
}
@@ -256,10 +259,11 @@ exports[`GalleryCardComponent renders 1`] = `
"iconName": "Delete",
}
}
onClick={[Function]}
title="Remove"
/>
</StyledTooltipHostBase>
</div>
</span>
</CardSection>
</Card>
`;

View File

@@ -0,0 +1,114 @@
import * as React from "react";
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";
import Explorer from "../../Explorer";
export interface GalleryAndNotebookViewerComponentProps {
container?: 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,
sortBy: SortBy.MostViewed,
searchText: undefined,
openNotebook: undefined,
onSelectedTabChange: undefined,
onSortByChange: undefined,
onSearchTextChange: undefined

View File

@@ -15,22 +15,23 @@ import {
} from "office-ui-fabric-react";
import * as React from "react";
import * as Logger from "../../../Common/Logger";
import * as ViewModels from "../../../Contracts/ViewModels";
import { IGalleryItem, JunoClient } from "../../../Juno/JunoClient";
import * as GalleryUtils from "../../../Utils/GalleryUtils";
import { NotificationConsoleUtils } from "../../../Utils/NotificationConsoleUtils";
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
import { DialogComponent, DialogProps } from "../DialogReactComponent/DialogComponent";
import { GalleryCardComponent, GalleryCardComponentProps } from "./Cards/GalleryCardComponent";
import "./GalleryViewerComponent.less";
import { HttpStatusCodes } from "../../../Common/Constants";
import Explorer from "../../Explorer";
export interface GalleryViewerComponentProps {
container?: ViewModels.Explorer;
container?: Explorer;
junoClient: JunoClient;
selectedTab: GalleryTab;
sortBy: SortBy;
searchText: string;
openNotebook: (data: IGalleryItem, isFavorite: boolean) => void;
onSelectedTabChange: (newTab: GalleryTab) => void;
onSortByChange: (sortBy: SortBy) => void;
onSearchTextChange: (searchText: string) => void;
@@ -66,36 +67,20 @@ interface GalleryTabInfo {
content: JSX.Element;
}
export class GalleryViewerComponent extends React.Component<GalleryViewerComponentProps, GalleryViewerComponentState>
implements GalleryUtils.DialogEnabledComponent {
export class GalleryViewerComponent extends React.Component<GalleryViewerComponentProps, GalleryViewerComponentState> {
public static readonly OfficialSamplesTitle = "Official samples";
public static readonly PublicGalleryTitle = "Public gallery";
public static readonly FavoritesTitle = "Liked";
public static readonly PublishedTitle = "Your published work";
private static readonly rowsPerPage = 5;
private static readonly mostViewedText = "Most viewed";
private static readonly mostDownloadedText = "Most downloaded";
private static readonly mostFavoritedText = "Most favorited";
private static readonly mostFavoritedText = "Most liked";
private static readonly mostRecentText = "Most recent";
private static readonly sortingOptions: IDropdownOption[] = [
{
key: SortBy.MostViewed,
text: GalleryViewerComponent.mostViewedText
},
{
key: SortBy.MostDownloaded,
text: GalleryViewerComponent.mostDownloadedText
},
{
key: SortBy.MostFavorited,
text: GalleryViewerComponent.mostFavoritedText
},
{
key: SortBy.MostRecent,
text: GalleryViewerComponent.mostRecentText
}
];
private readonly sortingOptions: IDropdownOption[];
private sampleNotebooks: IGalleryItem[];
private publicNotebooks: IGalleryItem[];
@@ -118,29 +103,40 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
dialogProps: undefined
};
this.sortingOptions = [
{
key: SortBy.MostViewed,
text: GalleryViewerComponent.mostViewedText
},
{
key: SortBy.MostDownloaded,
text: GalleryViewerComponent.mostDownloadedText
},
{
key: SortBy.MostRecent,
text: GalleryViewerComponent.mostRecentText
}
];
if (this.props.container?.isGalleryPublishEnabled()) {
this.sortingOptions.push({
key: SortBy.MostFavorited,
text: GalleryViewerComponent.mostFavoritedText
});
}
this.loadTabContent(this.state.selectedTab, this.state.searchText, this.state.sortBy, false);
if (this.props.container) {
if (this.props.container?.isGalleryPublishEnabled()) {
this.loadFavoriteNotebooks(this.state.searchText, this.state.sortBy, false); // Need this to show correct favorite button state
}
}
setDialogProps = (dialogProps: DialogProps): void => {
this.setState({ dialogProps });
};
public render(): JSX.Element {
const tabs: GalleryTabInfo[] = [this.createTab(GalleryTab.OfficialSamples, this.state.sampleNotebooks)];
if (this.props.container) {
if (this.props.container.isGalleryPublishEnabled()) {
tabs.push(this.createTab(GalleryTab.PublicGallery, this.state.publicNotebooks));
}
if (this.props.container?.isGalleryPublishEnabled()) {
tabs.push(this.createTab(GalleryTab.PublicGallery, this.state.publicNotebooks));
tabs.push(this.createTab(GalleryTab.Favorites, this.state.favoriteNotebooks));
if (this.props.container.isGalleryPublishEnabled()) {
tabs.push(this.createTab(GalleryTab.Published, this.state.publishedNotebooks));
}
tabs.push(this.createTab(GalleryTab.Published, this.state.publishedNotebooks));
}
const pivotProps: IPivotProps = {
@@ -180,8 +176,8 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
private createTabContent(data: IGalleryItem[]): JSX.Element {
return (
<Stack tokens={{ childrenGap: 20 }}>
<Stack horizontal tokens={{ childrenGap: 20 }}>
<Stack tokens={{ childrenGap: 10 }}>
<Stack horizontal tokens={{ childrenGap: 20, padding: 10 }}>
<Stack.Item grow>
<SearchBox value={this.state.searchText} placeholder="Search" onChange={this.onSearchBoxChange} />
</Stack.Item>
@@ -189,11 +185,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
<Label>Sort by</Label>
</Stack.Item>
<Stack.Item styles={{ root: { minWidth: 200 } }}>
<Dropdown
options={GalleryViewerComponent.sortingOptions}
selectedKey={this.state.sortBy}
onChange={this.onDropdownChange}
/>
<Dropdown options={this.sortingOptions} selectedKey={this.state.sortBy} onChange={this.onDropdownChange} />
</Stack.Item>
</Stack>
@@ -395,8 +387,10 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
}
private getPageSpecification = (itemIndex?: number, visibleRect?: IRectangle): IPageSpecification => {
this.columnCount = Math.floor(visibleRect.width / GalleryCardComponent.CARD_WIDTH);
this.rowCount = Math.floor(visibleRect.height / GalleryCardComponent.CARD_HEIGHT);
if (itemIndex === 0) {
this.columnCount = Math.floor(visibleRect.width / GalleryCardComponent.CARD_WIDTH) || this.columnCount;
this.rowCount = GalleryViewerComponent.rowsPerPage;
}
return {
height: visibleRect.height,
@@ -405,12 +399,16 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
};
private onRenderCell = (data?: IGalleryItem): JSX.Element => {
const isFavorite = this.favoriteNotebooks?.find(item => item.id === data.id) !== undefined;
let isFavorite: boolean;
if (this.props.container?.isGalleryPublishEnabled()) {
isFavorite = this.favoriteNotebooks?.find(item => item.id === data.id) !== undefined;
}
const props: GalleryCardComponentProps = {
data,
isFavorite,
showDownload: !!this.props.container,
showDelete: this.state.selectedTab === GalleryTab.Published,
onClick: () => this.openNotebook(data, isFavorite),
onClick: () => this.props.openNotebook(data, isFavorite),
onTagClick: this.loadTaggedItems,
onFavoriteClick: () => this.favoriteItem(data),
onUnfavoriteClick: () => this.unfavoriteItem(data),
@@ -425,19 +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
});
window.open(`/notebookViewer.html?${params.toString()}`);
}
};
private loadTaggedItems = (tag: string): void => {
const searchText = tag;
this.setState({
@@ -467,9 +452,7 @@ export class GalleryViewerComponent extends React.Component<GalleryViewerCompone
};
private downloadItem = async (data: IGalleryItem): Promise<void> => {
GalleryUtils.downloadItem(this, this.props.container, this.props.junoClient, data, item =>
this.refreshSelectedTab(item)
);
GalleryUtils.downloadItem(this.props.container, this.props.junoClient, data, item => this.refreshSelectedTab(item));
};
private deleteItem = async (data: IGalleryItem): Promise<void> => {

View File

@@ -21,7 +21,7 @@ exports[`GalleryViewerComponent renders 1`] = `
<Stack
tokens={
Object {
"childrenGap": 20,
"childrenGap": 10,
}
}
>
@@ -30,6 +30,7 @@ exports[`GalleryViewerComponent renders 1`] = `
tokens={
Object {
"childrenGap": 20,
"padding": 10,
}
}
>
@@ -67,10 +68,6 @@ exports[`GalleryViewerComponent renders 1`] = `
"key": 1,
"text": "Most downloaded",
},
Object {
"key": 2,
"text": "Most favorited",
},
Object {
"key": 3,
"text": "Most recent",

View File

@@ -16,11 +16,12 @@ import * as React from "react";
import { IGalleryItem } from "../../../Juno/JunoClient";
import { FileSystemUtil } from "../../Notebook/FileSystemUtil";
import "./NotebookViewerComponent.less";
import CosmosDBLogo from "../../../../images/CosmosDB-logo.svg";
export interface NotebookMetadataComponentProps {
data: IGalleryItem;
isFavorite: boolean;
downloadButtonText: string;
downloadButtonText?: string;
onTagClick: (tag: string) => void;
onFavoriteClick: () => void;
onUnfavoriteClick: () => void;
@@ -44,17 +45,28 @@ export class NotebookMetadataComponent extends React.Component<NotebookMetadataC
{FileSystemUtil.stripExtension(this.props.data.name, "ipynb")}
</Text>
<Text>
<IconButton
iconProps={{ iconName: this.props.isFavorite ? "HeartFill" : "Heart" }}
onClick={this.props.isFavorite ? this.props.onUnfavoriteClick : this.props.onFavoriteClick}
/>
{this.props.data.favorites} likes
{this.props.isFavorite !== undefined && (
<>
<IconButton
iconProps={{ iconName: this.props.isFavorite ? "HeartFill" : "Heart" }}
onClick={this.props.isFavorite ? this.props.onUnfavoriteClick : this.props.onFavoriteClick}
/>
{this.props.data.favorites} likes
</>
)}
</Text>
<PrimaryButton text={this.props.downloadButtonText} onClick={this.props.onDownloadClick} />
{this.props.downloadButtonText && (
<PrimaryButton text={this.props.downloadButtonText} onClick={this.props.onDownloadClick} />
)}
</Stack>
<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>
<Icon iconName="RedEye" /> {this.props.data.views}

View File

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

View File

@@ -3,14 +3,14 @@
*/
import { Notebook } from "@nteract/commutable";
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 { contents } from "rx-jupyter";
import * as Logger from "../../../Common/Logger";
import * as ViewModels from "../../../Contracts/ViewModels";
import { IGalleryItem, JunoClient } from "../../../Juno/JunoClient";
import * as GalleryUtils from "../../../Utils/GalleryUtils";
import { NotificationConsoleUtils } from "../../../Utils/NotificationConsoleUtils";
import * as NotificationConsoleUtils from "../../../Utils/NotificationConsoleUtils";
import { ConsoleDataType } from "../../Menus/NotificationConsole/NotificationConsoleComponent";
import { NotebookClientV2 } from "../../Notebook/NotebookClientV2";
import { NotebookComponentBootstrapper } from "../../Notebook/NotebookComponent/NotebookComponentBootstrapper";
@@ -18,14 +18,17 @@ import NotebookReadOnlyRenderer from "../../Notebook/NotebookRenderer/NotebookRe
import { DialogComponent, DialogProps } from "../DialogReactComponent/DialogComponent";
import { NotebookMetadataComponent } from "./NotebookMetadataComponent";
import "./NotebookViewerComponent.less";
import Explorer from "../../Explorer";
import { SessionStorageUtility } from "../../../Shared/StorageUtility";
export interface NotebookViewerComponentProps {
container?: ViewModels.Explorer;
container?: Explorer;
junoClient?: JunoClient;
notebookUrl: string;
galleryItem?: IGalleryItem;
isFavorite?: boolean;
backNavigationText: string;
hideInputs?: boolean;
onBackClick: () => void;
onTagClick: (tag: string) => void;
}
@@ -35,10 +38,13 @@ interface NotebookViewerComponentState {
galleryItem?: IGalleryItem;
isFavorite?: boolean;
dialogProps: DialogProps;
showProgressBar: boolean;
}
export class NotebookViewerComponent extends React.Component<NotebookViewerComponentProps, NotebookViewerComponentState>
implements GalleryUtils.DialogEnabledComponent {
export class NotebookViewerComponent extends React.Component<
NotebookViewerComponentProps,
NotebookViewerComponentState
> {
private clientManager: NotebookClientV2;
private notebookComponentBootstrapper: NotebookComponentBootstrapper;
@@ -64,36 +70,35 @@ export class NotebookViewerComponent extends React.Component<NotebookViewerCompo
content: undefined,
galleryItem: props.galleryItem,
isFavorite: props.isFavorite,
dialogProps: undefined
dialogProps: undefined,
showProgressBar: true
};
this.loadNotebookContent();
}
setDialogProps = (dialogProps: DialogProps): void => {
this.setState({ dialogProps });
};
private async loadNotebookContent(): Promise<void> {
try {
const response = await fetch(this.props.notebookUrl);
if (!response.ok) {
this.setState({ showProgressBar: false });
throw new Error(`Received HTTP ${response.status} while fetching ${this.props.notebookUrl}`);
}
const notebook: Notebook = await response.json();
this.notebookComponentBootstrapper.setContent("json", notebook);
this.setState({ content: notebook });
this.setState({ content: notebook, showProgressBar: false });
if (this.props.galleryItem) {
if (this.props.galleryItem && !SessionStorageUtility.getEntry(this.props.galleryItem.id)) {
const response = await this.props.junoClient.increaseNotebookViews(this.props.galleryItem.id);
if (!response.data) {
throw new Error(`Received HTTP ${response.status} while increasing notebook views`);
}
this.setState({ galleryItem: response.data });
SessionStorageUtility.setEntry(this.props.galleryItem?.id, "true");
}
} catch (error) {
this.setState({ showProgressBar: false });
const message = `Failed to load notebook content: ${error}`;
Logger.logError(message, "NotebookViewerComponent/loadNotebookContent");
NotificationConsoleUtils.logConsoleMessage(ConsoleDataType.Error, message);
@@ -116,9 +121,7 @@ export class NotebookViewerComponent extends React.Component<NotebookViewerCompo
<NotebookMetadataComponent
data={this.state.galleryItem}
isFavorite={this.state.isFavorite}
downloadButtonText={
this.props.container ? "Download to my notebooks" : "Edit/Run in Cosmos DB data explorer"
}
downloadButtonText={this.props.container && "Download to my notebooks"}
onTagClick={this.props.onTagClick}
onFavoriteClick={this.favoriteItem}
onUnfavoriteClick={this.unfavoriteItem}
@@ -129,7 +132,11 @@ export class NotebookViewerComponent extends React.Component<NotebookViewerCompo
<></>
)}
{this.notebookComponentBootstrapper.renderComponent(NotebookReadOnlyRenderer, { hideInputs: true })}
{this.state.showProgressBar && <ProgressIndicator />}
{this.notebookComponentBootstrapper.renderComponent(NotebookReadOnlyRenderer, {
hideInputs: this.props.hideInputs
})}
{this.state.dialogProps && <DialogComponent {...this.state.dialogProps} />}
</div>
@@ -170,7 +177,7 @@ export class NotebookViewerComponent extends React.Component<NotebookViewerCompo
};
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 })
);
};

View File

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

View File

@@ -27,9 +27,10 @@ import { TextField, ITextFieldProps, ITextField } from "office-ui-fabric-react/l
import TelemetryProcessor from "../../../Shared/Telemetry/TelemetryProcessor";
import SaveQueryBannerIcon from "../../../../images/save_query_banner.png";
import { QueriesClient } from "../../../Common/QueriesClient";
export interface QueriesGridComponentProps {
queriesClient: ViewModels.QueriesClient;
queriesClient: QueriesClient;
onQuerySelect: (query: DataModels.Query) => void;
containerVisible: boolean;
saveQueryEnabled: boolean;
@@ -217,7 +218,7 @@ export class QueriesGridComponent extends React.Component<QueriesGridComponentPr
menuItem: any
) => {
if (window.confirm("Are you sure you want to delete this query?")) {
const container: ViewModels.Explorer = window.dataExplorer;
const container = window.dataExplorer;
const startKey: number = TelemetryProcessor.traceStart(Action.DeleteSavedQuery, {
databaseAccountName: container && container.databaseAccount().name,
defaultExperience: container && container.defaultExperience(),

View File

@@ -8,11 +8,12 @@ import * as React from "react";
import * as ViewModels from "../../../Contracts/ViewModels";
import { QueriesGridComponent, QueriesGridComponentProps } from "./QueriesGridComponent";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
import Explorer from "../../Explorer";
export class QueriesGridComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<number>;
constructor(private container: ViewModels.Explorer) {
constructor(private container: Explorer) {
this.parameters = ko.observable<number>(Date.now());
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,17 +0,0 @@
@import "../../../../less/Common/Constants";
.labelWithRedAsterisk {
line-height: 18px;
font-size: @DefaultFontSize;
font-family: @DataExplorerFont;
color: @DefaultFontColor;
}
.labelWithRedAsterisk::before {
content: "* ";
color: @SelectionHigh;
}
.clusterSettingsDropdown {
margin-bottom: 10px;
}

View File

@@ -1,159 +0,0 @@
import * as React from "react";
import { Dropdown, IDropdownOption, IDropdownProps } from "office-ui-fabric-react/lib/Dropdown";
import { Slider, ISliderProps } from "office-ui-fabric-react/lib/Slider";
import { Stack, IStackItemStyles, IStackStyles } from "office-ui-fabric-react/lib/Stack";
import { TextField, ITextFieldProps } from "office-ui-fabric-react/lib/TextField";
import { Spark } from "../../../Common/Constants";
import { SparkCluster } from "../../../Contracts/DataModels";
export interface ClusterSettingsComponentProps {
cluster: SparkCluster;
onClusterSettingsChanged: (cluster: SparkCluster) => void;
}
export class ClusterSettingsComponent extends React.Component<ClusterSettingsComponentProps, {}> {
constructor(props: ClusterSettingsComponentProps) {
super(props);
}
public render(): JSX.Element {
return (
<>
{this.getMasterSizeDropdown()}
{this.getWorkerSizeDropdown()}
{this.getWorkerCountSliderInput()}
</>
);
}
private getMasterSizeDropdown(): JSX.Element {
const driverSize: string =
this.props.cluster && this.props.cluster.properties && this.props.cluster.properties.driverSize;
const masterSizeOptions: IDropdownOption[] = Spark.SKUs.keys().map(sku => ({
key: sku,
text: Spark.SKUs.get(sku)
}));
const masterSizeDropdownProps: IDropdownProps = {
label: "Master Size",
options: masterSizeOptions,
defaultSelectedKey: driverSize,
onChange: this._onDriverSizeChange,
styles: {
root: "clusterSettingsDropdown"
}
};
return <Dropdown {...masterSizeDropdownProps} />;
}
private getWorkerSizeDropdown(): JSX.Element {
const workerSize: string =
this.props.cluster && this.props.cluster.properties && this.props.cluster.properties.workerSize;
const workerSizeOptions: IDropdownOption[] = Spark.SKUs.keys().map(sku => ({
key: sku,
text: Spark.SKUs.get(sku)
}));
const workerSizeDropdownProps: IDropdownProps = {
label: "Worker Size",
options: workerSizeOptions,
defaultSelectedKey: workerSize,
onChange: this._onWorkerSizeChange,
styles: {
label: "labelWithRedAsterisk",
root: "clusterSettingsDropdown"
}
};
return <Dropdown {...workerSizeDropdownProps} />;
}
private getWorkerCountSliderInput(): JSX.Element {
const workerCount: number =
(this.props.cluster &&
this.props.cluster.properties &&
this.props.cluster.properties.workerInstanceCount !== undefined &&
this.props.cluster.properties.workerInstanceCount) ||
0;
const stackStyle: IStackStyles = {
root: {
paddingTop: 5
}
};
const sliderItemStyle: IStackItemStyles = {
root: {
width: "100%",
paddingRight: 20
}
};
const workerCountSliderProps: ISliderProps = {
min: 0,
max: Spark.MaxWorkerCount,
step: 1,
value: workerCount,
showValue: false,
onChange: this._onWorkerCountChange,
styles: {
root: {
width: "100%",
paddingRight: 20
}
}
};
const workerCountTextFieldProps: ITextFieldProps = {
value: workerCount.toString(),
styles: {
fieldGroup: {
width: 45,
height: 25
},
field: {
textAlign: "center"
}
},
onChange: this._onWorkerCountTextFieldChange
};
return (
<Stack styles={stackStyle}>
<span className="labelWithRedAsterisk">Worker Nodes</span>
<Stack horizontal verticalAlign="center">
<Slider {...workerCountSliderProps} />
<TextField {...workerCountTextFieldProps} />
</Stack>
</Stack>
);
}
private _onDriverSizeChange = (_event: React.FormEvent, selectedOption: IDropdownOption) => {
const newValue: string = selectedOption.key as string;
const cluster = this.props.cluster;
if (cluster) {
cluster.properties.driverSize = newValue;
this.props.onClusterSettingsChanged(cluster);
}
};
private _onWorkerSizeChange = (_event: React.FormEvent, selectedOption: IDropdownOption) => {
const newValue: string = selectedOption.key as string;
const cluster = this.props.cluster;
if (cluster) {
cluster.properties.workerSize = newValue;
this.props.onClusterSettingsChanged(cluster);
}
};
private _onWorkerCountChange = (count: number) => {
count = Math.min(count, Spark.MaxWorkerCount);
const cluster = this.props.cluster;
if (cluster) {
cluster.properties.workerInstanceCount = count;
this.props.onClusterSettingsChanged(cluster);
}
};
private _onWorkerCountTextFieldChange = (_event: React.FormEvent, newValue: string) => {
const count = parseInt(newValue);
if (!isNaN(count)) {
this._onWorkerCountChange(count);
}
};
}

View File

@@ -1,11 +0,0 @@
import * as React from "react";
import { ClusterSettingsComponent, ClusterSettingsComponentProps } from "./ClusterSettingsComponent";
import { ReactAdapter } from "../../../Bindings/ReactBindingHandler";
export class ClusterSettingsComponentAdapter implements ReactAdapter {
public parameters: ko.Observable<ClusterSettingsComponentProps>;
public renderComponent(): JSX.Element {
return <ClusterSettingsComponent {...this.parameters()} />;
}
}

View File

@@ -1,12 +0,0 @@
/*!---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*----------------------------------------------------------*/
import IToolbarDisplayable from "./IToolbarDisplayable";
interface IToolbarAction extends IToolbarDisplayable {
type: "action";
action: () => void;
}
export default IToolbarAction;

View File

@@ -1,18 +0,0 @@
/*!---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*----------------------------------------------------------*/
interface IToolbarDisplayable {
id: string;
title: ko.Subscribable<string>;
displayName: ko.Subscribable<string>;
enabled: ko.Subscribable<boolean>;
visible: ko.Observable<boolean>;
focused: ko.Observable<boolean>;
icon: string;
mouseDown: (data: any, event: MouseEvent) => any;
keyUp: (data: any, event: KeyboardEvent) => any;
keyDown: (data: any, event: KeyboardEvent) => any;
}
export default IToolbarDisplayable;

View File

@@ -1,56 +0,0 @@
/*!---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*----------------------------------------------------------*/
import IToolbarDisplayable from "./IToolbarDisplayable";
interface IToolbarDropDown extends IToolbarDisplayable {
type: "dropdown";
subgroup: IActionConfigItem[];
expanded: ko.Observable<boolean>;
open: () => void;
}
export interface IDropdown {
type: "dropdown";
title: string;
displayName: string;
id: string;
enabled: ko.Observable<boolean>;
visible?: ko.Observable<boolean>;
icon?: string;
subgroup?: IActionConfigItem[];
}
export interface ISeperator {
type: "separator";
visible?: ko.Observable<boolean>;
}
export interface IToggle {
type: "toggle";
title: string;
displayName: string;
checkedTitle: string;
checkedDisplayName: string;
id: string;
checked: ko.Observable<boolean>;
enabled: ko.Observable<boolean>;
visible?: ko.Observable<boolean>;
icon?: string;
}
export interface IAction {
type: "action";
title: string;
displayName: string;
id: string;
action: () => any;
enabled: ko.Subscribable<boolean>;
visible?: ko.Observable<boolean>;
icon?: string;
}
export type IActionConfigItem = ISeperator | IAction | IToggle | IDropdown;
export default IToolbarDropDown;

View File

@@ -1,12 +0,0 @@
/*!---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*----------------------------------------------------------*/
import IToolbarAction from "./IToolbarAction";
import IToolbarToggle from "./IToolbarToggle";
import IToolbarSeperator from "./IToolbarSeperator";
import IToolbarDropDown from "./IToolbarDropDown";
type IToolbarItem = IToolbarAction | IToolbarToggle | IToolbarSeperator | IToolbarDropDown;
export default IToolbarItem;

View File

@@ -1,10 +0,0 @@
/*!---------------------------------------------------------
* Copyright (C) Microsoft Corporation. All rights reserved.
*----------------------------------------------------------*/
interface IToolbarSeperator {
type: "separator";
visible: ko.Observable<boolean>;
}
export default IToolbarSeperator;

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